add extra chart command output

Signed-off-by: Josh Dolitsky <jdolitsky@gmail.com>
pull/5243/head
Josh Dolitsky 7 years ago
parent acdd17fb4b
commit 084476b2c5

@ -16,6 +16,8 @@ limitations under the License.
package main package main
import ( import (
"io"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -30,19 +32,19 @@ Example usage:
$ helm chart pull [URL] $ helm chart pull [URL]
` `
func newChartCmd(cfg *action.Configuration) *cobra.Command { func newChartCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "chart", Use: "chart",
Short: "push, pull, tag, or remove Helm charts", Short: "push, pull, tag, or remove Helm charts",
Long: chartHelp, Long: chartHelp,
} }
cmd.AddCommand( cmd.AddCommand(
newChartListCmd(cfg), newChartListCmd(cfg, out),
newChartExportCmd(cfg), newChartExportCmd(cfg, out),
newChartPullCmd(cfg), newChartPullCmd(cfg, out),
newChartPushCmd(cfg), newChartPushCmd(cfg, out),
newChartRemoveCmd(cfg), newChartRemoveCmd(cfg, out),
newChartSaveCmd(cfg), newChartSaveCmd(cfg, out),
) )
return cmd return cmd
} }

@ -17,6 +17,8 @@ limitations under the License.
package main package main
import ( import (
"io"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
@ -27,7 +29,7 @@ const chartExportDesc = `
TODO TODO
` `
func newChartExportCmd(cfg *action.Configuration) *cobra.Command { func newChartExportCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "export [ref]", Use: "export [ref]",
Short: "export a chart to directory", Short: "export a chart to directory",
@ -35,7 +37,7 @@ func newChartExportCmd(cfg *action.Configuration) *cobra.Command {
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
ref := args[0] ref := args[0]
return action.NewChartExport(cfg).Run(ref) return action.NewChartExport(cfg).Run(out, ref)
}, },
} }
} }

@ -17,6 +17,8 @@ limitations under the License.
package main package main
import ( import (
"io"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/pkg/action" "k8s.io/helm/pkg/action"
@ -26,14 +28,14 @@ const chartListDesc = `
TODO TODO
` `
func newChartListCmd(cfg *action.Configuration) *cobra.Command { func newChartListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "list", Use: "list",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Short: "list all saved charts", Short: "list all saved charts",
Long: chartListDesc, Long: chartListDesc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return action.NewChartList(cfg).Run() return action.NewChartList(cfg).Run(out)
}, },
} }
} }

@ -17,6 +17,8 @@ limitations under the License.
package main package main
import ( import (
"io"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
@ -27,7 +29,7 @@ const chartPullDesc = `
TODO TODO
` `
func newChartPullCmd(cfg *action.Configuration) *cobra.Command { func newChartPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "pull [ref]", Use: "pull [ref]",
Short: "pull a chart from remote", Short: "pull a chart from remote",
@ -35,7 +37,7 @@ func newChartPullCmd(cfg *action.Configuration) *cobra.Command {
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
ref := args[0] ref := args[0]
return action.NewChartPull(cfg).Run(ref) return action.NewChartPull(cfg).Run(out, ref)
}, },
} }
} }

@ -17,6 +17,8 @@ limitations under the License.
package main package main
import ( import (
"io"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
@ -27,7 +29,7 @@ const chartPushDesc = `
TODO TODO
` `
func newChartPushCmd(cfg *action.Configuration) *cobra.Command { func newChartPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "push [ref]", Use: "push [ref]",
Short: "push a chart to remote", Short: "push a chart to remote",
@ -35,7 +37,7 @@ func newChartPushCmd(cfg *action.Configuration) *cobra.Command {
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
ref := args[0] ref := args[0]
return action.NewChartPush(cfg).Run(ref) return action.NewChartPush(cfg).Run(out, ref)
}, },
} }
} }

@ -17,6 +17,8 @@ limitations under the License.
package main package main
import ( import (
"io"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
@ -27,7 +29,7 @@ const chartRemoveDesc = `
TODO TODO
` `
func newChartRemoveCmd(cfg *action.Configuration) *cobra.Command { func newChartRemoveCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "remove [ref]", Use: "remove [ref]",
Aliases: []string{"rm"}, Aliases: []string{"rm"},
@ -36,7 +38,7 @@ func newChartRemoveCmd(cfg *action.Configuration) *cobra.Command {
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
ref := args[0] ref := args[0]
return action.NewChartRemove(cfg).Run(ref) return action.NewChartRemove(cfg).Run(out, ref)
}, },
} }
} }

@ -17,6 +17,8 @@ limitations under the License.
package main package main
import ( import (
"io"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
@ -27,7 +29,7 @@ const chartSaveDesc = `
TODO TODO
` `
func newChartSaveCmd(cfg *action.Configuration) *cobra.Command { func newChartSaveCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "save [path] [ref]", Use: "save [path] [ref]",
Short: "save a chart directory", Short: "save a chart directory",
@ -36,7 +38,7 @@ func newChartSaveCmd(cfg *action.Configuration) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
path := args[0] path := args[0]
ref := args[1] ref := args[1]
return action.NewChartSave(cfg).Run(path, ref) return action.NewChartSave(cfg).Run(out, path, ref)
}, },
} }
} }

@ -89,7 +89,7 @@ func newRootCmd(c helm.Interface, actionConfig *action.Configuration, out io.Wri
newRepoCmd(out), newRepoCmd(out),
newSearchCmd(out), newSearchCmd(out),
newVerifyCmd(out), newVerifyCmd(out),
newChartCmd(actionConfig), newChartCmd(actionConfig, out),
// release commands // release commands
newGetCmd(c, out), newGetCmd(c, out),

@ -17,6 +17,8 @@ limitations under the License.
package action package action
import ( import (
"fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
@ -37,7 +39,7 @@ func NewChartExport(cfg *Configuration) *ChartExport {
} }
// Run executes the chart export operation // 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) r, err := registry.ParseReference(ref)
if err != nil { if err != nil {
return err return err
@ -66,5 +68,6 @@ func (a *ChartExport) Run(ref string) error {
return err return err
} }
fmt.Fprintf(out, "Exported to %s/\n", ch.Metadata.Name)
return nil return nil
} }

@ -16,6 +16,10 @@ limitations under the License.
package action package action
import (
"io"
)
// ChartList performs a chart list operation. // ChartList performs a chart list operation.
type ChartList struct { type ChartList struct {
cfg *Configuration cfg *Configuration
@ -29,6 +33,6 @@ func NewChartList(cfg *Configuration) *ChartList {
} }
// Run executes the chart list operation // Run executes the chart list operation
func (a *ChartList) Run() error { func (a *ChartList) Run(out io.Writer) error {
return a.cfg.RegistryClient.PrintChartTable() return a.cfg.RegistryClient.PrintChartTable()
} }

@ -17,6 +17,8 @@ limitations under the License.
package action package action
import ( import (
"io"
"k8s.io/helm/pkg/registry" "k8s.io/helm/pkg/registry"
) )
@ -33,7 +35,7 @@ func NewChartPull(cfg *Configuration) *ChartPull {
} }
// Run executes the chart pull operation // 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) r, err := registry.ParseReference(ref)
if err != nil { if err != nil {
return err return err

@ -17,6 +17,8 @@ limitations under the License.
package action package action
import ( import (
"io"
"k8s.io/helm/pkg/registry" "k8s.io/helm/pkg/registry"
) )
@ -33,7 +35,7 @@ func NewChartPush(cfg *Configuration) *ChartPush {
} }
// Run executes the chart push operation // 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) r, err := registry.ParseReference(ref)
if err != nil { if err != nil {
return err return err

@ -17,6 +17,8 @@ limitations under the License.
package action package action
import ( import (
"io"
"k8s.io/helm/pkg/registry" "k8s.io/helm/pkg/registry"
) )
@ -33,7 +35,7 @@ func NewChartRemove(cfg *Configuration) *ChartRemove {
} }
// Run executes the chart remove operation // 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) r, err := registry.ParseReference(ref)
if err != nil { if err != nil {
return err return err

@ -17,6 +17,7 @@ limitations under the License.
package action package action
import ( import (
"io"
"path/filepath" "path/filepath"
"k8s.io/helm/pkg/chart/loader" "k8s.io/helm/pkg/chart/loader"
@ -36,7 +37,7 @@ func NewChartSave(cfg *Configuration) *ChartSave {
} }
// Run executes the chart save operation // 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) path, err := filepath.Abs(path)
if err != nil { if err != nil {
return err return err

@ -153,65 +153,83 @@ func (cache *filesystemCache) LoadReference(ref *Reference) ([]ocispec.Descripto
return nil, err return nil, err
} }
printChartSummary(cache.out, metaLayer, contentLayer)
layers := []ocispec.Descriptor{metaLayer, contentLayer} layers := []ocispec.Descriptor{metaLayer, contentLayer}
return layers, nil return layers, nil
} }
func (cache *filesystemCache) StoreReference(ref *Reference, layers []ocispec.Descriptor) error { func (cache *filesystemCache) StoreReference(ref *Reference, layers []ocispec.Descriptor) (bool, error) {
tagDir := mkdir(filepath.Join(cache.rootDir, "refs", ref.Locator, "tags", tagOrDefault(ref.Object))) tag := tagOrDefault(ref.Object)
tagDir := mkdir(filepath.Join(cache.rootDir, "refs", ref.Locator, "tags", tag))
// Retrieve just the meta and content layers // Retrieve just the meta and content layers
metaLayer, contentLayer, err := extractLayers(layers) metaLayer, contentLayer, err := extractLayers(layers)
if err != nil { if err != nil {
return err return false, err
} }
// Extract chart name and version // Extract chart name and version
name, version, err := extractChartNameVersionFromLayer(contentLayer) name, version, err := extractChartNameVersionFromLayer(contentLayer)
if err != nil { if err != nil {
return err return false, err
} }
// Create chart file // Create chart file
chartPath, err := createChartFile(filepath.Join(cache.rootDir, "charts"), name, version) chartPath, err := createChartFile(filepath.Join(cache.rootDir, "charts"), name, version)
if err != nil { if err != nil {
return err return false, err
} }
// Create chart symlink // Create chart symlink
err = createSymlink(chartPath, filepath.Join(tagDir, "chart")) err = createSymlink(chartPath, filepath.Join(tagDir, "chart"))
if err != nil { if err != nil {
return err return false, err
} }
// Save meta blob // Save meta blob
_, metaJSONRaw, ok := cache.store.Get(metaLayer) metaExists, metaPath := digestPath(filepath.Join(cache.rootDir, "blobs", "meta"), metaLayer.Digest)
if !ok { if !metaExists {
return errors.New("error retrieving meta layer") fmt.Fprintf(cache.out, "%s: Saving meta (%s)\n",
} shortDigest(metaLayer.Digest.Hex()), byteCountBinary(metaLayer.Size))
metaPath, err := createDigestFile(filepath.Join(cache.rootDir, "blobs", "meta"), metaJSONRaw) _, metaJSONRaw, ok := cache.store.Get(metaLayer)
if err != nil { if !ok {
return err return false, errors.New("error retrieving meta layer")
}
err = writeFile(metaPath, metaJSONRaw)
if err != nil {
return false, err
}
} }
// Create meta symlink // Create meta symlink
err = createSymlink(metaPath, filepath.Join(tagDir, "meta")) err = createSymlink(metaPath, filepath.Join(tagDir, "meta"))
if err != nil { if err != nil {
return err return false, err
} }
// Save content blob // Save content blob
_, contentRaw, ok := cache.store.Get(contentLayer) contentExists, contentPath := digestPath(filepath.Join(cache.rootDir, "blobs", "content"), contentLayer.Digest)
if !ok { if !contentExists {
return errors.New("error retrieving content layer") 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 { if err != nil {
return err return false, err
} }
// Create content symlink printChartSummary(cache.out, metaLayer, contentLayer)
return createSymlink(contentPath, filepath.Join(tagDir, "content")) return metaExists && contentExists, nil
} }
func (cache *filesystemCache) DeleteReference(ref *Reference) error { func (cache *filesystemCache) DeleteReference(ref *Reference) error {
@ -226,6 +244,22 @@ func (cache *filesystemCache) TableRows() ([][]string, error) {
return getRefsSorted(filepath.Join(cache.rootDir, "refs")) 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 // mkdir will create a directory (no error check) and return the path
func mkdir(dir string) string { func mkdir(dir string) string {
os.MkdirAll(dir, 0755) os.MkdirAll(dir, 0755)
@ -318,20 +352,18 @@ func createChartFile(chartsRootDir string, name string, version string) (string,
return chartPath, nil return chartPath, nil
} }
// createDigestFile calcultaes the sha256 digest of some content and creates a file, returning the path // digestPath returns the path to addressable content, and whether the file exists
func createDigestFile(rootDir string, c []byte) (string, error) { func digestPath(rootDir string, digest checksum.Digest) (bool, string) {
digest := checksum.FromBytes(c).String() digestLeft, digestRight := splitDigest(digest.Hex())
digestLeft, digestRight := splitDigest(digest) path := filepath.Join(rootDir, "sha256", digestLeft, digestRight)
pathDir := filepath.Join(rootDir, "sha256", digestLeft) exists := fileExists(path)
path := filepath.Join(pathDir, digestRight) return exists, path
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) { }
os.MkdirAll(pathDir, 0755)
err := ioutil.WriteFile(path, c, 0644) // writeFile creates a path, ensuring parent directory
if err != nil { func writeFile(path string, c []byte) error {
return "", err os.MkdirAll(filepath.Dir(path), 0755)
} return ioutil.WriteFile(path, c, 0644)
}
return path, nil
} }
// splitDigest returns a sha256 digest in two parts, on with first 2 chars and one with second 62 chars // 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 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 // getRefsSorted returns a map of all refs stored in a refsRootDir
func getRefsSorted(refsRootDir string) ([][]string, error) { func getRefsSorted(refsRootDir string) ([][]string, error) {
refsMap := map[string]map[string]string{} 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) // Make sure the filename looks like a sha256 digest (64 chars)
if len(digest) == 64 { if len(digest) == 64 {
refsMap[ref]["digest"] = digest[:7] refsMap[ref]["digest"] = shortDigest(digest)
refsMap[ref]["size"] = byteCountBinary(destFileInfo.Size()) refsMap[ref]["size"] = byteCountBinary(destFileInfo.Size())
refsMap[ref]["created"] = units.HumanDuration(time.Now().UTC().Sub(destFileInfo.ModTime())) refsMap[ref]["created"] = units.HumanDuration(time.Now().UTC().Sub(destFileInfo.ModTime()))
} }

@ -59,36 +59,63 @@ func NewClient(options *ClientOptions) *Client {
// PushChart uploads a chart to a registry // PushChart uploads a chart to a registry
func (c *Client) PushChart(ref *Reference) error { 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) layers, err := c.cache.LoadReference(ref)
if err != nil { if err != nil {
return err return err
} }
err = oras.Push(context.Background(), c.resolver, ref.String(), c.cache.store, layers) 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 // PullChart downloads a chart from a registry
func (c *Client) PullChart(ref *Reference) error { 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()...) layers, err := oras.Pull(context.Background(), c.resolver, ref.String(), c.cache.store, KnownMediaTypes()...)
if err != nil { if err != nil {
return err return err
} }
err = c.cache.StoreReference(ref, layers) exists, err := c.cache.StoreReference(ref, layers)
return err 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 // SaveChart stores a copy of chart in local cache
func (c *Client) SaveChart(ch *chart.Chart, ref *Reference) error { func (c *Client) SaveChart(ch *chart.Chart, ref *Reference) error {
c.setDefaultTag(ref)
layers, err := c.cache.ChartToLayers(ch) layers, err := c.cache.ChartToLayers(ch)
if err != nil { if err != nil {
return err return err
} }
err = c.cache.StoreReference(ref, layers) _, err = c.cache.StoreReference(ref, layers)
return err if err != nil {
return err
}
fmt.Fprintf(c.out, "%s: saved\n", ref.Object)
return nil
} }
// LoadChart retrieves a chart object by reference // LoadChart retrieves a chart object by reference
func (c *Client) LoadChart(ref *Reference) (*chart.Chart, error) { func (c *Client) LoadChart(ref *Reference) (*chart.Chart, error) {
c.setDefaultTag(ref)
layers, err := c.cache.LoadReference(ref) layers, err := c.cache.LoadReference(ref)
if err != nil { if err != nil {
return nil, err return nil, err
@ -99,7 +126,12 @@ func (c *Client) LoadChart(ref *Reference) (*chart.Chart, error) {
// RemoveChart deletes a locally saved chart // RemoveChart deletes a locally saved chart
func (c *Client) RemoveChart(ref *Reference) error { func (c *Client) RemoveChart(ref *Reference) error {
c.setDefaultTag(ref)
err := c.cache.DeleteReference(ref) err := c.cache.DeleteReference(ref)
if err != nil {
return err
}
fmt.Fprintf(c.out, "%s: removed\n", ref.Object)
return err return err
} }
@ -120,3 +152,10 @@ func (c *Client) PrintChartTable() error {
fmt.Fprintln(c.out, table.String()) fmt.Fprintln(c.out, table.String())
return nil return nil
} }
func (c *Client) setDefaultTag(ref *Reference) {
if ref.Object == "" {
ref.Object = HelmChartDefaultTag
fmt.Fprintf(c.out, "Using default tag: %s\n", HelmChartDefaultTag)
}
}

Loading…
Cancel
Save