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 }