github.com/sgoings/helm@v2.0.0-alpha.2.0.20170406211108-734e92851ac3+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 } 145 } 146 return nil 147 } 148 149 func validateChartSources(cf *chart.Metadata) error { 150 for _, source := range cf.Sources { 151 if source == "" || !govalidator.IsRequestURL(source) { 152 return fmt.Errorf("invalid source URL '%s'", source) 153 } 154 } 155 return nil 156 } 157 158 func validateChartIconPresence(cf *chart.Metadata) error { 159 if cf.Icon == "" { 160 return errors.New("icon is recommended") 161 } 162 return nil 163 } 164 165 func validateChartIconURL(cf *chart.Metadata) error { 166 if cf.Icon != "" && !govalidator.IsRequestURL(cf.Icon) { 167 return fmt.Errorf("invalid icon URL '%s'", cf.Icon) 168 } 169 return nil 170 }