github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/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) TestSeedSnapsWriteMetaCore18(c *C) { 764 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 765 "display-name": "my model", 766 "architecture": "amd64", 767 "base": "core18", 768 "gadget": "pc=18", 769 "kernel": "pc-kernel=18", 770 "required-snaps": []interface{}{"cont-consumer", "cont-producer"}, 771 }) 772 773 s.makeSnap(c, "snapd", "") 774 s.makeSnap(c, "core18", "") 775 s.makeSnap(c, "pc-kernel=18", "") 776 s.makeSnap(c, "pc=18", "") 777 s.makeSnap(c, "cont-producer", "developerid") 778 s.makeSnap(c, "cont-consumer", "developerid") 779 780 w, err := seedwriter.New(model, s.opts) 781 c.Assert(err, IsNil) 782 783 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "edge"}}) 784 c.Assert(err, IsNil) 785 786 _, err = w.Start(s.db, s.newFetcher) 787 c.Assert(err, IsNil) 788 789 snaps, err := w.SnapsToDownload() 790 c.Assert(err, IsNil) 791 c.Check(snaps, HasLen, 6) 792 793 s.AssertedSnapInfo("cont-producer").Contact = "author@cont-producer.net" 794 for _, sn := range snaps { 795 s.fillDownloadedSnap(c, w, sn) 796 } 797 798 complete, err := w.Downloaded() 799 c.Assert(err, IsNil) 800 c.Check(complete, Equals, true) 801 802 err = w.SeedSnaps(nil) 803 c.Assert(err, IsNil) 804 805 err = w.WriteMeta() 806 c.Assert(err, IsNil) 807 808 // check seed 809 seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml")) 810 c.Assert(err, IsNil) 811 812 c.Check(seedYaml.Snaps, HasLen, 6) 813 814 // check the files are in place 815 for i, name := range []string{"snapd", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer"} { 816 info := s.AssertedSnapInfo(name) 817 818 fn := info.Filename() 819 p := filepath.Join(s.opts.SeedDir, "snaps", fn) 820 c.Check(p, testutil.FilePresent) 821 822 channel := "stable" 823 switch name { 824 case "pc-kernel": 825 channel = "18" 826 case "pc": 827 channel = "18/edge" 828 } 829 830 c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{ 831 Name: info.SnapName(), 832 SnapID: info.SnapID, 833 Channel: channel, 834 File: fn, 835 Contact: info.Contact, 836 }) 837 } 838 839 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 840 c.Assert(err, IsNil) 841 c.Check(l, HasLen, 6) 842 843 // check assertions 844 seedAssertsDir := filepath.Join(s.opts.SeedDir, "assertions") 845 storeAccountKeyPK := s.StoreSigning.StoreAccountKey("").PublicKeyID() 846 brandAcctKeyPK := s.Brands.AccountKey("my-brand").PublicKeyID() 847 848 for _, fn := range []string{"model", brandAcctKeyPK + ".account-key", "my-brand.account", storeAccountKeyPK + ".account-key"} { 849 p := filepath.Join(seedAssertsDir, fn) 850 c.Check(p, testutil.FilePresent) 851 } 852 853 c.Check(filepath.Join(seedAssertsDir, "model"), testutil.FileEquals, asserts.Encode(model)) 854 855 acct := seedtest.ReadAssertions(c, filepath.Join(seedAssertsDir, "my-brand.account")) 856 c.Assert(acct, HasLen, 1) 857 c.Check(acct[0].Type(), Equals, asserts.AccountType) 858 c.Check(acct[0].HeaderString("account-id"), Equals, "my-brand") 859 860 // check the snap assertions are also in place 861 for _, snapName := range []string{"snapd", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer"} { 862 p := filepath.Join(seedAssertsDir, fmt.Sprintf("16,%s.snap-declaration", s.AssertedSnapID(snapName))) 863 decl := seedtest.ReadAssertions(c, p) 864 c.Assert(decl, HasLen, 1) 865 c.Check(decl[0].Type(), Equals, asserts.SnapDeclarationType) 866 c.Check(decl[0].HeaderString("snap-name"), Equals, snapName) 867 p = filepath.Join(seedAssertsDir, fmt.Sprintf("%s.snap-revision", s.AssertedSnapRevision(snapName).SnapSHA3_384())) 868 rev := seedtest.ReadAssertions(c, p) 869 c.Assert(rev, HasLen, 1) 870 c.Check(rev[0].Type(), Equals, asserts.SnapRevisionType) 871 c.Check(rev[0].HeaderString("snap-id"), Equals, s.AssertedSnapID(snapName)) 872 } 873 } 874 875 func (s *writerSuite) TestSeedSnapsWriteMetaCore18StoreAssertion(c *C) { 876 // add store assertion 877 storeAs, err := s.StoreSigning.Sign(asserts.StoreType, map[string]interface{}{ 878 "store": "my-store", 879 "operator-id": "canonical", 880 "timestamp": time.Now().UTC().Format(time.RFC3339), 881 }, nil, "") 882 c.Assert(err, IsNil) 883 err = s.StoreSigning.Add(storeAs) 884 c.Assert(err, IsNil) 885 886 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 887 "display-name": "my model", 888 "architecture": "amd64", 889 "base": "core18", 890 "gadget": "pc=18", 891 "kernel": "pc-kernel=18", 892 "store": "my-store", 893 }) 894 895 s.makeSnap(c, "snapd", "") 896 s.makeSnap(c, "core18", "") 897 s.makeSnap(c, "pc-kernel=18", "") 898 s.makeSnap(c, "pc=18", "") 899 900 complete, w, err := s.upToDownloaded(c, model, s.fillDownloadedSnap) 901 c.Assert(err, IsNil) 902 c.Check(complete, Equals, true) 903 904 err = w.SeedSnaps(nil) 905 c.Assert(err, IsNil) 906 907 err = w.WriteMeta() 908 c.Assert(err, IsNil) 909 910 // check assertions 911 seedAssertsDir := filepath.Join(s.opts.SeedDir, "assertions") 912 // check the store assertion was fetched 913 p := filepath.Join(seedAssertsDir, "my-store.store") 914 c.Check(p, testutil.FilePresent) 915 } 916 917 func (s *writerSuite) TestLocalSnaps(c *C) { 918 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 919 "display-name": "my model", 920 "architecture": "amd64", 921 "base": "core18", 922 "gadget": "pc=18", 923 "kernel": "pc-kernel=18", 924 "required-snaps": []interface{}{"cont-consumer", "cont-producer"}, 925 }) 926 927 core18Fn := s.makeLocalSnap(c, "core18") 928 pcKernelFn := s.makeLocalSnap(c, "pc-kernel=18") 929 pcFn := s.makeLocalSnap(c, "pc=18") 930 contConsumerFn := s.makeLocalSnap(c, "cont-consumer") 931 932 w, err := seedwriter.New(model, s.opts) 933 c.Assert(err, IsNil) 934 935 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{ 936 {Path: core18Fn}, 937 {Path: pcFn, Channel: "edge"}, 938 {Path: pcKernelFn}, 939 {Path: contConsumerFn}, 940 }) 941 c.Assert(err, IsNil) 942 943 _, err = w.Start(s.db, s.newFetcher) 944 c.Assert(err, IsNil) 945 946 localSnaps, err := w.LocalSnaps() 947 c.Assert(err, IsNil) 948 c.Assert(localSnaps, HasLen, 4) 949 c.Check(localSnaps[0].Path, Equals, core18Fn) 950 c.Check(localSnaps[1].Path, Equals, pcFn) 951 c.Check(localSnaps[2].Path, Equals, pcKernelFn) 952 c.Check(localSnaps[3].Path, Equals, contConsumerFn) 953 } 954 955 func (s *writerSuite) TestLocalSnapsCore18FullUse(c *C) { 956 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 957 "display-name": "my model", 958 "architecture": "amd64", 959 "base": "core18", 960 "gadget": "pc=18", 961 "kernel": "pc-kernel=18", 962 "required-snaps": []interface{}{"cont-consumer", "cont-producer"}, 963 }) 964 965 s.makeSnap(c, "snapd", "") 966 s.makeSnap(c, "cont-producer", "developerid") 967 968 core18Fn := s.makeLocalSnap(c, "core18") 969 pcKernelFn := s.makeLocalSnap(c, "pc-kernel=18") 970 pcFn := s.makeLocalSnap(c, "pc=18") 971 contConsumerFn := s.makeLocalSnap(c, "cont-consumer") 972 973 w, err := seedwriter.New(model, s.opts) 974 c.Assert(err, IsNil) 975 976 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{ 977 {Path: core18Fn}, 978 {Name: "pc-kernel", Channel: "candidate"}, 979 {Path: pcFn, Channel: "edge"}, 980 {Path: pcKernelFn}, 981 {Path: s.AssertedSnap("cont-producer")}, 982 {Path: contConsumerFn}, 983 }) 984 c.Assert(err, IsNil) 985 986 tf, err := w.Start(s.db, s.newFetcher) 987 c.Assert(err, IsNil) 988 989 localSnaps, err := w.LocalSnaps() 990 c.Assert(err, IsNil) 991 c.Assert(localSnaps, HasLen, 5) 992 993 for _, sn := range localSnaps { 994 si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db) 995 if !asserts.IsNotFound(err) { 996 c.Assert(err, IsNil) 997 } 998 f, err := snapfile.Open(sn.Path) 999 c.Assert(err, IsNil) 1000 info, err := snap.ReadInfoFromSnapFile(f, si) 1001 c.Assert(err, IsNil) 1002 w.SetInfo(sn, info) 1003 sn.ARefs = aRefs 1004 } 1005 1006 err = w.InfoDerived() 1007 c.Assert(err, IsNil) 1008 1009 snaps, err := w.SnapsToDownload() 1010 c.Assert(err, IsNil) 1011 c.Check(snaps, HasLen, 1) 1012 c.Check(naming.SameSnap(snaps[0], naming.Snap("snapd")), Equals, true) 1013 1014 for _, sn := range snaps { 1015 s.fillDownloadedSnap(c, w, sn) 1016 } 1017 1018 complete, err := w.Downloaded() 1019 c.Assert(err, IsNil) 1020 c.Check(complete, Equals, true) 1021 1022 copySnap := func(name, src, dst string) error { 1023 return osutil.CopyFile(src, dst, 0) 1024 } 1025 1026 err = w.SeedSnaps(copySnap) 1027 c.Assert(err, IsNil) 1028 1029 err = w.WriteMeta() 1030 c.Assert(err, IsNil) 1031 1032 // check seed 1033 seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml")) 1034 c.Assert(err, IsNil) 1035 1036 c.Check(seedYaml.Snaps, HasLen, 6) 1037 1038 assertedNum := 0 1039 // check the files are in place 1040 for i, name := range []string{"snapd", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer"} { 1041 info := s.AssertedSnapInfo(name) 1042 unasserted := false 1043 if info == nil { 1044 info = &snap.Info{ 1045 SuggestedName: name, 1046 } 1047 info.Revision = snap.R(-1) 1048 unasserted = true 1049 } else { 1050 assertedNum++ 1051 } 1052 1053 fn := info.Filename() 1054 p := filepath.Join(s.opts.SeedDir, "snaps", fn) 1055 c.Check(p, testutil.FilePresent) 1056 1057 channel := "" 1058 if !unasserted { 1059 channel = "stable" 1060 } 1061 1062 c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{ 1063 Name: info.SnapName(), 1064 SnapID: info.SnapID, 1065 Channel: channel, 1066 File: fn, 1067 Unasserted: unasserted, 1068 }) 1069 } 1070 c.Check(assertedNum, Equals, 2) 1071 1072 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 1073 c.Assert(err, IsNil) 1074 c.Check(l, HasLen, 6) 1075 1076 // check the snap assertions are in place 1077 seedAssertsDir := filepath.Join(s.opts.SeedDir, "assertions") 1078 for _, snapName := range []string{"snapd", "cont-producer"} { 1079 p := filepath.Join(seedAssertsDir, fmt.Sprintf("16,%s.snap-declaration", s.AssertedSnapID(snapName))) 1080 decl := seedtest.ReadAssertions(c, p) 1081 c.Assert(decl, HasLen, 1) 1082 c.Check(decl[0].Type(), Equals, asserts.SnapDeclarationType) 1083 c.Check(decl[0].HeaderString("snap-name"), Equals, snapName) 1084 p = filepath.Join(seedAssertsDir, fmt.Sprintf("%s.snap-revision", s.AssertedSnapRevision(snapName).SnapSHA3_384())) 1085 rev := seedtest.ReadAssertions(c, p) 1086 c.Assert(rev, HasLen, 1) 1087 c.Check(rev[0].Type(), Equals, asserts.SnapRevisionType) 1088 c.Check(rev[0].HeaderString("snap-id"), Equals, s.AssertedSnapID(snapName)) 1089 } 1090 1091 unassertedSnaps, err := w.UnassertedSnaps() 1092 c.Assert(err, IsNil) 1093 c.Check(unassertedSnaps, HasLen, 4) 1094 unassertedSet := naming.NewSnapSet(unassertedSnaps) 1095 for _, snapName := range []string{"core18", "pc-kernel", "pc", "cont-consumer"} { 1096 c.Check(unassertedSet.Contains(naming.Snap(snapName)), Equals, true) 1097 } 1098 } 1099 1100 func (s *writerSuite) TestSeedSnapsWriteMetaDefaultTrackCore18(c *C) { 1101 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1102 "display-name": "my model", 1103 "architecture": "amd64", 1104 "base": "core18", 1105 "gadget": "pc=18", 1106 "kernel": "pc-kernel=18", 1107 "required-snaps": []interface{}{"required18"}, 1108 }) 1109 1110 s.makeSnap(c, "snapd", "") 1111 s.makeSnap(c, "core18", "") 1112 s.makeSnap(c, "pc-kernel=18", "") 1113 s.makeSnap(c, "pc=18", "") 1114 s.makeSnap(c, "required18", "developerid") 1115 1116 w, err := seedwriter.New(model, s.opts) 1117 c.Assert(err, IsNil) 1118 1119 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "required18", Channel: "candidate"}}) 1120 c.Assert(err, IsNil) 1121 1122 _, err = w.Start(s.db, s.newFetcher) 1123 c.Assert(err, IsNil) 1124 1125 snaps, err := w.SnapsToDownload() 1126 c.Assert(err, IsNil) 1127 c.Check(snaps, HasLen, 5) 1128 1129 for _, sn := range snaps { 1130 s.fillDownloadedSnap(c, w, sn) 1131 if sn.SnapName() == "required18" { 1132 c.Check(sn.Channel, Equals, "candidate") 1133 w.SetRedirectChannel(sn, "default-track/candidate") 1134 } 1135 } 1136 1137 complete, err := w.Downloaded() 1138 c.Assert(err, IsNil) 1139 c.Check(complete, Equals, true) 1140 1141 err = w.SeedSnaps(nil) 1142 c.Assert(err, IsNil) 1143 1144 err = w.WriteMeta() 1145 c.Assert(err, IsNil) 1146 1147 // check seed 1148 seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml")) 1149 c.Assert(err, IsNil) 1150 1151 c.Check(seedYaml.Snaps, HasLen, 5) 1152 1153 info := s.AssertedSnapInfo("required18") 1154 fn := info.Filename() 1155 c.Check(filepath.Join(s.opts.SeedDir, "snaps", fn), testutil.FilePresent) 1156 c.Check(seedYaml.Snaps[4], DeepEquals, &seedwriter.InternalSnap16{ 1157 Name: info.SnapName(), 1158 SnapID: info.SnapID, 1159 Channel: "default-track/candidate", 1160 File: fn, 1161 }) 1162 1163 } 1164 1165 func (s *writerSuite) TestSetRedirectChannelErrors(c *C) { 1166 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1167 "display-name": "my model", 1168 "architecture": "amd64", 1169 "base": "core18", 1170 "gadget": "pc=18", 1171 "kernel": "pc-kernel=18", 1172 "required-snaps": []interface{}{"required18"}, 1173 }) 1174 1175 s.makeSnap(c, "snapd", "") 1176 s.makeSnap(c, "core18", "") 1177 s.makeSnap(c, "pc-kernel=18", "") 1178 s.makeSnap(c, "pc=18", "") 1179 s.makeSnap(c, "required18", "developerid") 1180 1181 w, err := seedwriter.New(model, s.opts) 1182 c.Assert(err, IsNil) 1183 1184 _, err = w.Start(s.db, s.newFetcher) 1185 c.Assert(err, IsNil) 1186 1187 snaps, err := w.SnapsToDownload() 1188 c.Assert(err, IsNil) 1189 c.Check(snaps, HasLen, 5) 1190 1191 sn := snaps[4] 1192 c.Assert(sn.SnapName(), Equals, "required18") 1193 1194 c.Check(w.SetRedirectChannel(sn, "default-track/stable"), ErrorMatches, `internal error: before using seedwriter.Writer.SetRedirectChannel snap "required18" Info should have been set`) 1195 1196 s.fillDownloadedSnap(c, w, sn) 1197 1198 c.Check(w.SetRedirectChannel(sn, "default-track//stable"), ErrorMatches, `invalid redirect channel for snap "required18":.*`) 1199 } 1200 1201 func (s *writerSuite) TestInfoDerivedInfosNotSet(c *C) { 1202 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1203 "display-name": "my model", 1204 "architecture": "amd64", 1205 "base": "core18", 1206 "gadget": "pc=18", 1207 "kernel": "pc-kernel=18", 1208 "required-snaps": []interface{}{"cont-consumer", "cont-producer"}, 1209 }) 1210 1211 core18Fn := s.makeLocalSnap(c, "core18") 1212 pcKernelFn := s.makeLocalSnap(c, "pc-kernel=18") 1213 pcFn := s.makeLocalSnap(c, "pc=18") 1214 1215 w, err := seedwriter.New(model, s.opts) 1216 c.Assert(err, IsNil) 1217 1218 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{ 1219 {Path: core18Fn}, 1220 {Path: pcFn, Channel: "edge"}, 1221 {Path: pcKernelFn}, 1222 }) 1223 c.Assert(err, IsNil) 1224 1225 _, err = w.Start(s.db, s.newFetcher) 1226 c.Assert(err, IsNil) 1227 1228 _, err = w.LocalSnaps() 1229 c.Assert(err, IsNil) 1230 1231 err = w.InfoDerived() 1232 c.Assert(err, ErrorMatches, `internal error: before seedwriter.Writer.InfoDerived snap ".*/core18.*.snap" Info should have been set`) 1233 } 1234 1235 func (s *writerSuite) TestInfoDerivedRepeatedLocalSnap(c *C) { 1236 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1237 "display-name": "my model", 1238 "architecture": "amd64", 1239 "base": "core18", 1240 "gadget": "pc=18", 1241 "kernel": "pc-kernel=18", 1242 "required-snaps": []interface{}{"cont-consumer", "cont-producer"}, 1243 }) 1244 1245 core18Fn := s.makeLocalSnap(c, "core18") 1246 pcKernelFn := s.makeLocalSnap(c, "pc-kernel=18") 1247 pcFn := s.makeLocalSnap(c, "pc=18") 1248 1249 w, err := seedwriter.New(model, s.opts) 1250 c.Assert(err, IsNil) 1251 1252 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{ 1253 {Path: core18Fn}, 1254 {Path: pcFn, Channel: "edge"}, 1255 {Path: pcKernelFn}, 1256 {Path: core18Fn}, 1257 }) 1258 c.Assert(err, IsNil) 1259 1260 _, err = w.Start(s.db, s.newFetcher) 1261 c.Assert(err, IsNil) 1262 1263 localSnaps, err := w.LocalSnaps() 1264 c.Assert(err, IsNil) 1265 c.Check(localSnaps, HasLen, 4) 1266 1267 for _, sn := range localSnaps { 1268 f, err := snapfile.Open(sn.Path) 1269 c.Assert(err, IsNil) 1270 info, err := snap.ReadInfoFromSnapFile(f, nil) 1271 c.Assert(err, IsNil) 1272 w.SetInfo(sn, info) 1273 } 1274 1275 err = w.InfoDerived() 1276 c.Assert(err, ErrorMatches, `local snap "core18" is repeated in options`) 1277 } 1278 1279 func (s *writerSuite) TestInfoDerivedInconsistentChannel(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{}{"cont-consumer", "cont-producer"}, 1287 }) 1288 1289 core18Fn := s.makeLocalSnap(c, "core18") 1290 pcKernelFn := s.makeLocalSnap(c, "pc-kernel=18") 1291 pcFn := s.makeLocalSnap(c, "pc=18") 1292 1293 w, err := seedwriter.New(model, s.opts) 1294 c.Assert(err, IsNil) 1295 1296 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{ 1297 {Path: core18Fn}, 1298 {Path: pcFn, Channel: "edge"}, 1299 {Path: pcKernelFn}, 1300 {Name: "pc", Channel: "beta"}, 1301 }) 1302 c.Assert(err, IsNil) 1303 1304 _, err = w.Start(s.db, s.newFetcher) 1305 c.Assert(err, IsNil) 1306 1307 localSnaps, err := w.LocalSnaps() 1308 c.Assert(err, IsNil) 1309 c.Check(localSnaps, HasLen, 3) 1310 1311 for _, sn := range localSnaps { 1312 f, err := snapfile.Open(sn.Path) 1313 c.Assert(err, IsNil) 1314 info, err := snap.ReadInfoFromSnapFile(f, nil) 1315 c.Assert(err, IsNil) 1316 w.SetInfo(sn, info) 1317 } 1318 1319 err = w.InfoDerived() 1320 c.Assert(err, ErrorMatches, `option snap has different channels specified: ".*/pc.*.snap"="edge" vs "pc"="beta"`) 1321 } 1322 1323 func (s *writerSuite) TestSetRedirectChannelLocalError(c *C) { 1324 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1325 "display-name": "my model", 1326 "architecture": "amd64", 1327 "base": "core18", 1328 "gadget": "pc=18", 1329 "kernel": "pc-kernel=18", 1330 }) 1331 1332 core18Fn := s.makeLocalSnap(c, "core18") 1333 1334 w, err := seedwriter.New(model, s.opts) 1335 c.Assert(err, IsNil) 1336 1337 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{ 1338 {Path: core18Fn}, 1339 }) 1340 c.Assert(err, IsNil) 1341 1342 _, err = w.Start(s.db, s.newFetcher) 1343 c.Assert(err, IsNil) 1344 1345 localSnaps, err := w.LocalSnaps() 1346 c.Assert(err, IsNil) 1347 c.Check(localSnaps, HasLen, 1) 1348 1349 sn := localSnaps[0] 1350 f, err := snapfile.Open(sn.Path) 1351 c.Assert(err, IsNil) 1352 info, err := snap.ReadInfoFromSnapFile(f, nil) 1353 c.Assert(err, IsNil) 1354 err = w.SetInfo(sn, info) 1355 c.Assert(err, IsNil) 1356 1357 c.Check(w.SetRedirectChannel(sn, "foo"), ErrorMatches, `internal error: cannot set redirect channel for local snap .*`) 1358 1359 } 1360 1361 func (s *writerSuite) TestSeedSnapsWriteMetaClassicWithCore(c *C) { 1362 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1363 "classic": "true", 1364 "architecture": "amd64", 1365 "gadget": "classic-gadget", 1366 "required-snaps": []interface{}{"required"}, 1367 }) 1368 1369 s.makeSnap(c, "core", "") 1370 s.makeSnap(c, "classic-gadget", "") 1371 s.makeSnap(c, "required", "developerid") 1372 1373 complete, w, err := s.upToDownloaded(c, model, s.fillDownloadedSnap) 1374 c.Assert(err, IsNil) 1375 c.Check(complete, Equals, false) 1376 1377 snaps, err := w.SnapsToDownload() 1378 c.Assert(err, IsNil) 1379 c.Assert(snaps, HasLen, 1) 1380 1381 s.fillDownloadedSnap(c, w, snaps[0]) 1382 1383 complete, err = w.Downloaded() 1384 c.Assert(err, IsNil) 1385 c.Check(complete, Equals, true) 1386 1387 _, err = w.BootSnaps() 1388 c.Check(err, ErrorMatches, "no snaps participating in boot on classic") 1389 1390 err = w.SeedSnaps(nil) 1391 c.Assert(err, IsNil) 1392 1393 err = w.WriteMeta() 1394 c.Assert(err, IsNil) 1395 1396 // check seed 1397 seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml")) 1398 c.Assert(err, IsNil) 1399 1400 c.Check(seedYaml.Snaps, HasLen, 3) 1401 1402 // check the files are in place 1403 for i, name := range []string{"core", "classic-gadget", "required"} { 1404 info := s.AssertedSnapInfo(name) 1405 1406 fn := info.Filename() 1407 p := filepath.Join(s.opts.SeedDir, "snaps", fn) 1408 c.Check(p, testutil.FilePresent) 1409 1410 c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{ 1411 Name: info.SnapName(), 1412 SnapID: info.SnapID, 1413 Channel: "stable", 1414 File: fn, 1415 Contact: info.Contact, 1416 }) 1417 } 1418 } 1419 1420 func (s *writerSuite) TestSeedSnapsWriteMetaClassicSnapdOnly(c *C) { 1421 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1422 "classic": "true", 1423 "architecture": "amd64", 1424 "gadget": "classic-gadget18", 1425 "required-snaps": []interface{}{"core18", "required18"}, 1426 }) 1427 1428 s.makeSnap(c, "snapd", "") 1429 s.makeSnap(c, "core18", "") 1430 s.makeSnap(c, "classic-gadget18", "") 1431 s.makeSnap(c, "required18", "developerid") 1432 1433 complete, w, err := s.upToDownloaded(c, model, s.fillDownloadedSnap) 1434 c.Assert(err, IsNil) 1435 c.Check(complete, Equals, false) 1436 1437 snaps, err := w.SnapsToDownload() 1438 c.Assert(err, IsNil) 1439 c.Assert(snaps, HasLen, 1) 1440 1441 s.fillDownloadedSnap(c, w, snaps[0]) 1442 1443 complete, err = w.Downloaded() 1444 c.Assert(err, IsNil) 1445 c.Check(complete, Equals, true) 1446 1447 err = w.SeedSnaps(nil) 1448 c.Assert(err, IsNil) 1449 1450 err = w.WriteMeta() 1451 c.Assert(err, IsNil) 1452 1453 // check seed 1454 seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml")) 1455 c.Assert(err, IsNil) 1456 c.Assert(seedYaml.Snaps, HasLen, 4) 1457 1458 // check the files are in place 1459 for i, name := range []string{"snapd", "core18", "classic-gadget18", "required18"} { 1460 info := s.AssertedSnapInfo(name) 1461 1462 fn := info.Filename() 1463 p := filepath.Join(s.opts.SeedDir, "snaps", fn) 1464 c.Check(p, testutil.FilePresent) 1465 1466 c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{ 1467 Name: info.SnapName(), 1468 SnapID: info.SnapID, 1469 Channel: "stable", 1470 File: fn, 1471 Contact: info.Contact, 1472 }) 1473 } 1474 } 1475 1476 func (s *writerSuite) TestSeedSnapsWriteMetaClassicSnapdOnlyMissingCore16(c *C) { 1477 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1478 "classic": "true", 1479 "architecture": "amd64", 1480 "gadget": "classic-gadget18", 1481 "required-snaps": []interface{}{"core18", "required-base-core16"}, 1482 }) 1483 1484 s.makeSnap(c, "snapd", "") 1485 s.makeSnap(c, "core18", "") 1486 s.makeSnap(c, "classic-gadget18", "") 1487 s.makeSnap(c, "required-base-core16", "developerid") 1488 1489 _, _, err := s.upToDownloaded(c, model, s.fillMetaDownloadedSnap) 1490 c.Check(err, ErrorMatches, `cannot use "required-base-core16" requiring base "core16" without adding "core16" \(or "core"\) explicitly`) 1491 } 1492 1493 func (s *writerSuite) TestSeedSnapsWriteMetaExtraSnaps(c *C) { 1494 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1495 "display-name": "my model", 1496 "architecture": "amd64", 1497 "base": "core18", 1498 "gadget": "pc=18", 1499 "kernel": "pc-kernel=18", 1500 "required-snaps": []interface{}{"cont-consumer", "cont-producer"}, 1501 }) 1502 1503 s.makeSnap(c, "snapd", "") 1504 s.makeSnap(c, "core18", "") 1505 s.makeSnap(c, "pc-kernel=18", "") 1506 s.makeSnap(c, "pc=18", "") 1507 s.makeSnap(c, "cont-producer", "developerid") 1508 s.makeSnap(c, "cont-consumer", "developerid") 1509 s.makeSnap(c, "core", "") 1510 s.makeSnap(c, "required", "developerid") 1511 1512 w, err := seedwriter.New(model, s.opts) 1513 c.Assert(err, IsNil) 1514 1515 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "required", Channel: "beta"}}) 1516 c.Assert(err, IsNil) 1517 1518 _, err = w.Start(s.db, s.newFetcher) 1519 c.Assert(err, IsNil) 1520 1521 snaps, err := w.SnapsToDownload() 1522 c.Assert(err, IsNil) 1523 c.Assert(snaps, HasLen, 6) 1524 1525 s.AssertedSnapInfo("cont-producer").Contact = "author@cont-producer.net" 1526 for _, sn := range snaps { 1527 s.fillDownloadedSnap(c, w, sn) 1528 } 1529 1530 complete, err := w.Downloaded() 1531 c.Assert(err, IsNil) 1532 c.Assert(complete, Equals, false) 1533 1534 snaps, err = w.SnapsToDownload() 1535 c.Assert(err, IsNil) 1536 c.Assert(snaps, HasLen, 1) 1537 c.Check(naming.SameSnap(snaps[0], naming.Snap("required")), Equals, true) 1538 1539 s.fillDownloadedSnap(c, w, snaps[0]) 1540 1541 complete, err = w.Downloaded() 1542 c.Assert(err, IsNil) 1543 c.Assert(complete, Equals, false) 1544 1545 snaps, err = w.SnapsToDownload() 1546 c.Assert(err, IsNil) 1547 c.Assert(snaps, HasLen, 1) 1548 c.Check(naming.SameSnap(snaps[0], naming.Snap("core")), Equals, true) 1549 1550 s.fillDownloadedSnap(c, w, snaps[0]) 1551 1552 complete, err = w.Downloaded() 1553 c.Assert(err, IsNil) 1554 c.Assert(complete, Equals, true) 1555 1556 err = w.SeedSnaps(nil) 1557 c.Assert(err, IsNil) 1558 1559 err = w.WriteMeta() 1560 c.Assert(err, IsNil) 1561 1562 // check seed 1563 seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml")) 1564 c.Assert(err, IsNil) 1565 c.Assert(seedYaml.Snaps, HasLen, 8) 1566 1567 // check the files are in place 1568 for i, name := range []string{"snapd", "core", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer", "required"} { 1569 info := s.AssertedSnapInfo(name) 1570 1571 fn := info.Filename() 1572 p := filepath.Join(s.opts.SeedDir, "snaps", fn) 1573 c.Check(osutil.FileExists(p), Equals, true) 1574 1575 channel := "stable" 1576 switch name { 1577 case "pc-kernel", "pc": 1578 channel = "18" 1579 case "required": 1580 channel = "beta" 1581 } 1582 1583 c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{ 1584 Name: info.SnapName(), 1585 SnapID: info.SnapID, 1586 Channel: channel, 1587 File: fn, 1588 Contact: info.Contact, 1589 }) 1590 } 1591 1592 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 1593 c.Assert(err, IsNil) 1594 c.Check(l, HasLen, 8) 1595 1596 // check the snap assertions are also in place 1597 seedAssertsDir := filepath.Join(s.opts.SeedDir, "assertions") 1598 for _, snapName := range []string{"snapd", "core", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer", "required"} { 1599 p := filepath.Join(seedAssertsDir, fmt.Sprintf("16,%s.snap-declaration", s.AssertedSnapID(snapName))) 1600 decl := seedtest.ReadAssertions(c, p) 1601 c.Assert(decl, HasLen, 1) 1602 c.Check(decl[0].Type(), Equals, asserts.SnapDeclarationType) 1603 c.Check(decl[0].HeaderString("snap-name"), Equals, snapName) 1604 p = filepath.Join(seedAssertsDir, fmt.Sprintf("%s.snap-revision", s.AssertedSnapRevision(snapName).SnapSHA3_384())) 1605 rev := seedtest.ReadAssertions(c, p) 1606 c.Assert(rev, HasLen, 1) 1607 c.Check(rev[0].Type(), Equals, asserts.SnapRevisionType) 1608 c.Check(rev[0].HeaderString("snap-id"), Equals, s.AssertedSnapID(snapName)) 1609 } 1610 1611 c.Check(w.Warnings(), DeepEquals, []string{ 1612 `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`, 1613 }) 1614 } 1615 1616 func (s *writerSuite) TestSeedSnapsWriteMetaLocalExtraSnaps(c *C) { 1617 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1618 "display-name": "my model", 1619 "architecture": "amd64", 1620 "base": "core18", 1621 "gadget": "pc=18", 1622 "kernel": "pc-kernel=18", 1623 "required-snaps": []interface{}{"cont-consumer", "cont-producer"}, 1624 }) 1625 1626 s.makeSnap(c, "snapd", "") 1627 s.makeSnap(c, "core18", "") 1628 s.makeSnap(c, "pc-kernel=18", "") 1629 s.makeSnap(c, "pc=18", "") 1630 s.makeSnap(c, "cont-producer", "developerid") 1631 s.makeSnap(c, "cont-consumer", "developerid") 1632 s.makeSnap(c, "core", "") 1633 requiredFn := s.makeLocalSnap(c, "required") 1634 1635 w, err := seedwriter.New(model, s.opts) 1636 c.Assert(err, IsNil) 1637 1638 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: requiredFn}}) 1639 c.Assert(err, IsNil) 1640 1641 tf, err := w.Start(s.db, s.newFetcher) 1642 c.Assert(err, IsNil) 1643 1644 localSnaps, err := w.LocalSnaps() 1645 c.Assert(err, IsNil) 1646 c.Assert(localSnaps, HasLen, 1) 1647 1648 for _, sn := range localSnaps { 1649 si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db) 1650 if !asserts.IsNotFound(err) { 1651 c.Assert(err, IsNil) 1652 } 1653 f, err := snapfile.Open(sn.Path) 1654 c.Assert(err, IsNil) 1655 info, err := snap.ReadInfoFromSnapFile(f, si) 1656 c.Assert(err, IsNil) 1657 w.SetInfo(sn, info) 1658 sn.ARefs = aRefs 1659 } 1660 1661 err = w.InfoDerived() 1662 c.Assert(err, IsNil) 1663 1664 snaps, err := w.SnapsToDownload() 1665 c.Assert(err, IsNil) 1666 c.Assert(snaps, HasLen, 6) 1667 1668 s.AssertedSnapInfo("cont-producer").Contact = "author@cont-producer.net" 1669 for _, sn := range snaps { 1670 s.fillDownloadedSnap(c, w, sn) 1671 } 1672 1673 complete, err := w.Downloaded() 1674 c.Assert(err, IsNil) 1675 c.Assert(complete, Equals, false) 1676 1677 snaps, err = w.SnapsToDownload() 1678 c.Assert(err, IsNil) 1679 c.Assert(snaps, HasLen, 0) 1680 1681 complete, err = w.Downloaded() 1682 c.Assert(err, IsNil) 1683 c.Assert(complete, Equals, false) 1684 1685 snaps, err = w.SnapsToDownload() 1686 c.Assert(err, IsNil) 1687 c.Assert(snaps, HasLen, 1) 1688 c.Check(naming.SameSnap(snaps[0], naming.Snap("core")), Equals, true) 1689 1690 s.fillDownloadedSnap(c, w, snaps[0]) 1691 1692 complete, err = w.Downloaded() 1693 c.Assert(err, IsNil) 1694 c.Assert(complete, Equals, true) 1695 1696 copySnap := func(name, src, dst string) error { 1697 return osutil.CopyFile(src, dst, 0) 1698 } 1699 1700 err = w.SeedSnaps(copySnap) 1701 c.Assert(err, IsNil) 1702 1703 err = w.WriteMeta() 1704 c.Assert(err, IsNil) 1705 1706 // check seed 1707 seedYaml, err := seedwriter.InternalReadSeedYaml(filepath.Join(s.opts.SeedDir, "seed.yaml")) 1708 c.Assert(err, IsNil) 1709 c.Assert(seedYaml.Snaps, HasLen, 8) 1710 1711 // check the files are in place 1712 for i, name := range []string{"snapd", "core", "pc-kernel", "core18", "pc", "cont-consumer", "cont-producer", "required"} { 1713 info := s.AssertedSnapInfo(name) 1714 unasserted := false 1715 if info == nil { 1716 info = &snap.Info{ 1717 SuggestedName: name, 1718 } 1719 info.Revision = snap.R(-1) 1720 unasserted = true 1721 } 1722 1723 fn := info.Filename() 1724 p := filepath.Join(s.opts.SeedDir, "snaps", fn) 1725 c.Check(osutil.FileExists(p), Equals, true) 1726 1727 channel := "" 1728 if !unasserted { 1729 switch name { 1730 case "pc-kernel", "pc": 1731 channel = "18" 1732 default: 1733 channel = "stable" 1734 } 1735 } 1736 1737 c.Check(seedYaml.Snaps[i], DeepEquals, &seedwriter.InternalSnap16{ 1738 Name: info.SnapName(), 1739 SnapID: info.SnapID, 1740 Channel: channel, 1741 File: fn, 1742 Contact: info.Contact, 1743 Unasserted: unasserted, 1744 }) 1745 } 1746 1747 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 1748 c.Assert(err, IsNil) 1749 c.Check(l, HasLen, 8) 1750 1751 unassertedSnaps, err := w.UnassertedSnaps() 1752 c.Assert(err, IsNil) 1753 c.Check(unassertedSnaps, HasLen, 1) 1754 c.Check(naming.SameSnap(unassertedSnaps[0], naming.Snap("required")), Equals, true) 1755 } 1756 1757 func (s *writerSuite) TestSeedSnapsWriteMetaCore20(c *C) { 1758 // add store assertion 1759 storeAs, err := s.StoreSigning.Sign(asserts.StoreType, map[string]interface{}{ 1760 "store": "my-store", 1761 "operator-id": "canonical", 1762 "timestamp": time.Now().UTC().Format(time.RFC3339), 1763 }, nil, "") 1764 c.Assert(err, IsNil) 1765 err = s.StoreSigning.Add(storeAs) 1766 c.Assert(err, IsNil) 1767 1768 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1769 "display-name": "my model", 1770 "architecture": "amd64", 1771 "store": "my-store", 1772 "base": "core20", 1773 "snaps": []interface{}{ 1774 map[string]interface{}{ 1775 "name": "pc-kernel", 1776 "id": s.AssertedSnapID("pc-kernel"), 1777 "type": "kernel", 1778 "default-channel": "20", 1779 }, 1780 map[string]interface{}{ 1781 "name": "pc", 1782 "id": s.AssertedSnapID("pc"), 1783 "type": "gadget", 1784 "default-channel": "20", 1785 }, 1786 map[string]interface{}{ 1787 "name": "core18", 1788 "id": s.AssertedSnapID("core18"), 1789 "type": "base", 1790 }, 1791 map[string]interface{}{ 1792 "name": "cont-consumer", 1793 "id": s.AssertedSnapID("cont-consumer"), 1794 }, 1795 map[string]interface{}{ 1796 "name": "cont-producer", 1797 "id": s.AssertedSnapID("cont-producer"), 1798 }, 1799 }, 1800 }) 1801 1802 // sanity 1803 c.Assert(model.Grade(), Equals, asserts.ModelSigned) 1804 1805 s.makeSnap(c, "snapd", "") 1806 s.makeSnap(c, "core20", "") 1807 s.makeSnap(c, "core18", "") 1808 s.makeSnap(c, "pc-kernel=20", "") 1809 s.makeSnap(c, "pc=20", "") 1810 s.makeSnap(c, "cont-producer", "developerid") 1811 s.makeSnap(c, "cont-consumer", "developerid") 1812 1813 s.opts.Label = "20191003" 1814 w, err := seedwriter.New(model, s.opts) 1815 c.Assert(err, IsNil) 1816 1817 _, err = w.Start(s.db, s.newFetcher) 1818 c.Assert(err, IsNil) 1819 1820 snaps, err := w.SnapsToDownload() 1821 c.Assert(err, IsNil) 1822 c.Check(snaps, HasLen, 7) 1823 1824 s.AssertedSnapInfo("cont-producer").Contact = "author@cont-producer.net" 1825 s.AssertedSnapInfo("cont-consumer").Private = true 1826 for _, sn := range snaps { 1827 // check the used channel at this level because in the 1828 // non-dangerous case is not written anywhere (it 1829 // reflects the model or default) 1830 channel := "latest/stable" 1831 switch sn.SnapName() { 1832 case "pc", "pc-kernel": 1833 channel = "20" 1834 } 1835 c.Check(sn.Channel, Equals, channel) 1836 s.fillDownloadedSnap(c, w, sn) 1837 } 1838 1839 complete, err := w.Downloaded() 1840 c.Assert(err, IsNil) 1841 c.Check(complete, Equals, true) 1842 1843 err = w.SeedSnaps(nil) 1844 c.Assert(err, IsNil) 1845 1846 err = w.WriteMeta() 1847 c.Assert(err, IsNil) 1848 1849 // check seed 1850 systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label) 1851 c.Check(systemDir, testutil.FilePresent) 1852 1853 // check the snaps are in place 1854 for _, name := range []string{"snapd", "pc-kernel", "core20", "pc", "core18", "cont-consumer", "cont-producer"} { 1855 info := s.AssertedSnapInfo(name) 1856 1857 fn := info.Filename() 1858 p := filepath.Join(s.opts.SeedDir, "snaps", fn) 1859 c.Check(p, testutil.FilePresent) 1860 } 1861 1862 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 1863 c.Assert(err, IsNil) 1864 c.Check(l, HasLen, 7) 1865 1866 // check assertions 1867 c.Check(filepath.Join(systemDir, "model"), testutil.FileEquals, asserts.Encode(model)) 1868 1869 assertsDir := filepath.Join(systemDir, "assertions") 1870 modelEtc := seedtest.ReadAssertions(c, filepath.Join(assertsDir, "model-etc")) 1871 c.Check(modelEtc, HasLen, 4) 1872 1873 keyPKs := make(map[string]bool) 1874 for _, a := range modelEtc { 1875 switch a.Type() { 1876 case asserts.AccountType: 1877 c.Check(a.HeaderString("account-id"), Equals, "my-brand") 1878 case asserts.StoreType: 1879 c.Check(a.HeaderString("store"), Equals, "my-store") 1880 case asserts.AccountKeyType: 1881 keyPKs[a.HeaderString("public-key-sha3-384")] = true 1882 default: 1883 c.Fatalf("unexpected assertion %s", a.Type().Name) 1884 } 1885 } 1886 c.Check(keyPKs, DeepEquals, map[string]bool{ 1887 s.StoreSigning.StoreAccountKey("").PublicKeyID(): true, 1888 s.Brands.AccountKey("my-brand").PublicKeyID(): true, 1889 }) 1890 1891 // check snap assertions 1892 snapAsserts := seedtest.ReadAssertions(c, filepath.Join(assertsDir, "snaps")) 1893 seen := make(map[string]bool) 1894 1895 for _, a := range snapAsserts { 1896 uniq := a.Ref().Unique() 1897 if a.Type() == asserts.SnapRevisionType { 1898 rev := a.(*asserts.SnapRevision) 1899 uniq = fmt.Sprintf("%s@%d", rev.SnapID(), rev.SnapRevision()) 1900 } 1901 seen[uniq] = true 1902 } 1903 1904 snapRevUniq := func(snapName string, revno int) string { 1905 return fmt.Sprintf("%s@%d", s.AssertedSnapID(snapName), revno) 1906 } 1907 snapDeclUniq := func(snapName string) string { 1908 return "snap-declaration/16/" + s.AssertedSnapID(snapName) 1909 } 1910 1911 c.Check(seen, DeepEquals, map[string]bool{ 1912 "account/developerid": true, 1913 snapDeclUniq("snapd"): true, 1914 snapDeclUniq("pc-kernel"): true, 1915 snapDeclUniq("pc"): true, 1916 snapDeclUniq("core20"): true, 1917 snapDeclUniq("core18"): true, 1918 snapDeclUniq("cont-consumer"): true, 1919 snapDeclUniq("cont-producer"): true, 1920 snapRevUniq("snapd", 1): true, 1921 snapRevUniq("pc-kernel", 1): true, 1922 snapRevUniq("pc", 1): true, 1923 snapRevUniq("core20", 1): true, 1924 snapRevUniq("core18", 1): true, 1925 snapRevUniq("cont-consumer", 1): true, 1926 snapRevUniq("cont-producer", 1): true, 1927 }) 1928 1929 c.Check(filepath.Join(systemDir, "extra-snaps"), testutil.FileAbsent) 1930 1931 // check auxiliary store info 1932 l, err = ioutil.ReadDir(filepath.Join(systemDir, "snaps")) 1933 c.Assert(err, IsNil) 1934 c.Check(l, HasLen, 1) 1935 1936 b, err := ioutil.ReadFile(filepath.Join(systemDir, "snaps", "aux-info.json")) 1937 c.Assert(err, IsNil) 1938 var auxInfos map[string]map[string]interface{} 1939 err = json.Unmarshal(b, &auxInfos) 1940 c.Assert(err, IsNil) 1941 c.Check(auxInfos, DeepEquals, map[string]map[string]interface{}{ 1942 s.AssertedSnapID("cont-consumer"): { 1943 "private": true, 1944 }, 1945 s.AssertedSnapID("cont-producer"): { 1946 "contact": "author@cont-producer.net", 1947 }, 1948 }) 1949 1950 c.Check(filepath.Join(systemDir, "options.yaml"), testutil.FileAbsent) 1951 } 1952 1953 func (s *writerSuite) TestCore20InvalidLabel(c *C) { 1954 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1955 "display-name": "my model", 1956 "architecture": "amd64", 1957 "store": "my-store", 1958 "base": "core20", 1959 "snaps": []interface{}{ 1960 map[string]interface{}{ 1961 "name": "pc-kernel", 1962 "id": s.AssertedSnapID("pc-kernel"), 1963 "type": "kernel", 1964 "default-channel": "20", 1965 }, 1966 map[string]interface{}{ 1967 "name": "pc", 1968 "id": s.AssertedSnapID("pc"), 1969 "type": "gadget", 1970 "default-channel": "20", 1971 }, 1972 }, 1973 }) 1974 1975 invalid := []string{ 1976 "-", 1977 "a.b", 1978 "aa--b", 1979 } 1980 1981 for _, inv := range invalid { 1982 s.opts.Label = inv 1983 w, err := seedwriter.New(model, s.opts) 1984 c.Assert(w, IsNil) 1985 c.Check(err, ErrorMatches, fmt.Sprintf(`invalid seed system label: %q`, inv)) 1986 } 1987 } 1988 1989 func (s *writerSuite) TestDownloadedCore20CheckBase(c *C) { 1990 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 1991 "display-name": "my model", 1992 "architecture": "amd64", 1993 "store": "my-store", 1994 "base": "core20", 1995 "snaps": []interface{}{ 1996 map[string]interface{}{ 1997 "name": "pc-kernel", 1998 "id": s.AssertedSnapID("pc-kernel"), 1999 "type": "kernel", 2000 "default-channel": "20", 2001 }, 2002 map[string]interface{}{ 2003 "name": "pc", 2004 "id": s.AssertedSnapID("pc"), 2005 "type": "gadget", 2006 "default-channel": "20", 2007 }, 2008 map[string]interface{}{ 2009 "name": "cont-producer", 2010 "id": s.AssertedSnapID("cont-producer"), 2011 }, 2012 }, 2013 }) 2014 2015 // sanity 2016 c.Assert(model.Grade(), Equals, asserts.ModelSigned) 2017 2018 s.makeSnap(c, "snapd", "") 2019 s.makeSnap(c, "core20", "") 2020 s.makeSnap(c, "core18", "") 2021 s.makeSnap(c, "pc-kernel=20", "") 2022 s.makeSnap(c, "pc=20", "") 2023 s.makeSnap(c, "cont-producer", "developerid") 2024 2025 s.opts.Label = "20191003" 2026 _, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap) 2027 c.Check(err, ErrorMatches, `cannot add snap "cont-producer" without also adding its base "core18" explicitly`) 2028 } 2029 2030 func (s *writerSuite) TestDownloadedCore20CheckBaseModes(c *C) { 2031 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2032 "display-name": "my model", 2033 "architecture": "amd64", 2034 "store": "my-store", 2035 "base": "core20", 2036 "snaps": []interface{}{ 2037 map[string]interface{}{ 2038 "name": "pc-kernel", 2039 "id": s.AssertedSnapID("pc-kernel"), 2040 "type": "kernel", 2041 "default-channel": "20", 2042 }, 2043 map[string]interface{}{ 2044 "name": "pc", 2045 "id": s.AssertedSnapID("pc"), 2046 "type": "gadget", 2047 "default-channel": "20", 2048 }, 2049 map[string]interface{}{ 2050 "name": "core18", 2051 "id": s.AssertedSnapID("core18"), 2052 "type": "base", 2053 }, 2054 map[string]interface{}{ 2055 "name": "cont-producer", 2056 "id": s.AssertedSnapID("cont-producer"), 2057 "modes": []interface{}{"run", "ephemeral"}, 2058 }, 2059 }, 2060 }) 2061 2062 // sanity 2063 c.Assert(model.Grade(), Equals, asserts.ModelSigned) 2064 2065 s.makeSnap(c, "snapd", "") 2066 s.makeSnap(c, "core20", "") 2067 s.makeSnap(c, "core18", "") 2068 s.makeSnap(c, "pc-kernel=20", "") 2069 s.makeSnap(c, "pc=20", "") 2070 s.makeSnap(c, "cont-producer", "developerid") 2071 2072 s.opts.Label = "20191003" 2073 _, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap) 2074 c.Check(err, ErrorMatches, `cannot add snap "cont-producer" without also adding its base "core18" explicitly for all relevant modes \(run, ephemeral\)`) 2075 } 2076 2077 func (s *writerSuite) TestDownloadedCore20CheckBaseEphemeralOK(c *C) { 2078 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2079 "display-name": "my model", 2080 "architecture": "amd64", 2081 "store": "my-store", 2082 "base": "core20", 2083 "snaps": []interface{}{ 2084 map[string]interface{}{ 2085 "name": "pc-kernel", 2086 "id": s.AssertedSnapID("pc-kernel"), 2087 "type": "kernel", 2088 "default-channel": "20", 2089 }, 2090 map[string]interface{}{ 2091 "name": "pc", 2092 "id": s.AssertedSnapID("pc"), 2093 "type": "gadget", 2094 "default-channel": "20", 2095 }, 2096 map[string]interface{}{ 2097 "name": "core18", 2098 "id": s.AssertedSnapID("core18"), 2099 "type": "base", 2100 "modes": []interface{}{"ephemeral"}, 2101 }, 2102 map[string]interface{}{ 2103 "name": "cont-producer", 2104 "id": s.AssertedSnapID("cont-producer"), 2105 "modes": []interface{}{"recover"}, 2106 }, 2107 }, 2108 }) 2109 2110 // sanity 2111 c.Assert(model.Grade(), Equals, asserts.ModelSigned) 2112 2113 s.makeSnap(c, "snapd", "") 2114 s.makeSnap(c, "core20", "") 2115 s.makeSnap(c, "core18", "") 2116 s.makeSnap(c, "pc-kernel=20", "") 2117 s.makeSnap(c, "pc=20", "") 2118 s.makeSnap(c, "cont-producer", "developerid") 2119 2120 s.opts.Label = "20191003" 2121 _, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap) 2122 c.Check(err, IsNil) 2123 } 2124 2125 func (s *writerSuite) TestDownloadedCore20CheckBaseCoreXX(c *C) { 2126 s.makeSnap(c, "snapd", "") 2127 s.makeSnap(c, "core20", "") 2128 s.makeSnap(c, "pc-kernel=20", "") 2129 s.makeSnap(c, "pc=20", "") 2130 s.makeSnap(c, "core", "") 2131 s.makeSnap(c, "required", "") 2132 s.makeSnap(c, "required-base-core16", "") 2133 2134 coreEnt := map[string]interface{}{ 2135 "name": "core", 2136 "id": s.AssertedSnapID("core"), 2137 "type": "core", 2138 } 2139 requiredEnt := map[string]interface{}{ 2140 "name": "required", 2141 "id": s.AssertedSnapID("required"), 2142 } 2143 2144 requiredBaseCore16Ent := map[string]interface{}{ 2145 "name": "required-base-core16", 2146 "id": s.AssertedSnapID("required-base-core16"), 2147 } 2148 2149 tests := []struct { 2150 snaps []interface{} 2151 err string 2152 }{ 2153 {[]interface{}{coreEnt, requiredEnt}, ""}, 2154 {[]interface{}{coreEnt, requiredBaseCore16Ent}, ""}, 2155 {[]interface{}{requiredEnt}, `cannot add snap "required" without also adding its base "core" explicitly`}, 2156 {[]interface{}{requiredBaseCore16Ent}, `cannot add snap "required-base-core16" without also adding its base "core16" \(or "core"\) explicitly`}, 2157 } 2158 2159 s.opts.Label = "20191003" 2160 for _, t := range tests { 2161 snaps := []interface{}{ 2162 map[string]interface{}{ 2163 "name": "pc-kernel", 2164 "id": s.AssertedSnapID("pc-kernel"), 2165 "type": "kernel", 2166 "default-channel": "20", 2167 }, 2168 map[string]interface{}{ 2169 "name": "pc", 2170 "id": s.AssertedSnapID("pc"), 2171 "type": "gadget", 2172 "default-channel": "20", 2173 }, 2174 } 2175 2176 snaps = append(snaps, t.snaps...) 2177 2178 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2179 "display-name": "my model", 2180 "architecture": "amd64", 2181 "store": "my-store", 2182 "base": "core20", 2183 "snaps": snaps, 2184 }) 2185 2186 _, _, err := s.upToDownloaded(c, model, s.fillMetaDownloadedSnap) 2187 if t.err == "" { 2188 c.Check(err, IsNil) 2189 } else { 2190 c.Check(err, ErrorMatches, t.err) 2191 } 2192 } 2193 } 2194 func (s *writerSuite) TestDownloadedCore20MissingDefaultProviderModes(c *C) { 2195 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2196 "display-name": "my model", 2197 "architecture": "amd64", 2198 "store": "my-store", 2199 "base": "core20", 2200 "snaps": []interface{}{ 2201 map[string]interface{}{ 2202 "name": "pc-kernel", 2203 "id": s.AssertedSnapID("pc-kernel"), 2204 "type": "kernel", 2205 "default-channel": "20", 2206 }, 2207 map[string]interface{}{ 2208 "name": "pc", 2209 "id": s.AssertedSnapID("pc"), 2210 "type": "gadget", 2211 "default-channel": "20", 2212 }, 2213 map[string]interface{}{ 2214 "name": "core18", 2215 "id": s.AssertedSnapID("core18"), 2216 "type": "base", 2217 "modes": []interface{}{"run", "ephemeral"}, 2218 }, 2219 map[string]interface{}{ 2220 "name": "cont-producer", 2221 "id": s.AssertedSnapID("cont-producer"), 2222 }, 2223 map[string]interface{}{ 2224 "name": "cont-consumer", 2225 "id": s.AssertedSnapID("cont-consumer"), 2226 "modes": []interface{}{"recover"}, 2227 }, 2228 }, 2229 }) 2230 2231 // sanity 2232 c.Assert(model.Grade(), Equals, asserts.ModelSigned) 2233 2234 s.makeSnap(c, "snapd", "") 2235 s.makeSnap(c, "core20", "") 2236 s.makeSnap(c, "core18", "") 2237 s.makeSnap(c, "pc-kernel=20", "") 2238 s.makeSnap(c, "pc=20", "") 2239 s.makeSnap(c, "cont-producer", "developerid") 2240 s.makeSnap(c, "cont-consumer", "developerid") 2241 2242 s.opts.Label = "20191003" 2243 _, _, err := s.upToDownloaded(c, model, s.fillDownloadedSnap) 2244 c.Check(err, ErrorMatches, `cannot use snap "cont-consumer" without its default content provider "cont-producer" being added explicitly for all relevant modes \(recover\)`) 2245 } 2246 2247 func (s *writerSuite) TestCore20NonDangerousDisallowedDevmodeSnaps(c *C) { 2248 2249 s.makeSnap(c, "my-devmode", "canonical") 2250 2251 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2252 "display-name": "my model", 2253 "architecture": "amd64", 2254 "store": "my-store", 2255 "base": "core20", 2256 "snaps": []interface{}{ 2257 map[string]interface{}{ 2258 "name": "pc-kernel", 2259 "id": s.AssertedSnapID("pc-kernel"), 2260 "type": "kernel", 2261 "default-channel": "20", 2262 }, 2263 map[string]interface{}{ 2264 "name": "pc", 2265 "id": s.AssertedSnapID("pc"), 2266 "type": "gadget", 2267 "default-channel": "20", 2268 }, 2269 map[string]interface{}{ 2270 "name": "my-devmode", 2271 "id": s.AssertedSnapID("my-devmode"), 2272 "type": "app", 2273 }, 2274 }, 2275 }) 2276 2277 s.opts.Label = "20191107" 2278 2279 const expectedErr = `cannot override channels, add local snaps or extra snaps with a model of grade higher than dangerous` 2280 2281 w, err := seedwriter.New(model, s.opts) 2282 c.Assert(err, IsNil) 2283 2284 _, err = w.Start(s.db, s.newFetcher) 2285 c.Assert(err, IsNil) 2286 2287 localSnaps, err := w.LocalSnaps() 2288 c.Assert(err, IsNil) 2289 c.Assert(localSnaps, HasLen, 0) 2290 2291 snaps, err := w.SnapsToDownload() 2292 c.Check(err, IsNil) 2293 c.Assert(snaps, HasLen, 5) 2294 2295 c.Assert(snaps[4].SnapName(), Equals, "my-devmode") 2296 sn := snaps[4] 2297 2298 info := s.AssertedSnapInfo(sn.SnapName()) 2299 c.Assert(info, NotNil, Commentf("%s not defined", sn.SnapName())) 2300 err = w.SetInfo(sn, info) 2301 c.Assert(err, ErrorMatches, "cannot override channels, add devmode snaps, local snaps, or extra snaps with a model of grade higher than dangerous") 2302 c.Check(sn.Info, Not(Equals), info) 2303 } 2304 2305 func (s *writerSuite) TestCore20NonDangerousDisallowedOptionsSnaps(c *C) { 2306 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2307 "display-name": "my model", 2308 "architecture": "amd64", 2309 "store": "my-store", 2310 "base": "core20", 2311 "snaps": []interface{}{ 2312 map[string]interface{}{ 2313 "name": "pc-kernel", 2314 "id": s.AssertedSnapID("pc-kernel"), 2315 "type": "kernel", 2316 "default-channel": "20", 2317 }, 2318 map[string]interface{}{ 2319 "name": "pc", 2320 "id": s.AssertedSnapID("pc"), 2321 "type": "gadget", 2322 "default-channel": "20", 2323 }, 2324 }, 2325 }) 2326 2327 pcFn := s.makeLocalSnap(c, "pc") 2328 2329 s.opts.Label = "20191107" 2330 2331 tests := []struct { 2332 optSnap *seedwriter.OptionsSnap 2333 }{ 2334 {&seedwriter.OptionsSnap{Name: "extra"}}, 2335 {&seedwriter.OptionsSnap{Path: pcFn}}, 2336 {&seedwriter.OptionsSnap{Name: "pc", Channel: "edge"}}, 2337 } 2338 2339 const expectedErr = `cannot override channels, add devmode snaps, local snaps, or extra snaps with a model of grade higher than dangerous` 2340 2341 for _, t := range tests { 2342 w, err := seedwriter.New(model, s.opts) 2343 c.Assert(err, IsNil) 2344 2345 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{t.optSnap}) 2346 if err != nil { 2347 c.Check(err, ErrorMatches, expectedErr) 2348 continue 2349 } 2350 2351 tf, err := w.Start(s.db, s.newFetcher) 2352 c.Assert(err, IsNil) 2353 2354 if t.optSnap.Path != "" { 2355 localSnaps, err := w.LocalSnaps() 2356 c.Assert(err, IsNil) 2357 c.Assert(localSnaps, HasLen, 1) 2358 2359 for _, sn := range localSnaps { 2360 si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db) 2361 if !asserts.IsNotFound(err) { 2362 c.Assert(err, IsNil) 2363 } 2364 f, err := snapfile.Open(sn.Path) 2365 c.Assert(err, IsNil) 2366 info, err := snap.ReadInfoFromSnapFile(f, si) 2367 c.Assert(err, IsNil) 2368 w.SetInfo(sn, info) 2369 sn.ARefs = aRefs 2370 } 2371 2372 err = w.InfoDerived() 2373 c.Check(err, ErrorMatches, expectedErr) 2374 continue 2375 } 2376 2377 _, err = w.SnapsToDownload() 2378 c.Check(err, ErrorMatches, expectedErr) 2379 } 2380 } 2381 2382 func (s *writerSuite) TestCore20NonDangerousNoChannelOverride(c *C) { 2383 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2384 "display-name": "my model", 2385 "architecture": "amd64", 2386 "store": "my-store", 2387 "base": "core20", 2388 "snaps": []interface{}{ 2389 map[string]interface{}{ 2390 "name": "pc-kernel", 2391 "id": s.AssertedSnapID("pc-kernel"), 2392 "type": "kernel", 2393 "default-channel": "20", 2394 }, 2395 map[string]interface{}{ 2396 "name": "pc", 2397 "id": s.AssertedSnapID("pc"), 2398 "type": "gadget", 2399 "default-channel": "20", 2400 }, 2401 }, 2402 }) 2403 2404 s.opts.DefaultChannel = "stable" 2405 s.opts.Label = "20191107" 2406 w, err := seedwriter.New(model, s.opts) 2407 c.Assert(w, IsNil) 2408 c.Check(err, ErrorMatches, `cannot override channels, add devmode snaps, local snaps, or extra snaps with a model of grade higher than dangerous`) 2409 } 2410 2411 func (s *writerSuite) TestSeedSnapsWriteMetaCore20LocalSnaps(c *C) { 2412 // add store assertion 2413 storeAs, err := s.StoreSigning.Sign(asserts.StoreType, map[string]interface{}{ 2414 "store": "my-store", 2415 "operator-id": "canonical", 2416 "timestamp": time.Now().UTC().Format(time.RFC3339), 2417 }, nil, "") 2418 c.Assert(err, IsNil) 2419 err = s.StoreSigning.Add(storeAs) 2420 c.Assert(err, IsNil) 2421 2422 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2423 "display-name": "my model", 2424 "architecture": "amd64", 2425 "store": "my-store", 2426 "base": "core20", 2427 "grade": "dangerous", 2428 "snaps": []interface{}{ 2429 map[string]interface{}{ 2430 "name": "pc-kernel", 2431 "id": s.AssertedSnapID("pc-kernel"), 2432 "type": "kernel", 2433 "default-channel": "20", 2434 }, 2435 map[string]interface{}{ 2436 "name": "pc", 2437 "id": s.AssertedSnapID("pc"), 2438 "type": "gadget", 2439 "default-channel": "20", 2440 }, 2441 map[string]interface{}{ 2442 "name": "required20", 2443 "id": s.AssertedSnapID("required20"), 2444 }, 2445 }, 2446 }) 2447 2448 // sanity 2449 c.Assert(model.Grade(), Equals, asserts.ModelDangerous) 2450 2451 s.makeSnap(c, "snapd", "") 2452 s.makeSnap(c, "core20", "") 2453 s.makeSnap(c, "pc-kernel=20", "") 2454 s.makeSnap(c, "pc=20", "") 2455 requiredFn := s.makeLocalSnap(c, "required20") 2456 2457 s.opts.Label = "20191030" 2458 w, err := seedwriter.New(model, s.opts) 2459 c.Assert(err, IsNil) 2460 2461 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: requiredFn}}) 2462 c.Assert(err, IsNil) 2463 2464 tf, err := w.Start(s.db, s.newFetcher) 2465 c.Assert(err, IsNil) 2466 2467 localSnaps, err := w.LocalSnaps() 2468 c.Assert(err, IsNil) 2469 c.Assert(localSnaps, HasLen, 1) 2470 2471 for _, sn := range localSnaps { 2472 _, _, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db) 2473 c.Assert(asserts.IsNotFound(err), Equals, true) 2474 f, err := snapfile.Open(sn.Path) 2475 c.Assert(err, IsNil) 2476 info, err := snap.ReadInfoFromSnapFile(f, nil) 2477 c.Assert(err, IsNil) 2478 w.SetInfo(sn, info) 2479 } 2480 2481 err = w.InfoDerived() 2482 c.Assert(err, IsNil) 2483 2484 snaps, err := w.SnapsToDownload() 2485 c.Assert(err, IsNil) 2486 c.Check(snaps, HasLen, 4) 2487 2488 for _, sn := range snaps { 2489 // check the used channel at this level because in the 2490 // non-dangerous case is not written anywhere (it 2491 // reflects the model or default) 2492 channel := "latest/stable" 2493 switch sn.SnapName() { 2494 case "pc", "pc-kernel": 2495 channel = "20" 2496 } 2497 c.Check(sn.Channel, Equals, channel) 2498 s.fillDownloadedSnap(c, w, sn) 2499 } 2500 2501 complete, err := w.Downloaded() 2502 c.Assert(err, IsNil) 2503 c.Check(complete, Equals, true) 2504 2505 copySnap := func(name, src, dst string) error { 2506 return osutil.CopyFile(src, dst, 0) 2507 } 2508 2509 err = w.SeedSnaps(copySnap) 2510 c.Assert(err, IsNil) 2511 2512 err = w.WriteMeta() 2513 c.Assert(err, IsNil) 2514 2515 // check seed 2516 systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label) 2517 c.Check(systemDir, testutil.FilePresent) 2518 2519 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 2520 c.Assert(err, IsNil) 2521 c.Check(l, HasLen, 4) 2522 2523 // local unasserted snap was put in system snaps dir 2524 c.Check(filepath.Join(systemDir, "snaps", "required20_1.0.snap"), testutil.FilePresent) 2525 2526 options20, err := seedwriter.InternalReadOptions20(filepath.Join(systemDir, "options.yaml")) 2527 c.Assert(err, IsNil) 2528 2529 c.Check(options20.Snaps, DeepEquals, []*seedwriter.InternalSnap20{ 2530 { 2531 Name: "required20", 2532 SnapID: s.AssertedSnapID("required20"), 2533 Unasserted: "required20_1.0.snap", 2534 }, 2535 }) 2536 } 2537 2538 func (s *writerSuite) TestSeedSnapsWriteMetaCore20ChannelOverrides(c *C) { 2539 // add store assertion 2540 storeAs, err := s.StoreSigning.Sign(asserts.StoreType, map[string]interface{}{ 2541 "store": "my-store", 2542 "operator-id": "canonical", 2543 "timestamp": time.Now().UTC().Format(time.RFC3339), 2544 }, nil, "") 2545 c.Assert(err, IsNil) 2546 err = s.StoreSigning.Add(storeAs) 2547 c.Assert(err, IsNil) 2548 2549 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2550 "display-name": "my model", 2551 "architecture": "amd64", 2552 "store": "my-store", 2553 "base": "core20", 2554 "grade": "dangerous", 2555 "snaps": []interface{}{ 2556 map[string]interface{}{ 2557 "name": "pc-kernel", 2558 "id": s.AssertedSnapID("pc-kernel"), 2559 "type": "kernel", 2560 "default-channel": "20", 2561 }, 2562 map[string]interface{}{ 2563 "name": "pc", 2564 "id": s.AssertedSnapID("pc"), 2565 "type": "gadget", 2566 "default-channel": "20", 2567 }, 2568 map[string]interface{}{ 2569 "name": "required20", 2570 "id": s.AssertedSnapID("required20"), 2571 }, 2572 }, 2573 }) 2574 2575 // sanity 2576 c.Assert(model.Grade(), Equals, asserts.ModelDangerous) 2577 2578 s.makeSnap(c, "snapd", "") 2579 s.makeSnap(c, "core20", "") 2580 s.makeSnap(c, "pc-kernel=20", "") 2581 s.makeSnap(c, "pc=20", "") 2582 s.makeSnap(c, "required20", "developerid") 2583 2584 s.opts.Label = "20191030" 2585 s.opts.DefaultChannel = "candidate" 2586 w, err := seedwriter.New(model, s.opts) 2587 c.Assert(err, IsNil) 2588 2589 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "pc", Channel: "edge"}}) 2590 c.Assert(err, IsNil) 2591 2592 _, err = w.Start(s.db, s.newFetcher) 2593 c.Assert(err, IsNil) 2594 2595 snaps, err := w.SnapsToDownload() 2596 c.Assert(err, IsNil) 2597 c.Check(snaps, HasLen, 5) 2598 2599 for _, sn := range snaps { 2600 s.fillDownloadedSnap(c, w, sn) 2601 } 2602 2603 complete, err := w.Downloaded() 2604 c.Assert(err, IsNil) 2605 c.Check(complete, Equals, true) 2606 2607 err = w.SeedSnaps(nil) 2608 c.Assert(err, IsNil) 2609 2610 err = w.WriteMeta() 2611 c.Assert(err, IsNil) 2612 2613 // check seed 2614 systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label) 2615 c.Check(systemDir, testutil.FilePresent) 2616 2617 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 2618 c.Assert(err, IsNil) 2619 c.Check(l, HasLen, 5) 2620 2621 options20, err := seedwriter.InternalReadOptions20(filepath.Join(systemDir, "options.yaml")) 2622 c.Assert(err, IsNil) 2623 2624 c.Check(options20.Snaps, DeepEquals, []*seedwriter.InternalSnap20{ 2625 { 2626 Name: "snapd", 2627 SnapID: s.AssertedSnapID("snapd"), // inferred 2628 Channel: "latest/candidate", 2629 }, 2630 { 2631 Name: "pc-kernel", 2632 SnapID: s.AssertedSnapID("pc-kernel"), 2633 Channel: "20/candidate", 2634 }, 2635 { 2636 Name: "core20", 2637 SnapID: s.AssertedSnapID("core20"), // inferred 2638 Channel: "latest/candidate", 2639 }, 2640 { 2641 Name: "pc", 2642 SnapID: s.AssertedSnapID("pc"), 2643 Channel: "20/edge", 2644 }, 2645 { 2646 Name: "required20", 2647 SnapID: s.AssertedSnapID("required20"), 2648 Channel: "latest/candidate", 2649 }, 2650 }) 2651 } 2652 2653 func (s *writerSuite) TestSeedSnapsWriteMetaCore20ModelOverrideSnapd(c *C) { 2654 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2655 "display-name": "my model", 2656 "architecture": "amd64", 2657 "base": "core20", 2658 "snaps": []interface{}{ 2659 map[string]interface{}{ 2660 "name": "snapd", 2661 "id": s.AssertedSnapID("snapd"), 2662 "type": "snapd", 2663 "default-channel": "latest/edge", 2664 }, 2665 map[string]interface{}{ 2666 "name": "pc-kernel", 2667 "id": s.AssertedSnapID("pc-kernel"), 2668 "type": "kernel", 2669 "default-channel": "20", 2670 }, 2671 map[string]interface{}{ 2672 "name": "pc", 2673 "id": s.AssertedSnapID("pc"), 2674 "type": "gadget", 2675 "default-channel": "20", 2676 }}, 2677 }) 2678 2679 // sanity 2680 c.Assert(model.Grade(), Equals, asserts.ModelSigned) 2681 2682 s.makeSnap(c, "snapd", "") 2683 s.makeSnap(c, "core20", "") 2684 s.makeSnap(c, "pc-kernel=20", "") 2685 s.makeSnap(c, "pc=20", "") 2686 2687 s.opts.Label = "20191121" 2688 w, err := seedwriter.New(model, s.opts) 2689 c.Assert(err, IsNil) 2690 2691 _, err = w.Start(s.db, s.newFetcher) 2692 c.Assert(err, IsNil) 2693 2694 snaps, err := w.SnapsToDownload() 2695 c.Assert(err, IsNil) 2696 c.Check(snaps, HasLen, 4) 2697 2698 for _, sn := range snaps { 2699 // check the used channel at this level because in the 2700 // non-dangerous case is not written anywhere (it 2701 // reflects the model or default) 2702 channel := "latest/stable" 2703 switch sn.SnapName() { 2704 case "snapd": 2705 channel = "latest/edge" 2706 case "pc", "pc-kernel": 2707 channel = "20" 2708 } 2709 c.Check(sn.Channel, Equals, channel) 2710 s.fillDownloadedSnap(c, w, sn) 2711 } 2712 2713 complete, err := w.Downloaded() 2714 c.Assert(err, IsNil) 2715 c.Check(complete, Equals, true) 2716 2717 err = w.SeedSnaps(nil) 2718 c.Assert(err, IsNil) 2719 2720 err = w.WriteMeta() 2721 c.Assert(err, IsNil) 2722 2723 // check seed 2724 systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label) 2725 c.Check(systemDir, testutil.FilePresent) 2726 2727 // check the snaps are in place 2728 for _, name := range []string{"snapd", "pc-kernel", "core20", "pc"} { 2729 info := s.AssertedSnapInfo(name) 2730 2731 fn := info.Filename() 2732 p := filepath.Join(s.opts.SeedDir, "snaps", fn) 2733 c.Check(p, testutil.FilePresent) 2734 } 2735 2736 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 2737 c.Assert(err, IsNil) 2738 c.Check(l, HasLen, 4) 2739 2740 c.Check(filepath.Join(systemDir, "extra-snaps"), testutil.FileAbsent) 2741 c.Check(filepath.Join(systemDir, "options.yaml"), testutil.FileAbsent) 2742 } 2743 2744 func (s *writerSuite) TestSnapsToDownloadCore20OptionalSnaps(c *C) { 2745 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2746 "display-name": "my model", 2747 "architecture": "amd64", 2748 "base": "core20", 2749 "snaps": []interface{}{ 2750 map[string]interface{}{ 2751 "name": "pc-kernel", 2752 "id": s.AssertedSnapID("pc-kernel"), 2753 "type": "kernel", 2754 "default-channel": "20", 2755 }, 2756 map[string]interface{}{ 2757 "name": "pc", 2758 "id": s.AssertedSnapID("pc"), 2759 "type": "gadget", 2760 "default-channel": "20", 2761 }, 2762 map[string]interface{}{ 2763 "name": "core18", 2764 "id": s.AssertedSnapID("core18"), 2765 "type": "base", 2766 }, 2767 map[string]interface{}{ 2768 "name": "optional20-a", 2769 "id": s.AssertedSnapID("optional20-a"), 2770 "presence": "optional", 2771 }, 2772 map[string]interface{}{ 2773 "name": "optional20-b", 2774 "id": s.AssertedSnapID("optional20-b"), 2775 "presence": "optional", 2776 }}, 2777 }) 2778 2779 // sanity 2780 c.Assert(model.Grade(), Equals, asserts.ModelSigned) 2781 2782 s.makeSnap(c, "snapd", "") 2783 s.makeSnap(c, "core20", "") 2784 s.makeSnap(c, "core18", "") 2785 s.makeSnap(c, "pc-kernel=20", "") 2786 s.makeSnap(c, "pc=20", "") 2787 s.makeSnap(c, "optional20-a", "developerid") 2788 s.makeSnap(c, "optional20-b", "developerid") 2789 2790 s.opts.Label = "20191122" 2791 w, err := seedwriter.New(model, s.opts) 2792 c.Assert(err, IsNil) 2793 2794 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "optional20-b"}}) 2795 c.Assert(err, IsNil) 2796 2797 _, err = w.Start(s.db, s.newFetcher) 2798 c.Assert(err, IsNil) 2799 2800 snaps, err := w.SnapsToDownload() 2801 c.Assert(err, IsNil) 2802 c.Check(snaps, HasLen, 6) 2803 c.Check(snaps[5].SnapName(), Equals, "optional20-b") 2804 } 2805 2806 func (s *writerSuite) TestSeedSnapsWriteMetaCore20ExtraSnaps(c *C) { 2807 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2808 "display-name": "my model", 2809 "architecture": "amd64", 2810 "base": "core20", 2811 "grade": "dangerous", 2812 "snaps": []interface{}{ 2813 map[string]interface{}{ 2814 "name": "pc-kernel", 2815 "id": s.AssertedSnapID("pc-kernel"), 2816 "type": "kernel", 2817 "default-channel": "20", 2818 }, 2819 map[string]interface{}{ 2820 "name": "pc", 2821 "id": s.AssertedSnapID("pc"), 2822 "type": "gadget", 2823 "default-channel": "20", 2824 }}, 2825 }) 2826 2827 // sanity 2828 c.Assert(model.Grade(), Equals, asserts.ModelDangerous) 2829 2830 s.makeSnap(c, "snapd", "") 2831 s.makeSnap(c, "core20", "") 2832 s.makeSnap(c, "pc-kernel=20", "") 2833 s.makeSnap(c, "pc=20", "") 2834 s.makeSnap(c, "core18", "") 2835 s.makeSnap(c, "cont-producer", "developerid") 2836 contConsumerFn := s.makeLocalSnap(c, "cont-consumer") 2837 2838 s.opts.Label = "20191122" 2839 w, err := seedwriter.New(model, s.opts) 2840 c.Assert(err, IsNil) 2841 2842 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Name: "cont-producer", Channel: "edge"}, {Name: "core18"}, {Path: contConsumerFn}}) 2843 c.Assert(err, IsNil) 2844 2845 tf, err := w.Start(s.db, s.newFetcher) 2846 c.Assert(err, IsNil) 2847 2848 localSnaps, err := w.LocalSnaps() 2849 c.Assert(err, IsNil) 2850 c.Assert(localSnaps, HasLen, 1) 2851 2852 for _, sn := range localSnaps { 2853 _, _, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db) 2854 c.Assert(asserts.IsNotFound(err), Equals, true) 2855 f, err := snapfile.Open(sn.Path) 2856 c.Assert(err, IsNil) 2857 info, err := snap.ReadInfoFromSnapFile(f, nil) 2858 c.Assert(err, IsNil) 2859 w.SetInfo(sn, info) 2860 } 2861 2862 err = w.InfoDerived() 2863 c.Assert(err, IsNil) 2864 2865 snaps, err := w.SnapsToDownload() 2866 c.Assert(err, IsNil) 2867 c.Check(snaps, HasLen, 4) 2868 2869 for _, sn := range snaps { 2870 channel := "latest/stable" 2871 switch sn.SnapName() { 2872 case "pc", "pc-kernel": 2873 channel = "20" 2874 } 2875 c.Check(sn.Channel, Equals, channel) 2876 s.fillDownloadedSnap(c, w, sn) 2877 } 2878 2879 complete, err := w.Downloaded() 2880 c.Assert(err, IsNil) 2881 c.Check(complete, Equals, false) 2882 2883 snaps, err = w.SnapsToDownload() 2884 c.Assert(err, IsNil) 2885 c.Assert(snaps, HasLen, 2) 2886 c.Check(snaps[0].SnapName(), Equals, "cont-producer") 2887 c.Check(snaps[1].SnapName(), Equals, "core18") 2888 2889 for _, sn := range snaps { 2890 channel := "latest/stable" 2891 switch sn.SnapName() { 2892 case "cont-producer": 2893 channel = "latest/edge" 2894 } 2895 c.Check(sn.Channel, Equals, channel) 2896 2897 info := s.doFillMetaDownloadedSnap(c, w, sn) 2898 2899 c.Assert(sn.Path, Equals, filepath.Join(s.opts.SeedDir, "systems", s.opts.Label, "snaps", info.Filename())) 2900 err := os.Rename(s.AssertedSnap(sn.SnapName()), sn.Path) 2901 c.Assert(err, IsNil) 2902 } 2903 2904 complete, err = w.Downloaded() 2905 c.Assert(err, IsNil) 2906 c.Check(complete, Equals, true) 2907 2908 copySnap := func(name, src, dst string) error { 2909 return osutil.CopyFile(src, dst, 0) 2910 } 2911 2912 err = w.SeedSnaps(copySnap) 2913 c.Assert(err, IsNil) 2914 2915 err = w.WriteMeta() 2916 c.Assert(err, IsNil) 2917 2918 // check seed 2919 systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label) 2920 c.Check(systemDir, testutil.FilePresent) 2921 2922 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 2923 c.Assert(err, IsNil) 2924 c.Check(l, HasLen, 4) 2925 2926 // extra snaps were put in system snaps dir 2927 c.Check(filepath.Join(systemDir, "snaps", "core18_1.snap"), testutil.FilePresent) 2928 c.Check(filepath.Join(systemDir, "snaps", "cont-producer_1.snap"), testutil.FilePresent) 2929 c.Check(filepath.Join(systemDir, "snaps", "cont-consumer_1.0.snap"), testutil.FilePresent) 2930 2931 // check extra-snaps in assertions 2932 snapAsserts := seedtest.ReadAssertions(c, filepath.Join(systemDir, "assertions", "extra-snaps")) 2933 seen := make(map[string]bool) 2934 2935 for _, a := range snapAsserts { 2936 uniq := a.Ref().Unique() 2937 if a.Type() == asserts.SnapRevisionType { 2938 rev := a.(*asserts.SnapRevision) 2939 uniq = fmt.Sprintf("%s@%d", rev.SnapID(), rev.SnapRevision()) 2940 } 2941 seen[uniq] = true 2942 } 2943 2944 snapRevUniq := func(snapName string, revno int) string { 2945 return fmt.Sprintf("%s@%d", s.AssertedSnapID(snapName), revno) 2946 } 2947 snapDeclUniq := func(snapName string) string { 2948 return "snap-declaration/16/" + s.AssertedSnapID(snapName) 2949 } 2950 2951 c.Check(seen, DeepEquals, map[string]bool{ 2952 "account/developerid": true, 2953 snapDeclUniq("core18"): true, 2954 snapDeclUniq("cont-producer"): true, 2955 snapRevUniq("core18", 1): true, 2956 snapRevUniq("cont-producer", 1): true, 2957 }) 2958 2959 options20, err := seedwriter.InternalReadOptions20(filepath.Join(systemDir, "options.yaml")) 2960 c.Assert(err, IsNil) 2961 2962 c.Check(options20.Snaps, DeepEquals, []*seedwriter.InternalSnap20{ 2963 { 2964 Name: "cont-producer", 2965 SnapID: s.AssertedSnapID("cont-producer"), 2966 Channel: "latest/edge", 2967 }, 2968 { 2969 Name: "core18", 2970 SnapID: s.AssertedSnapID("core18"), 2971 Channel: "latest/stable", 2972 }, 2973 { 2974 Name: "cont-consumer", 2975 Unasserted: "cont-consumer_1.0.snap", 2976 }, 2977 }) 2978 } 2979 2980 func (s *writerSuite) TestSeedSnapsWriteMetaCore20LocalAssertedSnaps(c *C) { 2981 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 2982 "display-name": "my model", 2983 "architecture": "amd64", 2984 "base": "core20", 2985 "grade": "dangerous", 2986 "snaps": []interface{}{ 2987 map[string]interface{}{ 2988 "name": "pc-kernel", 2989 "id": s.AssertedSnapID("pc-kernel"), 2990 "type": "kernel", 2991 "default-channel": "20", 2992 }, 2993 map[string]interface{}{ 2994 "name": "pc", 2995 "id": s.AssertedSnapID("pc"), 2996 "type": "gadget", 2997 "default-channel": "20", 2998 }}, 2999 }) 3000 3001 // sanity 3002 c.Assert(model.Grade(), Equals, asserts.ModelDangerous) 3003 3004 s.makeSnap(c, "snapd", "") 3005 s.makeSnap(c, "core20", "") 3006 s.makeSnap(c, "pc-kernel=20", "") 3007 s.makeSnap(c, "pc=20", "") 3008 s.makeSnap(c, "required20", "developerid") 3009 3010 s.opts.Label = "20191122" 3011 w, err := seedwriter.New(model, s.opts) 3012 c.Assert(err, IsNil) 3013 3014 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: s.AssertedSnap("pc"), Channel: "edge"}, {Path: s.AssertedSnap("required20")}}) 3015 c.Assert(err, IsNil) 3016 3017 tf, err := w.Start(s.db, s.newFetcher) 3018 c.Assert(err, IsNil) 3019 3020 localSnaps, err := w.LocalSnaps() 3021 c.Assert(err, IsNil) 3022 c.Assert(localSnaps, HasLen, 2) 3023 3024 for _, sn := range localSnaps { 3025 si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db) 3026 c.Assert(err, IsNil) 3027 f, err := snapfile.Open(sn.Path) 3028 c.Assert(err, IsNil) 3029 info, err := snap.ReadInfoFromSnapFile(f, si) 3030 c.Assert(err, IsNil) 3031 w.SetInfo(sn, info) 3032 sn.ARefs = aRefs 3033 } 3034 3035 err = w.InfoDerived() 3036 c.Assert(err, IsNil) 3037 3038 snaps, err := w.SnapsToDownload() 3039 c.Assert(err, IsNil) 3040 c.Check(snaps, HasLen, 3) 3041 3042 for _, sn := range snaps { 3043 channel := "latest/stable" 3044 switch sn.SnapName() { 3045 case "pc", "pc-kernel": 3046 channel = "20" 3047 } 3048 c.Check(sn.Channel, Equals, channel) 3049 s.fillDownloadedSnap(c, w, sn) 3050 } 3051 3052 complete, err := w.Downloaded() 3053 c.Assert(err, IsNil) 3054 c.Check(complete, Equals, false) 3055 3056 snaps, err = w.SnapsToDownload() 3057 c.Assert(err, IsNil) 3058 c.Assert(snaps, HasLen, 0) 3059 3060 complete, err = w.Downloaded() 3061 c.Assert(err, IsNil) 3062 c.Check(complete, Equals, true) 3063 3064 copySnap := func(name, src, dst string) error { 3065 return osutil.CopyFile(src, dst, 0) 3066 } 3067 3068 err = w.SeedSnaps(copySnap) 3069 c.Assert(err, IsNil) 3070 3071 err = w.WriteMeta() 3072 c.Assert(err, IsNil) 3073 3074 // check seed 3075 systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label) 3076 c.Check(systemDir, testutil.FilePresent) 3077 3078 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 3079 c.Assert(err, IsNil) 3080 c.Check(l, HasLen, 4) 3081 3082 // local asserted model snap was put in /snaps 3083 c.Check(filepath.Join(s.opts.SeedDir, "snaps", "pc_1.snap"), testutil.FilePresent) 3084 // extra snaps were put in system snaps dir 3085 c.Check(filepath.Join(systemDir, "snaps", "required20_1.snap"), testutil.FilePresent) 3086 3087 options20, err := seedwriter.InternalReadOptions20(filepath.Join(systemDir, "options.yaml")) 3088 c.Assert(err, IsNil) 3089 3090 c.Check(options20.Snaps, DeepEquals, []*seedwriter.InternalSnap20{ 3091 { 3092 Name: "pc", 3093 SnapID: s.AssertedSnapID("pc"), 3094 Channel: "20/edge", 3095 }, 3096 { 3097 Name: "required20", 3098 SnapID: s.AssertedSnapID("required20"), 3099 Channel: "latest/stable", 3100 }, 3101 }) 3102 } 3103 3104 func (s *writerSuite) TestSeedSnapsWriteMetaCore20SignedLocalAssertedSnaps(c *C) { 3105 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 3106 "display-name": "my model", 3107 "architecture": "amd64", 3108 "base": "core20", 3109 "grade": "signed", 3110 "snaps": []interface{}{ 3111 map[string]interface{}{ 3112 "name": "pc-kernel", 3113 "id": s.AssertedSnapID("pc-kernel"), 3114 "type": "kernel", 3115 "default-channel": "20", 3116 }, 3117 map[string]interface{}{ 3118 "name": "pc", 3119 "id": s.AssertedSnapID("pc"), 3120 "type": "gadget", 3121 "default-channel": "20", 3122 }}, 3123 }) 3124 3125 // soundness 3126 c.Assert(model.Grade(), Equals, asserts.ModelSigned) 3127 3128 s.makeSnap(c, "snapd", "") 3129 s.makeSnap(c, "core20", "") 3130 s.makeSnap(c, "pc-kernel=20", "") 3131 s.makeSnap(c, "pc=20", "") 3132 3133 s.opts.Label = "20191122" 3134 w, err := seedwriter.New(model, s.opts) 3135 c.Assert(err, IsNil) 3136 3137 // use a local asserted snap with signed, which is supported 3138 err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{{Path: s.AssertedSnap("pc")}}) 3139 c.Assert(err, IsNil) 3140 3141 tf, err := w.Start(s.db, s.newFetcher) 3142 c.Assert(err, IsNil) 3143 3144 localSnaps, err := w.LocalSnaps() 3145 c.Assert(err, IsNil) 3146 c.Assert(localSnaps, HasLen, 1) 3147 3148 for _, sn := range localSnaps { 3149 si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, tf, s.db) 3150 c.Assert(err, IsNil) 3151 f, err := snapfile.Open(sn.Path) 3152 c.Assert(err, IsNil) 3153 info, err := snap.ReadInfoFromSnapFile(f, si) 3154 c.Assert(err, IsNil) 3155 w.SetInfo(sn, info) 3156 sn.ARefs = aRefs 3157 } 3158 3159 err = w.InfoDerived() 3160 c.Assert(err, IsNil) 3161 3162 snaps, err := w.SnapsToDownload() 3163 c.Assert(err, IsNil) 3164 c.Check(snaps, HasLen, 3) 3165 3166 for _, sn := range snaps { 3167 channel := "latest/stable" 3168 switch sn.SnapName() { 3169 case "pc", "pc-kernel": 3170 channel = "20" 3171 } 3172 c.Check(sn.Channel, Equals, channel) 3173 s.fillDownloadedSnap(c, w, sn) 3174 } 3175 3176 complete, err := w.Downloaded() 3177 c.Assert(err, IsNil) 3178 c.Check(complete, Equals, true) 3179 3180 copySnap := func(name, src, dst string) error { 3181 return osutil.CopyFile(src, dst, 0) 3182 } 3183 3184 err = w.SeedSnaps(copySnap) 3185 c.Assert(err, IsNil) 3186 3187 err = w.WriteMeta() 3188 c.Assert(err, IsNil) 3189 3190 // check seed 3191 systemDir := filepath.Join(s.opts.SeedDir, "systems", s.opts.Label) 3192 c.Check(systemDir, testutil.FilePresent) 3193 3194 l, err := ioutil.ReadDir(filepath.Join(s.opts.SeedDir, "snaps")) 3195 c.Assert(err, IsNil) 3196 c.Check(l, HasLen, 4) 3197 3198 // local asserted model snap was put in /snaps 3199 c.Check(filepath.Join(s.opts.SeedDir, "snaps", "pc_1.snap"), testutil.FilePresent) 3200 3201 // no options file was created 3202 c.Check(filepath.Join(systemDir, "options.yaml"), testutil.FileAbsent) 3203 }