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