github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/hookstate/ctlcmd/set_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 "encoding/json" 24 "strings" 25 26 "github.com/snapcore/snapd/interfaces" 27 "github.com/snapcore/snapd/overlord/configstate/config" 28 "github.com/snapcore/snapd/overlord/hookstate" 29 "github.com/snapcore/snapd/overlord/hookstate/ctlcmd" 30 "github.com/snapcore/snapd/overlord/hookstate/hooktest" 31 "github.com/snapcore/snapd/overlord/state" 32 "github.com/snapcore/snapd/snap" 33 34 . "gopkg.in/check.v1" 35 ) 36 37 type setSuite struct { 38 mockContext *hookstate.Context 39 mockHandler *hooktest.MockHandler 40 } 41 42 type setAttrSuite struct { 43 mockPlugHookContext *hookstate.Context 44 mockSlotHookContext *hookstate.Context 45 mockHandler *hooktest.MockHandler 46 } 47 48 var _ = Suite(&setSuite{}) 49 var _ = Suite(&setAttrSuite{}) 50 51 func (s *setSuite) SetUpTest(c *C) { 52 s.mockHandler = hooktest.NewMockHandler() 53 54 state := state.New(nil) 55 state.Lock() 56 defer state.Unlock() 57 58 task := state.NewTask("test-task", "my test task") 59 setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "test-hook"} 60 61 var err error 62 s.mockContext, err = hookstate.NewContext(task, task.State(), setup, s.mockHandler, "") 63 c.Assert(err, IsNil) 64 } 65 66 func (s *setSuite) TestInvalidArguments(c *C) { 67 _, _, err := ctlcmd.Run(s.mockContext, []string{"set"}, 0) 68 c.Check(err, ErrorMatches, "set which option.*") 69 _, _, err = ctlcmd.Run(s.mockContext, []string{"set", "foo", "bar"}, 0) 70 c.Check(err, ErrorMatches, ".*invalid parameter.*want key=value.*") 71 _, _, err = ctlcmd.Run(s.mockContext, []string{"set", ":foo", "bar=baz"}, 0) 72 c.Check(err, ErrorMatches, ".*interface attributes can only be set during the execution of prepare hooks.*") 73 } 74 75 func (s *setSuite) TestCommand(c *C) { 76 stdout, stderr, err := ctlcmd.Run(s.mockContext, []string{"set", "foo=bar", "baz=qux"}, 0) 77 c.Check(err, IsNil) 78 c.Check(string(stdout), Equals, "") 79 c.Check(string(stderr), Equals, "") 80 81 // Verify that the previous set doesn't modify the global state 82 s.mockContext.State().Lock() 83 tr := config.NewTransaction(s.mockContext.State()) 84 s.mockContext.State().Unlock() 85 var value string 86 c.Check(tr.Get("test-snap", "foo", &value), ErrorMatches, ".*snap.*has no.*configuration.*") 87 c.Check(tr.Get("test-snap", "baz", &value), ErrorMatches, ".*snap.*has no.*configuration.*") 88 89 // Notify the context that we're done. This should save the config. 90 s.mockContext.Lock() 91 defer s.mockContext.Unlock() 92 c.Check(s.mockContext.Done(), IsNil) 93 94 // Verify that the global config has been updated. 95 tr = config.NewTransaction(s.mockContext.State()) 96 c.Check(tr.Get("test-snap", "foo", &value), IsNil) 97 c.Check(value, Equals, "bar") 98 c.Check(tr.Get("test-snap", "baz", &value), IsNil) 99 c.Check(value, Equals, "qux") 100 } 101 102 func (s *setSuite) TestSetRegularUserForbidden(c *C) { 103 _, _, err := ctlcmd.Run(s.mockContext, []string{"set", "test-key1"}, 1000) 104 c.Assert(err, ErrorMatches, `cannot use "set" with uid 1000, try with sudo`) 105 forbidden, _ := err.(*ctlcmd.ForbiddenCommandError) 106 c.Assert(forbidden, NotNil) 107 } 108 109 func (s *setSuite) TestSetHelpRegularUserAllowed(c *C) { 110 _, _, err := ctlcmd.Run(s.mockContext, []string{"set", "-h"}, 1000) 111 c.Assert(err, NotNil) 112 c.Assert(strings.HasPrefix(err.Error(), "Usage:"), Equals, true) 113 } 114 115 func (s *setSuite) TestSetConfigOptionWithColon(c *C) { 116 stdout, stderr, err := ctlcmd.Run(s.mockContext, []string{"set", "device-service.url=192.168.0.1:5555"}, 0) 117 c.Check(err, IsNil) 118 c.Check(string(stdout), Equals, "") 119 c.Check(string(stderr), Equals, "") 120 121 // Notify the context that we're done. This should save the config. 122 s.mockContext.Lock() 123 defer s.mockContext.Unlock() 124 c.Check(s.mockContext.Done(), IsNil) 125 126 // Verify that the global config has been updated. 127 var value string 128 tr := config.NewTransaction(s.mockContext.State()) 129 c.Check(tr.Get("test-snap", "device-service.url", &value), IsNil) 130 c.Check(value, Equals, "192.168.0.1:5555") 131 } 132 133 func (s *setSuite) TestUnsetConfigOptionWithInitialConfiguration(c *C) { 134 // Setup an initial configuration 135 s.mockContext.State().Lock() 136 tr := config.NewTransaction(s.mockContext.State()) 137 tr.Set("test-snap", "test-key1", "test-value1") 138 tr.Set("test-snap", "test-key2", "test-value2") 139 tr.Set("test-snap", "test-key3.foo", "foo-value") 140 tr.Set("test-snap", "test-key3.bar", "bar-value") 141 tr.Commit() 142 s.mockContext.State().Unlock() 143 144 stdout, stderr, err := ctlcmd.Run(s.mockContext, []string{"set", "test-key1!", "test-key3.foo!"}, 0) 145 c.Check(err, IsNil) 146 c.Check(string(stdout), Equals, "") 147 c.Check(string(stderr), Equals, "") 148 149 // Notify the context that we're done. This should save the config. 150 s.mockContext.Lock() 151 defer s.mockContext.Unlock() 152 c.Check(s.mockContext.Done(), IsNil) 153 154 // Verify that the global config has been updated. 155 var value string 156 tr = config.NewTransaction(s.mockContext.State()) 157 c.Check(tr.Get("test-snap", "test-key2", &value), IsNil) 158 c.Check(value, Equals, "test-value2") 159 c.Check(tr.Get("test-snap", "test-key1", &value), ErrorMatches, `snap "test-snap" has no "test-key1" configuration option`) 160 var value2 interface{} 161 c.Check(tr.Get("test-snap", "test-key3", &value2), IsNil) 162 c.Check(value2, DeepEquals, map[string]interface{}{"bar": "bar-value"}) 163 } 164 165 func (s *setSuite) TestUnsetConfigOptionWithNoInitialConfiguration(c *C) { 166 stdout, stderr, err := ctlcmd.Run(s.mockContext, []string{"set", "test-key.key1=value1", "test-key.key2=value2", "test-key.key1!"}, 0) 167 c.Check(err, IsNil) 168 c.Check(string(stdout), Equals, "") 169 c.Check(string(stderr), Equals, "") 170 171 // Notify the context that we're done. This should save the config. 172 s.mockContext.Lock() 173 defer s.mockContext.Unlock() 174 c.Check(s.mockContext.Done(), IsNil) 175 176 // Verify that the global config has been updated. 177 var value interface{} 178 tr := config.NewTransaction(s.mockContext.State()) 179 c.Check(tr.Get("test-snap", "test-key.key2", &value), IsNil) 180 c.Check(value, DeepEquals, "value2") 181 c.Check(tr.Get("test-snap", "test-key.key1", &value), ErrorMatches, `snap "test-snap" has no "test-key.key1" configuration option`) 182 c.Check(value, DeepEquals, "value2") 183 } 184 185 func (s *setSuite) TestSetNumbers(c *C) { 186 stdout, stderr, err := ctlcmd.Run(s.mockContext, []string{"set", "foo=1234567890", "bar=123456.7890"}, 0) 187 c.Check(err, IsNil) 188 c.Check(string(stdout), Equals, "") 189 c.Check(string(stderr), Equals, "") 190 191 // Notify the context that we're done. This should save the config. 192 s.mockContext.Lock() 193 defer s.mockContext.Unlock() 194 c.Check(s.mockContext.Done(), IsNil) 195 196 // Verify that the global config has been updated. 197 var value interface{} 198 tr := config.NewTransaction(s.mockContext.State()) 199 c.Check(tr.Get("test-snap", "foo", &value), IsNil) 200 c.Check(value, Equals, json.Number("1234567890")) 201 202 c.Check(tr.Get("test-snap", "bar", &value), IsNil) 203 c.Check(value, Equals, json.Number("123456.7890")) 204 } 205 206 func (s *setSuite) TestCommandSavesDeltasOnly(c *C) { 207 // Setup an initial configuration 208 s.mockContext.State().Lock() 209 tr := config.NewTransaction(s.mockContext.State()) 210 tr.Set("test-snap", "test-key1", "test-value1") 211 tr.Set("test-snap", "test-key2", "test-value2") 212 tr.Commit() 213 s.mockContext.State().Unlock() 214 215 stdout, stderr, err := ctlcmd.Run(s.mockContext, []string{"set", "test-key2=test-value3"}, 0) 216 c.Check(err, IsNil) 217 c.Check(string(stdout), Equals, "") 218 c.Check(string(stderr), Equals, "") 219 220 // Notify the context that we're done. This should save the config. 221 s.mockContext.Lock() 222 defer s.mockContext.Unlock() 223 c.Check(s.mockContext.Done(), IsNil) 224 225 // Verify that the global config has been updated, but only test-key2 226 tr = config.NewTransaction(s.mockContext.State()) 227 var value string 228 c.Check(tr.Get("test-snap", "test-key1", &value), IsNil) 229 c.Check(value, Equals, "test-value1") 230 c.Check(tr.Get("test-snap", "test-key2", &value), IsNil) 231 c.Check(value, Equals, "test-value3") 232 } 233 234 func (s *setSuite) TestCommandWithoutContext(c *C) { 235 _, _, err := ctlcmd.Run(nil, []string{"set", "foo=bar"}, 0) 236 c.Check(err, ErrorMatches, ".*cannot set without a context.*") 237 } 238 239 func (s *setAttrSuite) SetUpTest(c *C) { 240 s.mockHandler = hooktest.NewMockHandler() 241 state := state.New(nil) 242 state.Lock() 243 ch := state.NewChange("mychange", "mychange") 244 245 attrsTask := state.NewTask("connect-task", "my connect task") 246 attrsTask.Set("plug", &interfaces.PlugRef{Snap: "a", Name: "aplug"}) 247 attrsTask.Set("slot", &interfaces.SlotRef{Snap: "b", Name: "bslot"}) 248 staticAttrs := map[string]interface{}{ 249 "lorem": "ipsum", 250 "nested": map[string]interface{}{ 251 "x": "y", 252 }, 253 } 254 dynamicAttrs := make(map[string]interface{}) 255 attrsTask.Set("plug-static", staticAttrs) 256 attrsTask.Set("plug-dynamic", dynamicAttrs) 257 attrsTask.Set("slot-static", staticAttrs) 258 attrsTask.Set("slot-dynamic", dynamicAttrs) 259 ch.AddTask(attrsTask) 260 state.Unlock() 261 262 var err error 263 264 // setup plug hook task 265 state.Lock() 266 plugHookTask := state.NewTask("run-hook", "my test task") 267 state.Unlock() 268 plugTaskSetup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "prepare-plug-aplug"} 269 s.mockPlugHookContext, err = hookstate.NewContext(plugHookTask, plugHookTask.State(), plugTaskSetup, s.mockHandler, "") 270 c.Assert(err, IsNil) 271 272 s.mockPlugHookContext.Lock() 273 s.mockPlugHookContext.Set("attrs-task", attrsTask.ID()) 274 s.mockPlugHookContext.Unlock() 275 state.Lock() 276 ch.AddTask(plugHookTask) 277 state.Unlock() 278 279 // setup slot hook task 280 state.Lock() 281 slotHookTask := state.NewTask("run-hook", "my test task") 282 state.Unlock() 283 slotTaskSetup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "prepare-slot-aplug"} 284 s.mockSlotHookContext, err = hookstate.NewContext(slotHookTask, slotHookTask.State(), slotTaskSetup, s.mockHandler, "") 285 c.Assert(err, IsNil) 286 287 s.mockSlotHookContext.Lock() 288 s.mockSlotHookContext.Set("attrs-task", attrsTask.ID()) 289 s.mockSlotHookContext.Unlock() 290 291 state.Lock() 292 defer state.Unlock() 293 ch.AddTask(slotHookTask) 294 } 295 296 func (s *setAttrSuite) TestSetPlugAttributesInPlugHook(c *C) { 297 stdout, stderr, err := ctlcmd.Run(s.mockPlugHookContext, []string{"set", ":aplug", "foo=bar"}, 0) 298 c.Check(err, IsNil) 299 c.Check(string(stdout), Equals, "") 300 c.Check(string(stderr), Equals, "") 301 302 attrsTask, err := ctlcmd.AttributesTask(s.mockPlugHookContext) 303 c.Assert(err, IsNil) 304 st := s.mockPlugHookContext.State() 305 st.Lock() 306 defer st.Unlock() 307 dynattrs := make(map[string]interface{}) 308 err = attrsTask.Get("plug-dynamic", &dynattrs) 309 c.Assert(err, IsNil) 310 c.Check(dynattrs["foo"], Equals, "bar") 311 } 312 313 func (s *setAttrSuite) TestSetPlugAttributesSupportsDottedSyntax(c *C) { 314 stdout, stderr, err := ctlcmd.Run(s.mockPlugHookContext, []string{"set", ":aplug", "my.attr1=foo", "my.attr2=bar"}, 0) 315 c.Check(err, IsNil) 316 c.Check(string(stdout), Equals, "") 317 c.Check(string(stderr), Equals, "") 318 319 attrsTask, err := ctlcmd.AttributesTask(s.mockPlugHookContext) 320 c.Assert(err, IsNil) 321 st := s.mockPlugHookContext.State() 322 st.Lock() 323 defer st.Unlock() 324 dynattrs := make(map[string]interface{}) 325 err = attrsTask.Get("plug-dynamic", &dynattrs) 326 c.Assert(err, IsNil) 327 c.Check(dynattrs["my"], DeepEquals, map[string]interface{}{"attr1": "foo", "attr2": "bar"}) 328 } 329 330 func (s *setAttrSuite) TestPlugOrSlotEmpty(c *C) { 331 stdout, stderr, err := ctlcmd.Run(s.mockPlugHookContext, []string{"set", ":", "foo=bar"}, 0) 332 c.Check(err, ErrorMatches, "plug or slot name not provided") 333 c.Check(string(stdout), Equals, "") 334 c.Check(string(stderr), Equals, "") 335 } 336 337 func (s *setAttrSuite) TestSetCommandFailsOutsideOfValidContext(c *C) { 338 var err error 339 var mockContext *hookstate.Context 340 341 state := state.New(nil) 342 state.Lock() 343 defer state.Unlock() 344 345 task := state.NewTask("test-task", "my test task") 346 setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "not-a-connect-hook"} 347 mockContext, err = hookstate.NewContext(task, task.State(), setup, s.mockHandler, "") 348 c.Assert(err, IsNil) 349 350 stdout, stderr, err := ctlcmd.Run(mockContext, []string{"set", ":aplug", "foo=bar"}, 0) 351 c.Check(err, ErrorMatches, `interface attributes can only be set during the execution of prepare hooks`) 352 c.Check(string(stdout), Equals, "") 353 c.Check(string(stderr), Equals, "") 354 }