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