github.com/wangchanggan/helm@v0.0.0-20211020154240-11b1b7d5406d/cmd/helm/template.go (about) 1 /* 2 Copyright The Helm Authors. 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/spf13/cobra" 31 32 "k8s.io/apimachinery/pkg/util/validation" 33 "k8s.io/helm/pkg/chartutil" 34 "k8s.io/helm/pkg/manifest" 35 "k8s.io/helm/pkg/proto/hapi/chart" 36 "k8s.io/helm/pkg/proto/hapi/release" 37 "k8s.io/helm/pkg/renderutil" 38 "k8s.io/helm/pkg/tiller" 39 "k8s.io/helm/pkg/timeconv" 40 ) 41 42 const defaultDirectoryPermission = 0755 43 44 var ( 45 whitespaceRegex = regexp.MustCompile(`^\s*$`) 46 47 // defaultKubeVersion is the default value of --kube-version flag 48 defaultKubeVersion = fmt.Sprintf("%s.%s", chartutil.DefaultKubeVersion.Major, chartutil.DefaultKubeVersion.Minor) 49 ) 50 51 const templateDesc = ` 52 Render chart templates locally and display the output. 53 54 This does not require Tiller. However, any values that would normally be 55 looked up or retrieved in-cluster will be faked locally. Additionally, none 56 of the server-side testing of chart validity (e.g. whether an API is supported) 57 is done. 58 59 To render just one template in a chart, use '-x': 60 61 $ helm template mychart -x templates/deployment.yaml 62 ` 63 64 type templateCmd struct { 65 namespace string 66 valueFiles valueFiles 67 chartPath string 68 out io.Writer 69 values []string 70 stringValues []string 71 fileValues []string 72 nameTemplate string 73 showNotes bool 74 releaseName string 75 releaseIsUpgrade bool 76 renderFiles []string 77 kubeVersion string 78 apiVersions []string 79 outputDir string 80 } 81 82 func newTemplateCmd(out io.Writer) *cobra.Command { 83 84 t := &templateCmd{ 85 out: out, 86 } 87 88 cmd := &cobra.Command{ 89 Use: "template [flags] CHART", 90 Short: "Locally render templates", 91 Long: templateDesc, 92 RunE: t.run, 93 } 94 95 cmd.SetOutput(out) 96 f := cmd.Flags() 97 f.BoolVar(&t.showNotes, "notes", false, "Show the computed NOTES.txt file as well") 98 f.StringVarP(&t.releaseName, "name", "n", "release-name", "Release name") 99 f.BoolVar(&t.releaseIsUpgrade, "is-upgrade", false, "Set .Release.IsUpgrade instead of .Release.IsInstall") 100 f.StringArrayVarP(&t.renderFiles, "execute", "x", []string{}, "Only execute the given templates") 101 f.VarP(&t.valueFiles, "values", "f", "Specify values in a YAML file (can specify multiple)") 102 f.StringVar(&t.namespace, "namespace", "", "Namespace to install the release into") 103 f.StringArrayVar(&t.values, "set", []string{}, "Set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") 104 f.StringArrayVar(&t.stringValues, "set-string", []string{}, "Set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") 105 f.StringArrayVar(&t.fileValues, "set-file", []string{}, "Set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)") 106 f.StringVar(&t.nameTemplate, "name-template", "", "Specify template used to name the release") 107 f.StringVar(&t.kubeVersion, "kube-version", defaultKubeVersion, "Kubernetes version used as Capabilities.KubeVersion.Major/Minor") 108 f.StringArrayVarP(&t.apiVersions, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions") 109 f.StringVar(&t.outputDir, "output-dir", "", "Writes the executed templates to files in output-dir instead of stdout") 110 111 return cmd 112 } 113 114 func (t *templateCmd) run(cmd *cobra.Command, args []string) error { 115 if len(args) < 1 { 116 return errors.New("chart is required") 117 } 118 // verify chart path exists 119 if _, err := os.Stat(args[0]); err == nil { 120 if t.chartPath, err = filepath.Abs(args[0]); err != nil { 121 return err 122 } 123 } else { 124 return err 125 } 126 127 // verify that output-dir exists if provided 128 if t.outputDir != "" { 129 _, err := os.Stat(t.outputDir) 130 if os.IsNotExist(err) { 131 return fmt.Errorf("output-dir '%s' does not exist", t.outputDir) 132 } 133 } 134 135 if t.namespace == "" { 136 t.namespace = defaultNamespace() 137 } 138 // get combined values and create config 139 rawVals, err := vals(t.valueFiles, t.values, t.stringValues, t.fileValues, "", "", "") 140 if err != nil { 141 return err 142 } 143 config := &chart.Config{Raw: string(rawVals), Values: map[string]*chart.Value{}} 144 145 // If template is specified, try to run the template. 146 if t.nameTemplate != "" { 147 t.releaseName, err = generateName(t.nameTemplate) 148 if err != nil { 149 return err 150 } 151 } 152 153 if msgs := validation.IsDNS1123Subdomain(t.releaseName); t.releaseName != "" && len(msgs) > 0 { 154 return fmt.Errorf("release name %s is invalid: %s", t.releaseName, strings.Join(msgs, ";")) 155 } 156 157 // Check chart requirements to make sure all dependencies are present in /charts 158 c, err := chartutil.Load(t.chartPath) 159 if err != nil { 160 return prettyError(err) 161 } 162 163 renderOpts := renderutil.Options{ 164 ReleaseOptions: chartutil.ReleaseOptions{ 165 Name: t.releaseName, 166 IsInstall: !t.releaseIsUpgrade, 167 IsUpgrade: t.releaseIsUpgrade, 168 Time: timeconv.Now(), 169 Namespace: t.namespace, 170 }, 171 KubeVersion: t.kubeVersion, 172 APIVersions: t.apiVersions, 173 } 174 175 renderedTemplates, err := renderutil.Render(c, config, renderOpts) 176 if err != nil { 177 return err 178 } 179 180 if settings.Debug { 181 rel := &release.Release{ 182 Name: t.releaseName, 183 Chart: c, 184 Config: config, 185 Version: 1, 186 Namespace: t.namespace, 187 Info: &release.Info{LastDeployed: timeconv.Timestamp(time.Now())}, 188 } 189 printRelease(os.Stdout, rel) 190 } 191 192 listManifests := manifest.SplitManifests(renderedTemplates) 193 var manifestsToRender []manifest.Manifest 194 195 // if we have a list of files to render, then check that each of the 196 // provided files exists in the chart. 197 if len(t.renderFiles) > 0 { 198 for _, f := range t.renderFiles { 199 missing := true 200 if !filepath.IsAbs(f) { 201 newF, err := filepath.Abs(filepath.Join(t.chartPath, f)) 202 if err != nil { 203 return fmt.Errorf("could not turn template path %s into absolute path: %s", f, err) 204 } 205 f = newF 206 } 207 208 for _, manifest := range listManifests { 209 // manifest.Name is rendered using linux-style filepath separators on Windows as 210 // well as macOS/linux. 211 manifestPathSplit := strings.Split(manifest.Name, "/") 212 // remove the chart name from the path 213 manifestPathSplit = manifestPathSplit[1:] 214 toJoin := append([]string{t.chartPath}, manifestPathSplit...) 215 manifestPath := filepath.Join(toJoin...) 216 217 // if the filepath provided matches a manifest path in the 218 // chart, render that manifest 219 if f == manifestPath { 220 manifestsToRender = append(manifestsToRender, manifest) 221 missing = false 222 } 223 } 224 if missing { 225 return fmt.Errorf("could not find template %s in chart", f) 226 } 227 } 228 } else { 229 // no renderFiles provided, render all manifests in the chart 230 manifestsToRender = listManifests 231 } 232 233 for _, m := range tiller.SortByKind(manifestsToRender) { 234 data := m.Content 235 b := filepath.Base(m.Name) 236 if !t.showNotes && b == "NOTES.txt" { 237 continue 238 } 239 if strings.HasPrefix(b, "_") { 240 continue 241 } 242 243 if t.outputDir != "" { 244 // blank template after execution 245 if whitespaceRegex.MatchString(data) { 246 continue 247 } 248 err = writeToFile(t.outputDir, m.Name, data, t.out) 249 if err != nil { 250 return err 251 } 252 continue 253 } 254 fmt.Fprintf(t.out, "---\n# Source: %s\n", m.Name) 255 fmt.Fprintln(t.out, data) 256 } 257 return nil 258 } 259 260 // write the <data> to <output-dir>/<name> 261 func writeToFile(outputDir string, name string, data string, out io.Writer) error { 262 outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator)) 263 264 err := ensureDirectoryForFile(outfileName) 265 if err != nil { 266 return err 267 } 268 269 f, err := os.Create(outfileName) 270 if err != nil { 271 return err 272 } 273 274 defer f.Close() 275 276 _, err = f.WriteString(fmt.Sprintf("---\n# Source: %s\n%s", name, data)) 277 278 if err != nil { 279 return err 280 } 281 282 fmt.Fprintf(out, "wrote %s\n", outfileName) 283 return nil 284 } 285 286 // check if the directory exists to create file. creates if don't exists 287 func ensureDirectoryForFile(file string) error { 288 baseDir := path.Dir(file) 289 _, err := os.Stat(baseDir) 290 if err != nil && !os.IsNotExist(err) { 291 return err 292 } 293 294 return os.MkdirAll(baseDir, defaultDirectoryPermission) 295 }