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