many: let provide a SnapHandler to Seed.Load*Meta*

Merge pull request #11710 from pedronis/seed-snap-handling

This allows to perform dedicated handling for seed snaps (like copying
them already) together with digest computation.

SnapHandler is slightly complex because of unasserted vs asserted
snaps and the differences how UC16/18 vs 20+ seeds are processed.

Before there was always caching of essential snaps across
LoadEssentialMeta* and LoadMeta, this is turned off if handlers are
provided, OTOH specific caching can be done by handler logic if it makes sense.
This commit is contained in:
Samuele Pedroni
2022-04-26 12:54:07 +02:00
committed by GitHub
14 changed files with 709 additions and 97 deletions

View File

@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2016 Canonical Ltd
* Copyright (C) 2022 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -86,13 +86,24 @@ func CrossCheck(instanceName, snapSHA3_384 string, snapSize uint64, si *snap.Sid
return nil
}
// DeriveSideInfo tries to construct a SideInfo for the given snap using its digest to find the relevant snap assertions with the information in the given database. It will fail with an asserts.NotFoundError if it cannot find them.
// DeriveSideInfo tries to construct a SideInfo for the given snap
// using its digest to find the relevant snap assertions with the
// information in the given database. It will fail with an
// asserts.NotFoundError if it cannot find them.
func DeriveSideInfo(snapPath string, db Finder) (*snap.SideInfo, error) {
snapSHA3_384, snapSize, err := asserts.SnapFileSHA3_384(snapPath)
if err != nil {
return nil, err
}
return DeriveSideInfoFromDigestAndSize(snapPath, snapSHA3_384, snapSize, db)
}
// DeriveSideInfoFromDigestAndSize tries to construct a SideInfo
// using digest and size as provided for the snap to find the relevant
// snap assertions with the information in the given database. It will
// fail with an asserts.NotFoundError if it cannot find them.
func DeriveSideInfoFromDigestAndSize(snapPath string, snapSHA3_384 string, snapSize uint64, db Finder) (*snap.SideInfo, error) {
// get relevant assertions and reconstruct metadata
a, err := db.Find(asserts.SnapRevisionType, map[string]string{
"snap-sha3-384": snapSHA3_384,

View File

@@ -612,7 +612,7 @@ func (s *imageSuite) loadSeed(c *C, seeddir string) (essSnaps []*seed.Snap, runS
err = sd.LoadAssertions(db, commitTo)
c.Assert(err, IsNil)
err = sd.LoadMeta(seed.AllModes, timings.New(nil))
err = sd.LoadMeta(seed.AllModes, nil, timings.New(nil))
c.Assert(err, IsNil)
essSnaps = sd.EssentialSnaps()

View File

@@ -118,7 +118,7 @@ func writePreseedAssertion(artifactDigest []byte, opts *preseedOpts) error {
model := sd.Model()
tm := timings.New(nil)
if err := sd.LoadMeta("run", tm); err != nil {
if err := sd.LoadMeta("run", nil, tm); err != nil {
return err
}

View File

@@ -124,7 +124,11 @@ func (fs *FakeSeed) LoadEssentialMeta(essentialTypes []snap.Type, tm timings.Mea
return fs.LoadMetaErr
}
func (fs *FakeSeed) LoadMeta(mode string, tm timings.Measurer) error {
func (fs *FakeSeed) LoadEssentialMetaWithSnapHandler(essentialTypes []snap.Type, handler seed.SnapHandler, tm timings.Measurer) error {
return fs.LoadMetaErr
}
func (fs *FakeSeed) LoadMeta(mode string, handler seed.SnapHandler, tm timings.Measurer) error {
return fs.LoadMetaErr
}

View File

@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2014-2020 Canonical Ltd
* Copyright (C) 2014-2022 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -126,7 +126,7 @@ func populateStateFromSeedImpl(st *state.State, opts *populateStateFromSeedOptio
}
timings.Run(tm, "load-verified-snap-metadata", "load verified snap metadata from seed", func(nested timings.Measurer) {
err = deviceSeed.LoadMeta(mode, nested)
err = deviceSeed.LoadMeta(mode, nil, nested)
})
if release.OnClassic && err == seed.ErrNoMeta {
if preseed {

View File

@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2016-2021 Canonical Ltd
* Copyright (C) 2016-2022 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -29,6 +29,7 @@ import (
"github.com/snapcore/snapd/asserts/sysdb"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snapfile"
"github.com/snapcore/snapd/timings"
)
var trusted = sysdb.Trusted()
@@ -150,3 +151,13 @@ func findBrand(seed Seed, db asserts.RODatabase) (*asserts.Account, error) {
}
return a.(*asserts.Account), nil
}
type defaultSnapHandler struct{}
func (h defaultSnapHandler) HandleUnassertedSnap(name, path string, _ timings.Measurer) error {
return nil
}
func (h defaultSnapHandler) HandleAndDigestAssertedSnap(name, path string, essType snap.Type, _ *asserts.SnapRevision, _ func(string, uint64) (snap.Revision, error), _ timings.Measurer) (string, uint64, error) {
return asserts.SnapFileSHA3_384(path)
}

View File

@@ -106,6 +106,19 @@ type Seed interface {
// It will panic if called before LoadAssertions.
LoadEssentialMeta(essentialTypes []snap.Type, tm timings.Measurer) error
// LoadEssentialMetaWithSnapHandler loads the seed's snaps metadata
// for the essential snaps with types in the essentialTypes
// set while verifying them against assertions. It can return
// ErrNoMeta if there is no metadata nor snaps in the seed,
// this is legitimate only on classic. It can be called
// multiple times if needed before invoking LoadMeta.
// It will panic if called before LoadAssertions.
// A SnapHandler can be passed to perform dedicated seed snap
// handling at the same time as digest computation.
// No caching of essential snaps across Load*Meta* methods is
// performed if a handler is provided.
LoadEssentialMetaWithSnapHandler(essentialTypes []snap.Type, handler SnapHandler, tm timings.Measurer) error
// LoadMeta loads the seed and seed's snaps metadata while
// verifying the underlying snaps against assertions. It can
// return ErrNoMeta if there is no metadata nor snaps in the
@@ -115,7 +128,11 @@ type Seed interface {
// load the metadata only for the snaps of that mode.
// At which point ModeSnaps will only accept that mode
// and Iter and NumSnaps only consider the snaps for that mode.
LoadMeta(mode string, tm timings.Measurer) error
// An optional SnapHandler can be passed to perform dedicated
// seed snap handling at the same time as digest computation.
// No caching of essential snaps across Load*Meta* methods is
// performed if a handler is provided.
LoadMeta(mode string, handler SnapHandler, tm timings.Measurer) error
// UsesSnapdSnap returns whether the system as defined by the
// seed will use the snapd snap, after LoadMeta.
@@ -140,6 +157,24 @@ type Seed interface {
Iter(f func(sn *Snap) error) error
}
// A SnapHandler can be used to perform any dedicated handling of seed
// snaps and their digest computation while seed snap metadata loading
// and verification is being performed.
type SnapHandler interface {
// HandleAndDigestAssertedSnap should compute the digest of
// the given snap and perform any dedicated
// handling. essentialType is provided only for essential
// snaps.
// snapRev is provided by UC20+ seeds.
// deriveRev is provided by UC16/18 seeds, it can be used
// to get early access to the snap revision based on the digest.
HandleAndDigestAssertedSnap(name, path string, essentialType snap.Type, snapRev *asserts.SnapRevision, deriveRev func(snapSHA3_384 string, snapSize uint64) (snap.Revision, error), tm timings.Measurer) (snapSHA3_384 string, snapSize uint64, err error)
// HandleUnassertedSnap should perfrom any dedicated handling
// for the given unasserted snap.
HandleUnassertedSnap(name, path string, tm timings.Measurer) error
}
// Open returns a Seed implementation for the seed at seedDir.
// label if not empty is used to identify a Core 20 recovery system seed.
func Open(seedDir, label string) (Seed, error) {

View File

@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2014-2020 Canonical Ltd
* Copyright (C) 2014-2022 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -124,11 +124,16 @@ func (s *seed16) SetParallelism(int) {
// ignored
}
func (s *seed16) addSnap(sn *internal.Snap16, pinnedTrack string, cache map[string]*Snap, tm timings.Measurer) (*Snap, error) {
func (s *seed16) addSnap(sn *internal.Snap16, essType snap.Type, pinnedTrack string, handler SnapHandler, cache map[string]*Snap, tm timings.Measurer) (*Snap, error) {
path := filepath.Join(s.seedDir, "snaps", sn.File)
_, defaultHandler := handler.(defaultSnapHandler)
seedSnap := cache[path]
if seedSnap == nil {
// not cached, or ignore the cache if a non-default handler
// was passed, otherwise it would not be called which could be
// unexpected
if seedSnap == nil || !defaultHandler {
snapChannel := sn.Channel
if pinnedTrack != "" {
var err error
@@ -147,12 +152,33 @@ func (s *seed16) addSnap(sn *internal.Snap16, pinnedTrack string, cache map[stri
var sideInfo snap.SideInfo
if sn.Unasserted {
if err := handler.HandleUnassertedSnap(sn.Name, path, tm); err != nil {
return nil, err
}
sideInfo.RealName = sn.Name
} else {
var si *snap.SideInfo
var err error
deriveRev := func(snapSHA3_384 string, snapSize uint64) (snap.Revision, error) {
if si == nil {
var err error
si, err = snapasserts.DeriveSideInfoFromDigestAndSize(path, snapSHA3_384, snapSize, s.db)
if err != nil {
return snap.Revision{}, err
}
}
return si.Revision, nil
}
timings.Run(tm, "derive-side-info", fmt.Sprintf("hash and derive side info for snap %q", sn.Name), func(nested timings.Measurer) {
si, err = snapasserts.DeriveSideInfo(path, s.db)
var snapSHA3_384 string
var snapSize uint64
snapSHA3_384, snapSize, err = handler.HandleAndDigestAssertedSnap(sn.Name, path, essType, nil, deriveRev, tm)
if err != nil {
return
}
// sets si too
_, err = deriveRev(snapSHA3_384, snapSize)
})
if asserts.IsNotFound(err) {
return nil, fmt.Errorf("cannot find signatures with metadata for snap %q (%q)", sn.Name, path)
@@ -215,7 +241,7 @@ func (s *seed16) resetSnaps() {
s.essentialSnapsNum = 0
}
func (s *seed16) loadEssentialMeta(essentialTypes []snap.Type, required *naming.SnapSet, added map[string]bool, tm timings.Measurer) error {
func (s *seed16) loadEssentialMeta(essentialTypes []snap.Type, required *naming.SnapSet, handler SnapHandler, added map[string]bool, tm timings.Measurer) error {
model := s.Model()
seeding := make(map[string]*internal.Snap16, len(s.yamlSnaps))
@@ -266,7 +292,7 @@ func (s *seed16) loadEssentialMeta(essentialTypes []snap.Type, required *naming.
return nil, &essentialSnapMissingError{SnapName: snapName}
}
seedSnap, err := s.addSnap(yamlSnap, pinnedTrack, s.essCache, tm)
seedSnap, err := s.addSnap(yamlSnap, essType, pinnedTrack, handler, s.essCache, tm)
if err != nil {
return nil, err
}
@@ -339,6 +365,10 @@ func (s *seed16) loadEssentialMeta(essentialTypes []snap.Type, required *naming.
}
func (s *seed16) LoadEssentialMeta(essentialTypes []snap.Type, tm timings.Measurer) error {
return s.LoadEssentialMetaWithSnapHandler(essentialTypes, nil, tm)
}
func (s *seed16) LoadEssentialMetaWithSnapHandler(essentialTypes []snap.Type, handler SnapHandler, tm timings.Measurer) error {
model := s.Model()
if err := s.loadYaml(); err != nil {
@@ -350,10 +380,17 @@ func (s *seed16) LoadEssentialMeta(essentialTypes []snap.Type, tm timings.Measur
s.resetSnaps()
return s.loadEssentialMeta(essentialTypes, required, added, tm)
if handler == nil {
handler = defaultSnapHandler{}
}
if len(essentialTypes) == 0 {
essentialTypes = nil
}
return s.loadEssentialMeta(essentialTypes, required, handler, added, tm)
}
func (s *seed16) LoadMeta(mode string, tm timings.Measurer) error {
func (s *seed16) LoadMeta(mode string, handler SnapHandler, tm timings.Measurer) error {
if mode != AllModes && mode != "run" {
return fmt.Errorf("internal error: Core 16/18 have only run mode, got: %s", mode)
}
@@ -369,7 +406,11 @@ func (s *seed16) LoadMeta(mode string, tm timings.Measurer) error {
s.resetSnaps()
if err := s.loadEssentialMeta(nil, required, added, tm); err != nil {
if handler == nil {
handler = defaultSnapHandler{}
}
if err := s.loadEssentialMeta(nil, required, handler, added, tm); err != nil {
return err
}
@@ -378,7 +419,7 @@ func (s *seed16) LoadMeta(mode string, tm timings.Measurer) error {
if added[sn.Name] {
continue
}
seedSnap, err := s.addSnap(sn, "", nil, tm)
seedSnap, err := s.addSnap(sn, "", "", handler, nil, tm)
if err != nil {
return err
}

View File

@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2019-2020 Canonical Ltd
* Copyright (C) 2019-2022 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -207,7 +207,7 @@ func (s *seed16Suite) TestLoadMetaNoMeta(c *C) {
err = s.seed16.LoadAssertions(s.db, s.commitTo)
c.Assert(err, IsNil)
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
c.Check(err, Equals, seed.ErrNoMeta)
}
@@ -237,7 +237,7 @@ func (s *seed16Suite) TestLoadMetaInvalidSeedYaml(c *C) {
err = ioutil.WriteFile(filepath.Join(s.SeedDir, "seed.yaml"), content, 0644)
c.Assert(err, IsNil)
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
c.Check(err, ErrorMatches, `cannot read seed yaml: invalid risk in channel name: track/not-a-risk`)
}
@@ -424,7 +424,7 @@ func (s *seed16Suite) TestLoadMetaCore16Minimal(c *C) {
err := s.seed16.LoadAssertions(s.db, s.commitTo)
c.Assert(err, IsNil)
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
c.Assert(err, IsNil)
c.Check(s.seed16.UsesSnapdSnap(), Equals, false)
@@ -470,7 +470,7 @@ func (s *seed16Suite) TestLoadMetaCore16(c *C) {
err := s.seed16.LoadAssertions(s.db, s.commitTo)
c.Assert(err, IsNil)
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
c.Assert(err, IsNil)
essSnaps := s.seed16.EssentialSnaps()
@@ -508,7 +508,7 @@ func (s *seed16Suite) TestLoadMetaCore18Minimal(c *C) {
err := s.seed16.LoadAssertions(s.db, s.commitTo)
c.Assert(err, IsNil)
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
c.Assert(err, IsNil)
c.Check(s.seed16.UsesSnapdSnap(), Equals, true)
@@ -564,7 +564,7 @@ func (s *seed16Suite) TestLoadMetaCore18(c *C) {
err := s.seed16.LoadAssertions(s.db, s.commitTo)
c.Assert(err, IsNil)
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
c.Assert(err, IsNil)
essSnaps := s.seed16.EssentialSnaps()
@@ -635,7 +635,7 @@ func (s *seed16Suite) TestLoadMetaClassicNothing(c *C) {
err := s.seed16.LoadAssertions(s.db, s.commitTo)
c.Assert(err, IsNil)
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
c.Assert(err, IsNil)
c.Check(s.seed16.UsesSnapdSnap(), Equals, false)
@@ -656,7 +656,7 @@ func (s *seed16Suite) TestLoadMetaClassicCore(c *C) {
err := s.seed16.LoadAssertions(s.db, s.commitTo)
c.Assert(err, IsNil)
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
c.Assert(err, IsNil)
c.Check(s.seed16.UsesSnapdSnap(), Equals, false)
@@ -697,7 +697,7 @@ func (s *seed16Suite) TestLoadMetaClassicCoreWithGadget(c *C) {
err := s.seed16.LoadAssertions(s.db, s.commitTo)
c.Assert(err, IsNil)
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
c.Assert(err, IsNil)
c.Check(s.seed16.UsesSnapdSnap(), Equals, false)
@@ -737,7 +737,7 @@ func (s *seed16Suite) TestLoadMetaClassicSnapd(c *C) {
err := s.seed16.LoadAssertions(s.db, s.commitTo)
c.Assert(err, IsNil)
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
c.Assert(err, IsNil)
c.Check(s.seed16.UsesSnapdSnap(), Equals, true)
@@ -782,7 +782,7 @@ func (s *seed16Suite) TestLoadMetaClassicSnapdWithGadget(c *C) {
err := s.seed16.LoadAssertions(s.db, s.commitTo)
c.Assert(err, IsNil)
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
c.Assert(err, IsNil)
c.Check(s.seed16.UsesSnapdSnap(), Equals, true)
@@ -832,7 +832,7 @@ func (s *seed16Suite) TestLoadMetaClassicSnapdWithGadget18(c *C) {
err := s.seed16.LoadAssertions(s.db, s.commitTo)
c.Assert(err, IsNil)
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
c.Assert(err, IsNil)
c.Check(s.seed16.UsesSnapdSnap(), Equals, true)
@@ -901,7 +901,7 @@ func (s *seed16Suite) TestLoadMetaCore18Local(c *C) {
err := s.seed16.LoadAssertions(s.db, s.commitTo)
c.Assert(err, IsNil)
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
c.Assert(err, IsNil)
essSnaps := s.seed16.EssentialSnaps()
@@ -953,6 +953,86 @@ func (s *seed16Suite) TestLoadMetaCore18Local(c *C) {
})
}
func (s *seed16Suite) TestLoadMetaCore18SnapHandler(c *C) {
localRequired18Seed := &seed.InternalSnap16{
Name: "required18",
Unasserted: true,
DevMode: true,
}
s.makeSeed(c, map[string]interface{}{
"base": "core18",
"kernel": "pc-kernel=18",
"gadget": "pc=18",
"required-snaps": []interface{}{"core", "required18"},
}, snapdSeed, core18Seed, kernel18Seed, gadget18Seed, localRequired18Seed)
err := s.seed16.LoadAssertions(s.db, s.commitTo)
c.Assert(err, IsNil)
h := newTestSnapHandler(s.SeedDir)
err = s.seed16.LoadMeta(seed.AllModes, h, s.perfTimings)
c.Assert(err, IsNil)
essSnaps := s.seed16.EssentialSnaps()
c.Check(essSnaps, HasLen, 4)
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
Path: s.expectedPath("snapd"),
SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
EssentialType: snap.TypeSnapd,
Essential: true,
Required: true,
Channel: "stable",
}, {
Path: s.expectedPath("core18"),
SideInfo: &s.AssertedSnapInfo("core18").SideInfo,
EssentialType: snap.TypeBase,
Essential: true,
Required: true,
Channel: "stable",
}, {
Path: s.expectedPath("pc-kernel"),
SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
EssentialType: snap.TypeKernel,
Essential: true,
Required: true,
Channel: "18",
}, {
Path: s.expectedPath("pc"),
SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
EssentialType: snap.TypeGadget,
Essential: true,
Required: true,
Channel: "18",
},
})
runSnaps, err := s.seed16.ModeSnaps("run")
c.Assert(err, IsNil)
c.Check(runSnaps, HasLen, 1)
c.Check(runSnaps, DeepEquals, []*seed.Snap{
{
Path: filepath.Join(s.SeedDir, "snaps", "required18_1.0_all.snap"),
SideInfo: &snap.SideInfo{RealName: "required18"},
Required: true,
DevMode: true,
},
})
c.Check(h.asserted, DeepEquals, map[string]string{
"snapd": "snaps/snapd_1.0_all.snap:snapd:1",
"pc-kernel": "snaps/pc-kernel_1.0_all.snap:kernel:1",
"core18": "snaps/core18_1.0_all.snap:base:1",
"pc": "snaps/pc_1.0_all.snap:gadget:1",
})
c.Check(h.unasserted, DeepEquals, map[string]string{
"required18": "snaps/required18_1.0_all.snap",
})
}
func (s *seed16Suite) TestLoadMetaCore18StoreInfo(c *C) {
s.makeSeed(c, map[string]interface{}{
"base": "core18",
@@ -963,7 +1043,7 @@ func (s *seed16Suite) TestLoadMetaCore18StoreInfo(c *C) {
err := s.seed16.LoadAssertions(s.db, s.commitTo)
c.Assert(err, IsNil)
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
c.Assert(err, IsNil)
essSnaps := s.seed16.EssentialSnaps()
@@ -1013,7 +1093,7 @@ func (s *seed16Suite) TestLoadMetaCore18EnforcePinnedTracks(c *C) {
err := s.seed16.LoadAssertions(s.db, s.commitTo)
c.Assert(err, IsNil)
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
c.Assert(err, IsNil)
c.Check(s.seed16.UsesSnapdSnap(), Equals, true)
@@ -1134,7 +1214,7 @@ version: other-base
testSeedSnap16s = t.breakSeed(testSeedSnap16s)
s.writeSeed(c, testSeedSnap16s)
c.Check(seed16.LoadMeta(seed.AllModes, s.perfTimings), ErrorMatches, t.err)
c.Check(seed16.LoadMeta(seed.AllModes, nil, s.perfTimings), ErrorMatches, t.err)
}
}
@@ -1205,13 +1285,17 @@ func (s *seed16Suite) TestLoadEssentialMetaCore18(c *C) {
{[]snap.Type{snap.TypeSnapd, snap.TypeKernel, snap.TypeBase}, []*seed.Snap{snapdSnap, core18Snap, pcKernelSnap}},
{[]snap.Type{snap.TypeGadget, snap.TypeKernel}, []*seed.Snap{pcKernelSnap, pcSnap}},
// degenerate case
{[]snap.Type{}, []*seed.Snap(nil)},
{[]snap.Type{}, []*seed.Snap{snapdSnap, core18Snap, pcKernelSnap, pcSnap}},
{nil, []*seed.Snap{snapdSnap, core18Snap, pcKernelSnap, pcSnap}},
}
for _, t := range tests {
// hide the non-requested snaps to make sure they are not
// accessed
unhide := hideSnaps(c, all, t.onlyTypes)
var unhide func()
if len(t.onlyTypes) != 0 {
unhide = hideSnaps(c, all, t.onlyTypes)
}
seed16, err := seed.Open(s.SeedDir, "")
c.Assert(err, IsNil)
@@ -1233,10 +1317,83 @@ func (s *seed16Suite) TestLoadEssentialMetaCore18(c *C) {
c.Assert(err, IsNil)
c.Check(runSnaps, HasLen, 0)
unhide()
if unhide != nil {
unhide()
}
}
}
func (s *seed16Suite) TestLoadEssentialMetaWithSnapHandlerCore18(c *C) {
r := seed.MockTrusted(s.StoreSigning.Trusted)
defer r()
s.makeSeed(c, map[string]interface{}{
"base": "core18",
"kernel": "pc-kernel=18",
"gadget": "pc=18",
"required-snaps": []interface{}{"core", "required", "required18"},
}, snapdSeed, core18Seed, kernel18Seed, gadget18Seed, requiredSeed, coreSeed, required18Seed)
snapdSnap := &seed.Snap{
Path: s.expectedPath("snapd"),
SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
EssentialType: snap.TypeSnapd,
Essential: true,
Required: true,
Channel: "stable",
}
core18Snap := &seed.Snap{
Path: s.expectedPath("core18"),
SideInfo: &s.AssertedSnapInfo("core18").SideInfo,
EssentialType: snap.TypeBase,
Essential: true,
Required: true,
Channel: "stable",
}
pcKernelSnap := &seed.Snap{
Path: s.expectedPath("pc-kernel"),
SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
EssentialType: snap.TypeKernel,
Essential: true,
Required: true,
Channel: "18",
}
pcSnap := &seed.Snap{
Path: s.expectedPath("pc"),
SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
EssentialType: snap.TypeGadget,
Essential: true,
Required: true,
Channel: "18",
}
expected := []*seed.Snap{snapdSnap, core18Snap, pcKernelSnap, pcSnap}
seed16, err := seed.Open(s.SeedDir, "")
c.Assert(err, IsNil)
err = seed16.LoadAssertions(nil, nil)
c.Assert(err, IsNil)
h := newTestSnapHandler(s.SeedDir)
err = seed16.LoadEssentialMetaWithSnapHandler(nil, h, s.perfTimings)
c.Assert(err, IsNil)
c.Check(seed16.UsesSnapdSnap(), Equals, true)
essSnaps := seed16.EssentialSnaps()
c.Check(essSnaps, HasLen, len(expected))
c.Check(essSnaps, DeepEquals, expected)
c.Check(h.asserted, DeepEquals, map[string]string{
"snapd": "snaps/snapd_1.0_all.snap:snapd:1",
"pc-kernel": "snaps/pc-kernel_1.0_all.snap:kernel:1",
"core18": "snaps/core18_1.0_all.snap:base:1",
"pc": "snaps/pc_1.0_all.snap:gadget:1",
})
}
func (s *seed16Suite) TestLoadEssentialAndMetaCore18(c *C) {
r := seed.MockTrusted(s.StoreSigning.Trusted)
defer r()
@@ -1308,7 +1465,7 @@ func (s *seed16Suite) TestLoadEssentialAndMetaCore18(c *C) {
// caching in place
hideSnaps(c, []*seed.Snap{snapdSnap, core18Snap, pcKernelSnap}, nil)
err = seed16.LoadMeta(seed.AllModes, s.perfTimings)
err = seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
c.Assert(err, IsNil)
c.Check(seed16.UsesSnapdSnap(), Equals, true)

View File

@@ -268,7 +268,7 @@ func (e *noSnapDeclarationError) Error() string {
return fmt.Sprintf("cannot find snap-declaration for snap name: %s", e.snapRef.SnapName())
}
func (s *seed20) lookupVerifiedRevision(snapRef naming.SnapRef, snapsDir string) (snapPath string, snapRev *asserts.SnapRevision, snapDecl *asserts.SnapDeclaration, err error) {
func (s *seed20) lookupVerifiedRevision(snapRef naming.SnapRef, essType snap.Type, handler SnapHandler, snapsDir string, tm timings.Measurer) (snapPath string, snapRev *asserts.SnapRevision, snapDecl *asserts.SnapDeclaration, err error) {
snapID := snapRef.ID()
if snapID != "" {
snapDecl = s.snapDeclsByID[snapID]
@@ -304,7 +304,7 @@ func (s *seed20) lookupVerifiedRevision(snapRef naming.SnapRef, snapsDir string)
return "", nil, nil, fmt.Errorf("cannot validate %q for snap %q (snap-id %q), wrong size", snapPath, snapName, snapID)
}
snapSHA3_384, _, err := asserts.SnapFileSHA3_384(snapPath)
snapSHA3_384, _, err := handler.HandleAndDigestAssertedSnap(snapName, snapPath, essType, snapRev, nil, tm)
if err != nil {
return "", nil, nil, err
}
@@ -317,7 +317,7 @@ func (s *seed20) lookupVerifiedRevision(snapRef naming.SnapRef, snapsDir string)
return snapPath, snapRev, snapDecl, nil
}
func (s *seed20) lookupSnap(snapRef naming.SnapRef, optSnap *internal.Snap20, channel string, snapsDir string, tm timings.Measurer) (*Snap, error) {
func (s *seed20) lookupSnap(snapRef naming.SnapRef, essType snap.Type, optSnap *internal.Snap20, channel string, handler SnapHandler, snapsDir string, tm timings.Measurer) (*Snap, error) {
if optSnap != nil && optSnap.Channel != "" {
channel = optSnap.Channel
}
@@ -330,15 +330,18 @@ func (s *seed20) lookupSnap(snapRef naming.SnapRef, optSnap *internal.Snap20, ch
if err != nil {
return nil, fmt.Errorf("cannot read unasserted snap: %v", err)
}
sideInfo = &snap.SideInfo{RealName: info.SnapName()}
if err := handler.HandleUnassertedSnap(info.SnapName(), path, tm); err != nil {
return nil, err
}
// suppress channel
sideInfo = &snap.SideInfo{RealName: info.SnapName()}
channel = ""
} else {
var err error
timings.Run(tm, "derive-side-info", fmt.Sprintf("hash and derive side info for snap %q", snapRef.SnapName()), func(nested timings.Measurer) {
var snapRev *asserts.SnapRevision
var snapDecl *asserts.SnapDeclaration
path, snapRev, snapDecl, err = s.lookupVerifiedRevision(snapRef, snapsDir)
path, snapRev, snapDecl, err = s.lookupVerifiedRevision(snapRef, essType, handler, snapsDir, tm)
if err == nil {
sideInfo = snapasserts.SideInfoFromSnapAssertions(snapDecl, snapRev)
}
@@ -377,15 +380,19 @@ type snapToConsider struct {
var errSkipped = errors.New("skipped optional snap")
func (s *seed20) doLoadMetaOne(sntoc *snapToConsider, tm timings.Measurer) (*Snap, error) {
func (s *seed20) doLoadMetaOne(sntoc *snapToConsider, handler SnapHandler, tm timings.Measurer) (*Snap, error) {
var snapRef naming.SnapRef
var channel string
var snapsDir string
var essential bool
var essType snap.Type
var required bool
if sntoc.modelSnap != nil {
snapRef = sntoc.modelSnap
essential = sntoc.essential
if essential {
essType = snapTypeFromModel(sntoc.modelSnap)
}
required = essential || sntoc.modelSnap.Presence == "required"
channel = sntoc.modelSnap.DefaultChannel
snapsDir = "../../snaps"
@@ -394,7 +401,7 @@ func (s *seed20) doLoadMetaOne(sntoc *snapToConsider, tm timings.Measurer) (*Sna
channel = "latest/stable"
snapsDir = "snaps"
}
seedSnap, err := s.lookupSnap(snapRef, sntoc.optSnap, channel, snapsDir, tm)
seedSnap, err := s.lookupSnap(snapRef, essType, sntoc.optSnap, channel, handler, snapsDir, tm)
if err != nil {
if _, ok := err.(*noSnapDeclarationError); ok && !required {
// skipped optional snap is ok
@@ -418,26 +425,37 @@ func (s *seed20) doLoadMetaOne(sntoc *snapToConsider, tm timings.Measurer) (*Sna
// we need to add the gadget base here
}
seedSnap.EssentialType = snapTypeFromModel(sntoc.modelSnap)
seedSnap.EssentialType = essType
}
return seedSnap, nil
}
func (s *seed20) doLoadMeta(tm timings.Measurer) error {
// setup essential snaps cache
if s.essCache == nil {
// 4 = snapd+base+kernel+gadget
s.essCache = make(map[string]*Snap, 4)
}
cacheEssential := func(snType string, essSnap *Snap) {
s.essCacheMu.Lock()
defer s.essCacheMu.Unlock()
s.essCache[snType] = essSnap
}
cachedEssential := func(snType string) *Snap {
s.essCacheMu.Lock()
defer s.essCacheMu.Unlock()
return s.essCache[snType]
func (s *seed20) doLoadMeta(handler SnapHandler, tm timings.Measurer) error {
var cacheEssential func(snType string, essSnap *Snap)
var cachedEssential func(snType string) *Snap
if handler != nil {
// ignore caching if not using the default handler
// otherwise it would not always be called which could
// be unexpected
cacheEssential = func(string, *Snap) {}
cachedEssential = func(string) *Snap { return nil }
} else {
handler = defaultSnapHandler{}
// setup essential snaps cache
if s.essCache == nil {
// 4 = snapd+base+kernel+gadget
s.essCache = make(map[string]*Snap, 4)
}
cacheEssential = func(snType string, essSnap *Snap) {
s.essCacheMu.Lock()
defer s.essCacheMu.Unlock()
s.essCache[snType] = essSnap
}
cachedEssential = func(snType string) *Snap {
s.essCacheMu.Lock()
defer s.essCacheMu.Unlock()
return s.essCache[snType]
}
}
runMode := []string{"run"}
@@ -478,7 +496,7 @@ func (s *seed20) doLoadMeta(tm timings.Measurer) error {
}
if seedSnap == nil {
var err error
seedSnap, err = s.doLoadMetaOne(&sntoc, jtm)
seedSnap, err = s.doLoadMetaOne(&sntoc, handler, jtm)
if err != nil {
if err == errSkipped {
continue
@@ -549,7 +567,7 @@ func (s *seed20) considerModelSnap(modelSnap *asserts.ModelSnap, essential bool,
}
}
func (s *seed20) LoadMeta(mode string, tm timings.Measurer) error {
func (s *seed20) LoadMeta(mode string, handler SnapHandler, tm timings.Measurer) error {
const otherSnapsFollow = true
if err := s.queueEssentialMeta(nil, otherSnapsFollow, tm); err != nil {
return err
@@ -574,10 +592,14 @@ func (s *seed20) LoadMeta(mode string, tm timings.Measurer) error {
}
}
return s.doLoadMeta(tm)
return s.doLoadMeta(handler, tm)
}
func (s *seed20) LoadEssentialMeta(essentialTypes []snap.Type, tm timings.Measurer) error {
return s.LoadEssentialMetaWithSnapHandler(essentialTypes, nil, tm)
}
func (s *seed20) LoadEssentialMetaWithSnapHandler(essentialTypes []snap.Type, handler SnapHandler, tm timings.Measurer) error {
var filterEssential func(*asserts.ModelSnap) bool
if len(essentialTypes) != 0 {
filterEssential = essentialSnapTypesToModelFilter(essentialTypes)
@@ -589,7 +611,7 @@ func (s *seed20) LoadEssentialMeta(essentialTypes []snap.Type, tm timings.Measur
return err
}
err := s.doLoadMeta(tm)
err := s.doLoadMeta(handler, tm)
if err != nil {
return err
}

File diff suppressed because it is too large Load Diff

View File

@@ -363,7 +363,7 @@ func ValidateSeed(c *C, root, label string, usesSnapd bool, trusted []asserts.As
err = sd.LoadAssertions(db, commitTo)
c.Assert(err, IsNil)
err = sd.LoadMeta(seed.AllModes, tm)
err = sd.LoadMeta(seed.AllModes, nil, tm)
c.Assert(err, IsNil)
// core18/core20 use the snapd snap, old core does not

View File

@@ -96,7 +96,7 @@ func ValidateFromYaml(seedYamlFile string) error {
}
tm := timings.New(nil)
if err := seed.LoadMeta(AllModes, tm); err != nil {
if err := seed.LoadMeta(AllModes, nil, tm); err != nil {
if missingErr, ok := err.(*essentialSnapMissingError); ok {
if seed.Model().Classic() && missingErr.SnapName == "core" {
err = fmt.Errorf("essential snap core or snapd must be part of the seed")

View File

@@ -44,7 +44,7 @@ environment:
UBUNTU_IMAGE_SNAP_CHANNEL: "latest/candidate"
# controls whether ubuntu-image is built using the current snapd tree as a
# dependency or the one listed in its go.mod
UBUNTU_IMAGE_ALLOW_API_BREAK: '$(HOST: echo "${SPREAD_UBUNTU_IMAGE_ALLOW_API_BREAK:-true}")'
UBUNTU_IMAGE_ALLOW_API_BREAK: '$(HOST: echo "${SPREAD_UBUNTU_IMAGE_ALLOW_API_BREAK:-false}")'
CORE_CHANNEL: '$(HOST: echo "${SPREAD_CORE_CHANNEL:-edge}")'
BASE_CHANNEL: '$(HOST: echo "${SPREAD_BASE_CHANNEL:-edge}")'
KERNEL_CHANNEL: '$(HOST: echo "${SPREAD_KERNEL_CHANNEL:-edge}")'