github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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  	"github.com/snapcore/snapd/interfaces"
    24  	"github.com/snapcore/snapd/overlord/configstate"
    25  	"github.com/snapcore/snapd/overlord/configstate/config"
    26  	"github.com/snapcore/snapd/overlord/hookstate"
    27  	"github.com/snapcore/snapd/overlord/hookstate/ctlcmd"
    28  	"github.com/snapcore/snapd/overlord/hookstate/hooktest"
    29  	"github.com/snapcore/snapd/overlord/state"
    30  	"github.com/snapcore/snapd/snap"
    31  
    32  	"strings"
    33  
    34  	. "gopkg.in/check.v1"
    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  }