Merge pull request #8478 from mattfarina/fix-lockfiles

Lockfiles without version ranges
pull/8510/head
Matt Farina 5 years ago committed by GitHub
commit d481bc6cf3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -27,6 +27,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/provenance" "helm.sh/helm/v3/pkg/provenance"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v3/pkg/repo"
@ -68,14 +69,22 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
} }
if strings.HasPrefix(d.Repository, "file://") { if strings.HasPrefix(d.Repository, "file://") {
if _, err := GetLocalPath(d.Repository, r.chartpath); err != nil { chartpath, err := GetLocalPath(d.Repository, r.chartpath)
if err != nil {
return nil, err
}
// The version of the chart locked will be the version of the chart
// currently listed in the file system within the chart.
ch, err := loader.LoadDir(chartpath)
if err != nil {
return nil, err return nil, err
} }
locked[i] = &chart.Dependency{ locked[i] = &chart.Dependency{
Name: d.Name, Name: d.Name,
Repository: d.Repository, Repository: d.Repository,
Version: d.Version, Version: ch.Metadata.Version,
} }
continue continue
} }

@ -82,6 +82,17 @@ func TestResolve(t *testing.T) {
}, },
}, },
}, },
{
name: "repo from valid local path with range resolution",
req: []*chart.Dependency{
{Name: "base", Repository: "file://base", Version: "^0.1.0"},
},
expect: &chart.Lock{
Dependencies: []*chart.Dependency{
{Name: "base", Repository: "file://base", Version: "0.1.0"},
},
},
},
{ {
name: "repo from invalid local path", name: "repo from invalid local path",
req: []*chart.Dependency{ req: []*chart.Dependency{

@ -16,6 +16,8 @@ limitations under the License.
package downloader package downloader
import ( import (
"crypto"
"encoding/hex"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -158,14 +160,27 @@ func (m *Manager) Update() error {
return nil return nil
} }
// Check that all of the repos we're dependent on actually exist and // Get the names of the repositories the dependencies need that Helm is
// the repo index names. // configured to know about.
repoNames, err := m.resolveRepoNames(req) repoNames, err := m.resolveRepoNames(req)
if err != nil { if err != nil {
return err return err
} }
// For each repo in the file, update the cached copy of that repo // For the repositories Helm is not configured to know about, ensure Helm
// has some information about them and, when possible, the index files
// locally.
// TODO(mattfarina): Repositories should be explicitly added by end users
// rather than automattic. In Helm v4 require users to add repositories. They
// should have to add them in order to make sure they are aware of the
// respoitories and opt-in to any locations, for security.
repoNames, err = m.ensureMissingRepos(repoNames, req)
if err != nil {
return err
}
// For each of the repositories Helm is configured to know about, update
// the index information locally.
if !m.SkipUpdate { if !m.SkipUpdate {
if err := m.UpdateRepositories(); err != nil { if err := m.UpdateRepositories(); err != nil {
return err return err
@ -427,6 +442,62 @@ Loop:
return nil return nil
} }
// ensureMissingRepos attempts to ensure the repository information for repos
// not managed by Helm is present. This takes in the repoNames Helm is configured
// to work with along with the chart dependencies. It will find the deps not
// in a known repo and attempt to ensure the data is present for steps like
// version resolution.
func (m *Manager) ensureMissingRepos(repoNames map[string]string, deps []*chart.Dependency) (map[string]string, error) {
var ru []*repo.Entry
for _, dd := range deps {
// When the repoName for a dependency is known we can skip ensuring
if _, ok := repoNames[dd.Name]; ok {
continue
}
// The generated repository name, which will result in an index being
// locally cached, has a name pattern of "helm-manager-" followed by a
// sha256 of the repo name. This assumes end users will never create
// repositories with these names pointing to other repositories. Using
// this method of naming allows the existing repository pulling and
// resolution code to do most of the work.
rn, err := key(dd.Repository)
if err != nil {
return repoNames, err
}
rn = managerKeyPrefix + rn
repoNames[dd.Name] = rn
// Assuming the repository is generally available. For Helm managed
// access controls the repository needs to be added through the user
// managed system. This path will work for public charts, like those
// supplied by Bitnami, but not for protected charts, like corp ones
// behind a username and pass.
ri := &repo.Entry{
Name: rn,
URL: dd.Repository,
}
ru = append(ru, ri)
}
// Calls to UpdateRepositories (a public function) will only update
// repositories configured by the user. Here we update repos found in
// the dependencies that are not known to the user if update skipping
// is not configured.
if !m.SkipUpdate && len(ru) > 0 {
fmt.Fprintln(m.Out, "Getting updates for unmanaged Helm repositories...")
if err := m.parallelRepoUpdate(ru); err != nil {
return repoNames, err
}
}
return repoNames, nil
}
// resolveRepoNames returns the repo names of the referenced deps which can be used to fetch the cached index file // resolveRepoNames returns the repo names of the referenced deps which can be used to fetch the cached index file
// and replaces aliased repository URLs into resolved URLs in dependencies. // and replaces aliased repository URLs into resolved URLs in dependencies.
func (m *Manager) resolveRepoNames(deps []*chart.Dependency) (map[string]string, error) { func (m *Manager) resolveRepoNames(deps []*chart.Dependency) (map[string]string, error) {
@ -517,16 +588,18 @@ func (m *Manager) UpdateRepositories() error {
} }
repos := rf.Repositories repos := rf.Repositories
if len(repos) > 0 { if len(repos) > 0 {
fmt.Fprintln(m.Out, "Hang tight while we grab the latest from your chart repositories...")
// This prints warnings straight to out. // This prints warnings straight to out.
if err := m.parallelRepoUpdate(repos); err != nil { if err := m.parallelRepoUpdate(repos); err != nil {
return err return err
} }
fmt.Fprintln(m.Out, "Update Complete. ⎈Happy Helming!⎈")
} }
return nil return nil
} }
func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error { func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error {
fmt.Fprintln(m.Out, "Hang tight while we grab the latest from your chart repositories...")
var wg sync.WaitGroup var wg sync.WaitGroup
for _, c := range repos { for _, c := range repos {
r, err := repo.NewChartRepository(c, m.Getters) r, err := repo.NewChartRepository(c, m.Getters)
@ -536,15 +609,27 @@ func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error {
wg.Add(1) wg.Add(1)
go func(r *repo.ChartRepository) { go func(r *repo.ChartRepository) {
if _, err := r.DownloadIndexFile(); err != nil { if _, err := r.DownloadIndexFile(); err != nil {
// For those dependencies that are not known to helm and using a
// generated key name we display the repo url.
if strings.HasPrefix(r.Config.Name, managerKeyPrefix) {
fmt.Fprintf(m.Out, "...Unable to get an update from the %q chart repository:\n\t%s\n", r.Config.URL, err)
} else {
fmt.Fprintf(m.Out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", r.Config.Name, r.Config.URL, err) fmt.Fprintf(m.Out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", r.Config.Name, r.Config.URL, err)
}
} else {
// For those dependencies that are not known to helm and using a
// generated key name we display the repo url.
if strings.HasPrefix(r.Config.Name, managerKeyPrefix) {
fmt.Fprintf(m.Out, "...Successfully got an update from the %q chart repository\n", r.Config.URL)
} else { } else {
fmt.Fprintf(m.Out, "...Successfully got an update from the %q chart repository\n", r.Config.Name) fmt.Fprintf(m.Out, "...Successfully got an update from the %q chart repository\n", r.Config.Name)
} }
}
wg.Done() wg.Done()
}(r) }(r)
} }
wg.Wait() wg.Wait()
fmt.Fprintln(m.Out, "Update Complete. ⎈Happy Helming!⎈")
return nil return nil
} }
@ -739,3 +824,18 @@ func move(tmpPath, destPath string) error {
} }
return nil return nil
} }
// The prefix to use for cache keys created by the manager for repo names
const managerKeyPrefix = "helm-manager-"
// key is used to turn a name, such as a repository url, into a filesystem
// safe name that is unique for querying. To accomplish this a unique hash of
// the string is used.
func key(name string) (string, error) {
in := strings.NewReader(name)
hash := crypto.SHA256.New()
if _, err := io.Copy(hash, in); err != nil {
return "", nil
}
return hex.EncodeToString(hash.Sum(nil)), nil
}

@ -389,3 +389,33 @@ func TestErrRepoNotFound_Error(t *testing.T) {
}) })
} }
} }
func TestKey(t *testing.T) {
tests := []struct {
name string
expect string
}{
{
name: "file:////tmp",
expect: "afeed3459e92a874f6373aca264ce1459bfa91f9c1d6612f10ae3dc2ee955df3",
},
{
name: "https://example.com/charts",
expect: "7065c57c94b2411ad774638d76823c7ccb56415441f5ab2f5ece2f3845728e5d",
},
{
name: "foo/bar/baz",
expect: "15c46a4f8a189ae22f36f201048881d6c090c93583bedcf71f5443fdef224c82",
},
}
for _, tt := range tests {
o, err := key(tt.name)
if err != nil {
t.Fatalf("unable to generate key for %q with error: %s", tt.name, err)
}
if o != tt.expect {
t.Errorf("wrong key name generated for %q, expected %q but got %q", tt.name, tt.expect, o)
}
}
}

Loading…
Cancel
Save