github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/daemon/api_aliases_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 "net/http" 27 "net/http/httptest" 28 "os" 29 "path/filepath" 30 31 "gopkg.in/check.v1" 32 33 "github.com/snapcore/snapd/daemon" 34 "github.com/snapcore/snapd/dirs" 35 "github.com/snapcore/snapd/osutil" 36 "github.com/snapcore/snapd/overlord/snapstate" 37 "github.com/snapcore/snapd/overlord/state" 38 "github.com/snapcore/snapd/snap" 39 ) 40 41 var _ = check.Suite(&aliasesSuite{}) 42 43 type aliasesSuite struct { 44 apiBaseSuite 45 } 46 47 const aliasYaml = ` 48 name: alias-snap 49 version: 1 50 apps: 51 app: 52 app2: 53 ` 54 55 func (s *aliasesSuite) TestAliasSuccess(c *check.C) { 56 err := os.MkdirAll(dirs.SnapBinariesDir, 0755) 57 c.Assert(err, check.IsNil) 58 d := s.daemon(c) 59 60 s.mockSnap(c, aliasYaml) 61 62 oldAutoAliases := snapstate.AutoAliases 63 snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) { 64 return nil, nil 65 } 66 defer func() { snapstate.AutoAliases = oldAutoAliases }() 67 68 d.Overlord().Loop() 69 defer d.Overlord().Stop() 70 71 action := &daemon.AliasAction{ 72 Action: "alias", 73 Snap: "alias-snap", 74 App: "app", 75 Alias: "alias1", 76 } 77 text, err := json.Marshal(action) 78 c.Assert(err, check.IsNil) 79 buf := bytes.NewBuffer(text) 80 req, err := http.NewRequest("POST", "/v2/aliases", buf) 81 c.Assert(err, check.IsNil) 82 rec := httptest.NewRecorder() 83 s.req(c, req, nil).ServeHTTP(rec, req) 84 c.Assert(rec.Code, check.Equals, 202) 85 var body map[string]interface{} 86 err = json.Unmarshal(rec.Body.Bytes(), &body) 87 c.Check(err, check.IsNil) 88 id := body["change"].(string) 89 90 st := d.Overlord().State() 91 st.Lock() 92 chg := st.Change(id) 93 st.Unlock() 94 c.Assert(chg, check.NotNil) 95 96 <-chg.Ready() 97 98 st.Lock() 99 err = chg.Err() 100 st.Unlock() 101 c.Assert(err, check.IsNil) 102 103 // sanity check 104 c.Check(osutil.IsSymlink(filepath.Join(dirs.SnapBinariesDir, "alias1")), check.Equals, true) 105 } 106 107 func (s *aliasesSuite) TestAliasChangeConflict(c *check.C) { 108 err := os.MkdirAll(dirs.SnapBinariesDir, 0755) 109 c.Assert(err, check.IsNil) 110 s.daemon(c) 111 112 s.mockSnap(c, aliasYaml) 113 114 s.simulateConflict("alias-snap") 115 116 oldAutoAliases := snapstate.AutoAliases 117 snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) { 118 return nil, nil 119 } 120 defer func() { snapstate.AutoAliases = oldAutoAliases }() 121 122 action := &daemon.AliasAction{ 123 Action: "alias", 124 Snap: "alias-snap", 125 App: "app", 126 Alias: "alias1", 127 } 128 text, err := json.Marshal(action) 129 c.Assert(err, check.IsNil) 130 buf := bytes.NewBuffer(text) 131 req, err := http.NewRequest("POST", "/v2/aliases", buf) 132 c.Assert(err, check.IsNil) 133 rec := httptest.NewRecorder() 134 s.req(c, req, nil).ServeHTTP(rec, req) 135 c.Check(rec.Code, check.Equals, 409) 136 137 var body map[string]interface{} 138 err = json.Unmarshal(rec.Body.Bytes(), &body) 139 c.Check(err, check.IsNil) 140 c.Check(body, check.DeepEquals, map[string]interface{}{ 141 "status-code": 409., 142 "status": "Conflict", 143 "result": map[string]interface{}{ 144 "message": `snap "alias-snap" has "manip" change in progress`, 145 "kind": "snap-change-conflict", 146 "value": map[string]interface{}{ 147 "change-kind": "manip", 148 "snap-name": "alias-snap", 149 }, 150 }, 151 "type": "error"}) 152 } 153 154 func (s *aliasesSuite) TestAliasErrors(c *check.C) { 155 s.daemon(c) 156 157 errScenarios := []struct { 158 mangle func(*daemon.AliasAction) 159 err string 160 }{ 161 {func(a *daemon.AliasAction) { a.Action = "" }, `unsupported alias action: ""`}, 162 {func(a *daemon.AliasAction) { a.Action = "what" }, `unsupported alias action: "what"`}, 163 {func(a *daemon.AliasAction) { a.Snap = "lalala" }, `snap "lalala" is not installed`}, 164 {func(a *daemon.AliasAction) { a.Alias = ".foo" }, `invalid alias name: ".foo"`}, 165 {func(a *daemon.AliasAction) { a.Aliases = []string{"baz"} }, `cannot interpret request, snaps can no longer be expected to declare their aliases`}, 166 } 167 168 for _, scen := range errScenarios { 169 action := &daemon.AliasAction{ 170 Action: "alias", 171 Snap: "alias-snap", 172 App: "app", 173 Alias: "alias1", 174 } 175 scen.mangle(action) 176 177 text, err := json.Marshal(action) 178 c.Assert(err, check.IsNil) 179 buf := bytes.NewBuffer(text) 180 req, err := http.NewRequest("POST", "/v2/aliases", buf) 181 c.Assert(err, check.IsNil) 182 183 rsp := s.req(c, req, nil).(*daemon.Resp) 184 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 185 c.Check(rsp.Status, check.Equals, 400) 186 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, scen.err) 187 } 188 } 189 190 func (s *aliasesSuite) TestUnaliasSnapSuccess(c *check.C) { 191 err := os.MkdirAll(dirs.SnapBinariesDir, 0755) 192 c.Assert(err, check.IsNil) 193 d := s.daemon(c) 194 195 s.mockSnap(c, aliasYaml) 196 197 oldAutoAliases := snapstate.AutoAliases 198 snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) { 199 return nil, nil 200 } 201 defer func() { snapstate.AutoAliases = oldAutoAliases }() 202 203 d.Overlord().Loop() 204 defer d.Overlord().Stop() 205 206 action := &daemon.AliasAction{ 207 Action: "unalias", 208 Snap: "alias-snap", 209 } 210 text, err := json.Marshal(action) 211 c.Assert(err, check.IsNil) 212 buf := bytes.NewBuffer(text) 213 req, err := http.NewRequest("POST", "/v2/aliases", buf) 214 c.Assert(err, check.IsNil) 215 rec := httptest.NewRecorder() 216 s.req(c, req, nil).ServeHTTP(rec, req) 217 c.Assert(rec.Code, check.Equals, 202) 218 var body map[string]interface{} 219 err = json.Unmarshal(rec.Body.Bytes(), &body) 220 c.Check(err, check.IsNil) 221 id := body["change"].(string) 222 223 st := d.Overlord().State() 224 st.Lock() 225 chg := st.Change(id) 226 c.Check(chg.Summary(), check.Equals, `Disable all aliases for snap "alias-snap"`) 227 st.Unlock() 228 c.Assert(chg, check.NotNil) 229 230 <-chg.Ready() 231 232 st.Lock() 233 defer st.Unlock() 234 err = chg.Err() 235 c.Assert(err, check.IsNil) 236 237 // sanity check 238 var snapst snapstate.SnapState 239 err = snapstate.Get(st, "alias-snap", &snapst) 240 c.Assert(err, check.IsNil) 241 c.Check(snapst.AutoAliasesDisabled, check.Equals, true) 242 } 243 244 func (s *aliasesSuite) TestUnaliasDWIMSnapSuccess(c *check.C) { 245 err := os.MkdirAll(dirs.SnapBinariesDir, 0755) 246 c.Assert(err, check.IsNil) 247 d := s.daemon(c) 248 249 s.mockSnap(c, aliasYaml) 250 251 oldAutoAliases := snapstate.AutoAliases 252 snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) { 253 return nil, nil 254 } 255 defer func() { snapstate.AutoAliases = oldAutoAliases }() 256 257 d.Overlord().Loop() 258 defer d.Overlord().Stop() 259 260 action := &daemon.AliasAction{ 261 Action: "unalias", 262 Snap: "alias-snap", 263 Alias: "alias-snap", 264 } 265 text, err := json.Marshal(action) 266 c.Assert(err, check.IsNil) 267 buf := bytes.NewBuffer(text) 268 req, err := http.NewRequest("POST", "/v2/aliases", buf) 269 c.Assert(err, check.IsNil) 270 rec := httptest.NewRecorder() 271 s.req(c, req, nil).ServeHTTP(rec, req) 272 c.Assert(rec.Code, check.Equals, 202) 273 var body map[string]interface{} 274 err = json.Unmarshal(rec.Body.Bytes(), &body) 275 c.Check(err, check.IsNil) 276 id := body["change"].(string) 277 278 st := d.Overlord().State() 279 st.Lock() 280 chg := st.Change(id) 281 c.Check(chg.Summary(), check.Equals, `Disable all aliases for snap "alias-snap"`) 282 st.Unlock() 283 c.Assert(chg, check.NotNil) 284 285 <-chg.Ready() 286 287 st.Lock() 288 defer st.Unlock() 289 err = chg.Err() 290 c.Assert(err, check.IsNil) 291 292 // sanity check 293 var snapst snapstate.SnapState 294 err = snapstate.Get(st, "alias-snap", &snapst) 295 c.Assert(err, check.IsNil) 296 c.Check(snapst.AutoAliasesDisabled, check.Equals, true) 297 } 298 299 func (s *aliasesSuite) TestUnaliasAliasSuccess(c *check.C) { 300 err := os.MkdirAll(dirs.SnapBinariesDir, 0755) 301 c.Assert(err, check.IsNil) 302 d := s.daemon(c) 303 304 s.mockSnap(c, aliasYaml) 305 306 oldAutoAliases := snapstate.AutoAliases 307 snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) { 308 return nil, nil 309 } 310 defer func() { snapstate.AutoAliases = oldAutoAliases }() 311 312 d.Overlord().Loop() 313 defer d.Overlord().Stop() 314 315 action := &daemon.AliasAction{ 316 Action: "alias", 317 Snap: "alias-snap", 318 App: "app", 319 Alias: "alias1", 320 } 321 text, err := json.Marshal(action) 322 c.Assert(err, check.IsNil) 323 buf := bytes.NewBuffer(text) 324 req, err := http.NewRequest("POST", "/v2/aliases", buf) 325 c.Assert(err, check.IsNil) 326 rec := httptest.NewRecorder() 327 s.req(c, req, nil).ServeHTTP(rec, req) 328 c.Assert(rec.Code, check.Equals, 202) 329 var body map[string]interface{} 330 err = json.Unmarshal(rec.Body.Bytes(), &body) 331 c.Check(err, check.IsNil) 332 id := body["change"].(string) 333 334 st := d.Overlord().State() 335 st.Lock() 336 chg := st.Change(id) 337 st.Unlock() 338 c.Assert(chg, check.NotNil) 339 340 <-chg.Ready() 341 342 st.Lock() 343 err = chg.Err() 344 st.Unlock() 345 c.Assert(err, check.IsNil) 346 347 // unalias 348 action = &daemon.AliasAction{ 349 Action: "unalias", 350 Alias: "alias1", 351 } 352 text, err = json.Marshal(action) 353 c.Assert(err, check.IsNil) 354 buf = bytes.NewBuffer(text) 355 req, err = http.NewRequest("POST", "/v2/aliases", buf) 356 c.Assert(err, check.IsNil) 357 rec = httptest.NewRecorder() 358 s.req(c, req, nil).ServeHTTP(rec, req) 359 c.Assert(rec.Code, check.Equals, 202) 360 err = json.Unmarshal(rec.Body.Bytes(), &body) 361 c.Check(err, check.IsNil) 362 id = body["change"].(string) 363 364 st.Lock() 365 chg = st.Change(id) 366 c.Check(chg.Summary(), check.Equals, `Remove manual alias "alias1" for snap "alias-snap"`) 367 st.Unlock() 368 c.Assert(chg, check.NotNil) 369 370 <-chg.Ready() 371 372 st.Lock() 373 defer st.Unlock() 374 err = chg.Err() 375 c.Assert(err, check.IsNil) 376 377 // sanity check 378 c.Check(osutil.FileExists(filepath.Join(dirs.SnapBinariesDir, "alias1")), check.Equals, false) 379 } 380 381 func (s *aliasesSuite) TestUnaliasDWIMAliasSuccess(c *check.C) { 382 err := os.MkdirAll(dirs.SnapBinariesDir, 0755) 383 c.Assert(err, check.IsNil) 384 d := s.daemon(c) 385 386 s.mockSnap(c, aliasYaml) 387 388 oldAutoAliases := snapstate.AutoAliases 389 snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) { 390 return nil, nil 391 } 392 defer func() { snapstate.AutoAliases = oldAutoAliases }() 393 394 d.Overlord().Loop() 395 defer d.Overlord().Stop() 396 397 action := &daemon.AliasAction{ 398 Action: "alias", 399 Snap: "alias-snap", 400 App: "app", 401 Alias: "alias1", 402 } 403 text, err := json.Marshal(action) 404 c.Assert(err, check.IsNil) 405 buf := bytes.NewBuffer(text) 406 req, err := http.NewRequest("POST", "/v2/aliases", buf) 407 c.Assert(err, check.IsNil) 408 rec := httptest.NewRecorder() 409 s.req(c, req, nil).ServeHTTP(rec, req) 410 c.Assert(rec.Code, check.Equals, 202) 411 var body map[string]interface{} 412 err = json.Unmarshal(rec.Body.Bytes(), &body) 413 c.Check(err, check.IsNil) 414 id := body["change"].(string) 415 416 st := d.Overlord().State() 417 st.Lock() 418 chg := st.Change(id) 419 st.Unlock() 420 c.Assert(chg, check.NotNil) 421 422 <-chg.Ready() 423 424 st.Lock() 425 err = chg.Err() 426 st.Unlock() 427 c.Assert(err, check.IsNil) 428 429 // DWIM unalias an alias 430 action = &daemon.AliasAction{ 431 Action: "unalias", 432 Snap: "alias1", 433 Alias: "alias1", 434 } 435 text, err = json.Marshal(action) 436 c.Assert(err, check.IsNil) 437 buf = bytes.NewBuffer(text) 438 req, err = http.NewRequest("POST", "/v2/aliases", buf) 439 c.Assert(err, check.IsNil) 440 rec = httptest.NewRecorder() 441 s.req(c, req, nil).ServeHTTP(rec, req) 442 c.Assert(rec.Code, check.Equals, 202) 443 err = json.Unmarshal(rec.Body.Bytes(), &body) 444 c.Check(err, check.IsNil) 445 id = body["change"].(string) 446 447 st.Lock() 448 chg = st.Change(id) 449 c.Check(chg.Summary(), check.Equals, `Remove manual alias "alias1" for snap "alias-snap"`) 450 st.Unlock() 451 c.Assert(chg, check.NotNil) 452 453 <-chg.Ready() 454 455 st.Lock() 456 defer st.Unlock() 457 err = chg.Err() 458 c.Assert(err, check.IsNil) 459 460 // sanity check 461 c.Check(osutil.FileExists(filepath.Join(dirs.SnapBinariesDir, "alias1")), check.Equals, false) 462 } 463 464 func (s *aliasesSuite) TestPreferSuccess(c *check.C) { 465 err := os.MkdirAll(dirs.SnapBinariesDir, 0755) 466 c.Assert(err, check.IsNil) 467 d := s.daemon(c) 468 469 s.mockSnap(c, aliasYaml) 470 471 oldAutoAliases := snapstate.AutoAliases 472 snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) { 473 return nil, nil 474 } 475 defer func() { snapstate.AutoAliases = oldAutoAliases }() 476 477 d.Overlord().Loop() 478 defer d.Overlord().Stop() 479 480 action := &daemon.AliasAction{ 481 Action: "prefer", 482 Snap: "alias-snap", 483 } 484 text, err := json.Marshal(action) 485 c.Assert(err, check.IsNil) 486 buf := bytes.NewBuffer(text) 487 req, err := http.NewRequest("POST", "/v2/aliases", buf) 488 c.Assert(err, check.IsNil) 489 rec := httptest.NewRecorder() 490 s.req(c, req, nil).ServeHTTP(rec, req) 491 c.Assert(rec.Code, check.Equals, 202) 492 var body map[string]interface{} 493 err = json.Unmarshal(rec.Body.Bytes(), &body) 494 c.Check(err, check.IsNil) 495 id := body["change"].(string) 496 497 st := d.Overlord().State() 498 st.Lock() 499 chg := st.Change(id) 500 c.Check(chg.Summary(), check.Equals, `Prefer aliases of snap "alias-snap"`) 501 st.Unlock() 502 c.Assert(chg, check.NotNil) 503 504 <-chg.Ready() 505 506 st.Lock() 507 defer st.Unlock() 508 err = chg.Err() 509 c.Assert(err, check.IsNil) 510 511 // sanity check 512 var snapst snapstate.SnapState 513 err = snapstate.Get(st, "alias-snap", &snapst) 514 c.Assert(err, check.IsNil) 515 c.Check(snapst.AutoAliasesDisabled, check.Equals, false) 516 } 517 518 func (s *aliasesSuite) TestAliases(c *check.C) { 519 d := s.daemon(c) 520 521 st := d.Overlord().State() 522 st.Lock() 523 snapstate.Set(st, "alias-snap1", &snapstate.SnapState{ 524 Sequence: []*snap.SideInfo{ 525 {RealName: "alias-snap1", Revision: snap.R(11)}, 526 }, 527 Current: snap.R(11), 528 Active: true, 529 Aliases: map[string]*snapstate.AliasTarget{ 530 "alias1": {Manual: "cmd1x", Auto: "cmd1"}, 531 "alias2": {Auto: "cmd2"}, 532 }, 533 }) 534 snapstate.Set(st, "alias-snap2", &snapstate.SnapState{ 535 Sequence: []*snap.SideInfo{ 536 {RealName: "alias-snap2", Revision: snap.R(12)}, 537 }, 538 Current: snap.R(12), 539 Active: true, 540 AutoAliasesDisabled: true, 541 Aliases: map[string]*snapstate.AliasTarget{ 542 "alias2": {Auto: "cmd2"}, 543 "alias3": {Manual: "cmd3"}, 544 "alias4": {Manual: "cmd4x", Auto: "cmd4"}, 545 }, 546 }) 547 st.Unlock() 548 549 req, err := http.NewRequest("GET", "/v2/aliases", nil) 550 c.Assert(err, check.IsNil) 551 552 rsp := s.req(c, req, nil).(*daemon.Resp) 553 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync) 554 c.Check(rsp.Status, check.Equals, 200) 555 c.Check(rsp.Result, check.DeepEquals, map[string]map[string]daemon.AliasStatus{ 556 "alias-snap1": { 557 "alias1": { 558 Command: "alias-snap1.cmd1x", 559 Status: "manual", 560 Manual: "cmd1x", 561 Auto: "cmd1", 562 }, 563 "alias2": { 564 Command: "alias-snap1.cmd2", 565 Status: "auto", 566 Auto: "cmd2", 567 }, 568 }, 569 "alias-snap2": { 570 "alias2": { 571 Command: "alias-snap2.cmd2", 572 Status: "disabled", 573 Auto: "cmd2", 574 }, 575 "alias3": { 576 Command: "alias-snap2.cmd3", 577 Status: "manual", 578 Manual: "cmd3", 579 }, 580 "alias4": { 581 Command: "alias-snap2.cmd4x", 582 Status: "manual", 583 Manual: "cmd4x", 584 Auto: "cmd4", 585 }, 586 }, 587 }) 588 589 } 590 591 func (s *aliasesSuite) TestInstallUnaliased(c *check.C) { 592 var calledFlags snapstate.Flags 593 594 defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 595 calledFlags = flags 596 597 t := s.NewTask("fake-install-snap", "Doing a fake install") 598 return state.NewTaskSet(t), nil 599 })() 600 601 d := s.daemon(c) 602 inst := &daemon.SnapInstruction{ 603 Action: "install", 604 // Install the snap without enabled automatic aliases 605 Unaliased: true, 606 Snaps: []string{"fake"}, 607 } 608 609 st := d.Overlord().State() 610 st.Lock() 611 defer st.Unlock() 612 _, _, err := inst.Dispatch()(inst, st) 613 c.Check(err, check.IsNil) 614 615 c.Check(calledFlags.Unaliased, check.Equals, true) 616 } 617 618 func (s *aliasesSuite) TestInstallIgnoreRunning(c *check.C) { 619 var calledFlags snapstate.Flags 620 621 defer daemon.MockSnapstateInstall(func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) { 622 calledFlags = flags 623 624 t := s.NewTask("fake-install-snap", "Doing a fake install") 625 return state.NewTaskSet(t), nil 626 })() 627 628 d := s.daemon(c) 629 inst := &daemon.SnapInstruction{ 630 Action: "install", 631 // Install the snap without enabled automatic aliases 632 IgnoreRunning: true, 633 Snaps: []string{"fake"}, 634 } 635 636 st := d.Overlord().State() 637 st.Lock() 638 defer st.Unlock() 639 _, _, err := inst.Dispatch()(inst, st) 640 c.Check(err, check.IsNil) 641 642 c.Check(calledFlags.IgnoreRunning, check.Equals, true) 643 }