github.com/umeshredd/helm@v3.0.0-alpha.1+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  }
    59  
    60  func validateChartYamlNotDirectory(chartPath string) error {
    61  	fi, err := os.Stat(chartPath)
    62  
    63  	if err == nil && fi.IsDir() {
    64  		return errors.New("should be a file, not a directory")
    65  	}
    66  	return nil
    67  }
    68  
    69  func validateChartYamlFormat(chartFileError error) error {
    70  	if chartFileError != nil {
    71  		return errors.Errorf("unable to parse YAML\n\t%s", chartFileError.Error())
    72  	}
    73  	return nil
    74  }
    75  
    76  func validateChartName(cf *chart.Metadata) error {
    77  	if cf.Name == "" {
    78  		return errors.New("name is required")
    79  	}
    80  	return nil
    81  }
    82  
    83  func validateChartNameDirMatch(chartDir string, cf *chart.Metadata) error {
    84  	if cf.Name != filepath.Base(chartDir) {
    85  		return errors.Errorf("directory name (%s) and chart name (%s) must be the same", filepath.Base(chartDir), cf.Name)
    86  	}
    87  	return nil
    88  }
    89  
    90  func validateChartAPIVersion(cf *chart.Metadata) error {
    91  	if cf.APIVersion == "" {
    92  		return errors.New("apiVersion is required. The value must be either \"v1\" or \"v2\"")
    93  	}
    94  
    95  	if cf.APIVersion != "v1" && cf.APIVersion != "v2" {
    96  		return fmt.Errorf("apiVersion '%s' is not valid. The value must be either \"v1\" or \"v2\"", cf.APIVersion)
    97  	}
    98  
    99  	return nil
   100  }
   101  
   102  func validateChartVersion(cf *chart.Metadata) error {
   103  	if cf.Version == "" {
   104  		return errors.New("version is required")
   105  	}
   106  
   107  	version, err := semver.NewVersion(cf.Version)
   108  
   109  	if err != nil {
   110  		return errors.Errorf("version '%s' is not a valid SemVer", cf.Version)
   111  	}
   112  
   113  	c, err := semver.NewConstraint("> 0")
   114  	if err != nil {
   115  		return err
   116  	}
   117  	valid, msg := c.Validate(version)
   118  
   119  	if !valid && len(msg) > 0 {
   120  		return errors.Errorf("version %v", msg[0])
   121  	}
   122  
   123  	return nil
   124  }
   125  
   126  func validateChartMaintainer(cf *chart.Metadata) error {
   127  	for _, maintainer := range cf.Maintainers {
   128  		if maintainer.Name == "" {
   129  			return errors.New("each maintainer requires a name")
   130  		} else if maintainer.Email != "" && !govalidator.IsEmail(maintainer.Email) {
   131  			return errors.Errorf("invalid email '%s' for maintainer '%s'", maintainer.Email, maintainer.Name)
   132  		} else if maintainer.URL != "" && !govalidator.IsURL(maintainer.URL) {
   133  			return errors.Errorf("invalid url '%s' for maintainer '%s'", maintainer.URL, maintainer.Name)
   134  		}
   135  	}
   136  	return nil
   137  }
   138  
   139  func validateChartSources(cf *chart.Metadata) error {
   140  	for _, source := range cf.Sources {
   141  		if source == "" || !govalidator.IsRequestURL(source) {
   142  			return errors.Errorf("invalid source URL '%s'", source)
   143  		}
   144  	}
   145  	return nil
   146  }
   147  
   148  func validateChartIconPresence(cf *chart.Metadata) error {
   149  	if cf.Icon == "" {
   150  		return errors.New("icon is recommended")
   151  	}
   152  	return nil
   153  }
   154  
   155  func validateChartIconURL(cf *chart.Metadata) error {
   156  	if cf.Icon != "" && !govalidator.IsRequestURL(cf.Icon) {
   157  		return errors.Errorf("invalid icon URL '%s'", cf.Icon)
   158  	}
   159  	return nil
   160  }