Skip to content

Commit

Permalink
Handle gadget defaults via sysconfig. Unexport configcore.filesystemO…
Browse files Browse the repository at this point in the history
…nlyApply() and use it via sysconfig.ConfigureRunSystem.
  • Loading branch information
stolowski committed May 29, 2020
1 parent 048e6fe commit cc412fb
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 88 deletions.
8 changes: 5 additions & 3 deletions image/image_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ import (
"syscall"
"time"

// to set sysconfig.ConfigcoreFilesystemOnlyApply hook
_ "github.com/snapcore/snapd/overlord/configstate/configcore"

"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/asserts/sysdb"
"github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/gadget"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord/configstate/configcore"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/seed/seedwriter"
"github.com/snapcore/snapd/snap"
Expand Down Expand Up @@ -434,8 +436,8 @@ func setupSeed(tsto *ToolingStore, model *asserts.Model, opts *Options) error {
if err := os.MkdirAll(sysconfig.WritableDefaultsDir(rootDir, "/etc"), 0755); err != nil {
return err
}
applyOpts := &configcore.FilesystemOnlyApplyOptions{Classic: opts.Classic}
return configcore.FilesystemOnlyApply(defaultsDir, configcore.PlainCoreConfig(defaults), applyOpts)
applyOpts := &sysconfig.FilesystemOnlyApplyOptions{Classic: opts.Classic}
return sysconfig.ConfigcoreFilesystemOnlyApply(defaultsDir, defaults, applyOpts)
}
}

Expand Down
1 change: 1 addition & 0 deletions overlord/configstate/configcore/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var (
UpdateKeyValueStream = updateKeyValueStream
AddFSOnlyHandler = addFSOnlyHandler
AddWithStateHandler = addWithStateHandler
FilesystemOnlyApply = filesystemOnlyApply
)

func MockFindGid(f func(string) (uint64, error)) func() {
Expand Down
15 changes: 5 additions & 10 deletions overlord/configstate/configcore/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
"fmt"

"github.com/snapcore/snapd/overlord/configstate/config"
"github.com/snapcore/snapd/overlord/devicestate"
"github.com/snapcore/snapd/sysconfig"
)

type configHandler interface {
Expand Down Expand Up @@ -82,8 +82,8 @@ func init() {
// journal.persistent
addFSOnlyHandler(validateJournalSettings, handleJournalConfiguration, coreOnly)

devicestate.ConfigcoreFilesystemOnlyApply = func(rootDir string, defaults map[string]interface{}) error {
return FilesystemOnlyApply(rootDir, PlainCoreConfig(defaults), nil)
sysconfig.ConfigcoreFilesystemOnlyApplyImpl = func(rootDir string, defaults map[string]interface{}, options *sysconfig.FilesystemOnlyApplyOptions) error {
return filesystemOnlyApply(rootDir, PlainCoreConfig(defaults), options)
}
}

Expand Down Expand Up @@ -124,16 +124,11 @@ func (h *fsOnlyHandler) handle(cfg config.ConfGetter, opts *fsOnlyContext) error
return h.handleFunc(cfg, opts)
}

type FilesystemOnlyApplyOptions struct {
// Classic is true when the system in rootdir is a classic system
Classic bool
}

// FilesystemOnlyApply applies filesystem modifications under rootDir, according to the
// filesystemOnlyApply applies filesystem modifications under rootDir, according to the
// cfg configuration. This is a subset of core config options that is important
// early during boot, before all the configuration is applied as part of
// normal execution of configure hook.
func FilesystemOnlyApply(rootDir string, cfg config.ConfGetter, opts *FilesystemOnlyApplyOptions) error {
func filesystemOnlyApply(rootDir string, cfg config.ConfGetter, opts *sysconfig.FilesystemOnlyApplyOptions) error {
if rootDir == "" {
return fmt.Errorf("internal error: root directory for configcore.FilesystemOnlyApply() not set")
}
Expand Down
69 changes: 14 additions & 55 deletions overlord/devicestate/devicestate_install_mode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,10 @@ func (s *deviceMgrInstallModeSuite) TestInstallModeRunSysconfig(c *C) {

// and sysconfig.ConfigureRunSystem was run exactly once
c.Assert(s.configureRunSystemOptsPassed, DeepEquals, []*sysconfig.Options{
{TargetRootDir: boot.InstallHostWritableDir},
{
TargetRootDir: boot.InstallHostWritableDir,
GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"),
},
})
}

Expand All @@ -468,7 +471,10 @@ func (s *deviceMgrInstallModeSuite) TestInstallModeRunSysconfigErr(c *C) {
- Setup system for run mode \(error from sysconfig.ConfigureRunSystem\)`)
// and sysconfig.ConfigureRunSystem was run exactly once
c.Assert(s.configureRunSystemOptsPassed, DeepEquals, []*sysconfig.Options{
{TargetRootDir: boot.InstallHostWritableDir},
{
TargetRootDir: boot.InstallHostWritableDir,
GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"),
},
})
}

Expand All @@ -489,6 +495,7 @@ func (s *deviceMgrInstallModeSuite) TestInstallModeSupportsCloudInitInDangerous(
{
CloudInitSrcDir: filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d"),
TargetRootDir: boot.InstallHostWritableDir,
GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"),
},
})
}
Expand All @@ -508,7 +515,10 @@ func (s *deviceMgrInstallModeSuite) TestInstallModeNoCloudInitForSigned(c *C) {

// so no cloud-init src dir is passed
c.Assert(s.configureRunSystemOptsPassed, DeepEquals, []*sysconfig.Options{
{TargetRootDir: boot.InstallHostWritableDir},
{
TargetRootDir: boot.InstallHostWritableDir,
GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"),
},
})
}

Expand Down Expand Up @@ -538,61 +548,10 @@ func (s *deviceMgrInstallModeSuite) TestInstallModeSupportsCloudInitFromGadgetNo
TargetRootDir: boot.InstallHostWritableDir,
// not set
CloudInitSrcDir: "",
GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"),
})
}

func (s *deviceMgrInstallModeSuite) TestInstallModeAppliesEarlyDefaultsFromGadget(c *C) {
const gadgetDefaults = `
defaults:
system:
service:
rsyslog.disable: true
ssh.disable: true
journal.persistent: true
`

rsyslogServiceFile := filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/etc/systemd/system/rsyslog.service")
journalPath := filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/var/log/journal")
sshDontRunFile := filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/etc/ssh/sshd_not_to_be_run")

// sanity
c.Check(osutil.FileExists(rsyslogServiceFile), Equals, false)
c.Check(osutil.FileExists(sshDontRunFile), Equals, false)
exists, _, _ := osutil.DirExists(journalPath)
c.Check(exists, Equals, false)

s.mockInstallModeChange(c, "signed", gadgetDefaults)

c.Check(osutil.FileExists(rsyslogServiceFile), Equals, true)
c.Check(osutil.IsSymlink(rsyslogServiceFile), Equals, true)
c.Check(osutil.FileExists(sshDontRunFile), Equals, true)
exists, _, _ = osutil.DirExists(journalPath)
c.Check(exists, Equals, true)
}

func (s *deviceMgrInstallModeSuite) TestInstallModeEarlyDefaultsFromGadgetInvalid(c *C) {
const gadgetDefaults = `
defaults:
system:
service:
rsyslog:
disable: foo
`

s.mockInstallModeChange(c, "signed", gadgetDefaults)

path := filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/etc/systemd/system/rsyslog.service")
c.Check(osutil.FileExists(path), Equals, false)

s.state.Lock()
defer s.state.Unlock()

// install failed due to invalid defaults
installSystem := s.findInstallSystem()
c.Assert(installSystem, NotNil)
c.Check(installSystem.Err(), ErrorMatches, `.*\n.*Setup system for run mode \(option "rsyslog.service" has invalid value "foo".*`)
}

func (s *deviceMgrInstallModeSuite) TestInstallModeWritesModel(c *C) {
// pretend we have a cloud-init config on the seed partition
model := s.mockInstallModeChange(c, "dangerous", "")
Expand Down
20 changes: 1 addition & 19 deletions overlord/devicestate/handlers_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import (
// snap-bootstrap/bootstrap|partition into gadget or
// subpackages there cleanly
"github.com/snapcore/snapd/cmd/snap-bootstrap/bootstrap"
"github.com/snapcore/snapd/gadget"

"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/boot"
Expand All @@ -49,10 +48,6 @@ var (
bootstrapRun = bootstrap.Run
)

var ConfigcoreFilesystemOnlyApply = func(rootDir string, defaults map[string]interface{}) error {
panic("configcoreFilesystemOnlyApply not set")
}

func setSysconfigCloudOptions(opts *sysconfig.Options, gadgetDir string, model *asserts.Model) {
// TODO: add support for a single cloud-init `cloud.conf` file
// that is then passed to sysconfig
Expand Down Expand Up @@ -157,26 +152,13 @@ func (m *DeviceManager) doSetupRunSystem(t *state.Task, _ *tomb.Tomb) error {
}

// configure the run system
opts := &sysconfig.Options{TargetRootDir: boot.InstallHostWritableDir}
opts := &sysconfig.Options{TargetRootDir: boot.InstallHostWritableDir, GadgetDir: gadgetDir}
// configure cloud init
setSysconfigCloudOptions(opts, gadgetDir, deviceCtx.Model())
if err := sysconfigConfigureRunSystem(opts); err != nil {
return err
}

// early config
ginf, err := gadget.ReadInfo(gadgetDir, nil)
if err != nil {
return err
}
defaults := gadget.SystemDefaults(ginf.Defaults)
if len(defaults) > 0 {
defaultsDir := sysconfig.WritableDefaultsDir(boot.InstallHostWritableDir)
if err := ConfigcoreFilesystemOnlyApply(defaultsDir, defaults); err != nil {
return err
}
}

// make it bootable
logger.Noticef("make system bootable")
bootBaseInfo, err := snapstate.BootBaseInfo(st, deviceCtx)
Expand Down
107 changes: 107 additions & 0 deletions sysconfig/gadget_defaults_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2020 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
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package sysconfig_test

import (
"path/filepath"

. "gopkg.in/check.v1"

"github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/osutil"

// to set ConfigcoreFilesystemOnlyApply hook
_ "github.com/snapcore/snapd/overlord/configstate/configcore"

"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snaptest"
"github.com/snapcore/snapd/sysconfig"
)

var gadgetYaml = `
volumes:
pc:
bootloader: grub
`

func (s *sysconfigSuite) TestGadgetDefaults(c *C) {
const gadgetDefaultsYaml = `
defaults:
system:
service:
rsyslog.disable: true
ssh.disable: true
journal.persistent: true
`
si := &snap.SideInfo{
RealName: "pc",
Revision: snap.R(1),
SnapID: "idid",
}
snapInfo := snaptest.MockSnapWithFiles(c, "name: pc\ntype: gadget", si, [][]string{
{"meta/gadget.yaml", gadgetYaml + gadgetDefaultsYaml},
})

rsyslogServiceFile := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/systemd/system/rsyslog.service")
journalPath := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/var/log/journal")
sshDontRunFile := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/ssh/sshd_not_to_be_run")

// sanity
c.Check(osutil.FileExists(rsyslogServiceFile), Equals, false)
c.Check(osutil.FileExists(sshDontRunFile), Equals, false)
exists, _, _ := osutil.DirExists(journalPath)
c.Check(exists, Equals, false)

err := sysconfig.ConfigureRunSystem(&sysconfig.Options{
TargetRootDir: boot.InstallHostWritableDir,
GadgetDir: snapInfo.MountDir(),
})
c.Assert(err, IsNil)

c.Check(osutil.FileExists(rsyslogServiceFile), Equals, true)
c.Check(osutil.IsSymlink(rsyslogServiceFile), Equals, true)
c.Check(osutil.FileExists(sshDontRunFile), Equals, true)
exists, _, _ = osutil.DirExists(journalPath)
c.Check(exists, Equals, true)
}

func (s *sysconfigSuite) TestInstallModeEarlyDefaultsFromGadgetInvalid(c *C) {
const gadgetDefaultsYaml = `
defaults:
system:
service:
rsyslog:
disable: foo
`
si := &snap.SideInfo{
RealName: "pc",
Revision: snap.R(1),
SnapID: "idid",
}
snapInfo := snaptest.MockSnapWithFiles(c, "name: pc\ntype: gadget", si, [][]string{
{"meta/gadget.yaml", gadgetYaml + gadgetDefaultsYaml},
})

err := sysconfig.ConfigureRunSystem(&sysconfig.Options{
TargetRootDir: boot.InstallHostWritableDir,
GadgetDir: snapInfo.MountDir(),
})
c.Check(err, ErrorMatches, `option "rsyslog.service" has invalid value "foo"`)
}
40 changes: 39 additions & 1 deletion sysconfig/sysconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@

package sysconfig

import "path/filepath"
import (
"path/filepath"

"github.com/snapcore/snapd/gadget"
)

// See https://github.com/snapcore/core20/pull/46
const writableDefaultsDir = "_writable_defaults"
Expand All @@ -35,6 +39,25 @@ type Options struct {
// data, i.e. for cloud-init during the initramfs it will be something like
// boot.InstallHostWritableDir
TargetRootDir string

// GadgetDir is the path of the mounted gadget snap.
GadgetDir string
}

type FilesystemOnlyApplyOptions struct {
// Classic is true when the system in rootdir is a classic system
Classic bool
}

// ConfigcoreFilesystemOnlyApplyImpl is initialized by init() of configcore.
var ConfigcoreFilesystemOnlyApplyImpl = func(rootDir string, defaults map[string]interface{}, options *FilesystemOnlyApplyOptions) error {
panic("configcoreFilesystemOnlyApply not set")
}

// filesystemOnlyApply applies filesystem modifications under rootDir via
// configcore.filesystemOnlyApply().
func ConfigcoreFilesystemOnlyApply(rootDir string, defaults map[string]interface{}, options *FilesystemOnlyApplyOptions) error {
return ConfigcoreFilesystemOnlyApplyImpl(rootDir, defaults, options)
}

// ConfigureRunSystem configures the ubuntu-data partition with any
Expand All @@ -44,6 +67,21 @@ func ConfigureRunSystem(opts *Options) error {
return err
}

if opts.GadgetDir != "" {
ginf, err := gadget.ReadInfo(opts.GadgetDir, nil)
if err != nil {
return err
}
defaults := gadget.SystemDefaults(ginf.Defaults)
if len(defaults) > 0 {
// options are nil which implies core system
var options *FilesystemOnlyApplyOptions
if err := ConfigcoreFilesystemOnlyApply(WritableDefaultsDir(opts.TargetRootDir), defaults, options); err != nil {
return err
}
}
}

return nil
}

Expand Down

0 comments on commit cc412fb

Please sign in to comment.