github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/seed/seedwriter/writer.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 seedwrite implements writing image seeds. 21 package seedwriter 22 23 import ( 24 "errors" 25 "fmt" 26 "strings" 27 28 "github.com/snapcore/snapd/asserts" 29 "github.com/snapcore/snapd/asserts/snapasserts" 30 "github.com/snapcore/snapd/osutil" 31 "github.com/snapcore/snapd/seed/internal" 32 "github.com/snapcore/snapd/snap" 33 "github.com/snapcore/snapd/snap/channel" 34 "github.com/snapcore/snapd/snap/naming" 35 "github.com/snapcore/snapd/strutil" 36 ) 37 38 // Options holds the options for a Writer. 39 type Options struct { 40 SeedDir string 41 42 DefaultChannel string 43 44 // The label for the recovery system for Core20 models 45 Label string 46 47 // TestSkipCopyUnverifiedModel is set to support naive tests 48 // using an unverified model, the resulting image is broken 49 TestSkipCopyUnverifiedModel bool 50 } 51 52 // OptionsSnap represents an options-referred snap with its option values. 53 // E.g. a snap passed to ubuntu-image via --snap. 54 // If Name is set the snap is from the store. If Path is set the snap 55 // is local at Path location. 56 type OptionsSnap struct { 57 Name string 58 SnapID string 59 Path string 60 Channel string 61 } 62 63 func (s *OptionsSnap) SnapName() string { 64 return s.Name 65 } 66 67 func (s *OptionsSnap) ID() string { 68 return s.SnapID 69 } 70 71 var _ naming.SnapRef = (*OptionsSnap)(nil) 72 73 // SeedSnap holds details of a snap being added to a seed. 74 type SeedSnap struct { 75 naming.SnapRef 76 Channel string 77 Path string 78 79 // Info is the *snap.Info for the seed snap, filling this is 80 // delegated to the Writer using code, via Writer.SetInfo. 81 Info *snap.Info 82 // ARefs are references to the snap assertions if applicable, 83 // filling these is delegated to the Writer using code, the 84 // assumption is that the corresponding assertions can be 85 // found in the database passed to Writer.Start. 86 ARefs []*asserts.Ref 87 88 local bool 89 modelSnap *asserts.ModelSnap 90 optionSnap *OptionsSnap 91 } 92 93 func (sn *SeedSnap) modes() []string { 94 if sn.modelSnap == nil { 95 // run is the assumed mode for extra snaps not listed 96 // in the model 97 return []string{"run"} 98 } 99 return sn.modelSnap.Modes 100 } 101 102 var _ naming.SnapRef = (*SeedSnap)(nil) 103 104 /* Writer writes Core 16/18 and Core 20 seeds. 105 106 Its methods need to be called in sequences that match prescribed 107 flows. 108 109 Some methods can be skipped given some conditions. 110 111 SnapsToDownload and Downloaded needs to be called in a loop where the 112 SeedSnaps returned by SnapsToDownload get SetInfo called with 113 *snap.Info retrieved from the store and then the snaps can be 114 downloaded at SeedSnap.Path, after which Downloaded must be invoked 115 and the flow breaks out of the loop only when it returns complete = 116 true. In the loop as well assertions for the snaps can be fetched and 117 SeedSnap.ARefs set. 118 119 Optionally a similar but simpler mechanism covers local snaps, where 120 LocalSnaps returns SeedSnaps that can be filled with information 121 derived from the snap at SeedSnap.Path, then InfoDerived is called. 122 123 V-------->\ 124 | | 125 SetOptionsSnaps | 126 | v 127 | ________/ 128 v 129 / Start \ 130 | | | 131 | v | 132 no | / LocalSnaps | no option 133 local | | | | snaps 134 snaps | | v | 135 | | InfoDerived | 136 | | | | 137 | \ | / 138 > > SnapsToDownload< 139 | ^ 140 | | 141 | | complete = false 142 v / 143 Downloaded 144 | 145 | complete = true 146 | 147 v 148 SeedSnaps (copy files) 149 | 150 v 151 WriteMeta 152 153 */ 154 type Writer struct { 155 model *asserts.Model 156 opts *Options 157 policy policy 158 tree tree 159 160 // warnings keep a list of warnings produced during the 161 // process, no more warnings should be produced after 162 // Downloaded signaled complete 163 warnings []string 164 165 db asserts.RODatabase 166 167 expectedStep writerStep 168 169 modelRefs []*asserts.Ref 170 171 optionsSnaps []*OptionsSnap 172 // consumedOptSnapNum counts which options snaps have been consumed 173 // by either cross matching or matching with a model snap 174 consumedOptSnapNum int 175 // extraSnapsGuessNum is essentially #(optionsSnaps) - 176 // consumedOptSnapNum 177 extraSnapsGuessNum int 178 179 byNameOptSnaps *naming.SnapSet 180 181 localSnaps map[*OptionsSnap]*SeedSnap 182 byRefLocalSnaps *naming.SnapSet 183 184 availableSnaps *naming.SnapSet 185 availableByMode map[string]*naming.SnapSet 186 187 // toDownload tracks which set of snaps SnapsToDownload should compute 188 // next 189 toDownload snapsToDownloadSet 190 toDownloadConsideredNum int 191 192 snapsFromModel []*SeedSnap 193 extraSnaps []*SeedSnap 194 } 195 196 type policy interface { 197 allowsDangerousFeatures() error 198 199 checkDefaultChannel(channel.Channel) error 200 checkSnapChannel(ch channel.Channel, whichSnap string) error 201 202 systemSnap() *asserts.ModelSnap 203 204 modelSnapDefaultChannel() string 205 extraSnapDefaultChannel() string 206 207 checkBase(s *snap.Info, modes []string, availableByMode map[string]*naming.SnapSet) error 208 209 checkAvailable(snpRef naming.SnapRef, modes []string, availableByMode map[string]*naming.SnapSet) bool 210 211 needsImplicitSnaps(availableByMode map[string]*naming.SnapSet) (bool, error) 212 implicitSnaps(availableByMode map[string]*naming.SnapSet) []*asserts.ModelSnap 213 implicitExtraSnaps(availableByMode map[string]*naming.SnapSet) []*OptionsSnap 214 } 215 216 type tree interface { 217 mkFixedDirs() error 218 219 snapPath(*SeedSnap) (string, error) 220 221 localSnapPath(*SeedSnap) (string, error) 222 223 writeAssertions(db asserts.RODatabase, modelRefs []*asserts.Ref, snapsFromModel []*SeedSnap, extraSnaps []*SeedSnap) error 224 225 writeMeta(snapsFromModel []*SeedSnap, extraSnaps []*SeedSnap) error 226 } 227 228 // New returns a Writer to write a seed for the given model and using 229 // the given Options. 230 func New(model *asserts.Model, opts *Options) (*Writer, error) { 231 if opts == nil { 232 return nil, fmt.Errorf("internal error: Writer *Options is nil") 233 } 234 w := &Writer{ 235 model: model, 236 opts: opts, 237 238 expectedStep: setOptionsSnapsStep, 239 240 byNameOptSnaps: naming.NewSnapSet(nil), 241 byRefLocalSnaps: naming.NewSnapSet(nil), 242 } 243 244 var treeImpl tree 245 var pol policy 246 if model.Grade() != asserts.ModelGradeUnset { 247 // Core 20 248 if opts.Label == "" { 249 return nil, fmt.Errorf("internal error: cannot write Core 20 seed without Options.Label set") 250 } 251 if err := internal.ValidateUC20SeedSystemLabel(opts.Label); err != nil { 252 return nil, err 253 } 254 pol = &policy20{model: model, opts: opts, warningf: w.warningf} 255 treeImpl = &tree20{grade: model.Grade(), opts: opts} 256 } else { 257 pol = &policy16{model: model, opts: opts, warningf: w.warningf} 258 treeImpl = &tree16{opts: opts} 259 } 260 261 if opts.DefaultChannel != "" { 262 deflCh, err := channel.ParseVerbatim(opts.DefaultChannel, "_") 263 if err != nil { 264 return nil, fmt.Errorf("cannot use global default option channel: %v", err) 265 } 266 if err := pol.checkDefaultChannel(deflCh); err != nil { 267 return nil, err 268 } 269 } 270 271 w.tree = treeImpl 272 w.policy = pol 273 return w, nil 274 } 275 276 type writerStep int 277 278 const ( 279 setOptionsSnapsStep = iota 280 startStep 281 localSnapsStep 282 infoDerivedStep 283 snapsToDownloadStep 284 downloadedStep 285 seedSnapsStep 286 writeMetaStep 287 ) 288 289 var writerStepNames = map[writerStep]string{ 290 startStep: "Start", 291 setOptionsSnapsStep: "SetOptionsSnaps", 292 localSnapsStep: "LocalSnaps", 293 infoDerivedStep: "InfoDerived", 294 snapsToDownloadStep: "SnapsToDownload", 295 downloadedStep: "Downloaded", 296 seedSnapsStep: "SeedSnaps", 297 writeMetaStep: "WriteMeta", 298 } 299 300 func (ws writerStep) String() string { 301 name := writerStepNames[ws] 302 if name == "" { 303 panic(fmt.Sprintf("unknown writerStep: %d", ws)) 304 } 305 return name 306 } 307 308 func (w *Writer) checkStep(thisStep writerStep) error { 309 if thisStep != w.expectedStep { 310 // exceptions 311 alright := false 312 switch thisStep { 313 case startStep: 314 if w.expectedStep == setOptionsSnapsStep { 315 alright = true 316 } 317 case snapsToDownloadStep: 318 if w.expectedStep == localSnapsStep || w.expectedStep == infoDerivedStep { 319 if len(w.localSnaps) != 0 { 320 break 321 } 322 alright = true 323 } 324 } 325 if !alright { 326 expected := w.expectedStep.String() 327 switch w.expectedStep { 328 case setOptionsSnapsStep: 329 expected = "Start|SetOptionsSnaps" 330 case localSnapsStep: 331 if len(w.localSnaps) == 0 { 332 expected = "SnapsToDownload|LocalSnaps" 333 } 334 } 335 return fmt.Errorf("internal error: seedwriter.Writer expected %s to be invoked on it at this point, not %v", expected, thisStep) 336 } 337 } 338 w.expectedStep = thisStep + 1 339 return nil 340 } 341 342 // warningf adds a warning that can be later retrieved via Warnings. 343 func (w *Writer) warningf(format string, a ...interface{}) { 344 w.warnings = append(w.warnings, fmt.Sprintf(format, a...)) 345 } 346 347 // SetOptionsSnaps accepts options-referred snaps represented as OptionsSnap. 348 func (w *Writer) SetOptionsSnaps(optSnaps []*OptionsSnap) error { 349 if err := w.checkStep(setOptionsSnapsStep); err != nil { 350 return err 351 } 352 353 if len(optSnaps) == 0 { 354 return nil 355 } 356 357 for _, sn := range optSnaps { 358 var whichSnap string 359 local := false 360 if sn.Name != "" { 361 if sn.Path != "" { 362 return fmt.Errorf("cannot specify both name and path for option snap %q", sn.Name) 363 } 364 snapName := sn.Name 365 whichSnap = snapName 366 if _, instanceKey := snap.SplitInstanceName(snapName); instanceKey != "" { 367 // be specific about this error 368 return fmt.Errorf("cannot use snap %q, parallel snap instances are unsupported", snapName) 369 } 370 if err := naming.ValidateSnap(snapName); err != nil { 371 return err 372 } 373 374 if w.byNameOptSnaps.Contains(sn) { 375 return fmt.Errorf("snap %q is repeated in options", snapName) 376 } 377 w.byNameOptSnaps.Add(sn) 378 } else { 379 if !strings.HasSuffix(sn.Path, ".snap") { 380 return fmt.Errorf("local option snap %q does not end in .snap", sn.Path) 381 } 382 if !osutil.FileExists(sn.Path) { 383 return fmt.Errorf("local option snap %q does not exist", sn.Path) 384 } 385 386 whichSnap = sn.Path 387 local = true 388 } 389 if sn.Channel != "" { 390 ch, err := channel.ParseVerbatim(sn.Channel, "_") 391 if err != nil { 392 return fmt.Errorf("cannot use option channel for snap %q: %v", whichSnap, err) 393 } 394 if err := w.policy.checkSnapChannel(ch, whichSnap); err != nil { 395 return err 396 } 397 } 398 if local { 399 if w.localSnaps == nil { 400 w.localSnaps = make(map[*OptionsSnap]*SeedSnap) 401 } 402 w.localSnaps[sn] = &SeedSnap{ 403 SnapRef: nil, 404 Path: sn.Path, 405 406 local: true, 407 optionSnap: sn, 408 } 409 } 410 } 411 412 // used later to determine extra snaps 413 w.optionsSnaps = optSnaps 414 415 return nil 416 } 417 418 // Start starts the seed writing. It creates a RefAssertsFetcher using 419 // newFetcher and uses it to fetch model related assertions. For 420 // convenience it returns the fetcher possibly for use to fetch seed 421 // snap assertions, a task that the writer delegates as well as snap 422 // downloading. The writer assumes that the snap assertions will end up 423 // in the given db (writing assertion database). 424 func (w *Writer) Start(db asserts.RODatabase, newFetcher NewFetcherFunc) (RefAssertsFetcher, error) { 425 if err := w.checkStep(startStep); err != nil { 426 return nil, err 427 } 428 if db == nil { 429 return nil, fmt.Errorf("internal error: Writer *asserts.RODatabase is nil") 430 } 431 if newFetcher == nil { 432 return nil, fmt.Errorf("internal error: Writer newFetcherFunc is nil") 433 } 434 w.db = db 435 436 f := MakeRefAssertsFetcher(newFetcher) 437 438 if err := f.Save(w.model); err != nil { 439 const msg = "cannot fetch and check prerequisites for the model assertion: %v" 440 if !w.opts.TestSkipCopyUnverifiedModel { 441 return nil, fmt.Errorf(msg, err) 442 } 443 // Some naive tests including ubuntu-image ones use 444 // unverified models 445 w.warningf(msg, err) 446 f.ResetRefs() 447 } 448 449 // fetch device store assertion (and prereqs) if available 450 if w.model.Store() != "" { 451 err := snapasserts.FetchStore(f, w.model.Store()) 452 if err != nil { 453 if nfe, ok := err.(*asserts.NotFoundError); !ok || nfe.Type != asserts.StoreType { 454 return nil, err 455 } 456 } 457 } 458 459 w.modelRefs = f.Refs() 460 461 if err := w.tree.mkFixedDirs(); err != nil { 462 return nil, err 463 } 464 465 return f, nil 466 } 467 468 // LocalSnaps returns a list of seed snaps that are local. The writer 469 // delegates to produce *snap.Info for them to then be set via 470 // SetInfo. If matching snap assertions can be found as well they can 471 // be passed into SeedSnap ARefs, assuming they were added to the 472 // writing assertion database. 473 func (w *Writer) LocalSnaps() ([]*SeedSnap, error) { 474 if err := w.checkStep(localSnapsStep); err != nil { 475 return nil, err 476 } 477 478 if len(w.localSnaps) == 0 { 479 return nil, nil 480 } 481 482 lsnaps := make([]*SeedSnap, 0, len(w.localSnaps)) 483 for _, optSnap := range w.optionsSnaps { 484 if sn := w.localSnaps[optSnap]; sn != nil { 485 lsnaps = append(lsnaps, sn) 486 } 487 } 488 return lsnaps, nil 489 } 490 491 // InfoDerived checks the local snaps metadata provided via setting it 492 // into the SeedSnaps returned by the previous LocalSnaps. 493 func (w *Writer) InfoDerived() error { 494 if err := w.checkStep(infoDerivedStep); err != nil { 495 return err 496 } 497 498 // loop this way to process for consistency in the same order 499 // as LocalSnaps result 500 for _, optSnap := range w.optionsSnaps { 501 sn := w.localSnaps[optSnap] 502 if sn == nil { 503 continue 504 } 505 if sn.Info == nil { 506 return fmt.Errorf("internal error: before seedwriter.Writer.InfoDerived snap %q Info should have been set", sn.Path) 507 } 508 509 if sn.Info.ID() == "" { 510 // this check is here in case we relax the checks in 511 // SetOptionsSnaps 512 if err := w.policy.allowsDangerousFeatures(); err != nil { 513 return err 514 } 515 } 516 517 sn.SnapRef = sn.Info 518 519 // local snap gets local revision 520 if sn.Info.Revision.Unset() { 521 sn.Info.Revision = snap.R(-1) 522 } 523 524 if w.byRefLocalSnaps.Contains(sn) { 525 return fmt.Errorf("local snap %q is repeated in options", sn.SnapName()) 526 } 527 528 // in case, merge channel given by name separately 529 optSnap, _ := w.byNameOptSnaps.Lookup(sn).(*OptionsSnap) 530 if optSnap != nil { 531 w.consumedOptSnapNum++ 532 } 533 if optSnap != nil && optSnap.Channel != "" { 534 if sn.optionSnap.Channel != "" { 535 if sn.optionSnap.Channel != optSnap.Channel { 536 return fmt.Errorf("option snap has different channels specified: %q=%q vs %q=%q", sn.Path, sn.optionSnap.Channel, optSnap.Name, optSnap.Channel) 537 } 538 } else { 539 sn.optionSnap.Channel = optSnap.Channel 540 } 541 } 542 543 w.byRefLocalSnaps.Add(sn) 544 } 545 546 return nil 547 } 548 549 // SetInfo sets Info of the SeedSnap and possibly computes its 550 // destination Path. 551 func (w *Writer) SetInfo(sn *SeedSnap, info *snap.Info) error { 552 if info.Confinement == snap.DevModeConfinement { 553 if err := w.policy.allowsDangerousFeatures(); err != nil { 554 return err 555 } 556 } 557 sn.Info = info 558 559 if sn.local { 560 // nothing more to do 561 return nil 562 } 563 564 p, err := w.tree.snapPath(sn) 565 if err != nil { 566 return err 567 } 568 sn.Path = p 569 return nil 570 } 571 572 // SetRedirectChannel sets the redirect channel for the SeedSnap 573 // for the in case there is a default track for it. 574 func (w *Writer) SetRedirectChannel(sn *SeedSnap, redirectChannel string) error { 575 if sn.local { 576 return fmt.Errorf("internal error: cannot set redirect channel for local snap %q", sn.Path) 577 } 578 if sn.Info == nil { 579 return fmt.Errorf("internal error: before using seedwriter.Writer.SetRedirectChannel snap %q Info should have been set", sn.SnapName()) 580 } 581 if redirectChannel == "" { 582 // nothing to do 583 return nil 584 } 585 _, err := channel.ParseVerbatim(redirectChannel, "-") 586 if err != nil { 587 return fmt.Errorf("invalid redirect channel for snap %q: %v", sn.SnapName(), err) 588 } 589 sn.Channel = redirectChannel 590 return nil 591 592 } 593 594 // snapsToDownloadSet indicates which set of snaps SnapsToDownload should compute 595 type snapsToDownloadSet int 596 597 const ( 598 toDownloadModel snapsToDownloadSet = iota 599 toDownloadImplicit 600 toDownloadExtra 601 toDownloadExtraImplicit 602 ) 603 604 var errSkipOptional = errors.New("skip") 605 606 func (w *Writer) modelSnapToSeed(modSnap *asserts.ModelSnap) (*SeedSnap, error) { 607 sn, _ := w.byRefLocalSnaps.Lookup(modSnap).(*SeedSnap) 608 var optSnap *OptionsSnap 609 if sn == nil { 610 // not local, to download 611 optSnap, _ = w.byNameOptSnaps.Lookup(modSnap).(*OptionsSnap) 612 if modSnap.Presence == "optional" && optSnap == nil { 613 // an optional snap that is not confirmed 614 // by an OptionsSnap entry is skipped 615 return nil, errSkipOptional 616 } 617 sn = &SeedSnap{ 618 SnapRef: modSnap, 619 620 local: false, 621 optionSnap: optSnap, 622 } 623 } else { 624 optSnap = sn.optionSnap 625 } 626 627 channel, err := w.resolveChannel(modSnap.SnapName(), modSnap, optSnap) 628 if err != nil { 629 return nil, err 630 } 631 sn.modelSnap = modSnap 632 sn.Channel = channel 633 return sn, nil 634 } 635 636 func (w *Writer) modelSnapsToDownload(modSnaps []*asserts.ModelSnap) (toDownload []*SeedSnap, err error) { 637 if w.snapsFromModel == nil { 638 w.snapsFromModel = make([]*SeedSnap, 0, len(modSnaps)) 639 } 640 toDownload = make([]*SeedSnap, 0, len(modSnaps)) 641 642 alreadyConsidered := len(w.snapsFromModel) 643 for _, modSnap := range modSnaps { 644 sn, err := w.modelSnapToSeed(modSnap) 645 if err == errSkipOptional { 646 continue 647 } 648 if err != nil { 649 return nil, err 650 } 651 if !sn.local { 652 toDownload = append(toDownload, sn) 653 } 654 if sn.optionSnap != nil { 655 w.consumedOptSnapNum++ 656 } 657 w.snapsFromModel = append(w.snapsFromModel, sn) 658 } 659 w.toDownloadConsideredNum = len(w.snapsFromModel) - alreadyConsidered 660 w.extraSnapsGuessNum = len(w.optionsSnaps) - w.consumedOptSnapNum 661 662 return toDownload, nil 663 } 664 665 func (w *Writer) modSnaps() ([]*asserts.ModelSnap, error) { 666 // model snaps are accumulated/processed in the order 667 // * system snap if implicit 668 // * essential snaps (in Model.EssentialSnaps order) 669 // * not essential snaps 670 modSnaps := append([]*asserts.ModelSnap{}, w.model.EssentialSnaps()...) 671 if systemSnap := w.policy.systemSnap(); systemSnap != nil { 672 prepend := true 673 for _, modSnap := range modSnaps { 674 if naming.SameSnap(modSnap, systemSnap) { 675 prepend = false 676 modes := modSnap.Modes 677 expectedModes := systemSnap.Modes 678 if len(modes) != len(expectedModes) { 679 return nil, fmt.Errorf("internal error: system snap %q explicitly listed in model carries wrong modes: %q", systemSnap.SnapName(), modes) 680 } 681 for _, mod := range expectedModes { 682 if !strutil.ListContains(modes, mod) { 683 return nil, fmt.Errorf("internal error: system snap %q explicitly listed in model carries wrong modes: %q", systemSnap.SnapName(), modes) 684 } 685 } 686 break 687 } 688 } 689 if prepend { 690 modSnaps = append([]*asserts.ModelSnap{systemSnap}, modSnaps...) 691 } 692 } 693 modSnaps = append(modSnaps, w.model.SnapsWithoutEssential()...) 694 return modSnaps, nil 695 } 696 697 func (w *Writer) optExtraSnaps() []*OptionsSnap { 698 extra := make([]*OptionsSnap, 0, w.extraSnapsGuessNum) 699 for _, optSnap := range w.optionsSnaps { 700 var snapRef naming.SnapRef = optSnap 701 if sn := w.localSnaps[optSnap]; sn != nil { 702 snapRef = sn 703 } 704 if w.availableSnaps.Contains(snapRef) { 705 continue 706 } 707 extra = append(extra, optSnap) 708 } 709 return extra 710 } 711 712 func (w *Writer) extraSnapToSeed(optSnap *OptionsSnap) (*SeedSnap, error) { 713 sn := w.localSnaps[optSnap] 714 if sn == nil { 715 // not local, to download 716 sn = &SeedSnap{ 717 SnapRef: optSnap, 718 719 local: false, 720 optionSnap: optSnap, 721 } 722 } 723 if sn.SnapName() == "" { 724 return nil, fmt.Errorf("internal error: option extra snap has no associated name: %#v %#v", optSnap, sn) 725 } 726 727 channel, err := w.resolveChannel(sn.SnapName(), nil, optSnap) 728 if err != nil { 729 return nil, err 730 } 731 sn.Channel = channel 732 return sn, nil 733 } 734 735 func (w *Writer) extraSnapsToDownload(extraSnaps []*OptionsSnap) (toDownload []*SeedSnap, err error) { 736 if w.extraSnaps == nil { 737 w.extraSnaps = make([]*SeedSnap, 0, len(extraSnaps)) 738 } 739 toDownload = make([]*SeedSnap, 0, len(extraSnaps)) 740 741 alreadyConsidered := len(w.extraSnaps) 742 for _, optSnap := range extraSnaps { 743 sn, err := w.extraSnapToSeed(optSnap) 744 if err != nil { 745 return nil, err 746 } 747 if !sn.local { 748 toDownload = append(toDownload, sn) 749 } 750 w.extraSnaps = append(w.extraSnaps, sn) 751 } 752 w.toDownloadConsideredNum = len(w.extraSnaps) - alreadyConsidered 753 754 return toDownload, nil 755 } 756 757 // SnapsToDownload returns a list of seed snaps to download. Once that 758 // is done and their SeedSnaps Info with SetInfo and ARefs fields are 759 // set, Downloaded should be called next. 760 func (w *Writer) SnapsToDownload() (snaps []*SeedSnap, err error) { 761 if err := w.checkStep(snapsToDownloadStep); err != nil { 762 return nil, err 763 } 764 765 switch w.toDownload { 766 case toDownloadModel: 767 modSnaps, err := w.modSnaps() 768 if err != nil { 769 return nil, err 770 } 771 toDownload, err := w.modelSnapsToDownload(modSnaps) 772 if err != nil { 773 return nil, err 774 } 775 if w.extraSnapsGuessNum > 0 { 776 // this check is here in case we relax the checks in 777 // SetOptionsSnaps 778 if err := w.policy.allowsDangerousFeatures(); err != nil { 779 return nil, err 780 } 781 } 782 return toDownload, nil 783 case toDownloadImplicit: 784 return w.modelSnapsToDownload(w.policy.implicitSnaps(w.availableByMode)) 785 case toDownloadExtra: 786 return w.extraSnapsToDownload(w.optExtraSnaps()) 787 case toDownloadExtraImplicit: 788 return w.extraSnapsToDownload(w.policy.implicitExtraSnaps(w.availableByMode)) 789 default: 790 panic(fmt.Sprintf("unknown to-download set: %d", w.toDownload)) 791 } 792 } 793 794 func (w *Writer) resolveChannel(whichSnap string, modSnap *asserts.ModelSnap, optSnap *OptionsSnap) (string, error) { 795 var optChannel string 796 if optSnap != nil { 797 optChannel = optSnap.Channel 798 } 799 if optChannel == "" { 800 optChannel = w.opts.DefaultChannel 801 } 802 803 if modSnap != nil && modSnap.PinnedTrack != "" { 804 resChannel, err := channel.ResolvePinned(modSnap.PinnedTrack, optChannel) 805 if err == channel.ErrPinnedTrackSwitch { 806 return "", fmt.Errorf("option channel %q for %s has a track incompatible with the pinned track from model assertion: %s", optChannel, whichModelSnap(modSnap, w.model), modSnap.PinnedTrack) 807 } 808 if err != nil { 809 // shouldn't happen given that we check that 810 // the inputs parse before 811 return "", fmt.Errorf("internal error: cannot resolve pinned track %q and option channel %q for snap %q", modSnap.PinnedTrack, optChannel, whichSnap) 812 } 813 return resChannel, nil 814 } 815 816 var defaultChannel string 817 if modSnap != nil { 818 defaultChannel = modSnap.DefaultChannel 819 if defaultChannel == "" { 820 defaultChannel = w.policy.modelSnapDefaultChannel() 821 } 822 } else { 823 defaultChannel = w.policy.extraSnapDefaultChannel() 824 } 825 826 resChannel, err := channel.Resolve(defaultChannel, optChannel) 827 if err != nil { 828 // shouldn't happen given that we check that 829 // the inputs parse before 830 return "", fmt.Errorf("internal error: cannot resolve model default channel %q and option channel %q for snap %q", defaultChannel, optChannel, whichSnap) 831 } 832 return resChannel, nil 833 } 834 835 func (w *Writer) checkBase(info *snap.Info, modes []string) error { 836 // Sanity check, note that we could support this case 837 // if we have a use-case but it requires changes in the 838 // devicestate/firstboot.go ordering code. 839 if info.Type() == snap.TypeGadget && !w.model.Classic() && info.Base != w.model.Base() { 840 return fmt.Errorf("cannot use gadget snap because its base %q is different from model base %q", info.Base, w.model.Base()) 841 } 842 843 // snap explicitly listed as not needing a base snap (e.g. a content-only snap) 844 if info.Base == "none" { 845 return nil 846 } 847 848 return w.policy.checkBase(info, modes, w.availableByMode) 849 } 850 851 func (w *Writer) downloaded(seedSnaps []*SeedSnap) error { 852 if w.availableSnaps == nil { 853 w.availableSnaps = naming.NewSnapSet(nil) 854 w.availableByMode = make(map[string]*naming.SnapSet) 855 w.availableByMode["run"] = naming.NewSnapSet(nil) 856 } 857 858 for _, sn := range seedSnaps { 859 if sn.Info == nil { 860 return fmt.Errorf("internal error: before seedwriter.Writer.Downloaded snap %q Info should have been set", sn.SnapName()) 861 } 862 w.availableSnaps.Add(sn) 863 for _, mode := range sn.modes() { 864 byMode := w.availableByMode[mode] 865 if byMode == nil { 866 byMode = naming.NewSnapSet(nil) 867 w.availableByMode[mode] = byMode 868 } 869 byMode.Add(sn) 870 } 871 } 872 873 for _, sn := range seedSnaps { 874 info := sn.Info 875 if !sn.local { 876 if info.ID() == "" { 877 return fmt.Errorf("internal error: before seedwriter.Writer.Downloaded snap %q snap-id should have been set", sn.SnapName()) 878 } 879 } 880 if info.ID() != "" { 881 if sn.ARefs == nil { 882 return fmt.Errorf("internal error: before seedwriter.Writer.Downloaded snap %q ARefs should have been set", sn.SnapName()) 883 } 884 } 885 886 // TODO: optionally check that model snap name and 887 // info snap name match 888 889 if err := checkType(sn, w.model); err != nil { 890 return err 891 } 892 893 needsClassic := info.NeedsClassic() 894 if needsClassic && !w.model.Classic() { 895 return fmt.Errorf("cannot use classic snap %q in a core system", info.SnapName()) 896 } 897 898 modes := sn.modes() 899 900 if err := w.checkBase(info, modes); err != nil { 901 return err 902 } 903 // error about missing default providers 904 for dp := range snap.NeededDefaultProviders(info) { 905 if !w.policy.checkAvailable(naming.Snap(dp), modes, w.availableByMode) { 906 // TODO: have a way to ignore this issue on a snap by snap basis? 907 return fmt.Errorf("cannot use snap %q without its default content provider %q being added explicitly%s", info.SnapName(), dp, errorMsgForModesSuffix(modes)) 908 } 909 } 910 911 if err := w.checkPublisher(sn); err != nil { 912 return err 913 } 914 } 915 916 return nil 917 } 918 919 // Downloaded checks the downloaded snaps metadata provided via 920 // setting it into the SeedSnaps returned by the previous 921 // SnapsToDownload. It also returns whether the seed snap set is 922 // complete or SnapsToDownload should be called again. 923 func (w *Writer) Downloaded() (complete bool, err error) { 924 if err := w.checkStep(downloadedStep); err != nil { 925 return false, err 926 } 927 928 var considered []*SeedSnap 929 switch w.toDownload { 930 default: 931 panic(fmt.Sprintf("unknown to-download set: %d", w.toDownload)) 932 case toDownloadImplicit: 933 fallthrough 934 case toDownloadModel: 935 considered = w.snapsFromModel 936 case toDownloadExtraImplicit: 937 fallthrough 938 case toDownloadExtra: 939 considered = w.extraSnaps 940 } 941 942 considered = considered[len(considered)-w.toDownloadConsideredNum:] 943 err = w.downloaded(considered) 944 if err != nil { 945 return false, err 946 } 947 948 switch w.toDownload { 949 case toDownloadModel: 950 implicitNeeded, err := w.policy.needsImplicitSnaps(w.availableByMode) 951 if err != nil { 952 return false, err 953 } 954 if implicitNeeded { 955 w.toDownload = toDownloadImplicit 956 w.expectedStep = snapsToDownloadStep 957 return false, nil 958 } 959 fallthrough 960 case toDownloadImplicit: 961 if w.extraSnapsGuessNum > 0 { 962 w.toDownload = toDownloadExtra 963 w.expectedStep = snapsToDownloadStep 964 return false, nil 965 } 966 case toDownloadExtra: 967 implicitNeeded, err := w.policy.needsImplicitSnaps(w.availableByMode) 968 if err != nil { 969 return false, err 970 } 971 if implicitNeeded { 972 w.toDownload = toDownloadExtraImplicit 973 w.expectedStep = snapsToDownloadStep 974 return false, nil 975 } 976 case toDownloadExtraImplicit: 977 // nothing to do 978 // TODO: consider generalizing the logic and optionally asking 979 // the policy again 980 default: 981 panic(fmt.Sprintf("unknown to-download set: %d", w.toDownload)) 982 } 983 984 return true, nil 985 } 986 987 func (w *Writer) checkPublisher(sn *SeedSnap) error { 988 if sn.local && sn.ARefs == nil { 989 // nothing to do 990 return nil 991 } 992 info := sn.Info 993 var kind string 994 switch info.Type() { 995 case snap.TypeKernel: 996 kind = "kernel" 997 case snap.TypeGadget: 998 kind = "gadget" 999 default: 1000 return nil 1001 } 1002 // TODO: share helpers with devicestate if the policy becomes much more complicated 1003 snapDecl, err := w.snapDecl(sn) 1004 if err != nil { 1005 return err 1006 } 1007 publisher := snapDecl.PublisherID() 1008 if publisher != w.model.BrandID() && publisher != "canonical" { 1009 return fmt.Errorf("cannot use %s %q published by %q for model by %q", kind, info.SnapName(), publisher, w.model.BrandID()) 1010 } 1011 return nil 1012 } 1013 1014 func (w *Writer) snapDecl(sn *SeedSnap) (*asserts.SnapDeclaration, error) { 1015 for _, ref := range sn.ARefs { 1016 if ref.Type == asserts.SnapDeclarationType { 1017 a, err := ref.Resolve(w.db.Find) 1018 if err != nil { 1019 return nil, fmt.Errorf("internal error: lost saved assertion") 1020 } 1021 return a.(*asserts.SnapDeclaration), nil 1022 } 1023 } 1024 return nil, fmt.Errorf("internal error: snap %q has no snap-declaration set", sn.SnapName()) 1025 } 1026 1027 // Warnings returns the warning messages produced so far. No warnings 1028 // should be generated after Downloaded signaled complete. 1029 func (w *Writer) Warnings() []string { 1030 return w.warnings 1031 } 1032 1033 // SeedSnaps checks seed snaps and copies local snaps into the seed using copySnap. 1034 func (w *Writer) SeedSnaps(copySnap func(name, src, dst string) error) error { 1035 if err := w.checkStep(seedSnapsStep); err != nil { 1036 return err 1037 } 1038 1039 seedSnaps := func(snaps []*SeedSnap) error { 1040 for _, sn := range snaps { 1041 info := sn.Info 1042 if !sn.local { 1043 expectedPath, err := w.tree.snapPath(sn) 1044 if err != nil { 1045 return err 1046 } 1047 if sn.Path != expectedPath { 1048 return fmt.Errorf("internal error: before seedwriter.Writer.SeedSnaps snap %q Path should have been set to %q", sn.SnapName(), expectedPath) 1049 } 1050 if !osutil.FileExists(expectedPath) { 1051 return fmt.Errorf("internal error: before seedwriter.Writer.SeedSnaps snap file %q should exist", expectedPath) 1052 } 1053 } else { 1054 var snapPath func(*SeedSnap) (string, error) 1055 if sn.Info.ID() != "" { 1056 // actually asserted 1057 snapPath = w.tree.snapPath 1058 } else { 1059 // purely local 1060 snapPath = w.tree.localSnapPath 1061 } 1062 dst, err := snapPath(sn) 1063 if err != nil { 1064 return err 1065 } 1066 if err := copySnap(info.SnapName(), sn.Path, dst); err != nil { 1067 return err 1068 } 1069 // record final destination path 1070 sn.Path = dst 1071 } 1072 } 1073 return nil 1074 } 1075 1076 if err := seedSnaps(w.snapsFromModel); err != nil { 1077 return err 1078 } 1079 if err := seedSnaps(w.extraSnaps); err != nil { 1080 return err 1081 } 1082 1083 return nil 1084 } 1085 1086 // WriteMeta writes seed metadata and assertions into the seed. 1087 func (w *Writer) WriteMeta() error { 1088 if err := w.checkStep(writeMetaStep); err != nil { 1089 return err 1090 } 1091 1092 snapsFromModel := w.snapsFromModel 1093 extraSnaps := w.extraSnaps 1094 1095 if err := w.tree.writeAssertions(w.db, w.modelRefs, snapsFromModel, extraSnaps); err != nil { 1096 return err 1097 } 1098 1099 return w.tree.writeMeta(snapsFromModel, extraSnaps) 1100 } 1101 1102 // query accessors 1103 1104 func (w *Writer) checkSnapsAccessor() error { 1105 if w.expectedStep < seedSnapsStep { 1106 return fmt.Errorf("internal error: seedwriter.Writer cannot query seed snaps before Downloaded signaled complete") 1107 } 1108 return nil 1109 } 1110 1111 // BootSnaps returns the seed snaps involved in the boot process. 1112 // It can be invoked only after Downloaded returns complete == 1113 // true. It returns an error for classic models as for those no snaps 1114 // participate in boot before user space. 1115 func (w *Writer) BootSnaps() ([]*SeedSnap, error) { 1116 if err := w.checkSnapsAccessor(); err != nil { 1117 return nil, err 1118 } 1119 if w.model.Classic() { 1120 return nil, fmt.Errorf("no snaps participating in boot on classic") 1121 } 1122 var bootSnaps []*SeedSnap 1123 for _, sn := range w.snapsFromModel { 1124 bootSnaps = append(bootSnaps, sn) 1125 if sn.Info.Type() == snap.TypeGadget { 1126 break 1127 1128 } 1129 } 1130 return bootSnaps, nil 1131 } 1132 1133 // UnassertedSnaps returns references for all unasserted snaps in the seed. 1134 // It can be invoked only after Downloaded returns complete == 1135 // true. 1136 func (w *Writer) UnassertedSnaps() ([]naming.SnapRef, error) { 1137 if err := w.checkSnapsAccessor(); err != nil { 1138 return nil, err 1139 } 1140 var res []naming.SnapRef 1141 for _, sn := range w.snapsFromModel { 1142 if sn.Info.ID() != "" { 1143 continue 1144 } 1145 res = append(res, sn.SnapRef) 1146 } 1147 1148 for _, sn := range w.extraSnaps { 1149 if sn.Info.ID() != "" { 1150 continue 1151 } 1152 res = append(res, sn.SnapRef) 1153 } 1154 return res, nil 1155 }