Skip to content

Commit

Permalink
seed,image: changes necessary for ubuntu-image to support preseeding …
Browse files Browse the repository at this point in the history
…extra snaps in classic images

Merge pull request #11355 from GlenPickle/master

There are two changes here that are needed for ubuntu-image to support the --snap flag for classic image builds.

seed/seed.go: This change adds the NumSnaps and Iter functions to the seed interface. This is needed because there is a specific use case that requires ubuntu-image to parse seed.yaml and iterate through the snaps to create a list of the snaps and their channels. There is already a TODO for this task.

image/image_linux.go: This change is necessary to use image.Prepare on a prebuilt rootfs for which there is no model assertion. In this case the GenericClassicModel from sysdb is used
  • Loading branch information
pedronis authored Feb 25, 2022
2 parents 2607e64 + caed935 commit 41ba290
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 9 deletions.
8 changes: 8 additions & 0 deletions cmd/snap-preseed/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,14 @@ func (fs *Fake16Seed) ModeSnaps(mode string) ([]*seed.Snap, error) {
return nil, nil
}

func (fs *Fake16Seed) NumSnaps() int {
return 0
}

func (fs *Fake16Seed) Iter(f func(sn *seed.Snap) error) error {
return nil
}

func (s *startPreseedSuite) TestSystemSnapFromSeed(c *C) {
tmpDir := c.MkDir()

Expand Down
9 changes: 9 additions & 0 deletions image/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package image

import (
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/gadget"
"github.com/snapcore/snapd/overlord/auth"
"github.com/snapcore/snapd/store"
Expand Down Expand Up @@ -62,3 +63,11 @@ func MockWriteResolvedContent(f func(prepareImageDir string, info *gadget.Info,
writeResolvedContent = oldWriteResolvedContent
}
}

func MockNewToolingStoreFromModel(f func(model *asserts.Model, fallbackArchitecture string) (*ToolingStore, error)) (restore func()) {
old := newToolingStoreFromModel
newToolingStoreFromModel = f
return func() {
newToolingStoreFromModel = old
}
}
21 changes: 17 additions & 4 deletions image/image_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,23 @@ func classicHasSnaps(model *asserts.Model, opts *Options) bool {
return model.Gadget() != "" || len(model.RequiredNoEssentialSnaps()) != 0 || len(opts.Snaps) != 0
}

var newToolingStoreFromModel = NewToolingStoreFromModel

func Prepare(opts *Options) error {
model, err := decodeModelAssertion(opts)
if err != nil {
return err
var model *asserts.Model
var err error
if opts.Classic && opts.ModelFile == "" {
// ubuntu-image has a use case for preseeding snaps in an arbitrary rootfs
// using its --filesystem flag. This rootfs may or may not already have
// snaps preseeded in it. In the case where the provided rootfs has no
// snaps seeded image.Prepare will be called with no model assertion,
// and we then use the GenericClassicModel.
model = sysdb.GenericClassicModel()
} else {
model, err = decodeModelAssertion(opts)
if err != nil {
return err
}
}

if model.Architecture() != "" && opts.Architecture != "" && model.Architecture() != opts.Architecture {
Expand All @@ -117,7 +130,7 @@ func Prepare(opts *Options) error {
}
}

tsto, err := NewToolingStoreFromModel(model, opts.Architecture)
tsto, err := newToolingStoreFromModel(model, opts.Architecture)
if err != nil {
return err
}
Expand Down
38 changes: 38 additions & 0 deletions image/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (

"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/asserts/assertstest"
"github.com/snapcore/snapd/asserts/sysdb"
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/bootloader/assets"
"github.com/snapcore/snapd/bootloader/bootloadertest"
Expand Down Expand Up @@ -2019,6 +2020,43 @@ func (s *imageSuite) TestPrepareClassicModelSnapsButNoArchFails(c *C) {
c.Assert(err, ErrorMatches, "cannot have snaps for a classic image without an architecture in the model or from --arch")
}

func (s *imageSuite) TestPrepareClassicModelNoModelAssertion(c *C) {
preparedir := c.MkDir()
s.setupSnaps(c, nil, "")

restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
restore = sysdb.MockGenericClassicModel(s.StoreSigning.GenericClassicModel)
defer restore()
restore = image.MockNewToolingStoreFromModel(func(model *asserts.Model, fallbackArchitecture string) (*image.ToolingStore, error) {
return s.tsto, nil
})
defer restore()

// prepare an image with no model assertion but classic set to true
// to ensure the GenericClassicModel is used without error
err := image.Prepare(&image.Options{
Architecture: "amd64",
PrepareDir: preparedir,
Classic: true,
Snaps: []string{"required-snap18", "core18"},
})
c.Assert(err, IsNil)

// ensure the prepareDir was preseeded
seeddir := filepath.Join(preparedir, "var/lib/snapd/seed")
seedsnapsdir := filepath.Join(seeddir, "snaps")
c.Check(filepath.Join(seeddir, "seed.yaml"), testutil.FilePresent)
m, err := filepath.Glob(filepath.Join(seedsnapsdir, "*"))
c.Assert(err, IsNil)
// generic classic model has no other snaps, so we expect only the snaps
// that were passed in options to be present
c.Check(m, DeepEquals, []string{
filepath.Join(seedsnapsdir, "core18_18.snap"),
filepath.Join(seedsnapsdir, "required-snap18_6.snap"),
})
}

func (s *imageSuite) TestSetupSeedWithKernelAndGadgetTrack(c *C) {
restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
Expand Down
8 changes: 8 additions & 0 deletions seed/seed.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ type Seed interface {
// ModeSnaps returns the snaps that should be available
// in the given mode as defined by the seed, after LoadMeta.
ModeSnaps(mode string) ([]*Snap, error)

// NumSnaps returns the total number of snaps in a seed.
NumSnaps() int

// Iter provides a way to iterately perform a function on
// each of the snaps in a seed. For UC20 all snaps will be
// considered independent of mode.
Iter(f func(sn *Snap) error) error
}

// Open returns a Seed implementation for the seed at seedDir.
Expand Down
13 changes: 13 additions & 0 deletions seed/seed20.go
Original file line number Diff line number Diff line change
Expand Up @@ -559,3 +559,16 @@ func (s *seed20) ModeSnaps(mode string) ([]*Snap, error) {
}
return res, nil
}

func (s *seed20) NumSnaps() int {
return len(s.snaps)
}

func (s *seed20) Iter(f func(sn *Snap) error) error {
for _, sn := range s.snaps {
if err := f(sn); err != nil {
return err
}
}
return nil
}
73 changes: 73 additions & 0 deletions seed/seed20_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ func (s *seed20Suite) TestLoadMetaCore20Minimal(c *C) {
runSnaps, err := seed20.ModeSnaps("run")
c.Assert(err, IsNil)
c.Check(runSnaps, HasLen, 0)

c.Check(seed20.NumSnaps(), Equals, 4)
}

func (s *seed20Suite) makeCore20MinimalSeed(c *C, sysLabel string) string {
Expand Down Expand Up @@ -657,6 +659,8 @@ func (s *seed20Suite) TestLoadMetaCore20(c *C) {
},
})

c.Check(seed20.NumSnaps(), Equals, 5)

runSnaps, err := seed20.ModeSnaps("run")
c.Assert(err, IsNil)
c.Check(runSnaps, HasLen, 1)
Expand Down Expand Up @@ -2094,3 +2098,72 @@ func (s *seed20Suite) TestOpenInvalidLabel(c *C) {
c.Assert(seed20, IsNil)
}
}

func (s *seed20Suite) TestLoadMetaCore20Iter(c *C) {
s.makeSnap(c, "snapd", "")
s.makeSnap(c, "core20", "")
s.makeSnap(c, "pc-kernel=20", "")
s.makeSnap(c, "pc=20", "")
s.makeSnap(c, "required20", "developerid")

sysLabel := "20191209"
s.MakeSeed(c, sysLabel, "my-brand", "my-model", map[string]interface{}{
"display-name": "my model",
"architecture": "amd64",
"base": "core20",
"grade": "dangerous",
"snaps": []interface{}{
map[string]interface{}{
"name": "pc-kernel",
"id": s.AssertedSnapID("pc-kernel"),
"type": "kernel",
"default-channel": "20",
},
map[string]interface{}{
"name": "pc",
"id": s.AssertedSnapID("pc"),
"type": "gadget",
"default-channel": "20",
},
map[string]interface{}{
"name": "required20",
"id": s.AssertedSnapID("required20"),
},
},
}, nil)

seed20, err := seed.Open(s.SeedDir, sysLabel)
c.Assert(err, IsNil)

err = seed20.LoadAssertions(s.db, s.commitTo)
c.Assert(err, IsNil)

err = seed20.LoadMeta(s.perfTimings)
c.Assert(err, IsNil)

c.Check(seed20.NumSnaps(), Equals, 5)

// iterates over all snaps
seen := map[string]bool{}
err = seed20.Iter(func(sn *seed.Snap) error {
seen[sn.SnapName()] = true
return nil
})
c.Assert(err, IsNil)
c.Check(seen, DeepEquals, map[string]bool{
"snapd": true,
"pc-kernel": true,
"core20": true,
"pc": true,
"required20": true,
})

// and bubbles up the errors
err = seed20.Iter(func(sn *seed.Snap) error {
if sn.SnapName() == "core20" {
return fmt.Errorf("mock error for snap %q", sn.SnapName())
}
return nil
})
c.Assert(err, ErrorMatches, `mock error for snap "core20"`)
}
7 changes: 2 additions & 5 deletions seed/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,10 @@ func ValidateFromYaml(seedYamlFile string) error {
return newValidationError("", err)
}

// TODO:UC20: make the NumSnaps/Iter part of Seed
seed16 := seed.(*seed16)

ve := &ValidationError{}
// read the snap infos
snapInfos := make([]*snap.Info, 0, seed16.NumSnaps())
seed16.Iter(func(sn *Snap) error {
snapInfos := make([]*snap.Info, 0, seed.NumSnaps())
seed.Iter(func(sn *Snap) error {
snapf, err := snapfile.Open(sn.Path)
if err != nil {
ve.addErr("", err)
Expand Down

0 comments on commit 41ba290

Please sign in to comment.