gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/seed/seedwriter/writer_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019-2020 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package seedwriter_test 21 22 import ( 23 "encoding/json" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "testing" 29 "time" 30 31 . "gopkg.in/check.v1" 32 33 "github.com/snapcore/snapd/asserts" 34 "github.com/snapcore/snapd/asserts/assertstest" 35 "github.com/snapcore/snapd/osutil" 36 "github.com/snapcore/snapd/seed/seedtest" 37 "github.com/snapcore/snapd/seed/seedwriter" 38 "github.com/snapcore/snapd/snap" 39 "github.com/snapcore/snapd/snap/naming" 40 "github.com/snapcore/snapd/snap/snapfile" 41 "github.com/snapcore/snapd/snap/snaptest" 42 "github.com/snapcore/snapd/testutil" 43 ) 44 45 func Test(t *testing.T) { TestingT(t) } 46 47 type writerSuite struct { 48 testutil.BaseTest 49 50 // SeedSnaps helps creating and making available seed snaps 51 // (it provides MakeAssertedSnap etc.) for the tests. 52 *seedtest.SeedSnaps 53 54 opts *seedwriter.Options 55 56 db *asserts.Database 57 newFetcher seedwriter.NewFetcherFunc 58 rf seedwriter.RefAssertsFetcher 59 60 devAcct *asserts.Account 61 62 aRefs map[string][]*asserts.Ref 63 } 64 65 var _ = Suite(&writerSuite{}) 66 67 var ( 68 brandPrivKey, _ = assertstest.GenerateKey(752) 69 ) 70 71 func (s *writerSuite) SetUpTest(c *C) { 72 s.BaseTest.SetUpTest(c) 73 s.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})) 74 75 dir := c.MkDir() 76 seedDir := filepath.Join(dir, "seed") 77 err := os.Mkdir(seedDir, 0755) 78 c.Assert(err, IsNil) 79 80 s.opts = &seedwriter.Options{ 81 SeedDir: seedDir, 82 } 83 84 s.SeedSnaps = &seedtest.SeedSnaps{} 85 s.SetupAssertSigning("canonical") 86 s.Brands.Register("my-brand", brandPrivKey, map[string]interface{}{ 87 "verification": "verified", 88 }) 89 assertstest.AddMany(s.StoreSigning, s.Brands.AccountsAndKeys("my-brand")...) 90 91 s.devAcct = assertstest.NewAccount(s.StoreSigning, "developer", map[string]interface{}{ 92 "account-id": "developerid", 93 }, "") 94 assertstest.AddMany(s.StoreSigning, s.devAcct) 95 96 db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ 97 Backstore: asserts.NewMemoryBackstore(), 98 Trusted: s.StoreSigning.Trusted, 99 }) 100 c.Assert(err, IsNil) 101 s.db = db 102 103 retrieve := func(ref *asserts.Ref) (asserts.Assertion, error) { 104 return ref.Resolve(s.StoreSigning.Find) 105 } 106 s.newFetcher = func(save func(asserts.Assertion) error) asserts.Fetcher { 107 save2 := func(a asserts.Assertion) error { 108 // for checking 109 err := db.Add(a) 110 if err != nil { 111 if _, ok := err.(*asserts.RevisionError); ok { 112 return nil 113 } 114 return err 115 } 116 return save(a) 117 } 118 return asserts.NewFetcher(db, retrieve, save2) 119 } 120 s.rf = seedwriter.MakeRefAssertsFetcher(s.newFetcher) 121 122 s.aRefs = make(map[string][]*asserts.Ref) 123 } 124 125 var snapYaml = seedtest.MergeSampleSnapYaml(seedtest.SampleSnapYaml, map[string]string{ 126 "cont-producer": `name: cont-producer 127 type: app 128 base: core18 129 version: 1.1 130 slots: 131 cont: 132 interface: content 133 content: cont 134 `, 135 "cont-consumer": `name: cont-consumer 136 base: core18 137 version: 1.0 138 plugs: 139 cont: 140 interface: content 141 content: cont 142 default-provider: cont-producer 143 `, 144 "required-base-core16": `name: required-base-core16 145 type: app 146 base: core16 147 version: 1.0 148 `, 149 "my-devmode": `name: my-devmode 150 type: app 151 version: 1 152 confinement: devmode 153 `, 154 }) 155 156 const pcGadgetYaml = ` 157 volumes: 158 pc: 159 bootloader: grub 160 ` 161 162 var snapFiles = map[string][][]string{ 163 "pc": { 164 {"meta/gadget.yaml", pcGadgetYaml}, 165 }, 166 "pc=18": { 167 {"meta/gadget.yaml", pcGadgetYaml}, 168 }, 169 } 170 171 func (s *writerSuite) makeSnap(c *C, yamlKey, publisher string) { 172 if publisher == "" { 173 publisher = "canonical" 174 } 175 s.MakeAssertedSnap(c, snapYaml[yamlKey], snapFiles[yamlKey], snap.R(1), publisher, s.StoreSigning.Database) 176 } 177 178 func (s *writerSuite) makeLocalSnap(c *C, yamlKey string) (fname string) { 179 return snaptest.MakeTestSnapWithFiles(c, snapYaml[yamlKey], nil) 180 } 181 182 func (s *writerSuite) doFillMetaDownloadedSnap(c *C, w *seedwriter.Writer, sn *seedwriter.SeedSnap) *snap.Info { 183 info := s.AssertedSnapInfo(sn.SnapName()) 184 c.Assert(info, NotNil, Commentf("%s not defined", sn.SnapName())) 185 err := w.SetInfo(sn, info) 186 c.Assert(err, IsNil) 187 188 aRefs := s.aRefs[sn.SnapName()] 189 if aRefs == nil { 190 prev := len(s.rf.Refs()) 191 err = s.rf.Fetch(s.AssertedSnapRevision(sn.SnapName()).Ref()) 192 c.Assert(err, IsNil) 193 aRefs = s.rf.Refs()[prev:] 194 s.aRefs[sn.SnapName()] = aRefs 195 } 196 sn.ARefs = aRefs 197 198 return info 199 } 200 201 func (s *writerSuite) fillDownloadedSnap(c *C, w *seedwriter.Writer, sn *seedwriter.SeedSnap) { 202 info := s.doFillMetaDownloadedSnap(c, w, sn) 203 204 c.Assert(sn.Path, Equals, filepath.Join(s.opts.SeedDir, "snaps", info.Filename())) 205 err := os.Rename(s.AssertedSnap(sn.SnapName()), sn.Path) 206 c.Assert(err, IsNil) 207 } 208 209 func (s *writerSuite) fillMetaDownloadedSnap(c *C, w *seedwriter.Writer, sn *seedwriter.SeedSnap) { 210 s.doFillMetaDownloadedSnap(c, w, sn) 211 } 212 213 func (s *writerSuite) TestNewDefaultChannelError(c *C) { 214 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 215 "display-name": "my model", 216 "architecture": "amd64", 217 "gadget": "pc", 218 "kernel": "pc-kernel", 219 "required-snaps": []interface{}{"required"}, 220 }) 221 222 s.opts.DefaultChannel = "foo/bar" 223 w, err := seedwriter.New(model, s.opts) 224 c.Assert(w, IsNil) 225 c.Check(err, ErrorMatches, `cannot use global default option channel: invalid risk in channel name: foo/bar`) 226 } 227 228 func (s writerSuite) TestSetOptionsSnapsErrors(c *C) { 229 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 230 "display-name": "my model", 231 "architecture": "amd64", 232 "gadget": "pc", 233 "kernel": "pc-kernel", 234 "required-snaps": []interface{}{"required"}, 235 }) 236 237 tests := []struct { 238 snaps []*seedwriter.OptionsSnap 239 err string 240 }{ 241 {[]*seedwriter.OptionsSnap{{Name: "foo%&"}}, `invalid snap name: "foo%&"`}, 242 {[]*seedwriter.OptionsSnap{{Name: "foo_1"}}, `cannot use snap "foo_1", parallel snap instances are unsupported`}, 243 {[]*seedwriter.OptionsSnap{{Name: "foo"}, {Name: "foo"}}, `snap "foo" is repeated in options`}, 244 {[]*seedwriter.OptionsSnap{{Name: "foo", Channel: "track/foo"}}, `cannot use option channel for snap "foo": invalid risk in channel name: track/foo`}, 245 {[]*seedwriter.OptionsSnap{{Path: "not-a-snap"}}, `local option snap "not-a-snap" does not end in .snap`}, 246 {[]*seedwriter.OptionsSnap{{Path: "not-there.snap"}}, `local option snap "not-there.snap" does not exist`}, 247 {[]*seedwriter.OptionsSnap{{Name: "foo", Path: "foo.snap"}}, `cannot specify both name and path for option snap "foo"`}, 248 } 249 250 for _, t := range tests { 251 w, err := seedwriter.New(model, s.opts) 252 c.Assert(err, IsNil) 253 254 c.Check(w.SetOptionsSnaps(t.snaps), ErrorMatches, t.err) 255 } 256 } 257 258 func (s *writerSuite) TestSnapsToDownloadCore16(c *C) { 259 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 260 "display-name": "my model", 261 "architecture": "amd64", 262 "gadget": "pc", 263 "kernel": "pc-kernel", 264 "required-snaps": []interface{}{"required"}, 265 }) 266 267 w, err := seedwriter.New(model, s.opts) 268 c.Assert(err, IsNil) 269 270 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "edge"}}) 271 c.Assert(err, IsNil) 272 273 _, err = w.Start(s.db, s.newFetcher) 274 c.Assert(err, IsNil) 275 276 snaps, err := w.SnapsToDownload() 277 c.Assert(err, IsNil) 278 c.Check(snaps, HasLen, 4) 279 280 c.Check(naming.SameSnap(snaps[0], naming.Snap("core")), Equals, true) 281 c.Check(naming.SameSnap(snaps[1], naming.Snap("pc-kernel")), Equals, true) 282 c.Check(snaps[1].Channel, Equals, "stable") 283 c.Check(naming.SameSnap(snaps[2], naming.Snap("pc")), Equals, true) 284 c.Check(snaps[2].Channel, Equals, "edge") 285 c.Check(naming.SameSnap(snaps[3], naming.Snap("required")), Equals, true) 286 } 287 288 func (s *writerSuite) TestSnapsToDownloadOptionTrack(c *C) { 289 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 290 "display-name": "my model", 291 "architecture": "amd64", 292 "gadget": "pc", 293 "kernel": "pc-kernel", 294 "required-snaps": []interface{}{"required"}, 295 }) 296 297 w, err := seedwriter.New(model, s.opts) 298 c.Assert(err, IsNil) 299 300 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "track/edge"}}) 301 c.Assert(err, IsNil) 302 303 _, err = w.Start(s.db, s.newFetcher) 304 c.Assert(err, IsNil) 305 306 snaps, err := w.SnapsToDownload() 307 c.Assert(err, IsNil) 308 c.Check(snaps, HasLen, 4) 309 310 c.Check(naming.SameSnap(snaps[2], naming.Snap("pc")), Equals, true) 311 c.Check(snaps[2].Channel, Equals, "track/edge") 312 } 313 314 func (s *writerSuite) TestDownloadedCore16(c *C) { 315 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 316 "display-name": "my model", 317 "architecture": "amd64", 318 "gadget": "pc", 319 "kernel": "pc-kernel", 320 "required-snaps": []interface{}{"required"}, 321 }) 322 323 s.makeSnap(c, "core", "") 324 s.makeSnap(c, "pc-kernel", "") 325 s.makeSnap(c, "pc", "") 326 s.makeSnap(c, "required", "developerid") 327 328 w, err := seedwriter.New(model, s.opts) 329 c.Assert(err, IsNil) 330 331 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "edge"}}) 332 c.Assert(err, IsNil) 333 334 _, err = w.Start(s.db, s.newFetcher) 335 c.Assert(err, IsNil) 336 337 snaps, err := w.SnapsToDownload() 338 c.Assert(err, IsNil) 339 c.Check(snaps, HasLen, 4) 340 341 for _, sn := range snaps { 342 s.fillDownloadedSnap(c, w, sn) 343 } 344 345 complete, err := w.Downloaded() 346 c.Assert(err, IsNil) 347 c.Check(complete, Equals, true) 348 349 essSnaps, err := w.BootSnaps() 350 c.Assert(err, IsNil) 351 c.Check(essSnaps, DeepEquals, snaps[:3]) 352 c.Check(naming.SameSnap(essSnaps[2], naming.Snap("pc")), Equals, true) 353 c.Check(essSnaps[2].Channel, Equals, "edge") 354 } 355 356 func (s *writerSuite) TestDownloadedCore18(c *C) { 357 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 358 "display-name": "my model", 359 "architecture": "amd64", 360 "base": "core18", 361 "gadget": "pc=18", 362 "kernel": "pc-kernel=18", 363 "required-snaps": []interface{}{"cont-consumer", "cont-producer"}, 364 }) 365 366 s.makeSnap(c, "snapd", "") 367 s.makeSnap(c, "core18", "") 368 s.makeSnap(c, "pc-kernel=18", "") 369 s.makeSnap(c, "pc=18", "") 370 s.makeSnap(c, "cont-producer", "developerid") 371 s.makeSnap(c, "cont-consumer", "developerid") 372 373 w, err := seedwriter.New(model, s.opts) 374 c.Assert(err, IsNil) 375 376 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "edge"}}) 377 c.Assert(err, IsNil) 378 379 _, err = w.Start(s.db, s.newFetcher) 380 c.Assert(err, IsNil) 381 382 snaps, err := w.SnapsToDownload() 383 c.Assert(err, IsNil) 384 c.Check(snaps, HasLen, 6) 385 c.Check(naming.SameSnap(snaps[0], naming.Snap("snapd")), Equals, true) 386 c.Check(naming.SameSnap(snaps[1], naming.Snap("pc-kernel")), Equals, true) 387 c.Check(naming.SameSnap(snaps[2], naming.Snap("core18")), Equals, true) 388 c.Check(naming.SameSnap(snaps[3], naming.Snap("pc")), Equals, true) 389 c.Check(snaps[3].Channel, Equals, "18/edge") 390 391 for _, sn := range snaps { 392 s.fillDownloadedSnap(c, w, sn) 393 } 394 395 complete, err := w.Downloaded() 396 c.Assert(err, IsNil) 397 c.Check(complete, Equals, true) 398 399 essSnaps, err := w.BootSnaps() 400 c.Assert(err, IsNil) 401 c.Check(essSnaps, DeepEquals, snaps[:4]) 402 403 c.Check(w.Warnings(), HasLen, 0) 404 } 405 406 func (s *writerSuite) TestSnapsToDownloadCore18IncompatibleTrack(c *C) { 407 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 408 "display-name": "my model", 409 "architecture": "amd64", 410 "base": "core18", 411 "gadget": "pc=18", 412 "kernel": "pc-kernel=18", 413 "required-snaps": []interface{}{"cont-consumer", "cont-producer"}, 414 }) 415 416 s.makeSnap(c, "snapd", "") 417 s.makeSnap(c, "core18", "") 418 s.makeSnap(c, "pc-kernel=18", "") 419 s.makeSnap(c, "pc=18", "") 420 s.makeSnap(c, "cont-producer", "developerid") 421 s.makeSnap(c, "cont-consumer", "developerid") 422 423 w, err := seedwriter.New(model, s.opts) 424 c.Assert(err, IsNil) 425 426 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc-kernel", Channel: "18.1"}}) 427 c.Assert(err, IsNil) 428 429 _, err = w.Start(s.db, s.newFetcher) 430 c.Assert(err, IsNil) 431 432 _, err = w.SnapsToDownload() 433 c.Check(err, ErrorMatches, `option channel "18.1" for kernel "pc-kernel" has a track incompatible with the pinned track from model assertion: 18`) 434 } 435 436 func (s *writerSuite) TestSnapsToDownloadDefaultChannel(c *C) { 437 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 438 "display-name": "my model", 439 "architecture": "amd64", 440 "base": "core18", 441 "gadget": "pc=18", 442 "kernel": "pc-kernel=18", 443 "required-snaps": []interface{}{"cont-consumer", "cont-producer"}, 444 }) 445 446 s.makeSnap(c, "snapd", "") 447 s.makeSnap(c, "core18", "") 448 s.makeSnap(c, "pc-kernel=18", "") 449 s.makeSnap(c, "pc=18", "") 450 s.makeSnap(c, "cont-producer", "developerid") 451 s.makeSnap(c, "cont-consumer", "developerid") 452 453 s.opts.DefaultChannel = "candidate" 454 w, err := seedwriter.New(model, s.opts) 455 c.Assert(err, IsNil) 456 457 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "edge"}}) 458 c.Assert(err, IsNil) 459 460 _, err = w.Start(s.db, s.newFetcher) 461 c.Assert(err, IsNil) 462 463 snaps, err := w.SnapsToDownload() 464 c.Assert(err, IsNil) 465 c.Check(snaps, HasLen, 6) 466 467 for i, name := range []string{"snapd", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer"} { 468 c.Check(naming.SameSnap(snaps[i], naming.Snap(name)), Equals, true) 469 channel := "candidate" 470 switch name { 471 case "pc-kernel": 472 channel = "18/candidate" 473 case "pc": 474 channel = "18/edge" 475 } 476 c.Check(snaps[i].Channel, Equals, channel) 477 } 478 } 479 480 func (s *writerSuite) upToDownloaded(c *C, model *asserts.Model, fill func(c *C, w *seedwriter.Writer, sn *seedwriter.SeedSnap)) (complete bool, w *seedwriter.Writer, err error) { 481 w, err = seedwriter.New(model, s.opts) 482 c.Assert(err, IsNil) 483 484 _, err = w.Start(s.db, s.newFetcher) 485 c.Assert(err, IsNil) 486 487 snaps, err := w.SnapsToDownload() 488 c.Assert(err, IsNil) 489 490 for _, sn := range snaps { 491 fill(c, w, sn) 492 } 493 494 complete, err = w.Downloaded() 495 return complete, w, err 496 } 497 498 func (s *writerSuite) TestDownloadedCheckBaseGadget(c *C) { 499 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 500 "display-name": "my model", 501 "architecture": "amd64", 502 "base": "core18", 503 "gadget": "pc", 504 "kernel": "pc-kernel=18", 505 }) 506 507 s.makeSnap(c, "snapd", "") 508 s.makeSnap(c, "core18", "") 509 s.makeSnap(c, "pc-kernel=18", "") 510 s.makeSnap(c, "pc", "") 511 512 _, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap) 513 c.Check(err, ErrorMatches, `cannot use gadget snap because its base "" is different from model base "core18"`) 514 515 } 516 517 func (s *writerSuite) TestDownloadedCheckBase(c *C) { 518 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 519 "display-name": "my model", 520 "architecture": "amd64", 521 "gadget": "pc", 522 "kernel": "pc-kernel", 523 "required-snaps": []interface{}{"cont-producer"}, 524 }) 525 526 s.makeSnap(c, "core", "") 527 s.makeSnap(c, "pc-kernel", "") 528 s.makeSnap(c, "pc", "") 529 s.makeSnap(c, "cont-producer", "developerid") 530 531 _, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap) 532 c.Check(err, ErrorMatches, `cannot add snap "cont-producer" without also adding its base "core18" explicitly`) 533 534 } 535 536 func (s *writerSuite) TestOutOfOrder(c *C) { 537 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 538 "display-name": "my model", 539 "architecture": "amd64", 540 "gadget": "pc", 541 "kernel": "pc-kernel", 542 "required-snaps": []interface{}{"required"}, 543 }) 544 545 w, err := seedwriter.New(model, s.opts) 546 c.Assert(err, IsNil) 547 548 c.Check(w.WriteMeta(), ErrorMatches, "internal error: seedwriter.Writer expected Start|SetOptionsSnaps to be invoked on it at this point, not WriteMeta") 549 c.Check(w.SeedSnaps(nil), ErrorMatches, "internal error: seedwriter.Writer expected Start|SetOptionsSnaps to be invoked on it at this point, not SeedSnaps") 550 551 _, err = w.Start(s.db, s.newFetcher) 552 c.Assert(err, IsNil) 553 _, err = w.Downloaded() 554 c.Check(err, ErrorMatches, "internal error: seedwriter.Writer expected SnapToDownload|LocalSnaps to be invoked on it at this point, not Downloaded") 555 556 _, err = w.BootSnaps() 557 c.Check(err, ErrorMatches, "internal error: seedwriter.Writer cannot query seed snaps before Downloaded signaled complete") 558 559 _, err = w.UnassertedSnaps() 560 c.Check(err, ErrorMatches, "internal error: seedwriter.Writer cannot query seed snaps before Downloaded signaled complete") 561 562 } 563 564 func (s *writerSuite) TestOutOfOrderWithLocalSnaps(c *C) { 565 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 566 "display-name": "my model", 567 "architecture": "amd64", 568 "gadget": "pc", 569 "kernel": "pc-kernel", 570 "required-snaps": []interface{}{"required"}, 571 }) 572 573 w, err := seedwriter.New(model, s.opts) 574 c.Assert(err, IsNil) 575 576 requiredFn := s.makeLocalSnap(c, "required") 577 578 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: requiredFn}}) 579 c.Assert(err, IsNil) 580 581 _, err = w.Start(s.db, s.newFetcher) 582 c.Assert(err, IsNil) 583 584 _, err = w.SnapsToDownload() 585 c.Check(err, ErrorMatches, `internal error: seedwriter.Writer expected LocalSnaps to be invoked on it at this point, not SnapsToDownload`) 586 587 _, err = w.LocalSnaps() 588 c.Assert(err, IsNil) 589 590 _, err = w.SnapsToDownload() 591 c.Check(err, ErrorMatches, `internal error: seedwriter.Writer expected InfoDerived to be invoked on it at this point, not SnapsToDownload`) 592 } 593 594 func (s *writerSuite) TestDownloadedInfosNotSet(c *C) { 595 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 596 "display-name": "my model", 597 "architecture": "amd64", 598 "gadget": "pc", 599 "kernel": "pc-kernel", 600 "required-snaps": []interface{}{"required"}, 601 }) 602 603 doNothingFill := func(*C, *seedwriter.Writer, *seedwriter.SeedSnap) {} 604 605 _, _, err := s.upToDownloaded(c, model, doNothingFill) 606 c.Check(err, ErrorMatches, `internal error: before seedwriter.Writer.Downloaded snap \"core\" Info should have been set`) 607 } 608 609 func (s *writerSuite) TestDownloadedUnexpectedClassicSnap(c *C) { 610 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 611 "display-name": "my model", 612 "architecture": "amd64", 613 "gadget": "pc", 614 "kernel": "pc-kernel", 615 "required-snaps": []interface{}{"classic-snap"}, 616 }) 617 618 s.makeSnap(c, "core", "") 619 s.makeSnap(c, "pc-kernel", "") 620 s.makeSnap(c, "pc", "") 621 s.makeSnap(c, "classic-snap", "developerid") 622 623 _, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap) 624 c.Check(err, ErrorMatches, `cannot use classic snap "classic-snap" in a core system`) 625 } 626 627 func (s *writerSuite) TestDownloadedPublisherMismatchKernel(c *C) { 628 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 629 "display-name": "my model", 630 "architecture": "amd64", 631 "gadget": "pc", 632 "kernel": "pc-kernel", 633 }) 634 635 s.makeSnap(c, "core", "") 636 s.makeSnap(c, "pc-kernel", "developerid") 637 s.makeSnap(c, "pc", "") 638 639 _, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap) 640 c.Check(err, ErrorMatches, `cannot use kernel "pc-kernel" published by "developerid" for model by "my-brand"`) 641 } 642 643 func (s *writerSuite) TestDownloadedPublisherMismatchGadget(c *C) { 644 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 645 "display-name": "my model", 646 "architecture": "amd64", 647 "gadget": "pc", 648 "kernel": "pc-kernel", 649 }) 650 651 s.makeSnap(c, "core", "") 652 s.makeSnap(c, "pc-kernel", "") 653 s.makeSnap(c, "pc", "developerid") 654 655 _, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap) 656 c.Check(err, ErrorMatches, `cannot use gadget "pc" published by "developerid" for model by "my-brand"`) 657 } 658 659 func (s *writerSuite) TestDownloadedMissingDefaultProvider(c *C) { 660 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 661 "display-name": "my model", 662 "architecture": "amd64", 663 "base": "core18", 664 "gadget": "pc=18", 665 "kernel": "pc-kernel=18", 666 "required-snaps": []interface{}{"cont-consumer"}, 667 }) 668 669 s.makeSnap(c, "snapd", "") 670 s.makeSnap(c, "core18", "") 671 s.makeSnap(c, "pc-kernel=18", "") 672 s.makeSnap(c, "pc=18", "") 673 s.makeSnap(c, "cont-consumer", "developerid") 674 675 _, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap) 676 c.Check(err, ErrorMatches, `cannot use snap "cont-consumer" without its default content provider "cont-producer" being added explicitly`) 677 } 678 679 func (s *writerSuite) TestDownloadedCheckType(c *C) { 680 s.makeSnap(c, "snapd", "") 681 s.makeSnap(c, "core18", "") 682 s.makeSnap(c, "core", "") 683 s.makeSnap(c, "pc-kernel=18", "") 684 s.makeSnap(c, "pc=18", "") 685 s.makeSnap(c, "cont-producer", "developerid") 686 s.makeSnap(c, "cont-consumer", "developerid") 687 688 core18headers := map[string]interface{}{ 689 "display-name": "my model", 690 "architecture": "amd64", 691 "base": "core18", 692 "gadget": "pc=18", 693 "kernel": "pc-kernel=18", 694 "required-snaps": []interface{}{"cont-consumer", "cont-producer"}, 695 } 696 697 tests := []struct { 698 header string 699 wrongTypeSnap string 700 what string 701 }{ 702 {"base", "core", "boot base"}, 703 {"gadget", "cont-consumer", "gadget"}, 704 {"kernel", "cont-consumer", "kernel"}, 705 {"required-snaps", "pc", "snap"}, 706 {"required-snaps", "pc-kernel", "snap"}, 707 } 708 709 for _, t := range tests { 710 var wrongTypeSnap interface{} = t.wrongTypeSnap 711 if t.header == "required-snaps" { 712 wrongTypeSnap = []interface{}{wrongTypeSnap} 713 } 714 model := s.Brands.Model("my-brand", "my-model", core18headers, map[string]interface{}{ 715 t.header: wrongTypeSnap, 716 }) 717 718 _, _, err := s.upToDownloaded(c, model, s.fillMetaDownloadedSnap) 719 720 expErr := fmt.Sprintf("%s %q has unexpected type: %v", t.what, t.wrongTypeSnap, s.AssertedSnapInfo(t.wrongTypeSnap).Type()) 721 c.Check(err, ErrorMatches, expErr) 722 } 723 } 724 725 func (s *writerSuite) TestDownloadedCheckTypeSnapd(c *C) { 726 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 727 "display-name": "my model", 728 "architecture": "amd64", 729 "base": "core18", 730 "gadget": "pc=18", 731 "kernel": "pc-kernel=18", 732 }) 733 734 s.makeSnap(c, "snapd", "") 735 s.makeSnap(c, "core18", "") 736 s.makeSnap(c, "pc-kernel=18", "") 737 s.makeSnap(c, "pc=18", "") 738 739 // break type 740 s.AssertedSnapInfo("snapd").SnapType = snap.TypeGadget 741 _, _, err := s.upToDownloaded(c, model, s.fillMetaDownloadedSnap) 742 c.Check(err, ErrorMatches, `snapd snap has unexpected type: gadget`) 743 } 744 745 func (s *writerSuite) TestDownloadedCheckTypeCore(c *C) { 746 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 747 "display-name": "my model", 748 "architecture": "amd64", 749 "gadget": "pc", 750 "kernel": "pc-kernel", 751 }) 752 753 s.makeSnap(c, "core", "") 754 s.makeSnap(c, "pc-kernel", "") 755 s.makeSnap(c, "pc", "") 756 757 // break type 758 s.AssertedSnapInfo("core").SnapType = snap.TypeBase 759 _, _, err := s.upToDownloaded(c, model, s.fillMetaDownloadedSnap) 760 c.Check(err, ErrorMatches, `core snap has unexpected type: base`) 761 } 762 763 func (s *writerSuite) TestSeedSnapsWriteMetaCore16(c *C) { 764 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 765 "display-name": "my model", 766 "architecture": "amd64", 767 "gadget": "pc", 768 "kernel": "pc-kernel", 769 }) 770 771 // the minimum set of snaps 772 s.makeSnap(c, "core", "") 773 s.makeSnap(c, "pc-kernel", "") 774 s.makeSnap(c, "pc", "") 775 776 w, err := seedwriter.New(model, s.opts) 777 c.Assert(err, IsNil) 778 779 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "edge"}}) 780 c.Assert(err, IsNil) 781 782 _, err = w.Start(s.db, s.newFetcher) 783 c.Assert(err, IsNil) 784 785 snaps, err := w.SnapsToDownload() 786 c.Assert(err, IsNil) 787 c.Check(snaps, HasLen, 3) 788 789 for _, sn := range snaps { 790 s.fillDownloadedSnap(c, w, sn) 791 } 792 793 complete, err := w.Downloaded() 794 c.Assert(err, IsNil) 795 c.Check(complete, Equals, true) 796 797 err = w.SeedSnaps(nil) 798 c.Assert(err, IsNil) 799 800 err = w.WriteMeta() 801 c.Assert(err, IsNil) 802 803 // check seed 804 seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml")) 805 c.Assert(err, IsNil) 806 807 c.Check(seedYaml.Snaps, HasLen, 3) 808 809 // check the files are in place 810 for i, name := range []string{"core", "pc-kernel", "pc"} { 811 info := s.AssertedSnapInfo(name) 812 813 fn := info.Filename() 814 p := filepath.Join(s.opts.SeedDir, "snaps", fn) 815 c.Check(p, testutil.FilePresent) 816 817 channel := "stable" 818 if name == "pc" { 819 channel = "edge" 820 } 821 822 c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{ 823 Name: info.SnapName(), 824 SnapID: info.SnapID, 825 Channel: channel, 826 File: fn, 827 Contact: info.Contact(), 828 }) 829 } 830 831 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 832 c.Assert(err, IsNil) 833 c.Check(l, HasLen, 3) 834 835 // check assertions 836 seedAssertsDir := filepath.Join(s.opts.SeedDir, "assertions") 837 storeAccountKeyPK := s.StoreSigning.StoreAccountKey("").PublicKeyID() 838 brandAcctKeyPK := s.Brands.AccountKey("my-brand").PublicKeyID() 839 840 for _, fn := range []string{"model", brandAcctKeyPK + ".account-key", "my-brand.account", storeAccountKeyPK + ".account-key"} { 841 p := filepath.Join(seedAssertsDir, fn) 842 c.Check(p, testutil.FilePresent) 843 } 844 845 c.Check(filepath.Join(seedAssertsDir, "model"), testutil.FileEquals, asserts.Encode(model)) 846 847 acct := seedtest.ReadAssertions(c, filepath.Join(seedAssertsDir, "my-brand.account")) 848 c.Assert(acct, HasLen, 1) 849 c.Check(acct[0].Type(), Equals, asserts.AccountType) 850 c.Check(acct[0].HeaderString("account-id"), Equals, "my-brand") 851 852 // check the snap assertions are also in place 853 for _, snapName := range []string{"pc-kernel", "core", "pc"} { 854 p := filepath.Join(seedAssertsDir, fmt.Sprintf("16,%s.snap-declaration", s.AssertedSnapID(snapName))) 855 decl := seedtest.ReadAssertions(c, p) 856 c.Assert(decl, HasLen, 1) 857 c.Check(decl[0].Type(), Equals, asserts.SnapDeclarationType) 858 c.Check(decl[0].HeaderString("snap-name"), Equals, snapName) 859 p = filepath.Join(seedAssertsDir, fmt.Sprintf("%s.snap-revision", s.AssertedSnapRevision(snapName).SnapSHA3_384())) 860 rev := seedtest.ReadAssertions(c, p) 861 c.Assert(rev, HasLen, 1) 862 c.Check(rev[0].Type(), Equals, asserts.SnapRevisionType) 863 c.Check(rev[0].HeaderString("snap-id"), Equals, s.AssertedSnapID(snapName)) 864 } 865 866 // sanity check of seedtest helper 867 const usesSnapd = false 868 // core seeds do not use system labels 869 seedtest.ValidateSeed(c, s.opts.SeedDir, "", usesSnapd, s.StoreSigning.Trusted) 870 } 871 872 func (s *writerSuite) TestSeedSnapsWriteMetaCore18(c *C) { 873 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 874 "display-name": "my model", 875 "architecture": "amd64", 876 "base": "core18", 877 "gadget": "pc=18", 878 "kernel": "pc-kernel=18", 879 "required-snaps": []interface{}{"cont-consumer", "cont-producer"}, 880 }) 881 882 s.makeSnap(c, "snapd", "") 883 s.makeSnap(c, "core18", "") 884 s.makeSnap(c, "pc-kernel=18", "") 885 s.makeSnap(c, "pc=18", "") 886 s.makeSnap(c, "cont-producer", "developerid") 887 s.makeSnap(c, "cont-consumer", "developerid") 888 889 w, err := seedwriter.New(model, s.opts) 890 c.Assert(err, IsNil) 891 892 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "edge"}}) 893 c.Assert(err, IsNil) 894 895 _, err = w.Start(s.db, s.newFetcher) 896 c.Assert(err, IsNil) 897 898 snaps, err := w.SnapsToDownload() 899 c.Assert(err, IsNil) 900 c.Check(snaps, HasLen, 6) 901 902 s.AssertedSnapInfo("cont-producer").EditedContact = "mailto:author@cont-producer.net" 903 for _, sn := range snaps { 904 s.fillDownloadedSnap(c, w, sn) 905 } 906 907 complete, err := w.Downloaded() 908 c.Assert(err, IsNil) 909 c.Check(complete, Equals, true) 910 911 err = w.SeedSnaps(nil) 912 c.Assert(err, IsNil) 913 914 err = w.WriteMeta() 915 c.Assert(err, IsNil) 916 917 // check seed 918 seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml")) 919 c.Assert(err, IsNil) 920 921 c.Check(seedYaml.Snaps, HasLen, 6) 922 923 // check the files are in place 924 for i, name := range []string{"snapd", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer"} { 925 info := s.AssertedSnapInfo(name) 926 927 fn := info.Filename() 928 p := filepath.Join(s.opts.SeedDir, "snaps", fn) 929 c.Check(p, testutil.FilePresent) 930 931 channel := "stable" 932 switch name { 933 case "pc-kernel": 934 channel = "18" 935 case "pc": 936 channel = "18/edge" 937 } 938 939 c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{ 940 Name: info.SnapName(), 941 SnapID: info.SnapID, 942 Channel: channel, 943 File: fn, 944 Contact: info.Contact(), 945 }) 946 } 947 948 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 949 c.Assert(err, IsNil) 950 c.Check(l, HasLen, 6) 951 952 // check assertions 953 seedAssertsDir := filepath.Join(s.opts.SeedDir, "assertions") 954 storeAccountKeyPK := s.StoreSigning.StoreAccountKey("").PublicKeyID() 955 brandAcctKeyPK := s.Brands.AccountKey("my-brand").PublicKeyID() 956 957 for _, fn := range []string{"model", brandAcctKeyPK + ".account-key", "my-brand.account", storeAccountKeyPK + ".account-key"} { 958 p := filepath.Join(seedAssertsDir, fn) 959 c.Check(p, testutil.FilePresent) 960 } 961 962 c.Check(filepath.Join(seedAssertsDir, "model"), testutil.FileEquals, asserts.Encode(model)) 963 964 acct := seedtest.ReadAssertions(c, filepath.Join(seedAssertsDir, "my-brand.account")) 965 c.Assert(acct, HasLen, 1) 966 c.Check(acct[0].Type(), Equals, asserts.AccountType) 967 c.Check(acct[0].HeaderString("account-id"), Equals, "my-brand") 968 969 // check the snap assertions are also in place 970 for _, snapName := range []string{"snapd", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer"} { 971 p := filepath.Join(seedAssertsDir, fmt.Sprintf("16,%s.snap-declaration", s.AssertedSnapID(snapName))) 972 decl := seedtest.ReadAssertions(c, p) 973 c.Assert(decl, HasLen, 1) 974 c.Check(decl[0].Type(), Equals, asserts.SnapDeclarationType) 975 c.Check(decl[0].HeaderString("snap-name"), Equals, snapName) 976 p = filepath.Join(seedAssertsDir, fmt.Sprintf("%s.snap-revision", s.AssertedSnapRevision(snapName).SnapSHA3_384())) 977 rev := seedtest.ReadAssertions(c, p) 978 c.Assert(rev, HasLen, 1) 979 c.Check(rev[0].Type(), Equals, asserts.SnapRevisionType) 980 c.Check(rev[0].HeaderString("snap-id"), Equals, s.AssertedSnapID(snapName)) 981 } 982 983 // sanity check of seedtest helper 984 const usesSnapd = true 985 // core18 seeds do not use system labels 986 seedtest.ValidateSeed(c, s.opts.SeedDir, "", usesSnapd, s.StoreSigning.Trusted) 987 } 988 989 func (s *writerSuite) TestSeedSnapsWriteMetaCore18StoreAssertion(c *C) { 990 // add store assertion 991 storeAs, err := s.StoreSigning.Sign(asserts.StoreType, map[string]interface{}{ 992 "store": "my-store", 993 "operator-id": "canonical", 994 "timestamp": time.Now().UTC().Format(time.RFC3339), 995 }, nil, "") 996 c.Assert(err, IsNil) 997 err = s.StoreSigning.Add(storeAs) 998 c.Assert(err, IsNil) 999 1000 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1001 "display-name": "my model", 1002 "architecture": "amd64", 1003 "base": "core18", 1004 "gadget": "pc=18", 1005 "kernel": "pc-kernel=18", 1006 "store": "my-store", 1007 }) 1008 1009 s.makeSnap(c, "snapd", "") 1010 s.makeSnap(c, "core18", "") 1011 s.makeSnap(c, "pc-kernel=18", "") 1012 s.makeSnap(c, "pc=18", "") 1013 1014 complete, w, err := s.upToDownloaded(c, model, s.fillDownloadedSnap) 1015 c.Assert(err, IsNil) 1016 c.Check(complete, Equals, true) 1017 1018 err = w.SeedSnaps(nil) 1019 c.Assert(err, IsNil) 1020 1021 err = w.WriteMeta() 1022 c.Assert(err, IsNil) 1023 1024 // check assertions 1025 seedAssertsDir := filepath.Join(s.opts.SeedDir, "assertions") 1026 // check the store assertion was fetched 1027 p := filepath.Join(seedAssertsDir, "my-store.store") 1028 c.Check(p, testutil.FilePresent) 1029 } 1030 1031 func (s *writerSuite) TestLocalSnaps(c *C) { 1032 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1033 "display-name": "my model", 1034 "architecture": "amd64", 1035 "base": "core18", 1036 "gadget": "pc=18", 1037 "kernel": "pc-kernel=18", 1038 "required-snaps": []interface{}{"cont-consumer", "cont-producer"}, 1039 }) 1040 1041 core18Fn := s.makeLocalSnap(c, "core18") 1042 pcKernelFn := s.makeLocalSnap(c, "pc-kernel=18") 1043 pcFn := s.makeLocalSnap(c, "pc=18") 1044 contConsumerFn := s.makeLocalSnap(c, "cont-consumer") 1045 1046 w, err := seedwriter.New(model, s.opts) 1047 c.Assert(err, IsNil) 1048 1049 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{ 1050 {Path: core18Fn}, 1051 {Path: pcFn, Channel: "edge"}, 1052 {Path: pcKernelFn}, 1053 {Path: contConsumerFn}, 1054 }) 1055 c.Assert(err, IsNil) 1056 1057 _, err = w.Start(s.db, s.newFetcher) 1058 c.Assert(err, IsNil) 1059 1060 localSnaps, err := w.LocalSnaps() 1061 c.Assert(err, IsNil) 1062 c.Assert(localSnaps, HasLen, 4) 1063 c.Check(localSnaps[0].Path, Equals, core18Fn) 1064 c.Check(localSnaps[1].Path, Equals, pcFn) 1065 c.Check(localSnaps[2].Path, Equals, pcKernelFn) 1066 c.Check(localSnaps[3].Path, Equals, contConsumerFn) 1067 } 1068 1069 func (s *writerSuite) TestLocalSnapsCore18FullUse(c *C) { 1070 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1071 "display-name": "my model", 1072 "architecture": "amd64", 1073 "base": "core18", 1074 "gadget": "pc=18", 1075 "kernel": "pc-kernel=18", 1076 "required-snaps": []interface{}{"cont-consumer", "cont-producer"}, 1077 }) 1078 1079 s.makeSnap(c, "snapd", "") 1080 s.makeSnap(c, "cont-producer", "developerid") 1081 1082 core18Fn := s.makeLocalSnap(c, "core18") 1083 pcKernelFn := s.makeLocalSnap(c, "pc-kernel=18") 1084 pcFn := s.makeLocalSnap(c, "pc=18") 1085 contConsumerFn := s.makeLocalSnap(c, "cont-consumer") 1086 1087 w, err := seedwriter.New(model, s.opts) 1088 c.Assert(err, IsNil) 1089 1090 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{ 1091 {Path: core18Fn}, 1092 {Name: "pc-kernel", Channel: "candidate"}, 1093 {Path: pcFn, Channel: "edge"}, 1094 {Path: pcKernelFn}, 1095 {Path: s.AssertedSnap("cont-producer")}, 1096 {Path: contConsumerFn}, 1097 }) 1098 c.Assert(err, IsNil) 1099 1100 tf, err := w.Start(s.db, s.newFetcher) 1101 c.Assert(err, IsNil) 1102 1103 localSnaps, err := w.LocalSnaps() 1104 c.Assert(err, IsNil) 1105 c.Assert(localSnaps, HasLen, 5) 1106 1107 for _, sn := range localSnaps { 1108 si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db) 1109 if !asserts.IsNotFound(err) { 1110 c.Assert(err, IsNil) 1111 } 1112 f, err := snapfile.Open(sn.Path) 1113 c.Assert(err, IsNil) 1114 info, err := snap.ReadInfoFromSnapFile(f, si) 1115 c.Assert(err, IsNil) 1116 w.SetInfo(sn, info) 1117 sn.ARefs = aRefs 1118 } 1119 1120 err = w.InfoDerived() 1121 c.Assert(err, IsNil) 1122 1123 snaps, err := w.SnapsToDownload() 1124 c.Assert(err, IsNil) 1125 c.Check(snaps, HasLen, 1) 1126 c.Check(naming.SameSnap(snaps[0], naming.Snap("snapd")), Equals, true) 1127 1128 for _, sn := range snaps { 1129 s.fillDownloadedSnap(c, w, sn) 1130 } 1131 1132 complete, err := w.Downloaded() 1133 c.Assert(err, IsNil) 1134 c.Check(complete, Equals, true) 1135 1136 copySnap := func(name, src, dst string) error { 1137 return osutil.CopyFile(src, dst, 0) 1138 } 1139 1140 err = w.SeedSnaps(copySnap) 1141 c.Assert(err, IsNil) 1142 1143 err = w.WriteMeta() 1144 c.Assert(err, IsNil) 1145 1146 // check seed 1147 seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml")) 1148 c.Assert(err, IsNil) 1149 1150 c.Check(seedYaml.Snaps, HasLen, 6) 1151 1152 assertedNum := 0 1153 // check the files are in place 1154 for i, name := range []string{"snapd", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer"} { 1155 info := s.AssertedSnapInfo(name) 1156 unasserted := false 1157 if info == nil { 1158 info = &snap.Info{ 1159 SuggestedName: name, 1160 } 1161 info.Revision = snap.R(-1) 1162 unasserted = true 1163 } else { 1164 assertedNum++ 1165 } 1166 1167 fn := info.Filename() 1168 p := filepath.Join(s.opts.SeedDir, "snaps", fn) 1169 c.Check(p, testutil.FilePresent) 1170 1171 channel := "" 1172 if !unasserted { 1173 channel = "stable" 1174 } 1175 1176 c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{ 1177 Name: info.SnapName(), 1178 SnapID: info.SnapID, 1179 Channel: channel, 1180 File: fn, 1181 Unasserted: unasserted, 1182 }) 1183 } 1184 c.Check(assertedNum, Equals, 2) 1185 1186 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 1187 c.Assert(err, IsNil) 1188 c.Check(l, HasLen, 6) 1189 1190 // check the snap assertions are in place 1191 seedAssertsDir := filepath.Join(s.opts.SeedDir, "assertions") 1192 for _, snapName := range []string{"snapd", "cont-producer"} { 1193 p := filepath.Join(seedAssertsDir, fmt.Sprintf("16,%s.snap-declaration", s.AssertedSnapID(snapName))) 1194 decl := seedtest.ReadAssertions(c, p) 1195 c.Assert(decl, HasLen, 1) 1196 c.Check(decl[0].Type(), Equals, asserts.SnapDeclarationType) 1197 c.Check(decl[0].HeaderString("snap-name"), Equals, snapName) 1198 p = filepath.Join(seedAssertsDir, fmt.Sprintf("%s.snap-revision", s.AssertedSnapRevision(snapName).SnapSHA3_384())) 1199 rev := seedtest.ReadAssertions(c, p) 1200 c.Assert(rev, HasLen, 1) 1201 c.Check(rev[0].Type(), Equals, asserts.SnapRevisionType) 1202 c.Check(rev[0].HeaderString("snap-id"), Equals, s.AssertedSnapID(snapName)) 1203 } 1204 1205 unassertedSnaps, err := w.UnassertedSnaps() 1206 c.Assert(err, IsNil) 1207 c.Check(unassertedSnaps, HasLen, 4) 1208 unassertedSet := naming.NewSnapSet(unassertedSnaps) 1209 for _, snapName := range []string{"core18", "pc-kernel", "pc", "cont-consumer"} { 1210 c.Check(unassertedSet.Contains(naming.Snap(snapName)), Equals, true) 1211 } 1212 } 1213 1214 func (s *writerSuite) TestSeedSnapsWriteMetaDefaultTrackCore18(c *C) { 1215 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1216 "display-name": "my model", 1217 "architecture": "amd64", 1218 "base": "core18", 1219 "gadget": "pc=18", 1220 "kernel": "pc-kernel=18", 1221 "required-snaps": []interface{}{"required18"}, 1222 }) 1223 1224 s.makeSnap(c, "snapd", "") 1225 s.makeSnap(c, "core18", "") 1226 s.makeSnap(c, "pc-kernel=18", "") 1227 s.makeSnap(c, "pc=18", "") 1228 s.makeSnap(c, "required18", "developerid") 1229 1230 w, err := seedwriter.New(model, s.opts) 1231 c.Assert(err, IsNil) 1232 1233 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "required18", Channel: "candidate"}}) 1234 c.Assert(err, IsNil) 1235 1236 _, err = w.Start(s.db, s.newFetcher) 1237 c.Assert(err, IsNil) 1238 1239 snaps, err := w.SnapsToDownload() 1240 c.Assert(err, IsNil) 1241 c.Check(snaps, HasLen, 5) 1242 1243 for _, sn := range snaps { 1244 s.fillDownloadedSnap(c, w, sn) 1245 if sn.SnapName() == "required18" { 1246 c.Check(sn.Channel, Equals, "candidate") 1247 w.SetRedirectChannel(sn, "default-track/candidate") 1248 } 1249 } 1250 1251 complete, err := w.Downloaded() 1252 c.Assert(err, IsNil) 1253 c.Check(complete, Equals, true) 1254 1255 err = w.SeedSnaps(nil) 1256 c.Assert(err, IsNil) 1257 1258 err = w.WriteMeta() 1259 c.Assert(err, IsNil) 1260 1261 // check seed 1262 seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml")) 1263 c.Assert(err, IsNil) 1264 1265 c.Check(seedYaml.Snaps, HasLen, 5) 1266 1267 info := s.AssertedSnapInfo("required18") 1268 fn := info.Filename() 1269 c.Check(filepath.Join(s.opts.SeedDir, "snaps", fn), testutil.FilePresent) 1270 c.Check(seedYaml.Snaps[4], DeepEquals, &seedwriter.InternalSnap16{ 1271 Name: info.SnapName(), 1272 SnapID: info.SnapID, 1273 Channel: "default-track/candidate", 1274 File: fn, 1275 }) 1276 1277 } 1278 1279 func (s *writerSuite) TestSetRedirectChannelErrors(c *C) { 1280 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1281 "display-name": "my model", 1282 "architecture": "amd64", 1283 "base": "core18", 1284 "gadget": "pc=18", 1285 "kernel": "pc-kernel=18", 1286 "required-snaps": []interface{}{"required18"}, 1287 }) 1288 1289 s.makeSnap(c, "snapd", "") 1290 s.makeSnap(c, "core18", "") 1291 s.makeSnap(c, "pc-kernel=18", "") 1292 s.makeSnap(c, "pc=18", "") 1293 s.makeSnap(c, "required18", "developerid") 1294 1295 w, err := seedwriter.New(model, s.opts) 1296 c.Assert(err, IsNil) 1297 1298 _, err = w.Start(s.db, s.newFetcher) 1299 c.Assert(err, IsNil) 1300 1301 snaps, err := w.SnapsToDownload() 1302 c.Assert(err, IsNil) 1303 c.Check(snaps, HasLen, 5) 1304 1305 sn := snaps[4] 1306 c.Assert(sn.SnapName(), Equals, "required18") 1307 1308 c.Check(w.SetRedirectChannel(sn, "default-track/stable"), ErrorMatches, `internal error: before using seedwriter.Writer.SetRedirectChannel snap "required18" Info should have been set`) 1309 1310 s.fillDownloadedSnap(c, w, sn) 1311 1312 c.Check(w.SetRedirectChannel(sn, "default-track//stable"), ErrorMatches, `invalid redirect channel for snap "required18":.*`) 1313 } 1314 1315 func (s *writerSuite) TestInfoDerivedInfosNotSet(c *C) { 1316 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1317 "display-name": "my model", 1318 "architecture": "amd64", 1319 "base": "core18", 1320 "gadget": "pc=18", 1321 "kernel": "pc-kernel=18", 1322 "required-snaps": []interface{}{"cont-consumer", "cont-producer"}, 1323 }) 1324 1325 core18Fn := s.makeLocalSnap(c, "core18") 1326 pcKernelFn := s.makeLocalSnap(c, "pc-kernel=18") 1327 pcFn := s.makeLocalSnap(c, "pc=18") 1328 1329 w, err := seedwriter.New(model, s.opts) 1330 c.Assert(err, IsNil) 1331 1332 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{ 1333 {Path: core18Fn}, 1334 {Path: pcFn, Channel: "edge"}, 1335 {Path: pcKernelFn}, 1336 }) 1337 c.Assert(err, IsNil) 1338 1339 _, err = w.Start(s.db, s.newFetcher) 1340 c.Assert(err, IsNil) 1341 1342 _, err = w.LocalSnaps() 1343 c.Assert(err, IsNil) 1344 1345 err = w.InfoDerived() 1346 c.Assert(err, ErrorMatches, `internal error: before seedwriter.Writer.InfoDerived snap ".*/core18.*.snap" Info should have been set`) 1347 } 1348 1349 func (s *writerSuite) TestInfoDerivedRepeatedLocalSnap(c *C) { 1350 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1351 "display-name": "my model", 1352 "architecture": "amd64", 1353 "base": "core18", 1354 "gadget": "pc=18", 1355 "kernel": "pc-kernel=18", 1356 "required-snaps": []interface{}{"cont-consumer", "cont-producer"}, 1357 }) 1358 1359 core18Fn := s.makeLocalSnap(c, "core18") 1360 pcKernelFn := s.makeLocalSnap(c, "pc-kernel=18") 1361 pcFn := s.makeLocalSnap(c, "pc=18") 1362 1363 w, err := seedwriter.New(model, s.opts) 1364 c.Assert(err, IsNil) 1365 1366 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{ 1367 {Path: core18Fn}, 1368 {Path: pcFn, Channel: "edge"}, 1369 {Path: pcKernelFn}, 1370 {Path: core18Fn}, 1371 }) 1372 c.Assert(err, IsNil) 1373 1374 _, err = w.Start(s.db, s.newFetcher) 1375 c.Assert(err, IsNil) 1376 1377 localSnaps, err := w.LocalSnaps() 1378 c.Assert(err, IsNil) 1379 c.Check(localSnaps, HasLen, 4) 1380 1381 for _, sn := range localSnaps { 1382 f, err := snapfile.Open(sn.Path) 1383 c.Assert(err, IsNil) 1384 info, err := snap.ReadInfoFromSnapFile(f, nil) 1385 c.Assert(err, IsNil) 1386 w.SetInfo(sn, info) 1387 } 1388 1389 err = w.InfoDerived() 1390 c.Assert(err, ErrorMatches, `local snap "core18" is repeated in options`) 1391 } 1392 1393 func (s *writerSuite) TestInfoDerivedInconsistentChannel(c *C) { 1394 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1395 "display-name": "my model", 1396 "architecture": "amd64", 1397 "base": "core18", 1398 "gadget": "pc=18", 1399 "kernel": "pc-kernel=18", 1400 "required-snaps": []interface{}{"cont-consumer", "cont-producer"}, 1401 }) 1402 1403 core18Fn := s.makeLocalSnap(c, "core18") 1404 pcKernelFn := s.makeLocalSnap(c, "pc-kernel=18") 1405 pcFn := s.makeLocalSnap(c, "pc=18") 1406 1407 w, err := seedwriter.New(model, s.opts) 1408 c.Assert(err, IsNil) 1409 1410 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{ 1411 {Path: core18Fn}, 1412 {Path: pcFn, Channel: "edge"}, 1413 {Path: pcKernelFn}, 1414 {Name: "pc", Channel: "beta"}, 1415 }) 1416 c.Assert(err, IsNil) 1417 1418 _, err = w.Start(s.db, s.newFetcher) 1419 c.Assert(err, IsNil) 1420 1421 localSnaps, err := w.LocalSnaps() 1422 c.Assert(err, IsNil) 1423 c.Check(localSnaps, HasLen, 3) 1424 1425 for _, sn := range localSnaps { 1426 f, err := snapfile.Open(sn.Path) 1427 c.Assert(err, IsNil) 1428 info, err := snap.ReadInfoFromSnapFile(f, nil) 1429 c.Assert(err, IsNil) 1430 w.SetInfo(sn, info) 1431 } 1432 1433 err = w.InfoDerived() 1434 c.Assert(err, ErrorMatches, `option snap has different channels specified: ".*/pc.*.snap"="edge" vs "pc"="beta"`) 1435 } 1436 1437 func (s *writerSuite) TestSetRedirectChannelLocalError(c *C) { 1438 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1439 "display-name": "my model", 1440 "architecture": "amd64", 1441 "base": "core18", 1442 "gadget": "pc=18", 1443 "kernel": "pc-kernel=18", 1444 }) 1445 1446 core18Fn := s.makeLocalSnap(c, "core18") 1447 1448 w, err := seedwriter.New(model, s.opts) 1449 c.Assert(err, IsNil) 1450 1451 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{ 1452 {Path: core18Fn}, 1453 }) 1454 c.Assert(err, IsNil) 1455 1456 _, err = w.Start(s.db, s.newFetcher) 1457 c.Assert(err, IsNil) 1458 1459 localSnaps, err := w.LocalSnaps() 1460 c.Assert(err, IsNil) 1461 c.Check(localSnaps, HasLen, 1) 1462 1463 sn := localSnaps[0] 1464 f, err := snapfile.Open(sn.Path) 1465 c.Assert(err, IsNil) 1466 info, err := snap.ReadInfoFromSnapFile(f, nil) 1467 c.Assert(err, IsNil) 1468 err = w.SetInfo(sn, info) 1469 c.Assert(err, IsNil) 1470 1471 c.Check(w.SetRedirectChannel(sn, "foo"), ErrorMatches, `internal error: cannot set redirect channel for local snap .*`) 1472 1473 } 1474 1475 func (s *writerSuite) TestSeedSnapsWriteMetaClassicWithCore(c *C) { 1476 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1477 "classic": "true", 1478 "architecture": "amd64", 1479 "gadget": "classic-gadget", 1480 "required-snaps": []interface{}{"required"}, 1481 }) 1482 1483 s.makeSnap(c, "core", "") 1484 s.makeSnap(c, "classic-gadget", "") 1485 s.makeSnap(c, "required", "developerid") 1486 1487 complete, w, err := s.upToDownloaded(c, model, s.fillDownloadedSnap) 1488 c.Assert(err, IsNil) 1489 c.Check(complete, Equals, false) 1490 1491 snaps, err := w.SnapsToDownload() 1492 c.Assert(err, IsNil) 1493 c.Assert(snaps, HasLen, 1) 1494 1495 s.fillDownloadedSnap(c, w, snaps[0]) 1496 1497 complete, err = w.Downloaded() 1498 c.Assert(err, IsNil) 1499 c.Check(complete, Equals, true) 1500 1501 _, err = w.BootSnaps() 1502 c.Check(err, ErrorMatches, "no snaps participating in boot on classic") 1503 1504 err = w.SeedSnaps(nil) 1505 c.Assert(err, IsNil) 1506 1507 err = w.WriteMeta() 1508 c.Assert(err, IsNil) 1509 1510 // check seed 1511 seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml")) 1512 c.Assert(err, IsNil) 1513 1514 c.Check(seedYaml.Snaps, HasLen, 3) 1515 1516 // check the files are in place 1517 for i, name := range []string{"core", "classic-gadget", "required"} { 1518 info := s.AssertedSnapInfo(name) 1519 1520 fn := info.Filename() 1521 p := filepath.Join(s.opts.SeedDir, "snaps", fn) 1522 c.Check(p, testutil.FilePresent) 1523 1524 c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{ 1525 Name: info.SnapName(), 1526 SnapID: info.SnapID, 1527 Channel: "stable", 1528 File: fn, 1529 Contact: info.Contact(), 1530 }) 1531 } 1532 } 1533 1534 func (s *writerSuite) TestSeedSnapsWriteMetaClassicSnapdOnly(c *C) { 1535 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1536 "classic": "true", 1537 "architecture": "amd64", 1538 "gadget": "classic-gadget18", 1539 "required-snaps": []interface{}{"core18", "required18"}, 1540 }) 1541 1542 s.makeSnap(c, "snapd", "") 1543 s.makeSnap(c, "core18", "") 1544 s.makeSnap(c, "classic-gadget18", "") 1545 s.makeSnap(c, "required18", "developerid") 1546 1547 complete, w, err := s.upToDownloaded(c, model, s.fillDownloadedSnap) 1548 c.Assert(err, IsNil) 1549 c.Check(complete, Equals, false) 1550 1551 snaps, err := w.SnapsToDownload() 1552 c.Assert(err, IsNil) 1553 c.Assert(snaps, HasLen, 1) 1554 1555 s.fillDownloadedSnap(c, w, snaps[0]) 1556 1557 complete, err = w.Downloaded() 1558 c.Assert(err, IsNil) 1559 c.Check(complete, Equals, true) 1560 1561 err = w.SeedSnaps(nil) 1562 c.Assert(err, IsNil) 1563 1564 err = w.WriteMeta() 1565 c.Assert(err, IsNil) 1566 1567 // check seed 1568 seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml")) 1569 c.Assert(err, IsNil) 1570 c.Assert(seedYaml.Snaps, HasLen, 4) 1571 1572 // check the files are in place 1573 for i, name := range []string{"snapd", "core18", "classic-gadget18", "required18"} { 1574 info := s.AssertedSnapInfo(name) 1575 1576 fn := info.Filename() 1577 p := filepath.Join(s.opts.SeedDir, "snaps", fn) 1578 c.Check(p, testutil.FilePresent) 1579 1580 c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{ 1581 Name: info.SnapName(), 1582 SnapID: info.SnapID, 1583 Channel: "stable", 1584 File: fn, 1585 Contact: info.Contact(), 1586 }) 1587 } 1588 } 1589 1590 func (s *writerSuite) TestSeedSnapsWriteMetaClassicSnapdOnlyMissingCore16(c *C) { 1591 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1592 "classic": "true", 1593 "architecture": "amd64", 1594 "gadget": "classic-gadget18", 1595 "required-snaps": []interface{}{"core18", "required-base-core16"}, 1596 }) 1597 1598 s.makeSnap(c, "snapd", "") 1599 s.makeSnap(c, "core18", "") 1600 s.makeSnap(c, "classic-gadget18", "") 1601 s.makeSnap(c, "required-base-core16", "developerid") 1602 1603 _, _, err := s.upToDownloaded(c, model, s.fillMetaDownloadedSnap) 1604 c.Check(err, ErrorMatches, `cannot use "required-base-core16" requiring base "core16" without adding "core16" \(or "core"\) explicitly`) 1605 } 1606 1607 func (s *writerSuite) TestSeedSnapsWriteMetaExtraSnaps(c *C) { 1608 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1609 "display-name": "my model", 1610 "architecture": "amd64", 1611 "base": "core18", 1612 "gadget": "pc=18", 1613 "kernel": "pc-kernel=18", 1614 "required-snaps": []interface{}{"cont-consumer", "cont-producer"}, 1615 }) 1616 1617 s.makeSnap(c, "snapd", "") 1618 s.makeSnap(c, "core18", "") 1619 s.makeSnap(c, "pc-kernel=18", "") 1620 s.makeSnap(c, "pc=18", "") 1621 s.makeSnap(c, "cont-producer", "developerid") 1622 s.makeSnap(c, "cont-consumer", "developerid") 1623 s.makeSnap(c, "core", "") 1624 s.makeSnap(c, "required", "developerid") 1625 1626 w, err := seedwriter.New(model, s.opts) 1627 c.Assert(err, IsNil) 1628 1629 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "required", Channel: "beta"}}) 1630 c.Assert(err, IsNil) 1631 1632 _, err = w.Start(s.db, s.newFetcher) 1633 c.Assert(err, IsNil) 1634 1635 snaps, err := w.SnapsToDownload() 1636 c.Assert(err, IsNil) 1637 c.Assert(snaps, HasLen, 6) 1638 1639 s.AssertedSnapInfo("cont-producer").EditedContact = "mailto:author@cont-producer.net" 1640 for _, sn := range snaps { 1641 s.fillDownloadedSnap(c, w, sn) 1642 } 1643 1644 complete, err := w.Downloaded() 1645 c.Assert(err, IsNil) 1646 c.Assert(complete, Equals, false) 1647 1648 snaps, err = w.SnapsToDownload() 1649 c.Assert(err, IsNil) 1650 c.Assert(snaps, HasLen, 1) 1651 c.Check(naming.SameSnap(snaps[0], naming.Snap("required")), Equals, true) 1652 1653 s.fillDownloadedSnap(c, w, snaps[0]) 1654 1655 complete, err = w.Downloaded() 1656 c.Assert(err, IsNil) 1657 c.Assert(complete, Equals, false) 1658 1659 snaps, err = w.SnapsToDownload() 1660 c.Assert(err, IsNil) 1661 c.Assert(snaps, HasLen, 1) 1662 c.Check(naming.SameSnap(snaps[0], naming.Snap("core")), Equals, true) 1663 1664 s.fillDownloadedSnap(c, w, snaps[0]) 1665 1666 complete, err = w.Downloaded() 1667 c.Assert(err, IsNil) 1668 c.Assert(complete, Equals, true) 1669 1670 err = w.SeedSnaps(nil) 1671 c.Assert(err, IsNil) 1672 1673 err = w.WriteMeta() 1674 c.Assert(err, IsNil) 1675 1676 // check seed 1677 seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml")) 1678 c.Assert(err, IsNil) 1679 c.Assert(seedYaml.Snaps, HasLen, 8) 1680 1681 // check the files are in place 1682 for i, name := range []string{"snapd", "core", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer", "required"} { 1683 info := s.AssertedSnapInfo(name) 1684 1685 fn := info.Filename() 1686 p := filepath.Join(s.opts.SeedDir, "snaps", fn) 1687 c.Check(osutil.FileExists(p), Equals, true) 1688 1689 channel := "stable" 1690 switch name { 1691 case "pc-kernel", "pc": 1692 channel = "18" 1693 case "required": 1694 channel = "beta" 1695 } 1696 1697 c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{ 1698 Name: info.SnapName(), 1699 SnapID: info.SnapID, 1700 Channel: channel, 1701 File: fn, 1702 Contact: info.Contact(), 1703 }) 1704 } 1705 1706 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 1707 c.Assert(err, IsNil) 1708 c.Check(l, HasLen, 8) 1709 1710 // check the snap assertions are also in place 1711 seedAssertsDir := filepath.Join(s.opts.SeedDir, "assertions") 1712 for _, snapName := range []string{"snapd", "core", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer", "required"} { 1713 p := filepath.Join(seedAssertsDir, fmt.Sprintf("16,%s.snap-declaration", s.AssertedSnapID(snapName))) 1714 decl := seedtest.ReadAssertions(c, p) 1715 c.Assert(decl, HasLen, 1) 1716 c.Check(decl[0].Type(), Equals, asserts.SnapDeclarationType) 1717 c.Check(decl[0].HeaderString("snap-name"), Equals, snapName) 1718 p = filepath.Join(seedAssertsDir, fmt.Sprintf("%s.snap-revision", s.AssertedSnapRevision(snapName).SnapSHA3_384())) 1719 rev := seedtest.ReadAssertions(c, p) 1720 c.Assert(rev, HasLen, 1) 1721 c.Check(rev[0].Type(), Equals, asserts.SnapRevisionType) 1722 c.Check(rev[0].HeaderString("snap-id"), Equals, s.AssertedSnapID(snapName)) 1723 } 1724 1725 c.Check(w.Warnings(), DeepEquals, []string{ 1726 `model has base "core18" but some snaps ("required") require "core" as base as well, for compatibility it was added implicitly, adding "core" explicitly is recommended`, 1727 }) 1728 } 1729 1730 func (s *writerSuite) TestSeedSnapsWriteMetaLocalExtraSnaps(c *C) { 1731 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1732 "display-name": "my model", 1733 "architecture": "amd64", 1734 "base": "core18", 1735 "gadget": "pc=18", 1736 "kernel": "pc-kernel=18", 1737 "required-snaps": []interface{}{"cont-consumer", "cont-producer"}, 1738 }) 1739 1740 s.makeSnap(c, "snapd", "") 1741 s.makeSnap(c, "core18", "") 1742 s.makeSnap(c, "pc-kernel=18", "") 1743 s.makeSnap(c, "pc=18", "") 1744 s.makeSnap(c, "cont-producer", "developerid") 1745 s.makeSnap(c, "cont-consumer", "developerid") 1746 s.makeSnap(c, "core", "") 1747 requiredFn := s.makeLocalSnap(c, "required") 1748 1749 w, err := seedwriter.New(model, s.opts) 1750 c.Assert(err, IsNil) 1751 1752 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: requiredFn}}) 1753 c.Assert(err, IsNil) 1754 1755 tf, err := w.Start(s.db, s.newFetcher) 1756 c.Assert(err, IsNil) 1757 1758 localSnaps, err := w.LocalSnaps() 1759 c.Assert(err, IsNil) 1760 c.Assert(localSnaps, HasLen, 1) 1761 1762 for _, sn := range localSnaps { 1763 si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db) 1764 if !asserts.IsNotFound(err) { 1765 c.Assert(err, IsNil) 1766 } 1767 f, err := snapfile.Open(sn.Path) 1768 c.Assert(err, IsNil) 1769 info, err := snap.ReadInfoFromSnapFile(f, si) 1770 c.Assert(err, IsNil) 1771 w.SetInfo(sn, info) 1772 sn.ARefs = aRefs 1773 } 1774 1775 err = w.InfoDerived() 1776 c.Assert(err, IsNil) 1777 1778 snaps, err := w.SnapsToDownload() 1779 c.Assert(err, IsNil) 1780 c.Assert(snaps, HasLen, 6) 1781 1782 s.AssertedSnapInfo("cont-producer").EditedContact = "mailto:author@cont-producer.net" 1783 for _, sn := range snaps { 1784 s.fillDownloadedSnap(c, w, sn) 1785 } 1786 1787 complete, err := w.Downloaded() 1788 c.Assert(err, IsNil) 1789 c.Assert(complete, Equals, false) 1790 1791 snaps, err = w.SnapsToDownload() 1792 c.Assert(err, IsNil) 1793 c.Assert(snaps, HasLen, 0) 1794 1795 complete, err = w.Downloaded() 1796 c.Assert(err, IsNil) 1797 c.Assert(complete, Equals, false) 1798 1799 snaps, err = w.SnapsToDownload() 1800 c.Assert(err, IsNil) 1801 c.Assert(snaps, HasLen, 1) 1802 c.Check(naming.SameSnap(snaps[0], naming.Snap("core")), Equals, true) 1803 1804 s.fillDownloadedSnap(c, w, snaps[0]) 1805 1806 complete, err = w.Downloaded() 1807 c.Assert(err, IsNil) 1808 c.Assert(complete, Equals, true) 1809 1810 copySnap := func(name, src, dst string) error { 1811 return osutil.CopyFile(src, dst, 0) 1812 } 1813 1814 err = w.SeedSnaps(copySnap) 1815 c.Assert(err, IsNil) 1816 1817 err = w.WriteMeta() 1818 c.Assert(err, IsNil) 1819 1820 // check seed 1821 seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml")) 1822 c.Assert(err, IsNil) 1823 c.Assert(seedYaml.Snaps, HasLen, 8) 1824 1825 // check the files are in place 1826 for i, name := range []string{"snapd", "core", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer", "required"} { 1827 info := s.AssertedSnapInfo(name) 1828 unasserted := false 1829 if info == nil { 1830 info = &snap.Info{ 1831 SuggestedName: name, 1832 } 1833 info.Revision = snap.R(-1) 1834 unasserted = true 1835 } 1836 1837 fn := info.Filename() 1838 p := filepath.Join(s.opts.SeedDir, "snaps", fn) 1839 c.Check(osutil.FileExists(p), Equals, true) 1840 1841 channel := "" 1842 if !unasserted { 1843 switch name { 1844 case "pc-kernel", "pc": 1845 channel = "18" 1846 default: 1847 channel = "stable" 1848 } 1849 } 1850 1851 c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{ 1852 Name: info.SnapName(), 1853 SnapID: info.SnapID, 1854 Channel: channel, 1855 File: fn, 1856 Contact: info.Contact(), 1857 Unasserted: unasserted, 1858 }) 1859 } 1860 1861 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 1862 c.Assert(err, IsNil) 1863 c.Check(l, HasLen, 8) 1864 1865 unassertedSnaps, err := w.UnassertedSnaps() 1866 c.Assert(err, IsNil) 1867 c.Check(unassertedSnaps, HasLen, 1) 1868 c.Check(naming.SameSnap(unassertedSnaps[0], naming.Snap("required")), Equals, true) 1869 } 1870 1871 func (s *writerSuite) TestSeedSnapsWriteMetaCore20(c *C) { 1872 // add store assertion 1873 storeAs, err := s.StoreSigning.Sign(asserts.StoreType, map[string]interface{}{ 1874 "store": "my-store", 1875 "operator-id": "canonical", 1876 "timestamp": time.Now().UTC().Format(time.RFC3339), 1877 }, nil, "") 1878 c.Assert(err, IsNil) 1879 err = s.StoreSigning.Add(storeAs) 1880 c.Assert(err, IsNil) 1881 1882 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1883 "display-name": "my model", 1884 "architecture": "amd64", 1885 "store": "my-store", 1886 "base": "core20", 1887 "snaps": []interface{}{ 1888 map[string]interface{}{ 1889 "name": "pc-kernel", 1890 "id": s.AssertedSnapID("pc-kernel"), 1891 "type": "kernel", 1892 "default-channel": "20", 1893 }, 1894 map[string]interface{}{ 1895 "name": "pc", 1896 "id": s.AssertedSnapID("pc"), 1897 "type": "gadget", 1898 "default-channel": "20", 1899 }, 1900 map[string]interface{}{ 1901 "name": "core18", 1902 "id": s.AssertedSnapID("core18"), 1903 "type": "base", 1904 }, 1905 map[string]interface{}{ 1906 "name": "cont-consumer", 1907 "id": s.AssertedSnapID("cont-consumer"), 1908 }, 1909 map[string]interface{}{ 1910 "name": "cont-producer", 1911 "id": s.AssertedSnapID("cont-producer"), 1912 }, 1913 }, 1914 }) 1915 1916 // sanity 1917 c.Assert(model.Grade(), Equals, asserts.ModelSigned) 1918 1919 s.makeSnap(c, "snapd", "") 1920 s.makeSnap(c, "core20", "") 1921 s.makeSnap(c, "core18", "") 1922 s.makeSnap(c, "pc-kernel=20", "") 1923 s.makeSnap(c, "pc=20", "") 1924 s.makeSnap(c, "cont-producer", "developerid") 1925 s.makeSnap(c, "cont-consumer", "developerid") 1926 1927 s.opts.Label = "20191003" 1928 w, err := seedwriter.New(model, s.opts) 1929 c.Assert(err, IsNil) 1930 1931 _, err = w.Start(s.db, s.newFetcher) 1932 c.Assert(err, IsNil) 1933 1934 snaps, err := w.SnapsToDownload() 1935 c.Assert(err, IsNil) 1936 c.Check(snaps, HasLen, 7) 1937 1938 s.AssertedSnapInfo("cont-producer").EditedContact = "mailto:author@cont-producer.net" 1939 s.AssertedSnapInfo("cont-consumer").Private = true 1940 for _, sn := range snaps { 1941 // check the used channel at this level because in the 1942 // non-dangerous case is not written anywhere (it 1943 // reflects the model or default) 1944 channel := "latest/stable" 1945 switch sn.SnapName() { 1946 case "pc", "pc-kernel": 1947 channel = "20" 1948 } 1949 c.Check(sn.Channel, Equals, channel) 1950 s.fillDownloadedSnap(c, w, sn) 1951 } 1952 1953 complete, err := w.Downloaded() 1954 c.Assert(err, IsNil) 1955 c.Check(complete, Equals, true) 1956 1957 err = w.SeedSnaps(nil) 1958 c.Assert(err, IsNil) 1959 1960 err = w.WriteMeta() 1961 c.Assert(err, IsNil) 1962 1963 // check seed 1964 systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label) 1965 c.Check(systemDir, testutil.FilePresent) 1966 1967 // check the snaps are in place 1968 for _, name := range []string{"snapd", "pc-kernel", "core20", "pc", "core18", "cont-consumer", "cont-producer"} { 1969 info := s.AssertedSnapInfo(name) 1970 1971 fn := info.Filename() 1972 p := filepath.Join(s.opts.SeedDir, "snaps", fn) 1973 c.Check(p, testutil.FilePresent) 1974 } 1975 1976 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 1977 c.Assert(err, IsNil) 1978 c.Check(l, HasLen, 7) 1979 1980 // check assertions 1981 c.Check(filepath.Join(systemDir, "model"), testutil.FileEquals, asserts.Encode(model)) 1982 1983 assertsDir := filepath.Join(systemDir, "assertions") 1984 modelEtc := seedtest.ReadAssertions(c, filepath.Join(assertsDir, "model-etc")) 1985 c.Check(modelEtc, HasLen, 4) 1986 1987 keyPKs := make(map[string]bool) 1988 for _, a := range modelEtc { 1989 switch a.Type() { 1990 case asserts.AccountType: 1991 c.Check(a.HeaderString("account-id"), Equals, "my-brand") 1992 case asserts.StoreType: 1993 c.Check(a.HeaderString("store"), Equals, "my-store") 1994 case asserts.AccountKeyType: 1995 keyPKs[a.HeaderString("public-key-sha3-384")] = true 1996 default: 1997 c.Fatalf("unexpected assertion %s", a.Type().Name) 1998 } 1999 } 2000 c.Check(keyPKs, DeepEquals, map[string]bool{ 2001 s.StoreSigning.StoreAccountKey("").PublicKeyID(): true, 2002 s.Brands.AccountKey("my-brand").PublicKeyID(): true, 2003 }) 2004 2005 // check snap assertions 2006 snapAsserts := seedtest.ReadAssertions(c, filepath.Join(assertsDir, "snaps")) 2007 seen := make(map[string]bool) 2008 2009 for _, a := range snapAsserts { 2010 uniq := a.Ref().Unique() 2011 if a.Type() == asserts.SnapRevisionType { 2012 rev := a.(*asserts.SnapRevision) 2013 uniq = fmt.Sprintf("%s@%d", rev.SnapID(), rev.SnapRevision()) 2014 } 2015 seen[uniq] = true 2016 } 2017 2018 snapRevUniq := func(snapName string, revno int) string { 2019 return fmt.Sprintf("%s@%d", s.AssertedSnapID(snapName), revno) 2020 } 2021 snapDeclUniq := func(snapName string) string { 2022 return "snap-declaration/16/" + s.AssertedSnapID(snapName) 2023 } 2024 2025 c.Check(seen, DeepEquals, map[string]bool{ 2026 "account/developerid": true, 2027 snapDeclUniq("snapd"): true, 2028 snapDeclUniq("pc-kernel"): true, 2029 snapDeclUniq("pc"): true, 2030 snapDeclUniq("core20"): true, 2031 snapDeclUniq("core18"): true, 2032 snapDeclUniq("cont-consumer"): true, 2033 snapDeclUniq("cont-producer"): true, 2034 snapRevUniq("snapd", 1): true, 2035 snapRevUniq("pc-kernel", 1): true, 2036 snapRevUniq("pc", 1): true, 2037 snapRevUniq("core20", 1): true, 2038 snapRevUniq("core18", 1): true, 2039 snapRevUniq("cont-consumer", 1): true, 2040 snapRevUniq("cont-producer", 1): true, 2041 }) 2042 2043 c.Check(filepath.Join(systemDir, "extra-snaps"), testutil.FileAbsent) 2044 2045 // check auxiliary store info 2046 l, err = ioutil.ReadDir(filepath.Join(systemDir, "snaps")) 2047 c.Assert(err, IsNil) 2048 c.Check(l, HasLen, 1) 2049 2050 b, err := ioutil.ReadFile(filepath.Join(systemDir, "snaps", "aux-info.json")) 2051 c.Assert(err, IsNil) 2052 var auxInfos map[string]map[string]interface{} 2053 err = json.Unmarshal(b, &auxInfos) 2054 c.Assert(err, IsNil) 2055 c.Check(auxInfos, DeepEquals, map[string]map[string]interface{}{ 2056 s.AssertedSnapID("cont-consumer"): { 2057 "private": true, 2058 }, 2059 s.AssertedSnapID("cont-producer"): { 2060 "contact": "mailto:author@cont-producer.net", 2061 }, 2062 }) 2063 2064 c.Check(filepath.Join(systemDir, "options.yaml"), testutil.FileAbsent) 2065 2066 // sanity check of seedtest helper 2067 const usesSnapd = true 2068 seedtest.ValidateSeed(c, s.opts.SeedDir, s.opts.Label, usesSnapd, 2069 s.StoreSigning.Trusted) 2070 } 2071 2072 func (s *writerSuite) TestCore20InvalidLabel(c *C) { 2073 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2074 "display-name": "my model", 2075 "architecture": "amd64", 2076 "store": "my-store", 2077 "base": "core20", 2078 "snaps": []interface{}{ 2079 map[string]interface{}{ 2080 "name": "pc-kernel", 2081 "id": s.AssertedSnapID("pc-kernel"), 2082 "type": "kernel", 2083 "default-channel": "20", 2084 }, 2085 map[string]interface{}{ 2086 "name": "pc", 2087 "id": s.AssertedSnapID("pc"), 2088 "type": "gadget", 2089 "default-channel": "20", 2090 }, 2091 }, 2092 }) 2093 2094 invalid := []string{ 2095 "-", 2096 "a.b", 2097 "aa--b", 2098 } 2099 2100 for _, inv := range invalid { 2101 s.opts.Label = inv 2102 w, err := seedwriter.New(model, s.opts) 2103 c.Assert(w, IsNil) 2104 c.Check(err, ErrorMatches, fmt.Sprintf(`invalid seed system label: %q`, inv)) 2105 } 2106 } 2107 2108 func (s *writerSuite) TestDownloadedCore20CheckBase(c *C) { 2109 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2110 "display-name": "my model", 2111 "architecture": "amd64", 2112 "store": "my-store", 2113 "base": "core20", 2114 "snaps": []interface{}{ 2115 map[string]interface{}{ 2116 "name": "pc-kernel", 2117 "id": s.AssertedSnapID("pc-kernel"), 2118 "type": "kernel", 2119 "default-channel": "20", 2120 }, 2121 map[string]interface{}{ 2122 "name": "pc", 2123 "id": s.AssertedSnapID("pc"), 2124 "type": "gadget", 2125 "default-channel": "20", 2126 }, 2127 map[string]interface{}{ 2128 "name": "cont-producer", 2129 "id": s.AssertedSnapID("cont-producer"), 2130 }, 2131 }, 2132 }) 2133 2134 // sanity 2135 c.Assert(model.Grade(), Equals, asserts.ModelSigned) 2136 2137 s.makeSnap(c, "snapd", "") 2138 s.makeSnap(c, "core20", "") 2139 s.makeSnap(c, "core18", "") 2140 s.makeSnap(c, "pc-kernel=20", "") 2141 s.makeSnap(c, "pc=20", "") 2142 s.makeSnap(c, "cont-producer", "developerid") 2143 2144 s.opts.Label = "20191003" 2145 _, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap) 2146 c.Check(err, ErrorMatches, `cannot add snap "cont-producer" without also adding its base "core18" explicitly`) 2147 } 2148 2149 func (s *writerSuite) TestDownloadedCore20CheckBaseModes(c *C) { 2150 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2151 "display-name": "my model", 2152 "architecture": "amd64", 2153 "store": "my-store", 2154 "base": "core20", 2155 "snaps": []interface{}{ 2156 map[string]interface{}{ 2157 "name": "pc-kernel", 2158 "id": s.AssertedSnapID("pc-kernel"), 2159 "type": "kernel", 2160 "default-channel": "20", 2161 }, 2162 map[string]interface{}{ 2163 "name": "pc", 2164 "id": s.AssertedSnapID("pc"), 2165 "type": "gadget", 2166 "default-channel": "20", 2167 }, 2168 map[string]interface{}{ 2169 "name": "core18", 2170 "id": s.AssertedSnapID("core18"), 2171 "type": "base", 2172 }, 2173 map[string]interface{}{ 2174 "name": "cont-producer", 2175 "id": s.AssertedSnapID("cont-producer"), 2176 "modes": []interface{}{"run", "ephemeral"}, 2177 }, 2178 }, 2179 }) 2180 2181 // sanity 2182 c.Assert(model.Grade(), Equals, asserts.ModelSigned) 2183 2184 s.makeSnap(c, "snapd", "") 2185 s.makeSnap(c, "core20", "") 2186 s.makeSnap(c, "core18", "") 2187 s.makeSnap(c, "pc-kernel=20", "") 2188 s.makeSnap(c, "pc=20", "") 2189 s.makeSnap(c, "cont-producer", "developerid") 2190 2191 s.opts.Label = "20191003" 2192 _, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap) 2193 c.Check(err, ErrorMatches, `cannot add snap "cont-producer" without also adding its base "core18" explicitly for all relevant modes \(run, ephemeral\)`) 2194 } 2195 2196 func (s *writerSuite) TestDownloadedCore20CheckBaseEphemeralOK(c *C) { 2197 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2198 "display-name": "my model", 2199 "architecture": "amd64", 2200 "store": "my-store", 2201 "base": "core20", 2202 "snaps": []interface{}{ 2203 map[string]interface{}{ 2204 "name": "pc-kernel", 2205 "id": s.AssertedSnapID("pc-kernel"), 2206 "type": "kernel", 2207 "default-channel": "20", 2208 }, 2209 map[string]interface{}{ 2210 "name": "pc", 2211 "id": s.AssertedSnapID("pc"), 2212 "type": "gadget", 2213 "default-channel": "20", 2214 }, 2215 map[string]interface{}{ 2216 "name": "core18", 2217 "id": s.AssertedSnapID("core18"), 2218 "type": "base", 2219 "modes": []interface{}{"ephemeral"}, 2220 }, 2221 map[string]interface{}{ 2222 "name": "cont-producer", 2223 "id": s.AssertedSnapID("cont-producer"), 2224 "modes": []interface{}{"recover"}, 2225 }, 2226 }, 2227 }) 2228 2229 // sanity 2230 c.Assert(model.Grade(), Equals, asserts.ModelSigned) 2231 2232 s.makeSnap(c, "snapd", "") 2233 s.makeSnap(c, "core20", "") 2234 s.makeSnap(c, "core18", "") 2235 s.makeSnap(c, "pc-kernel=20", "") 2236 s.makeSnap(c, "pc=20", "") 2237 s.makeSnap(c, "cont-producer", "developerid") 2238 2239 s.opts.Label = "20191003" 2240 _, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap) 2241 c.Check(err, IsNil) 2242 } 2243 2244 func (s *writerSuite) TestDownloadedCore20CheckBaseCoreXX(c *C) { 2245 s.makeSnap(c, "snapd", "") 2246 s.makeSnap(c, "core20", "") 2247 s.makeSnap(c, "pc-kernel=20", "") 2248 s.makeSnap(c, "pc=20", "") 2249 s.makeSnap(c, "core", "") 2250 s.makeSnap(c, "required", "") 2251 s.makeSnap(c, "required-base-core16", "") 2252 2253 coreEnt := map[string]interface{}{ 2254 "name": "core", 2255 "id": s.AssertedSnapID("core"), 2256 "type": "core", 2257 } 2258 requiredEnt := map[string]interface{}{ 2259 "name": "required", 2260 "id": s.AssertedSnapID("required"), 2261 } 2262 2263 requiredBaseCore16Ent := map[string]interface{}{ 2264 "name": "required-base-core16", 2265 "id": s.AssertedSnapID("required-base-core16"), 2266 } 2267 2268 tests := []struct { 2269 snaps []interface{} 2270 err string 2271 }{ 2272 {[]interface{}{coreEnt, requiredEnt}, ""}, 2273 {[]interface{}{coreEnt, requiredBaseCore16Ent}, ""}, 2274 {[]interface{}{requiredEnt}, `cannot add snap "required" without also adding its base "core" explicitly`}, 2275 {[]interface{}{requiredBaseCore16Ent}, `cannot add snap "required-base-core16" without also adding its base "core16" \(or "core"\) explicitly`}, 2276 } 2277 2278 baseLabel := "20191003" 2279 for idx, t := range tests { 2280 s.opts.Label = fmt.Sprintf("%s%d", baseLabel, idx) 2281 snaps := []interface{}{ 2282 map[string]interface{}{ 2283 "name": "pc-kernel", 2284 "id": s.AssertedSnapID("pc-kernel"), 2285 "type": "kernel", 2286 "default-channel": "20", 2287 }, 2288 map[string]interface{}{ 2289 "name": "pc", 2290 "id": s.AssertedSnapID("pc"), 2291 "type": "gadget", 2292 "default-channel": "20", 2293 }, 2294 } 2295 2296 snaps = append(snaps, t.snaps...) 2297 2298 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2299 "display-name": "my model", 2300 "architecture": "amd64", 2301 "store": "my-store", 2302 "base": "core20", 2303 "snaps": snaps, 2304 }) 2305 2306 _, _, err := s.upToDownloaded(c, model, s.fillMetaDownloadedSnap) 2307 if t.err == "" { 2308 c.Check(err, IsNil) 2309 } else { 2310 c.Check(err, ErrorMatches, t.err) 2311 } 2312 } 2313 } 2314 func (s *writerSuite) TestDownloadedCore20MissingDefaultProviderModes(c *C) { 2315 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2316 "display-name": "my model", 2317 "architecture": "amd64", 2318 "store": "my-store", 2319 "base": "core20", 2320 "snaps": []interface{}{ 2321 map[string]interface{}{ 2322 "name": "pc-kernel", 2323 "id": s.AssertedSnapID("pc-kernel"), 2324 "type": "kernel", 2325 "default-channel": "20", 2326 }, 2327 map[string]interface{}{ 2328 "name": "pc", 2329 "id": s.AssertedSnapID("pc"), 2330 "type": "gadget", 2331 "default-channel": "20", 2332 }, 2333 map[string]interface{}{ 2334 "name": "core18", 2335 "id": s.AssertedSnapID("core18"), 2336 "type": "base", 2337 "modes": []interface{}{"run", "ephemeral"}, 2338 }, 2339 map[string]interface{}{ 2340 "name": "cont-producer", 2341 "id": s.AssertedSnapID("cont-producer"), 2342 }, 2343 map[string]interface{}{ 2344 "name": "cont-consumer", 2345 "id": s.AssertedSnapID("cont-consumer"), 2346 "modes": []interface{}{"recover"}, 2347 }, 2348 }, 2349 }) 2350 2351 // sanity 2352 c.Assert(model.Grade(), Equals, asserts.ModelSigned) 2353 2354 s.makeSnap(c, "snapd", "") 2355 s.makeSnap(c, "core20", "") 2356 s.makeSnap(c, "core18", "") 2357 s.makeSnap(c, "pc-kernel=20", "") 2358 s.makeSnap(c, "pc=20", "") 2359 s.makeSnap(c, "cont-producer", "developerid") 2360 s.makeSnap(c, "cont-consumer", "developerid") 2361 2362 s.opts.Label = "20191003" 2363 _, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap) 2364 c.Check(err, ErrorMatches, `cannot use snap "cont-consumer" without its default content provider "cont-producer" being added explicitly for all relevant modes \(recover\)`) 2365 } 2366 2367 func (s *writerSuite) TestCore20NonDangerousDisallowedDevmodeSnaps(c *C) { 2368 2369 s.makeSnap(c, "my-devmode", "canonical") 2370 2371 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2372 "display-name": "my model", 2373 "architecture": "amd64", 2374 "store": "my-store", 2375 "base": "core20", 2376 "snaps": []interface{}{ 2377 map[string]interface{}{ 2378 "name": "pc-kernel", 2379 "id": s.AssertedSnapID("pc-kernel"), 2380 "type": "kernel", 2381 "default-channel": "20", 2382 }, 2383 map[string]interface{}{ 2384 "name": "pc", 2385 "id": s.AssertedSnapID("pc"), 2386 "type": "gadget", 2387 "default-channel": "20", 2388 }, 2389 map[string]interface{}{ 2390 "name": "my-devmode", 2391 "id": s.AssertedSnapID("my-devmode"), 2392 "type": "app", 2393 }, 2394 }, 2395 }) 2396 2397 s.opts.Label = "20191107" 2398 2399 w, err := seedwriter.New(model, s.opts) 2400 c.Assert(err, IsNil) 2401 2402 _, err = w.Start(s.db, s.newFetcher) 2403 c.Assert(err, IsNil) 2404 2405 localSnaps, err := w.LocalSnaps() 2406 c.Assert(err, IsNil) 2407 c.Assert(localSnaps, HasLen, 0) 2408 2409 snaps, err := w.SnapsToDownload() 2410 c.Check(err, IsNil) 2411 c.Assert(snaps, HasLen, 5) 2412 2413 c.Assert(snaps[4].SnapName(), Equals, "my-devmode") 2414 sn := snaps[4] 2415 2416 info := s.AssertedSnapInfo(sn.SnapName()) 2417 c.Assert(info, NotNil, Commentf("%s not defined", sn.SnapName())) 2418 err = w.SetInfo(sn, info) 2419 c.Assert(err, ErrorMatches, "cannot override channels, add devmode snaps, local snaps, or extra snaps with a model of grade higher than dangerous") 2420 c.Check(sn.Info, Not(Equals), info) 2421 } 2422 2423 func (s *writerSuite) TestCore20NonDangerousDisallowedOptionsSnaps(c *C) { 2424 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2425 "display-name": "my model", 2426 "architecture": "amd64", 2427 "store": "my-store", 2428 "base": "core20", 2429 "snaps": []interface{}{ 2430 map[string]interface{}{ 2431 "name": "pc-kernel", 2432 "id": s.AssertedSnapID("pc-kernel"), 2433 "type": "kernel", 2434 "default-channel": "20", 2435 }, 2436 map[string]interface{}{ 2437 "name": "pc", 2438 "id": s.AssertedSnapID("pc"), 2439 "type": "gadget", 2440 "default-channel": "20", 2441 }, 2442 }, 2443 }) 2444 2445 pcFn := s.makeLocalSnap(c, "pc") 2446 2447 baseLabel := "20191107" 2448 2449 tests := []struct { 2450 optSnap *seedwriter.OptionsSnap 2451 }{ 2452 {&seedwriter.OptionsSnap{Name: "extra"}}, 2453 {&seedwriter.OptionsSnap{Path: pcFn}}, 2454 {&seedwriter.OptionsSnap{Name: "pc", Channel: "edge"}}, 2455 } 2456 2457 const expectedErr = `cannot override channels, add devmode snaps, local snaps, or extra snaps with a model of grade higher than dangerous` 2458 2459 for idx, t := range tests { 2460 s.opts.Label = fmt.Sprintf("%s%d", baseLabel, idx) 2461 w, err := seedwriter.New(model, s.opts) 2462 c.Assert(err, IsNil) 2463 2464 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{t.optSnap}) 2465 if err != nil { 2466 c.Check(err, ErrorMatches, expectedErr) 2467 continue 2468 } 2469 2470 tf, err := w.Start(s.db, s.newFetcher) 2471 c.Assert(err, IsNil) 2472 2473 if t.optSnap.Path != "" { 2474 localSnaps, err := w.LocalSnaps() 2475 c.Assert(err, IsNil) 2476 c.Assert(localSnaps, HasLen, 1) 2477 2478 for _, sn := range localSnaps { 2479 si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db) 2480 if !asserts.IsNotFound(err) { 2481 c.Assert(err, IsNil) 2482 } 2483 f, err := snapfile.Open(sn.Path) 2484 c.Assert(err, IsNil) 2485 info, err := snap.ReadInfoFromSnapFile(f, si) 2486 c.Assert(err, IsNil) 2487 w.SetInfo(sn, info) 2488 sn.ARefs = aRefs 2489 } 2490 2491 err = w.InfoDerived() 2492 c.Check(err, ErrorMatches, expectedErr) 2493 continue 2494 } 2495 2496 _, err = w.SnapsToDownload() 2497 c.Check(err, ErrorMatches, expectedErr) 2498 } 2499 } 2500 2501 func (s *writerSuite) TestCore20NonDangerousNoChannelOverride(c *C) { 2502 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2503 "display-name": "my model", 2504 "architecture": "amd64", 2505 "store": "my-store", 2506 "base": "core20", 2507 "snaps": []interface{}{ 2508 map[string]interface{}{ 2509 "name": "pc-kernel", 2510 "id": s.AssertedSnapID("pc-kernel"), 2511 "type": "kernel", 2512 "default-channel": "20", 2513 }, 2514 map[string]interface{}{ 2515 "name": "pc", 2516 "id": s.AssertedSnapID("pc"), 2517 "type": "gadget", 2518 "default-channel": "20", 2519 }, 2520 }, 2521 }) 2522 2523 s.opts.DefaultChannel = "stable" 2524 s.opts.Label = "20191107" 2525 w, err := seedwriter.New(model, s.opts) 2526 c.Assert(w, IsNil) 2527 c.Check(err, ErrorMatches, `cannot override channels, add devmode snaps, local snaps, or extra snaps with a model of grade higher than dangerous`) 2528 } 2529 2530 func (s *writerSuite) TestSeedSnapsWriteMetaCore20LocalSnaps(c *C) { 2531 // add store assertion 2532 storeAs, err := s.StoreSigning.Sign(asserts.StoreType, map[string]interface{}{ 2533 "store": "my-store", 2534 "operator-id": "canonical", 2535 "timestamp": time.Now().UTC().Format(time.RFC3339), 2536 }, nil, "") 2537 c.Assert(err, IsNil) 2538 err = s.StoreSigning.Add(storeAs) 2539 c.Assert(err, IsNil) 2540 2541 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2542 "display-name": "my model", 2543 "architecture": "amd64", 2544 "store": "my-store", 2545 "base": "core20", 2546 "grade": "dangerous", 2547 "snaps": []interface{}{ 2548 map[string]interface{}{ 2549 "name": "pc-kernel", 2550 "id": s.AssertedSnapID("pc-kernel"), 2551 "type": "kernel", 2552 "default-channel": "20", 2553 }, 2554 map[string]interface{}{ 2555 "name": "pc", 2556 "id": s.AssertedSnapID("pc"), 2557 "type": "gadget", 2558 "default-channel": "20", 2559 }, 2560 map[string]interface{}{ 2561 "name": "required20", 2562 "id": s.AssertedSnapID("required20"), 2563 }, 2564 }, 2565 }) 2566 2567 // sanity 2568 c.Assert(model.Grade(), Equals, asserts.ModelDangerous) 2569 2570 s.makeSnap(c, "snapd", "") 2571 s.makeSnap(c, "core20", "") 2572 s.makeSnap(c, "pc-kernel=20", "") 2573 s.makeSnap(c, "pc=20", "") 2574 requiredFn := s.makeLocalSnap(c, "required20") 2575 2576 s.opts.Label = "20191030" 2577 w, err := seedwriter.New(model, s.opts) 2578 c.Assert(err, IsNil) 2579 2580 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: requiredFn}}) 2581 c.Assert(err, IsNil) 2582 2583 tf, err := w.Start(s.db, s.newFetcher) 2584 c.Assert(err, IsNil) 2585 2586 localSnaps, err := w.LocalSnaps() 2587 c.Assert(err, IsNil) 2588 c.Assert(localSnaps, HasLen, 1) 2589 2590 for _, sn := range localSnaps { 2591 _, _, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db) 2592 c.Assert(asserts.IsNotFound(err), Equals, true) 2593 f, err := snapfile.Open(sn.Path) 2594 c.Assert(err, IsNil) 2595 info, err := snap.ReadInfoFromSnapFile(f, nil) 2596 c.Assert(err, IsNil) 2597 w.SetInfo(sn, info) 2598 } 2599 2600 err = w.InfoDerived() 2601 c.Assert(err, IsNil) 2602 2603 snaps, err := w.SnapsToDownload() 2604 c.Assert(err, IsNil) 2605 c.Check(snaps, HasLen, 4) 2606 2607 for _, sn := range snaps { 2608 // check the used channel at this level because in the 2609 // non-dangerous case is not written anywhere (it 2610 // reflects the model or default) 2611 channel := "latest/stable" 2612 switch sn.SnapName() { 2613 case "pc", "pc-kernel": 2614 channel = "20" 2615 } 2616 c.Check(sn.Channel, Equals, channel) 2617 s.fillDownloadedSnap(c, w, sn) 2618 } 2619 2620 complete, err := w.Downloaded() 2621 c.Assert(err, IsNil) 2622 c.Check(complete, Equals, true) 2623 2624 copySnap := func(name, src, dst string) error { 2625 return osutil.CopyFile(src, dst, 0) 2626 } 2627 2628 err = w.SeedSnaps(copySnap) 2629 c.Assert(err, IsNil) 2630 2631 err = w.WriteMeta() 2632 c.Assert(err, IsNil) 2633 2634 // check seed 2635 systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label) 2636 c.Check(systemDir, testutil.FilePresent) 2637 2638 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 2639 c.Assert(err, IsNil) 2640 c.Check(l, HasLen, 4) 2641 2642 // local unasserted snap was put in system snaps dir 2643 c.Check(filepath.Join(systemDir, "snaps", "required20_1.0.snap"), testutil.FilePresent) 2644 2645 options20, err := seedwriter.InternalReadOptions20(filepath.Join(systemDir, "options.yaml")) 2646 c.Assert(err, IsNil) 2647 2648 c.Check(options20.Snaps, DeepEquals, []*seedwriter.InternalSnap20{ 2649 { 2650 Name: "required20", 2651 SnapID: s.AssertedSnapID("required20"), 2652 Unasserted: "required20_1.0.snap", 2653 }, 2654 }) 2655 } 2656 2657 func (s *writerSuite) TestSeedSnapsWriteMetaCore20ChannelOverrides(c *C) { 2658 // add store assertion 2659 storeAs, err := s.StoreSigning.Sign(asserts.StoreType, map[string]interface{}{ 2660 "store": "my-store", 2661 "operator-id": "canonical", 2662 "timestamp": time.Now().UTC().Format(time.RFC3339), 2663 }, nil, "") 2664 c.Assert(err, IsNil) 2665 err = s.StoreSigning.Add(storeAs) 2666 c.Assert(err, IsNil) 2667 2668 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2669 "display-name": "my model", 2670 "architecture": "amd64", 2671 "store": "my-store", 2672 "base": "core20", 2673 "grade": "dangerous", 2674 "snaps": []interface{}{ 2675 map[string]interface{}{ 2676 "name": "pc-kernel", 2677 "id": s.AssertedSnapID("pc-kernel"), 2678 "type": "kernel", 2679 "default-channel": "20", 2680 }, 2681 map[string]interface{}{ 2682 "name": "pc", 2683 "id": s.AssertedSnapID("pc"), 2684 "type": "gadget", 2685 "default-channel": "20", 2686 }, 2687 map[string]interface{}{ 2688 "name": "required20", 2689 "id": s.AssertedSnapID("required20"), 2690 }, 2691 }, 2692 }) 2693 2694 // sanity 2695 c.Assert(model.Grade(), Equals, asserts.ModelDangerous) 2696 2697 s.makeSnap(c, "snapd", "") 2698 s.makeSnap(c, "core20", "") 2699 s.makeSnap(c, "pc-kernel=20", "") 2700 s.makeSnap(c, "pc=20", "") 2701 s.makeSnap(c, "required20", "developerid") 2702 2703 s.opts.Label = "20191030" 2704 s.opts.DefaultChannel = "candidate" 2705 w, err := seedwriter.New(model, s.opts) 2706 c.Assert(err, IsNil) 2707 2708 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "edge"}}) 2709 c.Assert(err, IsNil) 2710 2711 _, err = w.Start(s.db, s.newFetcher) 2712 c.Assert(err, IsNil) 2713 2714 snaps, err := w.SnapsToDownload() 2715 c.Assert(err, IsNil) 2716 c.Check(snaps, HasLen, 5) 2717 2718 for _, sn := range snaps { 2719 s.fillDownloadedSnap(c, w, sn) 2720 } 2721 2722 complete, err := w.Downloaded() 2723 c.Assert(err, IsNil) 2724 c.Check(complete, Equals, true) 2725 2726 err = w.SeedSnaps(nil) 2727 c.Assert(err, IsNil) 2728 2729 err = w.WriteMeta() 2730 c.Assert(err, IsNil) 2731 2732 // check seed 2733 systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label) 2734 c.Check(systemDir, testutil.FilePresent) 2735 2736 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 2737 c.Assert(err, IsNil) 2738 c.Check(l, HasLen, 5) 2739 2740 options20, err := seedwriter.InternalReadOptions20(filepath.Join(systemDir, "options.yaml")) 2741 c.Assert(err, IsNil) 2742 2743 c.Check(options20.Snaps, DeepEquals, []*seedwriter.InternalSnap20{ 2744 { 2745 Name: "snapd", 2746 SnapID: s.AssertedSnapID("snapd"), // inferred 2747 Channel: "latest/candidate", 2748 }, 2749 { 2750 Name: "pc-kernel", 2751 SnapID: s.AssertedSnapID("pc-kernel"), 2752 Channel: "20/candidate", 2753 }, 2754 { 2755 Name: "core20", 2756 SnapID: s.AssertedSnapID("core20"), // inferred 2757 Channel: "latest/candidate", 2758 }, 2759 { 2760 Name: "pc", 2761 SnapID: s.AssertedSnapID("pc"), 2762 Channel: "20/edge", 2763 }, 2764 { 2765 Name: "required20", 2766 SnapID: s.AssertedSnapID("required20"), 2767 Channel: "latest/candidate", 2768 }, 2769 }) 2770 } 2771 2772 func (s *writerSuite) TestSeedSnapsWriteMetaCore20ModelOverrideSnapd(c *C) { 2773 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2774 "display-name": "my model", 2775 "architecture": "amd64", 2776 "base": "core20", 2777 "snaps": []interface{}{ 2778 map[string]interface{}{ 2779 "name": "snapd", 2780 "id": s.AssertedSnapID("snapd"), 2781 "type": "snapd", 2782 "default-channel": "latest/edge", 2783 }, 2784 map[string]interface{}{ 2785 "name": "pc-kernel", 2786 "id": s.AssertedSnapID("pc-kernel"), 2787 "type": "kernel", 2788 "default-channel": "20", 2789 }, 2790 map[string]interface{}{ 2791 "name": "pc", 2792 "id": s.AssertedSnapID("pc"), 2793 "type": "gadget", 2794 "default-channel": "20", 2795 }}, 2796 }) 2797 2798 // sanity 2799 c.Assert(model.Grade(), Equals, asserts.ModelSigned) 2800 2801 s.makeSnap(c, "snapd", "") 2802 s.makeSnap(c, "core20", "") 2803 s.makeSnap(c, "pc-kernel=20", "") 2804 s.makeSnap(c, "pc=20", "") 2805 2806 s.opts.Label = "20191121" 2807 w, err := seedwriter.New(model, s.opts) 2808 c.Assert(err, IsNil) 2809 2810 _, err = w.Start(s.db, s.newFetcher) 2811 c.Assert(err, IsNil) 2812 2813 snaps, err := w.SnapsToDownload() 2814 c.Assert(err, IsNil) 2815 c.Check(snaps, HasLen, 4) 2816 2817 for _, sn := range snaps { 2818 // check the used channel at this level because in the 2819 // non-dangerous case is not written anywhere (it 2820 // reflects the model or default) 2821 channel := "latest/stable" 2822 switch sn.SnapName() { 2823 case "snapd": 2824 channel = "latest/edge" 2825 case "pc", "pc-kernel": 2826 channel = "20" 2827 } 2828 c.Check(sn.Channel, Equals, channel) 2829 s.fillDownloadedSnap(c, w, sn) 2830 } 2831 2832 complete, err := w.Downloaded() 2833 c.Assert(err, IsNil) 2834 c.Check(complete, Equals, true) 2835 2836 err = w.SeedSnaps(nil) 2837 c.Assert(err, IsNil) 2838 2839 err = w.WriteMeta() 2840 c.Assert(err, IsNil) 2841 2842 // check seed 2843 systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label) 2844 c.Check(systemDir, testutil.FilePresent) 2845 2846 // check the snaps are in place 2847 for _, name := range []string{"snapd", "pc-kernel", "core20", "pc"} { 2848 info := s.AssertedSnapInfo(name) 2849 2850 fn := info.Filename() 2851 p := filepath.Join(s.opts.SeedDir, "snaps", fn) 2852 c.Check(p, testutil.FilePresent) 2853 } 2854 2855 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 2856 c.Assert(err, IsNil) 2857 c.Check(l, HasLen, 4) 2858 2859 c.Check(filepath.Join(systemDir, "extra-snaps"), testutil.FileAbsent) 2860 c.Check(filepath.Join(systemDir, "options.yaml"), testutil.FileAbsent) 2861 } 2862 2863 func (s *writerSuite) TestSnapsToDownloadCore20OptionalSnaps(c *C) { 2864 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2865 "display-name": "my model", 2866 "architecture": "amd64", 2867 "base": "core20", 2868 "snaps": []interface{}{ 2869 map[string]interface{}{ 2870 "name": "pc-kernel", 2871 "id": s.AssertedSnapID("pc-kernel"), 2872 "type": "kernel", 2873 "default-channel": "20", 2874 }, 2875 map[string]interface{}{ 2876 "name": "pc", 2877 "id": s.AssertedSnapID("pc"), 2878 "type": "gadget", 2879 "default-channel": "20", 2880 }, 2881 map[string]interface{}{ 2882 "name": "core18", 2883 "id": s.AssertedSnapID("core18"), 2884 "type": "base", 2885 }, 2886 map[string]interface{}{ 2887 "name": "optional20-a", 2888 "id": s.AssertedSnapID("optional20-a"), 2889 "presence": "optional", 2890 }, 2891 map[string]interface{}{ 2892 "name": "optional20-b", 2893 "id": s.AssertedSnapID("optional20-b"), 2894 "presence": "optional", 2895 }}, 2896 }) 2897 2898 // sanity 2899 c.Assert(model.Grade(), Equals, asserts.ModelSigned) 2900 2901 s.makeSnap(c, "snapd", "") 2902 s.makeSnap(c, "core20", "") 2903 s.makeSnap(c, "core18", "") 2904 s.makeSnap(c, "pc-kernel=20", "") 2905 s.makeSnap(c, "pc=20", "") 2906 s.makeSnap(c, "optional20-a", "developerid") 2907 s.makeSnap(c, "optional20-b", "developerid") 2908 2909 s.opts.Label = "20191122" 2910 w, err := seedwriter.New(model, s.opts) 2911 c.Assert(err, IsNil) 2912 2913 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "optional20-b"}}) 2914 c.Assert(err, IsNil) 2915 2916 _, err = w.Start(s.db, s.newFetcher) 2917 c.Assert(err, IsNil) 2918 2919 snaps, err := w.SnapsToDownload() 2920 c.Assert(err, IsNil) 2921 c.Check(snaps, HasLen, 6) 2922 c.Check(snaps[5].SnapName(), Equals, "optional20-b") 2923 } 2924 2925 func (s *writerSuite) TestSeedSnapsWriteMetaCore20ExtraSnaps(c *C) { 2926 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2927 "display-name": "my model", 2928 "architecture": "amd64", 2929 "base": "core20", 2930 "grade": "dangerous", 2931 "snaps": []interface{}{ 2932 map[string]interface{}{ 2933 "name": "pc-kernel", 2934 "id": s.AssertedSnapID("pc-kernel"), 2935 "type": "kernel", 2936 "default-channel": "20", 2937 }, 2938 map[string]interface{}{ 2939 "name": "pc", 2940 "id": s.AssertedSnapID("pc"), 2941 "type": "gadget", 2942 "default-channel": "20", 2943 }}, 2944 }) 2945 2946 // sanity 2947 c.Assert(model.Grade(), Equals, asserts.ModelDangerous) 2948 2949 s.makeSnap(c, "snapd", "") 2950 s.makeSnap(c, "core20", "") 2951 s.makeSnap(c, "pc-kernel=20", "") 2952 s.makeSnap(c, "pc=20", "") 2953 s.makeSnap(c, "core18", "") 2954 s.makeSnap(c, "cont-producer", "developerid") 2955 contConsumerFn := s.makeLocalSnap(c, "cont-consumer") 2956 2957 s.opts.Label = "20191122" 2958 w, err := seedwriter.New(model, s.opts) 2959 c.Assert(err, IsNil) 2960 2961 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "cont-producer", Channel: "edge"}, {Name: "core18"}, {Path: contConsumerFn}}) 2962 c.Assert(err, IsNil) 2963 2964 tf, err := w.Start(s.db, s.newFetcher) 2965 c.Assert(err, IsNil) 2966 2967 localSnaps, err := w.LocalSnaps() 2968 c.Assert(err, IsNil) 2969 c.Assert(localSnaps, HasLen, 1) 2970 2971 for _, sn := range localSnaps { 2972 _, _, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db) 2973 c.Assert(asserts.IsNotFound(err), Equals, true) 2974 f, err := snapfile.Open(sn.Path) 2975 c.Assert(err, IsNil) 2976 info, err := snap.ReadInfoFromSnapFile(f, nil) 2977 c.Assert(err, IsNil) 2978 w.SetInfo(sn, info) 2979 } 2980 2981 err = w.InfoDerived() 2982 c.Assert(err, IsNil) 2983 2984 snaps, err := w.SnapsToDownload() 2985 c.Assert(err, IsNil) 2986 c.Check(snaps, HasLen, 4) 2987 2988 for _, sn := range snaps { 2989 channel := "latest/stable" 2990 switch sn.SnapName() { 2991 case "pc", "pc-kernel": 2992 channel = "20" 2993 } 2994 c.Check(sn.Channel, Equals, channel) 2995 s.fillDownloadedSnap(c, w, sn) 2996 } 2997 2998 complete, err := w.Downloaded() 2999 c.Assert(err, IsNil) 3000 c.Check(complete, Equals, false) 3001 3002 snaps, err = w.SnapsToDownload() 3003 c.Assert(err, IsNil) 3004 c.Assert(snaps, HasLen, 2) 3005 c.Check(snaps[0].SnapName(), Equals, "cont-producer") 3006 c.Check(snaps[1].SnapName(), Equals, "core18") 3007 3008 for _, sn := range snaps { 3009 channel := "latest/stable" 3010 switch sn.SnapName() { 3011 case "cont-producer": 3012 channel = "latest/edge" 3013 } 3014 c.Check(sn.Channel, Equals, channel) 3015 3016 info := s.doFillMetaDownloadedSnap(c, w, sn) 3017 3018 c.Assert(sn.Path, Equals, filepath.Join(s.opts.SeedDir, "systems", s.opts.Label, "snaps", info.Filename())) 3019 err := os.Rename(s.AssertedSnap(sn.SnapName()), sn.Path) 3020 c.Assert(err, IsNil) 3021 } 3022 3023 complete, err = w.Downloaded() 3024 c.Assert(err, IsNil) 3025 c.Check(complete, Equals, true) 3026 3027 copySnap := func(name, src, dst string) error { 3028 return osutil.CopyFile(src, dst, 0) 3029 } 3030 3031 err = w.SeedSnaps(copySnap) 3032 c.Assert(err, IsNil) 3033 3034 err = w.WriteMeta() 3035 c.Assert(err, IsNil) 3036 3037 // check seed 3038 systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label) 3039 c.Check(systemDir, testutil.FilePresent) 3040 3041 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 3042 c.Assert(err, IsNil) 3043 c.Check(l, HasLen, 4) 3044 3045 // extra snaps were put in system snaps dir 3046 c.Check(filepath.Join(systemDir, "snaps", "core18_1.snap"), testutil.FilePresent) 3047 c.Check(filepath.Join(systemDir, "snaps", "cont-producer_1.snap"), testutil.FilePresent) 3048 c.Check(filepath.Join(systemDir, "snaps", "cont-consumer_1.0.snap"), testutil.FilePresent) 3049 3050 // check extra-snaps in assertions 3051 snapAsserts := seedtest.ReadAssertions(c, filepath.Join(systemDir, "assertions", "extra-snaps")) 3052 seen := make(map[string]bool) 3053 3054 for _, a := range snapAsserts { 3055 uniq := a.Ref().Unique() 3056 if a.Type() == asserts.SnapRevisionType { 3057 rev := a.(*asserts.SnapRevision) 3058 uniq = fmt.Sprintf("%s@%d", rev.SnapID(), rev.SnapRevision()) 3059 } 3060 seen[uniq] = true 3061 } 3062 3063 snapRevUniq := func(snapName string, revno int) string { 3064 return fmt.Sprintf("%s@%d", s.AssertedSnapID(snapName), revno) 3065 } 3066 snapDeclUniq := func(snapName string) string { 3067 return "snap-declaration/16/" + s.AssertedSnapID(snapName) 3068 } 3069 3070 c.Check(seen, DeepEquals, map[string]bool{ 3071 "account/developerid": true, 3072 snapDeclUniq("core18"): true, 3073 snapDeclUniq("cont-producer"): true, 3074 snapRevUniq("core18", 1): true, 3075 snapRevUniq("cont-producer", 1): true, 3076 }) 3077 3078 options20, err := seedwriter.InternalReadOptions20(filepath.Join(systemDir, "options.yaml")) 3079 c.Assert(err, IsNil) 3080 3081 c.Check(options20.Snaps, DeepEquals, []*seedwriter.InternalSnap20{ 3082 { 3083 Name: "cont-producer", 3084 SnapID: s.AssertedSnapID("cont-producer"), 3085 Channel: "latest/edge", 3086 }, 3087 { 3088 Name: "core18", 3089 SnapID: s.AssertedSnapID("core18"), 3090 Channel: "latest/stable", 3091 }, 3092 { 3093 Name: "cont-consumer", 3094 Unasserted: "cont-consumer_1.0.snap", 3095 }, 3096 }) 3097 } 3098 3099 func (s *writerSuite) TestSeedSnapsWriteMetaCore20LocalAssertedSnaps(c *C) { 3100 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 3101 "display-name": "my model", 3102 "architecture": "amd64", 3103 "base": "core20", 3104 "grade": "dangerous", 3105 "snaps": []interface{}{ 3106 map[string]interface{}{ 3107 "name": "pc-kernel", 3108 "id": s.AssertedSnapID("pc-kernel"), 3109 "type": "kernel", 3110 "default-channel": "20", 3111 }, 3112 map[string]interface{}{ 3113 "name": "pc", 3114 "id": s.AssertedSnapID("pc"), 3115 "type": "gadget", 3116 "default-channel": "20", 3117 }}, 3118 }) 3119 3120 // sanity 3121 c.Assert(model.Grade(), Equals, asserts.ModelDangerous) 3122 3123 s.makeSnap(c, "snapd", "") 3124 s.makeSnap(c, "core20", "") 3125 s.makeSnap(c, "pc-kernel=20", "") 3126 s.makeSnap(c, "pc=20", "") 3127 s.makeSnap(c, "required20", "developerid") 3128 3129 s.opts.Label = "20191122" 3130 w, err := seedwriter.New(model, s.opts) 3131 c.Assert(err, IsNil) 3132 3133 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: s.AssertedSnap("pc"), Channel: "edge"}, {Path: s.AssertedSnap("required20")}}) 3134 c.Assert(err, IsNil) 3135 3136 tf, err := w.Start(s.db, s.newFetcher) 3137 c.Assert(err, IsNil) 3138 3139 localSnaps, err := w.LocalSnaps() 3140 c.Assert(err, IsNil) 3141 c.Assert(localSnaps, HasLen, 2) 3142 3143 for _, sn := range localSnaps { 3144 si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db) 3145 c.Assert(err, IsNil) 3146 f, err := snapfile.Open(sn.Path) 3147 c.Assert(err, IsNil) 3148 info, err := snap.ReadInfoFromSnapFile(f, si) 3149 c.Assert(err, IsNil) 3150 w.SetInfo(sn, info) 3151 sn.ARefs = aRefs 3152 } 3153 3154 err = w.InfoDerived() 3155 c.Assert(err, IsNil) 3156 3157 snaps, err := w.SnapsToDownload() 3158 c.Assert(err, IsNil) 3159 c.Check(snaps, HasLen, 3) 3160 3161 for _, sn := range snaps { 3162 channel := "latest/stable" 3163 switch sn.SnapName() { 3164 case "pc", "pc-kernel": 3165 channel = "20" 3166 } 3167 c.Check(sn.Channel, Equals, channel) 3168 s.fillDownloadedSnap(c, w, sn) 3169 } 3170 3171 complete, err := w.Downloaded() 3172 c.Assert(err, IsNil) 3173 c.Check(complete, Equals, false) 3174 3175 snaps, err = w.SnapsToDownload() 3176 c.Assert(err, IsNil) 3177 c.Assert(snaps, HasLen, 0) 3178 3179 complete, err = w.Downloaded() 3180 c.Assert(err, IsNil) 3181 c.Check(complete, Equals, true) 3182 3183 copySnap := func(name, src, dst string) error { 3184 return osutil.CopyFile(src, dst, 0) 3185 } 3186 3187 err = w.SeedSnaps(copySnap) 3188 c.Assert(err, IsNil) 3189 3190 err = w.WriteMeta() 3191 c.Assert(err, IsNil) 3192 3193 // check seed 3194 systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label) 3195 c.Check(systemDir, testutil.FilePresent) 3196 3197 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 3198 c.Assert(err, IsNil) 3199 c.Check(l, HasLen, 4) 3200 3201 // local asserted model snap was put in /snaps 3202 c.Check(filepath.Join(s.opts.SeedDir, "snaps", "pc_1.snap"), testutil.FilePresent) 3203 // extra snaps were put in system snaps dir 3204 c.Check(filepath.Join(systemDir, "snaps", "required20_1.snap"), testutil.FilePresent) 3205 3206 options20, err := seedwriter.InternalReadOptions20(filepath.Join(systemDir, "options.yaml")) 3207 c.Assert(err, IsNil) 3208 3209 c.Check(options20.Snaps, DeepEquals, []*seedwriter.InternalSnap20{ 3210 { 3211 Name: "pc", 3212 SnapID: s.AssertedSnapID("pc"), 3213 Channel: "20/edge", 3214 }, 3215 { 3216 Name: "required20", 3217 SnapID: s.AssertedSnapID("required20"), 3218 Channel: "latest/stable", 3219 }, 3220 }) 3221 } 3222 3223 func (s *writerSuite) TestSeedSnapsWriteMetaCore20SignedLocalAssertedSnaps(c *C) { 3224 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 3225 "display-name": "my model", 3226 "architecture": "amd64", 3227 "base": "core20", 3228 "grade": "signed", 3229 "snaps": []interface{}{ 3230 map[string]interface{}{ 3231 "name": "pc-kernel", 3232 "id": s.AssertedSnapID("pc-kernel"), 3233 "type": "kernel", 3234 "default-channel": "20", 3235 }, 3236 map[string]interface{}{ 3237 "name": "pc", 3238 "id": s.AssertedSnapID("pc"), 3239 "type": "gadget", 3240 "default-channel": "20", 3241 }}, 3242 }) 3243 3244 // soundness 3245 c.Assert(model.Grade(), Equals, asserts.ModelSigned) 3246 3247 s.makeSnap(c, "snapd", "") 3248 s.makeSnap(c, "core20", "") 3249 s.makeSnap(c, "pc-kernel=20", "") 3250 s.makeSnap(c, "pc=20", "") 3251 3252 s.opts.Label = "20191122" 3253 w, err := seedwriter.New(model, s.opts) 3254 c.Assert(err, IsNil) 3255 3256 // use a local asserted snap with signed, which is supported 3257 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: s.AssertedSnap("pc")}}) 3258 c.Assert(err, IsNil) 3259 3260 tf, err := w.Start(s.db, s.newFetcher) 3261 c.Assert(err, IsNil) 3262 3263 localSnaps, err := w.LocalSnaps() 3264 c.Assert(err, IsNil) 3265 c.Assert(localSnaps, HasLen, 1) 3266 3267 for _, sn := range localSnaps { 3268 si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db) 3269 c.Assert(err, IsNil) 3270 f, err := snapfile.Open(sn.Path) 3271 c.Assert(err, IsNil) 3272 info, err := snap.ReadInfoFromSnapFile(f, si) 3273 c.Assert(err, IsNil) 3274 w.SetInfo(sn, info) 3275 sn.ARefs = aRefs 3276 } 3277 3278 err = w.InfoDerived() 3279 c.Assert(err, IsNil) 3280 3281 snaps, err := w.SnapsToDownload() 3282 c.Assert(err, IsNil) 3283 c.Check(snaps, HasLen, 3) 3284 3285 for _, sn := range snaps { 3286 channel := "latest/stable" 3287 switch sn.SnapName() { 3288 case "pc", "pc-kernel": 3289 channel = "20" 3290 } 3291 c.Check(sn.Channel, Equals, channel) 3292 s.fillDownloadedSnap(c, w, sn) 3293 } 3294 3295 complete, err := w.Downloaded() 3296 c.Assert(err, IsNil) 3297 c.Check(complete, Equals, true) 3298 3299 copySnap := func(name, src, dst string) error { 3300 return osutil.CopyFile(src, dst, 0) 3301 } 3302 3303 err = w.SeedSnaps(copySnap) 3304 c.Assert(err, IsNil) 3305 3306 err = w.WriteMeta() 3307 c.Assert(err, IsNil) 3308 3309 // check seed 3310 systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label) 3311 c.Check(systemDir, testutil.FilePresent) 3312 3313 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 3314 c.Assert(err, IsNil) 3315 c.Check(l, HasLen, 4) 3316 3317 // local asserted model snap was put in /snaps 3318 c.Check(filepath.Join(s.opts.SeedDir, "snaps", "pc_1.snap"), testutil.FilePresent) 3319 3320 // no options file was created 3321 c.Check(filepath.Join(systemDir, "options.yaml"), testutil.FileAbsent) 3322 } 3323 3324 func (s *writerSuite) TestSeedSnapsWriteCore20ErrWhenDirExists(c *C) { 3325 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 3326 "display-name": "my model", 3327 "architecture": "amd64", 3328 "base": "core20", 3329 "grade": "signed", 3330 "snaps": []interface{}{ 3331 map[string]interface{}{ 3332 "name": "pc-kernel", 3333 "id": s.AssertedSnapID("pc-kernel"), 3334 "type": "kernel", 3335 "default-channel": "20", 3336 }, 3337 map[string]interface{}{ 3338 "name": "pc", 3339 "id": s.AssertedSnapID("pc"), 3340 "type": "gadget", 3341 "default-channel": "20", 3342 }}, 3343 }) 3344 3345 err := os.MkdirAll(filepath.Join(s.opts.SeedDir, "systems", "1234"), 0755) 3346 c.Assert(err, IsNil) 3347 s.opts.Label = "1234" 3348 w, err := seedwriter.New(model, s.opts) 3349 c.Assert(err, IsNil) 3350 c.Assert(w, NotNil) 3351 3352 _, err = w.Start(s.db, s.newFetcher) 3353 c.Assert(err, ErrorMatches, `system "1234" already exists`) 3354 c.Assert(seedwriter.IsSytemDirectoryExistsError(err), Equals, true) 3355 }