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