github.com/defensepoint-snyk-test/helm-new@v0.0.0-20211130153739-c57ea64d6603/cmd/helm/install.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 "bytes" 21 "errors" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "net/url" 26 "os" 27 "path/filepath" 28 "strings" 29 "text/template" 30 31 "github.com/Masterminds/sprig" 32 "github.com/ghodss/yaml" 33 "github.com/spf13/cobra" 34 35 "k8s.io/apimachinery/pkg/util/validation" 36 "k8s.io/helm/pkg/chartutil" 37 "k8s.io/helm/pkg/downloader" 38 "k8s.io/helm/pkg/getter" 39 "k8s.io/helm/pkg/helm" 40 "k8s.io/helm/pkg/kube" 41 "k8s.io/helm/pkg/proto/hapi/release" 42 "k8s.io/helm/pkg/renderutil" 43 "k8s.io/helm/pkg/repo" 44 "k8s.io/helm/pkg/strvals" 45 ) 46 47 const installDesc = ` 48 This command installs a chart archive. 49 50 The install argument must be a chart reference, a path to a packaged chart, 51 a path to an unpacked chart directory or a URL. 52 53 To override values in a chart, use either the '--values' flag and pass in a file 54 or use the '--set' flag and pass configuration from the command line. To force string 55 values in '--set', use '--set-string' instead. In case a value is large and therefore 56 you want not to use neither '--values' nor '--set', use '--set-file' to read the 57 single large value from file. 58 59 $ helm install -f myvalues.yaml ./redis 60 61 or 62 63 $ helm install --set name=prod ./redis 64 65 or 66 67 $ helm install --set-string long_int=1234567890 ./redis 68 69 or 70 $ helm install --set-file multiline_text=path/to/textfile 71 72 You can specify the '--values'/'-f' flag multiple times. The priority will be given to the 73 last (right-most) file specified. For example, if both myvalues.yaml and override.yaml 74 contained a key called 'Test', the value set in override.yaml would take precedence: 75 76 $ helm install -f myvalues.yaml -f override.yaml ./redis 77 78 You can specify the '--set' flag multiple times. The priority will be given to the 79 last (right-most) set specified. For example, if both 'bar' and 'newbar' values are 80 set for a key called 'foo', the 'newbar' value would take precedence: 81 82 $ helm install --set foo=bar --set foo=newbar ./redis 83 84 85 To check the generated manifests of a release without installing the chart, 86 the '--debug' and '--dry-run' flags can be combined. This will still require a 87 round-trip to the Tiller server. 88 89 If --verify is set, the chart MUST have a provenance file, and the provenance 90 file MUST pass all verification steps. 91 92 There are five different ways you can express the chart you want to install: 93 94 1. By chart reference: helm install stable/mariadb 95 2. By path to a packaged chart: helm install ./nginx-1.2.3.tgz 96 3. By path to an unpacked chart directory: helm install ./nginx 97 4. By absolute URL: helm install https://example.com/charts/nginx-1.2.3.tgz 98 5. By chart reference and repo url: helm install --repo https://example.com/charts/ nginx 99 100 CHART REFERENCES 101 102 A chart reference is a convenient way of reference a chart in a chart repository. 103 104 When you use a chart reference with a repo prefix ('stable/mariadb'), Helm will look in the local 105 configuration for a chart repository named 'stable', and will then look for a 106 chart in that repository whose name is 'mariadb'. It will install the latest 107 version of that chart unless you also supply a version number with the 108 '--version' flag. 109 110 To see the list of chart repositories, use 'helm repo list'. To search for 111 charts in a repository, use 'helm search'. 112 ` 113 114 type installCmd struct { 115 name string 116 namespace string 117 valueFiles valueFiles 118 chartPath string 119 dryRun bool 120 disableHooks bool 121 disableCRDHook bool 122 replace bool 123 verify bool 124 keyring string 125 out io.Writer 126 client helm.Interface 127 values []string 128 stringValues []string 129 fileValues []string 130 nameTemplate string 131 version string 132 timeout int64 133 wait bool 134 repoURL string 135 username string 136 password string 137 devel bool 138 depUp bool 139 description string 140 141 certFile string 142 keyFile string 143 caFile string 144 } 145 146 type valueFiles []string 147 148 func (v *valueFiles) String() string { 149 return fmt.Sprint(*v) 150 } 151 152 func (v *valueFiles) Type() string { 153 return "valueFiles" 154 } 155 156 func (v *valueFiles) Set(value string) error { 157 for _, filePath := range strings.Split(value, ",") { 158 *v = append(*v, filePath) 159 } 160 return nil 161 } 162 163 func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { 164 inst := &installCmd{ 165 out: out, 166 client: c, 167 } 168 169 cmd := &cobra.Command{ 170 Use: "install [CHART]", 171 Short: "install a chart archive", 172 Long: installDesc, 173 PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() }, 174 RunE: func(cmd *cobra.Command, args []string) error { 175 if err := checkArgsLength(len(args), "chart name"); err != nil { 176 return err 177 } 178 179 debug("Original chart version: %q", inst.version) 180 if inst.version == "" && inst.devel { 181 debug("setting version to >0.0.0-0") 182 inst.version = ">0.0.0-0" 183 } 184 185 cp, err := locateChartPath(inst.repoURL, inst.username, inst.password, args[0], inst.version, inst.verify, inst.keyring, 186 inst.certFile, inst.keyFile, inst.caFile) 187 if err != nil { 188 return err 189 } 190 inst.chartPath = cp 191 inst.client = ensureHelmClient(inst.client) 192 return inst.run() 193 }, 194 } 195 196 f := cmd.Flags() 197 settings.AddFlagsTLS(f) 198 f.VarP(&inst.valueFiles, "values", "f", "specify values in a YAML file or a URL(can specify multiple)") 199 f.StringVarP(&inst.name, "name", "n", "", "release name. If unspecified, it will autogenerate one for you") 200 f.StringVar(&inst.namespace, "namespace", "", "namespace to install the release into. Defaults to the current kube config namespace.") 201 f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install") 202 f.BoolVar(&inst.disableHooks, "no-hooks", false, "prevent hooks from running during install") 203 f.BoolVar(&inst.disableCRDHook, "no-crd-hook", false, "prevent CRD hooks from running, but run other hooks") 204 f.BoolVar(&inst.replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production") 205 f.StringArrayVar(&inst.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") 206 f.StringArrayVar(&inst.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") 207 f.StringArrayVar(&inst.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)") 208 f.StringVar(&inst.nameTemplate, "name-template", "", "specify template used to name the release") 209 f.BoolVar(&inst.verify, "verify", false, "verify the package before installing it") 210 f.StringVar(&inst.keyring, "keyring", defaultKeyring(), "location of public keys used for verification") 211 f.StringVar(&inst.version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed") 212 f.Int64Var(&inst.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") 213 f.BoolVar(&inst.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout") 214 f.StringVar(&inst.repoURL, "repo", "", "chart repository url where to locate the requested chart") 215 f.StringVar(&inst.username, "username", "", "chart repository username where to locate the requested chart") 216 f.StringVar(&inst.password, "password", "", "chart repository password where to locate the requested chart") 217 f.StringVar(&inst.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") 218 f.StringVar(&inst.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") 219 f.StringVar(&inst.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") 220 f.BoolVar(&inst.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") 221 f.BoolVar(&inst.depUp, "dep-up", false, "run helm dependency update before installing the chart") 222 f.StringVar(&inst.description, "description", "", "specify a description for the release") 223 224 // set defaults from environment 225 settings.InitTLS(f) 226 227 return cmd 228 } 229 230 func (i *installCmd) run() error { 231 debug("CHART PATH: %s\n", i.chartPath) 232 233 if i.namespace == "" { 234 i.namespace = defaultNamespace() 235 } 236 237 rawVals, err := vals(i.valueFiles, i.values, i.stringValues, i.fileValues, i.certFile, i.keyFile, i.caFile) 238 if err != nil { 239 return err 240 } 241 242 // If template is specified, try to run the template. 243 if i.nameTemplate != "" { 244 i.name, err = generateName(i.nameTemplate) 245 if err != nil { 246 return err 247 } 248 // Print the final name so the user knows what the final name of the release is. 249 fmt.Printf("FINAL NAME: %s\n", i.name) 250 } 251 252 if msgs := validation.IsDNS1123Label(i.name); i.name != "" && len(msgs) > 0 { 253 return fmt.Errorf("release name %s is not a valid DNS label: %s", i.name, strings.Join(msgs, ";")) 254 } 255 256 // Check chart requirements to make sure all dependencies are present in /charts 257 chartRequested, err := chartutil.Load(i.chartPath) 258 if err != nil { 259 return prettyError(err) 260 } 261 262 if req, err := chartutil.LoadRequirements(chartRequested); err == nil { 263 // If checkDependencies returns an error, we have unfulfilled dependencies. 264 // As of Helm 2.4.0, this is treated as a stopping condition: 265 // https://github.com/kubernetes/helm/issues/2209 266 if err := renderutil.CheckDependencies(chartRequested, req); err != nil { 267 if i.depUp { 268 man := &downloader.Manager{ 269 Out: i.out, 270 ChartPath: i.chartPath, 271 HelmHome: settings.Home, 272 Keyring: defaultKeyring(), 273 SkipUpdate: false, 274 Getters: getter.All(settings), 275 } 276 if err := man.Update(); err != nil { 277 return prettyError(err) 278 } 279 280 // Update all dependencies which are present in /charts. 281 chartRequested, err = chartutil.Load(i.chartPath) 282 if err != nil { 283 return prettyError(err) 284 } 285 } else { 286 return prettyError(err) 287 } 288 289 } 290 } else if err != chartutil.ErrRequirementsNotFound { 291 return fmt.Errorf("cannot load requirements: %v", err) 292 } 293 294 res, err := i.client.InstallReleaseFromChart( 295 chartRequested, 296 i.namespace, 297 helm.ValueOverrides(rawVals), 298 helm.ReleaseName(i.name), 299 helm.InstallDryRun(i.dryRun), 300 helm.InstallReuseName(i.replace), 301 helm.InstallDisableHooks(i.disableHooks), 302 helm.InstallDisableCRDHook(i.disableCRDHook), 303 helm.InstallTimeout(i.timeout), 304 helm.InstallWait(i.wait), 305 helm.InstallDescription(i.description)) 306 if err != nil { 307 return prettyError(err) 308 } 309 310 rel := res.GetRelease() 311 if rel == nil { 312 return nil 313 } 314 i.printRelease(rel) 315 316 // If this is a dry run, we can't display status. 317 if i.dryRun { 318 // This is special casing to avoid breaking backward compatibility: 319 if res.Release.Info.Description != "Dry run complete" { 320 fmt.Fprintf(os.Stdout, "WARNING: %s\n", res.Release.Info.Description) 321 } 322 return nil 323 } 324 325 // Print the status like status command does 326 status, err := i.client.ReleaseStatus(rel.Name) 327 if err != nil { 328 return prettyError(err) 329 } 330 PrintStatus(i.out, status) 331 return nil 332 } 333 334 // Merges source and destination map, preferring values from the source map 335 func mergeValues(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} { 336 for k, v := range src { 337 // If the key doesn't exist already, then just set the key to that value 338 if _, exists := dest[k]; !exists { 339 dest[k] = v 340 continue 341 } 342 nextMap, ok := v.(map[string]interface{}) 343 // If it isn't another map, overwrite the value 344 if !ok { 345 dest[k] = v 346 continue 347 } 348 // Edge case: If the key exists in the destination, but isn't a map 349 destMap, isMap := dest[k].(map[string]interface{}) 350 // If the source map has a map for this key, prefer it 351 if !isMap { 352 dest[k] = v 353 continue 354 } 355 // If we got to this point, it is a map in both, so merge them 356 dest[k] = mergeValues(destMap, nextMap) 357 } 358 return dest 359 } 360 361 // vals merges values from files specified via -f/--values and 362 // directly via --set or --set-string or --set-file, marshaling them to YAML 363 func vals(valueFiles valueFiles, values []string, stringValues []string, fileValues []string, CertFile, KeyFile, CAFile string) ([]byte, error) { 364 base := map[string]interface{}{} 365 366 // User specified a values files via -f/--values 367 for _, filePath := range valueFiles { 368 currentMap := map[string]interface{}{} 369 370 var bytes []byte 371 var err error 372 if strings.TrimSpace(filePath) == "-" { 373 bytes, err = ioutil.ReadAll(os.Stdin) 374 } else { 375 bytes, err = readFile(filePath, CertFile, KeyFile, CAFile) 376 } 377 378 if err != nil { 379 return []byte{}, err 380 } 381 382 if err := yaml.Unmarshal(bytes, ¤tMap); err != nil { 383 return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err) 384 } 385 // Merge with the previous map 386 base = mergeValues(base, currentMap) 387 } 388 389 // User specified a value via --set 390 for _, value := range values { 391 if err := strvals.ParseInto(value, base); err != nil { 392 return []byte{}, fmt.Errorf("failed parsing --set data: %s", err) 393 } 394 } 395 396 // User specified a value via --set-string 397 for _, value := range stringValues { 398 if err := strvals.ParseIntoString(value, base); err != nil { 399 return []byte{}, fmt.Errorf("failed parsing --set-string data: %s", err) 400 } 401 } 402 403 // User specified a value via --set-file 404 for _, value := range fileValues { 405 reader := func(rs []rune) (interface{}, error) { 406 bytes, err := readFile(string(rs), CertFile, KeyFile, CAFile) 407 return string(bytes), err 408 } 409 if err := strvals.ParseIntoFile(value, base, reader); err != nil { 410 return []byte{}, fmt.Errorf("failed parsing --set-file data: %s", err) 411 } 412 } 413 414 return yaml.Marshal(base) 415 } 416 417 // printRelease prints info about a release if the Debug is true. 418 func (i *installCmd) printRelease(rel *release.Release) { 419 if rel == nil { 420 return 421 } 422 // TODO: Switch to text/template like everything else. 423 fmt.Fprintf(i.out, "NAME: %s\n", rel.Name) 424 if settings.Debug { 425 printRelease(i.out, rel) 426 } 427 } 428 429 // locateChartPath looks for a chart directory in known places, and returns either the full path or an error. 430 // 431 // This does not ensure that the chart is well-formed; only that the requested filename exists. 432 // 433 // Order of resolution: 434 // - current working directory 435 // - if path is absolute or begins with '.', error out here 436 // - chart repos in $HELM_HOME 437 // - URL 438 // 439 // If 'verify' is true, this will attempt to also verify the chart. 440 func locateChartPath(repoURL, username, password, name, version string, verify bool, keyring, 441 certFile, keyFile, caFile string) (string, error) { 442 name = strings.TrimSpace(name) 443 version = strings.TrimSpace(version) 444 if fi, err := os.Stat(name); err == nil { 445 abs, err := filepath.Abs(name) 446 if err != nil { 447 return abs, err 448 } 449 if verify { 450 if fi.IsDir() { 451 return "", errors.New("cannot verify a directory") 452 } 453 if _, err := downloader.VerifyChart(abs, keyring); err != nil { 454 return "", err 455 } 456 } 457 return abs, nil 458 } 459 if filepath.IsAbs(name) || strings.HasPrefix(name, ".") { 460 return name, fmt.Errorf("path %q not found", name) 461 } 462 463 crepo := filepath.Join(settings.Home.Repository(), name) 464 if _, err := os.Stat(crepo); err == nil { 465 return filepath.Abs(crepo) 466 } 467 468 dl := downloader.ChartDownloader{ 469 HelmHome: settings.Home, 470 Out: os.Stdout, 471 Keyring: keyring, 472 Getters: getter.All(settings), 473 Username: username, 474 Password: password, 475 } 476 if verify { 477 dl.Verify = downloader.VerifyAlways 478 } 479 if repoURL != "" { 480 chartURL, err := repo.FindChartInAuthRepoURL(repoURL, username, password, name, version, 481 certFile, keyFile, caFile, getter.All(settings)) 482 if err != nil { 483 return "", err 484 } 485 name = chartURL 486 } 487 488 if _, err := os.Stat(settings.Home.Archive()); os.IsNotExist(err) { 489 os.MkdirAll(settings.Home.Archive(), 0744) 490 } 491 492 filename, _, err := dl.DownloadTo(name, version, settings.Home.Archive()) 493 if err == nil { 494 lname, err := filepath.Abs(filename) 495 if err != nil { 496 return filename, err 497 } 498 debug("Fetched %s to %s\n", name, filename) 499 return lname, nil 500 } else if settings.Debug { 501 return filename, err 502 } 503 504 return filename, fmt.Errorf("failed to download %q (hint: running `helm repo update` may help)", name) 505 } 506 507 func generateName(nameTemplate string) (string, error) { 508 t, err := template.New("name-template").Funcs(sprig.TxtFuncMap()).Parse(nameTemplate) 509 if err != nil { 510 return "", err 511 } 512 var b bytes.Buffer 513 err = t.Execute(&b, nil) 514 if err != nil { 515 return "", err 516 } 517 return b.String(), nil 518 } 519 520 func defaultNamespace() string { 521 if ns, _, err := kube.GetConfig(settings.KubeContext, settings.KubeConfig).Namespace(); err == nil { 522 return ns 523 } 524 return "default" 525 } 526 527 //readFile load a file from the local directory or a remote file with a url. 528 func readFile(filePath, CertFile, KeyFile, CAFile string) ([]byte, error) { 529 u, _ := url.Parse(filePath) 530 p := getter.All(settings) 531 532 // FIXME: maybe someone handle other protocols like ftp. 533 getterConstructor, err := p.ByScheme(u.Scheme) 534 535 if err != nil { 536 return ioutil.ReadFile(filePath) 537 } 538 539 getter, err := getterConstructor(filePath, CertFile, KeyFile, CAFile) 540 if err != nil { 541 return []byte{}, err 542 } 543 data, err := getter.Get(filePath) 544 return data.Bytes(), err 545 }