github.com/defensepoint-snyk-test/helm-new@v0.0.0-20211130153739-c57ea64d6603/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 outputDir string 79 } 80 81 func newTemplateCmd(out io.Writer) *cobra.Command { 82 83 t := &templateCmd{ 84 out: out, 85 } 86 87 cmd := &cobra.Command{ 88 Use: "template [flags] CHART", 89 Short: fmt.Sprintf("locally render templates"), 90 Long: templateDesc, 91 RunE: t.run, 92 } 93 94 f := cmd.Flags() 95 f.BoolVar(&t.showNotes, "notes", false, "show the computed NOTES.txt file as well") 96 f.StringVarP(&t.releaseName, "name", "n", "release-name", "release name") 97 f.BoolVar(&t.releaseIsUpgrade, "is-upgrade", false, "set .Release.IsUpgrade instead of .Release.IsInstall") 98 f.StringArrayVarP(&t.renderFiles, "execute", "x", []string{}, "only execute the given templates") 99 f.VarP(&t.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)") 100 f.StringVar(&t.namespace, "namespace", "", "namespace to install the release into") 101 f.StringArrayVar(&t.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") 102 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)") 103 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)") 104 f.StringVar(&t.nameTemplate, "name-template", "", "specify template used to name the release") 105 f.StringVar(&t.kubeVersion, "kube-version", defaultKubeVersion, "kubernetes version used as Capabilities.KubeVersion.Major/Minor") 106 f.StringVar(&t.outputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout") 107 108 return cmd 109 } 110 111 func (t *templateCmd) run(cmd *cobra.Command, args []string) error { 112 if len(args) < 1 { 113 return errors.New("chart is required") 114 } 115 // verify chart path exists 116 if _, err := os.Stat(args[0]); err == nil { 117 if t.chartPath, err = filepath.Abs(args[0]); err != nil { 118 return err 119 } 120 } else { 121 return err 122 } 123 124 // verify that output-dir exists if provided 125 if t.outputDir != "" { 126 _, err := os.Stat(t.outputDir) 127 if os.IsNotExist(err) { 128 return fmt.Errorf("output-dir '%s' does not exist", t.outputDir) 129 } 130 } 131 132 if t.namespace == "" { 133 t.namespace = defaultNamespace() 134 } 135 // get combined values and create config 136 rawVals, err := vals(t.valueFiles, t.values, t.stringValues, t.fileValues, "", "", "") 137 if err != nil { 138 return err 139 } 140 config := &chart.Config{Raw: string(rawVals), Values: map[string]*chart.Value{}} 141 142 // If template is specified, try to run the template. 143 if t.nameTemplate != "" { 144 t.releaseName, err = generateName(t.nameTemplate) 145 if err != nil { 146 return err 147 } 148 } 149 150 if msgs := validation.IsDNS1123Label(t.releaseName); t.releaseName != "" && len(msgs) > 0 { 151 return fmt.Errorf("release name %s is not a valid DNS label: %s", t.releaseName, strings.Join(msgs, ";")) 152 } 153 154 // Check chart requirements to make sure all dependencies are present in /charts 155 c, err := chartutil.Load(t.chartPath) 156 if err != nil { 157 return prettyError(err) 158 } 159 160 renderOpts := renderutil.Options{ 161 ReleaseOptions: chartutil.ReleaseOptions{ 162 Name: t.releaseName, 163 IsInstall: !t.releaseIsUpgrade, 164 IsUpgrade: t.releaseIsUpgrade, 165 Time: timeconv.Now(), 166 Namespace: t.namespace, 167 }, 168 KubeVersion: t.kubeVersion, 169 } 170 171 renderedTemplates, err := renderutil.Render(c, config, renderOpts) 172 if err != nil { 173 return err 174 } 175 176 if settings.Debug { 177 rel := &release.Release{ 178 Name: t.releaseName, 179 Chart: c, 180 Config: config, 181 Version: 1, 182 Namespace: t.namespace, 183 Info: &release.Info{LastDeployed: timeconv.Timestamp(time.Now())}, 184 } 185 printRelease(os.Stdout, rel) 186 } 187 188 listManifests := manifest.SplitManifests(renderedTemplates) 189 var manifestsToRender []manifest.Manifest 190 191 // if we have a list of files to render, then check that each of the 192 // provided files exists in the chart. 193 if len(t.renderFiles) > 0 { 194 for _, f := range t.renderFiles { 195 missing := true 196 if !filepath.IsAbs(f) { 197 newF, err := filepath.Abs(filepath.Join(t.chartPath, f)) 198 if err != nil { 199 return fmt.Errorf("could not turn template path %s into absolute path: %s", f, err) 200 } 201 f = newF 202 } 203 204 for _, manifest := range listManifests { 205 // manifest.Name is rendered using linux-style filepath separators on Windows as 206 // well as macOS/linux. 207 manifestPathSplit := strings.Split(manifest.Name, "/") 208 // remove the chart name from the path 209 manifestPathSplit = manifestPathSplit[1:] 210 toJoin := append([]string{t.chartPath}, manifestPathSplit...) 211 manifestPath := filepath.Join(toJoin...) 212 213 // if the filepath provided matches a manifest path in the 214 // chart, render that manifest 215 if f == manifestPath { 216 manifestsToRender = append(manifestsToRender, manifest) 217 missing = false 218 } 219 } 220 if missing { 221 return fmt.Errorf("could not find template %s in chart", f) 222 } 223 } 224 } else { 225 // no renderFiles provided, render all manifests in the chart 226 manifestsToRender = listManifests 227 } 228 229 for _, m := range tiller.SortByKind(manifestsToRender) { 230 data := m.Content 231 b := filepath.Base(m.Name) 232 if !t.showNotes && b == "NOTES.txt" { 233 continue 234 } 235 if strings.HasPrefix(b, "_") { 236 continue 237 } 238 239 if t.outputDir != "" { 240 // blank template after execution 241 if whitespaceRegex.MatchString(data) { 242 continue 243 } 244 err = writeToFile(t.outputDir, m.Name, data) 245 if err != nil { 246 return err 247 } 248 continue 249 } 250 fmt.Printf("---\n# Source: %s\n", m.Name) 251 fmt.Println(data) 252 } 253 return nil 254 } 255 256 // write the <data> to <output-dir>/<name> 257 func writeToFile(outputDir string, name string, data string) error { 258 outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator)) 259 260 err := ensureDirectoryForFile(outfileName) 261 if err != nil { 262 return err 263 } 264 265 f, err := os.Create(outfileName) 266 if err != nil { 267 return err 268 } 269 270 defer f.Close() 271 272 _, err = f.WriteString(fmt.Sprintf("---\n# Source: %s\n%s", name, data)) 273 274 if err != nil { 275 return err 276 } 277 278 fmt.Printf("wrote %s\n", outfileName) 279 return nil 280 } 281 282 // check if the directory exists to create file. creates if don't exists 283 func ensureDirectoryForFile(file string) error { 284 baseDir := path.Dir(file) 285 _, err := os.Stat(baseDir) 286 if err != nil && !os.IsNotExist(err) { 287 return err 288 } 289 290 return os.MkdirAll(baseDir, defaultDirectoryPermission) 291 }