diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 81085e1ef..d3c756b3b 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -97,6 +97,7 @@ func newRootCmd(out io.Writer) *cobra.Command { newPackageCmd(nil, out), newFetchCmd(out), newVerifyCmd(out), + newUpdateCmd(out), ) return cmd } diff --git a/cmd/helm/testdata/repositories.yaml b/cmd/helm/testdata/repositories.yaml new file mode 100644 index 000000000..6fe931e89 --- /dev/null +++ b/cmd/helm/testdata/repositories.yaml @@ -0,0 +1,2 @@ +charts: http://storage.googleapis.com/kubernetes-charts +local: http://localhost:8879/charts diff --git a/cmd/helm/update.go b/cmd/helm/update.go index ecd4710da..7ecb6afe7 100644 --- a/cmd/helm/update.go +++ b/cmd/helm/update.go @@ -19,6 +19,7 @@ package main import ( "errors" "fmt" + "io" "sync" "github.com/spf13/cobra" @@ -26,23 +27,39 @@ import ( "k8s.io/helm/pkg/repo" ) -var verboseUpdate bool +const updateDesc = ` +Update gets the latest information about charts from the respective chart repositories. +Information is cached locally, where it is used by commands like 'helm search'. +` -var updateCommand = &cobra.Command{ - Use: "update", - Aliases: []string{"up"}, - Short: "update information on available charts in the chart repositories", - RunE: runUpdate, +type updateCmd struct { + verbose bool + repoFile string + update func(map[string]string, bool, io.Writer) + out io.Writer } -func init() { - updateCommand.Flags().BoolVar(&verboseUpdate, "verbose", false, "verbose error messages") - RootCommand.AddCommand(updateCommand) +func newUpdateCmd(out io.Writer) *cobra.Command { + u := &updateCmd{ + out: out, + update: updateCharts, + repoFile: repositoriesFile(), + } + cmd := &cobra.Command{ + Use: "update", + Aliases: []string{"up"}, + Short: "update information on available charts in the chart repositories", + Long: updateDesc, + RunE: func(cmd *cobra.Command, args []string) error { + return u.run() + }, + } + cmd.Flags().BoolVar(&u.verbose, "verbose", false, "verbose error messages") + return cmd } -func runUpdate(cmd *cobra.Command, args []string) error { - - f, err := repo.LoadRepositoriesFile(repositoriesFile()) +func (u *updateCmd) run() error { + f, err := repo.LoadRepositoriesFile(u.repoFile) if err != nil { return err } @@ -51,12 +68,12 @@ func runUpdate(cmd *cobra.Command, args []string) error { return errors.New("no repositories found. You must add one before updating") } - updateCharts(f.Repositories, verboseUpdate) + u.update(f.Repositories, u.verbose, u.out) return nil } -func updateCharts(repos map[string]string, verbose bool) { - fmt.Println("Hang tight while we grab the latest from your chart repositories...") +func updateCharts(repos map[string]string, verbose bool, out io.Writer) { + fmt.Fprintln(out, "Hang tight while we grab the latest from your chart repositories...") var wg sync.WaitGroup for name, url := range repos { wg.Add(1) @@ -65,16 +82,16 @@ func updateCharts(repos map[string]string, verbose bool) { indexFileName := cacheDirectory(n + "-index.yaml") err := repo.DownloadIndexFile(n, u, indexFileName) if err != nil { - updateErr := "...Unable to get an update from the " + n + " chart repository" + updateErr := fmt.Sprintf("...Unable to get an update from the %q chart repository", n) if verbose { updateErr = updateErr + ": " + err.Error() } - fmt.Println(updateErr) + fmt.Fprintln(out, updateErr) } else { - fmt.Println("...Successfully got an update from the " + n + " chart repository") + fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", n) } }(name, url) } wg.Wait() - fmt.Println("Update Complete. Happy Helming!") + fmt.Fprintln(out, "Update Complete. Happy Helming!") } diff --git a/cmd/helm/update_test.go b/cmd/helm/update_test.go new file mode 100644 index 000000000..358622c81 --- /dev/null +++ b/cmd/helm/update_test.go @@ -0,0 +1,91 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "bytes" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestUpdateCmd(t *testing.T) { + out := bytes.NewBuffer(nil) + // Instead of using the HTTP updater, we provide our own for this test. + // The TestUpdateCharts test verifies the HTTP behavior independently. + updater := func(repos map[string]string, verbose bool, out io.Writer) { + if verbose { + for name := range repos { + fmt.Fprintln(out, name) + } + } + } + uc := &updateCmd{ + out: out, + update: updater, + verbose: true, + repoFile: "testdata/repositories.yaml", + } + uc.run() + + expect := "charts\nlocal\n" + if got := out.String(); got != expect { + t.Errorf("Expected %q, got %q", expect, got) + } +} + +const mockRepoIndex = ` +mychart-0.1.0: + name: mychart-0.1.0 + url: localhost:8879/charts/mychart-0.1.0.tgz + chartfile: + name: "" + home: "" + sources: [] + version: "" + description: "" + keywords: [] + maintainers: [] + engine: "" + icon: "" +` + +func TestUpdateCharts(t *testing.T) { + // This tests the repo in isolation. It creates a mock HTTP server that simply + // returns a static YAML file in the anticipate format. + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(mockRepoIndex)) + }) + srv := httptest.NewServer(handler) + defer srv.Close() + + buf := bytes.NewBuffer(nil) + repos := map[string]string{ + "charts": srv.URL, + } + updateCharts(repos, false, buf) + + got := buf.String() + if strings.Contains(got, "Unable to get an update") { + t.Errorf("Failed to get a repo: %q", got) + } + if !strings.Contains(got, "Update Complete.") { + t.Errorf("Update was not successful") + } +}