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