Unverified Commit c8f57384 authored by Taylor Thomas's avatar Taylor Thomas Committed by GitHub

Merge pull request #6381 from thomastaylor312/ref/cmd_output

Ref/cmd output
parents 17359e3f 3e5b181a
......@@ -227,7 +227,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
f.BoolVar(&inst.depUp, "dep-up", false, "Run helm dependency update before installing the chart")
f.BoolVar(&inst.subNotes, "render-subchart-notes", false, "Render subchart notes along with the parent")
f.StringVar(&inst.description, "description", "", "Specify a description for the release")
f.StringVarP(&inst.output, "output", "o", "table", "Prints the output in the specified format (json|table|yaml)")
bindOutputFlag(cmd, &inst.output)
// set defaults from environment
settings.InitTLS(f)
......@@ -338,7 +338,7 @@ func (i *installCmd) run() error {
return nil
}
if i.output == "table" {
if outputFormat(i.output) == outputTable {
i.printRelease(rel)
}
......@@ -357,15 +357,7 @@ func (i *installCmd) run() error {
return prettyError(err)
}
output, err := PrintStatusFormated(i.output, status)
if err != nil {
return err
}
fmt.Fprintf(i.out, output)
return nil
return write(i.out, &statusWriter{status}, outputFormat(i.output))
}
// Merges source and destination map, preferring values from the source map
......
......@@ -17,16 +17,31 @@ limitations under the License.
package main
import (
"encoding/json"
"fmt"
"io"
"text/template"
"time"
"github.com/ghodss/yaml"
"github.com/gosuri/uitable"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/timeconv"
)
type outputFormat string
const (
outputFlag = "output"
outputTable outputFormat = "table"
outputJSON outputFormat = "json"
outputYAML outputFormat = "yaml"
)
var printReleaseTemplate = `REVISION: {{.Release.Version}}
RELEASED: {{.ReleaseDate}}
CHART: {{.Release.Chart.Metadata.Name}}-{{.Release.Chart.Metadata.Version}}
......@@ -80,3 +95,66 @@ func debug(format string, args ...interface{}) {
fmt.Printf(format, args...)
}
}
// 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) {
cmd.Flags().StringVarP(varRef, outputFlag, "o", string(outputTable), fmt.Sprintf("Prints the output in the specified format. Allowed values: %s, %s, %s", outputTable, outputJSON, outputYAML))
}
type outputWriter interface {
WriteTable(out io.Writer) error
WriteJSON(out io.Writer) error
WriteYAML(out io.Writer) error
}
func write(out io.Writer, ow outputWriter, format outputFormat) error {
switch format {
case outputTable:
return ow.WriteTable(out)
case outputJSON:
return ow.WriteJSON(out)
case outputYAML:
return ow.WriteYAML(out)
}
return fmt.Errorf("unsupported format %s", format)
}
// 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 fmt.Errorf("unable to write JSON output: %s", err)
}
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 fmt.Errorf("unable to write YAML output: %s", err)
}
// Append a newline, as with a JSON encoder
raw = append(raw, []byte("\n")...)
_, err = out.Write(raw)
if err != nil {
return fmt.Errorf("unable to write YAML output: %s", err)
}
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 fmt.Errorf("unable to write table output: %s", err)
}
return nil
}
......@@ -17,12 +17,8 @@ limitations under the License.
package main
import (
"encoding/json"
"fmt"
"io"
"strings"
"github.com/ghodss/yaml"
"github.com/gosuri/uitable"
"github.com/spf13/cobra"
......@@ -41,10 +37,6 @@ type repositoryElement struct {
URL string
}
type repositories struct {
Repositories []*repositoryElement
}
func newRepoListCmd(out io.Writer) *cobra.Command {
list := &repoListCmd{out: out}
......@@ -57,8 +49,7 @@ func newRepoListCmd(out io.Writer) *cobra.Command {
},
}
f := cmd.Flags()
f.StringVarP(&list.output, "output", "o", "table", "Prints the output in the specified format (json|table|yaml)")
bindOutputFlag(cmd, &list.output)
return cmd
}
......@@ -68,59 +59,46 @@ func (a *repoListCmd) run() error {
return err
}
output, err := formatRepoListResult(a.output, repoFile)
if err != nil {
return err
}
fmt.Fprintln(a.out, output)
return nil
return write(a.out, &repoListWriter{repoFile.Repositories}, outputFormat(a.output))
}
func formatRepoListResult(format string, repoFile *repo.RepoFile) (string, error) {
var output string
var err error
//////////// Printer implementation below here
type repoListWriter struct {
repos []*repo.Entry
}
if len(repoFile.Repositories) == 0 {
err = fmt.Errorf("no repositories to show")
return output, err
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 encodeTable(out, table)
}
switch format {
case "table":
table := uitable.New()
table.AddRow("NAME", "URL")
for _, re := range repoFile.Repositories {
table.AddRow(re.Name, re.URL)
}
output = table.String()
case "json":
output, err = printFormatedRepoFile(format, repoFile, json.Marshal)
case "yaml":
output, err = printFormatedRepoFile(format, repoFile, yaml.Marshal)
}
func (r *repoListWriter) WriteJSON(out io.Writer) error {
return r.encodeByFormat(out, outputJSON)
}
return output, err
func (r *repoListWriter) WriteYAML(out io.Writer) error {
return r.encodeByFormat(out, outputYAML)
}
func printFormatedRepoFile(format string, repoFile *repo.RepoFile, obj func(v interface{}) ([]byte, error)) (string, error) {
var output string
var err error
var repolist repositories
func (r *repoListWriter) encodeByFormat(out io.Writer, format outputFormat) error {
var repolist []repositoryElement
for _, re := range repoFile.Repositories {
repolist.Repositories = append(repolist.Repositories, &repositoryElement{Name: re.Name, URL: re.URL})
for _, re := range r.repos {
repolist = append(repolist, repositoryElement{Name: re.Name, URL: re.URL})
}
o, e := obj(repolist)
if e != nil {
err = fmt.Errorf("Failed to Marshal %s output: %s", strings.ToUpper(format), e)
} else {
output = string(o)
switch format {
case outputJSON:
return encodeJSON(out, repolist)
case outputYAML:
return encodeYAML(out, repolist)
}
return output, err
// Because this is a non-exported function and only called internally by
// WriteJSON and WriteYAML, we shouldn't get invalid types
return nil
}
......@@ -17,13 +17,11 @@ limitations under the License.
package main
import (
"encoding/json"
"fmt"
"io"
"strings"
"github.com/Masterminds/semver"
"github.com/ghodss/yaml"
"github.com/gosuri/uitable"
"github.com/spf13/cobra"
......@@ -60,10 +58,6 @@ type chartElement struct {
Description string
}
type searchResult struct {
Charts []*chartElement
}
func newSearchCmd(out io.Writer) *cobra.Command {
sc := &searchCmd{out: out}
......@@ -82,7 +76,7 @@ func newSearchCmd(out io.Writer) *cobra.Command {
f.BoolVarP(&sc.versions, "versions", "l", false, "Show the long listing, with each version of each chart on its own line")
f.StringVarP(&sc.version, "version", "v", "", "Search using semantic versioning constraints")
f.UintVar(&sc.colWidth, "col-width", 60, "Specifies the max column width of output")
f.StringVarP(&sc.output, "output", "o", "table", "Prints the output in the specified format (json|table|yaml)")
bindOutputFlag(cmd, &sc.output)
return cmd
}
......@@ -110,14 +104,7 @@ func (s *searchCmd) run(args []string) error {
return err
}
o, err := s.formatSearchResults(s.output, data, s.colWidth)
if err != nil {
return err
}
fmt.Fprintln(s.out, o)
return nil
return write(s.out, &searchWriter{data, s.colWidth}, outputFormat(s.output))
}
func (s *searchCmd) applyConstraint(res []*search.Result) ([]*search.Result, error) {
......@@ -148,55 +135,6 @@ func (s *searchCmd) applyConstraint(res []*search.Result) ([]*search.Result, err
return data, nil
}
func (s *searchCmd) formatSearchResults(format string, res []*search.Result, colWidth uint) (string, error) {
var output string
var err error
switch format {
case "table":
if len(res) == 0 {
return "No results found", nil
}
table := uitable.New()
table.MaxColWidth = colWidth
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)
}
output = table.String()
case "json":
output, err = s.printFormated(format, res, json.Marshal)
case "yaml":
output, err = s.printFormated(format, res, yaml.Marshal)
}
return output, err
}
func (s *searchCmd) printFormated(format string, res []*search.Result, obj func(v interface{}) ([]byte, error)) (string, error) {
var sResult searchResult
var output string
var err error
if len(res) == 0 {
return "[]", nil
}
for _, r := range res {
sResult.Charts = append(sResult.Charts, &chartElement{r.Name, r.Chart.Version, r.Chart.AppVersion, r.Chart.Description})
}
o, e := obj(sResult)
if e != nil {
err = fmt.Errorf("Failed to Marshal %s output: %s", strings.ToUpper(format), e)
} else {
output = string(o)
}
return output, err
}
func (s *searchCmd) buildIndex() (*search.Index, error) {
// Load the repositories.yaml
rf, err := repo.LoadRepositoriesFile(s.helmhome.RepositoryFile())
......@@ -218,3 +156,53 @@ func (s *searchCmd) buildIndex() (*search.Index, error) {
}
return i, nil
}
//////////// Printer implementation below here
type searchWriter struct {
results []*search.Result
columnWidth uint
}
func (r *searchWriter) 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 encodeTable(out, table)
}
func (r *searchWriter) WriteJSON(out io.Writer) error {
return r.encodeByFormat(out, outputJSON)
}
func (r *searchWriter) WriteYAML(out io.Writer) error {
return r.encodeByFormat(out, outputYAML)
}
func (r *searchWriter) encodeByFormat(out io.Writer, format outputFormat) error {
var chartList []chartElement
for _, r := range r.results {
chartList = append(chartList, chartElement{r.Name, r.Chart.Version, r.Chart.AppVersion, r.Chart.Description})
}
switch format {
case outputJSON:
return encodeJSON(out, chartList)
case outputYAML:
return 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
}
......@@ -18,7 +18,6 @@ package main
import (
"io"
"regexp"
"strings"
"testing"
......@@ -90,25 +89,25 @@ func TestSearchCmd(t *testing.T) {
name: "search for 'maria', expect one match output json",
args: []string{"maria"},
flags: strings.Split("--output json", " "),
expected: regexp.QuoteMeta(`{"Charts":[{"Name":"testing/mariadb","Version":"0.3.0","AppVersion":"","Description":"Chart for MariaDB"}]}`),
expected: `[{"Name":"testing/mariadb","Version":"0.3.0","Appversion":"","Description":"Chart for MariaDB"}]`,
},
{
name: "search for 'alpine', expect two matches output json",
args: []string{"alpine"},
flags: strings.Split("--output json", " "),
expected: regexp.QuoteMeta(`{"Charts":[{"Name":"testing/alpine","Version":"0.2.0","AppVersion":"2.3.4","Description":"Deploy a basic Alpine Linux pod"}]}`),
expected: `[{"Name":"testing/alpine","Version":"0.2.0","Appversion":"2.3.4","Description":"Deploy a basic Alpine Linux pod"}]`,
},
{
name: "search for 'maria', expect one match output yaml",
args: []string{"maria"},
flags: strings.Split("--output yaml", " "),
expected: "Charts:\n- AppVersion: \"\"\n Description: Chart for MariaDB\n Name: testing/mariadb\n Version: 0.3.0\n\n",
expected: "- AppVersion: \"\"\n Description: Chart for MariaDB\n Name: testing/mariadb\n Version: 0.3.0\n\n",
},
{
name: "search for 'alpine', expect two matches output yaml",
args: []string{"alpine"},
flags: strings.Split("--output yaml", " "),
expected: "Charts:\n- AppVersion: 2.3.4\n Description: Deploy a basic Alpine Linux pod\n Name: testing/alpine\n Version: 0.2.0\n\n",
expected: "- AppVersion: 2.3.4\n Description: Deploy a basic Alpine Linux pod\n Name: testing/alpine\n Version: 0.2.0\n\n",
},
}
......
......@@ -17,14 +17,11 @@ limitations under the License.
package main
import (
"encoding/json"
"fmt"
"io"
"regexp"
"strings"
"text/tabwriter"
"github.com/ghodss/yaml"
"github.com/gosuri/uitable"
"github.com/gosuri/uitable/util/strutil"
"github.com/spf13/cobra"
......@@ -80,7 +77,7 @@ func newStatusCmd(client helm.Interface, out io.Writer) *cobra.Command {
f := cmd.Flags()
settings.AddFlagsTLS(f)
f.Int32Var(&status.version, "revision", 0, "If set, display the status of the named release with revision")
f.StringVarP(&status.outfmt, "output", "o", "table", "Prints the output in the specified format (json|table|yaml)")
bindOutputFlag(cmd, &status.outfmt)
// set defaults from environment
settings.InitTLS(f)
......@@ -94,82 +91,55 @@ func (s *statusCmd) run() error {
return prettyError(err)
}
output, err := PrintStatusFormated(s.outfmt, res)
if err != nil {
return err
}
return write(s.out, &statusWriter{res}, outputFormat(s.outfmt))
}
fmt.Fprintf(s.out, output)
type statusWriter struct {
status *services.GetReleaseStatusResponse
}
func (s *statusWriter) WriteTable(out io.Writer) error {
PrintStatus(out, s.status)
// There is no error handling here due to backwards compatibility with
// PrintStatus
return nil
}
// PrintStatusFormated prints out the status of a release. Shared because also used by
// install / upgrade
func PrintStatusFormated(format string, res *services.GetReleaseStatusResponse) (string, error) {
var output string
var err error
switch format {
case "table":
output = printStatus(res)
case "json":
output, err = printFormatedReleaseStatus(format, res, json.Marshal)
case "yaml":
output, err = printFormatedReleaseStatus(format, res, yaml.Marshal)
default:
err = fmt.Errorf("Unknown output format %q", err)
}
return output, err
func (s *statusWriter) WriteJSON(out io.Writer) error {
return encodeJSON(out, s.status)
}
func printFormatedReleaseStatus(format string, res *services.GetReleaseStatusResponse, obj func(v interface{}) ([]byte, error)) (string, error) {
var output string
var err error
o, err := obj(res)
if err != nil {
return "", fmt.Errorf("Failed to Marshal %s output: %s", strings.ToUpper(format), err)
}
output = string(o)
return output, err
func (s *statusWriter) WriteYAML(out io.Writer) error {
return encodeYAML(out, s.status)
}
func printStatus(res *services.GetReleaseStatusResponse) string {
var out strings.Builder
// PrintStatus prints out the status of a release. Shared because also used by
// install / upgrade
func PrintStatus(out io.Writer, res *services.GetReleaseStatusResponse) {
if res.Info.LastDeployed != nil {
fmt.Fprintf(&out, "LAST DEPLOYED: %s\n", timeconv.String(res.Info.LastDeployed))
fmt.Fprintf(out, "LAST DEPLOYED: %s\n", timeconv.String(res.Info.LastDeployed))
}
fmt.Fprintf(&out, "NAMESPACE: %s\n", res.Namespace)
fmt.Fprintf(&out, "STATUS: %s\n", res.Info.Status.Code)
fmt.Fprintf(&out, "\n")
fmt.Fprintf(out, "NAMESPACE: %s\n", res.Namespace)
fmt.Fprintf(out, "STATUS: %s\n", res.Info.Status.Code)
fmt.Fprintf(out, "\n")
if len(res.Info.Status.Resources) > 0 {
re := regexp.MustCompile(" +")
w := tabwriter.NewWriter(&out, 0, 0, 2, ' ', tabwriter.TabIndent)
w := tabwriter.NewWriter(out, 0, 0, 2, ' ', tabwriter.TabIndent)
fmt.Fprintf(w, "RESOURCES:\n%s\n", re.ReplaceAllString(res.Info.Status.Resources, "\t"))
w.Flush()
}
if res.Info.Status.LastTestSuiteRun != nil {
lastRun := res.Info.Status.LastTestSuiteRun
fmt.Fprintf(&out, "TEST SUITE:\n%s\n%s\n\n%s\n",
fmt.Fprintf(out, "TEST SUITE:\n%s\n%s\n\n%s\n",
fmt.Sprintf("Last Started: %s", timeconv.String(lastRun.StartedAt)),
fmt.Sprintf("Last Completed: %s", timeconv.String(lastRun.CompletedAt)),
formatTestResults(lastRun.Results))
}
if len(res.Info.Status.Notes) > 0 {
fmt.Fprintf(&out, "NOTES:\n%s\n", res.Info.Status.Notes)
fmt.Fprintf(out, "NOTES:\n%s\n", res.Info.Status.Notes)
}
return out.String()
}
func formatTestResults(results []*release.TestRun) string {
......
......@@ -182,7 +182,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
f.BoolVar(&upgrade.subNotes, "render-subchart-notes", false, "Render subchart notes along with parent")
f.StringVar(&upgrade.description, "description", "", "Specify the description to use for the upgrade, rather than the default")
f.BoolVar(&upgrade.cleanupOnFail, "cleanup-on-fail", false, "Allow deletion of new resources created in this upgrade when upgrade failed")
f.StringVarP(&upgrade.output, "output", "o", "table", "Prints the output in the specified format (json|table|yaml)")
bindOutputFlag(cmd, &upgrade.output)
f.MarkDeprecated("disable-hooks", "Use --no-hooks instead")
......@@ -309,7 +309,7 @@ func (u *upgradeCmd) run() error {
printRelease(u.out, resp.Release)
}
if u.output == "table" {
if outputFormat(u.output) == outputTable {
fmt.Fprintf(u.out, "Release %q has been upgraded.\n", u.release)
}
// Print the status like status command does
......@@ -318,13 +318,5 @@ func (u *upgradeCmd) run() error {
return prettyError(err)
}
output, err := PrintStatusFormated(u.output, status)
if err != nil {
return err
}
fmt.Fprintf(u.out, output)
return nil
return write(u.out, &statusWriter{status}, outputFormat(u.output))
}
......@@ -93,7 +93,7 @@ helm install [CHART] [flags]
--namespace string Namespace to install the release into. Defaults to the current kube config namespace.
--no-crd-hook Prevent CRD hooks from running, but run other hooks
--no-hooks Prevent hooks from running during install
-o, --output string Prints the output in the specified format (json|table|yaml) (default "table")
-o, --output string Prints the output in the specified format. Allowed values: table, json, yaml (default "table")
--password string Chart repository password where to locate the requested chart
--render-subchart-notes Render subchart notes along with the parent
--replace Re-use the given name, even if that name is already used. This is unsafe in production
......
......@@ -14,7 +14,7 @@ helm repo list [flags]
```
-h, --help help for list
-o, --output string Prints the output in the specified format (json|table|yaml) (default "table")
-o, --output string Prints the output in the specified format. Allowed values: table, json, yaml (default "table")
```
### Options inherited from parent commands
......
......@@ -20,7 +20,7 @@ helm search [keyword] [flags]
```
--col-width uint Specifies the max column width of output (default 60)
-h, --help help for search
-o, --output string Prints the output in the specified format (json|table|yaml) (default "table")
-o, --output string Prints the output in the specified format. Allowed values: table, json, yaml (default "table")
-r, --regexp Use regular expressions for searching
-v, --version string Search using semantic versioning constraints
-l, --versions Show the long listing, with each version of each chart on its own line
......
......@@ -23,7 +23,7 @@ helm status [flags] RELEASE_NAME
```
-h, --help help for status
-o, --output string Prints the output in the specified format (json|table|yaml) (default "table")
-o, --output string Prints the output in the specified format. Allowed values: table, json, yaml (default "table")
--revision int32 If set, display the status of the named release with revision
--tls Enable TLS for request
--tls-ca-cert string Path to TLS CA certificate file (default "$HELM_HOME/ca.pem")
......
......@@ -79,7 +79,7 @@ helm upgrade [RELEASE] [CHART] [flags]
--keyring string Path to the keyring that contains public signing keys (default "~/.gnupg/pubring.gpg")
--namespace string Namespace to install the release into (only used if --install is set). Defaults to the current kube config namespace
--no-hooks Disable pre/post upgrade hooks
-o, --output string Prints the output in the specified format (json|table|yaml) (default "table")
-o, --output string Prints the output in the specified format. Allowed values: table, json, yaml (default "table")
--password string Chart repository password where to locate the requested chart
--recreate-pods Performs pods restart for the resource if applicable
--render-subchart-notes Render subchart notes along with parent
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment