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 }