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