github.com/latiif/helm@v2.15.0+incompatible/cmd/helm/lint.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 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  	sValues    []string
    50  	fValues    []string
    51  	namespace  string
    52  	strict     bool
    53  	paths      []string
    54  	out        io.Writer
    55  }
    56  
    57  func newLintCmd(out io.Writer) *cobra.Command {
    58  	l := &lintCmd{
    59  		paths: []string{"."},
    60  		out:   out,
    61  	}
    62  	cmd := &cobra.Command{
    63  		Use:   "lint [flags] PATH",
    64  		Short: "Examines a chart for possible issues",
    65  		Long:  longLintHelp,
    66  		RunE: func(cmd *cobra.Command, args []string) error {
    67  			if len(args) > 0 {
    68  				l.paths = args
    69  			}
    70  			return l.run()
    71  		},
    72  	}
    73  
    74  	cmd.Flags().VarP(&l.valueFiles, "values", "f", "Specify values in a YAML file (can specify multiple)")
    75  	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)")
    76  	cmd.Flags().StringArrayVar(&l.sValues, "set-string", []string{}, "Set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
    77  	cmd.Flags().StringArrayVar(&l.fValues, "set-file", []string{}, "Set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)")
    78  	cmd.Flags().StringVar(&l.namespace, "namespace", "default", "Namespace to put the release into")
    79  	cmd.Flags().BoolVar(&l.strict, "strict", false, "Fail on lint warnings")
    80  
    81  	return cmd
    82  }
    83  
    84  var errLintNoChart = errors.New("No chart found for linting (missing Chart.yaml)")
    85  
    86  func (l *lintCmd) run() error {
    87  	var lowestTolerance int
    88  	if l.strict {
    89  		lowestTolerance = support.WarningSev
    90  	} else {
    91  		lowestTolerance = support.ErrorSev
    92  	}
    93  
    94  	// Get the raw values
    95  	rvals, err := l.vals()
    96  	if err != nil {
    97  		return err
    98  	}
    99  
   100  	var total int
   101  	var failures int
   102  	for _, path := range l.paths {
   103  		if linter, err := lintChart(path, rvals, l.namespace, l.strict); err != nil {
   104  			fmt.Println("==> Skipping", path)
   105  			fmt.Println(err)
   106  			if err == errLintNoChart {
   107  				failures = failures + 1
   108  			}
   109  		} else {
   110  			fmt.Println("==> Linting", path)
   111  
   112  			if len(linter.Messages) == 0 {
   113  				fmt.Println("Lint OK")
   114  			}
   115  
   116  			for _, msg := range linter.Messages {
   117  				fmt.Println(msg)
   118  			}
   119  
   120  			total = total + 1
   121  			if linter.HighestSeverity >= lowestTolerance {
   122  				failures = failures + 1
   123  			}
   124  		}
   125  		fmt.Println("")
   126  	}
   127  
   128  	msg := fmt.Sprintf("%d chart(s) linted", total)
   129  	if failures > 0 {
   130  		return fmt.Errorf("%s, %d chart(s) failed", msg, failures)
   131  	}
   132  
   133  	fmt.Fprintf(l.out, "%s, no failures\n", msg)
   134  
   135  	return nil
   136  }
   137  
   138  func lintChart(path string, vals []byte, namespace string, strict bool) (support.Linter, error) {
   139  	var chartPath string
   140  	linter := support.Linter{}
   141  
   142  	if strings.HasSuffix(path, ".tgz") {
   143  		tempDir, err := ioutil.TempDir("", "helm-lint")
   144  		if err != nil {
   145  			return linter, err
   146  		}
   147  		defer os.RemoveAll(tempDir)
   148  
   149  		file, err := os.Open(path)
   150  		if err != nil {
   151  			return linter, err
   152  		}
   153  		defer file.Close()
   154  
   155  		if err = chartutil.Expand(tempDir, file); err != nil {
   156  			return linter, err
   157  		}
   158  
   159  		files, err := ioutil.ReadDir(tempDir)
   160  		if err != nil {
   161  			return linter, fmt.Errorf("unable to read temporary output directory %s", tempDir)
   162  		}
   163  		if !files[0].IsDir() {
   164  			return linter, fmt.Errorf("unexpected file %s in temporary output directory %s", files[0].Name(), tempDir)
   165  		}
   166  
   167  		chartPath = filepath.Join(tempDir, files[0].Name())
   168  	} else {
   169  		chartPath = path
   170  	}
   171  
   172  	// Guard: Error out if this is not a chart.
   173  	if _, err := os.Stat(filepath.Join(chartPath, "Chart.yaml")); err != nil {
   174  		return linter, errLintNoChart
   175  	}
   176  
   177  	return lint.All(chartPath, vals, namespace, strict), nil
   178  }
   179  
   180  // vals merges values from files specified via -f/--values and
   181  // directly via --set or --set-string or --set-file, marshaling them to YAML
   182  //
   183  // This func is implemented intentionally and separately from the `vals` func for the `install` and `upgrade` commands.
   184  // Compared to the alternative func, this func lacks the parameters for tls opts - ca key, cert, and ca cert.
   185  // That's because this command, `lint`, is explicitly forbidden from making server connections.
   186  func (l *lintCmd) vals() ([]byte, error) {
   187  	base := map[string]interface{}{}
   188  
   189  	// User specified a values files via -f/--values
   190  	for _, filePath := range l.valueFiles {
   191  		currentMap := map[string]interface{}{}
   192  		bytes, err := ioutil.ReadFile(filePath)
   193  		if err != nil {
   194  			return []byte{}, err
   195  		}
   196  
   197  		if err := yaml.Unmarshal(bytes, &currentMap); err != nil {
   198  			return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err)
   199  		}
   200  		// Merge with the previous map
   201  		base = mergeValues(base, currentMap)
   202  	}
   203  
   204  	// User specified a value via --set
   205  	for _, value := range l.values {
   206  		if err := strvals.ParseInto(value, base); err != nil {
   207  			return []byte{}, fmt.Errorf("failed parsing --set data: %s", err)
   208  		}
   209  	}
   210  
   211  	// User specified a value via --set-string
   212  	for _, value := range l.sValues {
   213  		if err := strvals.ParseIntoString(value, base); err != nil {
   214  			return []byte{}, fmt.Errorf("failed parsing --set-string data: %s", err)
   215  		}
   216  	}
   217  
   218  	// User specified a value via --set-file
   219  	for _, value := range l.fValues {
   220  		reader := func(rs []rune) (interface{}, error) {
   221  			bytes, err := ioutil.ReadFile(string(rs))
   222  			return string(bytes), err
   223  		}
   224  		if err := strvals.ParseIntoFile(value, base, reader); err != nil {
   225  			return []byte{}, fmt.Errorf("failed parsing --set-file data: %s", err)
   226  		}
   227  	}
   228  
   229  	return yaml.Marshal(base)
   230  }