gitee.com/mysnapcore/mysnapd@v0.1.0/image/image_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2022 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 image_test 21 22 import ( 23 "bytes" 24 "context" 25 "fmt" 26 "io/ioutil" 27 "os" 28 "path/filepath" 29 "strings" 30 "testing" 31 "time" 32 33 . "gopkg.in/check.v1" 34 35 "gitee.com/mysnapcore/mysnapd/asserts" 36 "gitee.com/mysnapcore/mysnapd/asserts/assertstest" 37 "gitee.com/mysnapcore/mysnapd/asserts/sysdb" 38 "gitee.com/mysnapcore/mysnapd/bootloader" 39 "gitee.com/mysnapcore/mysnapd/bootloader/assets" 40 "gitee.com/mysnapcore/mysnapd/bootloader/bootloadertest" 41 "gitee.com/mysnapcore/mysnapd/bootloader/grubenv" 42 "gitee.com/mysnapcore/mysnapd/bootloader/ubootenv" 43 "gitee.com/mysnapcore/mysnapd/dirs" 44 "gitee.com/mysnapcore/mysnapd/gadget" 45 "gitee.com/mysnapcore/mysnapd/image" 46 "gitee.com/mysnapcore/mysnapd/osutil" 47 "gitee.com/mysnapcore/mysnapd/overlord/auth" 48 "gitee.com/mysnapcore/mysnapd/progress" 49 "gitee.com/mysnapcore/mysnapd/seed" 50 "gitee.com/mysnapcore/mysnapd/seed/seedtest" 51 "gitee.com/mysnapcore/mysnapd/snap" 52 "gitee.com/mysnapcore/mysnapd/snap/snaptest" 53 "gitee.com/mysnapcore/mysnapd/store" 54 "gitee.com/mysnapcore/mysnapd/store/tooling" 55 "gitee.com/mysnapcore/mysnapd/testutil" 56 "gitee.com/mysnapcore/mysnapd/timings" 57 ) 58 59 func Test(t *testing.T) { TestingT(t) } 60 61 type imageSuite struct { 62 testutil.BaseTest 63 root string 64 bootloader *bootloadertest.MockBootloader 65 66 stdout *bytes.Buffer 67 stderr *bytes.Buffer 68 69 storeActionsBunchSizes []int 70 storeActions []*store.SnapAction 71 curSnaps [][]*store.CurrentSnap 72 73 tsto *tooling.ToolingStore 74 75 // SeedSnaps helps creating and making available seed snaps 76 // (it provides MakeAssertedSnap etc.) for the tests. 77 *seedtest.SeedSnaps 78 79 model *asserts.Model 80 } 81 82 var _ = Suite(&imageSuite{}) 83 84 var ( 85 brandPrivKey, _ = assertstest.GenerateKey(752) 86 ) 87 88 func (s *imageSuite) SetUpTest(c *C) { 89 s.root = c.MkDir() 90 s.bootloader = bootloadertest.Mock("grub", c.MkDir()) 91 bootloader.Force(s.bootloader) 92 93 s.BaseTest.SetUpTest(c) 94 s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})) 95 96 s.stdout = &bytes.Buffer{} 97 image.Stdout = s.stdout 98 s.stderr = &bytes.Buffer{} 99 image.Stderr = s.stderr 100 s.tsto = tooling.MockToolingStore(s) 101 102 s.SeedSnaps = &seedtest.SeedSnaps{} 103 s.SetupAssertSigning("canonical") 104 s.Brands.Register("my-brand", brandPrivKey, map[string]interface{}{ 105 "verification": "verified", 106 }) 107 assertstest.AddMany(s.StoreSigning, s.Brands.AccountsAndKeys("my-brand")...) 108 109 s.model = s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 110 "display-name": "my display name", 111 "architecture": "amd64", 112 "gadget": "pc", 113 "kernel": "pc-kernel", 114 "required-snaps": []interface{}{"required-snap1"}, 115 }) 116 117 otherAcct := assertstest.NewAccount(s.StoreSigning, "other", map[string]interface{}{ 118 "account-id": "other", 119 }, "") 120 s.StoreSigning.Add(otherAcct) 121 122 // mock the mount cmds (for the extract kernel assets stuff) 123 c1 := testutil.MockCommand(c, "mount", "") 124 s.AddCleanup(c1.Restore) 125 c2 := testutil.MockCommand(c, "umount", "") 126 s.AddCleanup(c2.Restore) 127 128 restore := image.MockWriteResolvedContent(func(_ string, _ *gadget.Info, _, _ string) error { 129 return nil 130 }) 131 s.AddCleanup(restore) 132 } 133 134 func (s *imageSuite) TearDownTest(c *C) { 135 s.BaseTest.TearDownTest(c) 136 bootloader.Force(nil) 137 image.Stdout = os.Stdout 138 image.Stderr = os.Stderr 139 s.storeActions = nil 140 s.storeActionsBunchSizes = nil 141 s.curSnaps = nil 142 } 143 144 // interface for the store 145 func (s *imageSuite) SnapAction(_ context.Context, curSnaps []*store.CurrentSnap, actions []*store.SnapAction, assertQuery store.AssertionQuery, _ *auth.UserState, _ *store.RefreshOptions) ([]store.SnapActionResult, []store.AssertionResult, error) { 146 if assertQuery != nil { 147 return nil, nil, fmt.Errorf("unexpected assertion query") 148 } 149 150 s.storeActionsBunchSizes = append(s.storeActionsBunchSizes, len(actions)) 151 s.curSnaps = append(s.curSnaps, curSnaps) 152 sars := make([]store.SnapActionResult, 0, len(actions)) 153 for _, a := range actions { 154 if a.Action != "download" { 155 return nil, nil, fmt.Errorf("unexpected action %q", a.Action) 156 } 157 158 if _, instanceKey := snap.SplitInstanceName(a.InstanceName); instanceKey != "" { 159 return nil, nil, fmt.Errorf("unexpected instance key in %q", a.InstanceName) 160 } 161 // record 162 s.storeActions = append(s.storeActions, a) 163 164 info := s.AssertedSnapInfo(a.InstanceName) 165 if info == nil { 166 return nil, nil, fmt.Errorf("no %q in the fake store", a.InstanceName) 167 } 168 info1 := *info 169 channel := a.Channel 170 redirectChannel := "" 171 if strings.HasPrefix(a.InstanceName, "default-track-") { 172 channel = "default-track/stable" 173 redirectChannel = channel 174 } 175 info1.Channel = channel 176 sars = append(sars, store.SnapActionResult{ 177 Info: &info1, 178 RedirectChannel: redirectChannel, 179 }) 180 } 181 182 return sars, nil, nil 183 } 184 185 func (s *imageSuite) Download(ctx context.Context, name, targetFn string, downloadInfo *snap.DownloadInfo, pbar progress.Meter, user *auth.UserState, dlOpts *store.DownloadOptions) error { 186 return osutil.CopyFile(s.AssertedSnap(name), targetFn, 0) 187 } 188 189 func (s *imageSuite) Assertion(assertType *asserts.AssertionType, primaryKey []string, user *auth.UserState) (asserts.Assertion, error) { 190 ref := &asserts.Ref{Type: assertType, PrimaryKey: primaryKey} 191 return ref.Resolve(s.StoreSigning.Find) 192 } 193 194 // TODO: use seedtest.SampleSnapYaml for some of these 195 const packageGadget = ` 196 name: pc 197 version: 1.0 198 type: gadget 199 ` 200 201 const packageGadgetWithBase = ` 202 name: pc18 203 version: 1.0 204 type: gadget 205 base: core18 206 ` 207 const packageClassicGadget = ` 208 name: classic-gadget 209 version: 1.0 210 type: gadget 211 ` 212 213 const packageClassicGadget18 = ` 214 name: classic-gadget18 215 version: 1.0 216 type: gadget 217 base: core18 218 ` 219 220 const packageKernel = ` 221 name: pc-kernel 222 version: 4.4-1 223 type: kernel 224 ` 225 226 const packageCore = ` 227 name: core 228 version: 16.04 229 type: os 230 ` 231 232 const packageCore18 = ` 233 name: core18 234 version: 18.04 235 type: base 236 ` 237 238 const snapdSnap = ` 239 name: snapd 240 version: 3.14 241 type: snapd 242 ` 243 244 const otherBase = ` 245 name: other-base 246 version: 2.5029 247 type: base 248 ` 249 250 const devmodeSnap = ` 251 name: devmode-snap 252 version: 1.0 253 type: app 254 confinement: devmode 255 ` 256 257 const classicSnap = ` 258 name: classic-snap 259 version: 1.0 260 type: app 261 confinement: classic 262 ` 263 264 const requiredSnap1 = ` 265 name: required-snap1 266 version: 1.0 267 ` 268 269 const requiredSnap18 = ` 270 name: required-snap18 271 version: 1.0 272 base: core18 273 ` 274 275 const defaultTrackSnap18 = ` 276 name: default-track-snap18 277 version: 1.0 278 base: core18 279 ` 280 281 const snapReqOtherBase = ` 282 name: snap-req-other-base 283 version: 1.0 284 base: other-base 285 ` 286 287 const snapReqCore16Base = ` 288 name: snap-req-core16-base 289 version: 1.0 290 base: core16 291 ` 292 293 const snapReqContentProvider = ` 294 name: snap-req-content-provider 295 version: 1.0 296 plugs: 297 gtk-3-themes: 298 interface: content 299 default-provider: gtk-common-themes 300 target: $SNAP/data-dir/themes 301 ` 302 303 const snapBaseNone = ` 304 name: snap-base-none 305 version: 1.0 306 base: none 307 ` 308 309 func (s *imageSuite) TestMissingModelAssertions(c *C) { 310 _, err := image.DecodeModelAssertion(&image.Options{}) 311 c.Assert(err, ErrorMatches, "cannot read model assertion: open : no such file or directory") 312 } 313 314 func (s *imageSuite) TestIncorrectModelAssertions(c *C) { 315 fn := filepath.Join(c.MkDir(), "broken-model.assertion") 316 err := ioutil.WriteFile(fn, nil, 0644) 317 c.Assert(err, IsNil) 318 _, err = image.DecodeModelAssertion(&image.Options{ 319 ModelFile: fn, 320 }) 321 c.Assert(err, ErrorMatches, fmt.Sprintf(`cannot decode model assertion "%s": assertion content/signature separator not found`, fn)) 322 } 323 324 func (s *imageSuite) TestValidButDifferentAssertion(c *C) { 325 var differentAssertion = []byte(`type: snap-declaration 326 authority-id: canonical 327 series: 16 328 snap-id: snap-id-1 329 snap-name: first 330 publisher-id: dev-id1 331 timestamp: 2016-01-02T10:00:00-05:00 332 sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij 333 334 AXNpZw== 335 `) 336 337 fn := filepath.Join(c.MkDir(), "different.assertion") 338 err := ioutil.WriteFile(fn, differentAssertion, 0644) 339 c.Assert(err, IsNil) 340 341 _, err = image.DecodeModelAssertion(&image.Options{ 342 ModelFile: fn, 343 }) 344 c.Assert(err, ErrorMatches, fmt.Sprintf(`assertion in "%s" is not a model assertion`, fn)) 345 } 346 347 func (s *imageSuite) TestModelAssertionReservedHeaders(c *C) { 348 const mod = `type: model 349 authority-id: brand 350 series: 16 351 brand-id: brand 352 model: baz-3000 353 architecture: armhf 354 gadget: brand-gadget 355 kernel: kernel 356 timestamp: 2016-01-02T10:00:00-05:00 357 sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij 358 359 AXNpZw== 360 ` 361 362 reserved := []string{ 363 "core", 364 "os", 365 "class", 366 "allowed-modes", 367 } 368 369 for _, rsvd := range reserved { 370 tweaked := strings.Replace(mod, "kernel: kernel\n", fmt.Sprintf("kernel: kernel\n%s: stuff\n", rsvd), 1) 371 fn := filepath.Join(c.MkDir(), "model.assertion") 372 err := ioutil.WriteFile(fn, []byte(tweaked), 0644) 373 c.Assert(err, IsNil) 374 _, err = image.DecodeModelAssertion(&image.Options{ 375 ModelFile: fn, 376 }) 377 c.Check(err, ErrorMatches, fmt.Sprintf("model assertion cannot have reserved/unsupported header %q set", rsvd)) 378 } 379 } 380 381 func (s *imageSuite) TestModelAssertionNoParallelInstancesOfSnaps(c *C) { 382 const mod = `type: model 383 authority-id: brand 384 series: 16 385 brand-id: brand 386 model: baz-3000 387 architecture: armhf 388 gadget: brand-gadget 389 kernel: kernel 390 required-snaps: 391 - foo_instance 392 timestamp: 2016-01-02T10:00:00-05:00 393 sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij 394 395 AXNpZw== 396 ` 397 398 fn := filepath.Join(c.MkDir(), "model.assertion") 399 err := ioutil.WriteFile(fn, []byte(mod), 0644) 400 c.Assert(err, IsNil) 401 _, err = image.DecodeModelAssertion(&image.Options{ 402 ModelFile: fn, 403 }) 404 c.Check(err, ErrorMatches, `.* assertion model: invalid snap name in "required-snaps" header: foo_instance`) 405 } 406 407 func (s *imageSuite) TestModelAssertionNoParallelInstancesOfKernel(c *C) { 408 const mod = `type: model 409 authority-id: brand 410 series: 16 411 brand-id: brand 412 model: baz-3000 413 architecture: armhf 414 gadget: brand-gadget 415 kernel: kernel_instance 416 timestamp: 2016-01-02T10:00:00-05:00 417 sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij 418 419 AXNpZw== 420 ` 421 422 fn := filepath.Join(c.MkDir(), "model.assertion") 423 err := ioutil.WriteFile(fn, []byte(mod), 0644) 424 c.Assert(err, IsNil) 425 _, err = image.DecodeModelAssertion(&image.Options{ 426 ModelFile: fn, 427 }) 428 c.Check(err, ErrorMatches, `.* assertion model: invalid snap name in "kernel" header: kernel_instance`) 429 } 430 431 func (s *imageSuite) TestModelAssertionNoParallelInstancesOfGadget(c *C) { 432 const mod = `type: model 433 authority-id: brand 434 series: 16 435 brand-id: brand 436 model: baz-3000 437 architecture: armhf 438 gadget: brand-gadget_instance 439 kernel: kernel 440 timestamp: 2016-01-02T10:00:00-05:00 441 sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij 442 443 AXNpZw== 444 ` 445 446 fn := filepath.Join(c.MkDir(), "model.assertion") 447 err := ioutil.WriteFile(fn, []byte(mod), 0644) 448 c.Assert(err, IsNil) 449 _, err = image.DecodeModelAssertion(&image.Options{ 450 ModelFile: fn, 451 }) 452 c.Check(err, ErrorMatches, `.* assertion model: invalid snap name in "gadget" header: brand-gadget_instance`) 453 } 454 455 func (s *imageSuite) TestModelAssertionNoParallelInstancesOfBase(c *C) { 456 const mod = `type: model 457 authority-id: brand 458 series: 16 459 brand-id: brand 460 model: baz-3000 461 architecture: armhf 462 gadget: brand-gadget 463 kernel: kernel 464 base: core18_instance 465 timestamp: 2016-01-02T10:00:00-05:00 466 sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij 467 468 AXNpZw== 469 ` 470 471 fn := filepath.Join(c.MkDir(), "model.assertion") 472 err := ioutil.WriteFile(fn, []byte(mod), 0644) 473 c.Assert(err, IsNil) 474 _, err = image.DecodeModelAssertion(&image.Options{ 475 ModelFile: fn, 476 }) 477 c.Check(err, ErrorMatches, `.* assertion model: invalid snap name in "base" header: core18_instance`) 478 } 479 480 func (s *imageSuite) TestHappyDecodeModelAssertion(c *C) { 481 fn := filepath.Join(c.MkDir(), "model.assertion") 482 err := ioutil.WriteFile(fn, asserts.Encode(s.model), 0644) 483 c.Assert(err, IsNil) 484 485 a, err := image.DecodeModelAssertion(&image.Options{ 486 ModelFile: fn, 487 }) 488 c.Assert(err, IsNil) 489 c.Check(a.Type(), Equals, asserts.ModelType) 490 } 491 492 func (s *imageSuite) MakeAssertedSnap(c *C, snapYaml string, files [][]string, revision snap.Revision, developerID string) { 493 s.SeedSnaps.MakeAssertedSnap(c, snapYaml, files, revision, developerID, s.StoreSigning.Database) 494 } 495 496 const stableChannel = "stable" 497 498 const pcGadgetYaml = ` 499 volumes: 500 pc: 501 bootloader: grub 502 ` 503 504 const pcUC20GadgetYaml = ` 505 volumes: 506 pc: 507 bootloader: grub 508 structure: 509 - name: ubuntu-seed 510 role: system-seed 511 type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B 512 size: 100M 513 - name: ubuntu-data 514 role: system-data 515 type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 516 size: 200M 517 ` 518 519 const piUC20GadgetYaml = ` 520 volumes: 521 pi: 522 schema: mbr 523 bootloader: u-boot 524 structure: 525 - name: ubuntu-seed 526 role: system-seed 527 type: 0C 528 size: 100M 529 - name: ubuntu-data 530 role: system-data 531 type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 532 size: 200M 533 ` 534 535 func (s *imageSuite) setupSnaps(c *C, publishers map[string]string, defaultsYaml string) { 536 gadgetYaml := pcGadgetYaml + defaultsYaml 537 if _, ok := publishers["pc"]; ok { 538 s.MakeAssertedSnap(c, packageGadget, [][]string{ 539 {"grub.conf", ""}, {"grub.cfg", "I'm a grub.cfg"}, 540 {"meta/gadget.yaml", gadgetYaml}, 541 }, snap.R(1), publishers["pc"]) 542 } 543 if _, ok := publishers["pc18"]; ok { 544 s.MakeAssertedSnap(c, packageGadgetWithBase, [][]string{ 545 {"grub.conf", ""}, {"grub.cfg", "I'm a grub.cfg"}, 546 {"meta/gadget.yaml", gadgetYaml}, 547 }, snap.R(4), publishers["pc18"]) 548 } 549 550 if _, ok := publishers["classic-gadget"]; ok { 551 s.MakeAssertedSnap(c, packageClassicGadget, [][]string{ 552 {"some-file", "Some file"}, 553 }, snap.R(5), publishers["classic-gadget"]) 554 } 555 556 if _, ok := publishers["classic-gadget18"]; ok { 557 s.MakeAssertedSnap(c, packageClassicGadget18, [][]string{ 558 {"some-file", "Some file"}, 559 }, snap.R(5), publishers["classic-gadget18"]) 560 } 561 562 if _, ok := publishers["pc-kernel"]; ok { 563 s.MakeAssertedSnap(c, packageKernel, nil, snap.R(2), publishers["pc-kernel"]) 564 } 565 566 s.MakeAssertedSnap(c, packageCore, nil, snap.R(3), "canonical") 567 568 s.MakeAssertedSnap(c, packageCore18, nil, snap.R(18), "canonical") 569 s.MakeAssertedSnap(c, snapdSnap, nil, snap.R(18), "canonical") 570 571 s.MakeAssertedSnap(c, otherBase, nil, snap.R(18), "other") 572 573 s.MakeAssertedSnap(c, snapReqCore16Base, nil, snap.R(16), "other") 574 575 s.MakeAssertedSnap(c, requiredSnap1, nil, snap.R(3), "other") 576 s.AssertedSnapInfo("required-snap1").LegacyEditedContact = "mailto:foo@example.com" 577 578 s.MakeAssertedSnap(c, requiredSnap18, nil, snap.R(6), "other") 579 s.AssertedSnapInfo("required-snap18").LegacyEditedContact = "mailto:foo@example.com" 580 581 s.MakeAssertedSnap(c, defaultTrackSnap18, nil, snap.R(5), "other") 582 583 s.MakeAssertedSnap(c, snapReqOtherBase, nil, snap.R(5), "other") 584 585 s.MakeAssertedSnap(c, snapReqContentProvider, nil, snap.R(5), "other") 586 587 s.MakeAssertedSnap(c, snapBaseNone, nil, snap.R(1), "other") 588 } 589 590 func (s *imageSuite) loadSeed(c *C, seeddir string) (essSnaps []*seed.Snap, runSnaps []*seed.Snap, roDB asserts.RODatabase) { 591 label := "" 592 systems, err := filepath.Glob(filepath.Join(seeddir, "systems", "*")) 593 c.Assert(err, IsNil) 594 if len(systems) > 1 { 595 c.Fatal("expected at most 1 Core 20 recovery system") 596 } else if len(systems) == 1 { 597 label = filepath.Base(systems[0]) 598 } 599 600 sd, err := seed.Open(seeddir, label) 601 c.Assert(err, IsNil) 602 603 db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ 604 Backstore: asserts.NewMemoryBackstore(), 605 Trusted: s.StoreSigning.Trusted, 606 }) 607 c.Assert(err, IsNil) 608 609 commitTo := func(b *asserts.Batch) error { 610 return b.CommitTo(db, nil) 611 } 612 613 err = sd.LoadAssertions(db, commitTo) 614 c.Assert(err, IsNil) 615 616 err = sd.LoadMeta(seed.AllModes, nil, timings.New(nil)) 617 c.Assert(err, IsNil) 618 619 essSnaps = sd.EssentialSnaps() 620 runSnaps, err = sd.ModeSnaps("run") 621 c.Assert(err, IsNil) 622 623 return essSnaps, runSnaps, db 624 } 625 626 func (s *imageSuite) TestSetupSeed(c *C) { 627 restore := image.MockTrusted(s.StoreSigning.Trusted) 628 defer restore() 629 630 preparedir := c.MkDir() 631 rootdir := filepath.Join(preparedir, "image") 632 blobdir := filepath.Join(rootdir, "var/lib/snapd/snaps") 633 s.setupSnaps(c, map[string]string{ 634 "pc": "canonical", 635 "pc-kernel": "canonical", 636 }, "") 637 638 gadgetWriteResolvedContentCalled := 0 639 restore = image.MockWriteResolvedContent(func(prepareImageDir string, info *gadget.Info, gadgetRoot, kernelRoot string) error { 640 c.Check(prepareImageDir, Equals, preparedir) 641 c.Check(gadgetRoot, Equals, filepath.Join(preparedir, "gadget")) 642 c.Check(kernelRoot, Equals, filepath.Join(preparedir, "kernel")) 643 gadgetWriteResolvedContentCalled++ 644 return nil 645 }) 646 defer restore() 647 648 opts := &image.Options{ 649 PrepareDir: preparedir, 650 Customizations: image.Customizations{ 651 Validation: "ignore", 652 }, 653 } 654 655 err := image.SetupSeed(s.tsto, s.model, opts) 656 c.Assert(err, IsNil) 657 658 // check seed 659 seeddir := filepath.Join(rootdir, "var/lib/snapd/seed") 660 seedsnapsdir := filepath.Join(seeddir, "snaps") 661 essSnaps, runSnaps, roDB := s.loadSeed(c, seeddir) 662 c.Check(essSnaps, HasLen, 3) 663 c.Check(runSnaps, HasLen, 1) 664 665 // check the files are in place 666 for i, name := range []string{"core", "pc-kernel", "pc"} { 667 info := s.AssertedSnapInfo(name) 668 fn := info.Filename() 669 p := filepath.Join(seedsnapsdir, fn) 670 c.Check(p, testutil.FilePresent) 671 c.Check(essSnaps[i], DeepEquals, &seed.Snap{ 672 Path: p, 673 674 SideInfo: &info.SideInfo, 675 676 EssentialType: info.Type(), 677 Essential: true, 678 Required: true, 679 680 Channel: stableChannel, 681 }) 682 // precondition 683 if name == "core" { 684 c.Check(essSnaps[i].SideInfo.SnapID, Equals, s.AssertedSnapID("core")) 685 } 686 } 687 c.Check(runSnaps[0], DeepEquals, &seed.Snap{ 688 Path: filepath.Join(seedsnapsdir, s.AssertedSnapInfo("required-snap1").Filename()), 689 690 SideInfo: &s.AssertedSnapInfo("required-snap1").SideInfo, 691 692 Required: true, 693 694 Channel: stableChannel, 695 }) 696 c.Check(runSnaps[0].Path, testutil.FilePresent) 697 698 l, err := ioutil.ReadDir(seedsnapsdir) 699 c.Assert(err, IsNil) 700 c.Check(l, HasLen, 4) 701 702 // check assertions 703 model1, err := s.model.Ref().Resolve(roDB.Find) 704 c.Assert(err, IsNil) 705 c.Check(model1, DeepEquals, s.model) 706 707 storeAccountKey := s.StoreSigning.StoreAccountKey("") 708 brandPubKey := s.Brands.PublicKey("my-brand") 709 _, err = roDB.Find(asserts.AccountKeyType, map[string]string{ 710 "public-key-sha3-384": storeAccountKey.PublicKeyID(), 711 }) 712 c.Check(err, IsNil) 713 _, err = roDB.Find(asserts.AccountKeyType, map[string]string{ 714 "public-key-sha3-384": brandPubKey.ID(), 715 }) 716 c.Check(err, IsNil) 717 718 // check the bootloader config 719 m, err := s.bootloader.GetBootVars("snap_kernel", "snap_core", "snap_menuentry") 720 c.Assert(err, IsNil) 721 c.Check(m["snap_kernel"], Equals, "pc-kernel_2.snap") 722 c.Check(m["snap_core"], Equals, "core_3.snap") 723 c.Check(m["snap_menuentry"], Equals, "my display name") 724 725 // check symlinks from snap blob dir 726 kernelInfo := s.AssertedSnapInfo("pc-kernel") 727 coreInfo := s.AssertedSnapInfo("core") 728 kernelBlob := filepath.Join(blobdir, kernelInfo.Filename()) 729 dst, err := os.Readlink(kernelBlob) 730 c.Assert(err, IsNil) 731 c.Check(dst, Equals, "../seed/snaps/pc-kernel_2.snap") 732 c.Check(kernelBlob, testutil.FilePresent) 733 734 coreBlob := filepath.Join(blobdir, coreInfo.Filename()) 735 dst, err = os.Readlink(coreBlob) 736 c.Assert(err, IsNil) 737 c.Check(dst, Equals, "../seed/snaps/core_3.snap") 738 c.Check(coreBlob, testutil.FilePresent) 739 740 c.Check(s.stderr.String(), Equals, "") 741 742 // check the downloads 743 c.Check(s.storeActionsBunchSizes, DeepEquals, []int{4}) 744 c.Check(s.storeActions[0], DeepEquals, &store.SnapAction{ 745 Action: "download", 746 InstanceName: "core", 747 Channel: stableChannel, 748 Flags: store.SnapActionIgnoreValidation, 749 }) 750 c.Check(s.storeActions[1], DeepEquals, &store.SnapAction{ 751 Action: "download", 752 InstanceName: "pc-kernel", 753 Channel: stableChannel, 754 Flags: store.SnapActionIgnoreValidation, 755 }) 756 c.Check(s.storeActions[2], DeepEquals, &store.SnapAction{ 757 Action: "download", 758 InstanceName: "pc", 759 Channel: stableChannel, 760 Flags: store.SnapActionIgnoreValidation, 761 }) 762 763 // content was resolved and written for ubuntu-image 764 c.Check(gadgetWriteResolvedContentCalled, Equals, 1) 765 } 766 767 func (s *imageSuite) TestSetupSeedLocalCoreBrandKernel(c *C) { 768 restore := image.MockTrusted(s.StoreSigning.Trusted) 769 defer restore() 770 771 rootdir := filepath.Join(c.MkDir(), "image") 772 s.setupSnaps(c, map[string]string{ 773 "pc": "canonical", 774 "pc-kernel": "my-brand", 775 }, "") 776 777 coreFn := snaptest.MakeTestSnapWithFiles(c, packageCore, [][]string{{"local", ""}}) 778 requiredSnap1Fn := snaptest.MakeTestSnapWithFiles(c, requiredSnap1, [][]string{{"local", ""}}) 779 780 opts := &image.Options{ 781 Snaps: []string{ 782 coreFn, 783 requiredSnap1Fn, 784 }, 785 PrepareDir: filepath.Dir(rootdir), 786 Customizations: image.Customizations{ 787 Validation: "ignore", 788 }, 789 } 790 791 err := image.SetupSeed(s.tsto, s.model, opts) 792 c.Assert(err, IsNil) 793 794 // check seed 795 seeddir := filepath.Join(rootdir, "var/lib/snapd/seed") 796 seedsnapsdir := filepath.Join(seeddir, "snaps") 797 essSnaps, runSnaps, roDB := s.loadSeed(c, seeddir) 798 c.Check(essSnaps, HasLen, 3) 799 c.Check(runSnaps, HasLen, 1) 800 801 // check the files are in place 802 for i, name := range []string{"core_x1.snap", "pc-kernel", "pc"} { 803 channel := stableChannel 804 info := s.AssertedSnapInfo(name) 805 var pinfo snap.PlaceInfo = info 806 var sideInfo *snap.SideInfo 807 var snapType snap.Type 808 if info == nil { 809 switch name { 810 case "core_x1.snap": 811 pinfo = snap.MinimalPlaceInfo("core", snap.R(-1)) 812 sideInfo = &snap.SideInfo{ 813 RealName: "core", 814 } 815 channel = "" 816 snapType = snap.TypeOS 817 } 818 } else { 819 sideInfo = &info.SideInfo 820 snapType = info.Type() 821 } 822 823 fn := pinfo.Filename() 824 p := filepath.Join(seedsnapsdir, fn) 825 c.Check(p, testutil.FilePresent) 826 c.Check(essSnaps[i], DeepEquals, &seed.Snap{ 827 Path: p, 828 829 SideInfo: sideInfo, 830 831 EssentialType: snapType, 832 Essential: true, 833 Required: true, 834 835 Channel: channel, 836 }) 837 } 838 c.Check(runSnaps[0], DeepEquals, &seed.Snap{ 839 Path: filepath.Join(seedsnapsdir, "required-snap1_x1.snap"), 840 841 SideInfo: &snap.SideInfo{ 842 RealName: "required-snap1", 843 }, 844 Required: true, 845 }) 846 c.Check(runSnaps[0].Path, testutil.FilePresent) 847 848 // check assertions 849 decls, err := roDB.FindMany(asserts.SnapDeclarationType, nil) 850 c.Assert(err, IsNil) 851 // nothing for core 852 c.Check(decls, HasLen, 2) 853 854 // check the bootloader config 855 m, err := s.bootloader.GetBootVars("snap_kernel", "snap_core") 856 c.Assert(err, IsNil) 857 c.Check(m["snap_kernel"], Equals, "pc-kernel_2.snap") 858 c.Assert(err, IsNil) 859 c.Check(m["snap_core"], Equals, "core_x1.snap") 860 861 c.Check(s.stderr.String(), Equals, "WARNING: \"core\", \"required-snap1\" installed from local snaps disconnected from a store cannot be refreshed subsequently!\n") 862 } 863 864 func (s *imageSuite) TestSetupSeedWithWideCohort(c *C) { 865 restore := image.MockTrusted(s.StoreSigning.Trusted) 866 defer restore() 867 868 rootdir := filepath.Join(c.MkDir(), "image") 869 s.setupSnaps(c, map[string]string{ 870 "pc": "canonical", 871 "pc-kernel": "canonical", 872 }, "") 873 874 snapFile := snaptest.MakeTestSnapWithFiles(c, devmodeSnap, nil) 875 876 opts := &image.Options{ 877 Snaps: []string{snapFile}, 878 879 PrepareDir: filepath.Dir(rootdir), 880 WideCohortKey: "wide-cohort-key", 881 } 882 883 err := image.SetupSeed(s.tsto, s.model, opts) 884 c.Assert(err, IsNil) 885 886 // check the downloads 887 c.Check(s.storeActionsBunchSizes, DeepEquals, []int{4}) 888 c.Check(s.storeActions[0], DeepEquals, &store.SnapAction{ 889 Action: "download", 890 InstanceName: "core", 891 Channel: stableChannel, 892 CohortKey: "wide-cohort-key", 893 Flags: store.SnapActionIgnoreValidation, 894 }) 895 c.Check(s.storeActions[1], DeepEquals, &store.SnapAction{ 896 Action: "download", 897 InstanceName: "pc-kernel", 898 Channel: stableChannel, 899 CohortKey: "wide-cohort-key", 900 Flags: store.SnapActionIgnoreValidation, 901 }) 902 c.Check(s.storeActions[2], DeepEquals, &store.SnapAction{ 903 Action: "download", 904 InstanceName: "pc", 905 Channel: stableChannel, 906 CohortKey: "wide-cohort-key", 907 Flags: store.SnapActionIgnoreValidation, 908 }) 909 c.Check(s.storeActions[3], DeepEquals, &store.SnapAction{ 910 Action: "download", 911 InstanceName: "required-snap1", 912 Channel: stableChannel, 913 CohortKey: "wide-cohort-key", 914 Flags: store.SnapActionIgnoreValidation, 915 }) 916 } 917 918 func (s *imageSuite) TestSetupSeedDevmodeSnap(c *C) { 919 restore := image.MockTrusted(s.StoreSigning.Trusted) 920 defer restore() 921 922 rootdir := filepath.Join(c.MkDir(), "image") 923 s.setupSnaps(c, map[string]string{ 924 "pc": "canonical", 925 "pc-kernel": "canonical", 926 }, "") 927 928 snapFile := snaptest.MakeTestSnapWithFiles(c, devmodeSnap, nil) 929 930 opts := &image.Options{ 931 Snaps: []string{snapFile}, 932 933 PrepareDir: filepath.Dir(rootdir), 934 Channel: "beta", 935 } 936 937 err := image.SetupSeed(s.tsto, s.model, opts) 938 c.Assert(err, IsNil) 939 940 // check seed 941 seeddir := filepath.Join(rootdir, "var/lib/snapd/seed") 942 seedsnapsdir := filepath.Join(seeddir, "snaps") 943 essSnaps, runSnaps, _ := s.loadSeed(c, seeddir) 944 c.Check(essSnaps, HasLen, 3) 945 c.Check(runSnaps, HasLen, 2) 946 947 for i, name := range []string{"core", "pc-kernel", "pc"} { 948 info := s.AssertedSnapInfo(name) 949 c.Check(essSnaps[i], DeepEquals, &seed.Snap{ 950 Path: filepath.Join(seedsnapsdir, info.Filename()), 951 SideInfo: &info.SideInfo, 952 EssentialType: info.Type(), 953 Essential: true, 954 Required: true, 955 Channel: "beta", 956 }) 957 } 958 c.Check(runSnaps[0], DeepEquals, &seed.Snap{ 959 Path: filepath.Join(seedsnapsdir, "required-snap1_3.snap"), 960 SideInfo: &s.AssertedSnapInfo("required-snap1").SideInfo, 961 Required: true, 962 Channel: "beta", 963 }) 964 // ensure local snaps are put last in seed.yaml 965 c.Check(runSnaps[1], DeepEquals, &seed.Snap{ 966 Path: filepath.Join(seedsnapsdir, "devmode-snap_x1.snap"), 967 SideInfo: &snap.SideInfo{ 968 RealName: "devmode-snap", 969 }, 970 DevMode: true, 971 // no channel for unasserted snaps 972 Channel: "", 973 }) 974 // check devmode-snap blob 975 c.Check(runSnaps[1].Path, testutil.FilePresent) 976 } 977 978 func (s *imageSuite) TestSetupSeedWithClassicSnapFails(c *C) { 979 restore := image.MockTrusted(s.StoreSigning.Trusted) 980 defer restore() 981 982 rootdir := filepath.Join(c.MkDir(), "image") 983 s.setupSnaps(c, map[string]string{ 984 "pc": "canonical", 985 "pc-kernel": "canonical", 986 }, "") 987 988 s.MakeAssertedSnap(c, classicSnap, nil, snap.R(1), "other") 989 990 opts := &image.Options{ 991 Snaps: []string{s.AssertedSnap("classic-snap")}, 992 993 PrepareDir: filepath.Dir(rootdir), 994 Channel: "beta", 995 } 996 997 err := image.SetupSeed(s.tsto, s.model, opts) 998 c.Assert(err, ErrorMatches, `cannot use classic snap "classic-snap" in a core system`) 999 } 1000 1001 func (s *imageSuite) TestSetupSeedWithBase(c *C) { 1002 restore := image.MockTrusted(s.StoreSigning.Trusted) 1003 defer restore() 1004 1005 // replace model with a model that uses core18 1006 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1007 "architecture": "amd64", 1008 "gadget": "pc18", 1009 "kernel": "pc-kernel", 1010 "base": "core18", 1011 "required-snaps": []interface{}{"other-base"}, 1012 }) 1013 1014 rootdir := filepath.Join(c.MkDir(), "image") 1015 blobdir := filepath.Join(rootdir, "/var/lib/snapd/snaps") 1016 s.setupSnaps(c, map[string]string{ 1017 "core18": "canonical", 1018 "pc18": "canonical", 1019 "pc-kernel": "canonical", 1020 "snapd": "canonical", 1021 "other-base": "other", 1022 }, "") 1023 1024 opts := &image.Options{ 1025 PrepareDir: filepath.Dir(rootdir), 1026 Customizations: image.Customizations{ 1027 Validation: "ignore", 1028 }, 1029 } 1030 1031 err := image.SetupSeed(s.tsto, model, opts) 1032 c.Assert(err, IsNil) 1033 1034 // check seed 1035 seeddir := filepath.Join(rootdir, "var/lib/snapd/seed") 1036 seedsnapsdir := filepath.Join(seeddir, "snaps") 1037 essSnaps, runSnaps, _ := s.loadSeed(c, seeddir) 1038 c.Check(essSnaps, HasLen, 4) 1039 c.Check(runSnaps, HasLen, 1) 1040 1041 // check the files are in place 1042 for i, name := range []string{"snapd", "core18_18.snap", "pc-kernel", "pc18"} { 1043 info := s.AssertedSnapInfo(name) 1044 if info == nil { 1045 switch name { 1046 case "core18_18.snap": 1047 info = &snap.Info{ 1048 SideInfo: snap.SideInfo{ 1049 SnapID: s.AssertedSnapID("core18"), 1050 RealName: "core18", 1051 Revision: snap.R("18"), 1052 }, 1053 SnapType: snap.TypeBase, 1054 } 1055 } 1056 } 1057 1058 fn := info.Filename() 1059 p := filepath.Join(seedsnapsdir, fn) 1060 c.Check(p, testutil.FilePresent) 1061 c.Check(essSnaps[i], DeepEquals, &seed.Snap{ 1062 Path: p, 1063 SideInfo: &info.SideInfo, 1064 EssentialType: info.Type(), 1065 Essential: true, 1066 Required: true, 1067 Channel: stableChannel, 1068 }) 1069 } 1070 c.Check(runSnaps[0], DeepEquals, &seed.Snap{ 1071 Path: filepath.Join(seedsnapsdir, "other-base_18.snap"), 1072 SideInfo: &s.AssertedSnapInfo("other-base").SideInfo, 1073 Required: true, 1074 Channel: stableChannel, 1075 }) 1076 c.Check(runSnaps[0].Path, testutil.FilePresent) 1077 1078 l, err := ioutil.ReadDir(seedsnapsdir) 1079 c.Assert(err, IsNil) 1080 c.Check(l, HasLen, 5) 1081 1082 // check the bootloader config 1083 m, err := s.bootloader.GetBootVars("snap_kernel", "snap_core") 1084 c.Assert(err, IsNil) 1085 c.Check(m["snap_kernel"], Equals, "pc-kernel_2.snap") 1086 c.Assert(err, IsNil) 1087 c.Check(m["snap_core"], Equals, "core18_18.snap") 1088 1089 // check symlinks from snap blob dir 1090 kernelInfo := s.AssertedSnapInfo("pc-kernel") 1091 baseInfo := s.AssertedSnapInfo("core18") 1092 kernelBlob := filepath.Join(blobdir, kernelInfo.Filename()) 1093 dst, err := os.Readlink(kernelBlob) 1094 c.Assert(err, IsNil) 1095 c.Check(dst, Equals, "../seed/snaps/pc-kernel_2.snap") 1096 c.Check(kernelBlob, testutil.FilePresent) 1097 1098 baseBlob := filepath.Join(blobdir, baseInfo.Filename()) 1099 dst, err = os.Readlink(baseBlob) 1100 c.Assert(err, IsNil) 1101 c.Check(dst, Equals, "../seed/snaps/core18_18.snap") 1102 c.Check(baseBlob, testutil.FilePresent) 1103 1104 c.Check(s.stderr.String(), Equals, "") 1105 1106 // check the downloads 1107 c.Check(s.storeActionsBunchSizes, DeepEquals, []int{5}) 1108 c.Check(s.storeActions[0], DeepEquals, &store.SnapAction{ 1109 Action: "download", 1110 InstanceName: "snapd", 1111 Channel: stableChannel, 1112 Flags: store.SnapActionIgnoreValidation, 1113 }) 1114 c.Check(s.storeActions[1], DeepEquals, &store.SnapAction{ 1115 Action: "download", 1116 InstanceName: "pc-kernel", 1117 Channel: stableChannel, 1118 Flags: store.SnapActionIgnoreValidation, 1119 }) 1120 c.Check(s.storeActions[2], DeepEquals, &store.SnapAction{ 1121 Action: "download", 1122 InstanceName: "core18", 1123 Channel: stableChannel, 1124 Flags: store.SnapActionIgnoreValidation, 1125 }) 1126 c.Check(s.storeActions[3], DeepEquals, &store.SnapAction{ 1127 Action: "download", 1128 InstanceName: "pc18", 1129 Channel: stableChannel, 1130 Flags: store.SnapActionIgnoreValidation, 1131 }) 1132 } 1133 1134 func (s *imageSuite) TestSetupSeedWithBaseWithCloudConf(c *C) { 1135 restore := image.MockTrusted(s.StoreSigning.Trusted) 1136 defer restore() 1137 1138 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1139 "architecture": "amd64", 1140 "gadget": "pc18", 1141 "kernel": "pc-kernel", 1142 "base": "core18", 1143 }) 1144 1145 rootdir := filepath.Join(c.MkDir(), "image") 1146 s.setupSnaps(c, map[string]string{ 1147 "core18": "canonical", 1148 "pc-kernel": "canonical", 1149 "snapd": "canonical", 1150 }, "") 1151 s.MakeAssertedSnap(c, packageGadgetWithBase, [][]string{ 1152 {"grub.conf", ""}, 1153 {"grub.cfg", "I'm a grub.cfg"}, 1154 {"cloud.conf", "# cloud config"}, 1155 {"meta/gadget.yaml", pcGadgetYaml}, 1156 }, snap.R(5), "canonical") 1157 1158 opts := &image.Options{ 1159 PrepareDir: filepath.Dir(rootdir), 1160 } 1161 1162 err := image.SetupSeed(s.tsto, model, opts) 1163 c.Assert(err, IsNil) 1164 1165 c.Check(filepath.Join(rootdir, "/etc/cloud/cloud.cfg"), testutil.FileEquals, "# cloud config") 1166 } 1167 1168 func (s *imageSuite) TestSetupSeedWithBaseWithCustomizations(c *C) { 1169 restore := image.MockTrusted(s.StoreSigning.Trusted) 1170 defer restore() 1171 1172 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1173 "architecture": "amd64", 1174 "gadget": "pc18", 1175 "kernel": "pc-kernel", 1176 "base": "core18", 1177 }) 1178 1179 tmpdir := c.MkDir() 1180 rootdir := filepath.Join(tmpdir, "image") 1181 cloudInitUserData := filepath.Join(tmpdir, "cloudstuff") 1182 err := ioutil.WriteFile(cloudInitUserData, []byte(`# user cloud data`), 0644) 1183 c.Assert(err, IsNil) 1184 s.setupSnaps(c, map[string]string{ 1185 "core18": "canonical", 1186 "pc-kernel": "canonical", 1187 "snapd": "canonical", 1188 }, "") 1189 s.MakeAssertedSnap(c, packageGadgetWithBase, [][]string{ 1190 {"grub.conf", ""}, 1191 {"grub.cfg", "I'm a grub.cfg"}, 1192 {"meta/gadget.yaml", pcGadgetYaml}, 1193 }, snap.R(5), "canonical") 1194 1195 opts := &image.Options{ 1196 PrepareDir: filepath.Dir(rootdir), 1197 Customizations: image.Customizations{ 1198 ConsoleConf: "disabled", 1199 CloudInitUserData: cloudInitUserData, 1200 }, 1201 } 1202 1203 err = image.SetupSeed(s.tsto, model, opts) 1204 c.Assert(err, IsNil) 1205 1206 // check customization impl files were written 1207 varCloudDir := filepath.Join(rootdir, "/var/lib/cloud/seed/nocloud-net") 1208 c.Check(filepath.Join(varCloudDir, "meta-data"), testutil.FileEquals, "instance-id: nocloud-static\n") 1209 c.Check(filepath.Join(varCloudDir, "user-data"), testutil.FileEquals, "# user cloud data") 1210 // console-conf disable 1211 c.Check(filepath.Join(rootdir, "_writable_defaults", "var/lib/console-conf/complete"), testutil.FilePresent) 1212 } 1213 1214 func (s *imageSuite) TestPrepareUC20CustomizationsUnsupported(c *C) { 1215 restore := image.MockTrusted(s.StoreSigning.Trusted) 1216 defer restore() 1217 1218 model := s.makeUC20Model(nil) 1219 fn := filepath.Join(c.MkDir(), "model.assertion") 1220 err := ioutil.WriteFile(fn, asserts.Encode(model), 0644) 1221 c.Assert(err, IsNil) 1222 1223 err = image.Prepare(&image.Options{ 1224 ModelFile: fn, 1225 Customizations: image.Customizations{ 1226 ConsoleConf: "disabled", 1227 CloudInitUserData: "cloud-init-user-data", 1228 }, 1229 }) 1230 c.Assert(err, ErrorMatches, `cannot support with UC20\+ model requested customizations: console-conf disable, cloud-init user-data`) 1231 } 1232 1233 func (s *imageSuite) TestPrepareClassicCustomizationsUnsupported(c *C) { 1234 restore := image.MockTrusted(s.StoreSigning.Trusted) 1235 defer restore() 1236 1237 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1238 "classic": "true", 1239 }) 1240 fn := filepath.Join(c.MkDir(), "model.assertion") 1241 err := ioutil.WriteFile(fn, asserts.Encode(model), 0644) 1242 c.Assert(err, IsNil) 1243 1244 err = image.Prepare(&image.Options{ 1245 Classic: true, 1246 ModelFile: fn, 1247 Customizations: image.Customizations{ 1248 ConsoleConf: "disabled", 1249 CloudInitUserData: "cloud-init-user-data", 1250 BootFlags: []string{"boot-flag"}, 1251 }, 1252 }) 1253 c.Assert(err, ErrorMatches, `cannot support with classic model requested customizations: console-conf disable, boot flags \(boot-flag\)`) 1254 } 1255 1256 func (s *imageSuite) TestPrepareUC18CustomizationsUnsupported(c *C) { 1257 restore := image.MockTrusted(s.StoreSigning.Trusted) 1258 defer restore() 1259 1260 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1261 "architecture": "amd64", 1262 "gadget": "pc18", 1263 "kernel": "pc-kernel", 1264 "base": "core18", 1265 }) 1266 fn := filepath.Join(c.MkDir(), "model.assertion") 1267 err := ioutil.WriteFile(fn, asserts.Encode(model), 0644) 1268 c.Assert(err, IsNil) 1269 1270 err = image.Prepare(&image.Options{ 1271 ModelFile: fn, 1272 Customizations: image.Customizations{ 1273 ConsoleConf: "disabled", 1274 CloudInitUserData: "cloud-init-user-data", 1275 BootFlags: []string{"boot-flag"}, 1276 }, 1277 }) 1278 c.Assert(err, ErrorMatches, `cannot support with UC16/18 model requested customizations: boot flags \(boot-flag\)`) 1279 } 1280 1281 func (s *imageSuite) TestSetupSeedWithBaseLegacySnap(c *C) { 1282 restore := image.MockTrusted(s.StoreSigning.Trusted) 1283 defer restore() 1284 1285 // replace model with a model that uses core18 1286 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1287 "architecture": "amd64", 1288 "gadget": "pc18", 1289 "kernel": "pc-kernel", 1290 "base": "core18", 1291 "required-snaps": []interface{}{"required-snap1"}, 1292 }) 1293 1294 // required-snap1 needs core, for backward compatibility 1295 // we will add it implicitly but warn about this 1296 1297 rootdir := filepath.Join(c.MkDir(), "image") 1298 s.setupSnaps(c, map[string]string{ 1299 "core18": "canonical", 1300 "pc18": "canonical", 1301 "pc-kernel": "canonical", 1302 "snapd": "canonical", 1303 }, "") 1304 1305 opts := &image.Options{ 1306 PrepareDir: filepath.Dir(rootdir), 1307 Customizations: image.Customizations{ 1308 Validation: "ignore", 1309 }, 1310 } 1311 1312 err := image.SetupSeed(s.tsto, model, opts) 1313 c.Assert(err, IsNil) 1314 1315 // check seed 1316 seeddir := filepath.Join(rootdir, "var/lib/snapd/seed") 1317 seedsnapsdir := filepath.Join(seeddir, "snaps") 1318 essSnaps, runSnaps, _ := s.loadSeed(c, seeddir) 1319 c.Check(essSnaps, HasLen, 4) 1320 c.Check(runSnaps, HasLen, 2) 1321 1322 // check the files are in place 1323 for i, name := range []string{"snapd", "core18_18.snap", "pc-kernel", "pc18"} { 1324 info := s.AssertedSnapInfo(name) 1325 if info == nil { 1326 switch name { 1327 case "core18_18.snap": 1328 info = &snap.Info{ 1329 SideInfo: snap.SideInfo{ 1330 SnapID: s.AssertedSnapID("core18"), 1331 RealName: "core18", 1332 Revision: snap.R("18"), 1333 }, 1334 SnapType: snap.TypeBase, 1335 } 1336 } 1337 } 1338 1339 fn := info.Filename() 1340 p := filepath.Join(seedsnapsdir, fn) 1341 c.Check(p, testutil.FilePresent) 1342 c.Check(essSnaps[i], DeepEquals, &seed.Snap{ 1343 Path: p, 1344 SideInfo: &info.SideInfo, 1345 EssentialType: info.Type(), 1346 Essential: true, 1347 Required: true, 1348 Channel: stableChannel, 1349 }) 1350 } 1351 c.Check(runSnaps[0], DeepEquals, &seed.Snap{ 1352 Path: filepath.Join(seedsnapsdir, s.AssertedSnapInfo("core").Filename()), 1353 SideInfo: &s.AssertedSnapInfo("core").SideInfo, 1354 Required: false, // strange but expected 1355 Channel: stableChannel, 1356 }) 1357 c.Check(runSnaps[0].Path, testutil.FilePresent) 1358 c.Check(runSnaps[1], DeepEquals, &seed.Snap{ 1359 Path: filepath.Join(seedsnapsdir, s.AssertedSnapInfo("required-snap1").Filename()), 1360 SideInfo: &s.AssertedSnapInfo("required-snap1").SideInfo, 1361 Required: true, 1362 Channel: stableChannel, 1363 }) 1364 c.Check(runSnaps[1].Path, testutil.FilePresent) 1365 1366 l, err := ioutil.ReadDir(seedsnapsdir) 1367 c.Assert(err, IsNil) 1368 c.Check(l, HasLen, 6) 1369 1370 // check the bootloader config 1371 m, err := s.bootloader.GetBootVars("snap_kernel", "snap_core") 1372 c.Assert(err, IsNil) 1373 c.Check(m["snap_kernel"], Equals, "pc-kernel_2.snap") 1374 c.Assert(err, IsNil) 1375 c.Check(m["snap_core"], Equals, "core18_18.snap") 1376 1377 c.Check(s.stderr.String(), Equals, "WARNING: model has base \"core18\" but some snaps (\"required-snap1\") require \"core\" as base as well, for compatibility it was added implicitly, adding \"core\" explicitly is recommended\n") 1378 1379 // current snap info sent 1380 c.Check(s.curSnaps, HasLen, 2) 1381 c.Check(s.curSnaps[0], HasLen, 0) 1382 c.Check(s.curSnaps[1], DeepEquals, []*store.CurrentSnap{ 1383 { 1384 InstanceName: "snapd", 1385 SnapID: s.AssertedSnapID("snapd"), 1386 Revision: snap.R(18), 1387 TrackingChannel: "stable", 1388 Epoch: snap.E("0"), 1389 IgnoreValidation: true, 1390 }, 1391 { 1392 InstanceName: "pc-kernel", 1393 SnapID: s.AssertedSnapID("pc-kernel"), 1394 Revision: snap.R(2), 1395 TrackingChannel: "stable", 1396 Epoch: snap.E("0"), 1397 IgnoreValidation: true, 1398 }, 1399 { 1400 InstanceName: "core18", 1401 SnapID: s.AssertedSnapID("core18"), 1402 Revision: snap.R(18), 1403 TrackingChannel: "stable", 1404 Epoch: snap.E("0"), 1405 IgnoreValidation: true, 1406 }, 1407 { 1408 InstanceName: "pc18", 1409 SnapID: s.AssertedSnapID("pc18"), 1410 Revision: snap.R(4), 1411 TrackingChannel: "stable", 1412 Epoch: snap.E("0"), 1413 IgnoreValidation: true, 1414 }, 1415 { 1416 InstanceName: "required-snap1", 1417 SnapID: s.AssertedSnapID("required-snap1"), 1418 Revision: snap.R(3), 1419 TrackingChannel: "stable", 1420 Epoch: snap.E("0"), 1421 IgnoreValidation: true, 1422 }, 1423 }) 1424 } 1425 1426 func (s *imageSuite) TestSetupSeedWithBaseDefaultTrackSnap(c *C) { 1427 restore := image.MockTrusted(s.StoreSigning.Trusted) 1428 defer restore() 1429 1430 // replace model with a model that uses core18 1431 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1432 "architecture": "amd64", 1433 "gadget": "pc18", 1434 "kernel": "pc-kernel", 1435 "base": "core18", 1436 "required-snaps": []interface{}{"default-track-snap18"}, 1437 }) 1438 1439 // default-track-snap18 has a default-track 1440 1441 rootdir := filepath.Join(c.MkDir(), "image") 1442 s.setupSnaps(c, map[string]string{ 1443 "core18": "canonical", 1444 "pc18": "canonical", 1445 "pc-kernel": "canonical", 1446 "snapd": "canonical", 1447 }, "") 1448 1449 opts := &image.Options{ 1450 PrepareDir: filepath.Dir(rootdir), 1451 Customizations: image.Customizations{ 1452 Validation: "ignore", 1453 }, 1454 } 1455 1456 err := image.SetupSeed(s.tsto, model, opts) 1457 c.Assert(err, IsNil) 1458 1459 // check seed 1460 seeddir := filepath.Join(rootdir, "var/lib/snapd/seed") 1461 seedsnapsdir := filepath.Join(seeddir, "snaps") 1462 essSnaps, runSnaps, _ := s.loadSeed(c, seeddir) 1463 c.Check(essSnaps, HasLen, 4) 1464 c.Check(runSnaps, HasLen, 1) 1465 1466 // check the files are in place 1467 for i, name := range []string{"snapd", "core18_18.snap", "pc-kernel", "pc18"} { 1468 info := s.AssertedSnapInfo(name) 1469 if info == nil { 1470 switch name { 1471 case "core18_18.snap": 1472 info = &snap.Info{ 1473 SideInfo: snap.SideInfo{ 1474 SnapID: s.AssertedSnapID("core18"), 1475 RealName: "core18", 1476 Revision: snap.R("18"), 1477 }, 1478 SnapType: snap.TypeBase, 1479 } 1480 } 1481 } 1482 1483 fn := info.Filename() 1484 p := filepath.Join(seedsnapsdir, fn) 1485 c.Check(p, testutil.FilePresent) 1486 c.Check(essSnaps[i], DeepEquals, &seed.Snap{ 1487 Path: p, 1488 SideInfo: &info.SideInfo, 1489 EssentialType: info.Type(), 1490 Essential: true, 1491 Required: true, 1492 Channel: stableChannel, 1493 }) 1494 } 1495 c.Check(runSnaps[0], DeepEquals, &seed.Snap{ 1496 Path: filepath.Join(seedsnapsdir, s.AssertedSnapInfo("default-track-snap18").Filename()), 1497 SideInfo: &s.AssertedSnapInfo("default-track-snap18").SideInfo, 1498 Required: true, 1499 Channel: "default-track/stable", 1500 }) 1501 c.Check(runSnaps[0].Path, testutil.FilePresent) 1502 1503 l, err := ioutil.ReadDir(seedsnapsdir) 1504 c.Assert(err, IsNil) 1505 c.Check(l, HasLen, 5) 1506 1507 c.Check(s.stderr.String(), Equals, "") 1508 } 1509 1510 func (s *imageSuite) TestSetupSeedKernelPublisherMismatch(c *C) { 1511 restore := image.MockTrusted(s.StoreSigning.Trusted) 1512 defer restore() 1513 1514 rootdir := filepath.Join(c.MkDir(), "image") 1515 s.setupSnaps(c, map[string]string{ 1516 "pc": "canonical", 1517 "pc-kernel": "other", 1518 }, "") 1519 1520 opts := &image.Options{ 1521 PrepareDir: filepath.Dir(rootdir), 1522 } 1523 1524 err := image.SetupSeed(s.tsto, s.model, opts) 1525 c.Assert(err, ErrorMatches, `cannot use kernel "pc-kernel" published by "other" for model by "my-brand"`) 1526 } 1527 1528 func (s *imageSuite) TestInstallCloudConfigNoConfig(c *C) { 1529 targetDir := c.MkDir() 1530 emptyGadgetDir := c.MkDir() 1531 1532 err := image.InstallCloudConfig(targetDir, emptyGadgetDir) 1533 c.Assert(err, IsNil) 1534 c.Check(osutil.FileExists(filepath.Join(targetDir, "etc/cloud")), Equals, false) 1535 } 1536 1537 func (s *imageSuite) TestInstallCloudConfigWithCloudConfig(c *C) { 1538 canary := []byte("ni! ni! ni!") 1539 1540 targetDir := c.MkDir() 1541 gadgetDir := c.MkDir() 1542 err := ioutil.WriteFile(filepath.Join(gadgetDir, "cloud.conf"), canary, 0644) 1543 c.Assert(err, IsNil) 1544 1545 err = image.InstallCloudConfig(targetDir, gadgetDir) 1546 c.Assert(err, IsNil) 1547 c.Check(filepath.Join(targetDir, "etc/cloud/cloud.cfg"), testutil.FileEquals, canary) 1548 } 1549 1550 func (s *imageSuite) TestSetupSeedLocalSnapsWithStoreAsserts(c *C) { 1551 restore := image.MockTrusted(s.StoreSigning.Trusted) 1552 defer restore() 1553 1554 rootdir := filepath.Join(c.MkDir(), "image") 1555 s.setupSnaps(c, map[string]string{ 1556 "pc": "canonical", 1557 "pc-kernel": "my-brand", 1558 }, "") 1559 1560 opts := &image.Options{ 1561 Snaps: []string{ 1562 s.AssertedSnap("core"), 1563 s.AssertedSnap("required-snap1"), 1564 }, 1565 PrepareDir: filepath.Dir(rootdir), 1566 } 1567 1568 err := image.SetupSeed(s.tsto, s.model, opts) 1569 c.Assert(err, IsNil) 1570 1571 // check seed 1572 seeddir := filepath.Join(rootdir, "var/lib/snapd/seed") 1573 seedsnapsdir := filepath.Join(seeddir, "snaps") 1574 essSnaps, runSnaps, roDB := s.loadSeed(c, seeddir) 1575 c.Check(essSnaps, HasLen, 3) 1576 c.Check(runSnaps, HasLen, 1) 1577 1578 // check the files are in place 1579 for i, name := range []string{"core_3.snap", "pc-kernel", "pc"} { 1580 info := s.AssertedSnapInfo(name) 1581 if info == nil { 1582 switch name { 1583 case "core_3.snap": 1584 info = &snap.Info{ 1585 SideInfo: snap.SideInfo{ 1586 RealName: "core", 1587 SnapID: s.AssertedSnapID("core"), 1588 Revision: snap.R(3), 1589 }, 1590 SnapType: snap.TypeOS, 1591 } 1592 default: 1593 c.Errorf("cannot have %s", name) 1594 } 1595 } 1596 1597 fn := info.Filename() 1598 p := filepath.Join(seedsnapsdir, fn) 1599 c.Check(p, testutil.FilePresent) 1600 c.Check(essSnaps[i], DeepEquals, &seed.Snap{ 1601 Path: p, 1602 SideInfo: &info.SideInfo, 1603 EssentialType: info.Type(), 1604 Essential: true, 1605 Required: true, 1606 Channel: stableChannel, 1607 }) 1608 } 1609 c.Check(runSnaps[0], DeepEquals, &seed.Snap{ 1610 Path: filepath.Join(seedsnapsdir, "required-snap1_3.snap"), 1611 Required: true, 1612 SideInfo: &snap.SideInfo{ 1613 RealName: "required-snap1", 1614 SnapID: s.AssertedSnapID("required-snap1"), 1615 Revision: snap.R(3), 1616 }, 1617 Channel: stableChannel, 1618 }) 1619 c.Check(runSnaps[0].Path, testutil.FilePresent) 1620 1621 l, err := ioutil.ReadDir(seedsnapsdir) 1622 c.Assert(err, IsNil) 1623 c.Check(l, HasLen, 4) 1624 1625 // check assertions 1626 decls, err := roDB.FindMany(asserts.SnapDeclarationType, nil) 1627 c.Assert(err, IsNil) 1628 c.Check(decls, HasLen, 4) 1629 1630 // check the bootloader config 1631 m, err := s.bootloader.GetBootVars("snap_kernel", "snap_core") 1632 c.Assert(err, IsNil) 1633 c.Check(m["snap_kernel"], Equals, "pc-kernel_2.snap") 1634 c.Assert(err, IsNil) 1635 c.Check(m["snap_core"], Equals, "core_3.snap") 1636 1637 c.Check(s.stderr.String(), Equals, `WARNING: proceeding to download snaps ignoring validations, this default will change in the future. For now use --validation=enforce for validations to be taken into account, pass instead --validation=ignore to preserve current behavior going forward`+"\n") 1638 1639 // current snap info sent 1640 c.Check(s.curSnaps, HasLen, 1) 1641 c.Check(s.curSnaps[0], DeepEquals, []*store.CurrentSnap{ 1642 { 1643 InstanceName: "core", 1644 SnapID: s.AssertedSnapID("core"), 1645 Revision: snap.R(3), 1646 TrackingChannel: "stable", 1647 Epoch: snap.E("0"), 1648 IgnoreValidation: true, 1649 }, 1650 { 1651 InstanceName: "required-snap1", 1652 SnapID: s.AssertedSnapID("required-snap1"), 1653 Revision: snap.R(3), 1654 TrackingChannel: "stable", 1655 Epoch: snap.E("0"), 1656 IgnoreValidation: true, 1657 }, 1658 }) 1659 } 1660 1661 func (s *imageSuite) TestSetupSeedLocalSnapsWithChannels(c *C) { 1662 restore := image.MockTrusted(s.StoreSigning.Trusted) 1663 defer restore() 1664 1665 rootdir := filepath.Join(c.MkDir(), "image") 1666 s.setupSnaps(c, map[string]string{ 1667 "pc": "canonical", 1668 "pc-kernel": "my-brand", 1669 }, "") 1670 1671 opts := &image.Options{ 1672 Snaps: []string{ 1673 "core", 1674 s.AssertedSnap("required-snap1"), 1675 }, 1676 PrepareDir: filepath.Dir(rootdir), 1677 SnapChannels: map[string]string{ 1678 "core": "candidate", 1679 // keep this comment for gofmt 1.9 1680 s.AssertedSnap("required-snap1"): "edge", 1681 }, 1682 } 1683 1684 err := image.SetupSeed(s.tsto, s.model, opts) 1685 c.Assert(err, IsNil) 1686 1687 // check seed 1688 seeddir := filepath.Join(rootdir, "var/lib/snapd/seed") 1689 seedsnapsdir := filepath.Join(seeddir, "snaps") 1690 essSnaps, runSnaps, _ := s.loadSeed(c, seeddir) 1691 c.Check(essSnaps, HasLen, 3) 1692 c.Check(runSnaps, HasLen, 1) 1693 1694 // check the files are in place 1695 for i, name := range []string{"core_3.snap", "pc-kernel", "pc"} { 1696 info := s.AssertedSnapInfo(name) 1697 channel := stableChannel 1698 if info == nil { 1699 switch name { 1700 case "core_3.snap": 1701 info = &snap.Info{ 1702 SideInfo: snap.SideInfo{ 1703 RealName: "core", 1704 SnapID: s.AssertedSnapID("core"), 1705 Revision: snap.R(3), 1706 }, 1707 SnapType: snap.TypeOS, 1708 } 1709 channel = "candidate" 1710 default: 1711 c.Errorf("cannot have %s", name) 1712 } 1713 } 1714 1715 fn := info.Filename() 1716 p := filepath.Join(seedsnapsdir, fn) 1717 c.Check(p, testutil.FilePresent) 1718 c.Check(essSnaps[i], DeepEquals, &seed.Snap{ 1719 Path: p, 1720 SideInfo: &info.SideInfo, 1721 EssentialType: info.Type(), 1722 Essential: true, 1723 Required: true, 1724 Channel: channel, 1725 }) 1726 } 1727 c.Check(runSnaps[0], DeepEquals, &seed.Snap{ 1728 Path: filepath.Join(seedsnapsdir, "required-snap1_3.snap"), 1729 Required: true, 1730 SideInfo: &snap.SideInfo{ 1731 RealName: "required-snap1", 1732 SnapID: s.AssertedSnapID("required-snap1"), 1733 Revision: snap.R(3), 1734 }, 1735 Channel: "edge", 1736 }) 1737 c.Check(runSnaps[0].Path, testutil.FilePresent) 1738 } 1739 1740 func (s *imageSuite) TestSetupSeedLocalSnapsWithStoreAssertsValidationEnforce(c *C) { 1741 restore := image.MockTrusted(s.StoreSigning.Trusted) 1742 defer restore() 1743 1744 rootdir := filepath.Join(c.MkDir(), "image") 1745 s.setupSnaps(c, map[string]string{ 1746 "pc": "canonical", 1747 "pc-kernel": "my-brand", 1748 }, "") 1749 1750 opts := &image.Options{ 1751 Snaps: []string{ 1752 s.AssertedSnap("pc"), 1753 }, 1754 PrepareDir: filepath.Dir(rootdir), 1755 Customizations: image.Customizations{ 1756 Validation: "enforce", 1757 }, 1758 } 1759 1760 err := image.SetupSeed(s.tsto, s.model, opts) 1761 c.Assert(err, IsNil) 1762 1763 // check seed 1764 seeddir := filepath.Join(rootdir, "var/lib/snapd/seed") 1765 seedsnapsdir := filepath.Join(seeddir, "snaps") 1766 essSnaps, runSnaps, roDB := s.loadSeed(c, seeddir) 1767 c.Check(essSnaps, HasLen, 3) 1768 c.Check(runSnaps, HasLen, 1) 1769 1770 // check the files are in place 1771 for i, name := range []string{"core_3.snap", "pc-kernel", "pc"} { 1772 info := s.AssertedSnapInfo(name) 1773 if info == nil { 1774 switch name { 1775 case "core_3.snap": 1776 info = &snap.Info{ 1777 SideInfo: snap.SideInfo{ 1778 RealName: "core", 1779 SnapID: s.AssertedSnapID("core"), 1780 Revision: snap.R(3), 1781 }, 1782 SnapType: snap.TypeOS, 1783 } 1784 default: 1785 c.Errorf("cannot have %s", name) 1786 } 1787 } 1788 1789 fn := info.Filename() 1790 p := filepath.Join(seedsnapsdir, fn) 1791 c.Check(p, testutil.FilePresent) 1792 c.Check(essSnaps[i], DeepEquals, &seed.Snap{ 1793 Path: p, 1794 SideInfo: &info.SideInfo, 1795 EssentialType: info.Type(), 1796 Essential: true, 1797 Required: true, 1798 Channel: stableChannel, 1799 }) 1800 } 1801 c.Check(runSnaps[0], DeepEquals, &seed.Snap{ 1802 Path: filepath.Join(seedsnapsdir, "required-snap1_3.snap"), 1803 Required: true, 1804 SideInfo: &snap.SideInfo{ 1805 RealName: "required-snap1", 1806 SnapID: s.AssertedSnapID("required-snap1"), 1807 Revision: snap.R(3), 1808 LegacyEditedContact: "mailto:foo@example.com", 1809 }, 1810 Channel: stableChannel, 1811 }) 1812 c.Check(runSnaps[0].Path, testutil.FilePresent) 1813 1814 l, err := ioutil.ReadDir(seedsnapsdir) 1815 c.Assert(err, IsNil) 1816 c.Check(l, HasLen, 4) 1817 1818 // check assertions 1819 decls, err := roDB.FindMany(asserts.SnapDeclarationType, nil) 1820 c.Assert(err, IsNil) 1821 c.Check(decls, HasLen, 4) 1822 1823 // check the bootloader config 1824 m, err := s.bootloader.GetBootVars("snap_kernel", "snap_core") 1825 c.Assert(err, IsNil) 1826 c.Check(m["snap_kernel"], Equals, "pc-kernel_2.snap") 1827 c.Assert(err, IsNil) 1828 c.Check(m["snap_core"], Equals, "core_3.snap") 1829 1830 c.Check(s.stderr.String(), Equals, "") 1831 1832 // check the downloads 1833 c.Check(s.storeActionsBunchSizes, DeepEquals, []int{3}) 1834 c.Check(s.storeActions[0], DeepEquals, &store.SnapAction{ 1835 Action: "download", 1836 InstanceName: "core", 1837 Channel: stableChannel, 1838 Flags: store.SnapActionEnforceValidation, 1839 }) 1840 c.Check(s.storeActions[1], DeepEquals, &store.SnapAction{ 1841 Action: "download", 1842 InstanceName: "pc-kernel", 1843 Channel: stableChannel, 1844 Flags: store.SnapActionEnforceValidation, 1845 }) 1846 c.Check(s.storeActions[2], DeepEquals, &store.SnapAction{ 1847 Action: "download", 1848 InstanceName: "required-snap1", 1849 Channel: stableChannel, 1850 Flags: store.SnapActionEnforceValidation, 1851 }) 1852 c.Check(s.curSnaps, HasLen, 1) 1853 c.Check(s.curSnaps[0], DeepEquals, []*store.CurrentSnap{ 1854 { 1855 InstanceName: "pc", 1856 SnapID: s.AssertedSnapID("pc"), 1857 Revision: snap.R(1), 1858 TrackingChannel: "stable", 1859 Epoch: snap.E("0"), 1860 IgnoreValidation: false, 1861 }, 1862 }) 1863 } 1864 1865 func (s *imageSuite) TestCannotCreateGadgetUnpackDir(c *C) { 1866 fn := filepath.Join(c.MkDir(), "model.assertion") 1867 err := ioutil.WriteFile(fn, asserts.Encode(s.model), 0644) 1868 c.Assert(err, IsNil) 1869 1870 err = image.Prepare(&image.Options{ 1871 ModelFile: fn, 1872 Channel: "stable", 1873 PrepareDir: "/no-where", 1874 }) 1875 c.Assert(err, ErrorMatches, `cannot create unpack dir "/no-where/gadget": mkdir .*`) 1876 } 1877 1878 func (s *imageSuite) TestNoLocalParallelSnapInstances(c *C) { 1879 fn := filepath.Join(c.MkDir(), "model.assertion") 1880 err := ioutil.WriteFile(fn, asserts.Encode(s.model), 0644) 1881 c.Assert(err, IsNil) 1882 1883 err = image.Prepare(&image.Options{ 1884 ModelFile: fn, 1885 Snaps: []string{"foo_instance"}, 1886 }) 1887 c.Assert(err, ErrorMatches, `cannot use snap "foo_instance", parallel snap instances are unsupported`) 1888 } 1889 1890 func (s *imageSuite) TestNoInvalidSnapNames(c *C) { 1891 fn := filepath.Join(c.MkDir(), "model.assertion") 1892 err := ioutil.WriteFile(fn, asserts.Encode(s.model), 0644) 1893 c.Assert(err, IsNil) 1894 1895 err = image.Prepare(&image.Options{ 1896 ModelFile: fn, 1897 Snaps: []string{"foo.invalid.name"}, 1898 }) 1899 c.Assert(err, ErrorMatches, `invalid snap name: "foo.invalid.name"`) 1900 } 1901 1902 func (s *imageSuite) TestPrepareInvalidChannel(c *C) { 1903 fn := filepath.Join(c.MkDir(), "model.assertion") 1904 err := ioutil.WriteFile(fn, asserts.Encode(s.model), 0644) 1905 c.Assert(err, IsNil) 1906 1907 err = image.Prepare(&image.Options{ 1908 ModelFile: fn, 1909 Channel: "x/x/x/x", 1910 }) 1911 c.Assert(err, ErrorMatches, `cannot use global default option channel: channel name has too many components: x/x/x/x`) 1912 } 1913 1914 func (s *imageSuite) TestPrepareClassicModeNoClassicModel(c *C) { 1915 fn := filepath.Join(c.MkDir(), "model.assertion") 1916 err := ioutil.WriteFile(fn, asserts.Encode(s.model), 0644) 1917 c.Assert(err, IsNil) 1918 1919 err = image.Prepare(&image.Options{ 1920 Classic: true, 1921 ModelFile: fn, 1922 }) 1923 c.Assert(err, ErrorMatches, "cannot prepare the image for a core model with --classic mode specified") 1924 } 1925 1926 func (s *imageSuite) TestPrepareClassicModelNoClassicMode(c *C) { 1927 restore := image.MockTrusted(s.StoreSigning.Trusted) 1928 defer restore() 1929 1930 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1931 "classic": "true", 1932 }) 1933 1934 fn := filepath.Join(c.MkDir(), "model.assertion") 1935 err := ioutil.WriteFile(fn, asserts.Encode(model), 0644) 1936 c.Assert(err, IsNil) 1937 1938 err = image.Prepare(&image.Options{ 1939 ModelFile: fn, 1940 }) 1941 c.Assert(err, ErrorMatches, "--classic mode is required to prepare the image for a classic model") 1942 } 1943 1944 func (s *imageSuite) TestPrepareClassicModelArchOverrideFails(c *C) { 1945 restore := image.MockTrusted(s.StoreSigning.Trusted) 1946 defer restore() 1947 1948 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1949 "classic": "true", 1950 "architecture": "amd64", 1951 }) 1952 1953 fn := filepath.Join(c.MkDir(), "model.assertion") 1954 err := ioutil.WriteFile(fn, asserts.Encode(model), 0644) 1955 c.Assert(err, IsNil) 1956 1957 err = image.Prepare(&image.Options{ 1958 Classic: true, 1959 ModelFile: fn, 1960 Architecture: "i386", 1961 }) 1962 c.Assert(err, ErrorMatches, "cannot override model architecture: amd64") 1963 } 1964 1965 func (s *imageSuite) TestPrepareClassicModelSnapsButNoArchFails(c *C) { 1966 restore := image.MockTrusted(s.StoreSigning.Trusted) 1967 defer restore() 1968 1969 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1970 "classic": "true", 1971 "gadget": "classic-gadget", 1972 }) 1973 1974 fn := filepath.Join(c.MkDir(), "model.assertion") 1975 err := ioutil.WriteFile(fn, asserts.Encode(model), 0644) 1976 c.Assert(err, IsNil) 1977 1978 err = image.Prepare(&image.Options{ 1979 Classic: true, 1980 ModelFile: fn, 1981 }) 1982 c.Assert(err, ErrorMatches, "cannot have snaps for a classic image without an architecture in the model or from --arch") 1983 } 1984 1985 func (s *imageSuite) TestPrepareClassicModelNoModelAssertion(c *C) { 1986 preparedir := c.MkDir() 1987 s.setupSnaps(c, nil, "") 1988 1989 restore := image.MockTrusted(s.StoreSigning.Trusted) 1990 defer restore() 1991 restore = sysdb.MockGenericClassicModel(s.StoreSigning.GenericClassicModel) 1992 defer restore() 1993 restore = image.MockNewToolingStoreFromModel(func(model *asserts.Model, fallbackArchitecture string) (*tooling.ToolingStore, error) { 1994 return s.tsto, nil 1995 }) 1996 defer restore() 1997 1998 // prepare an image with no model assertion but classic set to true 1999 // to ensure the GenericClassicModel is used without error 2000 err := image.Prepare(&image.Options{ 2001 Architecture: "amd64", 2002 PrepareDir: preparedir, 2003 Classic: true, 2004 Snaps: []string{"required-snap18", "core18"}, 2005 }) 2006 c.Assert(err, IsNil) 2007 2008 // ensure the prepareDir was preseeded 2009 seeddir := filepath.Join(preparedir, "var/lib/snapd/seed") 2010 seedsnapsdir := filepath.Join(seeddir, "snaps") 2011 c.Check(filepath.Join(seeddir, "seed.yaml"), testutil.FilePresent) 2012 m, err := filepath.Glob(filepath.Join(seedsnapsdir, "*")) 2013 c.Assert(err, IsNil) 2014 // generic classic model has no other snaps, so we expect only the snaps 2015 // that were passed in options to be present 2016 c.Check(m, DeepEquals, []string{ 2017 filepath.Join(seedsnapsdir, "core18_18.snap"), 2018 filepath.Join(seedsnapsdir, "required-snap18_6.snap"), 2019 }) 2020 } 2021 2022 func (s *imageSuite) TestSetupSeedWithKernelAndGadgetTrack(c *C) { 2023 restore := image.MockTrusted(s.StoreSigning.Trusted) 2024 defer restore() 2025 2026 // replace model with a model that uses core18 2027 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2028 "architecture": "amd64", 2029 "gadget": "pc=18", 2030 "kernel": "pc-kernel=18", 2031 }) 2032 2033 rootdir := filepath.Join(c.MkDir(), "image") 2034 s.setupSnaps(c, map[string]string{ 2035 "core": "canonical", 2036 "pc": "canonical", 2037 "pc-kernel": "canonical", 2038 }, "") 2039 2040 opts := &image.Options{ 2041 PrepareDir: filepath.Dir(rootdir), 2042 Channel: "stable", 2043 } 2044 2045 err := image.SetupSeed(s.tsto, model, opts) 2046 c.Assert(err, IsNil) 2047 2048 // check seed 2049 seeddir := filepath.Join(rootdir, "var/lib/snapd/seed") 2050 seedsnapsdir := filepath.Join(seeddir, "snaps") 2051 essSnaps, runSnaps, _ := s.loadSeed(c, seeddir) 2052 c.Check(essSnaps, HasLen, 3) 2053 c.Check(runSnaps, HasLen, 0) 2054 2055 c.Check(essSnaps[0], DeepEquals, &seed.Snap{ 2056 Path: filepath.Join(seedsnapsdir, "core_3.snap"), 2057 SideInfo: &s.AssertedSnapInfo("core").SideInfo, 2058 EssentialType: snap.TypeOS, 2059 Essential: true, 2060 Required: true, 2061 Channel: "stable", 2062 }) 2063 c.Check(essSnaps[1], DeepEquals, &seed.Snap{ 2064 Path: filepath.Join(seedsnapsdir, "pc-kernel_2.snap"), 2065 SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo, 2066 EssentialType: snap.TypeKernel, 2067 Essential: true, 2068 Required: true, 2069 Channel: "18/stable", 2070 }) 2071 c.Check(essSnaps[2], DeepEquals, &seed.Snap{ 2072 Path: filepath.Join(seedsnapsdir, "pc_1.snap"), 2073 SideInfo: &s.AssertedSnapInfo("pc").SideInfo, 2074 EssentialType: snap.TypeGadget, 2075 Essential: true, 2076 Required: true, 2077 Channel: "18/stable", 2078 }) 2079 2080 // check the downloads 2081 c.Check(s.storeActionsBunchSizes, DeepEquals, []int{3}) 2082 c.Check(s.storeActions[0], DeepEquals, &store.SnapAction{ 2083 Action: "download", 2084 InstanceName: "core", 2085 Channel: "stable", 2086 Flags: store.SnapActionIgnoreValidation, 2087 }) 2088 c.Check(s.storeActions[1], DeepEquals, &store.SnapAction{ 2089 Action: "download", 2090 InstanceName: "pc-kernel", 2091 Channel: "18/stable", 2092 Flags: store.SnapActionIgnoreValidation, 2093 }) 2094 c.Check(s.storeActions[2], DeepEquals, &store.SnapAction{ 2095 Action: "download", 2096 InstanceName: "pc", 2097 Channel: "18/stable", 2098 Flags: store.SnapActionIgnoreValidation, 2099 }) 2100 } 2101 2102 func (s *imageSuite) TestSetupSeedWithKernelTrackWithDefaultChannel(c *C) { 2103 restore := image.MockTrusted(s.StoreSigning.Trusted) 2104 defer restore() 2105 2106 // replace model with a model that uses core18 2107 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2108 "architecture": "amd64", 2109 "gadget": "pc", 2110 "kernel": "pc-kernel=18", 2111 }) 2112 2113 s.setupSnaps(c, map[string]string{ 2114 "core": "canonical", 2115 "pc": "canonical", 2116 "pc-kernel": "canonical", 2117 }, "") 2118 2119 rootdir := filepath.Join(c.MkDir(), "image") 2120 opts := &image.Options{ 2121 PrepareDir: filepath.Dir(rootdir), 2122 Channel: "edge", 2123 } 2124 2125 err := image.SetupSeed(s.tsto, model, opts) 2126 c.Assert(err, IsNil) 2127 2128 // check seed 2129 seeddir := filepath.Join(rootdir, "var/lib/snapd/seed") 2130 seedsnapsdir := filepath.Join(seeddir, "snaps") 2131 essSnaps, runSnaps, _ := s.loadSeed(c, seeddir) 2132 c.Check(essSnaps, HasLen, 3) 2133 c.Check(runSnaps, HasLen, 0) 2134 2135 c.Check(essSnaps[0], DeepEquals, &seed.Snap{ 2136 Path: filepath.Join(seedsnapsdir, "core_3.snap"), 2137 SideInfo: &s.AssertedSnapInfo("core").SideInfo, 2138 EssentialType: snap.TypeOS, 2139 Essential: true, 2140 Required: true, 2141 Channel: "edge", 2142 }) 2143 c.Check(essSnaps[1], DeepEquals, &seed.Snap{ 2144 Path: filepath.Join(seedsnapsdir, "pc-kernel_2.snap"), 2145 SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo, 2146 EssentialType: snap.TypeKernel, 2147 Essential: true, 2148 Required: true, 2149 Channel: "18/edge", 2150 }) 2151 c.Check(essSnaps[2], DeepEquals, &seed.Snap{ 2152 Path: filepath.Join(seedsnapsdir, "pc_1.snap"), 2153 SideInfo: &s.AssertedSnapInfo("pc").SideInfo, 2154 EssentialType: snap.TypeGadget, 2155 Essential: true, 2156 Required: true, 2157 Channel: "edge", 2158 }) 2159 } 2160 2161 func (s *imageSuite) TestSetupSeedWithKernelTrackOnLocalSnap(c *C) { 2162 restore := image.MockTrusted(s.StoreSigning.Trusted) 2163 defer restore() 2164 2165 // replace model with a model that uses core18 2166 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2167 "architecture": "amd64", 2168 "gadget": "pc", 2169 "kernel": "pc-kernel=18", 2170 }) 2171 2172 rootdir := filepath.Join(c.MkDir(), "image") 2173 s.setupSnaps(c, map[string]string{ 2174 "core": "canonical", 2175 "pc": "canonical", 2176 "pc-kernel": "canonical", 2177 }, "") 2178 2179 // pretend we downloaded the core,kernel already 2180 cfn := s.AssertedSnap("core") 2181 kfn := s.AssertedSnap("pc-kernel") 2182 opts := &image.Options{ 2183 PrepareDir: filepath.Dir(rootdir), 2184 Snaps: []string{kfn, cfn}, 2185 Channel: "beta", 2186 } 2187 2188 err := image.SetupSeed(s.tsto, model, opts) 2189 c.Assert(err, IsNil) 2190 2191 // check seed 2192 seeddir := filepath.Join(rootdir, "var/lib/snapd/seed") 2193 seedsnapsdir := filepath.Join(seeddir, "snaps") 2194 essSnaps, runSnaps, _ := s.loadSeed(c, seeddir) 2195 c.Check(essSnaps, HasLen, 3) 2196 c.Check(runSnaps, HasLen, 0) 2197 2198 c.Check(essSnaps[0], DeepEquals, &seed.Snap{ 2199 Path: filepath.Join(seedsnapsdir, "core_3.snap"), 2200 SideInfo: &s.AssertedSnapInfo("core").SideInfo, 2201 EssentialType: snap.TypeOS, 2202 Essential: true, 2203 Required: true, 2204 Channel: "beta", 2205 }) 2206 c.Check(essSnaps[1], DeepEquals, &seed.Snap{ 2207 Path: filepath.Join(seedsnapsdir, "pc-kernel_2.snap"), 2208 SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo, 2209 EssentialType: snap.TypeKernel, 2210 Essential: true, 2211 Required: true, 2212 Channel: "18/beta", 2213 }) 2214 } 2215 2216 func (s *imageSuite) TestSetupSeedWithBaseAndLocalLegacyCoreOrdering(c *C) { 2217 restore := image.MockTrusted(s.StoreSigning.Trusted) 2218 defer restore() 2219 2220 // replace model with a model that uses core18 2221 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2222 "architecture": "amd64", 2223 "base": "core18", 2224 "gadget": "pc18", 2225 "kernel": "pc-kernel", 2226 "required-snaps": []interface{}{"required-snap1"}, 2227 }) 2228 2229 rootdir := filepath.Join(c.MkDir(), "image") 2230 s.setupSnaps(c, map[string]string{ 2231 "core18": "canonical", 2232 "pc18": "canonical", 2233 "pc-kernel": "canonical", 2234 }, "") 2235 2236 coreFn := snaptest.MakeTestSnapWithFiles(c, packageCore, [][]string{{"local", ""}}) 2237 2238 opts := &image.Options{ 2239 PrepareDir: filepath.Dir(rootdir), 2240 Snaps: []string{ 2241 coreFn, 2242 }, 2243 } 2244 2245 err := image.SetupSeed(s.tsto, model, opts) 2246 c.Assert(err, IsNil) 2247 2248 // check seed 2249 seeddir := filepath.Join(rootdir, "var/lib/snapd/seed") 2250 seedsnapsdir := filepath.Join(seeddir, "snaps") 2251 essSnaps, runSnaps, _ := s.loadSeed(c, seeddir) 2252 c.Check(essSnaps, HasLen, 4) 2253 c.Check(runSnaps, HasLen, 2) 2254 2255 c.Check(essSnaps[0].Path, Equals, filepath.Join(seedsnapsdir, "snapd_18.snap")) 2256 c.Check(essSnaps[1].Path, Equals, filepath.Join(seedsnapsdir, "core18_18.snap")) 2257 c.Check(essSnaps[2].Path, Equals, filepath.Join(seedsnapsdir, "pc-kernel_2.snap")) 2258 c.Check(essSnaps[3].Path, Equals, filepath.Join(seedsnapsdir, "pc18_4.snap")) 2259 2260 c.Check(runSnaps[0].Path, Equals, filepath.Join(seedsnapsdir, "core_x1.snap")) 2261 c.Check(runSnaps[1].Path, Equals, filepath.Join(seedsnapsdir, "required-snap1_3.snap")) 2262 } 2263 2264 func (s *imageSuite) TestSetupSeedWithBaseAndLegacyCoreOrdering(c *C) { 2265 restore := image.MockTrusted(s.StoreSigning.Trusted) 2266 defer restore() 2267 2268 // replace model with a model that uses core18 2269 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2270 "architecture": "amd64", 2271 "base": "core18", 2272 "gadget": "pc18", 2273 "kernel": "pc-kernel", 2274 "required-snaps": []interface{}{"required-snap1", "core"}, 2275 }) 2276 2277 rootdir := filepath.Join(c.MkDir(), "image") 2278 s.setupSnaps(c, map[string]string{ 2279 "core18": "canonical", 2280 "core": "canonical", 2281 "pc18": "canonical", 2282 "pc-kernel": "canonical", 2283 }, "") 2284 2285 opts := &image.Options{ 2286 PrepareDir: filepath.Dir(rootdir), 2287 } 2288 2289 err := image.SetupSeed(s.tsto, model, opts) 2290 c.Assert(err, IsNil) 2291 2292 // check seed 2293 seeddir := filepath.Join(rootdir, "var/lib/snapd/seed") 2294 seedsnapsdir := filepath.Join(seeddir, "snaps") 2295 essSnaps, runSnaps, _ := s.loadSeed(c, seeddir) 2296 c.Check(essSnaps, HasLen, 4) 2297 c.Check(runSnaps, HasLen, 2) 2298 2299 c.Check(essSnaps[0].Path, Equals, filepath.Join(seedsnapsdir, "snapd_18.snap")) 2300 c.Check(essSnaps[1].Path, Equals, filepath.Join(seedsnapsdir, "core18_18.snap")) 2301 c.Check(essSnaps[2].Path, Equals, filepath.Join(seedsnapsdir, "pc-kernel_2.snap")) 2302 c.Check(essSnaps[3].Path, Equals, filepath.Join(seedsnapsdir, "pc18_4.snap")) 2303 2304 c.Check(runSnaps[0].Path, Equals, filepath.Join(seedsnapsdir, "core_3.snap")) 2305 c.Check(runSnaps[1].Path, Equals, filepath.Join(seedsnapsdir, "required-snap1_3.snap")) 2306 } 2307 2308 func (s *imageSuite) TestSetupSeedGadgetBaseModelBaseMismatch(c *C) { 2309 restore := image.MockTrusted(s.StoreSigning.Trusted) 2310 defer restore() 2311 // replace model with a model that uses core18 and a gadget 2312 // without a base 2313 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2314 "architecture": "amd64", 2315 "base": "core18", 2316 "gadget": "pc", 2317 "kernel": "pc-kernel", 2318 "required-snaps": []interface{}{"required-snap1"}, 2319 }) 2320 2321 rootdir := filepath.Join(c.MkDir(), "image") 2322 s.setupSnaps(c, map[string]string{ 2323 "core18": "canonical", 2324 "pc": "canonical", 2325 "pc-kernel": "canonical", 2326 }, "") 2327 opts := &image.Options{ 2328 PrepareDir: filepath.Dir(rootdir), 2329 } 2330 2331 err := image.SetupSeed(s.tsto, model, opts) 2332 c.Assert(err, ErrorMatches, `cannot use gadget snap because its base "" is different from model base "core18"`) 2333 } 2334 2335 func (s *imageSuite) TestSetupSeedSnapReqBase(c *C) { 2336 restore := image.MockTrusted(s.StoreSigning.Trusted) 2337 defer restore() 2338 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2339 "architecture": "amd64", 2340 "gadget": "pc", 2341 "kernel": "pc-kernel", 2342 "required-snaps": []interface{}{"snap-req-other-base"}, 2343 }) 2344 2345 rootdir := filepath.Join(c.MkDir(), "image") 2346 s.setupSnaps(c, map[string]string{ 2347 "core": "canonical", 2348 "pc": "canonical", 2349 "pc-kernel": "canonical", 2350 "snap-req-other-base": "canonical", 2351 }, "") 2352 opts := &image.Options{ 2353 PrepareDir: filepath.Dir(rootdir), 2354 } 2355 2356 err := image.SetupSeed(s.tsto, model, opts) 2357 c.Assert(err, ErrorMatches, `cannot add snap "snap-req-other-base" without also adding its base "other-base" explicitly`) 2358 } 2359 2360 func (s *imageSuite) TestSetupSeedBaseNone(c *C) { 2361 restore := image.MockTrusted(s.StoreSigning.Trusted) 2362 defer restore() 2363 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2364 "architecture": "amd64", 2365 "gadget": "pc", 2366 "kernel": "pc-kernel", 2367 "required-snaps": []interface{}{"snap-base-none"}, 2368 }) 2369 2370 rootdir := filepath.Join(c.MkDir(), "image") 2371 s.setupSnaps(c, map[string]string{ 2372 "core": "canonical", 2373 "pc": "canonical", 2374 "pc-kernel": "canonical", 2375 "snap-base-none": "canonical", 2376 }, "") 2377 opts := &image.Options{ 2378 PrepareDir: filepath.Dir(rootdir), 2379 } 2380 2381 c.Assert(image.SetupSeed(s.tsto, model, opts), IsNil) 2382 } 2383 2384 func (s *imageSuite) TestSetupSeedCore18GadgetDefaults(c *C) { 2385 tmpDir := c.MkDir() 2386 dirs.SetRootDir(tmpDir) 2387 defer dirs.SetRootDir("") 2388 2389 systemctlMock := testutil.MockCommand(c, "systemctl", "") 2390 defer systemctlMock.Restore() 2391 2392 restore := image.MockTrusted(s.StoreSigning.Trusted) 2393 defer restore() 2394 2395 // replace model with a model that uses core18 2396 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2397 "architecture": "amd64", 2398 "gadget": "pc18", 2399 "kernel": "pc-kernel", 2400 "base": "core18", 2401 }) 2402 2403 defaults := `defaults: 2404 system: 2405 service: 2406 ssh: 2407 disable: true 2408 ` 2409 2410 rootdir := filepath.Join(c.MkDir(), "image") 2411 s.setupSnaps(c, map[string]string{ 2412 "pc18": "canonical", 2413 "pc-kernel": "canonical", 2414 }, defaults) 2415 2416 snapdFn := snaptest.MakeTestSnapWithFiles(c, snapdSnap, [][]string{{"local", ""}}) 2417 core18Fn := snaptest.MakeTestSnapWithFiles(c, packageCore18, [][]string{{"local", ""}}) 2418 2419 opts := &image.Options{ 2420 Snaps: []string{ 2421 snapdFn, 2422 core18Fn, 2423 }, 2424 2425 PrepareDir: filepath.Dir(rootdir), 2426 } 2427 2428 err := image.SetupSeed(s.tsto, model, opts) 2429 c.Assert(err, IsNil) 2430 c.Check(osutil.FileExists(filepath.Join(rootdir, "_writable_defaults/etc/ssh/sshd_not_to_be_run")), Equals, true) 2431 } 2432 2433 func (s *imageSuite) TestSetupSeedSnapCoreSatisfiesCore16(c *C) { 2434 restore := image.MockTrusted(s.StoreSigning.Trusted) 2435 defer restore() 2436 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2437 "architecture": "amd64", 2438 "gadget": "pc", 2439 "kernel": "pc-kernel", 2440 "required-snaps": []interface{}{"snap-req-core16-base"}, 2441 }) 2442 2443 rootdir := filepath.Join(c.MkDir(), "image") 2444 s.setupSnaps(c, map[string]string{ 2445 "core": "canonical", 2446 "pc": "canonical", 2447 "pc-kernel": "canonical", 2448 "snap-req-other-base": "canonical", 2449 }, "") 2450 opts := &image.Options{ 2451 PrepareDir: filepath.Dir(rootdir), 2452 } 2453 2454 err := image.SetupSeed(s.tsto, model, opts) 2455 c.Assert(err, IsNil) 2456 } 2457 2458 func (s *imageSuite) TestSetupSeedStoreAssertionMissing(c *C) { 2459 restore := image.MockTrusted(s.StoreSigning.Trusted) 2460 defer restore() 2461 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2462 "architecture": "amd64", 2463 "gadget": "pc", 2464 "kernel": "pc-kernel", 2465 "store": "my-store", 2466 }) 2467 2468 rootdir := filepath.Join(c.MkDir(), "image") 2469 s.setupSnaps(c, map[string]string{ 2470 "core": "canonical", 2471 "pc": "canonical", 2472 "pc-kernel": "canonical", 2473 }, "") 2474 opts := &image.Options{ 2475 PrepareDir: filepath.Dir(rootdir), 2476 } 2477 2478 err := image.SetupSeed(s.tsto, model, opts) 2479 c.Assert(err, IsNil) 2480 } 2481 2482 func (s *imageSuite) TestSetupSeedStoreAssertionFetched(c *C) { 2483 restore := image.MockTrusted(s.StoreSigning.Trusted) 2484 defer restore() 2485 2486 // add store assertion 2487 storeAs, err := s.StoreSigning.Sign(asserts.StoreType, map[string]interface{}{ 2488 "store": "my-store", 2489 "operator-id": "canonical", 2490 "timestamp": time.Now().UTC().Format(time.RFC3339), 2491 }, nil, "") 2492 c.Assert(err, IsNil) 2493 err = s.StoreSigning.Add(storeAs) 2494 c.Assert(err, IsNil) 2495 2496 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2497 "architecture": "amd64", 2498 "gadget": "pc", 2499 "kernel": "pc-kernel", 2500 "store": "my-store", 2501 }) 2502 2503 rootdir := filepath.Join(c.MkDir(), "image") 2504 s.setupSnaps(c, map[string]string{ 2505 "core": "canonical", 2506 "pc": "canonical", 2507 "pc-kernel": "canonical", 2508 }, "") 2509 opts := &image.Options{ 2510 PrepareDir: filepath.Dir(rootdir), 2511 } 2512 2513 err = image.SetupSeed(s.tsto, model, opts) 2514 c.Assert(err, IsNil) 2515 2516 seeddir := filepath.Join(rootdir, "var/lib/snapd/seed") 2517 essSnaps, runSnaps, roDB := s.loadSeed(c, seeddir) 2518 c.Check(essSnaps, HasLen, 3) 2519 c.Check(runSnaps, HasLen, 0) 2520 2521 // check the store assertion was fetched 2522 _, err = roDB.Find(asserts.StoreType, map[string]string{ 2523 "store": "my-store", 2524 }) 2525 c.Check(err, IsNil) 2526 } 2527 2528 func (s *imageSuite) TestSetupSeedSnapReqBaseFromLocal(c *C) { 2529 // As originally written it let an extra snap fullfil 2530 // the prereq of a required one, this does not work anymore! 2531 // See TestSetupSeedSnapReqBaseFromExtraFails. 2532 restore := image.MockTrusted(s.StoreSigning.Trusted) 2533 defer restore() 2534 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2535 "architecture": "amd64", 2536 "gadget": "pc", 2537 "kernel": "pc-kernel", 2538 "required-snaps": []interface{}{"other-base", "snap-req-other-base"}, 2539 }) 2540 2541 rootdir := filepath.Join(c.MkDir(), "image") 2542 s.setupSnaps(c, map[string]string{ 2543 "core": "canonical", 2544 "pc": "canonical", 2545 "pc-kernel": "canonical", 2546 "snap-req-other-base": "canonical", 2547 "other-base": "canonical", 2548 }, "") 2549 bfn := s.AssertedSnap("other-base") 2550 opts := &image.Options{ 2551 PrepareDir: filepath.Dir(rootdir), 2552 Snaps: []string{bfn}, 2553 } 2554 2555 err := image.SetupSeed(s.tsto, model, opts) 2556 c.Assert(err, IsNil) 2557 } 2558 2559 func (s *imageSuite) TestSetupSeedSnapReqBaseFromExtraFails(c *C) { 2560 restore := image.MockTrusted(s.StoreSigning.Trusted) 2561 defer restore() 2562 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2563 "architecture": "amd64", 2564 "gadget": "pc", 2565 "kernel": "pc-kernel", 2566 "required-snaps": []interface{}{"snap-req-other-base"}, 2567 }) 2568 2569 rootdir := filepath.Join(c.MkDir(), "image") 2570 s.setupSnaps(c, map[string]string{ 2571 "core": "canonical", 2572 "pc": "canonical", 2573 "pc-kernel": "canonical", 2574 "snap-req-other-base": "canonical", 2575 "other-base": "canonical", 2576 }, "") 2577 bfn := s.AssertedSnap("other-base") 2578 opts := &image.Options{ 2579 PrepareDir: filepath.Dir(rootdir), 2580 Snaps: []string{bfn}, 2581 } 2582 2583 err := image.SetupSeed(s.tsto, model, opts) 2584 c.Check(err, ErrorMatches, `cannot add snap "snap-req-other-base" without also adding its base "other-base" explicitly`) 2585 } 2586 2587 func (s *imageSuite) TestSetupSeedMissingContentProvider(c *C) { 2588 restore := image.MockTrusted(s.StoreSigning.Trusted) 2589 defer restore() 2590 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2591 "architecture": "amd64", 2592 "gadget": "pc", 2593 "kernel": "pc-kernel", 2594 "required-snaps": []interface{}{"snap-req-content-provider"}, 2595 }) 2596 2597 rootdir := filepath.Join(c.MkDir(), "image") 2598 s.setupSnaps(c, map[string]string{ 2599 "core": "canonical", 2600 "pc": "canonical", 2601 "pc-kernel": "canonical", 2602 "snap-req-content-snap": "canonical", 2603 }, "") 2604 opts := &image.Options{ 2605 PrepareDir: filepath.Dir(rootdir), 2606 } 2607 2608 err := image.SetupSeed(s.tsto, model, opts) 2609 c.Check(err, ErrorMatches, `cannot use snap "snap-req-content-provider" without its default content provider "gtk-common-themes" being added explicitly`) 2610 } 2611 2612 func (s *imageSuite) TestSetupSeedClassic(c *C) { 2613 restore := image.MockTrusted(s.StoreSigning.Trusted) 2614 defer restore() 2615 2616 // classic model with gadget etc 2617 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2618 "classic": "true", 2619 "architecture": "amd64", 2620 "gadget": "classic-gadget", 2621 "required-snaps": []interface{}{"required-snap1"}, 2622 }) 2623 2624 rootdir := c.MkDir() 2625 s.setupSnaps(c, map[string]string{ 2626 "classic-gadget": "my-brand", 2627 }, "") 2628 2629 opts := &image.Options{ 2630 Classic: true, 2631 PrepareDir: rootdir, 2632 } 2633 2634 err := image.SetupSeed(s.tsto, model, opts) 2635 c.Assert(err, IsNil) 2636 2637 // check seed 2638 seeddir := filepath.Join(rootdir, "var/lib/snapd/seed") 2639 seedsnapsdir := filepath.Join(seeddir, "snaps") 2640 essSnaps, runSnaps, _ := s.loadSeed(c, seeddir) 2641 c.Check(essSnaps, HasLen, 2) 2642 c.Check(runSnaps, HasLen, 1) 2643 2644 // check the files are in place 2645 c.Check(essSnaps[0], DeepEquals, &seed.Snap{ 2646 Path: filepath.Join(seedsnapsdir, "core_3.snap"), 2647 SideInfo: &s.AssertedSnapInfo("core").SideInfo, 2648 EssentialType: snap.TypeOS, 2649 Essential: true, 2650 Required: true, 2651 Channel: stableChannel, 2652 }) 2653 c.Check(essSnaps[0].Path, testutil.FilePresent) 2654 c.Check(essSnaps[1], DeepEquals, &seed.Snap{ 2655 Path: filepath.Join(seedsnapsdir, "classic-gadget_5.snap"), 2656 SideInfo: &s.AssertedSnapInfo("classic-gadget").SideInfo, 2657 EssentialType: snap.TypeGadget, 2658 Essential: true, 2659 Required: true, 2660 Channel: stableChannel, 2661 }) 2662 c.Check(essSnaps[1].Path, testutil.FilePresent) 2663 c.Check(runSnaps[0], DeepEquals, &seed.Snap{ 2664 Path: filepath.Join(seedsnapsdir, "required-snap1_3.snap"), 2665 SideInfo: &s.AssertedSnapInfo("required-snap1").SideInfo, 2666 Required: true, 2667 Channel: stableChannel, 2668 }) 2669 c.Check(runSnaps[0].Path, testutil.FilePresent) 2670 2671 l, err := ioutil.ReadDir(seedsnapsdir) 2672 c.Assert(err, IsNil) 2673 c.Check(l, HasLen, 3) 2674 2675 // check that the bootloader is unset 2676 m, err := s.bootloader.GetBootVars("snap_kernel", "snap_core") 2677 c.Assert(err, IsNil) 2678 c.Check(m, DeepEquals, map[string]string{ 2679 "snap_core": "", 2680 "snap_kernel": "", 2681 }) 2682 2683 c.Check(s.stderr.String(), Matches, `WARNING: ensure that the contents under .*/var/lib/snapd/seed are owned by root:root in the \(final\) image\n`) 2684 2685 // no blob dir created 2686 blobdir := filepath.Join(rootdir, "var/lib/snapd/snaps") 2687 c.Check(osutil.FileExists(blobdir), Equals, false) 2688 } 2689 2690 func (s *imageSuite) TestSetupSeedClassicUC20(c *C) { 2691 restore := image.MockTrusted(s.StoreSigning.Trusted) 2692 defer restore() 2693 2694 s.makeSnap(c, "snapd", nil, snap.R(1), "") 2695 s.makeSnap(c, "core20", nil, snap.R(20), "") 2696 s.makeSnap(c, "pc-kernel=20", nil, snap.R(1), "") 2697 gadgetContent := [][]string{ 2698 {"grub-recovery.conf", "# recovery grub.cfg"}, 2699 {"grub.conf", "# boot grub.cfg"}, 2700 {"meta/gadget.yaml", pcUC20GadgetYaml}, 2701 } 2702 s.makeSnap(c, "pc=20", gadgetContent, snap.R(22), "") 2703 s.makeSnap(c, "required20", nil, snap.R(21), "other") 2704 2705 // classic UC20+ based model 2706 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2707 "classic": "true", 2708 "distribution": "ubuntu", 2709 "display-name": "my model", 2710 "architecture": "amd64", 2711 "base": "core20", 2712 "snaps": []interface{}{ 2713 map[string]interface{}{ 2714 "name": "pc-kernel", 2715 "id": s.AssertedSnapID("pc-kernel"), 2716 "type": "kernel", 2717 "default-channel": "20", 2718 }, 2719 map[string]interface{}{ 2720 "name": "pc", 2721 "id": s.AssertedSnapID("pc"), 2722 "type": "gadget", 2723 "default-channel": "20", 2724 }, 2725 map[string]interface{}{ 2726 "name": "required20", 2727 "id": s.AssertedSnapID("required20"), 2728 }, 2729 }, 2730 }) 2731 2732 prepareDir := c.MkDir() 2733 2734 opts := &image.Options{ 2735 Classic: true, 2736 PrepareDir: prepareDir, 2737 } 2738 2739 err := image.SetupSeed(s.tsto, model, opts) 2740 c.Assert(err, IsNil) 2741 2742 // check seed 2743 seeddir := filepath.Join(prepareDir, "system-seed") 2744 seedsnapsdir := filepath.Join(seeddir, "snaps") 2745 essSnaps, runSnaps, _ := s.loadSeed(c, seeddir) 2746 c.Check(essSnaps, HasLen, 4) 2747 c.Check(runSnaps, HasLen, 1) 2748 2749 stableChannel := "latest/stable" 2750 2751 // check the files are in place 2752 for i, name := range []string{"snapd", "pc-kernel", "core20", "pc"} { 2753 info := s.AssertedSnapInfo(name) 2754 2755 channel := stableChannel 2756 switch name { 2757 case "pc", "pc-kernel": 2758 channel = "20" 2759 } 2760 2761 fn := info.Filename() 2762 p := filepath.Join(seedsnapsdir, fn) 2763 c.Check(p, testutil.FilePresent) 2764 c.Check(essSnaps[i], DeepEquals, &seed.Snap{ 2765 Path: p, 2766 SideInfo: &info.SideInfo, 2767 EssentialType: info.Type(), 2768 Essential: true, 2769 Required: true, 2770 Channel: channel, 2771 }) 2772 } 2773 c.Check(runSnaps[0], DeepEquals, &seed.Snap{ 2774 Path: filepath.Join(seedsnapsdir, "required20_21.snap"), 2775 SideInfo: &s.AssertedSnapInfo("required20").SideInfo, 2776 Required: true, 2777 Channel: stableChannel, 2778 }) 2779 c.Check(runSnaps[0].Path, testutil.FilePresent) 2780 2781 l, err := ioutil.ReadDir(seedsnapsdir) 2782 c.Assert(err, IsNil) 2783 c.Check(l, HasLen, 5) 2784 2785 // Ensure that system-seed/ dir does not contain any bootloader or 2786 // extra files other than "snaps" ans "systems". 2787 dirs, err := filepath.Glob(seeddir + "/*") 2788 c.Assert(err, IsNil) 2789 c.Check(dirs, DeepEquals, []string{ 2790 seeddir + "/snaps", seeddir + "/systems", 2791 }) 2792 } 2793 2794 func (s *imageSuite) TestSetupSeedClassicWithLocalClassicSnap(c *C) { 2795 restore := image.MockTrusted(s.StoreSigning.Trusted) 2796 defer restore() 2797 2798 // classic model 2799 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2800 "classic": "true", 2801 "architecture": "amd64", 2802 }) 2803 2804 rootdir := c.MkDir() 2805 s.setupSnaps(c, nil, "") 2806 2807 snapFile := snaptest.MakeTestSnapWithFiles(c, classicSnap, nil) 2808 2809 opts := &image.Options{ 2810 Classic: true, 2811 Snaps: []string{snapFile}, 2812 PrepareDir: rootdir, 2813 } 2814 2815 err := image.SetupSeed(s.tsto, model, opts) 2816 c.Assert(err, IsNil) 2817 2818 // check seed 2819 seeddir := filepath.Join(rootdir, "var/lib/snapd/seed") 2820 seedsnapsdir := filepath.Join(seeddir, "snaps") 2821 essSnaps, runSnaps, _ := s.loadSeed(c, seeddir) 2822 c.Check(essSnaps, HasLen, 1) 2823 c.Check(runSnaps, HasLen, 1) 2824 2825 c.Check(essSnaps[0], DeepEquals, &seed.Snap{ 2826 Path: filepath.Join(seedsnapsdir, "core_3.snap"), 2827 SideInfo: &s.AssertedSnapInfo("core").SideInfo, 2828 EssentialType: snap.TypeOS, 2829 Essential: true, 2830 Required: true, 2831 Channel: stableChannel, 2832 }) 2833 c.Check(essSnaps[0].Path, testutil.FilePresent) 2834 2835 c.Check(runSnaps[0], DeepEquals, &seed.Snap{ 2836 Path: filepath.Join(seedsnapsdir, "classic-snap_x1.snap"), 2837 SideInfo: &snap.SideInfo{ 2838 RealName: "classic-snap", 2839 }, 2840 Classic: true, 2841 }) 2842 c.Check(runSnaps[0].Path, testutil.FilePresent) 2843 2844 l, err := ioutil.ReadDir(seedsnapsdir) 2845 c.Assert(err, IsNil) 2846 c.Check(l, HasLen, 2) 2847 2848 // check that the bootloader is unset 2849 m, err := s.bootloader.GetBootVars("snap_kernel", "snap_core") 2850 c.Assert(err, IsNil) 2851 c.Check(m, DeepEquals, map[string]string{ 2852 "snap_core": "", 2853 "snap_kernel": "", 2854 }) 2855 } 2856 2857 func (s *imageSuite) TestSetupSeedClassicSnapdOnly(c *C) { 2858 restore := image.MockTrusted(s.StoreSigning.Trusted) 2859 defer restore() 2860 2861 // classic model with gadget etc 2862 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2863 "classic": "true", 2864 "architecture": "amd64", 2865 "gadget": "classic-gadget18", 2866 "required-snaps": []interface{}{"core18", "required-snap18"}, 2867 }) 2868 2869 rootdir := c.MkDir() 2870 s.setupSnaps(c, map[string]string{ 2871 "classic-gadget18": "my-brand", 2872 }, "") 2873 2874 opts := &image.Options{ 2875 Classic: true, 2876 PrepareDir: rootdir, 2877 } 2878 2879 err := image.SetupSeed(s.tsto, model, opts) 2880 c.Assert(err, IsNil) 2881 2882 // check seed 2883 seeddir := filepath.Join(rootdir, "var/lib/snapd/seed") 2884 seedsnapsdir := filepath.Join(seeddir, "snaps") 2885 essSnaps, runSnaps, _ := s.loadSeed(c, seeddir) 2886 c.Check(essSnaps, HasLen, 3) 2887 c.Check(runSnaps, HasLen, 1) 2888 2889 // check the files are in place 2890 for i, name := range []string{"snapd", "classic-gadget18", "core18"} { 2891 info := s.AssertedSnapInfo(name) 2892 2893 fn := info.Filename() 2894 p := filepath.Join(seedsnapsdir, fn) 2895 c.Check(p, testutil.FilePresent) 2896 c.Check(essSnaps[i], DeepEquals, &seed.Snap{ 2897 Path: p, 2898 SideInfo: &info.SideInfo, 2899 EssentialType: info.Type(), 2900 Essential: true, 2901 Required: true, 2902 Channel: stableChannel, 2903 }) 2904 } 2905 c.Check(runSnaps[0], DeepEquals, &seed.Snap{ 2906 Path: filepath.Join(seedsnapsdir, "required-snap18_6.snap"), 2907 SideInfo: &s.AssertedSnapInfo("required-snap18").SideInfo, 2908 Required: true, 2909 Channel: stableChannel, 2910 }) 2911 c.Check(runSnaps[0].Path, testutil.FilePresent) 2912 2913 l, err := ioutil.ReadDir(seedsnapsdir) 2914 c.Assert(err, IsNil) 2915 c.Check(l, HasLen, 4) 2916 2917 // check that the bootloader is unset 2918 m, err := s.bootloader.GetBootVars("snap_kernel", "snap_core") 2919 c.Assert(err, IsNil) 2920 c.Check(m, DeepEquals, map[string]string{ 2921 "snap_core": "", 2922 "snap_kernel": "", 2923 }) 2924 2925 c.Check(s.stderr.String(), Matches, `WARNING: ensure that the contents under .*/var/lib/snapd/seed are owned by root:root in the \(final\) image\n`) 2926 2927 // no blob dir created 2928 blobdir := filepath.Join(rootdir, "var/lib/snapd/snaps") 2929 c.Check(osutil.FileExists(blobdir), Equals, false) 2930 } 2931 2932 func (s *imageSuite) TestSetupSeedClassicNoSnaps(c *C) { 2933 restore := image.MockTrusted(s.StoreSigning.Trusted) 2934 defer restore() 2935 2936 // classic model with gadget etc 2937 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2938 "classic": "true", 2939 }) 2940 2941 rootdir := c.MkDir() 2942 2943 opts := &image.Options{ 2944 Classic: true, 2945 PrepareDir: rootdir, 2946 } 2947 2948 err := image.SetupSeed(s.tsto, model, opts) 2949 c.Assert(err, IsNil) 2950 2951 // check seed 2952 seeddir := filepath.Join(rootdir, "var/lib/snapd/seed") 2953 seedsnapsdir := filepath.Join(seeddir, "snaps") 2954 essSnaps, runSnaps, _ := s.loadSeed(c, seeddir) 2955 c.Check(essSnaps, HasLen, 0) 2956 c.Check(runSnaps, HasLen, 0) 2957 2958 l, err := ioutil.ReadDir(seedsnapsdir) 2959 c.Assert(err, IsNil) 2960 c.Check(l, HasLen, 0) 2961 2962 // check that the bootloader is unset 2963 m, err := s.bootloader.GetBootVars("snap_kernel", "snap_core") 2964 c.Assert(err, IsNil) 2965 c.Check(m, DeepEquals, map[string]string{ 2966 "snap_core": "", 2967 "snap_kernel": "", 2968 }) 2969 2970 // no blob dir created 2971 blobdir := filepath.Join(rootdir, "var/lib/snapd/snaps") 2972 c.Check(osutil.FileExists(blobdir), Equals, false) 2973 } 2974 2975 func (s *imageSuite) TestSetupSeedClassicSnapdOnlyMissingCore16(c *C) { 2976 restore := image.MockTrusted(s.StoreSigning.Trusted) 2977 defer restore() 2978 2979 // classic model with gadget etc 2980 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2981 "classic": "true", 2982 "architecture": "amd64", 2983 "gadget": "classic-gadget18", 2984 "required-snaps": []interface{}{"core18", "snap-req-core16-base"}, 2985 }) 2986 2987 rootdir := c.MkDir() 2988 s.setupSnaps(c, map[string]string{ 2989 "classic-gadget18": "my-brand", 2990 }, "") 2991 2992 opts := &image.Options{ 2993 Classic: true, 2994 PrepareDir: rootdir, 2995 } 2996 2997 err := image.SetupSeed(s.tsto, model, opts) 2998 c.Assert(err, ErrorMatches, `cannot use "snap-req-core16-base" requiring base "core16" without adding "core16" \(or "core"\) explicitly`) 2999 } 3000 3001 func (s *imageSuite) TestSetupSeedLocalSnapd(c *C) { 3002 restore := image.MockTrusted(s.StoreSigning.Trusted) 3003 defer restore() 3004 3005 // replace model with a model that uses core18 3006 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 3007 "architecture": "amd64", 3008 "gadget": "pc18", 3009 "kernel": "pc-kernel", 3010 "base": "core18", 3011 }) 3012 3013 rootdir := filepath.Join(c.MkDir(), "image") 3014 s.setupSnaps(c, map[string]string{ 3015 "pc18": "canonical", 3016 "pc-kernel": "canonical", 3017 }, "") 3018 3019 snapdFn := snaptest.MakeTestSnapWithFiles(c, snapdSnap, [][]string{{"local", ""}}) 3020 core18Fn := snaptest.MakeTestSnapWithFiles(c, packageCore18, [][]string{{"local", ""}}) 3021 3022 opts := &image.Options{ 3023 Snaps: []string{ 3024 snapdFn, 3025 core18Fn, 3026 }, 3027 3028 PrepareDir: filepath.Dir(rootdir), 3029 } 3030 3031 err := image.SetupSeed(s.tsto, model, opts) 3032 c.Assert(err, IsNil) 3033 c.Assert(s.stdout.String(), Matches, `(?ms).*Copying ".*/snapd_3.14_all.snap" \(snapd\)`) 3034 } 3035 3036 func (s *imageSuite) TestCore20MakeLabel(c *C) { 3037 c.Check(image.MakeLabel(time.Date(2019, 10, 30, 0, 0, 0, 0, time.UTC)), Equals, "20191030") 3038 } 3039 3040 func (s *imageSuite) makeSnap(c *C, yamlKey string, files [][]string, revno snap.Revision, publisher string) { 3041 if publisher == "" { 3042 publisher = "canonical" 3043 } 3044 s.MakeAssertedSnap(c, seedtest.SampleSnapYaml[yamlKey], files, revno, publisher) 3045 } 3046 3047 func (s *imageSuite) makeUC20Model(extraHeaders map[string]interface{}) *asserts.Model { 3048 headers := map[string]interface{}{ 3049 "display-name": "my model", 3050 "architecture": "amd64", 3051 "base": "core20", 3052 "snaps": []interface{}{ 3053 map[string]interface{}{ 3054 "name": "pc-kernel", 3055 "id": s.AssertedSnapID("pc-kernel"), 3056 "type": "kernel", 3057 "default-channel": "20", 3058 }, 3059 map[string]interface{}{ 3060 "name": "pc", 3061 "id": s.AssertedSnapID("pc"), 3062 "type": "gadget", 3063 "default-channel": "20", 3064 }, 3065 map[string]interface{}{ 3066 "name": "required20", 3067 "id": s.AssertedSnapID("required20"), 3068 }}, 3069 } 3070 for k, v := range extraHeaders { 3071 headers[k] = v 3072 } 3073 3074 return s.Brands.Model("my-brand", "my-model", headers) 3075 } 3076 3077 func (s *imageSuite) TestSetupSeedCore20Grub(c *C) { 3078 bootloader.Force(nil) 3079 restore := image.MockTrusted(s.StoreSigning.Trusted) 3080 defer restore() 3081 3082 // a model that uses core20 3083 model := s.makeUC20Model(nil) 3084 3085 prepareDir := c.MkDir() 3086 3087 s.makeSnap(c, "snapd", nil, snap.R(1), "") 3088 s.makeSnap(c, "core20", nil, snap.R(20), "") 3089 s.makeSnap(c, "pc-kernel=20", nil, snap.R(1), "") 3090 gadgetContent := [][]string{ 3091 {"grub-recovery.conf", "# recovery grub.cfg"}, 3092 {"grub.conf", "# boot grub.cfg"}, 3093 {"meta/gadget.yaml", pcUC20GadgetYaml}, 3094 } 3095 s.makeSnap(c, "pc=20", gadgetContent, snap.R(22), "") 3096 s.makeSnap(c, "required20", nil, snap.R(21), "other") 3097 3098 opts := &image.Options{ 3099 PrepareDir: prepareDir, 3100 Customizations: image.Customizations{ 3101 BootFlags: []string{"factory"}, 3102 Validation: "ignore", 3103 }, 3104 } 3105 3106 err := image.SetupSeed(s.tsto, model, opts) 3107 c.Assert(err, IsNil) 3108 3109 // check seed 3110 seeddir := filepath.Join(prepareDir, "system-seed") 3111 seedsnapsdir := filepath.Join(seeddir, "snaps") 3112 essSnaps, runSnaps, _ := s.loadSeed(c, seeddir) 3113 c.Check(essSnaps, HasLen, 4) 3114 c.Check(runSnaps, HasLen, 1) 3115 3116 stableChannel := "latest/stable" 3117 3118 // check the files are in place 3119 for i, name := range []string{"snapd", "pc-kernel", "core20", "pc"} { 3120 info := s.AssertedSnapInfo(name) 3121 3122 channel := stableChannel 3123 switch name { 3124 case "pc", "pc-kernel": 3125 channel = "20" 3126 } 3127 3128 fn := info.Filename() 3129 p := filepath.Join(seedsnapsdir, fn) 3130 c.Check(p, testutil.FilePresent) 3131 c.Check(essSnaps[i], DeepEquals, &seed.Snap{ 3132 Path: p, 3133 SideInfo: &info.SideInfo, 3134 EssentialType: info.Type(), 3135 Essential: true, 3136 Required: true, 3137 Channel: channel, 3138 }) 3139 } 3140 c.Check(runSnaps[0], DeepEquals, &seed.Snap{ 3141 Path: filepath.Join(seedsnapsdir, "required20_21.snap"), 3142 SideInfo: &s.AssertedSnapInfo("required20").SideInfo, 3143 Required: true, 3144 Channel: stableChannel, 3145 }) 3146 c.Check(runSnaps[0].Path, testutil.FilePresent) 3147 3148 l, err := ioutil.ReadDir(seedsnapsdir) 3149 c.Assert(err, IsNil) 3150 c.Check(l, HasLen, 5) 3151 3152 // check boot config 3153 grubCfg := filepath.Join(prepareDir, "system-seed", "EFI/ubuntu/grub.cfg") 3154 seedGrubenv := filepath.Join(prepareDir, "system-seed", "EFI/ubuntu/grubenv") 3155 grubRecoveryCfgAsset := assets.Internal("grub-recovery.cfg") 3156 c.Assert(grubRecoveryCfgAsset, NotNil) 3157 c.Check(grubCfg, testutil.FileEquals, string(grubRecoveryCfgAsset)) 3158 // make sure that grub.cfg and grubenv are the only files present inside 3159 // the directory 3160 gl, err := filepath.Glob(filepath.Join(prepareDir, "system-seed/EFI/ubuntu/*")) 3161 c.Assert(err, IsNil) 3162 c.Check(gl, DeepEquals, []string{ 3163 grubCfg, 3164 seedGrubenv, 3165 }) 3166 3167 // check recovery system specific config 3168 systems, err := filepath.Glob(filepath.Join(seeddir, "systems", "*")) 3169 c.Assert(err, IsNil) 3170 c.Assert(systems, HasLen, 1) 3171 3172 seedGenv := grubenv.NewEnv(seedGrubenv) 3173 c.Assert(seedGenv.Load(), IsNil) 3174 c.Check(seedGenv.Get("snapd_recovery_system"), Equals, filepath.Base(systems[0])) 3175 c.Check(seedGenv.Get("snapd_recovery_mode"), Equals, "install") 3176 c.Check(seedGenv.Get("snapd_boot_flags"), Equals, "factory") 3177 3178 c.Check(s.stderr.String(), Equals, "") 3179 3180 systemGenv := grubenv.NewEnv(filepath.Join(systems[0], "grubenv")) 3181 c.Assert(systemGenv.Load(), IsNil) 3182 c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_1.snap") 3183 3184 // check the downloads 3185 c.Check(s.storeActionsBunchSizes, DeepEquals, []int{5}) 3186 c.Check(s.storeActions[0], DeepEquals, &store.SnapAction{ 3187 Action: "download", 3188 InstanceName: "snapd", 3189 Channel: stableChannel, 3190 Flags: store.SnapActionIgnoreValidation, 3191 }) 3192 c.Check(s.storeActions[1], DeepEquals, &store.SnapAction{ 3193 Action: "download", 3194 InstanceName: "pc-kernel", 3195 Channel: "20", 3196 Flags: store.SnapActionIgnoreValidation, 3197 }) 3198 c.Check(s.storeActions[2], DeepEquals, &store.SnapAction{ 3199 Action: "download", 3200 InstanceName: "core20", 3201 Channel: stableChannel, 3202 Flags: store.SnapActionIgnoreValidation, 3203 }) 3204 c.Check(s.storeActions[3], DeepEquals, &store.SnapAction{ 3205 Action: "download", 3206 InstanceName: "pc", 3207 Channel: "20", 3208 Flags: store.SnapActionIgnoreValidation, 3209 }) 3210 c.Check(s.storeActions[4], DeepEquals, &store.SnapAction{ 3211 Action: "download", 3212 InstanceName: "required20", 3213 Channel: stableChannel, 3214 Flags: store.SnapActionIgnoreValidation, 3215 }) 3216 } 3217 3218 func (s *imageSuite) TestSetupSeedCore20UBoot(c *C) { 3219 bootloader.Force(nil) 3220 restore := image.MockTrusted(s.StoreSigning.Trusted) 3221 defer restore() 3222 3223 // a model that uses core20 and our gadget 3224 headers := map[string]interface{}{ 3225 "display-name": "my model", 3226 "architecture": "arm64", 3227 "base": "core20", 3228 "snaps": []interface{}{ 3229 map[string]interface{}{ 3230 "name": "arm-kernel", 3231 "id": s.AssertedSnapID("arm-kernel"), 3232 "type": "kernel", 3233 "default-channel": "20", 3234 }, 3235 map[string]interface{}{ 3236 "name": "uboot-gadget", 3237 "id": s.AssertedSnapID("uboot-gadget"), 3238 "type": "gadget", 3239 "default-channel": "20", 3240 }, 3241 }, 3242 } 3243 model := s.Brands.Model("my-brand", "my-model", headers) 3244 3245 prepareDir := c.MkDir() 3246 3247 s.makeSnap(c, "snapd", nil, snap.R(1), "") 3248 s.makeSnap(c, "core20", nil, snap.R(20), "") 3249 kernelContent := [][]string{ 3250 {"kernel.img", "some kernel"}, 3251 {"initrd.img", "some initrd"}, 3252 {"dtbs/foo.dtb", "some dtb"}, 3253 } 3254 s.makeSnap(c, "arm-kernel=20", kernelContent, snap.R(1), "") 3255 gadgetContent := [][]string{ 3256 // this file must be empty 3257 // TODO:UC20: write this test with non-empty uboot.env when we support 3258 // that 3259 {"uboot.conf", ""}, 3260 {"meta/gadget.yaml", piUC20GadgetYaml}, 3261 } 3262 s.makeSnap(c, "uboot-gadget=20", gadgetContent, snap.R(22), "") 3263 3264 opts := &image.Options{ 3265 PrepareDir: prepareDir, 3266 Customizations: image.Customizations{ 3267 BootFlags: []string{"factory"}, 3268 }, 3269 } 3270 3271 err := image.SetupSeed(s.tsto, model, opts) 3272 c.Assert(err, IsNil) 3273 3274 // validity checks 3275 seeddir := filepath.Join(prepareDir, "system-seed") 3276 seedsnapsdir := filepath.Join(seeddir, "snaps") 3277 essSnaps, runSnaps, _ := s.loadSeed(c, seeddir) 3278 c.Check(essSnaps, HasLen, 4) 3279 c.Check(runSnaps, HasLen, 0) 3280 l, err := ioutil.ReadDir(seedsnapsdir) 3281 c.Assert(err, IsNil) 3282 c.Check(l, HasLen, 4) 3283 3284 // check boot config 3285 3286 // uboot.env will be missing 3287 ubootEnv := filepath.Join(prepareDir, "system-seed", "uboot.env") 3288 c.Check(ubootEnv, testutil.FileAbsent) 3289 3290 // boot.sel will be present and have snapd_recovery_system set 3291 expectedLabel := image.MakeLabel(time.Now()) 3292 bootSel := filepath.Join(prepareDir, "system-seed", "uboot", "ubuntu", "boot.sel") 3293 3294 env, err := ubootenv.Open(bootSel) 3295 c.Assert(err, IsNil) 3296 c.Assert(env.Get("snapd_recovery_system"), Equals, expectedLabel) 3297 c.Assert(env.Get("snapd_recovery_mode"), Equals, "install") 3298 c.Assert(env.Get("snapd_boot_flags"), Equals, "factory") 3299 3300 // check recovery system specific config 3301 systems, err := filepath.Glob(filepath.Join(seeddir, "systems", "*")) 3302 c.Assert(err, IsNil) 3303 c.Assert(systems, HasLen, 1) 3304 c.Check(filepath.Base(systems[0]), Equals, expectedLabel) 3305 3306 // check we extracted the kernel assets 3307 for _, fileAndContent := range kernelContent { 3308 file := fileAndContent[0] 3309 content := fileAndContent[1] 3310 c.Assert(filepath.Join(systems[0], "kernel", file), testutil.FileEquals, content) 3311 } 3312 } 3313 3314 func (s *imageSuite) TestSetupSeedCore20NoKernelRefsConsumed(c *C) { 3315 bootloader.Force(nil) 3316 restore := image.MockTrusted(s.StoreSigning.Trusted) 3317 defer restore() 3318 3319 // a model that uses core20 and our gadget 3320 headers := map[string]interface{}{ 3321 "display-name": "my model", 3322 "architecture": "arm64", 3323 "base": "core20", 3324 "snaps": []interface{}{ 3325 map[string]interface{}{ 3326 "name": "arm-kernel", 3327 "id": s.AssertedSnapID("arm-kernel"), 3328 "type": "kernel", 3329 "default-channel": "20", 3330 }, 3331 map[string]interface{}{ 3332 "name": "uboot-gadget", 3333 "id": s.AssertedSnapID("uboot-gadget"), 3334 "type": "gadget", 3335 "default-channel": "20", 3336 }, 3337 }, 3338 } 3339 model := s.Brands.Model("my-brand", "my-model", headers) 3340 3341 prepareDir := c.MkDir() 3342 3343 s.makeSnap(c, "snapd", nil, snap.R(1), "") 3344 s.makeSnap(c, "core20", nil, snap.R(20), "") 3345 kernelYaml := ` 3346 assets: 3347 ref: 3348 update: true 3349 content: 3350 - dtbs/` 3351 kernelContent := [][]string{ 3352 {"meta/kernel.yaml", kernelYaml}, 3353 {"kernel.img", "some kernel"}, 3354 {"initrd.img", "some initrd"}, 3355 {"dtbs/foo.dtb", "some dtb"}, 3356 } 3357 s.makeSnap(c, "arm-kernel=20", kernelContent, snap.R(1), "") 3358 gadgetContent := [][]string{ 3359 // this file must be empty 3360 // TODO:UC20: write this test with non-empty uboot.env when we support 3361 // that 3362 {"uboot.conf", ""}, 3363 {"meta/gadget.yaml", piUC20GadgetYaml}, 3364 } 3365 s.makeSnap(c, "uboot-gadget=20", gadgetContent, snap.R(22), "") 3366 3367 opts := &image.Options{ 3368 PrepareDir: prepareDir, 3369 } 3370 3371 err := image.SetupSeed(s.tsto, model, opts) 3372 c.Assert(err, ErrorMatches, `no asset from the kernel.yaml needing synced update is consumed by the gadget at "/.*"`) 3373 } 3374 3375 func (s *imageSuite) TestPrepareWithUC20Preseed(c *C) { 3376 restoreSetupSeed := image.MockSetupSeed(func(tsto *tooling.ToolingStore, model *asserts.Model, opts *image.Options) error { 3377 return nil 3378 }) 3379 defer restoreSetupSeed() 3380 3381 var preseedCalled bool 3382 restorePreseedCore20 := image.MockPreseedCore20(func(dir, key, aaDir string) error { 3383 preseedCalled = true 3384 c.Assert(dir, Equals, "/a/dir") 3385 c.Assert(key, Equals, "foo") 3386 c.Assert(aaDir, Equals, "/custom/aa/features") 3387 return nil 3388 }) 3389 defer restorePreseedCore20() 3390 3391 model := s.makeUC20Model(nil) 3392 fn := filepath.Join(c.MkDir(), "model.assertion") 3393 c.Assert(ioutil.WriteFile(fn, asserts.Encode(model), 0644), IsNil) 3394 3395 err := image.Prepare(&image.Options{ 3396 ModelFile: fn, 3397 Preseed: true, 3398 PrepareDir: "/a/dir", 3399 PreseedSignKey: "foo", 3400 3401 AppArmorKernelFeaturesDir: "/custom/aa/features", 3402 }) 3403 c.Assert(err, IsNil) 3404 c.Check(preseedCalled, Equals, true) 3405 } 3406 3407 func (s *imageSuite) TestPrepareWithClassicPreseedError(c *C) { 3408 restoreSetupSeed := image.MockSetupSeed(func(tsto *tooling.ToolingStore, model *asserts.Model, opts *image.Options) error { 3409 return nil 3410 }) 3411 defer restoreSetupSeed() 3412 3413 err := image.Prepare(&image.Options{ 3414 Preseed: true, 3415 Classic: true, 3416 PrepareDir: "/a/dir", 3417 }) 3418 c.Assert(err, ErrorMatches, `cannot preseed the image for a classic model`) 3419 } 3420 3421 func (s *imageSuite) TestSetupSeedCore20DelegatedSnap(c *C) { 3422 bootloader.Force(nil) 3423 restore := image.MockTrusted(s.StoreSigning.Trusted) 3424 defer restore() 3425 3426 // a model that uses core20 3427 model := s.makeUC20Model(nil) 3428 3429 prepareDir := c.MkDir() 3430 3431 s.makeSnap(c, "snapd", nil, snap.R(1), "") 3432 s.makeSnap(c, "core20", nil, snap.R(20), "") 3433 s.makeSnap(c, "pc-kernel=20", nil, snap.R(1), "") 3434 gadgetContent := [][]string{ 3435 {"grub.conf", "# boot grub.cfg"}, 3436 {"meta/gadget.yaml", pcUC20GadgetYaml}, 3437 } 3438 s.makeSnap(c, "pc=20", gadgetContent, snap.R(22), "") 3439 3440 ra := map[string]interface{}{ 3441 "account-id": "my-brand", 3442 "provenance": []interface{}{"delegated-prov"}, 3443 } 3444 s.MakeAssertedDelegatedSnap(c, seedtest.SampleSnapYaml["required20"]+"\nprovenance: delegated-prov\n", nil, snap.R(1), "my-brand", "my-brand", "delegated-prov", ra, s.StoreSigning.Database) 3445 3446 opts := &image.Options{ 3447 PrepareDir: prepareDir, 3448 Customizations: image.Customizations{ 3449 BootFlags: []string{"factory"}, 3450 Validation: "ignore", 3451 }, 3452 } 3453 3454 err := image.SetupSeed(s.tsto, model, opts) 3455 c.Check(err, IsNil) 3456 }