mirror of https://github.com/helm/helm
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
236 lines
6.2 KiB
236 lines
6.2 KiB
/*
|
|
Copyright The Helm Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package action
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
|
|
"helm.sh/helm/v4/pkg/cli"
|
|
"helm.sh/helm/v4/pkg/downloader"
|
|
"helm.sh/helm/v4/pkg/getter"
|
|
"helm.sh/helm/v4/pkg/registry"
|
|
"helm.sh/helm/v4/pkg/repo"
|
|
)
|
|
|
|
// Pull is the action for checking a given release's information.
|
|
//
|
|
// It provides the implementation of 'helm pull'.
|
|
type Pull struct {
|
|
ChartPathOptions
|
|
|
|
Settings *cli.EnvSettings // TODO: refactor this out of pkg/action
|
|
|
|
Devel bool
|
|
Untar bool
|
|
VerifyLater bool
|
|
UntarDir string
|
|
DestDir string
|
|
cfg *Configuration
|
|
}
|
|
|
|
type PullOpt func(*Pull)
|
|
|
|
func WithConfig(cfg *Configuration) PullOpt {
|
|
return func(p *Pull) {
|
|
p.cfg = cfg
|
|
}
|
|
}
|
|
|
|
// NewPull creates a new Pull with configuration options.
|
|
func NewPull(opts ...PullOpt) *Pull {
|
|
p := &Pull{}
|
|
for _, fn := range opts {
|
|
fn(p)
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
// SetRegistryClient sets the registry client on the pull configuration object.
|
|
func (p *Pull) SetRegistryClient(client *registry.Client) {
|
|
p.cfg.RegistryClient = client
|
|
}
|
|
|
|
// Run executes 'helm pull' against the given release.
|
|
func (p *Pull) Run(chartRef string) (string, error) {
|
|
var out strings.Builder
|
|
|
|
c := downloader.ChartDownloader{
|
|
Out: &out,
|
|
Keyring: p.Keyring,
|
|
Verify: downloader.VerifyNever,
|
|
Getters: getter.All(p.Settings),
|
|
Options: []getter.Option{
|
|
getter.WithBasicAuth(p.Username, p.Password),
|
|
getter.WithPassCredentialsAll(p.PassCredentialsAll),
|
|
getter.WithTLSClientConfig(p.CertFile, p.KeyFile, p.CaFile),
|
|
getter.WithInsecureSkipVerifyTLS(p.InsecureSkipTLSverify),
|
|
getter.WithPlainHTTP(p.PlainHTTP),
|
|
},
|
|
RegistryClient: p.cfg.RegistryClient,
|
|
RepositoryConfig: p.Settings.RepositoryConfig,
|
|
RepositoryCache: p.Settings.RepositoryCache,
|
|
ContentCache: p.Settings.ContentCache,
|
|
}
|
|
|
|
if registry.IsOCI(chartRef) {
|
|
c.Options = append(c.Options,
|
|
getter.WithRegistryClient(p.cfg.RegistryClient))
|
|
c.RegistryClient = p.cfg.RegistryClient
|
|
}
|
|
|
|
if p.Verify {
|
|
c.Verify = downloader.VerifyAlways
|
|
} else if p.VerifyLater {
|
|
c.Verify = downloader.VerifyLater
|
|
}
|
|
|
|
// If untar is set, we fetch to a tempdir, then untar and copy after
|
|
// verification.
|
|
dest := p.DestDir
|
|
if p.Untar {
|
|
var err error
|
|
dest, err = os.MkdirTemp("", "helm-")
|
|
if err != nil {
|
|
return out.String(), fmt.Errorf("failed to untar: %w", err)
|
|
}
|
|
defer os.RemoveAll(dest)
|
|
}
|
|
|
|
downloadSourceRef := chartRef
|
|
if p.RepoURL != "" {
|
|
chartURL, err := repo.FindChartInRepoURL(
|
|
p.RepoURL,
|
|
chartRef,
|
|
getter.All(p.Settings),
|
|
repo.WithChartVersion(p.Version),
|
|
repo.WithClientTLS(p.CertFile, p.KeyFile, p.CaFile),
|
|
repo.WithUsernamePassword(p.Username, p.Password),
|
|
repo.WithInsecureSkipTLSverify(p.InsecureSkipTLSverify),
|
|
repo.WithPassCredentialsAll(p.PassCredentialsAll),
|
|
)
|
|
if err != nil {
|
|
return out.String(), err
|
|
}
|
|
downloadSourceRef = chartURL
|
|
}
|
|
|
|
saved, v, err := c.DownloadTo(downloadSourceRef, p.Version, dest)
|
|
if err != nil {
|
|
return out.String(), err
|
|
}
|
|
|
|
// Print a pull summary for repo mode and non-OCI downloads.
|
|
// Some repos (e.g., Bitnami) resolve --repo pulls to oci:// refs, but that
|
|
// path does not go through the registry client's summary printer, so we emit
|
|
// a consistent summary here. We mirror the OCI output format:
|
|
//
|
|
// Pulled: <chartUrl>:<version>
|
|
// Digest: sha256:<digest>
|
|
//
|
|
// For HTTP/repo pulls, the digest is the SHA-256 of the downloaded .tgz archive.
|
|
// For direct OCI pulls, the registry client already prints "Pulled:" and
|
|
// "Digest:" (manifest digest), so we do not print here to avoid duplicates.
|
|
if p.RepoURL != "" || !registry.IsOCI(downloadSourceRef) {
|
|
base := trimAnySuffix(downloadSourceRef, ".tar.gz", ".tgz")
|
|
chart, ver := splitChartNameVersion(base)
|
|
|
|
tag := chart
|
|
if ver != "" {
|
|
tag += ":" + ver
|
|
} else if p.Version != "" {
|
|
tag += ":" + p.Version
|
|
}
|
|
fmt.Fprintf(&out, "Pulled: %s\n", tag)
|
|
|
|
if sum, err := sha256File(saved); err == nil {
|
|
fmt.Fprintf(&out, "Digest: sha256:%x\n", sum)
|
|
}
|
|
}
|
|
|
|
if p.Verify {
|
|
for name := range v.SignedBy.Identities {
|
|
fmt.Fprintf(&out, "Signed by: %v\n", name)
|
|
}
|
|
fmt.Fprintf(&out, "Using Key With Fingerprint: %X\n", v.SignedBy.PrimaryKey.Fingerprint)
|
|
fmt.Fprintf(&out, "Chart Hash Verified: %s\n", v.FileHash)
|
|
}
|
|
|
|
// After verification, untar the chart into the requested directory.
|
|
if p.Untar {
|
|
ud := p.UntarDir
|
|
if !filepath.IsAbs(ud) {
|
|
ud = filepath.Join(p.DestDir, ud)
|
|
}
|
|
// Let udCheck to check conflict file/dir without replacing ud when untarDir is the current directory(.).
|
|
udCheck := ud
|
|
if udCheck == "." {
|
|
_, udCheck = filepath.Split(chartRef)
|
|
} else {
|
|
_, chartName := filepath.Split(chartRef)
|
|
udCheck = filepath.Join(udCheck, chartName)
|
|
}
|
|
|
|
if _, err := os.Stat(udCheck); err != nil {
|
|
if err := os.MkdirAll(udCheck, 0755); err != nil {
|
|
return out.String(), fmt.Errorf("failed to untar (mkdir): %w", err)
|
|
}
|
|
} else {
|
|
return out.String(), fmt.Errorf("failed to untar: a file or directory with the name %s already exists", udCheck)
|
|
}
|
|
|
|
return out.String(), chartutil.ExpandFile(ud, saved)
|
|
}
|
|
return out.String(), nil
|
|
}
|
|
|
|
func trimAnySuffix(s string, suffixes ...string) string {
|
|
for _, suf := range suffixes {
|
|
if strings.HasSuffix(s, suf) {
|
|
return strings.TrimSuffix(s, suf)
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
func splitChartNameVersion(s string) (name, version string) {
|
|
if i := strings.LastIndex(s, "-"); i > 0 {
|
|
return s[:i], s[i+1:]
|
|
}
|
|
return s, ""
|
|
}
|
|
|
|
func sha256File(path string) ([]byte, error) {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
|
|
h := sha256.New()
|
|
if _, err := io.Copy(h, f); err != nil {
|
|
return nil, err
|
|
}
|
|
return h.Sum(nil), nil
|
|
}
|