github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/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").Contact = "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").Contact = "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").Contact = "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").Contact = "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": "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 const expectedErr = `cannot override channels, add local snaps or extra snaps with a model of grade higher than dangerous` 2400 2401 w, err := seedwriter.New(model, s.opts) 2402 c.Assert(err, IsNil) 2403 2404 _, err = w.Start(s.db, s.newFetcher) 2405 c.Assert(err, IsNil) 2406 2407 localSnaps, err := w.LocalSnaps() 2408 c.Assert(err, IsNil) 2409 c.Assert(localSnaps, HasLen, 0) 2410 2411 snaps, err := w.SnapsToDownload() 2412 c.Check(err, IsNil) 2413 c.Assert(snaps, HasLen, 5) 2414 2415 c.Assert(snaps[4].SnapName(), Equals, "my-devmode") 2416 sn := snaps[4] 2417 2418 info := s.AssertedSnapInfo(sn.SnapName()) 2419 c.Assert(info, NotNil, Commentf("%s not defined", sn.SnapName())) 2420 err = w.SetInfo(sn, info) 2421 c.Assert(err, ErrorMatches, "cannot override channels, add devmode snaps, local snaps, or extra snaps with a model of grade higher than dangerous") 2422 c.Check(sn.Info, Not(Equals), info) 2423 } 2424 2425 func (s *writerSuite) TestCore20NonDangerousDisallowedOptionsSnaps(c *C) { 2426 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2427 "display-name": "my model", 2428 "architecture": "amd64", 2429 "store": "my-store", 2430 "base": "core20", 2431 "snaps": []interface{}{ 2432 map[string]interface{}{ 2433 "name": "pc-kernel", 2434 "id": s.AssertedSnapID("pc-kernel"), 2435 "type": "kernel", 2436 "default-channel": "20", 2437 }, 2438 map[string]interface{}{ 2439 "name": "pc", 2440 "id": s.AssertedSnapID("pc"), 2441 "type": "gadget", 2442 "default-channel": "20", 2443 }, 2444 }, 2445 }) 2446 2447 pcFn := s.makeLocalSnap(c, "pc") 2448 2449 baseLabel := "20191107" 2450 2451 tests := []struct { 2452 optSnap *seedwriter.OptionsSnap 2453 }{ 2454 {&seedwriter.OptionsSnap{Name: "extra"}}, 2455 {&seedwriter.OptionsSnap{Path: pcFn}}, 2456 {&seedwriter.OptionsSnap{Name: "pc", Channel: "edge"}}, 2457 } 2458 2459 const expectedErr = `cannot override channels, add devmode snaps, local snaps, or extra snaps with a model of grade higher than dangerous` 2460 2461 for idx, t := range tests { 2462 s.opts.Label = fmt.Sprintf("%s%d", baseLabel, idx) 2463 w, err := seedwriter.New(model, s.opts) 2464 c.Assert(err, IsNil) 2465 2466 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{t.optSnap}) 2467 if err != nil { 2468 c.Check(err, ErrorMatches, expectedErr) 2469 continue 2470 } 2471 2472 tf, err := w.Start(s.db, s.newFetcher) 2473 c.Assert(err, IsNil) 2474 2475 if t.optSnap.Path != "" { 2476 localSnaps, err := w.LocalSnaps() 2477 c.Assert(err, IsNil) 2478 c.Assert(localSnaps, HasLen, 1) 2479 2480 for _, sn := range localSnaps { 2481 si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db) 2482 if !asserts.IsNotFound(err) { 2483 c.Assert(err, IsNil) 2484 } 2485 f, err := snapfile.Open(sn.Path) 2486 c.Assert(err, IsNil) 2487 info, err := snap.ReadInfoFromSnapFile(f, si) 2488 c.Assert(err, IsNil) 2489 w.SetInfo(sn, info) 2490 sn.ARefs = aRefs 2491 } 2492 2493 err = w.InfoDerived() 2494 c.Check(err, ErrorMatches, expectedErr) 2495 continue 2496 } 2497 2498 _, err = w.SnapsToDownload() 2499 c.Check(err, ErrorMatches, expectedErr) 2500 } 2501 } 2502 2503 func (s *writerSuite) TestCore20NonDangerousNoChannelOverride(c *C) { 2504 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2505 "display-name": "my model", 2506 "architecture": "amd64", 2507 "store": "my-store", 2508 "base": "core20", 2509 "snaps": []interface{}{ 2510 map[string]interface{}{ 2511 "name": "pc-kernel", 2512 "id": s.AssertedSnapID("pc-kernel"), 2513 "type": "kernel", 2514 "default-channel": "20", 2515 }, 2516 map[string]interface{}{ 2517 "name": "pc", 2518 "id": s.AssertedSnapID("pc"), 2519 "type": "gadget", 2520 "default-channel": "20", 2521 }, 2522 }, 2523 }) 2524 2525 s.opts.DefaultChannel = "stable" 2526 s.opts.Label = "20191107" 2527 w, err := seedwriter.New(model, s.opts) 2528 c.Assert(w, IsNil) 2529 c.Check(err, ErrorMatches, `cannot override channels, add devmode snaps, local snaps, or extra snaps with a model of grade higher than dangerous`) 2530 } 2531 2532 func (s *writerSuite) TestSeedSnapsWriteMetaCore20LocalSnaps(c *C) { 2533 // add store assertion 2534 storeAs, err := s.StoreSigning.Sign(asserts.StoreType, map[string]interface{}{ 2535 "store": "my-store", 2536 "operator-id": "canonical", 2537 "timestamp": time.Now().UTC().Format(time.RFC3339), 2538 }, nil, "") 2539 c.Assert(err, IsNil) 2540 err = s.StoreSigning.Add(storeAs) 2541 c.Assert(err, IsNil) 2542 2543 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2544 "display-name": "my model", 2545 "architecture": "amd64", 2546 "store": "my-store", 2547 "base": "core20", 2548 "grade": "dangerous", 2549 "snaps": []interface{}{ 2550 map[string]interface{}{ 2551 "name": "pc-kernel", 2552 "id": s.AssertedSnapID("pc-kernel"), 2553 "type": "kernel", 2554 "default-channel": "20", 2555 }, 2556 map[string]interface{}{ 2557 "name": "pc", 2558 "id": s.AssertedSnapID("pc"), 2559 "type": "gadget", 2560 "default-channel": "20", 2561 }, 2562 map[string]interface{}{ 2563 "name": "required20", 2564 "id": s.AssertedSnapID("required20"), 2565 }, 2566 }, 2567 }) 2568 2569 // sanity 2570 c.Assert(model.Grade(), Equals, asserts.ModelDangerous) 2571 2572 s.makeSnap(c, "snapd", "") 2573 s.makeSnap(c, "core20", "") 2574 s.makeSnap(c, "pc-kernel=20", "") 2575 s.makeSnap(c, "pc=20", "") 2576 requiredFn := s.makeLocalSnap(c, "required20") 2577 2578 s.opts.Label = "20191030" 2579 w, err := seedwriter.New(model, s.opts) 2580 c.Assert(err, IsNil) 2581 2582 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: requiredFn}}) 2583 c.Assert(err, IsNil) 2584 2585 tf, err := w.Start(s.db, s.newFetcher) 2586 c.Assert(err, IsNil) 2587 2588 localSnaps, err := w.LocalSnaps() 2589 c.Assert(err, IsNil) 2590 c.Assert(localSnaps, HasLen, 1) 2591 2592 for _, sn := range localSnaps { 2593 _, _, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db) 2594 c.Assert(asserts.IsNotFound(err), Equals, true) 2595 f, err := snapfile.Open(sn.Path) 2596 c.Assert(err, IsNil) 2597 info, err := snap.ReadInfoFromSnapFile(f, nil) 2598 c.Assert(err, IsNil) 2599 w.SetInfo(sn, info) 2600 } 2601 2602 err = w.InfoDerived() 2603 c.Assert(err, IsNil) 2604 2605 snaps, err := w.SnapsToDownload() 2606 c.Assert(err, IsNil) 2607 c.Check(snaps, HasLen, 4) 2608 2609 for _, sn := range snaps { 2610 // check the used channel at this level because in the 2611 // non-dangerous case is not written anywhere (it 2612 // reflects the model or default) 2613 channel := "latest/stable" 2614 switch sn.SnapName() { 2615 case "pc", "pc-kernel": 2616 channel = "20" 2617 } 2618 c.Check(sn.Channel, Equals, channel) 2619 s.fillDownloadedSnap(c, w, sn) 2620 } 2621 2622 complete, err := w.Downloaded() 2623 c.Assert(err, IsNil) 2624 c.Check(complete, Equals, true) 2625 2626 copySnap := func(name, src, dst string) error { 2627 return osutil.CopyFile(src, dst, 0) 2628 } 2629 2630 err = w.SeedSnaps(copySnap) 2631 c.Assert(err, IsNil) 2632 2633 err = w.WriteMeta() 2634 c.Assert(err, IsNil) 2635 2636 // check seed 2637 systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label) 2638 c.Check(systemDir, testutil.FilePresent) 2639 2640 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 2641 c.Assert(err, IsNil) 2642 c.Check(l, HasLen, 4) 2643 2644 // local unasserted snap was put in system snaps dir 2645 c.Check(filepath.Join(systemDir, "snaps", "required20_1.0.snap"), testutil.FilePresent) 2646 2647 options20, err := seedwriter.InternalReadOptions20(filepath.Join(systemDir, "options.yaml")) 2648 c.Assert(err, IsNil) 2649 2650 c.Check(options20.Snaps, DeepEquals, []*seedwriter.InternalSnap20{ 2651 { 2652 Name: "required20", 2653 SnapID: s.AssertedSnapID("required20"), 2654 Unasserted: "required20_1.0.snap", 2655 }, 2656 }) 2657 } 2658 2659 func (s *writerSuite) TestSeedSnapsWriteMetaCore20ChannelOverrides(c *C) { 2660 // add store assertion 2661 storeAs, err := s.StoreSigning.Sign(asserts.StoreType, map[string]interface{}{ 2662 "store": "my-store", 2663 "operator-id": "canonical", 2664 "timestamp": time.Now().UTC().Format(time.RFC3339), 2665 }, nil, "") 2666 c.Assert(err, IsNil) 2667 err = s.StoreSigning.Add(storeAs) 2668 c.Assert(err, IsNil) 2669 2670 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2671 "display-name": "my model", 2672 "architecture": "amd64", 2673 "store": "my-store", 2674 "base": "core20", 2675 "grade": "dangerous", 2676 "snaps": []interface{}{ 2677 map[string]interface{}{ 2678 "name": "pc-kernel", 2679 "id": s.AssertedSnapID("pc-kernel"), 2680 "type": "kernel", 2681 "default-channel": "20", 2682 }, 2683 map[string]interface{}{ 2684 "name": "pc", 2685 "id": s.AssertedSnapID("pc"), 2686 "type": "gadget", 2687 "default-channel": "20", 2688 }, 2689 map[string]interface{}{ 2690 "name": "required20", 2691 "id": s.AssertedSnapID("required20"), 2692 }, 2693 }, 2694 }) 2695 2696 // sanity 2697 c.Assert(model.Grade(), Equals, asserts.ModelDangerous) 2698 2699 s.makeSnap(c, "snapd", "") 2700 s.makeSnap(c, "core20", "") 2701 s.makeSnap(c, "pc-kernel=20", "") 2702 s.makeSnap(c, "pc=20", "") 2703 s.makeSnap(c, "required20", "developerid") 2704 2705 s.opts.Label = "20191030" 2706 s.opts.DefaultChannel = "candidate" 2707 w, err := seedwriter.New(model, s.opts) 2708 c.Assert(err, IsNil) 2709 2710 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "edge"}}) 2711 c.Assert(err, IsNil) 2712 2713 _, err = w.Start(s.db, s.newFetcher) 2714 c.Assert(err, IsNil) 2715 2716 snaps, err := w.SnapsToDownload() 2717 c.Assert(err, IsNil) 2718 c.Check(snaps, HasLen, 5) 2719 2720 for _, sn := range snaps { 2721 s.fillDownloadedSnap(c, w, sn) 2722 } 2723 2724 complete, err := w.Downloaded() 2725 c.Assert(err, IsNil) 2726 c.Check(complete, Equals, true) 2727 2728 err = w.SeedSnaps(nil) 2729 c.Assert(err, IsNil) 2730 2731 err = w.WriteMeta() 2732 c.Assert(err, IsNil) 2733 2734 // check seed 2735 systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label) 2736 c.Check(systemDir, testutil.FilePresent) 2737 2738 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 2739 c.Assert(err, IsNil) 2740 c.Check(l, HasLen, 5) 2741 2742 options20, err := seedwriter.InternalReadOptions20(filepath.Join(systemDir, "options.yaml")) 2743 c.Assert(err, IsNil) 2744 2745 c.Check(options20.Snaps, DeepEquals, []*seedwriter.InternalSnap20{ 2746 { 2747 Name: "snapd", 2748 SnapID: s.AssertedSnapID("snapd"), // inferred 2749 Channel: "latest/candidate", 2750 }, 2751 { 2752 Name: "pc-kernel", 2753 SnapID: s.AssertedSnapID("pc-kernel"), 2754 Channel: "20/candidate", 2755 }, 2756 { 2757 Name: "core20", 2758 SnapID: s.AssertedSnapID("core20"), // inferred 2759 Channel: "latest/candidate", 2760 }, 2761 { 2762 Name: "pc", 2763 SnapID: s.AssertedSnapID("pc"), 2764 Channel: "20/edge", 2765 }, 2766 { 2767 Name: "required20", 2768 SnapID: s.AssertedSnapID("required20"), 2769 Channel: "latest/candidate", 2770 }, 2771 }) 2772 } 2773 2774 func (s *writerSuite) TestSeedSnapsWriteMetaCore20ModelOverrideSnapd(c *C) { 2775 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2776 "display-name": "my model", 2777 "architecture": "amd64", 2778 "base": "core20", 2779 "snaps": []interface{}{ 2780 map[string]interface{}{ 2781 "name": "snapd", 2782 "id": s.AssertedSnapID("snapd"), 2783 "type": "snapd", 2784 "default-channel": "latest/edge", 2785 }, 2786 map[string]interface{}{ 2787 "name": "pc-kernel", 2788 "id": s.AssertedSnapID("pc-kernel"), 2789 "type": "kernel", 2790 "default-channel": "20", 2791 }, 2792 map[string]interface{}{ 2793 "name": "pc", 2794 "id": s.AssertedSnapID("pc"), 2795 "type": "gadget", 2796 "default-channel": "20", 2797 }}, 2798 }) 2799 2800 // sanity 2801 c.Assert(model.Grade(), Equals, asserts.ModelSigned) 2802 2803 s.makeSnap(c, "snapd", "") 2804 s.makeSnap(c, "core20", "") 2805 s.makeSnap(c, "pc-kernel=20", "") 2806 s.makeSnap(c, "pc=20", "") 2807 2808 s.opts.Label = "20191121" 2809 w, err := seedwriter.New(model, s.opts) 2810 c.Assert(err, IsNil) 2811 2812 _, err = w.Start(s.db, s.newFetcher) 2813 c.Assert(err, IsNil) 2814 2815 snaps, err := w.SnapsToDownload() 2816 c.Assert(err, IsNil) 2817 c.Check(snaps, HasLen, 4) 2818 2819 for _, sn := range snaps { 2820 // check the used channel at this level because in the 2821 // non-dangerous case is not written anywhere (it 2822 // reflects the model or default) 2823 channel := "latest/stable" 2824 switch sn.SnapName() { 2825 case "snapd": 2826 channel = "latest/edge" 2827 case "pc", "pc-kernel": 2828 channel = "20" 2829 } 2830 c.Check(sn.Channel, Equals, channel) 2831 s.fillDownloadedSnap(c, w, sn) 2832 } 2833 2834 complete, err := w.Downloaded() 2835 c.Assert(err, IsNil) 2836 c.Check(complete, Equals, true) 2837 2838 err = w.SeedSnaps(nil) 2839 c.Assert(err, IsNil) 2840 2841 err = w.WriteMeta() 2842 c.Assert(err, IsNil) 2843 2844 // check seed 2845 systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label) 2846 c.Check(systemDir, testutil.FilePresent) 2847 2848 // check the snaps are in place 2849 for _, name := range []string{"snapd", "pc-kernel", "core20", "pc"} { 2850 info := s.AssertedSnapInfo(name) 2851 2852 fn := info.Filename() 2853 p := filepath.Join(s.opts.SeedDir, "snaps", fn) 2854 c.Check(p, testutil.FilePresent) 2855 } 2856 2857 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 2858 c.Assert(err, IsNil) 2859 c.Check(l, HasLen, 4) 2860 2861 c.Check(filepath.Join(systemDir, "extra-snaps"), testutil.FileAbsent) 2862 c.Check(filepath.Join(systemDir, "options.yaml"), testutil.FileAbsent) 2863 } 2864 2865 func (s *writerSuite) TestSnapsToDownloadCore20OptionalSnaps(c *C) { 2866 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2867 "display-name": "my model", 2868 "architecture": "amd64", 2869 "base": "core20", 2870 "snaps": []interface{}{ 2871 map[string]interface{}{ 2872 "name": "pc-kernel", 2873 "id": s.AssertedSnapID("pc-kernel"), 2874 "type": "kernel", 2875 "default-channel": "20", 2876 }, 2877 map[string]interface{}{ 2878 "name": "pc", 2879 "id": s.AssertedSnapID("pc"), 2880 "type": "gadget", 2881 "default-channel": "20", 2882 }, 2883 map[string]interface{}{ 2884 "name": "core18", 2885 "id": s.AssertedSnapID("core18"), 2886 "type": "base", 2887 }, 2888 map[string]interface{}{ 2889 "name": "optional20-a", 2890 "id": s.AssertedSnapID("optional20-a"), 2891 "presence": "optional", 2892 }, 2893 map[string]interface{}{ 2894 "name": "optional20-b", 2895 "id": s.AssertedSnapID("optional20-b"), 2896 "presence": "optional", 2897 }}, 2898 }) 2899 2900 // sanity 2901 c.Assert(model.Grade(), Equals, asserts.ModelSigned) 2902 2903 s.makeSnap(c, "snapd", "") 2904 s.makeSnap(c, "core20", "") 2905 s.makeSnap(c, "core18", "") 2906 s.makeSnap(c, "pc-kernel=20", "") 2907 s.makeSnap(c, "pc=20", "") 2908 s.makeSnap(c, "optional20-a", "developerid") 2909 s.makeSnap(c, "optional20-b", "developerid") 2910 2911 s.opts.Label = "20191122" 2912 w, err := seedwriter.New(model, s.opts) 2913 c.Assert(err, IsNil) 2914 2915 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "optional20-b"}}) 2916 c.Assert(err, IsNil) 2917 2918 _, err = w.Start(s.db, s.newFetcher) 2919 c.Assert(err, IsNil) 2920 2921 snaps, err := w.SnapsToDownload() 2922 c.Assert(err, IsNil) 2923 c.Check(snaps, HasLen, 6) 2924 c.Check(snaps[5].SnapName(), Equals, "optional20-b") 2925 } 2926 2927 func (s *writerSuite) TestSeedSnapsWriteMetaCore20ExtraSnaps(c *C) { 2928 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2929 "display-name": "my model", 2930 "architecture": "amd64", 2931 "base": "core20", 2932 "grade": "dangerous", 2933 "snaps": []interface{}{ 2934 map[string]interface{}{ 2935 "name": "pc-kernel", 2936 "id": s.AssertedSnapID("pc-kernel"), 2937 "type": "kernel", 2938 "default-channel": "20", 2939 }, 2940 map[string]interface{}{ 2941 "name": "pc", 2942 "id": s.AssertedSnapID("pc"), 2943 "type": "gadget", 2944 "default-channel": "20", 2945 }}, 2946 }) 2947 2948 // sanity 2949 c.Assert(model.Grade(), Equals, asserts.ModelDangerous) 2950 2951 s.makeSnap(c, "snapd", "") 2952 s.makeSnap(c, "core20", "") 2953 s.makeSnap(c, "pc-kernel=20", "") 2954 s.makeSnap(c, "pc=20", "") 2955 s.makeSnap(c, "core18", "") 2956 s.makeSnap(c, "cont-producer", "developerid") 2957 contConsumerFn := s.makeLocalSnap(c, "cont-consumer") 2958 2959 s.opts.Label = "20191122" 2960 w, err := seedwriter.New(model, s.opts) 2961 c.Assert(err, IsNil) 2962 2963 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "cont-producer", Channel: "edge"}, {Name: "core18"}, {Path: contConsumerFn}}) 2964 c.Assert(err, IsNil) 2965 2966 tf, err := w.Start(s.db, s.newFetcher) 2967 c.Assert(err, IsNil) 2968 2969 localSnaps, err := w.LocalSnaps() 2970 c.Assert(err, IsNil) 2971 c.Assert(localSnaps, HasLen, 1) 2972 2973 for _, sn := range localSnaps { 2974 _, _, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db) 2975 c.Assert(asserts.IsNotFound(err), Equals, true) 2976 f, err := snapfile.Open(sn.Path) 2977 c.Assert(err, IsNil) 2978 info, err := snap.ReadInfoFromSnapFile(f, nil) 2979 c.Assert(err, IsNil) 2980 w.SetInfo(sn, info) 2981 } 2982 2983 err = w.InfoDerived() 2984 c.Assert(err, IsNil) 2985 2986 snaps, err := w.SnapsToDownload() 2987 c.Assert(err, IsNil) 2988 c.Check(snaps, HasLen, 4) 2989 2990 for _, sn := range snaps { 2991 channel := "latest/stable" 2992 switch sn.SnapName() { 2993 case "pc", "pc-kernel": 2994 channel = "20" 2995 } 2996 c.Check(sn.Channel, Equals, channel) 2997 s.fillDownloadedSnap(c, w, sn) 2998 } 2999 3000 complete, err := w.Downloaded() 3001 c.Assert(err, IsNil) 3002 c.Check(complete, Equals, false) 3003 3004 snaps, err = w.SnapsToDownload() 3005 c.Assert(err, IsNil) 3006 c.Assert(snaps, HasLen, 2) 3007 c.Check(snaps[0].SnapName(), Equals, "cont-producer") 3008 c.Check(snaps[1].SnapName(), Equals, "core18") 3009 3010 for _, sn := range snaps { 3011 channel := "latest/stable" 3012 switch sn.SnapName() { 3013 case "cont-producer": 3014 channel = "latest/edge" 3015 } 3016 c.Check(sn.Channel, Equals, channel) 3017 3018 info := s.doFillMetaDownloadedSnap(c, w, sn) 3019 3020 c.Assert(sn.Path, Equals, filepath.Join(s.opts.SeedDir, "systems", s.opts.Label, "snaps", info.Filename())) 3021 err := os.Rename(s.AssertedSnap(sn.SnapName()), sn.Path) 3022 c.Assert(err, IsNil) 3023 } 3024 3025 complete, err = w.Downloaded() 3026 c.Assert(err, IsNil) 3027 c.Check(complete, Equals, true) 3028 3029 copySnap := func(name, src, dst string) error { 3030 return osutil.CopyFile(src, dst, 0) 3031 } 3032 3033 err = w.SeedSnaps(copySnap) 3034 c.Assert(err, IsNil) 3035 3036 err = w.WriteMeta() 3037 c.Assert(err, IsNil) 3038 3039 // check seed 3040 systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label) 3041 c.Check(systemDir, testutil.FilePresent) 3042 3043 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 3044 c.Assert(err, IsNil) 3045 c.Check(l, HasLen, 4) 3046 3047 // extra snaps were put in system snaps dir 3048 c.Check(filepath.Join(systemDir, "snaps", "core18_1.snap"), testutil.FilePresent) 3049 c.Check(filepath.Join(systemDir, "snaps", "cont-producer_1.snap"), testutil.FilePresent) 3050 c.Check(filepath.Join(systemDir, "snaps", "cont-consumer_1.0.snap"), testutil.FilePresent) 3051 3052 // check extra-snaps in assertions 3053 snapAsserts := seedtest.ReadAssertions(c, filepath.Join(systemDir, "assertions", "extra-snaps")) 3054 seen := make(map[string]bool) 3055 3056 for _, a := range snapAsserts { 3057 uniq := a.Ref().Unique() 3058 if a.Type() == asserts.SnapRevisionType { 3059 rev := a.(*asserts.SnapRevision) 3060 uniq = fmt.Sprintf("%s@%d", rev.SnapID(), rev.SnapRevision()) 3061 } 3062 seen[uniq] = true 3063 } 3064 3065 snapRevUniq := func(snapName string, revno int) string { 3066 return fmt.Sprintf("%s@%d", s.AssertedSnapID(snapName), revno) 3067 } 3068 snapDeclUniq := func(snapName string) string { 3069 return "snap-declaration/16/" + s.AssertedSnapID(snapName) 3070 } 3071 3072 c.Check(seen, DeepEquals, map[string]bool{ 3073 "account/developerid": true, 3074 snapDeclUniq("core18"): true, 3075 snapDeclUniq("cont-producer"): true, 3076 snapRevUniq("core18", 1): true, 3077 snapRevUniq("cont-producer", 1): true, 3078 }) 3079 3080 options20, err := seedwriter.InternalReadOptions20(filepath.Join(systemDir, "options.yaml")) 3081 c.Assert(err, IsNil) 3082 3083 c.Check(options20.Snaps, DeepEquals, []*seedwriter.InternalSnap20{ 3084 { 3085 Name: "cont-producer", 3086 SnapID: s.AssertedSnapID("cont-producer"), 3087 Channel: "latest/edge", 3088 }, 3089 { 3090 Name: "core18", 3091 SnapID: s.AssertedSnapID("core18"), 3092 Channel: "latest/stable", 3093 }, 3094 { 3095 Name: "cont-consumer", 3096 Unasserted: "cont-consumer_1.0.snap", 3097 }, 3098 }) 3099 } 3100 3101 func (s *writerSuite) TestSeedSnapsWriteMetaCore20LocalAssertedSnaps(c *C) { 3102 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 3103 "display-name": "my model", 3104 "architecture": "amd64", 3105 "base": "core20", 3106 "grade": "dangerous", 3107 "snaps": []interface{}{ 3108 map[string]interface{}{ 3109 "name": "pc-kernel", 3110 "id": s.AssertedSnapID("pc-kernel"), 3111 "type": "kernel", 3112 "default-channel": "20", 3113 }, 3114 map[string]interface{}{ 3115 "name": "pc", 3116 "id": s.AssertedSnapID("pc"), 3117 "type": "gadget", 3118 "default-channel": "20", 3119 }}, 3120 }) 3121 3122 // sanity 3123 c.Assert(model.Grade(), Equals, asserts.ModelDangerous) 3124 3125 s.makeSnap(c, "snapd", "") 3126 s.makeSnap(c, "core20", "") 3127 s.makeSnap(c, "pc-kernel=20", "") 3128 s.makeSnap(c, "pc=20", "") 3129 s.makeSnap(c, "required20", "developerid") 3130 3131 s.opts.Label = "20191122" 3132 w, err := seedwriter.New(model, s.opts) 3133 c.Assert(err, IsNil) 3134 3135 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: s.AssertedSnap("pc"), Channel: "edge"}, {Path: s.AssertedSnap("required20")}}) 3136 c.Assert(err, IsNil) 3137 3138 tf, err := w.Start(s.db, s.newFetcher) 3139 c.Assert(err, IsNil) 3140 3141 localSnaps, err := w.LocalSnaps() 3142 c.Assert(err, IsNil) 3143 c.Assert(localSnaps, HasLen, 2) 3144 3145 for _, sn := range localSnaps { 3146 si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db) 3147 c.Assert(err, IsNil) 3148 f, err := snapfile.Open(sn.Path) 3149 c.Assert(err, IsNil) 3150 info, err := snap.ReadInfoFromSnapFile(f, si) 3151 c.Assert(err, IsNil) 3152 w.SetInfo(sn, info) 3153 sn.ARefs = aRefs 3154 } 3155 3156 err = w.InfoDerived() 3157 c.Assert(err, IsNil) 3158 3159 snaps, err := w.SnapsToDownload() 3160 c.Assert(err, IsNil) 3161 c.Check(snaps, HasLen, 3) 3162 3163 for _, sn := range snaps { 3164 channel := "latest/stable" 3165 switch sn.SnapName() { 3166 case "pc", "pc-kernel": 3167 channel = "20" 3168 } 3169 c.Check(sn.Channel, Equals, channel) 3170 s.fillDownloadedSnap(c, w, sn) 3171 } 3172 3173 complete, err := w.Downloaded() 3174 c.Assert(err, IsNil) 3175 c.Check(complete, Equals, false) 3176 3177 snaps, err = w.SnapsToDownload() 3178 c.Assert(err, IsNil) 3179 c.Assert(snaps, HasLen, 0) 3180 3181 complete, err = w.Downloaded() 3182 c.Assert(err, IsNil) 3183 c.Check(complete, Equals, true) 3184 3185 copySnap := func(name, src, dst string) error { 3186 return osutil.CopyFile(src, dst, 0) 3187 } 3188 3189 err = w.SeedSnaps(copySnap) 3190 c.Assert(err, IsNil) 3191 3192 err = w.WriteMeta() 3193 c.Assert(err, IsNil) 3194 3195 // check seed 3196 systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label) 3197 c.Check(systemDir, testutil.FilePresent) 3198 3199 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 3200 c.Assert(err, IsNil) 3201 c.Check(l, HasLen, 4) 3202 3203 // local asserted model snap was put in /snaps 3204 c.Check(filepath.Join(s.opts.SeedDir, "snaps", "pc_1.snap"), testutil.FilePresent) 3205 // extra snaps were put in system snaps dir 3206 c.Check(filepath.Join(systemDir, "snaps", "required20_1.snap"), testutil.FilePresent) 3207 3208 options20, err := seedwriter.InternalReadOptions20(filepath.Join(systemDir, "options.yaml")) 3209 c.Assert(err, IsNil) 3210 3211 c.Check(options20.Snaps, DeepEquals, []*seedwriter.InternalSnap20{ 3212 { 3213 Name: "pc", 3214 SnapID: s.AssertedSnapID("pc"), 3215 Channel: "20/edge", 3216 }, 3217 { 3218 Name: "required20", 3219 SnapID: s.AssertedSnapID("required20"), 3220 Channel: "latest/stable", 3221 }, 3222 }) 3223 } 3224 3225 func (s *writerSuite) TestSeedSnapsWriteMetaCore20SignedLocalAssertedSnaps(c *C) { 3226 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 3227 "display-name": "my model", 3228 "architecture": "amd64", 3229 "base": "core20", 3230 "grade": "signed", 3231 "snaps": []interface{}{ 3232 map[string]interface{}{ 3233 "name": "pc-kernel", 3234 "id": s.AssertedSnapID("pc-kernel"), 3235 "type": "kernel", 3236 "default-channel": "20", 3237 }, 3238 map[string]interface{}{ 3239 "name": "pc", 3240 "id": s.AssertedSnapID("pc"), 3241 "type": "gadget", 3242 "default-channel": "20", 3243 }}, 3244 }) 3245 3246 // soundness 3247 c.Assert(model.Grade(), Equals, asserts.ModelSigned) 3248 3249 s.makeSnap(c, "snapd", "") 3250 s.makeSnap(c, "core20", "") 3251 s.makeSnap(c, "pc-kernel=20", "") 3252 s.makeSnap(c, "pc=20", "") 3253 3254 s.opts.Label = "20191122" 3255 w, err := seedwriter.New(model, s.opts) 3256 c.Assert(err, IsNil) 3257 3258 // use a local asserted snap with signed, which is supported 3259 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: s.AssertedSnap("pc")}}) 3260 c.Assert(err, IsNil) 3261 3262 tf, err := w.Start(s.db, s.newFetcher) 3263 c.Assert(err, IsNil) 3264 3265 localSnaps, err := w.LocalSnaps() 3266 c.Assert(err, IsNil) 3267 c.Assert(localSnaps, HasLen, 1) 3268 3269 for _, sn := range localSnaps { 3270 si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db) 3271 c.Assert(err, IsNil) 3272 f, err := snapfile.Open(sn.Path) 3273 c.Assert(err, IsNil) 3274 info, err := snap.ReadInfoFromSnapFile(f, si) 3275 c.Assert(err, IsNil) 3276 w.SetInfo(sn, info) 3277 sn.ARefs = aRefs 3278 } 3279 3280 err = w.InfoDerived() 3281 c.Assert(err, IsNil) 3282 3283 snaps, err := w.SnapsToDownload() 3284 c.Assert(err, IsNil) 3285 c.Check(snaps, HasLen, 3) 3286 3287 for _, sn := range snaps { 3288 channel := "latest/stable" 3289 switch sn.SnapName() { 3290 case "pc", "pc-kernel": 3291 channel = "20" 3292 } 3293 c.Check(sn.Channel, Equals, channel) 3294 s.fillDownloadedSnap(c, w, sn) 3295 } 3296 3297 complete, err := w.Downloaded() 3298 c.Assert(err, IsNil) 3299 c.Check(complete, Equals, true) 3300 3301 copySnap := func(name, src, dst string) error { 3302 return osutil.CopyFile(src, dst, 0) 3303 } 3304 3305 err = w.SeedSnaps(copySnap) 3306 c.Assert(err, IsNil) 3307 3308 err = w.WriteMeta() 3309 c.Assert(err, IsNil) 3310 3311 // check seed 3312 systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label) 3313 c.Check(systemDir, testutil.FilePresent) 3314 3315 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 3316 c.Assert(err, IsNil) 3317 c.Check(l, HasLen, 4) 3318 3319 // local asserted model snap was put in /snaps 3320 c.Check(filepath.Join(s.opts.SeedDir, "snaps", "pc_1.snap"), testutil.FilePresent) 3321 3322 // no options file was created 3323 c.Check(filepath.Join(systemDir, "options.yaml"), testutil.FileAbsent) 3324 } 3325 3326 func (s *writerSuite) TestSeedSnapsWriteCore20ErrWhenDirExists(c *C) { 3327 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 3328 "display-name": "my model", 3329 "architecture": "amd64", 3330 "base": "core20", 3331 "grade": "signed", 3332 "snaps": []interface{}{ 3333 map[string]interface{}{ 3334 "name": "pc-kernel", 3335 "id": s.AssertedSnapID("pc-kernel"), 3336 "type": "kernel", 3337 "default-channel": "20", 3338 }, 3339 map[string]interface{}{ 3340 "name": "pc", 3341 "id": s.AssertedSnapID("pc"), 3342 "type": "gadget", 3343 "default-channel": "20", 3344 }}, 3345 }) 3346 3347 err := os.MkdirAll(filepath.Join(s.opts.SeedDir, "systems", "1234"), 0755) 3348 c.Assert(err, IsNil) 3349 s.opts.Label = "1234" 3350 w, err := seedwriter.New(model, s.opts) 3351 c.Assert(err, IsNil) 3352 c.Assert(w, NotNil) 3353 3354 _, err = w.Start(s.db, s.newFetcher) 3355 c.Assert(err, ErrorMatches, `system "1234" already exists`) 3356 c.Assert(seedwriter.IsSytemDirectoryExistsError(err), Equals, true) 3357 }