github.com/werf/3p-helm@v2.8.1+incompatible/cmd/helm/lint.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 main
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"io"
    23  	"io/ioutil"
    24  	"os"
    25  	"path/filepath"
    26  	"strings"
    27  
    28  	"github.com/ghodss/yaml"
    29  	"github.com/spf13/cobra"
    30  
    31  	"k8s.io/helm/pkg/chartutil"
    32  	"k8s.io/helm/pkg/lint"
    33  	"k8s.io/helm/pkg/lint/support"
    34  	"k8s.io/helm/pkg/strvals"
    35  )
    36  
    37  var longLintHelp = `
    38  This command takes a path to a chart and runs a series of tests to verify that
    39  the chart is well-formed.
    40  
    41  If the linter encounters things that will cause the chart to fail installation,
    42  it will emit [ERROR] messages. If it encounters issues that break with convention
    43  or recommendation, it will emit [WARNING] messages.
    44  `
    45  
    46  type lintCmd struct {
    47  	valueFiles valueFiles
    48  	values     []string
    49  	namespace  string
    50  	strict     bool
    51  	paths      []string
    52  	out        io.Writer
    53  }
    54  
    55  func newLintCmd(out io.Writer) *cobra.Command {
    56  	l := &lintCmd{
    57  		paths: []string{"."},
    58  		out:   out,
    59  	}
    60  	cmd := &cobra.Command{
    61  		Use:   "lint [flags] PATH",
    62  		Short: "examines a chart for possible issues",
    63  		Long:  longLintHelp,
    64  		RunE: func(cmd *cobra.Command, args []string) error {
    65  			if len(args) > 0 {
    66  				l.paths = args
    67  			}
    68  			return l.run()
    69  		},
    70  	}
    71  
    72  	cmd.Flags().VarP(&l.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)")
    73  	cmd.Flags().StringArrayVar(&l.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
    74  	cmd.Flags().StringVar(&l.namespace, "namespace", "default", "namespace to install the release into (only used if --install is set)")
    75  	cmd.Flags().BoolVar(&l.strict, "strict", false, "fail on lint warnings")
    76  
    77  	return cmd
    78  }
    79  
    80  var errLintNoChart = errors.New("No chart found for linting (missing Chart.yaml)")
    81  
    82  func (l *lintCmd) run() error {
    83  	var lowestTolerance int
    84  	if l.strict {
    85  		lowestTolerance = support.WarningSev
    86  	} else {
    87  		lowestTolerance = support.ErrorSev
    88  	}
    89  
    90  	// Get the raw values
    91  	rvals, err := l.vals()
    92  	if err != nil {
    93  		return err
    94  	}
    95  
    96  	var total int
    97  	var failures int
    98  	for _, path := range l.paths {
    99  		if linter, err := lintChart(path, rvals, l.namespace, l.strict); err != nil {
   100  			fmt.Println("==> Skipping", path)
   101  			fmt.Println(err)
   102  		} else {
   103  			fmt.Println("==> Linting", path)
   104  
   105  			if len(linter.Messages) == 0 {
   106  				fmt.Println("Lint OK")
   107  			}
   108  
   109  			for _, msg := range linter.Messages {
   110  				fmt.Println(msg)
   111  			}
   112  
   113  			total = total + 1
   114  			if linter.HighestSeverity >= lowestTolerance {
   115  				failures = failures + 1
   116  			}
   117  		}
   118  		fmt.Println("")
   119  	}
   120  
   121  	msg := fmt.Sprintf("%d chart(s) linted", total)
   122  	if failures > 0 {
   123  		return fmt.Errorf("%s, %d chart(s) failed", msg, failures)
   124  	}
   125  
   126  	fmt.Fprintf(l.out, "%s, no failures\n", msg)
   127  
   128  	return nil
   129  }
   130  
   131  func lintChart(path string, vals []byte, namespace string, strict bool) (support.Linter, error) {
   132  	var chartPath string
   133  	linter := support.Linter{}
   134  
   135  	if strings.HasSuffix(path, ".tgz") {
   136  		tempDir, err := ioutil.TempDir("", "helm-lint")
   137  		if err != nil {
   138  			return linter, err
   139  		}
   140  		defer os.RemoveAll(tempDir)
   141  
   142  		file, err := os.Open(path)
   143  		if err != nil {
   144  			return linter, err
   145  		}
   146  		defer file.Close()
   147  
   148  		if err = chartutil.Expand(tempDir, file); err != nil {
   149  			return linter, err
   150  		}
   151  
   152  		base := strings.Split(filepath.Base(path), "-")[0]
   153  		chartPath = filepath.Join(tempDir, base)
   154  	} else {
   155  		chartPath = path
   156  	}
   157  
   158  	// Guard: Error out of this is not a chart.
   159  	if _, err := os.Stat(filepath.Join(chartPath, "Chart.yaml")); err != nil {
   160  		return linter, errLintNoChart
   161  	}
   162  
   163  	return lint.All(chartPath, vals, namespace, strict), nil
   164  }
   165  
   166  func (l *lintCmd) vals() ([]byte, error) {
   167  	base := map[string]interface{}{}
   168  
   169  	// User specified a values files via -f/--values
   170  	for _, filePath := range l.valueFiles {
   171  		currentMap := map[string]interface{}{}
   172  		bytes, err := ioutil.ReadFile(filePath)
   173  		if err != nil {
   174  			return []byte{}, err
   175  		}
   176  
   177  		if err := yaml.Unmarshal(bytes, &currentMap); err != nil {
   178  			return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err)
   179  		}
   180  		// Merge with the previous map
   181  		base = mergeValues(base, currentMap)
   182  	}
   183  
   184  	// User specified a value via --set
   185  	for _, value := range l.values {
   186  		if err := strvals.ParseInto(value, base); err != nil {
   187  			return []byte{}, fmt.Errorf("failed parsing --set data: %s", err)
   188  		}
   189  	}
   190  
   191  	return yaml.Marshal(base)
   192  }