github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/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: "mailto: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: "mailto: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) TestInstallIgnoreValidation(c *check.C) { 1428 var calledFlags snapstate.Flags 1429 installQueue := []string{} 1430 1431 defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1432 installQueue = append(installQueue, name) 1433 calledFlags = flags 1434 t := s.NewTask("fake-install-snap", "Doing a fake install") 1435 return state.NewTaskSet(t), nil 1436 })() 1437 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 1438 return nil 1439 })() 1440 1441 d := s.daemon(c) 1442 inst := &daemon.SnapInstruction{ 1443 Action: "install", 1444 IgnoreValidation: true, 1445 Snaps: []string{"some-snap"}, 1446 } 1447 1448 st := d.Overlord().State() 1449 st.Lock() 1450 defer st.Unlock() 1451 summary, _, err := inst.Dispatch()(inst, st) 1452 c.Check(err, check.IsNil) 1453 1454 flags := snapstate.Flags{} 1455 flags.IgnoreValidation = true 1456 1457 c.Check(calledFlags, check.DeepEquals, flags) 1458 c.Check(err, check.IsNil) 1459 c.Check(installQueue, check.DeepEquals, []string{"some-snap"}) 1460 c.Check(summary, check.Equals, `Install "some-snap" snap`) 1461 } 1462 1463 func (s *snapsSuite) TestInstallEmptyName(c *check.C) { 1464 defer daemon.MockSnapstateInstall(func(ctx context.Context, _ *state.State, _ string, _ *snapstate.RevisionOptions, _ int, _ snapstate.Flags) (*state.TaskSet, error) { 1465 return nil, errors.New("should not be called") 1466 })() 1467 1468 d := s.daemon(c) 1469 inst := &daemon.SnapInstruction{ 1470 Action: "install", 1471 Snaps: []string{""}, 1472 } 1473 1474 st := d.Overlord().State() 1475 st.Lock() 1476 defer st.Unlock() 1477 _, _, err := inst.Dispatch()(inst, st) 1478 c.Check(err, check.ErrorMatches, "cannot install snap with empty name") 1479 } 1480 1481 func (s *snapsSuite) TestInstallOnNonDevModeDistro(c *check.C) { 1482 s.testInstall(c, false, snapstate.Flags{}, snap.R(0)) 1483 } 1484 func (s *snapsSuite) TestInstallOnDevModeDistro(c *check.C) { 1485 s.testInstall(c, true, snapstate.Flags{}, snap.R(0)) 1486 } 1487 func (s *snapsSuite) TestInstallRevision(c *check.C) { 1488 s.testInstall(c, false, snapstate.Flags{}, snap.R(42)) 1489 } 1490 1491 func (s *snapsSuite) testInstall(c *check.C, forcedDevmode bool, flags snapstate.Flags, revision snap.Revision) { 1492 calledFlags := snapstate.Flags{} 1493 installQueue := []string{} 1494 restore := sandbox.MockForceDevMode(forcedDevmode) 1495 defer restore() 1496 1497 defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1498 calledFlags = flags 1499 installQueue = append(installQueue, name) 1500 c.Check(revision, check.Equals, opts.Revision) 1501 1502 t := s.NewTask("fake-install-snap", "Doing a fake install") 1503 return state.NewTaskSet(t), nil 1504 })() 1505 1506 d := s.daemonWithFakeSnapManager(c) 1507 1508 var buf bytes.Buffer 1509 if revision.Unset() { 1510 buf.WriteString(`{"action": "install"}`) 1511 } else { 1512 fmt.Fprintf(&buf, `{"action": "install", "revision": %s}`, revision.String()) 1513 } 1514 req, err := http.NewRequest("POST", "/v2/snaps/some-snap", &buf) 1515 c.Assert(err, check.IsNil) 1516 1517 rsp := s.asyncReq(c, req, nil) 1518 1519 st := d.Overlord().State() 1520 st.Lock() 1521 defer st.Unlock() 1522 chg := st.Change(rsp.Change) 1523 c.Assert(chg, check.NotNil) 1524 1525 c.Check(chg.Tasks(), check.HasLen, 1) 1526 1527 st.Unlock() 1528 s.waitTrivialChange(c, chg) 1529 st.Lock() 1530 1531 c.Check(chg.Status(), check.Equals, state.DoneStatus) 1532 c.Check(calledFlags, check.Equals, flags) 1533 c.Check(err, check.IsNil) 1534 c.Check(installQueue, check.DeepEquals, []string{"some-snap"}) 1535 c.Check(chg.Kind(), check.Equals, "install-snap") 1536 c.Check(chg.Summary(), check.Equals, `Install "some-snap" snap`) 1537 } 1538 1539 func (s *snapsSuite) TestInstallUserAgentContextCreated(c *check.C) { 1540 defer daemon.MockSnapstateInstall(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1541 s.ctx = ctx 1542 t := st.NewTask("fake-install-snap", "Doing a fake install") 1543 return state.NewTaskSet(t), nil 1544 })() 1545 1546 s.daemonWithFakeSnapManager(c) 1547 1548 var buf bytes.Buffer 1549 buf.WriteString(`{"action": "install"}`) 1550 req, err := http.NewRequest("POST", "/v2/snaps/some-snap", &buf) 1551 s.asRootAuth(req) 1552 c.Assert(err, check.IsNil) 1553 req.Header.Add("User-Agent", "some-agent/1.0") 1554 1555 rec := httptest.NewRecorder() 1556 s.serveHTTP(c, rec, req) 1557 c.Assert(rec.Code, check.Equals, 202) 1558 c.Check(store.ClientUserAgent(s.ctx), check.Equals, "some-agent/1.0") 1559 } 1560 1561 func (s *snapsSuite) TestInstallFails(c *check.C) { 1562 defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1563 t := s.NewTask("fake-install-snap-error", "Install task") 1564 return state.NewTaskSet(t), nil 1565 })() 1566 1567 d := s.daemonWithFakeSnapManager(c) 1568 buf := bytes.NewBufferString(`{"action": "install"}`) 1569 req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf) 1570 c.Assert(err, check.IsNil) 1571 1572 rsp := s.asyncReq(c, req, nil) 1573 1574 st := d.Overlord().State() 1575 st.Lock() 1576 defer st.Unlock() 1577 chg := st.Change(rsp.Change) 1578 c.Assert(chg, check.NotNil) 1579 1580 c.Check(chg.Tasks(), check.HasLen, 1) 1581 1582 st.Unlock() 1583 s.waitTrivialChange(c, chg) 1584 st.Lock() 1585 1586 c.Check(chg.Err(), check.ErrorMatches, `(?sm).*Install task \(fake-install-snap-error errored\)`) 1587 } 1588 1589 func (s *snapsSuite) TestRefresh(c *check.C) { 1590 var calledFlags snapstate.Flags 1591 calledUserID := 0 1592 installQueue := []string{} 1593 assertstateCalledUserID := 0 1594 1595 defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1596 calledFlags = flags 1597 calledUserID = userID 1598 installQueue = append(installQueue, name) 1599 1600 t := s.NewTask("fake-refresh-snap", "Doing a fake install") 1601 return state.NewTaskSet(t), nil 1602 })() 1603 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 1604 assertstateCalledUserID = userID 1605 return nil 1606 })() 1607 1608 d := s.daemon(c) 1609 inst := &daemon.SnapInstruction{ 1610 Action: "refresh", 1611 Snaps: []string{"some-snap"}, 1612 } 1613 inst.SetUserID(17) 1614 1615 st := d.Overlord().State() 1616 st.Lock() 1617 defer st.Unlock() 1618 summary, _, err := inst.Dispatch()(inst, st) 1619 c.Check(err, check.IsNil) 1620 1621 c.Check(assertstateCalledUserID, check.Equals, 17) 1622 c.Check(calledFlags, check.DeepEquals, snapstate.Flags{}) 1623 c.Check(calledUserID, check.Equals, 17) 1624 c.Check(err, check.IsNil) 1625 c.Check(installQueue, check.DeepEquals, []string{"some-snap"}) 1626 c.Check(summary, check.Equals, `Refresh "some-snap" snap`) 1627 } 1628 1629 func (s *snapsSuite) TestRefreshDevMode(c *check.C) { 1630 var calledFlags snapstate.Flags 1631 calledUserID := 0 1632 installQueue := []string{} 1633 1634 defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1635 calledFlags = flags 1636 calledUserID = userID 1637 installQueue = append(installQueue, name) 1638 1639 t := s.NewTask("fake-refresh-snap", "Doing a fake install") 1640 return state.NewTaskSet(t), nil 1641 })() 1642 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 1643 return nil 1644 })() 1645 1646 d := s.daemon(c) 1647 inst := &daemon.SnapInstruction{ 1648 Action: "refresh", 1649 DevMode: true, 1650 Snaps: []string{"some-snap"}, 1651 } 1652 inst.SetUserID(17) 1653 1654 st := d.Overlord().State() 1655 st.Lock() 1656 defer st.Unlock() 1657 summary, _, err := inst.Dispatch()(inst, st) 1658 c.Check(err, check.IsNil) 1659 1660 flags := snapstate.Flags{} 1661 flags.DevMode = true 1662 c.Check(calledFlags, check.DeepEquals, flags) 1663 c.Check(calledUserID, check.Equals, 17) 1664 c.Check(err, check.IsNil) 1665 c.Check(installQueue, check.DeepEquals, []string{"some-snap"}) 1666 c.Check(summary, check.Equals, `Refresh "some-snap" snap`) 1667 } 1668 1669 func (s *snapsSuite) TestRefreshClassic(c *check.C) { 1670 var calledFlags snapstate.Flags 1671 1672 defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1673 calledFlags = flags 1674 return nil, nil 1675 })() 1676 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 1677 return nil 1678 })() 1679 1680 d := s.daemon(c) 1681 inst := &daemon.SnapInstruction{ 1682 Action: "refresh", 1683 Classic: true, 1684 Snaps: []string{"some-snap"}, 1685 } 1686 inst.SetUserID(17) 1687 1688 st := d.Overlord().State() 1689 st.Lock() 1690 defer st.Unlock() 1691 _, _, err := inst.Dispatch()(inst, st) 1692 c.Check(err, check.IsNil) 1693 1694 c.Check(calledFlags, check.DeepEquals, snapstate.Flags{Classic: true}) 1695 } 1696 1697 func (s *snapsSuite) TestRefreshIgnoreValidation(c *check.C) { 1698 var calledFlags snapstate.Flags 1699 calledUserID := 0 1700 installQueue := []string{} 1701 1702 defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1703 calledFlags = flags 1704 calledUserID = userID 1705 installQueue = append(installQueue, name) 1706 1707 t := s.NewTask("fake-refresh-snap", "Doing a fake install") 1708 return state.NewTaskSet(t), nil 1709 })() 1710 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 1711 return nil 1712 })() 1713 1714 d := s.daemon(c) 1715 inst := &daemon.SnapInstruction{ 1716 Action: "refresh", 1717 IgnoreValidation: true, 1718 Snaps: []string{"some-snap"}, 1719 } 1720 inst.SetUserID(17) 1721 1722 st := d.Overlord().State() 1723 st.Lock() 1724 defer st.Unlock() 1725 summary, _, err := inst.Dispatch()(inst, st) 1726 c.Check(err, check.IsNil) 1727 1728 flags := snapstate.Flags{} 1729 flags.IgnoreValidation = true 1730 1731 c.Check(calledFlags, check.DeepEquals, flags) 1732 c.Check(calledUserID, check.Equals, 17) 1733 c.Check(err, check.IsNil) 1734 c.Check(installQueue, check.DeepEquals, []string{"some-snap"}) 1735 c.Check(summary, check.Equals, `Refresh "some-snap" snap`) 1736 } 1737 1738 func (s *snapsSuite) TestRefreshIgnoreRunning(c *check.C) { 1739 var calledFlags snapstate.Flags 1740 installQueue := []string{} 1741 1742 defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1743 calledFlags = flags 1744 installQueue = append(installQueue, name) 1745 1746 t := s.NewTask("fake-refresh-snap", "Doing a fake install") 1747 return state.NewTaskSet(t), nil 1748 })() 1749 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 1750 return nil 1751 })() 1752 1753 d := s.daemon(c) 1754 inst := &daemon.SnapInstruction{ 1755 Action: "refresh", 1756 IgnoreRunning: true, 1757 Snaps: []string{"some-snap"}, 1758 } 1759 1760 st := d.Overlord().State() 1761 st.Lock() 1762 defer st.Unlock() 1763 summary, _, err := inst.Dispatch()(inst, st) 1764 c.Check(err, check.IsNil) 1765 1766 flags := snapstate.Flags{} 1767 flags.IgnoreRunning = true 1768 1769 c.Check(calledFlags, check.DeepEquals, flags) 1770 c.Check(err, check.IsNil) 1771 c.Check(installQueue, check.DeepEquals, []string{"some-snap"}) 1772 c.Check(summary, check.Equals, `Refresh "some-snap" snap`) 1773 } 1774 1775 func (s *snapsSuite) TestRefreshCohort(c *check.C) { 1776 cohort := "" 1777 1778 defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1779 cohort = opts.CohortKey 1780 1781 t := s.NewTask("fake-refresh-snap", "Doing a fake install") 1782 return state.NewTaskSet(t), nil 1783 })() 1784 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 1785 return nil 1786 })() 1787 1788 d := s.daemon(c) 1789 inst := &daemon.SnapInstruction{ 1790 Action: "refresh", 1791 Snaps: []string{"some-snap"}, 1792 } 1793 inst.CohortKey = "xyzzy" 1794 1795 st := d.Overlord().State() 1796 st.Lock() 1797 defer st.Unlock() 1798 summary, _, err := inst.Dispatch()(inst, st) 1799 c.Check(err, check.IsNil) 1800 1801 c.Check(cohort, check.Equals, "xyzzy") 1802 c.Check(summary, check.Equals, `Refresh "some-snap" snap`) 1803 } 1804 1805 func (s *snapsSuite) TestRefreshLeaveCohort(c *check.C) { 1806 var leave *bool 1807 1808 defer daemon.MockSnapstateUpdate(func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 1809 leave = &opts.LeaveCohort 1810 1811 t := s.NewTask("fake-refresh-snap", "Doing a fake install") 1812 return state.NewTaskSet(t), nil 1813 })() 1814 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 1815 return nil 1816 })() 1817 1818 d := s.daemon(c) 1819 inst := &daemon.SnapInstruction{ 1820 Action: "refresh", 1821 Snaps: []string{"some-snap"}, 1822 } 1823 inst.LeaveCohort = true 1824 1825 st := d.Overlord().State() 1826 st.Lock() 1827 defer st.Unlock() 1828 summary, _, err := inst.Dispatch()(inst, st) 1829 c.Check(err, check.IsNil) 1830 1831 c.Check(*leave, check.Equals, true) 1832 c.Check(summary, check.Equals, `Refresh "some-snap" snap`) 1833 } 1834 1835 func (s *snapsSuite) TestSwitchInstruction(c *check.C) { 1836 var cohort, channel string 1837 var leave *bool 1838 1839 defer daemon.MockSnapstateSwitch(func(s *state.State, name string, opts *snapstate.RevisionOptions) (*state.TaskSet, error) { 1840 cohort = opts.CohortKey 1841 leave = &opts.LeaveCohort 1842 channel = opts.Channel 1843 1844 t := s.NewTask("fake-switch", "Doing a fake switch") 1845 return state.NewTaskSet(t), nil 1846 })() 1847 1848 d := s.daemon(c) 1849 st := d.Overlord().State() 1850 1851 type T struct { 1852 channel string 1853 cohort string 1854 leave bool 1855 summary string 1856 } 1857 table := []T{ 1858 {"", "some-cohort", false, `Switch "some-snap" snap to cohort "…me-cohort"`}, 1859 {"some-channel", "", false, `Switch "some-snap" snap to channel "some-channel"`}, 1860 {"some-channel", "some-cohort", false, `Switch "some-snap" snap to channel "some-channel" and cohort "…me-cohort"`}, 1861 {"", "", true, `Switch "some-snap" snap away from cohort`}, 1862 {"some-channel", "", true, `Switch "some-snap" snap to channel "some-channel" and away from cohort`}, 1863 } 1864 1865 for _, t := range table { 1866 cohort, channel = "", "" 1867 leave = nil 1868 inst := &daemon.SnapInstruction{ 1869 Action: "switch", 1870 Snaps: []string{"some-snap"}, 1871 } 1872 inst.CohortKey = t.cohort 1873 inst.LeaveCohort = t.leave 1874 inst.Channel = t.channel 1875 1876 st.Lock() 1877 summary, _, err := inst.Dispatch()(inst, st) 1878 st.Unlock() 1879 c.Check(err, check.IsNil) 1880 1881 c.Check(cohort, check.Equals, t.cohort) 1882 c.Check(channel, check.Equals, t.channel) 1883 c.Check(summary, check.Equals, t.summary) 1884 c.Check(*leave, check.Equals, t.leave) 1885 } 1886 } 1887 1888 func (s *snapsSuite) testRevertSnap(inst *daemon.SnapInstruction, c *check.C) { 1889 queue := []string{} 1890 1891 instFlags, err := inst.ModeFlags() 1892 c.Assert(err, check.IsNil) 1893 1894 defer daemon.MockSnapstateRevert(func(s *state.State, name string, flags snapstate.Flags) (*state.TaskSet, error) { 1895 c.Check(flags, check.Equals, instFlags) 1896 queue = append(queue, name) 1897 return nil, nil 1898 })() 1899 defer daemon.MockSnapstateRevertToRevision(func(s *state.State, name string, rev snap.Revision, flags snapstate.Flags) (*state.TaskSet, error) { 1900 c.Check(flags, check.Equals, instFlags) 1901 queue = append(queue, fmt.Sprintf("%s (%s)", name, rev)) 1902 return nil, nil 1903 })() 1904 1905 d := s.daemon(c) 1906 inst.Action = "revert" 1907 inst.Snaps = []string{"some-snap"} 1908 1909 st := d.Overlord().State() 1910 st.Lock() 1911 defer st.Unlock() 1912 summary, _, err := inst.Dispatch()(inst, st) 1913 c.Check(err, check.IsNil) 1914 if inst.Revision.Unset() { 1915 c.Check(queue, check.DeepEquals, []string{inst.Snaps[0]}) 1916 } else { 1917 c.Check(queue, check.DeepEquals, []string{fmt.Sprintf("%s (%s)", inst.Snaps[0], inst.Revision)}) 1918 } 1919 c.Check(summary, check.Equals, `Revert "some-snap" snap`) 1920 } 1921 1922 func (s *snapsSuite) TestRevertSnap(c *check.C) { 1923 s.testRevertSnap(&daemon.SnapInstruction{}, c) 1924 } 1925 1926 func (s *snapsSuite) TestRevertSnapDevMode(c *check.C) { 1927 s.testRevertSnap(&daemon.SnapInstruction{DevMode: true}, c) 1928 } 1929 1930 func (s *snapsSuite) TestRevertSnapJailMode(c *check.C) { 1931 s.testRevertSnap(&daemon.SnapInstruction{JailMode: true}, c) 1932 } 1933 1934 func (s *snapsSuite) TestRevertSnapClassic(c *check.C) { 1935 s.testRevertSnap(&daemon.SnapInstruction{Classic: true}, c) 1936 } 1937 1938 func (s *snapsSuite) TestRevertSnapToRevision(c *check.C) { 1939 inst := &daemon.SnapInstruction{} 1940 inst.Revision = snap.R(1) 1941 s.testRevertSnap(inst, c) 1942 } 1943 1944 func (s *snapsSuite) TestRevertSnapToRevisionDevMode(c *check.C) { 1945 inst := &daemon.SnapInstruction{} 1946 inst.Revision = snap.R(1) 1947 inst.DevMode = true 1948 s.testRevertSnap(inst, c) 1949 } 1950 1951 func (s *snapsSuite) TestRevertSnapToRevisionJailMode(c *check.C) { 1952 inst := &daemon.SnapInstruction{} 1953 inst.Revision = snap.R(1) 1954 inst.JailMode = true 1955 s.testRevertSnap(inst, c) 1956 } 1957 1958 func (s *snapsSuite) TestRevertSnapToRevisionClassic(c *check.C) { 1959 inst := &daemon.SnapInstruction{} 1960 inst.Revision = snap.R(1) 1961 inst.Classic = true 1962 s.testRevertSnap(inst, c) 1963 } 1964 1965 func (s *snapsSuite) TestErrToResponseNoSnapsDoesNotPanic(c *check.C) { 1966 si := &daemon.SnapInstruction{Action: "frobble"} 1967 errors := []error{ 1968 store.ErrSnapNotFound, 1969 &store.RevisionNotAvailableError{}, 1970 store.ErrNoUpdateAvailable, 1971 store.ErrLocalSnap, 1972 &snap.AlreadyInstalledError{Snap: "foo"}, 1973 &snap.NotInstalledError{Snap: "foo"}, 1974 &snapstate.SnapNeedsDevModeError{Snap: "foo"}, 1975 &snapstate.SnapNeedsClassicError{Snap: "foo"}, 1976 &snapstate.SnapNeedsClassicSystemError{Snap: "foo"}, 1977 fakeNetError{message: "other"}, 1978 fakeNetError{message: "timeout", timeout: true}, 1979 fakeNetError{message: "temp", temporary: true}, 1980 errors.New("some other error"), 1981 } 1982 1983 for _, err := range errors { 1984 rspe := si.ErrToResponse(err) 1985 com := check.Commentf("%v", err) 1986 c.Assert(rspe, check.NotNil, com) 1987 status := rspe.Status 1988 c.Check(status/100 == 4 || status/100 == 5, check.Equals, true, com) 1989 } 1990 } 1991 1992 func (s *snapsSuite) TestErrToResponseForRevisionNotAvailable(c *check.C) { 1993 si := &daemon.SnapInstruction{Action: "frobble", Snaps: []string{"foo"}} 1994 1995 thisArch := arch.DpkgArchitecture() 1996 1997 err := &store.RevisionNotAvailableError{ 1998 Action: "install", 1999 Channel: "stable", 2000 Releases: []channel.Channel{ 2001 snaptest.MustParseChannel("beta", thisArch), 2002 }, 2003 } 2004 rspe := si.ErrToResponse(err) 2005 c.Check(rspe, check.DeepEquals, &daemon.APIError{ 2006 Status: 404, 2007 Message: "no snap revision on specified channel", 2008 Kind: client.ErrorKindSnapChannelNotAvailable, 2009 Value: map[string]interface{}{ 2010 "snap-name": "foo", 2011 "action": "install", 2012 "channel": "stable", 2013 "architecture": thisArch, 2014 "releases": []map[string]interface{}{ 2015 {"architecture": thisArch, "channel": "beta"}, 2016 }, 2017 }, 2018 }) 2019 2020 err = &store.RevisionNotAvailableError{ 2021 Action: "install", 2022 Channel: "stable", 2023 Releases: []channel.Channel{ 2024 snaptest.MustParseChannel("beta", "other-arch"), 2025 }, 2026 } 2027 rspe = si.ErrToResponse(err) 2028 c.Check(rspe, check.DeepEquals, &daemon.APIError{ 2029 Status: 404, 2030 Message: "no snap revision on specified architecture", 2031 Kind: client.ErrorKindSnapArchitectureNotAvailable, 2032 Value: map[string]interface{}{ 2033 "snap-name": "foo", 2034 "action": "install", 2035 "channel": "stable", 2036 "architecture": thisArch, 2037 "releases": []map[string]interface{}{ 2038 {"architecture": "other-arch", "channel": "beta"}, 2039 }, 2040 }, 2041 }) 2042 2043 err = &store.RevisionNotAvailableError{} 2044 rspe = si.ErrToResponse(err) 2045 c.Check(rspe, check.DeepEquals, &daemon.APIError{ 2046 Status: 404, 2047 Message: "no snap revision available as specified", 2048 Kind: client.ErrorKindSnapRevisionNotAvailable, 2049 Value: "foo", 2050 }) 2051 } 2052 2053 func (s *snapsSuite) TestErrToResponseForChangeConflict(c *check.C) { 2054 si := &daemon.SnapInstruction{Action: "frobble", Snaps: []string{"foo"}} 2055 2056 err := &snapstate.ChangeConflictError{Snap: "foo", ChangeKind: "install"} 2057 rspe := si.ErrToResponse(err) 2058 c.Check(rspe, check.DeepEquals, &daemon.APIError{ 2059 Status: 409, 2060 Message: `snap "foo" has "install" change in progress`, 2061 Kind: client.ErrorKindSnapChangeConflict, 2062 Value: map[string]interface{}{ 2063 "snap-name": "foo", 2064 "change-kind": "install", 2065 }, 2066 }) 2067 2068 // only snap 2069 err = &snapstate.ChangeConflictError{Snap: "foo"} 2070 rspe = si.ErrToResponse(err) 2071 c.Check(rspe, check.DeepEquals, &daemon.APIError{ 2072 Status: 409, 2073 Message: `snap "foo" has changes in progress`, 2074 Kind: client.ErrorKindSnapChangeConflict, 2075 Value: map[string]interface{}{ 2076 "snap-name": "foo", 2077 }, 2078 }) 2079 2080 // only kind 2081 err = &snapstate.ChangeConflictError{Message: "specific error msg", ChangeKind: "some-global-op"} 2082 rspe = si.ErrToResponse(err) 2083 c.Check(rspe, check.DeepEquals, &daemon.APIError{ 2084 Status: 409, 2085 Message: "specific error msg", 2086 Kind: client.ErrorKindSnapChangeConflict, 2087 Value: map[string]interface{}{ 2088 "change-kind": "some-global-op", 2089 }, 2090 }) 2091 }