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