From eabd30c51096cd36e8530d27d1b69d163f777d4a Mon Sep 17 00:00:00 2001
From: Jessica Yuen <im.jessicayuen@gmail.com>
Date: Thu, 2 Nov 2017 12:42:24 -0700
Subject: [PATCH] Introduce command: 'ks param list [component] [--env=<env>]'

Pretty prints component or environment parameters.

This command will display all parameters for the component specified. If
a component is not specified, parameters for all components will be
listed.

Furthermore, parameters can be listed on a per-environment basis.

Examples:

List all component parameters
  ks param list

List all parameters for the component "guestbook"
  ks param list guestbook

List all parameters for the environment "dev"
  ks param list --env=dev

List all parameters for the component "guestbook" in the environment
"dev"
  ks param list guestbook --env=dev`,
---
 cmd/param.go         |  45 ++++++++++++
 pkg/kubecfg/param.go | 167 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 212 insertions(+)

diff --git a/cmd/param.go b/cmd/param.go
index 6679ff6f..af41950c 100644
--- a/cmd/param.go
+++ b/cmd/param.go
@@ -31,8 +31,10 @@ func init() {
 	RootCmd.AddCommand(paramCmd)
 
 	paramCmd.AddCommand(paramSetCmd)
+	paramCmd.AddCommand(paramListCmd)
 
 	paramSetCmd.PersistentFlags().String(flagParamEnv, "", "Specify environment to set parameters for")
+	paramListCmd.PersistentFlags().String(flagParamEnv, "", "Specify environment to list parameters for")
 }
 
 var paramCmd = &cobra.Command{
@@ -93,3 +95,46 @@ of environment parameters, we suggest modifying the
   # 'dev'
   ks param set guestbook replicas 2 --env=dev`,
 }
+
+var paramListCmd = &cobra.Command{
+	Use:   "list [component-name]",
+	Short: "List all parameters for a component(s)",
+	RunE: func(cmd *cobra.Command, args []string) error {
+		flags := cmd.Flags()
+		if len(args) > 1 {
+			return fmt.Errorf("'param list' takes at most one argument, that is the name of the component")
+		}
+
+		component := ""
+		if len(args) == 1 {
+			component = args[0]
+		}
+
+		env, err := flags.GetString(flagParamEnv)
+		if err != nil {
+			return err
+		}
+
+		c := kubecfg.NewParamListCmd(component, env)
+
+		return c.Run(cmd.OutOrStdout())
+	},
+	Long: `"List all component parameters or environment parameters.
+
+This command will display all parameters for the component specified. If a
+component is not specified, parameters for all components will be listed.
+
+Furthermore, parameters can be listed on a per-environment basis.
+`,
+	Example: `  # List all component parameters
+  ks param list
+
+  # List all parameters for the component "guestbook"
+  ks param list guestbook
+
+  # List all parameters for the environment "dev"
+  ks param list --env=dev
+
+  # List all parameters for the component "guestbook" in the environment "dev"
+  ks param list guestbook --env=dev`,
+}
diff --git a/pkg/kubecfg/param.go b/pkg/kubecfg/param.go
index e06e2ae7..3ddfb07d 100644
--- a/pkg/kubecfg/param.go
+++ b/pkg/kubecfg/param.go
@@ -17,11 +17,38 @@ package kubecfg
 
 import (
 	"fmt"
+	"io"
+	"sort"
 	"strconv"
+	"strings"
+
+	param "github.com/ksonnet/ksonnet/metadata/params"
 
 	log "github.com/sirupsen/logrus"
 )
 
+func sortedKeys(params map[string]param.Params) []string {
+	// keys maintains an alphabetically sorted list of the components
+	keys := make([]string, 0, len(params))
+	for key := range params {
+		keys = append(keys, key)
+	}
+	sort.Strings(keys)
+	return keys
+}
+
+func sortedParams(params param.Params) []string {
+	// keys maintains an alphabetically sorted list of the params
+	keys := make([]string, 0, len(params))
+	for key := range params {
+		keys = append(keys, key)
+	}
+	sort.Strings(keys)
+	return keys
+}
+
+// ----------------------------------------------------------------------------
+
 // ParamSetCmd stores the information necessary to set component and
 // environment params.
 type ParamSetCmd struct {
@@ -73,3 +100,143 @@ func sanitizeParamValue(value string) string {
 	// string
 	return fmt.Sprintf(`"%s"`, value)
 }
+
+// ----------------------------------------------------------------------------
+
+const (
+	paramComponentHeader = "COMPONENT"
+	paramNameHeader      = "PARAM"
+	paramValueHeader     = "VALUE"
+)
+
+// ParamListCmd stores the information necessary display component or
+// environment parameters
+type ParamListCmd struct {
+	component string
+	env       string
+}
+
+// NewParamListCmd acts as a constructor for ParamListCmd.
+func NewParamListCmd(component, env string) *ParamListCmd {
+	return &ParamListCmd{component: component, env: env}
+}
+
+// Run executes the displaying of params.
+func (c *ParamListCmd) Run(out io.Writer) error {
+	manager, err := manager()
+	if err != nil {
+		return err
+	}
+
+	var params map[string]param.Params
+	if len(c.env) != 0 {
+		params, err = manager.GetEnvironmentParams(c.env)
+		if err != nil {
+			return err
+		}
+	} else {
+		params, err = manager.GetAllComponentParams()
+		if err != nil {
+			return err
+		}
+	}
+
+	if len(c.component) != 0 {
+		if _, ok := params[c.component]; !ok {
+			return fmt.Errorf("No such component '%s' found", c.component)
+		}
+
+		p := params[c.component]
+		return outputParamsFor(c.component, p, out)
+	}
+
+	return outputParams(params, out)
+}
+
+func outputParamsFor(component string, params param.Params, out io.Writer) error {
+	keys := sortedParams(params)
+
+	//
+	// Format each parameter information for pretty printing.
+	// Each parameter will be outputted alphabetically like the following:
+	//
+	//   PARAM    VALUE
+	//   name     "foo"
+	//   replicas 1
+	//
+
+	maxParamLen := len(paramNameHeader)
+	for _, k := range keys {
+		if l := len(k); l > maxParamLen {
+			maxParamLen = l
+		}
+	}
+
+	nameSpacing := strings.Repeat(" ", maxParamLen-len(paramNameHeader)+1)
+
+	lines := []string{}
+	lines = append(lines, paramNameHeader+nameSpacing+paramValueHeader+"\n")
+	lines = append(lines, strings.Repeat("=", len(paramNameHeader))+nameSpacing+
+		strings.Repeat("=", len(paramValueHeader))+"\n")
+
+	for _, k := range keys {
+		nameSpacing = strings.Repeat(" ", maxParamLen-len(k)+1)
+		lines = append(lines, k+nameSpacing+params[k]+"\n")
+	}
+
+	_, err := fmt.Fprint(out, strings.Join(lines, ""))
+	return err
+}
+
+func outputParams(params map[string]param.Params, out io.Writer) error {
+	keys := sortedKeys(params)
+
+	//
+	// Format each component parameter information for pretty printing.
+	// Each component will be outputted alphabetically like the following:
+	//
+	//   COMPONENT PARAM     VALUE
+	//   bar       name      "bar"
+	//   bar       replicas  2
+	//   foo       name      "foo"
+	//   foo       replicas  1
+	//
+
+	maxComponentLen := len(paramComponentHeader)
+	for _, k := range keys {
+		if l := len(k); l > maxComponentLen {
+			maxComponentLen = l
+		}
+	}
+
+	maxParamLen := len(paramNameHeader) + maxComponentLen + 1
+	for _, k := range keys {
+		for p := range params[k] {
+			if l := len(p) + maxComponentLen + 1; l > maxParamLen {
+				maxParamLen = l
+			}
+		}
+	}
+
+	componentSpacing := strings.Repeat(" ", maxComponentLen-len(paramComponentHeader)+1)
+	nameSpacing := strings.Repeat(" ", maxParamLen-maxComponentLen-len(paramNameHeader))
+
+	lines := []string{}
+	lines = append(lines, paramComponentHeader+componentSpacing+paramNameHeader+nameSpacing+paramValueHeader+"\n")
+	lines = append(lines, strings.Repeat("=", len(paramComponentHeader))+componentSpacing+
+		strings.Repeat("=", len(paramNameHeader))+nameSpacing+strings.Repeat("=", len(paramValueHeader))+"\n")
+
+	for _, k := range keys {
+		// sort params to display alphabetically
+		ps := sortedParams(params[k])
+
+		for _, p := range ps {
+			componentSpacing = strings.Repeat(" ", maxComponentLen-len(k)+1)
+			nameSpacing = strings.Repeat(" ", maxParamLen-maxComponentLen-len(p))
+			lines = append(lines, k+componentSpacing+p+nameSpacing+params[k][p]+"\n")
+		}
+	}
+
+	_, err := fmt.Fprint(out, strings.Join(lines, ""))
+	return err
+}
-- 
GitLab