summaryrefslogtreecommitdiff
diff options
authorMaciej Borzecki <maciej.zenon.borzecki@canonical.com>2021-07-07 08:42:32 +0200
committerMaciej Borzecki <maciej.zenon.borzecki@canonical.com>2021-07-07 08:42:32 +0200
commitd84e631699cb0e1ae15490a0ac1e039d04ca6be7 (patch)
tree9f9a2af662ff583401782cf6089e58c519707dc7
parentb590a78fa7ec689973422142ecb9e422e63237f1 (diff)
parent1d23bf1e1464feb1f0f6664f89ea6fba81be68de (diff)
Merge remote-tracking branch 'upstream/master' into bboozzoo/pick-alternative-label-for-remodel
-rw-r--r--.github/workflows/test.yaml1
-rw-r--r--asserts/extkeypairmgr.go28
-rw-r--r--asserts/extkeypairmgr_test.go27
-rw-r--r--asserts/gpgkeypairmgr.go17
-rw-r--r--asserts/gpgkeypairmgr_test.go12
-rw-r--r--cmd/libsnap-confine-private/cgroup-support-test.c15
-rw-r--r--cmd/libsnap-confine-private/cgroup-support.c29
-rw-r--r--cmd/snap/cmd_export_key.go11
-rw-r--r--cmd/snap/cmd_keys.go25
-rw-r--r--cmd/snap/cmd_sign.go8
-rw-r--r--cmd/snap/cmd_sign_build.go11
-rw-r--r--cmd/snap/complete.go20
-rw-r--r--cmd/snap/export_test.go2
-rw-r--r--cmd/snap/keymgr.go49
-rw-r--r--cmd/snap/keymgr_test.go72
-rw-r--r--interfaces/apparmor/template.go4
-rw-r--r--sandbox/cgroup/export_test.go2
-rw-r--r--sandbox/cgroup/freezer.go71
-rw-r--r--sandbox/cgroup/freezer_test.go94
-rw-r--r--spread.yaml2
-rwxr-xr-xtests/lib/pkgdb.sh2
-rw-r--r--tests/lib/snaps/store/test-snapd-refresh-control-provider.v1/build-aux/snap/snapcraft.yaml24
-rw-r--r--tests/lib/snaps/store/test-snapd-refresh-control-provider.v2/build-aux/snap/snapcraft.yaml24
-rw-r--r--tests/lib/snaps/store/test-snapd-refresh-control.v1/build-aux/snap/hooks/gate-auto-refresh10
-rw-r--r--tests/lib/snaps/store/test-snapd-refresh-control.v1/build-aux/snap/snapcraft.yaml23
-rw-r--r--tests/lib/snaps/store/test-snapd-refresh-control.v2/build-aux/snap/hooks/gate-auto-refresh10
-rw-r--r--tests/lib/snaps/store/test-snapd-refresh-control.v2/build-aux/snap/snapcraft.yaml23
-rw-r--r--tests/main/auto-refresh-gating/task.yaml167
-rw-r--r--tests/main/desktop-portal-filechooser/task.yaml2
-rw-r--r--tests/main/desktop-portal-open-file/task.yaml2
-rw-r--r--tests/main/desktop-portal-open-uri/task.yaml2
-rw-r--r--tests/main/desktop-portal-screenshot/task.yaml2
-rw-r--r--tests/main/preseed-lxd/task.yaml4
33 files changed, 726 insertions, 69 deletions
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index a9c4987c92..55fb2c8f26 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -240,6 +240,7 @@ jobs:
- ubuntu-14.04-64
- ubuntu-16.04-32
- ubuntu-16.04-64
+ - ubuntu-18.04-32
- ubuntu-18.04-64
- ubuntu-20.04-64
- ubuntu-20.10-64
diff --git a/asserts/extkeypairmgr.go b/asserts/extkeypairmgr.go
index b7e345b51b..0a945a8f8d 100644
--- a/asserts/extkeypairmgr.go
+++ b/asserts/extkeypairmgr.go
@@ -34,6 +34,11 @@ import (
"github.com/snapcore/snapd/strutil"
)
+type ExternalKeyInfo struct {
+ Name string
+ ID string
+}
+
// ExternalKeypairManager is key pair manager implemented via an external program interface.
// TODO: points to interface docs
type ExternalKeypairManager struct {
@@ -189,24 +194,24 @@ func (em *ExternalKeypairManager) Put(privKey PrivateKey) error {
return fmt.Errorf("cannot import private key into external keypair manager")
}
-func (em *ExternalKeypairManager) loadAllKeys() error {
+func (em *ExternalKeypairManager) loadAllKeys() ([]string, error) {
names, err := em.keyNames()
if err != nil {
- return err
+ return nil, err
}
for _, name := range names {
if _, err := em.loadKey(name); err != nil {
- return err
+ return nil, err
}
}
- return nil
+ return names, nil
}
func (em *ExternalKeypairManager) Get(keyID string) (PrivateKey, error) {
cachedKey, ok := em.cache[keyID]
if !ok {
// try to load all keys
- if err := em.loadAllKeys(); err != nil {
+ if _, err := em.loadAllKeys(); err != nil {
return nil, err
}
cachedKey, ok = em.cache[keyID]
@@ -217,6 +222,19 @@ func (em *ExternalKeypairManager) Get(keyID string) (PrivateKey, error) {
return em.privateKey(cachedKey), nil
}
+func (em *ExternalKeypairManager) List() ([]ExternalKeyInfo, error) {
+ names, err := em.loadAllKeys()
+ if err != nil {
+ return nil, err
+ }
+ res := make([]ExternalKeyInfo, len(names))
+ for i, name := range names {
+ res[i].Name = name
+ res[i].ID = em.cache[em.nameToID[name]].pubKey.ID()
+ }
+ return res, nil
+}
+
// see https://datatracker.ietf.org/doc/html/rfc2313 and more recently
// and more precisely about SHA-512:
// https://datatracker.ietf.org/doc/html/rfc3447#section-9.2 Notes 1.
diff --git a/asserts/extkeypairmgr_test.go b/asserts/extkeypairmgr_test.go
index d2f2206903..c33180fa2a 100644
--- a/asserts/extkeypairmgr_test.go
+++ b/asserts/extkeypairmgr_test.go
@@ -272,3 +272,30 @@ func (s *extKeypairMgrSuite) TestExport(c *C) {
c.Check(exported, DeepEquals, expected)
}
}
+
+func (s *extKeypairMgrSuite) TestList(c *C) {
+ kmgr, err := asserts.NewExternalKeypairManager("keymgr")
+ c.Assert(err, IsNil)
+
+ keys, err := kmgr.List()
+ c.Assert(err, IsNil)
+
+ defaultID := asserts.RSAPublicKey(s.defaultPub).ID()
+ modelsID := asserts.RSAPublicKey(s.modelsPub).ID()
+
+ c.Check(keys, DeepEquals, []asserts.ExternalKeyInfo{
+ {Name: "default", ID: defaultID},
+ {Name: "models", ID: modelsID},
+ })
+}
+
+func (s *extKeypairMgrSuite) TestListError(c *C) {
+ kmgr, err := asserts.NewExternalKeypairManager("keymgr")
+ c.Assert(err, IsNil)
+
+ pgm := testutil.MockCommand(c, "keymgr", `exit 1`)
+ defer pgm.Restore()
+
+ _, err = kmgr.List()
+ c.Check(err, ErrorMatches, `cannot get all external keypair manager key names:.*exit status 1.*`)
+}
diff --git a/asserts/gpgkeypairmgr.go b/asserts/gpgkeypairmgr.go
index 2151a87c81..c6d0cafd8f 100644
--- a/asserts/gpgkeypairmgr.go
+++ b/asserts/gpgkeypairmgr.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2016 Canonical Ltd
+ * Copyright (C) 2016-2021 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
@@ -365,3 +365,18 @@ func (gkm *GPGKeypairManager) Delete(name string) error {
}
return nil
}
+
+func (gkm *GPGKeypairManager) List() (res []ExternalKeyInfo, err error) {
+ collect := func(privk PrivateKey, fpr string, uid string) error {
+ key := ExternalKeyInfo{
+ Name: uid,
+ ID: privk.PublicKey().ID(),
+ }
+ res = append(res, key)
+ return nil
+ }
+ if err := gkm.Walk(collect); err != nil {
+ return nil, err
+ }
+ return res, nil
+}
diff --git a/asserts/gpgkeypairmgr_test.go b/asserts/gpgkeypairmgr_test.go
index 1991b80aae..6885214bc3 100644
--- a/asserts/gpgkeypairmgr_test.go
+++ b/asserts/gpgkeypairmgr_test.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2016 Canonical Ltd
+ * Copyright (C) 2016-2021 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
@@ -328,3 +328,13 @@ Preferences: SHA512
c.Check(parameters, Equals, baseParameters+test.extraParameters)
}
}
+
+func (gkms *gpgKeypairMgrSuite) TestList(c *C) {
+ gpgKeypairMgr := gkms.keypairMgr.(*asserts.GPGKeypairManager)
+
+ keys, err := gpgKeypairMgr.List()
+ c.Assert(err, IsNil)
+ c.Check(keys, HasLen, 1)
+ c.Check(keys[0].ID, Equals, assertstest.DevKeyID)
+ c.Check(keys[0].Name, Not(Equals), "")
+}
diff --git a/cmd/libsnap-confine-private/cgroup-support-test.c b/cmd/libsnap-confine-private/cgroup-support-test.c
index 9a3f4b8b11..779c364af0 100644
--- a/cmd/libsnap-confine-private/cgroup-support-test.c
+++ b/cmd/libsnap-confine-private/cgroup-support-test.c
@@ -236,6 +236,19 @@ static void test_sc_cgroupv2_is_tracking_dir_permissions(cgroupv2_is_tracking_fi
g_test_trap_assert_stderr("cannot open directory entry \"badperm\": Permission denied\n");
}
+static void test_sc_cgroupv2_is_tracking_no_cgroup_root(cgroupv2_is_tracking_fixture *fixture,
+ gconstpointer user_data) {
+ GError *err = NULL;
+ g_file_set_contents(fixture->self_cgroup, "0::/foo/bar/baz/snap.foo.app.1234-1234.scope", -1, &err);
+ g_assert_no_error(err);
+
+ sc_set_cgroup_root("/does/not/exist");
+
+ // does not die when cgroup root is not present
+ bool is_tracking = sc_cgroup_v2_is_tracking_snap("foo");
+ g_assert_false(is_tracking);
+}
+
static void sc_set_self_cgroup_path(const char *mock) { self_cgroup = mock; }
typedef struct _cgroupv2_own_group_fixture {
@@ -370,4 +383,6 @@ static void __attribute__((constructor)) init(void) {
cgroupv2_is_tracking_tear_down);
g_test_add("/cgroup/v2/is_tracking_bad_nesting", cgroupv2_is_tracking_fixture, NULL, cgroupv2_is_tracking_set_up,
test_sc_cgroupv2_is_tracking_bad_nesting, cgroupv2_is_tracking_tear_down);
+ g_test_add("/cgroup/v2/is_tracking_no_cgroup_root", cgroupv2_is_tracking_fixture, NULL, cgroupv2_is_tracking_set_up,
+ test_sc_cgroupv2_is_tracking_no_cgroup_root, cgroupv2_is_tracking_tear_down);
}
diff --git a/cmd/libsnap-confine-private/cgroup-support.c b/cmd/libsnap-confine-private/cgroup-support.c
index cf2dcaba18..1ef4f789d0 100644
--- a/cmd/libsnap-confine-private/cgroup-support.c
+++ b/cmd/libsnap-confine-private/cgroup-support.c
@@ -109,7 +109,14 @@ static bool traverse_looking_for_prefix_in_dir(DIR *root, const char *prefix, co
errno = 0;
struct dirent *ent = readdir(root);
if (ent == NULL) {
+ // is this an error?
if (errno != 0) {
+ if (errno == ENOENT) {
+ // the processes may exit and the group entries may go away at
+ // any time
+ // the entries may go away at any time
+ break;
+ }
die("cannot read directory entry");
}
break;
@@ -133,11 +140,17 @@ static bool traverse_looking_for_prefix_in_dir(DIR *root, const char *prefix, co
// entfd is consumed by fdopendir() and freed with closedir()
int entfd = openat(dirfd(root), ent->d_name, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
if (entfd == -1) {
+ if (errno == ENOENT) {
+ // the processes may exit and the group entries may go away at
+ // any time
+ return false;
+ }
die("cannot open directory entry \"%s\"", ent->d_name);
}
// takes ownership of the file descriptor
DIR *entdir SC_CLEANUP(sc_cleanup_closedir) = fdopendir(entfd);
if (entdir == NULL) {
+ // we have the fd, so ENOENT isn't possible here
die("cannot fdopendir directory \"%s\"", ent->d_name);
}
bool found = traverse_looking_for_prefix_in_dir(entdir, prefix, skip, depth + 1);
@@ -181,6 +194,9 @@ bool sc_cgroup_v2_is_tracking_snap(const char *snap_instance) {
debug("opening cgroup root dir at %s", cgroup_dir);
DIR *root SC_CLEANUP(sc_cleanup_closedir) = opendir(cgroup_dir);
if (root == NULL) {
+ if (errno == ENOENT) {
+ return false;
+ }
die("cannot open cgroup root dir");
}
// traverse the cgroup hierarchy tree looking for other groups that
@@ -203,13 +219,12 @@ char *sc_cgroup_v2_own_path_full(void) {
char *line SC_CLEANUP(sc_cleanup_string) = NULL;
size_t linesz = 0;
ssize_t sz = getline(&line, &linesz, in);
- if (sz == -1) {
- if (feof(in)) {
- break;
- }
- if (ferror(in)) {
- die("cannot read line from %s", self_cgroup);
- }
+ if (sz < 0 && errno != 0) {
+ die("cannot read line from %s", self_cgroup);
+ }
+ if (sz < 0) {
+ // end of file
+ break;
}
if (!sc_startswith(line, "0::")) {
continue;
diff --git a/cmd/snap/cmd_export_key.go b/cmd/snap/cmd_export_key.go
index 94aa702945..0149b13a8b 100644
--- a/cmd/snap/cmd_export_key.go
+++ b/cmd/snap/cmd_export_key.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2016 Canonical Ltd
+ * Copyright (C) 2021 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
@@ -66,9 +66,12 @@ func (x *cmdExportKey) Execute(args []string) error {
keyName = "default"
}
- manager := asserts.NewGPGKeypairManager()
+ keypairMgr, err := getKeypairManager()
+ if err != nil {
+ return err
+ }
if x.Account != "" {
- privKey, err := manager.GetByName(keyName)
+ privKey, err := keypairMgr.GetByName(keyName)
if err != nil {
return err
}
@@ -90,7 +93,7 @@ func (x *cmdExportKey) Execute(args []string) error {
}
fmt.Fprint(Stdout, string(asserts.Encode(assertion)))
} else {
- encoded, err := manager.Export(keyName)
+ encoded, err := keypairMgr.Export(keyName)
if err != nil {
return err
}
diff --git a/cmd/snap/cmd_keys.go b/cmd/snap/cmd_keys.go
index 9b22e3356c..426ff654ee 100644
--- a/cmd/snap/cmd_keys.go
+++ b/cmd/snap/cmd_keys.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2016 Canonical Ltd
+ * Copyright (C) 2016-2021 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
@@ -25,7 +25,6 @@ import (
"github.com/jessevdk/go-flags"
- "github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/i18n"
)
@@ -86,21 +85,21 @@ func (x *cmdKeys) Execute(args []string) error {
return ErrExtraArgs
}
- keys := []Key{}
-
- manager := asserts.NewGPGKeypairManager()
- collect := func(privk asserts.PrivateKey, fpr string, uid string) error {
- key := Key{
- Name: uid,
- Sha3_384: privk.PublicKey().ID(),
- }
- keys = append(keys, key)
- return nil
+ keypairMgr, err := getKeypairManager()
+ if err != nil {
+ return err
}
- err := manager.Walk(collect)
+
+ kinfos, err := keypairMgr.List()
if err != nil {
return err
}
+ keys := make([]Key, len(kinfos))
+ for i, kinfo := range kinfos {
+ keys[i].Name = kinfo.Name
+ keys[i].Sha3_384 = kinfo.ID
+ }
+
if x.JSON {
return outputJSON(keys)
}
diff --git a/cmd/snap/cmd_sign.go b/cmd/snap/cmd_sign.go
index 5f4b407a12..bd001f32b4 100644
--- a/cmd/snap/cmd_sign.go
+++ b/cmd/snap/cmd_sign.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2014-2015 Canonical Ltd
+ * Copyright (C) 2014-2021 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
@@ -25,7 +25,6 @@ import (
"github.com/jessevdk/go-flags"
- "github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/asserts/signtool"
"github.com/snapcore/snapd/i18n"
)
@@ -81,7 +80,10 @@ func (x *cmdSign) Execute(args []string) error {
return fmt.Errorf(i18n.G("cannot read assertion input: %v"), err)
}
- keypairMgr := asserts.NewGPGKeypairManager()
+ keypairMgr, err := getKeypairManager()
+ if err != nil {
+ return err
+ }
privKey, err := keypairMgr.GetByName(string(x.KeyName))
if err != nil {
// TRANSLATORS: %q is the key name, %v the error message
diff --git a/cmd/snap/cmd_sign_build.go b/cmd/snap/cmd_sign_build.go
index 9cfcf0e01d..5780403512 100644
--- a/cmd/snap/cmd_sign_build.go
+++ b/cmd/snap/cmd_sign_build.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2014-2015 Canonical Ltd
+ * Copyright (C) 2014-2021 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
@@ -84,8 +84,11 @@ func (x *cmdSignBuild) Execute(args []string) error {
return err
}
- gkm := asserts.NewGPGKeypairManager()
- privKey, err := gkm.GetByName(string(x.KeyName))
+ keypairMgr, err := getKeypairManager()
+ if err != nil {
+ return err
+ }
+ privKey, err := keypairMgr.GetByName(string(x.KeyName))
if err != nil {
// TRANSLATORS: %q is the key name, %v the error message
return fmt.Errorf(i18n.G("cannot use %q key: %v"), x.KeyName, err)
@@ -105,7 +108,7 @@ func (x *cmdSignBuild) Execute(args []string) error {
}
adb, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
- KeypairManager: gkm,
+ KeypairManager: keypairMgr,
})
if err != nil {
return fmt.Errorf(i18n.G("cannot open the assertions database: %v"), err)
diff --git a/cmd/snap/complete.go b/cmd/snap/complete.go
index b1c7dc8840..617d72bed2 100644
--- a/cmd/snap/complete.go
+++ b/cmd/snap/complete.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2016 Canonical Ltd
+ * Copyright (C) 2016-2021 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
@@ -28,7 +28,6 @@ import (
"github.com/jessevdk/go-flags"
- "github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/i18n"
@@ -185,13 +184,20 @@ func (n assertTypeName) Complete(match string) []flags.Completion {
type keyName string
func (s keyName) Complete(match string) []flags.Completion {
+ keypairManager, err := getKeypairManager()
+ if err != nil {
+ return nil
+ }
+ keys, err := keypairManager.List()
+ if err != nil {
+ return nil
+ }
var res []flags.Completion
- asserts.NewGPGKeypairManager().Walk(func(_ asserts.PrivateKey, _ string, uid string) error {
- if strings.HasPrefix(uid, match) {
- res = append(res, flags.Completion{Item: uid})
+ for _, k := range keys {
+ if strings.HasPrefix(k.Name, match) {
+ res = append(res, flags.Completion{Item: k.Name})
}
- return nil
- })
+ }
return res
}
diff --git a/cmd/snap/export_test.go b/cmd/snap/export_test.go
index 891610d08b..6dbc430e91 100644
--- a/cmd/snap/export_test.go
+++ b/cmd/snap/export_test.go
@@ -91,6 +91,8 @@ var (
PrintInstallHint = printInstallHint
IsStopping = isStopping
+
+ GetKeypairManager = getKeypairManager
)
func HiddenCmd(descr string, completeHidden bool) *cmdInfo {
diff --git a/cmd/snap/keymgr.go b/cmd/snap/keymgr.go
new file mode 100644
index 0000000000..6a3c613a02
--- /dev/null
+++ b/cmd/snap/keymgr.go
@@ -0,0 +1,49 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2021 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/i18n"
+)
+
+type KeypairManager interface {
+ asserts.KeypairManager
+
+ GetByName(keyNname string) (asserts.PrivateKey, error)
+ Export(keyName string) ([]byte, error)
+ List() ([]asserts.ExternalKeyInfo, error)
+}
+
+func getKeypairManager() (KeypairManager, error) {
+ keymgrPath := os.Getenv("SNAPD_EXT_KEYMGR")
+ if keymgrPath != "" {
+ keypairMgr, err := asserts.NewExternalKeypairManager(keymgrPath)
+ if err != nil {
+ return nil, fmt.Errorf(i18n.G("cannot setup external keypair manager: %v"), err)
+ }
+ return keypairMgr, nil
+ }
+ keypairMgr := asserts.NewGPGKeypairManager()
+ return keypairMgr, nil
+}
diff --git a/cmd/snap/keymgr_test.go b/cmd/snap/keymgr_test.go
new file mode 100644
index 0000000000..5b4759e1a3
--- /dev/null
+++ b/cmd/snap/keymgr_test.go
@@ -0,0 +1,72 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2021 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package main_test
+
+import (
+ "os"
+
+ "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/asserts"
+ snap "github.com/snapcore/snapd/cmd/snap"
+ "github.com/snapcore/snapd/testutil"
+)
+
+type keymgrSuite struct{}
+
+var _ = check.Suite(&keymgrSuite{})
+
+func (keymgrSuite) TestGPGKeypairManager(c *check.C) {
+ keypairMgr, err := snap.GetKeypairManager()
+ c.Check(err, check.IsNil)
+ c.Check(keypairMgr, check.FitsTypeOf, &asserts.GPGKeypairManager{})
+}
+
+func (keymgrSuite) TestExternalKeypairManager(c *check.C) {
+ os.Setenv("SNAPD_EXT_KEYMGR", "keymgr")
+ defer os.Unsetenv("SNAPD_EXT_KEYMGR")
+
+ pgm := testutil.MockCommand(c, "keymgr", `
+if [ "$1" == "features" ]; then
+ echo '{"signing":["RSA-PKCS"] , "public-keys":["DER"]}'
+ exit 0
+fi
+exit 1
+`)
+ defer pgm.Restore()
+
+ keypairMgr, err := snap.GetKeypairManager()
+ c.Check(err, check.IsNil)
+ c.Check(keypairMgr, check.FitsTypeOf, &asserts.ExternalKeypairManager{})
+ c.Check(pgm.Calls(), check.HasLen, 1)
+}
+
+func (keymgrSuite) TestExternalKeypairManagerError(c *check.C) {
+ os.Setenv("SNAPD_EXT_KEYMGR", "keymgr")
+ defer os.Unsetenv("SNAPD_EXT_KEYMGR")
+
+ pgm := testutil.MockCommand(c, "keymgr", `
+exit 1
+`)
+ defer pgm.Restore()
+
+ _, err := snap.GetKeypairManager()
+ c.Check(err, check.ErrorMatches, `cannot setup external keypair manager: external keypair manager "keymgr" \[features\] failed: exit status 1.*`)
+}
diff --git a/interfaces/apparmor/template.go b/interfaces/apparmor/template.go
index 73e3d8ac0a..d262de67ba 100644
--- a/interfaces/apparmor/template.go
+++ b/interfaces/apparmor/template.go
@@ -927,6 +927,10 @@ profile snap-update-ns.###SNAP_INSTANCE_NAME### (attach_disconnected) {
# golang runtime variables
/sys/kernel/mm/transparent_hugepage/hpage_pmd_size r,
+ # glibc 2.27+ may poke this file to find out the number of CPUs
+ # available in the system when creating a new arena for malloc, see
+ # Golang issue 25628
+ /sys/devices/system/cpu/online r,
# Allow reading the command line (snap-update-ns uses it in pre-Go bootstrap code).
@{PROC}/@{pid}/cmdline r,
diff --git a/sandbox/cgroup/export_test.go b/sandbox/cgroup/export_test.go
index b739f15a4c..2d4fcc46dc 100644
--- a/sandbox/cgroup/export_test.go
+++ b/sandbox/cgroup/export_test.go
@@ -34,6 +34,8 @@ var (
ErrDBusSpawnChildExited = errDBusSpawnChildExited
SecurityTagFromCgroupPath = securityTagFromCgroupPath
+
+ ApplyToSnap = applyToSnap
)
func MockFsTypeForPath(mock func(string) (int64, error)) (restore func()) {
diff --git a/sandbox/cgroup/freezer.go b/sandbox/cgroup/freezer.go
index e4ead87d8c..cd3360de07 100644
--- a/sandbox/cgroup/freezer.go
+++ b/sandbox/cgroup/freezer.go
@@ -123,14 +123,25 @@ func thawSnapProcessesImplV1(snapName string) error {
return nil
}
-func applyToSnap(snapName string, action func(groupName string) error, skipErrs bool) error {
+func applyToSnap(snapName string, action func(groupName string) error, skipError func(err error) bool) error {
+ if action == nil {
+ return fmt.Errorf("internal error: action is nil")
+ }
+ if skipError == nil {
+ return fmt.Errorf("internal error: skip error is nil")
+ }
canary := fmt.Sprintf("snap.%s.", snapName)
cgroupRoot := filepath.Join(rootPath, cgroupMountPoint)
if _, dir, _ := osutil.DirExists(cgroupRoot); !dir {
return nil
}
return filepath.Walk(filepath.Join(rootPath, cgroupMountPoint), func(name string, info os.FileInfo, err error) error {
- if err != nil && !skipErrs {
+ if err != nil {
+ if skipError(err) {
+ // we don't know whether it's a file or
+ // directory, so just return nil instead
+ return nil
+ }
return err
}
if !info.IsDir() {
@@ -140,13 +151,29 @@ func applyToSnap(snapName string, action func(groupName string) error, skipErrs
return nil
}
// found a group
- if err := action(name); err != nil && !skipErrs {
+ if err := action(name); err != nil && !skipError(err) {
return err
}
return filepath.SkipDir
})
}
+// writeExistingFile can be used as a drop-in replacement for ioutil.WriteFile,
+// but does not create a file when it does not exist
+func writeExistingFile(where string, data []byte, mode os.FileMode) error {
+ f, err := os.OpenFile(where, os.O_WRONLY|os.O_TRUNC, mode)
+ if err != nil {
+ return err
+ }
+ _, errW := f.Write(data)
+ errC := f.Close()
+ // pick the right error
+ if errW != nil {
+ return errW
+ }
+ return errC
+}
+
// freezeSnapProcessesImplV2 freezes all the processes originating from the
// given snap. Processes are frozen regardless of which particular snap
// application they originate from.
@@ -166,15 +193,20 @@ func freezeSnapProcessesImplV2(snapName string) error {
return nil
}
fname := filepath.Join(dir, "cgroup.freeze")
- if err := ioutil.WriteFile(fname, []byte("1"), 0644); err != nil && os.IsNotExist(err) {
- // the group may be gone already
- return nil
- } else if err != nil {
+ if err := writeExistingFile(fname, []byte("1"), 0644); err != nil {
+ if os.IsNotExist(err) {
+ // the group may be gone already
+ return nil
+ }
return fmt.Errorf("cannot freeze processes of snap %q, %v", snapName, err)
}
for i := 0; i < 30; i++ {
data, err := ioutil.ReadFile(fname)
if err != nil {
+ if os.IsNotExist(err) {
+ // group may be gone
+ return nil
+ }
return fmt.Errorf("cannot determine the freeze state of processes of snap %q, %v", snapName, err)
}
// If the cgroup is still freezing then wait a moment and try again.
@@ -187,35 +219,40 @@ func freezeSnapProcessesImplV2(snapName string) error {
}
return fmt.Errorf("cannot freeze processes of snap %q in group %v", snapName, filepath.Base(dir))
}
- const skipFreezeErrs = false
- err = applyToSnap(snapName, freezeOne, skipFreezeErrs)
+ // freeze, skipping ENOENT errors
+ err = applyToSnap(snapName, freezeOne, os.IsNotExist)
if err == nil {
return nil
}
// we either got here because we hit a timeout freezing snap processes
// or some other error
- const skipThawErrs = true
- thawSnapProcessesV2(snapName, skipThawErrs) // ignore the error, this is best-effort.
+
+ // ignore errors when thawing processes, this is best-effort.
+ alwaysSkipError := func(_ error) bool { return true }
+ thawSnapProcessesV2(snapName, alwaysSkipError)
return fmt.Errorf("cannot finish freezing processes of snap %q: %v", snapName, err)
}
-func thawSnapProcessesV2(snapName string, skipErrs bool) error {
+func thawSnapProcessesV2(snapName string, skipError func(error) bool) error {
+ if skipError == nil {
+ return fmt.Errorf("internal error: skip error is nil")
+ }
thawOne := func(dir string) error {
fname := filepath.Join(dir, "cgroup.freeze")
- if err := ioutil.WriteFile(fname, []byte("0"), 0644); err != nil && os.IsNotExist(err) {
+ if err := writeExistingFile(fname, []byte("0"), 0644); err != nil && os.IsNotExist(err) {
// the group may be gone already
return nil
- } else if err != nil && !skipErrs {
+ } else if err != nil && !skipError(err) {
return fmt.Errorf("cannot thaw processes of snap %q, %v", snapName, err)
}
return nil
}
- return applyToSnap(snapName, thawOne, skipErrs)
+ return applyToSnap(snapName, thawOne, skipError)
}
func thawSnapProcessesImplV2(snapName string) error {
- const skipErrs = false
- return thawSnapProcessesV2(snapName, skipErrs)
+ // thaw skipping ENOENT errors
+ return thawSnapProcessesV2(snapName, os.IsNotExist)
}
// MockFreezing replaces the real implementation of freeze and thaw.
diff --git a/sandbox/cgroup/freezer_test.go b/sandbox/cgroup/freezer_test.go
index 16f346d4b3..22bcfb5854 100644
--- a/sandbox/cgroup/freezer_test.go
+++ b/sandbox/cgroup/freezer_test.go
@@ -24,6 +24,8 @@ import (
"io/ioutil"
"os"
"path/filepath"
+ "sort"
+ "strings"
. "gopkg.in/check.v1"
@@ -277,6 +279,9 @@ func (s *freezerV2Suite) TestThawSnapProcessesV2(c *C) {
}
func (s *freezerV2Suite) TestFreezeThawSnapProcessesV2ErrWalking(c *C) {
+ if os.Getuid() == 0 {
+ c.Skip("the test cannot be run by the root user")
+ }
defer cgroup.MockVersion(cgroup.V2, nil)()
dirs.SetRootDir(c.MkDir())
defer dirs.SetRootDir("")
@@ -336,3 +341,92 @@ func (s *freezerV2Suite) TestFreezeThawSnapProcessesV2ErrWalking(c *C) {
os.Chmod(filepath.Dir(gUnfreeze), 0755)
c.Check(gUnfreeze, testutil.FileEquals, "1")
}
+
+func (s *freezerV2Suite) TestFreezeThawSnapProcessesV2ErrNotFound(c *C) {
+ defer cgroup.MockVersion(cgroup.V2, nil)()
+ dirs.SetRootDir(c.MkDir())
+ defer dirs.SetRootDir("")
+
+ // app started by root
+ g1 := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.foo.app.1234-1234-1234.scope/cgroup.freeze")
+ g2 := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.foo.svc.service/cgroup.freeze")
+
+ pid := os.Getpid()
+ procPidCgroup := filepath.Join(dirs.GlobalRootDir, fmt.Sprintf("proc/%v/cgroup", pid))
+ c.Assert(os.MkdirAll(filepath.Dir(procPidCgroup), 0755), IsNil)
+ // mock our own group
+ c.Assert(ioutil.WriteFile(procPidCgroup, []byte("0::/system.slice/snap.foo.app.own-own-own.scope"), 0755), IsNil)
+ // prepare the directories, but not the files, those should trigger ENOENT
+ c.Assert(os.MkdirAll(filepath.Dir(g1), 0755), IsNil)
+ c.Assert(os.MkdirAll(filepath.Dir(g2), 0755), IsNil)
+
+ err := cgroup.FreezeSnapProcesses("foo")
+ c.Assert(err, IsNil)
+
+ c.Check(g1, testutil.FileAbsent)
+ c.Check(g2, testutil.FileAbsent)
+
+ err = cgroup.ThawSnapProcesses("foo")
+ c.Assert(err, IsNil)
+ c.Check(g1, testutil.FileAbsent)
+ c.Check(g2, testutil.FileAbsent)
+}
+
+func (s *freezerV2Suite) TestApplyToSnapCallbacks(c *C) {
+ defer cgroup.MockVersion(cgroup.V2, nil)()
+ dirs.SetRootDir(c.MkDir())
+ defer dirs.SetRootDir("")
+
+ c.Check(cgroup.ApplyToSnap("foo", nil, nil), ErrorMatches, "internal error: action is nil")
+ nop := func(_ string) error { return nil }
+ c.Check(cgroup.ApplyToSnap("foo", nop, nil), ErrorMatches, "internal error: skip error is nil")
+
+ g := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.foo.app.1234-1234-1234.scope/cgroup.freeze")
+ gErr := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.foo.app.fail.scope/cgroup.freeze")
+
+ for _, p := range []string{g, gErr} {
+ c.Assert(os.MkdirAll(filepath.Dir(p), 0755), IsNil)
+ // groups aren't frozen
+ c.Assert(ioutil.WriteFile(p, []byte("0"), 0644), IsNil)
+ }
+
+ var visited []string
+ err := cgroup.ApplyToSnap("foo",
+ func(p string) error {
+ visited = append(visited, p)
+ return nil
+ },
+ func(err error) bool {
+ return true
+ })
+ c.Assert(err, IsNil)
+ sort.Strings(visited)
+ c.Check(visited, DeepEquals, []string{filepath.Dir(g), filepath.Dir(gErr)})
+
+ visited = nil
+ skip := true
+ var errors []string
+ maybeFail := func(p string) error {
+ visited = append(visited, p)
+ if strings.HasSuffix(p, "fail.scope") {
+ return fmt.Errorf("do not skip")
+ }
+ return nil
+ }
+ maybeSkip := func(err error) bool {
+ errors = append(errors, err.Error())
+ return skip
+ }
+ err = cgroup.ApplyToSnap("foo", maybeFail, maybeSkip)
+ c.Assert(err, IsNil)
+ c.Check(visited, DeepEquals, []string{filepath.Dir(g), filepath.Dir(gErr)})
+ c.Check(errors, DeepEquals, []string{"do not skip"})
+
+ skip = false
+ visited = nil
+ errors = nil
+ err = cgroup.ApplyToSnap("foo", maybeFail, maybeSkip)
+ c.Assert(err, ErrorMatches, "do not skip")
+ c.Check(visited, DeepEquals, []string{filepath.Dir(g), filepath.Dir(gErr)})
+ c.Check(errors, DeepEquals, []string{"do not skip"})
+}
diff --git a/spread.yaml b/spread.yaml
index e0baa192fe..19992a0ca1 100644
--- a/spread.yaml
+++ b/spread.yaml
@@ -85,6 +85,8 @@ backends:
workers: 6
- ubuntu-16.04-64:
workers: 8
+ - ubuntu-18.04-32:
+ workers: 6
- ubuntu-18.04-64:
workers: 8
- ubuntu-20.04-64:
diff --git a/tests/lib/pkgdb.sh b/tests/lib/pkgdb.sh
index 8de60b321a..40f2c71d3d 100755
--- a/tests/lib/pkgdb.sh
+++ b/tests/lib/pkgdb.sh
@@ -550,7 +550,7 @@ pkg_dependencies_ubuntu_classic(){
ubuntu-14.04-*)
pkg_linux_image_extra
;;
- ubuntu-16.04-32)
+ ubuntu-16.04-32|ubuntu-18.04-32)
echo "
dbus-user-session
gccgo-6
diff --git a/tests/lib/snaps/store/test-snapd-refresh-control-provider.v1/build-aux/snap/snapcraft.yaml b/tests/lib/snaps/store/test-snapd-refresh-control-provider.v1/build-aux/snap/snapcraft.yaml
new file mode 100644
index 0000000000..8705f4ea4a
--- /dev/null
+++ b/tests/lib/snaps/store/test-snapd-refresh-control-provider.v1/build-aux/snap/snapcraft.yaml
@@ -0,0 +1,24 @@
+name: test-snapd-refresh-control-provider
+version: 1.0.0
+summary: Test snap for gate-auto-refresh-hook feature - content slot provider.
+description: |
+ Test snap for refresh control (gate-auto-refresh-hook) feature content slot
+ provider.
+grade: stable
+confinement: strict
+type: app
+base: core18
+architectures:
+ - build-on: amd64
+ run-on: all
+
+parts:
+ test-snapd-refresh-control-provider:
+ plugin: nil
+
+slots:
+ content:
+ interface: content
+ content: test-content
+ read:
+ - /
diff --git a/tests/lib/snaps/store/test-snapd-refresh-control-provider.v2/build-aux/snap/snapcraft.yaml b/tests/lib/snaps/store/test-snapd-refresh-control-provider.v2/build-aux/snap/snapcraft.yaml
new file mode 100644
index 0000000000..da1d1bfab1
--- /dev/null
+++ b/tests/lib/snaps/store/test-snapd-refresh-control-provider.v2/build-aux/snap/snapcraft.yaml
@@ -0,0 +1,24 @@
+name: test-snapd-refresh-control-provider
+version: 2.0.0
+summary: Test snap for gate-auto-refresh-hook feature - content slot provider.
+description: |
+ Test snap for refresh control (gate-auto-refresh-hook) feature content slot
+ provider.
+grade: stable
+confinement: strict
+type: app
+base: core18
+architectures:
+ - build-on: amd64
+ run-on: all
+
+parts:
+ test-snapd-refresh-control-provider:
+ plugin: nil
+
+slots:
+ content:
+ interface: content
+ content: test-content
+ read:
+ - /
diff --git a/tests/lib/snaps/store/test-snapd-refresh-control.v1/build-aux/snap/hooks/gate-auto-refresh b/tests/lib/snaps/store/test-snapd-refresh-control.v1/build-aux/snap/hooks/gate-auto-refresh
new file mode 100644
index 0000000000..7b8eb4fa7c
--- /dev/null
+++ b/tests/lib/snaps/store/test-snapd-refresh-control.v1/build-aux/snap/hooks/gate-auto-refresh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+CONTROL_FILE="$SNAP_COMMON"/control
+
+snapctl refresh --pending > "$SNAP_COMMON"/debug.log
+
+if [ -f "$CONTROL_FILE" ]; then
+ COMMAND=$(cat "$CONTROL_FILE")
+ snapctl refresh "$COMMAND" >> "$SNAP_COMMON"/debug.log 2>&1
+fi
diff --git a/tests/lib/snaps/store/test-snapd-refresh-control.v1/build-aux/snap/snapcraft.yaml b/tests/lib/snaps/store/test-snapd-refresh-control.v1/build-aux/snap/snapcraft.yaml
new file mode 100644
index 0000000000..44b07318c2
--- /dev/null
+++ b/tests/lib/snaps/store/test-snapd-refresh-control.v1/build-aux/snap/snapcraft.yaml
@@ -0,0 +1,23 @@
+name: test-snapd-refresh-control
+version: 1.0.0
+summary: Test snap for gate-auto-refresh-hook feature.
+description: |
+ Test snap for refresh control (gate-auto-refresh-hook) feature. The behavior
+ of the gate-auto-refresh hook of this snap can be driven by a control file
+ in /var/snap/test-snapd-refresh-control/common/control
+grade: stable
+confinement: strict
+base: core18
+architectures:
+ - build-on: amd64
+ run-on: all
+
+parts:
+ test-snapd-refresh-control:
+ plugin: nil
+
+plugs:
+ content:
+ interface: content
+ content: test-content
+ target: $SNAP/content
diff --git a/tests/lib/snaps/store/test-snapd-refresh-control.v2/build-aux/snap/hooks/gate-auto-refresh b/tests/lib/snaps/store/test-snapd-refresh-control.v2/build-aux/snap/hooks/gate-auto-refresh
new file mode 100644
index 0000000000..7b8eb4fa7c
--- /dev/null
+++ b/tests/lib/snaps/store/test-snapd-refresh-control.v2/build-aux/snap/hooks/gate-auto-refresh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+CONTROL_FILE="$SNAP_COMMON"/control
+
+snapctl refresh --pending > "$SNAP_COMMON"/debug.log
+
+if [ -f "$CONTROL_FILE" ]; then
+ COMMAND=$(cat "$CONTROL_FILE")
+ snapctl refresh "$COMMAND" >> "$SNAP_COMMON"/debug.log 2>&1
+fi
diff --git a/tests/lib/snaps/store/test-snapd-refresh-control.v2/build-aux/snap/snapcraft.yaml b/tests/lib/snaps/store/test-snapd-refresh-control.v2/build-aux/snap/snapcraft.yaml
new file mode 100644
index 0000000000..8e67bd03e0
--- /dev/null
+++ b/tests/lib/snaps/store/test-snapd-refresh-control.v2/build-aux/snap/snapcraft.yaml
@@ -0,0 +1,23 @@
+name: test-snapd-refresh-control
+version: 2.0.0
+summary: Test snap for gate-auto-refresh-hook feature.
+description: |
+ Test snap for refresh control (gate-auto-refresh-hook) feature. The behavior
+ of the gate-auto-refresh hook of this snap can be driven by a control file
+ in /var/snap/test-snapd-refresh-control/common/control
+grade: stable
+confinement: strict
+base: core18
+architectures:
+ - build-on: amd64
+ run-on: all
+
+parts:
+ test-snapd-refresh-control:
+ plugin: nil
+
+plugs:
+ content:
+ interface: content
+ content: test-content
+ target: $SNAP/content
diff --git a/tests/main/auto-refresh-gating/task.yaml b/tests/main/auto-refresh-gating/task.yaml
new file mode 100644
index 0000000000..7855c3860f
--- /dev/null
+++ b/tests/main/auto-refresh-gating/task.yaml
@@ -0,0 +1,167 @@
+summary: Check that auto-refresh with gate-auto-refresh hooks works.
+
+details: |
+ Test auto-refresh with gate-auto-refresh hook support enabled
+ (experimental.gate-auto-refresh-hook feature) and verify the hook can control
+ automatic refreshes. The test uses two test snaps, one of them
+ being a content provider of the other. There are a few versions of these
+ snaps in the store (in stable/beta/edge channels) for this test.
+
+environment:
+ SNAP_NAME: test-snapd-refresh-control
+ CONTENT_SNAP_NAME: test-snapd-refresh-control-provider
+ CONTROL_FILE: /var/snap/test-snapd-refresh-control/common/control
+ DEBUG_LOG_FILE: /var/snap/test-snapd-refresh-control/common/debug.log
+
+prepare: |
+ snap install --devmode jq
+ snap set system experimental.gate-auto-refresh-hook=true
+
+debug: |
+ jq -r '.data["snaps-hold"]' < /var/lib/snapd/state.json || true
+
+execute: |
+ force_autorefresh() {
+ echo "And force auto-refresh to happen"
+ jq ".data[\"last-refresh\"] = \"2007-08-22T09:30:44.449455783+01:00\"" /var/lib/snapd/state.json > /var/lib/snapd/state.json.new
+ mv /var/lib/snapd/state.json.new /var/lib/snapd/state.json
+ }
+
+ wait_for_autorefresh() {
+ local EXPECTED_SNAP="$1"
+ local LAST_CHANGE_ID="$2"
+ local CHANGE_ID="$2"
+ for _ in $(seq 200); do
+ # get last 2 lines of snap changes (the last one is always empty), match
+ # auto-refresh change of the expected snap; only proceed if the
+ # change has greater change id than the previously matched auto-refresh
+ # (this way we can match consecutive auto-refreshes for same snap).
+ if CHANGES=$(snap changes | tail -2 | grep "Done.*Auto-refresh snap \"$EXPECTED_SNAP\""); then
+ CHANGE_ID=$(echo "$CHANGES" | awk '{print $1}')
+ if [ "$CHANGE_ID" -gt "$LAST_CHANGE_ID" ]; then
+ break
+ fi
+ fi
+ snap debug ensure-state-soon
+ sleep 1
+ done
+ if [ "$LAST_CHANGE_ID" -eq "$CHANGE_ID" ]; then
+ echo "Expected a new auto-refresh change for $EXPECTED_SNAP with id greater than $LAST_CHANGE_ID, but it didn't happen"
+ exit 1
+ fi
+ echo "$CHANGE_ID"
+ }
+
+ force_channel_change() {
+ local SNAP="$1"
+ local CHANNEL="$2"
+ echo "Modify snap $SNAP to track the $CHANNEL channel"
+ jq ".data.snaps[\"$SNAP\"].channel = \"$CHANNEL\"" /var/lib/snapd/state.json > /var/lib/snapd/state.json.new
+ mv /var/lib/snapd/state.json.new /var/lib/snapd/state.json
+ }
+
+ LAST_REFRESH_CHANGE_ID=1
+
+ echo "Install test snaps"
+ snap install "$SNAP_NAME"
+ snap install "$CONTENT_SNAP_NAME"
+
+ snap connect "$SNAP_NAME:content" "$CONTENT_SNAP_NAME:content"
+
+ # sanity check
+ snap list | MATCH "$SNAP_NAME +1\.0\.0"
+ snap list | MATCH "$CONTENT_SNAP_NAME +1\.0\.0"
+
+ snap set core refresh.schedule="0:00-23:59"
+
+ force_channel_change "$CONTENT_SNAP_NAME" beta
+
+ # force auto-refresh a few times, we expect the gate-auto-refresh
+ # hook of test-snapd-refresh-control to be executed because of the refresh
+ # of content provider snap. The refresh is expected to be held every time.
+ for _ in $(seq 1 3); do
+ systemctl stop snapd.{service,socket}
+
+ # Request the snap to hold the refresh (itself and its content provider).
+ echo "--hold" > "$CONTROL_FILE"
+
+ echo "Trigger auto-refresh of test-snapd-refresh-control-provider but hold it via test-snapd-refresh-control's hook"
+ force_autorefresh
+ systemctl start snapd.{service,socket}
+ LAST_REFRESH_CHANGE_ID=$(wait_for_autorefresh "$CONTENT_SNAP_NAME" "$LAST_REFRESH_CHANGE_ID")
+
+ snap change --last=auto-refresh | MATCH "Run auto-refresh for ready snaps"
+ snap change --last=auto-refresh | MATCH "Run hook gate-auto-refresh of snap \"$SNAP_NAME\""
+
+ echo "Check that the --pending information indicates restart due to the content slot"
+ MATCH "restart: +true" < "$DEBUG_LOG_FILE"
+ MATCH "base: +false" < "$DEBUG_LOG_FILE"
+ MATCH "channel: +latest/stable" < "$DEBUG_LOG_FILE"
+ # test-snapd-refresh-control doesn't have update, so pending/version are not
+ # available.
+ MATCH "pending: none" < "$DEBUG_LOG_FILE"
+ NOMATCH "version:" < "$DEBUG_LOG_FILE"
+
+ echo "Ensure our content snap was held and is still at version 1"
+ snap list | MATCH "$CONTENT_SNAP_NAME +1\.0\.0"
+ # sanity check for the gating snap.
+ snap list | MATCH "$SNAP_NAME +1\.0\.0"
+ done
+
+ systemctl stop snapd.{service,socket}
+
+ # force auto-refresh again but this time we expect content provider snap to be
+ # refreshed because the gating hook of test-snapd-refresh-control calls --proceed.
+ echo "--proceed" > "$CONTROL_FILE"
+
+ force_autorefresh
+ systemctl start snapd.{service,socket}
+ LAST_REFRESH_CHANGE_ID=$(wait_for_autorefresh "$CONTENT_SNAP_NAME" "$LAST_REFRESH_CHANGE_ID")
+
+ snap change --last=auto-refresh | MATCH "Run auto-refresh for ready snaps"
+ snap change --last=auto-refresh | MATCH "Run hook gate-auto-refresh of snap \"$SNAP_NAME\""
+
+ echo "Check that the --pending information indicates test-snapd-refresh-control is affected by the content snap"
+ MATCH "restart: +true" < "$DEBUG_LOG_FILE"
+
+ echo "Ensure our content snap was refreshed"
+ snap list | MATCH "$CONTENT_SNAP_NAME +2\.0\.0"
+ # sanity check for the gating snap.
+ snap list | MATCH "$SNAP_NAME +1\.0\.0"
+
+ systemctl stop snapd.{service,socket}
+
+ # test the scenario where the test-snapd-refresh-control refresh is attempted
+ # and it holds itself.
+ echo "Trigger auto-refresh of test-snapd-refresh-control and hold it from its hook"
+ echo "--hold" > "$CONTROL_FILE"
+ force_channel_change "$SNAP_NAME" beta
+ force_autorefresh
+
+ systemctl start snapd.{service,socket}
+ LAST_REFRESH_CHANGE_ID=$(wait_for_autorefresh "$SNAP_NAME" "$LAST_REFRESH_CHANGE_ID")
+
+ echo "Check that the --pending information contains test-snapd-refresh-control refresh info"
+ MATCH "pending: +ready" < "$DEBUG_LOG_FILE"
+ MATCH "channel: +beta" < "$DEBUG_LOG_FILE"
+ MATCH "version: +2\.0" < "$DEBUG_LOG_FILE"
+ MATCH "base: +false" < "$DEBUG_LOG_FILE"
+ MATCH "restart: +false" < "$DEBUG_LOG_FILE"
+
+ echo "Ensure our snap was held"
+ snap list | MATCH "$SNAP_NAME +1\.0\.0"
+
+ systemctl stop snapd.{service,socket}
+
+ # test the scenario where the test-snapd-refresh-control refresh proceeds.
+ echo "Trigger auto-refresh of test-snapd-refresh-control and proceed from its hook"
+ echo "--proceed" > "$CONTROL_FILE"
+ force_autorefresh
+
+ systemctl start snapd.{service,socket}
+ LAST_REFRESH_CHANGE_ID=$(wait_for_autorefresh "$SNAP_NAME" "$LAST_REFRESH_CHANGE_ID")
+
+ echo "Ensure our snap was updated"
+ snap list | MATCH "$SNAP_NAME +2\.0\.0"
+
+ # TODO: edge channel, proceed via default behavior. error behavior (hold).
diff --git a/tests/main/desktop-portal-filechooser/task.yaml b/tests/main/desktop-portal-filechooser/task.yaml
index 22f32c8c1e..bf6ca7c526 100644
--- a/tests/main/desktop-portal-filechooser/task.yaml
+++ b/tests/main/desktop-portal-filechooser/task.yaml
@@ -17,7 +17,7 @@ details: |
# Only enable the test on systems we know portals to function on.
# Expand as needed.
# ubuntu-18.04-*: Ships xdg-desktop-portal 0.11
-systems: [ubuntu-18.04-*, ubuntu-2*]
+systems: [ubuntu-18.04-64, ubuntu-2*]
prepare: |
#shellcheck source=tests/lib/desktop-portal.sh
diff --git a/tests/main/desktop-portal-open-file/task.yaml b/tests/main/desktop-portal-open-file/task.yaml
index 3caf92e3f7..cc207a63a2 100644
--- a/tests/main/desktop-portal-open-file/task.yaml
+++ b/tests/main/desktop-portal-open-file/task.yaml
@@ -10,7 +10,7 @@ details: |
# Only enable the test on systems we know portals to function on.
# Expand as needed.
-systems: [ubuntu-18.04-*, ubuntu-2*]
+systems: [ubuntu-18.04-64, ubuntu-2*]
environment:
EDITOR_HISTORY: /tmp/editor-history.txt
diff --git a/tests/main/desktop-portal-open-uri/task.yaml b/tests/main/desktop-portal-open-uri/task.yaml
index 479d1da649..d9e5a14635 100644
--- a/tests/main/desktop-portal-open-uri/task.yaml
+++ b/tests/main/desktop-portal-open-uri/task.yaml
@@ -7,7 +7,7 @@ details: |
# Only enable the test on systems we know portals to function on.
# Expand as needed.
-systems: [ubuntu-18.04-*, ubuntu-2*]
+systems: [ubuntu-18.04-64, ubuntu-2*]
environment:
BROWSER_HISTORY: /tmp/browser-history.txt
diff --git a/tests/main/desktop-portal-screenshot/task.yaml b/tests/main/desktop-portal-screenshot/task.yaml
index a329544de1..388e7d0281 100644
--- a/tests/main/desktop-portal-screenshot/task.yaml
+++ b/tests/main/desktop-portal-screenshot/task.yaml
@@ -21,7 +21,7 @@ details: |
# Only enable the test on systems we know portals to function on.
# Expand as needed.
-systems: [ubuntu-18.04-*, ubuntu-2*]
+systems: [ubuntu-18.04-64, ubuntu-2*]
prepare: |
#shellcheck source=tests/lib/desktop-portal.sh
diff --git a/tests/main/preseed-lxd/task.yaml b/tests/main/preseed-lxd/task.yaml
index d1d5eebdc9..c3c3aa4118 100644
--- a/tests/main/preseed-lxd/task.yaml
+++ b/tests/main/preseed-lxd/task.yaml
@@ -5,9 +5,9 @@ details: |
command works in lxc container and that the resulting image can be run
in a container and seeding finishes successfully.
-# this test works only on 18.04 because it requires lxd from deb (lxd snap
+# this test works only on 18.04-64 because it requires lxd from deb (lxd snap
# as it wouldn't allow mount) and tries to replicate launchpad builder setup.
-systems: [ubuntu-18.04-*]
+systems: [ubuntu-18.04-64]
environment:
IMAGE_MOUNTPOINT: /mnt/cloudimg