-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
namespace_apply.go
259 lines (217 loc) · 6.05 KB
/
namespace_apply.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
package command
import (
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"github.com/hashicorp/hcl"
"github.com/hashicorp/hcl/hcl/ast"
"github.com/hashicorp/nomad/api"
flaghelper "github.com/hashicorp/nomad/helper/flags"
"github.com/mitchellh/mapstructure"
"github.com/posener/complete"
)
type NamespaceApplyCommand struct {
Meta
}
func (c *NamespaceApplyCommand) Help() string {
helpText := `
Usage: nomad namespace apply [options] <input>
Apply is used to create or update a namespace. The specification file
will be read from stdin by specifying "-", otherwise a path to the file is
expected.
Instead of a file, you may instead pass the namespace name to create
or update as the only argument.
If ACLs are enabled, this command requires a management ACL token.
General Options:
` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + `
Apply Options:
-quota
The quota to attach to the namespace.
-description
An optional description for the namespace.
-json
Parse the input as a JSON namespace specification.
`
return strings.TrimSpace(helpText)
}
func (c *NamespaceApplyCommand) AutocompleteFlags() complete.Flags {
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
complete.Flags{
"-description": complete.PredictAnything,
"-quota": QuotaPredictor(c.Meta.Client),
"-json": complete.PredictNothing,
})
}
func (c *NamespaceApplyCommand) AutocompleteArgs() complete.Predictor {
return complete.PredictOr(
NamespacePredictor(c.Meta.Client, nil),
complete.PredictFiles("*.hcl"),
complete.PredictFiles("*.json"),
)
}
func (c *NamespaceApplyCommand) Synopsis() string {
return "Create or update a namespace"
}
func (c *NamespaceApplyCommand) Name() string { return "namespace apply" }
func (c *NamespaceApplyCommand) Run(args []string) int {
var jsonInput bool
var description, quota *string
flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
flags.Usage = func() { c.Ui.Output(c.Help()) }
flags.Var((flaghelper.FuncVar)(func(s string) error {
description = &s
return nil
}), "description", "")
flags.Var((flaghelper.FuncVar)(func(s string) error {
quota = &s
return nil
}), "quota", "")
flags.BoolVar(&jsonInput, "json", false, "")
if err := flags.Parse(args); err != nil {
return 1
}
// Check that we get exactly one argument
args = flags.Args()
if l := len(args); l != 1 {
c.Ui.Error("This command takes one argument: <input>")
c.Ui.Error(commandErrorText(c))
return 1
}
file := args[0]
var rawNamespace []byte
var err error
var namespace *api.Namespace
// 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 fi, err := os.Stat(file); (file == "-" || err == nil) && !fi.IsDir() {
if quota != nil || description != nil {
c.Ui.Warn("Flags are ignored when a file is specified!")
}
if file == "-" {
rawNamespace, err = io.ReadAll(os.Stdin)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to read stdin: %v", err))
return 1
}
} else {
rawNamespace, err = os.ReadFile(file)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to read file: %v", err))
return 1
}
}
if jsonInput {
var jsonSpec api.Namespace
dec := json.NewDecoder(bytes.NewBuffer(rawNamespace))
if err := dec.Decode(&jsonSpec); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to parse quota: %v", err))
return 1
}
namespace = &jsonSpec
} else {
hclSpec, err := parseNamespaceSpec(rawNamespace)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing quota specification: %s", err))
return 1
}
namespace = hclSpec
}
} else {
name := args[0]
// Validate we have at-least a name
if name == "" {
c.Ui.Error("Namespace name required")
return 1
}
// Lookup the given namespace
namespace, _, err = client.Namespaces().Info(name, nil)
if err != nil && !strings.Contains(err.Error(), "404") {
c.Ui.Error(fmt.Sprintf("Error looking up namespace: %s", err))
return 1
}
if namespace == nil {
namespace = &api.Namespace{
Name: name,
}
}
// Add what is set
if description != nil {
namespace.Description = *description
}
if quota != nil {
namespace.Quota = *quota
}
}
_, err = client.Namespaces().Register(namespace, nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error applying namespace: %s", err))
return 1
}
c.Ui.Output(fmt.Sprintf("Successfully applied namespace %q!", namespace.Name))
return 0
}
// parseNamespaceSpec is used to parse the namespace specification from HCL
func parseNamespaceSpec(input []byte) (*api.Namespace, error) {
root, err := hcl.ParseBytes(input)
if err != nil {
return nil, err
}
// Top-level item should be a list
list, ok := root.Node.(*ast.ObjectList)
if !ok {
return nil, fmt.Errorf("error parsing: root should be an object")
}
var spec api.Namespace
if err := parseNamespaceSpecImpl(&spec, list); err != nil {
return nil, err
}
return &spec, nil
}
// parseNamespaceSpec parses the quota namespace taking as input the AST tree
func parseNamespaceSpecImpl(result *api.Namespace, list *ast.ObjectList) error {
// Decode the full thing into a map[string]interface for ease
var m map[string]interface{}
if err := hcl.DecodeObject(&m, list); err != nil {
return err
}
delete(m, "capabilities")
delete(m, "meta")
// Decode the rest
if err := mapstructure.WeakDecode(m, result); err != nil {
return err
}
cObj := list.Filter("capabilities")
if len(cObj.Items) > 0 {
for _, o := range cObj.Elem().Items {
ot, ok := o.Val.(*ast.ObjectType)
if !ok {
break
}
var opts *api.NamespaceCapabilities
if err := hcl.DecodeObject(&opts, ot.List); err != nil {
return err
}
result.Capabilities = opts
break
}
}
if metaO := list.Filter("meta"); len(metaO.Items) > 0 {
for _, o := range metaO.Elem().Items {
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return err
}
if err := mapstructure.WeakDecode(m, &result.Meta); err != nil {
return err
}
}
}
return nil
}