github.com/y-taka-23/helm@v2.8.0+incompatible/pkg/lint/rules/chartfile.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 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, validateChartName(chartFile)) 50 linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartNameDirMatch(linter.ChartDir, chartFile)) 51 52 // Chart metadata 53 linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartVersion(chartFile)) 54 linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartEngine(chartFile)) 55 linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartMaintainer(chartFile)) 56 linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartSources(chartFile)) 57 linter.RunLinterRule(support.InfoSev, chartFileName, validateChartIconPresence(chartFile)) 58 linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartIconURL(chartFile)) 59 } 60 61 func validateChartYamlNotDirectory(chartPath string) error { 62 fi, err := os.Stat(chartPath) 63 64 if err == nil && fi.IsDir() { 65 return errors.New("should be a file, not a directory") 66 } 67 return nil 68 } 69 70 func validateChartYamlFormat(chartFileError error) error { 71 if chartFileError != nil { 72 return fmt.Errorf("unable to parse YAML\n\t%s", chartFileError.Error()) 73 } 74 return nil 75 } 76 77 func validateChartName(cf *chart.Metadata) error { 78 if cf.Name == "" { 79 return errors.New("name is required") 80 } 81 return nil 82 } 83 84 func validateChartNameDirMatch(chartDir string, cf *chart.Metadata) error { 85 if cf.Name != filepath.Base(chartDir) { 86 return fmt.Errorf("directory name (%s) and chart name (%s) must be the same", filepath.Base(chartDir), cf.Name) 87 } 88 return nil 89 } 90 91 func validateChartVersion(cf *chart.Metadata) error { 92 if cf.Version == "" { 93 return errors.New("version is required") 94 } 95 96 version, err := semver.NewVersion(cf.Version) 97 98 if err != nil { 99 return fmt.Errorf("version '%s' is not a valid SemVer", cf.Version) 100 } 101 102 c, err := semver.NewConstraint("> 0") 103 if err != nil { 104 return err 105 } 106 valid, msg := c.Validate(version) 107 108 if !valid && len(msg) > 0 { 109 return fmt.Errorf("version %v", msg[0]) 110 } 111 112 return nil 113 } 114 115 func validateChartEngine(cf *chart.Metadata) error { 116 if cf.Engine == "" { 117 return nil 118 } 119 120 keys := make([]string, 0, len(chart.Metadata_Engine_value)) 121 for engine := range chart.Metadata_Engine_value { 122 str := strings.ToLower(engine) 123 124 if str == "unknown" { 125 continue 126 } 127 128 if str == cf.Engine { 129 return nil 130 } 131 132 keys = append(keys, str) 133 } 134 135 return fmt.Errorf("engine '%v' not valid. Valid options are %v", cf.Engine, keys) 136 } 137 138 func validateChartMaintainer(cf *chart.Metadata) error { 139 for _, maintainer := range cf.Maintainers { 140 if maintainer.Name == "" { 141 return errors.New("each maintainer requires a name") 142 } else if maintainer.Email != "" && !govalidator.IsEmail(maintainer.Email) { 143 return fmt.Errorf("invalid email '%s' for maintainer '%s'", maintainer.Email, maintainer.Name) 144 } else if maintainer.Url != "" && !govalidator.IsURL(maintainer.Url) { 145 return fmt.Errorf("invalid url '%s' for maintainer '%s'", maintainer.Url, maintainer.Name) 146 } 147 } 148 return nil 149 } 150 151 func validateChartSources(cf *chart.Metadata) error { 152 for _, source := range cf.Sources { 153 if source == "" || !govalidator.IsRequestURL(source) { 154 return fmt.Errorf("invalid source URL '%s'", source) 155 } 156 } 157 return nil 158 } 159 160 func validateChartIconPresence(cf *chart.Metadata) error { 161 if cf.Icon == "" { 162 return errors.New("icon is recommended") 163 } 164 return nil 165 } 166 167 func validateChartIconURL(cf *chart.Metadata) error { 168 if cf.Icon != "" && !govalidator.IsRequestURL(cf.Icon) { 169 return fmt.Errorf("invalid icon URL '%s'", cf.Icon) 170 } 171 return nil 172 }