diff --git a/cmd/helm/dependency_update.go b/cmd/helm/dependency_update.go index cb6e9c0cc..7a2f55a5d 100644 --- a/cmd/helm/dependency_update.go +++ b/cmd/helm/dependency_update.go @@ -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 } diff --git a/pkg/action/dependency.go b/pkg/action/dependency.go index 3265f1f17..2cd7bc311 100644 --- a/pkg/action/dependency.go +++ b/pkg/action/dependency.go @@ -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. diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index ec4056d27..be6b7caae 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -70,6 +70,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 @@ -123,7 +125,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") } } @@ -150,6 +152,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 @@ -189,9 +192,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 } @@ -903,3 +913,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 +} diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go index db2487d16..81a18bb18 100644 --- a/pkg/downloader/manager_test.go +++ b/pkg/downloader/manager_test.go @@ -598,3 +598,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) + } +}