github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/hookstate/ctlcmd/get.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package ctlcmd 21 22 import ( 23 "encoding/json" 24 "fmt" 25 "strings" 26 27 "github.com/snapcore/snapd/i18n" 28 "github.com/snapcore/snapd/interfaces" 29 "github.com/snapcore/snapd/overlord/configstate" 30 "github.com/snapcore/snapd/overlord/configstate/config" 31 "github.com/snapcore/snapd/overlord/hookstate" 32 "github.com/snapcore/snapd/overlord/state" 33 ) 34 35 type getCommand struct { 36 baseCommand 37 38 // these two options are mutually exclusive 39 ForceSlotSide bool `long:"slot" description:"return attribute values from the slot side of the connection"` 40 ForcePlugSide bool `long:"plug" description:"return attribute values from the plug side of the connection"` 41 42 Positional struct { 43 PlugOrSlotSpec string `positional-args:"true" positional-arg-name:":<plug|slot>"` 44 Keys []string `positional-arg-name:"<keys>" description:"option keys"` 45 } `positional-args:"yes"` 46 47 Document bool `short:"d" description:"always return document, even with single key"` 48 Typed bool `short:"t" description:"strict typing with nulls and quoted strings"` 49 } 50 51 var shortGetHelp = i18n.G("The get command prints configuration and interface connection settings.") 52 var longGetHelp = i18n.G(` 53 The get command prints configuration options for the current snap. 54 55 $ snapctl get username 56 frank 57 58 If multiple option names are provided, a document is returned: 59 60 $ snapctl get username password 61 { 62 "username": "frank", 63 "password": "..." 64 } 65 66 Nested values may be retrieved via a dotted path: 67 68 $ snapctl get author.name 69 frank 70 71 Values of interface connection settings may be printed with: 72 73 $ snapctl get :myplug usb-vendor 74 $ snapctl get :myslot path 75 76 This will return the named setting from the local interface endpoint, whether a plug 77 or a slot. Returning the setting from the connected snap's endpoint is also possible 78 by explicitly requesting that via the --plug and --slot command line options: 79 80 $ snapctl get :myplug --slot usb-vendor 81 82 This requests the "usb-vendor" setting from the slot that is connected to "myplug". 83 `) 84 85 func init() { 86 addCommand("get", shortGetHelp, longGetHelp, func() command { 87 return &getCommand{} 88 }) 89 } 90 91 func (c *getCommand) printValues(getByKey func(string) (interface{}, bool, error)) error { 92 patch := make(map[string]interface{}) 93 for _, key := range c.Positional.Keys { 94 value, output, err := getByKey(key) 95 if err == nil { 96 if output { 97 patch[key] = value 98 } // else skip this value 99 } else { 100 return err 101 } 102 } 103 104 var confToPrint interface{} = patch 105 if !c.Document && len(c.Positional.Keys) == 1 { 106 confToPrint = patch[c.Positional.Keys[0]] 107 } 108 109 if c.Typed && confToPrint == nil { 110 c.printf("null\n") 111 return nil 112 } 113 114 if s, ok := confToPrint.(string); ok && !c.Typed { 115 c.printf("%s\n", s) 116 return nil 117 } 118 119 var bytes []byte 120 if confToPrint != nil { 121 var err error 122 bytes, err = json.MarshalIndent(confToPrint, "", "\t") 123 if err != nil { 124 return err 125 } 126 } 127 128 c.printf("%s\n", string(bytes)) 129 130 return nil 131 } 132 133 func (c *getCommand) Execute(args []string) error { 134 if len(c.Positional.Keys) == 0 && c.Positional.PlugOrSlotSpec == "" { 135 return fmt.Errorf(i18n.G("get which option?")) 136 } 137 138 context := c.context() 139 if context == nil { 140 return fmt.Errorf("cannot get without a context") 141 } 142 143 if c.Typed && c.Document { 144 return fmt.Errorf("cannot use -d and -t together") 145 } 146 147 if strings.Contains(c.Positional.PlugOrSlotSpec, ":") { 148 parts := strings.SplitN(c.Positional.PlugOrSlotSpec, ":", 2) 149 snap, name := parts[0], parts[1] 150 if name == "" { 151 return fmt.Errorf("plug or slot name not provided") 152 } 153 if snap != "" { 154 return fmt.Errorf(`"snapctl get %s" not supported, use "snapctl get :%s" instead`, c.Positional.PlugOrSlotSpec, parts[1]) 155 } 156 if len(c.Positional.Keys) == 0 { 157 return fmt.Errorf(i18n.G("get which attribute?")) 158 } 159 160 return c.getInterfaceSetting(context, name) 161 } 162 163 // PlugOrSlotSpec is actually a configuration key. 164 c.Positional.Keys = append([]string{c.Positional.PlugOrSlotSpec}, c.Positional.Keys[0:]...) 165 c.Positional.PlugOrSlotSpec = "" 166 167 return c.getConfigSetting(context) 168 } 169 170 func (c *getCommand) getConfigSetting(context *hookstate.Context) error { 171 if c.ForcePlugSide || c.ForceSlotSide { 172 return fmt.Errorf("cannot use --plug or --slot without <snap>:<plug|slot> argument") 173 } 174 175 context.Lock() 176 transaction := configstate.ContextTransaction(context) 177 context.Unlock() 178 179 return c.printValues(func(key string) (interface{}, bool, error) { 180 var value interface{} 181 err := transaction.Get(c.context().InstanceName(), key, &value) 182 if err == nil { 183 return value, true, nil 184 } 185 if config.IsNoOption(err) { 186 if !c.Typed { 187 value = "" 188 } 189 return value, false, nil 190 } 191 return value, false, err 192 }) 193 } 194 195 type ifaceHookType int 196 197 const ( 198 preparePlugHook ifaceHookType = iota 199 prepareSlotHook 200 unpreparePlugHook 201 unprepareSlotHook 202 connectPlugHook 203 connectSlotHook 204 disconnectPlugHook 205 disconnectSlotHook 206 unknownHook 207 ) 208 209 func interfaceHookType(hookName string) (ifaceHookType, error) { 210 switch { 211 case strings.HasPrefix(hookName, "prepare-plug-"): 212 return preparePlugHook, nil 213 case strings.HasPrefix(hookName, "connect-plug-"): 214 return connectPlugHook, nil 215 case strings.HasPrefix(hookName, "prepare-slot-"): 216 return prepareSlotHook, nil 217 case strings.HasPrefix(hookName, "connect-slot-"): 218 return connectSlotHook, nil 219 case strings.HasPrefix(hookName, "disconnect-plug-"): 220 return disconnectPlugHook, nil 221 case strings.HasPrefix(hookName, "disconnect-slot-"): 222 return disconnectSlotHook, nil 223 case strings.HasPrefix(hookName, "unprepare-slot-"): 224 return unprepareSlotHook, nil 225 case strings.HasPrefix(hookName, "unprepare-plug-"): 226 return unpreparePlugHook, nil 227 default: 228 return unknownHook, fmt.Errorf("unknown hook type") 229 } 230 } 231 232 func validatePlugOrSlot(attrsTask *state.Task, plugSide bool, plugOrSlot string) error { 233 // check if the requested plug or slot is correct for given hook. 234 attrsTask.State().Lock() 235 defer attrsTask.State().Unlock() 236 237 var name string 238 var err error 239 if plugSide { 240 var plugRef interfaces.PlugRef 241 if err = attrsTask.Get("plug", &plugRef); err == nil { 242 name = plugRef.Name 243 } 244 } else { 245 var slotRef interfaces.SlotRef 246 if err = attrsTask.Get("slot", &slotRef); err == nil { 247 name = slotRef.Name 248 } 249 } 250 if err != nil { 251 return fmt.Errorf(i18n.G("internal error: cannot find plug or slot data in the appropriate task")) 252 } 253 if name != plugOrSlot { 254 return fmt.Errorf(i18n.G("unknown plug or slot %q"), plugOrSlot) 255 } 256 return nil 257 } 258 259 func attributesTask(context *hookstate.Context) (*state.Task, error) { 260 var attrsTaskID string 261 context.Lock() 262 defer context.Unlock() 263 264 if err := context.Get("attrs-task", &attrsTaskID); err != nil { 265 return nil, err 266 } 267 268 st := context.State() 269 270 attrsTask := st.Task(attrsTaskID) 271 if attrsTask == nil { 272 return nil, fmt.Errorf(i18n.G("internal error: cannot find attrs task")) 273 } 274 275 return attrsTask, nil 276 } 277 278 func (c *getCommand) getInterfaceSetting(context *hookstate.Context, plugOrSlot string) error { 279 // Make sure get :<plug|slot> is only supported during the execution of interface hooks 280 hookType, err := interfaceHookType(context.HookName()) 281 if err != nil { 282 return fmt.Errorf(i18n.G("interface attributes can only be read during the execution of interface hooks")) 283 } 284 285 var attrsTask *state.Task 286 attrsTask, err = attributesTask(context) 287 if err != nil { 288 return err 289 } 290 291 if c.ForcePlugSide && c.ForceSlotSide { 292 return fmt.Errorf("cannot use --plug and --slot together") 293 } 294 295 isPlugSide := (hookType == preparePlugHook || hookType == unpreparePlugHook || hookType == connectPlugHook || hookType == disconnectPlugHook) 296 if err = validatePlugOrSlot(attrsTask, isPlugSide, plugOrSlot); err != nil { 297 return err 298 } 299 300 var which string 301 if c.ForcePlugSide || (isPlugSide && !c.ForceSlotSide) { 302 which = "plug" 303 } else { 304 which = "slot" 305 } 306 307 st := context.State() 308 st.Lock() 309 defer st.Unlock() 310 311 var staticAttrs, dynamicAttrs map[string]interface{} 312 if err = attrsTask.Get(which+"-static", &staticAttrs); err != nil { 313 return fmt.Errorf(i18n.G("internal error: cannot get %s from appropriate task"), which) 314 } 315 if err = attrsTask.Get(which+"-dynamic", &dynamicAttrs); err != nil { 316 return fmt.Errorf(i18n.G("internal error: cannot get %s from appropriate task"), which) 317 } 318 319 return c.printValues(func(key string) (interface{}, bool, error) { 320 subkeys, err := config.ParseKey(key) 321 if err != nil { 322 return nil, false, err 323 } 324 325 var value interface{} 326 err = getAttribute(context.InstanceName(), subkeys, 0, staticAttrs, &value) 327 if err == nil { 328 return value, true, nil 329 } 330 if isNoAttribute(err) { 331 err = getAttribute(context.InstanceName(), subkeys, 0, dynamicAttrs, &value) 332 if err == nil { 333 return value, true, nil 334 } 335 } 336 return nil, false, err 337 }) 338 }