github.com/vtuson/helm@v2.8.2+incompatible/cmd/helm/lint.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors All rights reserved. 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 "errors" 21 "fmt" 22 "io" 23 "io/ioutil" 24 "os" 25 "path/filepath" 26 "strings" 27 28 "github.com/ghodss/yaml" 29 "github.com/spf13/cobra" 30 31 "k8s.io/helm/pkg/chartutil" 32 "k8s.io/helm/pkg/lint" 33 "k8s.io/helm/pkg/lint/support" 34 "k8s.io/helm/pkg/strvals" 35 ) 36 37 var longLintHelp = ` 38 This command takes a path to a chart and runs a series of tests to verify that 39 the chart is well-formed. 40 41 If the linter encounters things that will cause the chart to fail installation, 42 it will emit [ERROR] messages. If it encounters issues that break with convention 43 or recommendation, it will emit [WARNING] messages. 44 ` 45 46 type lintCmd struct { 47 valueFiles valueFiles 48 values []string 49 namespace string 50 strict bool 51 paths []string 52 out io.Writer 53 } 54 55 func newLintCmd(out io.Writer) *cobra.Command { 56 l := &lintCmd{ 57 paths: []string{"."}, 58 out: out, 59 } 60 cmd := &cobra.Command{ 61 Use: "lint [flags] PATH", 62 Short: "examines a chart for possible issues", 63 Long: longLintHelp, 64 RunE: func(cmd *cobra.Command, args []string) error { 65 if len(args) > 0 { 66 l.paths = args 67 } 68 return l.run() 69 }, 70 } 71 72 cmd.Flags().VarP(&l.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)") 73 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)") 74 cmd.Flags().StringVar(&l.namespace, "namespace", "default", "namespace to install the release into (only used if --install is set)") 75 cmd.Flags().BoolVar(&l.strict, "strict", false, "fail on lint warnings") 76 77 return cmd 78 } 79 80 var errLintNoChart = errors.New("No chart found for linting (missing Chart.yaml)") 81 82 func (l *lintCmd) run() error { 83 var lowestTolerance int 84 if l.strict { 85 lowestTolerance = support.WarningSev 86 } else { 87 lowestTolerance = support.ErrorSev 88 } 89 90 // Get the raw values 91 rvals, err := l.vals() 92 if err != nil { 93 return err 94 } 95 96 var total int 97 var failures int 98 for _, path := range l.paths { 99 if linter, err := lintChart(path, rvals, l.namespace, l.strict); err != nil { 100 fmt.Println("==> Skipping", path) 101 fmt.Println(err) 102 } else { 103 fmt.Println("==> Linting", path) 104 105 if len(linter.Messages) == 0 { 106 fmt.Println("Lint OK") 107 } 108 109 for _, msg := range linter.Messages { 110 fmt.Println(msg) 111 } 112 113 total = total + 1 114 if linter.HighestSeverity >= lowestTolerance { 115 failures = failures + 1 116 } 117 } 118 fmt.Println("") 119 } 120 121 msg := fmt.Sprintf("%d chart(s) linted", total) 122 if failures > 0 { 123 return fmt.Errorf("%s, %d chart(s) failed", msg, failures) 124 } 125 126 fmt.Fprintf(l.out, "%s, no failures\n", msg) 127 128 return nil 129 } 130 131 func lintChart(path string, vals []byte, namespace string, strict bool) (support.Linter, error) { 132 var chartPath string 133 linter := support.Linter{} 134 135 if strings.HasSuffix(path, ".tgz") { 136 tempDir, err := ioutil.TempDir("", "helm-lint") 137 if err != nil { 138 return linter, err 139 } 140 defer os.RemoveAll(tempDir) 141 142 file, err := os.Open(path) 143 if err != nil { 144 return linter, err 145 } 146 defer file.Close() 147 148 if err = chartutil.Expand(tempDir, file); err != nil { 149 return linter, err 150 } 151 152 base := strings.Split(filepath.Base(path), "-")[0] 153 chartPath = filepath.Join(tempDir, base) 154 } else { 155 chartPath = path 156 } 157 158 // Guard: Error out of this is not a chart. 159 if _, err := os.Stat(filepath.Join(chartPath, "Chart.yaml")); err != nil { 160 return linter, errLintNoChart 161 } 162 163 return lint.All(chartPath, vals, namespace, strict), nil 164 } 165 166 func (l *lintCmd) vals() ([]byte, error) { 167 base := map[string]interface{}{} 168 169 // User specified a values files via -f/--values 170 for _, filePath := range l.valueFiles { 171 currentMap := map[string]interface{}{} 172 bytes, err := ioutil.ReadFile(filePath) 173 if err != nil { 174 return []byte{}, err 175 } 176 177 if err := yaml.Unmarshal(bytes, ¤tMap); err != nil { 178 return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err) 179 } 180 // Merge with the previous map 181 base = mergeValues(base, currentMap) 182 } 183 184 // User specified a value via --set 185 for _, value := range l.values { 186 if err := strvals.ParseInto(value, base); err != nil { 187 return []byte{}, fmt.Errorf("failed parsing --set data: %s", err) 188 } 189 } 190 191 return yaml.Marshal(base) 192 }