github.com/koderover/helm@v2.17.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 renderedContentMap, err := e.Render(chart, valuesToRender) 81 82 renderOk := linter.RunLinterRule(support.ErrorSev, path, err) 83 84 if !renderOk { 85 return 86 } 87 88 /* Iterate over all the templates to check: 89 - It is a .yaml file 90 - All the values in the template file is defined 91 - {{}} include | quote 92 - Generated content is a valid Yaml file 93 - Metadata.Namespace is not set 94 */ 95 for _, template := range chart.Templates { 96 fileName, _ := template.Name, template.Data 97 path = fileName 98 99 linter.RunLinterRule(support.WarningSev, path, validateAllowedExtension(fileName)) 100 101 // We only apply the following lint rules to yaml files 102 if filepath.Ext(fileName) != ".yaml" || filepath.Ext(fileName) == ".yml" { 103 continue 104 } 105 106 renderedContent := renderedContentMap[filepath.Join(chart.GetMetadata().Name, fileName)] 107 var yamlStruct K8sYamlStruct 108 // Even though K8sYamlStruct only defines Metadata namespace, an error in any other 109 // key will be raised as well 110 err := yaml.Unmarshal([]byte(renderedContent), &yamlStruct) 111 112 validYaml := linter.RunLinterRule(support.ErrorSev, path, validateYamlContent(err)) 113 114 if !validYaml { 115 continue 116 } 117 } 118 } 119 120 // Validation functions 121 func validateTemplatesDir(templatesPath string) error { 122 if fi, err := os.Stat(templatesPath); err != nil { 123 return errors.New("directory not found") 124 } else if err == nil && !fi.IsDir() { 125 return errors.New("not a directory") 126 } 127 return nil 128 } 129 130 func validateAllowedExtension(fileName string) error { 131 ext := filepath.Ext(fileName) 132 validExtensions := []string{".yaml", ".yml", ".tpl", ".txt"} 133 134 for _, b := range validExtensions { 135 if b == ext { 136 return nil 137 } 138 } 139 140 return fmt.Errorf("file extension '%s' not valid. Valid extensions are .yaml, .yml, .tpl, or .txt", ext) 141 } 142 143 func validateYamlContent(err error) error { 144 if err != nil { 145 return fmt.Errorf("unable to parse YAML\n\t%s", err) 146 } 147 return nil 148 } 149 150 // K8sYamlStruct stubs a Kubernetes YAML file. 151 // Need to access for now to Namespace only 152 type K8sYamlStruct struct { 153 Metadata struct { 154 Namespace string 155 } 156 }