github.com/hernad/nomad@v1.6.112/command/var_list.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package command
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"os"
    10  	"sort"
    11  	"strings"
    12  
    13  	"github.com/hernad/nomad/api"
    14  	"github.com/posener/complete"
    15  )
    16  
    17  const (
    18  	msgWarnFilterPerformance = "Filter queries require a full scan of the data; use prefix searching where possible"
    19  )
    20  
    21  type VarListCommand struct {
    22  	prefix string
    23  	outFmt string
    24  	tmpl   string
    25  	Meta
    26  }
    27  
    28  func (c *VarListCommand) Help() string {
    29  	helpText := `
    30  Usage: nomad var list [options] <prefix>
    31  
    32    List is used to list available variables. Supplying an optional prefix,
    33    filters the list to variables having a path starting with the prefix.
    34    When using pagination, the next page token is provided in the JSON output
    35    or as a message to standard error to leave standard output for the listed
    36    variables from that page.
    37  
    38    If ACLs are enabled, this command will only return variables stored in
    39    namespaces and paths where the token has the 'variables:list' capability.
    40  
    41  General Options:
    42  
    43    ` + generalOptionsUsage(usageOptsDefault) + `
    44  
    45  List Options:
    46  
    47    -per-page
    48      How many results to show per page.
    49  
    50    -page-token
    51      Where to start pagination.
    52  
    53    -filter
    54      Specifies an expression used to filter query results. Queries using this
    55      option are less efficient than using the prefix parameter; therefore,
    56      the prefix parameter should be used whenever possible.
    57  
    58    -out (go-template | json | table | terse )
    59      Format to render created or updated variable. Defaults to "none" when
    60      stdout is a terminal and "json" when the output is redirected. The "terse"
    61  	format outputs as little information as possible to uniquely identify a
    62  	variable depending on whether or not the wildcard namespace was passed.
    63  
    64   -template
    65      Template to render output with. Required when format is "go-template",
    66      invalid for other formats.
    67  
    68  `
    69  	return strings.TrimSpace(helpText)
    70  }
    71  
    72  func (c *VarListCommand) AutocompleteFlags() complete.Flags {
    73  	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
    74  		complete.Flags{
    75  			"-out":      complete.PredictSet("go-template", "json", "terse", "table"),
    76  			"-template": complete.PredictAnything,
    77  		},
    78  	)
    79  }
    80  
    81  func (c *VarListCommand) AutocompleteArgs() complete.Predictor {
    82  	return complete.PredictNothing
    83  }
    84  
    85  func (c *VarListCommand) Synopsis() string {
    86  	return "List variable metadata"
    87  }
    88  
    89  func (c *VarListCommand) Name() string { return "var list" }
    90  func (c *VarListCommand) Run(args []string) int {
    91  	var perPage int
    92  	var pageToken, filter, prefix string
    93  
    94  	flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
    95  	flags.Usage = func() { c.Ui.Output(c.Help()) }
    96  	flags.StringVar(&c.tmpl, "template", "", "")
    97  
    98  	flags.IntVar(&perPage, "per-page", 0, "")
    99  	flags.StringVar(&pageToken, "page-token", "", "")
   100  	flags.StringVar(&filter, "filter", "", "")
   101  
   102  	if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) != 0 {
   103  		flags.StringVar(&c.outFmt, "out", "table", "")
   104  	} else {
   105  		flags.StringVar(&c.outFmt, "out", "json", "")
   106  	}
   107  
   108  	if err := flags.Parse(args); err != nil {
   109  		return 1
   110  	}
   111  
   112  	// Check that we got no arguments
   113  	args = flags.Args()
   114  	if l := len(args); l > 1 {
   115  		c.Ui.Error("This command takes flags and either no arguments or one: <prefix>")
   116  		c.Ui.Error(commandErrorText(c))
   117  		return 1
   118  	}
   119  
   120  	if len(args) == 1 {
   121  		prefix = args[0]
   122  	}
   123  
   124  	if err := c.validateOutputFlag(); err != nil {
   125  		c.Ui.Error(err.Error())
   126  		c.Ui.Error(commandErrorText(c))
   127  		return 1
   128  	}
   129  
   130  	// Get the HTTP client
   131  	client, err := c.Meta.Client()
   132  	if err != nil {
   133  		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
   134  		return 1
   135  	}
   136  
   137  	if filter != "" {
   138  		c.Ui.Warn(msgWarnFilterPerformance)
   139  	}
   140  
   141  	qo := &api.QueryOptions{
   142  		Filter:    filter,
   143  		PerPage:   int32(perPage),
   144  		NextToken: pageToken,
   145  		Params:    map[string]string{},
   146  	}
   147  
   148  	vars, qm, err := client.Variables().PrefixList(prefix, qo)
   149  	if err != nil {
   150  		c.Ui.Error(fmt.Sprintf("Error retrieving vars: %s", err))
   151  		return 1
   152  	}
   153  
   154  	switch c.outFmt {
   155  	case "json":
   156  		// obj and items enable us to rework the output before sending it
   157  		// to the Format method for transformation into JSON.
   158  		var obj, items interface{}
   159  		obj = vars
   160  		items = vars
   161  
   162  		// If the response is paginated, we need to provide a means for the
   163  		// caller to get to the pagination information. Wrapping the list
   164  		// in a struct for the special case allows this extra data without
   165  		// adding unnecessary structure in the non-paginated case.
   166  		if perPage > 0 {
   167  			obj = struct {
   168  				Data      interface{}
   169  				QueryMeta *api.QueryMeta
   170  			}{
   171  				items,
   172  				qm,
   173  			}
   174  		}
   175  
   176  		// By this point, the output is ready to be transformed to JSON via
   177  		// the Format func.
   178  		out, err := Format(true, "", obj)
   179  		if err != nil {
   180  			c.Ui.Error(err.Error())
   181  			return 1
   182  		}
   183  
   184  		c.Ui.Output(out)
   185  
   186  		// Since the JSON formatting deals with the pagination information
   187  		// itself, exit the command here so that it doesn't double print.
   188  		return 0
   189  
   190  	case "terse":
   191  		c.Ui.Output(
   192  			formatList(
   193  				dataToQuietStringSlice(vars, c.Meta.namespace)))
   194  
   195  	case "go-template":
   196  		out, err := Format(false, c.tmpl, vars)
   197  		if err != nil {
   198  			c.Ui.Error(err.Error())
   199  			return 1
   200  		}
   201  		c.Ui.Output(out)
   202  
   203  	default:
   204  		c.Ui.Output(formatVarStubs(vars))
   205  	}
   206  
   207  	if qm.NextToken != "" {
   208  		// This uses Ui.Warn to output the next page token to stderr
   209  		// so that scripts consuming paths from stdout will not have
   210  		// to special case the output.
   211  		c.Ui.Warn(fmt.Sprintf("Next page token: %s", qm.NextToken))
   212  	}
   213  
   214  	return 0
   215  }
   216  
   217  func formatVarStubs(vars []*api.VariableMetadata) string {
   218  	if len(vars) == 0 {
   219  		return errNoMatchingVariables
   220  	}
   221  
   222  	// Sort the output by variable namespace, path
   223  	sort.Slice(vars, func(i, j int) bool {
   224  		if vars[i].Namespace == vars[j].Namespace {
   225  			return vars[i].Path < vars[j].Path
   226  		}
   227  		return vars[i].Namespace < vars[j].Namespace
   228  	})
   229  
   230  	rows := make([]string, len(vars)+1)
   231  	rows[0] = "Namespace|Path|Last Updated"
   232  	for i, sv := range vars {
   233  		rows[i+1] = fmt.Sprintf("%s|%s|%s",
   234  			sv.Namespace,
   235  			sv.Path,
   236  			formatUnixNanoTime(sv.ModifyTime),
   237  		)
   238  	}
   239  	return formatList(rows)
   240  }
   241  
   242  func dataToQuietStringSlice(vars []*api.VariableMetadata, ns string) []string {
   243  	// If ns is the wildcard namespace, we have to provide namespace
   244  	// as part of the quiet output, otherwise it can be a simple list
   245  	// of paths.
   246  	toPathStr := func(v *api.VariableMetadata) string {
   247  		if ns == "*" {
   248  			return fmt.Sprintf("%s|%s", v.Namespace, v.Path)
   249  		}
   250  		return v.Path
   251  	}
   252  
   253  	// Reduce the items slice to a string slice containing only the
   254  	// variable paths.
   255  	pList := make([]string, len(vars))
   256  	for i, sv := range vars {
   257  		pList[i] = toPathStr(sv)
   258  	}
   259  
   260  	return pList
   261  }
   262  
   263  func (c *VarListCommand) validateOutputFlag() error {
   264  	if c.outFmt != "go-template" && c.tmpl != "" {
   265  		return errors.New(errUnexpectedTemplate)
   266  	}
   267  	switch c.outFmt {
   268  	case "json", "terse", "table":
   269  		return nil
   270  	case "go-template":
   271  		if c.tmpl == "" {
   272  			return errors.New(errMissingTemplate)
   273  		}
   274  		return nil
   275  	default:
   276  		return errors.New(errInvalidListOutFormat)
   277  	}
   278  }