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