github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/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.req(c, req, nil).(*daemon.Resp) 181 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeAsync) 182 n := 1 183 c.Assert(installQueue, check.HasLen, n) 184 c.Check(installQueue[n-1], check.Matches, "local::.*/"+regexp.QuoteMeta(dirs.LocalInstallBlobTempPrefix)+".*") 185 186 st := d.Overlord().State() 187 st.Lock() 188 defer st.Unlock() 189 chg := st.Change(rsp.Change) 190 c.Assert(chg, check.NotNil) 191 192 c.Check(soon, check.Equals, 1) 193 194 c.Assert(chg.Tasks(), check.HasLen, n) 195 196 st.Unlock() 197 s.waitTrivialChange(c, chg) 198 st.Lock() 199 200 c.Check(chg.Kind(), check.Equals, "install-snap") 201 var names []string 202 err = chg.Get("snap-names", &names) 203 c.Assert(err, check.IsNil) 204 c.Check(names, check.DeepEquals, []string{expectedInstanceName}) 205 var apiData map[string]interface{} 206 err = chg.Get("api-data", &apiData) 207 c.Assert(err, check.IsNil) 208 c.Check(apiData, check.DeepEquals, map[string]interface{}{ 209 "snap-name": expectedInstanceName, 210 }) 211 212 return chg.Summary() 213 } 214 215 func (s *sideloadSuite) TestSideloadSnapJailModeAndDevmode(c *check.C) { 216 body := "" + 217 "----hello--\r\n" + 218 "Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" + 219 "\r\n" + 220 "xyzzy\r\n" + 221 "----hello--\r\n" + 222 "Content-Disposition: form-data; name=\"jailmode\"\r\n" + 223 "\r\n" + 224 "true\r\n" + 225 "----hello--\r\n" + 226 "Content-Disposition: form-data; name=\"devmode\"\r\n" + 227 "\r\n" + 228 "true\r\n" + 229 "----hello--\r\n" 230 s.daemonWithOverlordMockAndStore(c) 231 232 req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body)) 233 c.Assert(err, check.IsNil) 234 req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--") 235 236 rsp := s.req(c, req, nil).(*daemon.Resp) 237 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError) 238 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, "cannot use devmode and jailmode flags together") 239 } 240 241 func (s *sideloadSuite) TestSideloadSnapJailModeInDevModeOS(c *check.C) { 242 body := "" + 243 "----hello--\r\n" + 244 "Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" + 245 "\r\n" + 246 "xyzzy\r\n" + 247 "----hello--\r\n" + 248 "Content-Disposition: form-data; name=\"jailmode\"\r\n" + 249 "\r\n" + 250 "true\r\n" + 251 "----hello--\r\n" 252 s.daemonWithOverlordMockAndStore(c) 253 254 req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body)) 255 c.Assert(err, check.IsNil) 256 req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--") 257 258 restore := sandbox.MockForceDevMode(true) 259 defer restore() 260 261 rsp := s.req(c, req, nil).(*daemon.Resp) 262 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError) 263 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, "this system cannot honour the jailmode flag") 264 } 265 266 func (s *sideloadSuite) TestLocalInstallSnapDeriveSideInfo(c *check.C) { 267 d := s.daemonWithOverlordMockAndStore(c) 268 // add the assertions first 269 st := d.Overlord().State() 270 271 dev1Acct := assertstest.NewAccount(s.StoreSigning, "devel1", nil, "") 272 273 snapDecl, err := s.StoreSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{ 274 "series": "16", 275 "snap-id": "x-id", 276 "snap-name": "x", 277 "publisher-id": dev1Acct.AccountID(), 278 "timestamp": time.Now().Format(time.RFC3339), 279 }, nil, "") 280 c.Assert(err, check.IsNil) 281 282 snapRev, err := s.StoreSigning.Sign(asserts.SnapRevisionType, map[string]interface{}{ 283 "snap-sha3-384": "YK0GWATaZf09g_fvspYPqm_qtaiqf-KjaNj5uMEQCjQpuXWPjqQbeBINL5H_A0Lo", 284 "snap-size": "5", 285 "snap-id": "x-id", 286 "snap-revision": "41", 287 "developer-id": dev1Acct.AccountID(), 288 "timestamp": time.Now().Format(time.RFC3339), 289 }, nil, "") 290 c.Assert(err, check.IsNil) 291 292 func() { 293 st.Lock() 294 defer st.Unlock() 295 assertstatetest.AddMany(st, s.StoreSigning.StoreAccountKey(""), dev1Acct, snapDecl, snapRev) 296 }() 297 298 body := "" + 299 "----hello--\r\n" + 300 "Content-Disposition: form-data; name=\"snap\"; filename=\"x.snap\"\r\n" + 301 "\r\n" + 302 "xyzzy\r\n" + 303 "----hello--\r\n" 304 req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body)) 305 c.Assert(err, check.IsNil) 306 req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--") 307 308 defer daemon.MockSnapstateInstallPath(func(s *state.State, si *snap.SideInfo, path, name, channel string, flags snapstate.Flags) (*state.TaskSet, *snap.Info, error) { 309 c.Check(flags, check.Equals, snapstate.Flags{RemoveSnapPath: true}) 310 c.Check(si, check.DeepEquals, &snap.SideInfo{ 311 RealName: "x", 312 SnapID: "x-id", 313 Revision: snap.R(41), 314 }) 315 316 return state.NewTaskSet(), &snap.Info{SuggestedName: "x"}, nil 317 })() 318 319 rsp := s.req(c, req, nil).(*daemon.Resp) 320 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeAsync) 321 322 st.Lock() 323 defer st.Unlock() 324 chg := st.Change(rsp.Change) 325 c.Assert(chg, check.NotNil) 326 c.Check(chg.Summary(), check.Equals, `Install "x" snap from file "x.snap"`) 327 var names []string 328 err = chg.Get("snap-names", &names) 329 c.Assert(err, check.IsNil) 330 c.Check(names, check.DeepEquals, []string{"x"}) 331 var apiData map[string]interface{} 332 err = chg.Get("api-data", &apiData) 333 c.Assert(err, check.IsNil) 334 c.Check(apiData, check.DeepEquals, map[string]interface{}{ 335 "snap-name": "x", 336 }) 337 } 338 339 func (s *sideloadSuite) TestSideloadSnapNoSignaturesDangerOff(c *check.C) { 340 body := "" + 341 "----hello--\r\n" + 342 "Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" + 343 "\r\n" + 344 "xyzzy\r\n" + 345 "----hello--\r\n" 346 s.daemonWithOverlordMockAndStore(c) 347 348 req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body)) 349 c.Assert(err, check.IsNil) 350 req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--") 351 352 // this is the prefix used for tempfiles for sideloading 353 glob := filepath.Join(os.TempDir(), "snapd-sideload-pkg-*") 354 glbBefore, _ := filepath.Glob(glob) 355 rsp := s.req(c, req, nil).(*daemon.Resp) 356 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError) 357 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, `cannot find signatures with metadata for snap "x"`) 358 glbAfter, _ := filepath.Glob(glob) 359 c.Check(len(glbBefore), check.Equals, len(glbAfter)) 360 } 361 362 func (s *sideloadSuite) TestSideloadSnapNotValidFormFile(c *check.C) { 363 s.daemon(c) 364 365 // try a multipart/form-data upload with missing "name" 366 content := "" + 367 "----hello--\r\n" + 368 "Content-Disposition: form-data; filename=\"x\"\r\n" + 369 "\r\n" + 370 "xyzzy\r\n" + 371 "----hello--\r\n" 372 head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"} 373 374 buf := bytes.NewBufferString(content) 375 req, err := http.NewRequest("POST", "/v2/snaps", buf) 376 c.Assert(err, check.IsNil) 377 for k, v := range head { 378 req.Header.Set(k, v) 379 } 380 381 rsp := s.req(c, req, nil).(*daemon.Resp) 382 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError) 383 c.Assert(rsp.Result.(*daemon.ErrorResult).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 rsp := s.req(c, req, nil).(*daemon.Resp) 412 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError) 413 c.Check(rsp.Result.(*daemon.ErrorResult).Kind, check.Equals, client.ErrorKindSnapChangeConflict) 414 } 415 416 func (s *sideloadSuite) TestSideloadSnapInstanceName(c *check.C) { 417 // try a multipart/form-data upload 418 body := sideLoadBodyWithoutDevMode + 419 "Content-Disposition: form-data; name=\"name\"\r\n" + 420 "\r\n" + 421 "local_instance\r\n" + 422 "----hello--\r\n" 423 head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"} 424 chgSummary := s.sideloadCheck(c, body, head, "local_instance", snapstate.Flags{RemoveSnapPath: true}) 425 c.Check(chgSummary, check.Equals, `Install "local_instance" snap from file "a/b/local.snap"`) 426 } 427 428 func (s *sideloadSuite) TestSideloadSnapInstanceNameNoKey(c *check.C) { 429 // try a multipart/form-data upload 430 body := sideLoadBodyWithoutDevMode + 431 "Content-Disposition: form-data; name=\"name\"\r\n" + 432 "\r\n" + 433 "local\r\n" + 434 "----hello--\r\n" 435 head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"} 436 chgSummary := s.sideloadCheck(c, body, head, "local", snapstate.Flags{RemoveSnapPath: true}) 437 c.Check(chgSummary, check.Equals, `Install "local" snap from file "a/b/local.snap"`) 438 } 439 440 func (s *sideloadSuite) TestSideloadSnapInstanceNameMismatch(c *check.C) { 441 s.daemonWithFakeSnapManager(c) 442 443 defer daemon.MockUnsafeReadSnapInfo(func(path string) (*snap.Info, error) { 444 return &snap.Info{SuggestedName: "bar"}, nil 445 })() 446 447 body := sideLoadBodyWithoutDevMode + 448 "Content-Disposition: form-data; name=\"name\"\r\n" + 449 "\r\n" + 450 "foo_instance\r\n" + 451 "----hello--\r\n" 452 453 req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body)) 454 c.Assert(err, check.IsNil) 455 req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--") 456 457 rsp := s.req(c, req, nil).(*daemon.Resp) 458 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError) 459 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, `instance name "foo_instance" does not match snap name "bar"`) 460 } 461 462 func (s *sideloadSuite) TestInstallPathUnaliased(c *check.C) { 463 body := "" + 464 "----hello--\r\n" + 465 "Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" + 466 "\r\n" + 467 "xyzzy\r\n" + 468 "----hello--\r\n" + 469 "Content-Disposition: form-data; name=\"devmode\"\r\n" + 470 "\r\n" + 471 "true\r\n" + 472 "----hello--\r\n" + 473 "Content-Disposition: form-data; name=\"unaliased\"\r\n" + 474 "\r\n" + 475 "true\r\n" + 476 "----hello--\r\n" 477 head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"} 478 // try a multipart/form-data upload 479 flags := snapstate.Flags{Unaliased: true, RemoveSnapPath: true, DevMode: true} 480 chgSummary := s.sideloadCheck(c, body, head, "local", flags) 481 c.Check(chgSummary, check.Equals, `Install "local" snap from file "x"`) 482 } 483 484 type trySuite struct { 485 apiBaseSuite 486 } 487 488 func (s *trySuite) TestTrySnap(c *check.C) { 489 d := s.daemonWithFakeSnapManager(c) 490 491 var err error 492 493 // mock a try dir 494 tryDir := c.MkDir() 495 snapYaml := filepath.Join(tryDir, "meta", "snap.yaml") 496 err = os.MkdirAll(filepath.Dir(snapYaml), 0755) 497 c.Assert(err, check.IsNil) 498 err = ioutil.WriteFile(snapYaml, []byte("name: foo\nversion: 1.0\n"), 0644) 499 c.Assert(err, check.IsNil) 500 501 reqForFlags := func(f snapstate.Flags) *http.Request { 502 b := "" + 503 "--hello\r\n" + 504 "Content-Disposition: form-data; name=\"action\"\r\n" + 505 "\r\n" + 506 "try\r\n" + 507 "--hello\r\n" + 508 "Content-Disposition: form-data; name=\"snap-path\"\r\n" + 509 "\r\n" + 510 tryDir + "\r\n" + 511 "--hello" 512 513 snip := "\r\n" + 514 "Content-Disposition: form-data; name=%q\r\n" + 515 "\r\n" + 516 "true\r\n" + 517 "--hello" 518 519 if f.DevMode { 520 b += fmt.Sprintf(snip, "devmode") 521 } 522 if f.JailMode { 523 b += fmt.Sprintf(snip, "jailmode") 524 } 525 if f.Classic { 526 b += fmt.Sprintf(snip, "classic") 527 } 528 b += "--\r\n" 529 530 req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(b)) 531 c.Assert(err, check.IsNil) 532 req.Header.Set("Content-Type", "multipart/thing; boundary=hello") 533 534 return req 535 } 536 537 st := d.Overlord().State() 538 st.Lock() 539 defer st.Unlock() 540 541 soon := 0 542 var origEnsureStateSoon func(*state.State) 543 origEnsureStateSoon, restore := daemon.MockEnsureStateSoon(func(st *state.State) { 544 soon++ 545 origEnsureStateSoon(st) 546 }) 547 defer restore() 548 549 for _, t := range []struct { 550 flags snapstate.Flags 551 desc string 552 }{ 553 {snapstate.Flags{}, "core; -"}, 554 {snapstate.Flags{DevMode: true}, "core; devmode"}, 555 {snapstate.Flags{JailMode: true}, "core; jailmode"}, 556 {snapstate.Flags{Classic: true}, "core; classic"}, 557 } { 558 soon = 0 559 560 tryWasCalled := true 561 defer daemon.MockSnapstateTryPath(func(s *state.State, name, path string, flags snapstate.Flags) (*state.TaskSet, error) { 562 c.Check(flags, check.DeepEquals, t.flags, check.Commentf(t.desc)) 563 tryWasCalled = true 564 t := s.NewTask("fake-install-snap", "Doing a fake try") 565 return state.NewTaskSet(t), nil 566 })() 567 568 defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 569 if name != "core" { 570 c.Check(flags, check.DeepEquals, t.flags, check.Commentf(t.desc)) 571 } 572 t := s.NewTask("fake-install-snap", "Doing a fake install") 573 return state.NewTaskSet(t), nil 574 })() 575 576 // try the snap (without an installed core) 577 st.Unlock() 578 rsp := s.req(c, reqForFlags(t.flags), nil).(*daemon.Resp) 579 st.Lock() 580 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeAsync, check.Commentf(t.desc)) 581 c.Assert(tryWasCalled, check.Equals, true, check.Commentf(t.desc)) 582 583 chg := st.Change(rsp.Change) 584 c.Assert(chg, check.NotNil, check.Commentf(t.desc)) 585 586 c.Assert(chg.Tasks(), check.HasLen, 1, check.Commentf(t.desc)) 587 588 st.Unlock() 589 s.waitTrivialChange(c, chg) 590 st.Lock() 591 592 c.Check(chg.Kind(), check.Equals, "try-snap", check.Commentf(t.desc)) 593 c.Check(chg.Summary(), check.Equals, fmt.Sprintf(`Try "%s" snap from %s`, "foo", tryDir), check.Commentf(t.desc)) 594 var names []string 595 err = chg.Get("snap-names", &names) 596 c.Assert(err, check.IsNil, check.Commentf(t.desc)) 597 c.Check(names, check.DeepEquals, []string{"foo"}, check.Commentf(t.desc)) 598 var apiData map[string]interface{} 599 err = chg.Get("api-data", &apiData) 600 c.Assert(err, check.IsNil, check.Commentf(t.desc)) 601 c.Check(apiData, check.DeepEquals, map[string]interface{}{ 602 "snap-name": "foo", 603 }, check.Commentf(t.desc)) 604 605 c.Check(soon, check.Equals, 1, check.Commentf(t.desc)) 606 } 607 } 608 609 func (s *trySuite) TestTrySnapRelative(c *check.C) { 610 d := s.daemon(c) 611 st := d.Overlord().State() 612 613 rsp := daemon.TrySnap(st, "relative-path", snapstate.Flags{}).(*daemon.Resp) 614 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError) 615 c.Check(rsp.Result.(*daemon.ErrorResult).Message, testutil.Contains, "need an absolute path") 616 } 617 618 func (s *trySuite) TestTrySnapNotDir(c *check.C) { 619 d := s.daemon(c) 620 st := d.Overlord().State() 621 622 rsp := daemon.TrySnap(st, "/does/not/exist", snapstate.Flags{}).(*daemon.Resp) 623 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError) 624 c.Check(rsp.Result.(*daemon.ErrorResult).Message, testutil.Contains, "not a snap directory") 625 } 626 627 func (s *trySuite) TestTryChangeConflict(c *check.C) { 628 d := s.daemonWithOverlordMockAndStore(c) 629 st := d.Overlord().State() 630 631 // mock a try dir 632 tryDir := c.MkDir() 633 634 defer daemon.MockUnsafeReadSnapInfo(func(path string) (*snap.Info, error) { 635 return &snap.Info{SuggestedName: "foo"}, nil 636 })() 637 638 defer daemon.MockSnapstateTryPath(func(s *state.State, name, path string, flags snapstate.Flags) (*state.TaskSet, error) { 639 return nil, &snapstate.ChangeConflictError{Snap: "foo"} 640 })() 641 642 rsp := daemon.TrySnap(st, tryDir, snapstate.Flags{}).(*daemon.Resp) 643 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError) 644 c.Check(rsp.Result.(*daemon.ErrorResult).Kind, check.Equals, client.ErrorKindSnapChangeConflict) 645 }