github.com/splunk/dan1-qbec@v0.7.3/internal/commands/show.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 "encoding/json" 21 "fmt" 22 "io" 23 "strings" 24 25 "github.com/ghodss/yaml" 26 "github.com/spf13/cobra" 27 "github.com/splunk/qbec/internal/model" 28 "github.com/splunk/qbec/internal/objsort" 29 "github.com/splunk/qbec/internal/sio" 30 "github.com/splunk/qbec/internal/types" 31 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 32 ) 33 34 type metaOnly struct { 35 model.K8sLocalObject 36 } 37 38 func (n *metaOnly) MarshalJSON() ([]byte, error) { 39 gvk := n.GroupVersionKind() 40 m := map[string]string{ 41 "apiVersion": gvk.GroupVersion().String(), 42 "component": n.Component(), 43 "environment": n.Environment(), 44 "kind": gvk.Kind, 45 "name": model.NameForDisplay(n), 46 } 47 if n.GetNamespace() != "" { 48 m["namespace"] = n.GetNamespace() 49 } 50 return json.Marshal(m) 51 } 52 53 func showNames(objects []model.K8sLocalObject, formatSpecified bool, format string, w io.Writer) error { 54 if !formatSpecified { // render as table 55 fmt.Fprintf(w, "%-30s %-30s %-40s %s\n", "COMPONENT", "KIND", "NAME", "NAMESPACE") 56 for _, o := range objects { 57 name := model.NameForDisplay(o) 58 fmt.Fprintf(w, "%-30s %-30s %-40s %s\n", o.Component(), o.GroupVersionKind().Kind, name, o.GetNamespace()) 59 } 60 return nil 61 } 62 63 out := make([]model.K8sLocalObject, 0, len(objects)) 64 for _, o := range objects { 65 out = append(out, &metaOnly{o}) 66 } 67 objects = out 68 69 switch format { 70 case "json": 71 encoder := json.NewEncoder(w) 72 encoder.SetIndent("", " ") 73 return encoder.Encode(objects) 74 default: 75 b, err := yaml.Marshal(objects) 76 if err != nil { 77 return err 78 } 79 fmt.Fprintln(w, "---") 80 fmt.Fprintf(w, "%s\n", b) 81 return nil 82 } 83 } 84 85 type showCommandConfig struct { 86 *Config 87 showSecrets bool 88 format string 89 formatSpecified bool 90 sortAsApply bool 91 namesOnly bool 92 filterFunc func() (filterParams, error) 93 } 94 95 func removeMetadataKey(un *unstructured.Unstructured, name string) { 96 meta := un.Object["metadata"] 97 if m, ok := meta.(map[string]interface{}); ok { 98 delete(m, name) 99 } 100 } 101 102 func cleanMeta(obj model.K8sLocalObject) *unstructured.Unstructured { 103 un := obj.ToUnstructured() 104 annotations := un.GetAnnotations() 105 labels := un.GetLabels() 106 deleteQbecKeys := func(obj map[string]string) { 107 for k := range obj { 108 if strings.HasPrefix(k, model.QBECMetadataPrefix) { 109 delete(obj, k) 110 } 111 } 112 } 113 deleteQbecKeys(labels) 114 deleteQbecKeys(annotations) 115 if len(labels) == 0 { 116 removeMetadataKey(un, "labels") 117 } else { 118 un.SetLabels(labels) 119 } 120 if len(annotations) == 0 { 121 removeMetadataKey(un, "annotations") 122 } else { 123 un.SetAnnotations(annotations) 124 } 125 return un 126 } 127 128 func doShow(args []string, config showCommandConfig) error { 129 if len(args) != 1 { 130 return newUsageError("exactly one environment required") 131 } 132 env := args[0] 133 format := config.format 134 if format != "json" && format != "yaml" { 135 return newUsageError(fmt.Sprintf("invalid output format: %q", format)) 136 } 137 fp, err := config.filterFunc() 138 if err != nil { 139 return err 140 } 141 142 // shallow duplicate check 143 keyFunc := func(obj model.K8sMeta) string { 144 gvk := obj.GroupVersionKind() 145 ns := obj.GetNamespace() 146 return fmt.Sprintf("%s:%s:%s:%s", gvk.Group, gvk.Kind, ns, obj.GetName()) 147 } 148 149 objects, err := filteredObjects(config.Config, env, keyFunc, fp) 150 if err != nil { 151 return err 152 } 153 154 if !config.showSecrets { 155 for i, o := range objects { 156 objects[i], _ = types.HideSensitiveLocalInfo(o) 157 } 158 } 159 160 if config.sortAsApply { 161 if env == model.Baseline { 162 sio.Warnln("cannot sort in apply order for baseline environment") 163 } else { 164 client, err := config.Client(env) 165 if err != nil { 166 return err 167 } 168 objects = objsort.Sort(objects, sortConfig(client.IsNamespaced)) 169 } 170 } 171 172 if config.namesOnly { 173 return showNames(objects, config.formatSpecified, format, config.Stdout()) 174 } 175 176 var displayObjects []*unstructured.Unstructured 177 mapper := func(o model.K8sLocalObject) *unstructured.Unstructured { return o.ToUnstructured() } 178 179 if config.cleanEvalMode { 180 mapper = cleanMeta 181 } 182 183 for _, o := range objects { 184 displayObjects = append(displayObjects, mapper(o)) 185 } 186 187 switch format { 188 case "json": 189 encoder := json.NewEncoder(config.Stdout()) 190 encoder.SetIndent("", " ") 191 return encoder.Encode(displayObjects) 192 default: 193 for _, o := range displayObjects { 194 b, err := yaml.Marshal(o) 195 if err != nil { 196 return err 197 } 198 fmt.Fprintln(config.Stdout(), "---") 199 fmt.Fprintf(config.Stdout(), "%s\n", b) 200 } 201 return nil 202 } 203 } 204 205 func newShowCommand(cp ConfigProvider) *cobra.Command { 206 cmd := &cobra.Command{ 207 Use: "show <environment>", 208 Short: "show output in YAML or JSON format for one or more components", 209 Example: showExamples(), 210 } 211 212 config := showCommandConfig{ 213 filterFunc: addFilterParams(cmd, true), 214 } 215 216 var clean bool 217 cmd.Flags().StringVarP(&config.format, "format", "o", "yaml", "Output format. Supported values are: json, yaml") 218 cmd.Flags().BoolVarP(&config.namesOnly, "objects", "O", false, "Only print names of objects instead of their contents") 219 cmd.Flags().BoolVar(&config.sortAsApply, "sort-apply", false, "sort output in apply order (requires cluster access)") 220 cmd.Flags().BoolVar(&clean, "clean", false, "do not display qbec-generated labels and annotations") 221 cmd.Flags().BoolVarP(&config.showSecrets, "show-secrets", "S", false, "do not obfuscate secret values in the output") 222 223 cmd.RunE = func(c *cobra.Command, args []string) error { 224 config.Config = cp() 225 config.formatSpecified = c.Flags().Changed("format") 226 config.Config.cleanEvalMode = clean 227 return wrapError(doShow(args, config)) 228 } 229 return cmd 230 }