github.com/qsis/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  }