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