github.com/strongmonkey/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 }