diff --git a/cmd/helm/history.go b/cmd/helm/history.go index 7ba4eb7b3..e60ce42b2 100644 --- a/cmd/helm/history.go +++ b/cmd/helm/history.go @@ -21,9 +21,13 @@ import ( "io" "github.com/spf13/cobra" + "github.com/gosuri/uitable" "helm.sh/helm/cmd/helm/require" "helm.sh/helm/pkg/action" + "helm.sh/helm/pkg/releaseutil" + "helm.sh/helm/pkg/chart" + "helm.sh/helm/pkg/release" ) var historyHelp = ` @@ -52,7 +56,7 @@ func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Aliases: []string{"hist"}, Args: require.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - history, err := client.Run(args[0]) + history, err := getHistory(client, args[0]) if err != nil { return err } @@ -67,3 +71,115 @@ func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return cmd } + +type releaseInfo struct { + Revision int `json:"revision"` + Updated string `json:"updated"` + Status string `json:"status"` + Chart string `json:"chart"` + AppVersion string `json:"app_version"` + Description string `json:"description"` +} + +type releaseHistory []releaseInfo + +func marshalHistory(format action.OutputFormat, hist releaseHistory) (byt []byte, err error) { + switch format { + case action.YAML, action.JSON: + byt, err = format.Marshal(hist) + case action.Table: + byt, err = format.MarshalTable(func(tbl *uitable.Table) { + tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION", "DESCRIPTION") + for i := 0; i <= len(hist)-1; i++ { + r := hist[i] + tbl.AddRow(r.Revision, r.Updated, r.Status, r.Chart, r.AppVersion, r.Description) + } + }) + default: + err = action.ErrInvalidFormatType + } + return +} + +func getHistory(client *action.History, name string) (string, error) { + hist, err := client.Run(name) + if err != nil { + return "", err + } + + releaseutil.Reverse(hist, releaseutil.SortByRevision) + + var rels []*release.Release + for i := 0; i < min(len(hist), client.Max); i++ { + rels = append(rels, hist[i]) + } + + if len(rels) == 0 { + return "", nil + } + + releaseHistory := getReleaseHistory(rels) + + outputFormat, err := action.ParseOutputFormat(client.OutputFormat) + if err != nil { + return "", err + } + history, formattingError := marshalHistory(outputFormat, releaseHistory) + if formattingError != nil { + return "", formattingError + } + + return string(history), nil +} + + +func getReleaseHistory(rls []*release.Release) (history releaseHistory) { + for i := len(rls) - 1; i >= 0; i-- { + r := rls[i] + c := formatChartname(r.Chart) + s := r.Info.Status.String() + v := r.Version + d := r.Info.Description + a := formatAppVersion(r.Chart) + + rInfo := releaseInfo{ + Revision: v, + Status: s, + Chart: c, + AppVersion: a, + Description: d, + } + if !r.Info.LastDeployed.IsZero() { + rInfo.Updated = r.Info.LastDeployed.String() + + } + history = append(history, rInfo) + } + + return history +} + +func formatChartname(c *chart.Chart) string { + if c == nil || c.Metadata == nil { + // This is an edge case that has happened in prod, though we don't + // know how: https://github.com/helm/helm/issues/1347 + return "MISSING" + } + return fmt.Sprintf("%s-%s", c.Name(), c.Metadata.Version) +} + +func formatAppVersion(c *chart.Chart) string { + if c == nil || c.Metadata == nil { + // This is an edge case that has happened in prod, though we don't + // know how: https://github.com/helm/helm/issues/1347 + return "MISSING" + } + return c.AppVersion() +} + +func min(x, y int) int { + if x < y { + return x + } + return y +} \ No newline at end of file diff --git a/cmd/helm/status.go b/cmd/helm/status.go index 8b33858a5..5b7cecd68 100644 --- a/cmd/helm/status.go +++ b/cmd/helm/status.go @@ -17,10 +17,8 @@ limitations under the License. package main import ( - "encoding/json" "io" - "github.com/ghodss/yaml" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -66,17 +64,10 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { case "": action.PrintRelease(out, rel) return nil - case action.JSON: - data, err := json.Marshal(rel) + case action.JSON, action.YAML: + data, err := outfmt.Marshal(rel) if err != nil { - return errors.Wrap(err, "failed to Marshal JSON output") - } - out.Write(data) - return nil - case action.YAML: - data, err := yaml.Marshal(rel) - if err != nil { - return errors.Wrap(err, "failed to Marshal YAML output") + return errors.Wrap(err, "failed to Marshal output") } out.Write(data) return nil diff --git a/pkg/action/history.go b/pkg/action/history.go index 36a11710e..5b7f028bd 100644 --- a/pkg/action/history.go +++ b/pkg/action/history.go @@ -17,71 +17,11 @@ limitations under the License. package action import ( - "encoding/json" - "fmt" - - "github.com/ghodss/yaml" - "github.com/gosuri/uitable" "github.com/pkg/errors" - "helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/release" - "helm.sh/helm/pkg/releaseutil" ) -type releaseInfo struct { - Revision int `json:"revision"` - Updated string `json:"updated"` - Status string `json:"status"` - Chart string `json:"chart"` - AppVersion string `json:"app_version"` - Description string `json:"description"` -} - -type releaseHistory []releaseInfo - -type OutputFormat string - -const ( - Table OutputFormat = "table" - JSON OutputFormat = "json" - YAML OutputFormat = "yaml" -) - -var ErrInvalidFormatType = errors.New("invalid format type") - -func (o OutputFormat) String() string { - return string(o) -} - -func ParseOutputFormat(s string) (out OutputFormat, err error) { - switch s { - case Table.String(): - out, err = Table, nil - case JSON.String(): - out, err = JSON, nil - case YAML.String(): - out, err = YAML, nil - default: - out, err = "", ErrInvalidFormatType - } - return -} - -func (o OutputFormat) MarshalHistory(hist releaseHistory) (byt []byte, err error) { - switch o { - case YAML: - byt, err = yaml.Marshal(hist) - case JSON: - byt, err = json.Marshal(hist) - case Table: - byt = formatAsTable(hist) - default: - err = ErrInvalidFormatType - } - return -} - // History is the action for checking the release's ledger. // // It provides the implementation of 'helm history'. @@ -100,93 +40,11 @@ func NewHistory(cfg *Configuration) *History { } // Run executes 'helm history' against the given release. -func (h *History) Run(name string) (string, error) { +func (h *History) Run(name string) ([]*release.Release, error) { if err := validateReleaseName(name); err != nil { - return "", errors.Errorf("getHistory: Release name is invalid: %s", name) + return nil, errors.Errorf("release name is invalid: %s", name) } h.cfg.Log("getting history for release %s", name) - hist, err := h.cfg.Releases.History(name) - if err != nil { - return "", err - } - - releaseutil.Reverse(hist, releaseutil.SortByRevision) - - var rels []*release.Release - for i := 0; i < min(len(hist), h.Max); i++ { - rels = append(rels, hist[i]) - } - - if len(rels) == 0 { - return "", nil - } - - releaseHistory := getReleaseHistory(rels) - - outputFormat, err := ParseOutputFormat(h.OutputFormat) - if err != nil { - return "", err - } - history, formattingError := outputFormat.MarshalHistory(releaseHistory) - if formattingError != nil { - return "", formattingError - } - - return string(history), nil -} - -func getReleaseHistory(rls []*release.Release) (history releaseHistory) { - for i := len(rls) - 1; i >= 0; i-- { - r := rls[i] - c := formatChartname(r.Chart) - s := r.Info.Status.String() - v := r.Version - d := r.Info.Description - a := formatAppVersion(r.Chart) - - rInfo := releaseInfo{ - Revision: v, - Status: s, - Chart: c, - AppVersion: a, - Description: d, - } - if !r.Info.LastDeployed.IsZero() { - rInfo.Updated = r.Info.LastDeployed.String() - - } - history = append(history, rInfo) - } - - return history -} - -func formatAsTable(releases releaseHistory) []byte { - tbl := uitable.New() - - tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION", "DESCRIPTION") - for i := 0; i <= len(releases)-1; i++ { - r := releases[i] - tbl.AddRow(r.Revision, r.Updated, r.Status, r.Chart, r.AppVersion, r.Description) - } - return tbl.Bytes() -} - -func formatChartname(c *chart.Chart) string { - if c == nil || c.Metadata == nil { - // This is an edge case that has happened in prod, though we don't - // know how: https://github.com/helm/helm/issues/1347 - return "MISSING" - } - return fmt.Sprintf("%s-%s", c.Name(), c.Metadata.Version) -} - -func formatAppVersion(c *chart.Chart) string { - if c == nil || c.Metadata == nil { - // This is an edge case that has happened in prod, though we don't - // know how: https://github.com/helm/helm/issues/1347 - return "MISSING" - } - return c.AppVersion() + return h.cfg.Releases.History(name) } diff --git a/pkg/action/output.go b/pkg/action/output.go new file mode 100644 index 000000000..06fb0d4e1 --- /dev/null +++ b/pkg/action/output.go @@ -0,0 +1,73 @@ +package action + +import ( + "fmt" + "encoding/json" + + "github.com/ghodss/yaml" + "github.com/gosuri/uitable" +) + +// OutputFormat is a type for capturing supported output formats +type OutputFormat string + +// TableFunc is a function that can be used to add rows to a table +type TableFunc func(tbl *uitable.Table) + +const ( + Table OutputFormat = "table" + JSON OutputFormat = "json" + YAML OutputFormat = "yaml" +) + +// ErrInvalidFormatType is returned when an unsupported format type is used +var ErrInvalidFormatType = fmt.Errorf("invalid format type") + +// String returns the string reprsentation of the OutputFormat +func (o OutputFormat) String() string { + return string(o) +} + +// Marshal uses the specified output format to marshal out the given data. It +// does not support tabular output. For tabular output, use MarshalTable +func (o OutputFormat) Marshal(data interface{}) (byt []byte, err error) { + switch o { + case YAML: + byt, err = yaml.Marshal(data) + case JSON: + byt, err = json.Marshal(data) + default: + err = ErrInvalidFormatType + } + return +} + +// MarshalTable returns a formatted table using the given headers. Rows can be +// added to the table using the given TableFunc +func (o OutputFormat) MarshalTable(f TableFunc) ([]byte, error) { + if o != Table { + return nil, ErrInvalidFormatType + } + tbl := uitable.New() + if f == nil { + return []byte{}, nil + } + f(tbl) + return tbl.Bytes(), nil +} + +// ParseOutputFormat takes a raw string and returns the matching OutputFormat. +// If the format does not exists, ErrInvalidFormatType is returned +func ParseOutputFormat(s string) (out OutputFormat, err error) { + switch s { + case Table.String(): + out, err = Table, nil + case JSON.String(): + out, err = JSON, nil + case YAML.String(): + out, err = YAML, nil + default: + out, err = "", ErrInvalidFormatType + } + return +} \ No newline at end of file diff --git a/pkg/action/util.go b/pkg/action/util.go deleted file mode 100644 index f80002c99..000000000 --- a/pkg/action/util.go +++ /dev/null @@ -1,24 +0,0 @@ -/* -Copyright The Helm Authors. - -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 action - -func min(x, y int) int { - if x < y { - return x - } - return y -}