github.com/sgoings/helm@v2.0.0-alpha.2.0.20170406211108-734e92851ac3+incompatible/pkg/lint/rules/template.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
    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  	"k8s.io/helm/pkg/timeconv"
    30  )
    31  
    32  // Templates lints the templates in the Linter.
    33  func Templates(linter *support.Linter) {
    34  	path := "templates/"
    35  	templatesPath := filepath.Join(linter.ChartDir, path)
    36  
    37  	templatesDirExist := linter.RunLinterRule(support.WarningSev, path, validateTemplatesDir(templatesPath))
    38  
    39  	// Templates directory is optional for now
    40  	if !templatesDirExist {
    41  		return
    42  	}
    43  
    44  	// Load chart and parse templates, based on tiller/release_server
    45  	chart, err := chartutil.Load(linter.ChartDir)
    46  
    47  	chartLoaded := linter.RunLinterRule(support.ErrorSev, path, err)
    48  
    49  	if !chartLoaded {
    50  		return
    51  	}
    52  
    53  	options := chartutil.ReleaseOptions{Name: "testRelease", Time: timeconv.Now(), Namespace: "testNamespace"}
    54  	caps := &chartutil.Capabilities{APIVersions: chartutil.DefaultVersionSet}
    55  	valuesToRender, err := chartutil.ToRenderValuesCaps(chart, chart.Values, options, caps)
    56  	if err != nil {
    57  		// FIXME: This seems to generate a duplicate, but I can't find where the first
    58  		// error is coming from.
    59  		//linter.RunLinterRule(support.ErrorSev, err)
    60  		return
    61  	}
    62  	renderedContentMap, err := engine.New().Render(chart, valuesToRender)
    63  
    64  	renderOk := linter.RunLinterRule(support.ErrorSev, path, err)
    65  
    66  	if !renderOk {
    67  		return
    68  	}
    69  
    70  	/* Iterate over all the templates to check:
    71  	- It is a .yaml file
    72  	- All the values in the template file is defined
    73  	- {{}} include | quote
    74  	- Generated content is a valid Yaml file
    75  	- Metadata.Namespace is not set
    76  	*/
    77  	for _, template := range chart.Templates {
    78  		fileName, _ := template.Name, template.Data
    79  		path = fileName
    80  
    81  		linter.RunLinterRule(support.ErrorSev, path, validateAllowedExtension(fileName))
    82  
    83  		// We only apply the following lint rules to yaml files
    84  		if filepath.Ext(fileName) != ".yaml" {
    85  			continue
    86  		}
    87  
    88  		// NOTE: disabled for now, Refs https://github.com/kubernetes/helm/issues/1463
    89  		// Check that all the templates have a matching value
    90  		//linter.RunLinterRule(support.WarningSev, path, validateNoMissingValues(templatesPath, valuesToRender, preExecutedTemplate))
    91  
    92  		// NOTE: disabled for now, Refs https://github.com/kubernetes/helm/issues/1037
    93  		// linter.RunLinterRule(support.WarningSev, path, validateQuotes(string(preExecutedTemplate)))
    94  
    95  		renderedContent := renderedContentMap[filepath.Join(chart.GetMetadata().Name, fileName)]
    96  		var yamlStruct K8sYamlStruct
    97  		// Even though K8sYamlStruct only defines Metadata namespace, an error in any other
    98  		// key will be raised as well
    99  		err := yaml.Unmarshal([]byte(renderedContent), &yamlStruct)
   100  
   101  		validYaml := linter.RunLinterRule(support.ErrorSev, path, validateYamlContent(err))
   102  
   103  		if !validYaml {
   104  			continue
   105  		}
   106  	}
   107  }
   108  
   109  // Validation functions
   110  func validateTemplatesDir(templatesPath string) error {
   111  	if fi, err := os.Stat(templatesPath); err != nil {
   112  		return errors.New("directory not found")
   113  	} else if err == nil && !fi.IsDir() {
   114  		return errors.New("not a directory")
   115  	}
   116  	return nil
   117  }
   118  
   119  func validateAllowedExtension(fileName string) error {
   120  	ext := filepath.Ext(fileName)
   121  	validExtensions := []string{".yaml", ".tpl", ".txt"}
   122  
   123  	for _, b := range validExtensions {
   124  		if b == ext {
   125  			return nil
   126  		}
   127  	}
   128  
   129  	return fmt.Errorf("file extension '%s' not valid. Valid extensions are .yaml, .tpl, or .txt", ext)
   130  }
   131  
   132  func validateYamlContent(err error) error {
   133  	if err != nil {
   134  		return fmt.Errorf("unable to parse YAML\n\t%s", err)
   135  	}
   136  	return nil
   137  }
   138  
   139  // K8sYamlStruct stubs a Kubernetes YAML file.
   140  // Need to access for now to Namespace only
   141  type K8sYamlStruct struct {
   142  	Metadata struct {
   143  		Namespace string
   144  	}
   145  }