github.com/latiif/helm@v2.15.0+incompatible/pkg/lint/rules/template.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
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"os"
    23  	"path/filepath"
    24  
    25  	"github.com/ghodss/yaml"
    26  	"k8s.io/helm/pkg/chartutil"
    27  	"k8s.io/helm/pkg/engine"
    28  	"k8s.io/helm/pkg/lint/support"
    29  	cpb "k8s.io/helm/pkg/proto/hapi/chart"
    30  	"k8s.io/helm/pkg/timeconv"
    31  	tversion "k8s.io/helm/pkg/version"
    32  )
    33  
    34  // Templates lints the templates in the Linter.
    35  func Templates(linter *support.Linter, values []byte, namespace string, strict bool) {
    36  	path := "templates/"
    37  	templatesPath := filepath.Join(linter.ChartDir, path)
    38  
    39  	templatesDirExist := linter.RunLinterRule(support.WarningSev, path, validateTemplatesDir(templatesPath))
    40  
    41  	// Templates directory is optional for now
    42  	if !templatesDirExist {
    43  		return
    44  	}
    45  
    46  	// Load chart and parse templates, based on tiller/release_server
    47  	chart, err := chartutil.Load(linter.ChartDir)
    48  
    49  	chartLoaded := linter.RunLinterRule(support.ErrorSev, path, err)
    50  
    51  	if !chartLoaded {
    52  		return
    53  	}
    54  
    55  	options := chartutil.ReleaseOptions{Name: "testRelease", Time: timeconv.Now(), Namespace: namespace}
    56  	caps := &chartutil.Capabilities{
    57  		APIVersions:   chartutil.DefaultVersionSet,
    58  		KubeVersion:   chartutil.DefaultKubeVersion,
    59  		TillerVersion: tversion.GetVersionProto(),
    60  	}
    61  	cvals, err := chartutil.CoalesceValues(chart, &cpb.Config{Raw: string(values)})
    62  	if err != nil {
    63  		return
    64  	}
    65  	// convert our values back into config
    66  	yvals, err := cvals.YAML()
    67  	if err != nil {
    68  		return
    69  	}
    70  	cc := &cpb.Config{Raw: yvals}
    71  	valuesToRender, err := chartutil.ToRenderValuesCaps(chart, cc, options, caps)
    72  	if err != nil {
    73  		// FIXME: This seems to generate a duplicate, but I can't find where the first
    74  		// error is coming from.
    75  		//linter.RunLinterRule(support.ErrorSev, err)
    76  		return
    77  	}
    78  	e := engine.New()
    79  	e.LintMode = true
    80  	if strict {
    81  		e.Strict = true
    82  	}
    83  	renderedContentMap, err := e.Render(chart, valuesToRender)
    84  
    85  	renderOk := linter.RunLinterRule(support.ErrorSev, path, err)
    86  
    87  	if !renderOk {
    88  		return
    89  	}
    90  
    91  	/* Iterate over all the templates to check:
    92  	- It is a .yaml file
    93  	- All the values in the template file is defined
    94  	- {{}} include | quote
    95  	- Generated content is a valid Yaml file
    96  	- Metadata.Namespace is not set
    97  	*/
    98  	for _, template := range chart.Templates {
    99  		fileName, _ := template.Name, template.Data
   100  		path = fileName
   101  
   102  		linter.RunLinterRule(support.WarningSev, path, validateAllowedExtension(fileName))
   103  
   104  		// We only apply the following lint rules to yaml files
   105  		if filepath.Ext(fileName) != ".yaml" || filepath.Ext(fileName) == ".yml" {
   106  			continue
   107  		}
   108  
   109  		renderedContent := renderedContentMap[filepath.Join(chart.GetMetadata().Name, fileName)]
   110  		var yamlStruct K8sYamlStruct
   111  		// Even though K8sYamlStruct only defines Metadata namespace, an error in any other
   112  		// key will be raised as well
   113  		err := yaml.Unmarshal([]byte(renderedContent), &yamlStruct)
   114  
   115  		validYaml := linter.RunLinterRule(support.ErrorSev, path, validateYamlContent(err))
   116  
   117  		if !validYaml {
   118  			continue
   119  		}
   120  	}
   121  }
   122  
   123  // Validation functions
   124  func validateTemplatesDir(templatesPath string) error {
   125  	if fi, err := os.Stat(templatesPath); err != nil {
   126  		return errors.New("directory not found")
   127  	} else if err == nil && !fi.IsDir() {
   128  		return errors.New("not a directory")
   129  	}
   130  	return nil
   131  }
   132  
   133  func validateAllowedExtension(fileName string) error {
   134  	ext := filepath.Ext(fileName)
   135  	validExtensions := []string{".yaml", ".yml", ".tpl", ".txt"}
   136  
   137  	for _, b := range validExtensions {
   138  		if b == ext {
   139  			return nil
   140  		}
   141  	}
   142  
   143  	return fmt.Errorf("file extension '%s' not valid. Valid extensions are .yaml, .yml, .tpl, or .txt", ext)
   144  }
   145  
   146  func validateYamlContent(err error) error {
   147  	if err != nil {
   148  		return fmt.Errorf("unable to parse YAML\n\t%s", err)
   149  	}
   150  	return nil
   151  }
   152  
   153  // K8sYamlStruct stubs a Kubernetes YAML file.
   154  // Need to access for now to Namespace only
   155  type K8sYamlStruct struct {
   156  	Metadata struct {
   157  		Namespace string
   158  	}
   159  }