github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/daemon/api_snaps_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2021 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 "encoding/json" 26 "errors" 27 "fmt" 28 "net/http" 29 "net/http/httptest" 30 "net/url" 31 "os" 32 "path/filepath" 33 "strings" 34 "time" 35 36 "gopkg.in/check.v1" 37 38 "github.com/snapcore/snapd/arch" 39 "github.com/snapcore/snapd/client" 40 "github.com/snapcore/snapd/daemon" 41 "github.com/snapcore/snapd/dirs" 42 "github.com/snapcore/snapd/overlord/assertstate" 43 "github.com/snapcore/snapd/overlord/auth" 44 "github.com/snapcore/snapd/overlord/healthstate" 45 "github.com/snapcore/snapd/overlord/snapstate" 46 "github.com/snapcore/snapd/overlord/state" 47 "github.com/snapcore/snapd/sandbox" 48 "github.com/snapcore/snapd/snap" 49 "github.com/snapcore/snapd/snap/channel" 50 "github.com/snapcore/snapd/snap/snaptest" 51 "github.com/snapcore/snapd/store" 52 "github.com/snapcore/snapd/testutil" 53 ) 54 55 type snapsSuite struct { 56 apiBaseSuite 57 } 58 59 var _ = check.Suite(&snapsSuite{}) 60 61 func (s *snapsSuite) SetUpTest(c *check.C) { 62 s.apiBaseSuite.SetUpTest(c) 63 64 s.expectWriteAccess(daemon.AuthenticatedAccess{Polkit: "io.snapcraft.snapd.manage"}) 65 } 66 67 func (s *snapsSuite) TestSnapsInfoIntegration(c *check.C) { 68 s.checkSnapsInfoIntegration(c, false, nil) 69 } 70 71 func (s *snapsSuite) TestSnapsInfoIntegrationSome(c *check.C) { 72 s.checkSnapsInfoIntegration(c, false, []string{"foo", "baz"}) 73 } 74 75 func (s *snapsSuite) TestSnapsInfoIntegrationAll(c *check.C) { 76 s.checkSnapsInfoIntegration(c, true, nil) 77 } 78 79 func (s *snapsSuite) TestSnapsInfoIntegrationAllSome(c *check.C) { 80 s.checkSnapsInfoIntegration(c, true, []string{"foo", "baz"}) 81 } 82 83 func snapList(rawSnaps interface{}) []map[string]interface{} { 84 snaps := make([]map[string]interface{}, len(rawSnaps.([]*json.RawMessage))) 85 for i, raw := range rawSnaps.([]*json.RawMessage) { 86 err := json.Unmarshal([]byte(*raw), &snaps[i]) 87 if err != nil { 88 panic(err) 89 } 90 } 91 return snaps 92 } 93 94 func (s *snapsSuite) checkSnapsInfoIntegration(c *check.C, all bool, names []string) { 95 d := s.daemon(c) 96 97 type tsnap struct { 98 name string 99 dev string 100 ver string 101 rev int 102 active bool 103 104 wanted bool 105 } 106 107 tsnaps := []tsnap{ 108 {name: "foo", dev: "bar", ver: "v0.9", rev: 1}, 109 {name: "foo", dev: "bar", ver: "v1", rev: 5, active: true}, 110 {name: "bar", dev: "baz", ver: "v2", rev: 10, active: true}, 111 {name: "baz", dev: "qux", ver: "v3", rev: 15, active: true}, 112 {name: "qux", dev: "mip", ver: "v4", rev: 20, active: true}, 113 } 114 numExpected := 0 115 116 for _, snp := range tsnaps { 117 if all || snp.active { 118 if len(names) == 0 { 119 numExpected++ 120 snp.wanted = true 121 } 122 for _, n := range names { 123 if snp.name == n { 124 numExpected++ 125 snp.wanted = true 126 break 127 } 128 } 129 } 130 s.mkInstalledInState(c, d, snp.name, snp.dev, snp.ver, snap.R(snp.rev), snp.active, "") 131 } 132 133 q := url.Values{} 134 if all { 135 q.Set("select", "all") 136 } 137 if len(names) > 0 { 138 q.Set("snaps", strings.Join(names, ",")) 139 } 140 req, err := http.NewRequest("GET", "/v2/snaps?"+q.Encode(), nil) 141 c.Assert(err, check.IsNil) 142 143 rsp := s.syncReq(c, req, nil) 144 c.Check(rsp.Status, check.Equals, 200) 145 c.Check(rsp.Result, check.NotNil) 146 147 snaps := snapList(rsp.Result) 148 c.Check(snaps, check.HasLen, numExpected) 149 150 for _, s := range tsnaps { 151 if !((all || s.active) && s.wanted) { 152 continue 153 } 154 var got map[string]interface{} 155 for _, got = range snaps { 156 if got["name"].(string) == s.name && got["revision"].(string) == snap.R(s.rev).String() { 157 break 158 } 159 } 160 c.Check(got["name"], check.Equals, s.name) 161 c.Check(got["version"], check.Equals, s.ver) 162 c.Check(got["revision"], check.Equals, snap.R(s.rev).String()) 163 c.Check(got["developer"], check.Equals, s.dev) 164 c.Check(got["confinement"], check.Equals, "strict") 165 } 166 } 167 168 func (s *snapsSuite) TestSnapsInfoOnlyLocal(c *check.C) { 169 d := s.daemon(c) 170 171 s.rsnaps = []*snap.Info{{ 172 SideInfo: snap.SideInfo{ 173 RealName: "store", 174 }, 175 Publisher: snap.StoreAccount{ 176 ID: "foo-id", 177 Username: "foo", 178 DisplayName: "Foo", 179 Validation: "unproven", 180 }, 181 }} 182 s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(10), true, "") 183 st := d.Overlord().State() 184 st.Lock() 185 st.Set("health", map[string]healthstate.HealthState{ 186 "local": {Status: healthstate.OkayStatus}, 187 }) 188 st.Unlock() 189 190 req, err := http.NewRequest("GET", "/v2/snaps?sources=local", nil) 191 c.Assert(err, check.IsNil) 192 193 rsp := s.syncReq(c, req, nil) 194 195 c.Assert(rsp.Sources, check.DeepEquals, []string{"local"}) 196 197 snaps := snapList(rsp.Result) 198 c.Assert(snaps, check.HasLen, 1) 199 c.Assert(snaps[0]["name"], check.Equals, "local") 200 c.Check(snaps[0]["health"], check.DeepEquals, map[string]interface{}{ 201 "status": "okay", 202 "revision": "unset", 203 "timestamp": "0001-01-01T00:00:00Z", 204 }) 205 } 206 207 func (s *snapsSuite) TestSnapsInfoAllMixedPublishers(c *check.C) { 208 d := s.daemon(c) 209 210 // the first 'local' is from a 'local' snap 211 s.mkInstalledInState(c, d, "local", "", "v1", snap.R(-1), false, "") 212 s.mkInstalledInState(c, d, "local", "foo", "v2", snap.R(1), false, "") 213 s.mkInstalledInState(c, d, "local", "foo", "v3", snap.R(2), true, "") 214 215 req, err := http.NewRequest("GET", "/v2/snaps?select=all", nil) 216 c.Assert(err, check.IsNil) 217 rsp := s.syncReq(c, req, nil) 218 219 snaps := snapList(rsp.Result) 220 c.Assert(snaps, check.HasLen, 3) 221 222 publisher := map[string]interface{}{ 223 "id": "foo-id", 224 "username": "foo", 225 "display-name": "Foo", 226 "validation": "unproven", 227 } 228 229 c.Check(snaps[0]["publisher"], check.IsNil) 230 c.Check(snaps[1]["publisher"], check.DeepEquals, publisher) 231 c.Check(snaps[2]["publisher"], check.DeepEquals, publisher) 232 } 233 234 func (s *snapsSuite) TestSnapsInfoAll(c *check.C) { 235 d := s.daemon(c) 236 237 s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(1), false, "") 238 s.mkInstalledInState(c, d, "local", "foo", "v2", snap.R(2), false, "") 239 s.mkInstalledInState(c, d, "local", "foo", "v3", snap.R(3), true, "") 240 s.mkInstalledInState(c, d, "local_foo", "foo", "v4", snap.R(4), true, "") 241 brokenInfo := s.mkInstalledInState(c, d, "local_bar", "foo", "v5", snap.R(5), true, "") 242 // make sure local_bar is 'broken' 243 err := os.Remove(filepath.Join(brokenInfo.MountDir(), "meta", "snap.yaml")) 244 c.Assert(err, check.IsNil) 245 246 expectedHappy := map[string]bool{ 247 "local": true, 248 "local_foo": true, 249 "local_bar": true, 250 } 251 for _, t := range []struct { 252 q string 253 numSnaps int 254 typ daemon.ResponseType 255 }{ 256 {"?select=enabled", 3, "sync"}, 257 {`?select=`, 3, "sync"}, 258 {"", 3, "sync"}, 259 {"?select=all", 5, "sync"}, 260 {"?select=invalid-field", 0, "error"}, 261 } { 262 c.Logf("trying: %v", t) 263 req, err := http.NewRequest("GET", fmt.Sprintf("/v2/snaps%s", t.q), nil) 264 c.Assert(err, check.IsNil) 265 rsp := s.jsonReq(c, req, nil) 266 c.Assert(rsp.Type, check.Equals, t.typ) 267 268 if rsp.Type != "error" { 269 snaps := snapList(rsp.Result) 270 c.Assert(snaps, check.HasLen, t.numSnaps) 271 seen := map[string]bool{} 272 for _, s := range snaps { 273 seen[s["name"].(string)] = true 274 } 275 c.Assert(seen, check.DeepEquals, expectedHappy) 276 } 277 } 278 } 279 280 func (s *snapsSuite) TestSnapsInfoOnlyStore(c *check.C) { 281 d := s.daemon(c) 282 283 s.suggestedCurrency = "EUR" 284 285 s.rsnaps = []*snap.Info{{ 286 SideInfo: snap.SideInfo{ 287 RealName: "store", 288 }, 289 Publisher: snap.StoreAccount{ 290 ID: "foo-id", 291 Username: "foo", 292 DisplayName: "Foo", 293 Validation: "unproven", 294 }, 295 }} 296 s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(10), true, "") 297 298 req, err := http.NewRequest("GET", "/v2/snaps?sources=store", nil) 299 c.Assert(err, check.IsNil) 300 301 rsp := s.syncReq(c, req, nil) 302 303 c.Assert(rsp.Sources, check.DeepEquals, []string{"store"}) 304 305 snaps := snapList(rsp.Result) 306 c.Assert(snaps, check.HasLen, 1) 307 c.Assert(snaps[0]["name"], check.Equals, "store") 308 c.Check(snaps[0]["prices"], check.IsNil) 309 310 c.Check(rsp.SuggestedCurrency, check.Equals, "EUR") 311 } 312 313 func (s *snapsSuite) TestSnapsInfoStoreWithAuth(c *check.C) { 314 d := s.daemon(c) 315 316 state := d.Overlord().State() 317 state.Lock() 318 user, err := auth.NewUser(state, "username", "email@test.com", "macaroon", []string{"discharge"}) 319 state.Unlock() 320 c.Check(err, check.IsNil) 321 322 req, err := http.NewRequest("GET", "/v2/snaps?sources=store", nil) 323 c.Assert(err, check.IsNil) 324 325 c.Assert(s.user, check.IsNil) 326 327 _ = s.syncReq(c, req, user) 328 329 // ensure user was set 330 c.Assert(s.user, check.DeepEquals, user) 331 } 332 333 func (s *snapsSuite) TestSnapsInfoLocalAndStore(c *check.C) { 334 d := s.daemon(c) 335 336 s.rsnaps = []*snap.Info{{ 337 Version: "v42", 338 SideInfo: snap.SideInfo{ 339 RealName: "remote", 340 }, 341 Publisher: snap.StoreAccount{ 342 ID: "foo-id", 343 Username: "foo", 344 DisplayName: "Foo", 345 Validation: "unproven", 346 }, 347 }} 348 s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(10), true, "") 349 350 req, err := http.NewRequest("GET", "/v2/snaps?sources=local,store", nil) 351 c.Assert(err, check.IsNil) 352 353 rsp := s.syncReq(c, req, nil) 354 355 // presence of 'store' in sources bounces request over to /find 356 c.Assert(rsp.Sources, check.DeepEquals, []string{"store"}) 357 358 snaps := snapList(rsp.Result) 359 c.Assert(snaps, check.HasLen, 1) 360 c.Check(snaps[0]["version"], check.Equals, "v42") 361 362 // as does a 'q' 363 req, err = http.NewRequest("GET", "/v2/snaps?q=what", nil) 364 c.Assert(err, check.IsNil) 365 rsp = s.syncReq(c, req, nil) 366 snaps = snapList(rsp.Result) 367 c.Assert(snaps, check.HasLen, 1) 368 c.Check(snaps[0]["version"], check.Equals, "v42") 369 370 // otherwise, local only 371 req, err = http.NewRequest("GET", "/v2/snaps", nil) 372 c.Assert(err, check.IsNil) 373 rsp = s.syncReq(c, req, nil) 374 snaps = snapList(rsp.Result) 375 c.Assert(snaps, check.HasLen, 1) 376 c.Check(snaps[0]["version"], check.Equals, "v1") 377 } 378 379 func (s *snapsSuite) TestSnapsInfoDefaultSources(c *check.C) { 380 d := s.daemon(c) 381 382 s.rsnaps = []*snap.Info{{ 383 SideInfo: snap.SideInfo{ 384 RealName: "remote", 385 }, 386 Publisher: snap.StoreAccount{ 387 ID: "foo-id", 388 Username: "foo", 389 DisplayName: "Foo", 390 Validation: "unproven", 391 }, 392 }} 393 s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(10), true, "") 394 395 req, err := http.NewRequest("GET", "/v2/snaps", nil) 396 c.Assert(err, check.IsNil) 397 398 rsp := s.syncReq(c, req, nil) 399 400 c.Assert(rsp.Sources, check.DeepEquals, []string{"local"}) 401 snaps := snapList(rsp.Result) 402 c.Assert(snaps, check.HasLen, 1) 403 } 404 405 func (s *snapsSuite) TestSnapsInfoFilterRemote(c *check.C) { 406 s.daemon(c) 407 408 s.rsnaps = nil 409 410 req, err := http.NewRequest("GET", "/v2/snaps?q=foo&sources=store", nil) 411 c.Assert(err, check.IsNil) 412 413 rsp := s.syncReq(c, req, nil) 414 415 c.Check(s.storeSearch, check.DeepEquals, store.Search{Query: "foo"}) 416 417 c.Assert(rsp.Result, check.NotNil) 418 } 419 420 func (s *snapsSuite) TestPostSnapsVerifyMultiSnapInstruction(c *check.C) { 421 s.daemonWithOverlordMockAndStore(c) 422 423 buf := strings.NewReader(`{"action": "install","snaps":["ubuntu-core"]}`) 424 req, err := http.NewRequest("POST", "/v2/snaps", buf) 425 c.Assert(err, check.IsNil) 426 req.Header.Set("Content-Type", "application/json") 427 428 rspe := s.errorReq(c, req, nil) 429 c.Check(rspe.Status, check.Equals, 400) 430 c.Check(rspe.Message, testutil.Contains, `cannot install "ubuntu-core", please use "core" instead`) 431 } 432 433 func (s *snapsSuite) TestPostSnapsUnsupportedMultiOp(c *check.C) { 434 s.daemonWithOverlordMockAndStore(c) 435 436 buf := strings.NewReader(`{"action": "switch","snaps":["foo"]}`) 437 req, err := http.NewRequest("POST", "/v2/snaps", buf) 438 c.Assert(err, check.IsNil) 439 req.Header.Set("Content-Type", "application/json") 440 441 rspe := s.errorReq(c, req, nil) 442 c.Check(rspe.Status, check.Equals, 400) 443 c.Check(rspe.Message, testutil.Contains, `unsupported multi-snap operation "switch"`) 444 } 445 446 func (s *snapsSuite) TestPostSnapsNoWeirdses(c *check.C) { 447 s.daemonWithOverlordMockAndStore(c) 448 449 // one could add more actions here ... 🤷 450 for _, action := range []string{"install", "refresh", "remove"} { 451 for weird, v := range map[string]string{ 452 "channel": `"beta"`, 453 "revision": `"1"`, 454 "devmode": "true", 455 "jailmode": "true", 456 "cohort-key": `"what"`, 457 "leave-cohort": "true", 458 "purge": "true", 459 } { 460 buf := strings.NewReader(fmt.Sprintf(`{"action": "%s","snaps":["foo","bar"], "%s": %s}`, action, weird, v)) 461 req, err := http.NewRequest("POST", "/v2/snaps", buf) 462 c.Assert(err, check.IsNil) 463 req.Header.Set("Content-Type", "application/json") 464 465 rspe := s.errorReq(c, req, nil) 466 c.Check(rspe.Status, check.Equals, 400) 467 c.Check(rspe.Message, testutil.Contains, `unsupported option provided for multi-snap operation`) 468 } 469 } 470 } 471 472 func (s *snapsSuite) TestPostSnapsOp(c *check.C) { 473 s.testPostSnapsOp(c, "application/json") 474 } 475 476 func (s *snapsSuite) TestPostSnapsOpMoreComplexContentType(c *check.C) { 477 s.testPostSnapsOp(c, "application/json; charset=utf-8") 478 } 479 480 func (s *snapsSuite) testPostSnapsOp(c *check.C, contentType string) { 481 defer daemon.MockAssertstateRefreshSnapDeclarations(func(*state.State, int) error { return nil })() 482 defer daemon.MockSnapstateUpdateMany(func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) { 483 c.Check(names, check.HasLen, 0) 484 t := s.NewTask("fake-refresh-all", "Refreshing everything") 485 return []string{"fake1", "fake2"}, []*state.TaskSet{state.NewTaskSet(t)}, nil 486 })() 487 488 d := s.daemonWithOverlordMockAndStore(c) 489 490 buf := bytes.NewBufferString(`{"action": "refresh"}`) 491 req, err := http.NewRequest("POST", "/v2/snaps", buf) 492 c.Assert(err, check.IsNil) 493 req.Header.Set("Content-Type", contentType) 494 495 rsp := s.asyncReq(c, req, nil) 496 497 st := d.Overlord().State() 498 st.Lock() 499 defer st.Unlock() 500 chg := st.Change(rsp.Change) 501 c.Check(chg.Summary(), check.Equals, `Refresh snaps "fake1", "fake2"`) 502 var apiData map[string]interface{} 503 c.Check(chg.Get("api-data", &apiData), check.IsNil) 504 c.Check(apiData["snap-names"], check.DeepEquals, []interface{}{"fake1", "fake2"}) 505 } 506 507 func (s *snapsSuite) TestPostSnapsOpInvalidCharset(c *check.C) { 508 s.daemon(c) 509 510 buf := bytes.NewBufferString(`{"action": "refresh"}`) 511 req, err := http.NewRequest("POST", "/v2/snaps", buf) 512 c.Assert(err, check.IsNil) 513 req.Header.Set("Content-Type", "application/json; charset=iso-8859-1") 514 515 rspe := s.errorReq(c, req, nil) 516 c.Check(rspe.Status, check.Equals, 400) 517 c.Check(rspe.Message, testutil.Contains, "unknown charset in content type") 518 } 519 520 func (s *snapsSuite) TestRefreshAll(c *check.C) { 521 refreshSnapDecls := false 522 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 523 refreshSnapDecls = true 524 return assertstate.RefreshSnapDeclarations(s, userID) 525 })() 526 527 d := s.daemon(c) 528 529 for _, tst := range []struct { 530 snaps []string 531 msg string 532 }{ 533 {nil, "Refresh all snaps: no updates"}, 534 {[]string{"fake"}, `Refresh snap "fake"`}, 535 {[]string{"fake1", "fake2"}, `Refresh snaps "fake1", "fake2"`}, 536 } { 537 refreshSnapDecls = false 538 539 defer daemon.MockSnapstateUpdateMany(func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) { 540 c.Check(names, check.HasLen, 0) 541 t := s.NewTask("fake-refresh-all", "Refreshing everything") 542 return tst.snaps, []*state.TaskSet{state.NewTaskSet(t)}, nil 543 })() 544 545 inst := &daemon.SnapInstruction{Action: "refresh"} 546 st := d.Overlord().State() 547 st.Lock() 548 res, err := inst.DispatchForMany()(inst, st) 549 st.Unlock() 550 c.Assert(err, check.IsNil) 551 c.Check(res.Summary, check.Equals, tst.msg) 552 c.Check(refreshSnapDecls, check.Equals, true) 553 } 554 } 555 556 func (s *snapsSuite) TestRefreshAllNoChanges(c *check.C) { 557 refreshSnapDecls := false 558 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 559 refreshSnapDecls = true 560 return assertstate.RefreshSnapDeclarations(s, userID) 561 })() 562 563 defer daemon.MockSnapstateUpdateMany(func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) { 564 c.Check(names, check.HasLen, 0) 565 return nil, nil, nil 566 })() 567 568 d := s.daemon(c) 569 inst := &daemon.SnapInstruction{Action: "refresh"} 570 st := d.Overlord().State() 571 st.Lock() 572 res, err := inst.DispatchForMany()(inst, st) 573 st.Unlock() 574 c.Assert(err, check.IsNil) 575 c.Check(res.Summary, check.Equals, `Refresh all snaps: no updates`) 576 c.Check(refreshSnapDecls, check.Equals, true) 577 } 578 579 func (s *snapsSuite) TestRefreshMany(c *check.C) { 580 refreshSnapDecls := false 581 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 582 refreshSnapDecls = true 583 return nil 584 })() 585 586 defer daemon.MockSnapstateUpdateMany(func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) { 587 c.Check(names, check.HasLen, 2) 588 t := s.NewTask("fake-refresh-2", "Refreshing two") 589 return names, []*state.TaskSet{state.NewTaskSet(t)}, nil 590 })() 591 592 d := s.daemon(c) 593 inst := &daemon.SnapInstruction{Action: "refresh", Snaps: []string{"foo", "bar"}} 594 st := d.Overlord().State() 595 st.Lock() 596 res, err := inst.DispatchForMany()(inst, st) 597 st.Unlock() 598 c.Assert(err, check.IsNil) 599 c.Check(res.Summary, check.Equals, `Refresh snaps "foo", "bar"`) 600 c.Check(res.Affected, check.DeepEquals, inst.Snaps) 601 c.Check(refreshSnapDecls, check.Equals, true) 602 } 603 604 func (s *snapsSuite) TestRefreshMany1(c *check.C) { 605 refreshSnapDecls := false 606 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 607 refreshSnapDecls = true 608 return nil 609 })() 610 611 defer daemon.MockSnapstateUpdateMany(func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) { 612 c.Check(names, check.HasLen, 1) 613 t := s.NewTask("fake-refresh-1", "Refreshing one") 614 return names, []*state.TaskSet{state.NewTaskSet(t)}, nil 615 })() 616 617 d := s.daemon(c) 618 inst := &daemon.SnapInstruction{Action: "refresh", Snaps: []string{"foo"}} 619 st := d.Overlord().State() 620 st.Lock() 621 res, err := inst.DispatchForMany()(inst, st) 622 st.Unlock() 623 c.Assert(err, check.IsNil) 624 c.Check(res.Summary, check.Equals, `Refresh snap "foo"`) 625 c.Check(res.Affected, check.DeepEquals, inst.Snaps) 626 c.Check(refreshSnapDecls, check.Equals, true) 627 } 628 629 func (s *snapsSuite) TestInstallMany(c *check.C) { 630 defer daemon.MockSnapstateInstallMany(func(s *state.State, names []string, userID int) ([]string, []*state.TaskSet, error) { 631 c.Check(names, check.HasLen, 2) 632 t := s.NewTask("fake-install-2", "Install two") 633 return names, []*state.TaskSet{state.NewTaskSet(t)}, nil 634 })() 635 636 d := s.daemon(c) 637 inst := &daemon.SnapInstruction{Action: "install", Snaps: []string{"foo", "bar"}} 638 st := d.Overlord().State() 639 st.Lock() 640 res, err := inst.DispatchForMany()(inst, st) 641 st.Unlock() 642 c.Assert(err, check.IsNil) 643 c.Check(res.Summary, check.Equals, `Install snaps "foo", "bar"`) 644 c.Check(res.Affected, check.DeepEquals, inst.Snaps) 645 } 646 647 func (s *snapsSuite) TestInstallManyEmptyName(c *check.C) { 648 defer daemon.MockSnapstateInstallMany(func(_ *state.State, _ []string, _ int) ([]string, []*state.TaskSet, error) { 649 return nil, nil, errors.New("should not be called") 650 })() 651 d := s.daemon(c) 652 inst := &daemon.SnapInstruction{Action: "install", Snaps: []string{"", "bar"}} 653 st := d.Overlord().State() 654 st.Lock() 655 res, err := inst.DispatchForMany()(inst, st) 656 st.Unlock() 657 c.Assert(res, check.IsNil) 658 c.Assert(err, check.ErrorMatches, "cannot install snap with empty name") 659 } 660 661 func (s *snapsSuite) TestRemoveMany(c *check.C) { 662 defer daemon.MockSnapstateRemoveMany(func(s *state.State, names []string) ([]string, []*state.TaskSet, error) { 663 c.Check(names, check.HasLen, 2) 664 t := s.NewTask("fake-remove-2", "Remove two") 665 return names, []*state.TaskSet{state.NewTaskSet(t)}, nil 666 })() 667 668 d := s.daemon(c) 669 inst := &daemon.SnapInstruction{Action: "remove", Snaps: []string{"foo", "bar"}} 670 st := d.Overlord().State() 671 st.Lock() 672 res, err := inst.DispatchForMany()(inst, st) 673 st.Unlock() 674 c.Assert(err, check.IsNil) 675 c.Check(res.Summary, check.Equals, `Remove snaps "foo", "bar"`) 676 c.Check(res.Affected, check.DeepEquals, inst.Snaps) 677 } 678 679 func (s *snapsSuite) TestSnapInfoOneIntegration(c *check.C) { 680 d := s.daemon(c) 681 682 // we have v0 [r5] installed 683 s.mkInstalledInState(c, d, "foo", "bar", "v0", snap.R(5), false, "") 684 // and v1 [r10] is current 685 s.mkInstalledInState(c, d, "foo", "bar", "v1", snap.R(10), true, `title: title 686 description: description 687 summary: summary 688 license: GPL-3.0 689 base: base18 690 apps: 691 cmd: 692 command: some.cmd 693 cmd2: 694 command: other.cmd 695 cmd3: 696 command: other.cmd 697 common-id: org.foo.cmd 698 svc1: 699 command: somed1 700 daemon: simple 701 svc2: 702 command: somed2 703 daemon: forking 704 svc3: 705 command: somed3 706 daemon: oneshot 707 svc4: 708 command: somed4 709 daemon: notify 710 svc5: 711 command: some5 712 timer: mon1,12:15 713 daemon: simple 714 svc6: 715 command: some6 716 daemon: simple 717 sockets: 718 sock: 719 listen-stream: $SNAP_COMMON/run.sock 720 svc7: 721 command: some7 722 daemon: simple 723 sockets: 724 other-sock: 725 listen-stream: $SNAP_COMMON/other-run.sock 726 `) 727 df := s.mkInstalledDesktopFile(c, "foo_cmd.desktop", "[Desktop]\nExec=foo.cmd %U") 728 s.SysctlBufs = [][]byte{ 729 []byte(`Type=simple 730 Id=snap.foo.svc1.service 731 ActiveState=fumbling 732 UnitFileState=enabled 733 `), 734 []byte(`Type=forking 735 Id=snap.foo.svc2.service 736 ActiveState=active 737 UnitFileState=disabled 738 `), 739 []byte(`Type=oneshot 740 Id=snap.foo.svc3.service 741 ActiveState=reloading 742 UnitFileState=static 743 `), 744 []byte(`Type=notify 745 Id=snap.foo.svc4.service 746 ActiveState=inactive 747 UnitFileState=potatoes 748 `), 749 []byte(`Type=simple 750 Id=snap.foo.svc5.service 751 ActiveState=inactive 752 UnitFileState=static 753 `), 754 []byte(`Id=snap.foo.svc5.timer 755 ActiveState=active 756 UnitFileState=enabled 757 `), 758 []byte(`Type=simple 759 Id=snap.foo.svc6.service 760 ActiveState=inactive 761 UnitFileState=static 762 `), 763 []byte(`Id=snap.foo.svc6.sock.socket 764 ActiveState=active 765 UnitFileState=enabled 766 `), 767 []byte(`Type=simple 768 Id=snap.foo.svc7.service 769 ActiveState=inactive 770 UnitFileState=static 771 `), 772 []byte(`Id=snap.foo.svc7.other-sock.socket 773 ActiveState=inactive 774 UnitFileState=enabled 775 `), 776 } 777 778 var snapst snapstate.SnapState 779 st := d.Overlord().State() 780 st.Lock() 781 st.Set("health", map[string]healthstate.HealthState{ 782 "foo": {Status: healthstate.OkayStatus}, 783 }) 784 err := snapstate.Get(st, "foo", &snapst) 785 st.Unlock() 786 c.Assert(err, check.IsNil) 787 788 // modify state 789 snapst.TrackingChannel = "beta" 790 snapst.IgnoreValidation = true 791 snapst.CohortKey = "some-long-cohort-key" 792 st.Lock() 793 snapstate.Set(st, "foo", &snapst) 794 st.Unlock() 795 796 req, err := http.NewRequest("GET", "/v2/snaps/foo", nil) 797 c.Assert(err, check.IsNil) 798 rsp := s.syncReq(c, req, nil) 799 800 c.Assert(rsp.Result, check.FitsTypeOf, &client.Snap{}) 801 m := rsp.Result.(*client.Snap) 802 803 // installed-size depends on vagaries of the filesystem, just check type 804 c.Check(m.InstalledSize, check.FitsTypeOf, int64(0)) 805 m.InstalledSize = 0 806 // ditto install-date 807 c.Check(m.InstallDate, check.FitsTypeOf, time.Time{}) 808 m.InstallDate = time.Time{} 809 810 expected := &daemon.RespJSON{ 811 Type: daemon.ResponseTypeSync, 812 Status: 200, 813 Result: &client.Snap{ 814 ID: "foo-id", 815 Name: "foo", 816 Revision: snap.R(10), 817 Version: "v1", 818 Channel: "stable", 819 TrackingChannel: "beta", 820 IgnoreValidation: true, 821 Title: "title", 822 Summary: "summary", 823 Description: "description", 824 Developer: "bar", 825 Publisher: &snap.StoreAccount{ 826 ID: "bar-id", 827 Username: "bar", 828 DisplayName: "Bar", 829 Validation: "unproven", 830 }, 831 Status: "active", 832 Health: &client.SnapHealth{Status: "okay"}, 833 Icon: "/v2/icons/foo/icon", 834 Type: string(snap.TypeApp), 835 Base: "base18", 836 Private: false, 837 DevMode: false, 838 JailMode: false, 839 Confinement: string(snap.StrictConfinement), 840 TryMode: false, 841 MountedFrom: filepath.Join(dirs.SnapBlobDir, "foo_10.snap"), 842 Apps: []client.AppInfo{ 843 { 844 Snap: "foo", Name: "cmd", 845 DesktopFile: df, 846 }, { 847 // no desktop file 848 Snap: "foo", Name: "cmd2", 849 }, { 850 // has AppStream ID 851 Snap: "foo", Name: "cmd3", 852 CommonID: "org.foo.cmd", 853 }, { 854 // services 855 Snap: "foo", Name: "svc1", 856 Daemon: "simple", 857 DaemonScope: snap.SystemDaemon, 858 Enabled: true, 859 Active: false, 860 }, { 861 Snap: "foo", Name: "svc2", 862 Daemon: "forking", 863 DaemonScope: snap.SystemDaemon, 864 Enabled: false, 865 Active: true, 866 }, { 867 Snap: "foo", Name: "svc3", 868 Daemon: "oneshot", 869 DaemonScope: snap.SystemDaemon, 870 Enabled: true, 871 Active: true, 872 }, { 873 Snap: "foo", Name: "svc4", 874 Daemon: "notify", 875 DaemonScope: snap.SystemDaemon, 876 Enabled: false, 877 Active: false, 878 }, { 879 Snap: "foo", Name: "svc5", 880 Daemon: "simple", 881 DaemonScope: snap.SystemDaemon, 882 Enabled: true, 883 Active: false, 884 Activators: []client.AppActivator{ 885 {Name: "svc5", Type: "timer", Active: true, Enabled: true}, 886 }, 887 }, { 888 Snap: "foo", Name: "svc6", 889 Daemon: "simple", 890 DaemonScope: snap.SystemDaemon, 891 Enabled: true, 892 Active: false, 893 Activators: []client.AppActivator{ 894 {Name: "sock", Type: "socket", Active: true, Enabled: true}, 895 }, 896 }, { 897 Snap: "foo", Name: "svc7", 898 Daemon: "simple", 899 DaemonScope: snap.SystemDaemon, 900 Enabled: true, 901 Active: false, 902 Activators: []client.AppActivator{ 903 {Name: "other-sock", Type: "socket", Active: false, Enabled: true}, 904 }, 905 }, 906 }, 907 Broken: "", 908 Contact: "", 909 License: "GPL-3.0", 910 CommonIDs: []string{"org.foo.cmd"}, 911 CohortKey: "some-long-cohort-key", 912 }, 913 } 914 915 c.Check(rsp.Result, check.DeepEquals, expected.Result) 916 } 917 918 func (s *snapsSuite) TestSnapInfoNotFound(c *check.C) { 919 s.daemon(c) 920 921 req, err := http.NewRequest("GET", "/v2/snaps/gfoo", nil) 922 c.Assert(err, check.IsNil) 923 c.Check(s.errorReq(c, req, nil).Status, check.Equals, 404) 924 } 925 926 func (s *snapsSuite) TestSnapInfoNoneFound(c *check.C) { 927 s.daemon(c) 928 929 req, err := http.NewRequest("GET", "/v2/snaps/gfoo", nil) 930 c.Assert(err, check.IsNil) 931 c.Check(s.errorReq(c, req, nil).Status, check.Equals, 404) 932 } 933 934 func (s *snapsSuite) TestSnapInfoIgnoresRemoteErrors(c *check.C) { 935 s.daemon(c) 936 s.err = errors.New("weird") 937 938 req, err := http.NewRequest("GET", "/v2/snaps/gfoo", nil) 939 c.Assert(err, check.IsNil) 940 rspe := s.errorReq(c, req, nil) 941 c.Check(rspe.Status, check.Equals, 404) 942 c.Check(rspe.Message, check.Not(check.Equals), "") 943 } 944 945 func (s *snapsSuite) TestMapLocalFields(c *check.C) { 946 media := snap.MediaInfos{ 947 { 948 Type: "screenshot", 949 URL: "https://example.com/shot1.svg", 950 }, { 951 Type: "icon", 952 URL: "https://example.com/icon.png", 953 }, { 954 Type: "screenshot", 955 URL: "https://example.com/shot2.svg", 956 }, 957 } 958 959 publisher := snap.StoreAccount{ 960 ID: "some-dev-id", 961 Username: "some-dev", 962 DisplayName: "Some Developer", 963 Validation: "poor", 964 } 965 info := &snap.Info{ 966 SideInfo: snap.SideInfo{ 967 SnapID: "some-snap-id", 968 RealName: "some-snap", 969 EditedTitle: "A Title", 970 EditedSummary: "a summary", 971 EditedDescription: "the\nlong\ndescription", 972 Channel: "bleeding/edge", 973 EditedContact: "alice@example.com", 974 Revision: snap.R(7), 975 Private: true, 976 }, 977 InstanceKey: "instance", 978 SnapType: "app", 979 Base: "the-base", 980 Version: "v1.0", 981 License: "MIT", 982 Broken: "very", 983 Confinement: "very strict", 984 CommonIDs: []string{"foo", "bar"}, 985 Media: media, 986 DownloadInfo: snap.DownloadInfo{ 987 Size: 42, 988 Sha3_384: "some-sum", 989 }, 990 Publisher: publisher, 991 } 992 993 // make InstallDate work 994 c.Assert(os.MkdirAll(info.MountDir(), 0755), check.IsNil) 995 c.Assert(os.Symlink("7", filepath.Join(info.MountDir(), "..", "current")), check.IsNil) 996 997 info.Apps = map[string]*snap.AppInfo{ 998 "foo": {Snap: info, Name: "foo", Command: "foo"}, 999 "bar": {Snap: info, Name: "bar", Command: "bar"}, 1000 } 1001 about := daemon.MakeAboutSnap(info, &snapstate.SnapState{ 1002 Active: true, 1003 TrackingChannel: "flaky/beta", 1004 Current: snap.R(7), 1005 Flags: snapstate.Flags{ 1006 IgnoreValidation: true, 1007 DevMode: true, 1008 JailMode: true, 1009 }, 1010 }, 1011 ) 1012 1013 expected := &client.Snap{ 1014 ID: "some-snap-id", 1015 Name: "some-snap_instance", 1016 Summary: "a summary", 1017 Description: "the\nlong\ndescription", 1018 Developer: "some-dev", 1019 Publisher: &publisher, 1020 Icon: "https://example.com/icon.png", 1021 Type: "app", 1022 Base: "the-base", 1023 Version: "v1.0", 1024 Revision: snap.R(7), 1025 Channel: "bleeding/edge", 1026 TrackingChannel: "flaky/beta", 1027 InstallDate: info.InstallDate(), 1028 InstalledSize: 42, 1029 Status: "active", 1030 Confinement: "very strict", 1031 IgnoreValidation: true, 1032 DevMode: true, 1033 JailMode: true, 1034 Private: true, 1035 Broken: "very", 1036 Contact: "alice@example.com", 1037 Title: "A Title", 1038 License: "MIT", 1039 CommonIDs: []string{"foo", "bar"}, 1040 MountedFrom: filepath.Join(dirs.SnapBlobDir, "some-snap_instance_7.snap"), 1041 Media: media, 1042 Apps: []client.AppInfo{ 1043 {Snap: "some-snap_instance", Name: "bar"}, 1044 {Snap: "some-snap_instance", Name: "foo"}, 1045 }, 1046 } 1047 c.Check(daemon.MapLocal(about, nil), check.DeepEquals, expected) 1048 } 1049 1050 func (s *snapsSuite) TestMapLocalOfTryResolvesSymlink(c *check.C) { 1051 c.Assert(os.MkdirAll(dirs.SnapBlobDir, 0755), check.IsNil) 1052 1053 info := snap.Info{SideInfo: snap.SideInfo{RealName: "hello", Revision: snap.R(1)}} 1054 snapst := snapstate.SnapState{} 1055 mountFile := info.MountFile() 1056 about := daemon.MakeAboutSnap(&info, &snapst) 1057 1058 // if not a 'try', then MountedFrom is just MountFile() 1059 c.Check(daemon.MapLocal(about, nil).MountedFrom, check.Equals, mountFile) 1060 1061 // if it's a try, then MountedFrom resolves the symlink 1062 // (note it doesn't matter, here, whether the target of the link exists) 1063 snapst.TryMode = true 1064 c.Assert(os.Symlink("/xyzzy", mountFile), check.IsNil) 1065 c.Check(daemon.MapLocal(about, nil).MountedFrom, check.Equals, "/xyzzy") 1066 1067 // if the readlink fails, it's unset 1068 c.Assert(os.Remove(mountFile), check.IsNil) 1069 c.Check(daemon.MapLocal(about, nil).MountedFrom, check.Equals, "") 1070 } 1071 1072 func (s *snapsSuite) TestPostSnapBadRequest(c *check.C) { 1073 s.daemon(c) 1074 1075 buf := bytes.NewBufferString(`hello`) 1076 req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf) 1077 c.Assert(err, check.IsNil) 1078 1079 rspe := s.errorReq(c, req, nil) 1080 c.Check(rspe.Status, check.Equals, 400) 1081 c.Check(rspe.Message, check.Not(check.Equals), "") 1082 } 1083 1084 func (s *snapsSuite) TestPostSnapBadAction(c *check.C) { 1085 s.daemon(c) 1086 1087 buf := bytes.NewBufferString(`{"action": "potato"}`) 1088 req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf) 1089 c.Assert(err, check.IsNil) 1090 1091 rspe := s.errorReq(c, req, nil) 1092 c.Check(rspe.Status, check.Equals, 400) 1093 c.Check(rspe.Message, check.Not(check.Equals), "") 1094 } 1095 1096 func (s *snapsSuite) TestPostSnapBadChannel(c *check.C) { 1097 s.daemon(c) 1098 1099 buf := bytes.NewBufferString(`{"channel": "1/2/3/4"}`) 1100 req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf) 1101 c.Assert(err, check.IsNil) 1102 1103 rspe := s.errorReq(c, req, nil) 1104 c.Check(rspe.Status, check.Equals, 400) 1105 c.Check(rspe.Message, check.Not(check.Equals), "") 1106 } 1107 1108 func (s *snapsSuite) TestPostSnap(c *check.C) { 1109 s.testPostSnap(c, false) 1110 } 1111 1112 func (s *snapsSuite) TestPostSnapWithChannel(c *check.C) { 1113 s.testPostSnap(c, true) 1114 } 1115 1116 func (s *snapsSuite) testPostSnap(c *check.C, withChannel bool) { 1117 d := s.daemonWithOverlordMock(c) 1118 1119 soon := 0 1120 var origEnsureStateSoon func(*state.State) 1121 origEnsureStateSoon, restore := daemon.MockEnsureStateSoon(func(st *state.State) { 1122 soon++ 1123 origEnsureStateSoon(st) 1124 }) 1125 defer restore() 1126 1127 checked := false 1128 defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1129 if withChannel { 1130 // channel in -> channel out 1131 c.Check(opts.Channel, check.Equals, "xyzzy") 1132 } else { 1133 // no channel in -> no channel out 1134 c.Check(opts.Channel, check.Equals, "") 1135 } 1136 checked = true 1137 1138 t := s.NewTask("fake-install-snap", "Doing a fake install") 1139 return state.NewTaskSet(t), nil 1140 })() 1141 1142 var buf *bytes.Buffer 1143 if withChannel { 1144 buf = bytes.NewBufferString(`{"action": "install", "channel": "xyzzy"}`) 1145 } else { 1146 buf = bytes.NewBufferString(`{"action": "install"}`) 1147 } 1148 req, err := http.NewRequest("POST", "/v2/snaps/foo", buf) 1149 c.Assert(err, check.IsNil) 1150 1151 rsp := s.asyncReq(c, req, nil) 1152 1153 st := d.Overlord().State() 1154 st.Lock() 1155 defer st.Unlock() 1156 chg := st.Change(rsp.Change) 1157 c.Assert(chg, check.NotNil) 1158 if withChannel { 1159 c.Check(chg.Summary(), check.Equals, `Install "foo" snap from "xyzzy" channel`) 1160 } else { 1161 c.Check(chg.Summary(), check.Equals, `Install "foo" snap`) 1162 } 1163 var names []string 1164 err = chg.Get("snap-names", &names) 1165 c.Assert(err, check.IsNil) 1166 c.Check(names, check.DeepEquals, []string{"foo"}) 1167 1168 c.Check(checked, check.Equals, true) 1169 c.Check(soon, check.Equals, 1) 1170 c.Check(chg.Tasks()[0].Summary(), check.Equals, "Doing a fake install") 1171 } 1172 1173 func (s *snapsSuite) TestPostSnapVerifySnapInstruction(c *check.C) { 1174 s.daemonWithOverlordMock(c) 1175 1176 buf := bytes.NewBufferString(`{"action": "install"}`) 1177 req, err := http.NewRequest("POST", "/v2/snaps/ubuntu-core", buf) 1178 c.Assert(err, check.IsNil) 1179 1180 rspe := s.errorReq(c, req, nil) 1181 c.Check(rspe.Status, check.Equals, 400) 1182 c.Check(rspe.Message, testutil.Contains, `cannot install "ubuntu-core", please use "core" instead`) 1183 } 1184 1185 func (s *snapsSuite) TestPostSnapCohortUnsupportedAction(c *check.C) { 1186 s.daemonWithOverlordMock(c) 1187 const expectedErr = "cohort-key can only be specified for install, refresh, or switch" 1188 1189 for _, action := range []string{"remove", "revert", "enable", "disable", "xyzzy"} { 1190 buf := strings.NewReader(fmt.Sprintf(`{"action": "%s", "cohort-key": "32"}`, action)) 1191 req, err := http.NewRequest("POST", "/v2/snaps/some-snap", buf) 1192 c.Assert(err, check.IsNil) 1193 1194 rspe := s.errorReq(c, req, nil) 1195 c.Check(rspe.Status, check.Equals, 400, check.Commentf("%q", action)) 1196 c.Check(rspe.Message, check.Equals, expectedErr, check.Commentf("%q", action)) 1197 } 1198 } 1199 1200 func (s *snapsSuite) TestPostSnapLeaveCohortUnsupportedAction(c *check.C) { 1201 s.daemonWithOverlordMock(c) 1202 const expectedErr = "leave-cohort can only be specified for refresh or switch" 1203 1204 for _, action := range []string{"install", "remove", "revert", "enable", "disable", "xyzzy"} { 1205 buf := strings.NewReader(fmt.Sprintf(`{"action": "%s", "leave-cohort": true}`, action)) 1206 req, err := http.NewRequest("POST", "/v2/snaps/some-snap", buf) 1207 c.Assert(err, check.IsNil) 1208 1209 rspe := s.errorReq(c, req, nil) 1210 c.Check(rspe.Status, check.Equals, 400, check.Commentf("%q", action)) 1211 c.Check(rspe.Message, check.Equals, expectedErr, check.Commentf("%q", action)) 1212 } 1213 } 1214 1215 func (s *snapsSuite) TestPostSnapCohortIncompat(c *check.C) { 1216 s.daemonWithOverlordMock(c) 1217 type T struct { 1218 opts string 1219 errmsg string 1220 } 1221 1222 for i, t := range []T{ 1223 // TODO: more? 1224 {`"cohort-key": "what", "revision": "42"`, `cannot specify both cohort-key and revision`}, 1225 {`"cohort-key": "what", "leave-cohort": true`, `cannot specify both cohort-key and leave-cohort`}, 1226 } { 1227 buf := strings.NewReader(fmt.Sprintf(`{"action": "refresh", %s}`, t.opts)) 1228 req, err := http.NewRequest("POST", "/v2/snaps/some-snap", buf) 1229 c.Assert(err, check.IsNil, check.Commentf("%d (%s)", i, t.opts)) 1230 1231 rspe := s.errorReq(c, req, nil) 1232 c.Check(rspe.Status, check.Equals, 400, check.Commentf("%d (%s)", i, t.opts)) 1233 c.Check(rspe.Message, check.Equals, t.errmsg, check.Commentf("%d (%s)", i, t.opts)) 1234 } 1235 } 1236 1237 func (s *snapsSuite) TestPostSnapSetsUser(c *check.C) { 1238 d := s.daemon(c) 1239 1240 _, restore := daemon.MockEnsureStateSoon(func(st *state.State) {}) 1241 defer restore() 1242 1243 checked := false 1244 defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1245 c.Check(userID, check.Equals, 1) 1246 checked = true 1247 t := s.NewTask("fake-install-snap", "Doing a fake install") 1248 return state.NewTaskSet(t), nil 1249 })() 1250 1251 state := d.Overlord().State() 1252 state.Lock() 1253 user, err := auth.NewUser(state, "username", "email@test.com", "macaroon", []string{"discharge"}) 1254 state.Unlock() 1255 c.Check(err, check.IsNil) 1256 1257 buf := bytes.NewBufferString(`{"action": "install"}`) 1258 req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf) 1259 c.Assert(err, check.IsNil) 1260 1261 rsp := s.asyncReq(c, req, user) 1262 1263 st := d.Overlord().State() 1264 st.Lock() 1265 defer st.Unlock() 1266 chg := st.Change(rsp.Change) 1267 c.Assert(chg, check.NotNil) 1268 c.Check(checked, check.Equals, true) 1269 } 1270 1271 func (s *snapsSuite) TestPostSnapEnableDisableSwitchRevision(c *check.C) { 1272 s.daemon(c) 1273 1274 for _, action := range []string{"enable", "disable", "switch"} { 1275 buf := bytes.NewBufferString(`{"action": "` + action + `", "revision": "42"}`) 1276 req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf) 1277 c.Assert(err, check.IsNil) 1278 1279 rspe := s.errorReq(c, req, nil) 1280 c.Check(rspe.Status, check.Equals, 400) 1281 c.Check(rspe.Message, testutil.Contains, "takes no revision") 1282 } 1283 } 1284 1285 func (s *snapsSuite) TestInstall(c *check.C) { 1286 var calledName string 1287 1288 defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1289 calledName = name 1290 1291 t := s.NewTask("fake-install-snap", "Doing a fake install") 1292 return state.NewTaskSet(t), nil 1293 })() 1294 1295 d := s.daemon(c) 1296 inst := &daemon.SnapInstruction{ 1297 Action: "install", 1298 // Install the snap in developer mode 1299 DevMode: true, 1300 Snaps: []string{"fake"}, 1301 } 1302 1303 st := d.Overlord().State() 1304 st.Lock() 1305 defer st.Unlock() 1306 _, _, err := inst.Dispatch()(inst, st) 1307 c.Check(err, check.IsNil) 1308 c.Check(calledName, check.Equals, "fake") 1309 } 1310 1311 func (s *snapsSuite) TestInstallDevMode(c *check.C) { 1312 var calledFlags snapstate.Flags 1313 1314 defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1315 calledFlags = flags 1316 1317 t := s.NewTask("fake-install-snap", "Doing a fake install") 1318 return state.NewTaskSet(t), nil 1319 })() 1320 1321 d := s.daemon(c) 1322 inst := &daemon.SnapInstruction{ 1323 Action: "install", 1324 // Install the snap in developer mode 1325 DevMode: true, 1326 Snaps: []string{"fake"}, 1327 } 1328 1329 st := d.Overlord().State() 1330 st.Lock() 1331 defer st.Unlock() 1332 _, _, err := inst.Dispatch()(inst, st) 1333 c.Check(err, check.IsNil) 1334 1335 c.Check(calledFlags.DevMode, check.Equals, true) 1336 } 1337 1338 func (s *snapsSuite) TestInstallJailMode(c *check.C) { 1339 var calledFlags snapstate.Flags 1340 1341 defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1342 calledFlags = flags 1343 1344 t := s.NewTask("fake-install-snap", "Doing a fake install") 1345 return state.NewTaskSet(t), nil 1346 })() 1347 1348 d := s.daemon(c) 1349 inst := &daemon.SnapInstruction{ 1350 Action: "install", 1351 JailMode: true, 1352 Snaps: []string{"fake"}, 1353 } 1354 1355 st := d.Overlord().State() 1356 st.Lock() 1357 defer st.Unlock() 1358 _, _, err := inst.Dispatch()(inst, st) 1359 c.Check(err, check.IsNil) 1360 1361 c.Check(calledFlags.JailMode, check.Equals, true) 1362 } 1363 1364 func (s *snapsSuite) TestInstallJailModeDevModeOS(c *check.C) { 1365 restore := sandbox.MockForceDevMode(true) 1366 defer restore() 1367 1368 d := s.daemon(c) 1369 inst := &daemon.SnapInstruction{ 1370 Action: "install", 1371 JailMode: true, 1372 Snaps: []string{"foo"}, 1373 } 1374 1375 st := d.Overlord().State() 1376 st.Lock() 1377 defer st.Unlock() 1378 _, _, err := inst.Dispatch()(inst, st) 1379 c.Check(err, check.ErrorMatches, "this system cannot honour the jailmode flag") 1380 } 1381 1382 func (s *snapsSuite) TestInstallJailModeDevMode(c *check.C) { 1383 d := s.daemon(c) 1384 inst := &daemon.SnapInstruction{ 1385 Action: "install", 1386 DevMode: true, 1387 JailMode: true, 1388 Snaps: []string{"foo"}, 1389 } 1390 1391 st := d.Overlord().State() 1392 st.Lock() 1393 defer st.Unlock() 1394 _, _, err := inst.Dispatch()(inst, st) 1395 c.Check(err, check.ErrorMatches, "cannot use devmode and jailmode flags together") 1396 } 1397 1398 func (s *snapsSuite) TestInstallCohort(c *check.C) { 1399 var calledName string 1400 var calledCohort string 1401 1402 defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1403 calledName = name 1404 calledCohort = opts.CohortKey 1405 1406 t := s.NewTask("fake-install-snap", "Doing a fake install") 1407 return state.NewTaskSet(t), nil 1408 })() 1409 1410 d := s.daemon(c) 1411 inst := &daemon.SnapInstruction{ 1412 Action: "install", 1413 Snaps: []string{"fake"}, 1414 } 1415 inst.CohortKey = "To the legion of the lost ones, to the cohort of the damned." 1416 1417 st := d.Overlord().State() 1418 st.Lock() 1419 defer st.Unlock() 1420 msg, _, err := inst.Dispatch()(inst, st) 1421 c.Check(err, check.IsNil) 1422 c.Check(calledName, check.Equals, "fake") 1423 c.Check(calledCohort, check.Equals, "To the legion of the lost ones, to the cohort of the damned.") 1424 c.Check(msg, check.Equals, `Install "fake" snap from "…e damned." cohort`) 1425 } 1426 1427 func (s *snapsSuite) TestInstallEmptyName(c *check.C) { 1428 defer daemon.MockSnapstateInstall(func(ctx context.Context, _ *state.State, _ string, _ *snapstate.RevisionOptions, _ int, _ snapstate.Flags) (*state.TaskSet, error) { 1429 return nil, errors.New("should not be called") 1430 })() 1431 1432 d := s.daemon(c) 1433 inst := &daemon.SnapInstruction{ 1434 Action: "install", 1435 Snaps: []string{""}, 1436 } 1437 1438 st := d.Overlord().State() 1439 st.Lock() 1440 defer st.Unlock() 1441 _, _, err := inst.Dispatch()(inst, st) 1442 c.Check(err, check.ErrorMatches, "cannot install snap with empty name") 1443 } 1444 1445 func (s *snapsSuite) TestInstallOnNonDevModeDistro(c *check.C) { 1446 s.testInstall(c, false, snapstate.Flags{}, snap.R(0)) 1447 } 1448 func (s *snapsSuite) TestInstallOnDevModeDistro(c *check.C) { 1449 s.testInstall(c, true, snapstate.Flags{}, snap.R(0)) 1450 } 1451 func (s *snapsSuite) TestInstallRevision(c *check.C) { 1452 s.testInstall(c, false, snapstate.Flags{}, snap.R(42)) 1453 } 1454 1455 func (s *snapsSuite) testInstall(c *check.C, forcedDevmode bool, flags snapstate.Flags, revision snap.Revision) { 1456 calledFlags := snapstate.Flags{} 1457 installQueue := []string{} 1458 restore := sandbox.MockForceDevMode(forcedDevmode) 1459 defer restore() 1460 1461 defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1462 calledFlags = flags 1463 installQueue = append(installQueue, name) 1464 c.Check(revision, check.Equals, opts.Revision) 1465 1466 t := s.NewTask("fake-install-snap", "Doing a fake install") 1467 return state.NewTaskSet(t), nil 1468 })() 1469 1470 d := s.daemonWithFakeSnapManager(c) 1471 1472 var buf bytes.Buffer 1473 if revision.Unset() { 1474 buf.WriteString(`{"action": "install"}`) 1475 } else { 1476 fmt.Fprintf(&buf, `{"action": "install", "revision": %s}`, revision.String()) 1477 } 1478 req, err := http.NewRequest("POST", "/v2/snaps/some-snap", &buf) 1479 c.Assert(err, check.IsNil) 1480 1481 rsp := s.asyncReq(c, req, nil) 1482 1483 st := d.Overlord().State() 1484 st.Lock() 1485 defer st.Unlock() 1486 chg := st.Change(rsp.Change) 1487 c.Assert(chg, check.NotNil) 1488 1489 c.Check(chg.Tasks(), check.HasLen, 1) 1490 1491 st.Unlock() 1492 s.waitTrivialChange(c, chg) 1493 st.Lock() 1494 1495 c.Check(chg.Status(), check.Equals, state.DoneStatus) 1496 c.Check(calledFlags, check.Equals, flags) 1497 c.Check(err, check.IsNil) 1498 c.Check(installQueue, check.DeepEquals, []string{"some-snap"}) 1499 c.Check(chg.Kind(), check.Equals, "install-snap") 1500 c.Check(chg.Summary(), check.Equals, `Install "some-snap" snap`) 1501 } 1502 1503 func (s *snapsSuite) TestInstallUserAgentContextCreated(c *check.C) { 1504 defer daemon.MockSnapstateInstall(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1505 s.ctx = ctx 1506 t := st.NewTask("fake-install-snap", "Doing a fake install") 1507 return state.NewTaskSet(t), nil 1508 })() 1509 1510 s.daemonWithFakeSnapManager(c) 1511 1512 var buf bytes.Buffer 1513 buf.WriteString(`{"action": "install"}`) 1514 req, err := http.NewRequest("POST", "/v2/snaps/some-snap", &buf) 1515 s.asRootAuth(req) 1516 c.Assert(err, check.IsNil) 1517 req.Header.Add("User-Agent", "some-agent/1.0") 1518 1519 rec := httptest.NewRecorder() 1520 s.serveHTTP(c, rec, req) 1521 c.Assert(rec.Code, check.Equals, 202) 1522 c.Check(store.ClientUserAgent(s.ctx), check.Equals, "some-agent/1.0") 1523 } 1524 1525 func (s *snapsSuite) TestInstallFails(c *check.C) { 1526 defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1527 t := s.NewTask("fake-install-snap-error", "Install task") 1528 return state.NewTaskSet(t), nil 1529 })() 1530 1531 d := s.daemonWithFakeSnapManager(c) 1532 buf := bytes.NewBufferString(`{"action": "install"}`) 1533 req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf) 1534 c.Assert(err, check.IsNil) 1535 1536 rsp := s.asyncReq(c, req, nil) 1537 1538 st := d.Overlord().State() 1539 st.Lock() 1540 defer st.Unlock() 1541 chg := st.Change(rsp.Change) 1542 c.Assert(chg, check.NotNil) 1543 1544 c.Check(chg.Tasks(), check.HasLen, 1) 1545 1546 st.Unlock() 1547 s.waitTrivialChange(c, chg) 1548 st.Lock() 1549 1550 c.Check(chg.Err(), check.ErrorMatches, `(?sm).*Install task \(fake-install-snap-error errored\)`) 1551 } 1552 1553 func (s *snapsSuite) TestRefresh(c *check.C) { 1554 var calledFlags snapstate.Flags 1555 calledUserID := 0 1556 installQueue := []string{} 1557 assertstateCalledUserID := 0 1558 1559 defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1560 calledFlags = flags 1561 calledUserID = userID 1562 installQueue = append(installQueue, name) 1563 1564 t := s.NewTask("fake-refresh-snap", "Doing a fake install") 1565 return state.NewTaskSet(t), nil 1566 })() 1567 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 1568 assertstateCalledUserID = userID 1569 return nil 1570 })() 1571 1572 d := s.daemon(c) 1573 inst := &daemon.SnapInstruction{ 1574 Action: "refresh", 1575 Snaps: []string{"some-snap"}, 1576 } 1577 inst.SetUserID(17) 1578 1579 st := d.Overlord().State() 1580 st.Lock() 1581 defer st.Unlock() 1582 summary, _, err := inst.Dispatch()(inst, st) 1583 c.Check(err, check.IsNil) 1584 1585 c.Check(assertstateCalledUserID, check.Equals, 17) 1586 c.Check(calledFlags, check.DeepEquals, snapstate.Flags{}) 1587 c.Check(calledUserID, check.Equals, 17) 1588 c.Check(err, check.IsNil) 1589 c.Check(installQueue, check.DeepEquals, []string{"some-snap"}) 1590 c.Check(summary, check.Equals, `Refresh "some-snap" snap`) 1591 } 1592 1593 func (s *snapsSuite) TestRefreshDevMode(c *check.C) { 1594 var calledFlags snapstate.Flags 1595 calledUserID := 0 1596 installQueue := []string{} 1597 1598 defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1599 calledFlags = flags 1600 calledUserID = userID 1601 installQueue = append(installQueue, name) 1602 1603 t := s.NewTask("fake-refresh-snap", "Doing a fake install") 1604 return state.NewTaskSet(t), nil 1605 })() 1606 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 1607 return nil 1608 })() 1609 1610 d := s.daemon(c) 1611 inst := &daemon.SnapInstruction{ 1612 Action: "refresh", 1613 DevMode: true, 1614 Snaps: []string{"some-snap"}, 1615 } 1616 inst.SetUserID(17) 1617 1618 st := d.Overlord().State() 1619 st.Lock() 1620 defer st.Unlock() 1621 summary, _, err := inst.Dispatch()(inst, st) 1622 c.Check(err, check.IsNil) 1623 1624 flags := snapstate.Flags{} 1625 flags.DevMode = true 1626 c.Check(calledFlags, check.DeepEquals, flags) 1627 c.Check(calledUserID, check.Equals, 17) 1628 c.Check(err, check.IsNil) 1629 c.Check(installQueue, check.DeepEquals, []string{"some-snap"}) 1630 c.Check(summary, check.Equals, `Refresh "some-snap" snap`) 1631 } 1632 1633 func (s *snapsSuite) TestRefreshClassic(c *check.C) { 1634 var calledFlags snapstate.Flags 1635 1636 defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1637 calledFlags = flags 1638 return nil, nil 1639 })() 1640 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 1641 return nil 1642 })() 1643 1644 d := s.daemon(c) 1645 inst := &daemon.SnapInstruction{ 1646 Action: "refresh", 1647 Classic: true, 1648 Snaps: []string{"some-snap"}, 1649 } 1650 inst.SetUserID(17) 1651 1652 st := d.Overlord().State() 1653 st.Lock() 1654 defer st.Unlock() 1655 _, _, err := inst.Dispatch()(inst, st) 1656 c.Check(err, check.IsNil) 1657 1658 c.Check(calledFlags, check.DeepEquals, snapstate.Flags{Classic: true}) 1659 } 1660 1661 func (s *snapsSuite) TestRefreshIgnoreValidation(c *check.C) { 1662 var calledFlags snapstate.Flags 1663 calledUserID := 0 1664 installQueue := []string{} 1665 1666 defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1667 calledFlags = flags 1668 calledUserID = userID 1669 installQueue = append(installQueue, name) 1670 1671 t := s.NewTask("fake-refresh-snap", "Doing a fake install") 1672 return state.NewTaskSet(t), nil 1673 })() 1674 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 1675 return nil 1676 })() 1677 1678 d := s.daemon(c) 1679 inst := &daemon.SnapInstruction{ 1680 Action: "refresh", 1681 IgnoreValidation: true, 1682 Snaps: []string{"some-snap"}, 1683 } 1684 inst.SetUserID(17) 1685 1686 st := d.Overlord().State() 1687 st.Lock() 1688 defer st.Unlock() 1689 summary, _, err := inst.Dispatch()(inst, st) 1690 c.Check(err, check.IsNil) 1691 1692 flags := snapstate.Flags{} 1693 flags.IgnoreValidation = true 1694 1695 c.Check(calledFlags, check.DeepEquals, flags) 1696 c.Check(calledUserID, check.Equals, 17) 1697 c.Check(err, check.IsNil) 1698 c.Check(installQueue, check.DeepEquals, []string{"some-snap"}) 1699 c.Check(summary, check.Equals, `Refresh "some-snap" snap`) 1700 } 1701 1702 func (s *snapsSuite) TestRefreshIgnoreRunning(c *check.C) { 1703 var calledFlags snapstate.Flags 1704 installQueue := []string{} 1705 1706 defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1707 calledFlags = flags 1708 installQueue = append(installQueue, name) 1709 1710 t := s.NewTask("fake-refresh-snap", "Doing a fake install") 1711 return state.NewTaskSet(t), nil 1712 })() 1713 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 1714 return nil 1715 })() 1716 1717 d := s.daemon(c) 1718 inst := &daemon.SnapInstruction{ 1719 Action: "refresh", 1720 IgnoreRunning: true, 1721 Snaps: []string{"some-snap"}, 1722 } 1723 1724 st := d.Overlord().State() 1725 st.Lock() 1726 defer st.Unlock() 1727 summary, _, err := inst.Dispatch()(inst, st) 1728 c.Check(err, check.IsNil) 1729 1730 flags := snapstate.Flags{} 1731 flags.IgnoreRunning = true 1732 1733 c.Check(calledFlags, check.DeepEquals, flags) 1734 c.Check(err, check.IsNil) 1735 c.Check(installQueue, check.DeepEquals, []string{"some-snap"}) 1736 c.Check(summary, check.Equals, `Refresh "some-snap" snap`) 1737 } 1738 1739 func (s *snapsSuite) TestRefreshCohort(c *check.C) { 1740 cohort := "" 1741 1742 defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1743 cohort = opts.CohortKey 1744 1745 t := s.NewTask("fake-refresh-snap", "Doing a fake install") 1746 return state.NewTaskSet(t), nil 1747 })() 1748 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 1749 return nil 1750 })() 1751 1752 d := s.daemon(c) 1753 inst := &daemon.SnapInstruction{ 1754 Action: "refresh", 1755 Snaps: []string{"some-snap"}, 1756 } 1757 inst.CohortKey = "xyzzy" 1758 1759 st := d.Overlord().State() 1760 st.Lock() 1761 defer st.Unlock() 1762 summary, _, err := inst.Dispatch()(inst, st) 1763 c.Check(err, check.IsNil) 1764 1765 c.Check(cohort, check.Equals, "xyzzy") 1766 c.Check(summary, check.Equals, `Refresh "some-snap" snap`) 1767 } 1768 1769 func (s *snapsSuite) TestRefreshLeaveCohort(c *check.C) { 1770 var leave *bool 1771 1772 defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1773 leave = &opts.LeaveCohort 1774 1775 t := s.NewTask("fake-refresh-snap", "Doing a fake install") 1776 return state.NewTaskSet(t), nil 1777 })() 1778 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 1779 return nil 1780 })() 1781 1782 d := s.daemon(c) 1783 inst := &daemon.SnapInstruction{ 1784 Action: "refresh", 1785 Snaps: []string{"some-snap"}, 1786 } 1787 inst.LeaveCohort = true 1788 1789 st := d.Overlord().State() 1790 st.Lock() 1791 defer st.Unlock() 1792 summary, _, err := inst.Dispatch()(inst, st) 1793 c.Check(err, check.IsNil) 1794 1795 c.Check(*leave, check.Equals, true) 1796 c.Check(summary, check.Equals, `Refresh "some-snap" snap`) 1797 } 1798 1799 func (s *snapsSuite) TestSwitchInstruction(c *check.C) { 1800 var cohort, channel string 1801 var leave *bool 1802 1803 defer daemon.MockSnapstateSwitch(func(s *state.State, name string, opts *snapstate.RevisionOptions) (*state.TaskSet, error) { 1804 cohort = opts.CohortKey 1805 leave = &opts.LeaveCohort 1806 channel = opts.Channel 1807 1808 t := s.NewTask("fake-switch", "Doing a fake switch") 1809 return state.NewTaskSet(t), nil 1810 })() 1811 1812 d := s.daemon(c) 1813 st := d.Overlord().State() 1814 1815 type T struct { 1816 channel string 1817 cohort string 1818 leave bool 1819 summary string 1820 } 1821 table := []T{ 1822 {"", "some-cohort", false, `Switch "some-snap" snap to cohort "…me-cohort"`}, 1823 {"some-channel", "", false, `Switch "some-snap" snap to channel "some-channel"`}, 1824 {"some-channel", "some-cohort", false, `Switch "some-snap" snap to channel "some-channel" and cohort "…me-cohort"`}, 1825 {"", "", true, `Switch "some-snap" snap away from cohort`}, 1826 {"some-channel", "", true, `Switch "some-snap" snap to channel "some-channel" and away from cohort`}, 1827 } 1828 1829 for _, t := range table { 1830 cohort, channel = "", "" 1831 leave = nil 1832 inst := &daemon.SnapInstruction{ 1833 Action: "switch", 1834 Snaps: []string{"some-snap"}, 1835 } 1836 inst.CohortKey = t.cohort 1837 inst.LeaveCohort = t.leave 1838 inst.Channel = t.channel 1839 1840 st.Lock() 1841 summary, _, err := inst.Dispatch()(inst, st) 1842 st.Unlock() 1843 c.Check(err, check.IsNil) 1844 1845 c.Check(cohort, check.Equals, t.cohort) 1846 c.Check(channel, check.Equals, t.channel) 1847 c.Check(summary, check.Equals, t.summary) 1848 c.Check(*leave, check.Equals, t.leave) 1849 } 1850 } 1851 1852 func (s *snapsSuite) testRevertSnap(inst *daemon.SnapInstruction, c *check.C) { 1853 queue := []string{} 1854 1855 instFlags, err := inst.ModeFlags() 1856 c.Assert(err, check.IsNil) 1857 1858 defer daemon.MockSnapstateRevert(func(s *state.State, name string, flags snapstate.Flags) (*state.TaskSet, error) { 1859 c.Check(flags, check.Equals, instFlags) 1860 queue = append(queue, name) 1861 return nil, nil 1862 })() 1863 defer daemon.MockSnapstateRevertToRevision(func(s *state.State, name string, rev snap.Revision, flags snapstate.Flags) (*state.TaskSet, error) { 1864 c.Check(flags, check.Equals, instFlags) 1865 queue = append(queue, fmt.Sprintf("%s (%s)", name, rev)) 1866 return nil, nil 1867 })() 1868 1869 d := s.daemon(c) 1870 inst.Action = "revert" 1871 inst.Snaps = []string{"some-snap"} 1872 1873 st := d.Overlord().State() 1874 st.Lock() 1875 defer st.Unlock() 1876 summary, _, err := inst.Dispatch()(inst, st) 1877 c.Check(err, check.IsNil) 1878 if inst.Revision.Unset() { 1879 c.Check(queue, check.DeepEquals, []string{inst.Snaps[0]}) 1880 } else { 1881 c.Check(queue, check.DeepEquals, []string{fmt.Sprintf("%s (%s)", inst.Snaps[0], inst.Revision)}) 1882 } 1883 c.Check(summary, check.Equals, `Revert "some-snap" snap`) 1884 } 1885 1886 func (s *snapsSuite) TestRevertSnap(c *check.C) { 1887 s.testRevertSnap(&daemon.SnapInstruction{}, c) 1888 } 1889 1890 func (s *snapsSuite) TestRevertSnapDevMode(c *check.C) { 1891 s.testRevertSnap(&daemon.SnapInstruction{DevMode: true}, c) 1892 } 1893 1894 func (s *snapsSuite) TestRevertSnapJailMode(c *check.C) { 1895 s.testRevertSnap(&daemon.SnapInstruction{JailMode: true}, c) 1896 } 1897 1898 func (s *snapsSuite) TestRevertSnapClassic(c *check.C) { 1899 s.testRevertSnap(&daemon.SnapInstruction{Classic: true}, c) 1900 } 1901 1902 func (s *snapsSuite) TestRevertSnapToRevision(c *check.C) { 1903 inst := &daemon.SnapInstruction{} 1904 inst.Revision = snap.R(1) 1905 s.testRevertSnap(inst, c) 1906 } 1907 1908 func (s *snapsSuite) TestRevertSnapToRevisionDevMode(c *check.C) { 1909 inst := &daemon.SnapInstruction{} 1910 inst.Revision = snap.R(1) 1911 inst.DevMode = true 1912 s.testRevertSnap(inst, c) 1913 } 1914 1915 func (s *snapsSuite) TestRevertSnapToRevisionJailMode(c *check.C) { 1916 inst := &daemon.SnapInstruction{} 1917 inst.Revision = snap.R(1) 1918 inst.JailMode = true 1919 s.testRevertSnap(inst, c) 1920 } 1921 1922 func (s *snapsSuite) TestRevertSnapToRevisionClassic(c *check.C) { 1923 inst := &daemon.SnapInstruction{} 1924 inst.Revision = snap.R(1) 1925 inst.Classic = true 1926 s.testRevertSnap(inst, c) 1927 } 1928 1929 func (s *snapsSuite) TestErrToResponseNoSnapsDoesNotPanic(c *check.C) { 1930 si := &daemon.SnapInstruction{Action: "frobble"} 1931 errors := []error{ 1932 store.ErrSnapNotFound, 1933 &store.RevisionNotAvailableError{}, 1934 store.ErrNoUpdateAvailable, 1935 store.ErrLocalSnap, 1936 &snap.AlreadyInstalledError{Snap: "foo"}, 1937 &snap.NotInstalledError{Snap: "foo"}, 1938 &snapstate.SnapNeedsDevModeError{Snap: "foo"}, 1939 &snapstate.SnapNeedsClassicError{Snap: "foo"}, 1940 &snapstate.SnapNeedsClassicSystemError{Snap: "foo"}, 1941 fakeNetError{message: "other"}, 1942 fakeNetError{message: "timeout", timeout: true}, 1943 fakeNetError{message: "temp", temporary: true}, 1944 errors.New("some other error"), 1945 } 1946 1947 for _, err := range errors { 1948 rspe := si.ErrToResponse(err) 1949 com := check.Commentf("%v", err) 1950 c.Assert(rspe, check.NotNil, com) 1951 status := rspe.Status 1952 c.Check(status/100 == 4 || status/100 == 5, check.Equals, true, com) 1953 } 1954 } 1955 1956 func (s *snapsSuite) TestErrToResponseForRevisionNotAvailable(c *check.C) { 1957 si := &daemon.SnapInstruction{Action: "frobble", Snaps: []string{"foo"}} 1958 1959 thisArch := arch.DpkgArchitecture() 1960 1961 err := &store.RevisionNotAvailableError{ 1962 Action: "install", 1963 Channel: "stable", 1964 Releases: []channel.Channel{ 1965 snaptest.MustParseChannel("beta", thisArch), 1966 }, 1967 } 1968 rspe := si.ErrToResponse(err) 1969 c.Check(rspe, check.DeepEquals, &daemon.APIError{ 1970 Status: 404, 1971 Message: "no snap revision on specified channel", 1972 Kind: client.ErrorKindSnapChannelNotAvailable, 1973 Value: map[string]interface{}{ 1974 "snap-name": "foo", 1975 "action": "install", 1976 "channel": "stable", 1977 "architecture": thisArch, 1978 "releases": []map[string]interface{}{ 1979 {"architecture": thisArch, "channel": "beta"}, 1980 }, 1981 }, 1982 }) 1983 1984 err = &store.RevisionNotAvailableError{ 1985 Action: "install", 1986 Channel: "stable", 1987 Releases: []channel.Channel{ 1988 snaptest.MustParseChannel("beta", "other-arch"), 1989 }, 1990 } 1991 rspe = si.ErrToResponse(err) 1992 c.Check(rspe, check.DeepEquals, &daemon.APIError{ 1993 Status: 404, 1994 Message: "no snap revision on specified architecture", 1995 Kind: client.ErrorKindSnapArchitectureNotAvailable, 1996 Value: map[string]interface{}{ 1997 "snap-name": "foo", 1998 "action": "install", 1999 "channel": "stable", 2000 "architecture": thisArch, 2001 "releases": []map[string]interface{}{ 2002 {"architecture": "other-arch", "channel": "beta"}, 2003 }, 2004 }, 2005 }) 2006 2007 err = &store.RevisionNotAvailableError{} 2008 rspe = si.ErrToResponse(err) 2009 c.Check(rspe, check.DeepEquals, &daemon.APIError{ 2010 Status: 404, 2011 Message: "no snap revision available as specified", 2012 Kind: client.ErrorKindSnapRevisionNotAvailable, 2013 Value: "foo", 2014 }) 2015 } 2016 2017 func (s *snapsSuite) TestErrToResponseForChangeConflict(c *check.C) { 2018 si := &daemon.SnapInstruction{Action: "frobble", Snaps: []string{"foo"}} 2019 2020 err := &snapstate.ChangeConflictError{Snap: "foo", ChangeKind: "install"} 2021 rspe := si.ErrToResponse(err) 2022 c.Check(rspe, check.DeepEquals, &daemon.APIError{ 2023 Status: 409, 2024 Message: `snap "foo" has "install" change in progress`, 2025 Kind: client.ErrorKindSnapChangeConflict, 2026 Value: map[string]interface{}{ 2027 "snap-name": "foo", 2028 "change-kind": "install", 2029 }, 2030 }) 2031 2032 // only snap 2033 err = &snapstate.ChangeConflictError{Snap: "foo"} 2034 rspe = si.ErrToResponse(err) 2035 c.Check(rspe, check.DeepEquals, &daemon.APIError{ 2036 Status: 409, 2037 Message: `snap "foo" has changes in progress`, 2038 Kind: client.ErrorKindSnapChangeConflict, 2039 Value: map[string]interface{}{ 2040 "snap-name": "foo", 2041 }, 2042 }) 2043 2044 // only kind 2045 err = &snapstate.ChangeConflictError{Message: "specific error msg", ChangeKind: "some-global-op"} 2046 rspe = si.ErrToResponse(err) 2047 c.Check(rspe, check.DeepEquals, &daemon.APIError{ 2048 Status: 409, 2049 Message: "specific error msg", 2050 Kind: client.ErrorKindSnapChangeConflict, 2051 Value: map[string]interface{}{ 2052 "change-kind": "some-global-op", 2053 }, 2054 }) 2055 }