forked from snapcore/snapd
/
get.go
338 lines (282 loc) · 9.23 KB
/
get.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
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2016 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package ctlcmd
import (
"encoding/json"
"fmt"
"strings"
"github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/overlord/configstate"
"github.com/snapcore/snapd/overlord/configstate/config"
"github.com/snapcore/snapd/overlord/hookstate"
"github.com/snapcore/snapd/overlord/state"
)
type getCommand struct {
baseCommand
// these two options are mutually exclusive
ForceSlotSide bool `long:"slot" description:"return attribute values from the slot side of the connection"`
ForcePlugSide bool `long:"plug" description:"return attribute values from the plug side of the connection"`
Positional struct {
PlugOrSlotSpec string `positional-args:"true" positional-arg-name:":<plug|slot>"`
Keys []string `positional-arg-name:"<keys>" description:"option keys"`
} `positional-args:"yes"`
Document bool `short:"d" description:"always return document, even with single key"`
Typed bool `short:"t" description:"strict typing with nulls and quoted strings"`
}
var shortGetHelp = i18n.G("The get command prints configuration and interface connection settings.")
var longGetHelp = i18n.G(`
The get command prints configuration options for the current snap.
$ snapctl get username
frank
If multiple option names are provided, a document is returned:
$ snapctl get username password
{
"username": "frank",
"password": "..."
}
Nested values may be retrieved via a dotted path:
$ snapctl get author.name
frank
Values of interface connection settings may be printed with:
$ snapctl get :myplug usb-vendor
$ snapctl get :myslot path
This will return the named setting from the local interface endpoint, whether a plug
or a slot. Returning the setting from the connected snap's endpoint is also possible
by explicitly requesting that via the --plug and --slot command line options:
$ snapctl get :myplug --slot usb-vendor
This requests the "usb-vendor" setting from the slot that is connected to "myplug".
`)
func init() {
addCommand("get", shortGetHelp, longGetHelp, func() command {
return &getCommand{}
})
}
func (c *getCommand) printValues(getByKey func(string) (interface{}, bool, error)) error {
patch := make(map[string]interface{})
for _, key := range c.Positional.Keys {
value, output, err := getByKey(key)
if err == nil {
if output {
patch[key] = value
} // else skip this value
} else {
return err
}
}
var confToPrint interface{} = patch
if !c.Document && len(c.Positional.Keys) == 1 {
confToPrint = patch[c.Positional.Keys[0]]
}
if c.Typed && confToPrint == nil {
c.printf("null\n")
return nil
}
if s, ok := confToPrint.(string); ok && !c.Typed {
c.printf("%s\n", s)
return nil
}
var bytes []byte
if confToPrint != nil {
var err error
bytes, err = json.MarshalIndent(confToPrint, "", "\t")
if err != nil {
return err
}
}
c.printf("%s\n", string(bytes))
return nil
}
func (c *getCommand) Execute(args []string) error {
if len(c.Positional.Keys) == 0 && c.Positional.PlugOrSlotSpec == "" {
return fmt.Errorf(i18n.G("get which option?"))
}
context := c.context()
if context == nil {
return fmt.Errorf("cannot get without a context")
}
if c.Typed && c.Document {
return fmt.Errorf("cannot use -d and -t together")
}
if strings.Contains(c.Positional.PlugOrSlotSpec, ":") {
parts := strings.SplitN(c.Positional.PlugOrSlotSpec, ":", 2)
snap, name := parts[0], parts[1]
if name == "" {
return fmt.Errorf("plug or slot name not provided")
}
if snap != "" {
return fmt.Errorf(`"snapctl get %s" not supported, use "snapctl get :%s" instead`, c.Positional.PlugOrSlotSpec, parts[1])
}
if len(c.Positional.Keys) == 0 {
return fmt.Errorf(i18n.G("get which attribute?"))
}
return c.getInterfaceSetting(context, name)
}
// PlugOrSlotSpec is actually a configuration key.
c.Positional.Keys = append([]string{c.Positional.PlugOrSlotSpec}, c.Positional.Keys[0:]...)
c.Positional.PlugOrSlotSpec = ""
return c.getConfigSetting(context)
}
func (c *getCommand) getConfigSetting(context *hookstate.Context) error {
if c.ForcePlugSide || c.ForceSlotSide {
return fmt.Errorf("cannot use --plug or --slot without <snap>:<plug|slot> argument")
}
context.Lock()
transaction := configstate.ContextTransaction(context)
context.Unlock()
return c.printValues(func(key string) (interface{}, bool, error) {
var value interface{}
err := transaction.Get(c.context().InstanceName(), key, &value)
if err == nil {
return value, true, nil
}
if config.IsNoOption(err) {
if !c.Typed {
value = ""
}
return value, false, nil
}
return value, false, err
})
}
type ifaceHookType int
const (
preparePlugHook ifaceHookType = iota
prepareSlotHook
unpreparePlugHook
unprepareSlotHook
connectPlugHook
connectSlotHook
disconnectPlugHook
disconnectSlotHook
unknownHook
)
func interfaceHookType(hookName string) (ifaceHookType, error) {
switch {
case strings.HasPrefix(hookName, "prepare-plug-"):
return preparePlugHook, nil
case strings.HasPrefix(hookName, "connect-plug-"):
return connectPlugHook, nil
case strings.HasPrefix(hookName, "prepare-slot-"):
return prepareSlotHook, nil
case strings.HasPrefix(hookName, "connect-slot-"):
return connectSlotHook, nil
case strings.HasPrefix(hookName, "disconnect-plug-"):
return disconnectPlugHook, nil
case strings.HasPrefix(hookName, "disconnect-slot-"):
return disconnectSlotHook, nil
case strings.HasPrefix(hookName, "unprepare-slot-"):
return unprepareSlotHook, nil
case strings.HasPrefix(hookName, "unprepare-plug-"):
return unpreparePlugHook, nil
default:
return unknownHook, fmt.Errorf("unknown hook type")
}
}
func validatePlugOrSlot(attrsTask *state.Task, plugSide bool, plugOrSlot string) error {
// check if the requested plug or slot is correct for given hook.
attrsTask.State().Lock()
defer attrsTask.State().Unlock()
var name string
var err error
if plugSide {
var plugRef interfaces.PlugRef
if err = attrsTask.Get("plug", &plugRef); err == nil {
name = plugRef.Name
}
} else {
var slotRef interfaces.SlotRef
if err = attrsTask.Get("slot", &slotRef); err == nil {
name = slotRef.Name
}
}
if err != nil {
return fmt.Errorf(i18n.G("internal error: cannot find plug or slot data in the appropriate task"))
}
if name != plugOrSlot {
return fmt.Errorf(i18n.G("unknown plug or slot %q"), plugOrSlot)
}
return nil
}
func attributesTask(context *hookstate.Context) (*state.Task, error) {
var attrsTaskID string
context.Lock()
defer context.Unlock()
if err := context.Get("attrs-task", &attrsTaskID); err != nil {
return nil, err
}
st := context.State()
attrsTask := st.Task(attrsTaskID)
if attrsTask == nil {
return nil, fmt.Errorf(i18n.G("internal error: cannot find attrs task"))
}
return attrsTask, nil
}
func (c *getCommand) getInterfaceSetting(context *hookstate.Context, plugOrSlot string) error {
// Make sure get :<plug|slot> is only supported during the execution of interface hooks
hookType, err := interfaceHookType(context.HookName())
if err != nil {
return fmt.Errorf(i18n.G("interface attributes can only be read during the execution of interface hooks"))
}
var attrsTask *state.Task
attrsTask, err = attributesTask(context)
if err != nil {
return err
}
if c.ForcePlugSide && c.ForceSlotSide {
return fmt.Errorf("cannot use --plug and --slot together")
}
isPlugSide := (hookType == preparePlugHook || hookType == unpreparePlugHook || hookType == connectPlugHook || hookType == disconnectPlugHook)
if err = validatePlugOrSlot(attrsTask, isPlugSide, plugOrSlot); err != nil {
return err
}
var which string
if c.ForcePlugSide || (isPlugSide && !c.ForceSlotSide) {
which = "plug"
} else {
which = "slot"
}
st := context.State()
st.Lock()
defer st.Unlock()
var staticAttrs, dynamicAttrs map[string]interface{}
if err = attrsTask.Get(which+"-static", &staticAttrs); err != nil {
return fmt.Errorf(i18n.G("internal error: cannot get %s from appropriate task"), which)
}
if err = attrsTask.Get(which+"-dynamic", &dynamicAttrs); err != nil {
return fmt.Errorf(i18n.G("internal error: cannot get %s from appropriate task"), which)
}
return c.printValues(func(key string) (interface{}, bool, error) {
subkeys, err := config.ParseKey(key)
if err != nil {
return nil, false, err
}
var value interface{}
err = getAttribute(context.InstanceName(), subkeys, 0, staticAttrs, &value)
if err == nil {
return value, true, nil
}
if isNoAttribute(err) {
err = getAttribute(context.InstanceName(), subkeys, 0, dynamicAttrs, &value)
if err == nil {
return value, true, nil
}
}
return nil, false, err
})
}