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