github.com/latiif/helm@v2.15.0+incompatible/pkg/lint/rules/template.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 rules 18 19 import ( 20 "errors" 21 "fmt" 22 "os" 23 "path/filepath" 24 25 "github.com/ghodss/yaml" 26 "k8s.io/helm/pkg/chartutil" 27 "k8s.io/helm/pkg/engine" 28 "k8s.io/helm/pkg/lint/support" 29 cpb "k8s.io/helm/pkg/proto/hapi/chart" 30 "k8s.io/helm/pkg/timeconv" 31 tversion "k8s.io/helm/pkg/version" 32 ) 33 34 // Templates lints the templates in the Linter. 35 func Templates(linter *support.Linter, values []byte, namespace string, strict bool) { 36 path := "templates/" 37 templatesPath := filepath.Join(linter.ChartDir, path) 38 39 templatesDirExist := linter.RunLinterRule(support.WarningSev, path, validateTemplatesDir(templatesPath)) 40 41 // Templates directory is optional for now 42 if !templatesDirExist { 43 return 44 } 45 46 // Load chart and parse templates, based on tiller/release_server 47 chart, err := chartutil.Load(linter.ChartDir) 48 49 chartLoaded := linter.RunLinterRule(support.ErrorSev, path, err) 50 51 if !chartLoaded { 52 return 53 } 54 55 options := chartutil.ReleaseOptions{Name: "testRelease", Time: timeconv.Now(), Namespace: namespace} 56 caps := &chartutil.Capabilities{ 57 APIVersions: chartutil.DefaultVersionSet, 58 KubeVersion: chartutil.DefaultKubeVersion, 59 TillerVersion: tversion.GetVersionProto(), 60 } 61 cvals, err := chartutil.CoalesceValues(chart, &cpb.Config{Raw: string(values)}) 62 if err != nil { 63 return 64 } 65 // convert our values back into config 66 yvals, err := cvals.YAML() 67 if err != nil { 68 return 69 } 70 cc := &cpb.Config{Raw: yvals} 71 valuesToRender, err := chartutil.ToRenderValuesCaps(chart, cc, options, caps) 72 if err != nil { 73 // FIXME: This seems to generate a duplicate, but I can't find where the first 74 // error is coming from. 75 //linter.RunLinterRule(support.ErrorSev, err) 76 return 77 } 78 e := engine.New() 79 e.LintMode = true 80 if strict { 81 e.Strict = true 82 } 83 renderedContentMap, err := e.Render(chart, valuesToRender) 84 85 renderOk := linter.RunLinterRule(support.ErrorSev, path, err) 86 87 if !renderOk { 88 return 89 } 90 91 /* Iterate over all the templates to check: 92 - It is a .yaml file 93 - All the values in the template file is defined 94 - {{}} include | quote 95 - Generated content is a valid Yaml file 96 - Metadata.Namespace is not set 97 */ 98 for _, template := range chart.Templates { 99 fileName, _ := template.Name, template.Data 100 path = fileName 101 102 linter.RunLinterRule(support.WarningSev, path, validateAllowedExtension(fileName)) 103 104 // We only apply the following lint rules to yaml files 105 if filepath.Ext(fileName) != ".yaml" || filepath.Ext(fileName) == ".yml" { 106 continue 107 } 108 109 renderedContent := renderedContentMap[filepath.Join(chart.GetMetadata().Name, fileName)] 110 var yamlStruct K8sYamlStruct 111 // Even though K8sYamlStruct only defines Metadata namespace, an error in any other 112 // key will be raised as well 113 err := yaml.Unmarshal([]byte(renderedContent), &yamlStruct) 114 115 validYaml := linter.RunLinterRule(support.ErrorSev, path, validateYamlContent(err)) 116 117 if !validYaml { 118 continue 119 } 120 } 121 } 122 123 // Validation functions 124 func validateTemplatesDir(templatesPath string) error { 125 if fi, err := os.Stat(templatesPath); err != nil { 126 return errors.New("directory not found") 127 } else if err == nil && !fi.IsDir() { 128 return errors.New("not a directory") 129 } 130 return nil 131 } 132 133 func validateAllowedExtension(fileName string) error { 134 ext := filepath.Ext(fileName) 135 validExtensions := []string{".yaml", ".yml", ".tpl", ".txt"} 136 137 for _, b := range validExtensions { 138 if b == ext { 139 return nil 140 } 141 } 142 143 return fmt.Errorf("file extension '%s' not valid. Valid extensions are .yaml, .yml, .tpl, or .txt", ext) 144 } 145 146 func validateYamlContent(err error) error { 147 if err != nil { 148 return fmt.Errorf("unable to parse YAML\n\t%s", err) 149 } 150 return nil 151 } 152 153 // K8sYamlStruct stubs a Kubernetes YAML file. 154 // Need to access for now to Namespace only 155 type K8sYamlStruct struct { 156 Metadata struct { 157 Namespace string 158 } 159 }