github.com/splunk/dan1-qbec@v0.7.3/internal/commands/param.go (about)

     1  /*
     2     Copyright 2019 Splunk Inc.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package commands
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	"fmt"
    23  	"io"
    24  	"sort"
    25  
    26  	"github.com/ghodss/yaml"
    27  	"github.com/spf13/cobra"
    28  	"github.com/splunk/qbec/internal/diff"
    29  	"github.com/splunk/qbec/internal/eval"
    30  	"github.com/splunk/qbec/internal/model"
    31  	"github.com/splunk/qbec/internal/sio"
    32  )
    33  
    34  var maxDisplayValueLength = 1024
    35  
    36  func newParamCommand(cp ConfigProvider) *cobra.Command {
    37  	cmd := &cobra.Command{
    38  		Use:     "param <subcommand>",
    39  		Short:   "parameter lists and diffs",
    40  		Aliases: []string{"params"},
    41  	}
    42  	cmd.AddCommand(newParamListCommand(cp), newParamDiffCommand(cp))
    43  	return cmd
    44  }
    45  
    46  func listParams(components map[string]interface{}, formatSpecified bool, format string, w io.Writer) error {
    47  	var p []param
    48  	for c, v := range components {
    49  		val, ok := v.(map[string]interface{})
    50  		if !ok {
    51  			sio.Warnln("invalid parameter format for", c, ",expected object")
    52  			continue
    53  		}
    54  		for n, v := range val {
    55  			p = append(p, param{Component: c, Name: n, Value: v})
    56  		}
    57  	}
    58  	sort.Slice(p, func(i, j int) bool {
    59  		if p[i].Component != p[j].Component {
    60  			return p[i].Component < p[j].Component
    61  		}
    62  		return p[i].Name < p[j].Name
    63  	})
    64  	if !formatSpecified {
    65  		fmt.Fprintf(w, "%-30s %-30s %s\n", "COMPONENT", "NAME", "VALUE")
    66  		for _, param := range p {
    67  			valBytes, _ := json.Marshal(param.Value)
    68  			valStr := string(valBytes)
    69  			if len(valStr) > maxDisplayValueLength {
    70  				valStr = valStr[:maxDisplayValueLength-3] + "..."
    71  			}
    72  			fmt.Fprintf(w, "%-30s %-30s %s\n", param.Component, param.Name, valStr)
    73  		}
    74  		return nil
    75  	}
    76  	switch format {
    77  	case "yaml":
    78  		b, err := yaml.Marshal(p)
    79  		if err != nil {
    80  			return err
    81  		}
    82  		fmt.Fprintln(w, "---")
    83  		fmt.Fprintf(w, "%s\n", b)
    84  		return nil
    85  	case "json":
    86  		encoder := json.NewEncoder(w)
    87  		encoder.SetIndent("", "  ")
    88  		return encoder.Encode(p)
    89  	default:
    90  		return newUsageError(fmt.Sprintf("listParams: unsupported format %q", format))
    91  	}
    92  }
    93  
    94  type param struct {
    95  	Component string      `json:"component"`
    96  	Name      string      `json:"name"`
    97  	Value     interface{} `json:"value"`
    98  }
    99  
   100  func extractComponentParams(paramsObject map[string]interface{}, fp filterParams) (map[string]interface{}, error) {
   101  	cf, err := model.NewComponentFilter(fp.includes, fp.excludes)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  	baseComponents, ok := paramsObject["components"].(map[string]interface{})
   106  	if !ok {
   107  		return nil, fmt.Errorf("unable to find 'components' key in the parameter object")
   108  	}
   109  	var components map[string]interface{}
   110  	if !cf.HasFilters() {
   111  		components = baseComponents
   112  	} else {
   113  		components = map[string]interface{}{}
   114  		for k, v := range baseComponents {
   115  			if cf.ShouldInclude(k) {
   116  				components[k] = v
   117  			}
   118  		}
   119  	}
   120  	return components, nil
   121  }
   122  
   123  type paramListCommandConfig struct {
   124  	*Config
   125  	format     string
   126  	filterFunc func() (filterParams, error)
   127  }
   128  
   129  func doParamList(args []string, config paramListCommandConfig) error {
   130  	if len(args) != 1 {
   131  		return newUsageError("exactly one environment required")
   132  	}
   133  	env := args[0]
   134  	if env != model.Baseline {
   135  		_, err := config.App().ServerURL(env)
   136  		if err != nil {
   137  			return err
   138  		}
   139  	}
   140  	paramsFile := config.App().ParamsFile()
   141  	paramsObject, err := eval.Params(paramsFile, config.EvalContext(env))
   142  	if err != nil {
   143  		return err
   144  	}
   145  	fp, err := config.filterFunc()
   146  	if err != nil {
   147  		return err
   148  	}
   149  	components, err := extractComponentParams(paramsObject, fp)
   150  	if err != nil {
   151  		return err
   152  	}
   153  	return listParams(components, config.format != "", config.format, config.Stdout())
   154  }
   155  
   156  func newParamListCommand(cp ConfigProvider) *cobra.Command {
   157  	cmd := &cobra.Command{
   158  		Use:     "list [-c component]...  <environment>|_",
   159  		Short:   "list all parameters for an environment, optionally for a subset of components",
   160  		Example: paramListExamples(),
   161  	}
   162  	config := paramListCommandConfig{
   163  		filterFunc: addFilterParams(cmd, false),
   164  	}
   165  	cmd.Flags().StringVarP(&config.format, "format", "o", "", "use json|yaml to display machine readable input")
   166  	cmd.RunE = func(c *cobra.Command, args []string) error {
   167  		config.Config = cp()
   168  		return wrapError(doParamList(args, config))
   169  	}
   170  	return cmd
   171  }
   172  
   173  type paramDiffCommandConfig struct {
   174  	*Config
   175  	filterFunc func() (filterParams, error)
   176  }
   177  
   178  func doParamDiff(args []string, config paramDiffCommandConfig) error {
   179  	var leftEnv, rightEnv string
   180  	switch len(args) {
   181  	case 1:
   182  		leftEnv = model.Baseline
   183  		rightEnv = args[0]
   184  	case 2:
   185  		leftEnv = args[0]
   186  		rightEnv = args[1]
   187  	default:
   188  		return newUsageError("one or two environments required")
   189  	}
   190  
   191  	fp, err := config.filterFunc()
   192  	if err != nil {
   193  		return err
   194  	}
   195  	getParams := func(env string) (str string, name string, err error) {
   196  		if env != model.Baseline {
   197  			_, err := config.App().ServerURL(env)
   198  			if err != nil {
   199  				return "", "", err
   200  			}
   201  		}
   202  		paramsFile := config.App().ParamsFile()
   203  		paramsObject, err := eval.Params(paramsFile, config.EvalContext(env))
   204  		if err != nil {
   205  			return "", "", err
   206  		}
   207  		components, err := extractComponentParams(paramsObject, fp)
   208  		if err != nil {
   209  			return "", "", err
   210  		}
   211  		var buf bytes.Buffer
   212  		if err := listParams(components, false, "", &buf); err != nil {
   213  			return "", "", err
   214  		}
   215  		name = "environment: " + env
   216  		if env == model.Baseline {
   217  			name = "baseline"
   218  		}
   219  		return buf.String(), name, nil
   220  	}
   221  
   222  	var left, right, leftName, rightName string
   223  
   224  	left, leftName, err = getParams(leftEnv)
   225  	if err != nil {
   226  		return err
   227  	}
   228  	right, rightName, err = getParams(rightEnv)
   229  	if err != nil {
   230  		return err
   231  	}
   232  
   233  	opts := diff.Options{Context: -1, LeftName: leftName, RightName: rightName, Colorize: config.Colorize()}
   234  	d, err := diff.Strings(left, right, opts)
   235  	if err != nil {
   236  		return err
   237  	}
   238  	fmt.Fprintln(config.Stdout(), string(d))
   239  	return nil
   240  
   241  }
   242  
   243  func newParamDiffCommand(cp ConfigProvider) *cobra.Command {
   244  	cmd := &cobra.Command{
   245  		Use:     "diff [-c component]... <environment>|_ [<environment>|_]",
   246  		Short:   "diff parameter lists across two environments or between the baseline (use _ for baseline) and an environment",
   247  		Example: paramDiffExamples(),
   248  	}
   249  
   250  	config := paramDiffCommandConfig{
   251  		filterFunc: addFilterParams(cmd, false),
   252  	}
   253  
   254  	cmd.RunE = func(c *cobra.Command, args []string) error {
   255  		config.Config = cp()
   256  		return wrapError(doParamDiff(args, config))
   257  	}
   258  	return cmd
   259  }