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