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