github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/hookstate/ctlcmd/get_test.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_test 21 22 import ( 23 "strings" 24 25 . "gopkg.in/check.v1" 26 27 "github.com/snapcore/snapd/interfaces" 28 "github.com/snapcore/snapd/overlord/configstate" 29 "github.com/snapcore/snapd/overlord/configstate/config" 30 "github.com/snapcore/snapd/overlord/hookstate" 31 "github.com/snapcore/snapd/overlord/hookstate/ctlcmd" 32 "github.com/snapcore/snapd/overlord/hookstate/hooktest" 33 "github.com/snapcore/snapd/overlord/state" 34 "github.com/snapcore/snapd/snap" 35 ) 36 37 type getSuite struct { 38 mockContext *hookstate.Context 39 mockHandler *hooktest.MockHandler 40 } 41 42 type getAttrSuite struct { 43 mockPlugHookContext *hookstate.Context 44 mockSlotHookContext *hookstate.Context 45 mockHandler *hooktest.MockHandler 46 } 47 48 var _ = Suite(&getSuite{}) 49 50 var _ = Suite(&getAttrSuite{}) 51 52 func (s *getSuite) SetUpTest(c *C) { 53 s.mockHandler = hooktest.NewMockHandler() 54 55 state := state.New(nil) 56 state.Lock() 57 defer state.Unlock() 58 59 task := state.NewTask("test-task", "my test task") 60 setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "test-hook"} 61 62 var err error 63 s.mockContext, err = hookstate.NewContext(task, task.State(), setup, s.mockHandler, "") 64 c.Assert(err, IsNil) 65 66 // Initialize configuration 67 tr := config.NewTransaction(state) 68 tr.Set("test-snap", "initial-key", "initial-value") 69 tr.Commit() 70 } 71 72 var getTests = []struct { 73 args, stdout, error string 74 }{{ 75 args: "get", 76 error: ".*get which option.*", 77 }, { 78 args: "get --plug key", 79 error: "cannot use --plug or --slot without <snap>:<plug|slot> argument", 80 }, { 81 args: "get --slot key", 82 error: "cannot use --plug or --slot without <snap>:<plug|slot> argument", 83 }, { 84 args: "get --foo", 85 error: ".*unknown flag.*foo.*", 86 }, { 87 args: "get :foo bar", 88 error: ".*interface attributes can only be read during the execution of interface hooks.*", 89 }, { 90 args: "get test-key1", 91 stdout: "test-value1\n", 92 }, { 93 args: "get test-key2", 94 stdout: "2\n", 95 }, { 96 args: "get missing-key", 97 stdout: "\n", 98 }, { 99 args: "get -t test-key1", 100 stdout: "\"test-value1\"\n", 101 }, { 102 args: "get -t test-key2", 103 stdout: "2\n", 104 }, { 105 args: "get -t missing-key", 106 stdout: "null\n", 107 }, { 108 args: "get -t test-key2.sub", 109 error: "snap \"test-snap\" option \"test-key2\" is not a map", 110 }, { 111 args: "get -d test-key1", 112 stdout: "{\n\t\"test-key1\": \"test-value1\"\n}\n", 113 }, { 114 args: "get test-key1 test-key2", 115 stdout: "{\n\t\"test-key1\": \"test-value1\",\n\t\"test-key2\": 2\n}\n", 116 }} 117 118 func (s *getSuite) TestGetTests(c *C) { 119 for _, test := range getTests { 120 c.Logf("Test: %s", test.args) 121 122 mockHandler := hooktest.NewMockHandler() 123 124 state := state.New(nil) 125 state.Lock() 126 127 task := state.NewTask("test-task", "my test task") 128 setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "test-hook"} 129 130 var err error 131 mockContext, err := hookstate.NewContext(task, task.State(), setup, mockHandler, "") 132 c.Check(err, IsNil) 133 134 // Initialize configuration 135 tr := config.NewTransaction(state) 136 tr.Set("test-snap", "test-key1", "test-value1") 137 tr.Set("test-snap", "test-key2", 2) 138 tr.Commit() 139 140 state.Unlock() 141 142 stdout, stderr, err := ctlcmd.Run(mockContext, strings.Fields(test.args), 0) 143 if test.error != "" { 144 c.Check(err, ErrorMatches, test.error) 145 } else { 146 c.Check(err, IsNil) 147 c.Check(string(stderr), Equals, "") 148 c.Check(string(stdout), Equals, test.stdout) 149 } 150 } 151 } 152 153 var getTests2 = []struct { 154 setPath string 155 setValue interface{} 156 args, stdout string 157 }{{ 158 setPath: "root.key1", 159 setValue: "c", 160 args: "get root", 161 stdout: "{\n\t\"key1\": \"c\",\n\t\"key2\": \"b\",\n\t\"key3\": {\n\t\t\"sub1\": \"x\",\n\t\t\"sub2\": \"y\"\n\t}\n}\n", 162 }, { 163 setPath: "root.key3", 164 setValue: "d", 165 args: "get root", 166 stdout: "{\n\t\"key1\": \"a\",\n\t\"key2\": \"b\",\n\t\"key3\": \"d\"\n}\n", 167 }, { 168 setPath: "root.key3.sub1", 169 setValue: "z", 170 args: "get root.key3", 171 stdout: "{\n\t\"sub1\": \"z\",\n\t\"sub2\": \"y\"\n}\n", 172 }, { 173 setPath: "root.key3", 174 setValue: map[string]interface{}{"sub3": "z"}, 175 args: "get root", 176 stdout: "{\n\t\"key1\": \"a\",\n\t\"key2\": \"b\",\n\t\"key3\": {\n\t\t\"sub3\": \"z\"\n\t}\n}\n", 177 }} 178 179 func (s *getSuite) TestGetPartialNestedStruct(c *C) { 180 for _, test := range getTests2 { 181 c.Logf("Test: %s", test.args) 182 183 mockHandler := hooktest.NewMockHandler() 184 185 state := state.New(nil) 186 state.Lock() 187 188 task := state.NewTask("test-task", "my test task") 189 setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "test-hook"} 190 191 var err error 192 mockContext, err := hookstate.NewContext(task, task.State(), setup, mockHandler, "") 193 c.Check(err, IsNil) 194 195 // Initialize configuration 196 tr := config.NewTransaction(state) 197 tr.Set("test-snap", "root", map[string]interface{}{"key1": "a", "key2": "b", "key3": map[string]interface{}{"sub1": "x", "sub2": "y"}}) 198 tr.Commit() 199 200 state.Unlock() 201 202 mockContext.Lock() 203 tr2 := configstate.ContextTransaction(mockContext) 204 tr2.Set("test-snap", test.setPath, test.setValue) 205 mockContext.Unlock() 206 207 stdout, stderr, err := ctlcmd.Run(mockContext, strings.Fields(test.args), 0) 208 c.Assert(err, IsNil) 209 c.Assert(string(stderr), Equals, "") 210 c.Check(string(stdout), Equals, test.stdout) 211 212 // transaction not committed, drop it 213 tr2 = nil 214 215 // another transaction doesn't see uncommitted changes of tr2 216 state.Lock() 217 defer state.Unlock() 218 tr3 := config.NewTransaction(state) 219 var config map[string]interface{} 220 c.Assert(tr3.Get("test-snap", "root", &config), IsNil) 221 c.Assert(config, DeepEquals, map[string]interface{}{"key1": "a", "key2": "b", "key3": map[string]interface{}{"sub1": "x", "sub2": "y"}}) 222 } 223 } 224 225 func (s *getSuite) TestGetRegularUser(c *C) { 226 state := state.New(nil) 227 state.Lock() 228 229 task := state.NewTask("test-task", "my test task") 230 setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "test-hook"} 231 232 // Initialize configuration 233 tr := config.NewTransaction(state) 234 tr.Set("test-snap", "test-key1", "test-value1") 235 tr.Commit() 236 237 state.Unlock() 238 239 mockHandler := hooktest.NewMockHandler() 240 mockContext, err := hookstate.NewContext(task, task.State(), setup, mockHandler, "") 241 c.Assert(err, IsNil) 242 stdout, stderr, err := ctlcmd.Run(mockContext, []string{"get", "test-key1"}, 1000) 243 c.Assert(err, IsNil) 244 c.Assert(string(stdout), Equals, "test-value1\n") 245 c.Assert(string(stderr), Equals, "") 246 } 247 248 func (s *getSuite) TestCommandWithoutContext(c *C) { 249 _, _, err := ctlcmd.Run(nil, []string{"get", "foo"}, 0) 250 c.Check(err, ErrorMatches, ".*cannot get without a context.*") 251 } 252 253 func (s *setSuite) TestNull(c *C) { 254 _, _, err := ctlcmd.Run(s.mockContext, []string{"set", "foo=null"}, 0) 255 c.Check(err, IsNil) 256 257 _, _, err = ctlcmd.Run(s.mockContext, []string{"set", `bar=[null]`}, 0) 258 c.Check(err, IsNil) 259 260 // Notify the context that we're done. This should save the config. 261 s.mockContext.Lock() 262 defer s.mockContext.Unlock() 263 c.Check(s.mockContext.Done(), IsNil) 264 265 // Verify config value 266 var value interface{} 267 tr := config.NewTransaction(s.mockContext.State()) 268 c.Assert(config.IsNoOption(tr.Get("test-snap", "foo", &value)), Equals, true) 269 c.Assert(tr.Get("test-snap", "bar", &value), IsNil) 270 c.Assert(value, DeepEquals, []interface{}{nil}) 271 } 272 273 func (s *getAttrSuite) SetUpTest(c *C) { 274 s.mockHandler = hooktest.NewMockHandler() 275 276 state := state.New(nil) 277 state.Lock() 278 ch := state.NewChange("mychange", "mychange") 279 280 attrsTask := state.NewTask("connect-task", "my connect task") 281 attrsTask.Set("plug", &interfaces.PlugRef{Snap: "a", Name: "aplug"}) 282 attrsTask.Set("slot", &interfaces.SlotRef{Snap: "b", Name: "bslot"}) 283 staticPlugAttrs := map[string]interface{}{ 284 "aattr": "foo", 285 "baz": []string{"a", "b"}, 286 "mapattr": map[string]interface{}{"mapattr1": "mapval1", "mapattr2": "mapval2"}, 287 } 288 dynamicPlugAttrs := map[string]interface{}{ 289 "dyn-plug-attr": "c", 290 "nilattr": nil, 291 } 292 dynamicSlotAttrs := map[string]interface{}{ 293 "dyn-slot-attr": "d", 294 } 295 296 staticSlotAttrs := map[string]interface{}{ 297 "battr": "bar", 298 } 299 attrsTask.Set("plug-static", staticPlugAttrs) 300 attrsTask.Set("plug-dynamic", dynamicPlugAttrs) 301 attrsTask.Set("slot-static", staticSlotAttrs) 302 attrsTask.Set("slot-dynamic", dynamicSlotAttrs) 303 ch.AddTask(attrsTask) 304 state.Unlock() 305 306 var err error 307 308 // setup plug hook task 309 state.Lock() 310 plugHookTask := state.NewTask("run-hook", "my test task") 311 state.Unlock() 312 plugTaskSetup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "connect-plug-aplug"} 313 s.mockPlugHookContext, err = hookstate.NewContext(plugHookTask, plugHookTask.State(), plugTaskSetup, s.mockHandler, "") 314 c.Assert(err, IsNil) 315 316 s.mockPlugHookContext.Lock() 317 s.mockPlugHookContext.Set("attrs-task", attrsTask.ID()) 318 s.mockPlugHookContext.Unlock() 319 state.Lock() 320 ch.AddTask(plugHookTask) 321 state.Unlock() 322 323 // setup slot hook task 324 state.Lock() 325 slotHookTask := state.NewTask("run-hook", "my test task") 326 state.Unlock() 327 slotTaskSetup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "connect-slot-aplug"} 328 s.mockSlotHookContext, err = hookstate.NewContext(slotHookTask, slotHookTask.State(), slotTaskSetup, s.mockHandler, "") 329 c.Assert(err, IsNil) 330 331 s.mockSlotHookContext.Lock() 332 s.mockSlotHookContext.Set("attrs-task", attrsTask.ID()) 333 s.mockSlotHookContext.Unlock() 334 335 state.Lock() 336 defer state.Unlock() 337 ch.AddTask(slotHookTask) 338 } 339 340 var getPlugAttributesTests = []struct { 341 args, stdout, error string 342 }{{ 343 args: "get :aplug aattr", 344 stdout: "foo\n", 345 }, { 346 args: "get :aplug aattr.sub", 347 error: "snap \"test-snap\" attribute \"aattr\" is not a map", 348 }, { 349 args: "get -d :aplug baz", 350 stdout: "{\n\t\"baz\": [\n\t\t\"a\",\n\t\t\"b\"\n\t]\n}\n", 351 }, { 352 args: "get :aplug", 353 error: `.*get which attribute.*`, 354 }, { 355 args: "get :aplug mapattr.mapattr1", 356 stdout: "mapval1\n", 357 }, { 358 args: "get -d :aplug mapattr.mapattr1", 359 stdout: "{\n\t\"mapattr.mapattr1\": \"mapval1\"\n}\n", 360 }, { 361 args: "get :aplug dyn-plug-attr", 362 stdout: "c\n", 363 }, { 364 args: "get -t :aplug nilattr", 365 stdout: "null\n", 366 }, { 367 // The --plug parameter doesn't do anything if used on plug side 368 args: "get --plug :aplug aattr", 369 stdout: "foo\n", 370 }, { 371 args: "get --slot :aplug battr", 372 stdout: "bar\n", 373 }, { 374 args: "get :aplug x", 375 error: `no "x" attribute`, 376 }, { 377 args: "get :bslot x", 378 error: `unknown plug or slot "bslot"`, 379 }, { 380 args: "get : foo", 381 error: "plug or slot name not provided", 382 }} 383 384 func (s *getAttrSuite) TestPlugHookTests(c *C) { 385 for _, test := range getPlugAttributesTests { 386 c.Logf("Test: %s", test.args) 387 388 stdout, stderr, err := ctlcmd.Run(s.mockPlugHookContext, strings.Fields(test.args), 0) 389 if test.error != "" { 390 c.Check(err, ErrorMatches, test.error) 391 } else { 392 c.Check(err, IsNil) 393 c.Check(string(stderr), Equals, "") 394 c.Check(string(stdout), Equals, test.stdout) 395 } 396 } 397 } 398 399 var getSlotAttributesTests = []struct { 400 args, stdout, error string 401 }{{ 402 args: "get :bslot battr", 403 stdout: "bar\n", 404 }, { 405 args: "get :bslot dyn-slot-attr", 406 stdout: "d\n", 407 }, { 408 // The --slot parameter doesn't do anything if used on slot side 409 args: "get --slot :bslot battr", 410 stdout: "bar\n", 411 }, { 412 args: "get --plug :bslot aattr", 413 stdout: "foo\n", 414 }, { 415 args: "get :bslot x", 416 error: `no "x" attribute`, 417 }, { 418 args: "get :aplug x", 419 error: `unknown plug or slot "aplug"`, 420 }, { 421 args: "get --slot --plug :aplug x", 422 error: `cannot use --plug and --slot together`, 423 }} 424 425 func (s *getAttrSuite) TestSlotHookTests(c *C) { 426 for _, test := range getSlotAttributesTests { 427 c.Logf("Test: %s", test.args) 428 429 stdout, stderr, err := ctlcmd.Run(s.mockSlotHookContext, strings.Fields(test.args), 0) 430 if test.error != "" { 431 c.Check(err, ErrorMatches, test.error) 432 } else { 433 c.Check(err, IsNil) 434 c.Check(string(stderr), Equals, "") 435 c.Check(string(stdout), Equals, test.stdout) 436 } 437 } 438 }