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  }