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