diff --git a/cmd/helm/dependency_build.go b/cmd/helm/dependency_build.go index 2cf0c6c81..624bc6c20 100644 --- a/cmd/helm/dependency_build.go +++ b/cmd/helm/dependency_build.go @@ -64,6 +64,7 @@ func newDependencyBuildCmd(cfg *action.Configuration, out io.Writer) *cobra.Comm RepositoryConfig: settings.RepositoryConfig, RepositoryCache: settings.RepositoryCache, Debug: settings.Debug, + OptimizedUpdate: client.OptimizedUpdate, } if client.Verify { man.Verify = downloader.VerifyIfPossible @@ -80,6 +81,7 @@ func newDependencyBuildCmd(cfg *action.Configuration, out io.Writer) *cobra.Comm 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.BoolVar(&client.OptimizedUpdate, "optimized-update", false, "Refresh only the repos in chart.yaml") return cmd } diff --git a/cmd/helm/dependency_update.go b/cmd/helm/dependency_update.go index cb6e9c0cc..be6d834df 100644 --- a/cmd/helm/dependency_update.go +++ b/cmd/helm/dependency_update.go @@ -67,6 +67,7 @@ func newDependencyUpdateCmd(cfg *action.Configuration, out io.Writer) *cobra.Com RepositoryConfig: settings.RepositoryConfig, RepositoryCache: settings.RepositoryCache, Debug: settings.Debug, + OptimizedUpdate: client.OptimizedUpdate, } if client.Verify { man.Verify = downloader.VerifyAlways @@ -79,6 +80,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.BoolVar(&client.OptimizedUpdate, "optimized-update", false, "Refresh only the repos in chart.yaml") return cmd } diff --git a/pkg/action/dependency.go b/pkg/action/dependency.go index 3265f1f17..1ce340d25 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 + OptimizedUpdate bool } // NewDependency creates a new Dependency object with the given configuration. diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index ec4056d27..0f8fe398e 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 + // Update repos only in the chart.yaml + OptimizedUpdate bool // Getter collection for the operation Getters []getter.Provider RegistryClient *registry.Client @@ -134,10 +136,17 @@ func (m *Manager) Build() error { } if !m.SkipUpdate { - // For each repo in the file, update the cached copy of that repo - if err := m.UpdateRepositories(); err != nil { - return err + if !m.OptimizedUpdate { + if err := m.UpdateRepositories(); err != nil { + return err + } + } else { + // For each repo in the file, update the cached copy of that repo + if err := m.UpdateRepositoriesInChart(c); err != nil { + return err + } } + } // Now we need to fetch every package here into charts/ @@ -184,9 +193,17 @@ func (m *Manager) Update() error { // For each of the repositories Helm is configured to know about, update // the index information locally. if !m.SkipUpdate { - if err := m.UpdateRepositories(); err != nil { - return err + if !m.OptimizedUpdate { + if err := m.UpdateRepositories(); err != nil { + return err + } + } else { + // For each repo in the file, update the cached copy of that repo + if err := m.UpdateRepositoriesInChart(c); err != nil { + return err + } } + } // Now we need to find out which version of a chart best satisfies the @@ -648,18 +665,42 @@ func (m *Manager) UpdateRepositories() error { return err } repos := rf.Repositories - 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. - if err := m.parallelRepoUpdate(repos); err != nil { - return err + if err := m.parallelRepoUpdate(repos); err != nil { + return err + } + return nil +} + +// UpdateRepositoriesInChart updates only the local repos in the chart.yaml to the latest. +func (m *Manager) UpdateRepositoriesInChart(c *chart.Chart) error { + rf, err := loadRepoConfig(m.RepositoryConfig) + if err != nil { + return err + } + repos := []*repo.Entry{} + // Map of repos in the chart yaml file + reposRequired := map[string]bool{} + for _, d := range c.Metadata.Dependencies { + reposRequired[d.Repository] = true + } + //Only updating repos in chart yaml + for _, e := range rf.Repositories { + if _, isMapContainsKey := reposRequired[e.URL]; isMapContainsKey { + repos = append(repos, e) } - fmt.Fprintln(m.Out, "Update Complete. ⎈Happy Helming!⎈") + } + if err := m.parallelRepoUpdate(repos); err != nil { + return err } return nil } func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error { + if len(repos) <= 0 { + return nil + } + fmt.Fprintln(m.Out, "Hang tight while we grab the latest from your chart repositories...") + // This prints warnings straight to out. var wg sync.WaitGroup for _, c := range repos { @@ -692,6 +733,7 @@ func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error { } wg.Wait() + fmt.Fprintln(m.Out, "Update Complete. ⎈Happy Helming!⎈") return nil } diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go index db2487d16..9b01cb091 100644 --- a/pkg/downloader/manager_test.go +++ b/pkg/downloader/manager_test.go @@ -598,3 +598,57 @@ func TestKey(t *testing.T) { } } } + +// See issue https://github.com/helm/helm/issues/11509 +func TestUpdateOnlyRequiredRepos(t *testing.T) { + // Set up a fake repo + srv, err := repotest.NewTempServerWithCleanupAndMultipleRepos(t, "testdata/*.tgz*") + + if err != nil { + t.Fatal(err) + } + defer srv.Stop() + if err := srv.LinkIndices(); err != nil { + t.Fatal(err) + } + dir := func(p ...string) string { + return filepath.Join(append([]string{srv.Root()}, p...)...) + } + + // Save a chart + c := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "with-dependency", + Version: "0.1.0", + APIVersion: "v2", + Dependencies: []*chart.Dependency{{ + Name: "local-subchart", + Version: ">=0.1.0", + Repository: srv.URL(), + }}, + }, + } + if err := chartutil.SaveDir(c, dir()); err != nil { + t.Fatal(err) + } + + // Set-up a manager + b := bytes.NewBuffer(nil) + g := getter.Providers{getter.Provider{ + Schemes: []string{"http", "https"}, + New: getter.NewHTTPGetter, + }} + m := &Manager{ + ChartPath: dir(c.Metadata.Name), + Out: b, + Getters: g, + RepositoryConfig: dir("repositories.yaml"), + RepositoryCache: dir(), + OptimizedUpdate: true, + } + + err = m.Update() + if err != nil { + t.Fatal(err) + } +} diff --git a/pkg/repo/repotest/server.go b/pkg/repo/repotest/server.go index 4a86707cf..5542072d1 100644 --- a/pkg/repo/repotest/server.go +++ b/pkg/repo/repotest/server.go @@ -54,6 +54,21 @@ func NewTempServerWithCleanup(t *testing.T, glob string) (*Server, error) { return srv, err } +// Set up a server with multiple fake repo +func NewTempServerWithCleanupAndMultipleRepos(t *testing.T, glob string) (*Server, error) { + srv, tdir, err := NewTempServerWithMultipleRepos(glob) + urls := []string{srv.URL(), "http://foobarbazz:9001"} + if err := setTestingRepositories(urls, filepath.Join(tdir, "repositories.yaml")); err != nil { + panic(err) + } + if _, err := srv.CopyCharts(glob); err != nil { + srv.Stop() + return srv, err + } + t.Cleanup(func() { os.RemoveAll(srv.docroot) }) + return srv, err +} + // Set up a fake repo with basic auth enabled func NewTempServerWithCleanupAndBasicAuth(t *testing.T, glob string) *Server { srv, err := NewTempServerWithCleanup(t, glob) @@ -264,6 +279,23 @@ func NewTempServer(glob string) (*Server, error) { return srv, nil } +func NewTempServerWithMultipleRepos(glob string) (*Server, string, error) { + tdir, err := ioutil.TempDir("", "helm-repotest-") + if err != nil { + return nil, tdir, err + } + srv := NewServer(tdir) + + if glob != "" { + if _, err := srv.CopyCharts(glob); err != nil { + srv.Stop() + return srv, tdir, err + } + } + + return srv, tdir, nil +} + // NewServer creates a repository server for testing. // // docroot should be a temp dir managed by the caller. @@ -424,3 +456,16 @@ func setTestingRepository(url, fname string) error { }) return r.WriteFile(fname, 0640) } + +// setTestingRepository sets up a testing repository.yaml with only the given URL. +func setTestingRepositories(urls []string, fname string) error { + r := repo.NewFile() + for _, url := range urls { + r.Add(&repo.Entry{ + Name: "test", + URL: url, + }) + } + + return r.WriteFile(fname, 0644) +}