github.com/Beeketing/helm@v2.12.1+incompatible/cmd/helm/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 main
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"io"
    23  	"os"
    24  	"path"
    25  	"path/filepath"
    26  	"regexp"
    27  	"strings"
    28  	"time"
    29  
    30  	"github.com/spf13/cobra"
    31  
    32  	"k8s.io/apimachinery/pkg/util/validation"
    33  	"k8s.io/helm/pkg/chartutil"
    34  	"k8s.io/helm/pkg/manifest"
    35  	"k8s.io/helm/pkg/proto/hapi/chart"
    36  	"k8s.io/helm/pkg/proto/hapi/release"
    37  	"k8s.io/helm/pkg/renderutil"
    38  	"k8s.io/helm/pkg/tiller"
    39  	"k8s.io/helm/pkg/timeconv"
    40  )
    41  
    42  const defaultDirectoryPermission = 0755
    43  
    44  var (
    45  	whitespaceRegex = regexp.MustCompile(`^\s*$`)
    46  
    47  	// defaultKubeVersion is the default value of --kube-version flag
    48  	defaultKubeVersion = fmt.Sprintf("%s.%s", chartutil.DefaultKubeVersion.Major, chartutil.DefaultKubeVersion.Minor)
    49  )
    50  
    51  const templateDesc = `
    52  Render chart templates locally and display the output.
    53  
    54  This does not require Tiller. However, any values that would normally be
    55  looked up or retrieved in-cluster will be faked locally. Additionally, none
    56  of the server-side testing of chart validity (e.g. whether an API is supported)
    57  is done.
    58  
    59  To render just one template in a chart, use '-x':
    60  
    61  	$ helm template mychart -x templates/deployment.yaml
    62  `
    63  
    64  type templateCmd struct {
    65  	namespace        string
    66  	valueFiles       valueFiles
    67  	chartPath        string
    68  	out              io.Writer
    69  	values           []string
    70  	stringValues     []string
    71  	fileValues       []string
    72  	nameTemplate     string
    73  	showNotes        bool
    74  	releaseName      string
    75  	releaseIsUpgrade bool
    76  	renderFiles      []string
    77  	kubeVersion      string
    78  	outputDir        string
    79  }
    80  
    81  func newTemplateCmd(out io.Writer) *cobra.Command {
    82  
    83  	t := &templateCmd{
    84  		out: out,
    85  	}
    86  
    87  	cmd := &cobra.Command{
    88  		Use:   "template [flags] CHART",
    89  		Short: fmt.Sprintf("locally render templates"),
    90  		Long:  templateDesc,
    91  		RunE:  t.run,
    92  	}
    93  
    94  	f := cmd.Flags()
    95  	f.BoolVar(&t.showNotes, "notes", false, "show the computed NOTES.txt file as well")
    96  	f.StringVarP(&t.releaseName, "name", "n", "release-name", "release name")
    97  	f.BoolVar(&t.releaseIsUpgrade, "is-upgrade", false, "set .Release.IsUpgrade instead of .Release.IsInstall")
    98  	f.StringArrayVarP(&t.renderFiles, "execute", "x", []string{}, "only execute the given templates")
    99  	f.VarP(&t.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)")
   100  	f.StringVar(&t.namespace, "namespace", "", "namespace to install the release into")
   101  	f.StringArrayVar(&t.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
   102  	f.StringArrayVar(&t.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
   103  	f.StringArrayVar(&t.fileValues, "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)")
   104  	f.StringVar(&t.nameTemplate, "name-template", "", "specify template used to name the release")
   105  	f.StringVar(&t.kubeVersion, "kube-version", defaultKubeVersion, "kubernetes version used as Capabilities.KubeVersion.Major/Minor")
   106  	f.StringVar(&t.outputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout")
   107  
   108  	return cmd
   109  }
   110  
   111  func (t *templateCmd) run(cmd *cobra.Command, args []string) error {
   112  	if len(args) < 1 {
   113  		return errors.New("chart is required")
   114  	}
   115  	// verify chart path exists
   116  	if _, err := os.Stat(args[0]); err == nil {
   117  		if t.chartPath, err = filepath.Abs(args[0]); err != nil {
   118  			return err
   119  		}
   120  	} else {
   121  		return err
   122  	}
   123  
   124  	// verify that output-dir exists if provided
   125  	if t.outputDir != "" {
   126  		_, err := os.Stat(t.outputDir)
   127  		if os.IsNotExist(err) {
   128  			return fmt.Errorf("output-dir '%s' does not exist", t.outputDir)
   129  		}
   130  	}
   131  
   132  	if t.namespace == "" {
   133  		t.namespace = defaultNamespace()
   134  	}
   135  	// get combined values and create config
   136  	rawVals, err := vals(t.valueFiles, t.values, t.stringValues, t.fileValues, "", "", "")
   137  	if err != nil {
   138  		return err
   139  	}
   140  	config := &chart.Config{Raw: string(rawVals), Values: map[string]*chart.Value{}}
   141  
   142  	// If template is specified, try to run the template.
   143  	if t.nameTemplate != "" {
   144  		t.releaseName, err = generateName(t.nameTemplate)
   145  		if err != nil {
   146  			return err
   147  		}
   148  	}
   149  
   150  	if msgs := validation.IsDNS1123Label(t.releaseName); t.releaseName != "" && len(msgs) > 0 {
   151  		return fmt.Errorf("release name %s is not a valid DNS label: %s", t.releaseName, strings.Join(msgs, ";"))
   152  	}
   153  
   154  	// Check chart requirements to make sure all dependencies are present in /charts
   155  	c, err := chartutil.Load(t.chartPath)
   156  	if err != nil {
   157  		return prettyError(err)
   158  	}
   159  
   160  	renderOpts := renderutil.Options{
   161  		ReleaseOptions: chartutil.ReleaseOptions{
   162  			Name:      t.releaseName,
   163  			IsInstall: !t.releaseIsUpgrade,
   164  			IsUpgrade: t.releaseIsUpgrade,
   165  			Time:      timeconv.Now(),
   166  			Namespace: t.namespace,
   167  		},
   168  		KubeVersion: t.kubeVersion,
   169  	}
   170  
   171  	renderedTemplates, err := renderutil.Render(c, config, renderOpts)
   172  	if err != nil {
   173  		return err
   174  	}
   175  
   176  	if settings.Debug {
   177  		rel := &release.Release{
   178  			Name:      t.releaseName,
   179  			Chart:     c,
   180  			Config:    config,
   181  			Version:   1,
   182  			Namespace: t.namespace,
   183  			Info:      &release.Info{LastDeployed: timeconv.Timestamp(time.Now())},
   184  		}
   185  		printRelease(os.Stdout, rel)
   186  	}
   187  
   188  	listManifests := manifest.SplitManifests(renderedTemplates)
   189  	var manifestsToRender []manifest.Manifest
   190  
   191  	// if we have a list of files to render, then check that each of the
   192  	// provided files exists in the chart.
   193  	if len(t.renderFiles) > 0 {
   194  		for _, f := range t.renderFiles {
   195  			missing := true
   196  			if !filepath.IsAbs(f) {
   197  				newF, err := filepath.Abs(filepath.Join(t.chartPath, f))
   198  				if err != nil {
   199  					return fmt.Errorf("could not turn template path %s into absolute path: %s", f, err)
   200  				}
   201  				f = newF
   202  			}
   203  
   204  			for _, manifest := range listManifests {
   205  				// manifest.Name is rendered using linux-style filepath separators on Windows as
   206  				// well as macOS/linux.
   207  				manifestPathSplit := strings.Split(manifest.Name, "/")
   208  				// remove the chart name from the path
   209  				manifestPathSplit = manifestPathSplit[1:]
   210  				toJoin := append([]string{t.chartPath}, manifestPathSplit...)
   211  				manifestPath := filepath.Join(toJoin...)
   212  
   213  				// if the filepath provided matches a manifest path in the
   214  				// chart, render that manifest
   215  				if f == manifestPath {
   216  					manifestsToRender = append(manifestsToRender, manifest)
   217  					missing = false
   218  				}
   219  			}
   220  			if missing {
   221  				return fmt.Errorf("could not find template %s in chart", f)
   222  			}
   223  		}
   224  	} else {
   225  		// no renderFiles provided, render all manifests in the chart
   226  		manifestsToRender = listManifests
   227  	}
   228  
   229  	for _, m := range tiller.SortByKind(manifestsToRender) {
   230  		data := m.Content
   231  		b := filepath.Base(m.Name)
   232  		if !t.showNotes && b == "NOTES.txt" {
   233  			continue
   234  		}
   235  		if strings.HasPrefix(b, "_") {
   236  			continue
   237  		}
   238  
   239  		if t.outputDir != "" {
   240  			// blank template after execution
   241  			if whitespaceRegex.MatchString(data) {
   242  				continue
   243  			}
   244  			err = writeToFile(t.outputDir, m.Name, data)
   245  			if err != nil {
   246  				return err
   247  			}
   248  			continue
   249  		}
   250  		fmt.Printf("---\n# Source: %s\n", m.Name)
   251  		fmt.Println(data)
   252  	}
   253  	return nil
   254  }
   255  
   256  // write the <data> to <output-dir>/<name>
   257  func writeToFile(outputDir string, name string, data string) error {
   258  	outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator))
   259  
   260  	err := ensureDirectoryForFile(outfileName)
   261  	if err != nil {
   262  		return err
   263  	}
   264  
   265  	f, err := os.Create(outfileName)
   266  	if err != nil {
   267  		return err
   268  	}
   269  
   270  	defer f.Close()
   271  
   272  	_, err = f.WriteString(fmt.Sprintf("---\n# Source: %s\n%s", name, data))
   273  
   274  	if err != nil {
   275  		return err
   276  	}
   277  
   278  	fmt.Printf("wrote %s\n", outfileName)
   279  	return nil
   280  }
   281  
   282  // check if the directory exists to create file. creates if don't exists
   283  func ensureDirectoryForFile(file string) error {
   284  	baseDir := path.Dir(file)
   285  	_, err := os.Stat(baseDir)
   286  	if err != nil && !os.IsNotExist(err) {
   287  		return err
   288  	}
   289  
   290  	return os.MkdirAll(baseDir, defaultDirectoryPermission)
   291  }