gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/daemon/api_sideload_n_try_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2020 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 daemon_test 21 22 import ( 23 "bytes" 24 "context" 25 "fmt" 26 "io/ioutil" 27 "net/http" 28 "os" 29 "path/filepath" 30 "regexp" 31 "time" 32 33 "gopkg.in/check.v1" 34 35 "github.com/snapcore/snapd/asserts" 36 "github.com/snapcore/snapd/asserts/assertstest" 37 "github.com/snapcore/snapd/client" 38 "github.com/snapcore/snapd/daemon" 39 "github.com/snapcore/snapd/dirs" 40 "github.com/snapcore/snapd/overlord/assertstate/assertstatetest" 41 "github.com/snapcore/snapd/overlord/snapstate" 42 "github.com/snapcore/snapd/overlord/state" 43 "github.com/snapcore/snapd/sandbox" 44 "github.com/snapcore/snapd/snap" 45 "github.com/snapcore/snapd/testutil" 46 ) 47 48 var ( 49 _ = check.Suite(&sideloadSuite{}) 50 _ = check.Suite(&trySuite{}) 51 ) 52 53 type sideloadSuite struct { 54 apiBaseSuite 55 } 56 57 func (s *sideloadSuite) SetUpTest(c *check.C) { 58 s.apiBaseSuite.SetUpTest(c) 59 60 s.expectWriteAccess(daemon.AuthenticatedAccess{Polkit: "io.snapcraft.snapd.manage"}) 61 } 62 63 var sideLoadBodyWithoutDevMode = "" + 64 "----hello--\r\n" + 65 "Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" + 66 "\r\n" + 67 "xyzzy\r\n" + 68 "----hello--\r\n" + 69 "Content-Disposition: form-data; name=\"dangerous\"\r\n" + 70 "\r\n" + 71 "true\r\n" + 72 "----hello--\r\n" + 73 "Content-Disposition: form-data; name=\"snap-path\"\r\n" + 74 "\r\n" + 75 "a/b/local.snap\r\n" + 76 "----hello--\r\n" 77 78 func (s *sideloadSuite) TestSideloadSnapOnNonDevModeDistro(c *check.C) { 79 // try a multipart/form-data upload 80 body := sideLoadBodyWithoutDevMode 81 head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"} 82 chgSummary := s.sideloadCheck(c, body, head, "local", snapstate.Flags{RemoveSnapPath: true}) 83 c.Check(chgSummary, check.Equals, `Install "local" snap from file "a/b/local.snap"`) 84 } 85 86 func (s *sideloadSuite) TestSideloadSnapOnDevModeDistro(c *check.C) { 87 // try a multipart/form-data upload 88 body := sideLoadBodyWithoutDevMode 89 head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"} 90 restore := sandbox.MockForceDevMode(true) 91 defer restore() 92 flags := snapstate.Flags{RemoveSnapPath: true} 93 chgSummary := s.sideloadCheck(c, body, head, "local", flags) 94 c.Check(chgSummary, check.Equals, `Install "local" snap from file "a/b/local.snap"`) 95 } 96 97 func (s *sideloadSuite) TestSideloadSnapDevMode(c *check.C) { 98 body := "" + 99 "----hello--\r\n" + 100 "Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" + 101 "\r\n" + 102 "xyzzy\r\n" + 103 "----hello--\r\n" + 104 "Content-Disposition: form-data; name=\"devmode\"\r\n" + 105 "\r\n" + 106 "true\r\n" + 107 "----hello--\r\n" 108 head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"} 109 // try a multipart/form-data upload 110 flags := snapstate.Flags{RemoveSnapPath: true} 111 flags.DevMode = true 112 chgSummary := s.sideloadCheck(c, body, head, "local", flags) 113 c.Check(chgSummary, check.Equals, `Install "local" snap from file "x"`) 114 } 115 116 func (s *sideloadSuite) TestSideloadSnapJailMode(c *check.C) { 117 body := "" + 118 "----hello--\r\n" + 119 "Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" + 120 "\r\n" + 121 "xyzzy\r\n" + 122 "----hello--\r\n" + 123 "Content-Disposition: form-data; name=\"jailmode\"\r\n" + 124 "\r\n" + 125 "true\r\n" + 126 "----hello--\r\n" + 127 "Content-Disposition: form-data; name=\"dangerous\"\r\n" + 128 "\r\n" + 129 "true\r\n" + 130 "----hello--\r\n" 131 head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"} 132 // try a multipart/form-data upload 133 flags := snapstate.Flags{JailMode: true, RemoveSnapPath: true} 134 chgSummary := s.sideloadCheck(c, body, head, "local", flags) 135 c.Check(chgSummary, check.Equals, `Install "local" snap from file "x"`) 136 } 137 138 func (s *sideloadSuite) sideloadCheck(c *check.C, content string, head map[string]string, expectedInstanceName string, expectedFlags snapstate.Flags) string { 139 d := s.daemonWithFakeSnapManager(c) 140 141 soon := 0 142 var origEnsureStateSoon func(*state.State) 143 origEnsureStateSoon, restore := daemon.MockEnsureStateSoon(func(st *state.State) { 144 soon++ 145 origEnsureStateSoon(st) 146 }) 147 defer restore() 148 149 c.Assert(expectedInstanceName != "", check.Equals, true, check.Commentf("expected instance name must be set")) 150 mockedName, _ := snap.SplitInstanceName(expectedInstanceName) 151 152 // setup done 153 installQueue := []string{} 154 defer daemon.MockUnsafeReadSnapInfo(func(path string) (*snap.Info, error) { 155 return &snap.Info{SuggestedName: mockedName}, nil 156 })() 157 158 defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 159 // NOTE: ubuntu-core is not installed in developer mode 160 c.Check(flags, check.Equals, snapstate.Flags{}) 161 installQueue = append(installQueue, name) 162 163 t := s.NewTask("fake-install-snap", "Doing a fake install") 164 return state.NewTaskSet(t), nil 165 })() 166 167 defer daemon.MockSnapstateInstallPath(func(s *state.State, si *snap.SideInfo, path, name, channel string, flags snapstate.Flags) (*state.TaskSet, *snap.Info, error) { 168 c.Check(flags, check.DeepEquals, expectedFlags) 169 170 c.Check(path, testutil.FileEquals, "xyzzy") 171 172 c.Check(name, check.Equals, expectedInstanceName) 173 174 installQueue = append(installQueue, si.RealName+"::"+path) 175 t := s.NewTask("fake-install-snap", "Doing a fake install") 176 return state.NewTaskSet(t), &snap.Info{SuggestedName: name}, nil 177 })() 178 179 buf := bytes.NewBufferString(content) 180 req, err := http.NewRequest("POST", "/v2/snaps", buf) 181 c.Assert(err, check.IsNil) 182 for k, v := range head { 183 req.Header.Set(k, v) 184 } 185 186 rsp := s.asyncReq(c, req, nil) 187 n := 1 188 c.Assert(installQueue, check.HasLen, n) 189 c.Check(installQueue[n-1], check.Matches, "local::.*/"+regexp.QuoteMeta(dirs.LocalInstallBlobTempPrefix)+".*") 190 191 st := d.Overlord().State() 192 st.Lock() 193 defer st.Unlock() 194 chg := st.Change(rsp.Change) 195 c.Assert(chg, check.NotNil) 196 197 c.Check(soon, check.Equals, 1) 198 199 c.Assert(chg.Tasks(), check.HasLen, n) 200 201 st.Unlock() 202 s.waitTrivialChange(c, chg) 203 st.Lock() 204 205 c.Check(chg.Kind(), check.Equals, "install-snap") 206 var names []string 207 err = chg.Get("snap-names", &names) 208 c.Assert(err, check.IsNil) 209 c.Check(names, check.DeepEquals, []string{expectedInstanceName}) 210 var apiData map[string]interface{} 211 err = chg.Get("api-data", &apiData) 212 c.Assert(err, check.IsNil) 213 c.Check(apiData, check.DeepEquals, map[string]interface{}{ 214 "snap-name": expectedInstanceName, 215 }) 216 217 return chg.Summary() 218 } 219 220 func (s *sideloadSuite) TestSideloadSnapJailModeAndDevmode(c *check.C) { 221 body := "" + 222 "----hello--\r\n" + 223 "Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" + 224 "\r\n" + 225 "xyzzy\r\n" + 226 "----hello--\r\n" + 227 "Content-Disposition: form-data; name=\"jailmode\"\r\n" + 228 "\r\n" + 229 "true\r\n" + 230 "----hello--\r\n" + 231 "Content-Disposition: form-data; name=\"devmode\"\r\n" + 232 "\r\n" + 233 "true\r\n" + 234 "----hello--\r\n" 235 s.daemonWithOverlordMockAndStore(c) 236 237 req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body)) 238 c.Assert(err, check.IsNil) 239 req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--") 240 241 rspe := s.errorReq(c, req, nil) 242 c.Check(rspe.Message, check.Equals, "cannot use devmode and jailmode flags together") 243 } 244 245 func (s *sideloadSuite) TestSideloadSnapJailModeInDevModeOS(c *check.C) { 246 body := "" + 247 "----hello--\r\n" + 248 "Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" + 249 "\r\n" + 250 "xyzzy\r\n" + 251 "----hello--\r\n" + 252 "Content-Disposition: form-data; name=\"jailmode\"\r\n" + 253 "\r\n" + 254 "true\r\n" + 255 "----hello--\r\n" 256 s.daemonWithOverlordMockAndStore(c) 257 258 req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body)) 259 c.Assert(err, check.IsNil) 260 req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--") 261 262 restore := sandbox.MockForceDevMode(true) 263 defer restore() 264 265 rspe := s.errorReq(c, req, nil) 266 c.Check(rspe.Message, check.Equals, "this system cannot honour the jailmode flag") 267 } 268 269 func (s *sideloadSuite) TestLocalInstallSnapDeriveSideInfo(c *check.C) { 270 d := s.daemonWithOverlordMockAndStore(c) 271 // add the assertions first 272 st := d.Overlord().State() 273 274 dev1Acct := assertstest.NewAccount(s.StoreSigning, "devel1", nil, "") 275 276 snapDecl, err := s.StoreSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{ 277 "series": "16", 278 "snap-id": "x-id", 279 "snap-name": "x", 280 "publisher-id": dev1Acct.AccountID(), 281 "timestamp": time.Now().Format(time.RFC3339), 282 }, nil, "") 283 c.Assert(err, check.IsNil) 284 285 snapRev, err := s.StoreSigning.Sign(asserts.SnapRevisionType, map[string]interface{}{ 286 "snap-sha3-384": "YK0GWATaZf09g_fvspYPqm_qtaiqf-KjaNj5uMEQCjQpuXWPjqQbeBINL5H_A0Lo", 287 "snap-size": "5", 288 "snap-id": "x-id", 289 "snap-revision": "41", 290 "developer-id": dev1Acct.AccountID(), 291 "timestamp": time.Now().Format(time.RFC3339), 292 }, nil, "") 293 c.Assert(err, check.IsNil) 294 295 func() { 296 st.Lock() 297 defer st.Unlock() 298 assertstatetest.AddMany(st, s.StoreSigning.StoreAccountKey(""), dev1Acct, snapDecl, snapRev) 299 }() 300 301 body := "" + 302 "----hello--\r\n" + 303 "Content-Disposition: form-data; name=\"snap\"; filename=\"x.snap\"\r\n" + 304 "\r\n" + 305 "xyzzy\r\n" + 306 "----hello--\r\n" 307 req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body)) 308 c.Assert(err, check.IsNil) 309 req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--") 310 311 defer daemon.MockSnapstateInstallPath(func(s *state.State, si *snap.SideInfo, path, name, channel string, flags snapstate.Flags) (*state.TaskSet, *snap.Info, error) { 312 c.Check(flags, check.Equals, snapstate.Flags{RemoveSnapPath: true}) 313 c.Check(si, check.DeepEquals, &snap.SideInfo{ 314 RealName: "x", 315 SnapID: "x-id", 316 Revision: snap.R(41), 317 }) 318 319 return state.NewTaskSet(), &snap.Info{SuggestedName: "x"}, nil 320 })() 321 322 rsp := s.asyncReq(c, req, nil) 323 324 st.Lock() 325 defer st.Unlock() 326 chg := st.Change(rsp.Change) 327 c.Assert(chg, check.NotNil) 328 c.Check(chg.Summary(), check.Equals, `Install "x" snap from file "x.snap"`) 329 var names []string 330 err = chg.Get("snap-names", &names) 331 c.Assert(err, check.IsNil) 332 c.Check(names, check.DeepEquals, []string{"x"}) 333 var apiData map[string]interface{} 334 err = chg.Get("api-data", &apiData) 335 c.Assert(err, check.IsNil) 336 c.Check(apiData, check.DeepEquals, map[string]interface{}{ 337 "snap-name": "x", 338 }) 339 } 340 341 func (s *sideloadSuite) TestSideloadSnapNoSignaturesDangerOff(c *check.C) { 342 body := "" + 343 "----hello--\r\n" + 344 "Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" + 345 "\r\n" + 346 "xyzzy\r\n" + 347 "----hello--\r\n" 348 s.daemonWithOverlordMockAndStore(c) 349 350 req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body)) 351 c.Assert(err, check.IsNil) 352 req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--") 353 354 // this is the prefix used for tempfiles for sideloading 355 glob := filepath.Join(os.TempDir(), "snapd-sideload-pkg-*") 356 glbBefore, _ := filepath.Glob(glob) 357 rspe := s.errorReq(c, req, nil) 358 c.Check(rspe.Message, check.Equals, `cannot find signatures with metadata for snap "x"`) 359 glbAfter, _ := filepath.Glob(glob) 360 c.Check(len(glbBefore), check.Equals, len(glbAfter)) 361 } 362 363 func (s *sideloadSuite) TestSideloadSnapNotValidFormFile(c *check.C) { 364 s.daemon(c) 365 366 // try a multipart/form-data upload with missing "name" 367 content := "" + 368 "----hello--\r\n" + 369 "Content-Disposition: form-data; filename=\"x\"\r\n" + 370 "\r\n" + 371 "xyzzy\r\n" + 372 "----hello--\r\n" 373 head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"} 374 375 buf := bytes.NewBufferString(content) 376 req, err := http.NewRequest("POST", "/v2/snaps", buf) 377 c.Assert(err, check.IsNil) 378 for k, v := range head { 379 req.Header.Set(k, v) 380 } 381 382 rspe := s.errorReq(c, req, nil) 383 c.Assert(rspe.Message, check.Matches, `cannot find "snap" file field in provided multipart/form-data payload`) 384 } 385 386 func (s *sideloadSuite) TestSideloadSnapChangeConflict(c *check.C) { 387 body := "" + 388 "----hello--\r\n" + 389 "Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" + 390 "\r\n" + 391 "xyzzy\r\n" + 392 "----hello--\r\n" + 393 "Content-Disposition: form-data; name=\"dangerous\"\r\n" + 394 "\r\n" + 395 "true\r\n" + 396 "----hello--\r\n" 397 s.daemonWithOverlordMockAndStore(c) 398 399 defer daemon.MockUnsafeReadSnapInfo(func(path string) (*snap.Info, error) { 400 return &snap.Info{SuggestedName: "foo"}, nil 401 })() 402 403 defer daemon.MockSnapstateInstallPath(func(s *state.State, si *snap.SideInfo, path, name, channel string, flags snapstate.Flags) (*state.TaskSet, *snap.Info, error) { 404 return nil, nil, &snapstate.ChangeConflictError{Snap: "foo"} 405 })() 406 407 req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body)) 408 c.Assert(err, check.IsNil) 409 req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--") 410 411 rspe := s.errorReq(c, req, nil) 412 c.Check(rspe.Kind, check.Equals, client.ErrorKindSnapChangeConflict) 413 } 414 415 func (s *sideloadSuite) TestSideloadSnapInstanceName(c *check.C) { 416 // try a multipart/form-data upload 417 body := sideLoadBodyWithoutDevMode + 418 "Content-Disposition: form-data; name=\"name\"\r\n" + 419 "\r\n" + 420 "local_instance\r\n" + 421 "----hello--\r\n" 422 head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"} 423 chgSummary := s.sideloadCheck(c, body, head, "local_instance", snapstate.Flags{RemoveSnapPath: true}) 424 c.Check(chgSummary, check.Equals, `Install "local_instance" snap from file "a/b/local.snap"`) 425 } 426 427 func (s *sideloadSuite) TestSideloadSnapInstanceNameNoKey(c *check.C) { 428 // try a multipart/form-data upload 429 body := sideLoadBodyWithoutDevMode + 430 "Content-Disposition: form-data; name=\"name\"\r\n" + 431 "\r\n" + 432 "local\r\n" + 433 "----hello--\r\n" 434 head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"} 435 chgSummary := s.sideloadCheck(c, body, head, "local", snapstate.Flags{RemoveSnapPath: true}) 436 c.Check(chgSummary, check.Equals, `Install "local" snap from file "a/b/local.snap"`) 437 } 438 439 func (s *sideloadSuite) TestSideloadSnapInstanceNameMismatch(c *check.C) { 440 s.daemonWithFakeSnapManager(c) 441 442 defer daemon.MockUnsafeReadSnapInfo(func(path string) (*snap.Info, error) { 443 return &snap.Info{SuggestedName: "bar"}, nil 444 })() 445 446 body := sideLoadBodyWithoutDevMode + 447 "Content-Disposition: form-data; name=\"name\"\r\n" + 448 "\r\n" + 449 "foo_instance\r\n" + 450 "----hello--\r\n" 451 452 req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body)) 453 c.Assert(err, check.IsNil) 454 req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--") 455 456 rspe := s.errorReq(c, req, nil) 457 c.Check(rspe.Message, check.Equals, `instance name "foo_instance" does not match snap name "bar"`) 458 } 459 460 func (s *sideloadSuite) TestInstallPathUnaliased(c *check.C) { 461 body := "" + 462 "----hello--\r\n" + 463 "Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" + 464 "\r\n" + 465 "xyzzy\r\n" + 466 "----hello--\r\n" + 467 "Content-Disposition: form-data; name=\"devmode\"\r\n" + 468 "\r\n" + 469 "true\r\n" + 470 "----hello--\r\n" + 471 "Content-Disposition: form-data; name=\"unaliased\"\r\n" + 472 "\r\n" + 473 "true\r\n" + 474 "----hello--\r\n" 475 head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"} 476 // try a multipart/form-data upload 477 flags := snapstate.Flags{Unaliased: true, RemoveSnapPath: true, DevMode: true} 478 chgSummary := s.sideloadCheck(c, body, head, "local", flags) 479 c.Check(chgSummary, check.Equals, `Install "local" snap from file "x"`) 480 } 481 482 type trySuite struct { 483 apiBaseSuite 484 } 485 486 func (s *trySuite) SetUpTest(c *check.C) { 487 s.apiBaseSuite.SetUpTest(c) 488 489 s.expectWriteAccess(daemon.AuthenticatedAccess{Polkit: "io.snapcraft.snapd.manage"}) 490 } 491 492 func (s *trySuite) TestTrySnap(c *check.C) { 493 d := s.daemonWithFakeSnapManager(c) 494 495 var err error 496 497 // mock a try dir 498 tryDir := c.MkDir() 499 snapYaml := filepath.Join(tryDir, "meta", "snap.yaml") 500 err = os.MkdirAll(filepath.Dir(snapYaml), 0755) 501 c.Assert(err, check.IsNil) 502 err = ioutil.WriteFile(snapYaml, []byte("name: foo\nversion: 1.0\n"), 0644) 503 c.Assert(err, check.IsNil) 504 505 reqForFlags := func(f snapstate.Flags) *http.Request { 506 b := "" + 507 "--hello\r\n" + 508 "Content-Disposition: form-data; name=\"action\"\r\n" + 509 "\r\n" + 510 "try\r\n" + 511 "--hello\r\n" + 512 "Content-Disposition: form-data; name=\"snap-path\"\r\n" + 513 "\r\n" + 514 tryDir + "\r\n" + 515 "--hello" 516 517 snip := "\r\n" + 518 "Content-Disposition: form-data; name=%q\r\n" + 519 "\r\n" + 520 "true\r\n" + 521 "--hello" 522 523 if f.DevMode { 524 b += fmt.Sprintf(snip, "devmode") 525 } 526 if f.JailMode { 527 b += fmt.Sprintf(snip, "jailmode") 528 } 529 if f.Classic { 530 b += fmt.Sprintf(snip, "classic") 531 } 532 b += "--\r\n" 533 534 req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(b)) 535 c.Assert(err, check.IsNil) 536 req.Header.Set("Content-Type", "multipart/thing; boundary=hello") 537 538 return req 539 } 540 541 st := d.Overlord().State() 542 st.Lock() 543 defer st.Unlock() 544 545 soon := 0 546 var origEnsureStateSoon func(*state.State) 547 origEnsureStateSoon, restore := daemon.MockEnsureStateSoon(func(st *state.State) { 548 soon++ 549 origEnsureStateSoon(st) 550 }) 551 defer restore() 552 553 for _, t := range []struct { 554 flags snapstate.Flags 555 desc string 556 }{ 557 {snapstate.Flags{}, "core; -"}, 558 {snapstate.Flags{DevMode: true}, "core; devmode"}, 559 {snapstate.Flags{JailMode: true}, "core; jailmode"}, 560 {snapstate.Flags{Classic: true}, "core; classic"}, 561 } { 562 soon = 0 563 564 tryWasCalled := true 565 defer daemon.MockSnapstateTryPath(func(s *state.State, name, path string, flags snapstate.Flags) (*state.TaskSet, error) { 566 c.Check(flags, check.DeepEquals, t.flags, check.Commentf(t.desc)) 567 tryWasCalled = true 568 t := s.NewTask("fake-install-snap", "Doing a fake try") 569 return state.NewTaskSet(t), nil 570 })() 571 572 defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 573 if name != "core" { 574 c.Check(flags, check.DeepEquals, t.flags, check.Commentf(t.desc)) 575 } 576 t := s.NewTask("fake-install-snap", "Doing a fake install") 577 return state.NewTaskSet(t), nil 578 })() 579 580 // try the snap (without an installed core) 581 st.Unlock() 582 rsp := s.asyncReq(c, reqForFlags(t.flags), nil) 583 st.Lock() 584 c.Assert(tryWasCalled, check.Equals, true, check.Commentf(t.desc)) 585 586 chg := st.Change(rsp.Change) 587 c.Assert(chg, check.NotNil, check.Commentf(t.desc)) 588 589 c.Assert(chg.Tasks(), check.HasLen, 1, check.Commentf(t.desc)) 590 591 st.Unlock() 592 s.waitTrivialChange(c, chg) 593 st.Lock() 594 595 c.Check(chg.Kind(), check.Equals, "try-snap", check.Commentf(t.desc)) 596 c.Check(chg.Summary(), check.Equals, fmt.Sprintf(`Try "%s" snap from %s`, "foo", tryDir), check.Commentf(t.desc)) 597 var names []string 598 err = chg.Get("snap-names", &names) 599 c.Assert(err, check.IsNil, check.Commentf(t.desc)) 600 c.Check(names, check.DeepEquals, []string{"foo"}, check.Commentf(t.desc)) 601 var apiData map[string]interface{} 602 err = chg.Get("api-data", &apiData) 603 c.Assert(err, check.IsNil, check.Commentf(t.desc)) 604 c.Check(apiData, check.DeepEquals, map[string]interface{}{ 605 "snap-name": "foo", 606 }, check.Commentf(t.desc)) 607 608 c.Check(soon, check.Equals, 1, check.Commentf(t.desc)) 609 } 610 } 611 612 func (s *trySuite) TestTrySnapRelative(c *check.C) { 613 d := s.daemon(c) 614 st := d.Overlord().State() 615 616 rspe := daemon.TrySnap(st, "relative-path", snapstate.Flags{}).(*daemon.APIError) 617 c.Check(rspe.Message, testutil.Contains, "need an absolute path") 618 } 619 620 func (s *trySuite) TestTrySnapNotDir(c *check.C) { 621 d := s.daemon(c) 622 st := d.Overlord().State() 623 624 rspe := daemon.TrySnap(st, "/does/not/exist", snapstate.Flags{}).(*daemon.APIError) 625 c.Check(rspe.Message, testutil.Contains, "not a snap directory") 626 } 627 628 func (s *trySuite) TestTryChangeConflict(c *check.C) { 629 d := s.daemonWithOverlordMockAndStore(c) 630 st := d.Overlord().State() 631 632 // mock a try dir 633 tryDir := c.MkDir() 634 635 defer daemon.MockUnsafeReadSnapInfo(func(path string) (*snap.Info, error) { 636 return &snap.Info{SuggestedName: "foo"}, nil 637 })() 638 639 defer daemon.MockSnapstateTryPath(func(s *state.State, name, path string, flags snapstate.Flags) (*state.TaskSet, error) { 640 return nil, &snapstate.ChangeConflictError{Snap: "foo"} 641 })() 642 643 rspe := daemon.TrySnap(st, tryDir, snapstate.Flags{}).(*daemon.APIError) 644 c.Check(rspe.Kind, check.Equals, client.ErrorKindSnapChangeConflict) 645 }