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  }