Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

snap-preseed: support for preseeding of snapd and core18 #8170

Merged
merged 13 commits into from Feb 28, 2020
Merged
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() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably leave a TODO note to enable this on core as well.

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"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably still fail here if the model is not classic ? that needs its own tests and need to deal with wrappers/core18.go, no ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm you're right, and I missed wrappers for core18, definately needs a close look, probably needs extra checks in the spread test too. Thanks

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be clear the code in wrappers/core18.go is a nop for classic afair

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, it's a noop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably still fail here if the model is not classic ? that needs its own tests and need to deal with wrappers/core18.go, no ?

Added error on non-classic + test.

} 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

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)
stolowski marked this conversation as resolved.
Show resolved Hide resolved
}
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