github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/command/var_list.go (about)

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