issue-11308: allow individual dependency updates

This adds a new option to `helm dependency update` called `--dependency` which allows
the user to select one or more dependencies from Chart.yaml to be updated while leaving
the remaining dependencies set at their current values in Chart.lock.

Signed-off-by: Paul Gier <paul.gier@datastax.com>
pull/11362/head
Paul Gier 2 years ago
parent bed23120b0
commit 42ef0fe2ff
No known key found for this signature in database
GPG Key ID: 480CB62016073EFB

@ -40,6 +40,12 @@ rebuild the dependencies to an exact version.
Dependencies are not required to be represented in 'Chart.yaml'. For that
reason, an update command will not remove charts unless they are (a) present
in the Chart.yaml file, but (b) at the wrong version.
By default all dependencies will be updated to the latest version in the specified
range. For projects with an existing 'Chart.lock' file, one or more dependencies
can be selected for update without affecting other dependencies by using the
'-d/--dependency' option. Only the selected dependencies will be updated in
'Chart.lock' and the remaining dependencies will be unaffected.
`
// newDependencyUpdateCmd creates a new dependency update command.
@ -62,6 +68,7 @@ func newDependencyUpdateCmd(cfg *action.Configuration, out io.Writer) *cobra.Com
ChartPath: chartpath,
Keyring: client.Keyring,
SkipUpdate: client.SkipRefresh,
Dependencies: client.SelectedDependencies,
Getters: getter.All(settings),
RegistryClient: cfg.RegistryClient,
RepositoryConfig: settings.RepositoryConfig,
@ -79,6 +86,7 @@ func newDependencyUpdateCmd(cfg *action.Configuration, out io.Writer) *cobra.Com
f.BoolVar(&client.Verify, "verify", false, "verify the packages against signatures")
f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "keyring containing public keys")
f.BoolVar(&client.SkipRefresh, "skip-refresh", false, "do not refresh the local repository cache")
f.StringSliceVarP(&client.SelectedDependencies, "dependency", "d", []string{}, "update a single dependency by name, requires an existing Chart.lock file")
return cmd
}

@ -34,10 +34,11 @@ import (
//
// It provides the implementation of 'helm dependency' and its respective subcommands.
type Dependency struct {
Verify bool
Keyring string
SkipRefresh bool
ColumnWidth uint
Verify bool
Keyring string
SkipRefresh bool
ColumnWidth uint
SelectedDependencies []string
}
// NewDependency creates a new Dependency object with the given configuration.

@ -71,6 +71,8 @@ type Manager struct {
Keyring string
// SkipUpdate indicates that the repository should not be updated first.
SkipUpdate bool
// Dependencies is a list of specific dependencies to update instead of updating all.
Dependencies []string
// Getter collection for the operation
Getters []getter.Provider
RegistryClient *registry.Client
@ -124,7 +126,7 @@ func (m *Manager) Build() error {
if v2Sum != lock.Digest {
return errors.New("the lock file (requirements.lock) is out of sync with the dependencies file (requirements.yaml). Please update the dependencies")
}
} else {
} else if len(m.Dependencies) == 0 {
return errors.New("the lock file (Chart.lock) is out of sync with the dependencies file (Chart.yaml). Please update the dependencies")
}
}
@ -151,6 +153,7 @@ func (m *Manager) Build() error {
// negotiate versions based on that. It will download the versions
// from remote chart repositories unless SkipUpdate is true.
func (m *Manager) Update() error {
c, err := m.loadChartDir()
if err != nil {
return err
@ -190,9 +193,16 @@ func (m *Manager) Update() error {
}
}
// If the --dependency option is set, we want to keep the existing contents
// of Chart.lock and only upgrade the dependencies specified by the user.
depsToResolve, err := dependenciesToResolve(req, c.Lock, m.Dependencies)
if err != nil {
return err
}
// Now we need to find out which version of a chart best satisfies the
// dependencies in the Chart.yaml
lock, err := m.resolve(req, repoNames)
lock, err := m.resolve(depsToResolve, repoNames)
if err != nil {
return err
}
@ -896,3 +906,54 @@ func key(name string) (string, error) {
}
return hex.EncodeToString(hash.Sum(nil)), nil
}
// dependenciesToResolve takes the requested (req) dependencies from Chart.yaml and
// overrides the version with the matching dependency from Chart.lock, except for those
// dependencies which have been selected for update by the user.
// The intention is to allow a user to update only selected dependencies in Chart.yaml
// If no selected deps are specified, returns the original request to update all dependencies.
// Returns an error if lock is nil or if some any unselected dependencies are missing
// from Chart.lock.
func dependenciesToResolve(req []*chart.Dependency, lock *chart.Lock, selected []string) ([]*chart.Dependency, error) {
if len(selected) == 0 {
return req, nil
}
if lock == nil {
return nil, fmt.Errorf("file Chart.lock not found, the '-d/--dependency' requires an existing Chart.lock file")
}
depsToUpdate := []*chart.Dependency{}
for _, dep := range req {
if stringSliceContains(selected, dep.Name) {
depsToUpdate = append(depsToUpdate, dep)
} else {
lockedDep := getDependencyByName(lock.Dependencies, dep.Name)
if lockedDep == nil {
return nil, fmt.Errorf("dependency '%s' missing from Chart.lock, this dependency must be selected with '-d/--dependency' in order to update Chart.lock", dep.Name)
}
depsToUpdate = append(depsToUpdate, lockedDep)
}
}
return depsToUpdate, nil
}
// stringSliceContains returns true if the slice contains the given string,
// false otherwise.
func stringSliceContains(slice []string, str string) bool {
for _, s := range slice {
if s == str {
return true
}
}
return false
}
// getDependencyByName returns the named dependency from the given list,
// or nil if no matching dependency is found.
func getDependencyByName(deps []*chart.Dependency, name string) *chart.Dependency {
for _, dep := range deps {
if dep.Name == name {
return dep
}
}
return nil
}

@ -572,3 +572,44 @@ func TestKey(t *testing.T) {
}
}
}
func TestGetDependenciesToResolve(t *testing.T) {
chartReq := []*chart.Dependency{
{
Name: "foo",
Version: "1.x",
},
{
Name: "bar",
Version: "2.1.x",
},
}
chartLock := &chart.Lock{
Dependencies: []*chart.Dependency{
{
Name: "foo",
Version: "1.5",
},
{
Name: "bar",
Version: "2.1.2",
},
},
}
selectedDeps := []string{
"foo",
}
depsToResolve, err := dependenciesToResolve(chartReq, chartLock, selectedDeps)
if err != nil {
t.Errorf("failed to get dependency list with error: %v", err)
}
if len(depsToResolve) != 2 {
t.Errorf("incorrect number of deps expected 2, got %v", depsToResolve)
}
if depsToResolve[0].Version != "1.x" {
t.Errorf("incorrect version set for dependency '%s', expected 1.x, got %s", depsToResolve[0].Name, depsToResolve[0].Version)
}
if depsToResolve[1].Version != "2.1.2" {
t.Errorf("incorrect version set for dependency '%s', expected 2.1.2, got %s", depsToResolve[1].Name, depsToResolve[1].Version)
}
}

Loading…
Cancel
Save