summaryrefslogtreecommitdiff
diff options
authorMichael Vogt <mvo@ubuntu.com>2018-08-20 14:48:00 +0200
committerMichael Vogt <mvo@ubuntu.com>2018-08-20 14:48:00 +0200
commit6110ed56fc51eca0f2ed32dbc0c317588b711321 (patch)
treead4d39fb1ebb972826df22705d6d04fef0879e5d
parent032dc6c90c36344bc92902ed9a58ec496a67e526 (diff)
parent8907449841ab5431335c29efa5062aa7001af461 (diff)
Merge remote-tracking branch 'upstream/master' into release-2.35release-2.35
-rw-r--r--cmd/libsnap-confine-private/classic-test.c137
-rw-r--r--cmd/libsnap-confine-private/classic.c11
-rw-r--r--cmd/libsnap-confine-private/snap-test.c185
-rw-r--r--cmd/libsnap-confine-private/snap.c93
-rw-r--r--cmd/libsnap-confine-private/snap.h29
-rw-r--r--cmd/snap-exec/main.go25
-rw-r--r--cmd/snap-exec/main_test.go77
-rw-r--r--cmd/snap-update-ns/bootstrap.c83
-rw-r--r--cmd/snap-update-ns/bootstrap.go13
-rw-r--r--cmd/snap-update-ns/bootstrap.h2
-rw-r--r--cmd/snap-update-ns/bootstrap_test.go35
-rw-r--r--cmd/snap-update-ns/export_test.go7
-rw-r--r--cmd/snap/cmd_get.go8
-rw-r--r--cmd/snap/cmd_get_test.go2
-rw-r--r--cmd/snap/cmd_run.go30
-rw-r--r--cmd/snap/cmd_run_test.go33
-rw-r--r--cmd/snap/cmd_snap_op.go9
-rw-r--r--cmd/snap/cmd_snap_op_test.go9
-rw-r--r--cmd/snap/cmd_wait.go22
-rw-r--r--cmd/snap/color.go5
-rw-r--r--cmd/snap/color_test.go2
-rw-r--r--cmd/snap/export_test.go16
-rw-r--r--cmd/snap/main.go4
-rw-r--r--cmd/snap/main_test.go15
-rw-r--r--daemon/api.go15
-rw-r--r--daemon/api_test.go55
-rw-r--r--interfaces/builtin/accounts_service.go9
-rw-r--r--interfaces/builtin/avahi_observe.go4
-rw-r--r--interfaces/builtin/bluez.go150
-rw-r--r--interfaces/builtin/hostname_control.go21
-rw-r--r--interfaces/builtin/modem_manager.go16
-rw-r--r--interfaces/builtin/network_manager.go11
-rw-r--r--interfaces/builtin/screencast_legacy.go64
-rw-r--r--interfaces/builtin/screencast_legacy_test.go107
-rw-r--r--interfaces/builtin/shutdown.go8
-rw-r--r--interfaces/builtin/system_observe.go8
-rw-r--r--interfaces/builtin/time_control.go8
-rw-r--r--interfaces/builtin/timeserver_control.go8
-rw-r--r--interfaces/builtin/timezone_control.go8
-rw-r--r--interfaces/builtin/udisks2.go10
-rw-r--r--interfaces/builtin/upower_observe.go23
-rw-r--r--interfaces/repo.go27
-rw-r--r--interfaces/repo_test.go49
-rwxr-xr-xmkversion.sh11
-rw-r--r--overlord/hookstate/ctlcmd/get.go26
-rw-r--r--overlord/hookstate/ctlcmd/get_test.go1
-rw-r--r--overlord/ifacestate/export_test.go14
-rw-r--r--overlord/ifacestate/handlers.go247
-rw-r--r--overlord/ifacestate/hooks.go46
-rw-r--r--overlord/ifacestate/ifacemgr.go3
-rw-r--r--overlord/ifacestate/ifacestate.go203
-rw-r--r--overlord/ifacestate/ifacestate_test.go387
-rw-r--r--overlord/managers_test.go190
-rw-r--r--overlord/snapstate/backend.go2
-rw-r--r--overlord/snapstate/backend/setup.go6
-rw-r--r--overlord/snapstate/backend/setup_test.go47
-rw-r--r--overlord/snapstate/backend_test.go55
-rw-r--r--overlord/snapstate/check_snap.go12
-rw-r--r--overlord/snapstate/check_snap_test.go104
-rw-r--r--overlord/snapstate/handlers.go10
-rw-r--r--overlord/snapstate/handlers_mount_test.go7
-rw-r--r--overlord/snapstate/snapstate.go106
-rw-r--r--overlord/snapstate/snapstate_test.go1003
-rw-r--r--packaging/opensuse/snapd.spec12
-rw-r--r--snap/hooktypes.go2
-rw-r--r--snap/info.go1
-rw-r--r--snap/info_snap_yaml.go4
-rw-r--r--snap/info_snap_yaml_test.go14
-rw-r--r--snap/info_test.go3
-rw-r--r--snap/validate.go11
-rw-r--r--snap/validate_test.go3
-rw-r--r--snapcraft.yaml3
-rw-r--r--tests/lib/journalctl.sh11
-rwxr-xr-xtests/lib/pkgdb.sh32
-rwxr-xr-xtests/lib/prepare.sh2
-rwxr-xr-xtests/lib/snaps/basic-iface-hooks-consumer/meta/hooks/configure3
-rwxr-xr-xtests/lib/snaps/basic-iface-hooks-consumer/meta/hooks/connect-plug-consumer6
-rwxr-xr-xtests/lib/snaps/basic-iface-hooks-consumer/meta/hooks/disconnect-plug-consumer61
-rwxr-xr-xtests/lib/snaps/basic-iface-hooks-consumer/meta/hooks/unprepare-plug-consumer5
-rwxr-xr-xtests/lib/snaps/basic-iface-hooks-producer/meta/hooks/configure1
-rwxr-xr-xtests/lib/snaps/basic-iface-hooks-producer/meta/hooks/disconnect-slot-producer52
-rwxr-xr-xtests/lib/snaps/basic-iface-hooks-producer/meta/hooks/unprepare-slot-producer3
-rwxr-xr-xtests/lib/snaps/command-chain/chain15
-rwxr-xr-xtests/lib/snaps/command-chain/chain25
-rwxr-xr-xtests/lib/snaps/command-chain/hello3
-rw-r--r--tests/lib/snaps/command-chain/meta/icon.pngbin0 -> 3371 bytes
-rw-r--r--tests/lib/snaps/command-chain/meta/snap.yaml9
-rwxr-xr-xtests/lib/snaps/snap-hooks/meta/hooks/remove2
-rwxr-xr-xtests/lib/snaps/test-snapd-juju-client-observe/bin/sh3
-rw-r--r--tests/lib/snaps/test-snapd-juju-client-observe/meta/snap.yaml9
-rw-r--r--tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml3
-rwxr-xr-xtests/lib/snaps/test-snapd-service/bin/stop-stop-mode2
-rw-r--r--tests/lib/snaps/test-snapd-udisks2/snapcraft.yaml17
-rwxr-xr-xtests/lib/snaps/test-snapd-udisks2/udisksctl3
-rw-r--r--tests/main/command-chain/task.yaml26
-rw-r--r--tests/main/fedora-base-smoke/task.yaml8
-rw-r--r--tests/main/install-refresh-remove-hooks/task.yaml2
-rw-r--r--tests/main/interfaces-hooks/task.yaml44
-rw-r--r--tests/main/interfaces-juju-client-observe/task.yaml52
-rw-r--r--tests/main/interfaces-snapd-control-with-manage/task.yaml25
-rw-r--r--tests/main/interfaces-ssh-keys/task.yaml7
-rw-r--r--tests/main/interfaces-ssh-public-keys/task.yaml9
-rw-r--r--tests/main/interfaces-system-observe/task.yaml8
-rw-r--r--tests/main/interfaces-time-control/task.yaml7
-rw-r--r--tests/main/interfaces-timezone-control/task.yaml11
-rw-r--r--tests/main/interfaces-udev/task.yaml3
-rw-r--r--tests/main/interfaces-udisks2/task.yaml59
-rw-r--r--tests/main/interfaces-uhid/task.yaml4
-rw-r--r--tests/main/interfaces-upower-observe/task.yaml6
-rw-r--r--tests/main/interfaces-wayland/task.yaml3
-rw-r--r--tests/main/kernel-snap-refresh-on-core/task.yaml7
-rw-r--r--tests/main/known-remote/task.yaml8
-rw-r--r--tests/main/layout/task.yaml13
-rw-r--r--tests/main/listing/task.yaml11
-rw-r--r--tests/main/login/task.yaml2
-rw-r--r--tests/main/lxd/task.yaml33
-rw-r--r--tests/main/media-sharing/task.yaml12
-rw-r--r--tests/main/nfs-support/task.yaml5
-rw-r--r--tests/main/op-install-failed-undone/task.yaml21
-rw-r--r--tests/main/op-remove-retry/task.yaml8
-rw-r--r--tests/main/op-remove/task.yaml13
-rw-r--r--tests/main/postrm-purge/task.yaml14
-rw-r--r--tests/main/prefer/task.yaml3
-rw-r--r--tests/main/prepare-image-grub/task.yaml40
-rw-r--r--tests/main/prepare-image-uboot/task.yaml34
-rw-r--r--tests/main/refresh-all-undo/task.yaml37
-rw-r--r--tests/main/refresh-all/task.yaml26
-rw-r--r--tests/main/refresh-amend/task.yaml2
-rw-r--r--tests/main/refresh-delta-from-core/task.yaml4
-rw-r--r--tests/main/refresh-delta/task.yaml4
-rw-r--r--tests/main/refresh-devmode/task.yaml17
-rw-r--r--tests/main/refresh-undo/task.yaml8
-rw-r--r--tests/main/refresh/task.yaml42
-rw-r--r--tests/main/regression-home-snap-root-owned/task.yaml16
-rw-r--r--tests/main/remove-errors/task.yaml4
-rw-r--r--tests/main/revert-devmode/task.yaml20
-rw-r--r--tests/main/revert-sideload/task.yaml2
-rw-r--r--tests/main/revert/task.yaml30
-rw-r--r--tests/main/searching/task.yaml24
-rw-r--r--tests/main/security-apparmor/task.yaml3
-rw-r--r--tests/main/security-device-cgroups-classic/task.yaml8
-rw-r--r--tests/main/security-device-cgroups-devmode/task.yaml10
-rw-r--r--tests/main/security-device-cgroups-jailmode/task.yaml6
-rw-r--r--tests/main/security-device-cgroups-serial-port/task.yaml3
-rw-r--r--tests/main/security-device-cgroups-strict/task.yaml6
-rw-r--r--tests/main/security-device-cgroups/task.yaml9
-rw-r--r--tests/main/security-devpts/task.yaml3
-rw-r--r--tests/main/security-private-tmp/task.yaml16
-rw-r--r--tests/main/security-profiles/task.yaml9
-rw-r--r--tests/main/security-setuid-root/task.yaml12
-rw-r--r--tests/main/security-udev-input-subsystem/task.yaml4
-rw-r--r--tests/main/server-snap/task.yaml6
-rw-r--r--tests/main/set-proxy-store/task.yaml24
-rw-r--r--tests/main/snap-advise-command/task.yaml8
-rw-r--r--tests/unit/spread-shellcheck/can-fail54
-rw-r--r--testutil/dbustest.go14
156 files changed, 4217 insertions, 935 deletions
diff --git a/cmd/libsnap-confine-private/classic-test.c b/cmd/libsnap-confine-private/classic-test.c
index 55c6a2fb73..cf6e5bcc0e 100644
--- a/cmd/libsnap-confine-private/classic-test.c
+++ b/cmd/libsnap-confine-private/classic-test.c
@@ -20,56 +20,104 @@
#include <glib.h>
-const char *os_release_classic = ""
+/* restore_os_release is an internal helper for mock_os_release */
+static void restore_os_release(gpointer * old)
+{
+ unlink(os_release);
+ os_release = (const char *)old;
+}
+
+/* mock_os_release replaces the presence and contents of /etc/os-release
+ as seen by classic.c. The mocked value may be NULL to have the code refer
+ to an absent file. */
+static void mock_os_release(const char *mocked)
+{
+ const char *old = os_release;
+ if (mocked != NULL) {
+ os_release = "os-release.test";
+ g_file_set_contents(os_release, mocked, -1, NULL);
+ } else {
+ os_release = "os-release.missing";
+ }
+ g_test_queue_destroy((GDestroyNotify) restore_os_release,
+ (gpointer) old);
+}
+
+/* restore_meta_snap_yaml is an internal helper for mock_meta_snap_yaml */
+static void restore_meta_snap_yaml(gpointer * old)
+{
+ unlink(meta_snap_yaml);
+ meta_snap_yaml = (const char *)old;
+}
+
+/* mock_meta_snap_yaml replaces the presence and contents of /meta/snap.yaml
+ as seen by classic.c. The mocked value may be NULL to have the code refer
+ to an absent file. */
+static void mock_meta_snap_yaml(const char *mocked)
+{
+ const char *old = meta_snap_yaml;
+ if (mocked != NULL) {
+ meta_snap_yaml = "snap-yaml.test";
+ g_file_set_contents(meta_snap_yaml, mocked, -1, NULL);
+ } else {
+ meta_snap_yaml = "snap-yaml.missing";
+ }
+ g_test_queue_destroy((GDestroyNotify) restore_meta_snap_yaml,
+ (gpointer) old);
+}
+
+static const char *os_release_classic = ""
"NAME=\"Ubuntu\"\n"
"VERSION=\"17.04 (Zesty Zapus)\"\n" "ID=ubuntu\n" "ID_LIKE=debian\n";
static void test_is_on_classic(void)
{
- g_file_set_contents("os-release.classic", os_release_classic,
- strlen(os_release_classic), NULL);
- os_release = "os-release.classic";
+ mock_os_release(os_release_classic);
+ mock_meta_snap_yaml(NULL);
g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CLASSIC);
- unlink("os-release.classic");
}
-const char *os_release_core16 = ""
+static const char *os_release_core16 = ""
"NAME=\"Ubuntu Core\"\n" "VERSION_ID=\"16\"\n" "ID=ubuntu-core\n";
+static const char *meta_snap_yaml_core16 = ""
+ "name: core\n"
+ "version: 16-something\n" "type: core\n" "architectures: [amd64]\n";
+
static void test_is_on_core_on16(void)
{
- g_file_set_contents("os-release.core", os_release_core16,
- strlen(os_release_core16), NULL);
- os_release = "os-release.core";
+ mock_os_release(os_release_core16);
+ mock_meta_snap_yaml(meta_snap_yaml_core16);
g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CORE16);
- unlink("os-release.core");
}
-const char *os_release_core18 = ""
+static const char *os_release_core18 = ""
"NAME=\"Ubuntu Core\"\n" "VERSION_ID=\"18\"\n" "ID=ubuntu-core\n";
+static const char *meta_snap_yaml_core18 = ""
+ "name: core18\n" "type: base\n" "architectures: [amd64]\n";
+
static void test_is_on_core_on18(void)
{
- g_file_set_contents("os-release.core", os_release_core18,
- strlen(os_release_core18), NULL);
- os_release = "os-release.core";
+ mock_os_release(os_release_core18);
+ mock_meta_snap_yaml(meta_snap_yaml_core18);
g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CORE_OTHER);
- unlink("os-release.core");
}
const char *os_release_core20 = ""
"NAME=\"Ubuntu Core\"\n" "VERSION_ID=\"20\"\n" "ID=ubuntu-core\n";
+static const char *meta_snap_yaml_core20 = ""
+ "name: core20\n" "type: base\n" "architectures: [amd64]\n";
+
static void test_is_on_core_on20(void)
{
- g_file_set_contents("os-release.core", os_release_core20,
- strlen(os_release_core20), NULL);
- os_release = "os-release.core";
+ mock_os_release(os_release_core20);
+ mock_meta_snap_yaml(meta_snap_yaml_core20);
g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CORE_OTHER);
- unlink("os-release.core");
}
-const char *os_release_classic_with_long_line = ""
+static const char *os_release_classic_with_long_line = ""
"NAME=\"Ubuntu\"\n"
"VERSION=\"17.04 (Zesty Zapus)\"\n"
"ID=ubuntu\n"
@@ -78,36 +126,54 @@ const char *os_release_classic_with_long_line = ""
static void test_is_on_classic_with_long_line(void)
{
- g_file_set_contents("os-release.classic-with-long-line",
- os_release_classic, strlen(os_release_classic),
- NULL);
- os_release = "os-release.classic-with-long-line";
+ mock_os_release(os_release_classic_with_long_line);
+ mock_meta_snap_yaml(NULL);
g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CLASSIC);
- unlink("os-release.classic-with-long-line");
}
-const char *os_release_fedora_base = ""
+static const char *os_release_fedora_base = ""
"NAME=Fedora\nID=fedora\nVARIANT_ID=snappy\n";
+static const char *meta_snap_yaml_fedora_base = ""
+ "name: fedora29\n" "type: base\n" "architectures: [amd64]\n";
+
static void test_is_on_fedora_base(void)
{
- g_file_set_contents("os-release.core", os_release_fedora_base,
- strlen(os_release_fedora_base), NULL);
- os_release = "os-release.core";
+ mock_os_release(os_release_fedora_base);
+ mock_meta_snap_yaml(meta_snap_yaml_fedora_base);
g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CORE_OTHER);
- unlink("os-release.core");
}
-const char *os_release_fedora_ws = ""
+static const char *os_release_fedora_ws = ""
"NAME=Fedora\nID=fedora\nVARIANT_ID=workstation\n";
static void test_is_on_fedora_ws(void)
{
- g_file_set_contents("os-release.core", os_release_fedora_ws,
- strlen(os_release_fedora_ws), NULL);
- os_release = "os-release.core";
+ mock_os_release(os_release_fedora_ws);
+ mock_meta_snap_yaml(NULL);
+ g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CLASSIC);
+}
+
+static const char *os_release_custom = ""
+ "NAME=\"Custom Distribution\"\nID=custom\n";
+
+static const char *meta_snap_yaml_custom = ""
+ "name: custom\n"
+ "version: rolling\n"
+ "summary: Runtime environment based on Custom Distribution\n"
+ "type: base\n" "architectures: [amd64]\n";
+
+static void test_is_on_custom_base(void)
+{
+ mock_os_release(os_release_custom);
+
+ /* Without /meta/snap.yaml we treat "Custom Distribution" as classic. */
+ mock_meta_snap_yaml(NULL);
g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CLASSIC);
- unlink("os-release.core");
+
+ /* With /meta/snap.yaml we treat it as core instead. */
+ mock_meta_snap_yaml(meta_snap_yaml_custom);
+ g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CORE_OTHER);
}
static void test_should_use_normal_mode(void)
@@ -132,6 +198,7 @@ static void __attribute__ ((constructor)) init(void)
g_test_add_func("/classic/on-core-on20", test_is_on_core_on20);
g_test_add_func("/classic/on-fedora-base", test_is_on_fedora_base);
g_test_add_func("/classic/on-fedora-ws", test_is_on_fedora_ws);
+ g_test_add_func("/classic/on-custom-base", test_is_on_custom_base);
g_test_add_func("/classic/should-use-normal-mode",
test_should_use_normal_mode);
}
diff --git a/cmd/libsnap-confine-private/classic.c b/cmd/libsnap-confine-private/classic.c
index 81baaca962..57f681c3ab 100644
--- a/cmd/libsnap-confine-private/classic.c
+++ b/cmd/libsnap-confine-private/classic.c
@@ -8,7 +8,8 @@
#include <string.h>
#include <unistd.h>
-char *os_release = "/etc/os-release";
+static const char *os_release = "/etc/os-release";
+static const char *meta_snap_yaml = "/meta/snap.yaml";
sc_distro sc_classify_distro(void)
{
@@ -38,6 +39,14 @@ sc_distro sc_classify_distro(void)
}
}
+ if (!is_core) {
+ /* Since classic systems don't have a /meta/snap.yaml file the simple
+ presence of that file qualifies as SC_DISTRO_CORE_OTHER. */
+ if (access(meta_snap_yaml, F_OK) == 0) {
+ is_core = true;
+ }
+ }
+
if (is_core) {
if (core_version == 16) {
return SC_DISTRO_CORE16;
diff --git a/cmd/libsnap-confine-private/snap-test.c b/cmd/libsnap-confine-private/snap-test.c
index e4042129f7..b605e1257e 100644
--- a/cmd/libsnap-confine-private/snap-test.c
+++ b/cmd/libsnap-confine-private/snap-test.c
@@ -30,6 +30,12 @@ static void test_verify_security_tag(void)
g_assert_true(verify_security_tag("snap.f00.bar-baz1", "f00"));
g_assert_true(verify_security_tag("snap.foo.hook.bar", "foo"));
g_assert_true(verify_security_tag("snap.foo.hook.bar-baz", "foo"));
+ g_assert_true(verify_security_tag
+ ("snap.foo_instance.bar-baz", "foo_instance"));
+ g_assert_true(verify_security_tag
+ ("snap.foo_instance.hook.bar-baz", "foo_instance"));
+ g_assert_true(verify_security_tag
+ ("snap.foo_bar.hook.bar-baz", "foo_bar"));
// Now, test the names we know are bad
g_assert_false(verify_security_tag
@@ -62,31 +68,63 @@ static void test_verify_security_tag(void)
g_assert_false(verify_security_tag("snap..name.app", ".name"));
g_assert_false(verify_security_tag("snap.name..app", "name."));
g_assert_false(verify_security_tag("snap.name.app..", "name"));
+ // These contain invalid instance key
+ g_assert_false(verify_security_tag("snap.foo_.bar-baz", "foo"));
+ g_assert_false(verify_security_tag
+ ("snap.foo_toolonginstance.bar-baz", "foo"));
+ g_assert_false(verify_security_tag
+ ("snap.foo_inst@nace.bar-baz", "foo"));
+ g_assert_false(verify_security_tag
+ ("snap.foo_in-stan-ce.bar-baz", "foo"));
+ g_assert_false(verify_security_tag("snap.foo_in stan.bar-baz", "foo"));
// Test names that are both good, but snap name doesn't match security tag
g_assert_false(verify_security_tag("snap.foo.hook.bar", "fo"));
g_assert_false(verify_security_tag("snap.foo.hook.bar", "fooo"));
g_assert_false(verify_security_tag("snap.foo.hook.bar", "snap"));
g_assert_false(verify_security_tag("snap.foo.hook.bar", "bar"));
+ g_assert_false(verify_security_tag("snap.foo_instance.bar", "foo_bar"));
// Regression test 12to8
g_assert_true(verify_security_tag("snap.12to8.128to8", "12to8"));
g_assert_true(verify_security_tag("snap.123test.123test", "123test"));
g_assert_true(verify_security_tag
("snap.123test.hook.configure", "123test"));
+}
+static void test_sc_is_hook_security_tag(void)
+{
+ // First, test the names we know are good
+ g_assert_true(sc_is_hook_security_tag("snap.foo.hook.bar"));
+ g_assert_true(sc_is_hook_security_tag("snap.foo.hook.bar-baz"));
+ g_assert_true(sc_is_hook_security_tag
+ ("snap.foo_instance.hook.bar-baz"));
+ g_assert_true(sc_is_hook_security_tag("snap.foo_bar.hook.bar-baz"));
+
+ // Now, test the names we know are not valid hook security tags
+ g_assert_false(sc_is_hook_security_tag("snap.foo_instance.bar-baz"));
+ g_assert_false(sc_is_hook_security_tag("snap.name.app!hook.foo"));
+ g_assert_false(sc_is_hook_security_tag("snap.name.app.hook!foo"));
+ g_assert_false(sc_is_hook_security_tag("snap.name.app.hook.-foo"));
+ g_assert_false(sc_is_hook_security_tag("snap.name.app.hook.f00"));
}
-static void test_sc_snap_name_validate(void)
+static void test_sc_snap_or_instance_name_validate(gconstpointer data)
{
+ typedef void (*validate_func_t) (const char *, struct sc_error **);
+
+ validate_func_t validate = (validate_func_t) data;
+ bool is_instance =
+ (validate == sc_instance_name_validate) ? true : false;
+
struct sc_error *err = NULL;
// Smoke test, a valid snap name
- sc_snap_name_validate("hello-world", &err);
+ validate("hello-world", &err);
g_assert_null(err);
// Smoke test: invalid character
- sc_snap_name_validate("hello world", &err);
+ validate("hello world", &err);
g_assert_nonnull(err);
g_assert_true(sc_error_match
(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
@@ -95,7 +133,7 @@ static void test_sc_snap_name_validate(void)
sc_error_free(err);
// Smoke test: no letters
- sc_snap_name_validate("", &err);
+ validate("", &err);
g_assert_nonnull(err);
g_assert_true(sc_error_match
(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
@@ -104,7 +142,7 @@ static void test_sc_snap_name_validate(void)
sc_error_free(err);
// Smoke test: leading dash
- sc_snap_name_validate("-foo", &err);
+ validate("-foo", &err);
g_assert_nonnull(err);
g_assert_true(sc_error_match
(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
@@ -113,7 +151,7 @@ static void test_sc_snap_name_validate(void)
sc_error_free(err);
// Smoke test: trailing dash
- sc_snap_name_validate("foo-", &err);
+ validate("foo-", &err);
g_assert_nonnull(err);
g_assert_true(sc_error_match
(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
@@ -122,7 +160,7 @@ static void test_sc_snap_name_validate(void)
sc_error_free(err);
// Smoke test: double dash
- sc_snap_name_validate("f--oo", &err);
+ validate("f--oo", &err);
g_assert_nonnull(err);
g_assert_true(sc_error_match
(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
@@ -131,11 +169,22 @@ static void test_sc_snap_name_validate(void)
sc_error_free(err);
// Smoke test: NULL name is not valid
- sc_snap_name_validate(NULL, &err);
+ validate(NULL, &err);
g_assert_nonnull(err);
- g_assert_true(sc_error_match
- (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
- g_assert_cmpstr(sc_error_msg(err), ==, "snap name cannot be NULL");
+ // the only case when instance name validation diverges from snap name
+ // validation
+ if (!is_instance) {
+ g_assert_true(sc_error_match
+ (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
+ g_assert_cmpstr(sc_error_msg(err), ==,
+ "snap name cannot be NULL");
+ } else {
+ g_assert_true(sc_error_match
+ (err, SC_SNAP_DOMAIN,
+ SC_SNAP_INVALID_INSTANCE_NAME));
+ g_assert_cmpstr(sc_error_msg(err), ==,
+ "snap instance name cannot be NULL");
+ }
sc_error_free(err);
const char *valid_names[] = {
@@ -146,7 +195,7 @@ static void test_sc_snap_name_validate(void)
};
for (size_t i = 0; i < sizeof valid_names / sizeof *valid_names; ++i) {
g_test_message("checking valid snap name: %s", valid_names[i]);
- sc_snap_name_validate(valid_names[i], &err);
+ validate(valid_names[i], &err);
g_assert_null(err);
}
const char *invalid_names[] = {
@@ -175,16 +224,16 @@ static void test_sc_snap_name_validate(void)
++i) {
g_test_message("checking invalid snap name: >%s<",
invalid_names[i]);
- sc_snap_name_validate(invalid_names[i], &err);
+ validate(invalid_names[i], &err);
g_assert_nonnull(err);
g_assert_true(sc_error_match
(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
sc_error_free(err);
}
// Regression test: 12to8 and 123test
- sc_snap_name_validate("12to8", &err);
+ validate("12to8", &err);
g_assert_null(err);
- sc_snap_name_validate("123test", &err);
+ validate("123test", &err);
g_assert_null(err);
// In case we switch to a regex, here's a test that could break things.
@@ -194,7 +243,7 @@ static void test_sc_snap_name_validate(void)
g_assert_nonnull(memcpy(varname, good_bad_name, i));
varname[i] = 0;
g_test_message("checking valid snap name: >%s<", varname);
- sc_snap_name_validate(varname, &err);
+ validate(varname, &err);
g_assert_null(err);
sc_error_free(err);
}
@@ -214,6 +263,94 @@ static void test_sc_snap_name_validate__respects_error_protocol(void)
("snap name must use lower case letters, digits or dashes\n");
}
+static void test_sc_instance_name_validate(void)
+{
+ struct sc_error *err = NULL;
+
+ sc_instance_name_validate("hello-world", &err);
+ g_assert_null(err);
+ sc_instance_name_validate("hello-world_foo", &err);
+ g_assert_null(err);
+
+ // just the separator
+ sc_instance_name_validate("_", &err);
+ g_assert_nonnull(err);
+ g_assert_true(sc_error_match
+ (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
+ g_assert_cmpstr(sc_error_msg(err), ==,
+ "snap name must contain at least one letter");
+ sc_error_free(err);
+
+ // just name, with separator, missing instance key
+ sc_instance_name_validate("hello-world_", &err);
+ g_assert_nonnull(err);
+ g_assert_true(sc_error_match
+ (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_KEY));
+ g_assert_cmpstr(sc_error_msg(err), ==,
+ "instance key must contain at least one letter or digit");
+ sc_error_free(err);
+
+ // only separator and instance key, missing name
+ sc_instance_name_validate("_bar", &err);
+ g_assert_nonnull(err);
+ g_assert_true(sc_error_match
+ (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
+ g_assert_cmpstr(sc_error_msg(err), ==,
+ "snap name must contain at least one letter");
+ sc_error_free(err);
+
+ sc_instance_name_validate("", &err);
+ g_assert_nonnull(err);
+ g_assert_true(sc_error_match
+ (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
+ g_assert_cmpstr(sc_error_msg(err), ==,
+ "snap name must contain at least one letter");
+ sc_error_free(err);
+
+ // third separator
+ sc_instance_name_validate("foo_bar_baz", &err);
+ g_assert_nonnull(err);
+ g_assert_true(sc_error_match
+ (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_NAME));
+ g_assert_cmpstr(sc_error_msg(err), ==,
+ "snap instance name can contain only one underscore");
+ sc_error_free(err);
+
+ const char *valid_names[] = {
+ "a", "aa", "aaa", "aaaa",
+ "a_a", "aa_1", "a_123", "a_0123456789",
+ };
+ for (size_t i = 0; i < sizeof valid_names / sizeof *valid_names; ++i) {
+ g_test_message("checking valid instance name: %s",
+ valid_names[i]);
+ sc_instance_name_validate(valid_names[i], &err);
+ g_assert_null(err);
+ }
+ const char *invalid_names[] = {
+ // only letters and digits in the instance key
+ "a_--23))", "a_ ", "a_091234#", "a_123_456",
+ // up to 10 characters for the instance key
+ "a_01234567891", "a_0123456789123",
+ // snap name must not be more than 40 characters, regardless of instance
+ // key
+ "01234567890123456789012345678901234567890_foobar",
+ "01234567890123456789-01234567890123456789_foobar",
+ // instance key must be plain ASCII
+ "foobar_日本語",
+ // way too many underscores
+ "foobar_baz_zed_daz",
+ "foobar______",
+ };
+ for (size_t i = 0; i < sizeof invalid_names / sizeof *invalid_names;
+ ++i) {
+ g_test_message("checking invalid instance name: >%s<",
+ invalid_names[i]);
+ sc_instance_name_validate(invalid_names[i], &err);
+ g_assert_nonnull(err);
+ sc_error_free(err);
+ }
+}
+
static void test_sc_snap_drop_instance_key_no_dest(void)
{
if (g_test_subprocess()) {
@@ -392,10 +529,21 @@ static void test_sc_snap_split_instance_name_basic(void)
static void __attribute__ ((constructor)) init(void)
{
g_test_add_func("/snap/verify_security_tag", test_verify_security_tag);
- g_test_add_func("/snap/sc_snap_name_validate",
- test_sc_snap_name_validate);
+ g_test_add_func("/snap/sc_is_hook_security_tag",
+ test_sc_is_hook_security_tag);
+
+ g_test_add_data_func("/snap/sc_snap_name_validate",
+ sc_snap_name_validate,
+ test_sc_snap_or_instance_name_validate);
g_test_add_func("/snap/sc_snap_name_validate/respects_error_protocol",
test_sc_snap_name_validate__respects_error_protocol);
+
+ g_test_add_data_func("/snap/sc_instance_name_validate/just_name",
+ sc_instance_name_validate,
+ test_sc_snap_or_instance_name_validate);
+ g_test_add_func("/snap/sc_instance_name_validate/full",
+ test_sc_instance_name_validate);
+
g_test_add_func("/snap/sc_snap_drop_instance_key/basic",
test_sc_snap_drop_instance_key_basic);
g_test_add_func("/snap/sc_snap_drop_instance_key/no_dest",
@@ -406,6 +554,7 @@ static void __attribute__ ((constructor)) init(void)
test_sc_snap_drop_instance_key_short_dest);
g_test_add_func("/snap/sc_snap_drop_instance_key/short_dest2",
test_sc_snap_drop_instance_key_short_dest2);
+
g_test_add_func("/snap/sc_snap_split_instance_name/basic",
test_sc_snap_split_instance_name_basic);
g_test_add_func("/snap/sc_snap_split_instance_name/trailing_nil",
diff --git a/cmd/libsnap-confine-private/snap.c b/cmd/libsnap-confine-private/snap.c
index 8619d7849f..f4fb8507f9 100644
--- a/cmd/libsnap-confine-private/snap.c
+++ b/cmd/libsnap-confine-private/snap.c
@@ -22,6 +22,7 @@
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
+#include <ctype.h>
#include "utils.h"
#include "string-utils.h"
@@ -30,7 +31,7 @@
bool verify_security_tag(const char *security_tag, const char *snap_name)
{
const char *whitelist_re =
- "^snap\\.([a-z0-9](-?[a-z0-9])*)\\.([a-zA-Z0-9](-?[a-zA-Z0-9])*|hook\\.[a-z](-?[a-z])*)$";
+ "^snap\\.([a-z0-9](-?[a-z0-9])*(_[a-z0-9]{1,10})?)\\.([a-zA-Z0-9](-?[a-zA-Z0-9])*|hook\\.[a-z](-?[a-z])*)$";
regex_t re;
if (regcomp(&re, whitelist_re, REG_EXTENDED) != 0)
die("can not compile regex %s", whitelist_re);
@@ -56,7 +57,7 @@ bool verify_security_tag(const char *security_tag, const char *snap_name)
bool sc_is_hook_security_tag(const char *security_tag)
{
const char *whitelist_re =
- "^snap\\.[a-z](-?[a-z0-9])*\\.(hook\\.[a-z](-?[a-z])*)$";
+ "^snap\\.[a-z](-?[a-z0-9])*(_[a-z0-9]{1,10})?\\.(hook\\.[a-z](-?[a-z])*)$";
regex_t re;
if (regcomp(&re, whitelist_re, REG_EXTENDED | REG_NOSUB) != 0)
@@ -97,6 +98,94 @@ static int skip_one_char(const char **p, char c)
return 0;
}
+void sc_instance_name_validate(const char *instance_name,
+ struct sc_error **errorp)
+{
+ // NOTE: This function should be synchronized with the two other
+ // implementations: validate_instance_name and snap.ValidateInstanceName.
+ struct sc_error *err = NULL;
+
+ // Ensure that name is not NULL
+ if (instance_name == NULL) {
+ err =
+ sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_NAME,
+ "snap instance name cannot be NULL");
+ goto out;
+ }
+ // 40 char snap_name + '_' + 10 char instance_key + 1 extra overflow + 1
+ // NULL
+ char s[53] = { 0 };
+ strncpy(s, instance_name, sizeof(s) - 1);
+
+ char *t = s;
+ const char *snap_name = strsep(&t, "_");
+ const char *instance_key = strsep(&t, "_");
+ const char *third_separator = strsep(&t, "_");
+ if (third_separator != NULL) {
+ err =
+ sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_NAME,
+ "snap instance name can contain only one underscore");
+ goto out;
+ }
+
+ sc_snap_name_validate(snap_name, &err);
+ if (err != NULL) {
+ goto out;
+ }
+ // When the instance_name is a normal snap name, instance_key will be
+ // NULL, so only validate instance_key when we found one.
+ if (instance_key != NULL) {
+ sc_instance_key_validate(instance_key, &err);
+ }
+
+ out:
+ sc_error_forward(errorp, err);
+}
+
+void sc_instance_key_validate(const char *instance_key,
+ struct sc_error **errorp)
+{
+ // NOTE: see snap.ValidateInstanceName for reference of a valid instance key
+ // format
+ struct sc_error *err = NULL;
+
+ // Ensure that name is not NULL
+ if (instance_key == NULL) {
+ err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME,
+ "instance key cannot be NULL");
+ goto out;
+ }
+ // This is a regexp-free routine hand-coding the following pattern:
+ //
+ // "^[a-z]{1,10}$"
+ //
+ // The only motivation for not using regular expressions is so that we don't
+ // run untrusted input against a potentially complex regular expression
+ // engine.
+ int i = 0;
+ for (i = 0; instance_key[i] != '\0'; i++) {
+ if (islower(instance_key[i]) || isdigit(instance_key[i])) {
+ continue;
+ }
+ err =
+ sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_KEY,
+ "instance key must use lower case letters or digits");
+ goto out;
+ }
+
+ if (i == 0) {
+ err =
+ sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_KEY,
+ "instance key must contain at least one letter or digit");
+ } else if (i > 10) {
+ err =
+ sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_KEY,
+ "instance key must be shorter than 10 characters");
+ }
+ out:
+ sc_error_forward(errorp, err);
+}
+
void sc_snap_name_validate(const char *snap_name, struct sc_error **errorp)
{
// NOTE: This function should be synchronized with the two other
diff --git a/cmd/libsnap-confine-private/snap.h b/cmd/libsnap-confine-private/snap.h
index ef694c83d7..49851289f2 100644
--- a/cmd/libsnap-confine-private/snap.h
+++ b/cmd/libsnap-confine-private/snap.h
@@ -31,6 +31,10 @@
enum {
/** The name of the snap is not valid. */
SC_SNAP_INVALID_NAME = 1,
+ /** The instance key of the snap is not valid. */
+ SC_SNAP_INVALID_INSTANCE_KEY = 2,
+ /** The instance of the snap is not valid. */
+ SC_SNAP_INVALID_INSTANCE_NAME = 3,
};
/**
@@ -45,6 +49,31 @@ enum {
void sc_snap_name_validate(const char *snap_name, struct sc_error **errorp);
/**
+ * Validate the given instance key.
+ *
+ * Valid instance key cannot be NULL and must match a regular expression
+ * describing the strict naming requirements. Please refer to snapd source code
+ * for details.
+ *
+ * The error protocol is observed so if the caller doesn't provide an outgoing
+ * error pointer the function will die on any error.
+ **/
+void sc_instance_key_validate(const char *instance_key,
+ struct sc_error **errorp);
+
+/**
+ * Validate the given snap instance name.
+ *
+ * Valid instance name must be composed of a valid snap name and a valid
+ * instance key.
+ *
+ * The error protocol is observed so if the caller doesn't provide an outgoing
+ * error pointer the function will die on any error.
+ **/
+void sc_instance_name_validate(const char *instance_name,
+ struct sc_error **errorp);
+
+/**
* Validate security tag against strict naming requirements and snap name.
*
* The executable name is of form:
diff --git a/cmd/snap-exec/main.go b/cmd/snap-exec/main.go
index 8a5c865af7..a8e640e787 100644
--- a/cmd/snap-exec/main.go
+++ b/cmd/snap-exec/main.go
@@ -39,8 +39,9 @@ var syscallExec = syscall.Exec
// commandline args
var opts struct {
- Command string `long:"command" description:"use a different command like {stop,post-stop} from the app"`
- Hook string `long:"hook" description:"hook to run" hidden:"yes"`
+ Command string `long:"command" description:"use a different command like {stop,post-stop} from the app"`
+ SkipCommandChain bool `long:"skip-command-chain" description:"do not run command chain"`
+ Hook string `long:"hook" description:"hook to run" hidden:"yes"`
}
func init() {
@@ -92,7 +93,7 @@ func run() error {
return execHook(snapApp, revision, opts.Hook)
}
- return execApp(snapApp, revision, opts.Command, extraArgs)
+ return execApp(snapApp, revision, opts.Command, extraArgs, opts.SkipCommandChain)
}
const defaultShell = "/bin/bash"
@@ -124,6 +125,17 @@ func findCommand(app *snap.AppInfo, command string) (string, error) {
return cmd, nil
}
+func commandChain(app *snap.AppInfo) []string {
+ chain := make([]string, 0, len(app.CommandChain))
+ snapMountDir := app.Snap.MountDir()
+
+ for _, element := range app.CommandChain {
+ chain = append(chain, filepath.Join(snapMountDir, element))
+ }
+
+ return chain
+}
+
// expandEnvCmdArgs takes the string list of commandline arguments
// and expands any $VAR with the given var from the env argument.
func expandEnvCmdArgs(args []string, env map[string]string) []string {
@@ -139,7 +151,7 @@ func expandEnvCmdArgs(args []string, env map[string]string) []string {
return cmdArgs
}
-func execApp(snapApp, revision, command string, args []string) error {
+func execApp(snapApp, revision, command string, args []string, skipCommandChain bool) error {
rev, err := snap.ParseRevision(revision)
if err != nil {
return fmt.Errorf("cannot parse revision %q: %s", revision, err)
@@ -200,6 +212,11 @@ func execApp(snapApp, revision, command string, args []string) error {
}
fullCmd = append(fullCmd, cmdArgs...)
fullCmd = append(fullCmd, args...)
+
+ if !skipCommandChain {
+ fullCmd = append(commandChain(app), fullCmd...)
+ }
+
if err := syscallExec(fullCmd[0], fullCmd, env); err != nil {
return fmt.Errorf("cannot exec %q: %s", fullCmd[0], err)
}
diff --git a/cmd/snap-exec/main_test.go b/cmd/snap-exec/main_test.go
index 879f5d87dd..c1d678adbd 100644
--- a/cmd/snap-exec/main_test.go
+++ b/cmd/snap-exec/main_test.go
@@ -65,6 +65,11 @@ apps:
BASE_PATH: /some/path
LD_LIBRARY_PATH: ${BASE_PATH}/lib
MY_PATH: $PATH
+ app2:
+ command: run-app2
+ stop-command: stop-app2
+ post-stop-command: post-stop-app2
+ command-chain: [chain1, chain2]
nostop:
command: nostop
`)
@@ -146,7 +151,7 @@ func (s *snapExecSuite) TestSnapExecAppIntegration(c *C) {
defer restore()
// launch and verify its run the right way
- err := snapExec.ExecApp("snapname.app", "42", "stop", []string{"arg1", "arg2"})
+ err := snapExec.ExecApp("snapname.app", "42", "stop", []string{"arg1", "arg2"}, false)
c.Assert(err, IsNil)
c.Check(execArgv0, Equals, fmt.Sprintf("%s/snapname/42/stop-app", dirs.SnapMountDir))
c.Check(execArgs, DeepEquals, []string{execArgv0, "arg1", "arg2"})
@@ -155,6 +160,58 @@ func (s *snapExecSuite) TestSnapExecAppIntegration(c *C) {
c.Check(execEnv, testutil.Contains, fmt.Sprintf("MY_PATH=%s", os.Getenv("PATH")))
}
+func (s *snapExecSuite) TestSnapExecAppCommandChainIntegration(c *C) {
+ dirs.SetRootDir(c.MkDir())
+ snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{
+ Revision: snap.R("42"),
+ })
+
+ execArgv0 := ""
+ execArgs := []string{}
+ restore := snapExec.MockSyscallExec(func(argv0 string, argv []string, env []string) error {
+ execArgv0 = argv0
+ execArgs = argv
+ return nil
+ })
+ defer restore()
+
+ chain1_path := fmt.Sprintf("%s/snapname/42/chain1", dirs.SnapMountDir)
+ chain2_path := fmt.Sprintf("%s/snapname/42/chain2", dirs.SnapMountDir)
+ app_path := fmt.Sprintf("%s/snapname/42/run-app2", dirs.SnapMountDir)
+ stop_path := fmt.Sprintf("%s/snapname/42/stop-app2", dirs.SnapMountDir)
+ post_stop_path := fmt.Sprintf("%s/snapname/42/post-stop-app2", dirs.SnapMountDir)
+
+ for _, t := range []struct {
+ cmd string
+ args []string
+ skipCommandChain bool
+ expected []string
+ }{
+ // Normal command
+ {expected: []string{chain1_path, chain2_path, app_path}},
+ {skipCommandChain: true, expected: []string{app_path}},
+ {args: []string{"arg1", "arg2"}, expected: []string{chain1_path, chain2_path, app_path, "arg1", "arg2"}},
+ {args: []string{"arg1", "arg2"}, skipCommandChain: true, expected: []string{app_path, "arg1", "arg2"}},
+
+ // Stop command
+ {cmd: "stop", expected: []string{chain1_path, chain2_path, stop_path}},
+ {cmd: "stop", skipCommandChain: true, expected: []string{stop_path}},
+ {cmd: "stop", args: []string{"arg1", "arg2"}, expected: []string{chain1_path, chain2_path, stop_path, "arg1", "arg2"}},
+ {cmd: "stop", args: []string{"arg1", "arg2"}, skipCommandChain: true, expected: []string{stop_path, "arg1", "arg2"}},
+
+ // Post-stop command
+ {cmd: "post-stop", expected: []string{chain1_path, chain2_path, post_stop_path}},
+ {cmd: "post-stop", skipCommandChain: true, expected: []string{post_stop_path}},
+ {cmd: "post-stop", args: []string{"arg1", "arg2"}, expected: []string{chain1_path, chain2_path, post_stop_path, "arg1", "arg2"}},
+ {cmd: "post-stop", args: []string{"arg1", "arg2"}, skipCommandChain: true, expected: []string{post_stop_path, "arg1", "arg2"}},
+ } {
+ err := snapExec.ExecApp("snapname.app2", "42", t.cmd, t.args, t.skipCommandChain)
+ c.Assert(err, IsNil)
+ c.Check(execArgv0, Equals, t.expected[0])
+ c.Check(execArgs, DeepEquals, t.expected)
+ }
+}
+
func (s *snapExecSuite) TestSnapExecHookIntegration(c *C) {
dirs.SetRootDir(c.MkDir())
snaptest.MockSnap(c, string(mockHookYaml), &snap.SideInfo{
@@ -306,11 +363,25 @@ func (s *snapExecSuite) TestSnapExecShellIntegration(c *C) {
defer restore()
// launch and verify its run the right way
- err := snapExec.ExecApp("snapname.app", "42", "shell", []string{"-c", "echo foo"})
+ err := snapExec.ExecApp("snapname.app", "42", "shell", []string{"-c", "echo foo"}, false)
c.Assert(err, IsNil)
c.Check(execArgv0, Equals, "/bin/bash")
c.Check(execArgs, DeepEquals, []string{execArgv0, "-c", "echo foo"})
c.Check(execEnv, testutil.Contains, "LD_LIBRARY_PATH=/some/path/lib")
+
+ // launch and verify shell still runs the command chain
+ err = snapExec.ExecApp("snapname.app2", "42", "shell", []string{"-c", "echo foo"}, false)
+ c.Assert(err, IsNil)
+ chain1 := fmt.Sprintf("%s/snapname/42/chain1", dirs.SnapMountDir)
+ chain2 := fmt.Sprintf("%s/snapname/42/chain2", dirs.SnapMountDir)
+ c.Check(execArgv0, Equals, chain1)
+ c.Check(execArgs, DeepEquals, []string{chain1, chain2, "/bin/bash", "-c", "echo foo"})
+
+ // also verify that it supports skipping the command chain
+ err = snapExec.ExecApp("snapname.app2", "42", "shell", []string{"-c", "echo foo"}, true)
+ c.Assert(err, IsNil)
+ c.Check(execArgv0, Equals, "/bin/bash")
+ c.Check(execArgs, DeepEquals, []string{execArgv0, "-c", "echo foo"})
}
func (s *snapExecSuite) TestSnapExecAppIntegrationWithVars(c *C) {
@@ -335,7 +406,7 @@ func (s *snapExecSuite) TestSnapExecAppIntegrationWithVars(c *C) {
defer os.Unsetenv("SNAP_DATA")
// launch and verify its run the right way
- err := snapExec.ExecApp("snapname.app", "42", "", []string{"user-arg1"})
+ err := snapExec.ExecApp("snapname.app", "42", "", []string{"user-arg1"}, false)
c.Assert(err, IsNil)
c.Check(execArgv0, Equals, fmt.Sprintf("%s/snapname/42/run-app", dirs.SnapMountDir))
c.Check(execArgs, DeepEquals, []string{execArgv0, "cmd-arg1", "/var/snap/snapname/42", "user-arg1"})
diff --git a/cmd/snap-update-ns/bootstrap.c b/cmd/snap-update-ns/bootstrap.c
index 1dbb7bbff1..98c78b8d8e 100644
--- a/cmd/snap-update-ns/bootstrap.c
+++ b/cmd/snap-update-ns/bootstrap.c
@@ -24,6 +24,7 @@
#include "bootstrap.h"
+#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
@@ -247,6 +248,82 @@ int validate_snap_name(const char* snap_name)
return 0;
}
+static int instance_key_validate(const char *instance_key)
+{
+ // NOTE: see snap.ValidateInstanceName for reference of a valid instance key
+ // format
+
+ // Ensure that name is not NULL
+ if (instance_key == NULL) {
+ bootstrap_msg = "instance key cannot be NULL";
+ return -1;
+ }
+
+ // This is a regexp-free routine hand-coding the following pattern:
+ //
+ // "^[a-z]{1,10}$"
+ //
+ // The only motivation for not using regular expressions is so that we don't
+ // run untrusted input against a potentially complex regular expression
+ // engine.
+ int i = 0;
+ for (i = 0; instance_key[i] != '\0'; i++) {
+ if (islower(instance_key[i]) || isdigit(instance_key[i])) {
+ continue;
+ }
+ bootstrap_msg = "instance key must use lower case letters or digits";
+ return -1;
+ }
+
+ if (i == 0) {
+ bootstrap_msg = "instance key must contain at least one letter or digit";
+ return -1;
+ } else if (i > 10) {
+ bootstrap_msg = "instance key must be shorter than 10 characters";
+ return -1;
+ }
+ return 0;
+}
+
+// validate_instance_name performs full validation of the given snap instance name.
+int validate_instance_name(const char* instance_name)
+{
+ // NOTE: This function should be synchronized with the two other
+ // implementations: sc_instance_name_validate and snap.ValidateInstanceName.
+
+ if (instance_name == NULL) {
+ bootstrap_msg = "snap instance name cannot be NULL";
+ return -1;
+ }
+
+ // 40 char snap_name + '_' + 10 char instance_key + 1 extra overflow + 1
+ // NULL
+ char s[53] = {0};
+ strncpy(s, instance_name, sizeof(s)-1);
+
+ char *t = s;
+ const char *snap_name = strsep(&t, "_");
+ const char *instance_key = strsep(&t, "_");
+ const char *third_separator = strsep(&t, "_");
+ if (third_separator != NULL) {
+ bootstrap_msg = "snap instance name can contain only one underscore";
+ return -1;
+ }
+
+ if (validate_snap_name(snap_name) < 0) {
+ return -1;
+ }
+
+ // When the instance_name is a normal snap name, instance_key will be
+ // NULL, so only validate instance_key when we found one.
+ if (instance_key != NULL && instance_key_validate(instance_key) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+
// process_arguments parses given a command line
// argc and argv are defined as for the main() function
void process_arguments(int argc, char *const *argv, const char** snap_name_out, bool* should_setns_out, bool* process_user_fstab)
@@ -312,11 +389,11 @@ void process_arguments(int argc, char *const *argv, const char** snap_name_out,
return;
}
- // Ensure that the snap name is valid so that we don't blindly setns into
+ // Ensure that the snap instance name is valid so that we don't blindly setns into
// something that is controlled by a potential attacker.
- if (validate_snap_name(snap_name) < 0) {
+ if (validate_instance_name(snap_name) < 0) {
bootstrap_errno = 0;
- // bootstap_msg is set by validate_snap_name;
+ // bootstap_msg is set by validate_instance_name;
return;
}
// We have a valid snap name now so let's store it.
diff --git a/cmd/snap-update-ns/bootstrap.go b/cmd/snap-update-ns/bootstrap.go
index 48a8ad4afb..3b937ee77f 100644
--- a/cmd/snap-update-ns/bootstrap.go
+++ b/cmd/snap-update-ns/bootstrap.go
@@ -73,6 +73,11 @@ func BootstrapError() error {
return fmt.Errorf("%s", C.GoString(C.bootstrap_msg))
}
+func clearBootstrapError() {
+ C.bootstrap_msg = nil
+ C.bootstrap_errno = 0
+}
+
// END IMPORTANT
func makeArgv(args []string) []*C.char {
@@ -90,12 +95,12 @@ func freeArgv(argv []*C.char) {
}
}
-// validateSnapName checks if snap name is valid.
+// validateInstanceName checks if snap instance name is valid.
// This also sets bootstrap_msg on failure.
-func validateSnapName(snapName string) int {
- cStr := C.CString(snapName)
+func validateInstanceName(instanceName string) int {
+ cStr := C.CString(instanceName)
defer C.free(unsafe.Pointer(cStr))
- return int(C.validate_snap_name(cStr))
+ return int(C.validate_instance_name(cStr))
}
// processArguments parses commnad line arguments.
diff --git a/cmd/snap-update-ns/bootstrap.h b/cmd/snap-update-ns/bootstrap.h
index ba2850704d..da9f40a544 100644
--- a/cmd/snap-update-ns/bootstrap.h
+++ b/cmd/snap-update-ns/bootstrap.h
@@ -28,6 +28,6 @@ extern const char* bootstrap_msg;
void bootstrap(int argc, char **argv, char **envp);
void process_arguments(int argc, char *const *argv, const char** snap_name_out, bool* should_setns_out, bool* process_user_fstab);
-int validate_snap_name(const char* snap_name);
+int validate_instance_name(const char* instance_name);
#endif
diff --git a/cmd/snap-update-ns/bootstrap_test.go b/cmd/snap-update-ns/bootstrap_test.go
index de2f5afb5b..7d97f38975 100644
--- a/cmd/snap-update-ns/bootstrap_test.go
+++ b/cmd/snap-update-ns/bootstrap_test.go
@@ -31,12 +31,31 @@ var _ = Suite(&bootstrapSuite{})
// Check that ValidateSnapName rejects "/" and "..".
func (s *bootstrapSuite) TestValidateSnapName(c *C) {
- c.Assert(update.ValidateSnapName("hello-world"), Equals, 0)
- c.Assert(update.ValidateSnapName("hello/world"), Equals, -1)
- c.Assert(update.ValidateSnapName("hello..world"), Equals, -1)
- c.Assert(update.ValidateSnapName("INVALID"), Equals, -1)
- c.Assert(update.ValidateSnapName("-invalid"), Equals, -1)
- c.Assert(update.ValidateSnapName(""), Equals, -1)
+ c.Assert(update.ValidateInstanceName("hello-world"), Equals, 0)
+ c.Assert(update.ValidateInstanceName("a123456789012345678901234567890123456789"), Equals, 0)
+ c.Assert(update.ValidateInstanceName("a123456789012345678901234567890123456789_0123456789"), Equals, 0)
+ c.Assert(update.ValidateInstanceName("a123456789012345678901234567890123456789_01234567890"), Equals, -1)
+ c.Assert(update.ValidateInstanceName("hello/world"), Equals, -1)
+ c.Assert(update.ValidateInstanceName("hello..world"), Equals, -1)
+ c.Assert(update.ValidateInstanceName("hello-world_foo"), Equals, 0)
+ c.Assert(update.ValidateInstanceName("foo_0123456789"), Equals, 0)
+ c.Assert(update.ValidateInstanceName("foo_1234abcd"), Equals, 0)
+ c.Assert(update.ValidateInstanceName("a123456789012345678901234567890123456789"), Equals, 0)
+ c.Assert(update.ValidateInstanceName("a123456789012345678901234567890123456789_0123456789"), Equals, 0)
+
+ c.Assert(update.ValidateInstanceName("INVALID"), Equals, -1)
+ c.Assert(update.ValidateInstanceName("-invalid"), Equals, -1)
+ c.Assert(update.ValidateInstanceName(""), Equals, -1)
+ c.Assert(update.ValidateInstanceName("hello-world_"), Equals, -1)
+ c.Assert(update.ValidateInstanceName("_foo"), Equals, -1)
+ c.Assert(update.ValidateInstanceName("foo_01234567890"), Equals, -1)
+ c.Assert(update.ValidateInstanceName("foo_123_456"), Equals, -1)
+ c.Assert(update.ValidateInstanceName("foo__456"), Equals, -1)
+ c.Assert(update.ValidateInstanceName("foo_"), Equals, -1)
+ c.Assert(update.ValidateInstanceName("hello-world_foo_foo"), Equals, -1)
+ c.Assert(update.ValidateInstanceName("foo01234567890012345678900123456789001234567890"), Equals, -1)
+ c.Assert(update.ValidateInstanceName("foo01234567890012345678900123456789001234567890_foo"), Equals, -1)
+ c.Assert(update.ValidateInstanceName("a123456789012345678901234567890123456789_0123456789_"), Equals, -1)
}
// Test various cases of command line handling.
@@ -56,6 +75,7 @@ func (s *bootstrapSuite) TestProcessArguments(c *C) {
{[]string{"argv0"}, "", false, false, "snap name not provided"},
// Snap name is parsed correctly.
{[]string{"argv0", "snapname"}, "snapname", true, false, ""},
+ {[]string{"argv0", "snapname_instance"}, "snapname_instance", true, false, ""},
// Onlye one snap name is allowed.
{[]string{"argv0", "snapone", "snaptwo"}, "", false, false, "too many positional arguments"},
// Snap name is validated correctly.
@@ -64,6 +84,8 @@ func (s *bootstrapSuite) TestProcessArguments(c *C) {
{[]string{"argv0", "invalid-"}, "", false, false, "snap name cannot end with a dash"},
{[]string{"argv0", "@invalid"}, "", false, false, "snap name must use lower case letters, digits or dashes"},
{[]string{"argv0", "INVALID"}, "", false, false, "snap name must use lower case letters, digits or dashes"},
+ {[]string{"argv0", "foo_01234567890"}, "", false, false, "instance key must be shorter than 10 characters"},
+ {[]string{"argv0", "foo_0123456_2"}, "", false, false, "snap instance name can contain only one underscore"},
// The option --from-snap-confine disables setns.
{[]string{"argv0", "--from-snap-confine", "snapname"}, "snapname", false, false, ""},
{[]string{"argv0", "snapname", "--from-snap-confine"}, "snapname", false, false, ""},
@@ -75,6 +97,7 @@ func (s *bootstrapSuite) TestProcessArguments(c *C) {
{[]string{"argv0", "--from-snap-confine", "-invalid", "snapname"}, "", false, false, "unsupported option"},
}
for _, tc := range cases {
+ update.ClearBootstrapError()
snapName, shouldSetNs, userFstab := update.ProcessArguments(tc.cmdline)
err := update.BootstrapError()
comment := Commentf("failed with cmdline %q, expected error pattern %q, actual error %q",
diff --git a/cmd/snap-update-ns/export_test.go b/cmd/snap-update-ns/export_test.go
index fcff72d559..e3ba02a6ab 100644
--- a/cmd/snap-update-ns/export_test.go
+++ b/cmd/snap-update-ns/export_test.go
@@ -30,8 +30,8 @@ import (
var (
// change
- ValidateSnapName = validateSnapName
- ProcessArguments = processArguments
+ ValidateInstanceName = validateInstanceName
+ ProcessArguments = processArguments
// freezer
FreezeSnapProcesses = freezeSnapProcesses
ThawSnapProcesses = thawSnapProcesses
@@ -42,6 +42,9 @@ var (
// main
ComputeAndSaveChanges = computeAndSaveChanges
ApplyUserFstab = applyUserFstab
+
+ // bootstrap
+ ClearBootstrapError = clearBootstrapError
)
// SystemCalls encapsulates various system interactions performed by this module.
diff --git a/cmd/snap/cmd_get.go b/cmd/snap/cmd_get.go
index 0a9e869260..11082f63f7 100644
--- a/cmd/snap/cmd_get.go
+++ b/cmd/snap/cmd_get.go
@@ -22,14 +22,12 @@ package main
import (
"encoding/json"
"fmt"
- "os"
"sort"
"strings"
"github.com/jessevdk/go-flags"
"github.com/snapcore/snapd/i18n"
- "golang.org/x/crypto/ssh/terminal"
)
var shortGetHelp = i18n.G("Print configuration options")
@@ -176,10 +174,6 @@ func (x *cmdGet) outputList(conf map[string]interface{}) error {
return nil
}
-var isTerminal = func() bool {
- return terminal.IsTerminal(int(os.Stdin.Fd()))
-}
-
// outputDefault will be used when no commandline switch to override the
// output where used. The output follows the following rules:
// - a single key with a string value is printed directly
@@ -204,7 +198,7 @@ func (x *cmdGet) outputDefault(conf map[string]interface{}, snapName string, con
// conf looks like a map
if cfg, ok := confToPrint.(map[string]interface{}); ok {
- if isTerminal() {
+ if isStdinTTY {
return x.outputList(cfg)
}
diff --git a/cmd/snap/cmd_get_test.go b/cmd/snap/cmd_get_test.go
index 251ffd5ecc..6ab143ff09 100644
--- a/cmd/snap/cmd_get_test.go
+++ b/cmd/snap/cmd_get_test.go
@@ -109,7 +109,7 @@ func (s *SnapSuite) runTests(cmds []getCmdArgs, c *C) {
c.Logf("Test: %s", test.args)
- restore := snapset.MockIsTerminal(test.isTerminal)
+ restore := snapset.MockIsStdinTTY(test.isTerminal)
defer restore()
_, err := snapset.Parser().ParseArgs(strings.Fields(test.args))
diff --git a/cmd/snap/cmd_run.go b/cmd/snap/cmd_run.go
index 0006fd4d0a..b30d62fae6 100644
--- a/cmd/snap/cmd_run.go
+++ b/cmd/snap/cmd_run.go
@@ -55,10 +55,12 @@ var (
)
type cmdRun struct {
- Command string `long:"command" hidden:"yes"`
- HookName string `long:"hook" hidden:"yes"`
- Revision string `short:"r" default:"unset" hidden:"yes"`
- Shell bool `long:"shell" `
+ Command string `long:"command" hidden:"yes"`
+ HookName string `long:"hook" hidden:"yes"`
+ Revision string `short:"r" default:"unset" hidden:"yes"`
+ Shell bool `long:"shell" `
+ SkipCommandChain bool `long:"skip-command-chain"`
+
// This options is both a selector (use or don't use strace) and it
// can also carry extra options for strace. This is why there is
// "default" and "optional-value" to distinguish this.
@@ -81,14 +83,15 @@ and environment.
func() flags.Commander {
return &cmdRun{}
}, map[string]string{
- "command": i18n.G("Alternative command to run"),
- "hook": i18n.G("Hook to run"),
- "r": i18n.G("Use a specific snap revision when running hook"),
- "shell": i18n.G("Run a shell instead of the command (useful for debugging)"),
- "strace": i18n.G("Run the command under strace (useful for debugging). Extra strace options can be specified as well here. Pass --raw to strace early snap helpers."),
- "gdb": i18n.G("Run the command with gdb"),
- "timer": i18n.G("Run as a timer service with given schedule"),
- "parser-ran": "",
+ "command": i18n.G("Alternative command to run"),
+ "hook": i18n.G("Hook to run"),
+ "r": i18n.G("Use a specific snap revision when running hook"),
+ "shell": i18n.G("Run a shell instead of the command (useful for debugging)"),
+ "skip-command-chain": i18n.G("Do not run the command chain (useful for debugging)"),
+ "strace": i18n.G("Run the command under strace (useful for debugging). Extra strace options can be specified as well here. Pass --raw to strace early snap helpers."),
+ "gdb": i18n.G("Run the command with gdb"),
+ "timer": i18n.G("Run as a timer service with given schedule"),
+ "parser-ran": "",
}, nil)
}
@@ -749,6 +752,9 @@ func (x *cmdRun) runSnapConfine(info *snap.Info, securityTag, snapApp, hook stri
if x.Command != "" {
cmd = append(cmd, "--command="+x.Command)
}
+ if x.SkipCommandChain {
+ cmd = append(cmd, "--skip-command-chain")
+ }
if hook != "" {
cmd = append(cmd, "--hook="+hook)
diff --git a/cmd/snap/cmd_run_test.go b/cmd/snap/cmd_run_test.go
index 234076045a..4f7012abb6 100644
--- a/cmd/snap/cmd_run_test.go
+++ b/cmd/snap/cmd_run_test.go
@@ -136,6 +136,39 @@ func (s *SnapSuite) TestSnapRunAppIntegration(c *check.C) {
c.Check(execEnv, testutil.Contains, "SNAP_REVISION=x2")
}
+func (s *SnapSuite) TestSnapRunAppIntegrationSkipCommandChain(c *check.C) {
+ defer mockSnapConfine(dirs.DistroLibExecDir)()
+
+ // mock installed snap
+ snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{
+ Revision: snap.R("x2"),
+ })
+
+ // redirect exec
+ execArg0 := ""
+ execArgs := []string{}
+ execEnv := []string{}
+ restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error {
+ execArg0 = arg0
+ execArgs = args
+ execEnv = envv
+ return nil
+ })
+ defer restorer()
+
+ // and run it!
+ rest, err := snaprun.Parser().ParseArgs([]string{"run", "--skip-command-chain", "snapname.app", "--arg1", "arg2"})
+ c.Assert(err, check.IsNil)
+ c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"})
+ c.Check(execArg0, check.Equals, filepath.Join(dirs.DistroLibExecDir, "snap-confine"))
+ c.Check(execArgs, check.DeepEquals, []string{
+ filepath.Join(dirs.DistroLibExecDir, "snap-confine"),
+ "snap.snapname.app",
+ filepath.Join(dirs.CoreLibExecDir, "snap-exec"),
+ "--skip-command-chain", "snapname.app", "--arg1", "arg2"})
+ c.Check(execEnv, testutil.Contains, "SNAP_REVISION=x2")
+}
+
func (s *SnapSuite) TestSnapRunClassicAppIntegration(c *check.C) {
defer mockSnapConfine(dirs.DistroLibExecDir)()
diff --git a/cmd/snap/cmd_snap_op.go b/cmd/snap/cmd_snap_op.go
index e3043ca26b..79b4903ef0 100644
--- a/cmd/snap/cmd_snap_op.go
+++ b/cmd/snap/cmd_snap_op.go
@@ -466,6 +466,15 @@ func (x *cmdInstall) Execute([]string) error {
x.setModes(opts)
names := remoteSnapNames(x.Positional.Snaps)
+ if len(names) == 0 {
+ return errors.New(i18n.G("cannot install zero snaps"))
+ }
+ for _, name := range names {
+ if len(name) == 0 {
+ return errors.New(i18n.G("cannot install snap with empty name"))
+ }
+ }
+
if len(names) == 1 {
return x.installOne(names[0], x.Name, opts)
}
diff --git a/cmd/snap/cmd_snap_op_test.go b/cmd/snap/cmd_snap_op_test.go
index 156ff3c5b2..e84bbfcd1f 100644
--- a/cmd/snap/cmd_snap_op_test.go
+++ b/cmd/snap/cmd_snap_op_test.go
@@ -1468,6 +1468,15 @@ func (s *SnapOpSuite) TestInstallMany(c *check.C) {
c.Check(n, check.Equals, total)
}
+func (s *SnapOpSuite) TestInstallZeroEmpty(c *check.C) {
+ _, err := snap.Parser().ParseArgs([]string{"install"})
+ c.Assert(err, check.ErrorMatches, "cannot install zero snaps")
+ _, err = snap.Parser().ParseArgs([]string{"install", ""})
+ c.Assert(err, check.ErrorMatches, "cannot install snap with empty name")
+ _, err = snap.Parser().ParseArgs([]string{"install", "", "bar"})
+ c.Assert(err, check.ErrorMatches, "cannot install snap with empty name")
+}
+
func (s *SnapOpSuite) TestNoWait(c *check.C) {
s.srv.checker = func(r *http.Request) {}
diff --git a/cmd/snap/cmd_wait.go b/cmd/snap/cmd_wait.go
index 12b3f6a1cc..069469ea0d 100644
--- a/cmd/snap/cmd_wait.go
+++ b/cmd/snap/cmd_wait.go
@@ -22,6 +22,7 @@ package main
import (
"encoding/json"
"fmt"
+ "math/rand"
"reflect"
"time"
@@ -34,7 +35,7 @@ import (
type cmdWait struct {
Positional struct {
Snap installedSnapName `required:"yes"`
- Key string `required:"yes"`
+ Key string
} `positional-args:"yes"`
}
@@ -115,6 +116,25 @@ func (x *cmdWait) Execute(args []string) error {
snapName := string(x.Positional.Snap)
confKey := x.Positional.Key
+ // This is fine because not providing a confKey is unsupported so this
+ // won't interfere with supported uses of `snap wait`.
+ if snapName == "godot" && confKey == "" {
+ switch rand.Intn(10) {
+ case 0:
+ fmt.Fprintln(Stdout, `The tears of the world are a constant quantity.
+For each one who begins to weep somewhere else another stops.
+The same is true of the laugh.`)
+ case 1:
+ fmt.Fprintln(Stdout, "Nothing happens. Nobody comes, nobody goes. It's awful.")
+ default:
+ fmt.Fprintln(Stdout, `"Let's go." "We can't." "Why not?" "We're waiting for Godot."`)
+ }
+ return nil
+ }
+ if confKey == "" {
+ return fmt.Errorf("the required argument `<key>` was not provided")
+ }
+
cli := Client()
for {
conf, err := cli.Conf(snapName, []string{confKey})
diff --git a/cmd/snap/color.go b/cmd/snap/color.go
index 4b6676706f..c01109fb41 100644
--- a/cmd/snap/color.go
+++ b/cmd/snap/color.go
@@ -71,8 +71,7 @@ func canUnicode(mode string) bool {
return strings.Contains(lang, "UTF-8") || strings.Contains(lang, "UTF8")
}
-// TODO: maybe unify isTTY (~3 calls just in cmd/snap) (but note stdout vs stdin)
-var isTTY = terminal.IsTerminal(1)
+var isStdoutTTY = terminal.IsTerminal(1)
func colorTable(mode string) escapes {
switch mode {
@@ -81,7 +80,7 @@ func colorTable(mode string) escapes {
case "never":
return noesc
}
- if !isTTY {
+ if !isStdoutTTY {
return noesc
}
if _, ok := os.LookupEnv("NO_COLOR"); ok {
diff --git a/cmd/snap/color_test.go b/cmd/snap/color_test.go
index 315830acfb..e8d391de9a 100644
--- a/cmd/snap/color_test.go
+++ b/cmd/snap/color_test.go
@@ -108,7 +108,7 @@ func (s *SnapSuite) TestColorTable(c *check.C) {
{isTTY: true, term: "linux-m", expected: cmdsnap.MonoColorTable, desc: "is a tty, but TERM=linux-m"},
{isTTY: true, term: "xterm-mono", expected: cmdsnap.MonoColorTable, desc: "is a tty, but TERM=xterm-mono"},
} {
- restoreIsTTY := cmdsnap.MockIsTTY(t.isTTY)
+ restoreIsTTY := cmdsnap.MockIsStdoutTTY(t.isTTY)
restoreEnv := setEnviron(map[string]string{"NO_COLOR": t.noColor, "TERM": t.term})
c.Check(cmdsnap.ColorTable("never"), check.DeepEquals, cmdsnap.NoEscColorTable, check.Commentf(t.desc))
c.Check(cmdsnap.ColorTable("always"), check.DeepEquals, cmdsnap.ColorColorTable, check.Commentf(t.desc))
diff --git a/cmd/snap/export_test.go b/cmd/snap/export_test.go
index 3ba5c9b24f..5dd634b0b0 100644
--- a/cmd/snap/export_test.go
+++ b/cmd/snap/export_test.go
@@ -144,19 +144,19 @@ func AssertTypeNameCompletion(match string) []flags.Completion {
return assertTypeName("").Complete(match)
}
-func MockIsTTY(t bool) (restore func()) {
- oldIsTTY := isTTY
- isTTY = t
+func MockIsStdoutTTY(t bool) (restore func()) {
+ oldIsStdoutTTY := isStdoutTTY
+ isStdoutTTY = t
return func() {
- isTTY = oldIsTTY
+ isStdoutTTY = oldIsStdoutTTY
}
}
-func MockIsTerminal(t bool) (restore func()) {
- oldIsTerminal := isTerminal
- isTerminal = func() bool { return t }
+func MockIsStdinTTY(t bool) (restore func()) {
+ oldIsStdinTTY := isStdinTTY
+ isStdinTTY = t
return func() {
- isTerminal = oldIsTerminal
+ isStdinTTY = oldIsStdinTTY
}
}
diff --git a/cmd/snap/main.go b/cmd/snap/main.go
index f6da78a05a..ae08f2214a 100644
--- a/cmd/snap/main.go
+++ b/cmd/snap/main.go
@@ -286,12 +286,14 @@ snaps on the system. Start with 'snap list' to see installed snaps.`)
return parser
}
+var isStdinTTY = terminal.IsTerminal(0)
+
// ClientConfig is the configuration of the Client used by all commands.
var ClientConfig = client.Config{
// we need the powerful snapd socket
Socket: dirs.SnapdSocket,
// Allow interactivity if we have a terminal
- Interactive: terminal.IsTerminal(0),
+ Interactive: isStdinTTY,
}
// Client returns a new client using ClientConfig as configuration.
diff --git a/cmd/snap/main_test.go b/cmd/snap/main_test.go
index 3785a1a1d8..052110e401 100644
--- a/cmd/snap/main_test.go
+++ b/cmd/snap/main_test.go
@@ -77,17 +77,20 @@ func (s *BaseSnapSuite) SetUpTest(c *C) {
s.AuthFile = filepath.Join(c.MkDir(), "json")
os.Setenv(TestAuthFileEnvKey, s.AuthFile)
- snapdsnap.MockSanitizePlugsSlots(func(snapInfo *snapdsnap.Info) {})
+ s.AddCleanup(snapdsnap.MockSanitizePlugsSlots(func(snapInfo *snapdsnap.Info) {}))
+ s.AddCleanup(interfaces.MockSystemKey(`
+{
+"build-id": "7a94e9736c091b3984bd63f5aebfc883c4d859e0",
+"apparmor-features": ["caps", "dbus"]
+}`))
err := os.MkdirAll(filepath.Dir(dirs.SnapSystemKeyFile), 0755)
c.Assert(err, IsNil)
err = interfaces.WriteSystemKey()
c.Assert(err, IsNil)
- interfaces.MockSystemKey(`
-{
-"build-id": "7a94e9736c091b3984bd63f5aebfc883c4d859e0",
-"apparmor-features": ["caps", "dbus"]
-}`)
+
+ s.AddCleanup(snap.MockIsStdoutTTY(false))
+ s.AddCleanup(snap.MockIsStdinTTY(false))
}
func (s *BaseSnapSuite) TearDownTest(c *C) {
diff --git a/daemon/api.go b/daemon/api.go
index 7eed38e743..3bfd1cc142 100644
--- a/daemon/api.go
+++ b/daemon/api.go
@@ -1013,6 +1013,11 @@ func verifySnapInstructions(inst *snapInstruction) error {
}
func snapInstallMany(inst *snapInstruction, st *state.State) (*snapInstructionResult, error) {
+ for _, name := range inst.Snaps {
+ if len(name) == 0 {
+ return nil, fmt.Errorf(i18n.G("cannot install snap with empty name"))
+ }
+ }
installed, tasksets, err := snapstateInstallMany(st, inst.Snaps, inst.userID)
if err != nil {
return nil, err
@@ -1038,6 +1043,10 @@ func snapInstallMany(inst *snapInstruction, st *state.State) (*snapInstructionRe
}
func snapInstall(inst *snapInstruction, st *state.State) (string, []*state.TaskSet, error) {
+ if len(inst.Snaps[0]) == 0 {
+ return "", nil, fmt.Errorf(i18n.G("cannot install snap with empty name"))
+ }
+
flags, err := inst.installFlags()
if err != nil {
return "", nil, err
@@ -1865,7 +1874,11 @@ func changeInterfaces(c *Command, r *http.Request, user *auth.UserState) Respons
}
for _, connRef := range conns {
var ts *state.TaskSet
- ts, err = ifacestate.Disconnect(st, connRef.PlugRef.Snap, connRef.PlugRef.Name, connRef.SlotRef.Snap, connRef.SlotRef.Name)
+ conn, err := repo.Connection(connRef)
+ if err != nil {
+ break
+ }
+ ts, err = ifacestate.Disconnect(st, conn)
if err != nil {
break
}
diff --git a/daemon/api_test.go b/daemon/api_test.go
index 62be32c14b..a78327f4cd 100644
--- a/daemon/api_test.go
+++ b/daemon/api_test.go
@@ -3516,6 +3516,20 @@ func (s *apiSuite) TestInstallMany(c *check.C) {
c.Check(res.affected, check.DeepEquals, inst.Snaps)
}
+func (s *apiSuite) TestInstallManyEmptyName(c *check.C) {
+ snapstateInstallMany = func(_ *state.State, _ []string, _ int) ([]string, []*state.TaskSet, error) {
+ return nil, nil, errors.New("should not be called")
+ }
+ d := s.daemon(c)
+ inst := &snapInstruction{Action: "install", Snaps: []string{"", "bar"}}
+ st := d.overlord.State()
+ st.Lock()
+ res, err := snapInstallMany(inst, st)
+ st.Unlock()
+ c.Assert(res, check.IsNil)
+ c.Assert(err, check.ErrorMatches, "cannot install snap with empty name")
+}
+
func (s *apiSuite) TestRemoveMany(c *check.C) {
snapstateRemoveMany = func(s *state.State, names []string) ([]string, []*state.TaskSet, error) {
c.Check(names, check.HasLen, 2)
@@ -3541,14 +3555,14 @@ func (s *apiSuite) TestInstallFails(c *check.C) {
}
d := s.daemonWithFakeSnapManager(c)
-
+ s.vars = map[string]string{"name": "hello-world"}
buf := bytes.NewBufferString(`{"action": "install"}`)
req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf)
c.Assert(err, check.IsNil)
rsp := postSnap(snapCmd, req, nil).(*resp)
- c.Check(rsp.Type, check.Equals, ResponseTypeAsync)
+ c.Assert(rsp.Type, check.Equals, ResponseTypeAsync)
st := d.overlord.State()
st.Lock()
@@ -3663,6 +3677,23 @@ func (s *apiSuite) TestInstallJailModeDevModeOS(c *check.C) {
c.Check(err, check.ErrorMatches, "this system cannot honour the jailmode flag")
}
+func (s *apiSuite) TestInstallEmptyName(c *check.C) {
+ snapstateInstall = func(_ *state.State, _, _ string, _ snap.Revision, _ int, _ snapstate.Flags) (*state.TaskSet, error) {
+ return nil, errors.New("should not be called")
+ }
+ d := s.daemon(c)
+ inst := &snapInstruction{
+ Action: "install",
+ Snaps: []string{""},
+ }
+
+ st := d.overlord.State()
+ st.Lock()
+ defer st.Unlock()
+ _, _, err := inst.dispatch()(inst, st)
+ c.Check(err, check.ErrorMatches, "cannot install snap with empty name")
+}
+
func (s *apiSuite) TestInstallJailModeDevMode(c *check.C) {
d := s.daemon(c)
inst := &snapInstruction{
@@ -4224,6 +4255,15 @@ func (s *apiSuite) testDisconnect(c *check.C, plugSnap, plugName, slotSnap, slot
_, err := repo.Connect(connRef, nil, nil, nil)
c.Assert(err, check.IsNil)
+ st := d.overlord.State()
+ st.Lock()
+ st.Set("conns", map[string]interface{}{
+ "consumer:plug producer:slot": map[string]interface{}{
+ "interface": "test",
+ },
+ })
+ st.Unlock()
+
d.overlord.Loop()
defer d.overlord.Stop()
@@ -4245,7 +4285,6 @@ func (s *apiSuite) testDisconnect(c *check.C, plugSnap, plugName, slotSnap, slot
c.Check(err, check.IsNil)
id := body["change"].(string)
- st := d.overlord.State()
st.Lock()
chg := st.Change(id)
st.Unlock()
@@ -4429,6 +4468,15 @@ func (s *apiSuite) TestDisconnectCoreSystemAlias(c *check.C) {
_, err := repo.Connect(connRef, nil, nil, nil)
c.Assert(err, check.IsNil)
+ st := d.overlord.State()
+ st.Lock()
+ st.Set("conns", map[string]interface{}{
+ "consumer:plug core:slot": map[string]interface{}{
+ "interface": "test",
+ },
+ })
+ st.Unlock()
+
d.overlord.Loop()
defer d.overlord.Stop()
@@ -4450,7 +4498,6 @@ func (s *apiSuite) TestDisconnectCoreSystemAlias(c *check.C) {
c.Check(err, check.IsNil)
id := body["change"].(string)
- st := d.overlord.State()
st.Lock()
chg := st.Change(id)
st.Unlock()
diff --git a/interfaces/builtin/accounts_service.go b/interfaces/builtin/accounts_service.go
index 6d7e721c58..6d9e26daba 100644
--- a/interfaces/builtin/accounts_service.go
+++ b/interfaces/builtin/accounts_service.go
@@ -56,12 +56,17 @@ dbus (receive, send)
peer=(label=unconfined),
# Allow clients to introspect the service
+# do not use peer=(label=unconfined) here since this is DBus activated
+dbus (receive, send)
+ bus=session
+ interface=org.freedesktop.DBus.Properties
+ path=/org/gnome/OnlineAccounts{,/**}
+ member="Get{,All}",
dbus (send)
bus=session
interface=org.freedesktop.DBus.Introspectable
path=/com/ubuntu/OnlineAccounts{,/**}
- member=Introspect
- peer=(label=unconfined),
+ member=Introspect,
`
func init() {
diff --git a/interfaces/builtin/avahi_observe.go b/interfaces/builtin/avahi_observe.go
index 1fe250684c..efcda6a780 100644
--- a/interfaces/builtin/avahi_observe.go
+++ b/interfaces/builtin/avahi_observe.go
@@ -256,12 +256,12 @@ dbus (receive)
# Don't allow introspection since it reveals too much (path is not service
# specific for unconfined)
+# do not use peer=(label=unconfined) here since this is DBus activated
#dbus (send)
# bus=system
# path=/
# interface=org.freedesktop.DBus.Introspectable
-# member=Introspect
-# peer=(label=unconfined),
+# member=Introspect,
# These allows tampering with other snap's browsers, so don't autoconnect for
# now.
diff --git a/interfaces/builtin/bluez.go b/interfaces/builtin/bluez.go
index 7beff8384a..e608e6aabb 100644
--- a/interfaces/builtin/bluez.go
+++ b/interfaces/builtin/bluez.go
@@ -48,79 +48,91 @@ const bluezPermanentSlotAppArmor = `
# Description: Allow operating as the bluez service. This gives privileged
# access to the system.
- network bluetooth,
-
- capability net_admin,
- capability net_bind_service,
-
- # libudev
- network netlink raw,
-
- # File accesses
- /sys/bus/usb/drivers/btusb/ r,
- /sys/bus/usb/drivers/btusb/** r,
- /sys/class/bluetooth/ r,
- /sys/devices/**/bluetooth/ rw,
- /sys/devices/**/bluetooth/** rw,
- /sys/devices/**/id/chassis_type r,
-
- # TODO: use snappy hardware assignment for this once LP: #1498917 is fixed
- /dev/rfkill rw,
-
- # DBus accesses
- #include <abstractions/dbus-strict>
- dbus (send)
- bus=system
- path=/org/freedesktop/DBus
- interface=org.freedesktop.DBus
- member={Request,Release}Name
- peer=(name=org.freedesktop.DBus, label=unconfined),
-
- dbus (send)
+network bluetooth,
+
+capability net_admin,
+capability net_bind_service,
+
+# libudev
+network netlink raw,
+
+# File accesses
+/sys/bus/usb/drivers/btusb/ r,
+/sys/bus/usb/drivers/btusb/** r,
+/sys/class/bluetooth/ r,
+/sys/devices/**/bluetooth/ rw,
+/sys/devices/**/bluetooth/** rw,
+/sys/devices/**/id/chassis_type r,
+
+# TODO: use snappy hardware assignment for this once LP: #1498917 is fixed
+/dev/rfkill rw,
+
+# DBus accesses
+#include <abstractions/dbus-strict>
+dbus (send)
+ bus=system
+ path=/org/freedesktop/DBus
+ interface=org.freedesktop.DBus
+ member={Request,Release}Name
+ peer=(name=org.freedesktop.DBus, label=unconfined),
+
+dbus (send)
+ bus=system
+ path=/org/freedesktop/*
+ interface=org.freedesktop.DBus.Properties
+ peer=(label=unconfined),
+
+# Allow binding the service to the requested connection name
+dbus (bind)
+ bus=system
+ name="org.bluez",
+
+# Allow binding the service to the requested connection name
+dbus (bind)
+ bus=system
+ name="org.bluez.obex",
+
+# Allow traffic to/from our interface with any method for unconfined clients
+# to talk to our bluez services. For the org.bluez interface we don't specify
+# an Object Path since according to the bluez specification these can be
+# anything (https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc).
+dbus (receive, send)
+ bus=system
+ interface=org.bluez.*
+ peer=(label=unconfined),
+dbus (receive, send)
+ bus=system
+ path=/org/bluez{,/**}
+ interface=org.freedesktop.DBus.*
+ peer=(label=unconfined),
+
+# Allow traffic to/from org.freedesktop.DBus for bluez service. This rule is
+# not snap-specific and grants privileged access to the org.freedesktop.DBus
+# on the system bus.
+dbus (receive, send)
+ bus=system
+ path=/
+ interface=org.freedesktop.DBus.*
+ peer=(label=unconfined),
+
+# Allow access to hostname system service
+dbus (receive, send)
bus=system
- path=/org/freedesktop/*
+ path=/org/freedesktop/hostname1
interface=org.freedesktop.DBus.Properties
peer=(label=unconfined),
- # Allow binding the service to the requested connection name
- dbus (bind)
- bus=system
- name="org.bluez",
-
- # Allow binding the service to the requested connection name
- dbus (bind)
- bus=system
- name="org.bluez.obex",
-
- # Allow traffic to/from our interface with any method for unconfined clients
- # to talk to our bluez services. For the org.bluez interface we don't specify
- # an Object Path since according to the bluez specification these can be
- # anything (https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc).
- dbus (receive, send)
- bus=system
- interface=org.bluez.*
- peer=(label=unconfined),
- dbus (receive, send)
- bus=system
- path=/org/bluez{,/**}
- interface=org.freedesktop.DBus.*
- peer=(label=unconfined),
-
- # Allow traffic to/from org.freedesktop.DBus for bluez service. This rule is
- # not snap-specific and grants privileged access to the org.freedesktop.DBus
- # on the system bus.
- dbus (receive, send)
- bus=system
- path=/
- interface=org.freedesktop.DBus.*
- peer=(label=unconfined),
-
- # Allow access to hostname system service
- dbus (receive, send)
- bus=system
- path=/org/freedesktop/hostname1
- interface=org.freedesktop.DBus.Properties
- peer=(label=unconfined),
+# do not use peer=(label=unconfined) here since this is DBus activated
+dbus (send)
+ bus=system
+ path=/org/freedesktop/hostname1
+ interface=org.freedesktop.DBus.Properties
+ member="Get{,All}",
+dbus (send)
+ bus=system
+ path=/org/freedesktop/hostname1
+ interface=org.freedesktop.DBus.Introspectable
+ member=Introspect,
`
const bluezConnectedSlotAppArmor = `
diff --git a/interfaces/builtin/hostname_control.go b/interfaces/builtin/hostname_control.go
index b8697639e7..44bff8c635 100644
--- a/interfaces/builtin/hostname_control.go
+++ b/interfaces/builtin/hostname_control.go
@@ -38,24 +38,24 @@ const hostnameControlConnectedPlugAppArmor = `
/{,usr/}{,s}bin/hostnamectl ixr,
# Allow access to hostname system service
+# do not use peer=(label=unconfined) here since this is DBus activated
dbus (send)
bus=system
path=/org/freedesktop/hostname1
interface=org.freedesktop.DBus.Properties
- member="Get{,All}"
- peer=(label=unconfined),
+ member="Get{,All}",
+dbus (send)
+ bus=system
+ path=/org/freedesktop/hostname1
+ interface=org.freedesktop.DBus.Introspectable
+ member=Introspect,
+
dbus (receive)
bus=system
path=/org/freedesktop/hostname1
interface=org.freedesktop.DBus.Properties
member=PropertiesChanged
peer=(label=unconfined),
-dbus (send)
- bus=system
- path=/org/freedesktop/hostname1
- interface=org.freedesktop.DBus.Introspectable
- member=Introspect
- peer=(label=unconfined),
dbus(receive, send)
bus=system
path=/org/freedesktop/hostname1
@@ -63,9 +63,8 @@ dbus(receive, send)
member=Set{,Pretty,Static}Hostname
peer=(label=unconfined),
-# Needed to use 'sethostname'. See man 7 capabilities
-capability sys_admin,
-# Needed to use 'hostnamectl set-hostname'
+# Needed to use 'sethostname' and 'hostnamectl set-hostname'. See man 7
+# capabilities
capability sys_admin,
`
diff --git a/interfaces/builtin/modem_manager.go b/interfaces/builtin/modem_manager.go
index 4f39a47b02..5624fb000b 100644
--- a/interfaces/builtin/modem_manager.go
+++ b/interfaces/builtin/modem_manager.go
@@ -127,12 +127,12 @@ dbus (receive)
interface=org.freedesktop.login1.Manager
member={PrepareForSleep,SessionNew,SessionRemoved}
peer=(label=unconfined),
+# do not use peer=(label=unconfined) here since this is DBus activated
dbus (send)
bus=system
path=/org/freedesktop/login1
interface=org.freedesktop.login1.Manager
- member=Inhibit
- peer=(label=unconfined),
+ member=Inhibit,
`
const modemManagerConnectedSlotAppArmor = `
@@ -184,6 +184,18 @@ dbus (receive, send)
path=/org/freedesktop/ModemManager1{,/**}
interface=org.freedesktop.DBus.*
peer=(label=unconfined),
+
+# do not use peer=(label=unconfined) here since this is DBus activated
+dbus (send)
+ bus=system
+ path=/org/freedesktop/ModemManager1{,/**}
+ interface=org.freedesktop.DBus.Introspectable
+ member=Introspect,
+dbus (send)
+ bus=system
+ path=/org/freedesktop/ModemManager1{,/**}
+ interface=org.freedesktop.DBus.Properties
+ member="Get{,All}",
`
const modemManagerPermanentSlotSecComp = `
diff --git a/interfaces/builtin/network_manager.go b/interfaces/builtin/network_manager.go
index 69f19e92d4..f5d2d4b0d1 100644
--- a/interfaces/builtin/network_manager.go
+++ b/interfaces/builtin/network_manager.go
@@ -197,6 +197,13 @@ dbus (receive, send)
path=/org/freedesktop/hostname1
interface=org.freedesktop.DBus.Properties
peer=(label=unconfined),
+# do not use peer=(label=unconfined) here since this is DBus activated
+dbus (send)
+ bus=system
+ path=/org/freedesktop/hostname1
+ interface=org.freedesktop.DBus.Properties
+ member="Get{,All}",
+
dbus(receive, send)
bus=system
path=/org/freedesktop/hostname1
@@ -205,12 +212,12 @@ dbus(receive, send)
peer=(label=unconfined),
# Sleep monitor inside NetworkManager needs this
+# do not use peer=(label=unconfined) here since this is DBus activated
dbus (send)
bus=system
path=/org/freedesktop/login1
member=Inhibit
- interface=org.freedesktop.login1.Manager
- peer=(label=unconfined),
+ interface=org.freedesktop.login1.Manager,
dbus (receive)
bus=system
path=/org/freedesktop/login1
diff --git a/interfaces/builtin/screencast_legacy.go b/interfaces/builtin/screencast_legacy.go
new file mode 100644
index 0000000000..37dc066547
--- /dev/null
+++ b/interfaces/builtin/screencast_legacy.go
@@ -0,0 +1,64 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2018 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 builtin
+
+const screencastLegacySummary = `allows screen recording and audio recording, and also writing to arbitrary filesystem paths`
+
+const screencastLegacyBaseDeclarationSlots = `
+ screencast-legacy:
+ allow-installation:
+ slot-snap-type:
+ - core
+ deny-auto-connection: true
+`
+
+const screencastLegacyConnectedPlugAppArmor = `
+# Description: Can access common desktop screenshot, screencast and recording
+# methods thus giving privileged access to screen output and microphone via the
+# desktop session manager.
+
+#include <abstractions/dbus-session-strict>
+
+# gnome-shell screenshot and screencast. Note these APIs permit specifying
+# absolute file names as arguments to DBus methods which tells gnome-shell to
+# save to arbitrary locations permitted by the unconfined user.
+dbus (send)
+ bus=session
+ path=/org/gnome/Shell/Screen{cast,shot}
+ interface=org.freedesktop.DBus.Properties
+ member=Get{,All}
+ peer=(label=unconfined),
+dbus (send)
+ bus=session
+ path=/org/gnome/Shell/Screen{cast,shot}
+ interface=org.gnome.Shell.Screen{cast,shot}
+ peer=(label=unconfined),
+`
+
+func init() {
+ registerIface(&commonInterface{
+ name: "screencast-legacy",
+ summary: screencastLegacySummary,
+ implicitOnClassic: true,
+ baseDeclarationSlots: screencastLegacyBaseDeclarationSlots,
+ connectedPlugAppArmor: screencastLegacyConnectedPlugAppArmor,
+ reservedForOS: true,
+ })
+}
diff --git a/interfaces/builtin/screencast_legacy_test.go b/interfaces/builtin/screencast_legacy_test.go
new file mode 100644
index 0000000000..546cc7f8da
--- /dev/null
+++ b/interfaces/builtin/screencast_legacy_test.go
@@ -0,0 +1,107 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2018 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 builtin_test
+
+import (
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/interfaces/apparmor"
+ "github.com/snapcore/snapd/interfaces/builtin"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/testutil"
+)
+
+type ScreencastLegacyInterfaceSuite struct {
+ iface interfaces.Interface
+ coreSlotInfo *snap.SlotInfo
+ coreSlot *interfaces.ConnectedSlot
+ plugInfo *snap.PlugInfo
+ plug *interfaces.ConnectedPlug
+}
+
+var _ = Suite(&ScreencastLegacyInterfaceSuite{
+ iface: builtin.MustInterface("screencast-legacy"),
+})
+
+const screencastLegacyConsumerYaml = `name: consumer
+version: 0
+apps:
+ app:
+ plugs: [screencast-legacy]
+`
+
+const screencastLegacyCoreYaml = `name: core
+version: 0
+type: os
+slots:
+ screencast-legacy:
+`
+
+func (s *ScreencastLegacyInterfaceSuite) SetUpTest(c *C) {
+ s.plug, s.plugInfo = MockConnectedPlug(c, screencastLegacyConsumerYaml, nil, "screencast-legacy")
+ s.coreSlot, s.coreSlotInfo = MockConnectedSlot(c, screencastLegacyCoreYaml, nil, "screencast-legacy")
+}
+
+func (s *ScreencastLegacyInterfaceSuite) TestName(c *C) {
+ c.Assert(s.iface.Name(), Equals, "screencast-legacy")
+}
+
+func (s *ScreencastLegacyInterfaceSuite) TestSanitizeSlot(c *C) {
+ c.Assert(interfaces.BeforePrepareSlot(s.iface, s.coreSlotInfo), IsNil)
+ // screencast-legacy slot currently only used with core
+ slot := &snap.SlotInfo{
+ Snap: &snap.Info{SuggestedName: "some-snap"},
+ Name: "screencast-legacy",
+ Interface: "screencast-legacy",
+ }
+ c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches,
+ "screencast-legacy slots are reserved for the core snap")
+}
+
+func (s *ScreencastLegacyInterfaceSuite) TestSanitizePlug(c *C) {
+ c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil)
+}
+
+func (s *ScreencastLegacyInterfaceSuite) TestAppArmorSpec(c *C) {
+ // connected plug to core slot
+ spec := &apparmor.Specification{}
+ c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.coreSlot), IsNil)
+ c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"})
+ c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "# Description: Can access common desktop screenshot, screencast and recording")
+ c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "path=/org/gnome/Shell/Screen{cast,shot}")
+
+ // connected plug to core slot
+ spec = &apparmor.Specification{}
+ c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.coreSlot), IsNil)
+ c.Assert(spec.SecurityTags(), HasLen, 0)
+}
+
+func (s *ScreencastLegacyInterfaceSuite) TestStaticInfo(c *C) {
+ si := interfaces.StaticInfoOf(s.iface)
+ c.Assert(si.ImplicitOnCore, Equals, false)
+ c.Assert(si.ImplicitOnClassic, Equals, true)
+ c.Assert(si.Summary, Equals, `allows screen recording and audio recording, and also writing to arbitrary filesystem paths`)
+ c.Assert(si.BaseDeclarationSlots, testutil.Contains, "screencast-legacy")
+}
+
+func (s *ScreencastLegacyInterfaceSuite) TestInterfaces(c *C) {
+ c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface)
+}
diff --git a/interfaces/builtin/shutdown.go b/interfaces/builtin/shutdown.go
index 538dd74883..da74d34b85 100644
--- a/interfaces/builtin/shutdown.go
+++ b/interfaces/builtin/shutdown.go
@@ -49,19 +49,17 @@ dbus (send)
peer=(label=unconfined),
# Allow clients to introspect
+# do not use peer=(label=unconfined) here since this is DBus activated
dbus (send)
bus=system
path=/org/freedesktop/systemd1
interface=org.freedesktop.DBus.Introspectable
- member=Introspect
- peer=(label=unconfined),
-
+ member=Introspect,
dbus (send)
bus=system
path=/org/freedesktop/login1
interface=org.freedesktop.DBus.Introspectable
- member=Introspect
- peer=(label=unconfined),
+ member=Introspect,
`
func init() {
diff --git a/interfaces/builtin/system_observe.go b/interfaces/builtin/system_observe.go
index 098bcf6e3f..05240805af 100644
--- a/interfaces/builtin/system_observe.go
+++ b/interfaces/builtin/system_observe.go
@@ -74,20 +74,20 @@ deny ptrace (trace),
#include <abstractions/dbus-strict>
+# do not use peer=(label=unconfined) here since this is DBus activated
dbus (send)
bus=system
path=/org/freedesktop/hostname1
interface=org.freedesktop.DBus.Properties
- member=Get{,All}
- peer=(label=unconfined),
+ member=Get{,All},
# Allow clients to introspect hostname1
+# do not use peer=(label=unconfined) here since this is DBus activated
dbus (send)
bus=system
path=/org/freedesktop/hostname1
interface=org.freedesktop.DBus.Introspectable
- member=Introspect
- peer=(label=unconfined),
+ member=Introspect,
# Allow clients to enumerate DBus connection names on common buses
dbus (send)
diff --git a/interfaces/builtin/time_control.go b/interfaces/builtin/time_control.go
index 74ed469bd5..d4f430483a 100644
--- a/interfaces/builtin/time_control.go
+++ b/interfaces/builtin/time_control.go
@@ -38,12 +38,12 @@ const timeControlConnectedPlugAppArmor = `
#include <abstractions/dbus-strict>
# Introspection of org.freedesktop.timedate1
+# do not use peer=(label=unconfined) here since this is DBus activated
dbus (send)
bus=system
path=/org/freedesktop/timedate1
interface=org.freedesktop.DBus.Introspectable
- member=Introspect
- peer=(label=unconfined),
+ member=Introspect,
dbus (send)
bus=system
@@ -53,12 +53,12 @@ dbus (send)
peer=(label=unconfined),
# Read all properties from timedate1
+# do not use peer=(label=unconfined) here since this is DBus activated
dbus (send)
bus=system
path=/org/freedesktop/timedate1
interface=org.freedesktop.DBus.Properties
- member=Get{,All}
- peer=(label=unconfined),
+ member=Get{,All},
# Receive timedate1 property changed events
dbus (receive)
diff --git a/interfaces/builtin/timeserver_control.go b/interfaces/builtin/timeserver_control.go
index b5ac5ec09c..57dcae50b2 100644
--- a/interfaces/builtin/timeserver_control.go
+++ b/interfaces/builtin/timeserver_control.go
@@ -43,12 +43,12 @@ const timeserverControlConnectedPlugAppArmor = `
/etc/systemd/timesyncd.conf rw,
# Introspection of org.freedesktop.timedate1
+# do not use peer=(label=unconfined) here since this is DBus activated
dbus (send)
bus=system
path=/org/freedesktop/timedate1
interface=org.freedesktop.DBus.Introspectable
- member=Introspect
- peer=(label=unconfined),
+ member=Introspect,
dbus (send)
bus=system
@@ -58,12 +58,12 @@ dbus (send)
peer=(label=unconfined),
# Read all properties from timedate1
+# do not use peer=(label=unconfined) here since this is DBus activated
dbus (send)
bus=system
path=/org/freedesktop/timedate1
interface=org.freedesktop.DBus.Properties
- member=Get{,All}
- peer=(label=unconfined),
+ member=Get{,All},
# Receive timedate1 property changed events
dbus (receive)
diff --git a/interfaces/builtin/timezone_control.go b/interfaces/builtin/timezone_control.go
index 3126af040f..8194e9eb3c 100644
--- a/interfaces/builtin/timezone_control.go
+++ b/interfaces/builtin/timezone_control.go
@@ -45,12 +45,12 @@ const timezoneControlConnectedPlugAppArmor = `
/etc/{,writable/}localtime.tmp rw, # Required for the timedatectl wrapper (LP: #1650688)
# Introspection of org.freedesktop.timedate1
+# do not use peer=(label=unconfined) here since this is DBus activated
dbus (send)
bus=system
path=/org/freedesktop/timedate1
interface=org.freedesktop.DBus.Introspectable
- member=Introspect
- peer=(label=unconfined),
+ member=Introspect,
dbus (send)
bus=system
@@ -60,12 +60,12 @@ dbus (send)
peer=(label=unconfined),
# Read all properties from timedate1
+# do not use peer=(label=unconfined) here since this is DBus activated
dbus (send)
bus=system
path=/org/freedesktop/timedate1
interface=org.freedesktop.DBus.Properties
- member=Get{,All}
- peer=(label=unconfined),
+ member=Get{,All},
# Receive timedate1 property changed events
dbus (receive)
diff --git a/interfaces/builtin/udisks2.go b/interfaces/builtin/udisks2.go
index b017d57802..8ec701a972 100644
--- a/interfaces/builtin/udisks2.go
+++ b/interfaces/builtin/udisks2.go
@@ -155,6 +155,12 @@ dbus (receive, send)
path=/org/freedesktop/UDisks2/**
interface=org.freedesktop.DBus.Properties
peer=(label=###SLOT_SECURITY_TAGS###),
+# do not use peer=(label=unconfined) here since this is DBus activated
+dbus (send)
+ bus=system
+ path=/org/freedesktop/UDisks2/**
+ interface=org.freedesktop.DBus.Properties
+ member="Get{,All}",
dbus (receive, send)
bus=system
@@ -170,12 +176,12 @@ dbus (receive, send)
peer=(label=###SLOT_SECURITY_TAGS###),
# Allow clients to introspect the service
+# do not use peer=(label=unconfined) here since this is DBus activated
dbus (send)
bus=system
path=/org/freedesktop/UDisks2
interface=org.freedesktop.DBus.Introspectable
- member=Introspect
- peer=(label=###SLOT_SECURITY_TAGS###),
+ member=Introspect,
`
const udisks2PermanentSlotSecComp = `
diff --git a/interfaces/builtin/upower_observe.go b/interfaces/builtin/upower_observe.go
index 59783a7dcf..c0ccf73c97 100644
--- a/interfaces/builtin/upower_observe.go
+++ b/interfaces/builtin/upower_observe.go
@@ -75,12 +75,12 @@ dbus (receive)
path=/org/freedesktop/login1{,/**}
interface=org.freedesktop.DBus.Properties
peer=(label=unconfined),
+# do not use peer=(label=unconfined) here since this is DBus activated
dbus (send)
bus=system
path=/org/freedesktop/login1{,/**}
interface=org.freedesktop.DBus.Properties
- member=Get{,All}
- peer=(label=unconfined),
+ member=Get{,All},
# Allow receiving any signals from the logind service
dbus (receive)
@@ -96,7 +96,7 @@ dbus (send)
bus=system
path=/org/freedesktop/login1{,/**}
interface=org.freedesktop.login1.Manager
- member={CanPowerOff,CanSuspend,CanHibernate,CanHybridSleep,PowerOff,Suspend,Hibernate,HybrisSleep}
+ member={CanPowerOff,CanSuspend,CanHibernate,CanHybridSleep,PowerOff,Suspend,Hibernate,HybridSleep}
peer=(label=unconfined),
`
@@ -169,19 +169,12 @@ dbus (send)
peer=(label=###SLOT_SECURITY_TAGS###),
# Read all properties from UPower and devices
+# do not use peer=(label=unconfined) here since this is DBus activated
dbus (send)
bus=system
- path=/org/freedesktop/UPower{,/devices/**}
+ path=/org/freedesktop/UPower{,/Wakeups,/devices/**}
interface=org.freedesktop.DBus.Properties
- member=Get{,All}
- peer=(label=###SLOT_SECURITY_TAGS###),
-
-dbus (send)
- bus=system
- path=/org/freedesktop/UPower/Wakeups
- interface=org.freedesktop.DBus.Properties
- member=Get{,All}
- peer=(label=###SLOT_SECURITY_TAGS###),
+ member=Get{,All},
dbus (send)
bus=system
@@ -213,12 +206,12 @@ dbus (receive)
peer=(label=###SLOT_SECURITY_TAGS###),
# Allow clients to introspect the service
+# do not use peer=(label=unconfined) here since this is DBus activated
dbus (send)
bus=system
interface=org.freedesktop.DBus.Introspectable
path=/org/freedesktop/UPower
- member=Introspect
- peer=(label=###SLOT_SECURITY_TAGS###),
+ member=Introspect,
`
type upowerObserveInterface struct{}
diff --git a/interfaces/repo.go b/interfaces/repo.go
index 38f53f5b44..f842d59fc7 100644
--- a/interfaces/repo.go
+++ b/interfaces/repo.go
@@ -277,6 +277,29 @@ func (r *Repository) Plug(snapName, plugName string) *snap.PlugInfo {
return r.plugs[snapName][plugName]
}
+// Connection returns the specified Connection object or an error.
+func (r *Repository) Connection(connRef *ConnRef) (*Connection, error) {
+ // Ensure that such plug exists
+ plug := r.plugs[connRef.PlugRef.Snap][connRef.PlugRef.Name]
+ if plug == nil {
+ return nil, fmt.Errorf("snap %q has no plug named %q", connRef.PlugRef.Snap, connRef.PlugRef.Name)
+ }
+ // Ensure that such slot exists
+ slot := r.slots[connRef.SlotRef.Snap][connRef.SlotRef.Name]
+ if slot == nil {
+ return nil, fmt.Errorf("snap %q has no slot named %q", connRef.SlotRef.Snap, connRef.SlotRef.Name)
+ }
+ // Ensure that slot and plug are connected
+ conn, ok := r.slotPlugs[slot][plug]
+ if !ok {
+ return nil, fmt.Errorf("no connection from %s:%s to %s:%s",
+ connRef.PlugRef.Snap, connRef.PlugRef.Name,
+ connRef.SlotRef.Snap, connRef.SlotRef.Name)
+ }
+
+ return conn, nil
+}
+
// AddPlug adds a plug to the repository.
// Plug names must be valid snap names, as defined by ValidateName.
// Plug name must be unique within a particular snap.
@@ -760,6 +783,10 @@ func (r *Repository) Connections(snapName string) ([]*ConnRef, error) {
}
for _, slotInfo := range r.slots[snapName] {
for plugInfo := range r.slotPlugs[slotInfo] {
+ // self-connection, ignore here as we got it already in the plugs loop above
+ if plugInfo.Snap == slotInfo.Snap {
+ continue
+ }
connRef := NewConnRef(plugInfo, slotInfo)
conns = append(conns, connRef)
}
diff --git a/interfaces/repo_test.go b/interfaces/repo_test.go
index d9638f3744..8cc2cb692f 100644
--- a/interfaces/repo_test.go
+++ b/interfaces/repo_test.go
@@ -36,6 +36,7 @@ type RepositorySuite struct {
testutil.BaseTest
iface Interface
plug *snap.PlugInfo
+ plugSelf *snap.PlugInfo
slot *snap.SlotInfo
emptyRepo *Repository
// Repository pre-populated with s.iface
@@ -83,9 +84,13 @@ slots:
interface: interface
label: label
attr: value
+plugs:
+ self:
+ interface: interface
+ label: label
`, nil)
s.slot = producer.Slots["slot"]
-
+ s.plugSelf = producer.Plugs["self"]
// NOTE: Each of the snaps below have one slot so that they can be picked
// up by the repository. Some tests rename the "slot" slot as appropriate.
s.ubuntuCoreSnap = snaptest.MockInfo(c, `
@@ -96,6 +101,9 @@ slots:
slot:
interface: interface
`, nil)
+ // NOTE: The core snap has a slot so that it shows up in the
+ // repository. The repository doesn't record snaps unless they
+ // have at least one interface.
s.coreSnap = snaptest.MockInfo(c, `
name: core
version: 0
@@ -1356,6 +1364,21 @@ func (s *RepositorySuite) TestConnections(c *C) {
c.Assert(conns, HasLen, 0)
}
+func (s *RepositorySuite) TestConnectionsWithSelfConnected(c *C) {
+ c.Assert(s.testRepo.AddPlug(s.plugSelf), IsNil)
+ c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
+ _, err := s.testRepo.Connect(NewConnRef(s.plugSelf, s.slot), nil, nil, nil)
+ c.Assert(err, IsNil)
+
+ conns, err := s.testRepo.Connections(s.plugSelf.Snap.InstanceName())
+ c.Assert(err, IsNil)
+ c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plugSelf, s.slot)})
+
+ conns, err = s.testRepo.Connections(s.slot.Snap.InstanceName())
+ c.Assert(err, IsNil)
+ c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plugSelf, s.slot)})
+}
+
// Tests for Repository.DisconnectAll()
func (s *RepositorySuite) TestDisconnectAll(c *C) {
@@ -2156,6 +2179,30 @@ func (s *RepositorySuite) TestBeforeConnectValidationPolicyCheckFailure(c *C) {
c.Assert(conn, IsNil)
}
+func (s *RepositorySuite) TestConnection(c *C) {
+ c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
+ c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
+
+ connRef := NewConnRef(s.plug, s.slot)
+
+ conn, err := s.testRepo.Connection(connRef)
+ c.Assert(err, ErrorMatches, `no connection from consumer:plug to producer:slot`)
+
+ _, err = s.testRepo.Connect(connRef, nil, nil, nil)
+ c.Assert(err, IsNil)
+
+ conn, err = s.testRepo.Connection(connRef)
+ c.Assert(err, IsNil)
+ c.Assert(conn.Plug.Name(), Equals, "plug")
+ c.Assert(conn.Slot.Name(), Equals, "slot")
+
+ conn, err = s.testRepo.Connection(&ConnRef{PlugRef: PlugRef{Snap: "a", Name: "b"}, SlotRef: SlotRef{Snap: "producer", Name: "slot"}})
+ c.Assert(err, ErrorMatches, `snap "a" has no plug named "b"`)
+
+ conn, err = s.testRepo.Connection(&ConnRef{PlugRef: PlugRef{Snap: "consumer", Name: "plug"}, SlotRef: SlotRef{Snap: "a", Name: "b"}})
+ c.Assert(err, ErrorMatches, `snap "a" has no slot named "b"`)
+}
+
type hotplugTestInterface struct{ InterfaceName string }
func (h *hotplugTestInterface) Name() string {
diff --git a/mkversion.sh b/mkversion.sh
index 515a7d5bdd..e7f0f37555 100755
--- a/mkversion.sh
+++ b/mkversion.sh
@@ -26,6 +26,12 @@ if [ "$GOPACKAGE" = "cmd" ]; then
GO_GENERATE_BUILDDIR="$(pwd)/.."
fi
+OUTPUT_ONLY=false
+if [ "$1" = "--output-only" ]; then
+ OUTPUT_ONLY=true
+ shift
+fi
+
# If the version is passed in as an argument to mkversion.sh, let's use that.
if [ ! -z "$1" ]; then
v="$1"
@@ -52,6 +58,11 @@ if [ -z "$v" ]; then
exit 1
fi
+if [ "$OUTPUT_ONLY" = true ]; then
+ echo "$v"
+ exit 0
+fi
+
echo "*** Setting version to '$v' from $o." >&2
cat <<EOF > "$GO_GENERATE_BUILDDIR/cmd/version_generated.go"
diff --git a/overlord/hookstate/ctlcmd/get.go b/overlord/hookstate/ctlcmd/get.go
index ee69896437..8b3524568b 100644
--- a/overlord/hookstate/ctlcmd/get.go
+++ b/overlord/hookstate/ctlcmd/get.go
@@ -194,22 +194,36 @@ type ifaceHookType int
const (
preparePlugHook ifaceHookType = iota
prepareSlotHook
+ unpreparePlugHook
+ unprepareSlotHook
connectPlugHook
connectSlotHook
+ disconnectPlugHook
+ disconnectSlotHook
unknownHook
)
func interfaceHookType(hookName string) (ifaceHookType, error) {
- if strings.HasPrefix(hookName, "prepare-plug-") {
+ switch {
+ case strings.HasPrefix(hookName, "prepare-plug-"):
return preparePlugHook, nil
- } else if strings.HasPrefix(hookName, "connect-plug-") {
+ case strings.HasPrefix(hookName, "connect-plug-"):
return connectPlugHook, nil
- } else if strings.HasPrefix(hookName, "prepare-slot-") {
+ case strings.HasPrefix(hookName, "prepare-slot-"):
return prepareSlotHook, nil
- } else if strings.HasPrefix(hookName, "connect-slot-") {
+ case strings.HasPrefix(hookName, "connect-slot-"):
return connectSlotHook, nil
+ case strings.HasPrefix(hookName, "disconnect-plug-"):
+ return disconnectPlugHook, nil
+ case strings.HasPrefix(hookName, "disconnect-slot-"):
+ return disconnectSlotHook, nil
+ case strings.HasPrefix(hookName, "unprepare-slot-"):
+ return unprepareSlotHook, nil
+ case strings.HasPrefix(hookName, "unprepare-plug-"):
+ return unpreparePlugHook, nil
+ default:
+ return unknownHook, fmt.Errorf("unknown hook type")
}
- return unknownHook, fmt.Errorf("unknown hook type")
}
func validatePlugOrSlot(attrsTask *state.Task, plugSide bool, plugOrSlot string) error {
@@ -275,7 +289,7 @@ func (c *getCommand) getInterfaceSetting(context *hookstate.Context, plugOrSlot
return fmt.Errorf("cannot use --plug and --slot together")
}
- isPlugSide := (hookType == preparePlugHook || hookType == connectPlugHook)
+ isPlugSide := (hookType == preparePlugHook || hookType == unpreparePlugHook || hookType == connectPlugHook || hookType == disconnectPlugHook)
if err = validatePlugOrSlot(attrsTask, isPlugSide, plugOrSlot); err != nil {
return err
}
diff --git a/overlord/hookstate/ctlcmd/get_test.go b/overlord/hookstate/ctlcmd/get_test.go
index 3d3692fb70..64bac281ec 100644
--- a/overlord/hookstate/ctlcmd/get_test.go
+++ b/overlord/hookstate/ctlcmd/get_test.go
@@ -223,7 +223,6 @@ func (s *getAttrSuite) SetUpTest(c *C) {
attrsTask.Set("plug-dynamic", dynamicPlugAttrs)
attrsTask.Set("slot-static", staticSlotAttrs)
attrsTask.Set("slot-dynamic", dynamicSlotAttrs)
-
ch.AddTask(attrsTask)
state.Unlock()
diff --git a/overlord/ifacestate/export_test.go b/overlord/ifacestate/export_test.go
index ede532aecd..e610f94a66 100644
--- a/overlord/ifacestate/export_test.go
+++ b/overlord/ifacestate/export_test.go
@@ -24,13 +24,13 @@ import (
)
var (
- AddImplicitSlots = addImplicitSlots
- SnapsWithSecurityProfiles = snapsWithSecurityProfiles
- CheckConnectConflicts = checkConnectConflicts
- FindSymmetricAutoconnect = findSymmetricAutoconnect
- ConnectPriv = connect
- GetConns = getConns
- SetConns = setConns
+ AddImplicitSlots = addImplicitSlots
+ SnapsWithSecurityProfiles = snapsWithSecurityProfiles
+ CheckAutoconnectConflicts = checkAutoconnectConflicts
+ FindSymmetricAutoconnectTask = findSymmetricAutoconnectTask
+ ConnectPriv = connect
+ GetConns = getConns
+ SetConns = setConns
)
func MockRemoveStaleConnections(f func(st *state.State) error) (restore func()) {
diff --git a/overlord/ifacestate/handlers.go b/overlord/ifacestate/handlers.go
index 9c9665dbe2..0571dd5cc4 100644
--- a/overlord/ifacestate/handlers.go
+++ b/overlord/ifacestate/handlers.go
@@ -28,6 +28,7 @@ import (
"gopkg.in/tomb.v2"
"github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/snap"
@@ -488,7 +489,21 @@ func (m *InterfaceManager) doDisconnect(task *state.Task, _ *tomb.Tomb) error {
}
cref := interfaces.ConnRef{PlugRef: plugRef, SlotRef: slotRef}
- if conn, ok := conns[cref.ID()]; ok && conn.Auto {
+ conn, ok := conns[cref.ID()]
+ if !ok {
+ return fmt.Errorf("internal error: connection %q not found in state", cref.ID())
+ }
+
+ // store old connection for undo
+ task.Set("old-conn", conn)
+
+ // "auto-disconnect" flag indicates it's a disconnect triggered automatically as part of snap removal;
+ // such disconnects should not set undesired flag and instead just remove the connection.
+ var autoDisconnect bool
+ if err := task.Get("auto-disconnect", &autoDisconnect); err != nil && err != state.ErrNoState {
+ return fmt.Errorf("internal error: failed to read 'auto-disconnect' flag: %s", err)
+ }
+ if conn.Auto && !autoDisconnect {
conn.Undesired = true
conn.DynamicPlugAttrs = nil
conn.DynamicSlotAttrs = nil
@@ -502,6 +517,70 @@ func (m *InterfaceManager) doDisconnect(task *state.Task, _ *tomb.Tomb) error {
return nil
}
+func (m *InterfaceManager) undoDisconnect(task *state.Task, _ *tomb.Tomb) error {
+ st := task.State()
+ st.Lock()
+ defer st.Unlock()
+
+ var oldconn connState
+ err := task.Get("old-conn", &oldconn)
+ if err == state.ErrNoState {
+ return nil
+ }
+ if err != nil {
+ return err
+ }
+
+ plugRef, slotRef, err := getPlugAndSlotRefs(task)
+ if err != nil {
+ return err
+ }
+
+ conns, err := getConns(st)
+ if err != nil {
+ return err
+ }
+
+ var plugSnapst snapstate.SnapState
+ if err := snapstate.Get(st, plugRef.Snap, &plugSnapst); err != nil {
+ return err
+ }
+ var slotSnapst snapstate.SnapState
+ if err := snapstate.Get(st, slotRef.Snap, &slotSnapst); err != nil {
+ return err
+ }
+
+ connRef := &interfaces.ConnRef{PlugRef: plugRef, SlotRef: slotRef}
+
+ plug := m.repo.Plug(connRef.PlugRef.Snap, connRef.PlugRef.Name)
+ if plug == nil {
+ return fmt.Errorf("snap %q has no %q plug", connRef.PlugRef.Snap, connRef.PlugRef.Name)
+ }
+ slot := m.repo.Slot(connRef.SlotRef.Snap, connRef.SlotRef.Name)
+ if slot == nil {
+ return fmt.Errorf("snap %q has no %q slot", connRef.SlotRef.Snap, connRef.SlotRef.Name)
+ }
+
+ _, err = m.repo.Connect(connRef, oldconn.DynamicPlugAttrs, oldconn.DynamicSlotAttrs, nil)
+ if err != nil {
+ return err
+ }
+
+ slotOpts := confinementOptions(slotSnapst.Flags)
+ if err := m.setupSnapSecurity(task, slot.Snap, slotOpts); err != nil {
+ return err
+ }
+ plugOpts := confinementOptions(plugSnapst.Flags)
+ if err := m.setupSnapSecurity(task, plug.Snap, plugOpts); err != nil {
+ return err
+ }
+
+ conns[connRef.ID()] = oldconn
+ setConns(st, conns)
+
+ return nil
+}
+
func (m *InterfaceManager) undoConnect(task *state.Task, _ *tomb.Tomb) error {
st := task.State()
st.Lock()
@@ -552,6 +631,100 @@ func (m *InterfaceManager) defaultContentProviders(snapName string) map[string]b
return defaultProviders
}
+func checkAutoconnectConflicts(st *state.State, plugSnap, slotSnap string) error {
+ for _, task := range st.Tasks() {
+ if task.Status().Ready() {
+ continue
+ }
+
+ k := task.Kind()
+ if k == "connect" || k == "disconnect" {
+ // retry if we found another connect/disconnect affecting same snap; note we can only encounter
+ // connects/disconnects created by doAutoDisconnect / doAutoConnect here as manual interface ops
+ // are rejected by conflict check logic in snapstate.
+ plugRef, slotRef, err := getPlugAndSlotRefs(task)
+ if err != nil {
+ return err
+ }
+ if plugRef.Snap == plugSnap || slotRef.Snap == slotSnap {
+ return &state.Retry{After: connectRetryTimeout}
+ }
+ continue
+ }
+
+ snapsup, err := snapstate.TaskSnapSetup(task)
+ // e.g. hook tasks don't have task snap setup
+ if err != nil {
+ continue
+ }
+
+ otherSnapName := snapsup.InstanceName()
+
+ // different snaps - no conflict
+ if otherSnapName != plugSnap && otherSnapName != slotSnap {
+ continue
+ }
+
+ // other snap that affects us because of plug or slot
+ if k == "unlink-snap" || k == "link-snap" || k == "setup-profiles" {
+ // if snap is getting removed, we will retry but the snap will be gone and auto-connect becomes no-op
+ // if snap is getting installed/refreshed - temporary conflict, retry later
+ return &state.Retry{After: connectRetryTimeout}
+ }
+ }
+ return nil
+}
+
+func checkDisconnectConflicts(st *state.State, disconnectingSnap, plugSnap, slotSnap string) error {
+ for _, task := range st.Tasks() {
+ if task.Status().Ready() {
+ continue
+ }
+
+ k := task.Kind()
+ if k == "connect" || k == "disconnect" {
+ // retry if we found another connect/disconnect affecting same snap; note we can only encounter
+ // connects/disconnects created by doAutoDisconnect / doAutoConnect here as manual interface ops
+ // are rejected by conflict check logic in snapstate.
+ plugRef, slotRef, err := getPlugAndSlotRefs(task)
+ if err != nil {
+ return err
+ }
+ if plugRef.Snap == plugSnap || slotRef.Snap == slotSnap {
+ return &state.Retry{After: connectRetryTimeout}
+ }
+ continue
+ }
+
+ snapsup, err := snapstate.TaskSnapSetup(task)
+ // e.g. hook tasks don't have task snap setup
+ if err != nil {
+ continue
+ }
+
+ otherSnapName := snapsup.InstanceName()
+
+ // different snaps - no conflict
+ if otherSnapName != plugSnap && otherSnapName != slotSnap {
+ continue
+ }
+
+ // another task related to same snap op (unrelated op would be blocked by snapstate conflict logic)
+ if otherSnapName == disconnectingSnap {
+ continue
+ }
+
+ // note, don't care about unlink-snap for the opposite end. This relies
+ // on the fact that auto-disconnect will create conflicting "disconnect" tasks that
+ // we will retry with the logic above.
+ if k == "link-snap" || k == "setup-profiles" {
+ // other snap is getting installed/refreshed - temporary conflict
+ return &state.Retry{After: connectRetryTimeout}
+ }
+ }
+ return nil
+}
+
// doAutoConnect creates task(s) to connect the given snap to viable candidates.
func (m *InterfaceManager) doAutoConnect(task *state.Task, _ *tomb.Tomb) error {
st := task.State()
@@ -646,7 +819,7 @@ func (m *InterfaceManager) doAutoConnect(task *state.Task, _ *tomb.Tomb) error {
continue
}
- ignore, err := findSymmetricAutoconnect(st, plug.Snap.InstanceName(), slot.Snap.InstanceName(), task)
+ ignore, err := findSymmetricAutoconnectTask(st, plug.Snap.InstanceName(), slot.Snap.InstanceName(), task)
if err != nil {
return err
}
@@ -655,10 +828,10 @@ func (m *InterfaceManager) doAutoConnect(task *state.Task, _ *tomb.Tomb) error {
continue
}
- const auto = true
- if err := checkConnectConflicts(st, plug.Snap.InstanceName(), slot.Snap.InstanceName(), auto); err != nil {
+ if err := checkAutoconnectConflicts(st, plug.Snap.InstanceName(), slot.Snap.InstanceName()); err != nil {
if _, retry := err.(*state.Retry); retry {
- task.Logf("auto-connect of snap %q will be retried because of %q - %q conflict", snapName, plug.Snap.InstanceName(), slot.Snap.InstanceName())
+ logger.Debugf("auto-connect of snap %q will be retried because of %q - %q conflict", snapName, plug.Snap.InstanceName(), slot.Snap.InstanceName())
+ task.Logf("Waiting for conflicting change in progress...")
return err // will retry
}
return fmt.Errorf("auto-connect conflict check failed: %s", err)
@@ -698,7 +871,7 @@ func (m *InterfaceManager) doAutoConnect(task *state.Task, _ *tomb.Tomb) error {
continue
}
- ignore, err := findSymmetricAutoconnect(st, plug.Snap.InstanceName(), slot.Snap.InstanceName(), task)
+ ignore, err := findSymmetricAutoconnectTask(st, plug.Snap.InstanceName(), slot.Snap.InstanceName(), task)
if err != nil {
return err
}
@@ -707,10 +880,10 @@ func (m *InterfaceManager) doAutoConnect(task *state.Task, _ *tomb.Tomb) error {
continue
}
- const auto = true
- if err := checkConnectConflicts(st, plug.Snap.InstanceName(), slot.Snap.InstanceName(), auto); err != nil {
+ if err := checkAutoconnectConflicts(st, plug.Snap.InstanceName(), slot.Snap.InstanceName()); err != nil {
if _, retry := err.(*state.Retry); retry {
- task.Logf("auto-connect of snap %q will be retried because of %q - %q conflict", snapName, plug.Snap.InstanceName(), slot.Snap.InstanceName())
+ logger.Debugf("auto-connect of snap %q will be retried because of %q - %q conflict", snapName, plug.Snap.InstanceName(), slot.Snap.InstanceName())
+ task.Logf("Waiting for conflicting change in progress...")
return err // will retry
}
return fmt.Errorf("auto-connect conflict check failed: %s", err)
@@ -738,6 +911,59 @@ func (m *InterfaceManager) doAutoConnect(task *state.Task, _ *tomb.Tomb) error {
return nil
}
+// doAutoDisconnect creates tasks for disconnecting all interfaces of a snap and running its interface hooks.
+func (m *InterfaceManager) doAutoDisconnect(task *state.Task, _ *tomb.Tomb) error {
+ st := task.State()
+ st.Lock()
+ defer st.Unlock()
+
+ snapsup, err := snapstate.TaskSnapSetup(task)
+ if err != nil {
+ return err
+ }
+
+ snapName := snapsup.InstanceName()
+ connections, err := m.repo.Connections(snapName)
+ if err != nil {
+ return err
+ }
+
+ // check for conflicts on all connections first before creating disconnect hooks
+ for _, connRef := range connections {
+ const auto = true
+ if err := checkDisconnectConflicts(st, snapName, connRef.PlugRef.Snap, connRef.SlotRef.Snap); err != nil {
+ if _, retry := err.(*state.Retry); retry {
+ logger.Debugf("disconnecting interfaces of snap %q will be retried because of %q - %q conflict", snapName, connRef.PlugRef.Snap, connRef.SlotRef.Snap)
+ task.Logf("Waiting for conflicting change in progress...")
+ return err // will retry
+ }
+ return fmt.Errorf("cannot check conflicts when disconnecting interfaces: %s", err)
+ }
+ }
+
+ hookTasks := state.NewTaskSet()
+ for _, connRef := range connections {
+ conn, err := m.repo.Connection(connRef)
+ if err != nil {
+ break
+ }
+ // "auto-disconnect" flag indicates it's a disconnect triggered as part of snap removal, in which
+ // case we want to skip the logic of marking auto-connections as 'undesired' and instead just remove
+ // them so they can be automatically connected if the snap is installed again.
+ ts, err := disconnectTasks(st, conn, disconnectOpts{AutoDisconnect: true})
+ if err != nil {
+ return err
+ }
+ hookTasks.AddAll(ts)
+ }
+
+ snapstate.InjectTasks(task, hookTasks)
+
+ // make sure that we add tasks and mark this task done in the same atomic write, otherwise there is a risk of re-adding tasks again
+ task.SetStatus(state.DoneStatus)
+ return nil
+}
+
func (m *InterfaceManager) undoAutoConnect(task *state.Task, _ *tomb.Tomb) error {
// TODO Introduce disconnection hooks, and run them here as well to give a chance
// for the snap to undo whatever it did when the connection was established.
@@ -864,8 +1090,7 @@ func (m *InterfaceManager) doGadgetConnect(task *state.Task, _ *tomb.Tomb) error
continue
}
- const auto = true
- if err := checkConnectConflicts(st, plug.Snap.InstanceName(), slot.Snap.InstanceName(), auto); err != nil {
+ if err := checkAutoconnectConflicts(st, plug.Snap.InstanceName(), slot.Snap.InstanceName()); err != nil {
if _, retry := err.(*state.Retry); retry {
task.Logf("gadget connect will be retried because of %q - %q conflict", plug.Snap.InstanceName(), slot.Snap.InstanceName())
return err // will retry
diff --git a/overlord/ifacestate/hooks.go b/overlord/ifacestate/hooks.go
index 3cd8cc4ecb..e6af690128 100644
--- a/overlord/ifacestate/hooks.go
+++ b/overlord/ifacestate/hooks.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2016 Canonical Ltd
+ * Copyright (C) 2016-2018 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,50 +25,34 @@ import (
"github.com/snapcore/snapd/overlord/hookstate"
)
-type prepareHandler struct {
+type interfaceHookHandler struct {
context *hookstate.Context
}
-type connectHandler struct {
- context *hookstate.Context
-}
-
-func (h *prepareHandler) Before() error {
- return nil
-}
-
-func (h *prepareHandler) Done() error {
- return nil
-}
-
-func (h *prepareHandler) Error(err error) error {
+func (h *interfaceHookHandler) Before() error {
return nil
}
-func (h *connectHandler) Before() error {
+func (h *interfaceHookHandler) Done() error {
return nil
}
-func (h *connectHandler) Done() error {
- return nil
-}
-
-func (h *connectHandler) Error(err error) error {
+func (h *interfaceHookHandler) Error(err error) error {
return nil
}
// setupHooks sets hooks of InterfaceManager up
func setupHooks(hookMgr *hookstate.HookManager) {
- prepareGenerator := func(context *hookstate.Context) hookstate.Handler {
- return &prepareHandler{context: context}
- }
-
- connectGenerator := func(context *hookstate.Context) hookstate.Handler {
- return &connectHandler{context: context}
+ gen := func(context *hookstate.Context) hookstate.Handler {
+ return &interfaceHookHandler{context: context}
}
- hookMgr.Register(regexp.MustCompile("^prepare-plug-[-a-z0-9]+$"), prepareGenerator)
- hookMgr.Register(regexp.MustCompile("^prepare-slot-[-a-z0-9]+$"), prepareGenerator)
- hookMgr.Register(regexp.MustCompile("^connect-plug-[-a-z0-9]+$"), connectGenerator)
- hookMgr.Register(regexp.MustCompile("^connect-slot-[-a-z0-9]+$"), connectGenerator)
+ hookMgr.Register(regexp.MustCompile("^prepare-plug-[-a-z0-9]+$"), gen)
+ hookMgr.Register(regexp.MustCompile("^prepare-slot-[-a-z0-9]+$"), gen)
+ hookMgr.Register(regexp.MustCompile("^unprepare-plug-[-a-z0-9]+$"), gen)
+ hookMgr.Register(regexp.MustCompile("^unprepare-slot-[-a-z0-9]+$"), gen)
+ hookMgr.Register(regexp.MustCompile("^connect-plug-[-a-z0-9]+$"), gen)
+ hookMgr.Register(regexp.MustCompile("^connect-slot-[-a-z0-9]+$"), gen)
+ hookMgr.Register(regexp.MustCompile("^disconnect-plug-[-a-z0-9]+$"), gen)
+ hookMgr.Register(regexp.MustCompile("^disconnect-slot-[-a-z0-9]+$"), gen)
}
diff --git a/overlord/ifacestate/ifacemgr.go b/overlord/ifacestate/ifacemgr.go
index b7ab4b7ad6..6f00f73ec2 100644
--- a/overlord/ifacestate/ifacemgr.go
+++ b/overlord/ifacestate/ifacemgr.go
@@ -65,12 +65,13 @@ func Manager(s *state.State, hookManager *hookstate.HookManager, runner *state.T
}
addHandler("connect", m.doConnect, m.undoConnect)
- addHandler("disconnect", m.doDisconnect, nil)
+ addHandler("disconnect", m.doDisconnect, m.undoDisconnect)
addHandler("setup-profiles", m.doSetupProfiles, m.undoSetupProfiles)
addHandler("remove-profiles", m.doRemoveProfiles, m.doSetupProfiles)
addHandler("discard-conns", m.doDiscardConns, m.undoDiscardConns)
addHandler("auto-connect", m.doAutoConnect, m.undoAutoConnect)
addHandler("gadget-connect", m.doGadgetConnect, nil)
+ addHandler("auto-disconnect", m.doAutoDisconnect, nil)
// helper for ubuntu-core -> core
addHandler("transition-ubuntu-core", m.doTransitionUbuntuCore, m.undoTransitionUbuntuCore)
diff --git a/overlord/ifacestate/ifacestate.go b/overlord/ifacestate/ifacestate.go
index fe0780c242..e2c4de1258 100644
--- a/overlord/ifacestate/ifacestate.go
+++ b/overlord/ifacestate/ifacestate.go
@@ -52,18 +52,18 @@ func (e ErrAlreadyConnected) Error() string {
return fmt.Sprintf("already connected: %q", e.Connection.ID())
}
-// findSymmetricAutoconnect checks if there is another auto-connect task affecting same snap.
-func findSymmetricAutoconnect(st *state.State, plugSnap, slotSnap string, autoConnectTask *state.Task) (bool, error) {
- snapsup, err := snapstate.TaskSnapSetup(autoConnectTask)
+// findSymmetricAutoconnectTask checks if there is another auto-connect task affecting same snap because of plug/slot.
+func findSymmetricAutoconnectTask(st *state.State, plugSnap, slotSnap string, installTask *state.Task) (bool, error) {
+ snapsup, err := snapstate.TaskSnapSetup(installTask)
if err != nil {
- return false, fmt.Errorf("internal error: cannot obtain snap setup from task: %s", autoConnectTask.Summary())
+ return false, fmt.Errorf("internal error: cannot obtain snap setup from task: %s", installTask.Summary())
}
installedSnap := snapsup.InstanceName()
// if we find any auto-connect task that's not ready and is affecting our snap, return true to indicate that
// it should be ignored (we shouldn't create connect tasks for it)
for _, task := range st.Tasks() {
- if !task.Status().Ready() && task != autoConnectTask && task.Kind() == "auto-connect" {
+ if !task.Status().Ready() && task.ID() != installTask.ID() && task.Kind() == "auto-connect" {
snapsup, err := snapstate.TaskSnapSetup(task)
if err != nil {
return false, fmt.Errorf("internal error: cannot obtain snap setup from task: %s", task.Summary())
@@ -78,70 +78,6 @@ func findSymmetricAutoconnect(st *state.State, plugSnap, slotSnap string, autoCo
return false, nil
}
-func checkConnectConflicts(st *state.State, plugSnap, slotSnap string, auto bool) error {
- if !auto {
- for _, chg := range st.Changes() {
- if chg.Kind() == "transition-ubuntu-core" {
- return fmt.Errorf("ubuntu-core to core transition in progress, no other changes allowed until this is done")
- }
- }
- }
-
- for _, task := range st.Tasks() {
- if task.Status().Ready() {
- continue
- }
-
- k := task.Kind()
- if auto && k == "connect" {
- var autoConnect bool
- // the auto flag is set for connect tasks created as part of auto-connect
- if err := task.Get("auto", &autoConnect); err != nil && err != state.ErrNoState {
- return err
- }
- // wait for connect task with "auto" flag if they affect our snap
- if autoConnect {
- plugRef, slotRef, err := getPlugAndSlotRefs(task)
- if err != nil {
- return err
- }
- if plugRef.Snap == plugSnap || slotRef.Snap == slotSnap {
- return &state.Retry{After: connectRetryTimeout}
- }
- }
- }
-
- // FIXME: revisit this check for normal connects
- if k == "connect" || k == "disconnect" {
- continue
- }
-
- snapsup, err := snapstate.TaskSnapSetup(task)
- // e.g. hook tasks don't have task snap setup
- if err != nil {
- continue
- }
-
- snapName := snapsup.InstanceName()
-
- // different snaps - no conflict
- if snapName != plugSnap && snapName != slotSnap {
- continue
- }
-
- if k == "unlink-snap" || k == "link-snap" || k == "setup-profiles" {
- if auto {
- // if snap is getting removed, we will retry but the snap will be gone and auto-connect becomes no-op
- // if snap is getting installed/refreshed - temporary conflict, retry later
- return &state.Retry{After: connectRetryTimeout}
- }
- // for connect it's a conflict
- return &snapstate.ChangeConflictError{Snap: snapName, ChangeKind: task.Change().Kind()}
- }
- }
- return nil
-}
-
// Connect returns a set of tasks for connecting an interface.
//
func Connect(st *state.State, plugSnap, plugName, slotSnap, slotName string) (*state.TaskSet, error) {
@@ -196,18 +132,30 @@ func connect(st *state.State, plugSnap, plugName, slotSnap, slotName string, fla
Hook: "prepare-plug-" + plugName,
Optional: true,
}
+ undoPrepPlugHookSetup := &hookstate.HookSetup{
+ Snap: plugSnap,
+ Hook: "unprepare-plug-" + plugName,
+ Optional: true,
+ IgnoreError: true,
+ }
summary = fmt.Sprintf(i18n.G("Run hook %s of snap %q"), plugHookSetup.Hook, plugHookSetup.Snap)
- preparePlugConnection := hookstate.HookTask(st, summary, plugHookSetup, initialContext)
+ preparePlugConnection := hookstate.HookTaskWithUndo(st, summary, plugHookSetup, undoPrepPlugHookSetup, initialContext)
slotHookSetup := &hookstate.HookSetup{
Snap: slotSnap,
Hook: "prepare-slot-" + slotName,
Optional: true,
}
+ undoPrepSlotHookSetup := &hookstate.HookSetup{
+ Snap: slotSnap,
+ Hook: "unprepare-slot-" + slotName,
+ Optional: true,
+ IgnoreError: true,
+ }
summary = fmt.Sprintf(i18n.G("Run hook %s of snap %q"), slotHookSetup.Hook, slotHookSetup.Snap)
- prepareSlotConnection := hookstate.HookTask(st, summary, slotHookSetup, initialContext)
+ prepareSlotConnection := hookstate.HookTaskWithUndo(st, summary, slotHookSetup, undoPrepSlotHookSetup, initialContext)
prepareSlotConnection.WaitFor(preparePlugConnection)
connectInterface.Set("slot", interfaces.SlotRef{Snap: slotSnap, Name: slotName})
@@ -231,9 +179,15 @@ func connect(st *state.State, plugSnap, plugName, slotSnap, slotName string, fla
Hook: "connect-slot-" + slotName,
Optional: true,
}
+ undoConnectSlotHookSetup := &hookstate.HookSetup{
+ Snap: slotSnap,
+ Hook: "disconnect-slot-" + slotName,
+ Optional: true,
+ IgnoreError: true,
+ }
summary = fmt.Sprintf(i18n.G("Run hook %s of snap %q"), connectSlotHookSetup.Hook, connectSlotHookSetup.Snap)
- connectSlotConnection := hookstate.HookTask(st, summary, connectSlotHookSetup, initialContext)
+ connectSlotConnection := hookstate.HookTaskWithUndo(st, summary, connectSlotHookSetup, undoConnectSlotHookSetup, initialContext)
connectSlotConnection.WaitFor(connectInterface)
connectPlugHookSetup := &hookstate.HookSetup{
@@ -241,9 +195,15 @@ func connect(st *state.State, plugSnap, plugName, slotSnap, slotName string, fla
Hook: "connect-plug-" + plugName,
Optional: true,
}
+ undoConnectPlugHookSetup := &hookstate.HookSetup{
+ Snap: plugSnap,
+ Hook: "disconnect-plug-" + plugName,
+ Optional: true,
+ IgnoreError: true,
+ }
summary = fmt.Sprintf(i18n.G("Run hook %s of snap %q"), connectPlugHookSetup.Hook, connectPlugHookSetup.Snap)
- connectPlugConnection := hookstate.HookTask(st, summary, connectPlugHookSetup, initialContext)
+ connectPlugConnection := hookstate.HookTaskWithUndo(st, summary, connectPlugHookSetup, undoConnectPlugHookSetup, initialContext)
connectPlugConnection.WaitFor(connectSlotConnection)
return state.NewTaskSet(preparePlugConnection, prepareSlotConnection, connectInterface, connectSlotConnection, connectPlugConnection), nil
@@ -285,17 +245,104 @@ func initialConnectAttributes(st *state.State, plugSnap string, plugName string,
}
// Disconnect returns a set of tasks for disconnecting an interface.
-func Disconnect(st *state.State, plugSnap, plugName, slotSnap, slotName string) (*state.TaskSet, error) {
+func Disconnect(st *state.State, conn *interfaces.Connection) (*state.TaskSet, error) {
+ plugSnap := conn.Plug.Snap().InstanceName()
+ slotSnap := conn.Slot.Snap().InstanceName()
if err := snapstate.CheckChangeConflictMany(st, []string{plugSnap, slotSnap}, ""); err != nil {
return nil, err
}
+ return disconnectTasks(st, conn, disconnectOpts{})
+}
+
+type disconnectOpts struct {
+ AutoDisconnect bool
+}
+
+// disconnectTasks creates a set of tasks for disconnect, including hooks, but does not do any conflict checking.
+func disconnectTasks(st *state.State, conn *interfaces.Connection, flags disconnectOpts) (*state.TaskSet, error) {
+ plugSnap := conn.Plug.Snap().InstanceName()
+ slotSnap := conn.Slot.Snap().InstanceName()
+ plugName := conn.Plug.Name()
+ slotName := conn.Slot.Name()
+
+ var plugSnapst, slotSnapst snapstate.SnapState
+ if err := snapstate.Get(st, slotSnap, &slotSnapst); err != nil {
+ return nil, err
+ }
+ if err := snapstate.Get(st, plugSnap, &plugSnapst); err != nil {
+ return nil, err
+ }
+
summary := fmt.Sprintf(i18n.G("Disconnect %s:%s from %s:%s"),
plugSnap, plugName, slotSnap, slotName)
- task := st.NewTask("disconnect", summary)
- task.Set("slot", interfaces.SlotRef{Snap: slotSnap, Name: slotName})
- task.Set("plug", interfaces.PlugRef{Snap: plugSnap, Name: plugName})
- return state.NewTaskSet(task), nil
+ disconnectTask := st.NewTask("disconnect", summary)
+ disconnectTask.Set("slot", interfaces.SlotRef{Snap: slotSnap, Name: slotName})
+ disconnectTask.Set("plug", interfaces.PlugRef{Snap: plugSnap, Name: plugName})
+
+ disconnectTask.Set("slot-static", conn.Slot.StaticAttrs())
+ disconnectTask.Set("slot-dynamic", conn.Slot.DynamicAttrs())
+ disconnectTask.Set("plug-static", conn.Plug.StaticAttrs())
+ disconnectTask.Set("plug-dynamic", conn.Plug.DynamicAttrs())
+
+ if flags.AutoDisconnect {
+ disconnectTask.Set("auto-disconnect", true)
+ }
+
+ ts := state.NewTaskSet()
+
+ initialContext := make(map[string]interface{})
+ initialContext["attrs-task"] = disconnectTask.ID()
+
+ var disconnectSlot *state.Task
+
+ // only run slot hooks if slotSnap is active
+ if slotSnapst.Active {
+ disconnectSlotHookSetup := &hookstate.HookSetup{
+ Snap: slotSnap,
+ Hook: "disconnect-slot-" + slotName,
+ Optional: true,
+ }
+ undoDisconnectSlotHookSetup := &hookstate.HookSetup{
+ Snap: slotSnap,
+ Hook: "connect-slot-" + slotName,
+ Optional: true,
+ }
+
+ summary := fmt.Sprintf(i18n.G("Run hook %s of snap %q"), disconnectSlotHookSetup.Hook, disconnectSlotHookSetup.Snap)
+ disconnectSlot = hookstate.HookTaskWithUndo(st, summary, disconnectSlotHookSetup, undoDisconnectSlotHookSetup, initialContext)
+
+ ts.AddTask(disconnectSlot)
+ disconnectTask.WaitFor(disconnectSlot)
+ }
+
+ // only run plug hooks if plugSnap is active
+ if plugSnapst.Active {
+ disconnectPlugHookSetup := &hookstate.HookSetup{
+ Snap: plugSnap,
+ Hook: "disconnect-plug-" + plugName,
+ Optional: true,
+ }
+ undoDisconnectPlugHookSetup := &hookstate.HookSetup{
+ Snap: plugSnap,
+ Hook: "connect-plug-" + plugName,
+ Optional: true,
+ }
+
+ summary := fmt.Sprintf(i18n.G("Run hook %s of snap %q"), disconnectPlugHookSetup.Hook, disconnectPlugHookSetup.Snap)
+ disconnectPlug := hookstate.HookTaskWithUndo(st, summary, disconnectPlugHookSetup, undoDisconnectPlugHookSetup, initialContext)
+ disconnectPlug.WaitAll(ts)
+
+ if disconnectSlot != nil {
+ disconnectPlug.WaitFor(disconnectSlot)
+ }
+
+ ts.AddTask(disconnectPlug)
+ disconnectTask.WaitFor(disconnectPlug)
+ }
+
+ ts.AddTask(disconnectTask)
+ return ts, nil
}
// CheckInterfaces checks whether plugs and slots of snap are allowed for installation.
diff --git a/overlord/ifacestate/ifacestate_test.go b/overlord/ifacestate/ifacestate_test.go
index 574e9a2269..7ec57f7a65 100644
--- a/overlord/ifacestate/ifacestate_test.go
+++ b/overlord/ifacestate/ifacestate_test.go
@@ -195,27 +195,27 @@ func (s *interfaceManagerSuite) TestConnectTask(c *C) {
i := 0
task := ts.Tasks()[i]
c.Check(task.Kind(), Equals, "run-hook")
- var hookSetup hookstate.HookSetup
- err = task.Get("hook-setup", &hookSetup)
- c.Assert(err, IsNil)
+ var hookSetup, undoHookSetup hookstate.HookSetup
+ c.Assert(task.Get("hook-setup", &hookSetup), IsNil)
c.Assert(hookSetup, Equals, hookstate.HookSetup{Snap: "consumer", Hook: "prepare-plug-plug", Optional: true})
+ c.Assert(task.Get("undo-hook-setup", &undoHookSetup), IsNil)
+ c.Assert(undoHookSetup, Equals, hookstate.HookSetup{Snap: "consumer", Hook: "unprepare-plug-plug", Optional: true, IgnoreError: true})
i++
task = ts.Tasks()[i]
c.Check(task.Kind(), Equals, "run-hook")
- err = task.Get("hook-setup", &hookSetup)
- c.Assert(err, IsNil)
+ c.Assert(task.Get("hook-setup", &hookSetup), IsNil)
c.Assert(hookSetup, Equals, hookstate.HookSetup{Snap: "producer", Hook: "prepare-slot-slot", Optional: true})
+ c.Assert(task.Get("undo-hook-setup", &undoHookSetup), IsNil)
+ c.Assert(undoHookSetup, Equals, hookstate.HookSetup{Snap: "producer", Hook: "unprepare-slot-slot", Optional: true, IgnoreError: true})
i++
task = ts.Tasks()[i]
c.Assert(task.Kind(), Equals, "connect")
var plug interfaces.PlugRef
- err = task.Get("plug", &plug)
- c.Assert(err, IsNil)
+ c.Assert(task.Get("plug", &plug), IsNil)
c.Assert(plug.Snap, Equals, "consumer")
c.Assert(plug.Name, Equals, "plug")
var slot interfaces.SlotRef
- err = task.Get("slot", &slot)
- c.Assert(err, IsNil)
+ c.Assert(task.Get("slot", &slot), IsNil)
c.Assert(slot.Snap, Equals, "producer")
c.Assert(slot.Name, Equals, "slot")
@@ -227,34 +227,32 @@ func (s *interfaceManagerSuite) TestConnectTask(c *C) {
// verify initial attributes are present in connect task
var plugStaticAttrs map[string]interface{}
var plugDynamicAttrs map[string]interface{}
- err = task.Get("plug-static", &plugStaticAttrs)
- c.Assert(err, IsNil)
+ c.Assert(task.Get("plug-static", &plugStaticAttrs), IsNil)
c.Assert(plugStaticAttrs, DeepEquals, map[string]interface{}{"attr1": "value1"})
- err = task.Get("plug-dynamic", &plugDynamicAttrs)
- c.Assert(err, IsNil)
+ c.Assert(task.Get("plug-dynamic", &plugDynamicAttrs), IsNil)
c.Assert(plugDynamicAttrs, DeepEquals, map[string]interface{}{})
var slotStaticAttrs map[string]interface{}
var slotDynamicAttrs map[string]interface{}
- err = task.Get("slot-static", &slotStaticAttrs)
- c.Assert(err, IsNil)
+ c.Assert(task.Get("slot-static", &slotStaticAttrs), IsNil)
c.Assert(slotStaticAttrs, DeepEquals, map[string]interface{}{"attr2": "value2"})
- err = task.Get("slot-dynamic", &slotDynamicAttrs)
- c.Assert(err, IsNil)
+ c.Assert(task.Get("slot-dynamic", &slotDynamicAttrs), IsNil)
c.Assert(slotDynamicAttrs, DeepEquals, map[string]interface{}{})
i++
task = ts.Tasks()[i]
c.Check(task.Kind(), Equals, "run-hook")
- err = task.Get("hook-setup", &hs)
- c.Assert(err, IsNil)
+ c.Assert(task.Get("hook-setup", &hs), IsNil)
c.Assert(hs, Equals, hookstate.HookSetup{Snap: "producer", Hook: "connect-slot-slot", Optional: true})
+ c.Assert(task.Get("undo-hook-setup", &undoHookSetup), IsNil)
+ c.Assert(undoHookSetup, Equals, hookstate.HookSetup{Snap: "producer", Hook: "disconnect-slot-slot", Optional: true, IgnoreError: true})
i++
task = ts.Tasks()[i]
c.Check(task.Kind(), Equals, "run-hook")
- err = task.Get("hook-setup", &hs)
- c.Assert(err, IsNil)
+ c.Assert(task.Get("hook-setup", &hs), IsNil)
c.Assert(hs, Equals, hookstate.HookSetup{Snap: "consumer", Hook: "connect-plug-plug", Optional: true})
+ c.Assert(task.Get("undo-hook-setup", &undoHookSetup), IsNil)
+ c.Assert(undoHookSetup, Equals, hookstate.HookSetup{Snap: "consumer", Hook: "disconnect-plug-plug", Optional: true, IgnoreError: true})
}
func (s *interfaceManagerSuite) TestParallelInstallConnectTask(c *C) {
@@ -389,6 +387,27 @@ func (s *interfaceManagerSuite) testConnectDisconnectConflicts(c *C, f func(*sta
c.Assert(err, ErrorMatches, expectedErr)
}
+func (s *interfaceManagerSuite) testDisconnectConflicts(c *C, snapName string, otherTaskKind string, expectedErr string) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ chg := s.state.NewChange("other-chg", "...")
+ t := s.state.NewTask(otherTaskKind, "...")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{
+ RealName: snapName},
+ })
+ chg.AddTask(t)
+
+ conn := &interfaces.Connection{
+ Plug: interfaces.NewConnectedPlug(&snap.PlugInfo{Snap: &snap.Info{SuggestedName: "consumer"}, Name: "plug"}, nil),
+ Slot: interfaces.NewConnectedSlot(&snap.SlotInfo{Snap: &snap.Info{SuggestedName: "producer"}, Name: "slot"}, nil),
+ }
+
+ _, err := ifacestate.Disconnect(s.state, conn)
+ c.Assert(err, ErrorMatches, expectedErr)
+}
+
func (s *interfaceManagerSuite) TestConnectConflictsPlugSnapOnLinkSnap(c *C) {
s.testConnectDisconnectConflicts(c, ifacestate.Connect, "consumer", "link-snap", `snap "consumer" has "other-chg" change in progress`)
}
@@ -406,11 +425,11 @@ func (s *interfaceManagerSuite) TestConnectConflictsSlotSnapOnUnlink(c *C) {
}
func (s *interfaceManagerSuite) TestDisconnectConflictsPlugSnapOnLink(c *C) {
- s.testConnectDisconnectConflicts(c, ifacestate.Disconnect, "consumer", "link-snap", `snap "consumer" has "other-chg" change in progress`)
+ s.testDisconnectConflicts(c, "consumer", "link-snap", `snap "consumer" has "other-chg" change in progress`)
}
func (s *interfaceManagerSuite) TestDisconnectConflictsSlotSnapOnLink(c *C) {
- s.testConnectDisconnectConflicts(c, ifacestate.Disconnect, "producer", "link-snap", `snap "producer" has "other-chg" change in progress`)
+ s.testDisconnectConflicts(c, "producer", "link-snap", `snap "producer" has "other-chg" change in progress`)
}
func (s *interfaceManagerSuite) TestConnectDoesConflict(c *C) {
@@ -430,7 +449,11 @@ func (s *interfaceManagerSuite) TestConnectDoesConflict(c *C) {
_, err := ifacestate.Connect(s.state, "consumer", "plug", "producer", "slot")
c.Assert(err, ErrorMatches, `snap "consumer" has "other-connect" change in progress`)
- _, err = ifacestate.Disconnect(s.state, "consumer", "plug", "producer", "slot")
+ conn := &interfaces.Connection{
+ Plug: interfaces.NewConnectedPlug(&snap.PlugInfo{Snap: &snap.Info{SuggestedName: "consumer"}, Name: "plug"}, nil),
+ Slot: interfaces.NewConnectedSlot(&snap.SlotInfo{Snap: &snap.Info{SuggestedName: "producer"}, Name: "slot"}, nil),
+ }
+ _, err = ifacestate.Disconnect(s.state, conn)
c.Assert(err, ErrorMatches, `snap "consumer" has "other-connect" change in progress`)
}
@@ -459,10 +482,10 @@ func (s *interfaceManagerSuite) TestAutoconnectDoesntConflictOnInstallingDiffere
t.Set("snap-setup", sup1)
chg.AddTask(t)
- ignore, err := ifacestate.FindSymmetricAutoconnect(s.state, "consumer", "producer", t)
+ ignore, err := ifacestate.FindSymmetricAutoconnectTask(s.state, "consumer", "producer", t)
c.Assert(err, IsNil)
c.Assert(ignore, Equals, false)
- c.Assert(ifacestate.CheckConnectConflicts(s.state, "consumer", "producer", true), IsNil)
+ c.Assert(ifacestate.CheckAutoconnectConflicts(s.state, "consumer", "producer"), IsNil)
ts, err := ifacestate.ConnectPriv(s.state, "consumer", "plug", "producer", "slot", []string{"auto"})
c.Assert(err, IsNil)
@@ -497,11 +520,11 @@ func (s *interfaceManagerSuite) createAutoconnectChange(c *C, conflictingTask *s
chg.AddTask(t2)
- ignore, err := ifacestate.FindSymmetricAutoconnect(s.state, "consumer", "producer", t2)
+ ignore, err := ifacestate.FindSymmetricAutoconnectTask(s.state, "consumer", "producer", t2)
c.Assert(err, IsNil)
c.Assert(ignore, Equals, false)
- return ifacestate.CheckConnectConflicts(s.state, "consumer", "producer", true)
+ return ifacestate.CheckAutoconnectConflicts(s.state, "consumer", "producer")
}
func (s *interfaceManagerSuite) testRetryError(c *C, err error) {
@@ -554,11 +577,11 @@ func (s *interfaceManagerSuite) TestSymmetricAutoconnectIgnore(c *C) {
t2.Set("snap-setup", sup2)
chg2.AddTask(t2)
- ignore, err := ifacestate.FindSymmetricAutoconnect(s.state, "consumer", "producer", t1)
+ ignore, err := ifacestate.FindSymmetricAutoconnectTask(s.state, "consumer", "producer", t1)
c.Assert(err, IsNil)
c.Assert(ignore, Equals, true)
- ignore, err = ifacestate.FindSymmetricAutoconnect(s.state, "consumer", "producer", t2)
+ ignore, err = ifacestate.FindSymmetricAutoconnectTask(s.state, "consumer", "producer", t2)
c.Assert(err, IsNil)
c.Assert(ignore, Equals, true)
}
@@ -576,8 +599,7 @@ func (s *interfaceManagerSuite) TestAutoconnectConflictOnConnectWithAutoFlag(c *
c.Assert(err, ErrorMatches, `task should be retried`)
}
-func (s *interfaceManagerSuite) TestAutoconnectNoConflictOnConnect(c *C) {
- // FIXME: flesh this test out once individual connect conflict check is fleshed out
+func (s *interfaceManagerSuite) TestAutoconnectRetryOnConnect(c *C) {
s.state.Lock()
task := s.state.NewTask("connect", "")
task.Set("slot", interfaces.SlotRef{Snap: "producer", Name: "slot"})
@@ -586,7 +608,7 @@ func (s *interfaceManagerSuite) TestAutoconnectNoConflictOnConnect(c *C) {
s.state.Unlock()
err := s.createAutoconnectChange(c, task)
- c.Assert(err, IsNil)
+ c.Assert(err, ErrorMatches, `task should be retried`)
}
func (s *interfaceManagerSuite) TestEnsureProcessesConnectTask(c *C) {
@@ -784,11 +806,58 @@ func (s *interfaceManagerSuite) TestDisconnectTask(c *C) {
s.state.Lock()
defer s.state.Unlock()
- ts, err := ifacestate.Disconnect(s.state, "consumer", "plug", "producer", "slot")
+ sideInfo := &snap.SideInfo{Revision: snap.R(1)}
+ snapInfo := snaptest.MockSnap(c, consumerYaml, sideInfo)
+ snapstate.Set(s.state, snapInfo.InstanceName(), &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{sideInfo},
+ Current: sideInfo.Revision,
+ })
+ snapInfo = snaptest.MockSnap(c, producerYaml, sideInfo)
+ snapstate.Set(s.state, snapInfo.InstanceName(), &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{sideInfo},
+ Current: sideInfo.Revision,
+ })
+
+ conn := &interfaces.Connection{
+ Plug: interfaces.NewConnectedPlug(&snap.PlugInfo{
+ Snap: &snap.Info{SuggestedName: "consumer"},
+ Name: "plug",
+ Attrs: map[string]interface{}{"attr1": "value1"}},
+ map[string]interface{}{"attr3": "value3"}),
+ Slot: interfaces.NewConnectedSlot(&snap.SlotInfo{
+ Snap: &snap.Info{SuggestedName: "producer"},
+ Name: "slot",
+ Attrs: map[string]interface{}{"attr2": "value2"}},
+ map[string]interface{}{"attr4": "value4"}),
+ }
+ ts, err := ifacestate.Disconnect(s.state, conn)
c.Assert(err, IsNil)
+ c.Assert(ts.Tasks(), HasLen, 3)
+ var hookSetup, undoHookSetup hookstate.HookSetup
task := ts.Tasks()[0]
+ c.Assert(task.Kind(), Equals, "run-hook")
+ c.Assert(task.Get("hook-setup", &hookSetup), IsNil)
+ c.Assert(hookSetup, Equals, hookstate.HookSetup{Snap: "producer", Hook: "disconnect-slot-slot", Optional: true, IgnoreError: false})
+ c.Assert(task.Get("undo-hook-setup", &undoHookSetup), IsNil)
+ c.Assert(undoHookSetup, Equals, hookstate.HookSetup{Snap: "producer", Hook: "connect-slot-slot", Optional: true, IgnoreError: false})
+
+ task = ts.Tasks()[1]
+ c.Assert(task.Kind(), Equals, "run-hook")
+ err = task.Get("hook-setup", &hookSetup)
+ c.Assert(err, IsNil)
+ c.Assert(hookSetup, Equals, hookstate.HookSetup{Snap: "consumer", Hook: "disconnect-plug-plug", Optional: true})
+ c.Assert(task.Get("undo-hook-setup", &undoHookSetup), IsNil)
+ c.Assert(undoHookSetup, Equals, hookstate.HookSetup{Snap: "consumer", Hook: "connect-plug-plug", Optional: true, IgnoreError: false})
+
+ task = ts.Tasks()[2]
c.Assert(task.Kind(), Equals, "disconnect")
+ var autoDisconnect bool
+ c.Assert(task.Get("auto-disconnect", &autoDisconnect), Equals, state.ErrNoState)
+ c.Assert(autoDisconnect, Equals, false)
+
var plug interfaces.PlugRef
err = task.Get("plug", &plug)
c.Assert(err, IsNil)
@@ -799,6 +868,19 @@ func (s *interfaceManagerSuite) TestDisconnectTask(c *C) {
c.Assert(err, IsNil)
c.Assert(slot.Snap, Equals, "producer")
c.Assert(slot.Name, Equals, "slot")
+
+ // verify connection attributes are present in the disconnect task
+ var plugStaticAttrs1, plugDynamicAttrs1, slotStaticAttrs1, slotDynamicAttrs1 map[string]interface{}
+
+ c.Assert(task.Get("plug-static", &plugStaticAttrs1), IsNil)
+ c.Assert(plugStaticAttrs1, DeepEquals, map[string]interface{}{"attr1": "value1"})
+ c.Assert(task.Get("plug-dynamic", &plugDynamicAttrs1), IsNil)
+ c.Assert(plugDynamicAttrs1, DeepEquals, map[string]interface{}{"attr3": "value3"})
+
+ c.Assert(task.Get("slot-static", &slotStaticAttrs1), IsNil)
+ c.Assert(slotStaticAttrs1, DeepEquals, map[string]interface{}{"attr2": "value2"})
+ c.Assert(task.Get("slot-dynamic", &slotDynamicAttrs1), IsNil)
+ c.Assert(slotDynamicAttrs1, DeepEquals, map[string]interface{}{"attr4": "value4"})
}
// Disconnect works when both plug and slot are specified
@@ -806,6 +888,16 @@ func (s *interfaceManagerSuite) TestDisconnectFull(c *C) {
s.testDisconnect(c, "consumer", "plug", "producer", "slot")
}
+func (s *interfaceManagerSuite) getConnection(c *C, plugSnap, plugName, slotSnap, slotName string) *interfaces.Connection {
+ conn, err := s.manager(c).Repository().Connection(&interfaces.ConnRef{
+ PlugRef: interfaces.PlugRef{Snap: plugSnap, Name: plugName},
+ SlotRef: interfaces.SlotRef{Snap: slotSnap, Name: slotName},
+ })
+ c.Assert(err, IsNil)
+ c.Assert(conn, NotNil)
+ return conn
+}
+
func (s *interfaceManagerSuite) testDisconnect(c *C, plugSnap, plugName, slotSnap, slotName string) {
// Put two snaps in place They consumer has an plug that can be connected
// to slot on the producer.
@@ -824,10 +916,12 @@ func (s *interfaceManagerSuite) testDisconnect(c *C, plugSnap, plugName, slotSna
// Initialize the manager. This registers both snaps and reloads the connection.
mgr := s.manager(c)
+ conn := s.getConnection(c, plugSnap, plugName, slotSnap, slotName)
+
// Run the disconnect task and let it finish.
s.state.Lock()
change := s.state.NewChange("disconnect", "...")
- ts, err := ifacestate.Disconnect(s.state, plugSnap, plugName, slotSnap, slotName)
+ ts, err := ifacestate.Disconnect(s.state, conn)
ts.Tasks()[0].Set("snap-setup", &snapstate.SnapSetup{
SideInfo: &snap.SideInfo{
RealName: "consumer",
@@ -837,15 +931,16 @@ func (s *interfaceManagerSuite) testDisconnect(c *C, plugSnap, plugName, slotSna
c.Assert(err, IsNil)
change.AddAll(ts)
s.state.Unlock()
- s.se.Ensure()
- s.se.Wait()
+
+ s.settle(c)
s.state.Lock()
defer s.state.Unlock()
// Ensure that the task succeeded.
c.Assert(change.Err(), IsNil)
- task := change.Tasks()[0]
+ c.Assert(change.Tasks(), HasLen, 3)
+ task := change.Tasks()[2]
c.Check(task.Kind(), Equals, "disconnect")
c.Check(task.Status(), Equals, state.DoneStatus)
@@ -872,6 +967,60 @@ func (s *interfaceManagerSuite) testDisconnect(c *C, plugSnap, plugName, slotSna
c.Check(s.secBackend.SetupCalls[1].Options, Equals, interfaces.ConfinementOptions{})
}
+func (s *interfaceManagerSuite) TestDisconnectUndo(c *C) {
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
+ s.mockSnap(c, consumerYaml)
+ s.mockSnap(c, producerYaml)
+
+ connState := map[string]interface{}{
+ "consumer:plug producer:slot": map[string]interface{}{
+ "interface": "test",
+ "slot-static": map[string]interface{}{"attr1": "value1"},
+ "slot-dynamic": map[string]interface{}{"attr2": "value2"},
+ "plug-static": map[string]interface{}{"attr3": "value3"},
+ "plug-dynamic": map[string]interface{}{"attr4": "value4"},
+ },
+ }
+
+ s.state.Lock()
+ s.state.Set("conns", connState)
+ s.state.Unlock()
+
+ // Initialize the manager. This registers both snaps and reloads the connection.
+ _ = s.manager(c)
+
+ conn := s.getConnection(c, "consumer", "plug", "producer", "slot")
+
+ // Run the disconnect task and let it finish.
+ s.state.Lock()
+ change := s.state.NewChange("disconnect", "...")
+ ts, err := ifacestate.Disconnect(s.state, conn)
+
+ c.Assert(err, IsNil)
+ change.AddAll(ts)
+ terr := s.state.NewTask("error-trigger", "provoking total undo")
+ terr.WaitAll(ts)
+ change.AddTask(terr)
+ c.Assert(change.Tasks(), HasLen, 4)
+ s.state.Unlock()
+
+ s.settle(c)
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ // Ensure that disconnect tasks were undone
+ for _, t := range ts.Tasks() {
+ c.Assert(t.Status(), Equals, state.UndoneStatus)
+ }
+
+ var conns map[string]interface{}
+ c.Assert(s.state.Get("conns", &conns), IsNil)
+ c.Assert(conns, DeepEquals, connState)
+
+ _ = s.getConnection(c, "consumer", "plug", "producer", "slot")
+}
+
func (s *interfaceManagerSuite) TestStaleConnectionsIgnoredInReloadConnections(c *C) {
s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
@@ -2035,9 +2184,10 @@ func (s *interfaceManagerSuite) TestDisconnectSetsUpSecurity(c *C) {
s.state.Unlock()
s.manager(c)
+ conn := s.getConnection(c, "consumer", "plug", "producer", "slot")
s.state.Lock()
- ts, err := ifacestate.Disconnect(s.state, "consumer", "plug", "producer", "slot")
+ ts, err := ifacestate.Disconnect(s.state, conn)
c.Assert(err, IsNil)
ts.Tasks()[0].Set("snap-setup", &snapstate.SnapSetup{
SideInfo: &snap.SideInfo{
@@ -2049,9 +2199,7 @@ func (s *interfaceManagerSuite) TestDisconnectSetsUpSecurity(c *C) {
change.AddAll(ts)
s.state.Unlock()
- s.se.Ensure()
- s.se.Wait()
- s.se.Stop()
+ s.settle(c)
s.state.Lock()
defer s.state.Unlock()
@@ -2080,8 +2228,9 @@ func (s *interfaceManagerSuite) TestDisconnectTracksConnectionsInState(c *C) {
s.manager(c)
+ conn := s.getConnection(c, "consumer", "plug", "producer", "slot")
s.state.Lock()
- ts, err := ifacestate.Disconnect(s.state, "consumer", "plug", "producer", "slot")
+ ts, err := ifacestate.Disconnect(s.state, conn)
c.Assert(err, IsNil)
ts.Tasks()[0].Set("snap-setup", &snapstate.SnapSetup{
SideInfo: &snap.SideInfo{
@@ -2093,9 +2242,7 @@ func (s *interfaceManagerSuite) TestDisconnectTracksConnectionsInState(c *C) {
change.AddAll(ts)
s.state.Unlock()
- s.se.Ensure()
- s.se.Wait()
- s.se.Stop()
+ s.settle(c)
s.state.Lock()
defer s.state.Unlock()
@@ -2121,7 +2268,12 @@ func (s *interfaceManagerSuite) TestDisconnectDisablesAutoConnect(c *C) {
s.manager(c)
s.state.Lock()
- ts, err := ifacestate.Disconnect(s.state, "consumer", "plug", "producer", "slot")
+ conn := &interfaces.Connection{
+ Plug: interfaces.NewConnectedPlug(&snap.PlugInfo{Snap: &snap.Info{SuggestedName: "consumer"}, Name: "plug"}, nil),
+ Slot: interfaces.NewConnectedSlot(&snap.SlotInfo{Snap: &snap.Info{SuggestedName: "producer"}, Name: "slot"}, nil),
+ }
+
+ ts, err := ifacestate.Disconnect(s.state, conn)
c.Assert(err, IsNil)
ts.Tasks()[0].Set("snap-setup", &snapstate.SnapSetup{
SideInfo: &snap.SideInfo{
@@ -2133,9 +2285,7 @@ func (s *interfaceManagerSuite) TestDisconnectDisablesAutoConnect(c *C) {
change.AddAll(ts)
s.state.Unlock()
- s.se.Ensure()
- s.se.Wait()
- s.se.Stop()
+ s.settle(c)
s.state.Lock()
defer s.state.Unlock()
@@ -3072,6 +3222,143 @@ func (s *interfaceManagerSuite) TestSnapsWithSecurityProfiles(c *C) {
})
}
+func (s *interfaceManagerSuite) TestDisconnectInterfaces(c *C) {
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
+ _ = s.manager(c)
+
+ consumerInfo := s.mockSnap(c, consumerYaml)
+ producerInfo := s.mockSnap(c, producerYaml)
+
+ s.state.Lock()
+
+ sup := &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{
+ RealName: "consumer"},
+ }
+
+ repo := s.manager(c).Repository()
+ c.Assert(repo.AddSnap(consumerInfo), IsNil)
+ c.Assert(repo.AddSnap(producerInfo), IsNil)
+
+ plugDynAttrs := map[string]interface{}{
+ "attr3": "value3",
+ }
+ slotDynAttrs := map[string]interface{}{
+ "attr4": "value4",
+ }
+ repo.Connect(&interfaces.ConnRef{
+ PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
+ SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
+ }, plugDynAttrs, slotDynAttrs, nil)
+
+ chg := s.state.NewChange("install", "")
+ t := s.state.NewTask("auto-disconnect", "")
+ t.Set("snap-setup", sup)
+ chg.AddTask(t)
+
+ s.state.Unlock()
+
+ s.se.Ensure()
+ s.se.Wait()
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ ht := t.HaltTasks()
+ c.Assert(ht, HasLen, 3)
+
+ c.Assert(ht[2].Kind(), Equals, "disconnect")
+ var autoDisconnect bool
+ c.Assert(ht[2].Get("auto-disconnect", &autoDisconnect), IsNil)
+ c.Assert(autoDisconnect, Equals, true)
+ var plugDynamic, slotDynamic, plugStatic, slotStatic map[string]interface{}
+ c.Assert(ht[2].Get("plug-static", &plugStatic), IsNil)
+ c.Assert(ht[2].Get("plug-dynamic", &plugDynamic), IsNil)
+ c.Assert(ht[2].Get("slot-static", &slotStatic), IsNil)
+ c.Assert(ht[2].Get("slot-dynamic", &slotDynamic), IsNil)
+
+ c.Assert(plugStatic, DeepEquals, map[string]interface{}{"attr1": "value1"})
+ c.Assert(slotStatic, DeepEquals, map[string]interface{}{"attr2": "value2"})
+ c.Assert(plugDynamic, DeepEquals, map[string]interface{}{"attr3": "value3"})
+ c.Assert(slotDynamic, DeepEquals, map[string]interface{}{"attr4": "value4"})
+
+ var expectedHooks = []struct{ snap, hook string }{
+ {snap: "producer", hook: "disconnect-slot-slot"},
+ {snap: "consumer", hook: "disconnect-plug-plug"},
+ }
+
+ for i := 0; i < 2; i++ {
+ var hsup hookstate.HookSetup
+ c.Assert(ht[i].Kind(), Equals, "run-hook")
+ c.Assert(ht[i].Get("hook-setup", &hsup), IsNil)
+
+ c.Assert(hsup.Snap, Equals, expectedHooks[i].snap)
+ c.Assert(hsup.Hook, Equals, expectedHooks[i].hook)
+ }
+}
+
+func (s *interfaceManagerSuite) testDisconnectInterfacesRetry(c *C, conflictingKind string) {
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
+ _ = s.manager(c)
+
+ consumerInfo := s.mockSnap(c, consumerYaml)
+ producerInfo := s.mockSnap(c, producerYaml)
+
+ supprod := &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{
+ RealName: "producer"},
+ }
+
+ s.state.Lock()
+
+ repo := s.manager(c).Repository()
+ c.Assert(repo.AddSnap(consumerInfo), IsNil)
+ c.Assert(repo.AddSnap(producerInfo), IsNil)
+
+ repo.Connect(&interfaces.ConnRef{
+ PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
+ SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
+ }, nil, nil, nil)
+
+ sup := &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{
+ RealName: "consumer"},
+ }
+
+ chg2 := s.state.NewChange("remove", "")
+ t2 := s.state.NewTask("auto-disconnect", "")
+ t2.Set("snap-setup", sup)
+ chg2.AddTask(t2)
+
+ // create conflicting task
+ chg1 := s.state.NewChange("conflicting change", "")
+ t1 := s.state.NewTask(conflictingKind, "")
+ t1.Set("snap-setup", supprod)
+ chg1.AddTask(t1)
+ t3 := s.state.NewTask("other", "")
+ t1.WaitFor(t3)
+ chg1.AddTask(t3)
+ t3.SetStatus(state.HoldStatus)
+
+ s.state.Unlock()
+ s.se.Ensure()
+ s.se.Wait()
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ c.Assert(strings.Join(t2.Log(), ""), Matches, `.*Waiting for conflicting change in progress...`)
+ c.Assert(t2.Status(), Equals, state.DoingStatus)
+}
+
+func (s *interfaceManagerSuite) TestDisconnectInterfacesRetryLink(c *C) {
+ s.testDisconnectInterfacesRetry(c, "link-snap")
+}
+
+func (s *interfaceManagerSuite) TestDisconnectInterfacesRetrySetupProfiles(c *C) {
+ s.testDisconnectInterfacesRetry(c, "setup-profiles")
+}
+
func (s *interfaceManagerSuite) setupGadgetConnect(c *C) {
s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
s.mockSnapDecl(c, "consumer", "publisher1", nil)
diff --git a/overlord/managers_test.go b/overlord/managers_test.go
index ae17c1788e..9d6259142a 100644
--- a/overlord/managers_test.go
+++ b/overlord/managers_test.go
@@ -2205,6 +2205,14 @@ func (ms *mgrsSuite) TestRemoveAndInstallWithAutoconnectHappy(c *C) {
c.Assert(chg2.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err()))
}
+const otherSnapYaml = `name: other-snap
+version: 1.0
+apps:
+ baz:
+ command: bin/bar
+ plugs: [media-hub]
+`
+
func (ms *mgrsSuite) TestUpdateManyWithAutoconnect(c *C) {
const someSnapYaml = `name: some-snap
version: 1.0
@@ -2214,13 +2222,7 @@ apps:
plugs: [network,home]
slots: [media-hub]
`
- const otherSnapYaml = `name: other-snap
-version: 1.0
-apps:
- baz:
- command: bin/bar
- plugs: [media-hub]
-`
+
const coreSnapYaml = `name: core
type: os
version: @VERSION@`
@@ -2335,21 +2337,15 @@ version: @VERSION@`
c.Assert(connections, HasLen, 3)
}
-func (ms *mgrsSuite) testUpdateWithAutoconnectRetry(c *C, updateSnapName, removeSnapName string) {
- const someSnapYaml = `name: some-snap
+const someSnapYaml = `name: some-snap
version: 1.0
apps:
foo:
command: bin/bar
slots: [media-hub]
`
- const otherSnapYaml = `name: other-snap
-version: 1.0
-apps:
- baz:
- command: bin/bar
- plugs: [media-hub]
-`
+
+func (ms *mgrsSuite) testUpdateWithAutoconnectRetry(c *C, updateSnapName, removeSnapName string) {
snapPath, _ := ms.makeStoreTestSnap(c, someSnapYaml, "40")
ms.serveSnap(snapPath, "40")
@@ -2421,7 +2417,7 @@ apps:
var retryCheck bool
for _, t := range st.Tasks() {
if t.Kind() == "auto-connect" {
- c.Assert(strings.Join(t.Log(), ""), Matches, `.*auto-connect of snap .* will be retried because of "other-snap" - "some-snap" conflict`)
+ c.Assert(strings.Join(t.Log(), ""), Matches, `.*Waiting for conflicting change in progress...`)
retryCheck = true
}
}
@@ -2449,3 +2445,163 @@ func (ms *mgrsSuite) TestUpdateWithAutoconnectRetrySlotSide(c *C) {
func (ms *mgrsSuite) TestUpdateWithAutoconnectRetryPlugSide(c *C) {
ms.testUpdateWithAutoconnectRetry(c, "other-snap", "some-snap")
}
+
+func (ms *mgrsSuite) TestDisconnectIgnoredOnSymmetricRemove(c *C) {
+ const someSnapYaml = `name: some-snap
+version: 1.0
+apps:
+ foo:
+ command: bin/bar
+ slots: [media-hub]
+`
+ const otherSnapYaml = `name: other-snap
+version: 1.0
+apps:
+ baz:
+ command: bin/bar
+ plugs: [media-hub]
+`
+ st := ms.o.State()
+ st.Lock()
+ defer st.Unlock()
+
+ st.Set("conns", map[string]interface{}{
+ "other-snap:media-hub some-snap:media-hub": map[string]interface{}{"interface": "media-hub", "auto": false},
+ })
+
+ si := &snap.SideInfo{RealName: "some-snap", SnapID: fakeSnapID("some-snap"), Revision: snap.R(1)}
+ snapInfo := snaptest.MockSnap(c, someSnapYaml, si)
+ c.Assert(snapInfo.Slots, HasLen, 1)
+
+ oi := &snap.SideInfo{RealName: "other-snap", SnapID: fakeSnapID("other-snap"), Revision: snap.R(1)}
+ otherInfo := snaptest.MockSnap(c, otherSnapYaml, oi)
+ c.Assert(otherInfo.Plugs, HasLen, 1)
+
+ snapstate.Set(st, "some-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{si},
+ Current: snap.R(1),
+ SnapType: "app",
+ })
+ snapstate.Set(st, "other-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{oi},
+ Current: snap.R(1),
+ SnapType: "app",
+ })
+
+ repo := ms.o.InterfaceManager().Repository()
+
+ // add snaps to the repo to have plugs/slots
+ c.Assert(repo.AddSnap(snapInfo), IsNil)
+ c.Assert(repo.AddSnap(otherInfo), IsNil)
+ repo.Connect(&interfaces.ConnRef{
+ PlugRef: interfaces.PlugRef{Snap: "other-snap", Name: "media-hub"},
+ SlotRef: interfaces.SlotRef{Snap: "some-snap", Name: "media-hub"},
+ }, nil, nil, nil)
+
+ ts, err := snapstate.Remove(st, "some-snap", snap.R(0))
+ c.Assert(err, IsNil)
+ chg := st.NewChange("uninstall", "...")
+ chg.AddAll(ts)
+
+ // remove other-snap
+ ts2, err := snapstate.Remove(st, "other-snap", snap.R(0))
+ c.Assert(err, IsNil)
+ chg2 := st.NewChange("uninstall", "...")
+ chg2.AddAll(ts2)
+
+ st.Unlock()
+ err = ms.o.Settle(settleTimeout)
+ st.Lock()
+ c.Assert(err, IsNil)
+
+ c.Assert(chg.Status(), Equals, state.DoneStatus)
+
+ // check connections
+ var conns map[string]interface{}
+ st.Get("conns", &conns)
+ c.Assert(conns, HasLen, 0)
+
+ var disconnectInterfacesCount, slotHookCount, plugHookCount int
+ for _, t := range st.Tasks() {
+ if t.Kind() == "auto-disconnect" {
+ disconnectInterfacesCount++
+ }
+ if t.Kind() == "run-hook" {
+ var hsup hookstate.HookSetup
+ c.Assert(t.Get("hook-setup", &hsup), IsNil)
+ if hsup.Hook == "disconnect-plug-media-hub" {
+ plugHookCount++
+ }
+ if hsup.Hook == "disconnect-slot-media-hub" {
+ slotHookCount++
+ }
+ }
+ }
+ c.Assert(plugHookCount, Equals, 1)
+ c.Assert(slotHookCount, Equals, 1)
+ c.Assert(disconnectInterfacesCount, Equals, 2)
+
+ var snst snapstate.SnapState
+ err = snapstate.Get(st, "other-snap", &snst)
+ c.Assert(err, Equals, state.ErrNoState)
+ _, err = repo.Connected("other-snap", "media-hub")
+ c.Assert(err, ErrorMatches, `snap "other-snap" has no plug or slot named "media-hub"`)
+}
+
+func (ms *mgrsSuite) TestDisconnectOnUninstallRemovesAutoconnection(c *C) {
+ st := ms.o.State()
+ st.Lock()
+ defer st.Unlock()
+
+ st.Set("conns", map[string]interface{}{
+ "other-snap:media-hub some-snap:media-hub": map[string]interface{}{"interface": "media-hub", "auto": true},
+ })
+
+ si := &snap.SideInfo{RealName: "some-snap", SnapID: fakeSnapID("some-snap"), Revision: snap.R(1)}
+ snapInfo := snaptest.MockSnap(c, someSnapYaml, si)
+
+ oi := &snap.SideInfo{RealName: "other-snap", SnapID: fakeSnapID("other-snap"), Revision: snap.R(1)}
+ otherInfo := snaptest.MockSnap(c, otherSnapYaml, oi)
+
+ snapstate.Set(st, "some-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{si},
+ Current: snap.R(1),
+ SnapType: "app",
+ })
+ snapstate.Set(st, "other-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{oi},
+ Current: snap.R(1),
+ SnapType: "app",
+ })
+
+ repo := ms.o.InterfaceManager().Repository()
+
+ // add snaps to the repo to have plugs/slots
+ c.Assert(repo.AddSnap(snapInfo), IsNil)
+ c.Assert(repo.AddSnap(otherInfo), IsNil)
+ repo.Connect(&interfaces.ConnRef{
+ PlugRef: interfaces.PlugRef{Snap: "other-snap", Name: "media-hub"},
+ SlotRef: interfaces.SlotRef{Snap: "some-snap", Name: "media-hub"},
+ }, nil, nil, nil)
+
+ ts, err := snapstate.Remove(st, "some-snap", snap.R(0))
+ c.Assert(err, IsNil)
+ chg := st.NewChange("uninstall", "...")
+ chg.AddAll(ts)
+
+ st.Unlock()
+ err = ms.o.Settle(settleTimeout)
+ st.Lock()
+ c.Assert(err, IsNil)
+
+ c.Assert(chg.Status(), Equals, state.DoneStatus)
+
+ // check connections; auto-connection should be removed completely from conns on uninstall.
+ var conns map[string]interface{}
+ st.Get("conns", &conns)
+ c.Assert(conns, HasLen, 0)
+}
diff --git a/overlord/snapstate/backend.go b/overlord/snapstate/backend.go
index 5fec77e0c4..11e91f8bf0 100644
--- a/overlord/snapstate/backend.go
+++ b/overlord/snapstate/backend.go
@@ -54,7 +54,7 @@ type StoreService interface {
type managerBackend interface {
// install releated
- SetupSnap(snapFilePath string, si *snap.SideInfo, meter progress.Meter) (snap.Type, error)
+ SetupSnap(snapFilePath, instanceName string, si *snap.SideInfo, meter progress.Meter) (snap.Type, error)
CopySnapData(newSnap, oldSnap *snap.Info, meter progress.Meter) error
LinkSnap(info *snap.Info, model *asserts.Model) error
StartServices(svcs []*snap.AppInfo, meter progress.Meter) error
diff --git a/overlord/snapstate/backend/setup.go b/overlord/snapstate/backend/setup.go
index 3a673b1799..83bce9b08f 100644
--- a/overlord/snapstate/backend/setup.go
+++ b/overlord/snapstate/backend/setup.go
@@ -30,13 +30,17 @@ import (
)
// SetupSnap does prepare and mount the snap for further processing.
-func (b Backend) SetupSnap(snapFilePath string, sideInfo *snap.SideInfo, meter progress.Meter) (snapType snap.Type, err error) {
+func (b Backend) SetupSnap(snapFilePath, instanceName string, sideInfo *snap.SideInfo, meter progress.Meter) (snapType snap.Type, err error) {
// This assumes that the snap was already verified or --dangerous was used.
s, snapf, oErr := OpenSnapFile(snapFilePath, sideInfo)
if oErr != nil {
return snapType, oErr
}
+
+ // update instance key to what was requested
+ _, s.InstanceKey = snap.SplitInstanceName(instanceName)
+
instdir := s.MountDir()
defer func() {
diff --git a/overlord/snapstate/backend/setup_test.go b/overlord/snapstate/backend/setup_test.go
index 10889aee35..46c2de24d6 100644
--- a/overlord/snapstate/backend/setup_test.go
+++ b/overlord/snapstate/backend/setup_test.go
@@ -80,7 +80,7 @@ func (s *setupSuite) TestSetupDoUndoSimple(c *C) {
Revision: snap.R(14),
}
- snapType, err := s.be.SetupSnap(snapPath, &si, progress.Null)
+ snapType, err := s.be.SetupSnap(snapPath, "hello", &si, progress.Null)
c.Assert(err, IsNil)
c.Check(snapType, Equals, snap.TypeApp)
@@ -108,6 +108,41 @@ func (s *setupSuite) TestSetupDoUndoSimple(c *C) {
}
+func (s *setupSuite) TestSetupDoUndoInstance(c *C) {
+ snapPath := makeTestSnap(c, helloYaml1)
+
+ si := snap.SideInfo{
+ RealName: "hello",
+ Revision: snap.R(14),
+ }
+
+ snapType, err := s.be.SetupSnap(snapPath, "hello_instance", &si, progress.Null)
+ c.Assert(err, IsNil)
+ c.Check(snapType, Equals, snap.TypeApp)
+
+ // after setup the snap file is in the right dir
+ c.Assert(osutil.FileExists(filepath.Join(dirs.SnapBlobDir, "hello_instance_14.snap")), Equals, true)
+
+ // ensure the right unit is created
+ mup := systemd.MountUnitPath(filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "hello_instance/14"))
+ c.Assert(mup, testutil.FileMatches, fmt.Sprintf("(?ms).*^Where=%s", filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "hello_instance/14")))
+ c.Assert(mup, testutil.FileMatches, "(?ms).*^What=/var/lib/snapd/snaps/hello_instance_14.snap")
+
+ minInfo := snap.MinimalPlaceInfo("hello_instance", snap.R(14))
+ // mount dir was created
+ c.Assert(osutil.FileExists(minInfo.MountDir()), Equals, true)
+
+ // undo undoes the mount unit and the instdir creation
+ err = s.be.UndoSetupSnap(minInfo, "app", progress.Null)
+ c.Assert(err, IsNil)
+
+ l, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.mount"))
+ c.Assert(l, HasLen, 0)
+ c.Assert(osutil.FileExists(minInfo.MountDir()), Equals, false)
+
+ c.Assert(osutil.FileExists(minInfo.MountFile()), Equals, false)
+}
+
func (s *setupSuite) TestSetupDoUndoKernelUboot(c *C) {
bootloader := boottest.NewMockBootloader("mock", c.MkDir())
partition.ForceBootloader(bootloader)
@@ -132,7 +167,7 @@ type: kernel
Revision: snap.R(140),
}
- snapType, err := s.be.SetupSnap(snapPath, &si, progress.Null)
+ snapType, err := s.be.SetupSnap(snapPath, "kernel", &si, progress.Null)
c.Assert(err, IsNil)
c.Check(snapType, Equals, snap.TypeKernel)
l, _ := filepath.Glob(filepath.Join(bootloader.Dir(), "*"))
@@ -177,11 +212,11 @@ type: kernel
Revision: snap.R(140),
}
- _, err := s.be.SetupSnap(snapPath, &si, progress.Null)
+ _, err := s.be.SetupSnap(snapPath, "kernel", &si, progress.Null)
c.Assert(err, IsNil)
// retry run
- _, err = s.be.SetupSnap(snapPath, &si, progress.Null)
+ _, err = s.be.SetupSnap(snapPath, "kernel", &si, progress.Null)
c.Assert(err, IsNil)
minInfo := snap.MinimalPlaceInfo("kernel", snap.R(140))
@@ -226,7 +261,7 @@ type: kernel
Revision: snap.R(140),
}
- _, err := s.be.SetupSnap(snapPath, &si, progress.Null)
+ _, err := s.be.SetupSnap(snapPath, "kernel", &si, progress.Null)
c.Assert(err, IsNil)
minInfo := snap.MinimalPlaceInfo("kernel", snap.R(140))
@@ -266,7 +301,7 @@ func (s *setupSuite) TestSetupCleanupAfterFail(c *C) {
})
defer r()
- _, err := s.be.SetupSnap(snapPath, &si, progress.Null)
+ _, err := s.be.SetupSnap(snapPath, "hello", &si, progress.Null)
c.Assert(err, ErrorMatches, "failed")
// everything is gone
diff --git a/overlord/snapstate/backend_test.go b/overlord/snapstate/backend_test.go
index 305d11f530..17463d9043 100644
--- a/overlord/snapstate/backend_test.go
+++ b/overlord/snapstate/backend_test.go
@@ -30,6 +30,7 @@ import (
"golang.org/x/net/context"
"github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord/auth"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/snapstate/backend"
@@ -95,6 +96,7 @@ func (ops fakeOps) First(op string) *fakeOp {
type fakeDownload struct {
name string
macaroon string
+ target string
}
type byName []store.CurrentSnap
@@ -141,6 +143,10 @@ func (f *fakeStore) pokeStateLock() {
func (f *fakeStore) SnapInfo(spec store.SnapSpec, user *auth.UserState) (*snap.Info, error) {
f.pokeStateLock()
+ _, instanceKey := snap.SplitInstanceName(spec.Name)
+ if instanceKey != "" {
+ return nil, fmt.Errorf("internal error: unexpected instance name: %q", spec.Name)
+ }
sspec := snapSpec{
Name: spec.Name,
}
@@ -381,9 +387,11 @@ func (f *fakeStore) SnapAction(ctx context.Context, currentSnaps []*store.Curren
return nil, fmt.Errorf("internal error: action without instance name")
}
+ snapName, instanceKey := snap.SplitInstanceName(a.InstanceName)
+
if a.Action == "install" {
spec := snapSpec{
- Name: snap.InstanceSnap(a.InstanceName),
+ Name: snapName,
Channel: a.Channel,
Revision: a.Revision,
}
@@ -401,6 +409,7 @@ func (f *fakeStore) SnapAction(ctx context.Context, currentSnaps []*store.Curren
if !a.Revision.Unset() {
info.Channel = ""
}
+ info.InstanceKey = instanceKey
res = append(res, info)
continue
}
@@ -449,6 +458,7 @@ func (f *fakeStore) SnapAction(ctx context.Context, currentSnaps []*store.Curren
if !a.Revision.Unset() {
info.Channel = ""
}
+ info.InstanceKey = instanceKey
res = append(res, info)
}
@@ -478,6 +488,9 @@ func (f *fakeStore) SuggestedCurrency() string {
func (f *fakeStore) Download(ctx context.Context, name, targetFn string, snapInfo *snap.DownloadInfo, pb progress.Meter, user *auth.UserState) error {
f.pokeStateLock()
+ if _, key := snap.SplitInstanceName(name); key != "" {
+ return fmt.Errorf("internal error: unsupported download with instance name %q", name)
+ }
var macaroon string
if user != nil {
macaroon = user.StoreMacaroon
@@ -485,6 +498,7 @@ func (f *fakeStore) Download(ctx context.Context, name, targetFn string, snapInf
f.downloads = append(f.downloads, fakeDownload{
macaroon: macaroon,
name: name,
+ target: targetFn,
})
f.fakeBackend.ops = append(f.fakeBackend.ops, fakeOp{op: "storesvc-download", name: name})
@@ -536,16 +550,34 @@ func (f *fakeSnappyBackend) OpenSnapFile(snapFilePath string, si *snap.SideInfo)
op.sinfo = *si
}
- name := filepath.Base(snapFilePath)
- if idx := strings.IndexByte(name, '_'); idx > -1 {
- name = name[:idx]
+ var name string
+ if !osutil.IsDirectory(snapFilePath) {
+ name = filepath.Base(snapFilePath)
+ split := strings.Split(name, "_")
+ if len(split) >= 2 {
+ // <snap>_<rev>.snap
+ // <snap>_<instance-key>_<rev>.snap
+ name = split[0]
+ }
+ } else {
+ // for snap try only
+ snapf, err := snap.Open(snapFilePath)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ info, err := snap.ReadInfoFromSnapFile(snapf, si)
+ if err != nil {
+ return nil, nil, err
+ }
+ name = info.SuggestedName
}
f.ops = append(f.ops, op)
return &snap.Info{SuggestedName: name, Architectures: []string{"all"}}, f.emptyContainer, nil
}
-func (f *fakeSnappyBackend) SetupSnap(snapFilePath string, si *snap.SideInfo, p progress.Meter) (snap.Type, error) {
+func (f *fakeSnappyBackend) SetupSnap(snapFilePath, instanceName string, si *snap.SideInfo, p progress.Meter) (snap.Type, error) {
p.Notify("setup-snap")
revno := snap.R(0)
if si != nil {
@@ -553,6 +585,7 @@ func (f *fakeSnappyBackend) SetupSnap(snapFilePath string, si *snap.SideInfo, p
}
f.ops = append(f.ops, fakeOp{
op: "setup-snap",
+ name: instanceName,
path: snapFilePath,
revno: revno,
})
@@ -573,17 +606,19 @@ func (f *fakeSnappyBackend) ReadInfo(name string, si *snap.SideInfo) (*snap.Info
if name == "not-there" && si.Revision == snap.R(2) {
return nil, &snap.NotFoundError{Snap: name, Revision: si.Revision}
}
+ snapName, instanceKey := snap.SplitInstanceName(name)
// naive emulation for now, always works
info := &snap.Info{
- SuggestedName: name,
+ SuggestedName: snapName,
SideInfo: *si,
Architectures: []string{"all"},
Type: snap.TypeApp,
}
- if strings.Contains(name, "alias-snap") {
- name = "alias-snap"
+ if strings.Contains(snapName, "alias-snap") {
+ // only for the switch below
+ snapName = "alias-snap"
}
- switch name {
+ switch snapName {
case "gadget":
info.Type = snap.TypeGadget
case "core":
@@ -619,6 +654,7 @@ apps:
info.SideInfo = *si
}
+ info.InstanceKey = instanceKey
return info, nil
}
@@ -706,6 +742,7 @@ func (f *fakeSnappyBackend) UndoSetupSnap(s snap.PlaceInfo, typ snap.Type, p pro
p.Notify("setup-snap")
f.ops = append(f.ops, fakeOp{
op: "undo-setup-snap",
+ name: s.InstanceName(),
path: s.MountDir(),
stype: typ,
})
diff --git a/overlord/snapstate/check_snap.go b/overlord/snapstate/check_snap.go
index a041da5fc4..ccf6d62e2e 100644
--- a/overlord/snapstate/check_snap.go
+++ b/overlord/snapstate/check_snap.go
@@ -192,7 +192,7 @@ func validateContainer(c snap.Container, s *snap.Info, logf func(format string,
}
// checkSnap ensures that the snap can be installed.
-func checkSnap(st *state.State, snapFilePath string, si *snap.SideInfo, curInfo *snap.Info, flags Flags) error {
+func checkSnap(st *state.State, snapFilePath, instanceName string, si *snap.SideInfo, curInfo *snap.Info, flags Flags) error {
// This assumes that the snap was already verified or --dangerous was used.
s, c, err := openSnapFile(snapFilePath, si)
@@ -208,9 +208,15 @@ func checkSnap(st *state.State, snapFilePath string, si *snap.SideInfo, curInfo
return err
}
+ snapName, instanceKey := snap.SplitInstanceName(instanceName)
+ // update instance key to what was requested
+ s.InstanceKey = instanceKey
+
st.Lock()
defer st.Unlock()
+ // allow registered checks to run first as they may produce more
+ // precise errors
for _, check := range checkSnapCallbacks {
err := check(st, s, curInfo, flags)
if err != nil {
@@ -218,6 +224,10 @@ func checkSnap(st *state.State, snapFilePath string, si *snap.SideInfo, curInfo
}
}
+ if snapName != s.SnapName() {
+ return fmt.Errorf("cannot install snap %q using instance name %q", s.SnapName(), instanceName)
+ }
+
return nil
}
diff --git a/overlord/snapstate/check_snap_test.go b/overlord/snapstate/check_snap_test.go
index 0c137a7a55..b6d0296525 100644
--- a/overlord/snapstate/check_snap_test.go
+++ b/overlord/snapstate/check_snap_test.go
@@ -78,7 +78,7 @@ architectures:
restore := snapstate.MockOpenSnapFile(openSnapFile)
defer restore()
- err = snapstate.CheckSnap(s.st, "snap-path", nil, nil, snapstate.Flags{})
+ err = snapstate.CheckSnap(s.st, "snap-path", "hello", nil, nil, snapstate.Flags{})
errorMsg := fmt.Sprintf(`snap "hello" supported architectures (yadayada, blahblah) are incompatible with this system (%s)`, arch.UbuntuArchitecture())
c.Assert(err.Error(), Equals, errorMsg)
@@ -168,7 +168,7 @@ func (s *checkSnapSuite) TestCheckSnapAssumes(c *C) {
}
restore := snapstate.MockOpenSnapFile(openSnapFile)
defer restore()
- err = snapstate.CheckSnap(s.st, "snap-path", nil, nil, snapstate.Flags{})
+ err = snapstate.CheckSnap(s.st, "snap-path", "foo", nil, nil, snapstate.Flags{})
if test.error != "" {
c.Check(err, ErrorMatches, test.error)
} else {
@@ -202,7 +202,7 @@ version: 1.0`
r2 := snapstate.MockCheckSnapCallbacks([]snapstate.CheckSnapCallback{checkCb})
defer r2()
- err := snapstate.CheckSnap(s.st, "snap-path", si, nil, snapstate.Flags{})
+ err := snapstate.CheckSnap(s.st, "snap-path", "foo", si, nil, snapstate.Flags{})
c.Check(err, IsNil)
c.Check(checkCbCalled, Equals, true)
@@ -229,7 +229,7 @@ version: 1.0`
defer r2()
snapstate.AddCheckSnapCallback(checkCb)
- err = snapstate.CheckSnap(s.st, "snap-path", nil, nil, snapstate.Flags{})
+ err = snapstate.CheckSnap(s.st, "snap-path", "foo", nil, nil, snapstate.Flags{})
c.Check(err, Equals, fail)
}
@@ -270,7 +270,7 @@ version: 2
defer restore()
st.Unlock()
- err = snapstate.CheckSnap(st, "snap-path", nil, nil, snapstate.Flags{})
+ err = snapstate.CheckSnap(st, "snap-path", "gadget", nil, nil, snapstate.Flags{})
st.Lock()
c.Check(err, IsNil)
}
@@ -312,7 +312,7 @@ version: 2
defer restore()
st.Unlock()
- err = snapstate.CheckSnap(st, "snap-path", nil, nil, snapstate.Flags{})
+ err = snapstate.CheckSnap(st, "snap-path", "gadget", nil, nil, snapstate.Flags{})
st.Lock()
c.Check(err, IsNil)
}
@@ -353,7 +353,7 @@ version: 2
defer restore()
st.Unlock()
- err = snapstate.CheckSnap(st, "snap-path", nil, nil, snapstate.Flags{})
+ err = snapstate.CheckSnap(st, "snap-path", "gadget", nil, nil, snapstate.Flags{})
st.Lock()
c.Check(err, ErrorMatches, `cannot replace signed gadget snap with an unasserted one`)
}
@@ -394,7 +394,7 @@ version: 2
defer restore()
st.Unlock()
- err = snapstate.CheckSnap(st, "snap-path", nil, nil, snapstate.Flags{})
+ err = snapstate.CheckSnap(st, "snap-path", "gadget", nil, nil, snapstate.Flags{})
st.Lock()
c.Check(err, ErrorMatches, "cannot replace gadget snap with a different one")
}
@@ -436,7 +436,7 @@ version: 2
defer restore()
st.Unlock()
- err = snapstate.CheckSnap(st, "snap-path", nil, nil, snapstate.Flags{})
+ err = snapstate.CheckSnap(st, "snap-path", "gadget", nil, nil, snapstate.Flags{})
st.Lock()
c.Check(err, ErrorMatches, "cannot replace gadget snap with a different one")
}
@@ -464,7 +464,7 @@ version: 1
defer restore()
st.Unlock()
- err = snapstate.CheckSnap(st, "snap-path", nil, nil, snapstate.Flags{})
+ err = snapstate.CheckSnap(st, "snap-path", "gadget", nil, nil, snapstate.Flags{})
st.Lock()
c.Check(err, IsNil)
}
@@ -485,7 +485,7 @@ confinement: devmode
restore := snapstate.MockOpenSnapFile(openSnapFile)
defer restore()
- err = snapstate.CheckSnap(s.st, "snap-path", nil, nil, snapstate.Flags{})
+ err = snapstate.CheckSnap(s.st, "snap-path", "hello", nil, nil, snapstate.Flags{})
c.Assert(err, ErrorMatches, ".* requires devmode or confinement override")
}
@@ -509,7 +509,7 @@ confinement: classic
restore = release.MockOnClassic(true)
defer restore()
- err = snapstate.CheckSnap(s.st, "snap-path", nil, nil, snapstate.Flags{})
+ err = snapstate.CheckSnap(s.st, "snap-path", "hello", nil, nil, snapstate.Flags{})
c.Assert(err, ErrorMatches, ".* requires classic confinement")
}
@@ -533,7 +533,7 @@ confinement: classic
restore = release.MockOnClassic(false)
defer restore()
- err = snapstate.CheckSnap(s.st, "snap-path", nil, nil, snapstate.Flags{Classic: true})
+ err = snapstate.CheckSnap(s.st, "snap-path", "hello", nil, nil, snapstate.Flags{Classic: true})
c.Assert(err, ErrorMatches, ".* requires classic confinement which is only available on classic systems")
}
@@ -575,7 +575,7 @@ version: 2
defer restore()
st.Unlock()
- err = snapstate.CheckSnap(st, "snap-path", nil, nil, snapstate.Flags{})
+ err = snapstate.CheckSnap(st, "snap-path", "kernel", nil, nil, snapstate.Flags{})
st.Lock()
c.Check(err, IsNil)
}
@@ -617,7 +617,7 @@ version: 2
defer restore()
st.Unlock()
- err = snapstate.CheckSnap(st, "snap-path", nil, nil, snapstate.Flags{})
+ err = snapstate.CheckSnap(st, "snap-path", "kernel", nil, nil, snapstate.Flags{})
st.Lock()
c.Check(err, ErrorMatches, "cannot replace kernel snap with a different one")
}
@@ -642,7 +642,7 @@ base: some-base
defer restore()
st.Unlock()
- err = snapstate.CheckSnap(st, "snap-path", nil, nil, snapstate.Flags{})
+ err = snapstate.CheckSnap(st, "snap-path", "requires-base", nil, nil, snapstate.Flags{})
st.Lock()
c.Check(err, ErrorMatches, "cannot find required base \"some-base\"")
}
@@ -680,7 +680,7 @@ base: some-base
defer restore()
st.Unlock()
- err = snapstate.CheckSnap(st, "snap-path", nil, nil, snapstate.Flags{})
+ err = snapstate.CheckSnap(st, "snap-path", "requires-base", nil, nil, snapstate.Flags{})
st.Lock()
c.Check(err, IsNil)
}
@@ -695,3 +695,73 @@ func emptyContainer(c *C) *snapdir.SnapDir {
c.Assert(ioutil.WriteFile(filepath.Join(d, "meta", "snap.yaml"), nil, 0444), IsNil)
return snapdir.New(d)
}
+
+func (s *checkSnapSuite) TestCheckSnapInstanceName(c *C) {
+ st := state.New(nil)
+ st.Lock()
+ defer st.Unlock()
+
+ si := &snap.SideInfo{RealName: "foo", Revision: snap.R(1), SnapID: "some-base-id"}
+ info := snaptest.MockSnap(c, `
+name: foo
+version: 1
+`, si)
+ snapstate.Set(st, "foo", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{si},
+ Current: si.Revision,
+ })
+
+ var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) {
+ return info, emptyContainer(c), nil
+ }
+ restore := snapstate.MockOpenSnapFile(openSnapFile)
+ defer restore()
+
+ st.Unlock()
+ err := snapstate.CheckSnap(st, "snap-path", "foo_instance", nil, nil, snapstate.Flags{})
+ st.Lock()
+ c.Check(err, IsNil)
+
+ st.Unlock()
+ err = snapstate.CheckSnap(st, "snap-path", "bar_instance", nil, nil, snapstate.Flags{})
+ st.Lock()
+ c.Check(err, ErrorMatches, `cannot install snap "foo" using instance name "bar_instance"`)
+
+ st.Unlock()
+ err = snapstate.CheckSnap(st, "snap-path", "other-name", nil, nil, snapstate.Flags{})
+ st.Lock()
+ c.Check(err, ErrorMatches, `cannot install snap "foo" using instance name "other-name"`)
+}
+
+func (s *checkSnapSuite) TestCheckSnapCheckCallInstanceKeySet(c *C) {
+ const yaml = `name: foo
+version: 1.0`
+
+ si := &snap.SideInfo{
+ SnapID: "snap-id",
+ }
+
+ var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) {
+ info := snaptest.MockInfo(c, yaml, si)
+ return info, emptyContainer(c), nil
+ }
+ r1 := snapstate.MockOpenSnapFile(openSnapFile)
+ defer r1()
+
+ checkCbCalled := false
+ checkCb := func(st *state.State, s, cur *snap.Info, flags snapstate.Flags) error {
+ c.Assert(s.InstanceName(), Equals, "foo_instance")
+ c.Assert(s.SnapName(), Equals, "foo")
+ c.Assert(s.SnapID, Equals, "snap-id")
+ checkCbCalled = true
+ return nil
+ }
+ r2 := snapstate.MockCheckSnapCallbacks([]snapstate.CheckSnapCallback{checkCb})
+ defer r2()
+
+ err := snapstate.CheckSnap(s.st, "snap-path", "foo_instance", si, nil, snapstate.Flags{})
+ c.Check(err, IsNil)
+
+ c.Check(checkCbCalled, Equals, true)
+}
diff --git a/overlord/snapstate/handlers.go b/overlord/snapstate/handlers.go
index ca7f7b4281..0ccd493de2 100644
--- a/overlord/snapstate/handlers.go
+++ b/overlord/snapstate/handlers.go
@@ -429,10 +429,10 @@ func (m *SnapManager) doDownloadSnap(t *state.Task, tomb *tomb.Tomb) error {
if err != nil {
return err
}
- err = theStore.Download(tomb.Context(nil), snapsup.InstanceName(), targetFn, &storeInfo.DownloadInfo, meter, user)
+ err = theStore.Download(tomb.Context(nil), snapsup.SnapName(), targetFn, &storeInfo.DownloadInfo, meter, user)
snapsup.SideInfo = &storeInfo.SideInfo
} else {
- err = theStore.Download(tomb.Context(nil), snapsup.InstanceName(), targetFn, snapsup.DownloadInfo, meter, user)
+ err = theStore.Download(tomb.Context(nil), snapsup.SnapName(), targetFn, snapsup.DownloadInfo, meter, user)
}
if err != nil {
return err
@@ -466,14 +466,14 @@ func (m *SnapManager) doMountSnap(t *state.Task, _ *tomb.Tomb) error {
m.backend.CurrentInfo(curInfo)
- if err := checkSnap(t.State(), snapsup.SnapPath, snapsup.SideInfo, curInfo, snapsup.Flags); err != nil {
+ if err := checkSnap(t.State(), snapsup.SnapPath, snapsup.InstanceName(), snapsup.SideInfo, curInfo, snapsup.Flags); err != nil {
return err
}
pb := NewTaskProgressAdapterUnlocked(t)
// TODO Use snapsup.Revision() to obtain the right info to mount
// instead of assuming the candidate is the right one.
- snapType, err := m.backend.SetupSnap(snapsup.SnapPath, snapsup.SideInfo, pb)
+ snapType, err := m.backend.SetupSnap(snapsup.SnapPath, snapsup.InstanceName(), snapsup.SideInfo, pb)
if err != nil {
return err
}
@@ -761,6 +761,8 @@ func (m *SnapManager) doLinkSnap(t *state.Task, _ *tomb.Tomb) error {
snapst.UserID = snapsup.UserID
}
}
+ // keep instance key
+ snapst.InstanceKey = snapsup.InstanceKey
newInfo, err := readInfo(snapsup.InstanceName(), cand, 0)
if err != nil {
diff --git a/overlord/snapstate/handlers_mount_test.go b/overlord/snapstate/handlers_mount_test.go
index 0ef5f1663a..8bca852a55 100644
--- a/overlord/snapstate/handlers_mount_test.go
+++ b/overlord/snapstate/handlers_mount_test.go
@@ -112,11 +112,13 @@ func (s *mountSnapSuite) TestDoUndoMountSnap(c *C) {
},
{
op: "setup-snap",
+ name: "core",
path: testSnap,
revno: snap.R(2),
},
{
op: "undo-setup-snap",
+ name: "core",
path: filepath.Join(dirs.SnapMountDir, "core/2"),
stype: "os",
},
@@ -170,11 +172,13 @@ func (s *mountSnapSuite) TestDoMountSnapError(c *C) {
},
{
op: "setup-snap",
+ name: "borken",
path: testSnap,
revno: snap.R(2),
},
{
op: "undo-setup-snap",
+ name: "borken",
path: filepath.Join(dirs.SnapMountDir, "borken/2"),
stype: "app",
},
@@ -230,11 +234,13 @@ func (s *mountSnapSuite) TestDoMountSnapErrorNotFound(c *C) {
},
{
op: "setup-snap",
+ name: "not-there",
path: testSnap,
revno: snap.R(2),
},
{
op: "undo-setup-snap",
+ name: "not-there",
path: filepath.Join(dirs.SnapMountDir, "not-there/2"),
stype: "app",
},
@@ -304,6 +310,7 @@ func (s *mountSnapSuite) TestDoMountNotMountedRetryRetry(c *C) {
},
{
op: "setup-snap",
+ name: "not-there",
path: testSnap,
revno: snap.R(2),
},
diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go
index 772eeaef19..6a337054bf 100644
--- a/overlord/snapstate/snapstate.go
+++ b/overlord/snapstate/snapstate.go
@@ -452,14 +452,30 @@ func defaultContentPlugProviders(st *state.State, info *snap.Info) []string {
return out
}
+func getFeatureFlagBool(tr *config.Transaction, flag string) (bool, error) {
+ var v interface{} = false
+ if err := tr.GetMaybe("core", flag, &v); err != nil {
+ return false, err
+ }
+ switch value := v.(type) {
+ case string:
+ if value == "" {
+ return false, nil
+ }
+ case bool:
+ return value, nil
+ }
+ return false, fmt.Errorf("internal error: feature flag %v has unexpected value %#v (%T)", flag, v, v)
+}
+
// validateFeatureFlags validates the given snap only uses experimental
// features that are enabled by the user.
func validateFeatureFlags(st *state.State, info *snap.Info) error {
tr := config.NewTransaction(st)
if len(info.Layout) > 0 {
- var flag bool
- if err := tr.GetMaybe("core", "experimental.layouts", &flag); err != nil {
+ flag, err := getFeatureFlagBool(tr, "experimental.layouts")
+ if err != nil {
return err
}
if !flag {
@@ -468,8 +484,8 @@ func validateFeatureFlags(st *state.State, info *snap.Info) error {
}
if info.InstanceKey != "" {
- var flag bool
- if err := tr.GetMaybe("core", "experimental.parallel-instances", &flag); err != nil {
+ flag, err := getFeatureFlagBool(tr, "experimental.parallel-instances")
+ if err != nil {
return err
}
if !flag {
@@ -588,6 +604,7 @@ func Install(st *state.State, name, channel string, revision snap.Revision, user
SideInfo: &info.SideInfo,
Type: info.Type,
PlugsOnly: len(info.Slots) == 0,
+ InstanceKey: info.InstanceKey,
}
return doInstall(st, &snapst, snapsup, 0)
@@ -747,6 +764,7 @@ func doUpdate(st *state.State, names []string, updates []*snap.Info, params func
SideInfo: &update.SideInfo,
Type: update.Type,
PlugsOnly: len(update.Slots) == 0,
+ InstanceKey: update.InstanceKey,
}
ts, err := doInstall(st, snapst, snapsup, 0)
@@ -844,6 +862,8 @@ func applyAutoAliasesDelta(st *state.State, delta map[string][]string, op string
snapsup := &SnapSetup{
SideInfo: &snap.SideInfo{RealName: snapName},
+ // TODO parallel-install: review and update the aliases
+ // code in the context of parallel installs
}
alias := st.NewTask(kind, fmt.Sprintf(msg, snapsup.InstanceName()))
alias.Set("snap-setup", &snapsup)
@@ -999,8 +1019,9 @@ func Switch(st *state.State, name, channel string) (*state.TaskSet, error) {
}
snapsup := &SnapSetup{
- SideInfo: snapst.CurrentSideInfo(),
- Channel: channel,
+ SideInfo: snapst.CurrentSideInfo(),
+ Channel: channel,
+ InstanceKey: snapst.InstanceKey,
}
switchSnap := st.NewTask("switch-snap", fmt.Sprintf(i18n.G("Switch snap %q to %s"), snapsup.InstanceName(), snapsup.Channel))
@@ -1068,8 +1089,9 @@ func Update(st *state.State, name, channel string, revision snap.Revision, userI
}
snapsup := &SnapSetup{
- SideInfo: snapst.CurrentSideInfo(),
- Flags: snapst.Flags.ForSnapSetup(),
+ SideInfo: snapst.CurrentSideInfo(),
+ Flags: snapst.Flags.ForSnapSetup(),
+ InstanceKey: snapst.InstanceKey,
}
if snapst.Channel != channel {
@@ -1197,10 +1219,11 @@ func Enable(st *state.State, name string) (*state.TaskSet, error) {
}
snapsup := &SnapSetup{
- SideInfo: snapst.CurrentSideInfo(),
- Flags: snapst.Flags.ForSnapSetup(),
- Type: info.Type,
- PlugsOnly: len(info.Slots) == 0,
+ SideInfo: snapst.CurrentSideInfo(),
+ Flags: snapst.Flags.ForSnapSetup(),
+ Type: info.Type,
+ PlugsOnly: len(info.Slots) == 0,
+ InstanceKey: snapst.InstanceKey,
}
prepareSnap := st.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q (%s)"), snapsup.InstanceName(), snapst.Current))
@@ -1254,11 +1277,12 @@ func Disable(st *state.State, name string) (*state.TaskSet, error) {
snapsup := &SnapSetup{
SideInfo: &snap.SideInfo{
- RealName: name,
+ RealName: snap.InstanceSnap(name),
Revision: snapst.Current,
},
- Type: info.Type,
- PlugsOnly: len(info.Slots) == 0,
+ Type: info.Type,
+ PlugsOnly: len(info.Slots) == 0,
+ InstanceKey: snapst.InstanceKey,
}
stopSnapServices := st.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q (%s) services"), snapsup.InstanceName(), snapst.Current))
@@ -1459,11 +1483,12 @@ func Remove(st *state.State, name string, revision snap.Revision) (*state.TaskSe
// main/current SnapSetup
snapsup := SnapSetup{
SideInfo: &snap.SideInfo{
- RealName: name,
+ RealName: snap.InstanceSnap(name),
Revision: revision,
},
- Type: info.Type,
- PlugsOnly: len(info.Slots) == 0,
+ Type: info.Type,
+ PlugsOnly: len(info.Slots) == 0,
+ InstanceKey: snapst.InstanceKey,
}
// trigger remove
@@ -1485,15 +1510,29 @@ func Remove(st *state.State, name string, revision snap.Revision) (*state.TaskSe
removeHook = SetupRemoveHook(st, snapsup.InstanceName())
}
- if active { // unlink
- var prev *state.Task
-
- stopSnapServices := st.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q services"), name))
+ var prev *state.Task
+ var stopSnapServices *state.Task
+ if active {
+ stopSnapServices = st.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q services"), name))
stopSnapServices.Set("snap-setup", snapsup)
stopSnapServices.Set("stop-reason", snap.StopReasonRemove)
+ addNext(state.NewTaskSet(stopSnapServices))
prev = stopSnapServices
+ }
+
+ if removeAll {
+ // run disconnect hooks
+ disconnect := st.NewTask("auto-disconnect", fmt.Sprintf(i18n.G("Disconnect interfaces of snap %q"), snapsup.InstanceName()))
+ disconnect.Set("snap-setup", snapsup)
+ if prev != nil {
+ disconnect.WaitFor(prev)
+ }
+ addNext(state.NewTaskSet(disconnect))
+ prev = disconnect
+ }
- tasks := []*state.Task{stopSnapServices}
+ if active { // unlink
+ var tasks []*state.Task
if removeHook != nil {
tasks = append(tasks, removeHook)
removeHook.WaitFor(prev)
@@ -1524,14 +1563,6 @@ func Remove(st *state.State, name string, revision snap.Revision) (*state.TaskSe
si := seq[i]
addNext(removeInactiveRevision(st, name, si.Revision))
}
-
- discardConns := st.NewTask("discard-conns", fmt.Sprintf(i18n.G("Discard interface connections for snap %q (%s)"), name, revision))
- discardConns.Set("snap-setup", &SnapSetup{
- SideInfo: &snap.SideInfo{
- RealName: name,
- },
- })
- addNext(state.NewTaskSet(discardConns))
} else {
addNext(removeInactiveRevision(st, name, revision))
}
@@ -1540,11 +1571,13 @@ func Remove(st *state.State, name string, revision snap.Revision) (*state.TaskSe
}
func removeInactiveRevision(st *state.State, name string, revision snap.Revision) *state.TaskSet {
+ snapName, instanceKey := snap.SplitInstanceName(name)
snapsup := SnapSetup{
SideInfo: &snap.SideInfo{
- RealName: name,
+ RealName: snapName,
Revision: revision,
},
+ InstanceKey: instanceKey,
}
clearData := st.NewTask("clear-snap", fmt.Sprintf(i18n.G("Remove data for snap %q (%s)"), name, revision))
@@ -1635,10 +1668,11 @@ func RevertToRevision(st *state.State, name string, rev snap.Revision, flags Fla
}
snapsup := &SnapSetup{
- SideInfo: snapst.Sequence[i],
- Flags: flags.ForSnapSetup(),
- Type: info.Type,
- PlugsOnly: len(info.Slots) == 0,
+ SideInfo: snapst.Sequence[i],
+ Flags: flags.ForSnapSetup(),
+ Type: info.Type,
+ PlugsOnly: len(info.Slots) == 0,
+ InstanceKey: snapst.InstanceKey,
}
return doInstall(st, &snapst, snapsup, 0)
}
diff --git a/overlord/snapstate/snapstate_test.go b/overlord/snapstate/snapstate_test.go
index 768f62ecbf..8100f74702 100644
--- a/overlord/snapstate/snapstate_test.go
+++ b/overlord/snapstate/snapstate_test.go
@@ -202,6 +202,7 @@ func AddForeignTaskHandlers(runner *state.TaskRunner, tracker ForeignTaskTracker
}
runner.AddHandler("setup-profiles", fakeHandler, fakeHandler)
runner.AddHandler("auto-connect", fakeHandler, nil)
+ runner.AddHandler("auto-disconnect", fakeHandler, nil)
runner.AddHandler("remove-profiles", fakeHandler, fakeHandler)
runner.AddHandler("discard-conns", fakeHandler, fakeHandler)
runner.AddHandler("validate-snap", fakeHandler, nil)
@@ -427,13 +428,13 @@ func verifyUpdateTasks(c *C, opts, discards int, ts *state.TaskSet, st *state.St
func verifyRemoveTasks(c *C, ts *state.TaskSet) {
c.Assert(taskKinds(ts.Tasks()), DeepEquals, []string{
"stop-snap-services",
+ "auto-disconnect",
"run-hook[remove]",
"remove-aliases",
"unlink-snap",
"remove-profiles",
"clear-snap",
"discard-snap",
- "discard-conns",
})
verifyStopReason(c, ts, "remove")
}
@@ -1887,6 +1888,7 @@ func (s *snapmgrTestSuite) TestInstallRunThrough(c *C) {
c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{{
macaroon: s.user.StoreMacaroon,
name: "some-snap",
+ target: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"),
}})
expected := fakeOps{
{
@@ -1928,6 +1930,7 @@ func (s *snapmgrTestSuite) TestInstallRunThrough(c *C) {
},
{
op: "setup-snap",
+ name: "some-snap",
path: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"),
revno: snap.R(11),
},
@@ -2014,6 +2017,7 @@ func (s *snapmgrTestSuite) TestInstallRunThrough(c *C) {
c.Assert(err, IsNil)
snapst := snaps["some-snap"]
+ c.Assert(snapst, NotNil)
c.Assert(snapst.Active, Equals, true)
c.Assert(snapst.Channel, Equals, "some-channel")
c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{
@@ -2025,6 +2029,174 @@ func (s *snapmgrTestSuite) TestInstallRunThrough(c *C) {
c.Assert(snapst.Required, Equals, false)
}
+func (s *snapmgrTestSuite) TestParallelInstanceInstallRunThrough(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ tr := config.NewTransaction(s.state)
+ tr.Set("core", "experimental.parallel-instances", true)
+ tr.Commit()
+
+ chg := s.state.NewChange("install", "install a snap")
+ ts, err := snapstate.Install(s.state, "some-snap_instance", "some-channel", snap.R(0), s.user.ID, snapstate.Flags{})
+ c.Assert(err, IsNil)
+ chg.AddAll(ts)
+
+ s.state.Unlock()
+ s.settle(c)
+ s.state.Lock()
+
+ // ensure all our tasks ran
+ c.Assert(chg.Err(), IsNil)
+ c.Assert(chg.IsReady(), Equals, true)
+ c.Check(snapstate.Installing(s.state), Equals, false)
+ c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{{
+ macaroon: s.user.StoreMacaroon,
+ // TODO parallel-install: fix once store changes are in place
+ name: "some-snap",
+ target: filepath.Join(dirs.SnapBlobDir, "some-snap_instance_11.snap"),
+ }})
+ expected := fakeOps{
+ {
+ op: "storesvc-snap-action",
+ userID: 1,
+ },
+ {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "install",
+ InstanceName: "some-snap_instance",
+ Channel: "some-channel",
+ },
+ revno: snap.R(11),
+ userID: 1,
+ },
+ {
+ op: "storesvc-download",
+ name: "some-snap",
+ },
+ {
+ op: "validate-snap:Doing",
+ name: "some-snap_instance",
+ revno: snap.R(11),
+ },
+ {
+ op: "current",
+ old: "<no-current>",
+ },
+ {
+ op: "open-snap-file",
+ path: filepath.Join(dirs.SnapBlobDir, "some-snap_instance_11.snap"),
+ sinfo: snap.SideInfo{
+ RealName: "some-snap",
+ SnapID: "some-snap-id",
+ Channel: "some-channel",
+ Revision: snap.R(11),
+ },
+ },
+ {
+ op: "setup-snap",
+ name: "some-snap_instance",
+ path: filepath.Join(dirs.SnapBlobDir, "some-snap_instance_11.snap"),
+ revno: snap.R(11),
+ },
+ {
+ op: "copy-data",
+ path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/11"),
+ old: "<no-old>",
+ },
+ {
+ op: "setup-profiles:Doing",
+ name: "some-snap_instance",
+ revno: snap.R(11),
+ },
+ {
+ op: "candidate",
+ sinfo: snap.SideInfo{
+ RealName: "some-snap",
+ SnapID: "some-snap-id",
+ Channel: "some-channel",
+ Revision: snap.R(11),
+ },
+ },
+ {
+ op: "link-snap",
+ path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/11"),
+ },
+ {
+ op: "auto-connect:Doing",
+ name: "some-snap_instance",
+ revno: snap.R(11),
+ },
+ {
+ op: "update-aliases",
+ },
+ {
+ op: "cleanup-trash",
+ name: "some-snap_instance",
+ revno: snap.R(11),
+ },
+ }
+ // start with an easier-to-read error if this fails:
+ c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
+ c.Assert(s.fakeBackend.ops, DeepEquals, expected)
+
+ // check progress
+ ta := ts.Tasks()
+ task := ta[1]
+ _, cur, total := task.Progress()
+ c.Assert(cur, Equals, s.fakeStore.fakeCurrentProgress)
+ c.Assert(total, Equals, s.fakeStore.fakeTotalProgress)
+ c.Check(task.Summary(), Equals, `Download snap "some-snap_instance" (11) from channel "some-channel"`)
+
+ // check link/start snap summary
+ linkTask := ta[len(ta)-7]
+ c.Check(linkTask.Summary(), Equals, `Make snap "some-snap_instance" (11) available to the system`)
+ startTask := ta[len(ta)-2]
+ c.Check(startTask.Summary(), Equals, `Start snap "some-snap_instance" (11) services`)
+
+ // verify snap-setup in the task state
+ var snapsup snapstate.SnapSetup
+ err = task.Get("snap-setup", &snapsup)
+ c.Assert(err, IsNil)
+ c.Assert(snapsup, DeepEquals, snapstate.SnapSetup{
+ Channel: "some-channel",
+ UserID: s.user.ID,
+ SnapPath: filepath.Join(dirs.SnapBlobDir, "some-snap_instance_11.snap"),
+ DownloadInfo: &snap.DownloadInfo{
+ DownloadURL: "https://some-server.com/some/path.snap",
+ },
+ SideInfo: snapsup.SideInfo,
+ Type: snap.TypeApp,
+ PlugsOnly: true,
+ InstanceKey: "instance",
+ })
+ c.Assert(snapsup.SideInfo, DeepEquals, &snap.SideInfo{
+ RealName: "some-snap",
+ Channel: "some-channel",
+ Revision: snap.R(11),
+ SnapID: "some-snap-id",
+ })
+
+ // verify snaps in the system state
+ var snaps map[string]*snapstate.SnapState
+ err = s.state.Get("snaps", &snaps)
+ c.Assert(err, IsNil)
+
+ snapst := snaps["some-snap_instance"]
+ c.Assert(snapst, NotNil)
+ c.Assert(snapst.Active, Equals, true)
+ c.Assert(snapst.Channel, Equals, "some-channel")
+ c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{
+ RealName: "some-snap",
+ SnapID: "some-snap-id",
+ Channel: "some-channel",
+ Revision: snap.R(11),
+ })
+ c.Assert(snapst.Required, Equals, false)
+ c.Assert(snapst.InstanceKey, Equals, "instance")
+}
+
func (s *snapmgrTestSuite) TestInstallWithRevisionRunThrough(c *C) {
s.state.Lock()
defer s.state.Unlock()
@@ -2046,6 +2218,7 @@ func (s *snapmgrTestSuite) TestInstallWithRevisionRunThrough(c *C) {
c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{{
macaroon: s.user.StoreMacaroon,
name: "some-snap",
+ target: filepath.Join(dirs.SnapBlobDir, "some-snap_42.snap"),
}})
expected := fakeOps{
{
@@ -2086,6 +2259,7 @@ func (s *snapmgrTestSuite) TestInstallWithRevisionRunThrough(c *C) {
},
{
op: "setup-snap",
+ name: "some-snap",
path: filepath.Join(dirs.SnapBlobDir, "some-snap_42.snap"),
revno: snap.R(42),
},
@@ -2170,6 +2344,7 @@ func (s *snapmgrTestSuite) TestInstallWithRevisionRunThrough(c *C) {
c.Assert(err, IsNil)
snapst := snaps["some-snap"]
+ c.Assert(snapst, NotNil)
c.Assert(snapst.Active, Equals, true)
c.Assert(snapst.Channel, Equals, "some-channel")
c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{
@@ -2279,6 +2454,7 @@ func (s *snapmgrTestSuite) TestUpdateRunThrough(c *C) {
},
{
op: "setup-snap",
+ name: "services-snap",
path: filepath.Join(dirs.SnapBlobDir, "services-snap_11.snap"),
revno: snap.R(11),
},
@@ -2340,6 +2516,7 @@ func (s *snapmgrTestSuite) TestUpdateRunThrough(c *C) {
c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{{
macaroon: s.user.StoreMacaroon,
name: "services-snap",
+ target: filepath.Join(dirs.SnapBlobDir, "services-snap_11.snap"),
}})
// start with an easier-to-read error if this fails:
c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
@@ -2403,6 +2580,223 @@ func (s *snapmgrTestSuite) TestUpdateRunThrough(c *C) {
})
}
+func (s *snapmgrTestSuite) TestParallelInstanceUpdateRunThrough(c *C) {
+ // use services-snap here to make sure services would be stopped/started appropriately
+ si := snap.SideInfo{
+ RealName: "services-snap",
+ Revision: snap.R(7),
+ SnapID: "services-snap-id",
+ }
+ snaptest.MockSnapInstance(c, "services-snap_instance", `name: services-snap`, &si)
+ fi, err := os.Stat(snap.MountFile("services-snap_instance", si.Revision))
+ c.Assert(err, IsNil)
+ refreshedDate := fi.ModTime()
+ // look at disk
+ r := snapstate.MockRevisionDate(nil)
+ defer r()
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ tr := config.NewTransaction(s.state)
+ tr.Set("core", "experimental.parallel-instances", true)
+ tr.Commit()
+
+ snapstate.Set(s.state, "services-snap_instance", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{&si},
+ Current: si.Revision,
+ SnapType: "app",
+ Channel: "stable",
+ InstanceKey: "instance",
+ })
+
+ chg := s.state.NewChange("refresh", "refresh a snap")
+ ts, err := snapstate.Update(s.state, "services-snap_instance", "some-channel", snap.R(0), s.user.ID, snapstate.Flags{})
+ c.Assert(err, IsNil)
+ chg.AddAll(ts)
+
+ s.state.Unlock()
+ s.settle(c)
+ s.state.Lock()
+
+ expected := fakeOps{
+ {
+ op: "storesvc-snap-action",
+ curSnaps: []store.CurrentSnap{{
+ InstanceName: "services-snap_instance",
+ SnapID: "services-snap-id",
+ Revision: snap.R(7),
+ TrackingChannel: "stable",
+ RefreshedDate: refreshedDate,
+ }},
+ userID: 1,
+ },
+ {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "refresh",
+ SnapID: "services-snap-id",
+ InstanceName: "services-snap_instance",
+ Channel: "some-channel",
+ Flags: store.SnapActionEnforceValidation,
+ },
+ revno: snap.R(11),
+ userID: 1,
+ },
+ {
+ op: "storesvc-download",
+ name: "services-snap",
+ },
+ {
+ op: "validate-snap:Doing",
+ name: "services-snap_instance",
+ revno: snap.R(11),
+ },
+ {
+ op: "current",
+ old: filepath.Join(dirs.SnapMountDir, "services-snap_instance/7"),
+ },
+ {
+ op: "open-snap-file",
+ path: filepath.Join(dirs.SnapBlobDir, "services-snap_instance_11.snap"),
+ sinfo: snap.SideInfo{
+ RealName: "services-snap",
+ SnapID: "services-snap-id",
+ Channel: "some-channel",
+ Revision: snap.R(11),
+ },
+ },
+ {
+ op: "setup-snap",
+ name: "services-snap_instance",
+ path: filepath.Join(dirs.SnapBlobDir, "services-snap_instance_11.snap"),
+ revno: snap.R(11),
+ },
+ {
+ op: "stop-snap-services:refresh",
+ path: filepath.Join(dirs.SnapMountDir, "services-snap_instance/7"),
+ },
+ {
+ op: "remove-snap-aliases",
+ name: "services-snap_instance",
+ },
+ {
+ op: "unlink-snap",
+ path: filepath.Join(dirs.SnapMountDir, "services-snap_instance/7"),
+ },
+ {
+ op: "copy-data",
+ path: filepath.Join(dirs.SnapMountDir, "services-snap_instance/11"),
+ old: filepath.Join(dirs.SnapMountDir, "services-snap_instance/7"),
+ },
+ {
+ op: "setup-profiles:Doing",
+ name: "services-snap_instance",
+ revno: snap.R(11),
+ },
+ {
+ op: "candidate",
+ sinfo: snap.SideInfo{
+ RealName: "services-snap",
+ SnapID: "services-snap-id",
+ Channel: "some-channel",
+ Revision: snap.R(11),
+ },
+ },
+ {
+ op: "link-snap",
+ path: filepath.Join(dirs.SnapMountDir, "services-snap_instance/11"),
+ },
+ {
+ op: "auto-connect:Doing",
+ name: "services-snap_instance",
+ revno: snap.R(11),
+ },
+ {
+ op: "update-aliases",
+ },
+ {
+ op: "start-snap-services",
+ path: filepath.Join(dirs.SnapMountDir, "services-snap_instance/11"),
+ },
+ {
+ op: "cleanup-trash",
+ name: "services-snap_instance",
+ revno: snap.R(11),
+ },
+ }
+
+ // ensure all our tasks ran
+ c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{{
+ macaroon: s.user.StoreMacaroon,
+ name: "services-snap",
+ target: filepath.Join(dirs.SnapBlobDir, "services-snap_instance_11.snap"),
+ }})
+ // start with an easier-to-read error if this fails:
+ c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
+ c.Assert(s.fakeBackend.ops, DeepEquals, expected)
+
+ // check progress
+ task := ts.Tasks()[1]
+ _, cur, total := task.Progress()
+ c.Assert(cur, Equals, s.fakeStore.fakeCurrentProgress)
+ c.Assert(total, Equals, s.fakeStore.fakeTotalProgress)
+
+ // verify snapSetup info
+ var snapsup snapstate.SnapSetup
+ err = task.Get("snap-setup", &snapsup)
+ c.Assert(err, IsNil)
+ c.Assert(snapsup, DeepEquals, snapstate.SnapSetup{
+ Channel: "some-channel",
+ UserID: s.user.ID,
+
+ SnapPath: filepath.Join(dirs.SnapBlobDir, "services-snap_instance_11.snap"),
+ DownloadInfo: &snap.DownloadInfo{
+ DownloadURL: "https://some-server.com/some/path.snap",
+ },
+ SideInfo: snapsup.SideInfo,
+ Type: snap.TypeApp,
+ PlugsOnly: true,
+ InstanceKey: "instance",
+ })
+ c.Assert(snapsup.SideInfo, DeepEquals, &snap.SideInfo{
+ RealName: "services-snap",
+ Revision: snap.R(11),
+ Channel: "some-channel",
+ SnapID: "services-snap-id",
+ })
+
+ // verify services stop reason
+ verifyStopReason(c, ts, "refresh")
+
+ // check post-refresh hook
+ task = ts.Tasks()[14]
+ c.Assert(task.Kind(), Equals, "run-hook")
+ c.Assert(task.Summary(), Matches, `Run post-refresh hook of "services-snap_instance" snap if present`)
+
+ // verify snaps in the system state
+ var snapst snapstate.SnapState
+ err = snapstate.Get(s.state, "services-snap_instance", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Assert(snapst.InstanceKey, Equals, "instance")
+ c.Assert(snapst.Active, Equals, true)
+ c.Assert(snapst.Sequence, HasLen, 2)
+ c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{
+ RealName: "services-snap",
+ SnapID: "services-snap-id",
+ Channel: "",
+ Revision: snap.R(7),
+ })
+ c.Assert(snapst.Sequence[1], DeepEquals, &snap.SideInfo{
+ RealName: "services-snap",
+ Channel: "some-channel",
+ SnapID: "services-snap-id",
+ Revision: snap.R(11),
+ })
+}
+
func (s *snapmgrTestSuite) TestUpdateWithNewBase(c *C) {
si := &snap.SideInfo{
RealName: "some-snap",
@@ -2433,8 +2827,8 @@ func (s *snapmgrTestSuite) TestUpdateWithNewBase(c *C) {
s.state.Lock()
c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{
- {macaroon: s.user.StoreMacaroon, name: "some-base"},
- {macaroon: s.user.StoreMacaroon, name: "some-snap"},
+ {macaroon: s.user.StoreMacaroon, name: "some-base", target: filepath.Join(dirs.SnapBlobDir, "some-base_11.snap")},
+ {macaroon: s.user.StoreMacaroon, name: "some-snap", target: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap")},
})
}
@@ -2479,7 +2873,7 @@ func (s *snapmgrTestSuite) TestUpdateWithAlreadyInstalledBase(c *C) {
s.state.Lock()
c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{
- {macaroon: s.user.StoreMacaroon, name: "some-snap"},
+ {macaroon: s.user.StoreMacaroon, name: "some-snap", target: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap")},
})
}
@@ -2516,8 +2910,8 @@ func (s *snapmgrTestSuite) TestUpdateWithNewDefaultProvider(c *C) {
s.state.Lock()
c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{
- {macaroon: s.user.StoreMacaroon, name: "snap-content-plug"},
- {macaroon: s.user.StoreMacaroon, name: "snap-content-slot"},
+ {macaroon: s.user.StoreMacaroon, name: "snap-content-plug", target: filepath.Join(dirs.SnapBlobDir, "snap-content-plug_11.snap")},
+ {macaroon: s.user.StoreMacaroon, name: "snap-content-slot", target: filepath.Join(dirs.SnapBlobDir, "snap-content-slot_11.snap")},
})
}
@@ -2565,7 +2959,7 @@ func (s *snapmgrTestSuite) TestUpdateWithInstalledDefaultProvider(c *C) {
s.state.Lock()
c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{
- {macaroon: s.user.StoreMacaroon, name: "snap-content-plug"},
+ {macaroon: s.user.StoreMacaroon, name: "snap-content-plug", target: filepath.Join(dirs.SnapBlobDir, "snap-content-plug_11.snap")},
})
}
@@ -2605,6 +2999,7 @@ func (s *snapmgrTestSuite) TestUpdateRememberedUserRunThrough(c *C) {
c.Check(s.fakeStore.downloads[0], DeepEquals, fakeDownload{
macaroon: "macaroon",
name: "some-snap",
+ target: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"),
}, Commentf(snapName))
}
}
@@ -2646,6 +3041,7 @@ func (s *snapmgrTestSuite) TestUpdateToRevisionRememberedUserRunThrough(c *C) {
c.Check(s.fakeStore.downloads[0], DeepEquals, fakeDownload{
macaroon: "macaroon",
name: "some-snap",
+ target: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"),
}, Commentf(snapName))
}
}
@@ -2722,7 +3118,11 @@ func (s *snapmgrTestSuite) TestUpdateManyMultipleCredsNoUserRunThrough(c *C) {
seen[snapID] = op.userID
case "storesvc-download":
snapName := op.name
- c.Check(s.fakeStore.downloads[di], DeepEquals, fakeDownload{
+ fakeDl := s.fakeStore.downloads[di]
+ // check target path separately and clear it
+ c.Check(fakeDl.target, Matches, filepath.Join(dirs.SnapBlobDir, fmt.Sprintf("%s_[0-9]+.snap", snapName)))
+ fakeDl.target = ""
+ c.Check(fakeDl, DeepEquals, fakeDownload{
macaroon: macaroonMap[snapName],
name: snapName,
}, Commentf(snapName))
@@ -2812,7 +3212,11 @@ func (s *snapmgrTestSuite) TestUpdateManyMultipleCredsUserRunThrough(c *C) {
seen[snapIDuserID{snapID: snapID, userID: op.userID}] = true
case "storesvc-download":
snapName := op.name
- c.Check(s.fakeStore.downloads[di], DeepEquals, fakeDownload{
+ fakeDl := s.fakeStore.downloads[di]
+ // check target path separately and clear it
+ c.Check(fakeDl.target, Matches, filepath.Join(dirs.SnapBlobDir, fmt.Sprintf("%s_[0-9]+.snap", snapName)))
+ fakeDl.target = ""
+ c.Check(fakeDl, DeepEquals, fakeDownload{
macaroon: macaroonMap[snapName],
name: snapName,
}, Commentf(snapName))
@@ -2913,7 +3317,11 @@ func (s *snapmgrTestSuite) TestUpdateManyMultipleCredsUserWithNoStoreAuthRunThro
seen[snapID] = op.userID
case "storesvc-download":
snapName := op.name
- c.Check(s.fakeStore.downloads[di], DeepEquals, fakeDownload{
+ fakeDl := s.fakeStore.downloads[di]
+ // check target path separately and clear it
+ c.Check(fakeDl.target, Matches, filepath.Join(dirs.SnapBlobDir, fmt.Sprintf("%s_[0-9]+.snap", snapName)))
+ fakeDl.target = ""
+ c.Check(fakeDl, DeepEquals, fakeDownload{
macaroon: macaroonMap[snapName],
name: snapName,
}, Commentf(snapName))
@@ -3005,6 +3413,7 @@ func (s *snapmgrTestSuite) TestUpdateUndoRunThrough(c *C) {
},
{
op: "setup-snap",
+ name: "some-snap",
path: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"),
revno: snap.R(11),
},
@@ -3062,6 +3471,7 @@ func (s *snapmgrTestSuite) TestUpdateUndoRunThrough(c *C) {
},
{
op: "undo-setup-snap",
+ name: "some-snap",
path: filepath.Join(dirs.SnapMountDir, "some-snap/11"),
stype: "app",
},
@@ -3071,6 +3481,7 @@ func (s *snapmgrTestSuite) TestUpdateUndoRunThrough(c *C) {
c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{{
macaroon: s.user.StoreMacaroon,
name: "some-snap",
+ target: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"),
}})
// start with an easier-to-read error if this fails:
c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
@@ -3178,6 +3589,7 @@ func (s *snapmgrTestSuite) TestUpdateTotalUndoRunThrough(c *C) {
},
{
op: "setup-snap",
+ name: "some-snap",
path: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"),
revno: snap.R(11),
},
@@ -3248,6 +3660,7 @@ func (s *snapmgrTestSuite) TestUpdateTotalUndoRunThrough(c *C) {
},
{
op: "undo-setup-snap",
+ name: "some-snap",
path: filepath.Join(dirs.SnapMountDir, "some-snap/11"),
stype: "app",
},
@@ -3257,6 +3670,7 @@ func (s *snapmgrTestSuite) TestUpdateTotalUndoRunThrough(c *C) {
c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{{
macaroon: s.user.StoreMacaroon,
name: "some-snap",
+ target: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"),
}})
// friendlier failure first
c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
@@ -4455,6 +4869,7 @@ version: 1.0`)
{
// and setup-snap
op: "setup-snap",
+ name: "mock",
path: mockSnap,
revno: snap.R("x1"),
},
@@ -4562,6 +4977,7 @@ version: 1.0`)
c.Check(ops[0].old, Equals, filepath.Join(dirs.SnapMountDir, "mock/x2"))
// and setup-snap
c.Check(ops[1].op, Equals, "setup-snap")
+ c.Check(ops[1].name, Matches, "mock")
c.Check(ops[1].path, Matches, `.*/mock_1.0_all.snap`)
c.Check(ops[1].revno, Equals, snap.R("x3"))
// and cleanup
@@ -4658,6 +5074,7 @@ version: 1.0`)
{
// and setup-snap
op: "setup-snap",
+ name: "mock",
path: mockSnap,
revno: snap.R("x1"),
},
@@ -4753,6 +5170,7 @@ version: 1.0`)
c.Check(s.fakeBackend.ops[0].old, Equals, "<no-current>")
// and setup-snap
c.Check(s.fakeBackend.ops[1].op, Equals, "setup-snap")
+ c.Check(s.fakeBackend.ops[1].name, Equals, "some-snap")
c.Check(s.fakeBackend.ops[1].path, Matches, `.*/orig-name_1.0_all.snap`)
c.Check(s.fakeBackend.ops[1].revno, Equals, snap.R(42))
@@ -4817,6 +5235,11 @@ func (s *snapmgrTestSuite) TestRemoveRunThrough(c *C) {
expected := fakeOps{
{
+ op: "auto-disconnect:Doing",
+ name: "some-snap",
+ revno: snap.R(7),
+ },
+ {
op: "remove-snap-aliases",
name: "some-snap",
},
@@ -4846,10 +5269,6 @@ func (s *snapmgrTestSuite) TestRemoveRunThrough(c *C) {
op: "discard-namespace",
name: "some-snap",
},
- {
- op: "discard-conns:Doing",
- name: "some-snap",
- },
}
// start with an easier-to-read error if this fails:
c.Check(len(s.fakeBackend.ops), Equals, len(expected))
@@ -4901,6 +5320,133 @@ func (s *snapmgrTestSuite) TestRemoveRunThrough(c *C) {
c.Assert(err, Equals, state.ErrNoState)
}
+func (s *snapmgrTestSuite) TestParallelInstanceRemoveRunThrough(c *C) {
+ si := snap.SideInfo{
+ RealName: "some-snap",
+ Revision: snap.R(7),
+ }
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ // pretend we have both a regular snap and a parallel instance
+ snapstate.Set(s.state, "some-snap_instance", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{&si},
+ Current: si.Revision,
+ SnapType: "app",
+ InstanceKey: "instance",
+ })
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{&si},
+ Current: si.Revision,
+ SnapType: "app",
+ })
+
+ chg := s.state.NewChange("remove", "remove a snap")
+ ts, err := snapstate.Remove(s.state, "some-snap_instance", snap.R(0))
+ c.Assert(err, IsNil)
+ chg.AddAll(ts)
+
+ s.state.Unlock()
+ s.settle(c)
+ s.state.Lock()
+
+ expected := fakeOps{
+ {
+ op: "auto-disconnect:Doing",
+ name: "some-snap_instance",
+ revno: snap.R(7),
+ },
+ {
+ op: "remove-snap-aliases",
+ name: "some-snap_instance",
+ },
+ {
+ op: "unlink-snap",
+ path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"),
+ },
+ {
+ op: "remove-profiles:Doing",
+ name: "some-snap_instance",
+ revno: snap.R(7),
+ },
+ {
+ op: "remove-snap-data",
+ path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"),
+ },
+ {
+ op: "remove-snap-common-data",
+ path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"),
+ },
+ {
+ op: "remove-snap-files",
+ path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"),
+ stype: "app",
+ },
+ {
+ op: "discard-namespace",
+ name: "some-snap_instance",
+ },
+ }
+ // start with an easier-to-read error if this fails:
+ c.Check(len(s.fakeBackend.ops), Equals, len(expected))
+ c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
+ c.Check(s.fakeBackend.ops, DeepEquals, expected)
+
+ // verify snapSetup info
+ tasks := ts.Tasks()
+ for _, t := range tasks {
+ if t.Kind() == "run-hook" {
+ continue
+ }
+ snapsup, err := snapstate.TaskSnapSetup(t)
+ c.Assert(err, IsNil)
+
+ var expSnapSetup *snapstate.SnapSetup
+ switch t.Kind() {
+ case "discard-conns":
+ expSnapSetup = &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{
+ RealName: "some-snap",
+ },
+ InstanceKey: "instance",
+ }
+ case "clear-snap", "discard-snap":
+ expSnapSetup = &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{
+ RealName: "some-snap",
+ Revision: snap.R(7),
+ },
+ InstanceKey: "instance",
+ }
+ default:
+ expSnapSetup = &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{
+ RealName: "some-snap",
+ Revision: snap.R(7),
+ },
+ Type: snap.TypeApp,
+ PlugsOnly: true,
+ InstanceKey: "instance",
+ }
+
+ }
+
+ c.Check(snapsup, DeepEquals, expSnapSetup, Commentf(t.Kind()))
+ }
+
+ // verify snaps in the system state
+ var snapst snapstate.SnapState
+ err = snapstate.Get(s.state, "some-snap_instance", &snapst)
+ c.Assert(err, Equals, state.ErrNoState)
+
+ // the non-instance snap is still there
+ err = snapstate.Get(s.state, "some-snap", &snapst)
+ c.Assert(err, IsNil)
+}
+
func (s *snapmgrTestSuite) TestRemoveWithManyRevisionsRunThrough(c *C) {
si3 := snap.SideInfo{
RealName: "some-snap",
@@ -4939,6 +5485,11 @@ func (s *snapmgrTestSuite) TestRemoveWithManyRevisionsRunThrough(c *C) {
expected := fakeOps{
{
+ op: "auto-disconnect:Doing",
+ name: "some-snap",
+ revno: snap.R(7),
+ },
+ {
op: "remove-snap-aliases",
name: "some-snap",
},
@@ -4986,10 +5537,6 @@ func (s *snapmgrTestSuite) TestRemoveWithManyRevisionsRunThrough(c *C) {
op: "discard-namespace",
name: "some-snap",
},
- {
- op: "discard-conns:Doing",
- name: "some-snap",
- },
}
// start with an easier-to-read error if this fails:
c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
@@ -5150,6 +5697,11 @@ func (s *snapmgrTestSuite) TestRemoveLastRevisionRunThrough(c *C) {
c.Check(len(s.fakeBackend.ops), Equals, 5)
expected := fakeOps{
{
+ op: "auto-disconnect:Doing",
+ name: "some-snap",
+ revno: snap.R(2),
+ },
+ {
op: "remove-snap-data",
path: filepath.Join(dirs.SnapMountDir, "some-snap/2"),
},
@@ -5166,10 +5718,6 @@ func (s *snapmgrTestSuite) TestRemoveLastRevisionRunThrough(c *C) {
op: "discard-namespace",
name: "some-snap",
},
- {
- op: "discard-conns:Doing",
- name: "some-snap",
- },
}
// start with an easier-to-read error if this fails:
c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
@@ -5192,6 +5740,10 @@ func (s *snapmgrTestSuite) TestRemoveLastRevisionRunThrough(c *C) {
if t.Kind() != "discard-conns" {
expSnapSetup.SideInfo.Revision = snap.R(2)
}
+ if t.Kind() == "auto-disconnect" {
+ expSnapSetup.PlugsOnly = true
+ expSnapSetup.Type = "app"
+ }
c.Check(snapsup, DeepEquals, expSnapSetup, Commentf(t.Kind()))
}
@@ -5762,6 +6314,112 @@ func (s *snapmgrTestSuite) TestRevertRunThrough(c *C) {
c.Assert(snapst.Block(), DeepEquals, []snap.Revision{snap.R(7)})
}
+func (s *snapmgrTestSuite) TestParallelInstanceRevertRunThrough(c *C) {
+ si := snap.SideInfo{
+ RealName: "some-snap",
+ Revision: snap.R(7),
+ }
+ siOld := snap.SideInfo{
+ RealName: "some-snap",
+ Revision: snap.R(2),
+ }
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "some-snap_instance", &snapstate.SnapState{
+ Active: true,
+ SnapType: "app",
+ Sequence: []*snap.SideInfo{&siOld, &si},
+ Current: si.Revision,
+ InstanceKey: "instance",
+ })
+
+ // another snap withouth instance key
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Active: true,
+ SnapType: "app",
+ Sequence: []*snap.SideInfo{&siOld, &si},
+ Current: si.Revision,
+ })
+
+ chg := s.state.NewChange("revert", "revert a snap backwards")
+ ts, err := snapstate.Revert(s.state, "some-snap_instance", snapstate.Flags{})
+ c.Assert(err, IsNil)
+ chg.AddAll(ts)
+
+ s.state.Unlock()
+ defer s.se.Stop()
+ s.settle(c)
+ s.state.Lock()
+
+ expected := fakeOps{
+ {
+ op: "remove-snap-aliases",
+ name: "some-snap_instance",
+ },
+ {
+ op: "unlink-snap",
+ path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"),
+ },
+ {
+ op: "setup-profiles:Doing",
+ name: "some-snap_instance",
+ revno: snap.R(2),
+ },
+ {
+ op: "candidate",
+ sinfo: snap.SideInfo{
+ RealName: "some-snap",
+ Revision: snap.R(2),
+ },
+ },
+ {
+ op: "link-snap",
+ path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/2"),
+ },
+ {
+ op: "auto-connect:Doing",
+ name: "some-snap_instance",
+ revno: snap.R(2),
+ },
+ {
+ op: "update-aliases",
+ },
+ }
+ // start with an easier-to-read error if this fails:
+ c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
+ c.Assert(s.fakeBackend.ops, DeepEquals, expected)
+
+ // verify that the R(2) version is active now and R(7) is still there
+ var snapst snapstate.SnapState
+ err = snapstate.Get(s.state, "some-snap_instance", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Assert(snapst.Active, Equals, true)
+ c.Assert(snapst.Current, Equals, snap.R(2))
+ c.Assert(snapst.InstanceKey, Equals, "instance")
+ c.Assert(snapst.Sequence, HasLen, 2)
+ c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{
+ RealName: "some-snap",
+ Channel: "",
+ Revision: snap.R(2),
+ })
+ c.Assert(snapst.Sequence[1], DeepEquals, &snap.SideInfo{
+ RealName: "some-snap",
+ Channel: "",
+ Revision: snap.R(7),
+ })
+ c.Assert(snapst.Block(), DeepEquals, []snap.Revision{snap.R(7)})
+
+ // non instance snap is not affected
+ var nonInstanceSnapst snapstate.SnapState
+ err = snapstate.Get(s.state, "some-snap", &nonInstanceSnapst)
+ c.Assert(err, IsNil)
+ c.Assert(nonInstanceSnapst.Current, Equals, snap.R(7))
+
+}
+
func (s *snapmgrTestSuite) TestRevertWithLocalRevisionRunThrough(c *C) {
si := snap.SideInfo{
RealName: "some-snap",
@@ -6257,6 +6915,167 @@ func (s *snapmgrTestSuite) TestDisableRunThrough(c *C) {
})
}
+func (s *snapmgrTestSuite) TestParallelInstanceEnableRunThrough(c *C) {
+ si := snap.SideInfo{
+ RealName: "some-snap",
+ Revision: snap.R(7),
+ Channel: "edge",
+ SnapID: "foo",
+ }
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ flags := snapstate.Flags{
+ DevMode: true,
+ JailMode: true,
+ Classic: true,
+ TryMode: true,
+ Required: true,
+ }
+ snapstate.Set(s.state, "some-snap_instance", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{&si},
+ Current: si.Revision,
+ Active: false,
+ Channel: "edge",
+ Flags: flags,
+ AliasesPending: true,
+ AutoAliasesDisabled: true,
+ InstanceKey: "instance",
+ })
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{&si},
+ Current: si.Revision,
+ Active: false,
+ Channel: "edge",
+ Flags: flags,
+ AliasesPending: true,
+ AutoAliasesDisabled: true,
+ })
+
+ chg := s.state.NewChange("enable", "enable a snap")
+ ts, err := snapstate.Enable(s.state, "some-snap_instance")
+ c.Assert(err, IsNil)
+ chg.AddAll(ts)
+
+ s.state.Unlock()
+ s.settle(c)
+ s.state.Lock()
+
+ expected := fakeOps{
+ {
+ op: "setup-profiles:Doing",
+ name: "some-snap_instance",
+ revno: snap.R(7),
+ },
+ {
+ op: "candidate",
+ sinfo: si,
+ },
+ {
+ op: "link-snap",
+ path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"),
+ },
+ {
+ op: "auto-connect:Doing",
+ name: "some-snap_instance",
+ revno: snap.R(7),
+ },
+ {
+ op: "update-aliases",
+ },
+ }
+ // start with an easier-to-read error if this fails:
+ c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
+ c.Assert(s.fakeBackend.ops, DeepEquals, expected)
+
+ var snapst snapstate.SnapState
+ err = snapstate.Get(s.state, "some-snap_instance", &snapst)
+ c.Assert(err, IsNil)
+ c.Check(snapst.Flags, DeepEquals, flags)
+
+ c.Assert(snapst.InstanceKey, Equals, "instance")
+ c.Assert(snapst.Active, Equals, true)
+ c.Assert(snapst.AliasesPending, Equals, false)
+ c.Assert(snapst.AutoAliasesDisabled, Equals, true)
+
+ info, err := snapst.CurrentInfo()
+ c.Assert(err, IsNil)
+ c.Assert(info.Channel, Equals, "edge")
+ c.Assert(info.SnapID, Equals, "foo")
+
+ // the non-parallel instance is still disabled
+ snapst = snapstate.SnapState{}
+ err = snapstate.Get(s.state, "some-snap", &snapst)
+ c.Assert(err, IsNil)
+ c.Assert(snapst.InstanceKey, Equals, "")
+ c.Assert(snapst.Active, Equals, false)
+}
+
+func (s *snapmgrTestSuite) TestParallelInstanceDisableRunThrough(c *C) {
+ si := snap.SideInfo{
+ RealName: "some-snap",
+ Revision: snap.R(7),
+ }
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{&si},
+ Current: si.Revision,
+ Active: true,
+ })
+ snapstate.Set(s.state, "some-snap_instance", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{&si},
+ Current: si.Revision,
+ Active: true,
+ InstanceKey: "instance",
+ })
+
+ chg := s.state.NewChange("disable", "disable a snap")
+ ts, err := snapstate.Disable(s.state, "some-snap_instance")
+ c.Assert(err, IsNil)
+ chg.AddAll(ts)
+
+ s.state.Unlock()
+ s.settle(c)
+ s.state.Lock()
+
+ expected := fakeOps{
+ {
+ op: "remove-snap-aliases",
+ name: "some-snap_instance",
+ },
+ {
+ op: "unlink-snap",
+ path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"),
+ },
+ {
+ op: "remove-profiles:Doing",
+ name: "some-snap_instance",
+ revno: snap.R(7),
+ },
+ }
+ // start with an easier-to-read error if this fails:
+ c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
+ c.Assert(s.fakeBackend.ops, DeepEquals, expected)
+
+ var snapst snapstate.SnapState
+ err = snapstate.Get(s.state, "some-snap_instance", &snapst)
+ c.Assert(err, IsNil)
+ c.Assert(snapst.InstanceKey, Equals, "instance")
+ c.Assert(snapst.Active, Equals, false)
+ c.Assert(snapst.AliasesPending, Equals, true)
+
+ // the non-parallel instance is still active
+ snapst = snapstate.SnapState{}
+ err = snapstate.Get(s.state, "some-snap", &snapst)
+ c.Assert(err, IsNil)
+ c.Assert(snapst.InstanceKey, Equals, "")
+ c.Assert(snapst.Active, Equals, true)
+}
+
func (s *snapmgrTestSuite) TestSwitchRunThrough(c *C) {
si := snap.SideInfo{
RealName: "some-snap",
@@ -6299,6 +7118,61 @@ func (s *snapmgrTestSuite) TestSwitchRunThrough(c *C) {
c.Assert(info.Channel, Equals, "edge")
}
+func (s *snapmgrTestSuite) TestParallelInstallSwitchRunThrough(c *C) {
+ si := snap.SideInfo{
+ RealName: "some-snap",
+ Revision: snap.R(7),
+ Channel: "edge",
+ SnapID: "foo",
+ }
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{&si},
+ Current: si.Revision,
+ Channel: "edge",
+ })
+
+ snapstate.Set(s.state, "some-snap_instance", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{&si},
+ Current: si.Revision,
+ Channel: "edge",
+ InstanceKey: "instance",
+ })
+
+ chg := s.state.NewChange("switch-snap", "switch snap to some-channel")
+ ts, err := snapstate.Switch(s.state, "some-snap_instance", "some-channel")
+ c.Assert(err, IsNil)
+ chg.AddAll(ts)
+
+ s.state.Unlock()
+ defer s.se.Stop()
+ s.settle(c)
+ s.state.Lock()
+
+ // switch is not really really doing anything backend related
+ c.Assert(s.fakeBackend.ops, HasLen, 0)
+
+ // ensure the desired channel has changed
+ var snapst snapstate.SnapState
+ err = snapstate.Get(s.state, "some-snap_instance", &snapst)
+ c.Assert(err, IsNil)
+ c.Assert(snapst.Channel, Equals, "some-channel")
+
+ // ensure the current info has not changed
+ info, err := snapst.CurrentInfo()
+ c.Assert(err, IsNil)
+ c.Assert(info.Channel, Equals, "edge")
+
+ // Ensure that the non-intance snap is unchanged
+ var nonInstanceSnapst snapstate.SnapState
+ err = snapstate.Get(s.state, "some-snap", &nonInstanceSnapst)
+ c.Assert(err, IsNil)
+ c.Assert(nonInstanceSnapst.Channel, Equals, "edge")
+}
+
func (s *snapmgrTestSuite) TestDisableDoesNotEnableAgain(c *C) {
si := snap.SideInfo{
RealName: "some-snap",
@@ -6375,6 +7249,7 @@ func (s *snapmgrTestSuite) TestUndoMountSnapFailsInCopyData(c *C) {
},
{
op: "setup-snap",
+ name: "some-snap",
path: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"),
revno: snap.R(11),
},
@@ -6385,6 +7260,7 @@ func (s *snapmgrTestSuite) TestUndoMountSnapFailsInCopyData(c *C) {
},
{
op: "undo-setup-snap",
+ name: "some-snap",
path: filepath.Join(dirs.SnapMountDir, "some-snap/11"),
stype: "app",
},
@@ -8390,13 +9266,13 @@ func (s *snapmgrTestSuite) TestRemoveMany(c *C) {
for i, ts := range tts {
c.Assert(taskKinds(ts.Tasks()), DeepEquals, []string{
"stop-snap-services",
+ "auto-disconnect",
"run-hook[remove]",
"remove-aliases",
"unlink-snap",
"remove-profiles",
"clear-snap",
"discard-snap",
- "discard-conns",
})
verifyStopReason(c, ts, "remove")
// check that tasksets are in separate lanes
@@ -8798,6 +9674,7 @@ func (s *snapmgrTestSuite) TestTransitionCoreRunThrough(c *C) {
name: "core",
// the transition has no user associcated with it
macaroon: "",
+ target: filepath.Join(dirs.SnapBlobDir, "core_11.snap"),
}})
expected := fakeOps{
{
@@ -8840,6 +9717,7 @@ func (s *snapmgrTestSuite) TestTransitionCoreRunThrough(c *C) {
},
{
op: "setup-snap",
+ name: "core",
path: filepath.Join(dirs.SnapBlobDir, "core_11.snap"),
revno: snap.R(11),
},
@@ -8879,6 +9757,11 @@ func (s *snapmgrTestSuite) TestTransitionCoreRunThrough(c *C) {
name: "ubuntu-core",
},
{
+ op: "auto-disconnect:Doing",
+ name: "ubuntu-core",
+ revno: snap.R(1),
+ },
+ {
op: "remove-snap-aliases",
name: "ubuntu-core",
},
@@ -8909,10 +9792,6 @@ func (s *snapmgrTestSuite) TestTransitionCoreRunThrough(c *C) {
name: "ubuntu-core",
},
{
- op: "discard-conns:Doing",
- name: "ubuntu-core",
- },
- {
op: "cleanup-trash",
name: "core",
revno: snap.R(11),
@@ -8964,6 +9843,11 @@ func (s *snapmgrTestSuite) TestTransitionCoreRunThroughWithCore(c *C) {
name: "ubuntu-core",
},
{
+ op: "auto-disconnect:Doing",
+ name: "ubuntu-core",
+ revno: snap.R(1),
+ },
+ {
op: "remove-snap-aliases",
name: "ubuntu-core",
},
@@ -8993,10 +9877,6 @@ func (s *snapmgrTestSuite) TestTransitionCoreRunThroughWithCore(c *C) {
op: "discard-namespace",
name: "ubuntu-core",
},
- {
- op: "discard-conns:Doing",
- name: "ubuntu-core",
- },
}
// start with an easier-to-read error if this fails:
c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
@@ -9427,10 +10307,12 @@ func (s *snapmgrTestSuite) TestInstallWithoutCoreRunThrough1(c *C) {
{
macaroon: s.user.StoreMacaroon,
name: "core",
+ target: filepath.Join(dirs.SnapBlobDir, "core_11.snap"),
},
{
macaroon: s.user.StoreMacaroon,
name: "some-snap",
+ target: filepath.Join(dirs.SnapBlobDir, "some-snap_42.snap"),
}})
expected := fakeOps{
// we check the snap
@@ -9489,6 +10371,7 @@ func (s *snapmgrTestSuite) TestInstallWithoutCoreRunThrough1(c *C) {
},
{
op: "setup-snap",
+ name: "core",
path: filepath.Join(dirs.SnapBlobDir, "core_11.snap"),
revno: snap.R(11),
},
@@ -9548,6 +10431,7 @@ func (s *snapmgrTestSuite) TestInstallWithoutCoreRunThrough1(c *C) {
},
{
op: "setup-snap",
+ name: "some-snap",
path: filepath.Join(dirs.SnapBlobDir, "some-snap_42.snap"),
revno: snap.R(42),
},
@@ -9606,6 +10490,7 @@ func (s *snapmgrTestSuite) TestInstallWithoutCoreRunThrough1(c *C) {
c.Assert(err, IsNil)
snapst := snaps["core"]
+ c.Assert(snapst, NotNil)
c.Assert(snapst.Active, Equals, true)
c.Assert(snapst.Channel, Equals, "stable")
c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{
@@ -9853,6 +10738,7 @@ func (s *snapmgrTestSuite) TestInstallWithoutCoreConflictingInstall(c *C) {
c.Assert(err, IsNil)
snapst := snaps["core"]
+ c.Assert(snapst, NotNil)
c.Assert(snapst.Active, Equals, true)
c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{
RealName: "core",
@@ -9860,6 +10746,7 @@ func (s *snapmgrTestSuite) TestInstallWithoutCoreConflictingInstall(c *C) {
})
snapst = snaps["some-snap"]
+ c.Assert(snapst, NotNil)
c.Assert(snapst.Active, Equals, true)
c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{
RealName: "some-snap",
@@ -10032,6 +10919,7 @@ func (s *snapmgrTestSuite) TestInstallDefaultProviderRunThrough(c *C) {
},
}, {
op: "setup-snap",
+ name: "snap-content-slot",
path: filepath.Join(dirs.SnapBlobDir, "snap-content-slot_11.snap"),
revno: snap.R(11),
}, {
@@ -10079,6 +10967,7 @@ func (s *snapmgrTestSuite) TestInstallDefaultProviderRunThrough(c *C) {
},
}, {
op: "setup-snap",
+ name: "snap-content-plug",
path: filepath.Join(dirs.SnapBlobDir, "snap-content-plug_42.snap"),
revno: snap.R(42),
}, {
@@ -10378,8 +11267,27 @@ func (s *snapmgrTestSuite) TestInstallLayoutsChecksFeatureFlag(c *C) {
_, err := snapstate.Install(s.state, "some-snap", "channel-for-layout", snap.R(0), s.user.ID, snapstate.Flags{})
c.Assert(err, ErrorMatches, "experimental feature disabled - test it by setting 'experimental.layouts' to true")
- // enable layouts
+ // check various forms of disabling
tr := config.NewTransaction(s.state)
+ tr.Set("core", "experimental.layouts", false)
+ tr.Commit()
+ _, err = snapstate.Install(s.state, "some-snap", "channel-for-layout", snap.R(0), s.user.ID, snapstate.Flags{})
+ c.Assert(err, ErrorMatches, "experimental feature disabled - test it by setting 'experimental.layouts' to true")
+
+ tr = config.NewTransaction(s.state)
+ tr.Set("core", "experimental.layouts", "")
+ tr.Commit()
+ _, err = snapstate.Install(s.state, "some-snap", "channel-for-layout", snap.R(0), s.user.ID, snapstate.Flags{})
+ c.Assert(err, ErrorMatches, "experimental feature disabled - test it by setting 'experimental.layouts' to true")
+
+ tr = config.NewTransaction(s.state)
+ tr.Set("core", "experimental.layouts", nil)
+ tr.Commit()
+ _, err = snapstate.Install(s.state, "some-snap", "channel-for-layout", snap.R(0), s.user.ID, snapstate.Flags{})
+ c.Assert(err, ErrorMatches, "experimental feature disabled - test it by setting 'experimental.layouts' to true")
+
+ // enable layouts
+ tr = config.NewTransaction(s.state)
tr.Set("core", "experimental.layouts", true)
tr.Commit()
@@ -10477,8 +11385,37 @@ func (s *snapmgrTestSuite) TestParallelInstallValidateFeatureFlag(c *C) {
err := snapstate.ValidateFeatureFlags(s.state, info)
c.Assert(err, ErrorMatches, `experimental feature disabled - test it by setting 'experimental.parallel-instances' to true`)
- // enable parallel instances
+ // various forms of disabling
tr := config.NewTransaction(s.state)
+ tr.Set("core", "experimental.parallel-instances", false)
+ tr.Commit()
+
+ err = snapstate.ValidateFeatureFlags(s.state, info)
+ c.Assert(err, ErrorMatches, `experimental feature disabled - test it by setting 'experimental.parallel-instances' to true`)
+
+ tr = config.NewTransaction(s.state)
+ tr.Set("core", "experimental.parallel-instances", "")
+ tr.Commit()
+
+ err = snapstate.ValidateFeatureFlags(s.state, info)
+ c.Assert(err, ErrorMatches, `experimental feature disabled - test it by setting 'experimental.parallel-instances' to true`)
+
+ tr = config.NewTransaction(s.state)
+ tr.Set("core", "experimental.parallel-instances", nil)
+ tr.Commit()
+
+ err = snapstate.ValidateFeatureFlags(s.state, info)
+ c.Assert(err, ErrorMatches, `experimental feature disabled - test it by setting 'experimental.parallel-instances' to true`)
+
+ tr = config.NewTransaction(s.state)
+ tr.Set("core", "experimental.parallel-instances", "veryfalse")
+ tr.Commit()
+
+ err = snapstate.ValidateFeatureFlags(s.state, info)
+ c.Assert(err, ErrorMatches, `internal error: feature flag experimental.parallel-instances has unexpected value "veryfalse" \(string\)`)
+
+ // enable parallel instances
+ tr = config.NewTransaction(s.state)
tr.Set("core", "experimental.parallel-instances", true)
tr.Commit()
diff --git a/packaging/opensuse/snapd.spec b/packaging/opensuse/snapd.spec
index bf85db3ef1..d0609ac3c9 100644
--- a/packaging/opensuse/snapd.spec
+++ b/packaging/opensuse/snapd.spec
@@ -194,13 +194,11 @@ go install -s -v -p 4 -x -tags withtestkeys github.com/snapcore/snapd/cmd/snapd
%gobuild cmd/snap
%gobuild cmd/snapctl
# build snap-exec and snap-update-ns completely static for base snaps
-CGO_ENABLED=0 %gobuild cmd/snap-exec
-# gobuild --ldflags '-extldflags "-static"' bin/snap-update-ns
-# FIXME: ^ this doesn't work yet, it's going to be fixed with another PR.
-%gobuild cmd/snap-update-ns
-
-# This is ok because snap-seccomp only requires static linking when it runs from the core-snap via re-exec.
-sed -e "s/-Bstatic -lseccomp/-Bstatic/g" -i %{_builddir}/go/src/%{provider_prefix}/cmd/snap-seccomp/main.go
+# NOTE: openSUSE's golang rpm helpers pass -buildmode=pie, but glibc is not
+# built with -fPIC so it'll blow up during linking, need to pass
+# -buildmode=default to override this
+%gobuild -buildmode=default -ldflags=-extldflags=-static cmd/snap-exec
+%gobuild -buildmode=default -ldflags=-extldflags=-static cmd/snap-update-ns
# build snap-seccomp
%gobuild cmd/snap-seccomp
diff --git a/snap/hooktypes.go b/snap/hooktypes.go
index 280c0d1ec1..97b59825a7 100644
--- a/snap/hooktypes.go
+++ b/snap/hooktypes.go
@@ -31,7 +31,9 @@ var supportedHooks = []*HookType{
NewHookType(regexp.MustCompile("^post-refresh$")),
NewHookType(regexp.MustCompile("^remove$")),
NewHookType(regexp.MustCompile("^prepare-(?:plug|slot)-[-a-z0-9]+$")),
+ NewHookType(regexp.MustCompile("^unprepare-(?:plug|slot)-[-a-z0-9]+$")),
NewHookType(regexp.MustCompile("^connect-(?:plug|slot)-[-a-z0-9]+$")),
+ NewHookType(regexp.MustCompile("^disconnect-(?:plug|slot)-[-a-z0-9]+$")),
}
// HookType represents a pattern of supported hook names.
diff --git a/snap/info.go b/snap/info.go
index e7ce222eac..a7d713e09f 100644
--- a/snap/info.go
+++ b/snap/info.go
@@ -685,6 +685,7 @@ type AppInfo struct {
Name string
LegacyAliases []string // FIXME: eventually drop this
Command string
+ CommandChain []string
CommonID string
Daemon string
diff --git a/snap/info_snap_yaml.go b/snap/info_snap_yaml.go
index 005d6bd72d..1a32e47306 100644
--- a/snap/info_snap_yaml.go
+++ b/snap/info_snap_yaml.go
@@ -58,7 +58,8 @@ type snapYaml struct {
type appYaml struct {
Aliases []string `yaml:"aliases,omitempty"`
- Command string `yaml:"command"`
+ Command string `yaml:"command"`
+ CommandChain []string `yaml:"command-chain,omitempty"`
Daemon string `yaml:"daemon"`
@@ -294,6 +295,7 @@ func setAppsFromSnapYaml(y snapYaml, snap *Info) error {
Name: appName,
LegacyAliases: yApp.Aliases,
Command: yApp.Command,
+ CommandChain: yApp.CommandChain,
Daemon: yApp.Daemon,
StopTimeout: yApp.StopTimeout,
StopCommand: yApp.StopCommand,
diff --git a/snap/info_snap_yaml_test.go b/snap/info_snap_yaml_test.go
index 3a57fdc4cf..fdd5433bb0 100644
--- a/snap/info_snap_yaml_test.go
+++ b/snap/info_snap_yaml_test.go
@@ -1751,3 +1751,17 @@ apps:
Equals,
true)
}
+
+func (s *YamlSuite) TestSnapYamlCommandChain(c *C) {
+ yAutostart := []byte(`name: wat
+version: 42
+apps:
+ foo:
+ command: bin/foo
+ command-chain: [chain1, chain2]
+`)
+ info, err := snap.InfoFromSnapYaml(yAutostart)
+ c.Assert(err, IsNil)
+ app := info.Apps["foo"]
+ c.Check(app.CommandChain, DeepEquals, []string{"chain1", "chain2"})
+}
diff --git a/snap/info_test.go b/snap/info_test.go
index e5c26a7d3d..b03362c2e6 100644
--- a/snap/info_test.go
+++ b/snap/info_test.go
@@ -185,6 +185,7 @@ apps:
command: bar
sample:
command: foobar
+ command-chain: [chain]
`
func (s *infoSuite) TestReadInfo(c *C) {
@@ -200,6 +201,7 @@ func (s *infoSuite) TestReadInfo(c *C) {
c.Check(snapInfo2.Summary(), Equals, "esummary")
c.Check(snapInfo2.Apps["app"].Command, Equals, "foo")
+ c.Check(snapInfo2.Apps["sample"].CommandChain, DeepEquals, []string{"chain"})
c.Check(snapInfo2, DeepEquals, snapInfo1)
}
@@ -218,6 +220,7 @@ func (s *infoSuite) TestReadInfoWithInstance(c *C) {
c.Check(snapInfo2.Summary(), Equals, "instance summary")
c.Check(snapInfo2.Apps["app"].Command, Equals, "foo")
+ c.Check(snapInfo2.Apps["sample"].CommandChain, DeepEquals, []string{"chain"})
c.Check(snapInfo2, DeepEquals, snapInfo1)
}
diff --git a/snap/validate.go b/snap/validate.go
index 90544dc62b..06eddd29de 100644
--- a/snap/validate.go
+++ b/snap/validate.go
@@ -584,8 +584,10 @@ func validateAppTimer(app *AppInfo) error {
// appContentWhitelist is the whitelist of legal chars in the "apps"
// section of snap.yaml. Do not allow any of [',",`] here or snap-exec
-// will get confused.
+// will get confused. chainContentWhitelist is the same, but for the
+// command-chain, which also doesn't allow whitespace.
var appContentWhitelist = regexp.MustCompile(`^[A-Za-z0-9/. _#:$-]*$`)
+var commandChainContentWhitelist = regexp.MustCompile(`^[A-Za-z0-9/._#:$-]*$`)
// ValidAppName tells whether a string is a valid application name.
func ValidAppName(n string) bool {
@@ -623,6 +625,13 @@ func ValidateApp(app *AppInfo) error {
}
}
+ // Also validate the command chain
+ for _, value := range app.CommandChain {
+ if err := validateField("command-chain", value, commandChainContentWhitelist); err != nil {
+ return err
+ }
+ }
+
// Socket activation requires the "network-bind" plug
if len(app.Sockets) > 0 {
if _, ok := app.Plugs["network-bind"]; !ok {
diff --git a/snap/validate_test.go b/snap/validate_test.go
index 9c1e7daf98..944887b38a 100644
--- a/snap/validate_test.go
+++ b/snap/validate_test.go
@@ -439,10 +439,13 @@ func (s *ValidateSuite) TestAppWhitelistWithVars(c *C) {
func (s *ValidateSuite) TestAppWhitelistIllegal(c *C) {
c.Check(ValidateApp(&AppInfo{Name: "x\n"}), NotNil)
c.Check(ValidateApp(&AppInfo{Name: "test!me"}), NotNil)
+ c.Check(ValidateApp(&AppInfo{Name: "test'me"}), NotNil)
c.Check(ValidateApp(&AppInfo{Name: "foo", Command: "foo\n"}), NotNil)
c.Check(ValidateApp(&AppInfo{Name: "foo", StopCommand: "foo\n"}), NotNil)
c.Check(ValidateApp(&AppInfo{Name: "foo", PostStopCommand: "foo\n"}), NotNil)
c.Check(ValidateApp(&AppInfo{Name: "foo", BusName: "foo\n"}), NotNil)
+ c.Check(ValidateApp(&AppInfo{Name: "foo", CommandChain: []string{"bar'baz"}}), NotNil)
+ c.Check(ValidateApp(&AppInfo{Name: "foo", CommandChain: []string{"bar baz"}}), NotNil)
}
func (s *ValidateSuite) TestAppDaemonValue(c *C) {
diff --git a/snapcraft.yaml b/snapcraft.yaml
index 0d76744c17..9de7386a87 100644
--- a/snapcraft.yaml
+++ b/snapcraft.yaml
@@ -9,8 +9,7 @@ description: |
Start with 'snap list' to see installed snaps.
version: set-by-version-script-thxbye
version-script: |
- # extract the version from the debian/changelog
- dpkg-parsechangelog -Sversion
+ ./mkversion.sh --output-only
grade: devel
parts:
diff --git a/tests/lib/journalctl.sh b/tests/lib/journalctl.sh
index 4b21370528..9f74e93722 100644
--- a/tests/lib/journalctl.sh
+++ b/tests/lib/journalctl.sh
@@ -47,12 +47,19 @@ check_journalctl_log(){
}
get_journalctl_log(){
- cursor=$(tail -n1 "$JOURNALCTL_CURSOR_FILE")
+ cursor=""
+ if [ -f "$JOURNALCTL_CURSOR_FILE" ]; then
+ cursor=$(tail -n1 "$JOURNALCTL_CURSOR_FILE")
+ fi
get_journalctl_log_from_cursor "$cursor" "$@"
}
get_journalctl_log_from_cursor(){
cursor=$1
shift
- journalctl "$@" --cursor "$cursor"
+ if [ -z "$cursor" ]; then
+ journalctl "$@"
+ else
+ journalctl "$@" --cursor "$cursor"
+ fi
}
diff --git a/tests/lib/pkgdb.sh b/tests/lib/pkgdb.sh
index 9fe12607b4..84cfd96d93 100755
--- a/tests/lib/pkgdb.sh
+++ b/tests/lib/pkgdb.sh
@@ -490,6 +490,7 @@ pkg_dependencies_ubuntu_generic(){
pkg-config
python3-docutils
udev
+ udisks2
upower
uuid-runtime
"
@@ -513,16 +514,14 @@ pkg_dependencies_ubuntu_classic(){
case "$SPREAD_SYSTEM" in
ubuntu-14.04-*)
- echo "
- linux-image-extra-$(uname -r)
- "
+ pkg_linux_image_extra
;;
ubuntu-16.04-32)
echo "
evolution-data-server
gnome-online-accounts
- linux-image-extra-$(uname -r)
"
+ pkg_linux_image_extra
;;
ubuntu-16.04-64)
echo "
@@ -531,17 +530,15 @@ pkg_dependencies_ubuntu_classic(){
gnome-online-accounts
kpartx
libvirt-bin
- linux-image-extra-$(uname -r)
nfs-kernel-server
qemu
x11-utils
xvfb
"
+ pkg_linux_image_extra
;;
ubuntu-17.10-64)
- echo "
- linux-image-extra-4.13.0-16-generic
- "
+ pkg_linux_image_extra
;;
ubuntu-18.04-64)
echo "
@@ -562,11 +559,24 @@ pkg_dependencies_ubuntu_classic(){
esac
}
+pkg_linux_image_extra (){
+ if apt-cache show "linux-image-extra-$(uname -r)" > /dev/null 2>&1; then
+ echo "linux-image-extra-$(uname -r)";
+ else
+ if apt-cache show "linux-modules-extra-$(uname -r)" > /dev/null 2>&1; then
+ echo "linux-modules-extra-$(uname -r)";
+ else
+ echo "cannot find a matching kernel modules package";
+ exit 1;
+ fi;
+ fi
+}
+
pkg_dependencies_ubuntu_core(){
echo "
- linux-image-extra-$(uname -r)
pollinate
"
+ pkg_linux_image_extra
}
pkg_dependencies_fedora(){
@@ -586,6 +596,7 @@ pkg_dependencies_fedora(){
python3-yaml
redhat-lsb-core
rpm-build
+ udisks2
xdg-user-dirs
"
}
@@ -607,6 +618,7 @@ pkg_dependencies_amazon(){
xdg-user-dirs
grub2-tools
nc
+ udisks2
"
}
@@ -625,6 +637,7 @@ pkg_dependencies_opensuse(){
python3-yaml
netcat-openbsd
osc
+ udisks2
uuidd
xdg-utils
xdg-user-dirs
@@ -654,6 +667,7 @@ pkg_dependencies_arch(){
squashfs-tools
shellcheck
strace
+ udisks2
xdg-user-dirs
xfsprogs
"
diff --git a/tests/lib/prepare.sh b/tests/lib/prepare.sh
index fa5f019d16..3d5681e940 100755
--- a/tests/lib/prepare.sh
+++ b/tests/lib/prepare.sh
@@ -410,7 +410,7 @@ EOF
fi
# extra_snap should contain only ONE snap
- if "${#extra_snap[@]}" -ne 1; then
+ if [ "${#extra_snap[@]}" -ne 1 ]; then
echo "unexpected number of globbed snaps: ${extra_snap[*]}"
exit 1
fi
diff --git a/tests/lib/snaps/basic-iface-hooks-consumer/meta/hooks/configure b/tests/lib/snaps/basic-iface-hooks-consumer/meta/hooks/configure
new file mode 100755
index 0000000000..163dbaccca
--- /dev/null
+++ b/tests/lib/snaps/basic-iface-hooks-consumer/meta/hooks/configure
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+# intentionally empty, needed to drive the tests via snapctl.
diff --git a/tests/lib/snaps/basic-iface-hooks-consumer/meta/hooks/connect-plug-consumer b/tests/lib/snaps/basic-iface-hooks-consumer/meta/hooks/connect-plug-consumer
index d759a888a8..4a2f63aae9 100755
--- a/tests/lib/snaps/basic-iface-hooks-consumer/meta/hooks/connect-plug-consumer
+++ b/tests/lib/snaps/basic-iface-hooks-consumer/meta/hooks/connect-plug-consumer
@@ -59,4 +59,10 @@ if snapctl set :consumer consumer-attr-4=foo; then
exit 1
fi
+output=$(snapctl get fail)
+if [ "$output" = "connect" ]; then
+ echo "Failing connect hook as requested"
+ exit 1
+fi
+
touch "$SNAP_COMMON/connect-plug-consumer-done"
diff --git a/tests/lib/snaps/basic-iface-hooks-consumer/meta/hooks/disconnect-plug-consumer b/tests/lib/snaps/basic-iface-hooks-consumer/meta/hooks/disconnect-plug-consumer
new file mode 100755
index 0000000000..f9e278ad17
--- /dev/null
+++ b/tests/lib/snaps/basic-iface-hooks-consumer/meta/hooks/disconnect-plug-consumer
@@ -0,0 +1,61 @@
+#!/bin/sh
+set -x
+
+echo "Getting attributes from disconnect-plug-consumer hook"
+
+if ! output=$(snapctl get --slot :consumer producer-attr-1); then
+ echo "Expected disconnect-plug-consumer to be able to read the value of 'producer-attr-1' attribute of the slot"
+ exit 1
+fi
+expected_output="producer-value-1"
+if [ "$output" != "$expected_output" ]; then
+ echo "Expected output to be '$expected_output', but it was '$output'"
+ exit 1
+fi
+
+# Read 'before-connect' attribute of the slot
+if ! output=$(snapctl get --slot :consumer before-connect); then
+ echo "Expected disconnect-plug-consumer be able to read the value of the 'before-connect' attribute of the slot"
+ exit 1
+fi
+expected_output="slot-changed(producer-value)"
+if [ "$output" != "$expected_output" ]; then
+ echo "Expected output to be '$expected_output', but it was '$output'"
+ exit 1
+fi
+
+# Read own 'consumer-attr-1' attribute
+if ! output=$(snapctl get :consumer consumer-attr-1); then
+ echo "Expected connect-plug-foo be able to read the value of own 'consumer-attr-1' attribute"
+ exit 1
+fi
+expected_output="consumer-value-1"
+if [ "$output" != "$expected_output" ]; then
+ echo "Expected output to be '$expected_output', but it was '$output'"
+ exit 1
+fi
+
+# Read own 'before-connect' attribute
+if ! output=$(snapctl get :consumer before-connect); then
+ echo "Expected disconnect-plug-consumer be able to read the value of own 'before-connect' attribute"
+ exit 1
+fi
+expected_output="plug-changed(consumer-value)"
+if [ "$output" != "$expected_output" ]; then
+ echo "Expected output to be '$expected_output', but it was '$output'"
+ exit 1
+fi
+
+# Failure on unknown plug
+if snapctl get :unknown target; then
+ echo "Expected snapctl get to fail on unknown plug"
+ exit 1
+fi
+
+# Attributes cannot be set in connect- or disconnect- hooks
+if snapctl set :consumer consumer-attr-4=foo; then
+ echo "Expected snapctl set to fail when run from connect-plug or disconnect-plug hook"
+ exit 1
+fi
+
+touch "$SNAP_COMMON/disconnect-plug-consumer-done"
diff --git a/tests/lib/snaps/basic-iface-hooks-consumer/meta/hooks/unprepare-plug-consumer b/tests/lib/snaps/basic-iface-hooks-consumer/meta/hooks/unprepare-plug-consumer
new file mode 100755
index 0000000000..fea3578ff2
--- /dev/null
+++ b/tests/lib/snaps/basic-iface-hooks-consumer/meta/hooks/unprepare-plug-consumer
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+# Read own 'before-connect' attribute
+output=$(snapctl get :consumer before-connect)
+echo "$output" > $SNAP_COMMON/unprepare-plug-consumer-done
diff --git a/tests/lib/snaps/basic-iface-hooks-producer/meta/hooks/configure b/tests/lib/snaps/basic-iface-hooks-producer/meta/hooks/configure
new file mode 100755
index 0000000000..96b4b06ad4
--- /dev/null
+++ b/tests/lib/snaps/basic-iface-hooks-producer/meta/hooks/configure
@@ -0,0 +1 @@
+#!/bin/sh \ No newline at end of file
diff --git a/tests/lib/snaps/basic-iface-hooks-producer/meta/hooks/disconnect-slot-producer b/tests/lib/snaps/basic-iface-hooks-producer/meta/hooks/disconnect-slot-producer
new file mode 100755
index 0000000000..d3ebe5462c
--- /dev/null
+++ b/tests/lib/snaps/basic-iface-hooks-producer/meta/hooks/disconnect-slot-producer
@@ -0,0 +1,52 @@
+#!/bin/sh
+set -x
+
+echo "Getting attributes from disconnect-slot-producer hook"
+
+# Read 'consumer-attr-1' attribute of the plug
+if ! output=$(snapctl get --plug :producer consumer-attr-1); then
+ echo "Expected disconnect-slot-producer be able to read the value of the 'consumer-attr-1' attribute of the plug"
+ exit 1
+fi
+expected_output="consumer-value-1"
+if [ "$output" != "$expected_output" ]; then
+ echo "Expected output to be '$expected_output', but it was '$output'"
+ exit 1
+fi
+
+# Read own 'before-connect' attribute
+if ! output=$(snapctl get :producer before-connect); then
+ echo "Expected disconnect-slot-producer to be able to read the value of own 'before-connect' attribute"
+ exit 1
+fi
+expected_output="slot-changed(producer-value)"
+if [ "$output" != "$expected_output" ]; then
+ echo "Expected output to be '$expected_output', but it was '$output'"
+ exit 1
+fi
+
+# Read 'before-connect' attribute of the plug
+if ! output=$(snapctl get --plug :producer before-connect); then
+ echo "Expected disconnect-slot-producer to be able to read the value of 'before-connect' attribute of the plug"
+ exit 1
+fi
+expected_output="plug-changed(consumer-value)"
+if [ "$output" != "$expected_output" ]; then
+ echo "Expected output to be '$expected_output', but it was '$output'"
+ exit 1
+fi
+
+# Failure on unknown slot
+if snapctl get :unknown consumer-attr-1; then
+ echo "Expected snapctl get to fail on unknown slot"
+ exit 1
+fi
+
+# Attributes cannot be set in connect- or disconnect- hooks
+if snapctl set :producer consumer-attr-4=foo; then
+ echo "Expected snapctl set to fail when run from connect-slot or disconnect-slot hook"
+ exit 1
+fi
+
+touch "$SNAP_COMMON/disconnect-slot-producer-done"
+
diff --git a/tests/lib/snaps/basic-iface-hooks-producer/meta/hooks/unprepare-slot-producer b/tests/lib/snaps/basic-iface-hooks-producer/meta/hooks/unprepare-slot-producer
new file mode 100755
index 0000000000..4c9996138e
--- /dev/null
+++ b/tests/lib/snaps/basic-iface-hooks-producer/meta/hooks/unprepare-slot-producer
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+touch "$SNAP_COMMON/unprepare-slot-producer-done" \ No newline at end of file
diff --git a/tests/lib/snaps/command-chain/chain1 b/tests/lib/snaps/command-chain/chain1
new file mode 100755
index 0000000000..6bcf996a26
--- /dev/null
+++ b/tests/lib/snaps/command-chain/chain1
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+export CHAIN_1_RAN=1
+printf "chain1 "
+exec "$@"
diff --git a/tests/lib/snaps/command-chain/chain2 b/tests/lib/snaps/command-chain/chain2
new file mode 100755
index 0000000000..f75047f179
--- /dev/null
+++ b/tests/lib/snaps/command-chain/chain2
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+export CHAIN_2_RAN=1
+printf "chain2 "
+exec "$@"
diff --git a/tests/lib/snaps/command-chain/hello b/tests/lib/snaps/command-chain/hello
new file mode 100755
index 0000000000..dc58c79ddd
--- /dev/null
+++ b/tests/lib/snaps/command-chain/hello
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo "hello"
diff --git a/tests/lib/snaps/command-chain/meta/icon.png b/tests/lib/snaps/command-chain/meta/icon.png
new file mode 100644
index 0000000000..1ec92f1241
--- /dev/null
+++ b/tests/lib/snaps/command-chain/meta/icon.png
Binary files differ
diff --git a/tests/lib/snaps/command-chain/meta/snap.yaml b/tests/lib/snaps/command-chain/meta/snap.yaml
new file mode 100644
index 0000000000..ad5e22ebef
--- /dev/null
+++ b/tests/lib/snaps/command-chain/meta/snap.yaml
@@ -0,0 +1,9 @@
+name: command-chain
+version: 1.0
+summary: Command chain snap
+description: A buildable snap that uses command chain
+
+apps:
+ hello:
+ command: hello
+ command-chain: [chain1, chain2]
diff --git a/tests/lib/snaps/snap-hooks/meta/hooks/remove b/tests/lib/snaps/snap-hooks/meta/hooks/remove
index 493f092ca9..2b3af25886 100755
--- a/tests/lib/snaps/snap-hooks/meta/hooks/remove
+++ b/tests/lib/snaps/snap-hooks/meta/hooks/remove
@@ -1,5 +1,5 @@
#!/bin/sh
RETVAL=$(snapctl get exitcode)
-echo "$RETVAL" > /root/remove-hook-executed
+echo "$RETVAL" > $SNAP_USER_COMMON/remove-hook-executed
exit "$RETVAL"
diff --git a/tests/lib/snaps/test-snapd-juju-client-observe/bin/sh b/tests/lib/snaps/test-snapd-juju-client-observe/bin/sh
new file mode 100755
index 0000000000..73837f7d62
--- /dev/null
+++ b/tests/lib/snaps/test-snapd-juju-client-observe/bin/sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+exec /bin/sh "$@"
diff --git a/tests/lib/snaps/test-snapd-juju-client-observe/meta/snap.yaml b/tests/lib/snaps/test-snapd-juju-client-observe/meta/snap.yaml
new file mode 100644
index 0000000000..78e62db702
--- /dev/null
+++ b/tests/lib/snaps/test-snapd-juju-client-observe/meta/snap.yaml
@@ -0,0 +1,9 @@
+name: test-snapd-juju-client-observe
+version: 1.0
+summary: Basic juju-client-observe snap
+description: A basic snap which allows reading juju client configuration
+
+apps:
+ sh:
+ command: bin/sh
+ plugs: [juju-client-observe]
diff --git a/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml b/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml
index a7d20e2517..0027761d20 100644
--- a/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml
+++ b/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml
@@ -260,6 +260,9 @@ apps:
screen-inhibit-control:
command: bin/run
plugs: [ screen-inhibit-control ]
+ screencast-legacy:
+ command: bin/run
+ plugs: [ screencast-legacy ]
shutdown:
command: bin/run
plugs: [ shutdown ]
diff --git a/tests/lib/snaps/test-snapd-service/bin/stop-stop-mode b/tests/lib/snaps/test-snapd-service/bin/stop-stop-mode
index 43d21e4389..2dd0cd7aa9 100755
--- a/tests/lib/snaps/test-snapd-service/bin/stop-stop-mode
+++ b/tests/lib/snaps/test-snapd-service/bin/stop-stop-mode
@@ -1,4 +1,4 @@
#!/bin/sh
echo "stop $1 process"
-rm -f $SNAP_COMMON/ready
+rm -f "$SNAP_COMMON/ready"
diff --git a/tests/lib/snaps/test-snapd-udisks2/snapcraft.yaml b/tests/lib/snaps/test-snapd-udisks2/snapcraft.yaml
new file mode 100644
index 0000000000..2f5cb5ed5e
--- /dev/null
+++ b/tests/lib/snaps/test-snapd-udisks2/snapcraft.yaml
@@ -0,0 +1,17 @@
+name: test-snapd-udisks2
+version: 1.0
+summary: Basic udisks2 snap
+description: A basic snap which allow operating as or interacting with the UDisks2 service
+grade: stable
+confinement: strict
+
+apps:
+ udisksctl:
+ command: udisksctl
+ plugs: [udisks2]
+
+parts:
+ copy:
+ plugin: dump
+ source: .
+ stage-packages: [udisks2]
diff --git a/tests/lib/snaps/test-snapd-udisks2/udisksctl b/tests/lib/snaps/test-snapd-udisks2/udisksctl
new file mode 100755
index 0000000000..cca2442724
--- /dev/null
+++ b/tests/lib/snaps/test-snapd-udisks2/udisksctl
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+udisksctl "$@"
diff --git a/tests/main/command-chain/task.yaml b/tests/main/command-chain/task.yaml
new file mode 100644
index 0000000000..2fb5572b47
--- /dev/null
+++ b/tests/main/command-chain/task.yaml
@@ -0,0 +1,26 @@
+summary: Check that command-chain is properly supported
+
+prepare: |
+ echo "Build command chain snap"
+ snap pack "$TESTSLIB/snaps/command-chain"
+ snap install --dangerous command-chain_1.0_all.snap
+
+restore: |
+ rm -f command-chain_1.0_all.snap
+
+execute: |
+ echo "Test that command-chain actually runs as expected"
+ [ "$(command-chain.hello)" = "chain1 chain2 hello" ]
+
+ echo "Ensure that the command-chain is run with 'snap run --shell' as well"
+ [ "$(snap run --shell command-chain.hello -c 'echo "shell"')" = "chain1 chain2 shell" ]
+ env="$(snap run --shell command-chain.hello -c 'env')"
+ echo "$env" | MATCH '^CHAIN_1_RAN=1$'
+ echo "$env" | MATCH '^CHAIN_2_RAN=1$'
+
+ echo "Also ensure that 'snap run' supports skipping the command chain"
+ [ "$(snap run --skip-command-chain command-chain.hello)" = "hello" ]
+ [ "$(snap run --shell --skip-command-chain command-chain.hello -c 'echo "shell"')" = "shell" ]
+ env="$(snap run --shell --skip-command-chain command-chain.hello -c 'env')"
+ echo "$env" | MATCH -v '^CHAIN_1_RAN=1$'
+ echo "$env" | MATCH -v '^CHAIN_2_RAN=1$'
diff --git a/tests/main/fedora-base-smoke/task.yaml b/tests/main/fedora-base-smoke/task.yaml
index d19e1695e1..29f2bcf1bc 100644
--- a/tests/main/fedora-base-smoke/task.yaml
+++ b/tests/main/fedora-base-smoke/task.yaml
@@ -1,11 +1,15 @@
summary: smoke test for Fedora 29 base snap
-# The hello-fedora snap is not yet available for i386
-systems: [-*-32]
+
+# The hello-fedora snap is just available for amd64 architecture
+systems: [-*-32, -*-arm*, -*-ppc64el, -*-s390x]
+
# not available on most arches in autopkgtest
backends: [-autopkgtest]
+
details: |
Smoke test for checking if we can run hello-world like application against a
Fedora 29 base snap correctly.
+
execute: |
# This is explicit because fedora29 snap is still in edge.
snap install --edge fedora29
diff --git a/tests/main/install-refresh-remove-hooks/task.yaml b/tests/main/install-refresh-remove-hooks/task.yaml
index 34b04b0447..f24aaab6d9 100644
--- a/tests/main/install-refresh-remove-hooks/task.yaml
+++ b/tests/main/install-refresh-remove-hooks/task.yaml
@@ -1,7 +1,7 @@
summary: Check install, remove and pre-refresh/post-refresh hooks.
environment:
- REMOVE_HOOK_FILE: "$HOME/remove-hook-executed"
+ REMOVE_HOOK_FILE: "$HOME/snap/snap-hooks/common/remove-hook-executed"
restore: |
rm -f "$REMOVE_HOOK_FILE"
diff --git a/tests/main/interfaces-hooks/task.yaml b/tests/main/interfaces-hooks/task.yaml
index 282c44cab6..cf1ae0671e 100644
--- a/tests/main/interfaces-hooks/task.yaml
+++ b/tests/main/interfaces-hooks/task.yaml
@@ -18,12 +18,20 @@ restore: |
rm -f "$PRODUCER_DATA/prepare-slot-producer-done"
rm -f "$CONSUMER_DATA/connect-plug-consumer-done"
rm -f "$PRODUCER_DATA/connect-slot-producer-done"
+ rm -f "$CONSUMER_DATA/disconnect-plug-consumer-done"
+ rm -f "$PRODUCER_DATA/disconnect-slot-producer-done"
+ rm -f "$CONSUMER_DATA/unprepare-plug-consumer-done"
+ rm -f "$PRODUCER_DATA/unprepare-slot-producer-done"
snap remove basic-iface-hooks-consumer
snap remove basic-iface-hooks-producer
execute: |
#shellcheck source=tests/lib/snaps.sh
. "$TESTSLIB/snaps.sh"
+ remove_markers() {
+ rm -f "$CONSUMER_DATA"/*-plug-*done
+ rm -f "$PRODUCER_DATA"/*-slot-*done
+ }
check_attributes(){
# static values should have the values defined in snap's yaml
jq -r '.data["conns"]["basic-iface-hooks-consumer:consumer basic-iface-hooks-producer:producer"]["plug-static"]["consumer-attr-1"]' /var/lib/snapd/state.json | MATCH "consumer-value-1"
@@ -56,3 +64,39 @@ execute: |
echo "Verify static and dynamic attributes have expected values"
check_attributes
systemctl start snapd.service snapd.socket
+
+ remove_markers
+
+ # make sure disconnect hooks are executed
+ snap disconnect basic-iface-hooks-consumer:consumer basic-iface-hooks-producer:producer
+ snap change --last=disconnect | MATCH "Run hook disconnect-slot-producer of snap \"basic-iface-hooks-producer"
+ snap change --last=disconnect | MATCH "Run hook disconnect-plug-consumer of snap \"basic-iface-hooks-consumer"
+
+ [ -f "$CONSUMER_DATA/disconnect-plug-consumer-done" ]
+ [ -f "$PRODUCER_DATA/disconnect-slot-producer-done" ]
+
+ remove_markers
+
+ # make connect hooks fail, check that undo hooks were executed
+ snap set basic-iface-hooks-consumer fail=connect
+ if snap connect basic-iface-hooks-consumer:consumer basic-iface-hooks-producer:producer ; then
+ echo "Expected snap connect to fail"
+ exit 1
+ fi
+
+ [ -f "$CONSUMER_DATA/unprepare-plug-consumer-done" ]
+ [ -f "$PRODUCER_DATA/unprepare-slot-producer-done" ]
+ [ -f "$PRODUCER_DATA/connect-slot-producer-done" ]
+ [ -f "$PRODUCER_DATA/disconnect-slot-producer-done" ]
+
+ MATCH "plug-changed.consumer-value" < "$CONSUMER_DATA/unprepare-plug-consumer-done"
+
+ remove_markers
+
+ # disconnect hooks should be executed on snap removal
+ snap set basic-iface-hooks-consumer fail=none
+ snap connect basic-iface-hooks-consumer:consumer basic-iface-hooks-producer:producer
+ snap remove basic-iface-hooks-consumer
+ [ -f "$PRODUCER_DATA/disconnect-slot-producer-done" ]
+ snap change --last=remove | MATCH "Run hook disconnect-slot-producer of snap \"basic-iface-hooks-producer"
+ snap change --last=remove | MATCH "Run hook disconnect-plug-consumer of snap \"basic-iface-hooks-consumer"
diff --git a/tests/main/interfaces-juju-client-observe/task.yaml b/tests/main/interfaces-juju-client-observe/task.yaml
new file mode 100644
index 0000000000..b517b87583
--- /dev/null
+++ b/tests/main/interfaces-juju-client-observe/task.yaml
@@ -0,0 +1,52 @@
+summary: Ensure that the juju client observe interface works.
+
+details: |
+ The juju-client-observe interface allows access to the juju client configuration
+
+# The interface is not defined for ubuntu core systems
+systems: [-ubuntu-core-*]
+
+prepare: |
+ # shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB/snaps.sh"
+ install_local test-snapd-juju-client-observe
+
+ # shellcheck source=tests/lib/files.sh
+ . "$TESTSLIB/files.sh"
+
+ ensure_dir_exists_backup_real "$HOME"/.local/share/juju
+ ensure_file_exists_backup_real "$HOME"/.local/share/juju/juju.conf
+
+restore: |
+ rm -f call.error
+
+ # shellcheck source=tests/lib/files.sh
+ . "$TESTSLIB/files.sh"
+
+ # Delete the created juju dir and configuration files
+ clean_file "$HOME"/.local/share/juju/juju.conf
+ clean_dir "$HOME"/.local/share/juju
+
+execute: |
+ echo "The interface is not connected by default"
+ snap interfaces -i juju-client-observe | MATCH -- '- +test-snapd-juju-client-observe:juju-client-observe'
+
+ echo "When the interface is connected"
+ snap connect test-snapd-juju-client-observe:juju-client-observe
+
+ echo "Then the snap is able to access the juju client configuration"
+ test-snapd-juju-client-observe.sh -c "cat $HOME/.local/share/juju/juju.conf"
+
+ echo "When the plug is disconnected"
+ snap disconnect test-snapd-juju-client-observe:juju-client-observe
+
+ if [ "$(snap debug confinement)" = partial ]; then
+ exit 0
+ fi
+
+ echo "Then the snap is not able to read the juju client configuration"
+ if test-snapd-juju-client-observe.sh -c "cat $HOME/.local/share/juju/juju.conf" 2>call.error; then
+ echo "Expected permission error accessing to input device"
+ exit 1
+ fi
+ MATCH "Permission denied" < call.error
diff --git a/tests/main/interfaces-snapd-control-with-manage/task.yaml b/tests/main/interfaces-snapd-control-with-manage/task.yaml
index 3511ec1514..c5b29d02e4 100644
--- a/tests/main/interfaces-snapd-control-with-manage/task.yaml
+++ b/tests/main/interfaces-snapd-control-with-manage/task.yaml
@@ -17,7 +17,7 @@ prepare: |
fi
echo "Ensure jq is installed"
- if ! which jq; then
+ if ! command -v jq; then
snap install --devmode jq
fi
@@ -25,12 +25,14 @@ prepare: |
snap ack "$TESTSLIB/assertions/testrootorg-store.account-key"
- . $TESTSLIB/store.sh
- setup_fake_store $BLOB_DIR
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ setup_fake_store "$BLOB_DIR"
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
snap_path=$(make_snap test-snapd-control-consumer)
- make_snap_installable $BLOB_DIR ${snap_path}
+ make_snap_installable "$BLOB_DIR" "${snap_path}"
cat > snap-decl.json <<'EOF'
{
"format": "1",
@@ -47,15 +49,16 @@ prepare: |
}
EOF
fakestore new-snap-declaration --dir "${BLOB_DIR}" --snap-decl-json snap-decl.json
- snap ack ${BLOB_DIR}/asserts/*.snap-declaration
+ snap ack "${BLOB_DIR}"/asserts/*.snap-declaration
restore: |
if [ "$TRUST_TEST_KEYS" = "false" ]; then
echo "This test needs test keys to be trusted"
exit
fi
- . $TESTSLIB/store.sh
- teardown_fake_store $BLOB_DIR
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ teardown_fake_store "$BLOB_DIR"
debug: |
jq .data.auth.device /var/lib/snapd/state.json
@@ -105,13 +108,13 @@ execute: |
systemctl start snapd.socket snapd.service
echo "Ensure that last-refresh-hit happens"
- for i in $(seq 120); do
- if jq '.data["last-refresh-hints"]' /var/lib/snapd/state.json | grep $(date +%Y); then
+ for _ in $(seq 120); do
+ if jq '.data["last-refresh-hints"]' /var/lib/snapd/state.json | grep "$(date +%Y)"; then
break
fi
sleep 1
done
- jq '.data["last-refresh-hints"]' /var/lib/snapd/state.json | grep $(date +%Y)
+ jq '.data["last-refresh-hints"]' /var/lib/snapd/state.json | grep "$(date +%Y)"
# prevent refreshes again
systemctl stop snapd.socket snapd.service
diff --git a/tests/main/interfaces-ssh-keys/task.yaml b/tests/main/interfaces-ssh-keys/task.yaml
index 48e6afbd7d..fafc1427e5 100644
--- a/tests/main/interfaces-ssh-keys/task.yaml
+++ b/tests/main/interfaces-ssh-keys/task.yaml
@@ -9,7 +9,8 @@ environment:
TESTKEY: "$HOME/.ssh/testkey"
prepare: |
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
install_local test-snapd-ssh-keys
if [ -d "$KEYSDIR" ]; then
@@ -29,7 +30,7 @@ restore: |
execute: |
echo "The interface is not connected by default"
- snap interfaces -i ssh-keys | MATCH "\- +test-snapd-ssh-keys:ssh-keys"
+ snap interfaces -i ssh-keys | MATCH -- '^- +test-snapd-ssh-keys:ssh-keys'
echo "When the interface is connected"
snap connect test-snapd-ssh-keys:ssh-keys
@@ -50,7 +51,7 @@ execute: |
snap disconnect test-snapd-ssh-keys:ssh-keys
echo "Then the snap is not able to read a ssh private key"
- if test-snapd-ssh-keys.sh -c "cat $TESTKEY" 2>${PWD}/call.error; then
+ if test-snapd-ssh-keys.sh -c "cat $TESTKEY" 2>"${PWD}"/call.error; then
echo "Expected permission error accessing to ssh"
exit 1
fi
diff --git a/tests/main/interfaces-ssh-public-keys/task.yaml b/tests/main/interfaces-ssh-public-keys/task.yaml
index 73e9818dfd..c80c0de404 100644
--- a/tests/main/interfaces-ssh-public-keys/task.yaml
+++ b/tests/main/interfaces-ssh-public-keys/task.yaml
@@ -9,6 +9,7 @@ environment:
TESTKEY: "/$HOME/.ssh/testkey"
prepare: |
+ #shellcheck source=tests/lib/snaps.sh
. "$TESTSLIB/snaps.sh"
install_local test-snapd-ssh-public-keys
@@ -30,8 +31,8 @@ restore: |
execute: |
echo "The interface is not connected by default"
- snap interfaces -i ssh-public-keys | MATCH "\- +test-snapd-ssh-public-keys:ssh-public-keys"
-
+ snap interfaces -i ssh-public-keys | MATCH -- '^- +test-snapd-ssh-public-keys:ssh-public-keys'
+
echo "When the interface is connected"
snap connect test-snapd-ssh-public-keys:ssh-public-keys
@@ -46,7 +47,7 @@ execute: |
fi
echo "And then the snap is not able to access to private keys"
- if test-snapd-ssh-public-keys.sh -c "cat $TESTKEY" 2>${PWD}/call.error; then
+ if test-snapd-ssh-public-keys.sh -c "cat $TESTKEY" 2>"${PWD}"/call.error; then
echo "Expected permission error accessing to ssh"
exit 1
fi
@@ -56,7 +57,7 @@ execute: |
snap disconnect test-snapd-ssh-public-keys:ssh-public-keys
echo "Then the snap is not able to access the ssh public keys"
- if test-snapd-ssh-public-keys.sh -c "cat $TESTKEY.pub" 2>${PWD}/call.error; then
+ if test-snapd-ssh-public-keys.sh -c "cat $TESTKEY.pub" 2>"${PWD}"/call.error; then
echo "Expected permission error accessing to ssh"
exit 1
fi
diff --git a/tests/main/interfaces-system-observe/task.yaml b/tests/main/interfaces-system-observe/task.yaml
index 2cfd4fea25..1d37ae3080 100644
--- a/tests/main/interfaces-system-observe/task.yaml
+++ b/tests/main/interfaces-system-observe/task.yaml
@@ -21,14 +21,14 @@ prepare: |
fi
restore: |
- rm -f *.error
+ rm -f ./*.error
if [[ "$SPREAD_SYSTEM" != ubuntu-14.04-* ]]; then
systemctl stop systemd-hostnamed
fi
execute: |
echo "The interface is disconnected by default"
- snap interfaces -i system-observe | MATCH "^\- +test-snapd-system-observe-consumer:system-observe"
+ snap interfaces -i system-observe | MATCH -- '^- +test-snapd-system-observe-consumer:system-observe'
echo "When the interface is connected"
snap connect test-snapd-system-observe-consumer:system-observe
@@ -52,7 +52,7 @@ execute: |
echo "Expected error with plug disconnected"
exit 1
fi
- cat consumer.error | MATCH "Permission denied"
+ MATCH "Permission denied" < consumer.error
if [[ "$SPREAD_SYSTEM" != ubuntu-14.04-* ]]; then
echo "And the snap is not able to introspect hostname1"
@@ -60,6 +60,6 @@ execute: |
echo "Expected error with plug disconnected"
exit 1
fi
- cat introspect.error | MATCH "Permission denied"
+ MATCH "Permission denied" < introspect.error
fi
fi
diff --git a/tests/main/interfaces-time-control/task.yaml b/tests/main/interfaces-time-control/task.yaml
index 31105f5b53..40908e0bf9 100644
--- a/tests/main/interfaces-time-control/task.yaml
+++ b/tests/main/interfaces-time-control/task.yaml
@@ -9,7 +9,8 @@ details: |
systems: [-opensuse-*,-fedora-*,-ubuntu-core-*,-ubuntu-14.04-*,-*-s390x,-arch-*]
prepare: |
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
# Install a snap declaring a plug on time-control
install_local test-snapd-timedate-control-consumer
@@ -29,7 +30,7 @@ execute: |
snap connect test-snapd-timedate-control-consumer:netlink-audit
echo "The interface is disconnected by default"
- snap interfaces -i time-control | MATCH "\- +test-snapd-timedate-control-consumer:time-control"
+ snap interfaces -i time-control | MATCH -- '^- +test-snapd-timedate-control-consumer:time-control'
# When the interface is connected
snap connect test-snapd-timedate-control-consumer:time-control
@@ -58,7 +59,7 @@ execute: |
# Disconnect the interface and check access to timedatectl status
snap disconnect test-snapd-timedate-control-consumer:time-control
- if test-snapd-timedate-control-consumer.timedatectl-time status 2>${PWD}/call.error; then
+ if test-snapd-timedate-control-consumer.timedatectl-time status 2>"${PWD}"/call.error; then
echo "Expected permission error calling timedatectl status with disconnected plug"
exit 1
fi
diff --git a/tests/main/interfaces-timezone-control/task.yaml b/tests/main/interfaces-timezone-control/task.yaml
index 69988df1dd..1f1a6138c5 100644
--- a/tests/main/interfaces-timezone-control/task.yaml
+++ b/tests/main/interfaces-timezone-control/task.yaml
@@ -11,7 +11,8 @@ details: |
systems: [-ubuntu-core-18-*]
prepare: |
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
# Install a snap declaring a plug on timezone-control
install_local test-snapd-timedate-control-consumer
@@ -26,7 +27,7 @@ restore: |
execute: |
echo "The interface is disconnected by default"
- snap interfaces -i timezone-control | MATCH "\- +test-snapd-timedate-control-consumer:timezone-control"
+ snap interfaces -i timezone-control | MATCH -- '^- +test-snapd-timedate-control-consumer:timezone-control'
echo "When the interface is connected"
snap connect test-snapd-timedate-control-consumer:timezone-control
@@ -44,11 +45,11 @@ execute: |
fi
# Set the timezone1 as timezone and check the status
- test-snapd-timedate-control-consumer.timedatectl-timezone set-timezone $timezone1
+ test-snapd-timedate-control-consumer.timedatectl-timezone set-timezone "$timezone1"
[ "$(test-snapd-timedate-control-consumer.timedatectl-timezone status | grep -oP 'Time zone: \K(.*)(?= \()')" = "$timezone1" ]
# Set the timezone2 as timezone and check the status
- test-snapd-timedate-control-consumer.timedatectl-timezone set-timezone $timezone2
+ test-snapd-timedate-control-consumer.timedatectl-timezone set-timezone "$timezone2"
[ "$(test-snapd-timedate-control-consumer.timedatectl-timezone status | grep -oP 'Time zone: \K(.*)(?= \()')" = "$timezone2" ]
if [ "$(snap debug confinement)" = partial ] ; then
@@ -57,7 +58,7 @@ execute: |
# Disconnect the interface and check access to timedatectl status
snap disconnect test-snapd-timedate-control-consumer:timezone-control
- if test-snapd-timedate-control-consumer.timedatectl-timezone status 2>${PWD}/call.error; then
+ if test-snapd-timedate-control-consumer.timedatectl-timezone status 2>"${PWD}"/call.error; then
echo "Expected permission error calling timedatectl status with disconnected plug"
exit 1
fi
diff --git a/tests/main/interfaces-udev/task.yaml b/tests/main/interfaces-udev/task.yaml
index 98954d0688..89c0392fce 100644
--- a/tests/main/interfaces-udev/task.yaml
+++ b/tests/main/interfaces-udev/task.yaml
@@ -11,7 +11,8 @@ details: |
more interfaces declare udev snippets.
prepare: |
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
echo "Given a snap declaring a slot with associated udev rules is installed"
install_local modem-manager-consumer
diff --git a/tests/main/interfaces-udisks2/task.yaml b/tests/main/interfaces-udisks2/task.yaml
new file mode 100644
index 0000000000..088872b8eb
--- /dev/null
+++ b/tests/main/interfaces-udisks2/task.yaml
@@ -0,0 +1,59 @@
+summary: Ensure that the udisks2 interface works.
+
+details: |
+ The udisks2 interface allows operating as or interacting with the UDisks2 service
+
+# Interfaces not defined for ubuntu core systems
+systems: [-ubuntu-core-*]
+
+prepare: |
+ snap install test-snapd-udisks2
+
+environment:
+ FS_PATH: "$(pwd)/dev0-fake0"
+ MMCBLK_PATH: /dev/mmcblk-fake0
+
+restore: |
+ rm -f call.error
+ losetup -d "$MMCBLK_PATH" || true
+ rm -f "$MMCBLK_PATH" "$FS_PATH"
+
+execute: |
+ echo "The interface is not connected by default"
+ snap interfaces -i udisks2 | MATCH -- "- +test-snapd-udisks2:udisks2"
+
+ echo "When the interface is connected"
+ snap connect test-snapd-udisks2:udisks2
+
+ echo "Check it is possible to see the udisks2 stauts"
+ test-snapd-udisks2.udisksctl status | MATCH "MODEL"
+
+ echo "Check it is possible to dump all the udisks objects info"
+ test-snapd-udisks2.udisksctl dump | MATCH "org.freedesktop.UDisks2.Manager"
+
+ echo "Check we can mount/unmount a block device using the snap"
+ # create a 10M filesystem in pwd
+ dd if=/dev/zero of="$FS_PATH" bs=1M count=10
+ mkfs.ext4 -F "$FS_PATH"
+ # create the loopback block device
+ mknod "$MMCBLK_PATH" b 7 200
+ losetup "$MMCBLK_PATH" "$FS_PATH"
+
+ device="$(losetup -j "$FS_PATH" | cut -d: -f1)"
+
+ test-snapd-udisks2.udisksctl mount -b "$device" -t ext4 | MATCH "Mounted /dev/"
+ test-snapd-udisks2.udisksctl unmount -b "$device" | MATCH "Unmounted /dev/"
+
+ if [ "$(snap debug confinement)" = partial ] ; then
+ exit 0
+ fi
+
+ echo "When the plug is disconnected"
+ snap disconnect test-snapd-udisks2:udisks2
+
+ echo "Then the snap is not able to check udisks2 status"
+ if test-snapd-udisks2.udisksctl status 2> call.error; then
+ echo "Expected permission error calling udisksctl status with disconnected plug"
+ exit 1
+ fi
+ MATCH "Permission denied" < call.error
diff --git a/tests/main/interfaces-uhid/task.yaml b/tests/main/interfaces-uhid/task.yaml
index 716e8b7a0b..1258844151 100644
--- a/tests/main/interfaces-uhid/task.yaml
+++ b/tests/main/interfaces-uhid/task.yaml
@@ -17,7 +17,7 @@ restore: |
execute: |
echo "The plug is not connected by default"
- snap interfaces -i uhid | MATCH "\- +test-snapd-uhid:uhid"
+ snap interfaces -i uhid | MATCH -- '^- +test-snapd-uhid:uhid'
echo "When the plug is connected"
snap connect test-snapd-uhid:uhid
@@ -38,7 +38,7 @@ execute: |
snap disconnect test-snapd-uhid:uhid
echo "Then the snap is not able to create/destroy a device on /dev/uhid"
- if test-snapd-uhid.test-device 2>${PWD}/call.error; then
+ if test-snapd-uhid.test-device 2>"${PWD}"/call.error; then
echo "Expected permission error calling uhid with disconnected plug"
exit 1
fi
diff --git a/tests/main/interfaces-upower-observe/task.yaml b/tests/main/interfaces-upower-observe/task.yaml
index 19548a13cd..f30e5899fe 100644
--- a/tests/main/interfaces-upower-observe/task.yaml
+++ b/tests/main/interfaces-upower-observe/task.yaml
@@ -45,7 +45,7 @@ execute: |
echo "When the plug is connected the snap is able to dump info about the upower devices"
expected="/org/freedesktop/UPower/devices/DisplayDevice.*"
- for i in $(seq 20); do
+ for _ in $(seq 20); do
if ! test-snapd-upower-observe-consumer.upower --dump | MATCH "$expected"; then
sleep 1
fi
@@ -57,9 +57,9 @@ execute: |
snap disconnect test-snapd-upower-observe-consumer:upower-observe
echo "Then the snap is not able to dump info about the upower devices"
- if test-snapd-upower-observe-consumer.upower --dump 2>${PWD}/upower.error; then
+ if test-snapd-upower-observe-consumer.upower --dump 2>"${PWD}"/upower.error; then
echo "Expected permission error accessing upower info with disconnected plug"
exit 1
fi
- cat upower.error | MATCH "Permission denied"
+ MATCH "Permission denied" < upower.error
fi
diff --git a/tests/main/interfaces-wayland/task.yaml b/tests/main/interfaces-wayland/task.yaml
index 41249ffe9a..cfe543f007 100644
--- a/tests/main/interfaces-wayland/task.yaml
+++ b/tests/main/interfaces-wayland/task.yaml
@@ -4,7 +4,8 @@ summary: Ensure that the wayland interface works
systems: [ ubuntu-1*-*64 ]
prepare: |
- . $TESTSLIB/pkgdb.sh
+ #shellcheck source=tests/lib/pkgdb.sh
+ . "$TESTSLIB"/pkgdb.sh
snap install --edge test-snapd-wayland
restore: |
diff --git a/tests/main/kernel-snap-refresh-on-core/task.yaml b/tests/main/kernel-snap-refresh-on-core/task.yaml
index f2b0957afa..09388b9034 100644
--- a/tests/main/kernel-snap-refresh-on-core/task.yaml
+++ b/tests/main/kernel-snap-refresh-on-core/task.yaml
@@ -39,7 +39,8 @@ execute: |
exit 0
fi
- . $TESTSLIB/boot.sh
+ #shellcheck source=tests/lib/boot.sh
+ . "$TESTSLIB"/boot.sh
if [ "$SPREAD_REBOOT" = 0 ]; then
# ensure we have a good starting place
@@ -47,7 +48,7 @@ execute: |
test-snapd-tools.echo hello | MATCH hello
# go to known good starting place
- snap refresh pc-kernel --${KERNEL_CHANNEL}
+ snap refresh pc-kernel "--${KERNEL_CHANNEL}"
REBOOT
elif [ "$SPREAD_REBOOT" = 1 ]; then
# from our good starting place we refresh
@@ -58,7 +59,7 @@ execute: |
cat /proc/version_signature > prevKernelSignature
# refresh
- snap refresh pc-kernel --${NEW_KERNEL_CHANNEL}
+ snap refresh pc-kernel "--${NEW_KERNEL_CHANNEL}"
# check boot env vars
snap list | awk "/^pc-kernel / {print(\$3)}" > nextBoot
diff --git a/tests/main/known-remote/task.yaml b/tests/main/known-remote/task.yaml
index 0ceba7d0ee..8a6ba4a36f 100644
--- a/tests/main/known-remote/task.yaml
+++ b/tests/main/known-remote/task.yaml
@@ -2,7 +2,7 @@ summary: Check snap known --store
execute: |
echo "Check getting assertion from the store"
output=$(snap known --remote model series=16 brand-id=canonical model=pi2)
- echo $output |MATCH "type: model"
- echo $output |MATCH "series: 16"
- echo $output |MATCH "brand-id: canonical"
- echo $output |MATCH "model: pi2"
+ echo "$output" |MATCH "type: model"
+ echo "$output" |MATCH "series: 16"
+ echo "$output" |MATCH "brand-id: canonical"
+ echo "$output" |MATCH "model: pi2"
diff --git a/tests/main/layout/task.yaml b/tests/main/layout/task.yaml
index e4f418b81f..c59dc9adc3 100644
--- a/tests/main/layout/task.yaml
+++ b/tests/main/layout/task.yaml
@@ -7,7 +7,8 @@ details: |
prepare: |
echo "Ensure feature flag is enabled"
snap set core experimental.layouts=true
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
install_local test-snapd-layout
debug: |
ls -ld /etc || :
@@ -15,7 +16,8 @@ debug: |
ls -ld /etc/demo.conf || :
ls -ld /etc/demo.cfg || :
execute: |
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
for i in $(seq 2); do
if [ "$i" -eq 2 ]; then
echo "The snap works across refreshes"
@@ -30,6 +32,7 @@ execute: |
test-snapd-layout.sh -c "test -d /etc/demo"
test-snapd-layout.sh -c "test -f /etc/demo.conf"
test-snapd-layout.sh -c "test -h /etc/demo.cfg"
+ #shellcheck disable=SC2016
test "$(test-snapd-layout.sh -c "readlink /etc/demo.cfg")" = "$(test-snapd-layout.sh -c 'echo $SNAP_COMMON/etc/demo.conf')"
test-snapd-layout.sh -c "test -d /usr/share/demo"
test-snapd-layout.sh -c "test -d /var/lib/demo"
@@ -57,19 +60,24 @@ execute: |
echo "and the writes go to the right place in the backing store"
test-snapd-layout.sh -c "echo foo-1 > /etc/demo/writable"
+ #shellcheck disable=SC2016
test "$(test-snapd-layout.sh -c 'cat $SNAP_COMMON/etc/demo/writable')" = "foo-1"
test-snapd-layout.sh -c "echo foo-2 > /etc/demo.conf"
+ #shellcheck disable=SC2016
test "$(test-snapd-layout.sh -c 'cat $SNAP_COMMON/etc/demo.conf')" = "foo-2"
# NOTE: this is a symlink to demo.conf, effectively
test-snapd-layout.sh -c "echo foo-3 > /etc/demo.cfg"
+ #shellcheck disable=SC2016
test "$(test-snapd-layout.sh -c 'cat $SNAP_COMMON/etc/demo.conf')" = "foo-3"
test-snapd-layout.sh -c "echo foo-4 > /var/lib/demo/writable"
+ #shellcheck disable=SC2016
test "$(test-snapd-layout.sh -c 'cat $SNAP_DATA/var/lib/demo/writable')" = "foo-4"
test-snapd-layout.sh -c "echo foo-5 > /var/cache/demo/writable"
+ #shellcheck disable=SC2016
test "$(test-snapd-layout.sh -c 'cat $SNAP_DATA/var/cache/demo/writable')" = "foo-5"
echo "layout locations pointing to SNAP are readable"
@@ -78,6 +86,7 @@ execute: |
test-snapd-layout.sh -c "test -r /opt/demo/file"
echo "layout locations in dynamically created SNAP directories are writable"
+ # shellcheck disable=SC2016
test-snapd-layout.sh -c 'test -w $SNAP/bin-very-weird-place'
test-snapd-layout.sh -c 'test -w /bin/very-weird-place'
done
diff --git a/tests/main/listing/task.yaml b/tests/main/listing/task.yaml
index 492ba5b27b..a54fcdfd78 100644
--- a/tests/main/listing/task.yaml
+++ b/tests/main/listing/task.yaml
@@ -1,7 +1,8 @@
summary: Check snap listings
prepare: |
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
install_local test-snapd-tools
# autopkgtest run only a subset of tests that deals with the integration
@@ -16,6 +17,7 @@ execute: |
# most core versions should be like "16-2", so [0-9]{2}-[0-9.]+
# but edge will have a timestamp in there, "16.2+201701010932", so add an optional \+[0-9]+ to the end
# *current* edge also has .git. and a hash snippet, so add an optional .git.[0-9a-f]+ to the already optional timestamp
+ #shellcheck disable=SC2166
if [ "$SPREAD_BACKEND" = "linode" -o "$SPREAD_BACKEND" = "google" -o "$SPREAD_BACKEND" == "qemu" ] && [ "$SPREAD_SYSTEM" = "ubuntu-core-16-64" ]; then
echo "With customized images the core snap is sideloaded"
expected='^core .* [0-9]{2}-[0-9.]+(~[a-z0-9]+)?(\+git[0-9]+\.[0-9a-f]+)? +x[0-9]+ +- +- +core *$'
@@ -33,17 +35,18 @@ execute: |
snap list | MATCH '^test-snapd-tools +[0-9]+(\.[0-9]+)* +x[0-9]+ +- +- +- *$'
echo "Install test-snapd-tools again"
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
install_local test-snapd-tools
echo "And run snap list --all"
output=$(snap list --all |grep test-snapd-tools)
if [ "$(grep -c test-snapd-tools <<< "$output")" != "2" ]; then
echo "Expected two test-snapd-tools in the output, got:"
- echo $output
+ echo "$output"
exit 1
fi
if [ "$(grep -c disabled <<< "$output")" != "1" ]; then
echo "Expected one disabled line in in the output, got:"
- echo $output
+ echo "$output"
exit 1
fi
diff --git a/tests/main/login/task.yaml b/tests/main/login/task.yaml
index 7d6d7825bf..2d56221a0d 100644
--- a/tests/main/login/task.yaml
+++ b/tests/main/login/task.yaml
@@ -21,7 +21,7 @@ execute: |
if [ -n "$SPREAD_STORE_USER" ] && [ -n "$SPREAD_STORE_PASSWORD" ]; then
echo "Checking successful login"
- expect -d -f $TESTSLIB/successful_login.exp
+ expect -d -f "$TESTSLIB"/successful_login.exp
output=$(snap managed)
if [ "$output" != "true" ]; then
diff --git a/tests/main/lxd/task.yaml b/tests/main/lxd/task.yaml
index 73d7ffd6a2..79af7c00b2 100644
--- a/tests/main/lxd/task.yaml
+++ b/tests/main/lxd/task.yaml
@@ -2,10 +2,7 @@ summary: Ensure that lxd works
# only run this on ubuntu 16+, lxd will not work on !ubuntu systems
# currently nor on ubuntu 14.04
-#systems: [ubuntu-16*, ubuntu-core-*]
-
-# FIXME LXD has some issue on Google images.
-systems: [ubuntu-16.04-32, ubuntu-core-16-*]
+systems: [ubuntu-16*, ubuntu-18*, ubuntu-2*, ubuntu-core-*]
# autopkgtest run only a subset of tests that deals with the integration
# with the distro
@@ -17,8 +14,15 @@ kill-timeout: 25m
# Start before anything else as it can take a really long time.
priority: 1000
+prepare: |
+ # using apt here is ok because this test only runs on ubuntu
+ echo "Remove any installed debs (some images carry them) to ensure we test the snap"
+ if command -v apt; then
+ apt autoremove -y lxd
+ fi
+
restore: |
- if [[ $(ls -1 "$GOHOME"/snapd_*.deb | wc -l || echo 0) -eq 0 ]]; then
+ if [[ "$(find "$GOHOME" -name 'snapd_*.deb' | wc -l || echo 0)" -eq 0 ]]; then
exit
fi
@@ -33,17 +37,17 @@ debug: |
get_journalctl_log -u snap.lxd.daemon.service
execute: |
- if [[ $(ls -1 "$GOHOME"/snapd_*.deb | wc -l || echo 0) -eq 0 ]]; then
+ if [[ "$(find "$GOHOME" -name 'snapd_*.deb' | wc -l || echo 0)" -eq 0 ]]; then
echo "No run lxd test when there are not .deb files built"
exit
fi
wait_for_lxd(){
- while ! printf "GET / HTTP/1.0\n\n" | nc -U /var/snap/lxd/common/lxd/unix.socket | MATCH "200 OK"; do sleep 1; done
+ while ! printf 'GET / HTTP/1.0\n\n' | nc -U /var/snap/lxd/common/lxd/unix.socket | MATCH '200 OK'; do sleep 1; done
}
echo "Install lxd"
- snap install lxd
+ snap install --candidate lxd
echo "Create a trivial container using the lxd snap"
wait_for_lxd
@@ -51,13 +55,16 @@ execute: |
echo "Setting up proxy for lxc"
if [ -n "${http_proxy:-}" ]; then
- lxd.lxc config set core.proxy_http $http_proxy
+ lxd.lxc config set core.proxy_http "$http_proxy"
fi
if [ -n "${https_proxy:-}" ]; then
- lxd.lxc config set core.proxy_https $http_proxy
+ lxd.lxc config set core.proxy_https "$http_proxy"
fi
- lxd.lxc launch ubuntu:16.04 my-ubuntu
+ # The snapd package we build as part of the tests will only run on the
+ # distro we build on. So we need to launch the right ubuntu version.
+ . /etc/os-release
+ lxd.lxc launch ubuntu:${VERSION_ID} my-ubuntu
echo "Ensure we can run things inside"
lxd.lxc exec my-ubuntu echo hello | MATCH hello
@@ -67,8 +74,8 @@ execute: |
echo "Install snapd"
lxd.lxc exec my-ubuntu -- mkdir -p "$GOHOME"
- lxd.lxc file push "$GOHOME"/snapd_*.deb my-ubuntu/$GOPATH/
- lxd.lxc exec my-ubuntu -- dpkg -i "$GOHOME"/snapd_*.deb
+ lxd.lxc file push "$GOHOME"/snapd_*.deb "my-ubuntu/$GOPATH/"
+ lxd.lxc exec my-ubuntu -- apt install -y "$GOHOME"/snapd_*.deb
echo "Setting up proxy *inside* the container"
if [ -n "${http_proxy:-}" ]; then
diff --git a/tests/main/media-sharing/task.yaml b/tests/main/media-sharing/task.yaml
index a1f9409280..eccf117ba6 100644
--- a/tests/main/media-sharing/task.yaml
+++ b/tests/main/media-sharing/task.yaml
@@ -5,19 +5,23 @@ details: |
Fedora and other systems that build udisks without --enable-fhs-media flag
use /run/media path instead.
prepare: |
- . $TESTSLIB/snaps.sh
- . $TESTSLIB/dirs.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
+ #shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB"/dirs.sh
install_local_devmode test-snapd-tools
mkdir -p ${MEDIA_DIR}/src
mkdir -p ${MEDIA_DIR}/dst
touch ${MEDIA_DIR}/src/canary
execute: |
- . $TESTSLIB/dirs.sh
+ #shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB"/dirs.sh
test ! -e ${MEDIA_DIR}/dst/canary
test-snapd-tools.cmd mount --bind ${MEDIA_DIR}/src ${MEDIA_DIR}/dst
test -e ${MEDIA_DIR}/dst/canary
restore: |
- . $TESTSLIB/dirs.sh
+ #shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB"/dirs.sh
# If this doesn't work maybe it is because the test didn't execute correctly
umount ${MEDIA_DIR}/dst || true
rm -f ${MEDIA_DIR}/src/canary
diff --git a/tests/main/nfs-support/task.yaml b/tests/main/nfs-support/task.yaml
index 6abe9f5ee9..8ec5dae86e 100644
--- a/tests/main/nfs-support/task.yaml
+++ b/tests/main/nfs-support/task.yaml
@@ -5,6 +5,7 @@ details: |
permissions sufficient for NFS to operate.
systems: [ubuntu-16.04-64] # TODO: expand this list
prepare: |
+ #shellcheck source=tests/lib/snaps.sh
. "$TESTSLIB/snaps.sh"
install_local test-snapd-sh
@@ -39,6 +40,7 @@ execute: |
ensure_extra_perms
# As a non-root user perform a write over NFS-mounted /home
+ #shellcheck disable=SC2016
su -c 'snap run test-snapd-sh.with-home-plug -c "touch \$SNAP_USER_DATA/smoke-nfs3-tcp"' test
# Unmount /home and restart snapd so that we can check another thing.
@@ -59,6 +61,7 @@ execute: |
ensure_extra_perms
# As a non-root user perform a write over NFS-mounted /home
+ #shellcheck disable=SC2016
su -c 'snap run test-snapd-sh.with-home-plug -c "touch \$SNAP_USER_DATA/smoke-nfs3-udp"' test
# Unmount /home and restart snapd so that we can check another thing.
@@ -79,6 +82,7 @@ execute: |
ensure_extra_perms
# As a non-root user perform a write over NFS-mounted /home
+ #shellcheck disable=SC2016
su -c 'snap run test-snapd-sh.with-home-plug -c "touch \$SNAP_USER_DATA/smoke-nfs4"' test
# Unmount /home and restart snapd so that we can check another thing.
@@ -100,6 +104,7 @@ execute: |
systemctl restart snapd
ensure_extra_perms
restore: |
+ #shellcheck source=tests/lib/pkgdb.sh
. "$TESTSLIB/pkgdb.sh"
# Unmount NFS mount over /home if one exists.
diff --git a/tests/main/op-install-failed-undone/task.yaml b/tests/main/op-install-failed-undone/task.yaml
index 79775c9bf5..b4e9b767ab 100644
--- a/tests/main/op-install-failed-undone/task.yaml
+++ b/tests/main/op-install-failed-undone/task.yaml
@@ -3,23 +3,26 @@ summary: Check that all tasks of a failed installtion are undone
systems: [-ubuntu-core-*]
restore: |
- . $TESTSLIB/dirs.sh
+ #shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB"/dirs.sh
rm -rf $SNAP_MOUNT_DIR/test-snapd-tools
execute: |
check_empty_glob(){
local base_path=$1
local glob=$2
- [ $(find $base_path -maxdepth 1 -name "$glob" | wc -l) -eq 0 ]
+ [ "$(find "$base_path" -maxdepth 1 -name "$glob" | wc -l)" -eq 0 ]
}
- . $TESTSLIB/dirs.sh
+ #shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB"/dirs.sh
echo "Given we make a snap uninstallable"
mkdir -p $SNAP_MOUNT_DIR/test-snapd-tools/current/foo
echo "And we try to install it"
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
if install_local test-snapd-tools; then
echo "A snap shouldn't be installable if its mount point is busy"
exit 1
@@ -30,23 +33,23 @@ execute: |
echo "And the installation task is reported as an error"
failed_task_id=$(snap changes | perl -ne 'print $1 if /(\d+) +Error.*?Install \"test-snapd-tools\" snap/')
- if [ -z $failed_task_id ]; then
+ if [ -z "$failed_task_id" ]; then
echo "Installation task should be reported as error"
exit 1
fi
echo "And the Mount subtask is actually undone"
- snap change $failed_task_id | grep -Pq "Undone +.*?Mount snap \"test-snapd-tools\""
+ snap change "$failed_task_id" | grep -Pq "Undone +.*?Mount snap \"test-snapd-tools\""
check_empty_glob $SNAP_MOUNT_DIR/test-snapd-tools [0-9]+
check_empty_glob /var/lib/snapd/snaps test-snapd-tools_[0-9]+.snap
echo "And the Data Copy subtask is actually undone"
- snap change $failed_task_id | grep -Pq "Undone +.*?Copy snap \"test-snapd-tools\" data"
- check_empty_glob $HOME/snap/test-snapd-tools [0-9]+
+ snap change "$failed_task_id" | grep -Pq "Undone +.*?Copy snap \"test-snapd-tools\" data"
+ check_empty_glob "$HOME"/snap/test-snapd-tools [0-9]+
check_empty_glob /var/snap/test-snapd-tools [0-9]+
echo "And the Security Profiles Setup subtask is actually undone"
- snap change $failed_task_id | grep -Pq "Undone +.*?Setup snap \"test-snapd-tools\" \(unset\) security profiles"
+ snap change "$failed_task_id" | grep -Pq 'Undone +.*?Setup snap "test-snapd-tools" \(unset\) security profiles'
check_empty_glob /var/lib/snapd/apparmor/profiles snap.test-snapd-tools.*
check_empty_glob /var/lib/snapd/seccomp/bpf snap.test-snapd-tools.*
check_empty_glob /etc/dbus-1/system.d snap.test-snapd-tools.*.conf
diff --git a/tests/main/op-remove-retry/task.yaml b/tests/main/op-remove-retry/task.yaml
index 3c8b67653e..fa9a5286fe 100644
--- a/tests/main/op-remove-retry/task.yaml
+++ b/tests/main/op-remove-retry/task.yaml
@@ -10,10 +10,12 @@ execute: |
while ! snap changes | grep -Pq "$expected"; do sleep 1; done
}
- . $TESTSLIB/systemd.sh
+ #shellcheck source=tests/lib/systemd.sh
+ . "$TESTSLIB"/systemd.sh
echo "Given a snap is installed"
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
install_local test-snapd-tools
echo "And its mount point is kept busy"
@@ -23,7 +25,7 @@ execute: |
MARKER=/var/snap/test-snapd-tools/current/block-running
rm -f $MARKER
- systemd_create_and_start_unit unmount-blocker "$(which test-snapd-tools.block)"
+ systemd_create_and_start_unit unmount-blocker "$(command -v test-snapd-tools.block)"
wait_for_service unmount-blocker active
while [ ! -f $MARKER ]; do sleep 1; done
diff --git a/tests/main/op-remove/task.yaml b/tests/main/op-remove/task.yaml
index 8391758796..47a2b8eeae 100644
--- a/tests/main/op-remove/task.yaml
+++ b/tests/main/op-remove/task.yaml
@@ -5,30 +5,31 @@ restore: |
rm -f stderr.out
execute: |
- . $TESTSLIB/dirs.sh
+ #shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB"/dirs.sh
snap_revisions(){
local snap_name=$1
- echo -n $(find $SNAP_MOUNT_DIR/"$snap_name"/ -maxdepth 1 -type d -name "x*" | wc -l)
+ echo -n "$(find "$SNAP_MOUNT_DIR/$snap_name/" -maxdepth 1 -type d -name "x*" | wc -l)"
}
echo "Given two revisions of a snap have been installed"
- snap pack $TESTSLIB/snaps/basic
+ snap pack "$TESTSLIB"/snaps/basic
snap install --dangerous basic_1.0_all.snap
snap install --dangerous basic_1.0_all.snap
echo "Then the two revisions are available on disk"
- [ $(snap_revisions basic) = "2" ]
+ [ "$(snap_revisions basic)" = "2" ]
echo "When the snap is removed"
snap remove basic
echo "Then the two revisions are removed from disk"
- [ $(snap_revisions basic) = "0" ]
+ [ "$(snap_revisions basic)" = "0" ]
echo "When the snap is removed again, snap exits with status 0"
snap remove basic 2> stderr.out
- cat stderr.out | MATCH 'snap "basic" is not installed'
+ MATCH 'snap "basic" is not installed' < stderr.out
echo "Install a snap that uses a base"
diff --git a/tests/main/postrm-purge/task.yaml b/tests/main/postrm-purge/task.yaml
index 956d121cce..8b7f9fc872 100644
--- a/tests/main/postrm-purge/task.yaml
+++ b/tests/main/postrm-purge/task.yaml
@@ -4,12 +4,14 @@ systems: [-ubuntu-core-*]
execute: |
echo "When some snaps are installed"
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
install_local test-snapd-tools
snap install test-snapd-control-consumer
snap install test-snapd-auto-aliases
- . $TESTSLIB/dirs.sh
+ #shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB"/dirs.sh
# purge is performed while/after removing the package
systemctl stop snapd.service snapd.socket
@@ -18,10 +20,10 @@ execute: |
# tool
if [[ "$SPREAD_SYSTEM" = ubuntu-* || "$SPREAD_SYSTEM" = debian-* ]]; then
# only available on trusty
- if [ -x ${SPREAD_PATH}/debian/snapd.prerm ]; then
- sh -x ${SPREAD_PATH}/debian/snapd.prerm
+ if [ -x "${SPREAD_PATH}/debian/snapd.prerm" ]; then
+ sh -x "${SPREAD_PATH}/debian/snapd.prerm"
fi
- sh -x ${SPREAD_PATH}/debian/snapd.postrm purge
+ sh -x "${SPREAD_PATH}/debian/snapd.postrm" purge
else
${LIBEXECDIR}/snapd/snap-mgmt --purge
fi
@@ -30,7 +32,7 @@ execute: |
for d in $SNAP_MOUNT_DIR /var/snap; do
if [ -d "$d" ]; then
echo "$d is not removed"
- ls -lR $d
+ ls -lR "$d"
exit 1
fi
done
diff --git a/tests/main/prefer/task.yaml b/tests/main/prefer/task.yaml
index 535543141c..b1fb133a0e 100644
--- a/tests/main/prefer/task.yaml
+++ b/tests/main/prefer/task.yaml
@@ -1,6 +1,7 @@
summary: Simple snap prefer test
execute: |
- . $TESTSLIB/dirs.sh
+ #shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB"/dirs.sh
echo "Install the snap with auto-aliases"
snap install test-snapd-auto-aliases
diff --git a/tests/main/prepare-image-grub/task.yaml b/tests/main/prepare-image-grub/task.yaml
index b4a39edf58..17c1d66646 100644
--- a/tests/main/prepare-image-grub/task.yaml
+++ b/tests/main/prepare-image-grub/task.yaml
@@ -19,8 +19,9 @@ prepare: |
exit
fi
- . $TESTSLIB/store.sh
- setup_fake_store $STORE_DIR
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ setup_fake_store "$STORE_DIR"
restore: |
if [ "$TRUST_TEST_KEYS" = "false" ]; then
@@ -28,9 +29,10 @@ restore: |
exit
fi
- . $TESTSLIB/store.sh
- teardown_fake_store $STORE_DIR
- rm -rf $ROOT
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ teardown_fake_store "$STORE_DIR"
+ rm -rf "$ROOT"
execute: |
if [ "$TRUST_TEST_KEYS" = "false" ]; then
@@ -39,8 +41,8 @@ execute: |
fi
echo Expose the needed assertions through the fakestore
- cp $TESTSLIB/assertions/developer1.account $STORE_DIR/asserts
- cp $TESTSLIB/assertions/developer1.account-key $STORE_DIR/asserts
+ cp "$TESTSLIB"/assertions/developer1.account "$STORE_DIR/asserts"
+ cp "$TESTSLIB"/assertions/developer1.account-key "$STORE_DIR/asserts"
# have snap use the fakestore for assertions (but nothing else)
export SNAPPY_FORCE_SAS_URL=http://$STORE_ADDR
@@ -48,23 +50,23 @@ execute: |
su -c "SNAPPY_USE_STAGING_STORE=$SNAPPY_USE_STAGING_STORE snap prepare-image --channel edge --extra-snaps snapweb $TESTSLIB/assertions/developer1-pc.model $ROOT" test
echo Verifying the result
- ls -lR $IMAGE
+ ls -lR "$IMAGE"
for f in pc pc-kernel core snapweb; do
- ls $IMAGE/var/lib/snapd/seed/snaps/${f}*.snap
+ ls "$IMAGE"/var/lib/snapd/seed/snaps/"${f}"*.snap
done
- MATCH snap_core=core < $IMAGE/boot/grub/grubenv
- MATCH snap_kernel=pc-kernel < $IMAGE/boot/grub/grubenv
+ MATCH snap_core=core < "$IMAGE/boot/grub/grubenv"
+ MATCH snap_kernel=pc-kernel < "$IMAGE/boot/grub/grubenv"
# check copied assertions
- cmp $TESTSLIB/assertions/developer1-pc.model $IMAGE/var/lib/snapd/seed/assertions/model
- cmp $TESTSLIB/assertions/developer1.account $IMAGE/var/lib/snapd/seed/assertions/developer1.account
+ cmp "$TESTSLIB"/assertions/developer1-pc.model "$IMAGE/var/lib/snapd/seed/assertions/model"
+ cmp "$TESTSLIB"/assertions/developer1.account "$IMAGE/var/lib/snapd/seed/assertions/developer1.account"
echo Verify the unpacked gadget
- ls -lR $GADGET
- ls $GADGET/meta/snap.yaml
+ ls -lR "$GADGET"
+ ls "$GADGET/meta/snap.yaml"
echo "Verify that we have valid looking seed.yaml"
- cat $IMAGE/var/lib/snapd/seed/seed.yaml
+ cat "$IMAGE/var/lib/snapd/seed/seed.yaml"
# snap-id of core
if [ "$REMOTE_STORE" = production ]; then
@@ -72,13 +74,13 @@ execute: |
else
core_snap_id="xMNMpEm0COPZy7jq9YRwWVLCD9q5peow"
fi
- MATCH "snap-id: ${core_snap_id}" < $IMAGE/var/lib/snapd/seed/seed.yaml
+ MATCH "snap-id: ${core_snap_id}" < "$IMAGE/var/lib/snapd/seed/seed.yaml"
for snap in pc pc-kernel core; do
- MATCH "name: $snap" < $IMAGE/var/lib/snapd/seed/seed.yaml
+ MATCH "name: $snap" < "$IMAGE/var/lib/snapd/seed/seed.yaml"
done
echo "Verify that we got some snap assertions"
for name in pc pc-kernel core; do
- cat $IMAGE/var/lib/snapd/seed/assertions/* | MATCH "snap-name: $name"
+ cat "$IMAGE"/var/lib/snapd/seed/assertions/* | MATCH "snap-name: $name"
done
diff --git a/tests/main/prepare-image-uboot/task.yaml b/tests/main/prepare-image-uboot/task.yaml
index 5285c77fe8..d4d544037f 100644
--- a/tests/main/prepare-image-uboot/task.yaml
+++ b/tests/main/prepare-image-uboot/task.yaml
@@ -10,16 +10,16 @@ environment:
GADGET: /home/test/tmp/gadget
prepare: |
- mkdir -p $ROOT
- chown test:test $ROOT
+ mkdir -p "$ROOT"
+ chown test:test "$ROOT"
restore: |
- rm -rf $ROOT
+ rm -rf "$ROOT"
execute: |
# TODO: switch to a prebuilt properly signed model assertion once we can do that consistently
echo Creating model assertion
- cat > $ROOT/model.assertion <<EOF
+ cat > "$ROOT/model.assertion" <<EOF
type: model
series: 16
authority-id: my-brand
@@ -41,24 +41,24 @@ execute: |
su -c "SNAPPY_USE_STAGING_STORE=$SNAPPY_USE_STAGING_STORE snap prepare-image --channel edge --extra-snaps snapweb $ROOT/model.assertion $ROOT" test
echo Verifying the result
- ls -lR $IMAGE
+ ls -lR "$IMAGE"
for f in pi2 pi2-kernel core snapweb; do
- ls $IMAGE/var/lib/snapd/seed/snaps/${f}*.snap
+ ls "$IMAGE/var/lib/snapd/seed/snaps/${f}"*.snap
done
- MATCH snap_core=core < $IMAGE/boot/uboot/uboot.env
- MATCH snap_kernel=pi2-kernel < $IMAGE/boot/uboot/uboot.env
+ MATCH snap_core=core < "$IMAGE/boot/uboot/uboot.env"
+ MATCH snap_kernel=pi2-kernel < "$IMAGE/boot/uboot/uboot.env"
echo Verify that the kernel is available unpacked
- ls $IMAGE/boot/uboot/pi2-kernel_*.snap/kernel.img
- ls $IMAGE/boot/uboot/pi2-kernel_*.snap/initrd.img
- ls $IMAGE/boot/uboot/pi2-kernel_*.snap/dtbs/
+ ls "$IMAGE"/boot/uboot/pi2-kernel_*.snap/kernel.img
+ ls "$IMAGE"/boot/uboot/pi2-kernel_*.snap/initrd.img
+ ls "$IMAGE"/boot/uboot/pi2-kernel_*.snap/dtbs/
echo Verify the unpacked gadget
- ls -lR $GADGET
- ls $GADGET/meta/snap.yaml
+ ls -lR "$GADGET"
+ ls "$GADGET/meta/snap.yaml"
echo Verify that we have valid looking seed.yaml
- cat $IMAGE/var/lib/snapd/seed/seed.yaml
+ cat "$IMAGE/var/lib/snapd/seed/seed.yaml"
# snap-id of core
if [ "$REMOTE_STORE" = staging ]; then
core_id="xMNMpEm0COPZy7jq9YRwWVLCD9q5peow"
@@ -66,12 +66,12 @@ execute: |
core_id="99T7MUlRhtI3U0QFgl5mXXESAiSwt776"
fi
- MATCH "snap-id: $core_id" < $IMAGE/var/lib/snapd/seed/seed.yaml
+ MATCH "snap-id: $core_id" < "$IMAGE/var/lib/snapd/seed/seed.yaml"
for snap in pi2 pi2-kernel core; do
- MATCH "name: $snap" < $IMAGE/var/lib/snapd/seed/seed.yaml
+ MATCH "name: $snap" < "$IMAGE/var/lib/snapd/seed/seed.yaml"
done
echo "Verify that we got some snap assertions"
for name in pi2 pi2-kernel core; do
- cat $IMAGE/var/lib/snapd/seed/assertions/* | MATCH "snap-name: $name"
+ cat "$IMAGE"/var/lib/snapd/seed/assertions/* | MATCH "snap-name: $name"
done
diff --git a/tests/main/refresh-all-undo/task.yaml b/tests/main/refresh-all-undo/task.yaml
index 78a9385e57..497dcdb0ee 100644
--- a/tests/main/refresh-all-undo/task.yaml
+++ b/tests/main/refresh-all-undo/task.yaml
@@ -13,15 +13,16 @@ prepare: |
exit
fi
- . $TESTSLIB/store.sh
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
echo "Given two snaps are installed"
for snap in $GOOD_SNAP $BAD_SNAP; do
- snap install $snap
+ snap install "$snap"
done
echo "And the daemon is configured to point to the fake store"
- setup_fake_store $BLOB_DIR
+ setup_fake_store "$BLOB_DIR"
restore: |
if [ "$TRUST_TEST_KEYS" = "false" ]; then
@@ -29,9 +30,10 @@ restore: |
exit
fi
- . $TESTSLIB/store.sh
- teardown_fake_store $BLOB_DIR
- rm -rf $BLOB_DIR
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ teardown_fake_store "$BLOB_DIR"
+ rm -rf "$BLOB_DIR"
execute: |
if [ "$TRUST_TEST_KEYS" = "false" ]; then
@@ -43,15 +45,17 @@ execute: |
snap refresh 2>&1 | MATCH "All snaps up to date"
echo "When the store is configured to make them refreshable"
- . $TESTSLIB/files.sh
- . $TESTSLIB/store.sh
+ #shellcheck source=tests/lib/files.sh
+ . "$TESTSLIB"/files.sh
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
init_fake_refreshes "$BLOB_DIR" "$GOOD_SNAP"
- wait_for_file "$BLOB_DIR"/"${GOOD_SNAP}"*fake1*.snap 4 .5
+ wait_for_file "$BLOB_DIR/${GOOD_SNAP}"*fake1*.snap 4 .5
init_fake_refreshes "$BLOB_DIR" "$BAD_SNAP"
- wait_for_file "$BLOB_DIR"/"${BAD_SNAP}"*fake1*.snap 4 .5
+ wait_for_file "$BLOB_DIR/${BAD_SNAP}"*fake1*.snap 4 .5
echo "When a snap is broken"
- echo "i-am-broken-now" >> $BLOB_DIR/${BAD_SNAP}*fake1*.snap
+ echo "i-am-broken-now" >> "$BLOB_DIR/${BAD_SNAP}"*fake1*.snap
echo "And a refresh is performed"
if snap refresh ; then
@@ -65,16 +69,17 @@ execute: |
echo "But the bad snap did not get updated"
snap list | MATCH -E "${BAD_SNAP}"| MATCH -v "fake"
- . $TESTSLIB/changes.sh
+ #shellcheck source=tests/lib/changes.sh
+ . "$TESTSLIB"/changes.sh
chg_id=$(change_id "Refresh snap" Error)
echo "Verify the snap change"
- snap change $chg_id | MATCH "Undone.*Download snap \"${BAD_SNAP}\""
- snap change $chg_id | MATCH "Done.*Download snap \"${GOOD_SNAP}\""
- snap change $chg_id | MATCH "ERROR cannot verify snap \"test-snapd-tools\", no matching signatures found"
+ snap change "$chg_id" | MATCH "Undone.*Download snap \"${BAD_SNAP}\""
+ snap change "$chg_id" | MATCH "Done.*Download snap \"${GOOD_SNAP}\""
+ snap change "$chg_id" | MATCH "ERROR cannot verify snap \"test-snapd-tools\", no matching signatures found"
echo "Verify the 'snap tasks' is the same as 'snap change'"
- snap tasks $chg_id | MATCH "Undone.*Download snap \"${BAD_SNAP}\""
+ snap tasks "$chg_id" | MATCH "Undone.*Download snap \"${BAD_SNAP}\""
echo "Verify the 'snap tasks --last' shows last refresh change"
snap tasks --last=refresh | MATCH "Undone.*Download snap \"${BAD_SNAP}\""
diff --git a/tests/main/refresh-all/task.yaml b/tests/main/refresh-all/task.yaml
index 5b61c29be8..5084fad659 100644
--- a/tests/main/refresh-all/task.yaml
+++ b/tests/main/refresh-all/task.yaml
@@ -16,7 +16,8 @@ prepare: |
exit
fi
- . $TESTSLIB/store.sh
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
echo "Given two snaps are installed"
for snap in test-snapd-tools test-snapd-python-webserver; do
@@ -24,7 +25,7 @@ prepare: |
done
echo "And the daemon is configured to point to the fake store"
- setup_fake_store $BLOB_DIR
+ setup_fake_store "$BLOB_DIR"
restore: |
if [ "$TRUST_TEST_KEYS" = "false" ]; then
@@ -32,9 +33,10 @@ restore: |
exit
fi
- . $TESTSLIB/store.sh
- teardown_fake_store $BLOB_DIR
- rm -rf $BLOB_DIR
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ teardown_fake_store "$BLOB_DIR"
+ rm -rf "$BLOB_DIR"
execute: |
if [ "$TRUST_TEST_KEYS" = "false" ]; then
@@ -46,12 +48,14 @@ execute: |
snap refresh 2>&1 | MATCH "All snaps up to date."
echo "When the store is configured to make them refreshable"
- . $TESTSLIB/files.sh
- . $TESTSLIB/store.sh
- init_fake_refreshes $BLOB_DIR test-snapd-tools
- wait_for_file $BLOB_DIR/test-snapd-tools*fake1*.snap 4 .5
- init_fake_refreshes $BLOB_DIR test-snapd-python-webserver
- wait_for_file $BLOB_DIR/test-snapd-python-webserver*fake1*.snap 4 .5
+ #shellcheck source=tests/lib/files.sh
+ . "$TESTSLIB"/files.sh
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ init_fake_refreshes "$BLOB_DIR" test-snapd-tools
+ wait_for_file "$BLOB_DIR"/test-snapd-tools*fake1*.snap 4 .5
+ init_fake_refreshes "$BLOB_DIR" test-snapd-python-webserver
+ wait_for_file "$BLOB_DIR"/test-snapd-python-webserver*fake1*.snap 4 .5
echo "And a refresh is performed"
snap refresh
diff --git a/tests/main/refresh-amend/task.yaml b/tests/main/refresh-amend/task.yaml
index 0196761efd..cc7b9b8f6e 100644
--- a/tests/main/refresh-amend/task.yaml
+++ b/tests/main/refresh-amend/task.yaml
@@ -14,7 +14,7 @@ execute: |
echo "snap refresh should error but did not"
exit 1
fi
- cat stderr.out | MATCH 'local snap "test-snapd-only-in-edge" is unknown to the store'
+ MATCH 'local snap "test-snapd-only-in-edge" is unknown to the store' < stderr.out
echo "A refresh with --amend is not enough, the channel needs to be added"
if snap refresh --amend test-snapd-only-in-edge 2> stderr.out; then
diff --git a/tests/main/refresh-delta-from-core/task.yaml b/tests/main/refresh-delta-from-core/task.yaml
index 28f7192bb5..569299c755 100644
--- a/tests/main/refresh-delta-from-core/task.yaml
+++ b/tests/main/refresh-delta-from-core/task.yaml
@@ -13,7 +13,7 @@ prepare: |
fi
echo "Given a snap is installed"
- snap install --edge $SNAP_NAME
+ snap install --edge "$SNAP_NAME"
restore: |
if [ -e /usr/bin/xdelta3.disabled ]; then
@@ -25,6 +25,6 @@ execute: |
. "$TESTSLIB/journalctl.sh"
echo "When the snap is refreshed"
- snap refresh --beta $SNAP_NAME
+ snap refresh --beta "$SNAP_NAME"
echo "Then deltas are successfully applied"
get_journalctl_log -u snapd | MATCH "Successfully applied delta"
diff --git a/tests/main/refresh-delta/task.yaml b/tests/main/refresh-delta/task.yaml
index a48c9ab7b4..2d1b9bc838 100644
--- a/tests/main/refresh-delta/task.yaml
+++ b/tests/main/refresh-delta/task.yaml
@@ -16,14 +16,14 @@ prepare: |
# r3 -> r5b
#
echo "Given a snap is installed"
- snap install --edge $SNAP_NAME
+ snap install --edge "$SNAP_NAME"
execute: |
# shellcheck source=tests/lib/journalctl.sh
. "$TESTSLIB/journalctl.sh"
echo "When the snap is refreshed"
- snap refresh --beta $SNAP_NAME
+ snap refresh --beta "$SNAP_NAME"
echo "Then deltas are successfully applied"
get_journalctl_log -u snapd | MATCH "Successfully applied delta"
diff --git a/tests/main/refresh-devmode/task.yaml b/tests/main/refresh-devmode/task.yaml
index 770eb7ff68..90d32a09a0 100644
--- a/tests/main/refresh-devmode/task.yaml
+++ b/tests/main/refresh-devmode/task.yaml
@@ -34,12 +34,14 @@ prepare: |
snap install --devmode test-snapd-tools
if [ "$STORE_TYPE" = "fake" ]; then
- . $TESTSLIB/store.sh
- setup_fake_store $BLOB_DIR
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ setup_fake_store "$BLOB_DIR"
echo "And a new version of that snap put in the controlled store"
- . $TESTSLIB/store.sh
- init_fake_refreshes $BLOB_DIR test-snapd-tools
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ init_fake_refreshes "$BLOB_DIR" test-snapd-tools
fi
restore: |
@@ -54,8 +56,9 @@ restore: |
echo "This test needs test keys to be trusted"
exit
fi
- . $TESTSLIB/store.sh
- teardown_fake_store $BLOB_DIR
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ teardown_fake_store "$BLOB_DIR"
fi
execute: |
@@ -80,7 +83,7 @@ execute: |
# echo "================================="
echo "When the snap is refreshed"
- snap refresh --devmode --channel=edge $SNAP_NAME
+ snap refresh --devmode --channel=edge "$SNAP_NAME"
echo "Then the new version is listed"
expected="$SNAP_NAME +$SNAP_VERSION_PATTERN .*devmode"
diff --git a/tests/main/refresh-undo/task.yaml b/tests/main/refresh-undo/task.yaml
index af8f156f14..79741dbe3c 100644
--- a/tests/main/refresh-undo/task.yaml
+++ b/tests/main/refresh-undo/task.yaml
@@ -15,8 +15,8 @@ environment:
prepare: |
echo "Given a good (v1) and a bad (v2) snap"
- snap pack $TESTSLIB/snaps/$SNAP_NAME_GOOD
- snap pack $TESTSLIB/snaps/$SNAP_NAME_BAD
+ snap pack "$TESTSLIB/snaps/$SNAP_NAME_GOOD"
+ snap pack "$TESTSLIB/snaps/$SNAP_NAME_BAD"
debug: |
# shellcheck source=tests/lib/journalctl.sh
@@ -37,12 +37,12 @@ execute: |
done
}
echo "When we install v1"
- snap install --dangerous ${SNAP_FILE_GOOD}
+ snap install --dangerous "${SNAP_FILE_GOOD}"
echo "The v1 service started correctly"
wait_for_service_status "service v1"
echo "When we refresh to v2"
- if snap install --dangerous ${SNAP_FILE_BAD}; then
+ if snap install --dangerous "${SNAP_FILE_BAD}"; then
echo "The ${SNAP_FILE_BAD} snap should not install cleanly, test broken"
exit 1
fi
diff --git a/tests/main/refresh/task.yaml b/tests/main/refresh/task.yaml
index 8bc6bd3991..545b90f4ba 100644
--- a/tests/main/refresh/task.yaml
+++ b/tests/main/refresh/task.yaml
@@ -31,7 +31,7 @@ prepare: |
fi
flags=
- if [[ $SNAP_NAME =~ classic ]]; then
+ if [[ "$SNAP_NAME" =~ classic ]]; then
case "$SPREAD_SYSTEM" in
ubuntu-core-*|fedora-*|arch-*)
exit
@@ -41,15 +41,17 @@ prepare: |
fi
echo "Given a snap is installed"
- snap install $flags $SNAP_NAME
+ snap install $flags "$SNAP_NAME"
if [ "$STORE_TYPE" = "fake" ]; then
- . $TESTSLIB/store.sh
- setup_fake_store $BLOB_DIR
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ setup_fake_store "$BLOB_DIR"
echo "And a new version of that snap put in the controlled store"
- . $TESTSLIB/store.sh
- init_fake_refreshes $BLOB_DIR $SNAP_NAME
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ init_fake_refreshes "$BLOB_DIR" "$SNAP_NAME"
fi
restore: |
@@ -65,8 +67,9 @@ restore: |
echo "This test needs test keys to be trusted"
exit
fi
- . $TESTSLIB/store.sh
- teardown_fake_store $BLOB_DIR
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ teardown_fake_store "$BLOB_DIR"
fi
execute: |
@@ -83,7 +86,7 @@ execute: |
fi
fi
- if [[ $SNAP_NAME =~ classic ]]; then
+ if [[ "$SNAP_NAME" =~ classic ]]; then
case "$SPREAD_SYSTEM" in
ubuntu-core-*|fedora-*|arch-*)
exit
@@ -99,33 +102,34 @@ execute: |
# echo "================================="
echo "When the snap is refreshed"
- snap refresh --channel=edge $SNAP_NAME
+ snap refresh --channel=edge "$SNAP_NAME"
echo "Then the new version is listed"
expected="$SNAP_NAME +$SNAP_VERSION_PATTERN"
snap list | grep -Pzq "$expected"
echo "When a snap is refreshed and has no update it exit 0"
- snap refresh $SNAP_NAME 2>stderr.out
- cat stderr.out | MATCH "snap \"$SNAP_NAME\" has no updates available"
+ snap refresh "$SNAP_NAME" 2>stderr.out
+ MATCH "snap \"$SNAP_NAME\" has no updates available" < stderr.out
echo "classic snaps "
echo "When multiple snaps have no update we have a good message"
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
install_local basic
- snap refresh $SNAP_NAME basic 2>&1 | MATCH "All snaps up to date."
+ snap refresh "$SNAP_NAME" basic 2>&1 | MATCH "All snaps up to date."
echo "When moving to stable"
- snap refresh --stable $SNAP_NAME
- snap info $SNAP_NAME | MATCH "tracking: +stable"
+ snap refresh --stable "$SNAP_NAME"
+ snap info "$SNAP_NAME" | MATCH "tracking: +stable"
- snap refresh --candidate $SNAP_NAME 2>&1 | MATCH "$SNAP_NAME \(candidate\).*"
- snap info $SNAP_NAME | MATCH "tracking: +candidate"
+ snap refresh --candidate "$SNAP_NAME" 2>&1 | MATCH "$SNAP_NAME \\(candidate\\).*"
+ snap info "$SNAP_NAME" | MATCH "tracking: +candidate"
echo "When multiple snaps are refreshed we error if we have unknown names"
if snap refresh core invälid-snap-name 2> out.err; then
echo "snap refresh invalid-snap-name should fail but it did not?"
exit 1
fi
- cat out.err | tr '\n' ' ' | tr -s ' ' | MATCH 'cannot refresh .* is not installed'
+ tr '\n' ' ' < out.err | tr -s ' ' | MATCH 'cannot refresh .* is not installed'
diff --git a/tests/main/regression-home-snap-root-owned/task.yaml b/tests/main/regression-home-snap-root-owned/task.yaml
index 9da584d15a..d99e0e860e 100644
--- a/tests/main/regression-home-snap-root-owned/task.yaml
+++ b/tests/main/regression-home-snap-root-owned/task.yaml
@@ -4,22 +4,24 @@ prepare: |
# ensure we have no snap user data directory yet
rm -rf /home/test/snap
rm -rf /root/snap
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
install_local test-snapd-tools
execute: |
- . $TESTSLIB/dirs.sh
+ #shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB"/dirs.sh
# run a snap command via sudo
output=$(su -l -c "sudo $SNAP_MOUNT_DIR/bin/test-snapd-tools.env" test)
# ensure SNAP_USER_DATA points to the right place
- echo $output | MATCH SNAP_USER_DATA=/root/snap/test-snapd-tools/x[0-9]+
- echo $output | MATCH HOME=/root/snap/test-snapd-tools/x[0-9]+
- echo $output | MATCH SNAP_USER_COMMON=/root/snap/test-snapd-tools/common
+ echo "$output" | MATCH SNAP_USER_DATA=/root/snap/test-snapd-tools/x[0-9]+
+ echo "$output" | MATCH HOME=/root/snap/test-snapd-tools/x[0-9]+
+ echo "$output" | MATCH SNAP_USER_COMMON=/root/snap/test-snapd-tools/common
echo "Verify that the /root/snap directory created and root owned"
- if [ $(stat -c '%U' /root/snap) != "root" ]; then
+ if [ "$(stat -c '%U' /root/snap)" != "root" ]; then
echo "The /root/snap directory is not owned by root"
ls -ld $SNAP_MOUNT_DIR/snap
exit 1
@@ -27,7 +29,7 @@ execute: |
echo "Verify that there is no /home/test/snap appearing"
if [ -e /home/test/snap ]; then
- user=$(stat -c '%U' /home/test/snap)
+ user="$(stat -c '%U' /home/test/snap)"
echo "An unexpected /home/test/snap directory got created (owner $user)"
ls -ld /home/test/snap
exit 1
diff --git a/tests/main/remove-errors/task.yaml b/tests/main/remove-errors/task.yaml
index a473f750b6..421fb9e6d8 100644
--- a/tests/main/remove-errors/task.yaml
+++ b/tests/main/remove-errors/task.yaml
@@ -5,13 +5,15 @@ systems: [-ubuntu-core-18-*]
execute: |
echo "Given a core snap is installed"
+ #shellcheck source=tests/lib/snaps.sh
. "$TESTSLIB/snaps.sh"
install_local test-snapd-tools
+ #shellcheck source=tests/lib/names.sh
. "$TESTSLIB/names.sh"
echo "Ensure the important snaps can not be removed"
for sn in core $kernel_name $gadget_name; do
- if snap remove $sn; then
+ if snap remove "$sn"; then
echo "It should not be possible to remove $sn"
exit 1
fi
diff --git a/tests/main/revert-devmode/task.yaml b/tests/main/revert-devmode/task.yaml
index 3977d685b4..9725cd6454 100644
--- a/tests/main/revert-devmode/task.yaml
+++ b/tests/main/revert-devmode/task.yaml
@@ -23,12 +23,14 @@ prepare: |
snap install --devmode test-snapd-tools
if [ "$STORE_TYPE" = "fake" ]; then
- . $TESTSLIB/store.sh
- setup_fake_store $BLOB_DIR
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ setup_fake_store "$BLOB_DIR"
echo "And a new version of that snap put in the controlled store"
- . $TESTSLIB/store.sh
- init_fake_refreshes $BLOB_DIR test-snapd-tools
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ init_fake_refreshes "$BLOB_DIR" test-snapd-tools
fi
restore: |
@@ -43,8 +45,9 @@ restore: |
echo "This test needs test keys to be trusted"
exit
fi
- . $TESTSLIB/store.sh
- teardown_fake_store $BLOB_DIR
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ teardown_fake_store "$BLOB_DIR"
fi
execute: |
@@ -61,7 +64,8 @@ execute: |
fi
fi
- . $TESTSLIB/dirs.sh
+ #shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB"/dirs.sh
echo "When a refresh is made"
snap refresh --devmode --edge test-snapd-tools
@@ -80,7 +84,7 @@ execute: |
snap list|MATCH 'test-snapd-tools .* devmode'
echo "When the latest revision is installed again"
- snap remove --revision=$LATEST test-snapd-tools
+ snap remove --revision="$LATEST" test-snapd-tools
snap refresh --edge test-snapd-tools
if [ "$(snap debug confinement)" = strict ] ; then
diff --git a/tests/main/revert-sideload/task.yaml b/tests/main/revert-sideload/task.yaml
index 24a7259965..ef085c86ae 100644
--- a/tests/main/revert-sideload/task.yaml
+++ b/tests/main/revert-sideload/task.yaml
@@ -1,7 +1,7 @@
summary: Checks for snap sideload reverts
prepare: |
- snap pack $TESTSLIB/snaps/basic
+ snap pack "$TESTSLIB"/snaps/basic
restore: |
rm -f ./basic_1.0_all.snap
diff --git a/tests/main/revert/task.yaml b/tests/main/revert/task.yaml
index b290595832..d5e28eaa86 100644
--- a/tests/main/revert/task.yaml
+++ b/tests/main/revert/task.yaml
@@ -23,12 +23,14 @@ prepare: |
snap install test-snapd-tools
if [ "$STORE_TYPE" = "fake" ]; then
- . $TESTSLIB/store.sh
- setup_fake_store $BLOB_DIR
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ setup_fake_store "$BLOB_DIR"
echo "And a new version of that snap put in the controlled store"
- . $TESTSLIB/store.sh
- init_fake_refreshes $BLOB_DIR test-snapd-tools
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ init_fake_refreshes "$BLOB_DIR" test-snapd-tools
fi
restore: |
@@ -43,8 +45,9 @@ restore: |
echo "This test needs test keys to be trusted"
exit
fi
- . $TESTSLIB/store.sh
- teardown_fake_store $BLOB_DIR
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ teardown_fake_store "$BLOB_DIR"
fi
execute: |
@@ -67,13 +70,14 @@ execute: |
exit 1
fi
- . $TESTSLIB/dirs.sh
+ #shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB"/dirs.sh
echo "When a refresh is made"
snap refresh --edge test-snapd-tools
echo "Then the new version is installed"
- snap list | MATCH "test-snapd-tools +[0-9]+\.[0-9]+\+fake1"
+ snap list | MATCH -- 'test-snapd-tools +[0-9]+\.[0-9]+\+fake1'
echo "And the snap runs"
test-snapd-tools.echo hello|MATCH hello
@@ -82,11 +86,11 @@ execute: |
snap revert test-snapd-tools
echo "Then the old version is active"
- snap list | MATCH "test-snapd-tools +[0-9]+\.[0-9]+ "
+ snap list | MATCH -- 'test-snapd-tools +[0-9]+\.[0-9]+ '
echo "And the data directories are present"
- ls $SNAP_MOUNT_DIR/test-snapd-tools | MATCH current
- ls /var/snap/test-snapd-tools | MATCH current
+ find $SNAP_MOUNT_DIR/test-snapd-tools -maxdepth 1 | MATCH current
+ find /var/snap/test-snapd-tools -maxdepth 1 | MATCH current
echo "And the snap runs confined"
snap list|MATCH 'test-snapd-tools.* -$'
@@ -102,8 +106,8 @@ execute: |
echo "And a refresh doesn't update the snap"
snap refresh
- snap list | MATCH "test-snapd-tools +[0-9]+\.[0-9]+ "
+ snap list | MATCH -- 'test-snapd-tools +[0-9]+\.[0-9]+ '
echo "Unless the snap is asked for explicitly"
snap refresh --edge test-snapd-tools
- snap list | MATCH "test-snapd-tools +[0-9]+\.[0-9]+\+fake1"
+ snap list | MATCH -- 'test-snapd-tools +[0-9]+\.[0-9]+\+fake1'
diff --git a/tests/main/searching/task.yaml b/tests/main/searching/task.yaml
index 713962c378..63eb861fbe 100644
--- a/tests/main/searching/task.yaml
+++ b/tests/main/searching/task.yaml
@@ -12,9 +12,7 @@ restore: |
execute: |
echo "List all featured snaps"
- expected="(?s).*Name +Version +Publisher +Notes +Summary *\n\
- (.*?\n)?\
- .*"
+ expected='(?s).*Name +Version +Publisher +Notes +Summary *\n(.*?\n)?.*'
snap find > featured.txt
if ! grep -Pzq "$expected" < featured.txt; then
echo "expected out put $expected not found in:"
@@ -24,12 +22,12 @@ execute: |
MATCH "No search term specified. Here are some interesting snaps" < featured.txt
MATCH "Provide a search term for more specific results." < featured.txt
- if [ $(snap find | wc -l) -gt 50 ]; then
+ if [ "$(snap find | wc -l)" -gt 50 ]; then
echo "Found more than 50 featured apps, this seems bogus:"
snap find
exit 1
fi
- if [ $(snap find | wc -l) -lt 2 ]; then
+ if [ "$(snap find | wc -l)" -lt 2 ]; then
echo "Not found any featured app, this seems bogus:"
snap find
exit 1
@@ -38,28 +36,22 @@ execute: |
echo "Exact matches"
for snapName in test-snapd-tools test-snapd-python-webserver
do
- expected="(?s)Name +Version +Publisher +Notes +Summary *\n\
- (.*?\n)?\
- $snapName +.*? *\n\
- .*"
+ expected="(?s)Name +Version +Publisher +Notes +Summary *\\n(.*?\\n)?${snapName} +.*? *\\n.*"
snap find $snapName | grep -Pzq "$expected"
done
echo "Partial terms work too"
- expected="(?s)Name +Version +Publisher +Notes +Summary *\n\
- (.*?\n)?\
- test-snapd-tools +.*? *\n\
- .*"
+ expected='(?s)Name +Version +Publisher +Notes +Summary *\n(.*?\n)?test-snapd-tools +.*? *\n.*'
snap find test-snapd- | grep -Pzq "$expected"
echo "List of snaps in a section works"
# NOTE: this shows featured snaps which change all the time, do not
# make any assumptions about the contents
- test $(snap find --section=featured | wc -l) -gt 1
+ test "$(snap find --section=featured | wc -l)" -gt 1
# TODO: discuss with the store how we can make this test stable, i.e.
# that section/snap changes do not break us
- if [ $(uname -m) = "x86_64" ]; then
+ if [ "$(uname -m)" = "x86_64" ]; then
snap find --section=video vlc | MATCH vlc
else
snap find --section=video vlc 2>&1 | MATCH 'No matching snaps'
@@ -69,4 +61,4 @@ execute: |
if snap find " " | grep "status code 403"; then
echo 'snap find " " returns non user friendly error with whitespace query'
exit 1
- fi \ No newline at end of file
+ fi
diff --git a/tests/main/security-apparmor/task.yaml b/tests/main/security-apparmor/task.yaml
index b2082aa138..fdee9fdee5 100644
--- a/tests/main/security-apparmor/task.yaml
+++ b/tests/main/security-apparmor/task.yaml
@@ -2,7 +2,8 @@ summary: Check basic apparmor confinement rules.
prepare: |
echo "Given a basic snap is installed"
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
install_local test-snapd-tools
execute: |
if [ "$(snap debug confinement)" = partial ] ; then
diff --git a/tests/main/security-device-cgroups-classic/task.yaml b/tests/main/security-device-cgroups-classic/task.yaml
index 7191cfc2eb..872fafc781 100644
--- a/tests/main/security-device-cgroups-classic/task.yaml
+++ b/tests/main/security-device-cgroups-classic/task.yaml
@@ -18,7 +18,8 @@ prepare: |
fi
echo "Given a snap declaring a plug on framebuffer is installed in classic"
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
install_local_classic test-classic-cgroup
restore: |
@@ -27,11 +28,12 @@ restore: |
fi
execute: |
- . $TESTSLIB/dirs.sh
+ #shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB"/dirs.sh
# classic snaps don't use 'plugs', so just test the accesses after install
echo "the classic snap can access the framebuffer"
"$SNAP_MOUNT_DIR"/bin/test-classic-cgroup.read-fb 2>&1 | MATCH -v '(Permission denied|Operation not permitted)'
echo "the classic snap can access other devices"
- test "`$SNAP_MOUNT_DIR/bin/test-classic-cgroup.read-kmsg`"
+ test "$($SNAP_MOUNT_DIR/bin/test-classic-cgroup.read-kmsg)"
diff --git a/tests/main/security-device-cgroups-devmode/task.yaml b/tests/main/security-device-cgroups-devmode/task.yaml
index 0259c4ee37..c4bdb1833f 100644
--- a/tests/main/security-device-cgroups-devmode/task.yaml
+++ b/tests/main/security-device-cgroups-devmode/task.yaml
@@ -14,7 +14,8 @@ prepare: |
fi
echo "Given a snap declaring a plug on framebuffer is installed in devmode"
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
install_local_devmode test-devmode-cgroup
restore: |
@@ -23,7 +24,8 @@ restore: |
fi
execute: |
- . $TESTSLIB/dirs.sh
+ #shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB"/dirs.sh
echo "And the framebuffer plug is connected"
snap connect test-devmode-cgroup:framebuffer
@@ -31,7 +33,7 @@ execute: |
"$SNAP_MOUNT_DIR"/bin/test-devmode-cgroup.read-fb 2>&1 | MATCH -v '(Permission denied|Operation not permitted)'
echo "the devmode snap can access other devices"
- test "`$SNAP_MOUNT_DIR/bin/test-devmode-cgroup.read-kmsg`"
+ test "$($SNAP_MOUNT_DIR/bin/test-devmode-cgroup.read-kmsg)"
echo "And the framebuffer plug is disconnected"
snap disconnect test-devmode-cgroup:framebuffer
@@ -39,4 +41,4 @@ execute: |
"$SNAP_MOUNT_DIR"/bin/test-devmode-cgroup.read-fb 2>&1 | MATCH -v '(Permission denied|Operation not permitted)'
echo "the devmode snap can access other devices"
- test "`$SNAP_MOUNT_DIR/bin/test-devmode-cgroup.read-kmsg`"
+ test "$($SNAP_MOUNT_DIR/bin/test-devmode-cgroup.read-kmsg)"
diff --git a/tests/main/security-device-cgroups-jailmode/task.yaml b/tests/main/security-device-cgroups-jailmode/task.yaml
index e2ed362a7c..9604f46715 100644
--- a/tests/main/security-device-cgroups-jailmode/task.yaml
+++ b/tests/main/security-device-cgroups-jailmode/task.yaml
@@ -17,7 +17,8 @@ prepare: |
fi
echo "Given a snap declaring a plug on framebuffer is installed in jailmode"
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
install_local_jailmode test-devmode-cgroup
restore: |
@@ -26,7 +27,8 @@ restore: |
fi
execute: |
- . $TESTSLIB/dirs.sh
+ #shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB"/dirs.sh
echo "And the framebuffer plug is connected"
snap connect test-devmode-cgroup:framebuffer
diff --git a/tests/main/security-device-cgroups-serial-port/task.yaml b/tests/main/security-device-cgroups-serial-port/task.yaml
index fca36bc891..e3c5a5acb0 100644
--- a/tests/main/security-device-cgroups-serial-port/task.yaml
+++ b/tests/main/security-device-cgroups-serial-port/task.yaml
@@ -22,7 +22,8 @@ restore: |
execute: |
echo "Given a snap is installed"
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
install_local test-snapd-tools
echo "Then the device is not assigned to that snap"
diff --git a/tests/main/security-device-cgroups-strict/task.yaml b/tests/main/security-device-cgroups-strict/task.yaml
index c3937cd9d7..912413ec0c 100644
--- a/tests/main/security-device-cgroups-strict/task.yaml
+++ b/tests/main/security-device-cgroups-strict/task.yaml
@@ -16,7 +16,8 @@ prepare: |
fi
echo "Given a snap declaring a plug on framebuffer is installed in strict"
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
install_local test-strict-cgroup
restore: |
@@ -25,7 +26,8 @@ restore: |
fi
execute: |
- . $TESTSLIB/dirs.sh
+ #shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB"/dirs.sh
echo "And the framebuffer plug is connected"
snap connect test-strict-cgroup:framebuffer
diff --git a/tests/main/security-device-cgroups/task.yaml b/tests/main/security-device-cgroups/task.yaml
index f4d4936a93..914a6ce68e 100644
--- a/tests/main/security-device-cgroups/task.yaml
+++ b/tests/main/security-device-cgroups/task.yaml
@@ -77,11 +77,12 @@ execute: |
fi
echo "Given a snap is installed"
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
install_local test-snapd-tools
echo "Then the device is not assigned to that snap"
- ! udevadm info $UDEVADM_PATH | MATCH "E: TAGS=.*snap_test-snapd-tools_env"
+ ! udevadm info "$UDEVADM_PATH" | MATCH "E: TAGS=.*snap_test-snapd-tools_env"
echo "And the device is not shown in the snap device list"
# FIXME: this is, apparently, a layered can of worms. Zyga says he needs to fix it.
@@ -100,10 +101,10 @@ execute: |
udevadm settle
echo "Then the device is shown as assigned to the snap"
- udevadm info $UDEVADM_PATH | MATCH "E: TAGS=.*snap_test-snapd-tools_env"
+ udevadm info "$UDEVADM_PATH" | MATCH "E: TAGS=.*snap_test-snapd-tools_env"
echo "And other devices are not shown as assigned to the snap"
- udevadm info $OTHER_UDEVADM_PATH | MATCH -v "E: TAGS=.*snap_test-snapd-tools_env"
+ udevadm info "$OTHER_UDEVADM_PATH" | MATCH -v "E: TAGS=.*snap_test-snapd-tools_env"
echo "================================================="
diff --git a/tests/main/security-devpts/task.yaml b/tests/main/security-devpts/task.yaml
index 6d633b07b7..95083dea41 100644
--- a/tests/main/security-devpts/task.yaml
+++ b/tests/main/security-devpts/task.yaml
@@ -6,7 +6,8 @@ execute: |
fi
echo "Given a basic snap is installed"
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
install_local test-snapd-devpts
echo "When no plugs are not connected"
diff --git a/tests/main/security-private-tmp/task.yaml b/tests/main/security-private-tmp/task.yaml
index 7b20a18cc8..d705327002 100644
--- a/tests/main/security-private-tmp/task.yaml
+++ b/tests/main/security-private-tmp/task.yaml
@@ -8,21 +8,23 @@ environment:
prepare: |
echo "Given a basic snap is installed"
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
install_local test-snapd-tools
echo "And another basic snap is installed"
- mkdir -p $SNAP_INSTALL_DIR
- cp -ra $TESTSLIB/snaps/test-snapd-tools/* $SNAP_INSTALL_DIR
- sed -i 's/test-snapd-tools/not-test-snapd-tools/g' $SNAP_INSTALL_DIR/meta/snap.yaml
- snap pack $SNAP_INSTALL_DIR
+ mkdir -p "$SNAP_INSTALL_DIR"
+ cp -ra "$TESTSLIB"/snaps/test-snapd-tools/* "$SNAP_INSTALL_DIR"
+ sed -i 's/test-snapd-tools/not-test-snapd-tools/g' "$SNAP_INSTALL_DIR/meta/snap.yaml"
+ snap pack "$SNAP_INSTALL_DIR"
snap install --dangerous not-test-snapd-tools_1.0_all.snap
restore: |
- rm -rf not-test-snapd-tools_1.0_all.snap "$SNAP_INSTALL_DIR" /tmp/foo *stat.error
+ rm -rf not-test-snapd-tools_1.0_all.snap "$SNAP_INSTALL_DIR" /tmp/foo ./*stat.error
execute: |
- . $TESTSLIB/dirs.sh
+ #shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB"/dirs.sh
echo "When a temporary file is created by one snap"
expect -d -f tmp-create.exp
diff --git a/tests/main/security-profiles/task.yaml b/tests/main/security-profiles/task.yaml
index 654b06c236..5b9380db6e 100644
--- a/tests/main/security-profiles/task.yaml
+++ b/tests/main/security-profiles/task.yaml
@@ -1,7 +1,7 @@
summary: Check security profile generation for apps and hooks.
prepare: |
- snap pack $TESTSLIB/snaps/basic-hooks
+ snap pack "$TESTSLIB"/snaps/basic-hooks
restore: |
rm -f basic-hooks_1.0_all.snap
@@ -13,13 +13,14 @@ execute: |
seccomp_profile_directory="/var/lib/snapd/seccomp/bpf"
echo "Security profiles are generated and loaded for apps"
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
install_local test-snapd-tools
loaded_profiles=$(cat /sys/kernel/security/apparmor/profiles)
for profile in snap.test-snapd-tools.block snap.test-snapd-tools.cat snap.test-snapd-tools.echo snap.test-snapd-tools.fail snap.test-snapd-tools.success
do
- MATCH "^${profile} \(enforce\)$" <<<"$loaded_profiles"
+ MATCH "^${profile} \\(enforce\\)$" <<<"$loaded_profiles"
[ -f "$seccomp_profile_directory/${profile}.bin" ]
done
@@ -27,5 +28,5 @@ execute: |
snap install --dangerous basic-hooks_1.0_all.snap
loaded_profiles=$(cat /sys/kernel/security/apparmor/profiles)
- echo "$loaded_profiles" | MATCH "^snap.basic-hooks.hook.configure \(enforce\)$"
+ echo "$loaded_profiles" | MATCH '^snap.basic-hooks.hook.configure \(enforce\)$'
[ -f "$seccomp_profile_directory/snap.basic-hooks.hook.configure.bin" ]
diff --git a/tests/main/security-setuid-root/task.yaml b/tests/main/security-setuid-root/task.yaml
index 486106cb17..1c963acb02 100644
--- a/tests/main/security-setuid-root/task.yaml
+++ b/tests/main/security-setuid-root/task.yaml
@@ -14,7 +14,8 @@ details: |
the usual location (/usr/lib/snapd/snap-confine). As a security precaution
it should detect and refuse to run if invoked from the core snap.
prepare: |
- . $TESTSLIB/snaps.sh
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
install_local test-snapd-tools
echo "Ensure the snap-confine profiles on core are not loaded"
for p in /var/lib/snapd/apparmor/profiles/snap-confine.*; do
@@ -23,10 +24,11 @@ prepare: |
restore: |
echo "Ensure the snap-confine profiles are restored"
for p in /var/lib/snapd/apparmor/profiles/snap-confine.*; do
- apparmor_parser -r $p
+ apparmor_parser -r "$p"
done
execute: |
- . $TESTSLIB/dirs.sh
+ #shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB"/dirs.sh
# NOTE: This has to run as the test user because the protection is only
# active if user gains elevated permissions as a result of using setuid
@@ -37,7 +39,7 @@ execute: |
fi
su test -c "sh -c \"SNAP_NAME=test-snapd-tools $SNAP_MOUNT_DIR/core/current/usr/lib/snapd/snap-confine snap.test-snapd-tools.cmd /bin/true 2>&1\"" | MATCH "Refusing to continue to avoid permission escalation attacks"
debug: |
- ls -ld $SNAP_MOUNT_DIR/core/current/usr/lib/snapd/snap-confine || true
- ls -ld $SNAP_MOUNT_DIR/ubuntu-core/current/usr/lib/snapd/snap-confine || true
+ ls -ld "$SNAP_MOUNT_DIR/core/current/usr/lib/snapd/snap-confine" || true
+ ls -ld "$SNAP_MOUNT_DIR/ubuntu-core/current/usr/lib/snapd/snap-confine" || true
ls -ld /usr/lib/snapd/snap-confine || true
snap list
diff --git a/tests/main/security-udev-input-subsystem/task.yaml b/tests/main/security-udev-input-subsystem/task.yaml
index 00e653e1d9..d49d35f7f2 100644
--- a/tests/main/security-udev-input-subsystem/task.yaml
+++ b/tests/main/security-udev-input-subsystem/task.yaml
@@ -50,7 +50,7 @@ execute: |
echo "When the mir plug is disconnected"
snap disconnect test-snapd-udev-input-subsystem:mir-plug test-snapd-udev-input-subsystem:mir-slot
- snap interfaces -i mir | MATCH "\- +test-snapd-udev-input-subsystem:mir-plug"
+ snap interfaces -i mir | MATCH -- '- +test-snapd-udev-input-subsystem:mir-plug'
echo "The snap's plug still cannot access an evdev keyboard"
if test-snapd-udev-input-subsystem.plug 2>"${PWD}"/call.error; then
@@ -61,7 +61,7 @@ execute: |
MATCH "Permission denied" < call.error
echo "When the time-control plug is disconnected"
- snap interfaces -i time-control | MATCH ":time-control +\-"
+ snap interfaces -i time-control | MATCH ':time-control +-'
echo "The snap's time-control plug cannot access an evdev keyboard when disconnected"
if test-snapd-udev-input-subsystem.plug-with-time-control 2>"${PWD}"/call.error; then
diff --git a/tests/main/server-snap/task.yaml b/tests/main/server-snap/task.yaml
index 2078982797..c85e58ac1c 100644
--- a/tests/main/server-snap/task.yaml
+++ b/tests/main/server-snap/task.yaml
@@ -18,14 +18,14 @@ environment:
warn-timeout: 3m
prepare: |
- snap install $SNAP_NAME
+ snap install "$SNAP_NAME"
cat > request.txt <<EOF
GET / HTTP/1.0
EOF
echo "Wait for the service to be listening, limited to the task kill-timeout"
# shellcheck source=tests/lib/network.sh
- . $TESTSLIB/network.sh
+ . "$TESTSLIB"/network.sh
wait_listen_port "$PORT"
restore: |
@@ -34,6 +34,6 @@ restore: |
execute: |
response=$(nc -w 5 -"$IP_VERSION" "$LOCALHOST" "$PORT" < request.txt)
- statusPattern="(?s)HTTP\/1\.0 200 OK\n*"
+ statusPattern='(?s)HTTP\/1\.0 200 OK\n*'
echo "$response" | grep -Pzq "$statusPattern"
echo "$response" | grep -Pzq "$TEXT"
diff --git a/tests/main/set-proxy-store/task.yaml b/tests/main/set-proxy-store/task.yaml
index e74c23edda..60b4055670 100644
--- a/tests/main/set-proxy-store/task.yaml
+++ b/tests/main/set-proxy-store/task.yaml
@@ -12,10 +12,11 @@ prepare: |
fi
echo "Given a snap is installed"
- snap install $SNAP_NAME
+ snap install "$SNAP_NAME"
- . $TESTSLIB/store.sh
- setup_fake_store $BLOB_DIR
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ setup_fake_store "$BLOB_DIR"
# undo the setup through envvars
systemctl stop snapd.service snapd.socket
rm /etc/systemd/system/snapd.service.d/store.conf
@@ -23,16 +24,18 @@ prepare: |
systemctl start snapd.socket
# prepare bundle
- cat $TESTSLIB/assertions/testrootorg-store.account-key >fake.store
+ cat "$TESTSLIB"/assertions/testrootorg-store.account-key >fake.store
+ #shellcheck disable=SC2129
echo >>fake.store
- cat $TESTSLIB/assertions/developer1.account >>fake.store
+ cat "$TESTSLIB"/assertions/developer1.account >>fake.store
+ #shellcheck disable=SC2129
echo >>fake.store
- cat $TESTSLIB/assertions/fake.store >>fake.store
+ cat "$TESTSLIB"/assertions/fake.store >>fake.store
echo "Ack fake store assertion"
snap ack fake.store
echo "And a new version of that snap put in the controlled store"
- init_fake_refreshes $BLOB_DIR $SNAP_NAME
+ init_fake_refreshes "$BLOB_DIR" "$SNAP_NAME"
restore: |
rm -f fake.store
@@ -44,8 +47,9 @@ restore: |
snap set core proxy.store=
- . $TESTSLIB/store.sh
- teardown_fake_store $BLOB_DIR
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ teardown_fake_store "$BLOB_DIR"
execute: |
if [ "$TRUST_TEST_KEYS" = "false" ]; then
@@ -68,7 +72,7 @@ execute: |
snap set core proxy.store=fake
echo "Now we can proceed with the refresh from the fakestore"
- snap refresh $SNAP_NAME
+ snap refresh "$SNAP_NAME"
echo "Then the new version is listed"
snap list | grep -Pzq "$expected"
diff --git a/tests/main/snap-advise-command/task.yaml b/tests/main/snap-advise-command/task.yaml
index 1a68a9b5c4..e07b7faa86 100644
--- a/tests/main/snap-advise-command/task.yaml
+++ b/tests/main/snap-advise-command/task.yaml
@@ -15,7 +15,7 @@ restore: |
execute: |
echo "wait for snapd to pull in the commands data"
echo "(it will do that on startup)"
- for i in $(seq 120); do
+ for _ in $(seq 120); do
if stat /var/cache/snapd/commands.db; then
break
fi
@@ -23,17 +23,17 @@ execute: |
done
stat /var/cache/snapd/commands.db
echo "Ensure the database is readable by a regular user"
- if [ $(stat -c "%a" /var/cache/snapd/commands.db) != "644" ]; then
+ if [ "$(stat -c '%a' /var/cache/snapd/commands.db)" != "644" ]; then
echo "incorrect permissions for /var/cache/snapd/commands.db"
echo "expected 0644 got:"
stat /var/cache/snapd/commands.db
exit 1
fi
- echo "Ensure `snap advise-snap --command` lookup works"
+ echo "Ensure 'snap advise-snap --command' lookup works"
snap advise-snap --command test-snapd-tools.echo | MATCH test-snapd-tools
- echo "Ensure `advise-snap --command` works as command-not-found symlink"
+ echo "Ensure 'advise-snap --command' works as command-not-found symlink"
ln -s /usr/bin/snap /usr/lib/command-not-found
/usr/lib/command-not-found test-snapd-tools.echo | MATCH test-snapd-tools
diff --git a/tests/unit/spread-shellcheck/can-fail b/tests/unit/spread-shellcheck/can-fail
index eeea62d9ba..dd7bf5ef9e 100644
--- a/tests/unit/spread-shellcheck/can-fail
+++ b/tests/unit/spread-shellcheck/can-fail
@@ -1,57 +1,3 @@
-tests/main/interfaces-snapd-control-with-manage/task.yaml
-tests/main/interfaces-ssh-keys/task.yaml
-tests/main/interfaces-ssh-public-keys/task.yaml
-tests/main/interfaces-system-observe/task.yaml
-tests/main/interfaces-time-control/task.yaml
-tests/main/interfaces-timezone-control/task.yaml
-tests/main/interfaces-udev/task.yaml
-tests/main/interfaces-uhid/task.yaml
-tests/main/interfaces-upower-observe/task.yaml
-tests/main/interfaces-wayland/task.yaml
-tests/main/kernel-snap-refresh-on-core/task.yaml
-tests/main/known-remote/task.yaml
-tests/main/layout/task.yaml
-tests/main/listing/task.yaml
-tests/main/login/task.yaml
-tests/main/lxd/task.yaml
-tests/main/media-sharing/task.yaml
-tests/main/nfs-support/task.yaml
-tests/main/op-install-failed-undone/task.yaml
-tests/main/op-remove-retry/task.yaml
-tests/main/op-remove/task.yaml
-tests/main/postrm-purge/task.yaml
-tests/main/prefer/task.yaml
-tests/main/prepare-image-grub/task.yaml
-tests/main/prepare-image-uboot/task.yaml
-tests/main/refresh-all-undo/task.yaml
-tests/main/refresh-all/task.yaml
-tests/main/refresh-amend/task.yaml
-tests/main/refresh-delta-from-core/task.yaml
-tests/main/refresh-delta/task.yaml
-tests/main/refresh-devmode/task.yaml
-tests/main/refresh-undo/task.yaml
-tests/main/refresh/task.yaml
-tests/main/regression-home-snap-root-owned/task.yaml
-tests/main/remove-errors/task.yaml
-tests/main/revert-devmode/task.yaml
-tests/main/revert-sideload/task.yaml
-tests/main/revert/task.yaml
-tests/main/searching/task.yaml
-tests/main/security-apparmor/task.yaml
-tests/main/security-device-cgroups-classic/task.yaml
-tests/main/security-device-cgroups-devmode/task.yaml
-tests/main/security-device-cgroups-jailmode/task.yaml
-tests/main/security-device-cgroups-serial-port/task.yaml
-tests/main/security-device-cgroups-strict/task.yaml
-tests/main/security-device-cgroups/task.yaml
-tests/main/security-devpts/task.yaml
-tests/main/security-private-tmp/task.yaml
-tests/main/security-profiles/task.yaml
-tests/main/security-setuid-root/task.yaml
-tests/main/security-udev-input-subsystem/task.yaml
-tests/main/server-snap/task.yaml
-tests/main/set-proxy-store/task.yaml
-tests/main/snap-advise-command/task.yaml
tests/main/snap-auto-import-asserts-spools/task.yaml
tests/main/snap-auto-import-asserts/task.yaml
tests/main/snap-auto-mount/task.yaml
diff --git a/testutil/dbustest.go b/testutil/dbustest.go
index 17c7036732..b8c6afe1ed 100644
--- a/testutil/dbustest.go
+++ b/testutil/dbustest.go
@@ -20,6 +20,7 @@
package testutil
import (
+ "bufio"
"fmt"
"os"
"os/exec"
@@ -50,10 +51,17 @@ func (s *DBusTest) SetUpSuite(c *C) {
}
s.tmpdir = c.MkDir()
- s.dbusDaemon = exec.Command("dbus-daemon", "--session", fmt.Sprintf("--address=unix:%s/user_bus_socket", s.tmpdir))
- err := s.dbusDaemon.Start()
+ s.dbusDaemon = exec.Command("dbus-daemon", "--session", "--print-address", fmt.Sprintf("--address=unix:path=%s/user_bus_socket", s.tmpdir))
+ pout, err := s.dbusDaemon.StdoutPipe()
c.Assert(err, IsNil)
+ err = s.dbusDaemon.Start()
+ c.Assert(err, IsNil)
+
+ scanner := bufio.NewScanner(pout)
+ scanner.Scan()
+ c.Assert(scanner.Err(), IsNil)
s.oldSessionBusEnv = os.Getenv("DBUS_SESSION_BUS_ADDRESS")
+ os.Setenv("DBUS_SESSION_BUS_ADDRESS", scanner.Text())
s.SessionBus, err = dbus.SessionBus()
c.Assert(err, IsNil)
@@ -64,6 +72,8 @@ func (s *DBusTest) TearDownSuite(c *C) {
if s.dbusDaemon != nil && s.dbusDaemon.Process != nil {
err := s.dbusDaemon.Process.Kill()
c.Assert(err, IsNil)
+ err = s.dbusDaemon.Wait() // do cleanup
+ c.Assert(err, ErrorMatches, `signal: killed`)
}
}