diff --git a/overlord/configstate/handler_test.go b/overlord/configstate/handler_test.go index 90c6bb639be..9fbd4da5ec5 100644 --- a/overlord/configstate/handler_test.go +++ b/overlord/configstate/handler_test.go @@ -49,10 +49,29 @@ type configureHandlerSuite struct { var _ = Suite(&configureHandlerSuite{}) func (s *configureHandlerSuite) SetUpTest(c *C) { + dirs.SetRootDir(c.MkDir()) + s.state = state.New(nil) s.state.Lock() defer s.state.Unlock() + coreSnapYaml := `name: core +version: 1.0 +type: os +` + snaptest.MockSnap(c, coreSnapYaml, &snap.SideInfo{ + RealName: "core", + Revision: snap.R(1), + }) + snapstate.Set(s.state, "core", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{ + {RealName: "core", Revision: snap.R(1), SnapID: "core-snap-id"}, + }, + Current: snap.R(1), + SnapType: "os", + }) + s.restore = snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}) task := s.state.NewTask("test-task", "my test task") @@ -67,6 +86,7 @@ func (s *configureHandlerSuite) SetUpTest(c *C) { func (s *configureHandlerSuite) TearDownTest(c *C) { s.restore() + dirs.SetRootDir("/") } func (s *configureHandlerSuite) TestBeforeInitializesTransaction(c *C) { @@ -91,8 +111,6 @@ func (s *configureHandlerSuite) TestBeforeInitializesTransaction(c *C) { func (s *configureHandlerSuite) TestBeforeInitializesTransactionUseDefaults(c *C) { r := release.MockOnClassic(false) defer r() - dirs.SetRootDir(c.MkDir()) - defer dirs.SetRootDir("/") const mockGadgetSnapYaml = ` name: canonical-pc @@ -162,8 +180,6 @@ hooks: func (s *configureHandlerSuite) TestBeforeUseDefaultsMissingHook(c *C) { r := release.MockOnClassic(false) defer r() - dirs.SetRootDir(c.MkDir()) - defer dirs.SetRootDir("/") const mockGadgetSnapYaml = ` name: canonical-pc diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index ee8e505f057..8552d2609a1 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -1844,7 +1844,9 @@ func CoreInfo(st *state.State) (*snap.Info, error) { return nil, fmt.Errorf("unexpected number of cores, got %d", len(res)) } -// ConfigDefaults returns the configuration defaults for the snap specified in the gadget. If gadget is absent or the snap has no snap-id it returns ErrNoState. +// ConfigDefaults returns the configuration defaults for the snap specified in +// the gadget. If gadget is absent or the snap has no snap-id it returns +// ErrNoState. func ConfigDefaults(st *state.State, snapName string) (map[string]interface{}, error) { gadget, err := GadgetInfo(st) if err != nil { @@ -1856,8 +1858,17 @@ func ConfigDefaults(st *state.State, snapName string) (map[string]interface{}, e return nil, err } + core, err := CoreInfo(st) + if err != nil { + return nil, err + } + isCoreDefaults := core.Name() == snapName + si := snapst.CurrentSideInfo() - if si.SnapID == "" { + // core snaps can be addressed even without a snap-id via the special + // "system" value in the config; first-boot always configures the core + // snap with UseConfigDefaults + if si.SnapID == "" && !isCoreDefaults { return nil, state.ErrNoState } @@ -1866,6 +1877,17 @@ func ConfigDefaults(st *state.State, snapName string) (map[string]interface{}, e return nil, err } + // we support setting core defaults via "system" + if isCoreDefaults { + if defaults, ok := gadgetInfo.Defaults["system"]; ok { + if _, ok := gadgetInfo.Defaults[si.SnapID]; ok && si.SnapID != "" { + logger.Noticef("core snap configuration defaults found under both 'system' key and core-snap-id, preferring 'system'") + } + + return defaults, nil + } + } + defaults, ok := gadgetInfo.Defaults[si.SnapID] if !ok { return nil, state.ErrNoState diff --git a/overlord/snapstate/snapstate_test.go b/overlord/snapstate/snapstate_test.go index 095b982f855..fcfee371a50 100644 --- a/overlord/snapstate/snapstate_test.go +++ b/overlord/snapstate/snapstate_test.go @@ -27,6 +27,7 @@ import ( "os" "path/filepath" "sort" + "strings" "testing" "time" @@ -7568,7 +7569,7 @@ volumes: bootloader: grub ` -func (s *snapmgrTestSuite) prepareGadget(c *C) { +func (s *snapmgrTestSuite) prepareGadget(c *C, extraGadgetYaml ...string) { gadgetSideInfo := &snap.SideInfo{RealName: "the-gadget", SnapID: "the-gadget-id", Revision: snap.R(1)} gadgetInfo := snaptest.MockSnap(c, ` name: the-gadget @@ -7576,7 +7577,8 @@ type: gadget version: 1.0 `, gadgetSideInfo) - err := ioutil.WriteFile(filepath.Join(gadgetInfo.MountDir(), "meta/gadget.yaml"), []byte(gadgetYaml), 0600) + gadgetYamlWhole := strings.Join(append([]string{gadgetYaml}, extraGadgetYaml...), "") + err := ioutil.WriteFile(filepath.Join(gadgetInfo.MountDir(), "meta/gadget.yaml"), []byte(gadgetYamlWhole), 0600) c.Assert(err, IsNil) snapstate.Set(s.state, "the-gadget", &snapstate.SnapState{ @@ -7607,6 +7609,7 @@ func (s *snapmgrTestSuite) TestConfigDefaults(c *C) { Current: snap.R(11), SnapType: "app", }) + makeInstalledMockCoreSnap(c) defls, err := snapstate.ConfigDefaults(s.state, "some-snap") c.Assert(err, IsNil) @@ -7624,6 +7627,65 @@ func (s *snapmgrTestSuite) TestConfigDefaults(c *C) { c.Assert(err, Equals, state.ErrNoState) } +func (s *snapmgrTestSuite) TestConfigDefaultsSystem(c *C) { + r := release.MockOnClassic(false) + defer r() + + // using MockSnapReadInfo, we want to read the bits on disk + snapstate.MockSnapReadInfo(snap.ReadInfo) + + s.state.Lock() + defer s.state.Unlock() + + s.prepareGadget(c, ` +defaults: + system: + foo: bar +`) + + makeInstalledMockCoreSnap(c) + + defls, err := snapstate.ConfigDefaults(s.state, "core") + c.Assert(err, IsNil) + c.Assert(defls, DeepEquals, map[string]interface{}{"foo": "bar"}) +} + +func (s *snapmgrTestSuite) TestConfigDefaultsSystemConflictsCoreSnapId(c *C) { + r := release.MockOnClassic(false) + defer r() + + // using MockSnapReadInfo, we want to read the bits on disk + snapstate.MockSnapReadInfo(snap.ReadInfo) + + s.state.Lock() + defer s.state.Unlock() + + s.prepareGadget(c, ` +defaults: + system: + foo: bar + the-core-snap: + foo: other-bar + other-key: other-key-default +`) + + snapstate.Set(s.state, "core", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{ + {RealName: "core", SnapID: "the-core-snap", Revision: snap.R(1)}, + }, + Current: snap.R(1), + SnapType: "os", + }) + + makeInstalledMockCoreSnap(c) + + // 'system' key defaults take precedence over snap-id ones + defls, err := snapstate.ConfigDefaults(s.state, "core") + c.Assert(err, IsNil) + c.Assert(defls, DeepEquals, map[string]interface{}{"foo": "bar"}) +} + func (s *snapmgrTestSuite) TestGadgetDefaultsAreNormalizedForConfigHook(c *C) { var mockGadgetSnapYaml = ` name: canonical-pc diff --git a/tests/main/ubuntu-core-gadget-config-defaults/task.yaml b/tests/main/ubuntu-core-gadget-config-defaults/task.yaml index 4a3201fbf87..80fd1c6d217 100644 --- a/tests/main/ubuntu-core-gadget-config-defaults/task.yaml +++ b/tests/main/ubuntu-core-gadget-config-defaults/task.yaml @@ -27,6 +27,10 @@ prepare: | ${TEST_SNAP_ID}: a: A b: B + system: + service: + rsyslog: + disable: true EOF mksquashfs squashfs-root pc_x1.snap -comp xz -no-fragments rm -rf squashfs-root @@ -47,6 +51,11 @@ restore: | echo "This test needs test keys to be trusted" exit fi + echo "Undo the rsyslog disable" + systemctl unmask rsyslog.service || true + systemctl enable rsyslog.service || true + systemctl start rsyslog.service || true + . $TESTSLIB/systemd.sh systemctl stop snapd.service snapd.socket rm -rf /var/lib/snapd/assertions/* @@ -100,3 +109,8 @@ execute: | echo "The configuration defaults from the gadget where applied" snap get test-snapd-with-configure a|MATCH "^A$" snap get test-snapd-with-configure b|MATCH "^B$" + + echo "The configuration for core is applied" + snap get core service.rsyslog.disable|MATCH true + echo "And the service is masked" + systemctl status rsyslog.service|MATCH masked