mirror of
https://github.com/helm/helm.git
synced 2026-05-28 04:35:48 -04:00
feat(*): Ports all output functionality from v2
As part of this port, I removed some now superfluous code from the `action` package. This is technically a breaking change, but since the package was introduced in v3, it is highly unlikely anyone is using it and we are still within the beta window. Also closes #6437 Signed-off-by: Taylor Thomas <taylor.thomas@microsoft.com>
This commit is contained in:
parent
85572df378
commit
eac6a60001
18 changed files with 477 additions and 170 deletions
57
cmd/helm/flags.go
Normal file
57
cmd/helm/flags.go
Normal file
|
|
@ -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
|
||||
}
|
||||
return
|
||||
func (r releaseHistory) WriteJSON(out io.Writer) error {
|
||||
return action.EncodeJSON(out, r)
|
||||
}
|
||||
|
||||
func getHistory(client *action.History, name string) (string, error) {
|
||||
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 action.EncodeTable(out, tbl)
|
||||
}
|
||||
|
||||
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 nil
|
||||
return outfmt.Write(out, newHubSearchWriter(results, o.searchEndpoint, o.maxColWidth))
|
||||
}
|
||||
|
||||
func (o *searchHubOptions) formatSearchResults(endpoint string, res []monocular.SearchResult) string {
|
||||
if len(res) == 0 {
|
||||
return "No results found"
|
||||
type hubChartElement struct {
|
||||
URL string
|
||||
Version string
|
||||
AppVersion string
|
||||
Description string
|
||||
}
|
||||
|
||||
type hubSearchWriter struct {
|
||||
elements []hubChartElement
|
||||
columnWidth uint
|
||||
}
|
||||
|
||||
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})
|
||||
}
|
||||
return &hubSearchWriter{elements, columnWidth}
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
// 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
|
||||
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 table.String()
|
||||
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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
|||
1
cmd/helm/testdata/output/history.yaml
vendored
1
cmd/helm/testdata/output/history.yaml
vendored
|
|
@ -10,4 +10,3 @@
|
|||
revision: 4
|
||||
status: deployed
|
||||
updated: 1977-09-02 22:04:05 +0000 UTC
|
||||
|
||||
|
|
|
|||
1
cmd/helm/testdata/output/search-output-json.txt
vendored
Normal file
1
cmd/helm/testdata/output/search-output-json.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
[{"Name":"testing/mariadb","Version":"0.3.0","AppVersion":"","Description":"Chart for MariaDB"}]
|
||||
4
cmd/helm/testdata/output/search-output-yaml.txt
vendored
Normal file
4
cmd/helm/testdata/output/search-output-yaml.txt
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
- AppVersion: 2.3.4
|
||||
Description: Deploy a basic Alpine Linux pod
|
||||
Name: testing/alpine
|
||||
Version: 0.2.0
|
||||
2
cmd/helm/testdata/output/status.json
vendored
2
cmd/helm/testdata/output/status.json
vendored
|
|
@ -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 w.WriteJSON(out)
|
||||
case YAML:
|
||||
return w.WriteYAML(out)
|
||||
}
|
||||
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
|
||||
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…
Reference in a new issue