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 }