github.com/sdbaiguanghe/helm@v2.16.7+incompatible/cmd/helm/lint.go (about) 1 /* 2 Copyright The Helm Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "fmt" 21 "io" 22 "io/ioutil" 23 "os" 24 "path/filepath" 25 "strings" 26 27 "github.com/ghodss/yaml" 28 "github.com/spf13/cobra" 29 30 "k8s.io/helm/pkg/chartutil" 31 "k8s.io/helm/pkg/lint" 32 "k8s.io/helm/pkg/lint/support" 33 "k8s.io/helm/pkg/strvals" 34 ) 35 36 var longLintHelp = ` 37 This command takes a path to a chart and runs a series of tests to verify that 38 the chart is well-formed. 39 40 If the linter encounters things that will cause the chart to fail installation, 41 it will emit [ERROR] messages. If it encounters issues that break with convention 42 or recommendation, it will emit [WARNING] messages. 43 ` 44 45 type lintCmd struct { 46 valueFiles valueFiles 47 values []string 48 sValues []string 49 fValues []string 50 namespace string 51 strict bool 52 paths []string 53 out io.Writer 54 } 55 56 func newLintCmd(out io.Writer) *cobra.Command { 57 l := &lintCmd{ 58 paths: []string{"."}, 59 out: out, 60 } 61 cmd := &cobra.Command{ 62 Use: "lint [flags] PATH", 63 Short: "Examines a chart for possible issues", 64 Long: longLintHelp, 65 RunE: func(cmd *cobra.Command, args []string) error { 66 if len(args) > 0 { 67 l.paths = args 68 } 69 return l.run() 70 }, 71 } 72 73 cmd.Flags().VarP(&l.valueFiles, "values", "f", "Specify values in a YAML file (can specify multiple)") 74 cmd.Flags().StringArrayVar(&l.values, "set", []string{}, "Set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") 75 cmd.Flags().StringArrayVar(&l.sValues, "set-string", []string{}, "Set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") 76 cmd.Flags().StringArrayVar(&l.fValues, "set-file", []string{}, "Set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)") 77 cmd.Flags().StringVar(&l.namespace, "namespace", "default", "Namespace to put the release into") 78 cmd.Flags().BoolVar(&l.strict, "strict", false, "Fail on lint warnings") 79 80 return cmd 81 } 82 83 func (l *lintCmd) run() error { 84 var lowestTolerance int 85 if l.strict { 86 lowestTolerance = support.WarningSev 87 } else { 88 lowestTolerance = support.ErrorSev 89 } 90 91 // Get the raw values 92 rvals, err := l.vals() 93 if err != nil { 94 return err 95 } 96 97 var total int 98 var failures int 99 for _, path := range l.paths { 100 linter, err := lintChart(path, rvals, l.namespace, l.strict) 101 if err != nil { 102 failures = failures + 1 103 fmt.Println("==> Skipping", path) 104 fmt.Println(err) 105 fmt.Println("") 106 continue 107 } 108 109 fmt.Println("==> Linting", path) 110 if len(linter.Messages) == 0 { 111 fmt.Println("Lint OK") 112 } 113 114 for _, msg := range linter.Messages { 115 fmt.Println(msg) 116 } 117 118 total = total + 1 119 if linter.HighestSeverity >= lowestTolerance { 120 failures = failures + 1 121 } 122 fmt.Println("") 123 } 124 125 msg := fmt.Sprintf("%d chart(s) linted", total) 126 if failures > 0 { 127 return fmt.Errorf("%s, %d chart(s) failed", msg, failures) 128 } 129 130 fmt.Fprintf(l.out, "%s, no failures\n", msg) 131 132 return nil 133 } 134 135 func lintChart(path string, vals []byte, namespace string, strict bool) (support.Linter, error) { 136 var chartPath string 137 linter := support.Linter{} 138 139 if strings.HasSuffix(path, ".tgz") { 140 tempDir, err := ioutil.TempDir("", "helm-lint") 141 if err != nil { 142 return linter, err 143 } 144 defer os.RemoveAll(tempDir) 145 146 file, err := os.Open(path) 147 if err != nil { 148 return linter, fmt.Errorf("unable to open tar ball %s: %s", path, err.Error()) 149 } 150 defer file.Close() 151 152 if err = chartutil.Expand(tempDir, file); err != nil { 153 return linter, fmt.Errorf("unable to extract tar ball: %s", err.Error()) 154 } 155 156 files, err := ioutil.ReadDir(tempDir) 157 if err != nil { 158 return linter, fmt.Errorf("unable to read temporary output directory %s", tempDir) 159 } 160 if !files[0].IsDir() { 161 return linter, fmt.Errorf("unexpected file %s in temporary output directory %s", files[0].Name(), tempDir) 162 } 163 164 chartPath = filepath.Join(tempDir, files[0].Name()) 165 } else { 166 chartPath = path 167 } 168 169 // Guard: Error out if this is not a chart. 170 if _, err := os.Stat(filepath.Join(chartPath, "Chart.yaml")); err != nil { 171 return linter, fmt.Errorf("unable to check Chart.yaml file in chart: %s", err.Error()) 172 } 173 174 return lint.All(chartPath, vals, namespace, strict), nil 175 } 176 177 // vals merges values from files specified via -f/--values and 178 // directly via --set or --set-string or --set-file, marshaling them to YAML 179 // 180 // This func is implemented intentionally and separately from the `vals` func for the `install` and `upgrade` commands. 181 // Compared to the alternative func, this func lacks the parameters for tls opts - ca key, cert, and ca cert. 182 // That's because this command, `lint`, is explicitly forbidden from making server connections. 183 func (l *lintCmd) vals() ([]byte, error) { 184 base := map[string]interface{}{} 185 186 // User specified a values files via -f/--values 187 for _, filePath := range l.valueFiles { 188 currentMap := map[string]interface{}{} 189 bytes, err := ioutil.ReadFile(filePath) 190 if err != nil { 191 return []byte{}, err 192 } 193 194 if err := yaml.Unmarshal(bytes, ¤tMap); err != nil { 195 return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err) 196 } 197 // Merge with the previous map 198 base = mergeValues(base, currentMap) 199 } 200 201 // User specified a value via --set 202 for _, value := range l.values { 203 if err := strvals.ParseInto(value, base); err != nil { 204 return []byte{}, fmt.Errorf("failed parsing --set data: %s", err) 205 } 206 } 207 208 // User specified a value via --set-string 209 for _, value := range l.sValues { 210 if err := strvals.ParseIntoString(value, base); err != nil { 211 return []byte{}, fmt.Errorf("failed parsing --set-string data: %s", err) 212 } 213 } 214 215 // User specified a value via --set-file 216 for _, value := range l.fValues { 217 reader := func(rs []rune) (interface{}, error) { 218 bytes, err := ioutil.ReadFile(string(rs)) 219 return string(bytes), err 220 } 221 if err := strvals.ParseIntoFile(value, base, reader); err != nil { 222 return []byte{}, fmt.Errorf("failed parsing --set-file data: %s", err) 223 } 224 } 225 226 return yaml.Marshal(base) 227 }