2016-06-23 02:28:45 +08:00
/ *
2018-08-25 03:03:55 +08:00
Copyright The Helm Authors .
2016-06-23 02:28:45 +08:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2019-10-04 02:27:05 +08:00
package rules // import "helm.sh/helm/v3/pkg/lint/rules"
2016-06-10 07:51:00 +08:00
import (
2019-05-15 02:46:40 +08:00
"fmt"
2016-06-10 07:51:00 +08:00
"os"
"path/filepath"
2019-10-04 01:42:59 +08:00
"github.com/Masterminds/semver/v3"
2016-06-10 07:51:00 +08:00
"github.com/asaskevich/govalidator"
2018-05-11 00:34:41 +08:00
"github.com/pkg/errors"
2019-12-15 22:43:56 +08:00
"sigs.k8s.io/yaml"
2018-04-19 05:53:38 +08:00
2019-10-04 02:27:05 +08:00
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/lint/support"
2016-06-10 07:51:00 +08:00
)
// Chartfile runs a set of linter rules related to Chart.yaml file
func Chartfile ( linter * support . Linter ) {
2016-07-01 10:07:56 +08:00
chartFileName := "Chart.yaml"
chartPath := filepath . Join ( linter . ChartDir , chartFileName )
2016-06-10 07:51:00 +08:00
2016-07-01 10:07:56 +08:00
linter . RunLinterRule ( support . ErrorSev , chartFileName , validateChartYamlNotDirectory ( chartPath ) )
2016-06-10 07:51:00 +08:00
chartFile , err := chartutil . LoadChartfile ( chartPath )
2016-07-01 10:07:56 +08:00
validChartFile := linter . RunLinterRule ( support . ErrorSev , chartFileName , validateChartYamlFormat ( err ) )
2016-06-10 07:51:00 +08:00
2019-12-18 20:04:08 +08:00
// Guard clause. Following linter rules require a parsable ChartFile
2016-06-10 07:51:00 +08:00
if ! validChartFile {
return
}
2019-12-15 22:43:56 +08:00
// type check for Chart.yaml . ignoring error as any parse
// errors would already be caught in the above load function
chartFileForTypeCheck , _ := loadChartFileForTypeCheck ( chartPath )
2016-07-01 10:07:56 +08:00
linter . RunLinterRule ( support . ErrorSev , chartFileName , validateChartName ( chartFile ) )
2016-06-10 07:51:00 +08:00
// Chart metadata
2019-05-15 02:46:40 +08:00
linter . RunLinterRule ( support . ErrorSev , chartFileName , validateChartAPIVersion ( chartFile ) )
2019-12-15 22:43:56 +08:00
linter . RunLinterRule ( support . ErrorSev , chartFileName , validateChartVersionType ( chartFileForTypeCheck ) )
2016-07-01 10:07:56 +08:00
linter . RunLinterRule ( support . ErrorSev , chartFileName , validateChartVersion ( chartFile ) )
2019-12-15 22:43:56 +08:00
linter . RunLinterRule ( support . ErrorSev , chartFileName , validateChartAppVersionType ( chartFileForTypeCheck ) )
2016-07-01 10:07:56 +08:00
linter . RunLinterRule ( support . ErrorSev , chartFileName , validateChartMaintainer ( chartFile ) )
linter . RunLinterRule ( support . ErrorSev , chartFileName , validateChartSources ( chartFile ) )
2017-01-12 22:15:01 +08:00
linter . RunLinterRule ( support . InfoSev , chartFileName , validateChartIconPresence ( chartFile ) )
linter . RunLinterRule ( support . ErrorSev , chartFileName , validateChartIconURL ( chartFile ) )
2019-07-30 22:01:32 +08:00
linter . RunLinterRule ( support . ErrorSev , chartFileName , validateChartType ( chartFile ) )
linter . RunLinterRule ( support . ErrorSev , chartFileName , validateChartDependencies ( chartFile ) )
2016-06-10 07:51:00 +08:00
}
2019-12-15 22:43:56 +08:00
func validateChartVersionType ( data map [ string ] interface { } ) error {
return isStringValue ( data , "version" )
}
func validateChartAppVersionType ( data map [ string ] interface { } ) error {
return isStringValue ( data , "appVersion" )
}
func isStringValue ( data map [ string ] interface { } , key string ) error {
value , ok := data [ key ]
if ! ok {
return nil
}
valueType := fmt . Sprintf ( "%T" , value )
if valueType != "string" {
return errors . Errorf ( "%s should be of type string but it's of type %s" , key , valueType )
}
return nil
}
2016-07-01 10:07:56 +08:00
func validateChartYamlNotDirectory ( chartPath string ) error {
2016-06-10 07:51:00 +08:00
fi , err := os . Stat ( chartPath )
if err == nil && fi . IsDir ( ) {
2016-07-01 10:07:56 +08:00
return errors . New ( "should be a file, not a directory" )
2016-06-10 07:51:00 +08:00
}
2016-07-01 10:07:56 +08:00
return nil
2016-06-10 07:51:00 +08:00
}
2016-07-01 10:07:56 +08:00
func validateChartYamlFormat ( chartFileError error ) error {
2016-06-10 07:51:00 +08:00
if chartFileError != nil {
2018-05-11 00:34:41 +08:00
return errors . Errorf ( "unable to parse YAML\n\t%s" , chartFileError . Error ( ) )
2016-06-10 07:51:00 +08:00
}
2016-07-01 10:07:56 +08:00
return nil
2016-06-10 07:51:00 +08:00
}
2016-07-01 10:07:56 +08:00
func validateChartName ( cf * chart . Metadata ) error {
2016-06-10 07:51:00 +08:00
if cf . Name == "" {
2016-07-01 10:07:56 +08:00
return errors . New ( "name is required" )
2016-06-10 07:51:00 +08:00
}
2016-07-01 10:07:56 +08:00
return nil
2016-06-10 07:51:00 +08:00
}
2019-05-15 02:46:40 +08:00
func validateChartAPIVersion ( cf * chart . Metadata ) error {
if cf . APIVersion == "" {
2019-05-15 04:14:52 +08:00
return errors . New ( "apiVersion is required. The value must be either \"v1\" or \"v2\"" )
2019-05-15 02:12:47 +08:00
}
2019-07-30 22:01:32 +08:00
if cf . APIVersion != chart . APIVersionV1 && cf . APIVersion != chart . APIVersionV2 {
2019-05-15 02:46:40 +08:00
return fmt . Errorf ( "apiVersion '%s' is not valid. The value must be either \"v1\" or \"v2\"" , cf . APIVersion )
2019-05-15 02:12:47 +08:00
}
return nil
}
2016-07-01 10:07:56 +08:00
func validateChartVersion ( cf * chart . Metadata ) error {
2016-06-10 07:51:00 +08:00
if cf . Version == "" {
2016-07-01 10:07:56 +08:00
return errors . New ( "version is required" )
2016-06-10 07:51:00 +08:00
}
version , err := semver . NewVersion ( cf . Version )
if err != nil {
2018-05-11 00:34:41 +08:00
return errors . Errorf ( "version '%s' is not a valid SemVer" , cf . Version )
2016-06-10 07:51:00 +08:00
}
2019-10-04 01:42:59 +08:00
c , err := semver . NewConstraint ( ">0.0.0-0" )
2016-10-11 05:58:33 +08:00
if err != nil {
return err
}
2016-06-10 07:51:00 +08:00
valid , msg := c . Validate ( version )
if ! valid && len ( msg ) > 0 {
2018-05-11 00:34:41 +08:00
return errors . Errorf ( "version %v" , msg [ 0 ] )
2016-06-10 07:51:00 +08:00
}
2016-07-01 10:07:56 +08:00
return nil
2016-06-10 07:51:00 +08:00
}
2016-07-01 10:07:56 +08:00
func validateChartMaintainer ( cf * chart . Metadata ) error {
2016-06-10 07:51:00 +08:00
for _ , maintainer := range cf . Maintainers {
if maintainer . Name == "" {
2016-07-01 10:07:56 +08:00
return errors . New ( "each maintainer requires a name" )
2016-06-10 07:51:00 +08:00
} else if maintainer . Email != "" && ! govalidator . IsEmail ( maintainer . Email ) {
2018-05-11 00:34:41 +08:00
return errors . Errorf ( "invalid email '%s' for maintainer '%s'" , maintainer . Email , maintainer . Name )
2018-05-09 23:37:20 +08:00
} else if maintainer . URL != "" && ! govalidator . IsURL ( maintainer . URL ) {
2018-05-11 00:34:41 +08:00
return errors . Errorf ( "invalid url '%s' for maintainer '%s'" , maintainer . URL , maintainer . Name )
2016-06-10 07:51:00 +08:00
}
}
2016-07-01 10:07:56 +08:00
return nil
2016-06-10 07:51:00 +08:00
}
2016-07-01 10:07:56 +08:00
func validateChartSources ( cf * chart . Metadata ) error {
2016-06-10 07:51:00 +08:00
for _ , source := range cf . Sources {
if source == "" || ! govalidator . IsRequestURL ( source ) {
2018-05-11 00:34:41 +08:00
return errors . Errorf ( "invalid source URL '%s'" , source )
2016-06-10 07:51:00 +08:00
}
}
2016-07-01 10:07:56 +08:00
return nil
2016-06-10 07:51:00 +08:00
}
2017-01-12 22:15:01 +08:00
func validateChartIconPresence ( cf * chart . Metadata ) error {
if cf . Icon == "" {
return errors . New ( "icon is recommended" )
}
return nil
}
func validateChartIconURL ( cf * chart . Metadata ) error {
if cf . Icon != "" && ! govalidator . IsRequestURL ( cf . Icon ) {
2018-05-11 00:34:41 +08:00
return errors . Errorf ( "invalid icon URL '%s'" , cf . Icon )
2017-01-12 22:15:01 +08:00
}
return nil
}
2019-07-30 22:01:32 +08:00
func validateChartDependencies ( cf * chart . Metadata ) error {
if len ( cf . Dependencies ) > 0 && cf . APIVersion != chart . APIVersionV2 {
return fmt . Errorf ( "dependencies are not valid in the Chart file with apiVersion '%s'. They are valid in apiVersion '%s'" , cf . APIVersion , chart . APIVersionV2 )
}
return nil
}
func validateChartType ( cf * chart . Metadata ) error {
if len ( cf . Type ) > 0 && cf . APIVersion != chart . APIVersionV2 {
return fmt . Errorf ( "chart type is not valid in apiVersion '%s'. It is valid in apiVersion '%s'" , cf . APIVersion , chart . APIVersionV2 )
}
return nil
}
2019-12-15 22:43:56 +08:00
// loadChartFileForTypeCheck loads the Chart.yaml
// in a generic form of a map[string]interface{}, so that the type
// of the values can be checked
func loadChartFileForTypeCheck ( filename string ) ( map [ string ] interface { } , error ) {
2023-03-22 21:31:16 +08:00
b , err := os . ReadFile ( filename )
2019-12-15 22:43:56 +08:00
if err != nil {
return nil , err
}
y := make ( map [ string ] interface { } )
err = yaml . Unmarshal ( b , & y )
return y , err
}