github.com/amundsenjunior/helm@v2.8.0-rc.1.0.20180119233529-2b92431476e1+incompatible/cmd/helm/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 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/Masterminds/semver"
    31  	"github.com/spf13/cobra"
    32  
    33  	"k8s.io/helm/pkg/chartutil"
    34  	"k8s.io/helm/pkg/engine"
    35  	"k8s.io/helm/pkg/proto/hapi/chart"
    36  	"k8s.io/helm/pkg/proto/hapi/release"
    37  	util "k8s.io/helm/pkg/releaseutil"
    38  	"k8s.io/helm/pkg/tiller"
    39  	"k8s.io/helm/pkg/timeconv"
    40  	tversion "k8s.io/helm/pkg/version"
    41  )
    42  
    43  const defaultDirectoryPermission = 0755
    44  
    45  var (
    46  	whitespaceRegex = regexp.MustCompile(`^\s*$`)
    47  
    48  	// defaultKubeVersion is the default value of --kube-version flag
    49  	defaultKubeVersion = fmt.Sprintf("%s.%s", chartutil.DefaultKubeVersion.Major, chartutil.DefaultKubeVersion.Minor)
    50  )
    51  
    52  const templateDesc = `
    53  Render chart templates locally and display the output.
    54  
    55  This does not require Tiller. However, any values that would normally be
    56  looked up or retrieved in-cluster will be faked locally. Additionally, none
    57  of the server-side testing of chart validity (e.g. whether an API is supported)
    58  is done.
    59  
    60  To render just one template in a chart, use '-x':
    61  
    62  	$ helm template mychart -x templates/deployment.yaml
    63  `
    64  
    65  type templateCmd struct {
    66  	namespace    string
    67  	valueFiles   valueFiles
    68  	chartPath    string
    69  	out          io.Writer
    70  	values       []string
    71  	nameTemplate string
    72  	showNotes    bool
    73  	releaseName  string
    74  	renderFiles  []string
    75  	kubeVersion  string
    76  	outputDir    string
    77  }
    78  
    79  func newTemplateCmd(out io.Writer) *cobra.Command {
    80  
    81  	t := &templateCmd{
    82  		out: out,
    83  	}
    84  
    85  	cmd := &cobra.Command{
    86  		Use:   "template [flags] CHART",
    87  		Short: fmt.Sprintf("locally render templates"),
    88  		Long:  templateDesc,
    89  		RunE:  t.run,
    90  	}
    91  
    92  	f := cmd.Flags()
    93  	f.BoolVar(&t.showNotes, "notes", false, "show the computed NOTES.txt file as well")
    94  	f.StringVarP(&t.releaseName, "name", "n", "RELEASE-NAME", "release name")
    95  	f.StringArrayVarP(&t.renderFiles, "execute", "x", []string{}, "only execute the given templates")
    96  	f.VarP(&t.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)")
    97  	f.StringVar(&t.namespace, "namespace", "", "namespace to install the release into")
    98  	f.StringArrayVar(&t.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
    99  	f.StringVar(&t.nameTemplate, "name-template", "", "specify template used to name the release")
   100  	f.StringVar(&t.kubeVersion, "kube-version", defaultKubeVersion, "kubernetes version used as Capabilities.KubeVersion.Major/Minor")
   101  	f.StringVar(&t.outputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout")
   102  
   103  	return cmd
   104  }
   105  
   106  func (t *templateCmd) run(cmd *cobra.Command, args []string) error {
   107  	if len(args) < 1 {
   108  		return errors.New("chart is required")
   109  	}
   110  	// verify chart path exists
   111  	if _, err := os.Stat(args[0]); err == nil {
   112  		if t.chartPath, err = filepath.Abs(args[0]); err != nil {
   113  			return err
   114  		}
   115  	} else {
   116  		return err
   117  	}
   118  	// verify specified templates exist relative to chart
   119  	rf := []string{}
   120  	var af string
   121  	var err error
   122  	if len(t.renderFiles) > 0 {
   123  		for _, f := range t.renderFiles {
   124  			if !filepath.IsAbs(f) {
   125  				af, err = filepath.Abs(t.chartPath + "/" + f)
   126  				if err != nil {
   127  					return fmt.Errorf("could not resolve template path: %s", err)
   128  				}
   129  			} else {
   130  				af = f
   131  			}
   132  			rf = append(rf, af)
   133  
   134  			if _, err := os.Stat(af); err != nil {
   135  				return fmt.Errorf("could not resolve template path: %s", err)
   136  			}
   137  		}
   138  	}
   139  
   140  	// verify that output-dir exists if provided
   141  	if t.outputDir != "" {
   142  		_, err = os.Stat(t.outputDir)
   143  		if os.IsNotExist(err) {
   144  			return fmt.Errorf("output-dir '%s' does not exist", t.outputDir)
   145  		}
   146  	}
   147  
   148  	if t.namespace == "" {
   149  		t.namespace = defaultNamespace()
   150  	}
   151  	// get combined values and create config
   152  	rawVals, err := vals(t.valueFiles, t.values)
   153  	if err != nil {
   154  		return err
   155  	}
   156  	config := &chart.Config{Raw: string(rawVals), Values: map[string]*chart.Value{}}
   157  
   158  	// If template is specified, try to run the template.
   159  	if t.nameTemplate != "" {
   160  		t.releaseName, err = generateName(t.nameTemplate)
   161  		if err != nil {
   162  			return err
   163  		}
   164  	}
   165  
   166  	// Check chart requirements to make sure all dependencies are present in /charts
   167  	c, err := chartutil.Load(t.chartPath)
   168  	if err != nil {
   169  		return prettyError(err)
   170  	}
   171  
   172  	if req, err := chartutil.LoadRequirements(c); err == nil {
   173  		if err := checkDependencies(c, req); err != nil {
   174  			return prettyError(err)
   175  		}
   176  	} else if err != chartutil.ErrRequirementsNotFound {
   177  		return fmt.Errorf("cannot load requirements: %v", err)
   178  	}
   179  	options := chartutil.ReleaseOptions{
   180  		Name:      t.releaseName,
   181  		Time:      timeconv.Now(),
   182  		Namespace: t.namespace,
   183  	}
   184  
   185  	err = chartutil.ProcessRequirementsEnabled(c, config)
   186  	if err != nil {
   187  		return err
   188  	}
   189  	err = chartutil.ProcessRequirementsImportValues(c)
   190  	if err != nil {
   191  		return err
   192  	}
   193  
   194  	// Set up engine.
   195  	renderer := engine.New()
   196  
   197  	caps := &chartutil.Capabilities{
   198  		APIVersions:   chartutil.DefaultVersionSet,
   199  		KubeVersion:   chartutil.DefaultKubeVersion,
   200  		TillerVersion: tversion.GetVersionProto(),
   201  	}
   202  
   203  	// kubernetes version
   204  	kv, err := semver.NewVersion(t.kubeVersion)
   205  	if err != nil {
   206  		return fmt.Errorf("could not parse a kubernetes version: %v", err)
   207  	}
   208  	caps.KubeVersion.Major = fmt.Sprint(kv.Major())
   209  	caps.KubeVersion.Minor = fmt.Sprint(kv.Minor())
   210  	caps.KubeVersion.GitVersion = fmt.Sprintf("v%d.%d.0", kv.Major(), kv.Minor())
   211  
   212  	vals, err := chartutil.ToRenderValuesCaps(c, config, options, caps)
   213  	if err != nil {
   214  		return err
   215  	}
   216  
   217  	out, err := renderer.Render(c, vals)
   218  	listManifests := []tiller.Manifest{}
   219  	if err != nil {
   220  		return err
   221  	}
   222  	// extract kind and name
   223  	re := regexp.MustCompile("kind:(.*)\n")
   224  	for k, v := range out {
   225  		match := re.FindStringSubmatch(v)
   226  		h := "Unknown"
   227  		if len(match) == 2 {
   228  			h = strings.TrimSpace(match[1])
   229  		}
   230  		m := tiller.Manifest{Name: k, Content: v, Head: &util.SimpleHead{Kind: h}}
   231  		listManifests = append(listManifests, m)
   232  	}
   233  	in := func(needle string, haystack []string) bool {
   234  		// make needle path absolute
   235  		d := strings.Split(needle, "/")
   236  		dd := d[1:]
   237  		an := t.chartPath + "/" + strings.Join(dd, "/")
   238  
   239  		for _, h := range haystack {
   240  			if h == an {
   241  				return true
   242  			}
   243  		}
   244  		return false
   245  	}
   246  	if settings.Debug {
   247  		rel := &release.Release{
   248  			Name:      t.releaseName,
   249  			Chart:     c,
   250  			Config:    config,
   251  			Version:   1,
   252  			Namespace: t.namespace,
   253  			Info:      &release.Info{LastDeployed: timeconv.Timestamp(time.Now())},
   254  		}
   255  		printRelease(os.Stdout, rel)
   256  	}
   257  
   258  	for _, m := range tiller.SortByKind(listManifests) {
   259  		if len(t.renderFiles) > 0 && !in(m.Name, rf) {
   260  			continue
   261  		}
   262  		data := m.Content
   263  		b := filepath.Base(m.Name)
   264  		if !t.showNotes && b == "NOTES.txt" {
   265  			continue
   266  		}
   267  		if strings.HasPrefix(b, "_") {
   268  			continue
   269  		}
   270  
   271  		if t.outputDir != "" {
   272  			// blank template after execution
   273  			if whitespaceRegex.MatchString(data) {
   274  				continue
   275  			}
   276  			err = writeToFile(t.outputDir, m.Name, data)
   277  			if err != nil {
   278  				return err
   279  			}
   280  			continue
   281  		}
   282  		fmt.Printf("---\n# Source: %s\n", m.Name)
   283  		fmt.Println(data)
   284  	}
   285  	return nil
   286  }
   287  
   288  // write the <data> to <output-dir>/<name>
   289  func writeToFile(outputDir string, name string, data string) error {
   290  	outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator))
   291  
   292  	err := ensureDirectoryForFile(outfileName)
   293  	if err != nil {
   294  		return err
   295  	}
   296  
   297  	f, err := os.Create(outfileName)
   298  	if err != nil {
   299  		return err
   300  	}
   301  
   302  	defer f.Close()
   303  
   304  	_, err = f.WriteString(fmt.Sprintf("##---\n# Source: %s\n%s", name, data))
   305  
   306  	if err != nil {
   307  		return err
   308  	}
   309  
   310  	fmt.Printf("wrote %s\n", outfileName)
   311  	return nil
   312  }
   313  
   314  // check if the directory exists to create file. creates if don't exists
   315  func ensureDirectoryForFile(file string) error {
   316  	baseDir := path.Dir(file)
   317  	_, err := os.Stat(baseDir)
   318  	if err != nil && !os.IsNotExist(err) {
   319  		return err
   320  	}
   321  
   322  	return os.MkdirAll(baseDir, defaultDirectoryPermission)
   323  }