From 084476b2c55368d405eb305cbb3d194bf73e5a14 Mon Sep 17 00:00:00 2001 From: Josh Dolitsky Date: Fri, 1 Feb 2019 15:13:41 -0600 Subject: [PATCH] add extra chart command output Signed-off-by: Josh Dolitsky --- cmd/helm/chart.go | 16 +++--- cmd/helm/chart_export.go | 6 +- cmd/helm/chart_list.go | 6 +- cmd/helm/chart_pull.go | 6 +- cmd/helm/chart_push.go | 6 +- cmd/helm/chart_remove.go | 6 +- cmd/helm/chart_save.go | 6 +- cmd/helm/root.go | 2 +- pkg/action/chart_export.go | 5 +- pkg/action/chart_list.go | 6 +- pkg/action/chart_pull.go | 4 +- pkg/action/chart_push.go | 4 +- pkg/action/chart_remove.go | 4 +- pkg/action/chart_save.go | 3 +- pkg/registry/cache.go | 112 +++++++++++++++++++++++++------------ pkg/registry/client.go | 49 ++++++++++++++-- 16 files changed, 174 insertions(+), 67 deletions(-) diff --git a/cmd/helm/chart.go b/cmd/helm/chart.go index 1c13e12bd..8cfed8801 100644 --- a/cmd/helm/chart.go +++ b/cmd/helm/chart.go @@ -16,6 +16,8 @@ limitations under the License. package main import ( + "io" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -30,19 +32,19 @@ Example usage: $ helm chart pull [URL] ` -func newChartCmd(cfg *action.Configuration) *cobra.Command { +func newChartCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "chart", Short: "push, pull, tag, or remove Helm charts", Long: chartHelp, } cmd.AddCommand( - newChartListCmd(cfg), - newChartExportCmd(cfg), - newChartPullCmd(cfg), - newChartPushCmd(cfg), - newChartRemoveCmd(cfg), - newChartSaveCmd(cfg), + newChartListCmd(cfg, out), + newChartExportCmd(cfg, out), + newChartPullCmd(cfg, out), + newChartPushCmd(cfg, out), + newChartRemoveCmd(cfg, out), + newChartSaveCmd(cfg, out), ) return cmd } diff --git a/cmd/helm/chart_export.go b/cmd/helm/chart_export.go index 238021299..696a5243f 100644 --- a/cmd/helm/chart_export.go +++ b/cmd/helm/chart_export.go @@ -17,6 +17,8 @@ limitations under the License. package main import ( + "io" + "github.com/spf13/cobra" "k8s.io/helm/cmd/helm/require" @@ -27,7 +29,7 @@ const chartExportDesc = ` TODO ` -func newChartExportCmd(cfg *action.Configuration) *cobra.Command { +func newChartExportCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return &cobra.Command{ Use: "export [ref]", Short: "export a chart to directory", @@ -35,7 +37,7 @@ func newChartExportCmd(cfg *action.Configuration) *cobra.Command { Args: require.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { ref := args[0] - return action.NewChartExport(cfg).Run(ref) + return action.NewChartExport(cfg).Run(out, ref) }, } } diff --git a/cmd/helm/chart_list.go b/cmd/helm/chart_list.go index 6347a50a0..87794c5d1 100644 --- a/cmd/helm/chart_list.go +++ b/cmd/helm/chart_list.go @@ -17,6 +17,8 @@ limitations under the License. package main import ( + "io" + "github.com/spf13/cobra" "k8s.io/helm/pkg/action" @@ -26,14 +28,14 @@ const chartListDesc = ` TODO ` -func newChartListCmd(cfg *action.Configuration) *cobra.Command { +func newChartListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return &cobra.Command{ Use: "list", Aliases: []string{"ls"}, Short: "list all saved charts", Long: chartListDesc, RunE: func(cmd *cobra.Command, args []string) error { - return action.NewChartList(cfg).Run() + return action.NewChartList(cfg).Run(out) }, } } diff --git a/cmd/helm/chart_pull.go b/cmd/helm/chart_pull.go index 70c5ddb06..ced6273a9 100644 --- a/cmd/helm/chart_pull.go +++ b/cmd/helm/chart_pull.go @@ -17,6 +17,8 @@ limitations under the License. package main import ( + "io" + "github.com/spf13/cobra" "k8s.io/helm/cmd/helm/require" @@ -27,7 +29,7 @@ const chartPullDesc = ` TODO ` -func newChartPullCmd(cfg *action.Configuration) *cobra.Command { +func newChartPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return &cobra.Command{ Use: "pull [ref]", Short: "pull a chart from remote", @@ -35,7 +37,7 @@ func newChartPullCmd(cfg *action.Configuration) *cobra.Command { Args: require.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { ref := args[0] - return action.NewChartPull(cfg).Run(ref) + return action.NewChartPull(cfg).Run(out, ref) }, } } diff --git a/cmd/helm/chart_push.go b/cmd/helm/chart_push.go index eb3776104..e078b9069 100644 --- a/cmd/helm/chart_push.go +++ b/cmd/helm/chart_push.go @@ -17,6 +17,8 @@ limitations under the License. package main import ( + "io" + "github.com/spf13/cobra" "k8s.io/helm/cmd/helm/require" @@ -27,7 +29,7 @@ const chartPushDesc = ` TODO ` -func newChartPushCmd(cfg *action.Configuration) *cobra.Command { +func newChartPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return &cobra.Command{ Use: "push [ref]", Short: "push a chart to remote", @@ -35,7 +37,7 @@ func newChartPushCmd(cfg *action.Configuration) *cobra.Command { Args: require.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { ref := args[0] - return action.NewChartPush(cfg).Run(ref) + return action.NewChartPush(cfg).Run(out, ref) }, } } diff --git a/cmd/helm/chart_remove.go b/cmd/helm/chart_remove.go index 9609d51ae..082d1ed12 100644 --- a/cmd/helm/chart_remove.go +++ b/cmd/helm/chart_remove.go @@ -17,6 +17,8 @@ limitations under the License. package main import ( + "io" + "github.com/spf13/cobra" "k8s.io/helm/cmd/helm/require" @@ -27,7 +29,7 @@ const chartRemoveDesc = ` TODO ` -func newChartRemoveCmd(cfg *action.Configuration) *cobra.Command { +func newChartRemoveCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return &cobra.Command{ Use: "remove [ref]", Aliases: []string{"rm"}, @@ -36,7 +38,7 @@ func newChartRemoveCmd(cfg *action.Configuration) *cobra.Command { Args: require.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { ref := args[0] - return action.NewChartRemove(cfg).Run(ref) + return action.NewChartRemove(cfg).Run(out, ref) }, } } diff --git a/cmd/helm/chart_save.go b/cmd/helm/chart_save.go index 85e821796..860a366d3 100644 --- a/cmd/helm/chart_save.go +++ b/cmd/helm/chart_save.go @@ -17,6 +17,8 @@ limitations under the License. package main import ( + "io" + "github.com/spf13/cobra" "k8s.io/helm/cmd/helm/require" @@ -27,7 +29,7 @@ const chartSaveDesc = ` TODO ` -func newChartSaveCmd(cfg *action.Configuration) *cobra.Command { +func newChartSaveCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return &cobra.Command{ Use: "save [path] [ref]", Short: "save a chart directory", @@ -36,7 +38,7 @@ func newChartSaveCmd(cfg *action.Configuration) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { path := args[0] ref := args[1] - return action.NewChartSave(cfg).Run(path, ref) + return action.NewChartSave(cfg).Run(out, path, ref) }, } } diff --git a/cmd/helm/root.go b/cmd/helm/root.go index 0407a25fe..ec47071d7 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -89,7 +89,7 @@ func newRootCmd(c helm.Interface, actionConfig *action.Configuration, out io.Wri newRepoCmd(out), newSearchCmd(out), newVerifyCmd(out), - newChartCmd(actionConfig), + newChartCmd(actionConfig, out), // release commands newGetCmd(c, out), diff --git a/pkg/action/chart_export.go b/pkg/action/chart_export.go index 9625f8e87..088469342 100644 --- a/pkg/action/chart_export.go +++ b/pkg/action/chart_export.go @@ -17,6 +17,8 @@ limitations under the License. package action import ( + "fmt" + "io" "io/ioutil" "os" @@ -37,7 +39,7 @@ func NewChartExport(cfg *Configuration) *ChartExport { } // Run executes the chart export operation -func (a *ChartExport) Run(ref string) error { +func (a *ChartExport) Run(out io.Writer, ref string) error { r, err := registry.ParseReference(ref) if err != nil { return err @@ -66,5 +68,6 @@ func (a *ChartExport) Run(ref string) error { return err } + fmt.Fprintf(out, "Exported to %s/\n", ch.Metadata.Name) return nil } diff --git a/pkg/action/chart_list.go b/pkg/action/chart_list.go index d26de578f..db764b3a3 100644 --- a/pkg/action/chart_list.go +++ b/pkg/action/chart_list.go @@ -16,6 +16,10 @@ limitations under the License. package action +import ( + "io" +) + // ChartList performs a chart list operation. type ChartList struct { cfg *Configuration @@ -29,6 +33,6 @@ func NewChartList(cfg *Configuration) *ChartList { } // Run executes the chart list operation -func (a *ChartList) Run() error { +func (a *ChartList) Run(out io.Writer) error { return a.cfg.RegistryClient.PrintChartTable() } diff --git a/pkg/action/chart_pull.go b/pkg/action/chart_pull.go index fe2de4cf3..eca743deb 100644 --- a/pkg/action/chart_pull.go +++ b/pkg/action/chart_pull.go @@ -17,6 +17,8 @@ limitations under the License. package action import ( + "io" + "k8s.io/helm/pkg/registry" ) @@ -33,7 +35,7 @@ func NewChartPull(cfg *Configuration) *ChartPull { } // Run executes the chart pull operation -func (a *ChartPull) Run(ref string) error { +func (a *ChartPull) Run(out io.Writer, ref string) error { r, err := registry.ParseReference(ref) if err != nil { return err diff --git a/pkg/action/chart_push.go b/pkg/action/chart_push.go index 6ff7205e6..229d62c4a 100644 --- a/pkg/action/chart_push.go +++ b/pkg/action/chart_push.go @@ -17,6 +17,8 @@ limitations under the License. package action import ( + "io" + "k8s.io/helm/pkg/registry" ) @@ -33,7 +35,7 @@ func NewChartPush(cfg *Configuration) *ChartPush { } // Run executes the chart push operation -func (a *ChartPush) Run(ref string) error { +func (a *ChartPush) Run(out io.Writer, ref string) error { r, err := registry.ParseReference(ref) if err != nil { return err diff --git a/pkg/action/chart_remove.go b/pkg/action/chart_remove.go index ca2dab705..dbf677b76 100644 --- a/pkg/action/chart_remove.go +++ b/pkg/action/chart_remove.go @@ -17,6 +17,8 @@ limitations under the License. package action import ( + "io" + "k8s.io/helm/pkg/registry" ) @@ -33,7 +35,7 @@ func NewChartRemove(cfg *Configuration) *ChartRemove { } // Run executes the chart remove operation -func (a *ChartRemove) Run(ref string) error { +func (a *ChartRemove) Run(out io.Writer, ref string) error { r, err := registry.ParseReference(ref) if err != nil { return err diff --git a/pkg/action/chart_save.go b/pkg/action/chart_save.go index 3ced9e7a1..5d756381f 100644 --- a/pkg/action/chart_save.go +++ b/pkg/action/chart_save.go @@ -17,6 +17,7 @@ limitations under the License. package action import ( + "io" "path/filepath" "k8s.io/helm/pkg/chart/loader" @@ -36,7 +37,7 @@ func NewChartSave(cfg *Configuration) *ChartSave { } // Run executes the chart save operation -func (a *ChartSave) Run(path string, ref string) error { +func (a *ChartSave) Run(out io.Writer, path string, ref string) error { path, err := filepath.Abs(path) if err != nil { return err diff --git a/pkg/registry/cache.go b/pkg/registry/cache.go index a661e3141..30136efe8 100644 --- a/pkg/registry/cache.go +++ b/pkg/registry/cache.go @@ -153,65 +153,83 @@ func (cache *filesystemCache) LoadReference(ref *Reference) ([]ocispec.Descripto return nil, err } + printChartSummary(cache.out, metaLayer, contentLayer) layers := []ocispec.Descriptor{metaLayer, contentLayer} return layers, nil } -func (cache *filesystemCache) StoreReference(ref *Reference, layers []ocispec.Descriptor) error { - tagDir := mkdir(filepath.Join(cache.rootDir, "refs", ref.Locator, "tags", tagOrDefault(ref.Object))) +func (cache *filesystemCache) StoreReference(ref *Reference, layers []ocispec.Descriptor) (bool, error) { + tag := tagOrDefault(ref.Object) + tagDir := mkdir(filepath.Join(cache.rootDir, "refs", ref.Locator, "tags", tag)) // Retrieve just the meta and content layers metaLayer, contentLayer, err := extractLayers(layers) if err != nil { - return err + return false, err } // Extract chart name and version name, version, err := extractChartNameVersionFromLayer(contentLayer) if err != nil { - return err + return false, err } // Create chart file chartPath, err := createChartFile(filepath.Join(cache.rootDir, "charts"), name, version) if err != nil { - return err + return false, err } // Create chart symlink err = createSymlink(chartPath, filepath.Join(tagDir, "chart")) if err != nil { - return err + return false, err } // Save meta blob - _, metaJSONRaw, ok := cache.store.Get(metaLayer) - if !ok { - return errors.New("error retrieving meta layer") - } - metaPath, err := createDigestFile(filepath.Join(cache.rootDir, "blobs", "meta"), metaJSONRaw) - if err != nil { - return err + metaExists, metaPath := digestPath(filepath.Join(cache.rootDir, "blobs", "meta"), metaLayer.Digest) + if !metaExists { + fmt.Fprintf(cache.out, "%s: Saving meta (%s)\n", + shortDigest(metaLayer.Digest.Hex()), byteCountBinary(metaLayer.Size)) + _, metaJSONRaw, ok := cache.store.Get(metaLayer) + if !ok { + return false, errors.New("error retrieving meta layer") + } + err = writeFile(metaPath, metaJSONRaw) + if err != nil { + return false, err + } } // Create meta symlink err = createSymlink(metaPath, filepath.Join(tagDir, "meta")) if err != nil { - return err + return false, err } // Save content blob - _, contentRaw, ok := cache.store.Get(contentLayer) - if !ok { - return errors.New("error retrieving content layer") + contentExists, contentPath := digestPath(filepath.Join(cache.rootDir, "blobs", "content"), contentLayer.Digest) + if !contentExists { + fmt.Fprintf(cache.out, "%s: Saving content (%s)\n", + shortDigest(contentLayer.Digest.Hex()), byteCountBinary(contentLayer.Size)) + _, contentRaw, ok := cache.store.Get(contentLayer) + if !ok { + return false, errors.New("error retrieving content layer") + } + err = writeFile(contentPath, contentRaw) + if err != nil { + return false, err + } } - contentPath, err := createDigestFile(filepath.Join(cache.rootDir, "blobs", "content"), contentRaw) + + // Create content symlink + err = createSymlink(contentPath, filepath.Join(tagDir, "content")) if err != nil { - return err + return false, err } - // Create content symlink - return createSymlink(contentPath, filepath.Join(tagDir, "content")) + printChartSummary(cache.out, metaLayer, contentLayer) + return metaExists && contentExists, nil } func (cache *filesystemCache) DeleteReference(ref *Reference) error { @@ -226,6 +244,22 @@ func (cache *filesystemCache) TableRows() ([][]string, error) { return getRefsSorted(filepath.Join(cache.rootDir, "refs")) } +// printChartSummary prints details about a chart layers +func printChartSummary(out io.Writer, metaLayer ocispec.Descriptor, contentLayer ocispec.Descriptor) { + fmt.Fprintf(out, "Name: %s\n", contentLayer.Annotations[HelmChartNameAnnotation]) + fmt.Fprintf(out, "Version: %s\n", contentLayer.Annotations[HelmChartVersionAnnotation]) + fmt.Fprintf(out, "Meta: %s\n", metaLayer.Digest) + fmt.Fprintf(out, "Content: %s\n", contentLayer.Digest) +} + +// fileExists determines if a file exists +func fileExists(path string) bool { + if _, err := os.Stat(path); os.IsNotExist(err) { + return false + } + return true +} + // mkdir will create a directory (no error check) and return the path func mkdir(dir string) string { os.MkdirAll(dir, 0755) @@ -318,20 +352,18 @@ func createChartFile(chartsRootDir string, name string, version string) (string, return chartPath, nil } -// createDigestFile calcultaes the sha256 digest of some content and creates a file, returning the path -func createDigestFile(rootDir string, c []byte) (string, error) { - digest := checksum.FromBytes(c).String() - digestLeft, digestRight := splitDigest(digest) - pathDir := filepath.Join(rootDir, "sha256", digestLeft) - path := filepath.Join(pathDir, digestRight) - if _, err := os.Stat(path); err != nil && os.IsNotExist(err) { - os.MkdirAll(pathDir, 0755) - err := ioutil.WriteFile(path, c, 0644) - if err != nil { - return "", err - } - } - return path, nil +// digestPath returns the path to addressable content, and whether the file exists +func digestPath(rootDir string, digest checksum.Digest) (bool, string) { + digestLeft, digestRight := splitDigest(digest.Hex()) + path := filepath.Join(rootDir, "sha256", digestLeft, digestRight) + exists := fileExists(path) + return exists, path +} + +// writeFile creates a path, ensuring parent directory +func writeFile(path string, c []byte) error { + os.MkdirAll(filepath.Dir(path), 0755) + return ioutil.WriteFile(path, c, 0644) } // splitDigest returns a sha256 digest in two parts, on with first 2 chars and one with second 62 chars @@ -367,6 +399,14 @@ func tagOrDefault(tag string) string { return HelmChartDefaultTag } +// shortDigest returns first 7 characters of a sha256 digest +func shortDigest(digest string) string { + if len(digest) == 64 { + return digest[:7] + } + return digest +} + // getRefsSorted returns a map of all refs stored in a refsRootDir func getRefsSorted(refsRootDir string) ([][]string, error) { refsMap := map[string]map[string]string{} @@ -404,7 +444,7 @@ func getRefsSorted(refsRootDir string) ([][]string, error) { // Make sure the filename looks like a sha256 digest (64 chars) if len(digest) == 64 { - refsMap[ref]["digest"] = digest[:7] + refsMap[ref]["digest"] = shortDigest(digest) refsMap[ref]["size"] = byteCountBinary(destFileInfo.Size()) refsMap[ref]["created"] = units.HumanDuration(time.Now().UTC().Sub(destFileInfo.ModTime())) } diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 68cb5d314..3c4823467 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -59,36 +59,63 @@ func NewClient(options *ClientOptions) *Client { // PushChart uploads a chart to a registry func (c *Client) PushChart(ref *Reference) error { + c.setDefaultTag(ref) + fmt.Fprintf(c.out, "The push refers to repository [%s]\n", ref.Locator) layers, err := c.cache.LoadReference(ref) if err != nil { return err } err = oras.Push(context.Background(), c.resolver, ref.String(), c.cache.store, layers) - return err + if err != nil { + return err + } + var totalSize int64 + for _, layer := range layers { + totalSize += layer.Size + } + fmt.Fprintf(c.out, + "%s: pushed to remote (%d layers, %s total)\n", ref.Object, len(layers), byteCountBinary(totalSize)) + return nil } // PullChart downloads a chart from a registry func (c *Client) PullChart(ref *Reference) error { + c.setDefaultTag(ref) + fmt.Fprintf(c.out, "%s: Pulling from %s\n", ref.Object, ref.Locator) layers, err := oras.Pull(context.Background(), c.resolver, ref.String(), c.cache.store, KnownMediaTypes()...) if err != nil { return err } - err = c.cache.StoreReference(ref, layers) - return err + exists, err := c.cache.StoreReference(ref, layers) + if err != nil { + return err + } + if !exists { + fmt.Fprintf(c.out, "Status: Downloaded newer chart for %s:%s\n", ref.Locator, ref.Object) + } else { + fmt.Fprintf(c.out, "Status: Chart is up to date for %s:%s\n", ref.Locator, ref.Object) + } + return nil } // SaveChart stores a copy of chart in local cache func (c *Client) SaveChart(ch *chart.Chart, ref *Reference) error { + c.setDefaultTag(ref) layers, err := c.cache.ChartToLayers(ch) if err != nil { return err } - err = c.cache.StoreReference(ref, layers) - return err + _, err = c.cache.StoreReference(ref, layers) + if err != nil { + return err + } + fmt.Fprintf(c.out, "%s: saved\n", ref.Object) + return nil } // LoadChart retrieves a chart object by reference func (c *Client) LoadChart(ref *Reference) (*chart.Chart, error) { + c.setDefaultTag(ref) layers, err := c.cache.LoadReference(ref) if err != nil { return nil, err @@ -99,7 +126,12 @@ func (c *Client) LoadChart(ref *Reference) (*chart.Chart, error) { // RemoveChart deletes a locally saved chart func (c *Client) RemoveChart(ref *Reference) error { + c.setDefaultTag(ref) err := c.cache.DeleteReference(ref) + if err != nil { + return err + } + fmt.Fprintf(c.out, "%s: removed\n", ref.Object) return err } @@ -120,3 +152,10 @@ func (c *Client) PrintChartTable() error { fmt.Fprintln(c.out, table.String()) return nil } + +func (c *Client) setDefaultTag(ref *Reference) { + if ref.Object == "" { + ref.Object = HelmChartDefaultTag + fmt.Fprintf(c.out, "Using default tag: %s\n", HelmChartDefaultTag) + } +}