Merge pull request #6504 from thomastaylor312/feat/output

feat(*): Ports all output functionality from v2
pull/6516/head
Taylor Thomas 5 years ago committed by GitHub
commit 15d277360e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,57 @@
/*
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 main
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/cli/values"
)
const outputFlag = "output"
func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) {
f.StringSliceVarP(&v.ValueFiles, "values", "f", []string{}, "specify values in a YAML file or a URL(can specify multiple)")
f.StringArrayVar(&v.Values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&v.StringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&v.FileValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)")
}
func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
f.StringVar(&c.Version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed")
f.BoolVar(&c.Verify, "verify", false, "verify the package before installing it")
f.StringVar(&c.Keyring, "keyring", defaultKeyring(), "location of public keys used for verification")
f.StringVar(&c.RepoURL, "repo", "", "chart repository url where to locate the requested chart")
f.StringVar(&c.Username, "username", "", "chart repository username where to locate the requested chart")
f.StringVar(&c.Password, "password", "", "chart repository password where to locate the requested chart")
f.StringVar(&c.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&c.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&c.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
}
// bindOutputFlag will add the output flag to the given command and bind the
// value to the given string pointer
func bindOutputFlag(cmd *cobra.Command, varRef *string) {
// NOTE(taylor): A possible refactor here is that we can implement all the
// validation for the OutputFormat type here so we don't have to do the
// parsing and checking in the command
cmd.Flags().StringVarP(varRef, outputFlag, "o", string(action.Table), fmt.Sprintf("Prints the output in the specified format. Allowed values: %s, %s, %s", action.Table, action.JSON, action.YAML))
}

@ -56,12 +56,19 @@ 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 {
// validate the output format first so we don't waste time running a
// request that we'll throw away
output, err := action.ParseOutputFormat(client.OutputFormat)
if err != nil {
return err
}
history, err := getHistory(client, args[0])
if err != nil {
return err
}
fmt.Fprintln(out, history)
return nil
return output.Write(out, history)
},
}
@ -83,28 +90,27 @@ type releaseInfo struct {
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
func (r releaseHistory) WriteJSON(out io.Writer) error {
return action.EncodeJSON(out, r)
}
func (r releaseHistory) WriteYAML(out io.Writer) error {
return action.EncodeYAML(out, r)
}
func (r releaseHistory) WriteTable(out io.Writer) error {
tbl := uitable.New()
tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION", "DESCRIPTION")
for _, item := range r {
tbl.AddRow(item.Revision, item.Updated, item.Status, item.Chart, item.AppVersion, item.Description)
}
return
return action.EncodeTable(out, tbl)
}
func getHistory(client *action.History, name string) (string, error) {
func getHistory(client *action.History, name string) (releaseHistory, error) {
hist, err := client.Run(name)
if err != nil {
return "", err
return nil, err
}
releaseutil.Reverse(hist, releaseutil.SortByRevision)
@ -115,21 +121,12 @@ func getHistory(client *action.History, name string) (string, error) {
}
if len(rels) == 0 {
return "", nil
return releaseHistory{}, 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
return releaseHistory, nil
}
func getReleaseHistory(rls []*release.Release) (history releaseHistory) {

@ -111,16 +111,24 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: installDesc,
Args: require.MinimumNArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
// validate the output format first so we don't waste time running a
// request that we'll throw away
output, err := action.ParseOutputFormat(client.OutputFormat)
if err != nil {
return err
}
rel, err := runInstall(args, client, valueOpts, out)
if err != nil {
return err
}
action.PrintRelease(out, rel)
return nil
return output.Write(out, &statusPrinter{rel})
},
}
addInstallFlags(cmd.Flags(), client, valueOpts)
bindOutputFlag(cmd, &client.OutputFormat)
return cmd
}
@ -141,25 +149,6 @@ func addInstallFlags(f *pflag.FlagSet, client *action.Install, valueOpts *values
addChartPathOptionsFlags(f, &client.ChartPathOptions)
}
func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) {
f.StringSliceVarP(&v.ValueFiles, "values", "f", []string{}, "specify values in a YAML file or a URL(can specify multiple)")
f.StringArrayVar(&v.Values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&v.StringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&v.FileValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)")
}
func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
f.StringVar(&c.Version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed")
f.BoolVar(&c.Verify, "verify", false, "verify the package before installing it")
f.StringVar(&c.Keyring, "keyring", defaultKeyring(), "location of public keys used for verification")
f.StringVar(&c.RepoURL, "repo", "", "chart repository url where to locate the requested chart")
f.StringVar(&c.Username, "username", "", "chart repository username where to locate the requested chart")
f.StringVar(&c.Password, "password", "", "chart repository password where to locate the requested chart")
f.StringVar(&c.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&c.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&c.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
}
func runInstall(args []string, client *action.Install, valueOpts *values.Options, out io.Writer) (*release.Release, error) {
debug("Original chart version: %q", client.Version)
if client.Version == "" && client.Devel {

@ -19,11 +19,14 @@ package main
import (
"fmt"
"io"
"strconv"
"github.com/gosuri/uitable"
"github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/release"
)
var listHelp = `
@ -63,6 +66,13 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Aliases: []string{"ls"},
Args: require.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
// validate the output format first so we don't waste time running a
// request that we'll throw away
output, err := action.ParseOutputFormat(client.OutputFormat)
if err != nil {
return err
}
if client.AllNamespaces {
initActionConfig(cfg, true)
}
@ -77,8 +87,7 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return err
}
fmt.Fprintln(out, action.FormatList(results))
return err
return output.Write(out, newReleaseListWriter(results))
},
}
@ -95,8 +104,60 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&client.Pending, "pending", false, "show pending releases")
f.BoolVar(&client.AllNamespaces, "all-namespaces", false, "list releases across all namespaces")
f.IntVarP(&client.Limit, "max", "m", 256, "maximum number of releases to fetch")
f.IntVarP(&client.Offset, "offset", "o", 0, "next release name in the list, used to offset from start value")
f.IntVar(&client.Offset, "offset", 0, "next release name in the list, used to offset from start value")
f.StringVarP(&client.Filter, "filter", "f", "", "a regular expression (Perl compatible). Any releases that match the expression will be included in the results")
bindOutputFlag(cmd, &client.OutputFormat)
return cmd
}
type releaseElement struct {
Name string
Namespace string
Revision string
Updated string
Status string
Chart string
}
type releaseListWriter struct {
releases []releaseElement
}
func newReleaseListWriter(releases []*release.Release) *releaseListWriter {
// Initialize the array so no results returns an empty array instead of null
elements := make([]releaseElement, 0, len(releases))
for _, r := range releases {
element := releaseElement{
Name: r.Name,
Namespace: r.Namespace,
Revision: strconv.Itoa(r.Version),
Status: r.Info.Status.String(),
Chart: fmt.Sprintf("%s-%s", r.Chart.Metadata.Name, r.Chart.Metadata.Version),
}
t := "-"
if tspb := r.Info.LastDeployed; !tspb.IsZero() {
t = tspb.String()
}
element.Updated = t
elements = append(elements, element)
}
return &releaseListWriter{elements}
}
func (r *releaseListWriter) WriteTable(out io.Writer) error {
table := uitable.New()
table.AddRow("NAME", "NAMESPACE", "REVISION", "UPDATED", "STATUS", "CHART")
for _, r := range r.releases {
table.AddRow(r.Name, r.Namespace, r.Revision, r.Updated, r.Status, r.Chart)
}
return action.EncodeTable(out, table)
}
func (r *releaseListWriter) WriteJSON(out io.Writer) error {
return action.EncodeJSON(out, r.releases)
}
func (r *releaseListWriter) WriteYAML(out io.Writer) error {
return action.EncodeYAML(out, r.releases)
}

@ -17,7 +17,6 @@ limitations under the License.
package main
import (
"fmt"
"io"
"github.com/gosuri/uitable"
@ -25,28 +24,79 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/repo"
)
func newRepoListCmd(out io.Writer) *cobra.Command {
var output string
cmd := &cobra.Command{
Use: "list",
Short: "list chart repositories",
Args: require.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
// validate the output format first so we don't waste time running a
// request that we'll throw away
outfmt, err := action.ParseOutputFormat(output)
if err != nil {
return err
}
f, err := repo.LoadFile(settings.RepositoryConfig)
if isNotExist(err) || len(f.Repositories) == 0 {
return errors.New("no repositories to show")
}
table := uitable.New()
table.AddRow("NAME", "URL")
for _, re := range f.Repositories {
table.AddRow(re.Name, re.URL)
}
fmt.Fprintln(out, table)
return nil
return outfmt.Write(out, &repoListWriter{f.Repositories})
},
}
bindOutputFlag(cmd, &output)
return cmd
}
type repositoryElement struct {
Name string
URL string
}
type repoListWriter struct {
repos []*repo.Entry
}
func (r *repoListWriter) WriteTable(out io.Writer) error {
table := uitable.New()
table.AddRow("NAME", "URL")
for _, re := range r.repos {
table.AddRow(re.Name, re.URL)
}
return action.EncodeTable(out, table)
}
func (r *repoListWriter) WriteJSON(out io.Writer) error {
return r.encodeByFormat(out, action.JSON)
}
func (r *repoListWriter) WriteYAML(out io.Writer) error {
return r.encodeByFormat(out, action.YAML)
}
func (r *repoListWriter) encodeByFormat(out io.Writer, format action.OutputFormat) error {
// Initialize the array so no results returns an empty array instead of null
repolist := make([]repositoryElement, 0, len(r.repos))
for _, re := range r.repos {
repolist = append(repolist, repositoryElement{Name: re.Name, URL: re.URL})
}
switch format {
case action.JSON:
return action.EncodeJSON(out, repolist)
case action.YAML:
return action.EncodeYAML(out, repolist)
}
// Because this is a non-exported function and only called internally by
// WriteJSON and WriteYAML, we shouldn't get invalid types
return nil
}

@ -26,6 +26,7 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/internal/monocular"
"helm.sh/helm/pkg/action"
)
const searchHubDesc = `
@ -43,6 +44,7 @@ Helm Hub. You can find it at https://github.com/helm/monocular
type searchHubOptions struct {
searchEndpoint string
maxColWidth uint
outputFormat string
}
func newSearchHubCmd(out io.Writer) *cobra.Command {
@ -60,11 +62,18 @@ func newSearchHubCmd(out io.Writer) *cobra.Command {
f := cmd.Flags()
f.StringVar(&o.searchEndpoint, "endpoint", "https://hub.helm.sh", "monocular instance to query for charts")
f.UintVar(&o.maxColWidth, "max-col-width", 50, "maximum column width for output table")
bindOutputFlag(cmd, &o.outputFormat)
return cmd
}
func (o *searchHubOptions) run(out io.Writer, args []string) error {
// validate the output format first so we don't waste time running a
// request that we'll throw away
outfmt, err := action.ParseOutputFormat(o.outputFormat)
if err != nil {
return err
}
c, err := monocular.New(o.searchEndpoint)
if err != nil {
@ -78,25 +87,71 @@ func (o *searchHubOptions) run(out io.Writer, args []string) error {
return fmt.Errorf("unable to perform search against %q", o.searchEndpoint)
}
fmt.Fprintln(out, o.formatSearchResults(o.searchEndpoint, results))
return outfmt.Write(out, newHubSearchWriter(results, o.searchEndpoint, o.maxColWidth))
}
type hubChartElement struct {
URL string
Version string
AppVersion string
Description string
}
return nil
type hubSearchWriter struct {
elements []hubChartElement
columnWidth uint
}
func (o *searchHubOptions) formatSearchResults(endpoint string, res []monocular.SearchResult) string {
if len(res) == 0 {
return "No results found"
func newHubSearchWriter(results []monocular.SearchResult, endpoint string, columnWidth uint) *hubSearchWriter {
var elements []hubChartElement
for _, r := range results {
url := endpoint + "/charts/" + r.ID
elements = append(elements, hubChartElement{url, r.Relationships.LatestChartVersion.Data.Version, r.Relationships.LatestChartVersion.Data.AppVersion, r.Attributes.Description})
}
table := uitable.New()
return &hubSearchWriter{elements, columnWidth}
}
// The max column width is configurable because a URL could be longer than the
// max value and we want the user to have the ability to display the whole url
table.MaxColWidth = o.maxColWidth
func (h *hubSearchWriter) WriteTable(out io.Writer) error {
if len(h.elements) == 0 {
_, err := out.Write([]byte("No results found\n"))
if err != nil {
return fmt.Errorf("unable to write results: %s", err)
}
return nil
}
table := uitable.New()
table.MaxColWidth = h.columnWidth
table.AddRow("URL", "CHART VERSION", "APP VERSION", "DESCRIPTION")
var url string
for _, r := range res {
url = endpoint + "/charts/" + r.ID
table.AddRow(url, r.Relationships.LatestChartVersion.Data.Version, r.Relationships.LatestChartVersion.Data.AppVersion, r.Attributes.Description)
for _, r := range h.elements {
table.AddRow(r.URL, r.Version, r.AppVersion, r.Description)
}
return action.EncodeTable(out, table)
}
func (h *hubSearchWriter) WriteJSON(out io.Writer) error {
return h.encodeByFormat(out, action.JSON)
}
func (h *hubSearchWriter) WriteYAML(out io.Writer) error {
return h.encodeByFormat(out, action.YAML)
}
func (h *hubSearchWriter) encodeByFormat(out io.Writer, format action.OutputFormat) error {
// Initialize the array so no results returns an empty array instead of null
chartList := make([]hubChartElement, 0, len(h.elements))
for _, r := range h.elements {
chartList = append(chartList, hubChartElement{r.URL, r.Version, r.AppVersion, r.Description})
}
switch format {
case action.JSON:
return action.EncodeJSON(out, chartList)
case action.YAML:
return action.EncodeYAML(out, chartList)
}
return table.String()
// Because this is a non-exported function and only called internally by
// WriteJSON and WriteYAML, we shouldn't get invalid types
return nil
}

@ -28,6 +28,7 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/search"
"helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/repo"
)
@ -50,6 +51,7 @@ type searchRepoOptions struct {
maxColWidth uint
repoFile string
repoCacheDir string
outputFormat string
}
func newSearchRepoCmd(out io.Writer) *cobra.Command {
@ -71,11 +73,19 @@ func newSearchRepoCmd(out io.Writer) *cobra.Command {
f.BoolVarP(&o.versions, "versions", "l", false, "show the long listing, with each version of each chart on its own line, for repositories you have added")
f.StringVar(&o.version, "version", "", "search using semantic versioning constraints on repositories you have added")
f.UintVar(&o.maxColWidth, "max-col-width", 50, "maximum column width for output table")
bindOutputFlag(cmd, &o.outputFormat)
return cmd
}
func (o *searchRepoOptions) run(out io.Writer, args []string) error {
// validate the output format first so we don't waste time running a
// request that we'll throw away
outfmt, err := action.ParseOutputFormat(o.outputFormat)
if err != nil {
return err
}
index, err := o.buildIndex(out)
if err != nil {
return err
@ -98,9 +108,7 @@ func (o *searchRepoOptions) run(out io.Writer, args []string) error {
return err
}
fmt.Fprintln(out, o.formatSearchResults(data))
return nil
return outfmt.Write(out, &repoSearchWriter{data, o.maxColWidth})
}
func (o *searchRepoOptions) applyConstraint(res []*search.Result) ([]*search.Result, error) {
@ -131,19 +139,6 @@ func (o *searchRepoOptions) applyConstraint(res []*search.Result) ([]*search.Res
return data, nil
}
func (o *searchRepoOptions) formatSearchResults(res []*search.Result) string {
if len(res) == 0 {
return "No results found"
}
table := uitable.New()
table.MaxColWidth = o.maxColWidth
table.AddRow("NAME", "CHART VERSION", "APP VERSION", "DESCRIPTION")
for _, r := range res {
table.AddRow(r.Name, r.Chart.Version, r.Chart.AppVersion, r.Chart.Description)
}
return table.String()
}
func (o *searchRepoOptions) buildIndex(out io.Writer) (*search.Index, error) {
// Load the repositories.yaml
rf, err := repo.LoadFile(o.repoFile)
@ -166,3 +161,60 @@ func (o *searchRepoOptions) buildIndex(out io.Writer) (*search.Index, error) {
}
return i, nil
}
type repoChartElement struct {
Name string
Version string
AppVersion string
Description string
}
type repoSearchWriter struct {
results []*search.Result
columnWidth uint
}
func (r *repoSearchWriter) WriteTable(out io.Writer) error {
if len(r.results) == 0 {
_, err := out.Write([]byte("No results found\n"))
if err != nil {
return fmt.Errorf("unable to write results: %s", err)
}
return nil
}
table := uitable.New()
table.MaxColWidth = r.columnWidth
table.AddRow("NAME", "CHART VERSION", "APP VERSION", "DESCRIPTION")
for _, r := range r.results {
table.AddRow(r.Name, r.Chart.Version, r.Chart.AppVersion, r.Chart.Description)
}
return action.EncodeTable(out, table)
}
func (r *repoSearchWriter) WriteJSON(out io.Writer) error {
return r.encodeByFormat(out, action.JSON)
}
func (r *repoSearchWriter) WriteYAML(out io.Writer) error {
return r.encodeByFormat(out, action.YAML)
}
func (r *repoSearchWriter) encodeByFormat(out io.Writer, format action.OutputFormat) error {
// Initialize the array so no results returns an empty array instead of null
chartList := make([]repoChartElement, 0, len(r.results))
for _, r := range r.results {
chartList = append(chartList, repoChartElement{r.Name, r.Chart.Version, r.Chart.AppVersion, r.Chart.Description})
}
switch format {
case action.JSON:
return action.EncodeJSON(out, chartList)
case action.YAML:
return action.EncodeYAML(out, chartList)
}
// Because this is a non-exported function and only called internally by
// WriteJSON and WriteYAML, we shouldn't get invalid types
return nil
}

@ -64,6 +64,14 @@ func TestSearchRepositoriesCmd(t *testing.T) {
name: "search for 'alp[', expect failure to compile regexp",
cmd: "search repo alp[ --regexp",
wantError: true,
}, {
name: "search for 'maria', expect valid json output",
cmd: "search repo maria --output json",
golden: "output/search-output-json.txt",
}, {
name: "search for 'alpine', expect valid yaml output",
cmd: "search repo alpine --output yaml",
golden: "output/search-output-yaml.txt",
}}
settings.Debug = true

@ -19,11 +19,11 @@ package main
import (
"io"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/release"
)
var statusHelp = `
@ -46,6 +46,13 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: statusHelp,
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
// validate the output format first so we don't waste time running a
// request that we'll throw away
outfmt, err := action.ParseOutputFormat(client.OutputFormat)
if err != nil {
return err
}
rel, err := client.Run(args[0])
if err != nil {
return err
@ -54,32 +61,30 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
// strip chart metadata from the output
rel.Chart = nil
outfmt, err := action.ParseOutputFormat(client.OutputFormat)
// We treat an invalid format type as the default
if err != nil && err != action.ErrInvalidFormatType {
return err
}
switch outfmt {
case "":
action.PrintRelease(out, rel)
return nil
case action.JSON, action.YAML:
data, err := outfmt.Marshal(rel)
if err != nil {
return errors.Wrap(err, "failed to Marshal output")
}
out.Write(data)
return nil
default:
return errors.Errorf("unknown output format %q", outfmt)
}
return outfmt.Write(out, &statusPrinter{rel})
},
}
f := cmd.PersistentFlags()
f.IntVar(&client.Version, "revision", 0, "if set, display the status of the named release with revision")
f.StringVarP(&client.OutputFormat, "output", "o", "", "output the status in the specified format (json or yaml)")
bindOutputFlag(cmd, &client.OutputFormat)
return cmd
}
type statusPrinter struct {
release *release.Release
}
func (s statusPrinter) WriteJSON(out io.Writer) error {
return action.EncodeJSON(out, s.release)
}
func (s statusPrinter) WriteYAML(out io.Writer) error {
return action.EncodeYAML(out, s.release)
}
func (s statusPrinter) WriteTable(out io.Writer) error {
action.PrintRelease(out, s.release)
return nil
}

@ -10,4 +10,3 @@
revision: 4
status: deployed
updated: 1977-09-02 22:04:05 +0000 UTC

@ -0,0 +1 @@
[{"Name":"testing/mariadb","Version":"0.3.0","AppVersion":"","Description":"Chart for MariaDB"}]

@ -0,0 +1,4 @@
- AppVersion: 2.3.4
Description: Deploy a basic Alpine Linux pod
Name: testing/alpine
Version: 0.2.0

@ -1 +1 @@
{"name":"flummoxed-chickadee","info":{"first_deployed":"0001-01-01T00:00:00Z","last_deployed":"2016-01-16T00:00:00Z","deleted":"0001-01-01T00:00:00Z","status":"deployed","notes":"release notes"},"namespace":"default"}
{"name":"flummoxed-chickadee","info":{"first_deployed":"0001-01-01T00:00:00Z","last_deployed":"2016-01-16T00:00:00Z","deleted":"0001-01-01T00:00:00Z","status":"deployed","notes":"release notes"},"namespace":"default"}

@ -69,6 +69,13 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: upgradeDesc,
Args: require.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
// validate the output format first so we don't waste time running a
// request that we'll throw away
output, err := action.ParseOutputFormat(client.OutputFormat)
if err != nil {
return err
}
client.Namespace = getNamespace()
if client.Version == "" && client.Devel {
@ -104,8 +111,10 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
instClient.Atomic = client.Atomic
rel, err := runInstall(args, instClient, valueOpts, out)
action.PrintRelease(out, rel)
return err
if err != nil {
return err
}
return output.Write(out, &statusPrinter{rel})
}
}
@ -129,7 +138,9 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
action.PrintRelease(out, resp)
}
fmt.Fprintf(out, "Release %q has been upgraded. Happy Helming!\n", args[0])
if output == action.Table {
fmt.Fprintf(out, "Release %q has been upgraded. Happy Helming!\n", args[0])
}
// Print the status like status command does
statusClient := action.NewStatus(cfg)
@ -137,9 +148,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
if err != nil {
return err
}
action.PrintRelease(out, rel)
return nil
return output.Write(out, &statusPrinter{rel})
},
}
@ -160,6 +170,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this upgrade when upgrade fails")
addChartPathOptionsFlags(f, &client.ChartPathOptions)
addValueOptionsFlags(f, valueOpts)
bindOutputFlag(cmd, &client.OutputFormat)
return cmd
}

@ -82,6 +82,7 @@ type Install struct {
OutputDir string
Atomic bool
SkipCRDs bool
OutputFormat string
}
// ChartPathOptions captures common options used for controlling chart paths

@ -17,11 +17,8 @@ limitations under the License.
package action
import (
"fmt"
"regexp"
"github.com/gosuri/uitable"
"helm.sh/helm/pkg/release"
"helm.sh/helm/pkg/releaseutil"
)
@ -128,6 +125,7 @@ type List struct {
Deployed bool
Failed bool
Pending bool
OutputFormat string
}
// NewList constructs a new *List
@ -278,21 +276,3 @@ func (l *List) SetStateMask() {
l.StateMask = state
}
func FormatList(rels []*release.Release) string {
table := uitable.New()
table.AddRow("NAME", "NAMESPACE", "REVISION", "UPDATED", "STATUS", "CHART")
for _, r := range rels {
md := r.Chart.Metadata
c := fmt.Sprintf("%s-%s", md.Name, md.Version)
t := "-"
if tspb := r.Info.LastDeployed; !tspb.IsZero() {
t = tspb.String()
}
s := r.Info.Status.String()
v := r.Version
n := r.Namespace
table.AddRow(r.Name, n, v, t, s, c)
}
return table.String()
}

@ -19,17 +19,16 @@ package action
import (
"encoding/json"
"fmt"
"io"
"github.com/gosuri/uitable"
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
)
// 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"
@ -44,32 +43,18 @@ 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) {
// Write the output in the given format to the io.Writer. Unsupported formats
// will return an error
func (o OutputFormat) Write(out io.Writer, w Writer) error {
switch o {
case YAML:
byt, err = yaml.Marshal(data)
case Table:
return w.WriteTable(out)
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
return w.WriteJSON(out)
case YAML:
return w.WriteYAML(out)
}
f(tbl)
return tbl.Bytes(), nil
return ErrInvalidFormatType
}
// ParseOutputFormat takes a raw string and returns the matching OutputFormat.
@ -87,3 +72,54 @@ func ParseOutputFormat(s string) (out OutputFormat, err error) {
}
return
}
// Writer is an interface that any type can implement to write supported formats
type Writer interface {
// WriteTable will write tabular output into the given io.Writer, returning
// an error if any occur
WriteTable(out io.Writer) error
// WriteJSON will write JSON formatted output into the given io.Writer,
// returning an error if any occur
WriteJSON(out io.Writer) error
// WriteYAML will write YAML formatted output into the given io.Writer,
// returning an error if any occur
WriteYAML(out io.Writer) error
}
// EncodeJSON is a helper function to decorate any error message with a bit more
// context and avoid writing the same code over and over for printers.
func EncodeJSON(out io.Writer, obj interface{}) error {
enc := json.NewEncoder(out)
err := enc.Encode(obj)
if err != nil {
return errors.Wrap(err, "unable to write JSON output")
}
return nil
}
// EncodeYAML is a helper function to decorate any error message with a bit more
// context and avoid writing the same code over and over for printers
func EncodeYAML(out io.Writer, obj interface{}) error {
raw, err := yaml.Marshal(obj)
if err != nil {
return errors.Wrap(err, "unable to write YAML output")
}
_, err = out.Write(raw)
if err != nil {
return errors.Wrap(err, "unable to write YAML output")
}
return nil
}
// EncodeTable is a helper function to decorate any error message with a bit
// more context and avoid writing the same code over and over for printers
func EncodeTable(out io.Writer, table *uitable.Table) error {
raw := table.Bytes()
raw = append(raw, []byte("\n")...)
_, err := out.Write(raw)
if err != nil {
return errors.Wrap(err, "unable to write table output")
}
return nil
}

@ -56,6 +56,7 @@ type Upgrade struct {
MaxHistory int
Atomic bool
CleanupOnFail bool
OutputFormat string
}
// NewUpgrade creates a new Upgrade object with the given configuration.

Loading…
Cancel
Save