Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Downloading chart dependencies and saving them using their alias when one is configured. #12930

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion internal/resolver/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
Name: d.Name,
Repository: "",
Version: d.Version,
Alias: d.Alias,
}
continue
}
Expand Down Expand Up @@ -103,17 +104,19 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
Name: d.Name,
Repository: d.Repository,
Version: ch.Metadata.Version,
Alias: d.Alias,
}
continue
}

repoName := repoNames[d.Name]
repoName := repoNames[d.ActualName()]
// if the repository was not defined, but the dependency defines a repository url, bypass the cache
if repoName == "" && d.Repository != "" {
locked[i] = &chart.Dependency{
Name: d.Name,
Repository: d.Repository,
Version: d.Version,
Alias: d.Alias,
}
continue
}
Expand Down Expand Up @@ -172,6 +175,7 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
Name: d.Name,
Repository: d.Repository,
Version: version,
Alias: d.Alias,
}
// The version are already sorted and hence the first one to satisfy the constraint is used
for _, ver := range vs {
Expand Down
26 changes: 26 additions & 0 deletions pkg/chart/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ limitations under the License.
package chart

import (
"fmt"
"path/filepath"
"regexp"
"strings"
Expand Down Expand Up @@ -56,6 +57,15 @@ type Chart struct {
dependencies []*Chart
}

// Archive is a helm package on disk with a possible alias
type Archive struct {
Name string
Version string
URL string
Alias string
Dir string
}

type CRD struct {
// Name is the File.Name for the crd file
Name string
Expand Down Expand Up @@ -167,6 +177,22 @@ func (ch *Chart) CRDObjects() []CRD {
return crds
}

func (a Archive) FileName() string {
if a.Alias != "" {
return filepath.Join(a.Dir, fmt.Sprintf("%s-%s.tgz", a.Alias, a.Version))
} else {
return filepath.Join(a.Dir, fmt.Sprintf("%s-%s.tgz", a.Name, a.Version))
}
}

func (a Archive) ProvFileName() string {
return fmt.Sprintf("%s.prov", a.FileName())
}

func (a Archive) SignatureFileName() string {
return fmt.Sprintf("%s-%s.tgz", a.Name, a.Version)
}

func hasManifestExtension(fname string) bool {
ext := filepath.Ext(fname)
return strings.EqualFold(ext, ".yaml") || strings.EqualFold(ext, ".yml") || strings.EqualFold(ext, ".json")
Expand Down
8 changes: 8 additions & 0 deletions pkg/chart/dependency.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ func (d *Dependency) Validate() error {
return nil
}

func (d *Dependency) ActualName() string {
if d.Alias != "" {
return d.Alias
} else {
return d.Name
}
}

// Lock is a lock file for dependencies.
//
// It represents the state that the dependencies should be in.
Expand Down
81 changes: 81 additions & 0 deletions pkg/downloader/chart_downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package downloader

import (
"fmt"
"helm.sh/helm/v3/pkg/chart"
"io"
"net/url"
"os"
Expand Down Expand Up @@ -141,6 +142,62 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
return destfile, ver, nil
}

func (c *ChartDownloader) DownloadArchiveTo(archive *chart.Archive) (string, *provenance.Verification, error) {
u, err := c.ResolveChartVersion(archive.URL, archive.Version)
if err != nil {
return "", nil, err
}

g, err := c.Getters.ByScheme(u.Scheme)
if err != nil {
return "", nil, err
}

data, err := g.Get(u.String(), c.Options...)
if err != nil {
return "", nil, err
}

name := filepath.Base(u.Path)
if u.Scheme == registry.OCIScheme {
idx := strings.LastIndexByte(name, ':')
name = fmt.Sprintf("%s-%s.tgz", name[:idx], name[idx+1:])
}

//destfile := archive.FileName()

if err := fileutil.AtomicWriteFile(archive.FileName(), data, 0644); err != nil {
return archive.FileName(), nil, err
}

// If provenance is requested, verify it.
ver := &provenance.Verification{}
if c.Verify > VerifyNever {
body, err := g.Get(u.String() + ".prov")
if err != nil {
if c.Verify == VerifyAlways {
return archive.FileName(), ver, errors.Errorf("failed to fetch provenance %q", u.String()+".prov")
}
fmt.Fprintf(c.Out, "WARNING: Verification not found for %s: %s\n", archive.URL, err)
return archive.FileName(), ver, nil
}
//provfile := destfile + ".prov"
if err := fileutil.AtomicWriteFile(archive.ProvFileName(), body, 0644); err != nil {
return archive.FileName(), nil, err
}

if c.Verify != VerifyLater {
ver, err = VerifyChartArchive(archive, c.Keyring)
if err != nil {
// Fail always in this case, since it means the verification step
// failed.
return archive.FileName(), ver, err
}
}
}
return archive.FileName(), ver, nil
}

func (c *ChartDownloader) getOciURI(ref, version string, u *url.URL) (*url.URL, error) {
var tag string
var err error
Expand Down Expand Up @@ -330,6 +387,30 @@ func VerifyChart(path, keyring string) (*provenance.Verification, error) {
return sig.Verify(path, provfile)
}

func VerifyChartArchive(archive *chart.Archive, keyring string) (*provenance.Verification, error) {
// For now, error out if it's not a tar file.
path := archive.FileName()
switch fi, err := os.Stat(path); {
case err != nil:
return nil, err
case fi.IsDir():
return nil, errors.New("unpacked charts cannot be verified")
case !isTar(path):
return nil, errors.New("chart must be a tgz file")
}

provfile := archive.ProvFileName()
if _, err := os.Stat(provfile); err != nil {
return nil, errors.Wrapf(err, "could not load provenance file %s", provfile)
}

sig, err := provenance.NewFromKeyring(keyring, "")
if err != nil {
return nil, errors.Wrap(err, "failed to load keyring")
}
return sig.VerifyArchive(archive)
}

// isTar tests whether the given file is a tar file.
//
// Currently, this simply checks extension, since a subsequent function will
Expand Down
74 changes: 74 additions & 0 deletions pkg/downloader/chart_downloader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ limitations under the License.
package downloader

import (
"helm.sh/helm/v3/pkg/chart"
"os"
"path/filepath"
"testing"
Expand Down Expand Up @@ -153,6 +154,25 @@ func TestVerifyChart(t *testing.T) {
}
}

func TestVerifyChartArchive(t *testing.T) {
archive := chart.Archive{
Name: "signtest",
Version: "0.1.0",
Alias: "signtest-alias",
Dir: "testdata",
}

v, err := VerifyChartArchive(&archive, "testdata/helm-test-key.pub")
if err != nil {
t.Fatal(err)
}
// The verification is tested at length in the provenance package. Here,
// we just want a quick sanity check that the v is not empty.
if len(v.FileHash) == 0 {
t.Error("Digest missing")
}
}

func TestIsTar(t *testing.T) {
tests := map[string]bool{
"foo.tgz": true,
Expand Down Expand Up @@ -216,6 +236,60 @@ func TestDownloadTo(t *testing.T) {
}
}

func TestDownloadArchiveTo(t *testing.T) {
srv := repotest.NewTempServerWithCleanupAndBasicAuth(t, "testdata/*.tgz*")
defer srv.Stop()
if err := srv.CreateIndex(); err != nil {
t.Fatal(err)
}

if err := srv.LinkIndices(); err != nil {
t.Fatal(err)
}

c := ChartDownloader{
Out: os.Stderr,
Verify: VerifyAlways,
Keyring: "testdata/helm-test-key.pub",
RepositoryConfig: repoConfig,
RepositoryCache: repoCache,
Getters: getter.All(&cli.EnvSettings{
RepositoryConfig: repoConfig,
RepositoryCache: repoCache,
}),
Options: []getter.Option{
getter.WithBasicAuth("username", "password"),
getter.WithPassCredentialsAll(false),
},
}
cname := "/signtest-0.1.0.tgz"
saveAs := "signtest-alias-0.1.0.tgz"
dest := srv.Root()
archive := chart.Archive{
Name: "signtest",
Version: "0.1.0",
URL: srv.URL() + cname,
Alias: "signtest-alias",
Dir: dest,
}
where, v, err := c.DownloadArchiveTo(&archive)
if err != nil {
t.Fatal(err)
}

if expect := filepath.Join(dest, saveAs); where != expect {
t.Errorf("Expected download to %s, got %s", expect, where)
}

if v.FileHash == "" {
t.Error("File hash was empty, but verification is required.")
}

if _, err := os.Stat(filepath.Join(dest, saveAs)); err != nil {
t.Error(err)
}
}

func TestDownloadTo_TLS(t *testing.T) {
// Set up mock server w/ tls enabled
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*")
Expand Down
16 changes: 12 additions & 4 deletions pkg/downloader/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,15 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
getter.WithTagName(version))
}

if _, _, err = dl.DownloadTo(churl, version, tmpPath); err != nil {
archive := chart.Archive{
Name: dep.Name,
Version: dep.Version,
Alias: dep.Alias,
URL: churl,
Dir: tmpPath,
}

if _, _, err = dl.DownloadArchiveTo(&archive); err != nil {
saveError = errors.Wrapf(err, "could not download %s", churl)
break
}
Expand Down Expand Up @@ -585,12 +593,12 @@ func (m *Manager) resolveRepoNames(deps []*chart.Dependency) (map[string]string,
if m.Debug {
fmt.Fprintf(m.Out, "Repository from local path: %s\n", dd.Repository)
}
reposMap[dd.Name] = dd.Repository
reposMap[dd.ActualName()] = dd.Repository
continue
}

if registry.IsOCI(dd.Repository) {
reposMap[dd.Name] = dd.Repository
reposMap[dd.ActualName()] = dd.Repository
continue
}

Expand All @@ -605,7 +613,7 @@ func (m *Manager) resolveRepoNames(deps []*chart.Dependency) (map[string]string,
break
} else if urlutil.Equal(repo.URL, dd.Repository) {
found = true
reposMap[dd.Name] = repo.Name
reposMap[dd.ActualName()] = repo.Name
break
}
}
Expand Down
Binary file added pkg/downloader/testdata/signtest-alias-0.1.0.tgz
Binary file not shown.
21 changes: 21 additions & 0 deletions pkg/downloader/testdata/signtest-alias-0.1.0.tgz.prov
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

apiVersion: v1
description: A Helm chart for Kubernetes
name: signtest
version: 0.1.0

...
files:
signtest-0.1.0.tgz: sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55
-----BEGIN PGP SIGNATURE-----

wsBcBAEBCgAQBQJcoosfCRCEO7+YH8GHYgAA220IALAs8T8NPgkcLvHu+5109cAN
BOCNPSZDNsqLZW/2Dc9cKoBG7Jen4Qad+i5l9351kqn3D9Gm6eRfAWcjfggRobV/
9daZ19h0nl4O1muQNAkjvdgZt8MOP3+PB3I3/Tu2QCYjI579SLUmuXlcZR5BCFPR
PJy+e3QpV2PcdeU2KZLG4tjtlrq+3QC9ZHHEJLs+BVN9d46Dwo6CxJdHJrrrAkTw
M8MhA92vbiTTPRSCZI9x5qDAwJYhoq0oxLflpuL2tIlo3qVoCsaTSURwMESEHO32
XwYG7BaVDMELWhAorBAGBGBwWFbJ1677qQ2gd9CN0COiVhekWlFRcnn60800r84=
=k9Y9
-----END PGP SIGNATURE-----