diff --git a/boot/boot_test.go b/boot/boot_test.go
index 3fd7081a76d..5a499ce3c83 100644
--- a/boot/boot_test.go
+++ b/boot/boot_test.go
@@ -21,6 +21,7 @@ package boot_test
import (
"errors"
+ "os"
"path/filepath"
"testing"
@@ -405,6 +406,15 @@ func (s *bootSetSuite) TestCoreParticipant20SetNextSameKernelSnap(c *C) {
coreDev := boottest.MockUC20Device("pc-kernel")
c.Assert(coreDev.HasModeenv(), Equals, true)
+ // default modeenv state
+ m := &boot.Modeenv{
+ Base: "core20_1.snap",
+ CurrentKernels: []string{"pc-kernel_1.snap"},
+ }
+ err := m.Write("")
+ c.Assert(err, IsNil)
+ defer os.Remove(dirs.SnapModeenvFileUnder(dirs.GlobalRootDir))
+
// set the current kernel
kernel, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap")
c.Assert(err, IsNil)
@@ -435,6 +445,11 @@ func (s *bootSetSuite) TestCoreParticipant20SetNextSameKernelSnap(c *C) {
_, enableKernelCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableTryKernel")
c.Assert(enableKernelCalls, Equals, 0)
+ // the modeenv is still the same as well
+ m2, err := boot.ReadModeenv("")
+ c.Assert(err, IsNil)
+ c.Assert(m2.CurrentKernels, DeepEquals, []string{"pc-kernel_1.snap"})
+
// finally we didn't call SetBootVars on the bootloader because nothing
// changed
c.Assert(s.bootloader.SetBootVarsCalls, Equals, 0)
@@ -444,6 +459,15 @@ func (s *bootSetSuite) TestCoreParticipant20SetNextNewKernelSnap(c *C) {
coreDev := boottest.MockUC20Device("pc-kernel")
c.Assert(coreDev.HasModeenv(), Equals, true)
+ // default modeenv state
+ m := &boot.Modeenv{
+ Base: "core20_1.snap",
+ CurrentKernels: []string{"pc-kernel_1.snap"},
+ }
+ err := m.Write("")
+ c.Assert(err, IsNil)
+ defer os.Remove(dirs.SnapModeenvFileUnder(dirs.GlobalRootDir))
+
// set the current kernel
kernel, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap")
c.Assert(err, IsNil)
@@ -477,6 +501,11 @@ func (s *bootSetSuite) TestCoreParticipant20SetNextNewKernelSnap(c *C) {
// and we were asked to enable kernel2 as the try kernel
actual, _ := s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableTryKernel")
c.Assert(actual, DeepEquals, []snap.PlaceInfo{kernel2})
+
+ // and that the modeenv now has this kernel listed
+ m2, err := boot.ReadModeenv("")
+ c.Assert(err, IsNil)
+ c.Assert(m2.CurrentKernels, DeepEquals, []string{"pc-kernel_1.snap", "pc-kernel_2.snap"})
}
func (s *bootSetSuite) TestMarkBootSuccessful20KernelStatusTryingNoKernelSnapCleansUp(c *C) {
@@ -663,9 +692,10 @@ func (s *bootSetSuite) TestMarkBootSuccessful20AllSnap(c *C) {
// we were trying a base snap
m := &boot.Modeenv{
- Base: "core20_1.snap",
- TryBase: "core20_2.snap",
- BaseStatus: boot.TryingStatus,
+ Base: "core20_1.snap",
+ TryBase: "core20_2.snap",
+ BaseStatus: boot.TryingStatus,
+ CurrentKernels: []string{"pc-kernel_1.snap", "pc-kernel_2.snap"},
}
err := m.Write("")
c.Assert(err, IsNil)
@@ -708,6 +738,7 @@ func (s *bootSetSuite) TestMarkBootSuccessful20AllSnap(c *C) {
c.Assert(m2.Base, Equals, "core20_2.snap")
c.Assert(m2.TryBase, Equals, "")
c.Assert(m2.BaseStatus, Equals, boot.DefaultStatus)
+ c.Assert(m2.CurrentKernels, DeepEquals, []string{"pc-kernel_2.snap"})
// do it again, verify its still valid
err = boot.MarkBootSuccessful(coreDev)
@@ -766,12 +797,13 @@ func (s *bootSetSuite) TestMarkBootSuccessfulBaseUpdate(c *C) {
}
func (s *bootSetSuite) TestMarkBootSuccessful20KernelUpdate(c *C) {
- r := boottest.ForceModeenv(dirs.GlobalRootDir, &boot.Modeenv{
- Mode: "run",
- RecoverySystem: "20191018",
+ // default modeenv
+ m := &boot.Modeenv{
Base: "core20_1.snap",
- })
- defer r()
+ CurrentKernels: []string{"pc-kernel_1.snap", "pc-kernel_2.snap"},
+ }
+ err := m.Write("")
+ c.Assert(err, IsNil)
coreDev := boottest.MockUC20Device("some-snap")
c.Assert(coreDev.HasModeenv(), Equals, true)
@@ -782,7 +814,7 @@ func (s *bootSetSuite) TestMarkBootSuccessful20KernelUpdate(c *C) {
// set the current Kernel
kernel1, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap")
c.Assert(err, IsNil)
- r = s.bootloader.SetRunKernelImageEnabledKernel(kernel1)
+ r := s.bootloader.SetRunKernelImageEnabledKernel(kernel1)
defer r()
// set the current try kernel
@@ -807,6 +839,11 @@ func (s *bootSetSuite) TestMarkBootSuccessful20KernelUpdate(c *C) {
_, nDisableTryCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("DisableTryKernel")
c.Assert(nDisableTryCalls, Equals, 1)
+ // check that the new kernel is the only one in modeenv
+ m2, err := boot.ReadModeenv("")
+ c.Assert(err, IsNil)
+ c.Assert(m2.CurrentKernels, DeepEquals, []string{"pc-kernel_2.snap"})
+
// do it again, verify its still valid
err = boot.MarkBootSuccessful(coreDev)
c.Assert(err, IsNil)
diff --git a/boot/bootstate20.go b/boot/bootstate20.go
index 3086391e154..840b04be65b 100644
--- a/boot/bootstate20.go
+++ b/boot/bootstate20.go
@@ -23,7 +23,6 @@ import (
"fmt"
"github.com/snapcore/snapd/bootloader"
- "github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/snap"
)
@@ -38,6 +37,28 @@ func newBootState20(typ snap.Type) bootState {
}
}
+//
+// modeenv methods
+//
+
+type bootState20Modeenv struct {
+ modeenv *Modeenv
+}
+
+func (bsm *bootState20Modeenv) loadModeenv() error {
+ // don't read modeenv multiple times
+ if bsm.modeenv != nil {
+ return nil
+ }
+ modeenv, err := ReadModeenv("")
+ if err != nil {
+ return fmt.Errorf("cannot get snap revision: unable to read modeenv: %v", err)
+ }
+ bsm.modeenv = modeenv
+
+ return nil
+}
+
//
// kernel snap methods
//
@@ -62,6 +83,12 @@ type bootState20Kernel struct {
// the kernel snap to try for setNext()
tryKernelSnap snap.PlaceInfo
+
+ // don't embed this struct - it will conflict with embedding
+ // bootState20Modeenv in bootState20Base when both bootState20Base and
+ // bootState20Kernel are embedded in bootState20MarkSuccessful
+ // also we only need to use it with setNext()
+ kModeenv bootState20Modeenv
}
func (ks20 *bootState20Kernel) loadBootenv() error {
@@ -109,12 +136,12 @@ func (ks20 *bootState20Kernel) revisions() (curSnap, trySnap snap.PlaceInfo, try
return nil, nil, "", fmt.Errorf("cannot identify kernel snap with bootloader %s: %v", ks20.ebl.Name(), err)
}
- tryKernel, tryKernelExists, err := ks20.ebl.TryKernel()
- if err != nil {
+ tryKernel, err := ks20.ebl.TryKernel()
+ if err != nil && err != bootloader.ErrNoTryKernelRef {
return nil, nil, "", fmt.Errorf("cannot identify try kernel snap with bootloader %s: %v", ks20.ebl.Name(), err)
}
- if tryKernelExists {
+ if err == nil {
tryBootSn = tryKernel
}
@@ -129,15 +156,23 @@ func (ks20 *bootState20Kernel) markSuccessful(update bootStateUpdate) (bootState
}
// u should always be non-nil if err is nil
+ // save the tried kernel snap here
u.triedKernelSnap = sn
return u, nil
}
func (ks20 *bootState20Kernel) setNext(next snap.PlaceInfo) (rebootRequired bool, u bootStateUpdate, err error) {
+ // commit() for setNext() also needs to add to the kernels in modeenv
+ err = ks20.kModeenv.loadModeenv()
+ if err != nil {
+ return false, nil, err
+ }
+
nextStatus, err := genericSetNext(ks20, next)
if err != nil {
return false, nil, err
}
+
// if we are setting a snap as a try snap, then we need to reboot
rebootRequired = false
if nextStatus == TryStatus {
@@ -158,22 +193,38 @@ func (ks20 *bootState20Kernel) commit() error {
// If we are about to try an update, and need to add the try-kernel symlink,
// we need to do things in this order:
- // 1. Add try-kernel symlink
- // 2. Update kernel_status to "try"
+ // 1. Add the kernel snap to the modeenv
+ // 2. Create try-kernel symlink
+ // 3. Update kernel_status to "try"
//
- // This is because if we get rebooted in between 1 and 2, kernel_status
- // is still unset and boot scripts proceeds to boot with the old kernel,
- // effectively ignoring the try-kernel symlink.
+ // This is because if we get rebooted in before 3, kernel_status is still
+ // unset and boot scripts proceeds to boot with the old kernel, effectively
+ // ignoring the try-kernel symlink.
// If we did it in the opposite order however, we would set kernel_status to
// "try" and then get rebooted before we could create the try-kernel
// symlink, so the bootloader would try to boot from the non-existent
// try-kernel symlink and become broken.
+ //
+ // Adding the kernel snap to the modeenv's list of trusted kernel snaps can
+ // effectively happen any time before we update the kernel_status to "try"
+ // for the same reasoning as for creating the try-kernel symlink. Putting it
+ // first is currently a purely aesthetic choice.
- // add the try-kernel symlink
- // trySnap could be nil here if we called setNext on the current kernel
- // snap
+ // add the kernel to the modeenv and add the try-kernel symlink
+ // tryKernelSnap could be nil here if we called setNext on the current
+ // kernel snap
if ks20.tryKernelSnap != nil {
- err := ks20.ebl.EnableTryKernel(ks20.tryKernelSnap)
+ // add the kernel to the modeenv
+ ks20.kModeenv.modeenv.CurrentKernels = append(
+ ks20.kModeenv.modeenv.CurrentKernels,
+ ks20.tryKernelSnap.Filename(),
+ )
+ err := ks20.kModeenv.modeenv.Write("")
+ if err != nil {
+ return err
+ }
+
+ err = ks20.ebl.EnableTryKernel(ks20.tryKernelSnap)
if err != nil {
return err
}
@@ -202,8 +253,7 @@ func (ks20 *bootState20Kernel) commit() error {
// note that for markSuccessful() a different bootStateUpdate implementation is
// returned, see bootState20MarkSuccessful
type bootState20Base struct {
- // the modeenv for the base snap, initialized with loadModeenv()
- modeenv *Modeenv
+ bootState20Modeenv
// the base_status to be written to the modeenv, stored separately to
// eliminate unnecessary writes to the modeenv when it's already in the
@@ -222,7 +272,7 @@ func (bs20 *bootState20Base) loadModeenv() error {
if bs20.modeenv != nil {
return nil
}
- modeenv, err := ReadModeenv(dirs.GlobalRootDir)
+ modeenv, err := ReadModeenv("")
if err != nil {
return fmt.Errorf("cannot get snap revision: unable to read modeenv: %v", err)
}
@@ -424,6 +474,10 @@ func genericMarkSuccessful(b bootState, update bootStateUpdate) (bsmark *bootSta
// this could end up auto-cleaning status variables for something it shouldn't
// be.
func (bsmark *bootState20MarkSuccessful) commit() error {
+ // the base and kernel snap updates will modify the modeenv, so we only
+ // issue a single write at the end if something changed
+ modeenvChanged := false
+
// kernel snap first, slightly higher priority
// the ordering here is very important for boot reliability!
@@ -435,6 +489,7 @@ func (bsmark *bootState20MarkSuccessful) commit() error {
// 1. Update kernel_status to ""
// 2. Move kernel symlink to point to the new try kernel
// 3. Remove try-kernel symlink
+ // 4. Remove old kernel from modeenv
//
// If we got rebooted after step 1, then the bootloader is booting the wrong
// kernel, but is at least booting a known good kernel and snapd in
@@ -450,6 +505,13 @@ func (bsmark *bootState20MarkSuccessful) commit() error {
// the boot failed, and revert to booting using the kernel symlink, but that
// now points to the new kernel we were trying and we did not successfully
// boot from that kernel to know we should trust it.
+ //
+ // Removing the old kernel from the modeenv needs to happen after it is
+ // impossible for the bootloader to boot from that kernel, otherwise we
+ // could end up in a state where the bootloader doesn't want to boot the
+ // new kernel, but the initramfs doesn't trust the old kernel and we are
+ // stuck. As such, do this last, after the symlink no longer exists.
+ //
// The try-kernel symlink removal should happen last because it will not
// affect anything, except that if it was removed before updating
// kernel_status to "", the bootloader will think that the try kernel failed
@@ -478,11 +540,15 @@ func (bsmark *bootState20MarkSuccessful) commit() error {
return err
}
- // finally disable the try kernel symlink
+ // disable the try kernel symlink
err = bsmark.ebl.DisableTryKernel()
if err != nil {
return err
}
+
+ // finally set current_kernels to be just this new kernel snap
+ bsmark.modeenv.CurrentKernels = []string{bsmark.triedKernelSnap.Filename()}
+ modeenvChanged = true
}
// base snap next
@@ -491,13 +557,11 @@ func (bsmark *bootState20MarkSuccessful) commit() error {
// atomic file writing operation, so it's not a concern if we get
// rebooted during this snippet like it is with the kernel snap above
- baseChanged := false
-
// always clear the base_status when marking successful, this has the useful
// side-effect of cleaning up if we have base_status=trying but no try_base
// set
if bsmark.modeenv.BaseStatus != DefaultStatus {
- baseChanged = true
+ modeenvChanged = true
bsmark.modeenv.BaseStatus = DefaultStatus
}
@@ -506,18 +570,18 @@ func (bsmark *bootState20MarkSuccessful) commit() error {
tryBase := bsmark.triedBaseSnap.Filename()
if bsmark.modeenv.Base != tryBase {
bsmark.modeenv.Base = tryBase
- baseChanged = true
+ modeenvChanged = true
}
// clear the TryBase
if bsmark.modeenv.TryBase != "" {
bsmark.modeenv.TryBase = ""
- baseChanged = true
+ modeenvChanged = true
}
}
// write the modeenv
- if baseChanged {
+ if modeenvChanged {
return bsmark.modeenv.Write("")
}
diff --git a/boot/boottest/modeenv.go b/boot/boottest/modeenv.go
index e6fb8543d43..997efda6973 100644
--- a/boot/boottest/modeenv.go
+++ b/boot/boottest/modeenv.go
@@ -23,6 +23,7 @@ import (
"os"
"github.com/snapcore/snapd/boot"
+ "github.com/snapcore/snapd/dirs"
)
// ForceModeenv forces ReadModeenv to always return a specific Modeenv for a
@@ -30,7 +31,7 @@ import (
// If rootdir is empty, then all invocations return the specified modeenv
func ForceModeenv(rootdir string, m *boot.Modeenv) (restore func()) {
mock := func(callerrootdir string) (*boot.Modeenv, error) {
- if rootdir == "" || callerrootdir == rootdir {
+ if rootdir == "" || rootdir == dirs.GlobalRootDir || callerrootdir == rootdir {
return m, nil
}
diff --git a/boot/kernel_os_test.go b/boot/kernel_os_test.go
index 2b0c351d79a..e0caf8e2164 100644
--- a/boot/kernel_os_test.go
+++ b/boot/kernel_os_test.go
@@ -22,6 +22,7 @@ package boot_test
import (
"errors"
"io/ioutil"
+ "os"
"path/filepath"
. "gopkg.in/check.v1"
@@ -167,6 +168,15 @@ func (s *coreBootSetSuite) TestSetNextBoot20ForKernel(c *C) {
coreDev := boottest.MockUC20Device("pc-kernel")
c.Assert(coreDev.HasModeenv(), Equals, true)
+ // default modeenv state
+ m := &boot.Modeenv{
+ Base: "core20_1.snap",
+ CurrentKernels: []string{"pc-kernel_1.snap"},
+ }
+ err := m.Write("")
+ c.Assert(err, IsNil)
+ defer os.Remove(dirs.SnapModeenvFileUnder(dirs.GlobalRootDir))
+
// setup current kernel
kernel1, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap")
c.Assert(err, IsNil)
@@ -202,6 +212,11 @@ func (s *coreBootSetSuite) TestSetNextBoot20ForKernel(c *C) {
// check that SetNextBoot asked the bootloader for a kernel
_, nKernelCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("Kernel")
c.Assert(nKernelCalls, Equals, 1)
+
+ // and that the modeenv now has this kernel listed
+ m2, err := boot.ReadModeenv("")
+ c.Assert(err, IsNil)
+ c.Assert(m2.CurrentKernels, DeepEquals, []string{"pc-kernel_1.snap", "pc-kernel_2.snap"})
}
func (s *coreBootSetSuite) TestSetNextBootForKernelForTheSameKernel(c *C) {
@@ -231,6 +246,15 @@ func (s *coreBootSetSuite) TestSetNextBoot20ForKernelForTheSameKernel(c *C) {
coreDev := boottest.MockUC20Device("pc-kernel")
c.Assert(coreDev.HasModeenv(), Equals, true)
+ // default modeenv state
+ m := &boot.Modeenv{
+ Base: "core20_1.snap",
+ CurrentKernels: []string{"pc-kernel_1.snap"},
+ }
+ err := m.Write("")
+ c.Assert(err, IsNil)
+ defer os.Remove(dirs.SnapModeenvFileUnder(dirs.GlobalRootDir))
+
// setup current kernel
kernel1, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap")
c.Assert(err, IsNil)
@@ -262,6 +286,11 @@ func (s *coreBootSetSuite) TestSetNextBoot20ForKernelForTheSameKernel(c *C) {
// check that SetNextBoot asked the bootloader for a kernel
_, nKernelCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("Kernel")
c.Assert(nKernelCalls, Equals, 1)
+
+ // and that the modeenv now has this kernel listed
+ m2, err := boot.ReadModeenv("")
+ c.Assert(err, IsNil)
+ c.Assert(m2.CurrentKernels, DeepEquals, []string{"pc-kernel_1.snap"})
}
func (s *coreBootSetSuite) TestSetNextBootForKernelForTheSameKernelTryMode(c *C) {
@@ -296,6 +325,15 @@ func (s *coreBootSetSuite) TestSetNextBoot20ForKernelForTheSameKernelTryMode(c *
coreDev := boottest.MockUC20Device("pc-kernel")
c.Assert(coreDev.HasModeenv(), Equals, true)
+ // default modeenv state
+ m := &boot.Modeenv{
+ Base: "core20_1.snap",
+ CurrentKernels: []string{"pc-kernel_1.snap"},
+ }
+ err := m.Write("")
+ c.Assert(err, IsNil)
+ defer os.Remove(dirs.SnapModeenvFileUnder(dirs.GlobalRootDir))
+
// setup current kernel
kernel1, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap")
c.Assert(err, IsNil)
@@ -332,6 +370,11 @@ func (s *coreBootSetSuite) TestSetNextBoot20ForKernelForTheSameKernelTryMode(c *
// check that SetNextBoot asked the bootloader for a kernel
_, nKernelCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("Kernel")
c.Assert(nKernelCalls, Equals, 1)
+
+ // and that the modeenv didn't change
+ m2, err := boot.ReadModeenv("")
+ c.Assert(err, IsNil)
+ c.Assert(m2.CurrentKernels, DeepEquals, m.CurrentKernels)
}
// ubootBootSetSuite tests the uboot specific code in the bootloader handling
diff --git a/boot/makebootable.go b/boot/makebootable.go
index 9f82f981918..1e61a8ce349 100644
--- a/boot/makebootable.go
+++ b/boot/makebootable.go
@@ -211,6 +211,7 @@ func makeBootable20RunMode(model *asserts.Model, rootdir string, bootWith *Boota
Mode: "run",
RecoverySystem: filepath.Base(bootWith.RecoverySystemDir),
Base: filepath.Base(bootWith.BasePath),
+ CurrentKernels: []string{bootWith.Kernel.Filename()},
}
if err := modeenv.Write(filepath.Join(runMnt, "ubuntu-data", "system-data")); err != nil {
return fmt.Errorf("cannot write modeenv: %v", err)
diff --git a/boot/makebootable_test.go b/boot/makebootable_test.go
index de020c8b1d7..303a8eb93c3 100644
--- a/boot/makebootable_test.go
+++ b/boot/makebootable_test.go
@@ -356,5 +356,6 @@ version: 5.0
c.Check(ubuntuDataModeEnvPath, testutil.FileEquals, `mode=run
recovery_system=20191216
base=core20_3.snap
+current_kernels=pc-kernel_5.snap
`)
}
diff --git a/boot/modeenv_test.go b/boot/modeenv_test.go
index fb42629e311..fd4fe450756 100644
--- a/boot/modeenv_test.go
+++ b/boot/modeenv_test.go
@@ -110,7 +110,7 @@ base_status=try
c.Check(modeenv.RecoverySystem, Equals, "20191126")
c.Check(modeenv.Base, Equals, "core20_123.snap")
c.Check(modeenv.TryBase, Equals, "core20_124.snap")
- c.Check(modeenv.BaseStatus, Equals, "try")
+ c.Check(modeenv.BaseStatus, Equals, boot.TryStatus)
}
func (s *modeenvSuite) TestReadModeWithCurrentKernels(c *C) {
@@ -186,7 +186,7 @@ func (s *modeenvSuite) TestWriteNonExistingFull(c *C) {
RecoverySystem: "20191128",
Base: "core20_321.snap",
TryBase: "core20_322.snap",
- BaseStatus: "try",
+ BaseStatus: boot.TryStatus,
CurrentKernels: []string{"pc-kernel_1.snap", "pc-kernel_2.snap"},
}
err := modeenv.Write(s.tmpdir)
diff --git a/bootloader/androidboot_test.go b/bootloader/androidboot_test.go
index 8ebfb959c37..0963904f48d 100644
--- a/bootloader/androidboot_test.go
+++ b/bootloader/androidboot_test.go
@@ -24,6 +24,7 @@ import (
. "gopkg.in/check.v1"
+ "github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/snap"
@@ -55,13 +56,13 @@ func (s *androidBootTestSuite) TestNewAndroidboot(c *C) {
func (s *androidBootTestSuite) TestSetGetBootVar(c *C) {
a := bootloader.NewAndroidBoot(s.rootdir)
- bootVars := map[string]string{"snap_mode": "try"}
+ bootVars := map[string]string{"snap_mode": boot.TryStatus}
a.SetBootVars(bootVars)
v, err := a.GetBootVars("snap_mode")
c.Assert(err, IsNil)
c.Check(v, HasLen, 1)
- c.Check(v["snap_mode"], Equals, "try")
+ c.Check(v["snap_mode"], Equals, boot.TryStatus)
}
func (s *androidBootTestSuite) TestExtractKernelAssetsNoUnpacksKernel(c *C) {
diff --git a/bootloader/bootloader.go b/bootloader/bootloader.go
index cef70057f1d..c3d7068c4b5 100644
--- a/bootloader/bootloader.go
+++ b/bootloader/bootloader.go
@@ -31,8 +31,12 @@ import (
)
var (
- // ErrBootloader is returned if the bootloader can not be determined
+ // ErrBootloader is returned if the bootloader can not be determined.
ErrBootloader = errors.New("cannot determine bootloader")
+
+ // ErrNoTryKernelRef is returned if the bootloader finds no enabled
+ // try-kernel.
+ ErrNoTryKernelRef = errors.New("no try-kernel referenced")
)
// Options carries bootloader options.
@@ -55,18 +59,18 @@ type Options struct {
}
// Bootloader provides an interface to interact with the system
-// bootloader
+// bootloader.
type Bootloader interface {
- // Return the value of the specified bootloader variable
+ // Return the value of the specified bootloader variable.
GetBootVars(names ...string) (map[string]string, error)
- // Set the value of the specified bootloader variable
+ // Set the value of the specified bootloader variable.
SetBootVars(values map[string]string) error
- // Name returns the bootloader name
+ // Name returns the bootloader name.
Name() string
- // ConfigFile returns the name of the config file
+ // ConfigFile returns the name of the config file.
ConfigFile() string
// InstallBootConfig will try to install the boot config in the
@@ -91,13 +95,43 @@ type RecoveryAwareBootloader interface {
SetRecoverySystemEnv(recoverySystemDir string, values map[string]string) error
}
+// ExtractedRunKernelImageBootloader is a Bootloader that also supports specific
+// methods needed to setup booting from an extracted kernel, which is needed to
+// implement encryption and/or secure boot. Prototypical implementation is UC20
+// grub implementation with FDE.
type ExtractedRunKernelImageBootloader interface {
Bootloader
- EnableKernel(snap.PlaceInfo) error // makes the symlink
- EnableTryKernel(snap.PlaceInfo) error // makes the symlink
- Kernel() (snap.PlaceInfo, error) // gives the symlink
- TryKernel() (snap.PlaceInfo, bool, error) // gives the symlink (if exists)
- DisableTryKernel() error // removes the symlink
+
+ // EnableKernel enables the specified kernel on ubuntu-boot to be used
+ // during normal boots. The specified kernel should already have been
+ // extracted. This is usually implemented with a "kernel.efi" symlink
+ // pointing to the extracted kernel image.
+ EnableKernel(snap.PlaceInfo) error
+
+ // EnableTryKernel enables the specified kernel on ubuntu-boot to be
+ // tried by the bootloader on a reboot, to be used in conjunction with
+ // setting "kernel_status" to "try". The specified kernel should already
+ // have been extracted. This is usually implemented with a
+ // "try-kernel.efi" symlink pointing to the extracted kernel image.
+ EnableTryKernel(snap.PlaceInfo) error
+
+ // Kernel returns the current enabled kernel on the bootloader, not
+ // necessarily the kernel that was used to boot the current session, but the
+ // kernel that is enabled to boot on "normal" boots.
+ // If error is not nil, the first argument shall be non-nil.
+ Kernel() (snap.PlaceInfo, error)
+
+ // TryKernel returns the current enabled try-kernel on the bootloader, if
+ // there is no such enabled try-kernel, then ErrNoTryKernelRef is returned.
+ // If error is not nil, the first argument shall be non-nil.
+ TryKernel() (snap.PlaceInfo, error)
+
+ // DisableTryKernel disables the current enabled try-kernel on the
+ // bootloader, if it exists. It does not need to return an error if the
+ // enabled try-kernel does not exist or is in an inconsistent state before
+ // disabling it, errors should only be returned when the implementation
+ // fails to disable the try-kernel.
+ DisableTryKernel() error
}
func genericInstallBootConfig(gadgetFile, systemFile string) (bool, error) {
diff --git a/bootloader/bootloader_test.go b/bootloader/bootloader_test.go
index 2a609dbfd65..e431f1c9b3f 100644
--- a/bootloader/bootloader_test.go
+++ b/bootloader/bootloader_test.go
@@ -29,7 +29,6 @@ import (
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/bootloader/bootloadertest"
- //"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/testutil"
diff --git a/bootloader/bootloadertest/bootloadertest.go b/bootloader/bootloadertest/bootloadertest.go
index b0cc7dc81ef..8e4eaa83248 100644
--- a/bootloader/bootloadertest/bootloadertest.go
+++ b/bootloader/bootloadertest/bootloadertest.go
@@ -23,6 +23,7 @@ import (
"fmt"
"path/filepath"
+ "github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/snap"
)
@@ -60,6 +61,7 @@ type MockBootloader struct {
// ensure MockBootloader implements the Bootloader interface
var _ bootloader.Bootloader = (*MockBootloader)(nil)
+var _ bootloader.ExtractedRunKernelImageBootloader = (*MockBootloader)(nil)
func Mock(name, bootdir string) *MockBootloader {
return &MockBootloader{
@@ -121,10 +123,10 @@ func (b *MockBootloader) SetBootBase(base string) {
}
func (b *MockBootloader) SetTryingDuringReboot() error {
- if b.BootVars["snap_mode"] != "try" {
+ if b.BootVars["snap_mode"] != boot.TryStatus {
return fmt.Errorf("bootloader must be in 'try' mode")
}
- b.BootVars["snap_mode"] = "trying"
+ b.BootVars["snap_mode"] = boot.TryingStatus
return nil
}
@@ -134,14 +136,14 @@ func (b *MockBootloader) SetTryingDuringReboot() error {
// "snap_try_{core,kernel}" and "snap_mode" which means the "old" kernel,core
// in "snap_{core,kernel}" will be used.
func (b *MockBootloader) SetRollbackAcrossReboot() error {
- if b.BootVars["snap_mode"] != "try" {
+ if b.BootVars["snap_mode"] != boot.TryStatus {
return fmt.Errorf("rollback can only be simulated in 'try' mode")
}
if b.BootVars["snap_core"] == "" && b.BootVars["snap_kernel"] == "" {
return fmt.Errorf("rollback can only be simulated if either snap_core or snap_kernel is set")
}
// clean try bootvars and snap_mode
- b.BootVars["snap_mode"] = ""
+ b.BootVars["snap_mode"] = boot.DefaultStatus
b.BootVars["snap_try_core"] = ""
b.BootVars["snap_try_kernel"] = ""
return nil
@@ -249,16 +251,16 @@ func (b *MockBootloader) Kernel() (snap.PlaceInfo, error) {
// TryKernel returns the current kernel set in the bootloader; part of
// ExtractedRunKernelImageBootloader.
-func (b *MockBootloader) TryKernel() (snap.PlaceInfo, bool, error) {
+func (b *MockBootloader) TryKernel() (snap.PlaceInfo, error) {
b.runKernelImageMockedNumCalls["TryKernel"]++
err := b.runKernelImageMockedErrs["TryKernel"]
if err != nil {
- return nil, false, err
+ return nil, err
}
if b.runKernelImageEnabledTryKernel == nil {
- return nil, false, nil
+ return nil, bootloader.ErrNoTryKernelRef
}
- return b.runKernelImageEnabledTryKernel, true, nil
+ return b.runKernelImageEnabledTryKernel, nil
}
// DisableTryKernel removes the current try-kernel "symlink" set in the
diff --git a/bootloader/grub.go b/bootloader/grub.go
index b5039347674..13343a90f33 100644
--- a/bootloader/grub.go
+++ b/bootloader/grub.go
@@ -285,7 +285,7 @@ func (g *grub) Kernel() (snap.PlaceInfo, error) {
// TryKernel will return the kernel snap currently being tried if it exists and
// false if there is not currently a try-kernel.efi symlink. Note if the symlink
// exists but does not point to an existing file an error will be returned.
-func (g *grub) TryKernel() (snap.PlaceInfo, bool, error) {
+func (g *grub) TryKernel() (snap.PlaceInfo, error) {
// check that the _symlink_ exists, not that it points to something real
// we check for whether it is a dangling symlink inside readKernelSymlink,
// which returns an error when the symlink is dangling
@@ -295,9 +295,9 @@ func (g *grub) TryKernel() (snap.PlaceInfo, bool, error) {
// if we failed to read the symlink, then the try kernel isn't usable,
// so return err because the symlink is there
if err != nil {
- return nil, false, err
+ return nil, err
}
- return p, true, nil
+ return p, nil
}
- return nil, false, nil
+ return nil, ErrNoTryKernelRef
}
diff --git a/bootloader/grub_test.go b/bootloader/grub_test.go
index 75da8476c05..7c352833bac 100644
--- a/bootloader/grub_test.go
+++ b/bootloader/grub_test.go
@@ -348,9 +348,8 @@ func (s *grubTestSuite) TestGrubExtractedRunKernelImageTryKernel(c *C) {
c.Assert(ok, Equals, true)
// ensure it doesn't return anything when the symlink doesn't exist
- _, exists, err := eg.TryKernel()
- c.Assert(err, IsNil)
- c.Assert(exists, Equals, false)
+ _, err := eg.TryKernel()
+ c.Assert(err, Equals, bootloader.ErrNoTryKernelRef)
// when a bad kernel snap name is in the extracted path, it will complain
// appropriately
@@ -366,9 +365,8 @@ func (s *grubTestSuite) TestGrubExtractedRunKernelImageTryKernel(c *C) {
err = os.Symlink("bad_snap_rev_name/kernel.efi", tryKernelSymlink)
c.Assert(err, IsNil)
- _, exists, err = eg.TryKernel()
+ _, err = eg.TryKernel()
c.Assert(err, ErrorMatches, "cannot parse kernel snap file name from symlink target \"bad_snap_rev_name\": .*")
- c.Assert(exists, Equals, false)
// remove the bad symlink
err = os.Remove(tryKernelSymlink)
@@ -378,16 +376,14 @@ func (s *grubTestSuite) TestGrubExtractedRunKernelImageTryKernel(c *C) {
tryKernel := s.makeKernelAssetSnapAndSymlink(c, "pc-kernel_2.snap", "try-kernel.efi")
// ensure that the returned kernel is the same as the one we put there
- sn, exists, err := eg.TryKernel()
+ sn, err := eg.TryKernel()
c.Assert(err, IsNil)
- c.Assert(exists, Equals, true)
c.Assert(sn, DeepEquals, tryKernel)
// if the destination of the symlink is removed, we get an error
err = os.Remove(filepath.Join(s.grubDir(), "pc-kernel_2.snap", "kernel.efi"))
c.Assert(err, IsNil)
- _, exists, err = eg.TryKernel()
- c.Assert(exists, Equals, false)
+ _, err = eg.TryKernel()
c.Assert(err, ErrorMatches, "cannot read dangling symlink try-kernel.efi")
}
diff --git a/bootloader/lk_test.go b/bootloader/lk_test.go
index f4956a3896e..04abf50c668 100644
--- a/bootloader/lk_test.go
+++ b/bootloader/lk_test.go
@@ -27,6 +27,7 @@ import (
. "gopkg.in/check.v1"
+ "github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/bootloader/lkenv"
"github.com/snapcore/snapd/osutil"
@@ -67,13 +68,13 @@ func (s *lkTestSuite) TestNewLkImageBuildingTime(c *C) {
func (s *lkTestSuite) TestSetGetBootVar(c *C) {
bootloader.MockLkFiles(c, s.rootdir, nil)
l := bootloader.NewLk(s.rootdir, nil)
- bootVars := map[string]string{"snap_mode": "try"}
+ bootVars := map[string]string{"snap_mode": boot.TryStatus}
l.SetBootVars(bootVars)
v, err := l.GetBootVars("snap_mode")
c.Assert(err, IsNil)
c.Check(v, HasLen, 1)
- c.Check(v["snap_mode"], Equals, "try")
+ c.Check(v["snap_mode"], Equals, boot.TryStatus)
}
func (s *lkTestSuite) TestExtractKernelAssetsUnpacksBootimgImageBuilding(c *C) {
diff --git a/bootloader/lkenv/lkenv_test.go b/bootloader/lkenv/lkenv_test.go
index cb7238f3ba9..edacbd9d8f1 100644
--- a/bootloader/lkenv/lkenv_test.go
+++ b/bootloader/lkenv/lkenv_test.go
@@ -22,12 +22,14 @@ package lkenv_test
import (
"bytes"
"compress/gzip"
- . "gopkg.in/check.v1"
"io"
"io/ioutil"
"path/filepath"
"testing"
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/bootloader/lkenv"
)
@@ -66,8 +68,8 @@ func (l *lkenvTestSuite) TestSet(c *C) {
env := lkenv.NewEnv(l.envPath)
c.Check(env, NotNil)
- env.Set("snap_mode", "try")
- c.Check(env.Get("snap_mode"), Equals, "try")
+ env.Set("snap_mode", boot.TryStatus)
+ c.Check(env.Get("snap_mode"), Equals, boot.TryStatus)
}
func (l *lkenvTestSuite) TestSave(c *C) {
diff --git a/build-aux/snap/snapcraft.yaml b/build-aux/snap/snapcraft.yaml
index 696379bd305..8b9a539e25c 100644
--- a/build-aux/snap/snapcraft.yaml
+++ b/build-aux/snap/snapcraft.yaml
@@ -1,6 +1,5 @@
name: snapd
-# TODO: enable this when the store is ready
-# type: snapd
+type: snapd
summary: Daemon and tooling that enable snap packages
description: |
Install, configure, refresh and remove snap packages. Snaps are
@@ -9,7 +8,7 @@ description: |
cloud, servers, desktops and the internet of things.
Start with 'snap list' to see installed snaps.
-adopt-info: snapd
+adopt-info: snapd-deb
# build-base is needed here for snapcraft to build this snap as with "modern"
# snapcraft
build-base: core
@@ -27,7 +26,7 @@ license: GPL-3.0
# See the comments from jdstrand in
# https://forum.snapcraft.io/t/5547/10
parts:
- snapd:
+ snapd-deb:
plugin: nil
source: .
build-snaps: [go/1.10/stable]
@@ -45,7 +44,7 @@ parts:
sudo apt-get build-dep -y ./
./get-deps.sh --skip-unused-check
# set version after installing dependencies so we have all the tools here
- snapcraftctl set-version $(./mkversion.sh --output-only)
+ snapcraftctl set-version "$(./mkversion.sh --output-only)"
override-build: |
# unset the LD_FLAGS and LD_LIBRARY_PATH vars that snapcraft sets for us
# as those will point to the $SNAPCRAFT_STAGE which on re-builds will
@@ -54,7 +53,8 @@ parts:
# TODO: should we unset $PATH to not include $SNAPCRAFT_STAGE too?
unset LD_FLAGS
unset LD_LIBRARY_PATH
- # if we are root, disable tests
+ # if we are root, disable tests because a number of them fail when run as
+ # root
if [ "$(id -u)" = "0" ]; then
DEB_BUILD_OPTIONS=nocheck
export DEB_BUILD_OPTIONS
diff --git a/client/snap_op.go b/client/snap_op.go
index 91774187a3f..db3e82dc0d6 100644
--- a/client/snap_op.go
+++ b/client/snap_op.go
@@ -314,20 +314,33 @@ type snapRevisionOptions struct {
}
type downloadAction struct {
- SnapName string `json:"snap-name,omitempty"`
+ SnapName string `json:"snap-name"`
+
snapRevisionOptions
+
+ HeaderPeek bool `json:"header-peek,omitempty"`
+ ResumeToken string `json:"resume-token,omitempty"`
}
type DownloadInfo struct {
SuggestedFileName string
Size int64
Sha3_384 string
+ ResumeToken string
+}
+
+type DownloadOptions struct {
+ SnapOptions
+
+ HeaderPeek bool
+ ResumeToken string
+ Resume int64
}
// Download will stream the given snap to the client
-func (client *Client) Download(name string, options *SnapOptions) (dlInfo *DownloadInfo, r io.ReadCloser, err error) {
+func (client *Client) Download(name string, options *DownloadOptions) (dlInfo *DownloadInfo, r io.ReadCloser, err error) {
if options == nil {
- options = &SnapOptions{}
+ options = &DownloadOptions{}
}
action := downloadAction{
SnapName: name,
@@ -336,6 +349,8 @@ func (client *Client) Download(name string, options *SnapOptions) (dlInfo *Downl
CohortKey: options.CohortKey,
Revision: options.Revision,
},
+ HeaderPeek: options.HeaderPeek,
+ ResumeToken: options.ResumeToken,
}
data, err := json.Marshal(&action)
if err != nil {
@@ -344,6 +359,9 @@ func (client *Client) Download(name string, options *SnapOptions) (dlInfo *Downl
headers := map[string]string{
"Content-Type": "application/json",
}
+ if options.Resume > 0 {
+ headers["range"] = fmt.Sprintf("bytes: %d-", options.Resume)
+ }
// no deadline for downloads
ctx := context.Background()
@@ -369,6 +387,7 @@ func (client *Client) Download(name string, options *SnapOptions) (dlInfo *Downl
SuggestedFileName: matches[1],
Size: rsp.ContentLength,
Sha3_384: rsp.Header.Get("Snap-Sha3-384"),
+ ResumeToken: rsp.Header.Get("Snap-Download-Token"),
}
return dlInfo, rsp.Body, nil
diff --git a/client/snap_op_test.go b/client/snap_op_test.go
index c04ba3366e1..4790a3166b8 100644
--- a/client/snap_op_test.go
+++ b/client/snap_op_test.go
@@ -446,24 +446,30 @@ func (cs *clientSuite) TestClientOpDownload(c *check.C) {
cs.header = http.Header{
"Content-Disposition": {"attachment; filename=foo_2.snap"},
"Snap-Sha3-384": {"sha3sha3sha3"},
+ "Snap-Download-Token": {"some-token"},
}
cs.contentLength = 1234
cs.rsp = `lots-of-foo-data`
- dlInfo, rc, err := cs.cli.Download("foo", &client.SnapOptions{
- Revision: "2",
- Channel: "edge",
+ dlInfo, rc, err := cs.cli.Download("foo", &client.DownloadOptions{
+ SnapOptions: client.SnapOptions{
+ Revision: "2",
+ Channel: "edge",
+ },
+ HeaderPeek: true,
})
c.Check(err, check.IsNil)
c.Check(dlInfo, check.DeepEquals, &client.DownloadInfo{
SuggestedFileName: "foo_2.snap",
Size: 1234,
Sha3_384: "sha3sha3sha3",
+ ResumeToken: "some-token",
})
// check we posted the right stuff
c.Assert(cs.req.Header.Get("Content-Type"), check.Equals, "application/json")
+ c.Assert(cs.req.Header.Get("range"), check.Equals, "")
body, err := ioutil.ReadAll(cs.req.Body)
c.Assert(err, check.IsNil)
var jsonBody client.DownloadAction
@@ -472,6 +478,56 @@ func (cs *clientSuite) TestClientOpDownload(c *check.C) {
c.Check(jsonBody.SnapName, check.DeepEquals, "foo")
c.Check(jsonBody.Revision, check.Equals, "2")
c.Check(jsonBody.Channel, check.Equals, "edge")
+ c.Check(jsonBody.HeaderPeek, check.Equals, true)
+
+ // ensure we can read the response
+ content, err := ioutil.ReadAll(rc)
+ c.Assert(err, check.IsNil)
+ c.Check(string(content), check.Equals, cs.rsp)
+ // and we can close it
+ c.Check(rc.Close(), check.IsNil)
+}
+
+func (cs *clientSuite) TestClientOpDownloadResume(c *check.C) {
+ cs.status = 200
+ cs.header = http.Header{
+ "Content-Disposition": {"attachment; filename=foo_2.snap"},
+ "Snap-Sha3-384": {"sha3sha3sha3"},
+ }
+ // we resume
+ cs.contentLength = 1234 - 64
+
+ cs.rsp = `lots-of-foo-data`
+
+ dlInfo, rc, err := cs.cli.Download("foo", &client.DownloadOptions{
+ SnapOptions: client.SnapOptions{
+ Revision: "2",
+ Channel: "edge",
+ },
+ HeaderPeek: true,
+ ResumeToken: "some-token",
+ Resume: 64,
+ })
+ c.Check(err, check.IsNil)
+ c.Check(dlInfo, check.DeepEquals, &client.DownloadInfo{
+ SuggestedFileName: "foo_2.snap",
+ Size: 1234 - 64,
+ Sha3_384: "sha3sha3sha3",
+ })
+
+ // check we posted the right stuff
+ c.Assert(cs.req.Header.Get("Content-Type"), check.Equals, "application/json")
+ c.Assert(cs.req.Header.Get("range"), check.Equals, "bytes: 64-")
+ body, err := ioutil.ReadAll(cs.req.Body)
+ c.Assert(err, check.IsNil)
+ var jsonBody client.DownloadAction
+ err = json.Unmarshal(body, &jsonBody)
+ c.Assert(err, check.IsNil)
+ c.Check(jsonBody.SnapName, check.DeepEquals, "foo")
+ c.Check(jsonBody.Revision, check.Equals, "2")
+ c.Check(jsonBody.Channel, check.Equals, "edge")
+ c.Check(jsonBody.HeaderPeek, check.Equals, true)
+ c.Check(jsonBody.ResumeToken, check.Equals, "some-token")
// ensure we can read the response
content, err := ioutil.ReadAll(rc)
diff --git a/cmd/libsnap-confine-private/locking-test.c b/cmd/libsnap-confine-private/locking-test.c
index c2f10e6aa69..72378a430ed 100644
--- a/cmd/libsnap-confine-private/locking-test.c
+++ b/cmd/libsnap-confine-private/locking-test.c
@@ -68,6 +68,11 @@ static const char *sc_test_use_fake_lock_dir(void)
// Check that locking a namespace actually flock's the mutex with LOCK_EX
static void test_sc_lock_unlock(void)
{
+ if (geteuid() != 0) {
+ g_test_skip("this test only runs as root");
+ return;
+ }
+
const char *lock_dir = sc_test_use_fake_lock_dir();
int fd = sc_lock_generic("foo", 123);
// Construct the name of the lock file
@@ -95,6 +100,11 @@ static void test_sc_lock_unlock(void)
// Check that holding a lock is properly detected.
static void test_sc_verify_snap_lock__locked(void)
{
+ if (geteuid() != 0) {
+ g_test_skip("this test only runs as root");
+ return;
+ }
+
(void)sc_test_use_fake_lock_dir();
int fd = sc_lock_snap("foo");
sc_verify_snap_lock("foo");
@@ -104,6 +114,11 @@ static void test_sc_verify_snap_lock__locked(void)
// Check that holding a lock is properly detected.
static void test_sc_verify_snap_lock__unlocked(void)
{
+ if (geteuid() != 0) {
+ g_test_skip("this test only runs as root");
+ return;
+ }
+
(void)sc_test_use_fake_lock_dir();
if (g_test_subprocess()) {
sc_verify_snap_lock("foo");
@@ -117,6 +132,11 @@ static void test_sc_verify_snap_lock__unlocked(void)
static void test_sc_enable_sanity_timeout(void)
{
+ if (geteuid() != 0) {
+ g_test_skip("this test only runs as root");
+ return;
+ }
+
if (g_test_subprocess()) {
sc_enable_sanity_timeout();
debug("waiting...");
diff --git a/cmd/snap-bootstrap/cmd_initramfs_mounts.go b/cmd/snap-bootstrap/cmd_initramfs_mounts.go
index 7b0e2bb7dbe..d5dd97c0d4b 100644
--- a/cmd/snap-bootstrap/cmd_initramfs_mounts.go
+++ b/cmd/snap-bootstrap/cmd_initramfs_mounts.go
@@ -30,6 +30,7 @@ import (
"github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/seed"
"github.com/snapcore/snapd/snap"
@@ -68,6 +69,31 @@ var (
osutilIsMounted = osutil.IsMounted
)
+func recoverySystemEssentialSnaps(seedDir, recoverySystem string, essentialTypes []snap.Type) ([]*seed.Snap, error) {
+ systemSeed, err := seed.Open(seedDir, recoverySystem)
+ if err != nil {
+ return nil, err
+ }
+
+ seed20, ok := systemSeed.(seed.EssentialMetaLoaderSeed)
+ if !ok {
+ return nil, fmt.Errorf("internal error: UC20 seed must implement EssentialMetaLoaderSeed")
+ }
+
+ // load assertions into a temporary database
+ if err := systemSeed.LoadAssertions(nil, nil); err != nil {
+ return nil, err
+ }
+
+ // load and verify metadata only for the relevant essential snaps
+ perf := timings.New(nil)
+ if err := seed20.LoadEssentialMeta(essentialTypes, perf); err != nil {
+ return nil, err
+ }
+
+ return seed20.EssentialSnaps(), nil
+}
+
// generateMountsMode* is called multiple times from initramfs until it
// no longer generates more mount points and just returns an empty output.
func generateMountsModeInstall(recoverySystem string) error {
@@ -97,46 +123,33 @@ func generateMountsModeInstall(recoverySystem string) error {
return err
}
if !isBaseMounted || !isKernelMounted || !isSnapdMounted {
- // load the recovery system and generate mounts for kernel/base
- systemSeed, err := seed.Open(seedDir, recoverySystem)
- if err != nil {
- return err
+ // load the recovery system and generate mounts for kernel/base
+ // and snapd
+ var whichTypes []snap.Type
+ if !isBaseMounted {
+ whichTypes = append(whichTypes, snap.TypeBase)
}
- // load assertions into a temporary database
- if err := systemSeed.LoadAssertions(nil, nil); err != nil {
- return err
+ if !isKernelMounted {
+ whichTypes = append(whichTypes, snap.TypeKernel)
}
- perf := timings.New(nil)
- // TODO:UC20: LoadMeta will verify all the snaps in the
- // seed, that is probably too much. We can expose more
- // dedicated helpers for this later.
- if err := systemSeed.LoadMeta(perf); err != nil {
- return err
+ if !isSnapdMounted {
+ whichTypes = append(whichTypes, snap.TypeSnapd)
}
+ essSnaps, err := recoverySystemEssentialSnaps(seedDir, recoverySystem, whichTypes)
+ if err != nil {
+ return fmt.Errorf("cannot load metadata and verify essential bootstrap snaps %v: %v", whichTypes, err)
+ }
+
// TODO:UC20: do we need more cross checks here?
- for _, essentialSnap := range systemSeed.EssentialSnaps() {
- snapf, err := snap.Open(essentialSnap.Path)
- if err != nil {
- return err
- }
- info, err := snap.ReadInfoFromSnapFile(snapf, essentialSnap.SideInfo)
- if err != nil {
- return err
- }
- switch info.GetType() {
+ for _, essentialSnap := range essSnaps {
+ switch essentialSnap.EssentialType {
case snap.TypeBase:
- if !isBaseMounted {
- fmt.Fprintf(stdout, "%s %s\n", essentialSnap.Path, filepath.Join(runMnt, "base"))
- }
+ fmt.Fprintf(stdout, "%s %s\n", essentialSnap.Path, filepath.Join(runMnt, "base"))
case snap.TypeKernel:
- if !isKernelMounted {
- // XXX: we need to cross-check the kernel path with snapd_recovery_kernel used by grub
- fmt.Fprintf(stdout, "%s %s\n", essentialSnap.Path, filepath.Join(runMnt, "kernel"))
- }
+ // TODO:UC20: we need to cross-check the kernel path with snapd_recovery_kernel used by grub
+ fmt.Fprintf(stdout, "%s %s\n", essentialSnap.Path, filepath.Join(runMnt, "kernel"))
case snap.TypeSnapd:
- if !isSnapdMounted {
- fmt.Fprintf(stdout, "%s %s\n", essentialSnap.Path, filepath.Join(runMnt, "snapd"))
- }
+ fmt.Fprintf(stdout, "%s %s\n", essentialSnap.Path, filepath.Join(runMnt, "snapd"))
}
}
}
@@ -218,7 +231,7 @@ func generateMountsModeRun() error {
// we have no fallback base!
return fmt.Errorf("modeenv corrupt: missing base setting")
}
- if modeEnv.BaseStatus == "try" {
+ if modeEnv.BaseStatus == boot.TryStatus {
// then we are trying a base snap update and there should be a
// try_base set in the modeenv too
if modeEnv.TryBase != "" {
@@ -227,17 +240,21 @@ func generateMountsModeRun() error {
if osutil.FileExists(tryBaseSnapPath) {
// set the TryBase and have the initramfs mount this base
// snap
- modeEnv.BaseStatus = "trying"
+ modeEnv.BaseStatus = boot.TryingStatus
base = modeEnv.TryBase
+ } else {
+ logger.Noticef("try-base snap %q does not exist", modeEnv.TryBase)
}
- // TODO:UC20: log a message somewhere if try base snap does not
- // exist?
+ } else {
+ logger.Noticef("try-base snap is empty, but \"base_status\" is \"trying\"")
}
// TODO:UC20: log a message if try_base is unset here?
- } else if modeEnv.BaseStatus == "trying" {
+ } else if modeEnv.BaseStatus == boot.TryingStatus {
// snapd failed to start with the base snap update, so we need to
// fallback to the old base snap and clear base_status
- modeEnv.BaseStatus = ""
+ modeEnv.BaseStatus = boot.DefaultStatus
+ } else if modeEnv.BaseStatus != boot.DefaultStatus {
+ logger.Noticef("\"base_status\" has an invalid setting: %q", modeEnv.BaseStatus)
}
baseSnapPath := filepath.Join(dataDir, "system-data", dirs.SnapBlobDir, base)
@@ -250,6 +267,12 @@ func generateMountsModeRun() error {
return err
}
if !isKernelMounted {
+ // make a map to easily check if a kernel snap is valid or not
+ validKernels := make(map[string]bool, len(modeEnv.CurrentKernels))
+ for _, validKernel := range modeEnv.CurrentKernels {
+ validKernels[validKernel] = true
+ }
+
// find ubuntu-boot bootloader to get the kernel_status and kernel.efi
// status so we can determine the right kernel snap to have mounted
@@ -278,27 +301,61 @@ func generateMountsModeRun() error {
return fmt.Errorf("no fallback kernel snap: %v", err)
}
+ kernelFile := kernel.Filename()
+ if !validKernels[kernelFile] {
+ // we don't trust the fallback kernel!
+ return fmt.Errorf("fallback kernel snap %q is not trusted in the modeenv", kernelFile)
+ }
+
// get kernel_status
m, err := ebl.GetBootVars("kernel_status")
if err != nil {
return fmt.Errorf("cannot get kernel_status from bootloader %s", ebl.Name())
}
- if m["kernel_status"] == "trying" {
+ if m["kernel_status"] == boot.TryingStatus {
// check for the try kernel
- tryKernel, tryKernelExists, err := ebl.TryKernel()
- // TODO:UC20: can we log somewhere if err != nil here?
- if tryKernelExists && err == nil {
- kernel = tryKernel
+ tryKernel, err := ebl.TryKernel()
+ if err == nil {
+ tryKernelFile := tryKernel.Filename()
+ if validKernels[tryKernelFile] {
+ kernelFile = tryKernelFile
+ } else {
+ logger.Noticef("try-kernel %q is not trusted in the modeenv", tryKernelFile)
+ }
+ } else if err != bootloader.ErrNoTryKernelRef {
+ logger.Noticef("missing try-kernel, even though \"kernel_status\" is \"trying\"")
}
// if we didn't have a try kernel, but we do have kernel_status ==
// trying we just fallback to using the normal kernel
+ // same goes for try kernel being untrusted - we will fallback to
+ // the normal kernel snap
}
- kernelPath := filepath.Join(dataDir, "system-data", dirs.SnapBlobDir, kernel.Filename())
+ kernelPath := filepath.Join(dataDir, "system-data", dirs.SnapBlobDir, kernelFile)
fmt.Fprintf(stdout, "%s %s\n", kernelPath, filepath.Join(runMnt, "kernel"))
}
- // 3.1 Write the modeenv out again
+
+ // 3.1 Maybe mount the snapd snap on first boot of run-mode
+ // TODO:UC20: Make RecoverySystem empty after successful first boot
+ // somewhere in devicestate
+ if modeEnv.RecoverySystem != "" {
+ isSnapdMounted, err := osutilIsMounted(filepath.Join(runMnt, "snapd"))
+ if err != nil {
+ return err
+ }
+
+ if !isSnapdMounted {
+ // load the recovery system and generate mount for snapd
+ essSnaps, err := recoverySystemEssentialSnaps(seedDir, modeEnv.RecoverySystem, []snap.Type{snap.TypeSnapd})
+ if err != nil {
+ return fmt.Errorf("cannot load metadata and verify snapd snap: %v", err)
+ }
+ fmt.Fprintf(stdout, "%s %s\n", essSnaps[0].Path, filepath.Join(runMnt, "snapd"))
+ }
+ }
+
+ // 4.1 Write the modeenv out again
return modeEnv.Write(filepath.Join(dataDir, "system-data"))
}
diff --git a/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go b/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go
index c0d5efb6bfc..fc6008422f4 100644
--- a/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go
+++ b/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go
@@ -279,6 +279,9 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep2(c *C) {
case 5:
c.Check(path, Equals, filepath.Join(s.runMnt, "kernel"))
return false, nil
+ case 6:
+ c.Check(path, Equals, filepath.Join(s.runMnt, "snapd"))
+ return false, nil
}
return false, fmt.Errorf("unexpected number of calls: %v", n)
})
@@ -286,7 +289,9 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep2(c *C) {
// write modeenv
modeEnv := boot.Modeenv{
- Base: "core20_123.snap",
+ RecoverySystem: "20191118",
+ Base: "core20_123.snap",
+ CurrentKernels: []string{"pc-kernel_1.snap"},
}
err := modeEnv.Write(filepath.Join(s.runMnt, "ubuntu-data", "system-data"))
c.Assert(err, IsNil)
@@ -304,9 +309,10 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep2(c *C) {
_, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
c.Assert(err, IsNil)
- c.Assert(n, Equals, 5)
+ c.Assert(n, Equals, 6)
c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`%[1]s/ubuntu-data/system-data/var/lib/snapd/snaps/core20_123.snap %[1]s/base
%[1]s/ubuntu-data/system-data/var/lib/snapd/snaps/pc-kernel_1.snap %[1]s/kernel
+%[1]s/ubuntu-seed/snaps/snapd_1.snap %[1]s/snapd
`, s.runMnt))
}
@@ -593,7 +599,8 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRunModeKernelSnapUpgradeHappy(
// write modeenv
modeEnv := &boot.Modeenv{
- Base: "core20_123.snap",
+ Base: "core20_123.snap",
+ CurrentKernels: []string{"pc-kernel_1.snap", "pc-kernel_2.snap"},
}
err := modeEnv.Write(filepath.Join(s.runMnt, "ubuntu-data", "system-data"))
c.Assert(err, IsNil)
@@ -630,3 +637,177 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRunModeKernelSnapUpgradeHappy(
c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`%[1]s/ubuntu-data/system-data/var/lib/snapd/snaps/pc-kernel_2.snap %[1]s/kernel
`, s.runMnt))
}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeUntrustedKernelSnap(c *C) {
+ n := 0
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ restore := main.MockOsutilIsMounted(func(path string) (bool, error) {
+ n++
+ switch n {
+ case 1:
+ c.Check(path, Equals, filepath.Join(s.runMnt, "ubuntu-seed"))
+ return true, nil
+ case 2:
+ c.Check(path, Equals, filepath.Join(s.runMnt, "ubuntu-boot"))
+ return true, nil
+ case 3:
+ c.Check(path, Equals, filepath.Join(s.runMnt, "ubuntu-data"))
+ return true, nil
+ case 4:
+ c.Check(path, Equals, filepath.Join(s.runMnt, "base"))
+ return true, nil
+ case 5:
+ c.Check(path, Equals, filepath.Join(s.runMnt, "kernel"))
+ return false, nil
+ }
+ return false, fmt.Errorf("unexpected number of calls: %v", n)
+ })
+ defer restore()
+
+ // write modeenv
+ modeEnv := boot.Modeenv{
+ Base: "core20_123.snap",
+ CurrentKernels: []string{"pc-kernel_1.snap"},
+ }
+ err := modeEnv.Write(filepath.Join(s.runMnt, "ubuntu-data", "system-data"))
+ c.Assert(err, IsNil)
+
+ // mock a bootloader
+ bloader := bootloadertest.Mock("mock", c.MkDir())
+ bootloader.Force(bloader)
+ defer bootloader.Force(nil)
+
+ // set the current kernel as a kernel not in CurrentKernels
+ kernel, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_2.snap")
+ c.Assert(err, IsNil)
+ r := bloader.SetRunKernelImageEnabledKernel(kernel)
+ defer r()
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, ErrorMatches, fmt.Sprintf("fallback kernel snap %q is not trusted in the modeenv", "pc-kernel_2.snap"))
+ c.Assert(n, Equals, 5)
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeUntrustedTryKernelSnapFallsBack(c *C) {
+ n := 0
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ restore := main.MockOsutilIsMounted(func(path string) (bool, error) {
+ n++
+ switch n {
+ case 1:
+ c.Check(path, Equals, filepath.Join(s.runMnt, "ubuntu-seed"))
+ return true, nil
+ case 2:
+ c.Check(path, Equals, filepath.Join(s.runMnt, "ubuntu-boot"))
+ return true, nil
+ case 3:
+ c.Check(path, Equals, filepath.Join(s.runMnt, "ubuntu-data"))
+ return true, nil
+ case 4:
+ c.Check(path, Equals, filepath.Join(s.runMnt, "base"))
+ return true, nil
+ case 5:
+ c.Check(path, Equals, filepath.Join(s.runMnt, "kernel"))
+ return false, nil
+ }
+ return false, fmt.Errorf("unexpected number of calls: %v", n)
+ })
+ defer restore()
+
+ // write modeenv
+ modeEnv := boot.Modeenv{
+ Base: "core20_123.snap",
+ CurrentKernels: []string{"pc-kernel_1.snap"},
+ }
+ err := modeEnv.Write(filepath.Join(s.runMnt, "ubuntu-data", "system-data"))
+ c.Assert(err, IsNil)
+
+ // mock a bootloader
+ bloader := bootloadertest.Mock("mock", c.MkDir())
+ bootloader.Force(bloader)
+ defer bootloader.Force(nil)
+
+ // set the try kernel as a kernel not in CurrentKernels
+ kernel2, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_2.snap")
+ c.Assert(err, IsNil)
+ r := bloader.SetRunKernelImageEnabledTryKernel(kernel2)
+ defer r()
+
+ // set the normal kernel as a valid kernel
+ kernel1, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap")
+ c.Assert(err, IsNil)
+ r = bloader.SetRunKernelImageEnabledKernel(kernel1)
+ defer r()
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+
+ // TODO:UC20: if we have somewhere to log errors from snap-bootstrap during
+ // the initramfs, check that log here
+ c.Assert(err, IsNil)
+ c.Assert(n, Equals, 5)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`%[1]s/ubuntu-data/system-data/var/lib/snapd/snaps/pc-kernel_1.snap %[1]s/kernel
+`, s.runMnt))
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeKernelStatusTryingNoTryKernel(c *C) {
+ n := 0
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ restore := main.MockOsutilIsMounted(func(path string) (bool, error) {
+ n++
+ switch n {
+ case 1:
+ c.Check(path, Equals, filepath.Join(s.runMnt, "ubuntu-seed"))
+ return true, nil
+ case 2:
+ c.Check(path, Equals, filepath.Join(s.runMnt, "ubuntu-boot"))
+ return true, nil
+ case 3:
+ c.Check(path, Equals, filepath.Join(s.runMnt, "ubuntu-data"))
+ return true, nil
+ case 4:
+ c.Check(path, Equals, filepath.Join(s.runMnt, "base"))
+ return true, nil
+ case 5:
+ c.Check(path, Equals, filepath.Join(s.runMnt, "kernel"))
+ return false, nil
+ }
+ return false, fmt.Errorf("unexpected number of calls: %v", n)
+ })
+ defer restore()
+
+ // write modeenv
+ modeEnv := boot.Modeenv{
+ Base: "core20_123.snap",
+ CurrentKernels: []string{"pc-kernel_1.snap"},
+ }
+ err := modeEnv.Write(filepath.Join(s.runMnt, "ubuntu-data", "system-data"))
+ c.Assert(err, IsNil)
+
+ // mock a bootloader
+ bloader := bootloadertest.Mock("mock", c.MkDir())
+ bootloader.Force(bloader)
+ defer bootloader.Force(nil)
+
+ // we are in trying mode, but don't set a try-kernel so we fallback to the
+ // fallback kernel
+ err = bloader.SetBootVars(map[string]string{"kernel_status": boot.TryingStatus})
+ c.Assert(err, IsNil)
+
+ // set the normal kernel as a valid kernel
+ kernel, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap")
+ c.Assert(err, IsNil)
+ r := bloader.SetRunKernelImageEnabledKernel(kernel)
+ defer r()
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+
+ // TODO:UC20: if we have somewhere to log errors from snap-bootstrap during
+ // the initramfs, check that log here
+ c.Assert(err, IsNil)
+ c.Assert(n, Equals, 5)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`%[1]s/ubuntu-data/system-data/var/lib/snapd/snaps/pc-kernel_1.snap %[1]s/kernel
+`, s.runMnt))
+}
diff --git a/cmd/snap-bootstrap/cmd_recovery_chooser_trigger.go b/cmd/snap-bootstrap/cmd_recovery_chooser_trigger.go
new file mode 100644
index 00000000000..b0b4cb724a4
--- /dev/null
+++ b/cmd/snap-bootstrap/cmd_recovery_chooser_trigger.go
@@ -0,0 +1,106 @@
+// -*- 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 .
+ *
+ */
+
+package main
+
+import (
+ "fmt"
+ "os"
+ "time"
+
+ "github.com/jessevdk/go-flags"
+
+ "github.com/snapcore/snapd/cmd/snap-bootstrap/triggerwatch"
+ "github.com/snapcore/snapd/logger"
+)
+
+func init() {
+ const (
+ short = "Detect Ubuntu Core recovery chooser trigger"
+ long = ""
+ )
+
+ addCommandBuilder(func(parser *flags.Parser) {
+ if _, err := parser.AddCommand("recovery-chooser-trigger", short, long, &cmdRecoveryChooserTrigger{}); err != nil {
+ panic(err)
+ }
+ })
+}
+
+var (
+ triggerwatchWait = triggerwatch.Wait
+
+ // default trigger wait timeout
+ defaultTimeout = 10 * time.Second
+
+ // default marker file location
+ defaultMarkerFile = "/run/snapd-recovery-chooser-triggered"
+)
+
+type cmdRecoveryChooserTrigger struct {
+ MarkerFile string `long:"marker-file" value-name:"filename" description:"trigger marker file location"`
+ WaitTimeout string `long:"wait-timeout" value-name:"duration" description:"trigger wait timeout"`
+}
+
+func (c *cmdRecoveryChooserTrigger) Execute(args []string) error {
+ // TODO:UC20: check in the gadget if there is a hook or some binary we
+ // should run for trigger detection. This will require some design work
+ // and also thinking if/how such a hook can be confined.
+
+ timeout := defaultTimeout
+ markerFile := defaultMarkerFile
+
+ if c.WaitTimeout != "" {
+ userTimeout, err := time.ParseDuration(c.WaitTimeout)
+ if err != nil {
+ logger.Noticef("cannot parse duration %q, using default", c.WaitTimeout)
+ } else {
+ timeout = userTimeout
+ }
+ }
+ if c.MarkerFile != "" {
+ markerFile = c.MarkerFile
+ }
+ logger.Noticef("trigger timeout %v", timeout)
+ logger.Noticef("marker file %v", markerFile)
+
+ _, err := os.Stat(markerFile)
+ if err == nil {
+ logger.Noticef("marker already present")
+ return nil
+ }
+
+ err = triggerwatchWait(timeout)
+ if err != nil {
+ if err == triggerwatch.ErrTriggerNotDetected {
+ logger.Noticef("trigger not detected")
+ return nil
+ }
+ return err
+ }
+
+ // got the trigger, try to create the marker file
+ m, err := os.Create(markerFile)
+ if err != nil {
+ return fmt.Errorf("cannot create the marker file: %q", err)
+ }
+ m.Close()
+
+ return nil
+}
diff --git a/cmd/snap-bootstrap/cmd_recovery_chooser_trigger_test.go b/cmd/snap-bootstrap/cmd_recovery_chooser_trigger_test.go
new file mode 100644
index 00000000000..7a9dae77180
--- /dev/null
+++ b/cmd/snap-bootstrap/cmd_recovery_chooser_trigger_test.go
@@ -0,0 +1,145 @@
+// -*- 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 .
+ *
+ */
+
+package main_test
+
+import (
+ "errors"
+ "io/ioutil"
+ "path/filepath"
+ "time"
+
+ . "gopkg.in/check.v1"
+
+ main "github.com/snapcore/snapd/cmd/snap-bootstrap"
+ "github.com/snapcore/snapd/cmd/snap-bootstrap/triggerwatch"
+ "github.com/snapcore/snapd/testutil"
+)
+
+func (s *cmdSuite) TestRecoveryChooserTriggerDefaults(c *C) {
+ n := 0
+ marker := filepath.Join(c.MkDir(), "marker")
+ passedTimeout := time.Duration(0)
+
+ restore := main.MockDefaultMarkerFile(marker)
+ defer restore()
+ restore = main.MockTriggerwatchWait(func(timeout time.Duration) error {
+ passedTimeout = timeout
+ n++
+ // trigger happened
+ return nil
+ })
+ defer restore()
+
+ rest, err := main.Parser().ParseArgs([]string{"recovery-chooser-trigger"})
+ c.Assert(err, IsNil)
+ c.Assert(rest, HasLen, 0)
+ c.Check(n, Equals, 1)
+ c.Check(passedTimeout, Equals, main.DefaultTimeout)
+ c.Check(marker, testutil.FilePresent)
+}
+
+func (s *cmdSuite) TestRecoveryChooserTriggerNoTrigger(c *C) {
+ n := 0
+ marker := filepath.Join(c.MkDir(), "marker")
+
+ restore := main.MockDefaultMarkerFile(marker)
+ defer restore()
+ restore = main.MockTriggerwatchWait(func(_ time.Duration) error {
+ n++
+ // trigger did not happen
+ return triggerwatch.ErrTriggerNotDetected
+ })
+ defer restore()
+
+ _, err := main.Parser().ParseArgs([]string{"recovery-chooser-trigger"})
+ c.Assert(err, IsNil)
+ c.Check(n, Equals, 1)
+ c.Check(marker, testutil.FileAbsent)
+}
+
+func (s *cmdSuite) TestRecoveryChooserTriggerTakesOptions(c *C) {
+ marker := filepath.Join(c.MkDir(), "foobar")
+ n := 0
+ passedTimeout := time.Duration(0)
+
+ restore := main.MockTriggerwatchWait(func(timeout time.Duration) error {
+ passedTimeout = timeout
+ n++
+ // trigger happened
+ return nil
+ })
+ defer restore()
+
+ rest, err := main.Parser().ParseArgs([]string{
+ "recovery-chooser-trigger",
+ "--wait-timeout", "2m",
+ "--marker-file", marker,
+ })
+ c.Assert(err, IsNil)
+ c.Assert(rest, HasLen, 0)
+ c.Check(n, Equals, 1)
+ c.Check(passedTimeout, Equals, 2*time.Minute)
+ c.Check(marker, testutil.FilePresent)
+}
+
+func (s *cmdSuite) TestRecoveryChooserTriggerDoesNothingWhenMarkerPresent(c *C) {
+ marker := filepath.Join(c.MkDir(), "foobar")
+ n := 0
+ restore := main.MockTriggerwatchWait(func(_ time.Duration) error {
+ n++
+ return errors.New("unexpected call")
+ })
+ defer restore()
+
+ err := ioutil.WriteFile(marker, nil, 0644)
+ c.Assert(err, IsNil)
+
+ rest, err := main.Parser().ParseArgs([]string{
+ "recovery-chooser-trigger",
+ "--marker-file", marker,
+ })
+ c.Assert(err, IsNil)
+ c.Assert(rest, HasLen, 0)
+ // not called
+ c.Check(n, Equals, 0)
+}
+
+func (s *cmdSuite) TestRecoveryChooserTriggerBadDurationFallback(c *C) {
+ n := 0
+ passedTimeout := time.Duration(0)
+ restore := main.MockDefaultMarkerFile(filepath.Join(c.MkDir(), "marker"))
+ defer restore()
+
+ restore = main.MockTriggerwatchWait(func(timeout time.Duration) error {
+ passedTimeout = timeout
+ n++
+ // trigger happened
+ return triggerwatch.ErrTriggerNotDetected
+ })
+ defer restore()
+
+ _, err := main.Parser().ParseArgs([]string{
+ "recovery-chooser-trigger",
+ "--wait-timeout=foobar",
+ })
+ c.Assert(err, IsNil)
+ c.Check(n, Equals, 1)
+ c.Check(passedTimeout, Equals, main.DefaultTimeout)
+}
diff --git a/cmd/snap-bootstrap/export_test.go b/cmd/snap-bootstrap/export_test.go
index 93a6496ff86..f90d0ed6848 100644
--- a/cmd/snap-bootstrap/export_test.go
+++ b/cmd/snap-bootstrap/export_test.go
@@ -21,6 +21,7 @@ package main
import (
"io"
+ "time"
"github.com/snapcore/snapd/cmd/snap-bootstrap/bootstrap"
)
@@ -60,3 +61,21 @@ func MockRunMnt(newRunMnt string) (restore func()) {
runMnt = oldRunMnt
}
}
+
+func MockTriggerwatchWait(f func(_ time.Duration) error) (restore func()) {
+ oldTriggerwatchWait := triggerwatchWait
+ triggerwatchWait = f
+ return func() {
+ triggerwatchWait = oldTriggerwatchWait
+ }
+}
+
+var DefaultTimeout = defaultTimeout
+
+func MockDefaultMarkerFile(p string) (restore func()) {
+ old := defaultMarkerFile
+ defaultMarkerFile = p
+ return func() {
+ defaultMarkerFile = old
+ }
+}
diff --git a/cmd/snap-bootstrap/main.go b/cmd/snap-bootstrap/main.go
index c6426455d26..27367d40203 100644
--- a/cmd/snap-bootstrap/main.go
+++ b/cmd/snap-bootstrap/main.go
@@ -23,6 +23,8 @@ import (
"os"
"github.com/jessevdk/go-flags"
+
+ "github.com/snapcore/snapd/logger"
)
var (
@@ -36,6 +38,13 @@ such as initramfs.
commandBuilders []func(*flags.Parser)
)
+func init() {
+ err := logger.SimpleSetup()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "WARNING: failed to activate logging: %s\n", err)
+ }
+}
+
func main() {
err := run(os.Args[1:])
if err != nil {
diff --git a/cmd/snap-bootstrap/main_test.go b/cmd/snap-bootstrap/main_test.go
index 783046e895c..739442c69e9 100644
--- a/cmd/snap-bootstrap/main_test.go
+++ b/cmd/snap-bootstrap/main_test.go
@@ -25,15 +25,25 @@ import (
. "gopkg.in/check.v1"
main "github.com/snapcore/snapd/cmd/snap-bootstrap"
+ "github.com/snapcore/snapd/logger"
+ "github.com/snapcore/snapd/testutil"
)
// Hook up check.v1 into the "go test" runner
func Test(t *testing.T) { TestingT(t) }
-type cmdSuite struct{}
+type cmdSuite struct {
+ testutil.BaseTest
+}
var _ = Suite(&cmdSuite{})
+func (s *cmdSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+ _, r := logger.MockLogger()
+ s.AddCleanup(r)
+}
+
func (s *cmdSuite) TestNoArgsErrors(c *C) {
_, err := main.Parser().ParseArgs(nil)
c.Assert(err, ErrorMatches, "Please specify .*")
diff --git a/cmd/snap-bootstrap/triggerwatch/evdev.go b/cmd/snap-bootstrap/triggerwatch/evdev.go
new file mode 100644
index 00000000000..a4fc432d917
--- /dev/null
+++ b/cmd/snap-bootstrap/triggerwatch/evdev.go
@@ -0,0 +1,244 @@
+// -*- 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 .
+ *
+ */
+
+package triggerwatch
+
+import (
+ "fmt"
+ "syscall"
+ "time"
+ "unsafe"
+
+ // TODO:UC20: not packaged, reimplement the minimal things we need?
+ evdev "github.com/gvalkov/golang-evdev"
+
+ "github.com/snapcore/snapd/logger"
+)
+
+type keyEvent struct {
+ Dev triggerDevice
+ Err error
+}
+
+type triggerEventFilter struct {
+ Key string
+}
+
+var (
+ strToKey = map[string]int{
+ "KEY_ESC": evdev.KEY_ESC,
+ "KEY_1": evdev.KEY_1,
+ "KEY_2": evdev.KEY_2,
+ "KEY_3": evdev.KEY_3,
+ "KEY_4": evdev.KEY_4,
+ "KEY_5": evdev.KEY_5,
+ "KEY_6": evdev.KEY_6,
+ "KEY_7": evdev.KEY_7,
+ "KEY_8": evdev.KEY_8,
+ "KEY_9": evdev.KEY_9,
+ "KEY_0": evdev.KEY_0,
+ }
+ evKeyCapability = evdev.CapabilityType{Type: evdev.EV_KEY, Name: "EV_KEY"}
+
+ // hold time needed to trigger the event
+ holdToTrigger = 2 * time.Second
+)
+
+func init() {
+ trigger = &evdevInput{}
+}
+
+type evdevKeyboardInputDevice struct {
+ keyCode uint16
+ dev *evdev.InputDevice
+}
+
+func (e *evdevKeyboardInputDevice) probeKeyState() (bool, error) {
+ // XXX: evdev defines EVIOCGKEY using MAX_NAME_SIZE which is larger than
+ // what is needed to store the key bitmap with KEY_MAX bits, but we need
+ // to play along since the value is already encoded
+ keyBitmap := new([evdev.MAX_NAME_SIZE]byte)
+
+ // obtain the large bitmap with all key states
+ // https://elixir.bootlin.com/linux/v5.5.5/source/drivers/input/evdev.c#L1163
+ _, _, err := syscall.RawSyscall(syscall.SYS_IOCTL, e.dev.File.Fd(), uintptr(evdev.EVIOCGKEY), uintptr(unsafe.Pointer(keyBitmap)))
+ if err != 0 {
+ return false, err
+ }
+ byteIdx := e.keyCode / 8
+ keyMask := byte(1 << (e.keyCode % 8))
+ isDown := keyBitmap[byteIdx]&keyMask != 0
+ return isDown, nil
+}
+
+func (e *evdevKeyboardInputDevice) WaitForTrigger(ch chan keyEvent) {
+ logger.Noticef("%s: starting wait", e)
+
+ // XXX: do not mess with setting the key repeat rate, as it's cumbersome
+ // and golang-evdev SetRepeatRate() parameter order is actually reversed
+ // wrt. what the kernel does. The evdev interprets EVIOCSREP arguments
+ // as (delay, repeat)
+ // https://elixir.bootlin.com/linux/latest/source/drivers/input/evdev.c#L1072
+ // but the wrapper is passing is passing (repeat, delay)
+ // https://github.com/gvalkov/golang-evdev/blob/287e62b94bcb850ab42e711bd74b2875da83af2c/device.go#L226-L230
+
+ keyDown, err := e.probeKeyState()
+ if err != nil {
+ ch <- keyEvent{Err: fmt.Errorf("cannot obtain initial key state: %v", err), Dev: e}
+ }
+ if keyDown {
+ // looks like the key is pressed initially, we don't know when
+ // that happened, but pretend it happened just now
+ logger.Noticef("%s: key is already down", e)
+ }
+
+ type evdevEvent struct {
+ kev *evdev.KeyEvent
+ err error
+ }
+
+ // buffer large enough to collect some events
+ evChan := make(chan evdevEvent, 10)
+
+ monitorKey := func() {
+ for {
+ ies, err := e.dev.Read()
+ if err != nil {
+ evChan <- evdevEvent{err: err}
+ break
+ }
+ for _, ie := range ies {
+ if ie.Type != evdev.EV_KEY || ie.Code != e.keyCode {
+ continue
+ }
+ kev := evdev.NewKeyEvent(&ie)
+ evChan <- evdevEvent{kev: kev}
+ }
+ }
+ close(evChan)
+ }
+
+ go monitorKey()
+
+ holdTimer := time.NewTimer(holdToTrigger)
+ // no sense to keep it running later either
+ defer holdTimer.Stop()
+
+ if !keyDown {
+ // key isn't held yet, stop the timer
+ holdTimer.Stop()
+ }
+
+ // invariant: tholdTimer is running iff keyDown is true, otherwise is stopped
+Loop:
+ for {
+ select {
+ case ev := <-evChan:
+ if ev.err != nil {
+ holdTimer.Stop()
+ ch <- keyEvent{Err: err, Dev: e}
+ break Loop
+ }
+ kev := ev.kev
+ switch kev.State {
+ case evdev.KeyDown:
+ if keyDown {
+ // unexpected, but possible if we missed
+ // a key up event right after checking
+ // the initial keyboard state when the
+ // key was still down
+ if !holdTimer.Stop() {
+ // drain the channel before the
+ // timer gets reset
+ <-holdTimer.C
+ }
+ }
+ keyDown = true
+ // timer is stopped at this point
+ holdTimer.Reset(holdToTrigger)
+ logger.Noticef("%s: trigger key down", e)
+ case evdev.KeyHold:
+ if !keyDown {
+ keyDown = true
+ // timer is not running yet at this point
+ holdTimer.Reset(holdToTrigger)
+ logger.Noticef("%s: unexpected hold without down", e)
+ }
+ case evdev.KeyUp:
+ // no need to drain the channel, if it expired,
+ // we'll handle it in next iteration
+ holdTimer.Stop()
+ keyDown = false
+ logger.Noticef("%s: trigger key up", e)
+ }
+ case <-holdTimer.C:
+ logger.Noticef("%s: hold complete", e)
+ ch <- keyEvent{Dev: e}
+ break Loop
+ }
+ }
+}
+
+func (e *evdevKeyboardInputDevice) String() string {
+ return fmt.Sprintf("%s: %s", e.dev.Phys, e.dev.Name)
+}
+
+func (e *evdevKeyboardInputDevice) Close() {
+ e.dev.File.Close()
+}
+
+type evdevInput struct{}
+
+func (e *evdevInput) FindMatchingDevices(filter triggerEventFilter) ([]triggerDevice, error) {
+ devices, err := evdev.ListInputDevices()
+ if err != nil {
+ return nil, fmt.Errorf("cannot list input devices: %v", err)
+ }
+
+ // NOTE: this supports so far only key input devices
+
+ kc, ok := strToKey[filter.Key]
+ if !ok {
+ return nil, fmt.Errorf("cannot find a key matching the filter %q", filter.Key)
+ }
+ cap := evdev.CapabilityCode{Code: kc, Name: filter.Key}
+
+ match := func(dev *evdev.InputDevice) triggerDevice {
+ for _, cc := range dev.Capabilities[evKeyCapability] {
+ if cc == cap {
+ return &evdevKeyboardInputDevice{
+ dev: dev,
+ keyCode: uint16(cap.Code),
+ }
+ }
+ }
+ return nil
+ }
+ // collect all input devices that can emit the trigger key
+ var devs []triggerDevice
+ for _, dev := range devices {
+ idev := match(dev)
+ if idev != nil {
+ devs = append(devs, idev)
+ } else {
+ defer dev.File.Close()
+ }
+ }
+ return devs, nil
+}
diff --git a/cmd/snap-bootstrap/triggerwatch/export_test.go b/cmd/snap-bootstrap/triggerwatch/export_test.go
new file mode 100644
index 00000000000..1cad33c2ccb
--- /dev/null
+++ b/cmd/snap-bootstrap/triggerwatch/export_test.go
@@ -0,0 +1,32 @@
+// -*- 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 .
+ *
+ */
+package triggerwatch
+
+func MockInput(newInput TriggerProvider) (restore func()) {
+ oldInput := trigger
+ trigger = newInput
+ return func() {
+ trigger = oldInput
+ }
+}
+
+type TriggerProvider = triggerProvider
+type TriggerDevice = triggerDevice
+type TriggerCapabilityFilter = triggerEventFilter
+type KeyEvent = keyEvent
diff --git a/cmd/snap-bootstrap/triggerwatch/triggerwatch.go b/cmd/snap-bootstrap/triggerwatch/triggerwatch.go
new file mode 100644
index 00000000000..13601e18127
--- /dev/null
+++ b/cmd/snap-bootstrap/triggerwatch/triggerwatch.go
@@ -0,0 +1,88 @@
+// -*- 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 .
+ *
+ */
+
+package triggerwatch
+
+import (
+ "errors"
+ "fmt"
+ "time"
+
+ "github.com/snapcore/snapd/logger"
+)
+
+type triggerProvider interface {
+ FindMatchingDevices(filter triggerEventFilter) ([]triggerDevice, error)
+}
+
+type triggerDevice interface {
+ WaitForTrigger(chan keyEvent)
+ String() string
+ Close()
+}
+
+var (
+ // trigger mechanism
+ trigger triggerProvider
+
+ // wait for '1' to be pressed
+ triggerFilter = triggerEventFilter{Key: "KEY_1"}
+
+ ErrTriggerNotDetected = errors.New("trigger not detected")
+)
+
+// Wait waits for a trigger on the available trigger devices for a given amount
+// of time. Returns nil if one was detected, ErrTriggerNotDetected if timeout
+// was hit, or other non-nil error.
+func Wait(timeout time.Duration) error {
+ if trigger == nil {
+ logger.Panicf("trigger is unset")
+ }
+
+ devices, err := trigger.FindMatchingDevices(triggerFilter)
+ if err != nil {
+ return fmt.Errorf("cannot list trigger devices: %v", err)
+ }
+ if devices == nil {
+ return fmt.Errorf("cannot find matching devices")
+ }
+
+ logger.Noticef("waiting for trigger key: %v", triggerFilter.Key)
+
+ // wait for a couple of second for the key
+ detectKeyCh := make(chan keyEvent, len(devices))
+
+ for _, dev := range devices {
+ go dev.WaitForTrigger(detectKeyCh)
+ defer dev.Close()
+ }
+
+ select {
+ case kev := <-detectKeyCh:
+ if kev.Err != nil {
+ return err
+ }
+ // channel got closed without an error
+ logger.Noticef("%s: + got trigger key %v", kev.Dev, triggerFilter.Key)
+ case <-time.After(timeout):
+ return ErrTriggerNotDetected
+ }
+
+ return nil
+}
diff --git a/cmd/snap-bootstrap/triggerwatch/triggerwatch_test.go b/cmd/snap-bootstrap/triggerwatch/triggerwatch_test.go
new file mode 100644
index 00000000000..2a3c6d704ca
--- /dev/null
+++ b/cmd/snap-bootstrap/triggerwatch/triggerwatch_test.go
@@ -0,0 +1,130 @@
+// -*- 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 .
+ *
+ */
+
+package triggerwatch_test
+
+import (
+ "fmt"
+ "testing"
+ "time"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/cmd/snap-bootstrap/triggerwatch"
+)
+
+// Hook up check.v1 into the "go test" runner
+func Test(t *testing.T) { TestingT(t) }
+
+type triggerwatchSuite struct{}
+
+var _ = Suite(&triggerwatchSuite{})
+
+type mockTriggerDevice struct {
+ waitForTriggerCalls int
+ closeCalls int
+ ev *triggerwatch.KeyEvent
+}
+
+func (m *mockTriggerDevice) WaitForTrigger(n chan triggerwatch.KeyEvent) {
+ m.waitForTriggerCalls++
+ if m.ev != nil {
+ ev := *m.ev
+ ev.Dev = m
+ n <- ev
+ }
+}
+
+func (m *mockTriggerDevice) String() string { return "mock-device" }
+func (m *mockTriggerDevice) Close() { m.closeCalls++ }
+
+type mockTrigger struct {
+ f triggerwatch.TriggerCapabilityFilter
+ d *mockTriggerDevice
+ err error
+
+ findMatchingCalls int
+}
+
+func (m *mockTrigger) FindMatchingDevices(f triggerwatch.TriggerCapabilityFilter) ([]triggerwatch.TriggerDevice, error) {
+ m.findMatchingCalls++
+
+ m.f = f
+ if m.err != nil {
+ return nil, m.err
+ }
+ if m.d != nil {
+ return []triggerwatch.TriggerDevice{m.d}, nil
+ }
+ return nil, nil
+}
+
+const testTriggerTimeout = 5 * time.Millisecond
+
+func (s *triggerwatchSuite) TestNoDevsWaitKey(c *C) {
+ md := &mockTriggerDevice{ev: &triggerwatch.KeyEvent{}}
+ mi := &mockTrigger{d: md}
+ restore := triggerwatch.MockInput(mi)
+ defer restore()
+
+ err := triggerwatch.Wait(testTriggerTimeout)
+ c.Assert(err, IsNil)
+ c.Assert(mi.findMatchingCalls, Equals, 1)
+ c.Assert(md.waitForTriggerCalls, Equals, 1)
+ c.Assert(md.closeCalls, Equals, 1)
+}
+
+func (s *triggerwatchSuite) TestNoDevsWaitKeyTimeout(c *C) {
+ md := &mockTriggerDevice{}
+ mi := &mockTrigger{d: md}
+ restore := triggerwatch.MockInput(mi)
+ defer restore()
+
+ err := triggerwatch.Wait(testTriggerTimeout)
+ c.Assert(err, Equals, triggerwatch.ErrTriggerNotDetected)
+ c.Assert(mi.findMatchingCalls, Equals, 1)
+ c.Assert(md.waitForTriggerCalls, Equals, 1)
+ c.Assert(md.closeCalls, Equals, 1)
+}
+
+func (s *triggerwatchSuite) TestNoDevsWaitNoMatching(c *C) {
+ mi := &mockTrigger{}
+ restore := triggerwatch.MockInput(mi)
+ defer restore()
+
+ err := triggerwatch.Wait(testTriggerTimeout)
+ c.Assert(err, ErrorMatches, "cannot find matching devices")
+}
+
+func (s *triggerwatchSuite) TestNoDevsWaitMatchingError(c *C) {
+ mi := &mockTrigger{err: fmt.Errorf("failed")}
+ restore := triggerwatch.MockInput(mi)
+ defer restore()
+
+ err := triggerwatch.Wait(testTriggerTimeout)
+ c.Assert(err, ErrorMatches, "cannot list trigger devices: failed")
+}
+
+func (s *triggerwatchSuite) TestChecksInput(c *C) {
+ restore := triggerwatch.MockInput(nil)
+ defer restore()
+
+ c.Assert(func() { triggerwatch.Wait(testTriggerTimeout) },
+ Panics, "trigger is unset")
+}
diff --git a/cmd/snap-confine/snap-confine.apparmor.in b/cmd/snap-confine/snap-confine.apparmor.in
index b3b92ba7b69..9f41bcb060c 100644
--- a/cmd/snap-confine/snap-confine.apparmor.in
+++ b/cmd/snap-confine/snap-confine.apparmor.in
@@ -565,4 +565,12 @@
# silence noisy denials breaks nested lxd. Until the cause is determined,
# do not use an explicit deny for unix. (LP: #1855355)
#deny unix,
+
+ # Explicitly deny these accesses which show up on Arch to silence the
+ # denials for this unneeded access.
+ deny /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libnss_files-[0-9]*.so* mr,
+ deny /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libnss_mymachines.[0-9]*.so* mr,
+ deny /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libnss_systemd.[0-9]*.so* mr,
+ deny /etc/nsswitch.conf r,
+ deny /etc/passwd r,
}
diff --git a/cmd/snap-failure/cmd_snapd.go b/cmd/snap-failure/cmd_snapd.go
index 14253aa69f5..cccdf4201d4 100644
--- a/cmd/snap-failure/cmd_snapd.go
+++ b/cmd/snap-failure/cmd_snapd.go
@@ -126,6 +126,7 @@ func (c *cmdSnapd) Execute(args []string) error {
cmd := exec.Command(snapdPath)
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "SNAPD_REVERT_TO_REV="+prevRev)
+ cmd.Env = append(cmd.Env, "SNAPD_DEBUG=1")
cmd.Stdout = Stdout
cmd.Stderr = Stderr
if err = cmd.Run(); err != nil {
@@ -133,14 +134,25 @@ func (c *cmdSnapd) Execute(args []string) error {
}
logger.Noticef("restarting snapd socket")
- // we need to reset the failure state to be able to restart again
- if output, err := exec.Command("systemctl", "reset-failed", "snapd.socket").CombinedOutput(); err != nil {
- return osutil.OutputErr(output, err)
- }
// at this point our manually started snapd stopped and
- // removed the /run/snap* sockets (this is a feature of
+ // should have removed the /run/snap* sockets (this is a feature of
// golang) - we need to restart snapd.socket to make them
// available again.
+
+ // we need to reset the failure state to be able to restart again
+ if output, err := exec.Command("systemctl", "reset-failed", "snapd.socket").CombinedOutput(); err != nil {
+ logger.Noticef("failed to reset-failed snapd.socket: %v", osutil.OutputErr(output, err))
+ // don't die if we fail to reset the failed state of snapd.socket, as
+ // the restart itself could still work
+ }
+ // be extra robust and if the socket file still somehow exists delete it
+ // before restarting, otherwise the restart command will fail because the
+ // systemd can't create the file
+ // always remove to avoid TOCTOU issues but don't complain about ENOENT
+ err = os.Remove(dirs.SnapdSocket)
+ if err != nil && !os.IsNotExist(err) {
+ logger.Noticef("snapd socket still exists before restarting socket service, but unable to remove: %v", err)
+ }
output, err = exec.Command("systemctl", "restart", "snapd.socket").CombinedOutput()
if err != nil {
return osutil.OutputErr(output, err)
diff --git a/cmd/snap-failure/cmd_snapd_test.go b/cmd/snap-failure/cmd_snapd_test.go
index db92a500724..aab1620c0ec 100644
--- a/cmd/snap-failure/cmd_snapd_test.go
+++ b/cmd/snap-failure/cmd_snapd_test.go
@@ -29,6 +29,7 @@ import (
failure "github.com/snapcore/snapd/cmd/snap-failure"
"github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/testutil"
)
@@ -235,3 +236,43 @@ exit 123
{"systemctl", "stop", "snapd.socket"},
})
}
+
+func (r *failureSuite) TestStickySnapdSocket(c *C) {
+ origArgs := os.Args
+ defer func() { os.Args = origArgs }()
+
+ writeSeqFile(c, "snapd", snap.R(123), []*snap.SideInfo{
+ {Revision: snap.R(100)},
+ {Revision: snap.R(123)},
+ })
+
+ err := os.MkdirAll(filepath.Dir(dirs.SnapdSocket), 0755)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(dirs.SnapdSocket, []byte{}, 0755)
+ c.Assert(err, IsNil)
+
+ // mock snapd in the core snap
+ snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "snapd", "100", "/usr/lib/snapd/snapd"),
+ `test "$SNAPD_REVERT_TO_REV" = "100"`)
+ defer snapdCmd.Restore()
+
+ systemctlCmd := testutil.MockCommand(c, "systemctl", "")
+ defer systemctlCmd.Restore()
+
+ os.Args = []string{"snap-failure", "snapd"}
+ err = failure.Run()
+ c.Check(err, IsNil)
+ c.Check(r.Stderr(), HasLen, 0)
+
+ c.Check(snapdCmd.Calls(), DeepEquals, [][]string{
+ {"snapd"},
+ })
+ c.Check(systemctlCmd.Calls(), DeepEquals, [][]string{
+ {"systemctl", "stop", "snapd.socket"},
+ {"systemctl", "reset-failed", "snapd.socket"},
+ {"systemctl", "restart", "snapd.socket"},
+ })
+
+ // make sure the socket file was deleted
+ c.Assert(osutil.FileExists(dirs.SnapdSocket), Equals, false)
+}
diff --git a/cmd/snap-preseed/main_test.go b/cmd/snap-preseed/main_test.go
index 5460b828c85..f5172062c9e 100644
--- a/cmd/snap-preseed/main_test.go
+++ b/cmd/snap-preseed/main_test.go
@@ -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"
@@ -171,6 +172,7 @@ func (s *startPreseedSuite) TestRunPreseedHappy(c *C) {
}
type Fake16Seed struct {
+ AssertsModel *asserts.Model
Essential []*seed.Snap
LoadMetaErr error
LoadAssertionsErr error
@@ -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 {
@@ -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()
@@ -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()
@@ -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()
@@ -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")
@@ -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)
diff --git a/cmd/snap-preseed/preseed_linux.go b/cmd/snap-preseed/preseed_linux.go
index cbb3317071e..2749e077820 100644
--- a/cmd/snap-preseed/preseed_linux.go
+++ b/cmd/snap-preseed/preseed_linux.go
@@ -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+`
diff --git a/data/selinux/snappy.te b/data/selinux/snappy.te
index d9f5c11376a..30cf93e990f 100644
--- a/data/selinux/snappy.te
+++ b/data/selinux/snappy.te
@@ -484,6 +484,11 @@ kernel_read_net_sysctls(snappy_mount_t)
kernel_search_network_sysctl(snappy_mount_t)
dev_read_sysfs(snappy_mount_t)
+# snap-update-ns is started using a file descriptor, meaning ld.so runs in the
+# mount ns and may try to read/mmap cache files inside
+fs_read_tmpfs_files(snappy_mount_t)
+mmap_read_files_pattern(snappy_mount_t, tmpfs_t, tmpfs_t)
+
########################################
#
# snap-confine local policy
diff --git a/data/systemd/snapd.recovery-chooser-trigger.service.in b/data/systemd/snapd.recovery-chooser-trigger.service.in
new file mode 100644
index 00000000000..1ac7964f46f
--- /dev/null
+++ b/data/systemd/snapd.recovery-chooser-trigger.service.in
@@ -0,0 +1,17 @@
+[Unit]
+Description=Wait for the Ubuntu Core chooser trigger
+Before=snapd.service
+# don't run on classic or uc16/uc18
+ConditionKernelCommandLine=snapd_recovery_mode
+
+[Service]
+# blocks the service startup until a trigger is detected or a timeout is hit
+Type=oneshot
+ExecStart=@libexecdir@/snapd/snap-bootstrap recovery-chooser-trigger
+RemainAfterExit=true
+
+[Install]
+WantedBy=multi-user.target
+
+# started on boot only
+# X-Snapd-Snap: do-not-start
diff --git a/data/systemd/snapd.service.in b/data/systemd/snapd.service.in
index cd10596cfd8..f9bd0555283 100644
--- a/data/systemd/snapd.service.in
+++ b/data/systemd/snapd.service.in
@@ -1,5 +1,5 @@
[Unit]
-Description=Snappy daemon
+Description=Snap Daemon
Requires=snapd.socket
OnFailure=snapd.failure.service
# This is handled by snapd
diff --git a/image/image_test.go b/image/image_test.go
index 64111b44f6a..d7b1cff21bd 100644
--- a/image/image_test.go
+++ b/image/image_test.go
@@ -586,8 +586,9 @@ func (s *imageSuite) TestSetupSeed(c *C) {
SideInfo: &info.SideInfo,
- Essential: true,
- Required: true,
+ EssentialType: info.GetType(),
+ Essential: true,
+ Required: true,
Channel: stableChannel,
})
@@ -707,6 +708,7 @@ func (s *imageSuite) TestSetupSeedLocalCoreBrandKernel(c *C) {
info := s.AssertedSnapInfo(name)
var pinfo snap.PlaceInfo = info
var sideInfo *snap.SideInfo
+ var snapType snap.Type
if info == nil {
switch name {
case "core_x1.snap":
@@ -715,9 +717,11 @@ func (s *imageSuite) TestSetupSeedLocalCoreBrandKernel(c *C) {
RealName: "core",
}
channel = ""
+ snapType = snap.TypeOS
}
} else {
sideInfo = &info.SideInfo
+ snapType = info.GetType()
}
fn := pinfo.Filename()
@@ -728,8 +732,9 @@ func (s *imageSuite) TestSetupSeedLocalCoreBrandKernel(c *C) {
SideInfo: sideInfo,
- Essential: true,
- Required: true,
+ EssentialType: snapType,
+ Essential: true,
+ Required: true,
Channel: channel,
})
@@ -792,11 +797,12 @@ func (s *imageSuite) TestSetupSeedDevmodeSnap(c *C) {
for i, name := range []string{"core", "pc-kernel", "pc"} {
info := s.AssertedSnapInfo(name)
c.Check(essSnaps[i], DeepEquals, &seed.Snap{
- Path: filepath.Join(seedsnapsdir, info.Filename()),
- SideInfo: &info.SideInfo,
- Essential: true,
- Required: true,
- Channel: "beta",
+ Path: filepath.Join(seedsnapsdir, info.Filename()),
+ SideInfo: &info.SideInfo,
+ EssentialType: info.GetType(),
+ Essential: true,
+ Required: true,
+ Channel: "beta",
})
}
c.Check(runSnaps[0], DeepEquals, &seed.Snap{
@@ -891,6 +897,7 @@ func (s *imageSuite) TestSetupSeedWithBase(c *C) {
RealName: "core18",
Revision: snap.R("18"),
},
+ SnapType: snap.TypeBase,
}
}
}
@@ -899,11 +906,12 @@ func (s *imageSuite) TestSetupSeedWithBase(c *C) {
p := filepath.Join(seedsnapsdir, fn)
c.Check(p, testutil.FilePresent)
c.Check(essSnaps[i], DeepEquals, &seed.Snap{
- Path: p,
- SideInfo: &info.SideInfo,
- Essential: true,
- Required: true,
- Channel: stableChannel,
+ Path: p,
+ SideInfo: &info.SideInfo,
+ EssentialType: info.GetType(),
+ Essential: true,
+ Required: true,
+ Channel: stableChannel,
})
}
c.Check(runSnaps[0], DeepEquals, &seed.Snap{
@@ -1050,6 +1058,7 @@ func (s *imageSuite) TestSetupSeedWithBaseLegacySnap(c *C) {
RealName: "core18",
Revision: snap.R("18"),
},
+ SnapType: snap.TypeBase,
}
}
}
@@ -1058,11 +1067,12 @@ func (s *imageSuite) TestSetupSeedWithBaseLegacySnap(c *C) {
p := filepath.Join(seedsnapsdir, fn)
c.Check(p, testutil.FilePresent)
c.Check(essSnaps[i], DeepEquals, &seed.Snap{
- Path: p,
- SideInfo: &info.SideInfo,
- Essential: true,
- Required: true,
- Channel: stableChannel,
+ Path: p,
+ SideInfo: &info.SideInfo,
+ EssentialType: info.GetType(),
+ Essential: true,
+ Required: true,
+ Channel: stableChannel,
})
}
c.Check(runSnaps[0], DeepEquals, &seed.Snap{
@@ -1213,6 +1223,7 @@ func (s *imageSuite) TestSetupSeedLocalSnapsWithStoreAsserts(c *C) {
SnapID: s.AssertedSnapID("core"),
Revision: snap.R(3),
},
+ SnapType: snap.TypeOS,
}
default:
c.Errorf("cannot have %s", name)
@@ -1223,11 +1234,12 @@ func (s *imageSuite) TestSetupSeedLocalSnapsWithStoreAsserts(c *C) {
p := filepath.Join(seedsnapsdir, fn)
c.Check(p, testutil.FilePresent)
c.Check(essSnaps[i], DeepEquals, &seed.Snap{
- Path: p,
- SideInfo: &info.SideInfo,
- Essential: true,
- Required: true,
- Channel: stableChannel,
+ Path: p,
+ SideInfo: &info.SideInfo,
+ EssentialType: info.GetType(),
+ Essential: true,
+ Required: true,
+ Channel: stableChannel,
})
}
c.Check(runSnaps[0], DeepEquals, &seed.Snap{
@@ -1307,6 +1319,7 @@ func (s *imageSuite) TestSetupSeedLocalSnapsWithChannels(c *C) {
SnapID: s.AssertedSnapID("core"),
Revision: snap.R(3),
},
+ SnapType: snap.TypeOS,
}
channel = "candidate"
default:
@@ -1318,11 +1331,12 @@ func (s *imageSuite) TestSetupSeedLocalSnapsWithChannels(c *C) {
p := filepath.Join(seedsnapsdir, fn)
c.Check(p, testutil.FilePresent)
c.Check(essSnaps[i], DeepEquals, &seed.Snap{
- Path: p,
- SideInfo: &info.SideInfo,
- Essential: true,
- Required: true,
- Channel: channel,
+ Path: p,
+ SideInfo: &info.SideInfo,
+ EssentialType: info.GetType(),
+ Essential: true,
+ Required: true,
+ Channel: channel,
})
}
c.Check(runSnaps[0], DeepEquals, &seed.Snap{
@@ -1492,25 +1506,28 @@ func (s *imageSuite) TestSetupSeedWithKernelAndGadgetTrack(c *C) {
c.Check(runSnaps, HasLen, 0)
c.Check(essSnaps[0], DeepEquals, &seed.Snap{
- Path: filepath.Join(seedsnapsdir, "core_3.snap"),
- SideInfo: &s.AssertedSnapInfo("core").SideInfo,
- Essential: true,
- Required: true,
- Channel: "stable",
+ Path: filepath.Join(seedsnapsdir, "core_3.snap"),
+ SideInfo: &s.AssertedSnapInfo("core").SideInfo,
+ EssentialType: snap.TypeOS,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
})
c.Check(essSnaps[1], DeepEquals, &seed.Snap{
- Path: filepath.Join(seedsnapsdir, "pc-kernel_2.snap"),
- SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
- Essential: true,
- Required: true,
- Channel: "18/stable",
+ Path: filepath.Join(seedsnapsdir, "pc-kernel_2.snap"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "18/stable",
})
c.Check(essSnaps[2], DeepEquals, &seed.Snap{
- Path: filepath.Join(seedsnapsdir, "pc_1.snap"),
- SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
- Essential: true,
- Required: true,
- Channel: "18/stable",
+ Path: filepath.Join(seedsnapsdir, "pc_1.snap"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "18/stable",
})
// check the downloads
@@ -1566,25 +1583,28 @@ func (s *imageSuite) TestSetupSeedWithKernelTrackWithDefaultChannel(c *C) {
c.Check(runSnaps, HasLen, 0)
c.Check(essSnaps[0], DeepEquals, &seed.Snap{
- Path: filepath.Join(seedsnapsdir, "core_3.snap"),
- SideInfo: &s.AssertedSnapInfo("core").SideInfo,
- Essential: true,
- Required: true,
- Channel: "edge",
+ Path: filepath.Join(seedsnapsdir, "core_3.snap"),
+ SideInfo: &s.AssertedSnapInfo("core").SideInfo,
+ EssentialType: snap.TypeOS,
+ Essential: true,
+ Required: true,
+ Channel: "edge",
})
c.Check(essSnaps[1], DeepEquals, &seed.Snap{
- Path: filepath.Join(seedsnapsdir, "pc-kernel_2.snap"),
- SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
- Essential: true,
- Required: true,
- Channel: "18/edge",
+ Path: filepath.Join(seedsnapsdir, "pc-kernel_2.snap"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "18/edge",
})
c.Check(essSnaps[2], DeepEquals, &seed.Snap{
- Path: filepath.Join(seedsnapsdir, "pc_1.snap"),
- SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
- Essential: true,
- Required: true,
- Channel: "edge",
+ Path: filepath.Join(seedsnapsdir, "pc_1.snap"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "edge",
})
}
@@ -1626,18 +1646,20 @@ func (s *imageSuite) TestSetupSeedWithKernelTrackOnLocalSnap(c *C) {
c.Check(runSnaps, HasLen, 0)
c.Check(essSnaps[0], DeepEquals, &seed.Snap{
- Path: filepath.Join(seedsnapsdir, "core_3.snap"),
- SideInfo: &s.AssertedSnapInfo("core").SideInfo,
- Essential: true,
- Required: true,
- Channel: "beta",
+ Path: filepath.Join(seedsnapsdir, "core_3.snap"),
+ SideInfo: &s.AssertedSnapInfo("core").SideInfo,
+ EssentialType: snap.TypeOS,
+ Essential: true,
+ Required: true,
+ Channel: "beta",
})
c.Check(essSnaps[1], DeepEquals, &seed.Snap{
- Path: filepath.Join(seedsnapsdir, "pc-kernel_2.snap"),
- SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
- Essential: true,
- Required: true,
- Channel: "18/beta",
+ Path: filepath.Join(seedsnapsdir, "pc-kernel_2.snap"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "18/beta",
})
}
@@ -2022,19 +2044,21 @@ func (s *imageSuite) TestSetupSeedClassic(c *C) {
// check the files are in place
c.Check(essSnaps[0], DeepEquals, &seed.Snap{
- Path: filepath.Join(seedsnapsdir, "core_3.snap"),
- SideInfo: &s.AssertedSnapInfo("core").SideInfo,
- Essential: true,
- Required: true,
- Channel: stableChannel,
+ Path: filepath.Join(seedsnapsdir, "core_3.snap"),
+ SideInfo: &s.AssertedSnapInfo("core").SideInfo,
+ EssentialType: snap.TypeOS,
+ Essential: true,
+ Required: true,
+ Channel: stableChannel,
})
c.Check(essSnaps[0].Path, testutil.FilePresent)
c.Check(essSnaps[1], DeepEquals, &seed.Snap{
- Path: filepath.Join(seedsnapsdir, "classic-gadget_5.snap"),
- SideInfo: &s.AssertedSnapInfo("classic-gadget").SideInfo,
- Essential: true,
- Required: true,
- Channel: stableChannel,
+ Path: filepath.Join(seedsnapsdir, "classic-gadget_5.snap"),
+ SideInfo: &s.AssertedSnapInfo("classic-gadget").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: stableChannel,
})
c.Check(essSnaps[1].Path, testutil.FilePresent)
c.Check(runSnaps[0], DeepEquals, &seed.Snap{
@@ -2096,11 +2120,12 @@ func (s *imageSuite) TestSetupSeedClassicWithLocalClassicSnap(c *C) {
c.Check(runSnaps, HasLen, 1)
c.Check(essSnaps[0], DeepEquals, &seed.Snap{
- Path: filepath.Join(seedsnapsdir, "core_3.snap"),
- SideInfo: &s.AssertedSnapInfo("core").SideInfo,
- Essential: true,
- Required: true,
- Channel: stableChannel,
+ Path: filepath.Join(seedsnapsdir, "core_3.snap"),
+ SideInfo: &s.AssertedSnapInfo("core").SideInfo,
+ EssentialType: snap.TypeOS,
+ Essential: true,
+ Required: true,
+ Channel: stableChannel,
})
c.Check(essSnaps[0].Path, testutil.FilePresent)
@@ -2166,11 +2191,12 @@ func (s *imageSuite) TestSetupSeedClassicSnapdOnly(c *C) {
p := filepath.Join(seedsnapsdir, fn)
c.Check(p, testutil.FilePresent)
c.Check(essSnaps[i], DeepEquals, &seed.Snap{
- Path: p,
- SideInfo: &info.SideInfo,
- Essential: true,
- Required: true,
- Channel: stableChannel,
+ Path: p,
+ SideInfo: &info.SideInfo,
+ EssentialType: info.GetType(),
+ Essential: true,
+ Required: true,
+ Channel: stableChannel,
})
}
c.Check(runSnaps[0], DeepEquals, &seed.Snap{
@@ -2390,11 +2416,12 @@ func (s *imageSuite) TestSetupSeedCore20(c *C) {
p := filepath.Join(seedsnapsdir, fn)
c.Check(p, testutil.FilePresent)
c.Check(essSnaps[i], DeepEquals, &seed.Snap{
- Path: p,
- SideInfo: &info.SideInfo,
- Essential: true,
- Required: true,
- Channel: channel,
+ Path: p,
+ SideInfo: &info.SideInfo,
+ EssentialType: info.GetType(),
+ Essential: true,
+ Required: true,
+ Channel: channel,
})
}
c.Check(runSnaps[0], DeepEquals, &seed.Snap{
diff --git a/osutil/udev/netlink/conn_test.go b/osutil/udev/netlink/conn_test.go
index df0ffff5b06..25cccd362e2 100644
--- a/osutil/udev/netlink/conn_test.go
+++ b/osutil/udev/netlink/conn_test.go
@@ -1,6 +1,7 @@
package netlink
import (
+ "syscall"
"testing"
"time"
)
@@ -33,3 +34,33 @@ func TestMonitorStop(t *testing.T) {
t.Fatal("stop timed out instead of working")
}
}
+
+func TestMonitorSelectTimeoutIsHarmless(t *testing.T) {
+ conn := new(UEventConn)
+ if err := conn.Connect(UdevEvent); err != nil {
+ t.Fatal("unable to subscribe to netlink uevent, err:", err)
+ }
+ defer conn.Close()
+
+ selectCalled := 0
+ oldStopperSelectTimeout := stopperSelectTimeout
+ stopperSelectTimeout = func() *syscall.Timeval {
+ selectCalled += 1
+ return &syscall.Timeval{
+ Usec: 10 * 1000, // 10ms
+ }
+ }
+ defer func() {
+ stopperSelectTimeout = oldStopperSelectTimeout
+ }()
+
+ stop := conn.Monitor(nil, nil, nil)
+ time.Sleep(100 * time.Millisecond)
+ ok := stop(200 * time.Millisecond)
+ if !ok {
+ t.Fatal("stop timed out instead of working")
+ }
+ if selectCalled <= 1 {
+ t.Fatal("select->read->select should have been exercised at least once")
+ }
+}
diff --git a/osutil/udev/netlink/rawsockstop.go b/osutil/udev/netlink/rawsockstop.go
index d448d4e2eb6..5b431f117db 100644
--- a/osutil/udev/netlink/rawsockstop.go
+++ b/osutil/udev/netlink/rawsockstop.go
@@ -36,8 +36,6 @@ func RawSockStopper(fd int) (readableOrStop func() (bool, error), stop func(), e
return readableOrStop, stop, nil
}
-var stopperSelectTimeout *syscall.Timeval
-
func stopperSelectReadable(fd, stopFd int) (bool, error) {
maxFd := fd
if maxFd < stopFd {
@@ -51,11 +49,12 @@ func stopperSelectReadable(fd, stopFd int) (bool, error) {
stopFdIdx := stopFd / bits.UintSize
stopFdShift := uint(stopFd) % bits.UintSize
readable := false
+ tout := stopperSelectTimeout()
for {
var r syscall.FdSet
r.Bits[fdIdx] = 1 << fdShift
r.Bits[stopFdIdx] |= 1 << stopFdShift
- _, err := syscall.Select(maxFd+1, &r, nil, nil, stopperSelectTimeout)
+ _, err := syscall.Select(maxFd+1, &r, nil, nil, tout)
if errno, ok := err.(syscall.Errno); ok && errno.Temporary() {
continue
}
diff --git a/osutil/udev/netlink/rawsockstop_arm64.go b/osutil/udev/netlink/rawsockstop_arm64.go
new file mode 100644
index 00000000000..49ef2124a9a
--- /dev/null
+++ b/osutil/udev/netlink/rawsockstop_arm64.go
@@ -0,0 +1,14 @@
+package netlink
+
+import (
+ "math"
+ "syscall"
+)
+
+// workaround a bug in go1.10 where syscall.Select() with nil Timeval
+// panics (c.f. https://github.com/golang/go/issues/24189)
+var stopperSelectTimeout = func() *syscall.Timeval {
+ return &syscall.Timeval{
+ Sec: math.MaxInt64,
+ }
+}
diff --git a/osutil/udev/netlink/rawsockstop_other.go b/osutil/udev/netlink/rawsockstop_other.go
new file mode 100644
index 00000000000..9eaacc6339e
--- /dev/null
+++ b/osutil/udev/netlink/rawsockstop_other.go
@@ -0,0 +1,14 @@
+// +build !arm64
+// don't remove the newline between the above statement and the package statement
+// or else the build constraint will be ignored and assumed to be part of the package comment!
+
+package netlink
+
+import "syscall"
+
+// once we use something other than go1.10 we can move this back into
+// rawsocketstop.go and remove rawsocketstop_arm64.go, see
+// rawsocketstop_arm64.go for details
+var stopperSelectTimeout = func() *syscall.Timeval {
+ return nil
+}
diff --git a/osutil/udev/netlink/rawsockstop_test.go b/osutil/udev/netlink/rawsockstop_test.go
index 067301d7a02..3ab44462cf5 100644
--- a/osutil/udev/netlink/rawsockstop_test.go
+++ b/osutil/udev/netlink/rawsockstop_test.go
@@ -12,11 +12,14 @@ func TestRawSockStopperReadable(t *testing.T) {
t.Fatalf("cannot make test pipe: %v", err)
}
- stopperSelectTimeout = &syscall.Timeval{
- Usec: 50 * 1000, // 50ms
+ oldStopperSelectTimeout := stopperSelectTimeout
+ stopperSelectTimeout = func() *syscall.Timeval {
+ return &syscall.Timeval{
+ Usec: 50 * 1000, // 50ms
+ }
}
defer func() {
- stopperSelectTimeout = nil
+ stopperSelectTimeout = oldStopperSelectTimeout
}()
readableOrStop, _, err := RawSockStopper(int(r.Fd()))
diff --git a/overlord/devicestate/devicestate_test.go b/overlord/devicestate/devicestate_test.go
index a585269ea70..5cff159c46b 100644
--- a/overlord/devicestate/devicestate_test.go
+++ b/overlord/devicestate/devicestate_test.go
@@ -411,7 +411,7 @@ func (s *deviceMgrSuite) TestDeviceManagerEnsureBootOkBootloaderHappy(c *C) {
s.setPCModelInState(c)
s.bootloader.SetBootVars(map[string]string{
- "snap_mode": "trying",
+ "snap_mode": boot.TryingStatus,
"snap_try_core": "core_1.snap",
})
@@ -478,7 +478,7 @@ func (s *deviceMgrSuite) TestDeviceManagerEnsureBootOkNotRunAgain(c *C) {
s.setPCModelInState(c)
s.bootloader.SetBootVars(map[string]string{
- "snap_mode": "trying",
+ "snap_mode": boot.TryingStatus,
"snap_try_core": "core_1.snap",
})
s.bootloader.SetErr = fmt.Errorf("ensure bootloader is not used")
diff --git a/overlord/devicestate/firstboot.go b/overlord/devicestate/firstboot.go
index cb3fccc2b85..f177ee6cb1f 100644
--- a/overlord/devicestate/firstboot.go
+++ b/overlord/devicestate/firstboot.go
@@ -147,9 +147,6 @@ func populateStateFromSeedImpl(st *state.State, opts *populateStateFromSeedOptio
return nil, err
}
- // collected snap infos
- infos := make([]*snap.Info, 0, len(essentialSeedSnaps)+len(seedSnaps))
-
tsAll := []*state.TaskSet{}
configTss := []*state.TaskSet{}
@@ -204,6 +201,9 @@ func populateStateFromSeedImpl(st *state.State, opts *populateStateFromSeedOptio
}
}
+ // collected snap infos
+ infos := make([]*snap.Info, 0, len(essentialSeedSnaps)+len(seedSnaps))
+
infoToTs := make(map[*snap.Info]*state.TaskSet, len(essentialSeedSnaps))
if len(essentialSeedSnaps) != 0 {
diff --git a/overlord/devicestate/firstboot20_test.go b/overlord/devicestate/firstboot20_test.go
index 23d918635ab..fa01461fca3 100644
--- a/overlord/devicestate/firstboot20_test.go
+++ b/overlord/devicestate/firstboot20_test.go
@@ -26,7 +26,6 @@ import (
. "gopkg.in/check.v1"
"github.com/snapcore/snapd/boot"
- "github.com/snapcore/snapd/boot/boottest"
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/bootloader/bootloadertest"
"github.com/snapcore/snapd/dirs"
@@ -118,12 +117,14 @@ volumes:
}
func (s *firstBoot20Suite) TestPopulateFromSeedCore20Happy(c *C) {
- r := boottest.ForceModeenv("", &boot.Modeenv{
+ m := boot.Modeenv{
Mode: "run",
RecoverySystem: "20191018",
Base: "core20_1.snap",
- })
- defer r()
+ }
+ err := m.Write("")
+ c.Assert(err, IsNil)
+ defer os.Remove(dirs.SnapModeenvFileUnder(dirs.GlobalRootDir))
// restart overlord to pick up the modeenv
s.startOverlord(c)
@@ -149,7 +150,7 @@ func (s *firstBoot20Suite) TestPopulateFromSeedCore20Happy(c *C) {
// bootloader, so set the current kernel there
kernel, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap")
c.Assert(err, IsNil)
- r = bloader.SetRunKernelImageEnabledKernel(kernel)
+ r := bloader.SetRunKernelImageEnabledKernel(kernel)
defer r()
opts := devicestate.PopulateStateFromSeedOptions{
@@ -237,6 +238,16 @@ func (s *firstBoot20Suite) TestPopulateFromSeedCore20Happy(c *C) {
c.Assert(err, IsNil)
c.Check(seedTime.IsZero(), Equals, false)
+ // check that we removed recovery_system from modeenv
+ m2, err := boot.ReadModeenv("")
+ c.Assert(err, IsNil)
+ c.Assert(m2.RecoverySystem, Equals, "")
+ c.Assert(m2.Base, Equals, m.Base)
+ c.Assert(m2.Mode, Equals, m.Mode)
+ // Note that we don't check CurrentKernels in the modeenv, even though in a
+ // real first boot that would also be set here, because setting that is done
+ // in the snapstate manager, not the devicestate manager
+
// check that the default device ctx has a Modeenv
dev, err := devicestate.DeviceCtx(s.overlord.State(), nil, nil)
c.Assert(err, IsNil)
diff --git a/overlord/devicestate/firstboot_preseed_test.go b/overlord/devicestate/firstboot_preseed_test.go
index 7e54bb88391..578addf6367 100644
--- a/overlord/devicestate/firstboot_preseed_test.go
+++ b/overlord/devicestate/firstboot_preseed_test.go
@@ -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)
@@ -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)
}
@@ -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)
}
diff --git a/overlord/devicestate/firstboot_test.go b/overlord/devicestate/firstboot_test.go
index 961a603bb58..a76d622b18f 100644
--- a/overlord/devicestate/firstboot_test.go
+++ b/overlord/devicestate/firstboot_test.go
@@ -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{}
}
diff --git a/overlord/devicestate/handlers.go b/overlord/devicestate/handlers.go
index 96a3ba61314..1647ae15486 100644
--- a/overlord/devicestate/handlers.go
+++ b/overlord/devicestate/handlers.go
@@ -99,9 +99,20 @@ func (m *DeviceManager) doMarkSeeded(t *state.Task, _ *tomb.Tomb) error {
if m.preseed {
return fmt.Errorf("internal error: mark-seeded task not expected in pre-seeding mode")
}
- // TODO:UC20: update "modeenv" and remove "recovery_system" from
- // it because this information is only needed for the initial
- // seeding.
+
+ deviceCtx, err := DeviceCtx(st, t, nil)
+ if err != nil {
+ return fmt.Errorf("cannot get device context: %v", err)
+ }
+
+ if deviceCtx.HasModeenv() && deviceCtx.RunMode() {
+ // unset recovery_system because that is only needed during install mode
+ m.modeEnv.RecoverySystem = ""
+ err := m.modeEnv.Write("")
+ if err != nil {
+ return err
+ }
+ }
st.Set("seed-time", time.Now())
st.Set("seeded", true)
diff --git a/overlord/managers_test.go b/overlord/managers_test.go
index ed4f205c207..0d1cae859c7 100644
--- a/overlord/managers_test.go
+++ b/overlord/managers_test.go
@@ -45,6 +45,7 @@ import (
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/asserts/assertstest"
"github.com/snapcore/snapd/asserts/sysdb"
+ "github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/bootloader/bootloadertest"
"github.com/snapcore/snapd/client"
@@ -1710,12 +1711,12 @@ type: os
c.Assert(bloader.BootVars, DeepEquals, map[string]string{
"snap_core": "core_99.snap",
"snap_try_core": "core_x1.snap",
- "snap_mode": "try",
+ "snap_mode": boot.TryStatus,
})
// simulate successful restart happened
state.MockRestarting(st, state.RestartUnset)
- bloader.BootVars["snap_mode"] = ""
+ bloader.BootVars["snap_mode"] = boot.DefaultStatus
bloader.SetBootBase("core_x1.snap")
st.Unlock()
@@ -1785,7 +1786,7 @@ type: kernel`
bloader.BootVars = map[string]string{
"snap_core": "core18_2.snap",
"snap_kernel": "pc-kernel_123.snap",
- "snap_mode": "",
+ "snap_mode": boot.DefaultStatus,
}
si1 := &snap.SideInfo{RealName: "pc-kernel", Revision: snap.R(123)}
snapstate.Set(st, "pc-kernel", &snapstate.SnapState{
@@ -1834,7 +1835,7 @@ type: kernel`
"snap_core": "core18_2.snap",
"snap_kernel": "pc-kernel_123.snap",
"snap_try_kernel": "pc-kernel_x1.snap",
- "snap_mode": "try",
+ "snap_mode": boot.TryStatus,
})
// pretend we restarted
s.mockSuccessfulReboot(c, bloader)
@@ -1877,7 +1878,7 @@ type: kernel`
bloader.BootVars = map[string]string{
"snap_core": "core18_2.snap",
"snap_kernel": "pc-kernel_123.snap",
- "snap_mode": "",
+ "snap_mode": boot.DefaultStatus,
}
si1 := &snap.SideInfo{RealName: "pc-kernel", Revision: snap.R(123)}
snapstate.Set(st, "pc-kernel", &snapstate.SnapState{
@@ -1926,7 +1927,7 @@ type: kernel`
"snap_core": "core18_2.snap",
"snap_kernel": "pc-kernel_123.snap",
"snap_try_kernel": "pc-kernel_x1.snap",
- "snap_mode": "try",
+ "snap_mode": boot.TryStatus,
})
// we are in restarting state and the change is not done yet
@@ -1949,7 +1950,7 @@ type: kernel`
"snap_try_core": "",
"snap_try_kernel": "pc-kernel_123.snap",
"snap_kernel": "pc-kernel_x1.snap",
- "snap_mode": "try",
+ "snap_mode": boot.TryStatus,
})
restarting, _ = st.Restarting()
c.Check(restarting, Equals, true)
@@ -3794,7 +3795,7 @@ func (ms *mgrsSuite) TestRemodelSwitchToDifferentBase(c *C) {
bootloader.Force(bloader)
defer bootloader.Force(nil)
bloader.SetBootVars(map[string]string{
- "snap_mode": "",
+ "snap_mode": boot.DefaultStatus,
"snap_core": "core18_1.snap",
"snap_kernel": "pc-kernel_1.snap",
})
@@ -3890,7 +3891,7 @@ version: 20.04`
bvars, err := bloader.GetBootVars("snap_mode", "snap_core", "snap_try_core", "snap_kernel", "snap_try_kernel")
c.Assert(err, IsNil)
c.Assert(bvars, DeepEquals, map[string]string{
- "snap_mode": "try",
+ "snap_mode": boot.TryStatus,
"snap_core": "core18_1.snap",
"snap_try_core": "core20_2.snap",
"snap_kernel": "pc-kernel_1.snap",
@@ -3901,7 +3902,7 @@ version: 20.04`
// got updated
state.MockRestarting(st, state.RestartUnset)
bloader.SetBootVars(map[string]string{
- "snap_mode": "",
+ "snap_mode": boot.DefaultStatus,
"snap_core": "core20_2.snap",
"snap_kernel": "pc-kernel_1.snap",
})
@@ -3937,7 +3938,7 @@ func (ms *mgrsSuite) TestRemodelSwitchToDifferentBaseUndo(c *C) {
bootloader.Force(bloader)
defer bootloader.Force(nil)
bloader.SetBootVars(map[string]string{
- "snap_mode": "",
+ "snap_mode": boot.DefaultStatus,
"snap_core": "core18_1.snap",
"snap_kernel": "pc-kernel_1.snap",
})
@@ -4036,7 +4037,7 @@ version: 20.04`
// check that the boot vars got updated as expected
c.Assert(bloader.BootVars, DeepEquals, map[string]string{
- "snap_mode": "try",
+ "snap_mode": boot.TryStatus,
"snap_core": "core18_1.snap",
"snap_try_core": "core20_2.snap",
"snap_kernel": "pc-kernel_1.snap",
@@ -4044,7 +4045,7 @@ version: 20.04`
// simulate successful restart happened
ms.mockSuccessfulReboot(c, bloader)
c.Assert(bloader.BootVars, DeepEquals, map[string]string{
- "snap_mode": "",
+ "snap_mode": boot.DefaultStatus,
"snap_core": "core20_2.snap",
"snap_try_core": "",
"snap_kernel": "pc-kernel_1.snap",
@@ -4070,7 +4071,7 @@ version: 20.04`
"snap_try_core": "core18_1.snap",
"snap_kernel": "pc-kernel_1.snap",
"snap_try_kernel": "",
- "snap_mode": "try",
+ "snap_mode": boot.TryStatus,
})
}
@@ -4079,7 +4080,7 @@ func (ms *mgrsSuite) TestRemodelSwitchToDifferentBaseUndoOnRollback(c *C) {
bootloader.Force(bloader)
defer bootloader.Force(nil)
bloader.SetBootVars(map[string]string{
- "snap_mode": "",
+ "snap_mode": boot.DefaultStatus,
"snap_core": "core18_1.snap",
"snap_kernel": "pc-kernel_1.snap",
})
@@ -4175,7 +4176,7 @@ version: 20.04`
// check that the boot vars got updated as expected
c.Assert(bloader.BootVars, DeepEquals, map[string]string{
- "snap_mode": "try",
+ "snap_mode": boot.TryStatus,
"snap_core": "core18_1.snap",
"snap_try_core": "core20_2.snap",
"snap_kernel": "pc-kernel_1.snap",
@@ -4183,7 +4184,7 @@ version: 20.04`
// simulate successful restart happened
ms.mockRollbackAcrossReboot(c, bloader)
c.Assert(bloader.BootVars, DeepEquals, map[string]string{
- "snap_mode": "",
+ "snap_mode": boot.DefaultStatus,
"snap_core": "core18_1.snap",
"snap_try_core": "",
"snap_kernel": "pc-kernel_1.snap",
@@ -4203,7 +4204,7 @@ version: 20.04`
c.Check(restarting, Equals, false)
// bootvars unchanged
c.Assert(bloader.BootVars, DeepEquals, map[string]string{
- "snap_mode": "",
+ "snap_mode": boot.DefaultStatus,
"snap_core": "core18_1.snap",
"snap_try_core": "",
"snap_kernel": "pc-kernel_1.snap",
@@ -4384,7 +4385,7 @@ func (ms *kernelSuite) TestRemodelSwitchToDifferentKernel(c *C) {
"snap_core": "core_1.snap",
"snap_kernel": "pc-kernel_1.snap",
"snap_try_kernel": "brand-kernel_2.snap",
- "snap_mode": "try",
+ "snap_mode": boot.TryStatus,
})
// simulate successful system-restart bootenv updates (those
// vars will be cleared by snapd on a restart)
@@ -4395,7 +4396,7 @@ func (ms *kernelSuite) TestRemodelSwitchToDifferentKernel(c *C) {
"snap_kernel": "brand-kernel_2.snap",
"snap_try_core": "",
"snap_try_kernel": "",
- "snap_mode": "",
+ "snap_mode": boot.DefaultStatus,
})
// continue
@@ -4413,7 +4414,7 @@ func (ms *kernelSuite) TestRemodelSwitchToDifferentKernel(c *C) {
"snap_kernel": "brand-kernel_2.snap",
"snap_try_kernel": "",
"snap_try_core": "",
- "snap_mode": "",
+ "snap_mode": boot.DefaultStatus,
})
// ensure tasks were run in the right order
@@ -4493,7 +4494,7 @@ func (ms *kernelSuite) TestRemodelSwitchToDifferentKernelUndo(c *C) {
"snap_try_core": "",
"snap_try_kernel": "pc-kernel_1.snap",
"snap_kernel": "brand-kernel_2.snap",
- "snap_mode": "try",
+ "snap_mode": boot.TryStatus,
})
}
@@ -4548,7 +4549,7 @@ func (ms *kernelSuite) TestRemodelSwitchToDifferentKernelUndoOnRollback(c *C) {
"snap_try_core": "",
"snap_kernel": "pc-kernel_1.snap",
"snap_try_kernel": "",
- "snap_mode": "",
+ "snap_mode": boot.DefaultStatus,
})
}
@@ -5354,7 +5355,7 @@ type: kernel`
bloader.BootVars = map[string]string{
"snap_core": "core18_2.snap",
"snap_kernel": "pc-kernel_123.snap",
- "snap_mode": "",
+ "snap_mode": boot.DefaultStatus,
}
si1 := &snap.SideInfo{RealName: "pc-kernel", Revision: snap.R(123)}
snapstate.Set(st, "pc-kernel", &snapstate.SnapState{
@@ -5400,7 +5401,7 @@ type: kernel`
"snap_core": "core18_2.snap",
"snap_kernel": "pc-kernel_123.snap",
"snap_try_kernel": "pc-kernel_x1.snap",
- "snap_mode": "try",
+ "snap_mode": boot.TryStatus,
})
// we are in restarting state and the change is not done yet
@@ -5428,7 +5429,7 @@ type: kernel`
c.Check(bloader.BootVars, DeepEquals, map[string]string{
"snap_core": "core18_2.snap",
"snap_kernel": "pc-kernel_123.snap",
- "snap_mode": "",
+ "snap_mode": boot.DefaultStatus,
"snap_try_core": "",
"snap_try_kernel": "",
})
diff --git a/overlord/snapstate/booted_test.go b/overlord/snapstate/booted_test.go
index 74fe9a6a32b..b30fbb46f98 100644
--- a/overlord/snapstate/booted_test.go
+++ b/overlord/snapstate/booted_test.go
@@ -29,6 +29,7 @@ import (
. "gopkg.in/check.v1"
+ "github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/bootloader/bootloadertest"
"github.com/snapcore/snapd/dirs"
@@ -304,7 +305,7 @@ func (bs *bootedSuite) TestWaitRestartCore(c *C) {
// core snap, restarted, waiting for current core revision
state.MockRestarting(st, state.RestartUnset)
- bs.bootloader.BootVars["snap_mode"] = "trying"
+ bs.bootloader.BootVars["snap_mode"] = boot.TryingStatus
err = snapstate.WaitRestart(task, snapsup)
c.Check(err, DeepEquals, &state.Retry{After: 5 * time.Second})
@@ -355,7 +356,7 @@ func (bs *bootedSuite) TestWaitRestartBootableBase(c *C) {
// core snap, restarted, waiting for current core revision
state.MockRestarting(st, state.RestartUnset)
- bs.bootloader.BootVars["snap_mode"] = "trying"
+ bs.bootloader.BootVars["snap_mode"] = boot.TryingStatus
err = snapstate.WaitRestart(task, snapsup)
c.Check(err, DeepEquals, &state.Retry{After: 5 * time.Second})
@@ -407,7 +408,7 @@ func (bs *bootedSuite) TestWaitRestartKernel(c *C) {
// kernel snap, restarted, waiting for current core revision
state.MockRestarting(st, state.RestartUnset)
- bs.bootloader.BootVars["snap_mode"] = "trying"
+ bs.bootloader.BootVars["snap_mode"] = boot.TryingStatus
err = snapstate.WaitRestart(task, snapsup)
c.Check(err, DeepEquals, &state.Retry{After: 5 * time.Second})
diff --git a/overlord/snapstate/handlers.go b/overlord/snapstate/handlers.go
index 686941809b2..6a53a8179cf 100644
--- a/overlord/snapstate/handlers.go
+++ b/overlord/snapstate/handlers.go
@@ -1622,7 +1622,9 @@ func (m *SnapManager) undoLinkSnap(t *state.Task, _ *tomb.Tomb) error {
return err
}
- if newInfo.GetType() == snap.TypeSnapd {
+ // only restart here if snapd was the installed and needs to
+ // get uninstalled again
+ if linkCtx.FirstInstall && newInfo.GetType() == snap.TypeSnapd {
// only way to get
deviceCtx, err := DeviceCtx(st, t, nil)
if err != nil {
diff --git a/overlord/snapstate/handlers_link_test.go b/overlord/snapstate/handlers_link_test.go
index b02c58049e5..f871eea66bb 100644
--- a/overlord/snapstate/handlers_link_test.go
+++ b/overlord/snapstate/handlers_link_test.go
@@ -28,6 +28,7 @@ import (
. "gopkg.in/check.v1"
+ "github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/bootloader/bootloadertest"
"github.com/snapcore/snapd/dirs"
@@ -1184,12 +1185,12 @@ func (s *linkSnapSuite) TestUndoLinkSnapdNthInstall(c *C) {
c.Check(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
c.Check(s.fakeBackend.ops, DeepEquals, expected)
- // 2 restarts, one from link snap, another one from undo
- c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon, state.RestartDaemon})
- c.Check(t.Log(), HasLen, 3)
+ // 1 restarts, one from link snap, the other restart happens
+ // in undoUnlinkCurrentSnap (not tested here)
+ c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon})
+ c.Check(t.Log(), HasLen, 2)
c.Check(t.Log()[0], Matches, `.*INFO Requested daemon restart \(snapd snap\)\.`)
c.Check(t.Log()[1], Matches, `.*INFO unlink`)
- c.Check(t.Log()[2], Matches, `.*INFO Requested daemon restart \(snapd snap\)\.`)
}
func (s *linkSnapSuite) TestDoUnlinkSnapRefreshAwarenessHardCheck(c *C) {
@@ -1365,7 +1366,7 @@ func (s *linkSnapSuite) TestMaybeUndoRemodelBootChangesNeedsUndo(c *C) {
// that will schedule a boot into the previous kernel
c.Assert(bloader.BootVars, DeepEquals, map[string]string{
- "snap_mode": "try",
+ "snap_mode": boot.TryStatus,
"snap_kernel": "new-kernel_1.snap",
"snap_try_kernel": "kernel_1.snap",
})
diff --git a/packaging/centos-8 b/packaging/centos-8
new file mode 120000
index 00000000000..100fe0cd7bb
--- /dev/null
+++ b/packaging/centos-8
@@ -0,0 +1 @@
+fedora
\ No newline at end of file
diff --git a/packaging/fedora/snapd.spec b/packaging/fedora/snapd.spec
index 29869280e4e..74f6829f88d 100644
--- a/packaging/fedora/snapd.spec
+++ b/packaging/fedora/snapd.spec
@@ -620,6 +620,7 @@ install -m 644 -D data/sysctl/rhel7-snap.conf %{buildroot}%{_sysctldir}/99-snap.
rm -fv %{buildroot}%{_unitdir}/snapd.system-shutdown.service
rm -fv %{buildroot}%{_unitdir}/snapd.snap-repair.*
rm -fv %{buildroot}%{_unitdir}/snapd.core-fixup.*
+rm -fv %{buildroot}%{_unitdir}/snapd.recovery-chooser-trigger.service
# Remove snappy core specific scripts
rm %{buildroot}%{_libexecdir}/snapd/snapd.core-fixup.sh
diff --git a/packaging/snapd.mk b/packaging/snapd.mk
index 52c4c3c9518..485c569a694 100644
--- a/packaging/snapd.mk
+++ b/packaging/snapd.mk
@@ -155,7 +155,7 @@ install::
ifeq ($(with_core_bits),0)
# Remove systemd units that are only used on core devices.
install::
- rm -f $(addprefix $(DESTDIR)$(unitdir)/,snapd.autoimport.service snapd.system-shutdown.service snapd.snap-repair.timer snapd.snap-repair.service snapd.core-fixup.service)
+ rm -f $(addprefix $(DESTDIR)$(unitdir)/,snapd.autoimport.service snapd.system-shutdown.service snapd.snap-repair.timer snapd.snap-repair.service snapd.core-fixup.service snapd.recovery-chooser-trigger.service)
# Remove fixup script that is only used on core devices.
install::
diff --git a/packaging/ubuntu-16.04/changelog b/packaging/ubuntu-16.04/changelog
index 58fb99346c2..4d0136e503c 100644
--- a/packaging/ubuntu-16.04/changelog
+++ b/packaging/ubuntu-16.04/changelog
@@ -1,3 +1,9 @@
+snapd (2.44~pre1) UNRELEASED; urgency=medium
+
+ * placeholder changelog
+
+ -- Michael Vogt Wed, 26 Feb 2020 09:17:32 +0100
+
snapd (2.43.3) xenial; urgency=medium
* New upstream release, LP: #1856159
diff --git a/run-checks b/run-checks
index 72f0bfc320a..f2f017477ad 100755
--- a/run-checks
+++ b/run-checks
@@ -155,22 +155,18 @@ if [ "$STATIC" = 1 ]; then
./check-pr-title.py "$TRAVIS_PULL_REQUEST"
fi
- echo Checking formatting
- fmt=""
- for dir in $(go list -f '{{.Dir}}' ./... | grep -v '/vendor/' ); do
- s="$(${GOFMT:-gofmt} -s -l -d "$dir" | grep -v /vendor/ || true)"
- if [ -n "$s" ]; then
- fmt="$s\\n$fmt"
- fi
- done
-
- if [ -n "$fmt" ]; then
- echo "Formatting wrong in following files:"
- echo "$fmt" | sed -e 's/\\n/\n/g'
- if [ -z "${SKIP_GOFMT:-}" ]; then
- exit 1
- else
- echo "Ignoring gofmt errors as requested"
+ if [ -z "${SKIP_GOFMT:-}" ]; then
+ echo Checking formatting
+ fmt=""
+ for dir in $(go list -f '{{.Dir}}' ./... | grep -v '/vendor/' ); do
+ s="$(${GOFMT:-gofmt} -s -l -d "$dir" | grep -v /vendor/ || true)"
+ if [ -n "$s" ]; then
+ fmt="$s\\n$fmt"
+ fi
+ done
+ if [ -n "$fmt" ]; then
+ echo "Formatting wrong in following files:"
+ echo "$fmt" | sed -e 's/\\n/\n/g'
fi
fi
diff --git a/seed/helpers.go b/seed/helpers.go
index d84e413c1e1..bbb88cee55e 100644
--- a/seed/helpers.go
+++ b/seed/helpers.go
@@ -100,3 +100,42 @@ func readInfo(snapPath string, si *snap.SideInfo) (*snap.Info, error) {
}
return snap.ReadInfoFromSnapFile(snapf, si)
}
+
+func snapTypeFromModel(modSnap *asserts.ModelSnap) snap.Type {
+ switch modSnap.SnapType {
+ case "base":
+ return snap.TypeBase
+ case "core":
+ return snap.TypeOS
+ case "gadget":
+ return snap.TypeGadget
+ case "kernel":
+ return snap.TypeKernel
+ case "snapd":
+ return snap.TypeSnapd
+ default:
+ return snap.TypeApp
+ }
+}
+
+func essentialSnapTypesToModelFilter(essentialTypes []snap.Type) func(modSnap *asserts.ModelSnap) bool {
+ m := make(map[string]bool, len(essentialTypes))
+ for _, t := range essentialTypes {
+ switch t {
+ case snap.TypeBase:
+ m["base"] = true
+ case snap.TypeOS:
+ m["core"] = true
+ case snap.TypeGadget:
+ m["gadget"] = true
+ case snap.TypeKernel:
+ m["kernel"] = true
+ case snap.TypeSnapd:
+ m["snapd"] = true
+ }
+ }
+
+ return func(modSnap *asserts.ModelSnap) bool {
+ return m[modSnap.SnapType]
+ }
+}
diff --git a/seed/seed.go b/seed/seed.go
index 4eb7ba44f8b..c36ef712391 100644
--- a/seed/seed.go
+++ b/seed/seed.go
@@ -40,6 +40,10 @@ type Snap struct {
SideInfo *snap.SideInfo
+ // EssentialType is the type of the snap as specified by the model.
+ // Provided only for essential snaps (Essential = true).
+ EssentialType snap.Type
+
Essential bool
Required bool
@@ -73,7 +77,8 @@ type Seed interface {
// error to call Model before LoadAssertions.
Model() (*asserts.Model, error)
- // LoadMeta loads the seed and seed's snaps metadata. It can
+ // LoadMeta loads the seed and seed's snaps metadata while
+ // verifying the underlying snaps against assertions. It can
// return ErrNoMeta if there is no metadata nor snaps in the
// seed, this is legitimate only on classic. It is an error to
// call LoadMeta before LoadAssertions.
@@ -92,6 +97,20 @@ type Seed interface {
ModeSnaps(mode string) ([]*Snap, error)
}
+// EssentialMetaLoaderSeed is a Seed that can be asked to load and verify
+// only a subset of the essential model snaps via LoadEssentialMeta.
+type EssentialMetaLoaderSeed interface {
+ Seed
+
+ // LoadEssentialMeta loads the seed's snaps metadata for the
+ // essential snaps with types in the essentialTypes set while
+ // verifying them against assertions. It can return ErrNoMeta
+ // if there is no metadata nor snaps in the seed, this is
+ // legitimate only on classic. It is an error to call LoadMeta
+ // before LoadAssertions or to mix it with LoadMeta.
+ LoadEssentialMeta(essentialTypes []snap.Type, tm timings.Measurer) error
+}
+
// Open returns a Seed implementation for the seed at seedDir.
// label if not empty is used to identify a Core 20 recovery system seed.
func Open(seedDir, label string) (Seed, error) {
diff --git a/seed/seed16.go b/seed/seed16.go
index 01acdcedd1e..38d16feab90 100644
--- a/seed/seed16.go
+++ b/seed/seed16.go
@@ -196,7 +196,7 @@ func (s *seed16) LoadMeta(tm timings.Measurer) error {
}
// add the essential snaps
- addEssential := func(snapName string, pinnedTrack string) (*Snap, error) {
+ addEssential := func(snapName string, pinnedTrack string, essType snap.Type) (*Snap, error) {
// be idempotent
if added[snapName] {
return nil, nil
@@ -211,6 +211,11 @@ func (s *seed16) LoadMeta(tm timings.Measurer) error {
return nil, err
}
+ if essType == snap.TypeBase && snapName == "core" {
+ essType = snap.TypeOS
+ }
+
+ seedSnap.EssentialType = essType
seedSnap.Essential = true
seedSnap.Required = true
added[snapName] = true
@@ -222,25 +227,25 @@ func (s *seed16) LoadMeta(tm timings.Measurer) error {
if len(yamlSnaps) != 0 {
// ensure "snapd" snap is installed first
if model.Base() != "" || classicWithSnapd {
- if _, err := addEssential("snapd", ""); err != nil {
+ if _, err := addEssential("snapd", "", snap.TypeSnapd); err != nil {
return err
}
}
if !classicWithSnapd {
- if _, err := addEssential(baseSnap, ""); err != nil {
+ if _, err := addEssential(baseSnap, "", snap.TypeBase); err != nil {
return err
}
}
}
if kernelName := model.Kernel(); kernelName != "" {
- if _, err := addEssential(kernelName, model.KernelTrack()); err != nil {
+ if _, err := addEssential(kernelName, model.KernelTrack(), snap.TypeKernel); err != nil {
return err
}
}
if gadgetName := model.Gadget(); gadgetName != "" {
- gadget, err := addEssential(gadgetName, model.GadgetTrack())
+ gadget, err := addEssential(gadgetName, model.GadgetTrack(), snap.TypeGadget)
if err != nil {
return err
}
@@ -260,7 +265,7 @@ func (s *seed16) LoadMeta(tm timings.Measurer) error {
if baseSnap != "" && gadgetBase != baseSnap {
return fmt.Errorf("cannot use gadget snap because its base %q is different from model base %q", gadgetBase, model.Base())
}
- if _, err = addEssential(gadgetBase, ""); err != nil {
+ if _, err = addEssential(gadgetBase, "", snap.TypeBase); err != nil {
return err
}
}
diff --git a/seed/seed16_test.go b/seed/seed16_test.go
index 78cb4891e56..e9325452f8d 100644
--- a/seed/seed16_test.go
+++ b/seed/seed16_test.go
@@ -441,23 +441,26 @@ func (s *seed16Suite) TestLoadMetaCore16Minimal(c *C) {
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("core"),
- SideInfo: &s.AssertedSnapInfo("core").SideInfo,
- Essential: true,
- Required: true,
- Channel: "stable",
+ Path: s.expectedPath("core"),
+ SideInfo: &s.AssertedSnapInfo("core").SideInfo,
+ EssentialType: snap.TypeOS,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
}, {
- Path: s.expectedPath("pc-kernel"),
- SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
- Essential: true,
- Required: true,
- Channel: "stable",
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
}, {
- Path: s.expectedPath("pc"),
- SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
- Essential: true,
- Required: true,
- Channel: "stable",
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
},
})
@@ -514,29 +517,33 @@ func (s *seed16Suite) TestLoadMetaCore18Minimal(c *C) {
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("snapd"),
- SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
- Essential: true,
- Required: true,
- Channel: "stable",
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ EssentialType: snap.TypeSnapd,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
}, {
- Path: s.expectedPath("core18"),
- SideInfo: &s.AssertedSnapInfo("core18").SideInfo,
- Essential: true,
- Required: true,
- Channel: "stable",
+ Path: s.expectedPath("core18"),
+ SideInfo: &s.AssertedSnapInfo("core18").SideInfo,
+ EssentialType: snap.TypeBase,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
}, {
- Path: s.expectedPath("pc-kernel"),
- SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
- Essential: true,
- Required: true,
- Channel: "18",
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "18",
}, {
- Path: s.expectedPath("pc"),
- SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
- Essential: true,
- Required: true,
- Channel: "18",
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "18",
},
})
@@ -564,29 +571,33 @@ func (s *seed16Suite) TestLoadMetaCore18(c *C) {
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("snapd"),
- SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
- Essential: true,
- Required: true,
- Channel: "stable",
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ EssentialType: snap.TypeSnapd,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
}, {
- Path: s.expectedPath("core18"),
- SideInfo: &s.AssertedSnapInfo("core18").SideInfo,
- Essential: true,
- Required: true,
- Channel: "stable",
+ Path: s.expectedPath("core18"),
+ SideInfo: &s.AssertedSnapInfo("core18").SideInfo,
+ EssentialType: snap.TypeBase,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
}, {
- Path: s.expectedPath("pc-kernel"),
- SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
- Essential: true,
- Required: true,
- Channel: "18",
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "18",
}, {
- Path: s.expectedPath("pc"),
- SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
- Essential: true,
- Required: true,
- Channel: "18",
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "18",
},
})
@@ -653,11 +664,12 @@ func (s *seed16Suite) TestLoadMetaClassicCore(c *C) {
c.Check(essSnaps, HasLen, 1)
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("core"),
- SideInfo: &s.AssertedSnapInfo("core").SideInfo,
- Essential: true,
- Required: true,
- Channel: "stable",
+ Path: s.expectedPath("core"),
+ SideInfo: &s.AssertedSnapInfo("core").SideInfo,
+ EssentialType: snap.TypeOS,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
},
})
@@ -693,18 +705,20 @@ func (s *seed16Suite) TestLoadMetaClassicCoreWithGadget(c *C) {
c.Check(essSnaps, HasLen, 2)
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("core"),
- SideInfo: &s.AssertedSnapInfo("core").SideInfo,
- Essential: true,
- Required: true,
- Channel: "stable",
+ Path: s.expectedPath("core"),
+ SideInfo: &s.AssertedSnapInfo("core").SideInfo,
+ EssentialType: snap.TypeOS,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
},
{
- Path: s.expectedPath("classic-gadget"),
- SideInfo: &s.AssertedSnapInfo("classic-gadget").SideInfo,
- Essential: true,
- Required: true,
- Channel: "stable",
+ Path: s.expectedPath("classic-gadget"),
+ SideInfo: &s.AssertedSnapInfo("classic-gadget").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
},
})
@@ -731,11 +745,12 @@ func (s *seed16Suite) TestLoadMetaClassicSnapd(c *C) {
c.Check(essSnaps, HasLen, 1)
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("snapd"),
- SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
- Essential: true,
- Required: true,
- Channel: "stable",
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ EssentialType: snap.TypeSnapd,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
},
})
@@ -775,20 +790,26 @@ func (s *seed16Suite) TestLoadMetaClassicSnapdWithGadget(c *C) {
c.Check(essSnaps, HasLen, 3)
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("snapd"),
- SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ EssentialType: snap.TypeSnapd,
+
Essential: true,
Required: true,
Channel: "stable",
}, {
- Path: s.expectedPath("classic-gadget"),
- SideInfo: &s.AssertedSnapInfo("classic-gadget").SideInfo,
+ Path: s.expectedPath("classic-gadget"),
+ SideInfo: &s.AssertedSnapInfo("classic-gadget").SideInfo,
+ EssentialType: snap.TypeGadget,
+
Essential: true,
Required: true,
Channel: "stable",
}, {
- Path: s.expectedPath("core"),
- SideInfo: &s.AssertedSnapInfo("core").SideInfo,
+ Path: s.expectedPath("core"),
+ SideInfo: &s.AssertedSnapInfo("core").SideInfo,
+ EssentialType: snap.TypeOS,
+
Essential: true,
Required: true,
Channel: "stable",
@@ -819,20 +840,26 @@ func (s *seed16Suite) TestLoadMetaClassicSnapdWithGadget18(c *C) {
c.Check(essSnaps, HasLen, 3)
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("snapd"),
- SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ EssentialType: snap.TypeSnapd,
+
Essential: true,
Required: true,
Channel: "stable",
}, {
- Path: s.expectedPath("classic-gadget18"),
- SideInfo: &s.AssertedSnapInfo("classic-gadget18").SideInfo,
+ Path: s.expectedPath("classic-gadget18"),
+ SideInfo: &s.AssertedSnapInfo("classic-gadget18").SideInfo,
+ EssentialType: snap.TypeGadget,
+
Essential: true,
Required: true,
Channel: "stable",
}, {
- Path: s.expectedPath("core18"),
- SideInfo: &s.AssertedSnapInfo("core18").SideInfo,
+ Path: s.expectedPath("core18"),
+ SideInfo: &s.AssertedSnapInfo("core18").SideInfo,
+ EssentialType: snap.TypeBase,
+
Essential: true,
Required: true,
Channel: "stable",
@@ -881,29 +908,33 @@ func (s *seed16Suite) TestLoadMetaCore18Local(c *C) {
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("snapd"),
- SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
- Essential: true,
- Required: true,
- Channel: "stable",
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ EssentialType: snap.TypeSnapd,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
}, {
- Path: s.expectedPath("core18"),
- SideInfo: &s.AssertedSnapInfo("core18").SideInfo,
- Essential: true,
- Required: true,
- Channel: "stable",
+ Path: s.expectedPath("core18"),
+ SideInfo: &s.AssertedSnapInfo("core18").SideInfo,
+ EssentialType: snap.TypeBase,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
}, {
- Path: s.expectedPath("pc-kernel"),
- SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
- Essential: true,
- Required: true,
- Channel: "18",
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "18",
}, {
- Path: s.expectedPath("pc"),
- SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
- Essential: true,
- Required: true,
- Channel: "18",
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "18",
},
})
@@ -991,29 +1022,33 @@ func (s *seed16Suite) TestLoadMetaCore18EnforcePinnedTracks(c *C) {
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("snapd"),
- SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
- Essential: true,
- Required: true,
- Channel: "stable",
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ EssentialType: snap.TypeSnapd,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
}, {
- Path: s.expectedPath("core18"),
- SideInfo: &s.AssertedSnapInfo("core18").SideInfo,
- Essential: true,
- Required: true,
- Channel: "stable",
+ Path: s.expectedPath("core18"),
+ SideInfo: &s.AssertedSnapInfo("core18").SideInfo,
+ EssentialType: snap.TypeBase,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
}, {
- Path: s.expectedPath("pc-kernel"),
- SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
- Essential: true,
- Required: true,
- Channel: "18",
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "18",
}, {
- Path: s.expectedPath("pc"),
- SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
- Essential: true,
- Required: true,
- Channel: "18/edge",
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "18/edge",
},
})
diff --git a/seed/seed20.go b/seed/seed20.go
index 292fea5e53b..a98be5c804c 100644
--- a/seed/seed20.go
+++ b/seed/seed20.go
@@ -31,6 +31,7 @@ package seed
import (
"encoding/json"
+ "errors"
"fmt"
"os"
"path/filepath"
@@ -352,8 +353,13 @@ func (s *seed20) addSnap(snapRef naming.SnapRef, optSnap *internal.Snap20, modes
return seedSnap, nil
}
-func (s *seed20) addModelSnap(modelSnap *asserts.ModelSnap, essential bool, tm timings.Measurer) (*Snap, error) {
+var errFiltered = errors.New("filtered out")
+
+func (s *seed20) addModelSnap(modelSnap *asserts.ModelSnap, essential bool, filter func(*asserts.ModelSnap) bool, tm timings.Measurer) (*Snap, error) {
optSnap, _ := s.nextOptSnap(modelSnap)
+ if !filter(modelSnap) {
+ return nil, errFiltered
+ }
seedSnap, err := s.addSnap(modelSnap, optSnap, modelSnap.Modes, modelSnap.DefaultChannel, "../../snaps", tm)
if err != nil {
return nil, err
@@ -362,6 +368,7 @@ func (s *seed20) addModelSnap(modelSnap *asserts.ModelSnap, essential bool, tm t
seedSnap.Essential = essential
seedSnap.Required = essential || modelSnap.Presence == "required"
if essential {
+ seedSnap.EssentialType = snapTypeFromModel(modelSnap)
s.essentialSnapsNum++
}
@@ -369,6 +376,43 @@ func (s *seed20) addModelSnap(modelSnap *asserts.ModelSnap, essential bool, tm t
}
func (s *seed20) LoadMeta(tm timings.Measurer) error {
+ if err := s.loadModelMeta(nil, tm); err != nil {
+ return err
+ }
+
+ // extra snaps
+ runMode := []string{"run"}
+ for {
+ optSnap, done := s.nextOptSnap(nil)
+ if done {
+ break
+ }
+
+ _, err := s.addSnap(optSnap, optSnap, runMode, "latest/stable", "snaps", tm)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (s *seed20) LoadEssentialMeta(essentialTypes []snap.Type, tm timings.Measurer) error {
+ filterEssential := essentialSnapTypesToModelFilter(essentialTypes)
+
+ if err := s.loadModelMeta(filterEssential, tm); err != nil {
+ return err
+ }
+
+ if s.essentialSnapsNum != len(essentialTypes) {
+ // did not find all the explicitly asked essential types
+ return fmt.Errorf("model does not specify all the requested essential snaps: %v", essentialTypes)
+ }
+
+ return nil
+}
+
+func (s *seed20) loadModelMeta(filterEssential func(*asserts.ModelSnap) bool, tm timings.Measurer) error {
model, err := s.Model()
if err != nil {
return err
@@ -382,19 +426,31 @@ func (s *seed20) LoadMeta(tm timings.Measurer) error {
return err
}
+ essentialOnly := filterEssential != nil
+ filter := filterEssential
+ if !essentialOnly {
+ // no filtering
+ filter = func(*asserts.ModelSnap) bool {
+ return true
+ }
+ }
+
allSnaps := model.AllSnaps()
// an explicit snapd is the first of all of snaps
if allSnaps[0].SnapType != "snapd" {
snapdSnap := internal.MakeSystemSnap("snapd", "latest/stable", []string{"run", "ephemeral"})
- if _, err := s.addModelSnap(snapdSnap, true, tm); err != nil {
+ if _, err := s.addModelSnap(snapdSnap, true, filter, tm); err != nil && err != errFiltered {
return err
}
}
essential := true
for _, modelSnap := range allSnaps {
- seedSnap, err := s.addModelSnap(modelSnap, essential, tm)
+ seedSnap, err := s.addModelSnap(modelSnap, essential, filter, tm)
if err != nil {
+ if err == errFiltered {
+ continue
+ }
if _, ok := err.(*NoSnapDeclarationError); ok && modelSnap.Presence == "optional" {
// skipped optional snap is ok
continue
@@ -413,22 +469,12 @@ func (s *seed20) LoadMeta(tm timings.Measurer) error {
// TODO: when we allow extend models for classic
// we need to add the gadget base here
- // done with essential snaps
+ // done with essential snaps, gadget is the last one
essential = false
- }
- }
-
- // extra snaps
- runMode := []string{"run"}
- for {
- optSnap, done := s.nextOptSnap(nil)
- if done {
- break
- }
-
- _, err := s.addSnap(optSnap, optSnap, runMode, "latest/stable", "snaps", tm)
- if err != nil {
- return err
+ if essentialOnly {
+ // will not find more
+ break
+ }
}
}
diff --git a/seed/seed20_test.go b/seed/seed20_test.go
index 77e5de84de2..ddca1454192 100644
--- a/seed/seed20_test.go
+++ b/seed/seed20_test.go
@@ -137,29 +137,33 @@ func (s *seed20Suite) TestLoadMetaCore20Minimal(c *C) {
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("snapd"),
- SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ EssentialType: snap.TypeSnapd,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc-kernel"),
- SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "20",
}, {
- Path: s.expectedPath("core20"),
- SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("core20"),
+ SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
+ EssentialType: snap.TypeBase,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc"),
- SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "20",
},
})
@@ -577,29 +581,33 @@ func (s *seed20Suite) TestLoadMetaCore20(c *C) {
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("snapd"),
- SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ EssentialType: snap.TypeSnapd,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc-kernel"),
- SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "20",
}, {
- Path: s.expectedPath("core20"),
- SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("core20"),
+ SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
+ EssentialType: snap.TypeBase,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc"),
- SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "20",
},
})
@@ -621,6 +629,144 @@ func (s *seed20Suite) TestLoadMetaCore20(c *C) {
c.Check(installSnaps, HasLen, 0)
}
+func hideSnaps(c *C, all []*seed.Snap, keepTypes []snap.Type) (unhide func()) {
+ var hidden [][]string
+Hiding:
+ for _, sn := range all {
+ for _, t := range keepTypes {
+ if sn.EssentialType == t {
+ continue Hiding
+ }
+ }
+ origFn := sn.Path
+ hiddenFn := sn.Path + ".hidden"
+ err := os.Rename(origFn, hiddenFn)
+ c.Assert(err, IsNil)
+ hidden = append(hidden, []string{origFn, hiddenFn})
+ }
+ return func() {
+ for _, h := range hidden {
+ err := os.Rename(h[1], h[0])
+ c.Assert(err, IsNil)
+ }
+ }
+}
+
+func (s *seed20Suite) TestLoadEssentialMetaCore20(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 := "20191018"
+ s.MakeSeed(c, sysLabel, "my-brand", "my-model", map[string]interface{}{
+ "display-name": "my model",
+ "architecture": "amd64",
+ "base": "core20",
+ "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)
+
+ snapdSnap := &seed.Snap{
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ EssentialType: snap.TypeSnapd,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
+ }
+ pcKernelSnap := &seed.Snap{
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "20",
+ }
+ core20Snap := &seed.Snap{Path: s.expectedPath("core20"),
+ SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
+ EssentialType: snap.TypeBase,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
+ }
+ pcSnap := &seed.Snap{
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "20",
+ }
+ required20Snap := &seed.Snap{
+ Path: s.expectedPath("required20"),
+ }
+
+ all := []*seed.Snap{snapdSnap, pcKernelSnap, core20Snap, pcSnap, required20Snap}
+
+ tests := []struct {
+ onlyTypes []snap.Type
+ expected []*seed.Snap
+ }{
+ {[]snap.Type{snap.TypeSnapd}, []*seed.Snap{snapdSnap}},
+ {[]snap.Type{snap.TypeKernel}, []*seed.Snap{pcKernelSnap}},
+ {[]snap.Type{snap.TypeBase}, []*seed.Snap{core20Snap}},
+ {[]snap.Type{snap.TypeGadget}, []*seed.Snap{pcSnap}},
+ {[]snap.Type{snap.TypeSnapd, snap.TypeKernel, snap.TypeBase}, []*seed.Snap{snapdSnap, pcKernelSnap, core20Snap}},
+ // the order in essentialTypes is not relevant
+ {[]snap.Type{snap.TypeGadget, snap.TypeKernel}, []*seed.Snap{pcKernelSnap, pcSnap}},
+ // degenerate case
+ {[]snap.Type{}, []*seed.Snap(nil)},
+ }
+
+ for _, t := range tests {
+ // hide the non-requested snaps to make sure they are not
+ // accessed
+ unhide := hideSnaps(c, all, t.onlyTypes)
+
+ seed20, err := seed.Open(s.SeedDir, sysLabel)
+ c.Assert(err, IsNil)
+
+ essSeed20, ok := seed20.(seed.EssentialMetaLoaderSeed)
+ c.Assert(ok, Equals, true)
+
+ err = essSeed20.LoadAssertions(s.db, s.commitTo)
+ c.Assert(err, IsNil)
+
+ err = essSeed20.LoadEssentialMeta(t.onlyTypes, s.perfTimings)
+ c.Assert(err, IsNil)
+
+ c.Check(essSeed20.UsesSnapdSnap(), Equals, true)
+
+ essSnaps := essSeed20.EssentialSnaps()
+ c.Check(essSnaps, HasLen, len(t.expected))
+
+ c.Check(essSnaps, DeepEquals, t.expected)
+
+ runSnaps, err := essSeed20.ModeSnaps("run")
+ c.Assert(err, IsNil)
+ c.Check(runSnaps, HasLen, 0)
+
+ unhide()
+ }
+}
+
func (s *seed20Suite) makeLocalSnap(c *C, yamlKey string) (fname string) {
return snaptest.MakeTestSnapWithFiles(c, snapYaml[yamlKey], nil)
}
@@ -675,29 +821,33 @@ func (s *seed20Suite) TestLoadMetaCore20LocalSnaps(c *C) {
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("snapd"),
- SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ EssentialType: snap.TypeSnapd,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc-kernel"),
- SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "20",
}, {
- Path: s.expectedPath("core20"),
- SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("core20"),
+ SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
+ EssentialType: snap.TypeBase,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc"),
- SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "20",
},
})
@@ -766,29 +916,33 @@ func (s *seed20Suite) TestLoadMetaCore20ChannelOverride(c *C) {
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("snapd"),
- SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ EssentialType: snap.TypeSnapd,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc-kernel"),
- SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "20",
}, {
- Path: s.expectedPath("core20"),
- SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("core20"),
+ SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
+ EssentialType: snap.TypeBase,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc"),
- SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20experimental/edge",
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "20experimental/edge",
},
})
@@ -857,29 +1011,33 @@ func (s *seed20Suite) TestLoadMetaCore20ChannelOverrideSnapd(c *C) {
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("snapd"),
- SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20experimental/edge",
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ EssentialType: snap.TypeSnapd,
+ Essential: true,
+ Required: true,
+ Channel: "20experimental/edge",
}, {
- Path: s.expectedPath("pc-kernel"),
- SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "20",
}, {
- Path: s.expectedPath("core20"),
- SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("core20"),
+ SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
+ EssentialType: snap.TypeBase,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc"),
- SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "20",
},
})
@@ -941,28 +1099,32 @@ func (s *seed20Suite) TestLoadMetaCore20LocalSnapd(c *C) {
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: filepath.Join(s.SeedDir, "systems", sysLabel, "snaps", "snapd_1.0.snap"),
- SideInfo: &snap.SideInfo{RealName: "snapd"},
- Essential: true,
- Required: true,
+ Path: filepath.Join(s.SeedDir, "systems", sysLabel, "snaps", "snapd_1.0.snap"),
+ SideInfo: &snap.SideInfo{RealName: "snapd"},
+ Essential: true,
+ EssentialType: snap.TypeSnapd,
+ Required: true,
}, {
- Path: s.expectedPath("pc-kernel"),
- SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "20",
}, {
- Path: s.expectedPath("core20"),
- SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("core20"),
+ SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
+ EssentialType: snap.TypeBase,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc"),
- SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "20",
},
})
@@ -1019,29 +1181,33 @@ func (s *seed20Suite) TestLoadMetaCore20ModelOverrideSnapd(c *C) {
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("snapd"),
- SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/edge",
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ EssentialType: snap.TypeSnapd,
+ Essential: true,
+ Required: true,
+ Channel: "latest/edge",
}, {
- Path: s.expectedPath("pc-kernel"),
- SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "20",
}, {
- Path: s.expectedPath("core20"),
- SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("core20"),
+ SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
+ EssentialType: snap.TypeBase,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc"),
- SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "20",
},
})
@@ -1107,29 +1273,33 @@ func (s *seed20Suite) TestLoadMetaCore20OptionalSnaps(c *C) {
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("snapd"),
- SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ EssentialType: snap.TypeSnapd,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc-kernel"),
- SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "20",
}, {
- Path: s.expectedPath("core20"),
- SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("core20"),
+ SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
+ EssentialType: snap.TypeBase,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc"),
- SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "20",
},
})
@@ -1203,29 +1373,33 @@ func (s *seed20Suite) TestLoadMetaCore20OptionalSnapsLocal(c *C) {
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("snapd"),
- SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ EssentialType: snap.TypeSnapd,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc-kernel"),
- SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "20",
}, {
- Path: s.expectedPath("core20"),
- SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("core20"),
+ SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
+ EssentialType: snap.TypeBase,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc"),
- SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "20",
},
})
@@ -1292,29 +1466,33 @@ func (s *seed20Suite) TestLoadMetaCore20ExtraSnaps(c *C) {
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("snapd"),
- SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ EssentialType: snap.TypeSnapd,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc-kernel"),
- SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "20",
}, {
- Path: s.expectedPath("core20"),
- SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("core20"),
+ SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
+ EssentialType: snap.TypeBase,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc"),
- SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "20",
},
})
@@ -1411,29 +1589,33 @@ func (s *seed20Suite) TestLoadMetaCore20NotRunSnaps(c *C) {
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("snapd"),
- SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ EssentialType: snap.TypeSnapd,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc-kernel"),
- SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "20",
}, {
- Path: s.expectedPath("core20"),
- SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("core20"),
+ SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
+ EssentialType: snap.TypeBase,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc"),
- SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "20",
},
})
@@ -1539,29 +1721,33 @@ func (s *seed20Suite) TestLoadMetaCore20LocalAssertedSnaps(c *C) {
c.Check(essSnaps, DeepEquals, []*seed.Snap{
{
- Path: s.expectedPath("snapd"),
- SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ EssentialType: snap.TypeSnapd,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc-kernel"),
- SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20",
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ EssentialType: snap.TypeKernel,
+ Essential: true,
+ Required: true,
+ Channel: "20",
}, {
- Path: s.expectedPath("core20"),
- SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
- Essential: true,
- Required: true,
- Channel: "latest/stable",
+ Path: s.expectedPath("core20"),
+ SideInfo: &s.AssertedSnapInfo("core20").SideInfo,
+ EssentialType: snap.TypeBase,
+ Essential: true,
+ Required: true,
+ Channel: "latest/stable",
}, {
- Path: s.expectedPath("pc"),
- SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
- Essential: true,
- Required: true,
- Channel: "20/edge",
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ EssentialType: snap.TypeGadget,
+ Essential: true,
+ Required: true,
+ Channel: "20/edge",
},
})
diff --git a/spread.yaml b/spread.yaml
index c5a279e8054..dac665c01e5 100644
--- a/spread.yaml
+++ b/spread.yaml
@@ -99,31 +99,33 @@ backends:
- opensuse-15.0-64:
workers: 6
manual: true
- - opensuse-15.1-64:
- workers: 6
- - opensuse-tumbleweed-64:
- workers: 6
- arch-linux-64:
- manual: true
workers: 6
+ - amazon-linux-2-64:
+ workers: 6
+ storage: preserve-size
- centos-7-64:
workers: 6
image: centos-7-64
+ - centos-8-64:
+ workers: 4
+ image: centos-8-64
# TODO: spread is really unhappy when it sees a backend without any systems,
# so this block is intentially kept commented out, until we need to add
# systems to it
#
google-unstable:
- type: google
- key: '$(HOST: echo "$SPREAD_GOOGLE_KEY")'
- location: computeengine/us-east1-b
- halt-timeout: 2h
- systems:
- - amazon-linux-2-64:
+ type: google
+ key: '$(HOST: echo "$SPREAD_GOOGLE_KEY")'
+ location: computeengine/us-east1-b
+ halt-timeout: 2h
+ systems:
+ - opensuse-15.1-64:
+ workers: 6
+ - opensuse-tumbleweed-64:
workers: 6
- storage: preserve-size
google-tpm:
@@ -202,7 +204,7 @@ backends:
username: ubuntu
password: ubuntu
- ubuntu-core-20-64:
- image: ubuntu-18.04-64
+ image: ubuntu-20.04-64
username: ubuntu
password: ubuntu
flags: [virtio]
@@ -586,6 +588,11 @@ prepare: |
apt-get update && apt-get install -y eatmydata
fi
+ if [[ "$SPREAD_SYSTEM" == centos-* ]]; then
+ # make sure EPEL is enabled
+ yum install -y epel-release
+ fi
+
# Unpack delta, or move content out of the prefixed directory (see rename and repack above).
# (needs to be in spread.yaml directly because there's nothing else on the filesystem yet)
if [ -f current.delta ]; then
@@ -597,12 +604,12 @@ prepare: |
apt-get update >& "$tf" || ( cat "$tf"; exit 1 )
apt-get install -y xdelta3 curl eatmydata >& "$tf" || ( cat "$tf"; exit 1 )
;;
- fedora-*)
- dnf install --refresh -y xdelta curl &> "$tf" || (cat "$tf"; exit 1)
- ;;
- amazon-*|centos-*)
+ amazon-*|centos-7-*)
yum install -y xdelta curl &> "$tf" || (cat "$tf"; exit 1)
;;
+ fedora-*|centos-*)
+ dnf install --refresh -y xdelta curl &> "$tf" || (cat "$tf"; exit 1)
+ ;;
opensuse-*)
zypper -q --gpg-auto-import-keys refresh
zypper -q install -y xdelta3 curl &> "$tf" || (cat "$tf"; exit 1)
diff --git a/tests/core/apt/task.yaml b/tests/core/apt/task.yaml
index cf4d2c876ac..82bd8c05198 100644
--- a/tests/core/apt/task.yaml
+++ b/tests/core/apt/task.yaml
@@ -1,16 +1,24 @@
summary: Ensure that the apt output on UC16 is correct
-# TODO:UC20: test that apt-get doesn't exist on UC18 / UC20 in this tests and
-# re-enable
-systems: [ubuntu-core-16-*]
-
execute: |
- expected="Ubuntu Core does not use apt-get, see 'snap --help'!"
- if apt-get update > output.txt; then
- echo "apt should exit 1 but did not"
- exit 1
- fi
- if [ "$(cat output.txt)" != "$expected" ]; then
- echo "Unexpected apt output: $(cat output.txt)"
- exit 1
+ # shellcheck source=tests/lib/systems.sh
+ . "$TESTSLIB/systems.sh"
+
+ if is_core16_system; then
+ # UC16 has apt-get output a message
+ expected="Ubuntu Core does not use apt-get, see 'snap --help'!"
+ if apt-get update > output.txt; then
+ echo "apt should exit 1 but did not"
+ exit 1
+ fi
+ if [ "$(cat output.txt)" != "$expected" ]; then
+ echo "Unexpected apt output: $(cat output.txt)"
+ exit 1
+ fi
+ else
+ # UC18+ does not have apt-get at all
+ if command -v apt-get; then
+ echo "apt-get command exists on Ubuntu Core, but should not!"
+ exit 1
+ fi
fi
diff --git a/tests/core/basic20/task.yaml b/tests/core/basic20/task.yaml
index ba8b1e8bf8b..35d5df5aa9e 100644
--- a/tests/core/basic20/task.yaml
+++ b/tests/core/basic20/task.yaml
@@ -27,9 +27,9 @@ execute: |
echo "Ensure passwd/group is available for snaps"
test-snapd-sh-core18.sh -c 'cat /var/lib/extrausers/passwd' | MATCH test
- # TODO:UC20: also check for /boot/grub/kernel.efi symlink once it's there
- echo "Check that we have an extracted kernel"
+ echo "Ensure extracted kernel.efi exists"
test -e /boot/grub/pc-kernel*/kernel.efi
+ test -e /boot/grub/kernel.efi
# ensure that our the-tool (and thus our snap-bootstrap ran)
echo "Check that we booted with the rebuilt initramfs in the kernel snap"
diff --git a/tests/core/classic-snap16/task.yaml b/tests/core/classic-snap16/task.yaml
index ede25d47601..9d7a1a8d34c 100644
--- a/tests/core/classic-snap16/task.yaml
+++ b/tests/core/classic-snap16/task.yaml
@@ -1,5 +1,7 @@
summary: Ensure classic dimension works correctly
+# classic snap is not maintained for UC18+, for these releases the lxd snaps is
+# recommended instead of the classic snap
systems: [ubuntu-core-16-*]
environment:
diff --git a/tests/core/config-defaults-once/task.yaml b/tests/core/config-defaults-once/task.yaml
index 7dd583bdad3..547e7f836b4 100644
--- a/tests/core/config-defaults-once/task.yaml
+++ b/tests/core/config-defaults-once/task.yaml
@@ -1,7 +1,9 @@
summary: |
- Test that configuration defaults are only applied once.
+ Test that configuration defaults are only applied once.
-# ubuntu-core-16: it is not yet possible to install snapd on core16
+# it is not yet possible to install snapd on UC16
+# TODO:UC20: enable for UC20, currently fails because there is no seed.yaml in
+# the same place as UC18
systems: [ubuntu-core-18-*]
environment:
diff --git a/tests/core/create-user-2/task.yaml b/tests/core/create-user-2/task.yaml
index 4449de6bcab..ca7e016bc88 100644
--- a/tests/core/create-user-2/task.yaml
+++ b/tests/core/create-user-2/task.yaml
@@ -8,6 +8,9 @@ environment:
USER_NAME: mvo
restore: |
+ if [ -e managed.device ]; then
+ exit 0
+ fi
snap remove-user "$USER_NAME"
execute: |
@@ -15,7 +18,7 @@ execute: |
expected="error: while creating user: access denied"
if obtained=$(su - test /bin/sh -c "snap create-user $USER_EMAIL 2>&1"); then
echo "create-user command should have failed"
- fi
+ fi
[[ "$obtained" =~ $expected ]]
if [ "$(snap managed)" = "true" ]; then
@@ -25,6 +28,10 @@ execute: |
exit 1
fi
MATCH "cannot create user: device already managed" < create.error
+
+ # Leave a file indicating the device was initially managed
+ touch managed.device
+
exit 0
fi
diff --git a/tests/core/create-user/task.yaml b/tests/core/create-user/task.yaml
index 535c9e59fcd..3501c41e0fa 100644
--- a/tests/core/create-user/task.yaml
+++ b/tests/core/create-user/task.yaml
@@ -4,21 +4,27 @@ summary: Ensure that snap create-user works in ubuntu-core
# TODO:UC20: enable for UC20
systems: [ubuntu-core-1*]
+environment:
+ USER_EMAIL: mvo@ubuntu.com
+ USER_NAME: mvo
+
restore: |
- # FIXME: use deluser here now that it supports --extrausers
- sed -i '/^mvo/d' /var/lib/extrausers/passwd
- sed -i '/^mvo/d' /var/lib/extrausers/shadow
- sed -i '/^mvo/d' /var/lib/extrausers/group
- rm -rf /home/mvo
- rm -f create.error
+ if [ -e managed.device ]; then
+ exit 0
+ fi
+ snap remove-user "$USER_NAME"
execute: |
if [ "$MANAGED_DEVICE" = "true" ]; then
- if snap create-user --sudoer mvo@ubuntu.com 2>create.error; then
+ if snap create-user --sudoer "$USER_EMAIL" 2>create.error; then
echo "Did not get expected error creating user in managed device"
exit 1
fi
MATCH "cannot create user: device already managed" < create.error
+
+ # Leave a file indicating the device was initially managed
+ touch managed.device
+
exit 0
fi
echo "Adding invalid user"
@@ -30,23 +36,23 @@ execute: |
MATCH "$expected" <<<"$output"
echo "Adding valid user"
- expected='created user "mvo"'
- output=$(snap create-user --sudoer mvo@ubuntu.com)
+ expected="created user \"$USER_NAME\""
+ output=$(snap create-user --sudoer "$USER_EMAIL")
if [ "$output" != "$expected" ]; then
echo "Unexpected output $output"
exit 1
fi
echo "Ensure there are ssh keys imported"
- MATCH ssh-rsa < /home/mvo/.ssh/authorized_keys
+ MATCH ssh-rsa < /home/"$USER_NAME"/.ssh/authorized_keys
echo "Ensure the user is a sudo user"
- sudo -u mvo sudo true
+ sudo -u "$USER_NAME" sudo true
echo "ensure the user's home directory exists"
- test -d /home/mvo
+ test -d /home/"$USER_NAME"
echo "ensure ~/.snap/auth.json was created"
- test -f /home/mvo/.snap/auth.json
+ test -f /home/"$USER_NAME"/.snap/auth.json
echo "ensure user's email was stored in ~/.snap/auth.json"
- MATCH '"email":"mvo@ubuntu.com"' < /home/mvo/.snap/auth.json
+ MATCH "\"email\":\"$USER_EMAIL\"" < /home/"$USER_NAME"/.snap/auth.json
diff --git a/tests/core/custom-device-reg-extras/task.yaml b/tests/core/custom-device-reg-extras/task.yaml
index 871678c3edd..fab4f069d8e 100644
--- a/tests/core/custom-device-reg-extras/task.yaml
+++ b/tests/core/custom-device-reg-extras/task.yaml
@@ -3,7 +3,8 @@ summary: |
with the prepare-device gadget hook and this can set request headers,
a proposed serial and the body of the serial assertion
-# TODO:UC20: enable for UC20
+# TODO:UC20: enable for UC20, it assumes /var/lib/snapd/seed/assertions/model
+# which we don't have currently
systems: [ubuntu-core-1*-64]
prepare: |
diff --git a/tests/core/custom-device-reg/task.yaml b/tests/core/custom-device-reg/task.yaml
index c081546a094..05c35fd0e1b 100644
--- a/tests/core/custom-device-reg/task.yaml
+++ b/tests/core/custom-device-reg/task.yaml
@@ -2,8 +2,9 @@ summary: |
Test that device initialisation and registration can be customized
with the prepare-device gadget hook
-# TODO:UC20: enable for UC20
-systems: [ubuntu-core-1*-64]
+# TODO:UC20: enable for UC20, it assumes /var/lib/snapd/seed/assertions/model
+# which we don't have currently
+systems: [ubuntu-core-1*]
prepare: |
if [ "$TRUST_TEST_KEYS" = "false" ]; then
diff --git a/tests/core/device-reg/task.yaml b/tests/core/device-reg/task.yaml
index 2c25d0e1b2d..fdc503933b4 100644
--- a/tests/core/device-reg/task.yaml
+++ b/tests/core/device-reg/task.yaml
@@ -2,8 +2,8 @@ summary: |
Ensure after device initialisation registration worked and
we have a serial and can acquire a session macaroon
-# TODO:UC20: enable for UC20
-systems: [ubuntu-core-1*]
+# TODO:UC20: enable on arm when we have the model names for these devices
+systems: [-ubuntu-core-20-arm*]
execute: |
#shellcheck source=tests/lib/names.sh
@@ -29,6 +29,9 @@ execute: |
snap model --serial --assertion | MATCH "authority-id: canonical"
snap model --serial --assertion | MATCH "brand-id: canonical"
case "$SPREAD_SYSTEM" in
+ ubuntu-core-20-64)
+ snap model --serial --assertion | MATCH "model: ubuntu-core-20-amd64"
+ ;;
ubuntu-core-18-64)
snap model --serial --assertion | MATCH "model: ubuntu-core-18-amd64"
;;
diff --git a/tests/core/grub-no-unpacked-assets/task.yaml b/tests/core/grub-no-unpacked-assets/task.yaml
index 48bdf91c329..a1e37e8975e 100644
--- a/tests/core/grub-no-unpacked-assets/task.yaml
+++ b/tests/core/grub-no-unpacked-assets/task.yaml
@@ -1,7 +1,6 @@
summary: Ensure we have no unpacked kernel.img/initrd.img on grub systems
-# TODO:UC20: enable for UC20 on ubuntu-seed grub?
-systems: [ubuntu-core-1*-64]
+systems: [ubuntu-core-*-64]
environment:
NAME/initrdimg: initrd.img*
diff --git a/tests/core/kernel-ver/task.yaml b/tests/core/kernel-ver/task.yaml
new file mode 100644
index 00000000000..5fb78b2bee4
--- /dev/null
+++ b/tests/core/kernel-ver/task.yaml
@@ -0,0 +1,18 @@
+summary: Ensure that we have the right kernel
+
+# TODO: enable for arm?
+systems: [ubuntu-core-*-64]
+
+execute: |
+ # shellcheck source=tests/lib/systems.sh
+ . "$TESTSLIB/systems.sh"
+
+ echo "Check kernel version"
+ if is_core16_system; then
+ VER="^4.4"
+ elif is_core18_system; then
+ VER="^4.15"
+ elif is_core20_system; then
+ VER="^5.4"
+ fi
+ uname -r | MATCH $VER
diff --git a/tests/core/kernel/task.yaml b/tests/core/kernel/task.yaml
deleted file mode 100644
index 8bc9a1bd55e..00000000000
--- a/tests/core/kernel/task.yaml
+++ /dev/null
@@ -1,9 +0,0 @@
-summary: Ensure that we have the right kernel
-
-# TODO:UC20: add 5.X series kernel for UC20
-# TODO: add 4.4 series kernel check for UC16
-systems: [ubuntu-core-18-*]
-
-execute: |
- echo "Check kernel version"
- uname -r | MATCH "^4.15"
diff --git a/tests/core/network-config/task.yaml b/tests/core/network-config/task.yaml
index 79dfbfe607f..ba4451774d7 100644
--- a/tests/core/network-config/task.yaml
+++ b/tests/core/network-config/task.yaml
@@ -1,9 +1,5 @@
summary: Check that `snap set {system,core} network.disable-ipv6` works
-# the test is only meaningful on core devices
-# TODO:UC20: enable for UC20
-systems: [ubuntu-core-1*]
-
environment:
snap_nick/system: system
snap_nick/core: core
@@ -11,7 +7,7 @@ environment:
execute: |
echo "Ensure we have inet6"
ip addr | MATCH inet6
-
+
echo "Disable ipv6"
# shellcheck disable=SC2154
snap set "$snap_nick" network.disable-ipv6=true
diff --git a/tests/core/os-release/task.yaml b/tests/core/os-release/task.yaml
index 468d5da5806..7301f907804 100644
--- a/tests/core/os-release/task.yaml
+++ b/tests/core/os-release/task.yaml
@@ -1,15 +1,20 @@
summary: check that os-release is correct
-# TODO:UC20: enable with DISTRIB_RELEASE=20
-systems: [ubuntu-core-1*]
+debug: |
+ cat /etc/lsb-release
execute: |
#shellcheck source=tests/lib/systems.sh
. "$TESTSLIB"/systems.sh
- if is_core18_system; then
+ if is_core16_system; then
+ MATCH "DISTRIB_RELEASE=16" < /etc/lsb-release
+ elif is_core18_system; then
MATCH "DISTRIB_RELEASE=18" < /etc/lsb-release
+ elif is_core20_system; then
+ MATCH "DISTRIB_RELEASE=20" < /etc/lsb-release
else
- MATCH "DISTRIB_RELEASE=16" < /etc/lsb-release
+ echo "Unknown Ubuntu Core system!"
+ exit 1
fi
MATCH "ID=ubuntu-core" < /etc/os-release
diff --git a/tests/core/reboot/task.yaml b/tests/core/reboot/task.yaml
index 7178cba98c1..fad7cf37d6c 100644
--- a/tests/core/reboot/task.yaml
+++ b/tests/core/reboot/task.yaml
@@ -1,8 +1,5 @@
summary: Ensure that service and apparmor profiles work after a reboot
-# TODO:UC20: enable for UC20
-systems: [ubuntu-core-1*]
-
# Start early as it takes a long time.
priority: 100
diff --git a/tests/core/remove/task.yaml b/tests/core/remove/task.yaml
index 710f8554657..69eebd1abda 100644
--- a/tests/core/remove/task.yaml
+++ b/tests/core/remove/task.yaml
@@ -1,17 +1,26 @@
summary: Check that removal of essential snaps does not work
-# TODO:UC20: enable for UC20+ with core20 as an unremovable base snap
-systems: [ubuntu-core-18-*]
+# UC16 does not seed the snapd snap (yet)
+systems: [-ubuntu-core-16-*]
execute: |
+ # shellcheck source=tests/lib/systems.sh
+ . "$TESTSLIB/systems.sh"
+
echo "Ensure snapd cannot be removed"
if snap remove --purge snapd; then
echo "The snapd snap should not be removable"
exit 1
fi
- echo "Ensure core18 cannot be removed"
- if snap remove --purge core18; then
- echo "The core18 snap should not removable"
+
+ if is_core18_system; then
+ base=core18
+ elif is_core20_system; then
+ base=core20
+ fi
+ echo "Ensure $base cannot be removed"
+ if snap remove --purge "$base"; then
+ echo "The $base snap should not removable"
exit 1
fi
@@ -19,7 +28,7 @@ execute: |
snap install test-snapd-sh
snap list | MATCH '^core '
if snap remove --purge core; then
- echo "core should not be removalble because test-snapd-tools needs it"
+ echo "core should not be removable because test-snapd-tools needs it"
exit 1
fi
diff --git a/tests/core/services/task.yaml b/tests/core/services/task.yaml
index 50b6feeecc7..3fa0ae2d86b 100644
--- a/tests/core/services/task.yaml
+++ b/tests/core/services/task.yaml
@@ -1,8 +1,5 @@
summary: Ensure all services on Core are active
-# TODO:UC20: enable for UC20
-systems: [ubuntu-core-1*]
-
execute: |
echo "Ensure one-shot services are working"
for oneshot in snapd.autoimport.service snapd.sshd-keygen.service; do
@@ -11,6 +8,6 @@ execute: |
echo "Ensure services are working"
systemctl status snapd.service |MATCH active
-
+
echo "Ensure timers are working"
systemctl is-active snapd.snap-repair.timer
diff --git a/tests/core/snap-auto-import-asserts-spools/task.yaml b/tests/core/snap-auto-import-asserts-spools/task.yaml
index ae2cb890ffb..4083d9137e1 100644
--- a/tests/core/snap-auto-import-asserts-spools/task.yaml
+++ b/tests/core/snap-auto-import-asserts-spools/task.yaml
@@ -1,7 +1,6 @@
summary: Check that `snap auto-import` works as expected
-# TODO:UC20: enable for UC20
-systems: [ubuntu-core-1*-64]
+systems: [ubuntu-core-*-64]
prepare: |
if [ "$TRUST_TEST_KEYS" = "false" ]; then
diff --git a/tests/core/snap-auto-import-asserts/task.yaml b/tests/core/snap-auto-import-asserts/task.yaml
index 4673db83dc9..1b7dd46dd95 100644
--- a/tests/core/snap-auto-import-asserts/task.yaml
+++ b/tests/core/snap-auto-import-asserts/task.yaml
@@ -1,7 +1,6 @@
summary: Check that `snap auto-import` works as expected
-# TODO:UC20: enable for UC20
-systems: [ubuntu-core-1*-64]
+systems: [ubuntu-core-*-64]
prepare: |
if [ "$TRUST_TEST_KEYS" = "false" ]; then
diff --git a/tests/core/snap-auto-mount/task.yaml b/tests/core/snap-auto-mount/task.yaml
index bdf81c49996..480df3657e2 100644
--- a/tests/core/snap-auto-mount/task.yaml
+++ b/tests/core/snap-auto-mount/task.yaml
@@ -1,6 +1,7 @@
summary: Check that `snap auto-import` works as expected
-# TODO:UC20: enable for UC20
+# TODO:UC20: fix for UC20 and enable, currently fails because mkfs.vfat is not
+# available on UC20
systems: [ubuntu-core-1*-64]
prepare: |
diff --git a/tests/core/snap-core-fixup/task.yaml b/tests/core/snap-core-fixup/task.yaml
index a80c7874f5d..3c30f63ac67 100644
--- a/tests/core/snap-core-fixup/task.yaml
+++ b/tests/core/snap-core-fixup/task.yaml
@@ -1,6 +1,6 @@
summary: Check that the core-fixup-sh script works
-# TODO:UC20: enable for UC20
+# TODO:UC20: enable for UC20, currently fails with "Image not repaired"
systems: [ubuntu-core-1*]
restore: |
diff --git a/tests/core/snap-set-core-config/task.yaml b/tests/core/snap-set-core-config/task.yaml
index 4fec7470971..3f793bd146a 100644
--- a/tests/core/snap-set-core-config/task.yaml
+++ b/tests/core/snap-set-core-config/task.yaml
@@ -1,6 +1,5 @@
summary: Ensure "snap set core" works
-# the test is only meaningful on core devices
# TODO:UC20: enable for UC20
systems: [ubuntu-core-1*]
diff --git a/tests/core/snapd-failover/task.yaml b/tests/core/snapd-failover/task.yaml
index b36320a1437..c125fda02d6 100644
--- a/tests/core/snapd-failover/task.yaml
+++ b/tests/core/snapd-failover/task.yaml
@@ -1,16 +1,18 @@
summary: Check that snapd failure handling works
-# TODO:UC20: enable for UC20, currently snapd.failure.service does not work on
-# UC20, it currently isn't activated for some reason
# UC16 still uses the core snap rather than the snapd snap, so disable this test
# for UC16
-systems: [-ubuntu-core-20-*, -ubuntu-core-16-*]
+systems: [-ubuntu-core-16-*]
debug: |
# dump failure data
journalctl -u snapd.failure.service
journalctl -u snapd.socket || true
ls -l /snap/snapd/
+ cat /etc/systemd/system/snapd.service
+ cat /etc/systemd/system/usr-lib-snapd.mount
+ /snap/snapd/x1/usr/bin/snap debug state /var/lib/snapd/state.json || true
+ /snap/snapd/x1/usr/bin/snap debug state --change="$(/snap/snapd/x1/usr/bin/snap debug state /var/lib/snapd/state.json|tail -n1|awk '{print $1}')" /var/lib/snapd/state.json || true
prepare: |
cp -a /etc/systemd/system/snapd.service.d/local.conf local.conf.bak
diff --git a/tests/main/ubuntu-core-snapd-failover/task.yaml b/tests/core/snapd-failover16/task.yaml
similarity index 100%
rename from tests/main/ubuntu-core-snapd-failover/task.yaml
rename to tests/core/snapd-failover16/task.yaml
diff --git a/tests/main/ubuntu-core-snapd/task.yaml b/tests/core/snapd16/task.yaml
similarity index 96%
rename from tests/main/ubuntu-core-snapd/task.yaml
rename to tests/core/snapd16/task.yaml
index fcfa5358ac3..6fb48df856e 100644
--- a/tests/main/ubuntu-core-snapd/task.yaml
+++ b/tests/core/snapd16/task.yaml
@@ -28,7 +28,7 @@ execute: |
# RESTORE CODE
# we cannot restore in "restore:" because we need a reboot
# right now to get a clean state again
- systemctl stop snapd.service snapd.socket snapd.autoimport.service snapd.snap-repair.service
+ systemctl stop snapd.service snapd.socket snapd.autoimport.service snapd.snap-repair.service snapd.snap-repair.timer
umount "/snap/snapd/$(readlink /snap/snapd/current)"
rm -f /etc/systemd/system/usr-lib-snapd.mount
diff --git a/tests/core/writablepaths/task.yaml b/tests/core/writablepaths/task.yaml
index a9ab84b755b..0309380d2a7 100644
--- a/tests/core/writablepaths/task.yaml
+++ b/tests/core/writablepaths/task.yaml
@@ -1,8 +1,5 @@
summary: Ensure that the writable paths on the image are correct
-# TODO:UC20: enable for uc20
-systems: [ubuntu-core-1*]
-
execute: |
echo "Ensure everything in writable-paths is actually writable"
#shellcheck disable=SC2002
diff --git a/tests/lib/assertions/ubuntu-core-20-amd64.model b/tests/lib/assertions/ubuntu-core-20-amd64.model
index db24a42b7e3..60bfa5df9d3 100644
--- a/tests/lib/assertions/ubuntu-core-20-amd64.model
+++ b/tests/lib/assertions/ubuntu-core-20-amd64.model
@@ -1,42 +1,42 @@
type: model
-authority-id: vxj7EkYOlg15uAHRswTXIUpzWqO2sTBi
+authority-id: canonical
series: 16
-brand-id: vxj7EkYOlg15uAHRswTXIUpzWqO2sTBi
-model: ubuntu-core-20-mvo-amd64
+brand-id: canonical
+model: ubuntu-core-20-amd64-dangerous
architecture: amd64
base: core20
grade: dangerous
snaps:
-
- default-channel: 20/edge
+ default-channel: 20/stable
id: UqFziVZDHLSyO3TqSWgNBoAdHbLI4dAH
name: pc
type: gadget
-
- default-channel: latest/edge
- id: DLqre5XGLbDqg9jPtiAhRRjDuPVa5X1q
- name: core20
- type: base
- -
- default-channel: 20/edge
+ default-channel: 20/stable
id: pYVQrBcKmBa0mZ4CCN7ExT6jH8rY1hza
name: pc-kernel
type: kernel
-
- default-channel: latest/edge
+ default-channel: latest/stable
+ id: DLqre5XGLbDqg9jPtiAhRRjDuPVa5X1q
+ name: core20
+ type: base
+ -
+ default-channel: latest/stable
id: PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4
name: snapd
type: snapd
-timestamp: 2019-08-26T00:00:00.0Z
-sign-key-sha3-384: 2XxnFfNz1CCA6OZ5v03QjbFaK_A6-FjnNzNYJSMhSm_RC77iriSBh9X_olPNmupv
+timestamp: 2020-01-13T15:47:00.0Z
+sign-key-sha3-384: 9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn
-AcLBcwQAAQoAHRYhBNPJP7iUC6HvARf0BQG87hgg441ZBQJeFYBTAAoJEAG87hgg441Zh8EQAKUj
-XYZAf8OlKDRD5uRPjV0eX4RA+UGieu/l392cNWsN18ngEMewFEKtLXltqeLF9SO/mwTVWPVn8jl4
-RbJESfxA8jDKYnfmMNT/kiytRMSxXkOcrxpW54LuFSg9LkZx4vQOQR1Snjic7z2HHLpq3Ain5Pry
-zjWddYi68ew2MDM2Y6jxGa/V651w5B1YTev4rNcmVkvXQFxFuZe5CYaFswpfZMtqk1N6K7pPF2RT
-OQh1Wl9fYA7tBjgt4jFcEr6L9qWj4oQbFXElhlvZGbKkjdv3A0b3BGyqKxA/1ptsAcp9DfWUNcXd
-/ya28u+n3bZkgKxJY/pL5Q9R90DaoIxoDKK4vzzQQ6twVQi/6ug0J7F8obkhuvGrMwhhmEltn2hr
-z5xMO6EZcQY1YxLXsPmH/5uZI4V4wH3+DAmpOBzNMRRFZOQjC8ngJIeKQI1e8rzH9qUzKXNvJRXC
-GMoHE7zqwe9z2lTAN1wHAHpctwepb/XNe+tRIcxxBpo83rbwii6zkikNRhsSF9djk0hUF1OM7dst
-e3wC0wi0wpp3tbmEvKOhWXbCQK0lZywkV6KpASM5uXn4Q4vC+Y8sp6CPhEnXCDCrfM7WolmjUeUC
-KHTZSZjNA94TZ/KUYvqeBWC2G9D9NMhdUVN+77y8AERz9mm9wjbMQuStpUM4dQXw5GEB5fBU
+AcLBXAQAAQoABgUCXiaAHgAKCRDgT5vottzAEtjfEACV1WqxZ5sfPNeCJJoyuvrq559j7puqz0l7
+VH7EhBGFJgUd+RgtBwVczcz5+93qpGJXdWn2NsTJ9YqJH7nDXMCmzueZn7ZggEvbIitXpjCgatcu
+Dv0QmtvZcEG27GVsTiENZqLrwAJsg1mSohj/N/ghBYC8mXEXP7mtF8GjRSnkCJTcjCjXQj1qXff5
+TEjBgYhbvbrw8iLzJjigWfhowEy31EE7SNzh5mT/Np91SIl3Y4EKGNmanI3xmvskNNULoHttfjZ0
+GTBoJrXsIak3tqKkC8saWurI4PDGpt8XsfegAP27f+PVOVgCx1vj/i+xaaskgisk+lW4BzIxkcm6
+ZkXaWWaW7xbFIutiluZLAxVX34XjAQUaAScfUAGG2FEQzi7sbJQTkXJtepoftMWH1ZVNBz4w4HIl
+t6FeHqsuLDnC5QSMZwds7fh+TOKW8g3EQTQ99cwiF6j7GUJiOR0r/z1YFXOvsi2ZYAD7wsCQrKS/
+eApEre3kTtHbTsZlp621/e/cIe+aIZ+Ian40rYLQ2RdLelEWLYvT6axnW6SceQAgnOsMEn8sApfI
+lmk02GpkGOA7kVTcaN/XhQOLY+kL3tFGFIQ/XG4hYorzZgd9sCGMZ2mG9WTT7fmA+ivBkf/Dk5iR
+PS8bi4ci6fyYjaMdL0HnYWRk26Pxm/fL+kRC/mt1FA==
diff --git a/tests/lib/pkgdb.sh b/tests/lib/pkgdb.sh
index 6530acb7e5b..6c173cc1bf4 100755
--- a/tests/lib/pkgdb.sh
+++ b/tests/lib/pkgdb.sh
@@ -128,12 +128,12 @@ distro_name_package() {
ubuntu-*|debian-*)
debian_name_package "$@"
;;
- fedora-*)
- fedora_name_package "$@"
- ;;
- amazon-*|centos-*)
+ amazon-*|centos-7-*)
amazon_name_package "$@"
;;
+ fedora-*|centos-*)
+ fedora_name_package "$@"
+ ;;
opensuse-*)
opensuse_name_package "$@"
;;
@@ -174,12 +174,12 @@ distro_install_local_package() {
# shellcheck disable=SC2086
apt install $flags "$@"
;;
- fedora-*)
- quiet dnf -y install --setopt=install_weak_deps=False "$@"
- ;;
- amazon-*|centos-*)
+ amazon-*|centos-7-*)
quiet yum -y localinstall "$@"
;;
+ fedora-*|centos-*)
+ quiet dnf -y install --setopt=install_weak_deps=False "$@"
+ ;;
opensuse-*)
quiet rpm -i --replacepkgs "$@"
;;
@@ -269,13 +269,13 @@ distro_install_package() {
# shellcheck disable=SC2086
quiet eatmydata apt-get install $APT_FLAGS -y "${pkg_names[@]}"
;;
- fedora-*)
+ amazon-*|centos-7-*)
# shellcheck disable=SC2086
- quiet dnf -y --refresh install $DNF_FLAGS "${pkg_names[@]}"
+ quiet yum -y install $YUM_FLAGS "${pkg_names[@]}"
;;
- amazon-*|centos-*)
+ fedora-*|centos-*)
# shellcheck disable=SC2086
- quiet yum -y install $YUM_FLAGS "${pkg_names[@]}"
+ quiet dnf -y --refresh install $DNF_FLAGS "${pkg_names[@]}"
;;
opensuse-*)
# packages may be downgraded in the repositories, which would be
@@ -319,13 +319,13 @@ distro_purge_package() {
ubuntu-*|debian-*)
quiet eatmydata apt-get remove -y --purge -y "$@"
;;
- fedora-*)
+ amazon-*|centos-7-*)
+ quiet yum -y remove "$@"
+ ;;
+ fedora-*|centos-*)
quiet dnf -y remove "$@"
quiet dnf clean all
;;
- amazon-*|centos-*)
- quiet yum -y remove "$@"
- ;;
opensuse-*)
quiet zypper remove -y "$@"
;;
@@ -344,14 +344,14 @@ distro_update_package_db() {
ubuntu-*|debian-*)
quiet eatmydata apt-get update
;;
- fedora-*)
- quiet dnf clean all
- quiet dnf makecache
- ;;
- amazon-*|centos-*)
+ amazon-*|centos-7-*)
quiet yum clean all
quiet yum makecache
;;
+ fedora-*|centos-*)
+ quiet dnf clean all
+ quiet dnf makecache
+ ;;
opensuse-*)
quiet zypper --gpg-auto-import-keys refresh
;;
@@ -370,12 +370,12 @@ distro_clean_package_cache() {
ubuntu-*|debian-*)
quiet eatmydata apt-get clean
;;
- fedora-*)
- dnf clean all
- ;;
- amazon-*|centos-*)
+ amazon-*|centos-7-*)
yum clean all
;;
+ fedora-*|centos-*)
+ dnf clean all
+ ;;
opensuse-*)
zypper -q clean --all
;;
@@ -394,12 +394,12 @@ distro_auto_remove_packages() {
ubuntu-*|debian-*)
quiet eatmydata apt-get -y autoremove
;;
- fedora-*)
- quiet dnf -y autoremove
- ;;
- amazon-*|centos-*)
+ amazon-*|centos-7-*)
quiet yum -y autoremove
;;
+ fedora-*|centos-*)
+ quiet dnf -y autoremove
+ ;;
opensuse-*)
;;
arch-*)
@@ -416,12 +416,12 @@ distro_query_package_info() {
ubuntu-*|debian-*)
apt-cache policy "$1"
;;
- fedora-*)
- dnf info "$1"
- ;;
- amazon-*|centos-*)
+ amazon-*|centos-7-*)
yum info "$1"
;;
+ fedora-*|centos-*)
+ dnf info "$1"
+ ;;
opensuse-*)
zypper info "$1"
;;
@@ -593,6 +593,7 @@ pkg_dependencies_ubuntu_classic(){
weston
xdg-user-dirs
xdg-utils
+ zsh
"
case "$SPREAD_SYSTEM" in
@@ -700,8 +701,8 @@ pkg_dependencies_fedora(){
jq
iptables-services
man
- mock
net-tools
+ nmap-ncat
nfs-utils
PackageKit
python3-yaml
@@ -714,6 +715,7 @@ pkg_dependencies_fedora(){
xdg-user-dirs
xdg-utils
strace
+ zsh
"
}
@@ -730,7 +732,6 @@ pkg_dependencies_amazon(){
jq
iptables-services
man
- mock
nc
net-tools
nfs-utils
@@ -741,6 +742,7 @@ pkg_dependencies_amazon(){
xdg-utils
udisks2
upower
+ zsh
"
}
@@ -768,6 +770,7 @@ pkg_dependencies_opensuse(){
uuidd
xdg-user-dirs
xdg-utils
+ zsh
"
}
@@ -788,6 +791,7 @@ pkg_dependencies_arch(){
libseccomp
libcap
libx11
+ man
net-tools
nfs-utils
openbsd-netcat
@@ -806,6 +810,7 @@ pkg_dependencies_arch(){
xdg-utils
xfsprogs
apparmor
+ zsh
"
}
@@ -819,12 +824,12 @@ pkg_dependencies(){
pkg_dependencies_ubuntu_generic
pkg_dependencies_ubuntu_classic
;;
- fedora-*)
- pkg_dependencies_fedora
- ;;
- amazon-*|centos-*)
+ amazon-*|centos-7-*)
pkg_dependencies_amazon
;;
+ fedora-*|centos-*)
+ pkg_dependencies_fedora
+ ;;
opensuse-*)
pkg_dependencies_opensuse
;;
diff --git a/tests/lib/prepare.sh b/tests/lib/prepare.sh
index 4427a1c74b3..408cd866b45 100755
--- a/tests/lib/prepare.sh
+++ b/tests/lib/prepare.sh
@@ -641,10 +641,6 @@ EOF
setup_reflash_magic() {
# install the stuff we need
distro_install_package kpartx busybox-static
- # for core20 we need debootstrap to build the initramfs in the kernel snap
- if is_core20_system; then
- distro_install_package debootstrap
- fi
distro_install_local_package "$GOHOME"/snapd_*.deb
distro_clean_package_cache
@@ -652,16 +648,23 @@ setup_reflash_magic() {
# need to be seeded to proceed with snap install
snap wait system seed.loaded
+ # download the snapd snap for all uc systems except uc16
+ if ! is_core16_system; then
+ snap download "--channel=${SNAPD_CHANNEL}" snapd
+ fi
+
# we cannot use "names.sh" here because no snaps are installed yet
core_name="core"
if is_core18_system; then
- snap download "--channel=${SNAPD_CHANNEL}" snapd
core_name="core18"
- elif is_core20_system; then
- snap download "--channel=${SNAPD_CHANNEL}" snapd
+ elif is_core20_system; then
core_name="core20"
fi
snap install "--channel=${CORE_CHANNEL}" "$core_name"
+ if is_core16_system || is_core18_system; then
+ UNPACK_DIR="/tmp/$core_name-snap"
+ unsquashfs -no-progress -d "$UNPACK_DIR" /var/lib/snapd/snaps/${core_name}_*.snap
+ fi
# install ubuntu-image
snap install --classic --edge ubuntu-image
@@ -685,15 +688,9 @@ setup_reflash_magic() {
IMAGE=core18-amd64.img
elif is_core20_system; then
repack_snapd_snap_with_deb_content_and_run_mode_firstboot_tweaks "$IMAGE_HOME"
- # TODO:UC20: use canonical model instead of "mvo" one
cp "$TESTSLIB/assertions/ubuntu-core-20-amd64.model" "$IMAGE_HOME/pc.model"
IMAGE=core20-amd64.img
else
- # modify the core snap so that the current root-pw works there
- # for spread to do the first login
- UNPACK_DIR="/tmp/core-snap"
- unsquashfs -no-progress -d "$UNPACK_DIR" /var/lib/snapd/snaps/core_*.snap
-
# FIXME: install would be better but we don't have dpkg on
# the image
# unpack our freshly build snapd into the new snapd snap
@@ -783,9 +780,7 @@ EOF
# /dev/loop19 0 0 1 1 /var/lib/snapd/snaps/test-snapd-netplan-apply_75.snap 0 512
devloop=$(losetup --list --noheadings | grep "$IMAGE_HOME/$IMAGE" | awk '{print $1}')
dev=$(basename "$devloop")
- if is_core18_system; then
- mount "/dev/mapper/${dev}p3" /mnt
- elif is_core20_system; then
+ if is_core20_system; then
# (ab)use ubuntu-seed
mount "/dev/mapper/${dev}p2" /mnt
else
@@ -838,12 +833,7 @@ EOF
/home/gopath /root/test-etc /var/lib/extrausers
fi
- # now modify the image writable partition
- if is_core18_system; then
- UNPACK_DIR="/tmp/core18-snap"
- unsquashfs -no-progress -d "$UNPACK_DIR" /var/lib/snapd/snaps/core18_*.snap
- fi
- # modifying "writable" is only possible on uc16/uc18
+ # now modify the image writable partition - only possible on uc16 / uc18
if is_core16_system || is_core18_system; then
# modify the writable partition of "core" so that we have the
# test user
diff --git a/tests/lib/reset.sh b/tests/lib/reset.sh
index 2916d216bf8..f1a3a97db3c 100755
--- a/tests/lib/reset.sh
+++ b/tests/lib/reset.sh
@@ -50,6 +50,7 @@ reset_classic() {
ls -lR "$SNAP_MOUNT_DIR"/ /var/snap/
exit 1
fi
+ rm -rf /tmp/snap.*
case "$SPREAD_SYSTEM" in
fedora-*|centos-*)
@@ -160,6 +161,7 @@ reset_all_snap() {
systemctl stop snapd.service snapd.socket
restore_snapd_state
rm -rf /root/.snap
+ rm -rf /tmp/snap.*
if [ "$1" != "--keep-stopped" ]; then
systemctl start snapd.service snapd.socket
fi
diff --git a/tests/lib/user.sh b/tests/lib/user.sh
index 3d84c5c356c..95337f135a4 100644
--- a/tests/lib/user.sh
+++ b/tests/lib/user.sh
@@ -60,3 +60,9 @@ as_user() {
as_user_simple() {
su -l -c "$*" "$TEST_USER"
}
+
+as_given_user() {
+ local user="$1"
+ shift
+ su -l -c "$*" "$user"
+}
diff --git a/tests/main/install-store/task.yaml b/tests/main/install-store/task.yaml
index c99c36b14de..20f0a3e21b7 100644
--- a/tests/main/install-store/task.yaml
+++ b/tests/main/install-store/task.yaml
@@ -13,7 +13,7 @@ environment:
execute: |
echo "Install from different channels"
- expected="(?s)$SNAP_NAME .* from Canonical\\* installed\\n"
+ expected="(?s)$SNAP_NAME .* from Test snaps \(test-snaps-canonical\) installed\\n"
for channel in edge beta candidate stable
do
snap install --unicode=never "$SNAP_NAME" --channel=$channel | grep -Pzq "$expected"
@@ -21,7 +21,7 @@ execute: |
done
echo "Install non-devmode snap with devmode option"
- expected="(?s)$SNAP_NAME .* from Canonical\\* installed\\n"
+ expected="(?s)$SNAP_NAME .* from Test snaps \(test-snaps-canonical\) installed\\n"
snap install --unicode=never "$SNAP_NAME" --devmode | grep -Pzq "$expected"
echo "Install devmode snap without devmode option"
diff --git a/tests/main/interfaces-packagekit-control/task.yaml b/tests/main/interfaces-packagekit-control/task.yaml
index bd7c129a5f9..24de9fefed4 100644
--- a/tests/main/interfaces-packagekit-control/task.yaml
+++ b/tests/main/interfaces-packagekit-control/task.yaml
@@ -5,6 +5,8 @@ systems:
- -ubuntu-core-*
# Ubuntu 14.04 PackageKit seems to be too old
- -ubuntu-14.04-*
+ # PackageKit is buggy https://bugzilla.redhat.com/show_bug.cgi?id=1807864
+ - -centos-8-*
execute: |
echo "Installing test-snapd-packagekit"
diff --git a/tests/main/local-install-w-metadata/task.yaml b/tests/main/local-install-w-metadata/task.yaml
index 6a454cd7ba4..fbc07730226 100644
--- a/tests/main/local-install-w-metadata/task.yaml
+++ b/tests/main/local-install-w-metadata/task.yaml
@@ -17,7 +17,7 @@ execute: |
snap install test-snapd-sh_*.snap
echo "The revision is not a local revision"
- snap list|MATCH '^test-snapd-sh.* [0-9]+\s+-\s+cachio'
+ snap list|MATCH '^test-snapd-sh.* [0-9]+\s+-\s+test-snaps-canonical'
echo "Test it"
test-snapd-sh.sh -c 'true'
diff --git a/tests/main/preseed-snapd-snap/seed.yaml.in b/tests/main/preseed-snapd-snap/seed.yaml.in
new file mode 100644
index 00000000000..353e8a05395
--- /dev/null
+++ b/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
diff --git a/tests/main/preseed-snapd-snap/task.yaml b/tests/main/preseed-snapd-snap/task.yaml
new file mode 100644
index 00000000000..52d74195e3f
--- /dev/null
+++ b/tests/main/preseed-snapd-snap/task.yaml
@@ -0,0 +1,110 @@
+summary: |
+ Check that preseeding of current ubuntu cloud image works with snapd
+ and core18.
+
+description: |
+ This test checks that preseeding of Ubuntu cloud images with snap-preseed
+ command works, up to the point where the image is ready to be booted.
+ The test assumes cloud image with a core and lxd snaps in its seeds/.
+
+systems: [ubuntu-19.10-*, ubuntu-20.04-*]
+
+environment:
+ IMAGE_MOUNTPOINT: /mnt/cloudimg
+
+prepare: |
+ # the get_image_url_for_nested_vm is a convenient helper that returns
+ # a cloud image url matching current $SPREAD_SYSTEM.
+ #shellcheck source=tests/lib/nested.sh
+ . "$TESTSLIB/nested.sh"
+ wget "$(get_image_url_for_nested_vm)" -O cloudimg.img
+ mkdir -p "$IMAGE_MOUNTPOINT"
+
+restore: |
+ #shellcheck source=tests/lib/preseed.sh
+ . "$TESTSLIB/preseed.sh"
+
+ # any of the restore commands can fail depending on where execute part stopped,
+ # account for that with ||true.
+ umount_ubuntu_image "$IMAGE_MOUNTPOINT" || true
+
+execute: |
+ setup_preseeding_core18() {
+ local IMAGE_MOUNTPOINT=$1
+ local SEEDS_DIR
+ SEEDS_DIR="$IMAGE_MOUNTPOINT/var/lib/snapd/seed/"
+ rm -f "$SEEDS_DIR"/snaps/*.snap
+ rm -f "$SEEDS_DIR"/seed.yaml
+
+ snap download --edge snapd
+ snap download --edge core18
+
+ CORE=$(ls core18*.snap)
+ SNAPD=$(ls snapd*.snap)
+ sed seed.yaml.in -E -e "s/@CORE@/$CORE/" -e "s/@SNAPD@/$SNAPD/" > "$SEEDS_DIR"/seed.yaml
+
+ cp "$CORE" "$SNAPD" "$SEEDS_DIR"/snaps/
+ }
+
+ #shellcheck source=tests/lib/preseed.sh
+ . "$TESTSLIB/preseed.sh"
+ mount_ubuntu_image cloudimg.img "$IMAGE_MOUNTPOINT"
+ setup_preseeding_core18 "$IMAGE_MOUNTPOINT"
+
+ echo "Running pre-seeeding"
+ /usr/lib/snapd/snap-preseed "$IMAGE_MOUNTPOINT"
+
+ # sanity, core snap mounted by snap-preseed got unmounted
+ mount | not MATCH "snap-preseed"
+
+ snap debug state "$IMAGE_MOUNTPOINT"/var/lib/snapd/state.json --change=1 > tasks.log
+
+ echo "Check that the tasks of preseeded snapd have expected statuses"
+ # Note, these checks match statuses, but not the order
+ MATCH "Done .+ prerequisites +Ensure prerequisites for \"snapd\" are available" < tasks.log
+ MATCH "Done .+ prepare-snap +Prepare snap \"/var/lib/snapd/seed/snaps/snapd_[0-9]+.snap" < tasks.log
+ MATCH "Done .+ mount-snap +Mount snap \"snapd\"" < tasks.log
+ MATCH "Done .+ copy-snap-data +Copy snap \"snapd\" data" < tasks.log
+ MATCH "Done .+ setup-profiles +Setup snap \"snapd\" \(unset\) security profiles" < tasks.log
+ MATCH "Done .+ link-snap +Make snap \"snapd\" \(unset\) available to the system" < tasks.log
+ MATCH "Done .+ auto-connect +Automatically connect eligible plugs and slots of snap \"snapd\"" < tasks.log
+ MATCH "Done .+ set-auto-aliases +Set automatic aliases for snap \"snapd\"" < tasks.log
+ MATCH "Done .+ setup-aliases +Setup snap \"snapd\" aliases" < tasks.log
+ MATCH "Done .+ prerequisites +Ensure prerequisites for \"core18\" are available" < tasks.log
+ MATCH "Done .+ prepare-snap +Prepare snap \"/var/lib/snapd/seed/snaps/core18_[0-9]+.snap\"" < tasks.log
+ MATCH "Done .+ mount-snap +Mount snap \"core18\" \(unset\)" < tasks.log
+ MATCH "Done .+ copy-snap-data +Copy snap \"core18\" data" < tasks.log
+ MATCH "Done .+ setup-profiles +Setup snap \"core18\" \(unset\) security profiles" < tasks.log
+ MATCH "Done .+ link-snap +Make snap \"core18\" \(unset\) available to the system" < tasks.log
+ MATCH "Done .+ auto-connect +Automatically connect eligible plugs and slots of snap \"core18\"" < tasks.log
+ MATCH "Done .+ setup-profiles +Setup snap \"core18\" \(unset\) security profiles" < tasks.log
+ MATCH "Done .+ set-auto-aliases +Set automatic aliases for snap \"core18\"" < tasks.log
+ MATCH "Done .+ setup-aliases +Setup snap \"core18\" aliases" < tasks.log
+
+ echo "Checking that there were no other 'Done' tasks when preseeding"
+ [ "$(grep -c ' Done ' tasks.log)" = "18" ]
+
+ # mark-preseeded task is where snap-preseed stopped, therefore it's in Doing.
+ MATCH "Doing .+ mark-preseeded +Mark system pre-seeded" < tasks.log
+
+ # everything below is pending execution on first boot
+ MATCH "Do .+ run-hook +Run install hook of \"snapd\" snap if present" < tasks.log
+ MATCH "Do .+ start-snap-services +Start snap \"snapd\" \(unset\) services" < tasks.log
+ MATCH "Do .+ run-hook +Run configure hook of \"core\" snap if present" < tasks.log
+ MATCH "Do .+ run-hook +Run install hook of \"core18\" snap if present" < tasks.log
+ MATCH "Do .+ start-snap-services +Start snap \"core18\" \(unset\) services" < tasks.log
+ MATCH "Do .+ run-hook +Run health check of \"core18\" snap" < tasks.log
+ MATCH "Do .+ mark-seeded +Mark system seeded" < tasks.log
+
+ echo "Checking that mount units have been created on the target image"
+ SYSTEMD_UNITS="$IMAGE_MOUNTPOINT"/etc/systemd
+ test -f "$SYSTEMD_UNITS"/system/snap-snapd-*.mount
+ test -f "$SYSTEMD_UNITS"/system/snap-core18-*.mount
+
+ echo "Checking enabled systemd mount units"
+ test -L "$SYSTEMD_UNITS"/system/multi-user.target.wants/snap-snapd-*.mount
+ test -L "$SYSTEMD_UNITS"/system/multi-user.target.wants/snap-core18-*.mount
+
+ #shellcheck source=tests/lib/preseed.sh
+ . "$TESTSLIB/preseed.sh"
+ umount_ubuntu_image "$IMAGE_MOUNTPOINT"
diff --git a/tests/main/remove-user/task.yaml b/tests/main/remove-user/task.yaml
index 58b89feebe4..d1739a2335c 100644
--- a/tests/main/remove-user/task.yaml
+++ b/tests/main/remove-user/task.yaml
@@ -9,18 +9,32 @@ environment:
USER_NAME: mvo
prepare: |
+ # Note: make this test work with the user already created in the device
+ if [ "$(snap managed)" = "true" ]; then
+ # Leave a file indicating the device was initially managed
+ touch managed.device
+
+ exit 0
+ fi
snap create-user --sudoer "$USER_EMAIL"
restore: |
+ if [ -e managed.device ]; then
+ exit 0
+ fi
userdel --extrausers -r "$USER_NAME" || true
rm -rf "/etc/sudoers.d/create-user-$USER_NAME"
execute: |
+ if [ -e managed.device ]; then
+ exit 0
+ fi
+
echo "sanity check: user in passwd"
id "$USER_NAME"
echo "sanity check: has sudoer file"
test -f "/etc/sudoers.d/create-user-$USER_NAME"
- echo "sanity check: user has a home"
+ echo "sanity check: user has a home"
test -d "/home/$USER_NAME"
echo "snap remove-user fails when run as non-root user without sudo"
diff --git a/tests/main/retry-network/task.yaml b/tests/main/retry-network/task.yaml
index 806c3f939a0..7490c5969d4 100644
--- a/tests/main/retry-network/task.yaml
+++ b/tests/main/retry-network/task.yaml
@@ -33,11 +33,13 @@ execute: |
# be paranoid and look at the low-level go error as well
MATCH "(Temporary failure in name resolution|network is unreachable)" < stderr
- echo "Now without DNS resolver"
- UBUNTU_IP="$(getent hosts www.ubuntu.com|awk '{print $1 }' | head -n1)"
+ echo "Now without DNS resolver with ipv4"
+ UBUNTU_IP="$(getent ahostsv4 www.ubuntu.com|awk '{print $1 }' | head -n1)"
SNAPD_DEBUG=1 nsenter --net=/var/run/netns/testns ./detect-retry "http://$UBUNTU_IP" > output.txt 2>stderr
# XXX: ShouldRetryError is slightly misbehaving, see comment above
MATCH "ShouldRetryError: true" < output.txt
MATCH "NoNetwork: true" < output.txt
# be paranoid and look at the low-level go error as well
MATCH "network is unreachable" < stderr
+
+ # XXX: also test with ipv6
diff --git a/tests/main/security-private-tmp/task.yaml b/tests/main/security-private-tmp/task.yaml
index 2e90679b414..e0d5bd4982a 100644
--- a/tests/main/security-private-tmp/task.yaml
+++ b/tests/main/security-private-tmp/task.yaml
@@ -20,7 +20,7 @@ prepare: |
snap install --dangerous not-test-snapd-sh_1.0_all.snap
restore: |
- rm -rf "$SNAP_INSTALL_DIR" /tmp/foo
+ rm -rf "$SNAP_INSTALL_DIR" /tmp/foo /tmp/snap.not-test-snapd-sh
execute: |
#shellcheck source=tests/lib/dirs.sh
diff --git a/tests/main/selinux-lxd/task.yaml b/tests/main/selinux-lxd/task.yaml
index 51f6e324853..6751774c502 100644
--- a/tests/main/selinux-lxd/task.yaml
+++ b/tests/main/selinux-lxd/task.yaml
@@ -29,6 +29,14 @@ restore: |
lxd-tool undo-lxd-mount-changes
+ rm -f avc-after
+
+debug: |
+ if [ -e avc-after ]; then
+ echo "AVC log from the test"
+ cat avc-after
+ fi
+
execute: |
snap install lxd
ausearch -i --checkpoint stamp --start checkpoint -m AVC 2>&1 | MATCH 'no matches'
@@ -65,6 +73,9 @@ execute: |
# there is a known problem with the reference policy that disallows systemd
# from creating a BPF map for unconfined_service_t, see:
# https://bugzilla.redhat.com/show_bug.cgi?id=1694115
- ausearch --checkpoint stamp --start checkpoint -m AVC 2>&1 | \
- grep -v -E 'avc: denied { map_create } for pid=[0-9]+ comm="systemd"' | \
+ #
+ # ausearch exits with 1 when there are no denials, which we expect on
+ # centos-8, but not on centos-7
+ ausearch --checkpoint stamp --start checkpoint -m AVC > avc-after 2>&1 || true
+ grep -v -E 'avc: denied { map_create } for pid=[0-9]+ comm="systemd"' < avc-after | \
not MATCH 'type=AVC'
diff --git a/tests/main/snap-confine-undesired-mode-group/task.yaml b/tests/main/snap-confine-undesired-mode-group/task.yaml
index a71047834df..c3548bdf7f5 100644
--- a/tests/main/snap-confine-undesired-mode-group/task.yaml
+++ b/tests/main/snap-confine-undesired-mode-group/task.yaml
@@ -7,8 +7,6 @@ prepare: |
snap install --dangerous ./test-snapd-app_1.0_all.snap
snap connect test-snapd-app:opengl
snap connect test-snapd-app:joystick
- # Remove any stale temp file left over by other runs.
- rm -rf /tmp/snap.test-snapd-app
execute: |
# Run the snap as a non-root user.
su test -c 'snap run test-snapd-app.sh -c /bin/true'
diff --git a/tests/main/user-session-env/task.yaml b/tests/main/user-session-env/task.yaml
new file mode 100644
index 00000000000..2692e0a8245
--- /dev/null
+++ b/tests/main/user-session-env/task.yaml
@@ -0,0 +1,84 @@
+summary: verify that user environment settings are added
+
+description: |
+ User environment variables are added via /etc/profile.d/snapd.sh (bash/sh
+ specific) or via /lib/environment.d/ helpers. Make sure that at least one of
+ the mechanisms works and XDG_DATA_DIRS and PATH are updated accordingly
+ inside the user session, no matter the shell they use.
+
+systems:
+ # cannot install zsh
+ - -ubuntu-core-*
+
+environment:
+ TEST_ZSH_USER: test-zsh
+
+prepare: |
+ #shellcheck source=tests/lib/user.sh
+ . "$TESTSLIB/user.sh"
+
+ echo "create a user with a different shell"
+ useradd --create-home --user-group -s /bin/zsh "$TEST_ZSH_USER"
+
+ if ! has_user_session_support; then
+ echo "NOTE: no user session support, skipping session setup"
+ exit 0
+ fi
+
+ touch ./has-sessions
+
+ # setup for systems supporting user sessions
+ for uid in $(id -u test) $(id -u "$TEST_ZSH_USER"); do
+ mkdir -p "/run/user/$uid"
+ chown -R "$uid:$uid" "/run/user/$uid"
+ systemctl start "user@$uid.service"
+ done
+
+restore: |
+ userdel -f -r "$TEST_ZSH_USER"
+
+ rm -f test-profile-env test-profile-zsh-env test-session-env test-zsh-session-env
+
+ if [ -e has-sessions ]; then
+ for uid in $(id -u test) $(id -u "$TEST_ZSH_USER"); do
+ systemctl stop "user@$uid.service"
+ done
+ fi
+ rm -f ./has-sessions
+
+execute: |
+ #shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB/dirs.sh"
+ #shellcheck source=tests/lib/user.sh
+ . "$TESTSLIB/user.sh"
+
+ for user in test "$TEST_ZSH_USER"; do
+ if [ -e ./has-sessions ]; then
+ uid=$(id -u "$user")
+ # dump the environment set up by the user session manager
+ as_given_user "$user" "XDG_RUNTIME_DIR=/run/user/$uid systemctl --user show-environment" > "${user}-session-env"
+ fi
+ as_given_user "$user" "env" > "${user}-profile-env"
+ done
+
+ for user in test "$TEST_ZSH_USER"; do
+ # even though there's user session support, systemd may be too old and
+ # not support user-environment-generators (specifically systemd versions
+ # earlier than 233)
+ if [ -e ./has-sessions ] && [ -d /usr/lib/systemd/user-environment-generators ]; then
+ MATCH 'XDG_DATA_DIRS=.*[:]?/var/lib/snapd/desktop[:]?.*' < "${user}-session-env"
+ MATCH "PATH=.*[:]?${SNAP_MOUNT_DIR}/bin[:]?.*" < "${user}-session-env"
+ fi
+ # profile should also be correctly set up
+ case "$user:$SPREAD_SYSTEM" in
+ test-zsh:ubuntu-*|test-zsh:debian-*)
+ # due to https://bugs.launchpad.net/ubuntu/+source/zsh/+bug/1640514
+ not MATCH 'XDG_DATA_DIRS=.*[:]?/var/lib/snapd/desktop[:]?.*' < "${user}-profile-env"
+ not MATCH "PATH=.*[:]?${SNAP_MOUNT_DIR}/bin[:]?.*" < "${user}-profile-env"
+ ;;
+ *)
+ MATCH 'XDG_DATA_DIRS=.*[:]?/var/lib/snapd/desktop[:]?.*' < "${user}-profile-env"
+ MATCH "PATH=.*[:]?${SNAP_MOUNT_DIR}/bin[:]?.*" < "${user}-profile-env"
+ ;;
+ esac
+ done
diff --git a/tests/unit/c-unit-tests-clang/task.yaml b/tests/unit/c-unit-tests-clang/task.yaml
index 6b6f306531f..ca51ff1ffa0 100644
--- a/tests/unit/c-unit-tests-clang/task.yaml
+++ b/tests/unit/c-unit-tests-clang/task.yaml
@@ -27,5 +27,7 @@ execute: |
build_dir="$SPREAD_PATH/cmd/autogarbage"
BUILD_DIR=$build_dir CC=clang ./autogen.sh
cd "$build_dir"
- # Build and run unit tests
+ # Build and run unit tests as root and as a user
make check
+ chown -R test.test "$build_dir"
+ su test -c 'make check'
diff --git a/tests/unit/c-unit-tests-gcc/task.yaml b/tests/unit/c-unit-tests-gcc/task.yaml
index 3e5f0758cce..6534c7f07ac 100644
--- a/tests/unit/c-unit-tests-gcc/task.yaml
+++ b/tests/unit/c-unit-tests-gcc/task.yaml
@@ -27,5 +27,7 @@ execute: |
build_dir="$SPREAD_PATH/cmd/autogarbage"
BUILD_DIR=$build_dir ./autogen.sh
cd "$build_dir"
- # Build and run unit tests
+ # Build and run unit tests as root and as a user
make check
+ chown -R test.test "$build_dir"
+ su test -c 'make check'
diff --git a/vendor/vendor.json b/vendor/vendor.json
index 62ec5dfbcf5..1e1a5b02b44 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -30,6 +30,12 @@
"revision": "d83b6ffe499a29cc05fc977988d0392851779620",
"revisionTime": "2019-07-01T20:26:33Z"
},
+ {
+ "checksumSHA1": "Q2fk2rjypvQOYxSMD59/h9Et5OM=",
+ "path": "github.com/gvalkov/golang-evdev",
+ "revision": "287e62b94bcb850ab42e711bd74b2875da83af2c",
+ "revisionTime": "2019-11-14T12:45:02Z"
+ },
{
"checksumSHA1": "GcYF2BhpiQkshVVXxHPS/Oq491c=",
"path": "github.com/jessevdk/go-flags",