gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/hookstate/ctlcmd/is_connected_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 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  	"fmt"
    24  
    25  	. "gopkg.in/check.v1"
    26  
    27  	"github.com/snapcore/snapd/dirs"
    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/snapstate"
    32  	"github.com/snapcore/snapd/overlord/state"
    33  	"github.com/snapcore/snapd/snap"
    34  	"github.com/snapcore/snapd/snap/snaptest"
    35  	"github.com/snapcore/snapd/testutil"
    36  )
    37  
    38  type isConnectedSuite struct {
    39  	testutil.BaseTest
    40  	st          *state.State
    41  	mockHandler *hooktest.MockHandler
    42  }
    43  
    44  var _ = Suite(&isConnectedSuite{})
    45  
    46  func (s *isConnectedSuite) SetUpTest(c *C) {
    47  	s.BaseTest.SetUpTest(c)
    48  	dirs.SetRootDir(c.MkDir())
    49  	s.AddCleanup(func() { dirs.SetRootDir("/") })
    50  	s.st = state.New(nil)
    51  	s.mockHandler = hooktest.NewMockHandler()
    52  }
    53  
    54  var isConnectedTests = []struct {
    55  	args                []string
    56  	stdout, stderr, err string
    57  	exitCode            int
    58  }{{
    59  	args: []string{"is-connected"},
    60  	err:  "the required argument `<plug|slot>` was not provided",
    61  }, {
    62  	args: []string{"is-connected", "plug1"},
    63  }, {
    64  	args: []string{"is-connected", "slot1"},
    65  }, {
    66  	// reported as not connected because of undesired flag
    67  	args:     []string{"is-connected", "plug2"},
    68  	exitCode: 1,
    69  }, {
    70  	// reported as not connected because of hotplug-gone flag
    71  	args:     []string{"is-connected", "plug3"},
    72  	exitCode: 1,
    73  }, {
    74  	args: []string{"is-connected", "slot2"},
    75  	err:  `snap "snap1" has no plug or slot named "slot2"`,
    76  }, {
    77  	args: []string{"is-connected", "foo"},
    78  	err:  `snap "snap1" has no plug or slot named "foo"`,
    79  }, {
    80  	// snap1:plug1 does not use an allowed interface
    81  	args: []string{"is-connected", "--pid", "1002", "plug1"},
    82  	err:  `cannot use --pid check with snap1:plug1`,
    83  }, {
    84  	// snap1:slot1 does not use an allowed interface
    85  	args: []string{"is-connected", "--pid", "1002", "slot1"},
    86  	err:  `cannot use --pid check with snap1:slot1`,
    87  }, {
    88  	// snap1:cc slot is not connected to snap2
    89  	args:     []string{"is-connected", "--pid", "1002", "cc"},
    90  	exitCode: 1,
    91  }, {
    92  	// snap1:cc slot is connected to snap3
    93  	args:     []string{"is-connected", "--pid", "1003", "cc"},
    94  	exitCode: 0,
    95  }, {
    96  	// snap1:cc slot is not connected to a non-snap pid
    97  	args:     []string{"is-connected", "--pid", "42", "cc"},
    98  	exitCode: ctlcmd.NotASnapCode,
    99  }, {
   100  	// snap1:cc slot is connected to a classic snap5
   101  	args:     []string{"is-connected", "--pid", "1005", "cc"},
   102  	exitCode: 0,
   103  }, {
   104  	// snap1:audio-record slot is not connected to classic snap5
   105  	args:     []string{"is-connected", "--pid", "1005", "audio-record"},
   106  	exitCode: ctlcmd.ClassicSnapCode,
   107  }, {
   108  	// snap1:plug1 does not use an allowed interface
   109  	args: []string{"is-connected", "--apparmor-label", "snap.snap2.app", "plug1"},
   110  	err:  `cannot use --apparmor-label check with snap1:plug1`,
   111  }, {
   112  	// snap1:slot1 does not use an allowed interface
   113  	args: []string{"is-connected", "--apparmor-label", "snap.snap2.app", "slot1"},
   114  	err:  `cannot use --apparmor-label check with snap1:slot1`,
   115  }, {
   116  	// snap1:cc slot is not connected to snap2
   117  	args:     []string{"is-connected", "--apparmor-label", "snap.snap2.app", "cc"},
   118  	exitCode: 1,
   119  }, {
   120  	// snap1:cc slot is connected to snap3
   121  	args:     []string{"is-connected", "--apparmor-label", "snap.snap3.app", "cc"},
   122  	exitCode: 0,
   123  }, {
   124  	// snap1:cc slot is not connected to a non-snap pid
   125  	args:     []string{"is-connected", "--apparmor-label", "/usr/bin/evince", "cc"},
   126  	exitCode: ctlcmd.NotASnapCode,
   127  }, {
   128  	// snap1:cc slot is connected to a classic snap5
   129  	args:     []string{"is-connected", "--apparmor-label", "snap.snap5.app", "cc"},
   130  	exitCode: 0,
   131  }, {
   132  	// snap1:audio-record slot is not connected to classic snap5
   133  	args:     []string{"is-connected", "--apparmor-label", "snap.snap5.app", "audio-record"},
   134  	exitCode: ctlcmd.ClassicSnapCode,
   135  }}
   136  
   137  func mockInstalledSnap(c *C, st *state.State, snapYaml string) {
   138  	info := snaptest.MockSnapCurrent(c, snapYaml, &snap.SideInfo{Revision: snap.R(1)})
   139  	snapstate.Set(st, info.InstanceName(), &snapstate.SnapState{
   140  		Active: true,
   141  		Sequence: []*snap.SideInfo{
   142  			{
   143  				RealName: info.SnapName(),
   144  				Revision: info.Revision,
   145  				SnapID:   info.InstanceName() + "-id",
   146  			},
   147  		},
   148  		Current:         info.Revision,
   149  		TrackingChannel: "stable",
   150  	})
   151  }
   152  
   153  func (s *isConnectedSuite) testIsConnected(c *C, context *hookstate.Context) {
   154  	mockInstalledSnap(c, s.st, `name: snap1
   155  plugs:
   156    plug1:
   157      interface: x11
   158    plug2:
   159      interface: x11
   160    plug3:
   161      interface: x11
   162  slots:
   163    slot1:
   164      interface: x11
   165    cc:
   166      interface: cups-control
   167    audio-record:
   168      interface: audio-record`)
   169  	mockInstalledSnap(c, s.st, `name: snap2
   170  slots:
   171    slot2:
   172      interface: x11`)
   173  	mockInstalledSnap(c, s.st, `name: snap3
   174  plugs:
   175    plug4:
   176      interface: x11
   177    cc:
   178      interface: cups-control
   179  slots:
   180    slot3:
   181      interface: x11`)
   182  	mockInstalledSnap(c, s.st, `name: snap4
   183  slots:
   184    slot4:
   185      interface: x11`)
   186  	mockInstalledSnap(c, s.st, `name: snap5
   187  confinement: classic
   188  plugs:
   189    cc:
   190      interface: cups-control`)
   191  	restore := ctlcmd.MockCgroupSnapNameFromPid(func(pid int) (string, error) {
   192  		switch {
   193  		case 1000 < pid && pid < 1100:
   194  			return fmt.Sprintf("snap%d", pid-1000), nil
   195  		default:
   196  			return "", fmt.Errorf("Not a snap")
   197  		}
   198  	})
   199  	defer restore()
   200  
   201  	s.st.Set("conns", map[string]interface{}{
   202  		"snap1:plug1 snap2:slot2": map[string]interface{}{},
   203  		"snap1:plug2 snap3:slot3": map[string]interface{}{"undesired": true},
   204  		"snap1:plug3 snap4:slot4": map[string]interface{}{"hotplug-gone": true},
   205  		"snap3:plug4 snap1:slot1": map[string]interface{}{},
   206  		"snap3:cc snap1:cc":       map[string]interface{}{},
   207  		"snap5:cc snap1:cc":       map[string]interface{}{},
   208  	})
   209  
   210  	s.st.Unlock()
   211  	defer s.st.Lock()
   212  
   213  	for _, test := range isConnectedTests {
   214  		stdout, stderr, err := ctlcmd.Run(context, test.args, 0)
   215  		comment := Commentf("%s", test.args)
   216  		if test.exitCode > 0 {
   217  			c.Check(err, DeepEquals, &ctlcmd.UnsuccessfulError{ExitCode: test.exitCode}, comment)
   218  		} else {
   219  			if test.err == "" {
   220  				c.Check(err, IsNil, comment)
   221  			} else {
   222  				c.Check(err, ErrorMatches, test.err, comment)
   223  			}
   224  		}
   225  
   226  		c.Check(string(stdout), Equals, test.stdout, comment)
   227  		c.Check(string(stderr), Equals, "", comment)
   228  	}
   229  }
   230  
   231  func (s *isConnectedSuite) TestIsConnectedFromHook(c *C) {
   232  	s.st.Lock()
   233  	defer s.st.Unlock()
   234  
   235  	task := s.st.NewTask("test-task", "my test task")
   236  	setup := &hookstate.HookSetup{Snap: "snap1", Revision: snap.R(1), Hook: "test-hook"}
   237  
   238  	mockContext, err := hookstate.NewContext(task, s.st, setup, s.mockHandler, "")
   239  	c.Check(err, IsNil)
   240  
   241  	s.testIsConnected(c, mockContext)
   242  }
   243  
   244  func (s *isConnectedSuite) TestIsConnectedFromApp(c *C) {
   245  	s.st.Lock()
   246  	defer s.st.Unlock()
   247  
   248  	// ephemeral context
   249  	setup := &hookstate.HookSetup{Snap: "snap1", Revision: snap.R(1)}
   250  	mockContext, err := hookstate.NewContext(nil, s.st, setup, s.mockHandler, "")
   251  	c.Check(err, IsNil)
   252  
   253  	// sanity
   254  	c.Assert(mockContext.IsEphemeral(), Equals, true)
   255  
   256  	s.testIsConnected(c, mockContext)
   257  }
   258  
   259  func (s *isConnectedSuite) TestNoContextError(c *C) {
   260  	stdout, stderr, err := ctlcmd.Run(nil, []string{"is-connected", "foo"}, 0)
   261  	c.Check(err, ErrorMatches, `cannot invoke snapctl operation commands \(here "is-connected"\) from outside of a snap`)
   262  	c.Check(string(stdout), Equals, "")
   263  	c.Check(string(stderr), Equals, "")
   264  }
   265  
   266  func (s *isConnectedSuite) TestGetRegularUser(c *C) {
   267  	s.st.Lock()
   268  
   269  	mockInstalledSnap(c, s.st, `name: snap1
   270  plugs:
   271    plug1:
   272      interface: x11`)
   273  
   274  	s.st.Set("conns", map[string]interface{}{
   275  		"snap1:plug1 snap2:slot2": map[string]interface{}{},
   276  	})
   277  
   278  	setup := &hookstate.HookSetup{Snap: "snap1", Revision: snap.R(1)}
   279  
   280  	s.st.Unlock()
   281  
   282  	mockContext, err := hookstate.NewContext(nil, s.st, setup, s.mockHandler, "")
   283  	c.Assert(err, IsNil)
   284  	stdout, stderr, err := ctlcmd.Run(mockContext, []string{"is-connected", "plug1"}, 1000)
   285  	c.Check(err, IsNil)
   286  	c.Check(string(stdout), Equals, "")
   287  	c.Check(string(stderr), Equals, "")
   288  }