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