github.com/koderover/helm@v2.17.0+incompatible/pkg/lint/rules/chartfile.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 // import "k8s.io/helm/pkg/lint/rules" 18 19 import ( 20 "errors" 21 "fmt" 22 "os" 23 "path/filepath" 24 "strings" 25 26 "github.com/Masterminds/semver" 27 28 "github.com/asaskevich/govalidator" 29 "k8s.io/helm/pkg/chartutil" 30 "k8s.io/helm/pkg/lint/support" 31 "k8s.io/helm/pkg/proto/hapi/chart" 32 ) 33 34 // Chartfile runs a set of linter rules related to Chart.yaml file 35 func Chartfile(linter *support.Linter) { 36 chartFileName := "Chart.yaml" 37 chartPath := filepath.Join(linter.ChartDir, chartFileName) 38 39 linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartYamlNotDirectory(chartPath)) 40 41 chartFile, err := chartutil.LoadChartfile(chartPath) 42 validChartFile := linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartYamlFormat(err)) 43 44 // Guard clause. Following linter rules require a parseable ChartFile 45 if !validChartFile { 46 return 47 } 48 49 linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartNamePresence(chartFile)) 50 linter.RunLinterRule(support.WarningSev, chartFileName, validateChartNameFormat(chartFile)) 51 linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartNameDirMatch(linter.ChartDir, chartFile)) 52 53 // Chart metadata 54 linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartAPIVersion(chartFile)) 55 linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartVersion(chartFile)) 56 linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartEngine(chartFile)) 57 linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartMaintainer(chartFile)) 58 linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartSources(chartFile)) 59 linter.RunLinterRule(support.InfoSev, chartFileName, validateChartIconPresence(chartFile)) 60 linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartIconURL(chartFile)) 61 } 62 63 func validateChartYamlNotDirectory(chartPath string) error { 64 fi, err := os.Stat(chartPath) 65 66 if err == nil && fi.IsDir() { 67 return errors.New("should be a file, not a directory") 68 } 69 return nil 70 } 71 72 func validateChartYamlFormat(chartFileError error) error { 73 if chartFileError != nil { 74 return fmt.Errorf("unable to parse YAML\n\t%s", chartFileError.Error()) 75 } 76 return nil 77 } 78 79 func validateChartNamePresence(cf *chart.Metadata) error { 80 if cf.Name == "" { 81 return errors.New("name is required") 82 } 83 return nil 84 } 85 86 func validateChartNameFormat(cf *chart.Metadata) error { 87 if strings.Contains(cf.Name, ".") { 88 return errors.New("name should be lower case letters and numbers. Words may be separated with dashes") 89 } 90 return nil 91 } 92 93 func validateChartNameDirMatch(chartDir string, cf *chart.Metadata) error { 94 if cf.Name != filepath.Base(chartDir) { 95 return fmt.Errorf("directory name (%s) and chart name (%s) must be the same", filepath.Base(chartDir), cf.Name) 96 } 97 return nil 98 } 99 100 func validateChartAPIVersion(cf *chart.Metadata) error { 101 if cf.ApiVersion == "" { 102 return errors.New("apiVersion is required") 103 } 104 105 if cf.ApiVersion != "v1" { 106 return fmt.Errorf("apiVersion '%s' is not valid. The value must be \"v1\"", cf.ApiVersion) 107 } 108 109 return nil 110 } 111 112 func validateChartVersion(cf *chart.Metadata) error { 113 if cf.Version == "" { 114 return errors.New("version is required") 115 } 116 117 version, err := semver.NewVersion(cf.Version) 118 119 if err != nil { 120 return fmt.Errorf("version '%s' is not a valid SemVer", cf.Version) 121 } 122 123 c, err := semver.NewConstraint(">0.0.0-0") 124 if err != nil { 125 return err 126 } 127 valid, msg := c.Validate(version) 128 129 if !valid && len(msg) > 0 { 130 return fmt.Errorf("version %v", msg[0]) 131 } 132 133 return nil 134 } 135 136 func validateChartEngine(cf *chart.Metadata) error { 137 if cf.Engine == "" { 138 return nil 139 } 140 141 keys := make([]string, 0, len(chart.Metadata_Engine_value)) 142 for engine := range chart.Metadata_Engine_value { 143 str := strings.ToLower(engine) 144 145 if str == "unknown" { 146 continue 147 } 148 149 if str == cf.Engine { 150 return nil 151 } 152 153 keys = append(keys, str) 154 } 155 156 return fmt.Errorf("engine '%v' not valid. Valid options are %v", cf.Engine, keys) 157 } 158 159 func validateChartMaintainer(cf *chart.Metadata) error { 160 for _, maintainer := range cf.Maintainers { 161 if maintainer.Name == "" { 162 return errors.New("each maintainer requires a name") 163 } else if maintainer.Email != "" && !govalidator.IsEmail(maintainer.Email) { 164 return fmt.Errorf("invalid email '%s' for maintainer '%s'", maintainer.Email, maintainer.Name) 165 } else if maintainer.Url != "" && !govalidator.IsURL(maintainer.Url) { 166 return fmt.Errorf("invalid url '%s' for maintainer '%s'", maintainer.Url, maintainer.Name) 167 } 168 } 169 return nil 170 } 171 172 func validateChartSources(cf *chart.Metadata) error { 173 for _, source := range cf.Sources { 174 if source == "" || !govalidator.IsRequestURL(source) { 175 return fmt.Errorf("invalid source URL '%s'", source) 176 } 177 } 178 return nil 179 } 180 181 func validateChartIconPresence(cf *chart.Metadata) error { 182 if cf.Icon == "" { 183 return errors.New("icon is recommended") 184 } 185 return nil 186 } 187 188 func validateChartIconURL(cf *chart.Metadata) error { 189 if cf.Icon != "" && !govalidator.IsRequestURL(cf.Icon) { 190 return fmt.Errorf("invalid icon URL '%s'", cf.Icon) 191 } 192 return nil 193 }