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