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 }