Skip to content

Commit

Permalink
Merge pull request #8170 from stolowski/prebaking/preseed-core18
Browse files Browse the repository at this point in the history
snap-preseed: support for preseeding of snapd and core18
  • Loading branch information
stolowski committed Feb 28, 2020
2 parents 3cf189a + be27139 commit e3e82b4
Show file tree
Hide file tree
Showing 6 changed files with 316 additions and 15 deletions.
67 changes: 61 additions & 6 deletions cmd/snap-preseed/main_test.go
Expand Up @@ -31,6 +31,7 @@ import (
. "gopkg.in/check.v1"

"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/asserts/assertstest"
"github.com/snapcore/snapd/cmd/snap-preseed"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/seed"
Expand Down Expand Up @@ -171,6 +172,7 @@ func (s *startPreseedSuite) TestRunPreseedHappy(c *C) {
}

type Fake16Seed struct {
AssertsModel *asserts.Model
Essential []*seed.Snap
LoadMetaErr error
LoadAssertionsErr error
Expand All @@ -179,12 +181,25 @@ type Fake16Seed struct {

// Fake implementation of seed.Seed interface

func mockClassicModel() *asserts.Model {
headers := map[string]interface{}{
"type": "model",
"authority-id": "brand",
"series": "16",
"brand-id": "brand",
"model": "classicbaz-3000",
"classic": "true",
"timestamp": "2018-01-01T08:00:00+00:00",
}
return assertstest.FakeAssertion(headers, nil).(*asserts.Model)
}

func (fs *Fake16Seed) LoadAssertions(db asserts.RODatabase, commitTo func(*asserts.Batch) error) error {
return fs.LoadAssertionsErr
}

func (fs *Fake16Seed) Model() (*asserts.Model, error) {
panic("not implemented")
return fs.AssertsModel, nil
}

func (fs *Fake16Seed) LoadMeta(tm timings.Measurer) error {
Expand All @@ -208,7 +223,8 @@ func (s *startPreseedSuite) TestSystemSnapFromSeed(c *C) {

restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) {
return &Fake16Seed{
Essential: []*seed.Snap{{Path: "/some/path/core", SideInfo: &snap.SideInfo{RealName: "core"}}},
AssertsModel: mockClassicModel(),
Essential: []*seed.Snap{{Path: "/some/path/core", SideInfo: &snap.SideInfo{RealName: "core"}}},
}, nil
})
defer restore()
Expand All @@ -218,6 +234,23 @@ func (s *startPreseedSuite) TestSystemSnapFromSeed(c *C) {
c.Check(path, Equals, "/some/path/core")
}

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

restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) {
return &Fake16Seed{
AssertsModel: mockClassicModel(),
Essential: []*seed.Snap{{Path: "/some/path/snapd.snap", SideInfo: &snap.SideInfo{RealName: "snapd"}}},
UsesSnapd: true,
}, nil
})
defer restore()

path, err := main.SystemSnapFromSeed(tmpDir)
c.Assert(err, IsNil)
c.Check(path, Equals, "/some/path/snapd.snap")
}

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

Expand All @@ -232,6 +265,7 @@ func (s *startPreseedSuite) TestSystemSnapFromSeedErrors(c *C) {
tmpDir := c.MkDir()

fakeSeed := &Fake16Seed{}
fakeSeed.AssertsModel = mockClassicModel()

restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) { return fakeSeed, nil })
defer restore()
Expand All @@ -244,10 +278,6 @@ func (s *startPreseedSuite) TestSystemSnapFromSeedErrors(c *C) {
_, err = main.SystemSnapFromSeed(tmpDir)
c.Assert(err, ErrorMatches, "core snap not found")

fakeSeed.UsesSnapd = true
_, err = main.SystemSnapFromSeed(tmpDir)
c.Assert(err, ErrorMatches, "preseeding with snapd snap is not supported yet")

fakeSeed.LoadMetaErr = fmt.Errorf("load meta failed")
_, err = main.SystemSnapFromSeed(tmpDir)
c.Assert(err, ErrorMatches, "load meta failed")
Expand All @@ -258,6 +288,31 @@ func (s *startPreseedSuite) TestSystemSnapFromSeedErrors(c *C) {
c.Assert(err, ErrorMatches, "load assertions failed")
}

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

headers := map[string]interface{}{
"type": "model",
"authority-id": "brand",
"series": "16",
"brand-id": "brand",
"model": "baz-3000",
"architecture": "armhf",
"gadget": "brand-gadget",
"kernel": "kernel",
"timestamp": "2018-01-01T08:00:00+00:00",
}

fakeSeed := &Fake16Seed{}
fakeSeed.AssertsModel = assertstest.FakeAssertion(headers, nil).(*asserts.Model)

restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) { return fakeSeed, nil })
defer restore()

_, err := main.SystemSnapFromSeed(tmpDir)
c.Assert(err, ErrorMatches, "preseeding is only supported on classic systems")
}

func (s *startPreseedSuite) TestRunPreseedUnsupportedVersion(c *C) {
tmpDir := c.MkDir()
dirs.SetRootDir(tmpDir)
Expand Down
29 changes: 21 additions & 8 deletions cmd/snap-preseed/preseed_linux.go
Expand Up @@ -85,24 +85,37 @@ var systemSnapFromSeed = func(rootDir string) (string, error) {
return "", err
}

// TODO: handle core18, snapd snap.
model, err := seed.Model()
if err != nil {
return "", err
}

// TODO: implement preseeding for core.
if !model.Classic() {
return "", fmt.Errorf("preseeding is only supported on classic systems")
}

var required string
if seed.UsesSnapdSnap() {
return "", fmt.Errorf("preseeding with snapd snap is not supported yet")
required = "snapd"
} else {
required = "core"
}

var coreSnapPath string
var snapPath string
ess := seed.EssentialSnaps()
if len(ess) > 0 {
if ess[0].SnapName() == "core" {
coreSnapPath = ess[0].Path
// core / snapd snap is the first essential snap.
if ess[0].SnapName() == required {
snapPath = ess[0].Path
}
}

if coreSnapPath == "" {
return "", fmt.Errorf("core snap not found")
if snapPath == "" {
return "", fmt.Errorf("%s snap not found", required)
}

return coreSnapPath, nil
return snapPath, nil
}

const snapdPreseedSupportVer = `2.43.3+`
Expand Down
116 changes: 116 additions & 0 deletions overlord/devicestate/firstboot_preseed_test.go
Expand Up @@ -102,6 +102,7 @@ func checkPreseedTaskStates(c *C, st *state.State) {
c.Fatalf("unhandled task kind %s", t.Kind())
}
}

// sanity: check that doneTasks is not declaring more tasks than
// actually expected.
c.Check(doneTasks, DeepEquals, seenDone)
Expand Down Expand Up @@ -183,6 +184,7 @@ func checkPreseedOrder(c *C, tsAll []*state.TaskSet, snaps ...string) {
c.Check(waitTasks, HasLen, 0)
} else {
c.Assert(waitTasks, HasLen, 1)
c.Assert(waitTasks[0].Kind(), Equals, prevTask.Kind())
c.Check(waitTasks[0], Equals, prevTask)
}

Expand Down Expand Up @@ -306,4 +308,118 @@ snaps:
c.Assert(chg.Err(), IsNil)

checkPreseedTaskStates(c, st)
c.Check(chg.Status(), Equals, state.DoingStatus)

// verify
r, err := os.Open(dirs.SnapStateFile)
c.Assert(err, IsNil)
diskState, err := state.ReadState(nil, r)
c.Assert(err, IsNil)

diskState.Lock()
defer diskState.Unlock()

// seeded snaps are installed
_, err = snapstate.CurrentInfo(diskState, "core")
c.Check(err, IsNil)
_, err = snapstate.CurrentInfo(diskState, "foo")
c.Check(err, IsNil)

// but we're not considered seeded
var seeded bool
err = diskState.Get("seeded", &seeded)
c.Assert(err, Equals, state.ErrNoState)
}

func (s *firstbootPreseed16Suite) TestPreseedClassicWithSnapdOnlyHappy(c *C) {
restorePreseedMode := release.MockPreseedMode(func() bool { return true })
defer restorePreseedMode()

restore := release.MockOnClassic(true)
defer restore()

mockMountCmd := testutil.MockCommand(c, "mount", "")
defer mockMountCmd.Restore()

mockUmountCmd := testutil.MockCommand(c, "umount", "")
defer mockUmountCmd.Restore()

core18Fname, snapdFname, _, _ := s.makeCore18Snaps(c, &core18SnapsOpts{
classic: true,
})

// put a firstboot snap into the SnapBlobDir
snapYaml := `name: foo
version: 1.0
base: core18
`
fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
s.WriteAssertions("foo.asserts", s.devAcct, fooRev, fooDecl)

// add a model assertion and its chain
assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil)
s.WriteAssertions("model.asserts", assertsChain...)

// create a seed.yaml
content := []byte(fmt.Sprintf(`
snaps:
- name: snapd
file: %s
- name: foo
file: %s
- name: core18
file: %s
`, snapdFname, fooFname, core18Fname))
err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644)
c.Assert(err, IsNil)

// run the firstboot stuff
s.startOverlord(c)
st := s.overlord.State()
st.Lock()
defer st.Unlock()

opts := &devicestate.PopulateStateFromSeedOptions{Preseed: true}
tsAll, err := devicestate.PopulateStateFromSeedImpl(st, opts, s.perfTimings)
c.Assert(err, IsNil)

checkPreseedOrder(c, tsAll, "snapd", "core18", "foo")

// now run the change and check the result
chg := st.NewChange("seed", "run the populate from seed changes")
for _, ts := range tsAll {
chg.AddAll(ts)
}
c.Assert(st.Changes(), HasLen, 1)
c.Assert(chg.Err(), IsNil)

st.Unlock()
err = s.overlord.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)

checkPreseedTaskStates(c, st)
c.Check(chg.Status(), Equals, state.DoingStatus)

// verify
r, err := os.Open(dirs.SnapStateFile)
c.Assert(err, IsNil)
diskState, err := state.ReadState(nil, r)
c.Assert(err, IsNil)

diskState.Lock()
defer diskState.Unlock()

// seeded snaps are installed
_, err = snapstate.CurrentInfo(diskState, "snapd")
c.Check(err, IsNil)
_, err = snapstate.CurrentInfo(diskState, "core18")
c.Check(err, IsNil)
_, err = snapstate.CurrentInfo(diskState, "foo")
c.Check(err, IsNil)

// but we're not considered seeded
var seeded bool
err = diskState.Get("seeded", &seeded)
c.Assert(err, Equals, state.ErrNoState)
}
2 changes: 1 addition & 1 deletion overlord/devicestate/firstboot_test.go
Expand Up @@ -1160,7 +1160,7 @@ type core18SnapsOpts struct {
gadget bool
}

func (s *firstBoot16Suite) makeCore18Snaps(c *C, opts *core18SnapsOpts) (core18Fn, snapdFn, kernelFn, gadgetFn string) {
func (s *firstBoot16BaseTest) makeCore18Snaps(c *C, opts *core18SnapsOpts) (core18Fn, snapdFn, kernelFn, gadgetFn string) {
if opts == nil {
opts = &core18SnapsOpts{}
}
Expand Down
7 changes: 7 additions & 0 deletions tests/main/preseed-snapd-snap/seed.yaml.in
@@ -0,0 +1,7 @@
snaps:
- name: snapd
file: @SNAPD@
unasserted: true
- name: core18
file: @CORE@
unasserted: true

0 comments on commit e3e82b4

Please sign in to comment.