github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/action/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 action 18 19 import ( 20 "bytes" 21 "context" 22 "fmt" 23 "io/ioutil" 24 "net/url" 25 "os" 26 "path" 27 "path/filepath" 28 "strings" 29 "sync" 30 "text/template" 31 "time" 32 33 "github.com/Masterminds/sprig/v3" 34 "github.com/pkg/errors" 35 v1 "k8s.io/api/core/v1" 36 apierrors "k8s.io/apimachinery/pkg/api/errors" 37 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 38 "k8s.io/cli-runtime/pkg/resource" 39 "sigs.k8s.io/yaml" 40 41 "github.com/stefanmcshane/helm/pkg/chart" 42 "github.com/stefanmcshane/helm/pkg/chartutil" 43 "github.com/stefanmcshane/helm/pkg/cli" 44 "github.com/stefanmcshane/helm/pkg/downloader" 45 "github.com/stefanmcshane/helm/pkg/getter" 46 "github.com/stefanmcshane/helm/pkg/kube" 47 kubefake "github.com/stefanmcshane/helm/pkg/kube/fake" 48 "github.com/stefanmcshane/helm/pkg/postrender" 49 "github.com/stefanmcshane/helm/pkg/registry" 50 "github.com/stefanmcshane/helm/pkg/release" 51 "github.com/stefanmcshane/helm/pkg/releaseutil" 52 "github.com/stefanmcshane/helm/pkg/repo" 53 "github.com/stefanmcshane/helm/pkg/storage" 54 "github.com/stefanmcshane/helm/pkg/storage/driver" 55 ) 56 57 // NOTESFILE_SUFFIX that we want to treat special. It goes through the templating engine 58 // but it's not a yaml file (resource) hence can't have hooks, etc. And the user actually 59 // wants to see this file after rendering in the status command. However, it must be a suffix 60 // since there can be filepath in front of it. 61 const notesFileSuffix = "NOTES.txt" 62 63 const defaultDirectoryPermission = 0755 64 65 // Install performs an installation operation. 66 type Install struct { 67 cfg *Configuration 68 69 ChartPathOptions 70 71 ClientOnly bool 72 CreateNamespace bool 73 DryRun bool 74 DisableHooks bool 75 Replace bool 76 Wait bool 77 WaitForJobs bool 78 Devel bool 79 DependencyUpdate bool 80 Timeout time.Duration 81 Namespace string 82 ReleaseName string 83 GenerateName bool 84 NameTemplate string 85 Description string 86 OutputDir string 87 Atomic bool 88 SkipCRDs bool 89 SubNotes bool 90 DisableOpenAPIValidation bool 91 IncludeCRDs bool 92 // KubeVersion allows specifying a custom kubernetes version to use and 93 // APIVersions allows a manual set of supported API Versions to be passed 94 // (for things like templating). These are ignored if ClientOnly is false 95 KubeVersion *chartutil.KubeVersion 96 APIVersions chartutil.VersionSet 97 // Used by helm template to render charts with .Release.IsUpgrade. Ignored if Dry-Run is false 98 IsUpgrade bool 99 // Used by helm template to add the release as part of OutputDir path 100 // OutputDir/<ReleaseName> 101 UseReleaseName bool 102 PostRenderer postrender.PostRenderer 103 // Lock to control raceconditions when the process receives a SIGTERM 104 Lock sync.Mutex 105 } 106 107 // ChartPathOptions captures common options used for controlling chart paths 108 type ChartPathOptions struct { 109 CaFile string // --ca-file 110 CertFile string // --cert-file 111 KeyFile string // --key-file 112 InsecureSkipTLSverify bool // --insecure-skip-verify 113 Keyring string // --keyring 114 Password string // --password 115 PassCredentialsAll bool // --pass-credentials 116 RepoURL string // --repo 117 Username string // --username 118 Verify bool // --verify 119 Version string // --version 120 121 // registryClient provides a registry client but is not added with 122 // options from a flag 123 registryClient *registry.Client 124 } 125 126 // NewInstall creates a new Install object with the given configuration. 127 func NewInstall(cfg *Configuration) *Install { 128 in := &Install{ 129 cfg: cfg, 130 } 131 in.ChartPathOptions.registryClient = cfg.RegistryClient 132 133 return in 134 } 135 136 func (i *Install) installCRDs(crds []chart.CRD) error { 137 // We do these one file at a time in the order they were read. 138 totalItems := []*resource.Info{} 139 for _, obj := range crds { 140 // Read in the resources 141 res, err := i.cfg.KubeClient.Build(bytes.NewBuffer(obj.File.Data), false) 142 if err != nil { 143 return errors.Wrapf(err, "failed to install CRD %s", obj.Name) 144 } 145 146 // Send them to Kube 147 if _, err := i.cfg.KubeClient.Create(res); err != nil { 148 // If the error is CRD already exists, continue. 149 if apierrors.IsAlreadyExists(err) { 150 crdName := res[0].Name 151 i.cfg.Log("CRD %s is already present. Skipping.", crdName) 152 continue 153 } 154 return errors.Wrapf(err, "failed to install CRD %s", obj.Name) 155 } 156 totalItems = append(totalItems, res...) 157 } 158 if len(totalItems) > 0 { 159 // Invalidate the local cache, since it will not have the new CRDs 160 // present. 161 discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient() 162 if err != nil { 163 return err 164 } 165 i.cfg.Log("Clearing discovery cache") 166 discoveryClient.Invalidate() 167 // Give time for the CRD to be recognized. 168 169 if err := i.cfg.KubeClient.Wait(totalItems, 60*time.Second); err != nil { 170 return err 171 } 172 173 // Make sure to force a rebuild of the cache. 174 discoveryClient.ServerGroups() 175 } 176 return nil 177 } 178 179 // Run executes the installation 180 // 181 // If DryRun is set to true, this will prepare the release, but not install it 182 183 func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.Release, error) { 184 ctx := context.Background() 185 return i.RunWithContext(ctx, chrt, vals) 186 } 187 188 // Run executes the installation with Context 189 func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals map[string]interface{}) (*release.Release, error) { 190 // Check reachability of cluster unless in client-only mode (e.g. `helm template` without `--validate`) 191 if !i.ClientOnly { 192 if err := i.cfg.KubeClient.IsReachable(); err != nil { 193 return nil, err 194 } 195 } 196 197 if err := i.availableName(); err != nil { 198 return nil, err 199 } 200 201 if err := chartutil.ProcessDependencies(chrt, vals); err != nil { 202 return nil, err 203 } 204 205 // Pre-install anything in the crd/ directory. We do this before Helm 206 // contacts the upstream server and builds the capabilities object. 207 if crds := chrt.CRDObjects(); !i.ClientOnly && !i.SkipCRDs && len(crds) > 0 { 208 // On dry run, bail here 209 if i.DryRun { 210 i.cfg.Log("WARNING: This chart or one of its subcharts contains CRDs. Rendering may fail or contain inaccuracies.") 211 } else if err := i.installCRDs(crds); err != nil { 212 return nil, err 213 } 214 } 215 216 if i.ClientOnly { 217 // Add mock objects in here so it doesn't use Kube API server 218 // NOTE(bacongobbler): used for `helm template` 219 i.cfg.Capabilities = chartutil.DefaultCapabilities.Copy() 220 if i.KubeVersion != nil { 221 i.cfg.Capabilities.KubeVersion = *i.KubeVersion 222 } 223 i.cfg.Capabilities.APIVersions = append(i.cfg.Capabilities.APIVersions, i.APIVersions...) 224 i.cfg.KubeClient = &kubefake.PrintingKubeClient{Out: ioutil.Discard} 225 226 mem := driver.NewMemory() 227 mem.SetNamespace(i.Namespace) 228 i.cfg.Releases = storage.Init(mem) 229 } else if !i.ClientOnly && len(i.APIVersions) > 0 { 230 i.cfg.Log("API Version list given outside of client only mode, this list will be ignored") 231 } 232 233 // Make sure if Atomic is set, that wait is set as well. This makes it so 234 // the user doesn't have to specify both 235 i.Wait = i.Wait || i.Atomic 236 237 caps, err := i.cfg.getCapabilities() 238 if err != nil { 239 return nil, err 240 } 241 242 // special case for helm template --is-upgrade 243 isUpgrade := i.IsUpgrade && i.DryRun 244 options := chartutil.ReleaseOptions{ 245 Name: i.ReleaseName, 246 Namespace: i.Namespace, 247 Revision: 1, 248 IsInstall: !isUpgrade, 249 IsUpgrade: isUpgrade, 250 } 251 valuesToRender, err := chartutil.ToRenderValues(chrt, vals, options, caps) 252 if err != nil { 253 return nil, err 254 } 255 256 rel := i.createRelease(chrt, vals) 257 258 var manifestDoc *bytes.Buffer 259 rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, i.DryRun) 260 // Even for errors, attach this if available 261 if manifestDoc != nil { 262 rel.Manifest = manifestDoc.String() 263 } 264 // Check error from render 265 if err != nil { 266 rel.SetStatus(release.StatusFailed, fmt.Sprintf("failed to render resource: %s", err.Error())) 267 // Return a release with partial data so that the client can show debugging information. 268 return rel, err 269 } 270 271 // Mark this release as in-progress 272 rel.SetStatus(release.StatusPendingInstall, "Initial install underway") 273 274 var toBeAdopted kube.ResourceList 275 resources, err := i.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest), !i.DisableOpenAPIValidation) 276 if err != nil { 277 return nil, errors.Wrap(err, "unable to build kubernetes objects from release manifest") 278 } 279 280 // It is safe to use "force" here because these are resources currently rendered by the chart. 281 err = resources.Visit(setMetadataVisitor(rel.Name, rel.Namespace, true)) 282 if err != nil { 283 return nil, err 284 } 285 286 // Install requires an extra validation step of checking that resources 287 // don't already exist before we actually create resources. If we continue 288 // forward and create the release object with resources that already exist, 289 // we'll end up in a state where we will delete those resources upon 290 // deleting the release because the manifest will be pointing at that 291 // resource 292 if !i.ClientOnly && !isUpgrade && len(resources) > 0 { 293 toBeAdopted, err = existingResourceConflict(resources, rel.Name, rel.Namespace) 294 if err != nil { 295 return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with install") 296 } 297 } 298 299 // Bail out here if it is a dry run 300 if i.DryRun { 301 rel.Info.Description = "Dry run complete" 302 return rel, nil 303 } 304 305 if i.CreateNamespace { 306 ns := &v1.Namespace{ 307 TypeMeta: metav1.TypeMeta{ 308 APIVersion: "v1", 309 Kind: "Namespace", 310 }, 311 ObjectMeta: metav1.ObjectMeta{ 312 Name: i.Namespace, 313 Labels: map[string]string{ 314 "name": i.Namespace, 315 }, 316 }, 317 } 318 buf, err := yaml.Marshal(ns) 319 if err != nil { 320 return nil, err 321 } 322 resourceList, err := i.cfg.KubeClient.Build(bytes.NewBuffer(buf), true) 323 if err != nil { 324 return nil, err 325 } 326 if _, err := i.cfg.KubeClient.Create(resourceList); err != nil && !apierrors.IsAlreadyExists(err) { 327 return nil, err 328 } 329 } 330 331 // If Replace is true, we need to supercede the last release. 332 if i.Replace { 333 if err := i.replaceRelease(rel); err != nil { 334 return nil, err 335 } 336 } 337 338 // Store the release in history before continuing (new in Helm 3). We always know 339 // that this is a create operation. 340 if err := i.cfg.Releases.Create(rel); err != nil { 341 // We could try to recover gracefully here, but since nothing has been installed 342 // yet, this is probably safer than trying to continue when we know storage is 343 // not working. 344 return rel, err 345 } 346 rChan := make(chan resultMessage) 347 doneChan := make(chan struct{}) 348 defer close(doneChan) 349 go i.performInstall(rChan, rel, toBeAdopted, resources) 350 go i.handleContext(ctx, rChan, doneChan, rel) 351 result := <-rChan 352 //start preformInstall go routine 353 return result.r, result.e 354 } 355 356 func (i *Install) performInstall(c chan<- resultMessage, rel *release.Release, toBeAdopted kube.ResourceList, resources kube.ResourceList) { 357 358 // pre-install hooks 359 if !i.DisableHooks { 360 if err := i.cfg.execHook(rel, release.HookPreInstall, i.Timeout); err != nil { 361 i.reportToRun(c, rel, fmt.Errorf("failed pre-install: %s", err)) 362 return 363 } 364 } 365 366 // At this point, we can do the install. Note that before we were detecting whether to 367 // do an update, but it's not clear whether we WANT to do an update if the re-use is set 368 // to true, since that is basically an upgrade operation. 369 if len(toBeAdopted) == 0 && len(resources) > 0 { 370 if _, err := i.cfg.KubeClient.Create(resources); err != nil { 371 i.reportToRun(c, rel, err) 372 return 373 } 374 } else if len(resources) > 0 { 375 if _, err := i.cfg.KubeClient.Update(toBeAdopted, resources, false); err != nil { 376 i.reportToRun(c, rel, err) 377 return 378 } 379 } 380 381 if i.Wait { 382 if i.WaitForJobs { 383 if err := i.cfg.KubeClient.WaitWithJobs(resources, i.Timeout); err != nil { 384 i.reportToRun(c, rel, err) 385 return 386 } 387 } else { 388 if err := i.cfg.KubeClient.Wait(resources, i.Timeout); err != nil { 389 i.reportToRun(c, rel, err) 390 return 391 } 392 } 393 } 394 395 if !i.DisableHooks { 396 if err := i.cfg.execHook(rel, release.HookPostInstall, i.Timeout); err != nil { 397 i.reportToRun(c, rel, fmt.Errorf("failed post-install: %s", err)) 398 return 399 } 400 } 401 402 if len(i.Description) > 0 { 403 rel.SetStatus(release.StatusDeployed, i.Description) 404 } else { 405 rel.SetStatus(release.StatusDeployed, "Install complete") 406 } 407 408 // This is a tricky case. The release has been created, but the result 409 // cannot be recorded. The truest thing to tell the user is that the 410 // release was created. However, the user will not be able to do anything 411 // further with this release. 412 // 413 // One possible strategy would be to do a timed retry to see if we can get 414 // this stored in the future. 415 if err := i.recordRelease(rel); err != nil { 416 i.cfg.Log("failed to record the release: %s", err) 417 } 418 419 i.reportToRun(c, rel, nil) 420 } 421 func (i *Install) handleContext(ctx context.Context, c chan<- resultMessage, done chan struct{}, rel *release.Release) { 422 select { 423 case <-ctx.Done(): 424 err := ctx.Err() 425 i.reportToRun(c, rel, err) 426 case <-done: 427 return 428 } 429 } 430 func (i *Install) reportToRun(c chan<- resultMessage, rel *release.Release, err error) { 431 i.Lock.Lock() 432 if err != nil { 433 rel, err = i.failRelease(rel, err) 434 } 435 c <- resultMessage{r: rel, e: err} 436 i.Lock.Unlock() 437 } 438 func (i *Install) failRelease(rel *release.Release, err error) (*release.Release, error) { 439 rel.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", i.ReleaseName, err.Error())) 440 if i.Atomic { 441 i.cfg.Log("Install failed and atomic is set, uninstalling release") 442 uninstall := NewUninstall(i.cfg) 443 uninstall.DisableHooks = i.DisableHooks 444 uninstall.KeepHistory = false 445 uninstall.Timeout = i.Timeout 446 if _, uninstallErr := uninstall.Run(i.ReleaseName); uninstallErr != nil { 447 return rel, errors.Wrapf(uninstallErr, "an error occurred while uninstalling the release. original install error: %s", err) 448 } 449 return rel, errors.Wrapf(err, "release %s failed, and has been uninstalled due to atomic being set", i.ReleaseName) 450 } 451 i.recordRelease(rel) // Ignore the error, since we have another error to deal with. 452 return rel, err 453 } 454 455 // availableName tests whether a name is available 456 // 457 // Roughly, this will return an error if name is 458 // 459 // - empty 460 // - too long 461 // - already in use, and not deleted 462 // - used by a deleted release, and i.Replace is false 463 func (i *Install) availableName() error { 464 start := i.ReleaseName 465 466 if err := chartutil.ValidateReleaseName(start); err != nil { 467 return errors.Wrapf(err, "release name %q", start) 468 } 469 if i.DryRun { 470 return nil 471 } 472 473 h, err := i.cfg.Releases.History(start) 474 if err != nil || len(h) < 1 { 475 return nil 476 } 477 releaseutil.Reverse(h, releaseutil.SortByRevision) 478 rel := h[0] 479 480 if st := rel.Info.Status; i.Replace && (st == release.StatusUninstalled || st == release.StatusFailed) { 481 return nil 482 } 483 return errors.New("cannot re-use a name that is still in use") 484 } 485 486 // createRelease creates a new release object 487 func (i *Install) createRelease(chrt *chart.Chart, rawVals map[string]interface{}) *release.Release { 488 ts := i.cfg.Now() 489 return &release.Release{ 490 Name: i.ReleaseName, 491 Namespace: i.Namespace, 492 Chart: chrt, 493 Config: rawVals, 494 Info: &release.Info{ 495 FirstDeployed: ts, 496 LastDeployed: ts, 497 Status: release.StatusUnknown, 498 }, 499 Version: 1, 500 } 501 } 502 503 // recordRelease with an update operation in case reuse has been set. 504 func (i *Install) recordRelease(r *release.Release) error { 505 // This is a legacy function which has been reduced to a oneliner. Could probably 506 // refactor it out. 507 return i.cfg.Releases.Update(r) 508 } 509 510 // replaceRelease replaces an older release with this one 511 // 512 // This allows us to re-use names by superseding an existing release with a new one 513 func (i *Install) replaceRelease(rel *release.Release) error { 514 hist, err := i.cfg.Releases.History(rel.Name) 515 if err != nil || len(hist) == 0 { 516 // No releases exist for this name, so we can return early 517 return nil 518 } 519 520 releaseutil.Reverse(hist, releaseutil.SortByRevision) 521 last := hist[0] 522 523 // Update version to the next available 524 rel.Version = last.Version + 1 525 526 // Do not change the status of a failed release. 527 if last.Info.Status == release.StatusFailed { 528 return nil 529 } 530 531 // For any other status, mark it as superseded and store the old record 532 last.SetStatus(release.StatusSuperseded, "superseded by new release") 533 return i.recordRelease(last) 534 } 535 536 // write the <data> to <output-dir>/<name>. <append> controls if the file is created or content will be appended 537 func writeToFile(outputDir string, name string, data string, append bool) error { 538 outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator)) 539 540 err := ensureDirectoryForFile(outfileName) 541 if err != nil { 542 return err 543 } 544 545 f, err := createOrOpenFile(outfileName, append) 546 if err != nil { 547 return err 548 } 549 550 defer f.Close() 551 552 _, err = f.WriteString(fmt.Sprintf("---\n# Source: %s\n%s\n", name, data)) 553 554 if err != nil { 555 return err 556 } 557 558 fmt.Printf("wrote %s\n", outfileName) 559 return nil 560 } 561 562 func createOrOpenFile(filename string, append bool) (*os.File, error) { 563 if append { 564 return os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600) 565 } 566 return os.Create(filename) 567 } 568 569 // check if the directory exists to create file. creates if don't exists 570 func ensureDirectoryForFile(file string) error { 571 baseDir := path.Dir(file) 572 _, err := os.Stat(baseDir) 573 if err != nil && !os.IsNotExist(err) { 574 return err 575 } 576 577 return os.MkdirAll(baseDir, defaultDirectoryPermission) 578 } 579 580 // NameAndChart returns the name and chart that should be used. 581 // 582 // This will read the flags and handle name generation if necessary. 583 func (i *Install) NameAndChart(args []string) (string, string, error) { 584 flagsNotSet := func() error { 585 if i.GenerateName { 586 return errors.New("cannot set --generate-name and also specify a name") 587 } 588 if i.NameTemplate != "" { 589 return errors.New("cannot set --name-template and also specify a name") 590 } 591 return nil 592 } 593 594 if len(args) > 2 { 595 return args[0], args[1], errors.Errorf("expected at most two arguments, unexpected arguments: %v", strings.Join(args[2:], ", ")) 596 } 597 598 if len(args) == 2 { 599 return args[0], args[1], flagsNotSet() 600 } 601 602 if i.NameTemplate != "" { 603 name, err := TemplateName(i.NameTemplate) 604 return name, args[0], err 605 } 606 607 if i.ReleaseName != "" { 608 return i.ReleaseName, args[0], nil 609 } 610 611 if !i.GenerateName { 612 return "", args[0], errors.New("must either provide a name or specify --generate-name") 613 } 614 615 base := filepath.Base(args[0]) 616 if base == "." || base == "" { 617 base = "chart" 618 } 619 // if present, strip out the file extension from the name 620 if idx := strings.Index(base, "."); idx != -1 { 621 base = base[0:idx] 622 } 623 624 return fmt.Sprintf("%s-%d", base, time.Now().Unix()), args[0], nil 625 } 626 627 // TemplateName renders a name template, returning the name or an error. 628 func TemplateName(nameTemplate string) (string, error) { 629 if nameTemplate == "" { 630 return "", nil 631 } 632 633 t, err := template.New("name-template").Funcs(sprig.TxtFuncMap()).Parse(nameTemplate) 634 if err != nil { 635 return "", err 636 } 637 var b bytes.Buffer 638 if err := t.Execute(&b, nil); err != nil { 639 return "", err 640 } 641 642 return b.String(), nil 643 } 644 645 // CheckDependencies checks the dependencies for a chart. 646 func CheckDependencies(ch *chart.Chart, reqs []*chart.Dependency) error { 647 var missing []string 648 649 OUTER: 650 for _, r := range reqs { 651 for _, d := range ch.Dependencies() { 652 if d.Name() == r.Name { 653 continue OUTER 654 } 655 } 656 missing = append(missing, r.Name) 657 } 658 659 if len(missing) > 0 { 660 return errors.Errorf("found in Chart.yaml, but missing in charts/ directory: %s", strings.Join(missing, ", ")) 661 } 662 return nil 663 } 664 665 // LocateChart looks for a chart directory in known places, and returns either the full path or an error. 666 // 667 // This does not ensure that the chart is well-formed; only that the requested filename exists. 668 // 669 // Order of resolution: 670 // - relative to current working directory 671 // - if path is absolute or begins with '.', error out here 672 // - URL 673 // 674 // If 'verify' was set on ChartPathOptions, this will attempt to also verify the chart. 675 func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (string, error) { 676 // If there is no registry client and the name is in an OCI registry return 677 // an error and a lookup will not occur. 678 if registry.IsOCI(name) && c.registryClient == nil { 679 return "", fmt.Errorf("unable to lookup chart %q, missing registry client", name) 680 } 681 682 name = strings.TrimSpace(name) 683 version := strings.TrimSpace(c.Version) 684 685 if _, err := os.Stat(name); err == nil { 686 abs, err := filepath.Abs(name) 687 if err != nil { 688 return abs, err 689 } 690 if c.Verify { 691 if _, err := downloader.VerifyChart(abs, c.Keyring); err != nil { 692 return "", err 693 } 694 } 695 return abs, nil 696 } 697 if filepath.IsAbs(name) || strings.HasPrefix(name, ".") { 698 return name, errors.Errorf("path %q not found", name) 699 } 700 701 dl := downloader.ChartDownloader{ 702 Out: os.Stdout, 703 Keyring: c.Keyring, 704 Getters: getter.All(settings), 705 Options: []getter.Option{ 706 getter.WithPassCredentialsAll(c.PassCredentialsAll), 707 getter.WithTLSClientConfig(c.CertFile, c.KeyFile, c.CaFile), 708 getter.WithInsecureSkipVerifyTLS(c.InsecureSkipTLSverify), 709 }, 710 RepositoryConfig: settings.RepositoryConfig, 711 RepositoryCache: settings.RepositoryCache, 712 RegistryClient: c.registryClient, 713 } 714 715 if registry.IsOCI(name) { 716 dl.Options = append(dl.Options, getter.WithRegistryClient(c.registryClient)) 717 } 718 719 if c.Verify { 720 dl.Verify = downloader.VerifyAlways 721 } 722 if c.RepoURL != "" { 723 chartURL, err := repo.FindChartInAuthAndTLSAndPassRepoURL(c.RepoURL, c.Username, c.Password, name, version, 724 c.CertFile, c.KeyFile, c.CaFile, c.InsecureSkipTLSverify, c.PassCredentialsAll, getter.All(settings)) 725 if err != nil { 726 return "", err 727 } 728 name = chartURL 729 730 // Only pass the user/pass on when the user has said to or when the 731 // location of the chart repo and the chart are the same domain. 732 u1, err := url.Parse(c.RepoURL) 733 if err != nil { 734 return "", err 735 } 736 u2, err := url.Parse(chartURL) 737 if err != nil { 738 return "", err 739 } 740 741 // Host on URL (returned from url.Parse) contains the port if present. 742 // This check ensures credentials are not passed between different 743 // services on different ports. 744 if c.PassCredentialsAll || (u1.Scheme == u2.Scheme && u1.Host == u2.Host) { 745 dl.Options = append(dl.Options, getter.WithBasicAuth(c.Username, c.Password)) 746 } else { 747 dl.Options = append(dl.Options, getter.WithBasicAuth("", "")) 748 } 749 } else { 750 dl.Options = append(dl.Options, getter.WithBasicAuth(c.Username, c.Password)) 751 } 752 753 if err := os.MkdirAll(settings.RepositoryCache, 0755); err != nil { 754 return "", err 755 } 756 757 filename, _, err := dl.DownloadTo(name, version, settings.RepositoryCache) 758 if err != nil { 759 return "", err 760 } 761 762 lname, err := filepath.Abs(filename) 763 if err != nil { 764 return filename, err 765 } 766 return lname, nil 767 }