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