summaryrefslogtreecommitdiff
diff options
authorZygmunt Krynicki <zygmunt.krynicki@canonical.com>2017-04-18 10:21:16 +0200
committerZygmunt Krynicki <zygmunt.krynicki@canonical.com>2017-04-18 10:21:16 +0200
commit7357dac122cc5164651704b2f02f9b7a6a89fa99 (patch)
tree6fe68b02da9172f1a749528490523cf0a79ab06c
parentbe6f4abcb5dbde5afd840b231a3b516fb664f757 (diff)
parentc8bd09c6eedf152f08739511a8295344e5d8a671 (diff)
Merge branch 'master' of github.com:snapcore/snapd into retry-logsretry-logs
-rw-r--r--.gitignore6
-rw-r--r--.travis.yml3
-rw-r--r--cmd/configure.ac3
-rw-r--r--cmd/snap-confine/mount-support.c21
-rw-r--r--cmd/snap-confine/seccomp-support.c12
-rw-r--r--cmd/snap-confine/snap-confine.apparmor.in1
-rw-r--r--cmd/snap-confine/snap-confine.c11
-rw-r--r--cmd/snap-update-ns/bootstrap.c187
-rw-r--r--cmd/snap-update-ns/bootstrap.go85
-rw-r--r--cmd/snap-update-ns/bootstrap.h34
-rw-r--r--cmd/snap-update-ns/bootstrap_test.go108
-rw-r--r--cmd/snap-update-ns/export_test.go27
-rw-r--r--cmd/snap-update-ns/main.go16
-rw-r--r--cmd/snap/cmd_auto_import.go4
-rw-r--r--cmd/snap/cmd_changes.go22
-rw-r--r--cmd/snap/cmd_changes_test.go18
-rw-r--r--cmd/snap/cmd_run.go20
-rw-r--r--cmd/snap/cmd_run_test.go62
-rw-r--r--cmd/snap/cmd_snap_op.go16
-rw-r--r--cmd/snap/export_test.go9
-rw-r--r--daemon/api.go24
-rw-r--r--daemon/api_test.go203
-rw-r--r--daemon/snap.go29
-rw-r--r--data/systemd/Makefile2
-rw-r--r--dirs/dirs.go21
-rw-r--r--dirs/dirs_test.go27
-rw-r--r--errtracker/errtracker.go10
-rw-r--r--errtracker/errtracker_test.go40
-rw-r--r--interfaces/apparmor/backend.go76
-rw-r--r--interfaces/apparmor/backend_test.go86
-rw-r--r--interfaces/apparmor/template.go2
-rw-r--r--interfaces/builtin/all.go3
-rw-r--r--interfaces/builtin/all_test.go3
-rw-r--r--interfaces/builtin/basedeclaration.go8
-rw-r--r--interfaces/builtin/basedeclaration_test.go21
-rw-r--r--interfaces/builtin/browser_support.go19
-rw-r--r--interfaces/builtin/browser_support_test.go12
-rw-r--r--interfaces/builtin/core_support.go9
-rw-r--r--interfaces/builtin/docker_support.go2
-rw-r--r--interfaces/builtin/firewall_control.go23
-rw-r--r--interfaces/builtin/firewall_control_test.go2
-rw-r--r--interfaces/builtin/kernel_module_control.go3
-rw-r--r--interfaces/builtin/kubernetes_support.go83
-rw-r--r--interfaces/builtin/kubernetes_support_test.go104
-rw-r--r--interfaces/builtin/log_observe.go1
-rw-r--r--interfaces/builtin/mir.go2
-rw-r--r--interfaces/builtin/pulseaudio.go4
-rw-r--r--interfaces/builtin/unity7.go113
-rw-r--r--interfaces/builtin/unity7_test.go21
-rw-r--r--interfaces/core.go4
-rw-r--r--interfaces/core_test.go4
-rw-r--r--interfaces/mount/entry.go51
-rw-r--r--interfaces/mount/entry_test.go48
-rw-r--r--interfaces/mount/mountinfo.go123
-rw-r--r--interfaces/mount/mountinfo_test.go119
-rw-r--r--interfaces/mount/profile.go105
-rw-r--r--interfaces/mount/profile_test.go127
-rw-r--r--interfaces/repo.go49
-rw-r--r--interfaces/repo_test.go90
-rw-r--r--osutil/buildid_test.go12
-rw-r--r--osutil/exec_test.go6
-rw-r--r--osutil/stat.go13
-rw-r--r--osutil/stat_test.go11
-rw-r--r--overlord/devicestate/crypto.go80
-rw-r--r--overlord/devicestate/devicestate_test.go19
-rw-r--r--overlord/devicestate/export_test.go4
-rw-r--r--overlord/devicestate/handlers.go7
-rw-r--r--overlord/ifacestate/helpers.go63
-rw-r--r--overlord/ifacestate/ifacestate_test.go162
-rw-r--r--overlord/managers_test.go254
-rw-r--r--overlord/overlord_test.go33
-rw-r--r--overlord/snapstate/aliases.go109
-rw-r--r--overlord/snapstate/aliases_test.go793
-rw-r--r--overlord/snapstate/aliasesv2.go92
-rw-r--r--overlord/snapstate/aliasesv2_test.go90
-rw-r--r--overlord/snapstate/backend_test.go17
-rw-r--r--overlord/snapstate/export_test.go3
-rw-r--r--overlord/snapstate/handlers.go198
-rw-r--r--overlord/snapstate/handlers_aliasesv2_test.go1372
-rw-r--r--overlord/snapstate/snapmgr.go15
-rw-r--r--overlord/snapstate/snapstate.go11
-rw-r--r--overlord/snapstate/snapstate_test.go28
-rw-r--r--packaging/ubuntu-14.04/changelog174
-rw-r--r--packaging/ubuntu-14.04/control2
-rw-r--r--packaging/ubuntu-16.04/changelog174
-rw-r--r--packaging/ubuntu-16.04/control2
-rw-r--r--packaging/ubuntu-16.04/snap-confine.maintscript1
-rw-r--r--packaging/ubuntu-16.04/snapd.postrm3
-rw-r--r--snap/broken.go35
-rw-r--r--snap/broken_test.go43
-rw-r--r--snap/export_test.go4
-rw-r--r--snap/implicit.go1
-rw-r--r--snap/info.go4
-rw-r--r--snap/info_snap_yaml.go3
-rw-r--r--snap/info_test.go67
-rw-r--r--snap/snaptest/snaptest.go17
-rw-r--r--snap/snaptest/snaptest_test.go17
-rw-r--r--spread.yaml9
-rw-r--r--store/store.go2
-rw-r--r--store/store_test.go281
-rw-r--r--tests/lib/prepare-project.sh28
-rwxr-xr-xtests/lib/prepare.sh31
-rw-r--r--tests/lib/snaps/test-snapd-kernel-module-control-consumer/snapcraft.yaml24
-rw-r--r--tests/main/alias/task.yaml6
-rw-r--r--tests/main/change-errors/task.yaml7
-rw-r--r--tests/main/classic-ubuntu-core-transition-auth/task.yaml11
-rw-r--r--tests/main/classic-ubuntu-core-transition-two-cores/task.yaml3
-rw-r--r--tests/main/classic-ubuntu-core-transition/task.yaml23
-rw-r--r--tests/main/interfaces-kernel-module-control/task.yaml96
-rw-r--r--tests/main/interfaces-network-bind/task.yaml2
-rw-r--r--tests/main/refresh-all-undo/task.yaml3
-rw-r--r--tests/main/security-setuid-root/task.yaml9
-rw-r--r--tests/main/snap-confine-from-core/task.yaml27
-rw-r--r--tests/main/snap-update-ns/task.yaml28
-rw-r--r--tests/nightly/unity/task.yaml7
115 files changed, 5503 insertions, 1371 deletions
diff --git a/.gitignore b/.gitignore
index d8f7b6891a..0afd0c6cb0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,9 +37,9 @@ data/systemd/*.service
*.log
*.trs
-# Automake
-Makefile
-Makefile.in
+# Automake for the cmd/ parts
+cmd/Makefile
+cmd/Makefile.in
snap-confine-*.tar.gz
.deps
diff --git a/.travis.yml b/.travis.yml
index e10101edc7..6c7d80a8b2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,6 +9,9 @@ env:
# SPREAD_LINODE_KEY
- secure: "bzALrfNSLwM0bjceal1PU5rFErvqVhi00Sygx8jruo6htpZay3hrC2sHCKCQKPn1kvCfHidrHX1vnomg5N+B9o25GZEYSjKSGxuvdNDfCZYqPNjMbz5y7xXYfKWgyo+xtrKRM85Nqy121SfRz3KLDvrOLwwreb+pZv8DG1WraFTd7D6rK7nLnnYNUyw665XBMFVnM8ue3Zu9496Ih/TfQXhnNpsZY8xFWte4+cH7JvVCVTs8snjoGVZi3972PzinNkfBgJa24cUzxFMfiN/AwSBXJQKdVv+FsbB4uRgXAqTNwuus7PptiPNxpWWojuhm1Qgbk0XhGIdJxyUYkmNA4UrZ3C29nIRWbuAiHJ6ZWd1ur3dqphqOcgFInltSHkpfEdlL3YK4dCa2SmJESzotUGnyowCUUCXkWdDaZmFTwyK0Y6He9oyXDK5f+/U7SFlPvok0caJCvB9HbTQR1kYdh048I/R+Ht5QrFOZPk21DYWDOYhn7SzthBDZLsaL6n5gX7Y547SsL4B35YVbpaeHzccG6Mox8rI4bqlGFvP1U5i8uXD4uQjJChlVxpmozUEMok9T5RVediJs540p5uc8DQl48Nke02tXzC/XpGAvpnXT7eiiRNW67zOj2QcIV+ni3lBj3HvZeB9cgjzLNrZSl/t9vseqnNwQWpl3V6nd/bU="
+git:
+ quiet: true
+
install:
- sudo apt-get update -qq
- sudo apt-get install -qq squashfs-tools xdelta3
diff --git a/cmd/configure.ac b/cmd/configure.ac
index e2b848bf00..39a6800349 100644
--- a/cmd/configure.ac
+++ b/cmd/configure.ac
@@ -23,7 +23,8 @@ AC_SYS_LARGEFILE
AC_CHECK_HEADERS([fcntl.h limits.h stdlib.h string.h sys/mount.h unistd.h])
AC_CHECK_HEADERS([sys/quota.h], [], [AC_MSG_ERROR(sys/quota.h unavailable)])
AC_CHECK_HEADERS([xfs/xqm.h], [], [AC_MSG_ERROR(xfs/xqm.h unavailable)],
-[[#define _FILE_OFFSET_BITS 64
+[[#define _GNU_SOURCE
+#define _FILE_OFFSET_BITS 64
#include <xfs/xqm.h>
]])
diff --git a/cmd/snap-confine/mount-support.c b/cmd/snap-confine/mount-support.c
index 4e681e9af2..2754f7afa5 100644
--- a/cmd/snap-confine/mount-support.c
+++ b/cmd/snap-confine/mount-support.c
@@ -127,15 +127,6 @@ static void setup_private_mount(const char *snap_name)
if (chdir(pwd) != 0)
die("cannot change current working directory to the original directory");
free(pwd);
-
- // ensure we set the various TMPDIRs to our newly created tmpdir
- const char *tmpd[] = { "TMPDIR", "TEMPDIR", NULL };
- int i;
- for (i = 0; tmpd[i] != NULL; i++) {
- if (setenv(tmpd[i], "/tmp", 1) != 0) {
- die("cannot set environment variable '%s'", tmpd[i]);
- }
- }
}
// TODO: fold this into bootstrap
@@ -240,7 +231,7 @@ struct sc_mount_config {
const char *rootfs_dir;
// The struct is terminated with an entry with NULL path.
const struct sc_mount *mounts;
- bool on_classic;
+ bool on_classic_distro;
};
/**
@@ -397,7 +388,7 @@ static void sc_bootstrap_mount_namespace(const struct sc_mount_config *config)
// uniform way after pivot_root but this is good enough and requires less
// code changes the nvidia code assumes it has access to the existing
// pre-pivot filesystem.
- if (config->on_classic) {
+ if (config->on_classic_distro) {
sc_mount_nvidia_driver(scratch_dir);
}
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
@@ -527,8 +518,8 @@ void sc_populate_mount_ns(const char *snap_name)
die("cannot get the current working directory");
}
// Remember if we are on classic, some things behave differently there.
- bool on_classic = is_running_on_classic_distribution();
- if (on_classic) {
+ bool on_classic_distro = is_running_on_classic_distribution();
+ if (on_classic_distro) {
const struct sc_mount mounts[] = {
{"/dev"}, // because it contains devices on host OS
{"/etc"}, // because that's where /etc/resolv.conf lives, perhaps a bad idea
@@ -555,7 +546,7 @@ void sc_populate_mount_ns(const char *snap_name)
struct sc_mount_config classic_config = {
.rootfs_dir = sc_get_outer_core_mount_point(),
.mounts = mounts,
- .on_classic = true,
+ .on_classic_distro = true,
};
sc_bootstrap_mount_namespace(&classic_config);
} else {
@@ -585,7 +576,7 @@ void sc_populate_mount_ns(const char *snap_name)
setup_private_pts();
// setup quirks for specific snaps
- if (on_classic) {
+ if (on_classic_distro) {
sc_setup_quirks();
}
// setup the security backend bind mounts
diff --git a/cmd/snap-confine/seccomp-support.c b/cmd/snap-confine/seccomp-support.c
index a08b23af9b..031f465ebd 100644
--- a/cmd/snap-confine/seccomp-support.c
+++ b/cmd/snap-confine/seccomp-support.c
@@ -34,9 +34,19 @@
#include <sys/types.h>
#include <sys/utsname.h>
#include <termios.h>
-#include <xfs/xqm.h>
#include <unistd.h>
+// The XFS interface requires a 64 bit file system interface
+// but we don't want to leak this anywhere else if not globally
+// defined.
+#ifndef _FILE_OFFSET_BITS
+#define _FILE_OFFSET_BITS 64
+#include <xfs/xqm.h>
+#undef _FILE_OFFSET_BITS
+#else
+#include <xfs/xqm.h>
+#endif
+
#include <seccomp.h>
#include "../libsnap-confine-private/secure-getenv.h"
diff --git a/cmd/snap-confine/snap-confine.apparmor.in b/cmd/snap-confine/snap-confine.apparmor.in
index 1413cf24cd..089196eaae 100644
--- a/cmd/snap-confine/snap-confine.apparmor.in
+++ b/cmd/snap-confine/snap-confine.apparmor.in
@@ -30,6 +30,7 @@
/dev/random r,
/dev/urandom r,
/dev/pts/[0-9]* rw,
+ /dev/tty rw,
# cgroups
capability sys_admin,
diff --git a/cmd/snap-confine/snap-confine.c b/cmd/snap-confine/snap-confine.c
index 9d07939942..cc5a716aff 100644
--- a/cmd/snap-confine/snap-confine.c
+++ b/cmd/snap-confine/snap-confine.c
@@ -175,6 +175,17 @@ int main(int argc, char **argv)
"/usr/bin:"
"/sbin:"
"/bin:" "/usr/games:" "/usr/local/games", 1);
+ // Ensure we set the various TMPDIRs to /tmp.
+ // One of the parts of setting up the mount namespace is to create a private /tmp
+ // directory (this is done in sc_populate_mount_ns() above). The host environment
+ // may point to a directory not accessible by snaps so we need to reset it here.
+ const char *tmpd[] = { "TMPDIR", "TEMPDIR", NULL };
+ int i;
+ for (i = 0; tmpd[i] != NULL; i++) {
+ if (setenv(tmpd[i], "/tmp", 1) != 0) {
+ die("cannot set environment variable '%s'", tmpd[i]);
+ }
+ }
struct snappy_udev udev_s;
if (snappy_udev_init(security_tag, &udev_s) == 0)
setup_devices_cgroup(security_tag, &udev_s);
diff --git a/cmd/snap-update-ns/bootstrap.c b/cmd/snap-update-ns/bootstrap.c
new file mode 100644
index 0000000000..ada96cd517
--- /dev/null
+++ b/cmd/snap-update-ns/bootstrap.c
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2017 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/>.
+ *
+ */
+
+#include "bootstrap.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+// bootstrap_errno contains a copy of errno if a system call fails.
+int bootstrap_errno = 0;
+// bootstrap_msg contains a static string if something fails.
+const char* bootstrap_msg = NULL;
+
+// read_cmdline reads /proc/self/cmdline into the specified buffer, returning
+// number of bytes read.
+ssize_t read_cmdline(char* buf, size_t buf_size)
+{
+ int fd = open("/proc/self/cmdline", O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
+ if (fd < 0) {
+ bootstrap_errno = errno;
+ bootstrap_msg = "cannot open /proc/self/cmdline";
+ return -1;
+ }
+ memset(buf, 0, buf_size);
+ ssize_t num_read = read(fd, buf, buf_size);
+ if (num_read < 0) {
+ bootstrap_errno = errno;
+ bootstrap_msg = "cannot read /proc/self/cmdline";
+ } else if (num_read == buf_size) {
+ bootstrap_errno = 0;
+ bootstrap_msg = "cannot fit all of /proc/self/cmdline, buffer too small";
+ num_read = -1;
+ }
+ close(fd);
+ return num_read;
+}
+
+// find_argv0 scans the command line buffer and looks for the 0st argument.
+const char*
+find_argv0(char* buf, size_t num_read)
+{
+ // cmdline is an array of NUL ('\0') separated strings.
+ size_t argv0_len = strnlen(buf, num_read);
+ if (argv0_len == num_read) {
+ // ensure that the buffer is properly terminated.
+ return NULL;
+ }
+ return buf;
+}
+
+// find_snap_name scans the command line buffer and looks for the 1st argument.
+// if the 1st argument exists but is empty NULL is returned.
+const char*
+find_snap_name(char* buf, size_t num_read)
+{
+ // cmdline is an array of NUL ('\0') separated strings. We can skip over
+ // the first entry (program name) and look at the second entry, in our case
+ // it should be the snap name.
+ size_t argv0_len = strnlen(buf, num_read);
+ if (argv0_len + 1 >= num_read) {
+ return NULL;
+ }
+ char* snap_name = &buf[argv0_len + 1];
+ if (*snap_name == '\0') {
+ return NULL;
+ }
+ return snap_name;
+}
+
+// setns_into_snap switches mount namespace into that of a given snap.
+static int
+setns_into_snap(const char* snap_name)
+{
+ // Construct the name of the .mnt file to open.
+ char buf[PATH_MAX];
+ int n = snprintf(buf, sizeof buf, "/run/snapd/ns/%s.mnt", snap_name);
+ if (n >= sizeof buf || n < 0) {
+ bootstrap_errno = errno;
+ bootstrap_msg = "cannot format mount namespace file name";
+ return -1;
+ }
+
+ // Open the mount namespace file, note that we don't specify O_NOFOLLOW as
+ // that file is always a special, broken symbolic link.
+ int fd = open(buf, O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ bootstrap_errno = errno;
+ bootstrap_msg = "cannot open mount namespace file";
+ return -1;
+ }
+
+ // Switch to the mount namespace of the given snap.
+ int err = setns(fd, CLONE_NEWNS);
+ if (err < 0) {
+ bootstrap_errno = errno;
+ bootstrap_msg = "cannot switch mount namespace";
+ };
+
+ close(fd);
+ return err;
+}
+
+// partially_validate_snap_name performs partial validation of the given name.
+// The goal is to ensure that there are no / or .. in the name.
+int partially_validate_snap_name(const char* snap_name)
+{
+ // NOTE: neither set bootstrap_{msg,errno} but the return value means that
+ // bootstrap does nothing. The name is re-validated by golang.
+ if (strstr(snap_name, "..") != NULL) {
+ return -1;
+ }
+ if (strchr(snap_name, '/') != NULL) {
+ return -1;
+ }
+}
+
+// bootstrap prepares snap-update-ns to work in the namespace of the snap given
+// on command line.
+void bootstrap(void)
+{
+ // We don't have argc/argv so let's imitate that by reading cmdline
+ char cmdline[1024];
+ memset(cmdline, 0, sizeof cmdline);
+ ssize_t num_read;
+ if ((num_read = read_cmdline(cmdline, sizeof cmdline)) < 0) {
+ return;
+ }
+
+ // Find the name of the called program. If it is ending with "-test" then do nothing.
+ // NOTE: This lets us use cgo/go to write tests without running the bulk
+ // of the code automatically. In snapd we can just set the required
+ // environment variable.
+ const char* argv0 = find_argv0(cmdline, (size_t)num_read);
+ if (argv0 == NULL) {
+ bootstrap_errno = 0;
+ bootstrap_msg = "argv0 is corrupted";
+ return;
+ }
+ const char* argv0_suffix_maybe = strstr(argv0, ".test");
+ if (argv0_suffix_maybe != NULL && argv0_suffix_maybe[strlen(".test")] == '\0') {
+ bootstrap_errno = 0;
+ bootstrap_msg = "bootstrap is not enabled while testing";
+ return;
+ }
+
+ // Find the name of the snap by scanning the cmdline. If there's no snap
+ // name given, just bail out. The go parts will scan this too.
+ const char* snap_name = find_snap_name(cmdline, (size_t)num_read);
+ if (snap_name == NULL) {
+ return;
+ }
+
+ // Look for known offenders in the snap name (.. and /) and do nothing if
+ // those are found. The golang code will validate snap name and print a
+ // proper error message but this just ensures we don't try to open / setns
+ // anything unusual.
+ if (partially_validate_snap_name(snap_name) < 0) {
+ return;
+ }
+
+ // Switch the mount namespace to that of the snap given on command line.
+ if (setns_into_snap(snap_name) < 0) {
+ return;
+ }
+}
diff --git a/cmd/snap-update-ns/bootstrap.go b/cmd/snap-update-ns/bootstrap.go
new file mode 100644
index 0000000000..b7239c0b03
--- /dev/null
+++ b/cmd/snap-update-ns/bootstrap.go
@@ -0,0 +1,85 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2017 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package main
+
+// Use a pre-main helper to switch the mount namespace. This is required as
+// golang creates threads at will and setns(..., CLONE_NEWNS) fails if any
+// threads apart from the main thread exist.
+
+/*
+
+#include <stdlib.h>
+#include "bootstrap.h"
+
+__attribute__((constructor)) static void init(void) {
+ bootstrap();
+}
+
+// NOTE: do not add anything before the following `import "C"'
+*/
+import "C"
+
+import (
+ "fmt"
+ "syscall"
+ "unsafe"
+)
+
+// Error returns error (if any) encountered in pre-main C code.
+func BootstrapError() error {
+ if C.bootstrap_msg == nil {
+ return nil
+ }
+ errno := syscall.Errno(C.bootstrap_errno)
+ if errno != 0 {
+ return fmt.Errorf("%s: %s", C.GoString(C.bootstrap_msg), errno)
+ }
+ return fmt.Errorf("%s", C.GoString(C.bootstrap_msg))
+}
+
+// readCmdline is a wrapper around the C function read_cmdline.
+func readCmdline(buf []byte) C.ssize_t {
+ return C.read_cmdline((*C.char)(unsafe.Pointer(&buf[0])), C.size_t(cap(buf)))
+}
+
+// findArgv0 parses the argv-like array and finds the 0st argument.
+func findArgv0(buf []byte) *string {
+ if ptr := C.find_argv0((*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))); ptr != nil {
+ str := C.GoString(ptr)
+ return &str
+ }
+ return nil
+}
+
+// findSnapName parses the argv-like array and finds the 1st argument.
+func findSnapName(buf []byte) *string {
+ if ptr := C.find_snap_name((*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))); ptr != nil {
+ str := C.GoString(ptr)
+ return &str
+ }
+ return nil
+}
+
+// partiallyValidateSnapName checks if snap name is seemingly valid.
+// The real part of the validation happens on the go side.
+func partiallyValidateSnapName(snapName string) int {
+ cStr := C.CString(snapName)
+ return int(C.partially_validate_snap_name(cStr))
+}
diff --git a/cmd/snap-update-ns/bootstrap.h b/cmd/snap-update-ns/bootstrap.h
new file mode 100644
index 0000000000..4ae11368d9
--- /dev/null
+++ b/cmd/snap-update-ns/bootstrap.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 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/>.
+ *
+ */
+
+#ifndef SNAPD_CMD_SNAP_UPDATE_NS_H
+#define SNAPD_CMD_SNAP_UPDATE_NS_H
+
+#define _GNU_SOURCE
+
+#include <unistd.h>
+
+extern int bootstrap_errno;
+extern const char* bootstrap_msg;
+
+void bootstrap(void);
+ssize_t read_cmdline(char* buf, size_t buf_size);
+const char* find_argv0(char* buf, size_t num_read);
+const char* find_snap_name(char* buf, size_t num_read);
+int partially_validate_snap_name(const char* snap_name);
+
+#endif
diff --git a/cmd/snap-update-ns/bootstrap_test.go b/cmd/snap-update-ns/bootstrap_test.go
new file mode 100644
index 0000000000..a333ddd639
--- /dev/null
+++ b/cmd/snap-update-ns/bootstrap_test.go
@@ -0,0 +1,108 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2017 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package main_test
+
+import (
+ "strings"
+
+ . "gopkg.in/check.v1"
+
+ update "github.com/snapcore/snapd/cmd/snap-update-ns"
+)
+
+type bootstrapSuite struct{}
+
+var _ = Suite(&bootstrapSuite{})
+
+func (s *bootstrapSuite) TestReadCmdLine(c *C) {
+ buf := make([]byte, 1024)
+ numRead := update.ReadCmdline(buf)
+ c.Assert(numRead, Not(Equals), -1)
+ c.Assert(numRead, Not(Equals), 1)
+ // Individual arguments are separated with NUL byte.
+ argv := strings.Split(string(buf[0:numRead]), "\x00")
+ // Smoke test, the actual value looks like
+ // "/tmp/go-build020699516/github.com/snapcore/snapd/cmd/snap-update-ns/_test/snap-update-ns.test"
+ c.Assert(strings.HasSuffix(argv[0], "snap-update-ns.test"), Equals, true, Commentf("argv[0] is %q", argv[0]))
+}
+
+// Check that if there is only one argument we return nil.
+func (s *bootstrapSuite) TestFindSnapName1(c *C) {
+ buf := []byte("arg0\x00")
+ result := update.FindSnapName(buf)
+ c.Assert(result, Equals, (*string)(nil))
+}
+
+// Check that if there are multiple arguments we return the 2nd one.
+func (s *bootstrapSuite) TestFindSnapName2(c *C) {
+ buf := []byte("arg0\x00arg1\x00arg2\x00")
+ result := update.FindSnapName(buf)
+ c.Assert(result, Not(Equals), (*string)(nil))
+ c.Assert(*result, Equals, "arg1")
+}
+
+// Check that if the 1st argument in the buffer is not terminated we don't crash.
+func (s *bootstrapSuite) TestFindSnapName3(c *C) {
+ buf := []byte("arg0")
+ result := update.FindSnapName(buf)
+ c.Assert(result, Equals, (*string)(nil))
+}
+
+// Check that if the 2nd argument in the buffer is not terminated we don't crash.
+func (s *bootstrapSuite) TestFindSnapName4(c *C) {
+ buf := []byte("arg0\x00arg1")
+ result := update.FindSnapName(buf)
+ c.Assert(result, Not(Equals), (*string)(nil))
+ c.Assert(*result, Equals, "arg1")
+}
+
+// Check that if the 2nd argument an empty string we return NULL.
+func (s *bootstrapSuite) TestFindSnapName5(c *C) {
+ buf := []byte("arg0\x00\x00")
+ result := update.FindSnapName(buf)
+ c.Assert(result, Equals, (*string)(nil))
+}
+
+// Check that if argv0 is returned as expected
+func (s *bootstrapSuite) TestFindArgv0(c *C) {
+ buf := []byte("arg0\x00argv1\x00")
+ result := update.FindArgv0(buf)
+ c.Assert(result, NotNil)
+ c.Assert(*result, Equals, "arg0")
+}
+
+// Check that if argv0 is unterminated we return NULL.
+func (s *bootstrapSuite) TestFindArgv0Unterminated(c *C) {
+ buf := []byte("arg0")
+ result := update.FindArgv0(buf)
+ c.Assert(result, Equals, (*string)(nil))
+}
+
+// Check that PartiallyValidateSnapName rejects "/" and "..".
+func (s *bootstrapSuite) TestPartiallyValidateSnapName(c *C) {
+ c.Assert(update.PartiallyValidateSnapName("hello-world"), Equals, 0)
+ c.Assert(update.PartiallyValidateSnapName("hello/world"), Equals, -1)
+ c.Assert(update.PartiallyValidateSnapName("hello..world"), Equals, -1)
+}
+
+// Check that pre-go bootstrap code is disabled while testing.
+func (s *bootstrapSuite) TestBootstrapDisabled(c *C) {
+ c.Assert(update.BootstrapError(), ErrorMatches, "bootstrap is not enabled while testing")
+}
diff --git a/cmd/snap-update-ns/export_test.go b/cmd/snap-update-ns/export_test.go
new file mode 100644
index 0000000000..b9edb3db9e
--- /dev/null
+++ b/cmd/snap-update-ns/export_test.go
@@ -0,0 +1,27 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2017 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package main
+
+var (
+ ReadCmdline = readCmdline
+ FindArgv0 = findArgv0
+ FindSnapName = findSnapName
+ PartiallyValidateSnapName = partiallyValidateSnapName
+)
diff --git a/cmd/snap-update-ns/main.go b/cmd/snap-update-ns/main.go
index a1d62dc471..e153baa554 100644
--- a/cmd/snap-update-ns/main.go
+++ b/cmd/snap-update-ns/main.go
@@ -24,6 +24,8 @@ import (
"os"
"github.com/jessevdk/go-flags"
+
+ "github.com/snapcore/snapd/snap"
)
var opts struct {
@@ -41,14 +43,22 @@ func main() {
func parseArgs(args []string) error {
parser := flags.NewParser(&opts, flags.HelpFlag|flags.PassDoubleDash|flags.PassAfterNonOption)
- _, err := parser.ParseArgs(args)
- return err
+ if _, err := parser.ParseArgs(args); err != nil {
+ return err
+ }
+ return snap.ValidateName(opts.Positionals.SnapName)
}
func run() error {
if err := parseArgs(os.Args[1:]); err != nil {
return err
}
-
+ // There is some C code that runs before main() is started.
+ // That code always runs and sets an error condition if it fails.
+ // Here we just check for the error.
+ if err := BootstrapError(); err != nil {
+ return err
+ }
+ // TODO: implement this
return fmt.Errorf("not implemented")
}
diff --git a/cmd/snap/cmd_auto_import.go b/cmd/snap/cmd_auto_import.go
index 542ccb2e2a..cac935f57c 100644
--- a/cmd/snap/cmd_auto_import.go
+++ b/cmd/snap/cmd_auto_import.go
@@ -84,6 +84,10 @@ func autoImportCandidates() ([]string, error) {
if strings.HasPrefix(mountSrc, "/dev/loop") {
continue
}
+ // skip all ram disks (unless in tests)
+ if !osutil.GetenvBool("SNAPPY_TESTING") && strings.HasPrefix(mountSrc, "/dev/ram") {
+ continue
+ }
mountPoint := l[4]
cand := filepath.Join(mountPoint, autoImportsName)
diff --git a/cmd/snap/cmd_changes.go b/cmd/snap/cmd_changes.go
index 220c27e15c..adceb7cba1 100644
--- a/cmd/snap/cmd_changes.go
+++ b/cmd/snap/cmd_changes.go
@@ -50,9 +50,16 @@ type cmdChange struct {
} `positional-args:"yes"`
}
+type cmdTasks struct {
+ Positional struct {
+ ID changeID `positional-arg-name:"<id>" required:"yes"`
+ } `positional-args:"yes"`
+}
+
func init() {
addCommand("changes", shortChangesHelp, longChangesHelp, func() flags.Commander { return &cmdChanges{} }, nil, nil)
- addCommand("change", shortChangeHelp, longChangeHelp, func() flags.Commander { return &cmdChange{} }, nil, nil)
+ addCommand("change", shortChangeHelp, longChangeHelp, func() flags.Commander { return &cmdChange{} }, nil, nil).hidden = true
+ addCommand("tasks", shortChangeHelp, longChangeHelp, func() flags.Commander { return &cmdTasks{} }, nil, nil)
}
type changesByTime []*client.Change
@@ -70,7 +77,7 @@ func (c *cmdChanges) Execute(args []string) error {
if allDigits(c.Positional.Snap) {
// TRANSLATORS: the %s is the argument given by the user to "snap changes"
- return fmt.Errorf(i18n.G(`"snap changes" command expects a snap name, try: "snap change %s"`), c.Positional.Snap)
+ return fmt.Errorf(i18n.G(`"snap changes" command expects a snap name, try: "snap tasks %s"`), c.Positional.Snap)
}
if c.Positional.Snap == "everything" {
@@ -113,9 +120,18 @@ func (c *cmdChanges) Execute(args []string) error {
return nil
}
+func (c *cmdTasks) Execute([]string) error {
+ cli := Client()
+ return showChange(cli, c.Positional.ID)
+}
+
func (c *cmdChange) Execute([]string) error {
cli := Client()
- chg, err := cli.Change(string(c.Positional.ID))
+ return showChange(cli, c.Positional.ID)
+}
+
+func showChange(cli *client.Client, chid changeID) error {
+ chg, err := cli.Change(string(chid))
if err != nil {
return err
}
diff --git a/cmd/snap/cmd_changes_test.go b/cmd/snap/cmd_changes_test.go
index f044ef7ef1..5c4b7690f1 100644
--- a/cmd/snap/cmd_changes_test.go
+++ b/cmd/snap/cmd_changes_test.go
@@ -42,23 +42,29 @@ var mockChangeJSON = `{"type": "sync", "result": {
func (s *SnapSuite) TestChangeSimple(c *check.C) {
n := 0
s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
- switch n {
- case 0:
+ if n < 2 {
c.Check(r.Method, check.Equals, "GET")
c.Check(r.URL.Path, check.Equals, "/v2/changes/42")
fmt.Fprintln(w, mockChangeJSON)
- default:
+ } else {
c.Fatalf("expected to get 1 requests, now on %d", n+1)
}
n++
})
+ expectedChange := `(?ms)Status +Spawn +Ready +Summary
+Do +2016-04-21T01:02:03Z +2016-04-21T01:02:04Z +some summary
+`
rest, err := snap.Parser().ParseArgs([]string{"change", "42"})
c.Assert(err, check.IsNil)
c.Assert(rest, check.DeepEquals, []string{})
- c.Check(s.Stdout(), check.Matches, `(?ms)Status +Spawn +Ready +Summary
-Do +2016-04-21T01:02:03Z +2016-04-21T01:02:04Z +some summary
-`)
+ c.Check(s.Stdout(), check.Matches, expectedChange)
+ c.Check(s.Stderr(), check.Equals, "")
+
+ rest, err = snap.Parser().ParseArgs([]string{"tasks", "42"})
+ c.Assert(err, check.IsNil)
+ c.Assert(rest, check.DeepEquals, []string{})
+ c.Check(s.Stdout(), check.Matches, expectedChange)
c.Check(s.Stderr(), check.Equals, "")
}
diff --git a/cmd/snap/cmd_run.go b/cmd/snap/cmd_run.go
index b1244acd55..f210c89be9 100644
--- a/cmd/snap/cmd_run.go
+++ b/cmd/snap/cmd_run.go
@@ -216,6 +216,17 @@ func snapRunHook(snapName, snapRevision, hookName string) error {
return runSnapConfine(info, hook.SecurityTag(), snapName, "", hook.Name, nil)
}
+var osReadlink = os.Readlink
+
+func isReexeced() bool {
+ exe, err := osReadlink("/proc/self/exe")
+ if err != nil {
+ logger.Noticef("cannot read /proc/self/exe: %v", err)
+ return false
+ }
+ return strings.HasPrefix(exe, dirs.SnapMountDir)
+}
+
func runSnapConfine(info *snap.Info, securityTag, snapApp, command, hook string, args []string) error {
snapConfine := filepath.Join(dirs.DistroLibExecDir, "snap-confine")
if !osutil.FileExists(snapConfine) {
@@ -249,5 +260,14 @@ func runSnapConfine(info *snap.Info, securityTag, snapApp, command, hook string,
cmd = append(cmd, snapApp)
cmd = append(cmd, args...)
+ // if we re-exec, we must run the snap-confine from the core snap
+ // as well, if they get out of sync, havoc will happen
+ if isReexeced() {
+ // run snap-confine from the core snap. that will work because
+ // snap-confine on the core snap is mostly statically linked
+ // (except libudev and libc)
+ cmd[0] = filepath.Join(dirs.SnapMountDir, "core/current", cmd[0])
+ }
+
return syscallExec(cmd[0], cmd, snapenv.ExecEnv(info))
}
diff --git a/cmd/snap/cmd_run_test.go b/cmd/snap/cmd_run_test.go
index 4f80969f53..b2814420d5 100644
--- a/cmd/snap/cmd_run_test.go
+++ b/cmd/snap/cmd_run_test.go
@@ -437,3 +437,65 @@ func (s *SnapSuite) TestSnapRunSaneEnvironmentHandling(c *check.C) {
c.Check(execEnv, check.Not(testutil.Contains), "SNAP_ARCH=PDP-7")
c.Check(execEnv, testutil.Contains, "SNAP_THE_WORLD=YES")
}
+
+func (s *SnapSuite) TestSnapRunIsReexeced(c *check.C) {
+ var osReadlinkResult string
+ restore := snaprun.MockOsReadlink(func(name string) (string, error) {
+ return osReadlinkResult, nil
+ })
+ defer restore()
+
+ for _, t := range []struct {
+ readlink string
+ expected bool
+ }{
+ {filepath.Join(dirs.SnapMountDir, "/usr/lib/snapd/snapd"), true},
+ {"/usr/lib/snapd/snapd", false},
+ } {
+ osReadlinkResult = t.readlink
+ c.Check(snaprun.IsReexeced(), check.Equals, t.expected)
+ }
+}
+
+func (s *SnapSuite) TestSnapRunAppIntegrationFromCore(c *check.C) {
+ // mock installed snap
+ dirs.SetRootDir(c.MkDir())
+ defer func() { dirs.SetRootDir("/") }()
+ defer mockSnapConfine()()
+
+ si := snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{
+ Revision: snap.R("x2"),
+ })
+ err := os.Symlink(si.MountDir(), filepath.Join(si.MountDir(), "../current"))
+ c.Assert(err, check.IsNil)
+
+ // pretend to be running from core
+ restorer := snaprun.MockOsReadlink(func(string) (string, error) {
+ return filepath.Join(dirs.SnapMountDir, "/core/111//usr/bin/snap"), nil
+ })
+ defer restorer()
+
+ // 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", "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.SnapMountDir, "/core/current", dirs.DistroLibExecDir, "snap-confine"))
+ c.Check(execArgs, check.DeepEquals, []string{
+ filepath.Join(dirs.SnapMountDir, "/core/current", dirs.DistroLibExecDir, "snap-confine"),
+ "snap.snapname.app",
+ filepath.Join(dirs.DistroLibExecDir, "snap-exec"),
+ "snapname.app", "--arg1", "arg2"})
+ c.Check(execEnv, testutil.Contains, "SNAP_REVISION=x2")
+}
diff --git a/cmd/snap/cmd_snap_op.go b/cmd/snap/cmd_snap_op.go
index 50095f4910..b67aa9d768 100644
--- a/cmd/snap/cmd_snap_op.go
+++ b/cmd/snap/cmd_snap_op.go
@@ -705,6 +705,20 @@ type cmdTry struct {
} `positional-args:"yes"`
}
+func hasSnapcraftYaml() bool {
+ for _, loc := range []string{
+ "snap/snapcraft.yaml",
+ "snapcraft.yaml",
+ ".snapcraft.yaml",
+ } {
+ if osutil.FileExists(loc) {
+ return true
+ }
+ }
+
+ return false
+}
+
func (x *cmdTry) Execute([]string) error {
if err := x.validateMode(); err != nil {
return err
@@ -715,7 +729,7 @@ func (x *cmdTry) Execute([]string) error {
x.setModes(opts)
if name == "" {
- if osutil.FileExists("snapcraft.yaml") && osutil.IsDirectory("prime") {
+ if hasSnapcraftYaml() && osutil.IsDirectory("prime") {
name = "prime"
} else {
if osutil.FileExists("meta/snap.yaml") {
diff --git a/cmd/snap/export_test.go b/cmd/snap/export_test.go
index b229d0ff89..45aa0746f2 100644
--- a/cmd/snap/export_test.go
+++ b/cmd/snap/export_test.go
@@ -35,6 +35,7 @@ var (
SnapRunHook = snapRunHook
Wait = wait
ResolveApp = resolveApp
+ IsReexeced = isReexeced
)
func MockPollTime(d time.Duration) (restore func()) {
@@ -85,6 +86,14 @@ func MockMountInfoPath(newMountInfoPath string) (restore func()) {
}
}
+func MockOsReadlink(f func(string) (string, error)) (restore func()) {
+ osReadlinkOrig := osReadlink
+ osReadlink = f
+ return func() {
+ osReadlink = osReadlinkOrig
+ }
+}
+
var AutoImportCandidates = autoImportCandidates
func AliasInfoLess(snapName1, alias1, app1, snapName2, alias2, app2 string) bool {
diff --git a/daemon/api.go b/daemon/api.go
index 065e7562cd..6865379688 100644
--- a/daemon/api.go
+++ b/daemon/api.go
@@ -239,13 +239,16 @@ func sysInfo(c *Command, r *http.Request, user *auth.UserState) Response {
}
m := map[string]interface{}{
- "series": release.Series,
- "version": c.d.Version,
- "os-release": release.ReleaseInfo,
- "on-classic": release.OnClassic,
- "managed": len(users) > 0,
-
+ "series": release.Series,
+ "version": c.d.Version,
+ "os-release": release.ReleaseInfo,
+ "on-classic": release.OnClassic,
+ "managed": len(users) > 0,
"kernel-version": release.KernelVersion(),
+ "locations": map[string]interface{}{
+ "snap-mount-dir": dirs.SnapMountDir,
+ "snap-bin-dir": dirs.SnapBinariesDir,
+ },
}
// TODO: set the store-id here from the model information
@@ -1277,7 +1280,6 @@ func postSnaps(c *Command, r *http.Request, user *auth.UserState) Response {
if err != nil {
return BadRequest(err.Error())
}
- flags.RemoveSnapPath = true
if len(form.Value["action"]) > 0 && form.Value["action"][0] == "try" {
if len(form.Value["snap-path"]) == 0 {
@@ -1285,6 +1287,7 @@ func postSnaps(c *Command, r *http.Request, user *auth.UserState) Response {
}
return trySnap(c, r, user, form.Value["snap-path"][0], flags)
}
+ flags.RemoveSnapPath = true
// find the file for the "snap" form field
var snapBody multipart.File
@@ -2262,10 +2265,12 @@ func changeAliases(c *Command, r *http.Request, user *auth.UserState) Response {
if err := decoder.Decode(&a); err != nil {
return BadRequest("cannot decode request body into an alias action: %v", err)
}
- if len(a.Aliases) == 0 {
- return BadRequest("at least one alias name is required")
+ if len(a.Aliases) != 0 {
+ return BadRequest("cannot interpret request, snaps can no longer be expected to declare their aliases")
}
+ return BadRequest("cannot yet interpret request")
+ /* TODO: rework this
var summary string
var taskset *state.TaskSet
var err error
@@ -2298,6 +2303,7 @@ func changeAliases(c *Command, r *http.Request, user *auth.UserState) Response {
state.EnsureBefore(0)
return AsyncResponse(nil, &Meta{Change: change.ID()})
+ */
}
type aliasStatus struct {
diff --git a/daemon/api_test.go b/daemon/api_test.go
index e560e7dab3..7799611b99 100644
--- a/daemon/api_test.go
+++ b/daemon/api_test.go
@@ -238,6 +238,15 @@ func (s *apiBaseSuite) mkInstalled(c *check.C, name, developer, version string,
return s.mkInstalledInState(c, nil, name, developer, version, revision, active, extraYaml)
}
+func (s *apiBaseSuite) mkInstalledDesktopFile(c *check.C, name, content string) string {
+ df := filepath.Join(dirs.SnapDesktopFilesDir, name)
+ err := os.MkdirAll(filepath.Dir(df), 0755)
+ c.Assert(err, check.IsNil)
+ err = ioutil.WriteFile(df, []byte(content), 0644)
+ c.Assert(err, check.IsNil)
+ return df
+}
+
func (s *apiBaseSuite) mkInstalledInState(c *check.C, daemon *Daemon, name, developer, version string, revision snap.Revision, active bool, extraYaml string) *snap.Info {
snapID := name + "-id"
// Collect arguments into a snap.SideInfo structure
@@ -347,7 +356,8 @@ func (s *apiSuite) TestSnapInfoOneIntegration(c *check.C) {
// we have v0 [r5] installed
s.mkInstalledInState(c, d, "foo", "bar", "v0", snap.R(5), false, "")
// and v1 [r10] is current
- s.mkInstalledInState(c, d, "foo", "bar", "v1", snap.R(10), true, "description: description\nsummary: summary")
+ s.mkInstalledInState(c, d, "foo", "bar", "v1", snap.R(10), true, "description: description\nsummary: summary\napps:\n cmd:\n command: some.cmd\n cmd2:\n command: other.cmd\n")
+ df := s.mkInstalledDesktopFile(c, "foo_cmd.desktop", "[Desktop]\nExec=foo.cmd %U")
req, err := http.NewRequest("GET", "/v2/snaps/foo", nil)
c.Assert(err, check.IsNil)
@@ -388,9 +398,13 @@ func (s *apiSuite) TestSnapInfoOneIntegration(c *check.C) {
"jailmode": false,
"confinement": snap.StrictConfinement,
"trymode": false,
- "apps": []appJSON{},
- "broken": "",
- "contact": "",
+ "apps": []appJSON{
+ {Name: "cmd", DesktopFile: df},
+ // no desktop file
+ {Name: "cmd2"},
+ },
+ "broken": "",
+ "contact": "",
},
Meta: meta,
}
@@ -558,6 +572,10 @@ func (s *apiSuite) TestSysInfo(c *check.C) {
},
"on-classic": true,
"managed": false,
+ "locations": map[string]interface{}{
+ "snap-mount-dir": dirs.SnapMountDir,
+ "snap-bin-dir": dirs.SnapBinariesDir,
+ },
}
var rsp resp
c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil)
@@ -2009,8 +2027,7 @@ func (s *apiSuite) TestTrySnap(c *check.C) {
d.overlord.Loop()
defer d.overlord.Stop()
- req, err := http.NewRequest("POST", "/v2/snaps", nil)
- c.Assert(err, check.IsNil)
+ var err error
// mock a try dir
tryDir := c.MkDir()
@@ -2020,6 +2037,42 @@ func (s *apiSuite) TestTrySnap(c *check.C) {
err = ioutil.WriteFile(snapYaml, []byte("name: foo\nversion: 1.0\n"), 0644)
c.Assert(err, check.IsNil)
+ reqForFlags := func(f snapstate.Flags) *http.Request {
+ b := "" +
+ "--hello\r\n" +
+ "Content-Disposition: form-data; name=\"action\"\r\n" +
+ "\r\n" +
+ "try\r\n" +
+ "--hello\r\n" +
+ "Content-Disposition: form-data; name=\"snap-path\"\r\n" +
+ "\r\n" +
+ tryDir + "\r\n" +
+ "--hello"
+
+ snip := "\r\n" +
+ "Content-Disposition: form-data; name=%q\r\n" +
+ "\r\n" +
+ "true\r\n" +
+ "--hello"
+
+ if f.DevMode {
+ b += fmt.Sprintf(snip, "devmode")
+ }
+ if f.JailMode {
+ b += fmt.Sprintf(snip, "jailmode")
+ }
+ if f.Classic {
+ b += fmt.Sprintf(snip, "classic")
+ }
+ b += "--\r\n"
+
+ req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(b))
+ c.Assert(err, check.IsNil)
+ req.Header.Set("Content-Type", "multipart/thing; boundary=hello")
+
+ return req
+ }
+
for _, t := range []struct {
coreInfoErr error
nTasks int
@@ -2067,7 +2120,7 @@ func (s *apiSuite) TestTrySnap(c *check.C) {
}
// try the snap (without an installed core)
- rsp := trySnap(snapsCmd, req, nil, tryDir, t.flags).(*resp)
+ rsp := postSnaps(snapsCmd, reqForFlags(t.flags), nil).(*resp)
c.Assert(rsp.Type, check.Equals, ResponseTypeAsync, check.Commentf(t.desc))
c.Assert(tryWasCalled, check.Equals, true, check.Commentf(t.desc))
@@ -4847,6 +4900,7 @@ func (s *apiSuite) TestAliasSuccess(c *check.C) {
d.overlord.Loop()
defer d.overlord.Stop()
+ // TODO: the details of this will change
action := &aliasAction{
Action: "alias",
Snap: "alias-snap",
@@ -4859,10 +4913,15 @@ func (s *apiSuite) TestAliasSuccess(c *check.C) {
c.Assert(err, check.IsNil)
rec := httptest.NewRecorder()
aliasesCmd.POST(aliasesCmd, req, nil).ServeHTTP(rec, req)
- c.Check(rec.Code, check.Equals, 202)
- var body map[string]interface{}
- err = json.Unmarshal(rec.Body.Bytes(), &body)
+ c.Check(rec.Code, check.Equals, 400)
+ var rsp resp
+ err = json.Unmarshal(rec.Body.Bytes(), &rsp)
c.Check(err, check.IsNil)
+ c.Check(rsp.Result, check.DeepEquals, map[string]interface{}{
+ "message": "cannot interpret request, snaps can no longer be expected to declare their aliases",
+ })
+
+ /* TODO: test the happy case again
id := body["change"].(string)
st := d.overlord.State()
@@ -4880,6 +4939,7 @@ func (s *apiSuite) TestAliasSuccess(c *check.C) {
// sanity check
c.Check(osutil.IsSymlink(filepath.Join(dirs.SnapBinariesDir, "alias1")), check.Equals, true)
+ */
}
func (s *apiSuite) TestAliasErrors(c *check.C) {
@@ -4889,10 +4949,7 @@ func (s *apiSuite) TestAliasErrors(c *check.C) {
mangle func(*aliasAction)
err string
}{
- {func(a *aliasAction) { a.Action = "" }, `unsupported alias action: ""`},
- {func(a *aliasAction) { a.Action = "what" }, `unsupported alias action: "what"`},
- {func(a *aliasAction) { a.Aliases = nil }, `at least one alias name is required`},
- {func(a *aliasAction) { a.Snap = "lalala" }, `cannot find snap "lalala"`},
+ {func(a *aliasAction) { a.Aliases = nil }, `cannot yet interpret request`},
}
for _, scen := range errScenarios {
@@ -4916,124 +4973,6 @@ func (s *apiSuite) TestAliasErrors(c *check.C) {
}
}
-func (s *apiSuite) TestUnaliasSuccess(c *check.C) {
- err := os.MkdirAll(dirs.SnapBinariesDir, 0755)
- c.Assert(err, check.IsNil)
- d := s.daemon(c)
-
- s.mockSnap(c, aliasYaml)
-
- oldAutoAliases := snapstate.AutoAliases
- snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) {
- return nil, nil
- }
- defer func() { snapstate.AutoAliases = oldAutoAliases }()
-
- d.overlord.Loop()
- defer d.overlord.Stop()
-
- action := &aliasAction{
- Action: "unalias",
- Snap: "alias-snap",
- Aliases: []string{"alias1"},
- }
- text, err := json.Marshal(action)
- c.Assert(err, check.IsNil)
- buf := bytes.NewBuffer(text)
- req, err := http.NewRequest("POST", "/v2/aliases", buf)
- c.Assert(err, check.IsNil)
- rec := httptest.NewRecorder()
- aliasesCmd.POST(aliasesCmd, req, nil).ServeHTTP(rec, req)
- c.Check(rec.Code, check.Equals, 202)
- var body map[string]interface{}
- err = json.Unmarshal(rec.Body.Bytes(), &body)
- c.Check(err, check.IsNil)
- id := body["change"].(string)
-
- st := d.overlord.State()
- st.Lock()
- chg := st.Change(id)
- st.Unlock()
- c.Assert(chg, check.NotNil)
-
- <-chg.Ready()
-
- st.Lock()
- defer st.Unlock()
- err = chg.Err()
- c.Assert(err, check.IsNil)
-
- var allAliases map[string]map[string]string
- err = st.Get("aliases", &allAliases)
- c.Assert(err, check.IsNil)
- c.Check(allAliases, check.DeepEquals, map[string]map[string]string{
- "alias-snap": {"alias1": "disabled"},
- })
-
-}
-
-func (s *apiSuite) TestResetAliasSuccess(c *check.C) {
- err := os.MkdirAll(dirs.SnapBinariesDir, 0755)
- c.Assert(err, check.IsNil)
- d := s.daemon(c)
-
- s.mockSnap(c, aliasYaml)
-
- oldAutoAliases := snapstate.AutoAliases
- snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) {
- return nil, nil
- }
- defer func() { snapstate.AutoAliases = oldAutoAliases }()
-
- d.overlord.Loop()
- defer d.overlord.Stop()
-
- st := d.overlord.State()
- st.Lock()
- defer st.Unlock()
-
- st.Set("aliases", map[string]map[string]string{
- "alias-snap": {
- "alias1": "disabled",
- },
- })
-
- action := &aliasAction{
- Action: "reset",
- Snap: "alias-snap",
- Aliases: []string{"alias1"},
- }
- text, err := json.Marshal(action)
- c.Assert(err, check.IsNil)
- buf := bytes.NewBuffer(text)
- req, err := http.NewRequest("POST", "/v2/aliases", buf)
- c.Assert(err, check.IsNil)
- rec := httptest.NewRecorder()
- st.Unlock()
- aliasesCmd.POST(aliasesCmd, req, nil).ServeHTTP(rec, req)
- st.Lock()
- c.Check(rec.Code, check.Equals, 202)
- var body map[string]interface{}
- err = json.Unmarshal(rec.Body.Bytes(), &body)
- c.Check(err, check.IsNil)
- id := body["change"].(string)
-
- chg := st.Change(id)
- c.Assert(chg, check.NotNil)
-
- st.Unlock()
- <-chg.Ready()
- st.Lock()
-
- err = chg.Err()
- c.Assert(err, check.IsNil)
-
- var allAliases map[string]map[string]string
- err = st.Get("aliases", &allAliases)
- c.Assert(err, check.IsNil)
- c.Check(allAliases, check.HasLen, 0)
-}
-
func (s *apiSuite) TestAliases(c *check.C) {
d := s.daemon(c)
diff --git a/daemon/snap.go b/daemon/snap.go
index 6d4c411558..5bd7c64bed 100644
--- a/daemon/snap.go
+++ b/daemon/snap.go
@@ -24,8 +24,10 @@ import (
"fmt"
"os"
"path/filepath"
+ "sort"
"time"
+ "github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord/assertstate"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
@@ -157,9 +159,10 @@ func allLocalSnapInfos(st *state.State, all bool, wanted map[string]bool) ([]abo
// appJSON contains the json for snap.AppInfo
type appJSON struct {
- Name string `json:"name"`
- Daemon string `json:"daemon"`
- Aliases []string `json:"aliases,omitempty"`
+ Name string `json:"name"`
+ Daemon string `json:"daemon"`
+ DesktopFile string `json:"desktop-file,omitempty"`
+ Aliases []string `json:"aliases,omitempty"`
}
// screenshotJSON contains the json for snap.ScreenshotInfo
@@ -176,12 +179,24 @@ func mapLocal(about aboutSnap) map[string]interface{} {
status = "active"
}
+ appNames := make([]string, 0, len(localSnap.Apps))
+ for appName := range localSnap.Apps {
+ appNames = append(appNames, appName)
+ }
+ sort.Strings(appNames)
apps := make([]appJSON, 0, len(localSnap.Apps))
- for _, app := range localSnap.Apps {
+ for _, appName := range appNames {
+ app := localSnap.Apps[appName]
+ var installedDesktopFile string
+ if osutil.FileExists(app.DesktopFile()) {
+ installedDesktopFile = app.DesktopFile()
+ }
+
apps = append(apps, appJSON{
- Name: app.Name,
- Daemon: app.Daemon,
- Aliases: app.Aliases,
+ Name: app.Name,
+ Daemon: app.Daemon,
+ DesktopFile: installedDesktopFile,
+ Aliases: app.Aliases,
})
}
diff --git a/data/systemd/Makefile b/data/systemd/Makefile
index b286001b68..8ebc6fe39b 100644
--- a/data/systemd/Makefile
+++ b/data/systemd/Makefile
@@ -33,7 +33,7 @@ SYSTEMD_UNITS := ${SYSTEMD_UNITS_GENERATED} snapd.refresh.timer snapd.socket
all: ${SYSTEMD_UNITS}
install: $(SYSTEMD_UNITS)
- $(foreach u,$^,install -D -m 0644 ${u} ${DESTDIR}/${SYSTEMDSYSTEMUNITDIR}/${u};)
+ install -D -m 0644 -t ${DESTDIR}/${SYSTEMDSYSTEMUNITDIR} $^
clean:
rm -f ${SYSTEMD_UNITS_GENERATED}
diff --git a/dirs/dirs.go b/dirs/dirs.go
index 25442b24b8..d32411466a 100644
--- a/dirs/dirs.go
+++ b/dirs/dirs.go
@@ -47,6 +47,7 @@ var (
SnapMetaDir string
SnapdSocket string
SnapSocket string
+ SnapRunDir string
SnapRunNsDir string
SnapSeedDir string
@@ -63,6 +64,9 @@ var (
SnapDesktopFilesDir string
SnapBusPolicyDir string
+ SystemApparmorDir string
+ SystemApparmorCacheDir string
+
CloudMetaDataFile string
ClassicDir string
@@ -73,6 +77,10 @@ var (
XdgRuntimeDirGlob string
)
+const (
+ defaultSnapMountDir = "/snap"
+)
+
var (
// not exported because it does not honor the global rootdir
snappyDir = filepath.Join("var", "lib", "snapd")
@@ -100,6 +108,11 @@ func StripRootDir(dir string) string {
return "/" + result
}
+// SupportsClassicConfinement returns true if the current directory layout supports classic confinement.
+func SupportsClassicConfinement() bool {
+ return SnapMountDir == defaultSnapMountDir
+}
+
// SetRootDir allows settings a new global root directory, this is useful
// for e.g. chroot operations
func SetRootDir(rootdir string) {
@@ -112,7 +125,7 @@ func SetRootDir(rootdir string) {
case "fedora", "centos", "rhel":
SnapMountDir = filepath.Join(rootdir, "/var/lib/snapd/snap")
default:
- SnapMountDir = filepath.Join(rootdir, "/snap")
+ SnapMountDir = filepath.Join(rootdir, defaultSnapMountDir)
}
SnapDataDir = filepath.Join(rootdir, "/var/snap")
@@ -125,7 +138,8 @@ func SetRootDir(rootdir string) {
SnapMetaDir = filepath.Join(rootdir, snappyDir, "meta")
SnapBlobDir = filepath.Join(rootdir, snappyDir, "snaps")
SnapDesktopFilesDir = filepath.Join(rootdir, snappyDir, "desktop", "applications")
- SnapRunNsDir = filepath.Join(rootdir, "/run/snapd/ns")
+ SnapRunDir = filepath.Join(rootdir, "/run/snapd")
+ SnapRunNsDir = filepath.Join(SnapRunDir, "/ns")
// keep in sync with the debian/snapd.socket file:
SnapdSocket = filepath.Join(rootdir, "/run/snapd.socket")
@@ -143,6 +157,9 @@ func SetRootDir(rootdir string) {
SnapServicesDir = filepath.Join(rootdir, "/etc/systemd/system")
SnapBusPolicyDir = filepath.Join(rootdir, "/etc/dbus-1/system.d")
+ SystemApparmorDir = filepath.Join(rootdir, "/etc/apparmor.d")
+ SystemApparmorCacheDir = filepath.Join(rootdir, "/etc/apparmor.d/cache")
+
CloudMetaDataFile = filepath.Join(rootdir, "/var/lib/cloud/seed/nocloud-net/meta-data")
SnapUdevRulesDir = filepath.Join(rootdir, "/etc/udev/rules.d")
diff --git a/dirs/dirs_test.go b/dirs/dirs_test.go
index 8e8f39aabb..761cdf1ade 100644
--- a/dirs/dirs_test.go
+++ b/dirs/dirs_test.go
@@ -25,6 +25,7 @@ import (
. "gopkg.in/check.v1"
"github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/release"
)
// Hook up check.v1 into the "go test" runner
@@ -47,3 +48,29 @@ func (s *DirsTestSuite) TestStripRootDir(c *C) {
// strip only works on paths that begin with the global root directory
c.Check(func() { dirs.StripRootDir("/other/foo/bar") }, Panics, `supplied path is not related to global root "/other/foo/bar"`)
}
+
+func (s *DirsTestSuite) TestClassicConfinementSupport(c *C) {
+ dirs.SetRootDir("/")
+ c.Assert(dirs.SupportsClassicConfinement(), Equals, true)
+ dirs.SetRootDir("/alt")
+ c.Assert(dirs.SupportsClassicConfinement(), Equals, false)
+}
+
+func (s *DirsTestSuite) TestClassicConfinementSupportOnSpecificDistributions(c *C) {
+ for _, current := range []struct {
+ Name string
+ Expected bool
+ }{
+ {"fedora", false},
+ {"rhel", false},
+ {"centos", false},
+ {"ubuntu", true},
+ {"debian", true},
+ {"suse", true},
+ {"yocto", true}} {
+ reset := release.MockReleaseInfo(&release.OS{ID: current.Name})
+ defer reset()
+ dirs.SetRootDir("/")
+ c.Assert(dirs.SupportsClassicConfinement(), Equals, current.Expected)
+ }
+}
diff --git a/errtracker/errtracker.go b/errtracker/errtracker.go
index 591905f2b3..395abfe001 100644
--- a/errtracker/errtracker.go
+++ b/errtracker/errtracker.go
@@ -33,6 +33,7 @@ import (
"github.com/snapcore/snapd/arch"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/httputil"
+ "github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/release"
)
@@ -108,6 +109,15 @@ func Report(snap, errMsg, dupSig string, extra map[string]string) (string, error
report[k] = v
}
}
+
+ // see if we run in testing mode
+ if osutil.GetenvBool("SNAPPY_TESTING") {
+ logger.Noticef("errtracker.Report is *not* sent because SNAPPY_TESTING is set")
+ logger.Noticef("report: %v", report)
+ return "oops-not-sent", nil
+ }
+
+ // send it for real
reportBson, err := bson.Marshal(report)
if err != nil {
return "", err
diff --git a/errtracker/errtracker_test.go b/errtracker/errtracker_test.go
index 3a433dc045..26d238c4bb 100644
--- a/errtracker/errtracker_test.go
+++ b/errtracker/errtracker_test.go
@@ -24,6 +24,7 @@ import (
"io/ioutil"
"net/http"
"net/http/httptest"
+ "os"
"path/filepath"
"strings"
"testing"
@@ -49,6 +50,9 @@ type ErrtrackerTestSuite struct {
var _ = Suite(&ErrtrackerTestSuite{})
+var truePath = osutil.LookPathDefault("true", "/bin/true")
+var falsePath = osutil.LookPathDefault("false", "/bin/false")
+
func (s *ErrtrackerTestSuite) SetUpTest(c *C) {
s.BaseTest.SetUpTest(c)
@@ -56,16 +60,16 @@ func (s *ErrtrackerTestSuite) SetUpTest(c *C) {
err := ioutil.WriteFile(p, []byte("bbb1a6a5bcdb418380056a2d759c3f7c"), 0644)
c.Assert(err, IsNil)
s.AddCleanup(errtracker.MockMachineIDPath(p))
- s.AddCleanup(errtracker.MockHostSnapd("/bin/true"))
- s.AddCleanup(errtracker.MockCoreSnapd("/bin/false"))
+ s.AddCleanup(errtracker.MockHostSnapd(truePath))
+ s.AddCleanup(errtracker.MockCoreSnapd(falsePath))
}
func (s *ErrtrackerTestSuite) TestReport(c *C) {
n := 0
identifier := ""
- hostBuildID, err := osutil.ReadBuildID("/bin/true")
+ hostBuildID, err := osutil.ReadBuildID(truePath)
c.Assert(err, IsNil)
- coreBuildID, err := osutil.ReadBuildID("/bin/false")
+ coreBuildID, err := osutil.ReadBuildID(falsePath)
c.Assert(err, IsNil)
prev := errtracker.SnapdVersion
@@ -136,3 +140,31 @@ func (s *ErrtrackerTestSuite) TestReport(c *C) {
c.Check(id, Equals, "c14388aa-f78d-11e6-8df0-fa163eaf9b83 OOPSID")
c.Check(n, Equals, 2)
}
+
+func (s *ErrtrackerTestSuite) TestReportUnderTesting(c *C) {
+ os.Setenv("SNAPPY_TESTING", "1")
+ defer os.Unsetenv("SNAPPY_TESTING")
+
+ n := 0
+ prev := errtracker.SnapdVersion
+ defer func() { errtracker.SnapdVersion = prev }()
+ errtracker.SnapdVersion = "some-snapd-version"
+
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ n++
+ }
+
+ server := httptest.NewServer(http.HandlerFunc(handler))
+ defer server.Close()
+ restorer := errtracker.MockCrashDbURL(server.URL)
+ defer restorer()
+ restorer = errtracker.MockTimeNow(func() time.Time { return time.Date(2017, 2, 17, 9, 51, 0, 0, time.UTC) })
+ defer restorer()
+
+ id, err := errtracker.Report("some-snap", "failed to do stuff", "[failed to do stuff]", map[string]string{
+ "Channel": "beta",
+ })
+ c.Check(err, IsNil)
+ c.Check(id, Equals, "oops-not-sent")
+ c.Check(n, Equals, 0)
+}
diff --git a/interfaces/apparmor/backend.go b/interfaces/apparmor/backend.go
index 190fff11ee..f6da80ffa3 100644
--- a/interfaces/apparmor/backend.go
+++ b/interfaces/apparmor/backend.go
@@ -39,14 +39,19 @@ package apparmor
import (
"fmt"
+ "io/ioutil"
"os"
+ "os/exec"
"path/filepath"
"regexp"
"sort"
+ "strings"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
)
@@ -58,6 +63,69 @@ func (b *Backend) Name() interfaces.SecuritySystem {
return interfaces.SecurityAppArmor
}
+// setupSnapConfineReexec will setup apparmor profiles on a classic
+// system on the hosts /etc/apparmor.d directory. This is needed for
+// running snap-confine from the core snap.
+//
+// Additionally it will cleanup stale apparmor profiles it created.
+func setupSnapConfineReexec(snapInfo *snap.Info) error {
+ // cleanup old
+ apparmorProfilePathPattern := strings.Replace(filepath.Join(dirs.SnapMountDir, "/core/*/usr/lib/snapd/snap-confine"), "/", ".", -1)[1:]
+
+ glob, err := filepath.Glob(filepath.Join(dirs.SystemApparmorDir, apparmorProfilePathPattern))
+ if err != nil {
+ return err
+ }
+
+ for _, path := range glob {
+ snapConfineInCore := "/" + strings.Replace(filepath.Base(path), ".", "/", -1)
+ if osutil.FileExists(snapConfineInCore) {
+ continue
+ }
+
+ // not using apparmor.UnloadProfile() because it uses a
+ // different cachedir
+ if output, err := exec.Command("apparmor_parser", "-R", filepath.Base(path)).CombinedOutput(); err != nil {
+ logger.Noticef("cannot unload apparmor profile %s: %v", filepath.Base(path), osutil.OutputErr(output, err))
+ }
+ if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
+ return err
+ }
+ if err := os.Remove(filepath.Join(dirs.SystemApparmorCacheDir, filepath.Base(path))); err != nil && !os.IsNotExist(err) {
+ return err
+ }
+ }
+
+ // add new confinement file
+ coreRoot := snapInfo.MountDir()
+ snapConfineInCore := filepath.Join(coreRoot, "usr/lib/snapd/snap-confine")
+ apparmorProfilePath := filepath.Join(dirs.SystemApparmorDir, strings.Replace(snapConfineInCore[1:], "/", ".", -1))
+
+ // we must test the ".real" suffix first, this is a workaround for
+ // https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=858004
+ apparmorProfile, err := ioutil.ReadFile(filepath.Join(coreRoot, "/etc/apparmor.d/usr.lib.snapd.snap-confine.real"))
+ if os.IsNotExist(err) {
+ apparmorProfile, err = ioutil.ReadFile(filepath.Join(coreRoot, "/etc/apparmor.d/usr.lib.snapd.snap-confine"))
+ if err != nil {
+ return err
+ }
+ }
+ apparmorProfileForCore := strings.Replace(string(apparmorProfile), "/usr/lib/snapd/snap-confine", snapConfineInCore, -1)
+
+ // /etc/apparmor.d is read/write OnClassic, so write out the
+ // new core's profile there
+ if err := osutil.AtomicWriteFile(apparmorProfilePath, []byte(apparmorProfileForCore), 0644, 0); err != nil {
+ return err
+ }
+
+ // not using apparmor.LoadProfile() because it uses a different cachedir
+ if output, err := exec.Command("apparmor_parser", "--replace", "--write-cache", apparmorProfilePath, "--cache-loc", dirs.SystemApparmorCacheDir).CombinedOutput(); err != nil {
+ return fmt.Errorf("cannot replace snap-confine apparmor profile: %v", osutil.OutputErr(output, err))
+ }
+
+ return nil
+}
+
// Setup creates and loads apparmor profiles specific to a given snap.
// The snap can be in developer mode to make security violations non-fatal to
// the offending application process.
@@ -70,6 +138,14 @@ func (b *Backend) Setup(snapInfo *snap.Info, opts interfaces.ConfinementOptions,
if err != nil {
return fmt.Errorf("cannot obtain apparmor specification for snap %q: %s", snapName, err)
}
+
+ // core on classic is special
+ if snapName == "core" && release.OnClassic && !release.ReleaseInfo.ForceDevMode() {
+ if err := setupSnapConfineReexec(snapInfo); err != nil {
+ logger.Noticef("cannot create host snap-confine apparmor configuration: %s", err)
+ }
+ }
+
// Get the files that this snap should have
content, err := b.deriveContent(spec.(*Specification), snapInfo, opts)
if err != nil {
diff --git a/interfaces/apparmor/backend_test.go b/interfaces/apparmor/backend_test.go
index 45ff79c05b..1cbb51efa0 100644
--- a/interfaces/apparmor/backend_test.go
+++ b/interfaces/apparmor/backend_test.go
@@ -24,6 +24,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
+ "strings"
. "gopkg.in/check.v1"
@@ -31,6 +32,8 @@ import (
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/apparmor"
"github.com/snapcore/snapd/interfaces/ifacetest"
+ "github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap/snaptest"
"github.com/snapcore/snapd/testutil"
)
@@ -396,3 +399,86 @@ func (s *backendSuite) TestCombineSnippets(c *C) {
s.RemoveSnap(c, snapInfo)
}
}
+
+var coreYaml string = `name: core
+version: 1
+`
+
+func (s *backendSuite) TestSetupHostSnapConfineApparmorForReexecCleans(c *C) {
+ restorer := release.MockOnClassic(true)
+ defer restorer()
+
+ canaryName := strings.Replace(filepath.Join(dirs.SnapMountDir, "/core/2718/usr/lib/snapd/snap-confine"), "/", ".", -1)[1:]
+ canary := filepath.Join(dirs.SystemApparmorDir, canaryName)
+ err := os.MkdirAll(filepath.Dir(canary), 0755)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(canary, nil, 0644)
+ c.Assert(err, IsNil)
+
+ // install the new core snap on classic triggers cleanup
+ s.InstallSnap(c, interfaces.ConfinementOptions{}, coreYaml, 111)
+
+ c.Check(osutil.FileExists(canary), Equals, false)
+ c.Check(s.parserCmd.Calls(), DeepEquals, [][]string{
+ {"apparmor_parser", "-R", canaryName},
+ })
+}
+
+func (s *backendSuite) TestSetupHostSnapConfineApparmorForReexecWritesNew(c *C) {
+ restorer := release.MockOnClassic(true)
+ defer restorer()
+
+ cmd := testutil.MockCommand(c, "apparmor_parser", "")
+ defer cmd.Restore()
+
+ var mockAA = []byte(`# Author: Jamie Strandboge <jamie@canonical.com>
+#include <tunables/global>
+
+/usr/lib/snapd/snap-confine (attach_disconnected) {
+ # We run privileged, so be fanatical about what we include and don't use
+ # any abstractions
+ /etc/ld.so.cache r,
+}
+`)
+
+ err := os.MkdirAll(dirs.SystemApparmorDir, 0755)
+ c.Assert(err, IsNil)
+
+ // meh, the paths/filenames are all complicated :/
+ coreRoot := filepath.Join(dirs.SnapMountDir, "/core/111")
+ snapConfineApparmorInCore := filepath.Join(coreRoot, "/etc/apparmor.d/usr.lib.snapd.snap-confine.real")
+ err = os.MkdirAll(filepath.Dir(snapConfineApparmorInCore), 0755)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(snapConfineApparmorInCore, mockAA, 0644)
+ c.Assert(err, IsNil)
+
+ // install the new core snap on classic triggers a new snap-confine
+ // for this snap-confine on core
+ s.InstallSnap(c, interfaces.ConfinementOptions{}, coreYaml, 111)
+
+ newAA, err := filepath.Glob(filepath.Join(dirs.SystemApparmorDir, "*"))
+ c.Assert(err, IsNil)
+
+ c.Assert(newAA, HasLen, 1)
+ c.Check(newAA[0], Matches, `.*/etc/apparmor.d/.*.snap.core.111.usr.lib.snapd.snap-confine`)
+
+ content, err := ioutil.ReadFile(newAA[0])
+ c.Assert(err, IsNil)
+ // this is the key, rewriting "/usr/lib/snapd/snap-confine
+ c.Check(string(content), testutil.Contains, "/snap/core/111/usr/lib/snapd/snap-confine (attach_disconnected) {")
+ // no other changes other than that to the input
+ c.Check(string(content), Equals, fmt.Sprintf(`# Author: Jamie Strandboge <jamie@canonical.com>
+#include <tunables/global>
+
+%s/core/111/usr/lib/snapd/snap-confine (attach_disconnected) {
+ # We run privileged, so be fanatical about what we include and don't use
+ # any abstractions
+ /etc/ld.so.cache r,
+}
+`, dirs.SnapMountDir))
+
+ c.Check(cmd.Calls(), DeepEquals, [][]string{
+ {"apparmor_parser", "--replace", "--write-cache", newAA[0], "--cache-loc", dirs.SystemApparmorCacheDir},
+ })
+
+}
diff --git a/interfaces/apparmor/template.go b/interfaces/apparmor/template.go
index 1424c4fb0f..9531072d2f 100644
--- a/interfaces/apparmor/template.go
+++ b/interfaces/apparmor/template.go
@@ -344,7 +344,7 @@ var defaultTemplate = `
# access in /dev/shm for shm_open() and files in subdirectories for open()
/{dev,run}/shm/snap.@{SNAP_NAME}.** mrwlkix,
# Also allow app-specific access for sem_open()
- /{dev,run}/shm/sem.snap.@{SNAP_NAME}.* rwk,
+ /{dev,run}/shm/sem.snap.@{SNAP_NAME}.* mrwk,
# Snap-specific XDG_RUNTIME_DIR that is based on the UID of the user
owner /run/user/[0-9]*/snap.@{SNAP_NAME}/ rw,
diff --git a/interfaces/builtin/all.go b/interfaces/builtin/all.go
index 330c97a885..5f8741de27 100644
--- a/interfaces/builtin/all.go
+++ b/interfaces/builtin/all.go
@@ -57,6 +57,7 @@ var allInterfaces = []interfaces.Interface{
&SerialPortInterface{},
&ThumbnailerServiceInterface{},
&TimeControlInterface{},
+ &Unity7Interface{},
&UDisks2Interface{},
&UbuntuDownloadManagerInterface{},
&Unity8Interface{},
@@ -77,6 +78,7 @@ var allInterfaces = []interfaces.Interface{
NewHardwareObserveInterface(),
NewHomeInterface(),
NewKernelModuleControlInterface(),
+ NewKubernetesSupportInterface(),
NewLibvirtInterface(),
NewLocaleControlInterface(),
NewLogObserveInterface(),
@@ -102,7 +104,6 @@ var allInterfaces = []interfaces.Interface{
NewTimeserverControlInterface(),
NewTimezoneControlInterface(),
NewTpmInterface(),
- NewUnity7Interface(),
NewUnity8CalendarInterface(),
NewUnity8ContactsInterface(),
NewX11Interface(),
diff --git a/interfaces/builtin/all_test.go b/interfaces/builtin/all_test.go
index 40f3ab9402..288309c3e6 100644
--- a/interfaces/builtin/all_test.go
+++ b/interfaces/builtin/all_test.go
@@ -63,6 +63,7 @@ func (s *AllSuite) TestInterfaces(c *C) {
c.Check(all, Contains, &builtin.Unity8Interface{})
c.Check(all, Contains, &builtin.UpowerObserveInterface{})
c.Check(all, Contains, &builtin.UhidInterface{})
+ c.Check(all, Contains, &builtin.Unity7Interface{})
c.Check(all, DeepContains, builtin.NewAccountControlInterface())
c.Check(all, DeepContains, builtin.NewAlsaInterface())
c.Check(all, DeepContains, builtin.NewAvahiObserveInterface())
@@ -75,6 +76,7 @@ func (s *AllSuite) TestInterfaces(c *C) {
c.Check(all, DeepContains, builtin.NewGsettingsInterface())
c.Check(all, DeepContains, builtin.NewHomeInterface())
c.Check(all, DeepContains, builtin.NewKernelModuleControlInterface())
+ c.Check(all, DeepContains, builtin.NewKubernetesSupportInterface())
c.Check(all, DeepContains, builtin.NewLocaleControlInterface())
c.Check(all, DeepContains, builtin.NewLogObserveInterface())
c.Check(all, DeepContains, builtin.NewMountObserveInterface())
@@ -96,7 +98,6 @@ func (s *AllSuite) TestInterfaces(c *C) {
c.Check(all, DeepContains, builtin.NewTimeserverControlInterface())
c.Check(all, DeepContains, builtin.NewTimezoneControlInterface())
c.Check(all, DeepContains, builtin.NewTpmInterface())
- c.Check(all, DeepContains, builtin.NewUnity7Interface())
c.Check(all, DeepContains, builtin.NewUnity8CalendarInterface())
c.Check(all, DeepContains, builtin.NewUnity8ContactsInterface())
c.Check(all, DeepContains, builtin.NewX11Interface())
diff --git a/interfaces/builtin/basedeclaration.go b/interfaces/builtin/basedeclaration.go
index 7995e83a7a..66a86f7ceb 100644
--- a/interfaces/builtin/basedeclaration.go
+++ b/interfaces/builtin/basedeclaration.go
@@ -151,6 +151,9 @@ plugs:
kernel-module-control:
allow-installation: false
deny-auto-connection: true
+ kubernetes-support:
+ allow-installation: false
+ deny-auto-connection: true
lxd-support:
allow-installation: false
deny-auto-connection: true
@@ -334,6 +337,11 @@ slots:
slot-snap-type:
- core
deny-auto-connection: true
+ kubernetes-support:
+ allow-installation:
+ slot-snap-type:
+ - core
+ deny-auto-connection: true
libvirt:
allow-installation:
slot-snap-type:
diff --git a/interfaces/builtin/basedeclaration_test.go b/interfaces/builtin/basedeclaration_test.go
index f1ddbe6b87..518f48545b 100644
--- a/interfaces/builtin/basedeclaration_test.go
+++ b/interfaces/builtin/basedeclaration_test.go
@@ -363,6 +363,24 @@ plugs:
c.Check(err, IsNil)
}
+func (s *baseDeclSuite) TestAutoConnectionKubernetesSupportOverride(c *C) {
+ cand := s.connectCand(c, "kubernetes-support", "", "")
+ err := cand.CheckAutoConnect()
+ c.Check(err, NotNil)
+ c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"kubernetes-support\"")
+
+ plugsSlots := `
+plugs:
+ kubernetes-support:
+ allow-auto-connection: true
+`
+
+ snapDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots)
+ cand.PlugSnapDeclaration = snapDecl
+ err = cand.CheckAutoConnect()
+ c.Check(err, IsNil)
+}
+
func (s *baseDeclSuite) TestAutoConnectionOverrideMultiple(c *C) {
plugsSlots := `
plugs:
@@ -427,6 +445,7 @@ var (
"hidraw": {"core", "gadget"},
"i2c": {"core", "gadget"},
"iio": {"core", "gadget"},
+ "kubernetes-support": {"core"},
"location-control": {"app"},
"location-observe": {"app"},
"lxd-support": {"core"},
@@ -531,6 +550,7 @@ func (s *baseDeclSuite) TestPlugInstallation(c *C) {
"classic-support": true,
"docker-support": true,
"kernel-module-control": true,
+ "kubernetes-support": true,
"lxd-support": true,
"snapd-control": true,
"unity8": true,
@@ -653,6 +673,7 @@ func (s *baseDeclSuite) TestSanity(c *C) {
"core-support": true,
"docker-support": true,
"kernel-module-control": true,
+ "kubernetes-support": true,
"lxd-support": true,
"snapd-control": true,
"unity8": true,
diff --git a/interfaces/builtin/browser_support.go b/interfaces/builtin/browser_support.go
index f1b7512c18..979194c569 100644
--- a/interfaces/builtin/browser_support.go
+++ b/interfaces/builtin/browser_support.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2016 Canonical Ltd
+ * Copyright (C) 2016-2017 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
@@ -27,9 +27,8 @@ import (
"github.com/snapcore/snapd/interfaces/seccomp"
)
-// http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/log-observe
const browserSupportConnectedPlugAppArmor = `
-# Description: Can access various APIs needed by modern browers (eg, Google
+# Description: Can access various APIs needed by modern browsers (eg, Google
# Chrome/Chromium and Mozilla) and file paths they expect. This interface is
# transitional and is only in place while upstream's work to change their paths
# and snappy is updated to properly mediate the APIs.
@@ -44,12 +43,15 @@ owner /var/tmp/etilqs_* rw,
# Chrome/Chromium should be modified to use snap.$SNAP_NAME.* or the snap
# packaging adjusted to use LD_PRELOAD technique from LP: #1577514
-owner /{dev,run}/shm/{,.}org.chromium.Chromium.* rw,
-owner /{dev,run}/shm/{,.}com.google.Chrome.* rw,
+owner /{dev,run}/shm/{,.}org.chromium.Chromium.* mrw,
+owner /{dev,run}/shm/{,.}com.google.Chrome.* mrw,
# Allow reading platform files
/run/udev/data/+platform:* r,
+# Chromium content api in gnome-shell reads this
+/etc/opt/chrome/{,**} r,
+
# Chrome/Chromium should be adjusted to not use gconf. It is only used with
# legacy systems that don't have snapd
deny dbus (send)
@@ -190,11 +192,14 @@ owner @{PROC}/@{pid}/uid_map rw,
owner @{PROC}/@{pid}/gid_map rw,
# Webkit uses a particular SHM names # LP: 1578217
-owner /{dev,run}/shm/WK2SharedMemory.* rw,
+owner /{dev,run}/shm/WK2SharedMemory.* mrw,
+
+# Chromium content api on (at least) later versions of Ubuntu just use this
+owner /{dev,run}/shm/shmfd-* mrw,
`
const browserSupportConnectedPlugSecComp = `
-# Description: Can access various APIs needed by modern browers (eg, Google
+# Description: Can access various APIs needed by modern browsers (eg, Google
# Chrome/Chromium and Mozilla) and file paths they expect. This interface is
# transitional and is only in place while upstream's work to change their paths
# and snappy is updated to properly mediate the APIs.
diff --git a/interfaces/builtin/browser_support_test.go b/interfaces/builtin/browser_support_test.go
index 3368002acd..1d479a95cd 100644
--- a/interfaces/builtin/browser_support_test.go
+++ b/interfaces/builtin/browser_support_test.go
@@ -109,7 +109,7 @@ func (s *BrowserSupportInterfaceSuite) TestConnectedPlugSnippetWithoutAttrib(c *
c.Assert(err, IsNil)
c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"})
snippet := apparmorSpec.SnippetForTag("snap.other.app2")
- c.Assert(string(snippet), testutil.Contains, `# Description: Can access various APIs needed by modern browers`)
+ c.Assert(string(snippet), testutil.Contains, `# Description: Can access various APIs needed by modern browsers`)
c.Assert(string(snippet), Not(testutil.Contains), `capability sys_admin,`)
c.Assert(string(snippet), testutil.Contains, `deny ptrace (trace) peer=snap.@{SNAP_NAME}.**`)
@@ -118,7 +118,7 @@ func (s *BrowserSupportInterfaceSuite) TestConnectedPlugSnippetWithoutAttrib(c *
c.Assert(err, IsNil)
c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"})
secCompSnippet := seccompSpec.SnippetForTag("snap.other.app2")
- c.Assert(secCompSnippet, testutil.Contains, `# Description: Can access various APIs needed by modern browers`)
+ c.Assert(secCompSnippet, testutil.Contains, `# Description: Can access various APIs needed by modern browsers`)
c.Assert(secCompSnippet, Not(testutil.Contains), `chroot`)
}
@@ -142,7 +142,7 @@ apps:
c.Assert(err, IsNil)
c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.browser-support-plug-snap.app2"})
snippet := apparmorSpec.SnippetForTag("snap.browser-support-plug-snap.app2")
- c.Assert(snippet, testutil.Contains, `# Description: Can access various APIs needed by modern browers`)
+ c.Assert(snippet, testutil.Contains, `# Description: Can access various APIs needed by modern browsers`)
c.Assert(snippet, Not(testutil.Contains), `capability sys_admin,`)
c.Assert(snippet, testutil.Contains, `deny ptrace (trace) peer=snap.@{SNAP_NAME}.**`)
@@ -151,7 +151,7 @@ apps:
c.Assert(err, IsNil)
c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.browser-support-plug-snap.app2"})
secCompSnippet := seccompSpec.SnippetForTag("snap.browser-support-plug-snap.app2")
- c.Assert(secCompSnippet, testutil.Contains, `# Description: Can access various APIs needed by modern browers`)
+ c.Assert(secCompSnippet, testutil.Contains, `# Description: Can access various APIs needed by modern browsers`)
c.Assert(secCompSnippet, Not(testutil.Contains), `chroot`)
}
@@ -174,7 +174,7 @@ apps:
c.Assert(err, IsNil)
c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.browser-support-plug-snap.app2"})
snippet := apparmorSpec.SnippetForTag("snap.browser-support-plug-snap.app2")
- c.Assert(snippet, testutil.Contains, `# Description: Can access various APIs needed by modern browers`)
+ c.Assert(snippet, testutil.Contains, `# Description: Can access various APIs needed by modern browsers`)
c.Assert(snippet, testutil.Contains, `ptrace (trace) peer=snap.@{SNAP_NAME}.**`)
c.Assert(snippet, Not(testutil.Contains), `deny ptrace (trace) peer=snap.@{SNAP_NAME}.**`)
@@ -183,7 +183,7 @@ apps:
c.Assert(err, IsNil)
c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.browser-support-plug-snap.app2"})
secCompSnippet := seccompSpec.SnippetForTag("snap.browser-support-plug-snap.app2")
- c.Assert(secCompSnippet, testutil.Contains, `# Description: Can access various APIs needed by modern browers`)
+ c.Assert(secCompSnippet, testutil.Contains, `# Description: Can access various APIs needed by modern browsers`)
c.Assert(secCompSnippet, testutil.Contains, `chroot`)
}
diff --git a/interfaces/builtin/core_support.go b/interfaces/builtin/core_support.go
index 7e28fab4dc..ce382f171d 100644
--- a/interfaces/builtin/core_support.go
+++ b/interfaces/builtin/core_support.go
@@ -53,8 +53,11 @@ const coreSupportConnectedPlugAppArmor = `
# Allow modifying logind configuration. For now, allow reading all logind
# configuration but only allow modifying NN-snap*.conf and snap*.conf files
-# in /etc/systemd/logind.conf.d.
+# in /etc/systemd/logind.conf.d. Also allow creating the logind.conf.d
+# directory as it may not be there for existing installs (wirtable-path
+# magic oddness).
/etc/systemd/logind.conf r,
+/etc/systemd/logind.conf.d/ rw,
/etc/systemd/logind.conf.d/{,*} r,
/etc/systemd/logind.conf.d/{,[0-9][0-9]-}snap*.conf w,
@@ -79,7 +82,9 @@ owner /boot/uboot/config.txt.tmp rwk,
func NewCoreSupportInterface() interfaces.Interface {
return &commonInterface{
name: "core-support",
- connectedPlugAppArmor: coreSupportConnectedPlugAppArmor,
+ // NOTE: cure-support implicitly contains the rules from network-bind.
+ connectedPlugAppArmor: coreSupportConnectedPlugAppArmor + networkBindConnectedPlugAppArmor,
+ connectedPlugSecComp: "" + networkBindConnectedPlugSecComp,
reservedForOS: true,
}
}
diff --git a/interfaces/builtin/docker_support.go b/interfaces/builtin/docker_support.go
index 72540017b9..0d09f3b03d 100644
--- a/interfaces/builtin/docker_support.go
+++ b/interfaces/builtin/docker_support.go
@@ -101,7 +101,7 @@ signal (send) peer=docker-default,
ptrace (read, trace) peer=docker-default,
# Graph (storage) driver bits
-/{dev,run}/shm/aufs.xino rw,
+/{dev,run}/shm/aufs.xino mrw,
/proc/fs/aufs/plink_maint w,
/sys/fs/aufs/** r,
diff --git a/interfaces/builtin/firewall_control.go b/interfaces/builtin/firewall_control.go
index 995de968c1..24a600d8d3 100644
--- a/interfaces/builtin/firewall_control.go
+++ b/interfaces/builtin/firewall_control.go
@@ -68,11 +68,24 @@ unix (bind) type=stream addr="@xtables",
@{PROC}/sys/net/netfilter/** r,
@{PROC}/sys/net/nf_conntrack_max r,
+# check the state of the Kmod modules
+/sys/module/arp_tables/ r,
+/sys/module/arp_tables/initstate r,
+/sys/module/br_netfilter/ r,
+/sys/module/br_netfilter/initstate r,
+/sys/module/iptable_filter/ r,
+/sys/module/iptable_filter/initstate r,
+/sys/module/ip6table_filter/ r,
+/sys/module/ip6table_filter/initstate r,
+
# read netfilter module parameters
-/sys/module/nf_*/ r,
-/sys/module/nf_*/parameters/{,*} r,
+/sys/module/nf_*/ r,
+/sys/module/nf_*/parameters/{,*} r,
# various firewall related sysctl files
+@{PROC}/sys/net/bridge/bridge-nf-call-arptables rw,
+@{PROC}/sys/net/bridge/bridge-nf-call-iptables rw,
+@{PROC}/sys/net/bridge/bridge-nf-call-ip6tables rw,
@{PROC}/sys/net/ipv4/conf/*/rp_filter w,
@{PROC}/sys/net/ipv{4,6}/conf/*/accept_source_route w,
@{PROC}/sys/net/ipv{4,6}/conf/*/accept_redirects w,
@@ -98,7 +111,11 @@ capset
setuid
`
-var firewallControlConnectedPlugKmod = []string{"ip6table_filter", "iptable_filter"}
+var firewallControlConnectedPlugKmod = []string{
+ "arp_tables",
+ "br_netfilter",
+ "ip6table_filter",
+ "iptable_filter"}
// NewFirewallControlInterface returns a new "firewall-control" interface.
func NewFirewallControlInterface() interfaces.Interface {
diff --git a/interfaces/builtin/firewall_control_test.go b/interfaces/builtin/firewall_control_test.go
index 927650b4b6..de3054bc00 100644
--- a/interfaces/builtin/firewall_control_test.go
+++ b/interfaces/builtin/firewall_control_test.go
@@ -106,6 +106,8 @@ func (s *FirewallControlInterfaceSuite) TestUsedSecuritySystems(c *C) {
err = spec.AddConnectedPlug(s.iface, s.plug, s.slot)
c.Assert(err, IsNil)
c.Assert(spec.Modules(), DeepEquals, map[string]bool{
+ "arp_tables": true,
+ "br_netfilter": true,
"ip6table_filter": true,
"iptable_filter": true,
})
diff --git a/interfaces/builtin/kernel_module_control.go b/interfaces/builtin/kernel_module_control.go
index 79bbc7bbbf..89bc4a1cfb 100644
--- a/interfaces/builtin/kernel_module_control.go
+++ b/interfaces/builtin/kernel_module_control.go
@@ -36,6 +36,9 @@ const kernelModuleControlConnectedPlugAppArmor = `
# /proc/sys/kernel/dmesg_restrict is '1' (syslog(2)). These operations are
# required to verify kernel modules that are loaded.
capability syslog,
+
+ # Allow plug side to read information about loaded kernel modules
+ /sys/module/{,**} r,
`
const kernelModuleControlConnectedPlugSecComp = `
diff --git a/interfaces/builtin/kubernetes_support.go b/interfaces/builtin/kubernetes_support.go
new file mode 100644
index 0000000000..a14d042f30
--- /dev/null
+++ b/interfaces/builtin/kubernetes_support.go
@@ -0,0 +1,83 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2017 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
+
+import (
+ "github.com/snapcore/snapd/interfaces"
+)
+
+const kubernetesSupportConnectedPlugAppArmor = `
+# Description: can use kubernetes to control Docker containers. This interface
+# is restricted because it gives wide ranging access to the host and other
+# processes.
+
+# what is this for?
+#include <abstractions/dbus-strict>
+
+# Allow reading all information regarding cgroups for all processes
+capability sys_resource,
+@{PROC}/@{pid}/cgroup r,
+/sys/fs/cgroup/{,**} r,
+
+# Allow adjusting the OOM score for Docker containers. Note, this allows
+# adjusting for all processes, not just containers.
+@{PROC}/@{pid}/oom_score_adj rw,
+/sys/kernel/mm/hugepages/ r,
+
+@{PROC}/sys/kernel/panic rw,
+@{PROC}/sys/kernel/panic_on_oops rw,
+@{PROC}/sys/kernel/keys/root_maxbytes r,
+@{PROC}/sys/kernel/keys/root_maxkeys r,
+@{PROC}/sys/vm/overcommit_memory rw,
+@{PROC}/sys/vm/panic_on_oom r,
+
+@{PROC}/diskstats r,
+@{PROC}/@{pid}/cmdline r,
+
+# Allow reading the state of modules kubernetes needs
+/sys/module/llc/initstate r,
+/sys/module/stp/initstate r,
+
+# Allow listing kernel modules. Note, seccomp blocks module loading syscalls
+/sys/module/apparmor/parameters/enabled r,
+/bin/kmod ixr,
+/etc/modprobe.d/{,**} r,
+
+# Allow ptracing Docker containers
+ptrace (read, trace) peer=docker-default,
+ptrace (read, trace) peer=snap.docker.dockerd,
+
+# Should we have a 'privileged' mode for kubernetes like we do with
+# docker-support? Right now kubernetes needs this which allows it to control
+# all processes on the system and on <4.8 kernels, escape confinement.
+ptrace (read, trace) peer=unconfined,
+`
+
+var kubernetesSupportConnectedPlugKmod = []string{`llc`, `stp`}
+
+// NewKubernetesSupportInterface returns a new "openvswitch-support" interface.
+func NewKubernetesSupportInterface() interfaces.Interface {
+ return &commonInterface{
+ name: "kubernetes-support",
+ connectedPlugAppArmor: kubernetesSupportConnectedPlugAppArmor,
+ connectedPlugKModModules: kubernetesSupportConnectedPlugKmod,
+ reservedForOS: true,
+ }
+}
diff --git a/interfaces/builtin/kubernetes_support_test.go b/interfaces/builtin/kubernetes_support_test.go
new file mode 100644
index 0000000000..cfb8917bb3
--- /dev/null
+++ b/interfaces/builtin/kubernetes_support_test.go
@@ -0,0 +1,104 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2017 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/interfaces/kmod"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/snaptest"
+ "github.com/snapcore/snapd/testutil"
+)
+
+type KubernetesSupportInterfaceSuite struct {
+ iface interfaces.Interface
+ slot *interfaces.Slot
+ plug *interfaces.Plug
+}
+
+const k8sMockPlugSnapInfoYaml = `name: other
+version: 1.0
+apps:
+ app2:
+ command: foo
+ plugs: [kubernetes-support]
+`
+
+var _ = Suite(&KubernetesSupportInterfaceSuite{})
+
+func (s *KubernetesSupportInterfaceSuite) SetUpTest(c *C) {
+ s.iface = builtin.NewKubernetesSupportInterface()
+ s.slot = &interfaces.Slot{
+ SlotInfo: &snap.SlotInfo{
+ Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS},
+ Name: "kubernetes-support",
+ Interface: "kubernetes-support",
+ },
+ }
+ plugSnap := snaptest.MockInfo(c, k8sMockPlugSnapInfoYaml, nil)
+ s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["kubernetes-support"]}
+}
+
+func (s *KubernetesSupportInterfaceSuite) TestName(c *C) {
+ c.Assert(s.iface.Name(), Equals, "kubernetes-support")
+}
+
+func (s *KubernetesSupportInterfaceSuite) TestSanitizeSlot(c *C) {
+ err := s.iface.SanitizeSlot(s.slot)
+ c.Assert(err, IsNil)
+ err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{
+ Snap: &snap.Info{SuggestedName: "some-snap"},
+ Name: "kubernetes-support",
+ Interface: "kubernetes-support",
+ }})
+ c.Assert(err, ErrorMatches, "kubernetes-support slots are reserved for the operating system snap")
+}
+
+func (s *KubernetesSupportInterfaceSuite) TestSanitizePlug(c *C) {
+ err := s.iface.SanitizePlug(s.plug)
+ c.Assert(err, IsNil)
+}
+
+func (s *KubernetesSupportInterfaceSuite) TestSanitizeIncorrectInterface(c *C) {
+ c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) },
+ PanicMatches, `slot is not of interface "kubernetes-support"`)
+ c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) },
+ PanicMatches, `plug is not of interface "kubernetes-support"`)
+}
+
+func (s *KubernetesSupportInterfaceSuite) TestUsedSecuritySystems(c *C) {
+ kmodSpec := &kmod.Specification{}
+ err := kmodSpec.AddConnectedPlug(s.iface, s.plug, s.slot)
+ c.Assert(err, IsNil)
+ c.Assert(kmodSpec.Modules(), DeepEquals, map[string]bool{
+ "llc": true,
+ "stp": true,
+ })
+
+ apparmorSpec := &apparmor.Specification{}
+ err = apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot)
+ c.Assert(err, IsNil)
+ c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"})
+ c.Check(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "# Allow reading the state of modules kubernetes needs\n")
+}
diff --git a/interfaces/builtin/log_observe.go b/interfaces/builtin/log_observe.go
index 80f26e31e4..2e1e6f49dd 100644
--- a/interfaces/builtin/log_observe.go
+++ b/interfaces/builtin/log_observe.go
@@ -32,6 +32,7 @@ const logObserveConnectedPlugAppArmor = `
/run/log/journal/ r,
/run/log/journal/** r,
/var/lib/systemd/catalog/database r,
+/{,usr/}bin/journalctl ixr,
# Allow sysctl -w kernel.printk_ratelimit=#
/{,usr/}sbin/sysctl ixr,
diff --git a/interfaces/builtin/mir.go b/interfaces/builtin/mir.go
index 731699c5f3..38ff62ec65 100644
--- a/interfaces/builtin/mir.go
+++ b/interfaces/builtin/mir.go
@@ -35,7 +35,7 @@ const mirPermanentSlotAppArmor = `
capability sys_tty_config,
/dev/tty[0-9]* rw,
-/{dev,run}/shm/\#* rw,
+/{dev,run}/shm/\#* mrw,
/run/mir_socket rw,
# Needed for mode setting via drmSetMaster() and drmDropMaster()
diff --git a/interfaces/builtin/pulseaudio.go b/interfaces/builtin/pulseaudio.go
index 96d42cc63d..f8632194a9 100644
--- a/interfaces/builtin/pulseaudio.go
+++ b/interfaces/builtin/pulseaudio.go
@@ -27,7 +27,7 @@ import (
)
const pulseaudioConnectedPlugAppArmor = `
-/{run,dev}/shm/pulse-shm-* rwk,
+/{run,dev}/shm/pulse-shm-* mrwk,
owner /{,var/}run/pulse/ r,
owner /{,var/}run/pulse/native rwk,
@@ -85,7 +85,7 @@ owner /{,var/}run/pulse/ rw,
owner /{,var/}run/pulse/** rwk,
# Shared memory based communication with clients
-/{run,dev}/shm/pulse-shm-* rwk,
+/{run,dev}/shm/pulse-shm-* mrwk,
/usr/share/applications/ r,
diff --git a/interfaces/builtin/unity7.go b/interfaces/builtin/unity7.go
index c6a7a06dbe..3651a58a26 100644
--- a/interfaces/builtin/unity7.go
+++ b/interfaces/builtin/unity7.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2016 Canonical Ltd
+ * Copyright (C) 2016-2017 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
@@ -20,14 +20,19 @@
package builtin
import (
+ "fmt"
+ "strings"
+
"github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/interfaces/apparmor"
+ "github.com/snapcore/snapd/interfaces/seccomp"
+ "github.com/snapcore/snapd/snap"
)
-// http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/unity7
const unity7ConnectedPlugAppArmor = `
-# Description: Can access Unity7. Restricted because Unity 7 runs on X and
-# requires access to various DBus services and this environment does not prevent
-# eavesdropping or apps interfering with one another.
+# Description: Can access Unity7. Note, Unity 7 runs on X and requires access
+# to various DBus services and this environment does not prevent eavesdropping
+# or apps interfering with one another.
#include <abstractions/dbus-strict>
#include <abstractions/dbus-session-strict>
@@ -401,6 +406,42 @@ dbus (receive)
member=Get*
peer=(label=unconfined),
+# unity messaging menu
+# first, allow finding the desktop file
+/usr/share/applications/ r,
+# this leaks the names of snaps with desktop files
+/var/lib/snapd/desktop/applications/ r,
+/var/lib/snapd/desktop/applications/mimeinfo.cache r,
+/var/lib/snapd/desktop/applications/@{SNAP_NAME}_*.desktop r,
+
+# then allow talking to Unity DBus service
+dbus (send)
+ bus=session
+ interface=org.freedesktop.DBus.Properties
+ path=/com/canonical/indicator/messages/service
+ member=GetAll
+ peer=(label=unconfined),
+
+dbus (send)
+ bus=session
+ path=/com/canonical/indicator/messages/service
+ interface=com.canonical.indicator.messages.service
+ member={Register,Unregister}Application
+ peer=(label=unconfined),
+
+dbus (receive)
+ bus=session
+ interface=org.freedesktop.DBus.Properties
+ path=/com/canonical/indicator/messages/###UNITY_SNAP_NAME###_*_desktop
+ member=GetAll
+ peer=(label=unconfined),
+
+dbus (receive, send)
+ bus=session
+ interface=com.canonical.indicator.messages.application
+ path=/com/canonical/indicator/messages/###UNITY_SNAP_NAME###_*_desktop
+ peer=(label=unconfined),
+
# This rule is meant to be covered by abstractions/dbus-session-strict but
# the unity launcher code has a typo that uses /org/freedesktop/dbus as the
# path instead of /org/freedesktop/DBus, so we need to all it here.
@@ -460,22 +501,60 @@ dbus (send)
deny /{dev,run,var/run}/shm/lttng-ust-* rw,
`
-// http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/seccomp/policygroups/ubuntu-core/16.04/unity7
-const unity7ConnectedPlugSecComp = `
-# Description: Can access Unity7. Restricted because Unity 7 runs on X and
-# requires access to various DBus services and this environment does not prevent
-# eavesdropping or apps interfering with one another.
+const unity7ConnectedPlugSeccomp = `
+# Description: Can access Unity7. Note, Unity 7 runs on X and requires access
+# to various DBus services and this environment does not prevent eavesdropping
+# or apps interfering with one another.
# X
shutdown
`
-// NewUnity7Interface returns a new "unity7" interface.
-func NewUnity7Interface() interfaces.Interface {
- return &commonInterface{
- name: "unity7",
- connectedPlugAppArmor: unity7ConnectedPlugAppArmor,
- connectedPlugSecComp: unity7ConnectedPlugSecComp,
- reservedForOS: true,
+type Unity7Interface struct{}
+
+func (iface *Unity7Interface) Name() string {
+ return "unity7"
+}
+
+func (iface *Unity7Interface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, slot *interfaces.Slot) error {
+ // Unity7 will take the desktop filename and convert all '-' (and '.',
+ // but we don't care about that here because the rule above already
+ // does that) to '_'. Since we know that the desktop filename starts
+ // with the snap name, perform this conversion on the snap name.
+ new := strings.Replace(plug.Snap.Name(), "-", "_", -1)
+ old := "###UNITY_SNAP_NAME###"
+ snippet := strings.Replace(unity7ConnectedPlugAppArmor, old, new, -1)
+ spec.AddSnippet(snippet)
+ return nil
+}
+
+func (iface *Unity7Interface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.Plug, slot *interfaces.Slot) error {
+ spec.AddSnippet(unity7ConnectedPlugSeccomp)
+ return nil
+}
+
+func (iface *Unity7Interface) SanitizePlug(plug *interfaces.Plug) error {
+ if iface.Name() != plug.Interface {
+ panic(fmt.Sprintf("plug is not of interface %q", iface.Name()))
}
+
+ return nil
+}
+
+func (iface *Unity7Interface) SanitizeSlot(slot *interfaces.Slot) error {
+ if iface.Name() != slot.Interface {
+ panic(fmt.Sprintf("slot is not of interface %q", iface.Name()))
+ }
+
+ // Creation of the slot of this type is allowed only by the os snap
+ if !(slot.Snap.Type == snap.TypeOS) {
+ return fmt.Errorf("%s slots are reserved for the operating system snap", iface.Name())
+ }
+
+ return nil
+}
+
+func (iface *Unity7Interface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool {
+ // allow what declarations allowed
+ return true
}
diff --git a/interfaces/builtin/unity7_test.go b/interfaces/builtin/unity7_test.go
index 912ac2f1b3..106a8a833c 100644
--- a/interfaces/builtin/unity7_test.go
+++ b/interfaces/builtin/unity7_test.go
@@ -39,7 +39,7 @@ type Unity7InterfaceSuite struct {
var _ = Suite(&Unity7InterfaceSuite{})
-const unity7mockPlugSnapInfoYaml = `name: other
+const unity7mockPlugSnapInfoYaml = `name: other-snap
version: 1.0
apps:
app2:
@@ -48,9 +48,7 @@ apps:
`
func (s *Unity7InterfaceSuite) SetUpTest(c *C) {
- s.iface = builtin.NewUnity7Interface()
- plugSnap := snaptest.MockInfo(c, unity7mockPlugSnapInfoYaml, nil)
- s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["unity7"]}
+ s.iface = &builtin.Unity7Interface{}
s.slot = &interfaces.Slot{
SlotInfo: &snap.SlotInfo{
Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS},
@@ -58,6 +56,8 @@ func (s *Unity7InterfaceSuite) SetUpTest(c *C) {
Interface: "unity7",
},
}
+ plugSnap := snaptest.MockInfo(c, unity7mockPlugSnapInfoYaml, nil)
+ s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["unity7"]}
}
func (s *Unity7InterfaceSuite) TestName(c *C) {
@@ -81,9 +81,9 @@ func (s *Unity7InterfaceSuite) TestSanitizePlug(c *C) {
}
func (s *Unity7InterfaceSuite) TestSanitizeIncorrectInterface(c *C) {
- c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) },
+ c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other-snap"}}) },
PanicMatches, `slot is not of interface "unity7"`)
- c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) },
+ c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other-snap"}}) },
PanicMatches, `plug is not of interface "unity7"`)
}
@@ -92,13 +92,14 @@ func (s *Unity7InterfaceSuite) TestUsedSecuritySystems(c *C) {
apparmorSpec := &apparmor.Specification{}
err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot)
c.Assert(err, IsNil)
- c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"})
- c.Assert(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, `/usr/share/pixmaps`)
+ c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other-snap.app2"})
+ c.Assert(apparmorSpec.SnippetForTag("snap.other-snap.app2"), testutil.Contains, `/usr/share/pixmaps`)
+ c.Assert(apparmorSpec.SnippetForTag("snap.other-snap.app2"), testutil.Contains, `path=/com/canonical/indicator/messages/other_snap_*_desktop`)
// connected plugs have a non-nil security snippet for seccomp
seccompSpec := &seccomp.Specification{}
err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot)
c.Assert(err, IsNil)
- c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"})
- c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "shutdown\n")
+ c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other-snap.app2"})
+ c.Check(seccompSpec.SnippetForTag("snap.other-snap.app2"), testutil.Contains, "shutdown\n")
}
diff --git a/interfaces/core.go b/interfaces/core.go
index d48c7bf7d2..bb1b3b6cdb 100644
--- a/interfaces/core.go
+++ b/interfaces/core.go
@@ -45,7 +45,7 @@ type PlugRef struct {
}
// String returns the "snap:plug" representation of a plug reference.
-func (ref *PlugRef) String() string {
+func (ref PlugRef) String() string {
return fmt.Sprintf("%s:%s", ref.Snap, ref.Name)
}
@@ -67,7 +67,7 @@ type SlotRef struct {
}
// String returns the "snap:slot" representation of a slot reference.
-func (ref *SlotRef) String() string {
+func (ref SlotRef) String() string {
return fmt.Sprintf("%s:%s", ref.Snap, ref.Name)
}
diff --git a/interfaces/core_test.go b/interfaces/core_test.go
index a224b81a39..7b2d0aaadf 100644
--- a/interfaces/core_test.go
+++ b/interfaces/core_test.go
@@ -133,6 +133,8 @@ func (s *CoreSuite) TestPlugRef(c *C) {
func (s *CoreSuite) TestPlugRefString(c *C) {
ref := PlugRef{Snap: "snap", Name: "plug"}
c.Check(ref.String(), Equals, "snap:plug")
+ refPtr := &PlugRef{Snap: "snap", Name: "plug"}
+ c.Check(refPtr.String(), Equals, "snap:plug")
}
// Slot.Ref works as expected
@@ -147,6 +149,8 @@ func (s *CoreSuite) TestSlotRef(c *C) {
func (s *CoreSuite) TestSlotRefString(c *C) {
ref := SlotRef{Snap: "snap", Name: "slot"}
c.Check(ref.String(), Equals, "snap:slot")
+ refPtr := &SlotRef{Snap: "snap", Name: "slot"}
+ c.Check(refPtr.String(), Equals, "snap:slot")
}
// ConnRef.ID works as expected
diff --git a/interfaces/mount/entry.go b/interfaces/mount/entry.go
index 4afb57f776..c176dc7061 100644
--- a/interfaces/mount/entry.go
+++ b/interfaces/mount/entry.go
@@ -20,10 +20,7 @@
package mount
import (
- "bufio"
- "bytes"
"fmt"
- "io"
"strconv"
"strings"
"syscall"
@@ -144,54 +141,6 @@ func ParseEntry(s string) (Entry, error) {
return e, nil
}
-// LoadFSTab reads and parses an fstab-like file.
-//
-// The supported format is described by fstab(5).
-func LoadFSTab(reader io.Reader) ([]Entry, error) {
- var entries []Entry
- scanner := bufio.NewScanner(reader)
- for scanner.Scan() {
- s := scanner.Text()
- if i := strings.IndexByte(s, '#'); i != -1 {
- s = s[0:i]
- }
- s = strings.TrimSpace(s)
- if s == "" {
- continue
- }
- entry, err := ParseEntry(s)
- if err != nil {
- return nil, err
- }
- entries = append(entries, entry)
- }
- if err := scanner.Err(); err != nil {
- return nil, err
- }
- return entries, nil
-}
-
-// SaveFSTab writes a list of entries to a fstab-like file.
-//
-// The supported format is described by fstab(5).
-//
-// Note that there is no support for comments, both the LoadFSTab function and
-// SaveFSTab just ignore them.
-//
-// Note that there is no attempt to use atomic file write/rename tricks. The
-// created file will typically live in /run/snapd/ns/$SNAP_NAME.fstab and will
-// be done so, while holidng a flock-based-lock, by the snap-update-ns program.
-func SaveFSTab(writer io.Writer, entries []Entry) error {
- var buf bytes.Buffer
- for i := range entries {
- if _, err := fmt.Fprintf(&buf, "%s\n", entries[i]); err != nil {
- return err
- }
- }
- _, err := buf.WriteTo(writer)
- return err
-}
-
// OptsToFlags converts mount options strings to a mount flag.
func OptsToFlags(opts []string) (flags int, err error) {
for _, opt := range opts {
diff --git a/interfaces/mount/entry_test.go b/interfaces/mount/entry_test.go
index 3b443ecc7e..47715d17ae 100644
--- a/interfaces/mount/entry_test.go
+++ b/interfaces/mount/entry_test.go
@@ -20,8 +20,6 @@
package mount_test
import (
- "bytes"
- "strings"
"syscall"
. "gopkg.in/check.v1"
@@ -153,52 +151,6 @@ func (s *entrySuite) TestParseEntry6(c *C) {
c.Assert(e.CheckPassNumber, Equals, 7)
}
-// Test that empty fstab is parsed without errors
-func (s *entrySuite) TestLoadFSTab1(c *C) {
- entries, err := mount.LoadFSTab(strings.NewReader(""))
- c.Assert(err, IsNil)
- c.Assert(entries, HasLen, 0)
-}
-
-// Test that '#'-comments are skipped
-func (s *entrySuite) TestLoadFSTab2(c *C) {
- entries, err := mount.LoadFSTab(strings.NewReader("# comment"))
- c.Assert(err, IsNil)
- c.Assert(entries, HasLen, 0)
-}
-
-// Test that simple profile can be loaded correctly.
-func (s *entrySuite) TestLoadFSTab3(c *C) {
- entries, err := mount.LoadFSTab(strings.NewReader(`
- name-1 dir-1 type-1 options-1 1 1 # 1st entry
- name-2 dir-2 type-2 options-2 2 2 # 2nd entry`))
- c.Assert(err, IsNil)
- c.Assert(entries, HasLen, 2)
- c.Assert(entries, DeepEquals, []mount.Entry{
- {"name-1", "dir-1", "type-1", []string{"options-1"}, 1, 1},
- {"name-2", "dir-2", "type-2", []string{"options-2"}, 2, 2},
- })
-}
-
-// Test that writing an empty fstab file works correctly.
-func (s *entrySuite) TestSaveFSTab1(c *C) {
- var buf bytes.Buffer
- mount.SaveFSTab(&buf, nil)
- c.Assert(buf.String(), Equals, "")
-}
-
-// Test that writing an trivial fstab file works correctly.
-func (s *entrySuite) TestSaveFSTab2(c *C) {
- var buf bytes.Buffer
- mount.SaveFSTab(&buf, []mount.Entry{
- {"name-1", "dir-1", "type-1", []string{"options-1"}, 1, 1},
- {"name-2", "dir-2", "type-2", []string{"options-2"}, 2, 2},
- })
- c.Assert(buf.String(), Equals, ("" +
- "name-1 dir-1 type-1 options-1 1 1\n" +
- "name-2 dir-2 type-2 options-2 2 2\n"))
-}
-
// Test (string) options -> (int) flag conversion code.
func (s *entrySuite) TestOptsToFlags(c *C) {
flags, err := mount.OptsToFlags(nil)
diff --git a/interfaces/mount/mountinfo.go b/interfaces/mount/mountinfo.go
new file mode 100644
index 0000000000..a160f1db43
--- /dev/null
+++ b/interfaces/mount/mountinfo.go
@@ -0,0 +1,123 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2017 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 mount
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+// InfoEntry contains data from /proc/$PID/mountinfo
+//
+// For details please refer to mountinfo documentation at
+// https://www.kernel.org/doc/Documentation/filesystems/proc.txt
+type InfoEntry struct {
+ MountID int
+ ParentID int
+ DevMajor int
+ DevMinor int
+ Root string
+ MountDir string
+ MountOptions map[string]string
+ OptionalFields []string
+ FsType string
+ MountSource string
+ SuperOptions map[string]string
+}
+
+// ParseInfoEntry parses a single line of /proc/$PID/mountinfo file.
+func ParseInfoEntry(s string) (*InfoEntry, error) {
+ var e InfoEntry
+ var err error
+ fields := strings.Fields(s)
+ // The format is variable-length, but at least 10 fields are mandatory.
+ // The (7) below is a list of optional field which is terminated with (8).
+ // 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
+ // (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11)
+ if len(fields) < 10 {
+ return nil, fmt.Errorf("incorrect number of fields, expected at least 10 but found %d", len(fields))
+ }
+ // Parse MountID (decimal number).
+ e.MountID, err = strconv.Atoi(fields[0])
+ if err != nil {
+ return nil, fmt.Errorf("cannot parse mount ID: %q", fields[0])
+ }
+ // Parse ParentID (decimal number).
+ e.ParentID, err = strconv.Atoi(fields[1])
+ if err != nil {
+ return nil, fmt.Errorf("cannot parse parent mount ID: %q", fields[1])
+ }
+ // Parses DevMajor:DevMinor pair (decimal numbers separated by colon).
+ subFields := strings.FieldsFunc(fields[2], func(r rune) bool { return r == ':' })
+ if len(subFields) != 2 {
+ return nil, fmt.Errorf("cannot parse device major:minor number pair: %q", fields[2])
+ }
+ e.DevMajor, err = strconv.Atoi(subFields[0])
+ if err != nil {
+ return nil, fmt.Errorf("cannot parse device major number: %q", subFields[0])
+ }
+ e.DevMinor, err = strconv.Atoi(subFields[1])
+ if err != nil {
+ return nil, fmt.Errorf("cannot parse device minor number: %q", subFields[1])
+ }
+ // NOTE: All string fields use the same escape/unescape logic as fstab files.
+ // Parse Root, MountDir and MountOptions fields.
+ e.Root = unescape(fields[3])
+ e.MountDir = unescape(fields[4])
+ e.MountOptions = parseMountOpts(unescape(fields[5]))
+ // Optional fields are terminated with a "-" value and start
+ // after the mount options field. Skip ahead until we see the "-"
+ // marker.
+ var i int
+ for i = 6; i < len(fields) && fields[i] != "-"; i++ {
+ }
+ if i == len(fields) {
+ return nil, fmt.Errorf("list of optional fields is not terminated properly")
+ }
+ e.OptionalFields = fields[6:i]
+ for j := range e.OptionalFields {
+ e.OptionalFields[j] = unescape(e.OptionalFields[j])
+ }
+ // Parse the last three fixed fields.
+ tailFields := fields[i+1:]
+ if len(tailFields) != 3 {
+ return nil, fmt.Errorf("incorrect number of tail fields, expected 3 but found %d", len(tailFields))
+ }
+ e.FsType = unescape(tailFields[0])
+ e.MountSource = unescape(tailFields[1])
+ e.SuperOptions = parseMountOpts(unescape(tailFields[2]))
+ return &e, nil
+}
+
+func parseMountOpts(opts string) map[string]string {
+ result := make(map[string]string)
+ for _, opt := range strings.Split(opts, ",") {
+ keyValue := strings.SplitN(opt, "=", 2)
+ key := keyValue[0]
+ if len(keyValue) == 2 {
+ value := keyValue[1]
+ result[key] = value
+ } else {
+ result[key] = ""
+ }
+ }
+ return result
+}
diff --git a/interfaces/mount/mountinfo_test.go b/interfaces/mount/mountinfo_test.go
new file mode 100644
index 0000000000..8873c76912
--- /dev/null
+++ b/interfaces/mount/mountinfo_test.go
@@ -0,0 +1,119 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2017 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 mount_test
+
+import (
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/interfaces/mount"
+)
+
+type mountinfoSuite struct{}
+
+var _ = Suite(&mountinfoSuite{})
+
+// Check that parsing the example from kernel documentation works correctly.
+func (s *mountinfoSuite) TestParseInfoEntry1(c *C) {
+ entry, err := mount.ParseInfoEntry(
+ "36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue")
+ c.Assert(err, IsNil)
+ c.Assert(entry.MountID, Equals, 36)
+ c.Assert(entry.ParentID, Equals, 35)
+ c.Assert(entry.DevMajor, Equals, 98)
+ c.Assert(entry.DevMinor, Equals, 0)
+ c.Assert(entry.Root, Equals, "/mnt1")
+ c.Assert(entry.MountDir, Equals, "/mnt2")
+ c.Assert(entry.MountOptions, DeepEquals, map[string]string{"rw": "", "noatime": ""})
+ c.Assert(entry.OptionalFields, DeepEquals, []string{"master:1"})
+ c.Assert(entry.FsType, Equals, "ext3")
+ c.Assert(entry.MountSource, Equals, "/dev/root")
+ c.Assert(entry.SuperOptions, DeepEquals, map[string]string{"rw": "", "errors": "continue"})
+}
+
+// Check that various combinations of optional fields are parsed correctly.
+func (s *mountinfoSuite) TestParseInfoEntry2(c *C) {
+ // No optional fields.
+ entry, err := mount.ParseInfoEntry(
+ "36 35 98:0 /mnt1 /mnt2 rw,noatime - ext3 /dev/root rw,errors=continue")
+ c.Assert(err, IsNil)
+ c.Assert(entry.MountOptions, DeepEquals, map[string]string{"rw": "", "noatime": ""})
+ c.Assert(entry.OptionalFields, HasLen, 0)
+ c.Assert(entry.FsType, Equals, "ext3")
+ // One optional field.
+ entry, err = mount.ParseInfoEntry(
+ "36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue")
+ c.Assert(err, IsNil)
+ c.Assert(entry.MountOptions, DeepEquals, map[string]string{"rw": "", "noatime": ""})
+ c.Assert(entry.OptionalFields, DeepEquals, []string{"master:1"})
+ c.Assert(entry.FsType, Equals, "ext3")
+ // Two optional fields.
+ entry, err = mount.ParseInfoEntry(
+ "36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 slave:2 - ext3 /dev/root rw,errors=continue")
+ c.Assert(err, IsNil)
+ c.Assert(entry.MountOptions, DeepEquals, map[string]string{"rw": "", "noatime": ""})
+ c.Assert(entry.OptionalFields, DeepEquals, []string{"master:1", "slave:2"})
+ c.Assert(entry.FsType, Equals, "ext3")
+}
+
+// Check that white-space is unescaped correctly.
+func (s *mountinfoSuite) TestParseInfoEntry3(c *C) {
+ entry, err := mount.ParseInfoEntry(
+ `36 35 98:0 /mnt\0401 /mnt\0402 rw\040,noatime mas\040ter:1 - ext\0403 /dev/ro\040ot rw\040,errors=continue`)
+ c.Assert(err, IsNil)
+ c.Assert(entry.MountID, Equals, 36)
+ c.Assert(entry.ParentID, Equals, 35)
+ c.Assert(entry.DevMajor, Equals, 98)
+ c.Assert(entry.DevMinor, Equals, 0)
+ c.Assert(entry.Root, Equals, "/mnt 1")
+ c.Assert(entry.MountDir, Equals, "/mnt 2")
+ c.Assert(entry.MountOptions, DeepEquals, map[string]string{"rw ": "", "noatime": ""})
+ // This field is still escaped as it is space-separated and needs further parsing.
+ c.Assert(entry.OptionalFields, DeepEquals, []string{"mas ter:1"})
+ c.Assert(entry.FsType, Equals, "ext 3")
+ c.Assert(entry.MountSource, Equals, "/dev/ro ot")
+ c.Assert(entry.SuperOptions, DeepEquals, map[string]string{"rw ": "", "errors": "continue"})
+}
+
+// Check that various malformed entries are detected.
+func (s *mountinfoSuite) TestParseInfoEntry4(c *C) {
+ var err error
+ _, err = mount.ParseInfoEntry("36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue foo")
+ c.Assert(err, ErrorMatches, "incorrect number of tail fields, expected 3 but found 4")
+ _, err = mount.ParseInfoEntry("36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root")
+ c.Assert(err, ErrorMatches, "incorrect number of tail fields, expected 3 but found 2")
+ _, err = mount.ParseInfoEntry("36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3")
+ c.Assert(err, ErrorMatches, "incorrect number of fields, expected at least 10 but found 9")
+ _, err = mount.ParseInfoEntry("36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 -")
+ c.Assert(err, ErrorMatches, "incorrect number of fields, expected at least 10 but found 8")
+ _, err = mount.ParseInfoEntry("36 35 98:0 /mnt1 /mnt2 rw,noatime master:1")
+ c.Assert(err, ErrorMatches, "incorrect number of fields, expected at least 10 but found 7")
+ _, err = mount.ParseInfoEntry("36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 garbage1 garbage2 garbage3")
+ c.Assert(err, ErrorMatches, "list of optional fields is not terminated properly")
+ _, err = mount.ParseInfoEntry("foo 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue foo")
+ c.Assert(err, ErrorMatches, `cannot parse mount ID: "foo"`)
+ _, err = mount.ParseInfoEntry("36 bar 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue foo")
+ c.Assert(err, ErrorMatches, `cannot parse parent mount ID: "bar"`)
+ _, err = mount.ParseInfoEntry("36 35 froz:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue foo")
+ c.Assert(err, ErrorMatches, `cannot parse device major number: "froz"`)
+ _, err = mount.ParseInfoEntry("36 35 98:bot /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue foo")
+ c.Assert(err, ErrorMatches, `cannot parse device minor number: "bot"`)
+ _, err = mount.ParseInfoEntry("36 35 corrupt /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue foo")
+ c.Assert(err, ErrorMatches, `cannot parse device major:minor number pair: "corrupt"`)
+}
diff --git a/interfaces/mount/profile.go b/interfaces/mount/profile.go
new file mode 100644
index 0000000000..e610fda4a7
--- /dev/null
+++ b/interfaces/mount/profile.go
@@ -0,0 +1,105 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2017 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 mount
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+
+ "github.com/snapcore/snapd/osutil"
+)
+
+// Profile represents an array of mount entries.
+type Profile struct {
+ Entries []Entry
+}
+
+// LoadProfile loads a mount profile from a given file.
+//
+// The file may be absent, in such case an empty profile is returned without errors.
+func LoadProfile(fname string) (*Profile, error) {
+ f, err := os.Open(fname)
+ if err != nil && os.IsNotExist(err) {
+ return &Profile{}, nil
+ }
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ return ReadProfile(f)
+}
+
+// Save saves a mount profile (fstab-like) to a given file.
+// The profile is saved with an atomic write+rename+sync operation.
+func (p *Profile) Save(fname string) error {
+ var buf bytes.Buffer
+ if _, err := p.WriteTo(&buf); err != nil {
+ return err
+ }
+ return osutil.AtomicWriteFile(fname, buf.Bytes(), 0600, osutil.AtomicWriteFlags(0))
+}
+
+// ReadProfile reads and parses a mount profile.
+//
+// The supported format is described by fstab(5).
+func ReadProfile(reader io.Reader) (*Profile, error) {
+ var p Profile
+ scanner := bufio.NewScanner(reader)
+ for scanner.Scan() {
+ s := scanner.Text()
+ if i := strings.IndexByte(s, '#'); i != -1 {
+ s = s[0:i]
+ }
+ s = strings.TrimSpace(s)
+ if s == "" {
+ continue
+ }
+ entry, err := ParseEntry(s)
+ if err != nil {
+ return nil, err
+ }
+ p.Entries = append(p.Entries, entry)
+ }
+ if err := scanner.Err(); err != nil {
+ return nil, err
+ }
+ return &p, nil
+}
+
+// WriteTo writes a mount profile to the given writer.
+//
+// The supported format is described by fstab(5).
+// Note that there is no support for comments.
+func (p *Profile) WriteTo(writer io.Writer) (int64, error) {
+ var written int64
+ for i := range p.Entries {
+ var n int
+ var err error
+ if n, err = fmt.Fprintf(writer, "%s\n", p.Entries[i]); err != nil {
+ return written, err
+ }
+ written += int64(n)
+ }
+ return written, nil
+}
diff --git a/interfaces/mount/profile_test.go b/interfaces/mount/profile_test.go
new file mode 100644
index 0000000000..8201d31025
--- /dev/null
+++ b/interfaces/mount/profile_test.go
@@ -0,0 +1,127 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2017 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 mount_test
+
+import (
+ "bytes"
+ "io/ioutil"
+ "path/filepath"
+ "strings"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/interfaces/mount"
+)
+
+type profileSuite struct{}
+
+var _ = Suite(&profileSuite{})
+
+// Test that loading a profile from inexisting file returns an empty profile.
+func (s *profileSuite) TestLoadProfile1(c *C) {
+ dir := c.MkDir()
+ p, err := mount.LoadProfile(filepath.Join(dir, "missing"))
+ c.Assert(err, IsNil)
+ c.Assert(p.Entries, HasLen, 0)
+}
+
+// Test that loading profile from a file works as expected.
+func (s *profileSuite) TestLoadProfile2(c *C) {
+ dir := c.MkDir()
+ fname := filepath.Join(dir, "existing")
+ err := ioutil.WriteFile(fname, []byte("name-1 dir-1 type-1 options-1 1 1 # 1st entry"), 0644)
+ c.Assert(err, IsNil)
+ p, err := mount.LoadProfile(fname)
+ c.Assert(err, IsNil)
+ c.Assert(p.Entries, HasLen, 1)
+ c.Assert(p.Entries, DeepEquals, []mount.Entry{
+ {Name: "name-1", Dir: "dir-1", Type: "type-1", Options: []string{"options-1"}, DumpFrequency: 1, CheckPassNumber: 1},
+ })
+}
+
+// Test that saving a profile to a file works correctly.
+func (s *profileSuite) TestSaveProfile1(c *C) {
+ dir := c.MkDir()
+ fname := filepath.Join(dir, "profile")
+ p := &mount.Profile{
+ Entries: []mount.Entry{
+ {Name: "name-1", Dir: "dir-1", Type: "type-1", Options: []string{"options-1"}, DumpFrequency: 1, CheckPassNumber: 1},
+ },
+ }
+ err := p.Save(fname)
+ c.Assert(err, IsNil)
+ data, err := ioutil.ReadFile(fname)
+ c.Assert(err, IsNil)
+ c.Assert(string(data), Equals, "name-1 dir-1 type-1 options-1 1 1\n")
+}
+
+// Test that empty fstab is parsed without errors
+func (s *profileSuite) TestReadProfile1(c *C) {
+ p, err := mount.ReadProfile(strings.NewReader(""))
+ c.Assert(err, IsNil)
+ c.Assert(p.Entries, HasLen, 0)
+}
+
+// Test that '#'-comments are skipped
+func (s *profileSuite) TestReadProfile2(c *C) {
+ p, err := mount.ReadProfile(strings.NewReader("# comment"))
+ c.Assert(err, IsNil)
+ c.Assert(p.Entries, HasLen, 0)
+}
+
+// Test that simple profile can be loaded correctly.
+func (s *profileSuite) TestReadProfile3(c *C) {
+ p, err := mount.ReadProfile(strings.NewReader(`
+ name-1 dir-1 type-1 options-1 1 1 # 1st entry
+ name-2 dir-2 type-2 options-2 2 2 # 2nd entry`))
+ c.Assert(err, IsNil)
+ c.Assert(p.Entries, HasLen, 2)
+ c.Assert(p.Entries, DeepEquals, []mount.Entry{
+ {Name: "name-1", Dir: "dir-1", Type: "type-1", Options: []string{"options-1"}, DumpFrequency: 1, CheckPassNumber: 1},
+ {Name: "name-2", Dir: "dir-2", Type: "type-2", Options: []string{"options-2"}, DumpFrequency: 2, CheckPassNumber: 2},
+ })
+}
+
+// Test that writing an empty fstab file works correctly.
+func (s *profileSuite) TestWriteTo1(c *C) {
+ p := &mount.Profile{}
+ var buf bytes.Buffer
+ n, err := p.WriteTo(&buf)
+ c.Assert(err, IsNil)
+ c.Assert(n, Equals, int64(0))
+ c.Assert(buf.String(), Equals, "")
+}
+
+// Test that writing an trivial fstab file works correctly.
+func (s *profileSuite) TestWriteTo2(c *C) {
+ p := &mount.Profile{
+ Entries: []mount.Entry{
+ {Name: "name-1", Dir: "dir-1", Type: "type-1", Options: []string{"options-1"}, DumpFrequency: 1, CheckPassNumber: 1},
+ {Name: "name-2", Dir: "dir-2", Type: "type-2", Options: []string{"options-2"}, DumpFrequency: 2, CheckPassNumber: 2},
+ },
+ }
+ var buf bytes.Buffer
+ n, err := p.WriteTo(&buf)
+ c.Assert(err, IsNil)
+ c.Assert(n, Equals, int64(68))
+ c.Assert(buf.String(), Equals, ("" +
+ "name-1 dir-1 type-1 options-1 1 1\n" +
+ "name-2 dir-2 type-2 options-2 2 2\n"))
+}
diff --git a/interfaces/repo.go b/interfaces/repo.go
index 4e5bc5238f..f6e4361f97 100644
--- a/interfaces/repo.go
+++ b/interfaces/repo.go
@@ -139,8 +139,10 @@ func (r *Repository) AddPlug(plug *Plug) error {
r.m.Lock()
defer r.m.Unlock()
+ snapName := plug.Snap.Name()
+
// Reject snaps with invalid names
- if err := snap.ValidateName(plug.Snap.Name()); err != nil {
+ if err := snap.ValidateName(snapName); err != nil {
return err
}
// Reject plug with invalid names
@@ -155,13 +157,16 @@ func (r *Repository) AddPlug(plug *Plug) error {
if err := i.SanitizePlug(plug); err != nil {
return fmt.Errorf("cannot add plug: %v", err)
}
- if _, ok := r.plugs[plug.Snap.Name()][plug.Name]; ok {
- return fmt.Errorf("cannot add plug, snap %q already has plug %q", plug.Snap.Name(), plug.Name)
+ if _, ok := r.plugs[snapName][plug.Name]; ok {
+ return fmt.Errorf("snap %q has plugs conflicting on name %q", snapName, plug.Name)
+ }
+ if _, ok := r.slots[snapName][plug.Name]; ok {
+ return fmt.Errorf("snap %q has plug and slot conflicting on name %q", snapName, plug.Name)
}
- if r.plugs[plug.Snap.Name()] == nil {
- r.plugs[plug.Snap.Name()] = make(map[string]*Plug)
+ if r.plugs[snapName] == nil {
+ r.plugs[snapName] = make(map[string]*Plug)
}
- r.plugs[plug.Snap.Name()][plug.Name] = plug
+ r.plugs[snapName][plug.Name] = plug
return nil
}
@@ -233,8 +238,10 @@ func (r *Repository) AddSlot(slot *Slot) error {
r.m.Lock()
defer r.m.Unlock()
+ snapName := slot.Snap.Name()
+
// Reject snaps with invalid names
- if err := snap.ValidateName(slot.Snap.Name()); err != nil {
+ if err := snap.ValidateName(snapName); err != nil {
return err
}
// Reject plug with invalid names
@@ -249,13 +256,16 @@ func (r *Repository) AddSlot(slot *Slot) error {
if err := i.SanitizeSlot(slot); err != nil {
return fmt.Errorf("cannot add slot: %v", err)
}
- if _, ok := r.slots[slot.Snap.Name()][slot.Name]; ok {
- return fmt.Errorf("cannot add slot, snap %q already has slot %q", slot.Snap.Name(), slot.Name)
+ if _, ok := r.slots[snapName][slot.Name]; ok {
+ return fmt.Errorf("snap %q has slots conflicting on name %q", snapName, slot.Name)
+ }
+ if _, ok := r.plugs[snapName][slot.Name]; ok {
+ return fmt.Errorf("snap %q has plug and slot conflicting on name %q", snapName, slot.Name)
}
- if r.slots[slot.Snap.Name()] == nil {
- r.slots[slot.Snap.Name()] = make(map[string]*Slot)
+ if r.slots[snapName] == nil {
+ r.slots[snapName] = make(map[string]*Slot)
}
- r.slots[slot.Snap.Name()][slot.Name] = slot
+ r.slots[snapName][slot.Name] = slot
return nil
}
@@ -742,6 +752,11 @@ func (e *BadInterfacesError) Error() string {
// Unknown interfaces and plugs/slots that don't validate are not added.
// Information about those failures are returned to the caller.
func (r *Repository) AddSnap(snapInfo *snap.Info) error {
+ err := snap.Validate(snapInfo)
+ if err != nil {
+ return err
+ }
+
r.m.Lock()
defer r.m.Unlock()
@@ -762,6 +777,11 @@ func (r *Repository) AddSnap(snapInfo *snap.Info) error {
bad.issues[plugName] = "unknown interface"
continue
}
+ // Reject plug with invalid name
+ if err := ValidateName(plugName); err != nil {
+ bad.issues[plugName] = err.Error()
+ continue
+ }
plug := &Plug{PlugInfo: plugInfo}
if err := iface.SanitizePlug(plug); err != nil {
bad.issues[plugName] = err.Error()
@@ -779,6 +799,11 @@ func (r *Repository) AddSnap(snapInfo *snap.Info) error {
bad.issues[slotName] = "unknown interface"
continue
}
+ // Reject slot with invalid name
+ if err := ValidateName(slotName); err != nil {
+ bad.issues[slotName] = err.Error()
+ continue
+ }
slot := &Slot{SlotInfo: slotInfo}
if err := iface.SanitizeSlot(slot); err != nil {
bad.issues[slotName] = err.Error()
diff --git a/interfaces/repo_test.go b/interfaces/repo_test.go
index b2b5615845..c931c25b0c 100644
--- a/interfaces/repo_test.go
+++ b/interfaces/repo_test.go
@@ -190,15 +190,35 @@ func (s *RepositorySuite) TestAddPlug(c *C) {
c.Assert(s.testRepo.Plug(s.plug.Snap.Name(), s.plug.Name), DeepEquals, s.plug)
}
-func (s *RepositorySuite) TestAddPlugClash(c *C) {
+func (s *RepositorySuite) TestAddPlugClashingPlug(c *C) {
err := s.testRepo.AddPlug(s.plug)
c.Assert(err, IsNil)
err = s.testRepo.AddPlug(s.plug)
- c.Assert(err, ErrorMatches, `cannot add plug, snap "consumer" already has plug "plug"`)
+ c.Assert(err, ErrorMatches, `snap "consumer" has plugs conflicting on name "plug"`)
c.Assert(s.testRepo.AllPlugs(""), HasLen, 1)
c.Assert(s.testRepo.Plug(s.plug.Snap.Name(), s.plug.Name), DeepEquals, s.plug)
}
+func (s *RepositorySuite) TestAddPlugClashingSlot(c *C) {
+ snapInfo := &snap.Info{SuggestedName: "snap"}
+ plug := &Plug{PlugInfo: &snap.PlugInfo{
+ Snap: snapInfo,
+ Name: "clashing",
+ Interface: "interface",
+ }}
+ slot := &Slot{SlotInfo: &snap.SlotInfo{
+ Snap: snapInfo,
+ Name: "clashing",
+ Interface: "interface",
+ }}
+ err := s.testRepo.AddSlot(slot)
+ c.Assert(err, IsNil)
+ err = s.testRepo.AddPlug(plug)
+ c.Assert(err, ErrorMatches, `snap "snap" has plug and slot conflicting on name "clashing"`)
+ c.Assert(s.testRepo.AllSlots(""), HasLen, 1)
+ c.Assert(s.testRepo.Slot(slot.Snap.Name(), slot.Name), DeepEquals, slot)
+}
+
func (s *RepositorySuite) TestAddPlugFailsWithInvalidSnapName(c *C) {
plug := &Plug{
PlugInfo: &snap.PlugInfo{
@@ -477,13 +497,33 @@ func (s *RepositorySuite) TestAddSlotFailsWithInvalidSnapName(c *C) {
c.Assert(s.emptyRepo.AllSlots(""), HasLen, 0)
}
-func (s *RepositorySuite) TestAddSlotFailsForDuplicates(c *C) {
+func (s *RepositorySuite) TestAddSlotClashingSlot(c *C) {
// Adding the first slot succeeds
err := s.testRepo.AddSlot(s.slot)
c.Assert(err, IsNil)
// Adding the slot again fails with appropriate error
err = s.testRepo.AddSlot(s.slot)
- c.Assert(err, ErrorMatches, `cannot add slot, snap "producer" already has slot "slot"`)
+ c.Assert(err, ErrorMatches, `snap "producer" has slots conflicting on name "slot"`)
+}
+
+func (s *RepositorySuite) TestAddSlotClashingPlug(c *C) {
+ snapInfo := &snap.Info{SuggestedName: "snap"}
+ plug := &Plug{PlugInfo: &snap.PlugInfo{
+ Snap: snapInfo,
+ Name: "clashing",
+ Interface: "interface",
+ }}
+ slot := &Slot{SlotInfo: &snap.SlotInfo{
+ Snap: snapInfo,
+ Name: "clashing",
+ Interface: "interface",
+ }}
+ err := s.testRepo.AddPlug(plug)
+ c.Assert(err, IsNil)
+ err = s.testRepo.AddSlot(slot)
+ c.Assert(err, ErrorMatches, `snap "snap" has plug and slot conflicting on name "clashing"`)
+ c.Assert(s.testRepo.AllPlugs(""), HasLen, 1)
+ c.Assert(s.testRepo.Plug(plug.Snap.Name(), plug.Name), DeepEquals, plug)
}
func (s *RepositorySuite) TestAddSlotFailsWithUnsanitizedSlot(c *C) {
@@ -947,7 +987,7 @@ func (s *RepositorySuite) TestResolveDisconnectMatrixTypical(c *C) {
c.Assert(s.testRepo.AddSnap(s.coreSnap), IsNil)
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
- connRef := ConnRef{s.plug.Ref(), s.slot.Ref()}
+ connRef := ConnRef{PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()}
c.Assert(s.testRepo.Connect(connRef), IsNil)
scenarios := []struct {
@@ -1178,13 +1218,13 @@ func (s *RepositorySuite) TestConnectedFindsConnections(c *C) {
conns, err := s.testRepo.Connected(s.plug.Snap.Name(), s.plug.Name)
c.Assert(err, IsNil)
c.Check(conns, DeepEquals, []ConnRef{
- {s.plug.Ref(), s.slot.Ref()},
+ {PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()},
})
conns, err = s.testRepo.Connected(s.slot.Snap.Name(), s.slot.Name)
c.Assert(err, IsNil)
c.Check(conns, DeepEquals, []ConnRef{
- {s.plug.Ref(), s.slot.Ref()},
+ {PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()},
})
}
@@ -1204,7 +1244,7 @@ func (s *RepositorySuite) TestConnectedFindsCoreSnap(c *C) {
conns, err := s.testRepo.Connected("", s.slot.Name)
c.Assert(err, IsNil)
c.Check(conns, DeepEquals, []ConnRef{
- {s.plug.Ref(), slot.Ref()},
+ {PlugRef: s.plug.Ref(), SlotRef: slot.Ref()},
})
}
@@ -1215,7 +1255,7 @@ func (s *RepositorySuite) TestDisconnectAll(c *C) {
c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
c.Assert(s.testRepo.Connect(ConnRef{PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()}), IsNil)
- conns := []ConnRef{{s.plug.Ref(), s.slot.Ref()}}
+ conns := []ConnRef{{PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()}}
s.testRepo.DisconnectAll(conns)
c.Assert(s.testRepo.Interfaces(), DeepEquals, &Interfaces{
Plugs: []*Plug{{PlugInfo: s.plug.PlugInfo}},
@@ -1485,6 +1525,26 @@ apps:
slots: [iface]
`
+const testConsumerInvalidSlotNameYaml = `
+name: consumer
+slots:
+ ttyS5:
+ interface: iface
+apps:
+ app:
+ slots: [iface]
+`
+
+const testConsumerInvalidPlugNameYaml = `
+name: consumer
+plugs:
+ ttyS3:
+ interface: iface
+apps:
+ app:
+ plugs: [iface]
+`
+
func (s *AddRemoveSuite) addSnap(c *C, yaml string) (*snap.Info, error) {
snapInfo := snaptest.MockInfo(c, yaml, nil)
return snapInfo, s.repo.AddSnap(snapInfo)
@@ -1497,6 +1557,18 @@ func (s *AddRemoveSuite) TestAddSnapAddsPlugs(c *C) {
c.Assert(s.repo.Plug("consumer", "iface"), Not(IsNil))
}
+func (s *AddRemoveSuite) TestAddSnapErrorsOnInvalidSlotNames(c *C) {
+ _, err := s.addSnap(c, testConsumerInvalidSlotNameYaml)
+ c.Assert(err, NotNil)
+ c.Check(err, ErrorMatches, `snap "consumer" has bad plugs or slots: ttyS5 \(invalid interface name: "ttyS5"\)`)
+}
+
+func (s *AddRemoveSuite) TestAddSnapErrorsOnInvalidPlugNames(c *C) {
+ _, err := s.addSnap(c, testConsumerInvalidPlugNameYaml)
+ c.Assert(err, NotNil)
+ c.Check(err, ErrorMatches, `snap "consumer" has bad plugs or slots: ttyS3 \(invalid interface name: "ttyS3"\)`)
+}
+
func (s *AddRemoveSuite) TestAddSnapErrorsOnExistingSnapPlugs(c *C) {
_, err := s.addSnap(c, testConsumerYaml)
c.Assert(err, IsNil)
diff --git a/osutil/buildid_test.go b/osutil/buildid_test.go
index b11a2e7aa8..5df65fa5c4 100644
--- a/osutil/buildid_test.go
+++ b/osutil/buildid_test.go
@@ -34,6 +34,10 @@ type buildIDSuite struct{}
var _ = Suite(&buildIDSuite{})
+var truePath = osutil.LookPathDefault("true", "/bin/true")
+var falsePath = osutil.LookPathDefault("false", "/bin/false")
+var gccPath = osutil.LookPathDefault("gcc", "/usr/bin/gcc")
+
func buildID(c *C, fname string) string {
output, err := exec.Command("file", fname).CombinedOutput()
c.Assert(err, IsNil)
@@ -46,7 +50,7 @@ func buildID(c *C, fname string) string {
}
func (s *buildIDSuite) TestReadBuildID(c *C) {
- for _, fname := range []string{"/bin/true", "/bin/false"} {
+ for _, fname := range []string{truePath, falsePath} {
id, err := osutil.ReadBuildID(fname)
c.Assert(err, IsNil)
@@ -56,7 +60,7 @@ func (s *buildIDSuite) TestReadBuildID(c *C) {
func (s *buildIDSuite) TestReadBuildIDNoID(c *C) {
stripedTruth := filepath.Join(c.MkDir(), "true")
- osutil.CopyFile("/bin/true", stripedTruth, 0)
+ osutil.CopyFile(truePath, stripedTruth, 0)
output, err := exec.Command("strip", "-R", ".note.gnu.build-id", stripedTruth).CombinedOutput()
c.Assert(string(output), Equals, "")
c.Assert(err, IsNil)
@@ -67,14 +71,14 @@ func (s *buildIDSuite) TestReadBuildIDNoID(c *C) {
}
func (s *buildIDSuite) TestReadBuildIDmd5(c *C) {
- if !osutil.FileExists("/usr/bin/gcc") {
+ if !osutil.FileExists(gccPath) {
c.Skip("No gcc found")
}
md5Truth := filepath.Join(c.MkDir(), "true")
err := ioutil.WriteFile(md5Truth+".c", []byte(`int main(){return 0;}`), 0644)
c.Assert(err, IsNil)
- output, err := exec.Command("gcc", "-Wl,-build-id=md5", "-xc", md5Truth+".c", "-o", md5Truth).CombinedOutput()
+ output, err := exec.Command(gccPath, "-Wl,-build-id=md5", "-xc", md5Truth+".c", "-o", md5Truth).CombinedOutput()
c.Assert(string(output), Equals, "")
c.Assert(err, IsNil)
diff --git a/osutil/exec_test.go b/osutil/exec_test.go
index 84f67f87aa..c0e725a3ef 100644
--- a/osutil/exec_test.go
+++ b/osutil/exec_test.go
@@ -71,11 +71,11 @@ func (s *commandFromCoreSuite) TestCommandFromCore(c *C) {
root := filepath.Join(dirs.SnapMountDir, "/core/current")
os.MkdirAll(filepath.Join(root, "/usr/bin"), 0755)
- osutil.CopyFile("/bin/true", filepath.Join(root, "/usr/bin/xdelta3"), 0)
+ osutil.CopyFile(truePath, filepath.Join(root, "/usr/bin/xdelta3"), 0)
cmd, err := osutil.CommandFromCore("/usr/bin/xdelta3", "--some-xdelta-arg")
c.Assert(err, IsNil)
- out, err := exec.Command("/bin/sh", "-c", "readelf -l /bin/true |grep interpreter:|cut -f2 -d:|cut -f1 -d]").Output()
+ out, err := exec.Command("/bin/sh", "-c", fmt.Sprintf("readelf -l %s |grep interpreter:|cut -f2 -d:|cut -f1 -d]", truePath)).Output()
c.Assert(err, IsNil)
interp := strings.TrimSpace(string(out))
@@ -93,7 +93,7 @@ func (s *commandFromCoreSuite) TestCommandFromCoreSymlinkCycle(c *C) {
root := filepath.Join(dirs.SnapMountDir, "/core/current")
os.MkdirAll(filepath.Join(root, "/usr/bin"), 0755)
- osutil.CopyFile("/bin/true", filepath.Join(root, "/usr/bin/xdelta3"), 0)
+ osutil.CopyFile(truePath, filepath.Join(root, "/usr/bin/xdelta3"), 0)
out, err := exec.Command("/bin/sh", "-c", "readelf -l /bin/true |grep interpreter:|cut -f2 -d:|cut -f1 -d]").Output()
c.Assert(err, IsNil)
diff --git a/osutil/stat.go b/osutil/stat.go
index d7478c39e5..3ccd5fa723 100644
--- a/osutil/stat.go
+++ b/osutil/stat.go
@@ -63,3 +63,16 @@ func ExecutableExists(name string) bool {
return err == nil
}
+
+var lookPath func(name string) (string, error) = exec.LookPath
+
+// LookPathDefault searches for a given command name in all directories
+// listed in the environment variable PATH and returns the found path or the
+// provided default path.
+func LookPathDefault(name string, defaultPath string) string {
+ p, err := lookPath(name)
+ if err != nil {
+ return defaultPath
+ }
+ return p
+}
diff --git a/osutil/stat_test.go b/osutil/stat_test.go
index bb265c8822..2d0420491f 100644
--- a/osutil/stat_test.go
+++ b/osutil/stat_test.go
@@ -20,6 +20,7 @@
package osutil
import (
+ "fmt"
"io/ioutil"
"os"
"path/filepath"
@@ -89,3 +90,13 @@ func (ts *StatTestSuite) TestExecutableExists(c *C) {
c.Assert(os.Chmod(fname, 0755), IsNil)
c.Check(ExecutableExists("xyzzy"), Equals, true)
}
+
+func (s *StatTestSuite) TestLookPathDefaultGivesCorrectPath(c *C) {
+ lookPath = func(name string) (string, error) { return "/bin/true", nil }
+ c.Assert(LookPathDefault("true", "/bin/foo"), Equals, "/bin/true")
+}
+
+func (s *StatTestSuite) TestLookPathDefaultReturnsDefaultWhenNotFound(c *C) {
+ lookPath = func(name string) (string, error) { return "", fmt.Errorf("Not found") }
+ c.Assert(LookPathDefault("bar", "/bin/bla"), Equals, "/bin/bla")
+}
diff --git a/overlord/devicestate/crypto.go b/overlord/devicestate/crypto.go
new file mode 100644
index 0000000000..0c2328a53d
--- /dev/null
+++ b/overlord/devicestate/crypto.go
@@ -0,0 +1,80 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+/*
+ * Copyright (C) 2016-2017 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 devicestate
+
+import (
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/pem"
+ "errors"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+
+ "github.com/snapcore/snapd/osutil"
+)
+
+func generateRSAKey(keyLength int) (*rsa.PrivateKey, error) {
+ // The temporary directory is created with mode
+ // 0700 by ioutil.TempDir, see:
+ // https://github.com/golang/go/blob/master/src/io/ioutil/tempfile.go#L84
+ tempDir, err := ioutil.TempDir(os.TempDir(), "snapd")
+ if err != nil {
+ return nil, err
+ }
+
+ defer os.RemoveAll(tempDir)
+
+ rsaKeyFile := filepath.Join(tempDir, "rsa.key")
+
+ cmd := exec.Command("ssh-keygen", "-t", "rsa", "-b", strconv.Itoa(keyLength), "-N", "", "-f", rsaKeyFile)
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return nil, osutil.OutputErr(out, err)
+ }
+
+ d, err := ioutil.ReadFile(rsaKeyFile)
+ if err != nil {
+ return nil, err
+ }
+
+ blk, _ := pem.Decode(d)
+ if blk == nil {
+ return nil, errors.New("cannot decode PEM block")
+ }
+
+ key, err := x509.ParsePKCS1PrivateKey(blk.Bytes)
+ if err != nil {
+ return nil, err
+ }
+
+ err = key.Validate()
+ if err != nil {
+ return nil, err
+ }
+
+ err = os.RemoveAll(tempDir)
+ if err != nil {
+ return nil, err
+ }
+
+ return key, err
+}
diff --git a/overlord/devicestate/devicestate_test.go b/overlord/devicestate/devicestate_test.go
index 76a17af137..0565b0fd49 100644
--- a/overlord/devicestate/devicestate_test.go
+++ b/overlord/devicestate/devicestate_test.go
@@ -26,6 +26,7 @@ import (
"io/ioutil"
"net/http"
"net/http/httptest"
+ "os"
"sync"
"testing"
"time"
@@ -72,6 +73,7 @@ type deviceMgrSuite struct {
}
var _ = Suite(&deviceMgrSuite{})
+var testKeyLength = 1024
type fakeStore struct {
state *state.State
@@ -129,10 +131,11 @@ func (sto *fakeStore) Sections(*auth.UserState) ([]string, error) {
func (s *deviceMgrSuite) SetUpTest(c *C) {
dirs.SetRootDir(c.MkDir())
+ os.MkdirAll(dirs.SnapRunDir, 0755)
s.restoreOnClassic = release.MockOnClassic(false)
- rootPrivKey, _ := assertstest.GenerateKey(1024)
+ rootPrivKey, _ := assertstest.GenerateKey(testKeyLength)
storePrivKey, _ := assertstest.GenerateKey(752)
s.storeSigning = assertstest.NewStoreStack("canonical", rootPrivKey, storePrivKey)
s.state = state.New(nil)
@@ -289,7 +292,7 @@ func (s *deviceMgrSuite) setupCore(c *C, name, snapYaml string, snapContents str
}
func (s *deviceMgrSuite) TestFullDeviceRegistrationHappy(c *C) {
- r1 := devicestate.MockKeyLength(752)
+ r1 := devicestate.MockKeyLength(testKeyLength)
defer r1()
s.reqID = "REQID-1"
@@ -361,7 +364,7 @@ version: gadget
}
func (s *deviceMgrSuite) TestDoRequestSerialIdempotentAfterAddSerial(c *C) {
- privKey, _ := assertstest.GenerateKey(1024)
+ privKey, _ := assertstest.GenerateKey(testKeyLength)
s.reqID = "REQID-1"
mockServer := s.mockServer(c)
@@ -430,7 +433,7 @@ version: gadget
}
func (s *deviceMgrSuite) TestDoRequestSerialIdempotentAfterGotSerial(c *C) {
- privKey, _ := assertstest.GenerateKey(1024)
+ privKey, _ := assertstest.GenerateKey(testKeyLength)
s.reqID = "REQID-1"
mockServer := s.mockServer(c)
@@ -499,7 +502,7 @@ version: gadget
}
func (s *deviceMgrSuite) TestFullDeviceRegistrationPollHappy(c *C) {
- r1 := devicestate.MockKeyLength(752)
+ r1 := devicestate.MockKeyLength(testKeyLength)
defer r1()
s.reqID = "REQID-POLL"
@@ -575,7 +578,7 @@ version: gadget
}
func (s *deviceMgrSuite) TestFullDeviceRegistrationHappyPrepareDeviceHook(c *C) {
- r1 := devicestate.MockKeyLength(752)
+ r1 := devicestate.MockKeyLength(testKeyLength)
defer r1()
s.reqID = "REQID-1"
@@ -677,7 +680,7 @@ hooks:
}
func (s *deviceMgrSuite) TestFullDeviceRegistrationErrorBackoff(c *C) {
- r1 := devicestate.MockKeyLength(752)
+ r1 := devicestate.MockKeyLength(testKeyLength)
defer r1()
s.reqID = "REQID-BADREQ"
@@ -882,7 +885,7 @@ func (s *deviceMgrSuite) TestDeviceAssertionsDeviceSessionRequest(c *C) {
// setup state as done by device initialisation
s.state.Lock()
- devKey, _ := assertstest.GenerateKey(1024)
+ devKey, _ := assertstest.GenerateKey(testKeyLength)
encDevKey, err := asserts.EncodePublicKey(devKey.PublicKey())
c.Check(err, IsNil)
seriala, err := s.storeSigning.Sign(asserts.SerialType, map[string]interface{}{
diff --git a/overlord/devicestate/export_test.go b/overlord/devicestate/export_test.go
index 9e721ee006..f539eed406 100644
--- a/overlord/devicestate/export_test.go
+++ b/overlord/devicestate/export_test.go
@@ -27,6 +27,10 @@ import (
)
func MockKeyLength(n int) (restore func()) {
+ if n < 1024 {
+ panic("key length must be >= 1024")
+ }
+
oldKeyLength := keyLength
keyLength = n
return func() {
diff --git a/overlord/devicestate/handlers.go b/overlord/devicestate/handlers.go
index 82f9841edc..320379b5e9 100644
--- a/overlord/devicestate/handlers.go
+++ b/overlord/devicestate/handlers.go
@@ -1,5 +1,4 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
-
/*
* Copyright (C) 2016-2017 Canonical Ltd
*
@@ -21,8 +20,6 @@ package devicestate
import (
"bytes"
- "crypto/rand"
- "crypto/rsa"
"encoding/json"
"errors"
"fmt"
@@ -85,7 +82,9 @@ func (m *DeviceManager) doGenerateDeviceKey(t *state.Task, _ *tomb.Tomb) error {
return nil
}
- keyPair, err := rsa.GenerateKey(rand.Reader, keyLength)
+ st.Unlock()
+ keyPair, err := generateRSAKey(keyLength)
+ st.Lock()
if err != nil {
return fmt.Errorf("cannot generate device key pair: %v", err)
}
diff --git a/overlord/ifacestate/helpers.go b/overlord/ifacestate/helpers.go
index c20a83e1c5..152a026a94 100644
--- a/overlord/ifacestate/helpers.go
+++ b/overlord/ifacestate/helpers.go
@@ -21,6 +21,7 @@ package ifacestate
import (
"fmt"
+ "strings"
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/interfaces"
@@ -47,6 +48,9 @@ func (m *InterfaceManager) initialize(extraInterfaces []interfaces.Interface, ex
if err := m.addSnaps(); err != nil {
return err
}
+ if err := m.renameCorePlugConnection(); err != nil {
+ return err
+ }
if err := m.reloadConnections(""); err != nil {
return err
}
@@ -153,6 +157,35 @@ func (m *InterfaceManager) regenerateAllSecurityProfiles() error {
return nil
}
+// renameCorePlugConnection renames one connection from "core-support" plug to
+// slot so that the plug name is "core-support-plug" while the slot is
+// unchanged. This matches a change introduced in 2.24, where the core snap no
+// longer has the "core-support" plug as that was clashing with the slot with
+// the same name.
+func (m *InterfaceManager) renameCorePlugConnection() error {
+ conns, err := getConns(m.state)
+ if err != nil {
+ return err
+ }
+ const oldPlugName = "core-support"
+ const newPlugName = "core-support-plug"
+ // old connection, note that slotRef is the same in both
+ slotRef := interfaces.SlotRef{Snap: "core", Name: oldPlugName}
+ oldPlugRef := interfaces.PlugRef{Snap: "core", Name: oldPlugName}
+ oldConnRef := interfaces.ConnRef{PlugRef: oldPlugRef, SlotRef: slotRef}
+ oldID := oldConnRef.ID()
+ // if the old connection is saved, replace it with the new connection
+ if cState, ok := conns[oldID]; ok {
+ newPlugRef := interfaces.PlugRef{Snap: "core", Name: newPlugName}
+ newConnRef := interfaces.ConnRef{PlugRef: newPlugRef, SlotRef: slotRef}
+ newID := newConnRef.ID()
+ delete(conns, oldID)
+ conns[newID] = cState
+ setConns(m.state, conns)
+ }
+ return nil
+}
+
// reloadConnections reloads connections stored in the state in the repository.
// Using non-empty snapName the operation can be scoped to connections
// affecting a given snap.
@@ -300,7 +333,27 @@ func (m *InterfaceManager) autoConnect(task *state.Task, snapName string, blackl
continue
}
candidates := m.repo.AutoConnectCandidateSlots(snapName, plug.Name, autochecker.check)
+ if len(candidates) == 0 {
+ continue
+ }
+ // If we are in a core transition we may have both the old ubuntu-core
+ // snap and the new core snap providing the same interface. In that
+ // situation we want to ignore any candidates in ubuntu-core and simply
+ // go with those from the new core snap.
+ if len(candidates) == 2 {
+ switch {
+ case candidates[0].Snap.Name() == "ubuntu-core" && candidates[1].Snap.Name() == "core":
+ candidates = candidates[1:2]
+ case candidates[1].Snap.Name() == "ubuntu-core" && candidates[0].Snap.Name() == "core":
+ candidates = candidates[0:1]
+ }
+ }
if len(candidates) != 1 {
+ crefs := make([]string, 0, len(candidates))
+ for _, candidate := range candidates {
+ crefs = append(crefs, candidate.Ref().String())
+ }
+ task.Logf("cannot auto connect %s (plug auto-connection), candidates found: %q", plug.Ref(), strings.Join(crefs, ", "))
continue
}
slot := candidates[0]
@@ -308,6 +361,7 @@ func (m *InterfaceManager) autoConnect(task *state.Task, snapName string, blackl
key := connRef.ID()
if _, ok := conns[key]; ok {
// Suggested connection already exist so don't clobber it.
+ task.Logf("cannot auto connect %s to %s: (plug auto-connection), existing connection state %q in the way", connRef.PlugRef, connRef.SlotRef, key)
continue
}
if err := m.repo.Connect(connRef); err != nil {
@@ -324,7 +378,15 @@ func (m *InterfaceManager) autoConnect(task *state.Task, snapName string, blackl
continue
}
candidates := m.repo.AutoConnectCandidatePlugs(snapName, slot.Name, autochecker.check)
+ if len(candidates) == 0 {
+ continue
+ }
if len(candidates) != 1 {
+ crefs := make([]string, 0, len(candidates))
+ for _, candidate := range candidates {
+ crefs = append(crefs, candidate.Ref().String())
+ }
+ task.Logf("cannot auto connect %s (slot auto-connection), candidates found: %q", slot.Ref(), strings.Join(crefs, ", "))
continue
}
plug := candidates[0]
@@ -332,6 +394,7 @@ func (m *InterfaceManager) autoConnect(task *state.Task, snapName string, blackl
key := connRef.ID()
if _, ok := conns[key]; ok {
// Suggested connection already exist so don't clobber it.
+ task.Logf("cannot auto connect %s to %s: (slot auto-connection), existing connection state %q in the way", connRef.PlugRef, connRef.SlotRef, key)
continue
}
if err := m.repo.Connect(connRef); err != nil {
diff --git a/overlord/ifacestate/ifacestate_test.go b/overlord/ifacestate/ifacestate_test.go
index 57f985709d..53c18e82ff 100644
--- a/overlord/ifacestate/ifacestate_test.go
+++ b/overlord/ifacestate/ifacestate_test.go
@@ -667,12 +667,18 @@ func (s *interfaceManagerSuite) addDiscardConnsChange(c *C, snapName string) *st
return change
}
-var osSnapYaml = `
+var ubuntuCoreSnapYaml = `
name: ubuntu-core
version: 1
type: os
`
+var coreSnapYaml = `
+name: core
+version: 1
+type: os
+`
+
var sampleSnapYaml = `
name: snap
version: 1
@@ -704,13 +710,20 @@ slots:
attr2: value2
`
+var httpdSnapYaml = `name: httpd
+version: 1
+plugs:
+ network:
+ interface: network
+`
+
// The setup-profiles task will not auto-connect an plug that was previously
// explicitly disconnected by the user.
func (s *interfaceManagerSuite) TestDoSetupSnapSecurityHonorsDisconnect(c *C) {
c.Skip("feature disabled until redesign/reimpl")
// Add an OS snap as well as a sample snap with a "network" plug.
// The plug is normally auto-connected.
- s.mockSnap(c, osSnapYaml)
+ s.mockSnap(c, ubuntuCoreSnapYaml)
snapInfo := s.mockSnap(c, sampleSnapYaml)
// Initialize the manager. This registers the two snaps.
@@ -749,7 +762,7 @@ func (s *interfaceManagerSuite) TestDoSetupSnapSecurityHonorsDisconnect(c *C) {
// The setup-profiles task will auto-connect plugs with viable candidates.
func (s *interfaceManagerSuite) TestDoSetupSnapSecurityAutoConnectsPlugs(c *C) {
// Add an OS snap.
- s.mockSnap(c, osSnapYaml)
+ s.mockSnap(c, ubuntuCoreSnapYaml)
// Initialize the manager. This registers the OS snap.
mgr := s.manager(c)
@@ -796,7 +809,7 @@ func (s *interfaceManagerSuite) TestDoSetupSnapSecurityAutoConnectsSlots(c *C) {
// Mock the interface that will be used by the test
s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
// Add an OS snap.
- s.mockSnap(c, osSnapYaml)
+ s.mockSnap(c, ubuntuCoreSnapYaml)
// Add a consumer snap with unconnect plug (interface "test")
s.mockSnap(c, consumerYaml)
@@ -920,7 +933,7 @@ slots:
// operates on or auto-connects to and will leave other state intact.
func (s *interfaceManagerSuite) TestDoSetupSnapSecuirtyKeepsExistingConnectionState(c *C) {
// Add an OS snap in place.
- s.mockSnap(c, osSnapYaml)
+ s.mockSnap(c, ubuntuCoreSnapYaml)
// Initialize the manager. This registers the two snaps.
mgr := s.manager(c)
@@ -976,7 +989,7 @@ func (s *interfaceManagerSuite) TestDoSetupProfilesAddsImplicitSlots(c *C) {
mgr := s.manager(c)
// Add an OS snap.
- snapInfo := s.mockSnap(c, osSnapYaml)
+ snapInfo := s.mockSnap(c, ubuntuCoreSnapYaml)
// Run the setup-profiles task and let it finish.
change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
@@ -1096,7 +1109,7 @@ func (s *interfaceManagerSuite) TestSetupProfilesHonorsDevMode(c *C) {
// of the affected set.
func (s *interfaceManagerSuite) TestSetupProfilesUsesFreshSnapInfo(c *C) {
// Put the OS and the sample snaps in place.
- coreSnapInfo := s.mockSnap(c, osSnapYaml)
+ coreSnapInfo := s.mockSnap(c, ubuntuCoreSnapYaml)
oldSnapInfo := s.mockSnap(c, sampleSnapYaml)
// Put connection information between the OS snap and the sample snap.
@@ -1151,7 +1164,7 @@ func (s *interfaceManagerSuite) TestSetupProfilesUsesFreshSnapInfo(c *C) {
// setup-profiles needs to setup security for connected slots after autoconnection
func (s *interfaceManagerSuite) TestAutoConnectSetupSecurityForConnectedSlots(c *C) {
// Add an OS snap.
- coreSnapInfo := s.mockSnap(c, osSnapYaml)
+ coreSnapInfo := s.mockSnap(c, ubuntuCoreSnapYaml)
// Initialize the manager. This registers the OS snap.
mgr := s.manager(c)
@@ -1646,7 +1659,7 @@ slots:
}
func (s *interfaceManagerSuite) TestCheckInterfacesConsidersImplicitSlots(c *C) {
- snapInfo := s.mockSnap(c, osSnapYaml)
+ snapInfo := s.mockSnap(c, ubuntuCoreSnapYaml)
s.state.Lock()
defer s.state.Unlock()
@@ -1740,26 +1753,9 @@ func (s *interfaceManagerSuite) TestUndoSetupProfilesOnRefresh(c *C) {
c.Check(s.secBackend.SetupCalls[0].Options, Equals, interfaces.ConfinementOptions{})
}
-var ubuntuCoreYaml = `name: ubuntu-core
-version: 1
-type: os
-`
-
-var coreYaml = `name: ubuntu-core
-version: 1
-type: os
-`
-
-var httpdSnapYaml = `name: httpd
-version: 1
-plugs:
- network:
- interface: network
-`
-
func (s *interfaceManagerSuite) TestManagerTransitionConnectionsCore(c *C) {
- s.mockSnap(c, ubuntuCoreYaml)
- s.mockSnap(c, coreYaml)
+ s.mockSnap(c, ubuntuCoreSnapYaml)
+ s.mockSnap(c, coreSnapYaml)
s.mockSnap(c, httpdSnapYaml)
mgr := s.manager(c)
@@ -1797,8 +1793,8 @@ func (s *interfaceManagerSuite) TestManagerTransitionConnectionsCore(c *C) {
}
func (s *interfaceManagerSuite) TestManagerTransitionConnectionsCoreUndo(c *C) {
- s.mockSnap(c, ubuntuCoreYaml)
- s.mockSnap(c, coreYaml)
+ s.mockSnap(c, ubuntuCoreSnapYaml)
+ s.mockSnap(c, coreSnapYaml)
s.mockSnap(c, httpdSnapYaml)
mgr := s.manager(c)
@@ -1841,3 +1837,109 @@ func (s *interfaceManagerSuite) TestManagerTransitionConnectionsCoreUndo(c *C) {
},
})
}
+
+// Test "core-support" connections that loop back to core is
+// renamed to match the rename of the plug.
+func (s *interfaceManagerSuite) TestCoreConnectionsRenamed(c *C) {
+ // Put state with old connection data.
+ s.state.Lock()
+ s.state.Set("conns", map[string]interface{}{
+ "core:core-support core:core-support": map[string]interface{}{
+ "interface": "core-support", "auto": true,
+ },
+ "snap:unrelated core:unrelated": map[string]interface{}{
+ "interface": "unrelated", "auto": true,
+ },
+ })
+ s.state.Unlock()
+
+ // Start the manager, this is where renames happen.
+ s.manager(c)
+
+ // Check that "core-support" connection got renamed.
+ s.state.Lock()
+ var conns map[string]interface{}
+ err := s.state.Get("conns", &conns)
+ s.state.Unlock()
+ c.Assert(err, IsNil)
+ c.Assert(conns, DeepEquals, map[string]interface{}{
+ "core:core-support-plug core:core-support": map[string]interface{}{
+ "interface": "core-support", "auto": true,
+ },
+ "snap:unrelated core:unrelated": map[string]interface{}{
+ "interface": "unrelated", "auto": true,
+ },
+ })
+}
+
+// Test that "network-bind" and "core-support" plugs are renamed to
+// "network-bind-plug" and "core-support-plug" in order not to clash with slots
+// with the same names.
+func (s *interfaceManagerSuite) TestAutomaticCorePlugsRenamed(c *C) {
+ s.mockSnap(c, coreSnapYaml+`
+plugs:
+ network-bind:
+ core-support:
+`)
+ mgr := s.manager(c)
+
+ // old plugs are gone
+ c.Assert(mgr.Repository().Plug("core", "network-bind"), IsNil)
+ c.Assert(mgr.Repository().Plug("core", "core-support"), IsNil)
+ // new plugs are present
+ c.Assert(mgr.Repository().Plug("core", "network-bind-plug"), Not(IsNil))
+ c.Assert(mgr.Repository().Plug("core", "core-support-plug"), Not(IsNil))
+ // slots are present and unchanged
+ c.Assert(mgr.Repository().Slot("core", "network-bind"), Not(IsNil))
+ c.Assert(mgr.Repository().Slot("core", "core-support"), Not(IsNil))
+}
+
+func (s *interfaceManagerSuite) TestAutoConnectDuringCoreTransition(c *C) {
+ // Add both the old and new core snaps
+ s.mockSnap(c, ubuntuCoreSnapYaml)
+ s.mockSnap(c, coreSnapYaml)
+
+ // Initialize the manager. This registers both of the core snaps.
+ mgr := s.manager(c)
+
+ // Add a sample snap with a "network" plug which should be auto-connected.
+ // Normally it would not be auto connected because there are multiple
+ // provides but we have special support for this case so the old
+ // ubuntu-core snap is ignored and we pick the new core snap.
+ snapInfo := s.mockSnap(c, sampleSnapYaml)
+
+ // Run the setup-snap-security task and let it finish.
+ change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{
+ RealName: snapInfo.Name(),
+ Revision: snapInfo.Revision,
+ },
+ })
+ mgr.Ensure()
+ mgr.Wait()
+ mgr.Stop()
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ // Ensure that the task succeeded.
+ c.Assert(change.Status(), Equals, state.DoneStatus)
+
+ // Ensure that "network" is now saved in the state as auto-connected and
+ // that it is connected to the new core snap rather than the old
+ // ubuntu-core snap.
+ var conns map[string]interface{}
+ err := s.state.Get("conns", &conns)
+ c.Assert(err, IsNil)
+ c.Check(conns, DeepEquals, map[string]interface{}{
+ "snap:network core:network": map[string]interface{}{
+ "interface": "network", "auto": true,
+ },
+ })
+
+ // Ensure that "network" is really connected.
+ repo := mgr.Repository()
+ plug := repo.Plug("snap", "network")
+ c.Assert(plug, Not(IsNil))
+ c.Check(plug.Connections, HasLen, 1)
+}
diff --git a/overlord/managers_test.go b/overlord/managers_test.go
index cad2af5fdd..8850046c57 100644
--- a/overlord/managers_test.go
+++ b/overlord/managers_test.go
@@ -1041,141 +1041,147 @@ apps:
}
func (ms *mgrsSuite) TestHappyAlias(c *C) {
- st := ms.o.State()
- st.Lock()
- defer st.Unlock()
-
- fooYaml := `name: foo
-version: 1.0
-apps:
- foo:
- command: bin/foo
- aliases: [foo_]
- bar:
- command: bin/bar
- aliases: [bar,bar1]
-`
- ms.installLocalTestSnap(c, fooYaml)
-
- ts, err := snapstate.Alias(st, "foo", []string{"foo_", "bar", "bar1"})
- c.Assert(err, IsNil)
- chg := st.NewChange("alias", "...")
- chg.AddAll(ts)
-
- st.Unlock()
- err = ms.o.Settle()
- st.Lock()
- c.Assert(err, IsNil)
-
- c.Assert(chg.Err(), IsNil)
- c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("alias change failed with: %v", chg.Err()))
-
- foo_Alias := filepath.Join(dirs.SnapBinariesDir, "foo_")
- dest, err := os.Readlink(foo_Alias)
- c.Assert(err, IsNil)
-
- c.Check(dest, Equals, "foo")
-
- barAlias := filepath.Join(dirs.SnapBinariesDir, "bar")
- dest, err = os.Readlink(barAlias)
- c.Assert(err, IsNil)
-
- c.Check(dest, Equals, "foo.bar")
-
- bar1Alias := filepath.Join(dirs.SnapBinariesDir, "bar1")
- dest, err = os.Readlink(bar1Alias)
- c.Assert(err, IsNil)
-
- c.Check(dest, Equals, "foo.bar")
-
- var allAliases map[string]map[string]string
- err = st.Get("aliases", &allAliases)
- c.Assert(err, IsNil)
- c.Check(allAliases, DeepEquals, map[string]map[string]string{
- "foo": {
- "foo_": "enabled",
- "bar": "enabled",
- "bar1": "enabled",
- },
- })
+ c.Skip("new semantics are wip")
+ /*
+ st := ms.o.State()
+ st.Lock()
+ defer st.Unlock()
+
+ fooYaml := `name: foo
+ version: 1.0
+ apps:
+ foo:
+ command: bin/foo
+ aliases: [foo_]
+ bar:
+ command: bin/bar
+ aliases: [bar,bar1]
+ `
+ ms.installLocalTestSnap(c, fooYaml)
+
+ ts, err := snapstate.Alias(st, "foo", []string{"foo_", "bar", "bar1"})
+ c.Assert(err, IsNil)
+ chg := st.NewChange("alias", "...")
+ chg.AddAll(ts)
+
+ st.Unlock()
+ err = ms.o.Settle()
+ st.Lock()
+ c.Assert(err, IsNil)
+
+ c.Assert(chg.Err(), IsNil)
+ c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("alias change failed with: %v", chg.Err()))
+
+ foo_Alias := filepath.Join(dirs.SnapBinariesDir, "foo_")
+ dest, err := os.Readlink(foo_Alias)
+ c.Assert(err, IsNil)
+
+ c.Check(dest, Equals, "foo")
+
+ barAlias := filepath.Join(dirs.SnapBinariesDir, "bar")
+ dest, err = os.Readlink(barAlias)
+ c.Assert(err, IsNil)
+
+ c.Check(dest, Equals, "foo.bar")
+
+ bar1Alias := filepath.Join(dirs.SnapBinariesDir, "bar1")
+ dest, err = os.Readlink(bar1Alias)
+ c.Assert(err, IsNil)
+
+ c.Check(dest, Equals, "foo.bar")
+
+ var allAliases map[string]map[string]string
+ err = st.Get("aliases", &allAliases)
+ c.Assert(err, IsNil)
+ c.Check(allAliases, DeepEquals, map[string]map[string]string{
+ "foo": {
+ "foo_": "enabled",
+ "bar": "enabled",
+ "bar1": "enabled",
+ },
+ })
- ms.removeSnap(c, "foo")
+ ms.removeSnap(c, "foo")
- c.Check(osutil.IsSymlink(foo_Alias), Equals, false)
- c.Check(osutil.IsSymlink(barAlias), Equals, false)
- c.Check(osutil.IsSymlink(bar1Alias), Equals, false)
+ c.Check(osutil.IsSymlink(foo_Alias), Equals, false)
+ c.Check(osutil.IsSymlink(barAlias), Equals, false)
+ c.Check(osutil.IsSymlink(bar1Alias), Equals, false)
- allAliases = nil
- err = st.Get("aliases", &allAliases)
- c.Assert(err, IsNil)
- c.Check(allAliases, HasLen, 0)
+ allAliases = nil
+ err = st.Get("aliases", &allAliases)
+ c.Assert(err, IsNil)
+ c.Check(allAliases, HasLen, 0)
+ */
}
func (ms *mgrsSuite) TestHappyUnalias(c *C) {
- st := ms.o.State()
- st.Lock()
- defer st.Unlock()
-
- fooYaml := `name: foo
-version: 1.0
-apps:
- foo:
- command: bin/foo
- aliases: [foo_]
-`
- ms.installLocalTestSnap(c, fooYaml)
-
- ts, err := snapstate.Alias(st, "foo", []string{"foo_"})
- c.Assert(err, IsNil)
- chg := st.NewChange("alias", "...")
- chg.AddAll(ts)
-
- st.Unlock()
- err = ms.o.Settle()
- st.Lock()
- c.Assert(err, IsNil)
-
- c.Assert(chg.Err(), IsNil)
- c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("alias change failed with: %v", chg.Err()))
-
- foo_Alias := filepath.Join(dirs.SnapBinariesDir, "foo_")
- dest, err := os.Readlink(foo_Alias)
- c.Assert(err, IsNil)
-
- c.Check(dest, Equals, "foo")
-
- var allAliases map[string]map[string]string
- err = st.Get("aliases", &allAliases)
- c.Assert(err, IsNil)
- c.Check(allAliases, DeepEquals, map[string]map[string]string{
- "foo": {
- "foo_": "enabled",
- },
- })
+ c.Skip("new semantics are wip")
+ /*
+ st := ms.o.State()
+ st.Lock()
+ defer st.Unlock()
+
+ fooYaml := `name: foo
+ version: 1.0
+ apps:
+ foo:
+ command: bin/foo
+ aliases: [foo_]
+ `
+ ms.installLocalTestSnap(c, fooYaml)
+
+ ts, err := snapstate.Alias(st, "foo", []string{"foo_"})
+ c.Assert(err, IsNil)
+ chg := st.NewChange("alias", "...")
+ chg.AddAll(ts)
+
+ st.Unlock()
+ err = ms.o.Settle()
+ st.Lock()
+ c.Assert(err, IsNil)
+
+ c.Assert(chg.Err(), IsNil)
+ c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("alias change failed with: %v", chg.Err()))
+
+ foo_Alias := filepath.Join(dirs.SnapBinariesDir, "foo_")
+ dest, err := os.Readlink(foo_Alias)
+ c.Assert(err, IsNil)
+
+ c.Check(dest, Equals, "foo")
+
+ var allAliases map[string]map[string]string
+ err = st.Get("aliases", &allAliases)
+ c.Assert(err, IsNil)
+ c.Check(allAliases, DeepEquals, map[string]map[string]string{
+ "foo": {
+ "foo_": "enabled",
+ },
+ })
- ts, err = snapstate.Unalias(st, "foo", []string{"foo_"})
- c.Assert(err, IsNil)
- chg = st.NewChange("unalias", "...")
- chg.AddAll(ts)
+ ts, err = snapstate.Unalias(st, "foo", []string{"foo_"})
+ c.Assert(err, IsNil)
+ chg = st.NewChange("unalias", "...")
+ chg.AddAll(ts)
- st.Unlock()
- err = ms.o.Settle()
- st.Lock()
- c.Assert(err, IsNil)
+ st.Unlock()
+ err = ms.o.Settle()
+ st.Lock()
+ c.Assert(err, IsNil)
- c.Assert(chg.Err(), IsNil)
- c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("unalias change failed with: %v", chg.Err()))
+ c.Assert(chg.Err(), IsNil)
+ c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("unalias change failed with: %v", chg.Err()))
- c.Check(osutil.IsSymlink(foo_Alias), Equals, false)
+ c.Check(osutil.IsSymlink(foo_Alias), Equals, false)
- allAliases = nil
- err = st.Get("aliases", &allAliases)
- c.Assert(err, IsNil)
- c.Check(allAliases, DeepEquals, map[string]map[string]string{
- "foo": {
- "foo_": "disabled",
- },
- })
+ allAliases = nil
+ err = st.Get("aliases", &allAliases)
+ c.Assert(err, IsNil)
+ c.Check(allAliases, DeepEquals, map[string]map[string]string{
+ "foo": {
+ "foo_": "disabled",
+ },
+ })
+ */
}
func (ms *mgrsSuite) TestHappyRemoteInstallAutoAliases(c *C) {
diff --git a/overlord/overlord_test.go b/overlord/overlord_test.go
index a010c02ea1..f1a5bee06e 100644
--- a/overlord/overlord_test.go
+++ b/overlord/overlord_test.go
@@ -370,11 +370,42 @@ func (ovs *overlordSuite) TestEnsureLoopPrune(c *C) {
chg1.AddTask(t1)
chg2 := st.NewChange("prune", "...")
chg2.SetStatus(state.DoneStatus)
+ t0 := chg2.ReadyTime()
st.Unlock()
+ // observe the loop cycles to detect when prune should have happened
+ pruneHappened := make(chan struct{})
+ cycles := -1
+ waitForPrune := func(_ *state.State) error {
+ if cycles == -1 {
+ if time.Since(t0) > 100*time.Millisecond {
+ cycles = 2 // wait a couple more loop cycles
+ }
+ return nil
+ }
+ if cycles > 0 {
+ cycles--
+ if cycles == 0 {
+ close(pruneHappened)
+ }
+ }
+ return nil
+ }
+ witness := &witnessManager{
+ ensureCallback: waitForPrune,
+ }
+ se := o.Engine()
+ se.AddManager(witness)
+
markSeeded(o)
o.Loop()
- time.Sleep(150 * time.Millisecond)
+
+ select {
+ case <-pruneHappened:
+ case <-time.After(2 * time.Second):
+ c.Fatal("Pruning should have happened by now")
+ }
+
err = o.Stop()
c.Assert(err, IsNil)
diff --git a/overlord/snapstate/aliases.go b/overlord/snapstate/aliases.go
index 2b611f2c94..703b3bed21 100644
--- a/overlord/snapstate/aliases.go
+++ b/overlord/snapstate/aliases.go
@@ -73,72 +73,9 @@ func setAliases(st *state.State, snapName string, aliases map[string]string) {
st.Set("aliases", allAliases)
}
-// Alias enables the provided aliases for the snap with the given name.
-func Alias(st *state.State, snapName string, aliases []string) (*state.TaskSet, error) {
- var snapst SnapState
- err := Get(st, snapName, &snapst)
- if err == state.ErrNoState {
- return nil, fmt.Errorf("cannot find snap %q", snapName)
- }
- if err != nil {
- return nil, err
- }
- if !snapst.Active {
- return nil, fmt.Errorf("enabling aliases for disabled snap %q not supported", snapName)
- }
- if err := CheckChangeConflict(st, snapName, nil); err != nil {
- return nil, err
- }
-
- snapsup := &SnapSetup{
- SideInfo: &snap.SideInfo{RealName: snapName},
- }
-
- alias := st.NewTask("alias", fmt.Sprintf(i18n.G("Enable aliases for snap %q"), snapsup.Name()))
- alias.Set("snap-setup", &snapsup)
- toEnable := map[string]string{}
- for _, alias := range aliases {
- toEnable[alias] = "enabled"
- }
- alias.Set("aliases", toEnable)
-
- return state.NewTaskSet(alias), nil
-}
-
-// Unalias explicitly disables the provided aliases for the snap with the given name.
-func Unalias(st *state.State, snapName string, aliases []string) (*state.TaskSet, error) {
- var snapst SnapState
- err := Get(st, snapName, &snapst)
- if err == state.ErrNoState {
- return nil, fmt.Errorf("cannot find snap %q", snapName)
- }
- if err != nil {
- return nil, err
- }
- if !snapst.Active {
- return nil, fmt.Errorf("disabling aliases for disabled snap %q not supported", snapName)
- }
- if err := CheckChangeConflict(st, snapName, nil); err != nil {
- return nil, err
- }
-
- snapsup := &SnapSetup{
- SideInfo: &snap.SideInfo{RealName: snapName},
- }
-
- alias := st.NewTask("alias", fmt.Sprintf(i18n.G("Disable aliases for snap %q"), snapsup.Name()))
- alias.Set("snap-setup", &snapsup)
- toDisable := map[string]string{}
- for _, alias := range aliases {
- toDisable[alias] = "disabled"
- }
- alias.Set("aliases", toDisable)
-
- return state.NewTaskSet(alias), nil
-}
+// TODO: reintroduce Alias, Unalias following the new meanings
-// ResetAliases resets the provided aliases for the snap with the given name to their default state, enabled for auto-aliases, disabled otherwise.
-func ResetAliases(st *state.State, snapName string, aliases []string) (*state.TaskSet, error) {
+func resetAliases(st *state.State, snapName string, aliases []string) (*state.TaskSet, error) {
var snapst SnapState
err := Get(st, snapName, &snapst)
if err == state.ErrNoState {
@@ -470,48 +407,6 @@ func (m *SnapManager) doSetupAliases(t *state.Task, _ *tomb.Tomb) error {
return m.backend.UpdateAliases(aliases, nil)
}
-func (m *SnapManager) undoSetupAliases(t *state.Task, _ *tomb.Tomb) error {
- st := t.State()
- st.Lock()
- defer st.Unlock()
- snapsup, snapst, err := snapSetupAndState(t)
- if err != nil {
- return err
- }
- snapName := snapsup.Name()
- curInfo, err := snapst.CurrentInfo()
- if err != nil {
- return err
- }
- aliasStatuses, err := getAliases(st, snapName)
- if err != nil && err != state.ErrNoState {
- return err
- }
- var aliases []*backend.Alias
- for alias, aliasStatus := range aliasStatuses {
- if enabledAlias(aliasStatus) {
- aliasApp := curInfo.Aliases[alias]
- if aliasApp == nil {
- // not a known alias, skip
- continue
- }
- aliases = append(aliases, &backend.Alias{
- Name: alias,
- Target: filepath.Base(aliasApp.WrapperPath()),
- })
- }
- }
- st.Unlock()
- rmAliases, err := m.backend.MatchingAliases(aliases)
- st.Lock()
- if err != nil {
- return fmt.Errorf("cannot list aliases for snap %q: %v", snapName, err)
- }
- st.Unlock()
- defer st.Lock()
- return m.backend.UpdateAliases(nil, rmAliases)
-}
-
func (m *SnapManager) doRemoveAliases(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
diff --git a/overlord/snapstate/aliases_test.go b/overlord/snapstate/aliases_test.go
index 0e4a741282..92f2fadfb6 100644
--- a/overlord/snapstate/aliases_test.go
+++ b/overlord/snapstate/aliases_test.go
@@ -20,7 +20,6 @@
package snapstate_test
import (
- "fmt"
"sort"
. "gopkg.in/check.v1"
@@ -119,12 +118,8 @@ func (s *snapmgrTestSuite) TestDoUndoSetupAliases(c *C) {
aliases: []*backend.Alias{{"alias1", "alias-snap.cmd1"}},
},
{
- op: "matching-aliases",
- aliases: []*backend.Alias{{"alias1", "alias-snap.cmd1"}},
- },
- {
- op: "update-aliases",
- rmAliases: []*backend.Alias{{"alias1", "alias-snap.cmd1"}},
+ op: "remove-snap-aliases",
+ name: "alias-snap",
},
}
// start with an easier-to-read error if this fails:
@@ -133,24 +128,27 @@ func (s *snapmgrTestSuite) TestDoUndoSetupAliases(c *C) {
}
func (s *snapmgrTestSuite) TestAliasTasks(c *C) {
- s.state.Lock()
- defer s.state.Unlock()
+ c.Skip("new semantics are wip")
+ /*
+ s.state.Lock()
+ defer s.state.Unlock()
- snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
- Sequence: []*snap.SideInfo{
- {RealName: "some-snap", Revision: snap.R(11)},
- },
- Current: snap.R(11),
- Active: true,
- })
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "some-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ })
- ts, err := snapstate.Alias(s.state, "some-snap", []string{"alias"})
- c.Assert(err, IsNil)
+ ts, err := snapstate.Alias(s.state, "some-snap", []string{"alias"})
+ c.Assert(err, IsNil)
- c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks()))
- c.Assert(taskKinds(ts.Tasks()), DeepEquals, []string{
- "alias",
- })
+ c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks()))
+ c.Assert(taskKinds(ts.Tasks()), DeepEquals, []string{
+ "alias",
+ })
+ */
}
func (s *snapmgrTestSuite) TestDoSetupAliasesAuto(c *C) {
@@ -240,12 +238,8 @@ func (s *snapmgrTestSuite) TestDoUndoSetupAliasesAuto(c *C) {
aliases: []*backend.Alias{{"alias1", "alias-snap.cmd1"}},
},
{
- op: "matching-aliases",
- aliases: []*backend.Alias{{"alias1", "alias-snap.cmd1"}},
- },
- {
- op: "update-aliases",
- rmAliases: []*backend.Alias{{"alias1", "alias-snap.cmd1"}},
+ op: "remove-snap-aliases",
+ name: "alias-snap",
},
}
// start with an easier-to-read error if this fails:
@@ -254,84 +248,93 @@ func (s *snapmgrTestSuite) TestDoUndoSetupAliasesAuto(c *C) {
}
func (s *snapmgrTestSuite) TestAliasRunThrough(c *C) {
- s.state.Lock()
- defer s.state.Unlock()
+ c.Skip("new semantics are wip")
+ /*
+ s.state.Lock()
+ defer s.state.Unlock()
- snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
- Sequence: []*snap.SideInfo{
- {RealName: "alias-snap", Revision: snap.R(11)},
- },
- Current: snap.R(11),
- Active: true,
- })
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ })
- chg := s.state.NewChange("alias", "enable an alias")
- ts, err := snapstate.Alias(s.state, "alias-snap", []string{"alias1"})
- c.Assert(err, IsNil)
- chg.AddAll(ts)
+ chg := s.state.NewChange("alias", "enable an alias")
+ ts, err := snapstate.Alias(s.state, "alias-snap", []string{"alias1"})
+ c.Assert(err, IsNil)
+ chg.AddAll(ts)
- s.state.Unlock()
- defer s.snapmgr.Stop()
- s.settle()
- s.state.Lock()
+ s.state.Unlock()
+ defer s.snapmgr.Stop()
+ s.settle()
+ s.state.Lock()
- c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("%v", chg.Err()))
- expected := fakeOps{
- {
- op: "update-aliases",
- aliases: []*backend.Alias{{"alias1", "alias-snap.cmd1"}},
- },
- }
- // 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)
+ c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("%v", chg.Err()))
+ expected := fakeOps{
+ {
+ op: "update-aliases",
+ aliases: []*backend.Alias{{"alias1", "alias-snap.cmd1"}},
+ },
+ }
+ // 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 allAliases map[string]map[string]string
- err = s.state.Get("aliases", &allAliases)
- c.Assert(err, IsNil)
- c.Check(allAliases, DeepEquals, map[string]map[string]string{
- "alias-snap": {"alias1": "enabled"},
- })
+ var allAliases map[string]map[string]string
+ err = s.state.Get("aliases", &allAliases)
+ c.Assert(err, IsNil)
+ c.Check(allAliases, DeepEquals, map[string]map[string]string{
+ "alias-snap": {"alias1": "enabled"},
+ })
+ */
}
func (s *snapmgrTestSuite) TestUpdateAliasChangeConflict(c *C) {
- s.state.Lock()
- defer s.state.Unlock()
+ c.Skip("new semantics are wip")
+ /*
+ s.state.Lock()
+ defer s.state.Unlock()
- snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
- Active: true,
- Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}},
- Current: snap.R(7),
- SnapType: "app",
- })
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}},
+ Current: snap.R(7),
+ SnapType: "app",
+ })
- ts, err := snapstate.Update(s.state, "some-snap", "some-channel", snap.R(0), s.user.ID, snapstate.Flags{})
- c.Assert(err, IsNil)
- // need a change to make the tasks visible
- s.state.NewChange("update", "...").AddAll(ts)
+ ts, err := snapstate.Update(s.state, "some-snap", "some-channel", snap.R(0), s.user.ID, snapstate.Flags{})
+ c.Assert(err, IsNil)
+ // need a change to make the tasks visible
+ s.state.NewChange("update", "...").AddAll(ts)
- _, err = snapstate.Alias(s.state, "some-snap", []string{"alias1"})
- c.Assert(err, ErrorMatches, `snap "some-snap" has changes in progress`)
+ _, err = snapstate.Alias(s.state, "some-snap", []string{"alias1"})
+ c.Assert(err, ErrorMatches, `snap "some-snap" has changes in progress`)
+ */
}
func (s *snapmgrTestSuite) TestUpdateUnaliasChangeConflict(c *C) {
- s.state.Lock()
- defer s.state.Unlock()
+ c.Skip("new semantics are wip")
+ /*
+ s.state.Lock()
+ defer s.state.Unlock()
- snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
- Active: true,
- Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}},
- Current: snap.R(7),
- SnapType: "app",
- })
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}},
+ Current: snap.R(7),
+ SnapType: "app",
+ })
- ts, err := snapstate.Update(s.state, "some-snap", "some-channel", snap.R(0), s.user.ID, snapstate.Flags{})
- c.Assert(err, IsNil)
- // need a change to make the tasks visible
- s.state.NewChange("update", "...").AddAll(ts)
+ ts, err := snapstate.Update(s.state, "some-snap", "some-channel", snap.R(0), s.user.ID, snapstate.Flags{})
+ c.Assert(err, IsNil)
+ // need a change to make the tasks visible
+ s.state.NewChange("update", "...").AddAll(ts)
- _, err = snapstate.Unalias(s.state, "some-snap", []string{"alias1"})
- c.Assert(err, ErrorMatches, `snap "some-snap" has changes in progress`)
+ _, err = snapstate.Unalias(s.state, "some-snap", []string{"alias1"})
+ c.Assert(err, ErrorMatches, `snap "some-snap" has changes in progress`)
+ */
}
func (s *snapmgrTestSuite) TestUpdateResetAliasesChangeConflict(c *C) {
@@ -355,148 +358,163 @@ func (s *snapmgrTestSuite) TestUpdateResetAliasesChangeConflict(c *C) {
}
func (s *snapmgrTestSuite) TestAliasUpdateChangeConflict(c *C) {
- s.state.Lock()
- defer s.state.Unlock()
+ c.Skip("new semantics coming")
+ /*
+ s.state.Lock()
+ defer s.state.Unlock()
- snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
- Active: true,
- Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}},
- Current: snap.R(7),
- SnapType: "app",
- })
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}},
+ Current: snap.R(7),
+ SnapType: "app",
+ })
- ts, err := snapstate.Alias(s.state, "some-snap", []string{"alias1"})
- c.Assert(err, IsNil)
- // need a change to make the tasks visible
- s.state.NewChange("alias", "...").AddAll(ts)
+ ts, err := snapstate.Alias(s.state, "some-snap", []string{"alias1"})
+ c.Assert(err, IsNil)
+ // need a change to make the tasks visible
+ s.state.NewChange("alias", "...").AddAll(ts)
- _, err = snapstate.Update(s.state, "some-snap", "some-channel", snap.R(0), s.user.ID, snapstate.Flags{})
- c.Assert(err, ErrorMatches, `snap "some-snap" has changes in progress`)
+ _, err = snapstate.Update(s.state, "some-snap", "some-channel", snap.R(0), s.user.ID, snapstate.Flags{})
+ c.Assert(err, ErrorMatches, `snap "some-snap" has changes in progress`)
+ */
}
func (s *snapmgrTestSuite) TestAliasNoAlias(c *C) {
- s.state.Lock()
- defer s.state.Unlock()
+ c.Skip("should become a new app test")
+ /*
+ s.state.Lock()
+ defer s.state.Unlock()
- snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
- Sequence: []*snap.SideInfo{
- {RealName: "some-snap", Revision: snap.R(11)},
- },
- Current: snap.R(11),
- Active: true,
- })
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "some-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ })
- chg := s.state.NewChange("alias", "enable an alias")
- ts, err := snapstate.Alias(s.state, "some-snap", []string{"alias1"})
- c.Assert(err, IsNil)
- chg.AddAll(ts)
+ chg := s.state.NewChange("alias", "enable an alias")
+ ts, err := snapstate.Alias(s.state, "some-snap", []string{"alias1"})
+ c.Assert(err, IsNil)
+ chg.AddAll(ts)
- s.state.Unlock()
+ s.state.Unlock()
- s.snapmgr.Ensure()
- s.snapmgr.Wait()
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
- s.state.Lock()
+ s.state.Lock()
- c.Check(chg.Status(), Equals, state.ErrorStatus)
- c.Check(chg.Err(), ErrorMatches, `(?s).*cannot enable alias "alias1" for "some-snap", no such alias.*`)
+ c.Check(chg.Status(), Equals, state.ErrorStatus)
+ c.Check(chg.Err(), ErrorMatches, `(?s).*cannot enable alias "alias1" for "some-snap", no such alias.*`)
+ */
}
func (s *snapmgrTestSuite) TestAliasAliasConflict(c *C) {
- s.state.Lock()
- defer s.state.Unlock()
+ c.Skip("new semantics are wip")
+ /*
+ s.state.Lock()
+ defer s.state.Unlock()
- snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
- Sequence: []*snap.SideInfo{
- {RealName: "alias-snap", Revision: snap.R(11)},
- },
- Current: snap.R(11),
- Active: true,
- })
- s.state.Set("aliases", map[string]map[string]string{
- "other-snap": {"alias1": "enabled"},
- })
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ })
+ s.state.Set("aliases", map[string]map[string]string{
+ "other-snap": {"alias1": "enabled"},
+ })
- chg := s.state.NewChange("alias", "enable an alias")
- ts, err := snapstate.Alias(s.state, "alias-snap", []string{"alias1"})
- c.Assert(err, IsNil)
- chg.AddAll(ts)
+ chg := s.state.NewChange("alias", "enable an alias")
+ ts, err := snapstate.Alias(s.state, "alias-snap", []string{"alias1"})
+ c.Assert(err, IsNil)
+ chg.AddAll(ts)
- s.state.Unlock()
+ s.state.Unlock()
- s.snapmgr.Ensure()
- s.snapmgr.Wait()
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
- s.state.Lock()
+ s.state.Lock()
- c.Check(chg.Status(), Equals, state.ErrorStatus)
- c.Check(chg.Err(), ErrorMatches, `(?s).*cannot enable alias "alias1" for "alias-snap", already enabled for "other-snap".*`)
+ c.Check(chg.Status(), Equals, state.ErrorStatus)
+ c.Check(chg.Err(), ErrorMatches, `(?s).*cannot enable alias "alias1" for "alias-snap", already enabled for "other-snap".*`)
+ */
}
func (s *snapmgrTestSuite) TestAliasAutoAliasConflict(c *C) {
- s.state.Lock()
- defer s.state.Unlock()
+ c.Skip("new semantics are wip")
+ /*
+ s.state.Lock()
+ defer s.state.Unlock()
- snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
- Sequence: []*snap.SideInfo{
- {RealName: "alias-snap", Revision: snap.R(11)},
- },
- Current: snap.R(11),
- Active: true,
- })
- s.state.Set("aliases", map[string]map[string]string{
- "other-snap": {"alias1": "auto"},
- })
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ })
+ s.state.Set("aliases", map[string]map[string]string{
+ "other-snap": {"alias1": "auto"},
+ })
- chg := s.state.NewChange("alias", "enable an alias")
- ts, err := snapstate.Alias(s.state, "alias-snap", []string{"alias1"})
- c.Assert(err, IsNil)
- chg.AddAll(ts)
+ chg := s.state.NewChange("alias", "enable an alias")
+ ts, err := snapstate.Alias(s.state, "alias-snap", []string{"alias1"})
+ c.Assert(err, IsNil)
+ chg.AddAll(ts)
- s.state.Unlock()
+ s.state.Unlock()
- s.snapmgr.Ensure()
- s.snapmgr.Wait()
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
- s.state.Lock()
+ s.state.Lock()
- c.Check(chg.Status(), Equals, state.ErrorStatus)
- c.Check(chg.Err(), ErrorMatches, `(?s).*cannot enable alias "alias1" for "alias-snap", already enabled for "other-snap".*`)
+ c.Check(chg.Status(), Equals, state.ErrorStatus)
+ c.Check(chg.Err(), ErrorMatches, `(?s).*cannot enable alias "alias1" for "alias-snap", already enabled for "other-snap".*`)
+ */
}
func (s *snapmgrTestSuite) TestAliasSnapCommandSpaceConflict(c *C) {
- s.state.Lock()
- defer s.state.Unlock()
+ c.Skip("new semantics are wip")
+ /*
+ s.state.Lock()
+ defer s.state.Unlock()
- snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
- Sequence: []*snap.SideInfo{
- {RealName: "alias-snap", Revision: snap.R(11)},
- },
- Current: snap.R(11),
- Active: true,
- })
- // the command namespace of this one will conflict
- snapstate.Set(s.state, "alias1", &snapstate.SnapState{
- Sequence: []*snap.SideInfo{
- {RealName: "alias1", Revision: snap.R(3)},
- },
- Current: snap.R(3),
- })
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ })
+ // the command namespace of this one will conflict
+ snapstate.Set(s.state, "alias1", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias1", Revision: snap.R(3)},
+ },
+ Current: snap.R(3),
+ })
- chg := s.state.NewChange("alias", "enable an alias")
- ts, err := snapstate.Alias(s.state, "alias-snap", []string{"alias1.cmd1"})
- c.Assert(err, IsNil)
- chg.AddAll(ts)
+ chg := s.state.NewChange("alias", "enable an alias")
+ ts, err := snapstate.Alias(s.state, "alias-snap", []string{"alias1.cmd1"})
+ c.Assert(err, IsNil)
+ chg.AddAll(ts)
- s.state.Unlock()
+ s.state.Unlock()
- s.snapmgr.Ensure()
- s.snapmgr.Wait()
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
- s.state.Lock()
+ s.state.Lock()
- c.Check(chg.Status(), Equals, state.ErrorStatus)
- c.Check(chg.Err(), ErrorMatches, `(?s).*cannot enable alias "alias1.cmd1" for "alias-snap", it conflicts with the command namespace of installed snap "alias1".*`)
+ c.Check(chg.Status(), Equals, state.ErrorStatus)
+ c.Check(chg.Err(), ErrorMatches, `(?s).*cannot enable alias "alias1.cmd1" for "alias-snap", it conflicts with the command namespace of installed snap "alias1".*`)
+ */
}
func (s *snapmgrTestSuite) TestDoClearAliases(c *C) {
@@ -690,224 +708,6 @@ var statusesMatrix = []struct {
{"alias5gone", "auto", "reset", "", "-"},
}
-func (s *snapmgrTestSuite) TestAliasMatrixRunThrough(c *C) {
- s.state.Lock()
- defer s.state.Unlock()
-
- snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
- Sequence: []*snap.SideInfo{
- {RealName: "alias-snap", Revision: snap.R(11)},
- },
- Current: snap.R(11),
- Active: true,
- })
-
- // alias1 is a non auto-alias
- // alias5 is an auto-alias
- // alias1gone is a non auto-alias and doesn't have an entry in the current snap revision anymore
- // alias5gone is an auto-alias and doesn't have an entry in the current snap revision anymore
- snapstate.AutoAliases = func(st *state.State, info *snap.Info) (map[string]string, error) {
- c.Check(info.Name(), Equals, "alias-snap")
- return map[string]string{"alias5": "cmd5", "alias5gone": "cmd5gone"}, nil
- }
- cmds := map[string]string{
- "alias1": "cmd1",
- "alias5": "cmd5",
- }
-
- defer s.snapmgr.Stop()
- for _, scenario := range statusesMatrix {
- scenAlias := scenario.alias
- if scenario.beforeStatus != "" {
- s.state.Set("aliases", map[string]map[string]string{
- "alias-snap": {
- scenAlias: scenario.beforeStatus,
- },
- })
- } else {
- s.state.Set("aliases", nil)
- }
-
- chg := s.state.NewChange("scenario", "...")
- var err error
- var ts *state.TaskSet
- targets := []string{scenAlias}
- switch scenario.action {
- case "alias":
- ts, err = snapstate.Alias(s.state, "alias-snap", targets)
- case "unalias":
- ts, err = snapstate.Unalias(s.state, "alias-snap", targets)
- case "reset":
- ts, err = snapstate.ResetAliases(s.state, "alias-snap", targets)
- }
- c.Assert(err, IsNil)
-
- chg.AddAll(ts)
-
- s.state.Unlock()
- s.settle()
- s.state.Lock()
-
- c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("%#v: %v", scenario, chg.Err()))
- var aliases []*backend.Alias
- var rmAliases []*backend.Alias
- beAlias := &backend.Alias{Name: scenAlias, Target: fmt.Sprintf("alias-snap.%s", cmds[scenAlias])}
- switch scenario.mutation {
- case "-":
- case "add":
- aliases = []*backend.Alias{beAlias}
- case "rm":
- rmAliases = []*backend.Alias{beAlias}
- }
-
- comm := Commentf("%#v", scenario)
- expected := fakeOps{
- {
- op: "update-aliases",
- aliases: aliases,
- rmAliases: rmAliases,
- },
- }
- // start with an easier-to-read error if this fails:
- c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops(), comm)
- c.Check(s.fakeBackend.ops, DeepEquals, expected, comm)
-
- var allAliases map[string]map[string]string
- err = s.state.Get("aliases", &allAliases)
- c.Assert(err, IsNil)
- if scenario.status != "" {
- c.Check(allAliases, DeepEquals, map[string]map[string]string{
- "alias-snap": {scenAlias: scenario.status},
- }, comm)
- } else {
- c.Check(allAliases, HasLen, 0, comm)
- }
-
- s.fakeBackend.ops = nil
- }
-}
-
-func (s *snapmgrTestSuite) TestAliasMatrixTotalUndoRunThrough(c *C) {
- s.state.Lock()
- defer s.state.Unlock()
-
- snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
- Sequence: []*snap.SideInfo{
- {RealName: "alias-snap", Revision: snap.R(11)},
- },
- Current: snap.R(11),
- Active: true,
- })
-
- // alias1 is a non auto-alias
- // alias5 is an auto-alias
- // alias1gone is a non auto-alias and doesn't have an entry in the snap anymore
- // alias5gone is an auto-alias and doesn't have an entry in the snap any
- snapstate.AutoAliases = func(st *state.State, info *snap.Info) (map[string]string, error) {
- c.Check(info.Name(), Equals, "alias-snap")
- return map[string]string{"alias5": "cmd5", "alias5gone": "cmd5gone"}, nil
- }
- cmds := map[string]string{
- "alias1": "cmd1",
- "alias5": "cmd5",
- }
-
- defer s.snapmgr.Stop()
- for _, scenario := range statusesMatrix {
- scenAlias := scenario.alias
- if scenario.beforeStatus != "" {
- s.state.Set("aliases", map[string]map[string]string{
- "alias-snap": {
- scenAlias: scenario.beforeStatus,
- },
- })
- } else {
- s.state.Set("aliases", nil)
- }
-
- chg := s.state.NewChange("scenario", "...")
- var err error
- var ts *state.TaskSet
- targets := []string{scenAlias}
-
- switch scenario.action {
- case "alias":
- ts, err = snapstate.Alias(s.state, "alias-snap", targets)
- case "unalias":
- ts, err = snapstate.Unalias(s.state, "alias-snap", targets)
- case "reset":
- ts, err = snapstate.ResetAliases(s.state, "alias-snap", targets)
- }
- c.Assert(err, IsNil)
-
- chg.AddAll(ts)
-
- tasks := ts.Tasks()
- last := tasks[len(tasks)-1]
-
- terr := s.state.NewTask("error-trigger", "provoking total undo")
- terr.WaitFor(last)
- chg.AddTask(terr)
-
- s.state.Unlock()
- for i := 0; i < 3; i++ {
- s.snapmgr.Ensure()
- s.snapmgr.Wait()
- }
- s.state.Lock()
-
- c.Assert(chg.Status(), Equals, state.ErrorStatus, Commentf("%#v: %v", scenario, chg.Err()))
- var aliases []*backend.Alias
- var rmAliases []*backend.Alias
- beAlias := &backend.Alias{Name: scenAlias, Target: fmt.Sprintf("alias-snap.%s", cmds[scenAlias])}
- switch scenario.mutation {
- case "-":
- case "add":
- aliases = []*backend.Alias{beAlias}
- case "rm":
- rmAliases = []*backend.Alias{beAlias}
- }
-
- comm := Commentf("%#v", scenario)
- expected := fakeOps{
- {
- op: "update-aliases",
- aliases: aliases,
- rmAliases: rmAliases,
- },
- {
- op: "matching-aliases",
- aliases: aliases,
- },
- {
- op: "missing-aliases",
- aliases: rmAliases,
- },
- {
- op: "update-aliases",
- aliases: rmAliases,
- rmAliases: aliases,
- },
- }
- // start with an easier-to-read error if this fails:
- c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops(), comm)
- c.Check(s.fakeBackend.ops, DeepEquals, expected, comm)
-
- var allAliases map[string]map[string]string
- err = s.state.Get("aliases", &allAliases)
- c.Assert(err, IsNil)
- if scenario.beforeStatus != "" {
- c.Check(allAliases, DeepEquals, map[string]map[string]string{
- "alias-snap": {scenAlias: scenario.beforeStatus},
- }, comm)
- } else {
- c.Check(allAliases, HasLen, 0, comm)
- }
-
- s.fakeBackend.ops = nil
- }
-}
-
func (s *snapmgrTestSuite) TestDisabledSnapResetAliasesRunThrough(c *C) {
s.state.Lock()
defer s.state.Unlock()
@@ -1066,111 +866,114 @@ func (s *snapmgrTestSuite) TestDisabledSnapResetAliasesTotalUndoRunThrough(c *C)
}
func (s *snapmgrTestSuite) TestUnliasTotalUndoRunThroughAliasConflict(c *C) {
- s.state.Lock()
- defer s.state.Unlock()
+ c.Skip("new semantics are wip")
+ /*
- snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
- Sequence: []*snap.SideInfo{
- {RealName: "alias-snap", Revision: snap.R(11)},
- },
- Current: snap.R(11),
- Active: true,
- })
-
- defer s.snapmgr.Stop()
- s.state.Set("aliases", map[string]map[string]string{
- "alias-snap": {
- "alias1": "enabled",
- },
- })
-
- chg := s.state.NewChange("scenario", "...")
- ts, err := snapstate.Unalias(s.state, "alias-snap", []string{"alias1"})
- c.Assert(err, IsNil)
-
- chg.AddAll(ts)
-
- tasks := ts.Tasks()
- last := tasks[len(tasks)-1]
-
- grabAlias1 := func(t *state.Task, _ *tomb.Tomb) error {
- st := t.State()
- st.Lock()
- defer st.Unlock()
+ s.state.Lock()
+ defer s.state.Unlock()
- var allAliases map[string]map[string]string
- err := st.Get("aliases", &allAliases)
- c.Assert(err, IsNil)
- c.Assert(allAliases, DeepEquals, map[string]map[string]string{
- "alias-snap": {
- "alias1": "disabled",
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
},
+ Current: snap.R(11),
+ Active: true,
})
- st.Set("aliases", map[string]map[string]string{
+ defer s.snapmgr.Stop()
+ s.state.Set("aliases", map[string]map[string]string{
"alias-snap": {
- "alias1": "disabled",
- },
- "other-snap": {
"alias1": "enabled",
},
})
- return nil
- }
- s.snapmgr.AddAdhocTaskHandler("grab-alias1", grabAlias1, nil)
+ chg := s.state.NewChange("scenario", "...")
+ ts, err := snapstate.Unalias(s.state, "alias-snap", []string{"alias1"})
+ c.Assert(err, IsNil)
- tgrab1 := s.state.NewTask("grab-alias1", "grab alias1 for other-snap")
- tgrab1.WaitFor(last)
- chg.AddTask(tgrab1)
+ chg.AddAll(ts)
- terr := s.state.NewTask("error-trigger", "provoking total undo")
- terr.WaitFor(tgrab1)
- chg.AddTask(terr)
+ tasks := ts.Tasks()
+ last := tasks[len(tasks)-1]
- s.state.Unlock()
+ grabAlias1 := func(t *state.Task, _ *tomb.Tomb) error {
+ st := t.State()
+ st.Lock()
+ defer st.Unlock()
- for i := 0; i < 5; i++ {
- s.snapmgr.Ensure()
- s.snapmgr.Wait()
- }
+ var allAliases map[string]map[string]string
+ err := st.Get("aliases", &allAliases)
+ c.Assert(err, IsNil)
+ c.Assert(allAliases, DeepEquals, map[string]map[string]string{
+ "alias-snap": {
+ "alias1": "disabled",
+ },
+ })
- s.state.Lock()
+ st.Set("aliases", map[string]map[string]string{
+ "alias-snap": {
+ "alias1": "disabled",
+ },
+ "other-snap": {
+ "alias1": "enabled",
+ },
+ })
+ return nil
+ }
- c.Assert(chg.Status(), Equals, state.ErrorStatus, Commentf("%v", chg.Err()))
- rmAliases := []*backend.Alias{{"alias1", "alias-snap.cmd1"}}
+ s.snapmgr.AddAdhocTaskHandler("grab-alias1", grabAlias1, nil)
- expected := fakeOps{
- {
- op: "update-aliases",
- rmAliases: rmAliases,
- },
- {
- op: "matching-aliases",
- },
- {
- op: "missing-aliases",
- },
- {
- 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)
+ tgrab1 := s.state.NewTask("grab-alias1", "grab alias1 for other-snap")
+ tgrab1.WaitFor(last)
+ chg.AddTask(tgrab1)
- var allAliases map[string]map[string]string
- err = s.state.Get("aliases", &allAliases)
- c.Assert(err, IsNil)
- c.Check(allAliases, DeepEquals, map[string]map[string]string{
- "other-snap": {
- "alias1": "enabled",
- },
- })
+ terr := s.state.NewTask("error-trigger", "provoking total undo")
+ terr.WaitFor(tgrab1)
+ chg.AddTask(terr)
- c.Check(last.Log(), HasLen, 1)
- c.Check(last.Log()[0], Matches, `.* ERROR cannot enable alias "alias1" for "alias-snap", already enabled for "other-snap"`)
+ s.state.Unlock()
+
+ for i := 0; i < 5; i++ {
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+ }
+
+ s.state.Lock()
+
+ c.Assert(chg.Status(), Equals, state.ErrorStatus, Commentf("%v", chg.Err()))
+ rmAliases := []*backend.Alias{{"alias1", "alias-snap.cmd1"}}
+
+ expected := fakeOps{
+ {
+ op: "update-aliases",
+ rmAliases: rmAliases,
+ },
+ {
+ op: "matching-aliases",
+ },
+ {
+ op: "missing-aliases",
+ },
+ {
+ 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 allAliases map[string]map[string]string
+ err = s.state.Get("aliases", &allAliases)
+ c.Assert(err, IsNil)
+ c.Check(allAliases, DeepEquals, map[string]map[string]string{
+ "other-snap": {
+ "alias1": "enabled",
+ },
+ })
+ c.Check(last.Log(), HasLen, 1)
+ c.Check(last.Log()[0], Matches, `.* ERROR cannot enable alias "alias1" for "alias-snap", already enabled for "other-snap"`)
+ */
}
func (s *snapmgrTestSuite) TestAutoAliasesDelta(c *C) {
diff --git a/overlord/snapstate/aliasesv2.go b/overlord/snapstate/aliasesv2.go
index fa6111224e..14e988c69c 100644
--- a/overlord/snapstate/aliasesv2.go
+++ b/overlord/snapstate/aliasesv2.go
@@ -30,33 +30,26 @@ import (
"github.com/snapcore/snapd/strutil"
)
-// AliasesStatus represents the global aliases status for a snap.
-type AliasesStatus string
-
-// Possible global aliases statuses for a snap.
-const (
- EnabledAliases AliasesStatus = "enabled"
- DisabledAliases AliasesStatus = "disabled"
-)
-
// AliasTarget carries the targets of an alias in the context of snap.
// If Manual is set it is the target of an enabled manual alias.
// Auto is set to the target for an automatic alias, enabled or
-// disabled depending on the aliases status of the whole snap.
+// disabled depending on the automatic aliases flag state.
type AliasTarget struct {
Manual string `json:"manual,omitempty"`
Auto string `json:"auto,omitempty"`
}
-// Effective returns the target to use based on the aliasesStatus of the whole snap, returns "" if the alias is disabled.
-func (at *AliasTarget) Effective(aliasesStatus AliasesStatus) string {
+// Effective returns the target to use considering whether automatic
+// aliases are disabled for the whole snap (autoDisabled), returns ""
+// if the alias is disabled.
+func (at *AliasTarget) Effective(autoDisabled bool) string {
if at == nil {
return ""
}
if at.Manual != "" {
return at.Manual
}
- if aliasesStatus == EnabledAliases {
+ if !autoDisabled {
return at.Auto
}
return ""
@@ -67,10 +60,9 @@ func (at *AliasTarget) Effective(aliasesStatus AliasesStatus) string {
type SnapState struct {
...
- Aliases map[string]*AliasTarget `json:"aliases,omitempty"`
- AliasesStatus AliasesStatus `json:"aliases-status,omitempty"`
+ Aliases map[string]*AliasTarget
+ AutoAliasesDisabled bool
}
-}
There are two kinds of aliases:
@@ -83,7 +75,7 @@ func (at *AliasTarget) Effective(aliasesStatus AliasesStatus) string {
Further
* all automatic aliases of a snap are either enabled
- or disabled together (tracked with AliasesStatus)
+ or disabled together (tracked with AutoAliasesDisabled)
* disabling a manual alias removes it from disk and state (for
simplicity there is no disabled state for manual aliases)
@@ -103,17 +95,17 @@ func composeTarget(snapName, targetApp string) string {
}
// applyAliasesChange applies the necessary changes to aliases on disk
-// to go from prevAliases under the snap global prevStatus for
-// automatic aliases to newAliases under newStatus for snapName.
-// It assumes that conflicts have already been checked.
-func applyAliasesChange(st *state.State, snapName string, prevStatus AliasesStatus, prevAliases map[string]*AliasTarget, newStatus AliasesStatus, newAliases map[string]*AliasTarget, be managerBackend) error {
+// to go from prevAliases consindering the automatic aliases flag
+// (prevAutoDisabled) to newAliases considering newAutoDisabled for
+// snapName. It assumes that conflicts have already been checked.
+func applyAliasesChange(st *state.State, snapName string, prevAutoDisabled bool, prevAliases map[string]*AliasTarget, newAutoDisabled bool, newAliases map[string]*AliasTarget, be managerBackend) error {
var add, remove []*backend.Alias
for alias, prevTargets := range prevAliases {
if _, ok := newAliases[alias]; ok {
continue
}
// gone
- if effTgt := prevTargets.Effective(prevStatus); effTgt != "" {
+ if effTgt := prevTargets.Effective(prevAutoDisabled); effTgt != "" {
remove = append(remove, &backend.Alias{
Name: alias,
Target: composeTarget(snapName, effTgt),
@@ -121,8 +113,8 @@ func applyAliasesChange(st *state.State, snapName string, prevStatus AliasesStat
}
}
for alias, newTargets := range newAliases {
- prevTgt := prevAliases[alias].Effective(prevStatus)
- newTgt := newTargets.Effective(newStatus)
+ prevTgt := prevAliases[alias].Effective(prevAutoDisabled)
+ newTgt := newTargets.Effective(newAutoDisabled)
if prevTgt == newTgt {
// nothing to do
continue
@@ -286,18 +278,18 @@ func addAliasConflicts(st *state.State, skipSnap string, testAliases map[string]
// skip
continue
}
- status := snapst.AliasesStatus
+ autoDisabled := snapst.AutoAliasesDisabled
var confls []string
if len(snapst.Aliases) < len(testAliases) {
for alias, target := range snapst.Aliases {
- if testAliases[alias] && target.Effective(status) != "" {
+ if testAliases[alias] && target.Effective(autoDisabled) != "" {
confls = append(confls, alias)
}
}
} else {
for alias := range testAliases {
target := snapst.Aliases[alias]
- if target != nil && target.Effective(status) != "" {
+ if target != nil && target.Effective(autoDisabled) != "" {
confls = append(confls, alias)
}
}
@@ -309,10 +301,10 @@ func addAliasConflicts(st *state.State, skipSnap string, testAliases map[string]
return nil
}
-// checkAliasesStatConflicts checks candAliases under candStatus for
-// conflicts against other snap aliases returning conflicting
-// snaps and aliases for alias conflicts.
-func checkAliasesConflicts(st *state.State, snapName string, candStatus AliasesStatus, candAliases map[string]*AliasTarget) (conflicts map[string][]string, err error) {
+// checkAliasesStatConflicts checks candAliases considering
+// candAutoDisabled for conflicts against other snap aliases returning
+// conflicting snaps and aliases for alias conflicts.
+func checkAliasesConflicts(st *state.State, snapName string, candAutoDisabled bool, candAliases map[string]*AliasTarget) (conflicts map[string][]string, err error) {
var snapNames map[string]*json.RawMessage
err = st.Get("snaps", &snapNames)
if err != nil && err != state.ErrNoState {
@@ -321,7 +313,7 @@ func checkAliasesConflicts(st *state.State, snapName string, candStatus AliasesS
enabled := make(map[string]bool, len(candAliases))
for alias, candTarget := range candAliases {
- if candTarget.Effective(candStatus) != "" {
+ if candTarget.Effective(candAutoDisabled) != "" {
enabled[alias] = true
} else {
continue
@@ -350,3 +342,39 @@ func checkAliasesConflicts(st *state.State, snapName string, candStatus AliasesS
}
return nil, nil
}
+
+// disableAliases returns newAliases corresponding to the disabling of
+// curAliases, for manual aliases that means removed.
+func disableAliases(curAliases map[string]*AliasTarget) (newAliases map[string]*AliasTarget) {
+ newAliases = make(map[string]*AliasTarget, len(curAliases))
+ for alias, curTarget := range curAliases {
+ if curTarget.Auto != "" {
+ newAliases[alias] = &AliasTarget{Auto: curTarget.Auto}
+ }
+ }
+ return newAliases
+}
+
+// pruneAutoAliases returns newAliases by dropping the automatic
+// aliases autoAliases from curAliases, used as the task
+// prune-auto-aliases to handle transfers of automatic aliases in a
+// refresh.
+func pruneAutoAliases(st *state.State, curAliases map[string]*AliasTarget, autoAliases []string) (newAliases map[string]*AliasTarget) {
+ newAliases = make(map[string]*AliasTarget, len(curAliases))
+ for alias, aliasTarget := range curAliases {
+ newAliases[alias] = aliasTarget
+ }
+ for _, alias := range autoAliases {
+ curTarget := curAliases[alias]
+ if curTarget == nil {
+ // nothing to do
+ continue
+ }
+ if curTarget.Manual == "" {
+ delete(newAliases, alias)
+ } else {
+ newAliases[alias] = &AliasTarget{Manual: curTarget.Manual}
+ }
+ }
+ return newAliases
+}
diff --git a/overlord/snapstate/aliasesv2_test.go b/overlord/snapstate/aliasesv2_test.go
index a24a927684..8fea24ed70 100644
--- a/overlord/snapstate/aliasesv2_test.go
+++ b/overlord/snapstate/aliasesv2_test.go
@@ -61,28 +61,24 @@ func (s *snapmgrTestSuite) TestApplyAliasesChange(c *C) {
Auto: "cmd1",
}
- const (
- en = snapstate.EnabledAliases
- dis = snapstate.DisabledAliases
- )
scenarios := []struct {
- status snapstate.AliasesStatus
- newStatus snapstate.AliasesStatus
- target *snapstate.AliasTarget
- newTarget *snapstate.AliasTarget
- ops string
+ autoDisabled bool
+ newAutoDisabled bool
+ target *snapstate.AliasTarget
+ newTarget *snapstate.AliasTarget
+ ops string
}{
- {en, en, nil, auto1, "add"},
- {en, dis, auto1, auto1, "rm"},
- {en, en, auto1, auto2, "rm add"},
- {en, en, auto1, nil, "rm"},
- {en, en, nil, manual1, "add"},
- {dis, dis, nil, manual1, "add"},
- {en, dis, auto1, manual2, "rm add"},
- {en, en, manual2, nil, "rm"},
- {en, en, manual2, auto1, "rm add"},
- {en, en, manual1, auto1, ""},
- {dis, en, manual1, auto1, ""},
+ {false, false, nil, auto1, "add"},
+ {false, true, auto1, auto1, "rm"},
+ {false, false, auto1, auto2, "rm add"},
+ {false, false, auto1, nil, "rm"},
+ {false, false, nil, manual1, "add"},
+ {true, true, nil, manual1, "add"},
+ {false, true, auto1, manual2, "rm add"},
+ {false, false, manual2, nil, "rm"},
+ {false, false, manual2, auto1, "rm add"},
+ {false, false, manual1, auto1, ""},
+ {true, false, manual1, auto1, ""},
}
for _, scenario := range scenarios {
@@ -95,7 +91,7 @@ func (s *snapmgrTestSuite) TestApplyAliasesChange(c *C) {
newAliases["myalias"] = scenario.newTarget
}
- err := snapstate.ApplyAliasesChange(s.state, "alias-snap1", scenario.status, prevAliases, scenario.newStatus, newAliases, s.fakeBackend)
+ err := snapstate.ApplyAliasesChange(s.state, "alias-snap1", scenario.autoDisabled, prevAliases, scenario.newAutoDisabled, newAliases, s.fakeBackend)
c.Assert(err, IsNil)
var add, rm []*backend.Alias
@@ -134,7 +130,7 @@ func (s *snapmgrTestSuite) TestApplyAliasesChangeMulti(c *C) {
"myalias1": {Auto: "alias-snap1"},
}
- err := snapstate.ApplyAliasesChange(s.state, "alias-snap1", snapstate.EnabledAliases, prevAliases, snapstate.EnabledAliases, newAliases, s.fakeBackend)
+ err := snapstate.ApplyAliasesChange(s.state, "alias-snap1", false, prevAliases, false, newAliases, s.fakeBackend)
c.Assert(err, IsNil)
expected := fakeOps{
@@ -331,9 +327,9 @@ func (s *snapmgrTestSuite) TestCheckAliasesConflictsAgainstAliases(c *C) {
Sequence: []*snap.SideInfo{
{RealName: "other-snap", Revision: snap.R(2)},
},
- Current: snap.R(2),
- Active: true,
- AliasesStatus: snapstate.DisabledAliases,
+ Current: snap.R(2),
+ Active: true,
+ AutoAliasesDisabled: true,
Aliases: map[string]*snapstate.AliasTarget{
"alias1": {Auto: "cmd1"},
"alias2": {Auto: "cmd2"},
@@ -344,9 +340,9 @@ func (s *snapmgrTestSuite) TestCheckAliasesConflictsAgainstAliases(c *C) {
Sequence: []*snap.SideInfo{
{RealName: "other-snap", Revision: snap.R(2)},
},
- Current: snap.R(2),
- Active: true,
- AliasesStatus: snapstate.EnabledAliases,
+ Current: snap.R(2),
+ Active: true,
+ AutoAliasesDisabled: false,
Aliases: map[string]*snapstate.AliasTarget{
"alias2": {Auto: "cmd2"},
"alias3": {Auto: "cmd3"},
@@ -357,23 +353,23 @@ func (s *snapmgrTestSuite) TestCheckAliasesConflictsAgainstAliases(c *C) {
Sequence: []*snap.SideInfo{
{RealName: "other-snap", Revision: snap.R(2)},
},
- Current: snap.R(2),
- Active: true,
- AliasesStatus: snapstate.DisabledAliases,
+ Current: snap.R(2),
+ Active: true,
+ AutoAliasesDisabled: true,
Aliases: map[string]*snapstate.AliasTarget{
"alias4": {Manual: "cmd8"},
"alias5": {Auto: "cmd5"},
},
})
- confl, err := snapstate.CheckAliasesConflicts(s.state, "alias-snap", snapstate.EnabledAliases, map[string]*snapstate.AliasTarget{
+ confl, err := snapstate.CheckAliasesConflicts(s.state, "alias-snap", false, map[string]*snapstate.AliasTarget{
"alias1": {Auto: "cmd1"},
"alias5": {Auto: "cmd5"},
})
c.Check(err, IsNil)
c.Check(confl, IsNil)
- confl, err = snapstate.CheckAliasesConflicts(s.state, "alias-snap", snapstate.EnabledAliases, map[string]*snapstate.AliasTarget{
+ confl, err = snapstate.CheckAliasesConflicts(s.state, "alias-snap", false, map[string]*snapstate.AliasTarget{
"alias1": {Auto: "cmd1"},
"alias2": {Auto: "cmd2"},
"alias3": {Auto: "cmd3"},
@@ -386,7 +382,7 @@ func (s *snapmgrTestSuite) TestCheckAliasesConflictsAgainstAliases(c *C) {
c.Check(which, DeepEquals, []string{"alias2", "alias3"})
c.Check(confl["other-snap3"], DeepEquals, []string{"alias4"})
- confl, err = snapstate.CheckAliasesConflicts(s.state, "alias-snap", snapstate.DisabledAliases, map[string]*snapstate.AliasTarget{
+ confl, err = snapstate.CheckAliasesConflicts(s.state, "alias-snap", true, map[string]*snapstate.AliasTarget{
"alias1": {Auto: "cmd1"},
"alias2": {Auto: "cmd2"},
"alias3": {Auto: "cmd3"},
@@ -395,7 +391,7 @@ func (s *snapmgrTestSuite) TestCheckAliasesConflictsAgainstAliases(c *C) {
c.Check(err, IsNil)
c.Check(confl, IsNil)
- confl, err = snapstate.CheckAliasesConflicts(s.state, "other-snap4", snapstate.EnabledAliases, map[string]*snapstate.AliasTarget{
+ confl, err = snapstate.CheckAliasesConflicts(s.state, "other-snap4", false, map[string]*snapstate.AliasTarget{
"alias2": {Manual: "cmd12"},
})
c.Check(err, FitsTypeOf, &snapstate.AliasConflictError{})
@@ -434,30 +430,46 @@ func (s *snapmgrTestSuite) TestCheckAliasesConflictsAgainstSnaps(c *C) {
SnapType: "app",
})
- confl, err := snapstate.CheckAliasesConflicts(s.state, "alias-snap", snapstate.EnabledAliases, map[string]*snapstate.AliasTarget{
+ confl, err := snapstate.CheckAliasesConflicts(s.state, "alias-snap", false, map[string]*snapstate.AliasTarget{
"alias1": {Auto: "cmd1"},
})
c.Check(err, IsNil)
c.Check(confl, IsNil)
- confl, err = snapstate.CheckAliasesConflicts(s.state, "alias-snap", snapstate.EnabledAliases, map[string]*snapstate.AliasTarget{
+ confl, err = snapstate.CheckAliasesConflicts(s.state, "alias-snap", false, map[string]*snapstate.AliasTarget{
"alias1": {Auto: "cmd1"},
"some-snap": {Auto: "cmd1"},
})
c.Check(err, ErrorMatches, `cannot enable alias "some-snap" for "alias-snap", it conflicts with the command namespace of installed snap "some-snap"`)
c.Check(confl, IsNil)
- confl, err = snapstate.CheckAliasesConflicts(s.state, "alias-snap", snapstate.DisabledAliases, map[string]*snapstate.AliasTarget{
+ confl, err = snapstate.CheckAliasesConflicts(s.state, "alias-snap", true, map[string]*snapstate.AliasTarget{
"alias1": {Auto: "cmd1"},
"some-snap": {Auto: "cmd1"},
})
c.Check(err, IsNil)
c.Check(confl, IsNil)
- confl, err = snapstate.CheckAliasesConflicts(s.state, "alias-snap", snapstate.EnabledAliases, map[string]*snapstate.AliasTarget{
+ confl, err = snapstate.CheckAliasesConflicts(s.state, "alias-snap", false, map[string]*snapstate.AliasTarget{
"alias1": {Auto: "cmd1"},
"some-snap.foo": {Auto: "cmd1"},
})
c.Check(err, ErrorMatches, `cannot enable alias "some-snap.foo" for "alias-snap", it conflicts with the command namespace of installed snap "some-snap"`)
c.Check(confl, IsNil)
}
+
+func (s *snapmgrTestSuite) TestDisableAliases(c *C) {
+ aliases := map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2"},
+ "alias3": {Manual: "manual3", Auto: "cmd3"},
+ "alias4": {Manual: "manual4"},
+ }
+
+ dis := snapstate.DisableAliases(aliases)
+ c.Check(dis, DeepEquals, map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2"},
+ "alias3": {Auto: "cmd3"},
+ })
+}
diff --git a/overlord/snapstate/backend_test.go b/overlord/snapstate/backend_test.go
index 7dac836741..72c4b58da9 100644
--- a/overlord/snapstate/backend_test.go
+++ b/overlord/snapstate/backend_test.go
@@ -22,6 +22,7 @@ package snapstate_test
import (
"errors"
"fmt"
+ "sort"
"golang.org/x/net/context"
@@ -527,7 +528,23 @@ func (f *fakeSnappyBackend) MissingAliases(aliases []*backend.Alias) ([]*backend
return aliases, nil
}
+type byAlias []*backend.Alias
+
+func (ba byAlias) Len() int { return len(ba) }
+func (ba byAlias) Swap(i, j int) { ba[i], ba[j] = ba[j], ba[i] }
+func (ba byAlias) Less(i, j int) bool {
+ return ba[i].Name < ba[j].Name
+}
+
func (f *fakeSnappyBackend) UpdateAliases(add []*backend.Alias, remove []*backend.Alias) error {
+ if len(add) != 0 {
+ add = append([]*backend.Alias(nil), add...)
+ sort.Sort(byAlias(add))
+ }
+ if len(remove) != 0 {
+ remove = append([]*backend.Alias(nil), remove...)
+ sort.Sort(byAlias(remove))
+ }
f.ops = append(f.ops, fakeOp{
op: "update-aliases",
aliases: add,
diff --git a/overlord/snapstate/export_test.go b/overlord/snapstate/export_test.go
index 9abadae523..a702c20144 100644
--- a/overlord/snapstate/export_test.go
+++ b/overlord/snapstate/export_test.go
@@ -113,6 +113,8 @@ var (
CanDisable = canDisable
CachedStore = cachedStore
NameAndRevnoFromSnap = nameAndRevnoFromSnap
+
+ ResetAliases = resetAliases
)
func PreviousSideInfo(snapst *SnapState) *snap.SideInfo {
@@ -125,4 +127,5 @@ var (
AutoAliasesDeltaV2 = autoAliasesDeltaV2
RefreshAliases = refreshAliases
CheckAliasesConflicts = checkAliasesConflicts
+ DisableAliases = disableAliases
)
diff --git a/overlord/snapstate/handlers.go b/overlord/snapstate/handlers.go
index 35c438c0a8..9f7655c0f5 100644
--- a/overlord/snapstate/handlers.go
+++ b/overlord/snapstate/handlers.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2016 Canonical Ltd
+ * Copyright (C) 2016-2017 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
@@ -30,7 +30,6 @@ import (
"github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/logger"
- "github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord/configstate/config"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/release"
@@ -148,10 +147,6 @@ func (m *SnapManager) undoPrepareSnap(t *state.Task, _ *tomb.Tomb) error {
return err
}
- // report this error to an error tracker
- if osutil.GetenvBool("SNAPPY_TESTING") {
- return nil
- }
if snapsup.SideInfo == nil || snapsup.SideInfo.RealName == "" {
return nil
}
@@ -824,3 +819,194 @@ func (m *SnapManager) doDiscardSnap(t *state.Task, _ *tomb.Tomb) error {
Set(st, snapsup.Name(), snapst)
return nil
}
+
+// aliases v2
+
+func (m *SnapManager) doSetAutoAliasesV2(t *state.Task, _ *tomb.Tomb) error {
+ st := t.State()
+ st.Lock()
+ defer st.Unlock()
+ snapsup, snapst, err := snapSetupAndState(t)
+ if err != nil {
+ return err
+ }
+ snapName := snapsup.Name()
+ curInfo, err := snapst.CurrentInfo()
+ if err != nil {
+ return err
+ }
+
+ curAliases := snapst.Aliases
+ // TODO: implement --prefer/--unaliased logic
+ newAliases, err := refreshAliases(st, curInfo, curAliases)
+ if err != nil {
+ return err
+ }
+ _, err = checkAliasesConflicts(st, snapName, snapst.AutoAliasesDisabled, newAliases)
+ if err != nil {
+ return err
+ }
+
+ t.Set("old-aliases-v2", curAliases)
+ // noop, except on first install where we need to set this here
+ snapst.AliasesPending = true
+ snapst.Aliases = newAliases
+ Set(st, snapName, snapst)
+ return nil
+}
+
+func (m *SnapManager) doRemoveAliasesV2(t *state.Task, _ *tomb.Tomb) error {
+ st := t.State()
+ st.Lock()
+ defer st.Unlock()
+ snapsup, snapst, err := snapSetupAndState(t)
+ if err != nil {
+ return err
+ }
+ snapName := snapsup.Name()
+
+ err = m.backend.RemoveSnapAliases(snapName)
+ if err != nil {
+ return err
+ }
+
+ snapst.AliasesPending = true
+ Set(st, snapName, snapst)
+ return nil
+}
+
+func (m *SnapManager) doSetupAliasesV2(t *state.Task, _ *tomb.Tomb) error {
+ st := t.State()
+ st.Lock()
+ defer st.Unlock()
+ snapsup, snapst, err := snapSetupAndState(t)
+ if err != nil {
+ return err
+ }
+ snapName := snapsup.Name()
+ curAliases := snapst.Aliases
+
+ err = applyAliasesChange(st, snapName, true, nil, snapst.AutoAliasesDisabled, curAliases, m.backend)
+ if err != nil {
+ return err
+ }
+
+ snapst.AliasesPending = false
+ Set(st, snapName, snapst)
+ return nil
+}
+
+func (m *SnapManager) doRefreshAliasesV2(t *state.Task, _ *tomb.Tomb) error {
+ // TODO: share code with doSetAutoAliasesV2 once the logic is more complex
+ st := t.State()
+ st.Lock()
+ defer st.Unlock()
+ snapsup, snapst, err := snapSetupAndState(t)
+ if err != nil {
+ return err
+ }
+ snapName := snapsup.Name()
+ curInfo, err := snapst.CurrentInfo()
+ if err != nil {
+ return err
+ }
+
+ autoDisabled := snapst.AutoAliasesDisabled
+ curAliases := snapst.Aliases
+ // TODO: implement --prefer/--unaliased logic
+ newAliases, err := refreshAliases(st, curInfo, curAliases)
+ if err != nil {
+ return err
+ }
+ _, err = checkAliasesConflicts(st, snapName, autoDisabled, newAliases)
+ if err != nil {
+ return err
+ }
+
+ if !snapst.AliasesPending {
+ if err := applyAliasesChange(st, snapName, autoDisabled, curAliases, autoDisabled, newAliases, m.backend); err != nil {
+ return err
+ }
+ }
+
+ t.Set("old-aliases-v2", curAliases)
+ snapst.Aliases = newAliases
+ Set(st, snapName, snapst)
+ return nil
+}
+
+func (m *SnapManager) undoRefreshAliasesV2(t *state.Task, _ *tomb.Tomb) error {
+ st := t.State()
+ st.Lock()
+ defer st.Unlock()
+ var oldAliases map[string]*AliasTarget
+ err := t.Get("old-aliases-v2", &oldAliases)
+ if err == state.ErrNoState {
+ // nothing to do
+ return nil
+ }
+ if err != nil {
+ return err
+ }
+ snapsup, snapst, err := snapSetupAndState(t)
+ if err != nil {
+ return err
+ }
+ snapName := snapsup.Name()
+ curAutoDisabled := snapst.AutoAliasesDisabled
+ autoDisabled := curAutoDisabled
+
+ // check if the old states creates conflicts now
+ _, err = checkAliasesConflicts(st, snapName, autoDisabled, oldAliases)
+ if _, ok := err.(*AliasConflictError); ok {
+ // best we can do is reinstate with all aliases disabled
+ t.Errorf("cannot reinstate alias state because of conflicts, disabling: %v", err)
+ oldAliases = disableAliases(oldAliases)
+ autoDisabled = true
+ } else if err != nil {
+ return err
+ }
+
+ if !snapst.AliasesPending {
+ curAliases := snapst.Aliases
+ if err := applyAliasesChange(st, snapName, curAutoDisabled, curAliases, autoDisabled, oldAliases, m.backend); err != nil {
+ return err
+ }
+ }
+
+ snapst.AutoAliasesDisabled = autoDisabled
+ snapst.Aliases = oldAliases
+ Set(st, snapName, snapst)
+ return nil
+}
+
+func (m *SnapManager) doPruneAutoAliasesV2(t *state.Task, _ *tomb.Tomb) error {
+ st := t.State()
+ st.Lock()
+ defer st.Unlock()
+ snapsup, snapst, err := snapSetupAndState(t)
+ if err != nil {
+ return err
+ }
+ var which []string
+ err = t.Get("aliases", &which)
+ if err != nil {
+ return err
+ }
+ snapName := snapsup.Name()
+ autoDisabled := snapst.AutoAliasesDisabled
+ curAliases := snapst.Aliases
+
+ newAliases := pruneAutoAliases(st, curAliases, which)
+
+ if !snapst.AliasesPending {
+ if err := applyAliasesChange(st, snapName, autoDisabled, curAliases, autoDisabled, newAliases, m.backend); err != nil {
+ return err
+ }
+ }
+
+ t.Set("old-aliases-v2", curAliases)
+ snapst.Aliases = newAliases
+ Set(st, snapName, snapst)
+ return nil
+}
diff --git a/overlord/snapstate/handlers_aliasesv2_test.go b/overlord/snapstate/handlers_aliasesv2_test.go
new file mode 100644
index 0000000000..ed62eb55ff
--- /dev/null
+++ b/overlord/snapstate/handlers_aliasesv2_test.go
@@ -0,0 +1,1372 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016-2017 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 snapstate_test
+
+import (
+ . "gopkg.in/check.v1"
+ "gopkg.in/tomb.v2"
+
+ "github.com/snapcore/snapd/overlord/snapstate"
+ "github.com/snapcore/snapd/overlord/snapstate/backend"
+ "github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/snap"
+)
+
+func (s *snapmgrTestSuite) TestDoSetAutoAliasesV2(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.AutoAliases = func(st *state.State, info *snap.Info) (map[string]string, error) {
+ c.Check(info.Name(), Equals, "alias-snap")
+ return map[string]string{
+ "alias1": "cmd1",
+ "alias2": "cmd2",
+ "alias4": "cmd4",
+ }, nil
+ }
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ AutoAliasesDisabled: false,
+ AliasesPending: true,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2x"},
+ "alias3": {Auto: "cmd3"},
+ },
+ })
+
+ t := s.state.NewTask("set-auto-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ s.state.Unlock()
+
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.DoneStatus, Commentf("%v", chg.Err()))
+
+ var snapst snapstate.SnapState
+ err := snapstate.Get(s.state, "alias-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Check(snapst.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2"},
+ "alias4": {Auto: "cmd4"},
+ })
+}
+
+func (s *snapmgrTestSuite) TestDoSetAutoAliasesV2FirstInstall(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.AutoAliases = func(st *state.State, info *snap.Info) (map[string]string, error) {
+ c.Check(info.Name(), Equals, "alias-snap")
+ return map[string]string{
+ "alias1": "cmd1",
+ "alias2": "cmd2",
+ "alias4": "cmd4",
+ }, nil
+ }
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ })
+
+ t := s.state.NewTask("set-auto-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ s.state.Unlock()
+
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.DoneStatus, Commentf("%v", chg.Err()))
+
+ var snapst snapstate.SnapState
+ err := snapstate.Get(s.state, "alias-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Check(snapst.AutoAliasesDisabled, Equals, false)
+ c.Check(snapst.AliasesPending, Equals, true)
+ c.Check(snapst.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2"},
+ "alias4": {Auto: "cmd4"},
+ })
+}
+
+func (s *snapmgrTestSuite) TestDoUndoSetAutoAliasesV2(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.AutoAliases = func(st *state.State, info *snap.Info) (map[string]string, error) {
+ c.Check(info.Name(), Equals, "alias-snap")
+ return map[string]string{
+ "alias1": "cmd1",
+ "alias2": "cmd2",
+ "alias4": "cmd4",
+ }, nil
+ }
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ AliasesPending: true,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2x"},
+ "alias3": {Auto: "cmd3"},
+ },
+ })
+
+ t := s.state.NewTask("set-auto-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ terr := s.state.NewTask("error-trigger", "provoking total undo")
+ terr.WaitFor(t)
+ chg.AddTask(terr)
+
+ s.state.Unlock()
+
+ for i := 0; i < 3; i++ {
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+ }
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.UndoneStatus, Commentf("%v", chg.Err()))
+
+ var snapst snapstate.SnapState
+ err := snapstate.Get(s.state, "alias-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Check(snapst.AliasesPending, Equals, true)
+ c.Check(snapst.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2x"},
+ "alias3": {Auto: "cmd3"},
+ })
+}
+
+func (s *snapmgrTestSuite) TestDoSetAutoAliasesV2Conflict(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.AutoAliases = func(st *state.State, info *snap.Info) (map[string]string, error) {
+ c.Check(info.Name(), Equals, "alias-snap")
+ return map[string]string{
+ "alias1": "cmd1",
+ "alias2": "cmd2",
+ "alias4": "cmd4",
+ }, nil
+ }
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ AliasesPending: true,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2x"},
+ "alias3": {Auto: "cmd3"},
+ },
+ })
+ snapstate.Set(s.state, "other-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "other-snap", Revision: snap.R(3)},
+ },
+ Current: snap.R(3),
+ Active: true,
+ AliasesPending: true,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "alias4": {Auto: "cmd4"},
+ },
+ })
+
+ t := s.state.NewTask("set-auto-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ s.state.Unlock()
+
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.ErrorStatus, Commentf("%v", chg.Err()))
+ c.Check(chg.Err(), ErrorMatches, `(?s).*cannot enable alias "alias4" for "alias-snap", already enabled for "other-snap".*`)
+}
+
+func (s *snapmgrTestSuite) TestDoUndoSetAutoAliasesV2Conflict(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.AutoAliases = func(st *state.State, info *snap.Info) (map[string]string, error) {
+ c.Check(info.Name(), Equals, "alias-snap")
+ return map[string]string{
+ "alias1": "cmd1",
+ "alias2": "cmd2",
+ "alias4": "cmd4",
+ }, nil
+ }
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ AliasesPending: true,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2x"},
+ "alias3": {Auto: "cmd3"},
+ },
+ })
+
+ otherSnapState := &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "other-snap", Revision: snap.R(3)},
+ },
+ Current: snap.R(3),
+ Active: true,
+ AutoAliasesDisabled: false,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "alias5": {Auto: "cmd5"},
+ },
+ }
+ snapstate.Set(s.state, "other-snap", otherSnapState)
+
+ grabAlias3 := func(t *state.Task, _ *tomb.Tomb) error {
+ st := t.State()
+ st.Lock()
+ defer st.Unlock()
+
+ otherSnapState.Aliases = map[string]*snapstate.AliasTarget{
+ "alias3": {Auto: "cmd3"},
+ "alias5": {Auto: "cmd5"},
+ }
+ snapstate.Set(s.state, "other-snap", otherSnapState)
+
+ return nil
+ }
+ s.snapmgr.AddAdhocTaskHandler("grab-alias3", grabAlias3, nil)
+
+ t := s.state.NewTask("set-auto-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ tgrab3 := s.state.NewTask("grab-alias3", "grab alias3 for other-snap")
+ tgrab3.WaitFor(t)
+ chg.AddTask(tgrab3)
+
+ terr := s.state.NewTask("error-trigger", "provoking total undo")
+ terr.WaitFor(t)
+ chg.AddTask(terr)
+
+ s.state.Unlock()
+
+ for i := 0; i < 5; i++ {
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+ }
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.UndoneStatus, Commentf("%v", chg.Err()))
+
+ var snapst snapstate.SnapState
+ err := snapstate.Get(s.state, "alias-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Check(snapst.AutoAliasesDisabled, Equals, true)
+ c.Check(snapst.AliasesPending, Equals, true)
+ c.Check(snapst.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2x"},
+ "alias3": {Auto: "cmd3"},
+ })
+
+ c.Check(t.Log(), HasLen, 1)
+ c.Check(t.Log()[0], Matches, `.* ERROR cannot reinstate alias state because of conflicts, disabling: cannot enable alias "alias3" for "alias-snap", already enabled for "other-snap".*`)
+}
+
+func (s *snapmgrTestSuite) TestDoSetupAliasesV2(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ AutoAliasesDisabled: true,
+ AliasesPending: true,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "manual1": {Manual: "cmd1"},
+ },
+ })
+
+ t := s.state.NewTask("setup-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ s.state.Unlock()
+
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.DoneStatus)
+ expected := fakeOps{
+ {
+ op: "update-aliases",
+ aliases: []*backend.Alias{{"manual1", "alias-snap.cmd1"}},
+ },
+ }
+ // 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, "alias-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Check(snapst.AutoAliasesDisabled, Equals, true)
+ c.Check(snapst.AliasesPending, Equals, false)
+}
+
+func (s *snapmgrTestSuite) TestDoUndoSetupAliasesV2(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ AutoAliasesDisabled: true,
+ AliasesPending: true,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "manual1": {Manual: "cmd1"},
+ },
+ })
+
+ t := s.state.NewTask("setup-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ terr := s.state.NewTask("error-trigger", "provoking total undo")
+ terr.WaitFor(t)
+ chg.AddTask(terr)
+
+ s.state.Unlock()
+
+ for i := 0; i < 3; i++ {
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+ }
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.UndoneStatus)
+ expected := fakeOps{
+ {
+ op: "update-aliases",
+ aliases: []*backend.Alias{{"manual1", "alias-snap.cmd1"}},
+ },
+ {
+ op: "remove-snap-aliases",
+ name: "alias-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)
+
+ var snapst snapstate.SnapState
+ err := snapstate.Get(s.state, "alias-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Check(snapst.AutoAliasesDisabled, Equals, true)
+ c.Check(snapst.AliasesPending, Equals, true)
+}
+
+// TODO: test and fix idempotence of setup-aliases
+
+func (s *snapmgrTestSuite) TestDoSetupAliasesV2Auto(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ AutoAliasesDisabled: false,
+ AliasesPending: true,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ },
+ })
+
+ t := s.state.NewTask("setup-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ s.state.Unlock()
+
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.DoneStatus)
+ expected := fakeOps{
+ {
+ op: "update-aliases",
+ aliases: []*backend.Alias{{"alias1", "alias-snap.cmd1"}},
+ },
+ }
+ // 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, "alias-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Check(snapst.AutoAliasesDisabled, Equals, false)
+ c.Check(snapst.AliasesPending, Equals, false)
+}
+
+func (s *snapmgrTestSuite) TestDoUndoSetupAliasesV2Auto(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ AutoAliasesDisabled: false,
+ AliasesPending: true,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ },
+ })
+
+ t := s.state.NewTask("setup-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ terr := s.state.NewTask("error-trigger", "provoking total undo")
+ terr.WaitFor(t)
+ chg.AddTask(terr)
+
+ s.state.Unlock()
+
+ for i := 0; i < 3; i++ {
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+ }
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.UndoneStatus)
+ expected := fakeOps{
+ {
+ op: "update-aliases",
+ aliases: []*backend.Alias{{"alias1", "alias-snap.cmd1"}},
+ },
+ {
+ op: "remove-snap-aliases",
+ name: "alias-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)
+
+ var snapst snapstate.SnapState
+ err := snapstate.Get(s.state, "alias-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Check(snapst.AutoAliasesDisabled, Equals, false)
+ c.Check(snapst.AliasesPending, Equals, true)
+}
+
+func (s *snapmgrTestSuite) TestDoSetupAliasesV2Nothing(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ })
+
+ t := s.state.NewTask("setup-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ s.state.Unlock()
+
+ for i := 0; i < 3; i++ {
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+ }
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.DoneStatus)
+ expected := fakeOps{
+ {
+ 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, "alias-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Check(snapst.AutoAliasesDisabled, Equals, false)
+ c.Check(snapst.AliasesPending, Equals, false)
+}
+
+func (s *snapmgrTestSuite) TestDoUndoSetupAliasesV2Nothing(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ })
+
+ t := s.state.NewTask("setup-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ terr := s.state.NewTask("error-trigger", "provoking total undo")
+ terr.WaitFor(t)
+ chg.AddTask(terr)
+
+ s.state.Unlock()
+
+ for i := 0; i < 3; i++ {
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+ }
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.UndoneStatus)
+ expected := fakeOps{
+ {
+ op: "update-aliases",
+ },
+ {
+ op: "remove-snap-aliases",
+ name: "alias-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)
+
+ var snapst snapstate.SnapState
+ err := snapstate.Get(s.state, "alias-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Check(snapst.AutoAliasesDisabled, Equals, false)
+ c.Check(snapst.AliasesPending, Equals, true)
+}
+
+func (s *snapmgrTestSuite) TestDoPruneAutoAliasesV2Auto(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ AliasesPending: false,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2"},
+ "alias3": {Auto: "cmd3"},
+ },
+ })
+
+ t := s.state.NewTask("prune-auto-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ t.Set("aliases", []string{"alias2", "alias3"})
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ s.state.Unlock()
+
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.DoneStatus, Commentf("%v", chg.Err()))
+
+ expected := fakeOps{
+ {
+ op: "update-aliases",
+ rmAliases: []*backend.Alias{
+ {"alias2", "alias-snap.cmd2"},
+ {"alias3", "alias-snap.cmd3"},
+ },
+ },
+ }
+ // 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, "alias-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Check(snapst.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ })
+}
+
+func (s *snapmgrTestSuite) TestDoPruneAutoAliasesV2AutoPending(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ AliasesPending: true,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2"},
+ "alias3": {Auto: "cmd3"},
+ },
+ })
+
+ t := s.state.NewTask("prune-auto-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ t.Set("aliases", []string{"alias2", "alias3"})
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ s.state.Unlock()
+
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.DoneStatus, Commentf("%v", chg.Err()))
+
+ // pending: nothing to do on disk
+ c.Assert(s.fakeBackend.ops, HasLen, 0)
+
+ var snapst snapstate.SnapState
+ err := snapstate.Get(s.state, "alias-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Check(snapst.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ })
+}
+
+func (s *snapmgrTestSuite) TestDoPruneAutoAliasesV2ManualAndDisabled(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ AutoAliasesDisabled: true,
+ AliasesPending: false,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2"},
+ "alias3": {Manual: "cmdx", Auto: "cmd3"},
+ "alias4": {Manual: "cmd4"},
+ },
+ })
+
+ t := s.state.NewTask("prune-auto-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ t.Set("aliases", []string{"alias2", "alias3", "alias4"})
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ s.state.Unlock()
+
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.DoneStatus, Commentf("%v", chg.Err()))
+
+ expected := fakeOps{
+ {
+ 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, "alias-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Check(snapst.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias3": {Manual: "cmdx"},
+ "alias4": {Manual: "cmd4"},
+ })
+}
+
+func (s *snapmgrTestSuite) TestDoRefreshAliasesV2(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.AutoAliases = func(st *state.State, info *snap.Info) (map[string]string, error) {
+ c.Check(info.Name(), Equals, "alias-snap")
+ return map[string]string{
+ "alias1": "cmd1",
+ "alias2": "cmd2",
+ "alias4": "cmd4",
+ }, nil
+ }
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ AliasesPending: false,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2x"},
+ "alias3": {Auto: "cmd3"},
+ },
+ })
+
+ t := s.state.NewTask("refresh-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ s.state.Unlock()
+
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.DoneStatus, Commentf("%v", chg.Err()))
+
+ expected := fakeOps{
+ {
+ op: "update-aliases",
+ rmAliases: []*backend.Alias{
+ {"alias2", "alias-snap.cmd2x"},
+ {"alias3", "alias-snap.cmd3"},
+ },
+ aliases: []*backend.Alias{
+ {"alias2", "alias-snap.cmd2"},
+ {"alias4", "alias-snap.cmd4"},
+ },
+ },
+ }
+ // 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, "alias-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Check(snapst.AutoAliasesDisabled, Equals, false)
+ c.Check(snapst.AliasesPending, Equals, false)
+ c.Check(snapst.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2"},
+ "alias4": {Auto: "cmd4"},
+ })
+}
+
+func (s *snapmgrTestSuite) TestDoUndoRefreshAliasesV2(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.AutoAliases = func(st *state.State, info *snap.Info) (map[string]string, error) {
+ c.Check(info.Name(), Equals, "alias-snap")
+ return map[string]string{
+ "alias1": "cmd1",
+ "alias2": "cmd2",
+ "alias4": "cmd4",
+ }, nil
+ }
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ AliasesPending: false,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2x"},
+ "alias3": {Auto: "cmd3"},
+ },
+ })
+
+ t := s.state.NewTask("refresh-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ terr := s.state.NewTask("error-trigger", "provoking total undo")
+ terr.WaitFor(t)
+ chg.AddTask(terr)
+
+ s.state.Unlock()
+
+ for i := 0; i < 3; i++ {
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+ }
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.UndoneStatus, Commentf("%v", chg.Err()))
+
+ expected := fakeOps{
+ {
+ op: "update-aliases",
+ rmAliases: []*backend.Alias{
+ {"alias2", "alias-snap.cmd2x"},
+ {"alias3", "alias-snap.cmd3"},
+ },
+ aliases: []*backend.Alias{
+ {"alias2", "alias-snap.cmd2"},
+ {"alias4", "alias-snap.cmd4"},
+ },
+ },
+ {
+ op: "update-aliases",
+ aliases: []*backend.Alias{
+ {"alias2", "alias-snap.cmd2x"},
+ {"alias3", "alias-snap.cmd3"},
+ },
+ rmAliases: []*backend.Alias{
+ {"alias2", "alias-snap.cmd2"},
+ {"alias4", "alias-snap.cmd4"},
+ },
+ },
+ }
+ // 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, "alias-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Check(snapst.AutoAliasesDisabled, Equals, false)
+ c.Check(snapst.AliasesPending, Equals, false)
+ c.Check(snapst.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2x"},
+ "alias3": {Auto: "cmd3"},
+ })
+}
+
+func (s *snapmgrTestSuite) TestDoUndoRefreshAliasesV2FromEmpty(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.AutoAliases = func(st *state.State, info *snap.Info) (map[string]string, error) {
+ c.Check(info.Name(), Equals, "alias-snap")
+ return map[string]string{
+ "alias1": "cmd1",
+ "alias2": "cmd2",
+ "alias4": "cmd4",
+ }, nil
+ }
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ AliasesPending: false,
+ })
+
+ t := s.state.NewTask("refresh-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ terr := s.state.NewTask("error-trigger", "provoking total undo")
+ terr.WaitFor(t)
+ chg.AddTask(terr)
+
+ s.state.Unlock()
+
+ for i := 0; i < 3; i++ {
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+ }
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.UndoneStatus, Commentf("%v", chg.Err()))
+
+ expected := fakeOps{
+ {
+ op: "update-aliases",
+ aliases: []*backend.Alias{
+ {"alias1", "alias-snap.cmd1"},
+ {"alias2", "alias-snap.cmd2"},
+ {"alias4", "alias-snap.cmd4"},
+ },
+ },
+ {
+ op: "update-aliases",
+ rmAliases: []*backend.Alias{
+ {"alias1", "alias-snap.cmd1"},
+ {"alias2", "alias-snap.cmd2"},
+ {"alias4", "alias-snap.cmd4"},
+ },
+ },
+ }
+ // 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, "alias-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Check(snapst.AutoAliasesDisabled, Equals, false)
+ c.Check(snapst.AliasesPending, Equals, false)
+ c.Check(snapst.Aliases, HasLen, 0)
+}
+
+func (s *snapmgrTestSuite) TestDoRefreshAliasesV2Pending(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.AutoAliases = func(st *state.State, info *snap.Info) (map[string]string, error) {
+ c.Check(info.Name(), Equals, "alias-snap")
+ return map[string]string{
+ "alias1": "cmd1",
+ "alias2": "cmd2",
+ "alias4": "cmd4",
+ }, nil
+ }
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ AliasesPending: true,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2x"},
+ "alias3": {Auto: "cmd3"},
+ },
+ })
+
+ t := s.state.NewTask("refresh-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ s.state.Unlock()
+
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.DoneStatus, Commentf("%v", chg.Err()))
+
+ // pending: nothing to do on disk
+ c.Assert(s.fakeBackend.ops, HasLen, 0)
+
+ var snapst snapstate.SnapState
+ err := snapstate.Get(s.state, "alias-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Check(snapst.AutoAliasesDisabled, Equals, false)
+ c.Check(snapst.AliasesPending, Equals, true)
+ c.Check(snapst.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2"},
+ "alias4": {Auto: "cmd4"},
+ })
+}
+
+func (s *snapmgrTestSuite) TestDoUndoRefreshAliasesV2Pending(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.AutoAliases = func(st *state.State, info *snap.Info) (map[string]string, error) {
+ c.Check(info.Name(), Equals, "alias-snap")
+ return map[string]string{
+ "alias1": "cmd1",
+ "alias2": "cmd2",
+ "alias4": "cmd4",
+ }, nil
+ }
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ AliasesPending: true,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2x"},
+ "alias3": {Auto: "cmd3"},
+ },
+ })
+
+ t := s.state.NewTask("refresh-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ terr := s.state.NewTask("error-trigger", "provoking total undo")
+ terr.WaitFor(t)
+ chg.AddTask(terr)
+
+ s.state.Unlock()
+
+ for i := 0; i < 3; i++ {
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+ }
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.UndoneStatus, Commentf("%v", chg.Err()))
+
+ // pending: nothing to do on disk
+ c.Assert(s.fakeBackend.ops, HasLen, 0)
+
+ var snapst snapstate.SnapState
+ err := snapstate.Get(s.state, "alias-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Check(snapst.AutoAliasesDisabled, Equals, false)
+ c.Check(snapst.AliasesPending, Equals, true)
+ c.Check(snapst.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2x"},
+ "alias3": {Auto: "cmd3"},
+ })
+}
+
+func (s *snapmgrTestSuite) TestDoRefreshAliasesV2Conflict(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.AutoAliases = func(st *state.State, info *snap.Info) (map[string]string, error) {
+ c.Check(info.Name(), Equals, "alias-snap")
+ return map[string]string{
+ "alias1": "cmd1",
+ "alias2": "cmd2",
+ "alias4": "cmd4",
+ }, nil
+ }
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2x"},
+ "alias3": {Auto: "cmd3"},
+ },
+ })
+ snapstate.Set(s.state, "other-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "other-snap", Revision: snap.R(3)},
+ },
+ Current: snap.R(3),
+ Active: true,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "alias4": {Auto: "cmd4"},
+ },
+ })
+
+ t := s.state.NewTask("refresh-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ s.state.Unlock()
+
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.ErrorStatus, Commentf("%v", chg.Err()))
+ c.Check(chg.Err(), ErrorMatches, `(?s).*cannot enable alias "alias4" for "alias-snap", already enabled for "other-snap".*`)
+}
+
+func (s *snapmgrTestSuite) TestDoUndoRefreshAliasesV2Conflict(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.AutoAliases = func(st *state.State, info *snap.Info) (map[string]string, error) {
+ c.Check(info.Name(), Equals, "alias-snap")
+ return map[string]string{
+ "alias1": "cmd1",
+ "alias2": "cmd2",
+ "alias4": "cmd4",
+ }, nil
+ }
+
+ snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "alias-snap", Revision: snap.R(11)},
+ },
+ Current: snap.R(11),
+ Active: true,
+ AliasesPending: false,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2x"},
+ "alias3": {Auto: "cmd3"},
+ },
+ })
+
+ grabAlias3 := func(t *state.Task, _ *tomb.Tomb) error {
+ st := t.State()
+ st.Lock()
+ defer st.Unlock()
+
+ snapstate.Set(s.state, "other-snap", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{
+ {RealName: "other-snap", Revision: snap.R(3)},
+ },
+ Current: snap.R(3),
+ Active: true,
+ AliasesPending: false,
+ Aliases: map[string]*snapstate.AliasTarget{
+ "alias3": {Auto: "cmd3x"},
+ },
+ })
+
+ return nil
+ }
+
+ s.snapmgr.AddAdhocTaskHandler("grab-alias3", grabAlias3, nil)
+
+ t := s.state.NewTask("refresh-aliases-v2", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{RealName: "alias-snap"},
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ tgrab3 := s.state.NewTask("grab-alias3", "grab alias3 for other-snap")
+ tgrab3.WaitFor(t)
+ chg.AddTask(tgrab3)
+
+ terr := s.state.NewTask("error-trigger", "provoking total undo")
+ terr.WaitFor(t)
+ chg.AddTask(terr)
+
+ s.state.Unlock()
+
+ for i := 0; i < 5; i++ {
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+ }
+
+ s.state.Lock()
+
+ c.Check(t.Status(), Equals, state.UndoneStatus, Commentf("%v", chg.Err()))
+
+ expected := fakeOps{
+ {
+ op: "update-aliases",
+ rmAliases: []*backend.Alias{
+ {"alias2", "alias-snap.cmd2x"},
+ {"alias3", "alias-snap.cmd3"},
+ },
+ aliases: []*backend.Alias{
+ {"alias2", "alias-snap.cmd2"},
+ {"alias4", "alias-snap.cmd4"},
+ },
+ },
+ {
+ op: "update-aliases",
+ rmAliases: []*backend.Alias{
+ {"alias1", "alias-snap.cmd1"},
+ {"alias2", "alias-snap.cmd2"},
+ {"alias4", "alias-snap.cmd4"},
+ },
+ },
+ }
+ // start with an easier-to-read error if this fails:
+ c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
+ c.Check(s.fakeBackend.ops, DeepEquals, expected)
+
+ var snapst snapstate.SnapState
+ err := snapstate.Get(s.state, "alias-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Check(snapst.AutoAliasesDisabled, Equals, true)
+ c.Check(snapst.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{
+ "alias1": {Auto: "cmd1"},
+ "alias2": {Auto: "cmd2x"},
+ "alias3": {Auto: "cmd3"},
+ })
+
+ var snapst2 snapstate.SnapState
+ err = snapstate.Get(s.state, "other-snap", &snapst2)
+ c.Assert(err, IsNil)
+
+ c.Check(snapst2.AutoAliasesDisabled, Equals, false)
+ c.Check(snapst2.Aliases, DeepEquals, map[string]*snapstate.AliasTarget{
+ "alias3": {Auto: "cmd3x"},
+ })
+
+ c.Check(t.Log(), HasLen, 1)
+ c.Check(t.Log()[0], Matches, `.* ERROR cannot reinstate alias state because of conflicts, disabling: cannot enable alias "alias3" for "alias-snap", already enabled for "other-snap".*`)
+}
diff --git a/overlord/snapstate/snapmgr.go b/overlord/snapstate/snapmgr.go
index ed54e01b35..a0638a57b1 100644
--- a/overlord/snapstate/snapmgr.go
+++ b/overlord/snapstate/snapmgr.go
@@ -135,8 +135,9 @@ type SnapState struct {
Channel string `json:"channel,omitempty"`
Flags
// aliases, see aliasesv2.go
- Aliases map[string]*AliasTarget `json:"aliases,omitempty"`
- AliasesStatus AliasesStatus `json:"aliases-status,omitempty"`
+ Aliases map[string]*AliasTarget `json:"aliases,omitempty"`
+ AutoAliasesDisabled bool `json:"auto-aliases-disabled,omitempty"`
+ AliasesPending bool `json:"aliases-pending,omitempty"`
}
// Type returns the type of the snap or an error.
@@ -338,9 +339,16 @@ func Manager(st *state.State) (*SnapManager, error) {
runner.AddHandler("alias", m.doAlias, m.undoAlias)
runner.AddHandler("clear-aliases", m.doClearAliases, m.undoClearAliases)
runner.AddHandler("set-auto-aliases", m.doSetAutoAliases, m.undoClearAliases)
- runner.AddHandler("setup-aliases", m.doSetupAliases, m.undoSetupAliases)
+ runner.AddHandler("setup-aliases", m.doSetupAliases, m.doRemoveAliases)
runner.AddHandler("remove-aliases", m.doRemoveAliases, m.doSetupAliases)
+ // XXX: WIP: aliases v2: temporary task names to be able to write tess until switching
+ runner.AddHandler("set-auto-aliases-v2", m.doSetAutoAliasesV2, m.undoRefreshAliasesV2)
+ runner.AddHandler("setup-aliases-v2", m.doSetupAliasesV2, m.doRemoveAliasesV2)
+ runner.AddHandler("refresh-aliases-v2", m.doRefreshAliasesV2, m.undoRefreshAliasesV2)
+ runner.AddHandler("prune-auto-aliases-v2", m.doPruneAutoAliasesV2, m.undoRefreshAliasesV2)
+ runner.AddHandler("remove-aliases-v2", m.doRemoveAliasesV2, m.doSetupAliasesV2)
+
// control serialisation
runner.SetBlocked(m.blockedTask)
@@ -356,6 +364,7 @@ func Manager(st *state.State) (*SnapManager, error) {
}
func diskAliasTask(t *state.Task) bool {
+ // TODO: aliases v2!
kind := t.Kind()
return kind == "setup-aliases" || kind == "remove-aliases" || kind == "alias"
}
diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go
index 306ef7c413..7bbe490de9 100644
--- a/overlord/snapstate/snapstate.go
+++ b/overlord/snapstate/snapstate.go
@@ -27,6 +27,7 @@ import (
"sort"
"github.com/snapcore/snapd/boot"
+ "github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/i18n/dumb"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/overlord/auth"
@@ -55,8 +56,12 @@ func needsMaybeCore(typ snap.Type) int {
}
func doInstall(st *state.State, snapst *SnapState, snapsup *SnapSetup, flags int) (*state.TaskSet, error) {
- if snapsup.Flags.Classic && !release.OnClassic {
- return nil, fmt.Errorf("classic confinement is only supported on classic systems")
+ if snapsup.Flags.Classic {
+ if !release.OnClassic {
+ return nil, fmt.Errorf("classic confinement is only supported on classic systems")
+ } else if !dirs.SupportsClassicConfinement() {
+ return nil, fmt.Errorf("classic confinement is not yet supported on your distribution")
+ }
}
if !snapst.HasCurrent() { // install?
// check that the snap command namespace doesn't conflict with an enabled alias
@@ -614,7 +619,7 @@ func doUpdate(st *state.State, names []string, updates []*snap.Info, params func
func applyAutoAliasesDelta(st *state.State, delta map[string][]string, op string, refreshAll bool, linkTs func(snapName string, ts *state.TaskSet)) (*state.TaskSet, error) {
applyTs := state.NewTaskSet()
for snapName, aliases := range delta {
- ts, err := ResetAliases(st, snapName, aliases)
+ ts, err := resetAliases(st, snapName, aliases)
if err != nil {
if refreshAll {
// doing "refresh all", just skip this snap
diff --git a/overlord/snapstate/snapstate_test.go b/overlord/snapstate/snapstate_test.go
index e9696eb012..0b53424d28 100644
--- a/overlord/snapstate/snapstate_test.go
+++ b/overlord/snapstate/snapstate_test.go
@@ -244,6 +244,22 @@ func (s *snapmgrTestSuite) TestInstallClassicConfinementFiltering(c *C) {
c.Assert(err, IsNil)
}
+func (s *snapmgrTestSuite) TestInstallFailsWhenClassicSnapsAreNotSupported(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ reset := release.MockReleaseInfo(&release.OS{
+ ID: "fedora",
+ })
+ defer func() { reset(); dirs.SetRootDir("/") }()
+
+ dirs.SetRootDir("/")
+
+ _, err := snapstate.Install(s.state, "some-snap", "channel-for-classic", snap.R(0), s.user.ID, snapstate.Flags{Classic: true})
+ c.Assert(err, Not(IsNil))
+ c.Assert(err, DeepEquals, fmt.Errorf("classic confinement is not yet supported on your distribution"))
+}
+
func (s *snapmgrTestSuite) TestInstallTasks(c *C) {
s.state.Lock()
defer s.state.Unlock()
@@ -1722,10 +1738,8 @@ func (s *snapmgrTestSuite) TestUpdateTotalUndoRunThrough(c *C) {
name: "/snap/some-snap/11",
},
{
- op: "matching-aliases",
- },
- {
- op: "update-aliases",
+ op: "remove-snap-aliases",
+ name: "some-snap",
},
{
op: "unlink-snap",
@@ -3763,10 +3777,8 @@ func (s *snapmgrTestSuite) TestRevertTotalUndoRunThrough(c *C) {
name: "/snap/some-snap/1",
},
{
- op: "matching-aliases",
- },
- {
- op: "update-aliases",
+ op: "remove-snap-aliases",
+ name: "some-snap",
},
{
op: "unlink-snap",
diff --git a/packaging/ubuntu-14.04/changelog b/packaging/ubuntu-14.04/changelog
index 1981865620..9f50c1d28a 100644
--- a/packaging/ubuntu-14.04/changelog
+++ b/packaging/ubuntu-14.04/changelog
@@ -1,3 +1,177 @@
+snapd (2.24~14.04) trusty; urgency=medium
+
+ * New upstream release, LP: #1681799:
+ - interfaces/mount: add InfoEntry type
+ - many: fix plug auto-connect during core transition
+ - interfaces: fold network bind into core support with tests
+ - .travis.yml: add option to make raw log less noisy
+ - interfaces: adjust shm accesses to use 'm' for updated mmap kernel
+ mediation
+ - many: rename two core plugs that clash with slot names
+ - snap-confine,browser-support: /dev/tty for snap-confine, misc
+ browser-support for gnome-shell
+ - store: add download test with EOF in the middle
+ - tests: adjust to look for network-bind-plug
+ - store: make hash error message more accurate
+ - overlord/snapstate: simplify AliasesStatus down to just an
+ AutoAliasesDisabled bool flag (aliases v2)
+ - errtracker: never send errtracker reports when running under
+ SNAPPY_TESTING
+ - interfaces/repo: validate slot/plug names
+ - daemon: Give the snap directories via GET /v2/system-info
+ - interfaces/unity7: support unity messaging menu
+ - interfaces/mount: add high-level Profile functions
+ - git: ignore only the cmd/Makefile{,.in}
+ - cmd: explicitly set _GNU_SOURCE and _FILE_OFFSET_BITS for xfs
+ support
+ - daemon: add desktop file location for app to the API
+ - overlord,release: disable classic snap support when not possible
+ - overlord: fix TestEnsureLoopPrune not to be so racy
+ - many: abstract path to /bin/{true,false}
+ - data/systemd: tweak data/systemd/Makefile to be slightly simpler
+ - store: handle EOF via url.Error check
+ - packaging: use templates for relevant systemd units
+ - tests: run gccgo only on ubuntu-16.04-64
+ - .travis.yml: remove travis matrix and do a single sequential run
+ - overlord/state: make sure that setting to nil a state key is
+ equivalent to deleting it
+ - tests: fix incorrect shell expression
+ - interfaces/mount: add OptsToFlags for converting arguments to
+ syscall…
+ - interfaces: add a joystick interface
+ - tests: enable docker test for more ubuntu-core systems
+ - tests: download and install additional dependencies when using
+ prepackaged snapd
+ - many: add support for partially static builds
+ - interfaces: allow slot to introspect dbus-daemon in dbus
+ interface, allow /usr/bin/arch by default
+ - interfaces/mount: fix golint issues
+ - interfaces/mount: add function for saving fstab-like file
+ - osutil: introducing GetenvInt64, like GetenvBool but Int64er.
+ - interfaces: drop udev tagging from framebuffer interface
+ - snapstate: more helpers to work with aliases state (aliases
+ v2)
+ - interfaces/mount: add function for parsing fstab-like file
+ - cmd: disable the re-associate fix as requested by jdstrand
+ - overlord/snapstate: unlock/relock the state less, especially not
+ across mutating the SnapState of a snap
+ - interfaces: allow executing ld.so (needed with new AppArmor base
+ abstraction)
+ - interfaces/mount: add function for parsing mount entries
+ - cmd: rework header check for xfs/xqm.h
+ - cmd: add poky to the list of distros which don't support reexec
+ - overlord: finish reorg, revert "be more conservative until we have
+ cut 2.23.x"
+ - cmd: select what socket to use in cmd/snap{,ctl}
+ - overlord: remove snap config values when snap is removed
+ - snapstate: introduce helper to apply to disk a alias states change
+ for a snap (aliases v2)
+ - configstate,hookstate: timeout the configure hook after 5 mins,
+ report failures to the errtracker
+ - interfaces/seccomp: add bind as part of the default seccomp policy
+ for hooks
+ - cmd: discard the C implementation of snap-update-ns
+ - tests: remove stale apt proxy leftover from cloud-init
+ - tests: move unity test to nightly suite
+ - interfaces: add support for location-observe for
+ dbus::ObjectManager session paths
+ - boot: log error in KernelOrOsRebootRequired
+ - interfaces: remove old API
+ - interfaces: use udev spec
+ - interfaces: convert systemd backend to new APIs
+ - osutil: add BootID
+ - tests: move docker test to new nightly suite
+ - interfaces/mount: compute mount changes required to transition
+ mount profiles
+ - data/selinux: add context definition for snapctl
+ - overlord: clean up organization under state packages
+ - overlord: make sure all managers packages have *state.go with the
+ main state manipulation/query APIs
+ - interfaces: use spec in the dbus backend
+ - store: download from authenticated URL if there is a device
+ session set
+ - tests: remove core_name variable
+ - interfaces: rename thumbnailer to thumbnailer-service
+ - interfaces: add chroot to base templates
+ - asserts: remove some unused things
+ - systemd: mount the squashfs with nodev
+ - overlord: when shutting down assume errors might be due to
+ cancellation so retry
+ - cmd: rename all unit tests to $command/unit-test
+ - cmd/snap: fix help string for version command
+ - asserts: don't allow revocations with other items for the same
+ developer
+ - tests: skip lp-1644439 test on older kernels
+ - interfaces: allow "sync" to be used by core support
+ - assertstate,snapstate: have assertstate.AutoAliases use the
+ "aliases" header
+ - interfaces: allow writing config.txt.tmp in the core-support
+ interface
+ - tests: adjust network-bind test
+ - interfaces: dbus backend spec
+ - asserts: introduce a snap-declaration "aliases" header to list
+ auto aliases with explicit targets
+ - cmd: enable large file support
+ - cmd/snap: handle missing snap-confine
+ - cmd/snap-confine: re-associate with pid-1 mount namespace if
+ required
+ - cmd/libsnap: make mountinfo structures public
+ - tests: fix interfaces-cups-control for zesty
+ - misc: revert "Log if the system goes into ForceDevMode"
+ - interfaces: seccomp tests cleanup
+ - cmd: validate SNAP_NAME
+ - interfaces: log if the system goes into ForceDevMode
+ - tests: fix classic-ubuntu-core-transition race
+ - interfaces: use apparmor spec in the apparmor backend
+ - interfaces: alphabetize framebuffer in base decl and add it to
+ all_test.go
+ - tests: add ubuntu-core-16-32 system to the external backend and
+ fix docker test
+ - cmd/libsnap: simplify sc_string_quote default case
+ - osutil: fix double expand in environment map code and add test
+ - interfaces: extend location-control out-of-process provider
+ support
+ - cmd/snap-update-ns: use bidirectional lists for mount entries
+ - tests: prevent automatic transition before setting the initial
+ state of the test
+ - release: detect if we are in ForcedDevMode by inspecting the
+ kernel
+ - tests: add core-snap-refresh test
+ - interfaces: add maliit input method interface
+ - interfaces: seccomp spec API tweaks for better tests
+ - interfaces: updates for mir-kiosk in browser-support, mir, opengl,
+ unity7
+ - testutils: address review feedback from PR#2997
+ - tests: specify the core version to be unsquashfs'ed in the
+ failover tests
+ - interfaces: use MockInfo in tests
+ - cmd/libsnap: add sc_quote_string
+ - cmd/snap-confine: use sc_do_umount everywhere
+ - interfaces: add unity8 plug permissions
+ - timeutil: a few helpers for the recurring events
+ - asserts: implement snap-developer type
+ - partition: deal with grub{,2}-editenv in tests
+ - many: add new (hidden) `snap debug ensure-state-soon` command and
+ use in tests
+ - interfaces/builtin: small refactor of dbus tests
+ - packaging, tests: use "systemctl list-unit-files --full"
+ everywhere
+ - many: some opensuse patches that are ready to go into master
+ - packaging: add opensuse permissions files
+ - client, daemon: move "snap list" name filtering into snapd.
+ - interfaces: use seccomp specs
+ - overlord/snapstate: small cleanup of
+ ensureForceDevmodeDropsDevmodeFromState
+ - interfaces/builtin/alsa: add read access to alsa state dir
+ - interfaces: use spec in kmod backend, updated firewall_control,
+ openvswitch_support, ppp
+ - cmd/snap-confine: use sc_do_mount everywhere
+ - tests: remove workaround for docker again, snap-declaration is
+ fixed now
+ - interfaces: interface to allow autopilot introspection
+
+ -- Michael Vogt <michael.vogt@ubuntu.com> Tue, 11 Apr 2017 13:31:46 +0200
+
snapd (2.23.6~14.04) trusty; urgency=medium
* New upstream release, LP: #1665608
diff --git a/packaging/ubuntu-14.04/control b/packaging/ubuntu-14.04/control
index a00cf72d85..d93df23129 100644
--- a/packaging/ubuntu-14.04/control
+++ b/packaging/ubuntu-14.04/control
@@ -23,6 +23,7 @@ Build-Depends: autoconf,
libglib2.0-dev,
libseccomp-dev (>= 2.1.1-1ubuntu1~trusty3),
libudev-dev,
+ openssh-client,
pkg-config,
python3,
python3-docutils,
@@ -60,6 +61,7 @@ Depends: adduser,
cgroup-lite,
gnupg1 | gnupg,
linux-generic-lts-xenial,
+ openssh-client,
squashfs-tools,
# only needed on trusty to pull in the right version.
sudo (>= 1.8.9p5-1ubuntu1.3),
diff --git a/packaging/ubuntu-16.04/changelog b/packaging/ubuntu-16.04/changelog
index adbf47eb1b..565179d023 100644
--- a/packaging/ubuntu-16.04/changelog
+++ b/packaging/ubuntu-16.04/changelog
@@ -1,3 +1,177 @@
+snapd (2.24) xenial; urgency=medium
+
+ * New upstream release, LP: #1681799:
+ - interfaces/mount: add InfoEntry type
+ - many: fix plug auto-connect during core transition
+ - interfaces: fold network bind into core support with tests
+ - .travis.yml: add option to make raw log less noisy
+ - interfaces: adjust shm accesses to use 'm' for updated mmap kernel
+ mediation
+ - many: rename two core plugs that clash with slot names
+ - snap-confine,browser-support: /dev/tty for snap-confine, misc
+ browser-support for gnome-shell
+ - store: add download test with EOF in the middle
+ - tests: adjust to look for network-bind-plug
+ - store: make hash error message more accurate
+ - overlord/snapstate: simplify AliasesStatus down to just an
+ AutoAliasesDisabled bool flag (aliases v2)
+ - errtracker: never send errtracker reports when running under
+ SNAPPY_TESTING
+ - interfaces/repo: validate slot/plug names
+ - daemon: Give the snap directories via GET /v2/system-info
+ - interfaces/unity7: support unity messaging menu
+ - interfaces/mount: add high-level Profile functions
+ - git: ignore only the cmd/Makefile{,.in}
+ - cmd: explicitly set _GNU_SOURCE and _FILE_OFFSET_BITS for xfs
+ support
+ - daemon: add desktop file location for app to the API
+ - overlord,release: disable classic snap support when not possible
+ - overlord: fix TestEnsureLoopPrune not to be so racy
+ - many: abstract path to /bin/{true,false}
+ - data/systemd: tweak data/systemd/Makefile to be slightly simpler
+ - store: handle EOF via url.Error check
+ - packaging: use templates for relevant systemd units
+ - tests: run gccgo only on ubuntu-16.04-64
+ - .travis.yml: remove travis matrix and do a single sequential run
+ - overlord/state: make sure that setting to nil a state key is
+ equivalent to deleting it
+ - tests: fix incorrect shell expression
+ - interfaces/mount: add OptsToFlags for converting arguments to
+ syscall…
+ - interfaces: add a joystick interface
+ - tests: enable docker test for more ubuntu-core systems
+ - tests: download and install additional dependencies when using
+ prepackaged snapd
+ - many: add support for partially static builds
+ - interfaces: allow slot to introspect dbus-daemon in dbus
+ interface, allow /usr/bin/arch by default
+ - interfaces/mount: fix golint issues
+ - interfaces/mount: add function for saving fstab-like file
+ - osutil: introducing GetenvInt64, like GetenvBool but Int64er.
+ - interfaces: drop udev tagging from framebuffer interface
+ - snapstate: more helpers to work with aliases state (aliases
+ v2)
+ - interfaces/mount: add function for parsing fstab-like file
+ - cmd: disable the re-associate fix as requested by jdstrand
+ - overlord/snapstate: unlock/relock the state less, especially not
+ across mutating the SnapState of a snap
+ - interfaces: allow executing ld.so (needed with new AppArmor base
+ abstraction)
+ - interfaces/mount: add function for parsing mount entries
+ - cmd: rework header check for xfs/xqm.h
+ - cmd: add poky to the list of distros which don't support reexec
+ - overlord: finish reorg, revert "be more conservative until we have
+ cut 2.23.x"
+ - cmd: select what socket to use in cmd/snap{,ctl}
+ - overlord: remove snap config values when snap is removed
+ - snapstate: introduce helper to apply to disk a alias states change
+ for a snap (aliases v2)
+ - configstate,hookstate: timeout the configure hook after 5 mins,
+ report failures to the errtracker
+ - interfaces/seccomp: add bind as part of the default seccomp policy
+ for hooks
+ - cmd: discard the C implementation of snap-update-ns
+ - tests: remove stale apt proxy leftover from cloud-init
+ - tests: move unity test to nightly suite
+ - interfaces: add support for location-observe for
+ dbus::ObjectManager session paths
+ - boot: log error in KernelOrOsRebootRequired
+ - interfaces: remove old API
+ - interfaces: use udev spec
+ - interfaces: convert systemd backend to new APIs
+ - osutil: add BootID
+ - tests: move docker test to new nightly suite
+ - interfaces/mount: compute mount changes required to transition
+ mount profiles
+ - data/selinux: add context definition for snapctl
+ - overlord: clean up organization under state packages
+ - overlord: make sure all managers packages have *state.go with the
+ main state manipulation/query APIs
+ - interfaces: use spec in the dbus backend
+ - store: download from authenticated URL if there is a device
+ session set
+ - tests: remove core_name variable
+ - interfaces: rename thumbnailer to thumbnailer-service
+ - interfaces: add chroot to base templates
+ - asserts: remove some unused things
+ - systemd: mount the squashfs with nodev
+ - overlord: when shutting down assume errors might be due to
+ cancellation so retry
+ - cmd: rename all unit tests to $command/unit-test
+ - cmd/snap: fix help string for version command
+ - asserts: don't allow revocations with other items for the same
+ developer
+ - tests: skip lp-1644439 test on older kernels
+ - interfaces: allow "sync" to be used by core support
+ - assertstate,snapstate: have assertstate.AutoAliases use the
+ "aliases" header
+ - interfaces: allow writing config.txt.tmp in the core-support
+ interface
+ - tests: adjust network-bind test
+ - interfaces: dbus backend spec
+ - asserts: introduce a snap-declaration "aliases" header to list
+ auto aliases with explicit targets
+ - cmd: enable large file support
+ - cmd/snap: handle missing snap-confine
+ - cmd/snap-confine: re-associate with pid-1 mount namespace if
+ required
+ - cmd/libsnap: make mountinfo structures public
+ - tests: fix interfaces-cups-control for zesty
+ - misc: revert "Log if the system goes into ForceDevMode"
+ - interfaces: seccomp tests cleanup
+ - cmd: validate SNAP_NAME
+ - interfaces: log if the system goes into ForceDevMode
+ - tests: fix classic-ubuntu-core-transition race
+ - interfaces: use apparmor spec in the apparmor backend
+ - interfaces: alphabetize framebuffer in base decl and add it to
+ all_test.go
+ - tests: add ubuntu-core-16-32 system to the external backend and
+ fix docker test
+ - cmd/libsnap: simplify sc_string_quote default case
+ - osutil: fix double expand in environment map code and add test
+ - interfaces: extend location-control out-of-process provider
+ support
+ - cmd/snap-update-ns: use bidirectional lists for mount entries
+ - tests: prevent automatic transition before setting the initial
+ state of the test
+ - release: detect if we are in ForcedDevMode by inspecting the
+ kernel
+ - tests: add core-snap-refresh test
+ - interfaces: add maliit input method interface
+ - interfaces: seccomp spec API tweaks for better tests
+ - interfaces: updates for mir-kiosk in browser-support, mir, opengl,
+ unity7
+ - testutils: address review feedback from PR#2997
+ - tests: specify the core version to be unsquashfs'ed in the
+ failover tests
+ - interfaces: use MockInfo in tests
+ - cmd/libsnap: add sc_quote_string
+ - cmd/snap-confine: use sc_do_umount everywhere
+ - interfaces: add unity8 plug permissions
+ - timeutil: a few helpers for the recurring events
+ - asserts: implement snap-developer type
+ - partition: deal with grub{,2}-editenv in tests
+ - many: add new (hidden) `snap debug ensure-state-soon` command and
+ use in tests
+ - interfaces/builtin: small refactor of dbus tests
+ - packaging, tests: use "systemctl list-unit-files --full"
+ everywhere
+ - many: some opensuse patches that are ready to go into master
+ - packaging: add opensuse permissions files
+ - client, daemon: move "snap list" name filtering into snapd.
+ - interfaces: use seccomp specs
+ - overlord/snapstate: small cleanup of
+ ensureForceDevmodeDropsDevmodeFromState
+ - interfaces/builtin/alsa: add read access to alsa state dir
+ - interfaces: use spec in kmod backend, updated firewall_control,
+ openvswitch_support, ppp
+ - cmd/snap-confine: use sc_do_mount everywhere
+ - tests: remove workaround for docker again, snap-declaration is
+ fixed now
+ - interfaces: interface to allow autopilot introspection
+
+ -- Michael Vogt <michael.vogt@ubuntu.com> Tue, 11 Apr 2017 13:31:46 +0200
+
snapd (2.23.6) xenial; urgency=medium
* New upstream release, LP: #1673568
diff --git a/packaging/ubuntu-16.04/control b/packaging/ubuntu-16.04/control
index 565d66ac0b..628e17dbc3 100644
--- a/packaging/ubuntu-16.04/control
+++ b/packaging/ubuntu-16.04/control
@@ -23,6 +23,7 @@ Build-Depends: autoconf,
libglib2.0-dev,
libseccomp-dev,
libudev-dev,
+ openssh-client,
pkg-config,
python3,
python3-docutils,
@@ -58,6 +59,7 @@ Depends: adduser,
apparmor (>= 2.10.95-0ubuntu2.2),
ca-certificates,
gnupg1 | gnupg,
+ openssh-client,
squashfs-tools,
systemd,
${misc:Depends},
diff --git a/packaging/ubuntu-16.04/snap-confine.maintscript b/packaging/ubuntu-16.04/snap-confine.maintscript
new file mode 100644
index 0000000000..16bc877472
--- /dev/null
+++ b/packaging/ubuntu-16.04/snap-confine.maintscript
@@ -0,0 +1 @@
+rm_conffile /etc/apparmor.d/usr.lib.snapd.snap-confine 2.23.6~
diff --git a/packaging/ubuntu-16.04/snapd.postrm b/packaging/ubuntu-16.04/snapd.postrm
index 1f5df2afbe..5738507f6d 100644
--- a/packaging/ubuntu-16.04/snapd.postrm
+++ b/packaging/ubuntu-16.04/snapd.postrm
@@ -72,6 +72,9 @@ if [ "$1" = "purge" ]; then
done
umount -l /run/snapd/ns/ || true
+ echo "Removing extra snap-confine apparmor rules"
+ rm -f /etc/apparmor.d/snap.core.*.usr.lib.snapd.snap-confine
+
echo "Removing snapd state"
rm -rf /var/lib/snapd
fi
diff --git a/snap/broken.go b/snap/broken.go
index 52ebede3c5..4c3d7f1e53 100644
--- a/snap/broken.go
+++ b/snap/broken.go
@@ -62,3 +62,38 @@ func GuessAppsForBroken(info *Info) map[string]*AppInfo {
return out
}
+
+// renameClashingCorePlugs renames plugs that clash with slot names on core snap.
+//
+// Some released core snaps had explicitly defined plugs "network-bind" and
+// "core-support" that clashed with implicit slots with the same names but this
+// was not validated before. To avoid a flag day and any potential issues,
+// transparently rename the two clashing plugs by appending the "-plug" suffix.
+func (info *Info) renameClashingCorePlugs() {
+ if info.Name() == "core" && info.Type == TypeOS {
+ for _, plugName := range []string{"network-bind", "core-support"} {
+ info.renamePlug(plugName, plugName+"-plug")
+ }
+ }
+}
+
+// renamePlug renames the plug from oldName to newName, if present.
+func (info *Info) renamePlug(oldName, newName string) {
+ if plugInfo, ok := info.Plugs[oldName]; ok {
+ delete(info.Plugs, oldName)
+ info.Plugs[newName] = plugInfo
+ plugInfo.Name = newName
+ for _, appInfo := range info.Apps {
+ if _, ok := appInfo.Plugs[oldName]; ok {
+ delete(appInfo.Plugs, oldName)
+ appInfo.Plugs[newName] = plugInfo
+ }
+ }
+ for _, hookInfo := range info.Hooks {
+ if _, ok := hookInfo.Plugs[oldName]; ok {
+ delete(hookInfo.Plugs, oldName)
+ hookInfo.Plugs[newName] = plugInfo
+ }
+ }
+ }
+}
diff --git a/snap/broken_test.go b/snap/broken_test.go
index 6a06adf958..17175ae03e 100644
--- a/snap/broken_test.go
+++ b/snap/broken_test.go
@@ -28,6 +28,7 @@ import (
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/snaptest"
)
type brokenSuite struct{}
@@ -70,3 +71,45 @@ func (s *brokenSuite) TestGuessAppsForBrokenServices(c *C) {
c.Check(apps["foo"], DeepEquals, &snap.AppInfo{Snap: info, Name: "foo", Daemon: "simple"})
c.Check(apps["bar"], DeepEquals, &snap.AppInfo{Snap: info, Name: "bar", Daemon: "simple"})
}
+
+func (s *brokenSuite) TestRenamePlug(c *C) {
+ snapInfo := snaptest.MockInvalidInfo(c, `name: core
+plugs:
+ old:
+ interface: iface
+slots:
+ old:
+ interface: iface
+apps:
+ app:
+hooks:
+ configure:
+`, nil)
+ c.Assert(snapInfo.Plugs["old"], Not(IsNil))
+ c.Assert(snapInfo.Plugs["old"].Name, Equals, "old")
+ c.Assert(snapInfo.Slots["old"], Not(IsNil))
+ c.Assert(snapInfo.Slots["old"].Name, Equals, "old")
+ c.Assert(snapInfo.Apps["app"].Plugs["old"], DeepEquals, snapInfo.Plugs["old"])
+ c.Assert(snapInfo.Apps["app"].Slots["old"], DeepEquals, snapInfo.Slots["old"])
+ c.Assert(snapInfo.Hooks["configure"].Plugs["old"], DeepEquals, snapInfo.Plugs["old"])
+
+ // Rename the plug now.
+ snapInfo.RenamePlug("old", "new")
+
+ // Check that there's no trace of the old plug name.
+ c.Assert(snapInfo.Plugs["old"], IsNil)
+ c.Assert(snapInfo.Plugs["new"], Not(IsNil))
+ c.Assert(snapInfo.Plugs["new"].Name, Equals, "new")
+ c.Assert(snapInfo.Apps["app"].Plugs["old"], IsNil)
+ c.Assert(snapInfo.Apps["app"].Plugs["new"], DeepEquals, snapInfo.Plugs["new"])
+ c.Assert(snapInfo.Hooks["configure"].Plugs["old"], IsNil)
+ c.Assert(snapInfo.Hooks["configure"].Plugs["new"], DeepEquals, snapInfo.Plugs["new"])
+
+ // Check that slots with the old name are unaffected.
+ c.Assert(snapInfo.Slots["old"], Not(IsNil))
+ c.Assert(snapInfo.Slots["old"].Name, Equals, "old")
+ c.Assert(snapInfo.Apps["app"].Slots["old"], DeepEquals, snapInfo.Slots["old"])
+
+ // Check that the rename made the snap valid now
+ c.Assert(snap.Validate(snapInfo), IsNil)
+}
diff --git a/snap/export_test.go b/snap/export_test.go
index 57e65a36f1..aacd5e7642 100644
--- a/snap/export_test.go
+++ b/snap/export_test.go
@@ -30,3 +30,7 @@ func MockSupportedHookTypes(hookTypes []*HookType) (restore func()) {
supportedHooks = hookTypes
return func() { supportedHooks = old }
}
+
+func (info *Info) RenamePlug(oldName, newName string) {
+ info.renamePlug(oldName, newName)
+}
diff --git a/snap/implicit.go b/snap/implicit.go
index 8db7eaff26..493e98d0a4 100644
--- a/snap/implicit.go
+++ b/snap/implicit.go
@@ -45,6 +45,7 @@ var implicitSlots = []string{
"io-ports-control",
"joystick",
"kernel-module-control",
+ "kubernetes-support",
"locale-control",
"log-observe",
"lxd-support",
diff --git a/snap/info.go b/snap/info.go
index 555bc60416..c18346bb71 100644
--- a/snap/info.go
+++ b/snap/info.go
@@ -402,6 +402,10 @@ func (app *AppInfo) SecurityTag() string {
return AppSecurityTag(app.Snap.Name(), app.Name)
}
+func (app *AppInfo) DesktopFile() string {
+ return filepath.Join(dirs.SnapDesktopFilesDir, fmt.Sprintf("%s_%s.desktop", app.Snap.Name(), app.Name))
+}
+
// WrapperPath returns the path to wrapper invoking the app binary.
func (app *AppInfo) WrapperPath() string {
var binName string
diff --git a/snap/info_snap_yaml.go b/snap/info_snap_yaml.go
index d33e63d4a5..b32fe2d5d8 100644
--- a/snap/info_snap_yaml.go
+++ b/snap/info_snap_yaml.go
@@ -118,6 +118,9 @@ func InfoFromSnapYaml(yamlData []byte) (*Info, error) {
// Bind unbound slots to all apps
bindUnboundSlots(globalSlotNames, snap)
+ // Rename specific plugs on the core snap.
+ snap.renameClashingCorePlugs()
+
// FIXME: validation of the fields
return snap, nil
}
diff --git a/snap/info_test.go b/snap/info_test.go
index 9e769248de..e4f8a77131 100644
--- a/snap/info_test.go
+++ b/snap/info_test.go
@@ -132,6 +132,10 @@ version: 1
apps:
app:
command: foo
+ app2:
+ command: bar
+ sample:
+ command: foobar
`
const sampleContents = "SNAP"
@@ -501,3 +505,66 @@ func (s *infoSuite) TestDirAndFileMethods(c *C) {
c.Check(info.CommonDataHomeDir(), Equals, "/home/*/snap/name/common")
c.Check(info.XdgRuntimeDirs(), Equals, "/run/user/*/snap.name")
}
+
+func makeFakeDesktopFile(c *C, name, content string) string {
+ df := filepath.Join(dirs.SnapDesktopFilesDir, name)
+ err := os.MkdirAll(filepath.Dir(df), 0755)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(df, []byte(content), 0644)
+ c.Assert(err, IsNil)
+ return df
+}
+
+func (s *infoSuite) TestAppDesktopFile(c *C) {
+ snaptest.MockSnap(c, sampleYaml, sampleContents, &snap.SideInfo{})
+ snapInfo, err := snap.ReadInfo("sample", &snap.SideInfo{})
+ c.Assert(err, IsNil)
+
+ c.Check(snapInfo.Name(), Equals, "sample")
+ c.Check(snapInfo.Apps["app"].DesktopFile(), Matches, `.*/var/lib/snapd/desktop/applications/sample_app.desktop`)
+ c.Check(snapInfo.Apps["sample"].DesktopFile(), Matches, `.*/var/lib/snapd/desktop/applications/sample_sample.desktop`)
+}
+
+const coreSnapYaml = `name: core
+type: os
+plugs:
+ network-bind:
+ core-support:
+`
+
+// reading snap via ReadInfoFromSnapFile renames clashing core plugs
+func (s *infoSuite) TestReadInfoFromSnapFileRenamesCorePlus(c *C) {
+ snapPath := snaptest.MakeTestSnapWithFiles(c, coreSnapYaml, nil)
+
+ snapf, err := snap.Open(snapPath)
+ c.Assert(err, IsNil)
+
+ info, err := snap.ReadInfoFromSnapFile(snapf, nil)
+ c.Assert(err, IsNil)
+ c.Check(info.Plugs["network-bind"], IsNil)
+ c.Check(info.Plugs["core-support"], IsNil)
+ c.Check(info.Plugs["network-bind-plug"], NotNil)
+ c.Check(info.Plugs["core-support-plug"], NotNil)
+}
+
+// reading snap via ReadInfo renames clashing core plugs
+func (s *infoSuite) TestReadInfoRenamesCorePlugs(c *C) {
+ si := &snap.SideInfo{Revision: snap.R(42), RealName: "core"}
+ snaptest.MockSnap(c, coreSnapYaml, sampleContents, si)
+ info, err := snap.ReadInfo("core", si)
+ c.Assert(err, IsNil)
+ c.Check(info.Plugs["network-bind"], IsNil)
+ c.Check(info.Plugs["core-support"], IsNil)
+ c.Check(info.Plugs["network-bind-plug"], NotNil)
+ c.Check(info.Plugs["core-support-plug"], NotNil)
+}
+
+// reading snap via InfoFromSnapYaml renames clashing core plugs
+func (s *infoSuite) TestInfoFromSnapYamlRenamesCorePlugs(c *C) {
+ info, err := snap.InfoFromSnapYaml([]byte(coreSnapYaml))
+ c.Assert(err, IsNil)
+ c.Check(info.Plugs["network-bind"], IsNil)
+ c.Check(info.Plugs["core-support"], IsNil)
+ c.Check(info.Plugs["network-bind-plug"], NotNil)
+ c.Check(info.Plugs["core-support-plug"], NotNil)
+}
diff --git a/snap/snaptest/snaptest.go b/snap/snaptest/snaptest.go
index 1def2ddeed..989903f4b5 100644
--- a/snap/snaptest/snaptest.go
+++ b/snap/snaptest/snaptest.go
@@ -79,6 +79,23 @@ func MockInfo(c *check.C, yamlText string, sideInfo *snap.SideInfo) *snap.Info {
return snapInfo
}
+// MockInvalidInfo parses the given snap.yaml text and returns the snap.Info object including the optional SideInfo.
+//
+// The result is just kept in memory, there is nothing kept on disk. If that is
+// desired please use MockSnap instead.
+func MockInvalidInfo(c *check.C, yamlText string, sideInfo *snap.SideInfo) *snap.Info {
+ if sideInfo == nil {
+ sideInfo = &snap.SideInfo{}
+ }
+
+ snapInfo, err := snap.InfoFromSnapYaml([]byte(yamlText))
+ c.Assert(err, check.IsNil)
+ snapInfo.SideInfo = *sideInfo
+ err = snap.Validate(snapInfo)
+ c.Assert(err, check.NotNil)
+ return snapInfo
+}
+
// PopulateDir populates the directory with files specified as pairs of relative file path and its content. Useful to add extra files to a snap.
func PopulateDir(dir string, files [][]string) {
for _, filenameAndContent := range files {
diff --git a/snap/snaptest/snaptest_test.go b/snap/snaptest/snaptest_test.go
index 3765d2b784..370ff6fe74 100644
--- a/snap/snaptest/snaptest_test.go
+++ b/snap/snaptest/snaptest_test.go
@@ -88,3 +88,20 @@ func (s *snapTestSuite) TestMockInfo(c *C) {
c.Check(snapInfo.Apps["app"].Command, Equals, "foo")
c.Check(snapInfo.Plugs["network"].Interface, Equals, "network")
}
+
+func (s *snapTestSuite) TestMockInvalidInfo(c *C) {
+ snapInfo := snaptest.MockInvalidInfo(c, sampleYaml+"\nslots:\n network:\n", &snap.SideInfo{Revision: snap.R(42)})
+ // Data from YAML is used
+ c.Check(snapInfo.Name(), Equals, "sample")
+ // Data from SideInfo is used
+ c.Check(snapInfo.Revision, Equals, snap.R(42))
+ // The YAML is *not* placed on disk
+ _, err := os.Stat(filepath.Join(dirs.SnapMountDir, "sample", "42", "meta", "snap.yaml"))
+ c.Assert(os.IsNotExist(err), Equals, true)
+ // More
+ c.Check(snapInfo.Apps["app"].Command, Equals, "foo")
+ c.Check(snapInfo.Plugs["network"].Interface, Equals, "network")
+ c.Check(snapInfo.Slots["network"].Interface, Equals, "network")
+ // They info object is not valid
+ c.Check(snap.Validate(snapInfo), ErrorMatches, `cannot have plug and slot with the same name: "network"`)
+}
diff --git a/spread.yaml b/spread.yaml
index 556d320899..98b6e6866a 100644
--- a/spread.yaml
+++ b/spread.yaml
@@ -30,11 +30,12 @@ environment:
SNAPPY_USE_STAGING_STORE: "$(HOST: if [ $SPREAD_REMOTE_STORE = staging ]; then echo 1; else echo 0; fi)"
DELTA_REF: 2.17
DELTA_PREFIX: snapd-$DELTA_REF/
- SNAPD_PPA_VERSION: "$(HOST: echo $SPREAD_SNAPD_PPA_VERSION)"
+ SNAPD_PUBLISHED_VERSION: "$(HOST: echo $SPREAD_SNAPD_PUBLISHED_VERSION)"
HTTP_PROXY: "$(HOST: echo $HTTP_PROXY)"
HTTPS_PROXY: "$(HOST: echo $HTTPS_PROXY)"
NO_PROXY: "127.0.0.1"
NEW_CORE_CHANNEL: "$(HOST: echo $SPREAD_NEW_CORE_CHANNEL)"
+ SRU_VALIDATION: "$(HOST: echo ${SPREAD_SRU_VALIDATION:-0})"
backends:
linode:
@@ -184,8 +185,14 @@ prepare-each: |
dmesg -c > /dev/null
debug-each: |
+ echo '# journal messages for snapd'
journalctl -u snapd
+ echo '# apparmor denials '
dmesg | grep DENIED || true
+ echo '# seccomp denials (kills) '
+ dmesg | grep 1326 || true
+ echo '# snap interfaces'
+ snap interfaces || true
rename:
# Move content into a directory, so that deltas computed by repack benefit
diff --git a/store/store.go b/store/store.go
index ecccf8f266..8b3b2ee1db 100644
--- a/store/store.go
+++ b/store/store.go
@@ -1266,7 +1266,7 @@ type HashError struct {
}
func (e HashError) Error() string {
- return fmt.Sprintf("sha3-384 mismatch after patching %q: got %s but expected %s", e.name, e.sha3_384, e.targetSha3_384)
+ return fmt.Sprintf("sha3-384 mismatch for %q: got %s but expected %s", e.name, e.sha3_384, e.targetSha3_384)
}
// Download downloads the snap addressed by download info and returns its
diff --git a/store/store_test.go b/store/store_test.go
index aff445f882..cd0ac704f3 100644
--- a/store/store_test.go
+++ b/store/store_test.go
@@ -221,6 +221,10 @@ func (t *remoteRepoTestSuite) SetUpTest(c *C) {
dirs.SetRootDir(c.MkDir())
c.Assert(os.MkdirAll(dirs.SnapMountDir, 0755), IsNil)
+ oldSnapdDebug := os.Getenv("SNAPD_DEBUG")
+ os.Setenv("SNAPD_DEBUG", "1")
+ t.AddCleanup(func() { os.Setenv("SNAPD_DEBUG", oldSnapdDebug) })
+
t.logbuf = bytes.NewBuffer(nil)
l, err := logger.NewConsoleLog(t.logbuf, logger.DefaultFlags)
c.Assert(err, IsNil)
@@ -326,6 +330,50 @@ func (t *remoteRepoTestSuite) TestDownloadRangeRequest(c *C) {
c.Assert(string(content), Equals, partialContentStr+"was downloaded")
}
+func (t *remoteRepoTestSuite) TestDownloadEOFHandlesResumeHashCorrectly(c *C) {
+ n := 0
+ var mockServer *httptest.Server
+
+ // our mock download content
+ buf := make([]byte, 50000)
+ for i := range buf {
+ buf[i] = 'x'
+ }
+ h := crypto.SHA3_384.New()
+ io.Copy(h, bytes.NewBuffer(buf))
+
+ // raise an EOF shortly before the end
+ mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ n++
+ if n < 2 {
+ w.Header().Add("Content-Length", fmt.Sprintf("%d", len(buf)))
+ w.Write(buf[0 : len(buf)-5])
+ mockServer.CloseClientConnections()
+ return
+ }
+ w.Write(buf[len(buf)-5:])
+ }))
+
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ snap := &snap.Info{}
+ snap.RealName = "foo"
+ snap.AnonDownloadURL = mockServer.URL
+ snap.DownloadURL = "AUTH-URL"
+ snap.Sha3_384 = fmt.Sprintf("%x", h.Sum(nil))
+
+ targetFn := filepath.Join(c.MkDir(), "foo_1.0_all.snap")
+ err := t.store.Download(context.TODO(), "foo", targetFn, &snap.DownloadInfo, nil, nil)
+ c.Assert(err, IsNil)
+
+ content, err := ioutil.ReadFile(targetFn)
+ c.Assert(err, IsNil)
+ c.Assert(content, DeepEquals, buf)
+
+ c.Assert(t.logbuf.String(), Matches, "(?s).*Retrying .* attempt 2, .*")
+}
+
func (t *remoteRepoTestSuite) TestDownloadRangeRequestRetryOnHashError(c *C) {
partialContentStr := "partial content "
@@ -381,7 +429,7 @@ func (t *remoteRepoTestSuite) TestDownloadRangeRequestFailOnHashError(c *C) {
err = t.store.Download(context.TODO(), "foo", targetFn, &snap.DownloadInfo, nil, nil)
c.Assert(err, NotNil)
- c.Assert(err, ErrorMatches, `sha3-384 mismatch after patching "foo": got 1234 but expected 5678`)
+ c.Assert(err, ErrorMatches, `sha3-384 mismatch for "foo": got 1234 but expected 5678`)
c.Assert(n, Equals, 2)
}
@@ -2201,7 +2249,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreFindFails(c *C) {
c.Assert(mockServer, NotNil)
defer mockServer.Close()
- var err error
searchURI, err := url.Parse(mockServer.URL)
c.Assert(err, IsNil)
cfg := Config{
@@ -2224,7 +2271,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreFindBadContentType(c *C) {
c.Assert(mockServer, NotNil)
defer mockServer.Close()
- var err error
searchURI, err := url.Parse(mockServer.URL)
c.Assert(err, IsNil)
cfg := Config{
@@ -2249,7 +2295,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreFindBadBody(c *C) {
c.Assert(mockServer, NotNil)
defer mockServer.Close()
- var err error
searchURI, err := url.Parse(mockServer.URL)
c.Assert(err, IsNil)
cfg := Config{
@@ -2273,7 +2318,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreFind500(c *C) {
c.Assert(mockServer, NotNil)
defer mockServer.Close()
- var err error
searchURI, err := url.Parse(mockServer.URL)
c.Assert(err, IsNil)
cfg := Config{
@@ -2303,7 +2347,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreFind500once(c *C) {
c.Assert(mockServer, NotNil)
defer mockServer.Close()
- var err error
searchURI, err := url.Parse(mockServer.URL)
c.Assert(err, IsNil)
cfg := Config{
@@ -2348,7 +2391,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreFindAuthFailed(c *C) {
c.Assert(mockPurchasesServer, NotNil)
defer mockPurchasesServer.Close()
- var err error
searchURI, err := url.Parse(mockServer.URL)
c.Assert(err, IsNil)
ordersURI, err := url.Parse(mockPurchasesServer.URL + ordersPath)
@@ -2459,7 +2501,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryListRefresh(c *C) {
c.Assert(mockServer, NotNil)
defer mockServer.Close()
- var err error
bulkURI, err := url.Parse(mockServer.URL + "/updates/")
c.Assert(err, IsNil)
cfg := Config{
@@ -2510,7 +2551,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryListRefreshRetryOnEOF(c *
c.Assert(mockServer, NotNil)
defer mockServer.Close()
- var err error
bulkURI, err := url.Parse(mockServer.URL + "/updates/")
c.Assert(err, IsNil)
cfg := Config{
@@ -2520,18 +2560,71 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryListRefreshRetryOnEOF(c *
repo := New(&cfg, authContext)
c.Assert(repo, NotNil)
- results, err := repo.ListRefresh([]*RefreshCandidate{
- {
+ results, err := repo.ListRefresh([]*RefreshCandidate{{
+ SnapID: helloWorldSnapID,
+ Channel: "stable",
+ Revision: snap.R(1),
+ Epoch: "0",
+ }}, nil)
+ c.Assert(err, IsNil)
+ c.Assert(n, Equals, 4)
+ c.Assert(results, HasLen, 1)
+ c.Assert(results[0].Name(), Equals, "hello-world")
+}
+
+func (t *remoteRepoTestSuite) TestUbuntuStoreUnexpectedEOFhandling(c *C) {
+ permanentlyBrokenSrvCalls := 0
+ somewhatBrokenSrvCalls := 0
+
+ mockPermanentlyBrokenServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ permanentlyBrokenSrvCalls++
+ w.Header().Add("Content-Length", "1000")
+ }))
+ mockSomewhatBrokenServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ somewhatBrokenSrvCalls++
+ if somewhatBrokenSrvCalls > 3 {
+ io.WriteString(w, MockUpdatesJSON)
+ return
+ }
+ w.Header().Add("Content-Length", "1000")
+ }))
+
+ queryServer := func(mockServer *httptest.Server) error {
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ bulkURI, err := url.Parse(mockServer.URL + "/updates/")
+ c.Assert(err, IsNil)
+ cfg := Config{
+ BulkURI: bulkURI,
+ }
+ authContext := &testAuthContext{c: c, device: t.device}
+ repo := New(&cfg, authContext)
+ c.Assert(repo, NotNil)
+
+ _, err = repo.ListRefresh([]*RefreshCandidate{{
SnapID: helloWorldSnapID,
Channel: "stable",
Revision: snap.R(1),
Epoch: "0",
},
- }, nil)
+ }, nil)
+ return err
+ }
+
+ // Check that we really recognize unexpected EOF error by failing on all retries
+ err := queryServer(mockPermanentlyBrokenServer)
+ c.Assert(err, NotNil)
+ c.Assert(err, Equals, io.ErrUnexpectedEOF)
+ c.Assert(err, ErrorMatches, "unexpected EOF")
+ // check that we exhausted all retries (as defined by mocked retry strategy)
+ c.Assert(permanentlyBrokenSrvCalls, Equals, 5)
+
+ // Check that we retry on unexpected EOF and eventually succeed
+ err = queryServer(mockSomewhatBrokenServer)
c.Assert(err, IsNil)
- c.Assert(n, Equals, 4)
- c.Assert(results, HasLen, 1)
- c.Assert(results[0].Name(), Equals, "hello-world")
+ // check that we retried 4 times
+ c.Assert(somewhatBrokenSrvCalls, Equals, 4)
}
func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryListRefreshEOF(c *C) {
@@ -2556,14 +2649,12 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryListRefreshEOF(c *C) {
repo := New(&cfg, authContext)
c.Assert(repo, NotNil)
- _, err = repo.ListRefresh([]*RefreshCandidate{
- {
- SnapID: helloWorldSnapID,
- Channel: "stable",
- Revision: snap.R(1),
- Epoch: "0",
- },
- }, nil)
+ _, err = repo.ListRefresh([]*RefreshCandidate{{
+ SnapID: helloWorldSnapID,
+ Channel: "stable",
+ Revision: snap.R(1),
+ Epoch: "0",
+ }}, nil)
c.Assert(err, NotNil)
c.Assert(err, ErrorMatches, `^Post http://127.0.0.1:.*?/updates/: EOF$`)
c.Assert(n, Equals, 5)
@@ -2591,14 +2682,12 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryListRefreshUnauthorised(c
repo := New(&cfg, authContext)
c.Assert(repo, NotNil)
- _, err = repo.ListRefresh([]*RefreshCandidate{
- {
- SnapID: helloWorldSnapID,
- Channel: "stable",
- Revision: snap.R(24),
- Epoch: "0",
- },
- }, nil)
+ _, err = repo.ListRefresh([]*RefreshCandidate{{
+ SnapID: helloWorldSnapID,
+ Channel: "stable",
+ Revision: snap.R(24),
+ Epoch: "0",
+ }}, nil)
c.Assert(n, Equals, 1)
c.Assert(err, ErrorMatches, `cannot query the store for updates: got unexpected HTTP status code 401 via POST to "http://.*?/updates/"`)
}
@@ -2611,7 +2700,6 @@ func (t *remoteRepoTestSuite) TestListRefresh500(c *C) {
c.Assert(mockServer, NotNil)
defer mockServer.Close()
- var err error
bulkURI, err := url.Parse(mockServer.URL + "/updates/")
c.Assert(err, IsNil)
cfg := Config{
@@ -2621,14 +2709,12 @@ func (t *remoteRepoTestSuite) TestListRefresh500(c *C) {
repo := New(&cfg, authContext)
c.Assert(repo, NotNil)
- _, err = repo.ListRefresh([]*RefreshCandidate{
- {
- SnapID: helloWorldSnapID,
- Channel: "stable",
- Revision: snap.R(24),
- Epoch: "0",
- },
- }, nil)
+ _, err = repo.ListRefresh([]*RefreshCandidate{{
+ SnapID: helloWorldSnapID,
+ Channel: "stable",
+ Revision: snap.R(24),
+ Epoch: "0",
+ }}, nil)
c.Assert(err, ErrorMatches, `cannot query the store for updates: got unexpected HTTP status code 500 via POST to "http://.*?/updates/"`)
c.Assert(n, Equals, 5)
}
@@ -2643,7 +2729,6 @@ func (t *remoteRepoTestSuite) TestListRefresh500DurationExceeded(c *C) {
c.Assert(mockServer, NotNil)
defer mockServer.Close()
- var err error
bulkURI, err := url.Parse(mockServer.URL + "/updates/")
c.Assert(err, IsNil)
cfg := Config{
@@ -2653,14 +2738,12 @@ func (t *remoteRepoTestSuite) TestListRefresh500DurationExceeded(c *C) {
repo := New(&cfg, authContext)
c.Assert(repo, NotNil)
- _, err = repo.ListRefresh([]*RefreshCandidate{
- {
- SnapID: helloWorldSnapID,
- Channel: "stable",
- Revision: snap.R(24),
- Epoch: "0",
- },
- }, nil)
+ _, err = repo.ListRefresh([]*RefreshCandidate{{
+ SnapID: helloWorldSnapID,
+ Channel: "stable",
+ Revision: snap.R(24),
+ Epoch: "0",
+ }}, nil)
c.Assert(err, ErrorMatches, `cannot query the store for updates: got unexpected HTTP status code 500 via POST to "http://.*?/updates/"`)
c.Assert(n, Equals, 1)
}
@@ -2691,7 +2774,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryListRefreshSkipCurrent(c
c.Assert(mockServer, NotNil)
defer mockServer.Close()
- var err error
bulkURI, err := url.Parse(mockServer.URL + "/updates/")
c.Assert(err, IsNil)
cfg := Config{
@@ -2700,14 +2782,12 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryListRefreshSkipCurrent(c
repo := New(&cfg, nil)
c.Assert(repo, NotNil)
- results, err := repo.ListRefresh([]*RefreshCandidate{
- {
- SnapID: helloWorldSnapID,
- Channel: "stable",
- Revision: snap.R(26),
- Epoch: "0",
- },
- }, nil)
+ results, err := repo.ListRefresh([]*RefreshCandidate{{
+ SnapID: helloWorldSnapID,
+ Channel: "stable",
+ Revision: snap.R(26),
+ Epoch: "0",
+ }}, nil)
c.Assert(err, IsNil)
c.Assert(results, HasLen, 0)
}
@@ -2739,7 +2819,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryListRefreshSkipBlocked(c
c.Assert(mockServer, NotNil)
defer mockServer.Close()
- var err error
bulkURI, err := url.Parse(mockServer.URL + "/updates/")
c.Assert(err, IsNil)
cfg := Config{
@@ -2748,15 +2827,13 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryListRefreshSkipBlocked(c
repo := New(&cfg, nil)
c.Assert(repo, NotNil)
- results, err := repo.ListRefresh([]*RefreshCandidate{
- {
- SnapID: helloWorldSnapID,
- Channel: "stable",
- Revision: snap.R(25),
- Epoch: "0",
- Block: []snap.Revision{snap.R(26)},
- },
- }, nil)
+ results, err := repo.ListRefresh([]*RefreshCandidate{{
+ SnapID: helloWorldSnapID,
+ Channel: "stable",
+ Revision: snap.R(25),
+ Epoch: "0",
+ Block: []snap.Revision{snap.R(26)},
+ }}, nil)
c.Assert(err, IsNil)
c.Assert(results, HasLen, 0)
}
@@ -2847,7 +2924,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryDefaultsDeltasOnClassicOn
}))
defer mockServer.Close()
- var err error
bulkURI, err := url.Parse(mockServer.URL + "/updates/")
c.Assert(err, IsNil)
cfg := Config{
@@ -2856,14 +2932,12 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryDefaultsDeltasOnClassicOn
repo := New(&cfg, nil)
c.Assert(repo, NotNil)
- repo.ListRefresh([]*RefreshCandidate{
- {
- SnapID: helloWorldSnapID,
- Channel: "stable",
- Revision: snap.R(24),
- Epoch: "0",
- },
- }, nil)
+ repo.ListRefresh([]*RefreshCandidate{{
+ SnapID: helloWorldSnapID,
+ Channel: "stable",
+ Revision: snap.R(24),
+ Epoch: "0",
+ }}, nil)
}
}
@@ -2900,7 +2974,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryListRefreshWithDeltas(c *
c.Assert(mockServer, NotNil)
defer mockServer.Close()
- var err error
bulkURI, err := url.Parse(mockServer.URL + "/updates/")
c.Assert(err, IsNil)
cfg := Config{
@@ -2909,14 +2982,12 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryListRefreshWithDeltas(c *
repo := New(&cfg, nil)
c.Assert(repo, NotNil)
- results, err := repo.ListRefresh([]*RefreshCandidate{
- {
- SnapID: helloWorldSnapID,
- Channel: "stable",
- Revision: snap.R(24),
- Epoch: "0",
- },
- }, nil)
+ results, err := repo.ListRefresh([]*RefreshCandidate{{
+ SnapID: helloWorldSnapID,
+ Channel: "stable",
+ Revision: snap.R(24),
+ Epoch: "0",
+ }}, nil)
c.Assert(err, IsNil)
c.Assert(results, HasLen, 1)
c.Assert(results[0].Deltas, HasLen, 2)
@@ -2974,7 +3045,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryListRefreshWithoutDeltas(
c.Assert(mockServer, NotNil)
defer mockServer.Close()
- var err error
bulkURI, err := url.Parse(mockServer.URL + "/updates/")
c.Assert(err, IsNil)
cfg := Config{
@@ -2983,14 +3053,12 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryListRefreshWithoutDeltas(
repo := New(&cfg, nil)
c.Assert(repo, NotNil)
- results, err := repo.ListRefresh([]*RefreshCandidate{
- {
- SnapID: helloWorldSnapID,
- Channel: "stable",
- Revision: snap.R(24),
- Epoch: "0",
- },
- }, nil)
+ results, err := repo.ListRefresh([]*RefreshCandidate{{
+ SnapID: helloWorldSnapID,
+ Channel: "stable",
+ Revision: snap.R(24),
+ Epoch: "0",
+ }}, nil)
c.Assert(err, IsNil)
c.Assert(results, HasLen, 1)
c.Assert(results[0].Deltas, HasLen, 0)
@@ -3021,7 +3089,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryUpdateNotSendLocalRevs(c
c.Assert(mockServer, NotNil)
defer mockServer.Close()
- var err error
bulkURI, err := url.Parse(mockServer.URL + "/updates/")
c.Assert(err, IsNil)
cfg := Config{
@@ -3030,14 +3097,12 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryUpdateNotSendLocalRevs(c
repo := New(&cfg, nil)
c.Assert(repo, NotNil)
- _, err = repo.ListRefresh([]*RefreshCandidate{
- {
- SnapID: helloWorldSnapID,
- Channel: "stable",
- Revision: snap.R(-2),
- Epoch: "0",
- },
- }, nil)
+ _, err = repo.ListRefresh([]*RefreshCandidate{{
+ SnapID: helloWorldSnapID,
+ Channel: "stable",
+ Revision: snap.R(-2),
+ Epoch: "0",
+ }}, nil)
c.Assert(err, IsNil)
}
@@ -3154,7 +3219,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryAssertion(c *C) {
c.Assert(mockServer, NotNil)
defer mockServer.Close()
- var err error
assertionsURI, err := url.Parse(mockServer.URL + "/assertions/")
c.Assert(err, IsNil)
@@ -3182,7 +3246,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryAssertionNotFound(c *C) {
c.Assert(mockServer, NotNil)
defer mockServer.Close()
- var err error
assertionsURI, err := url.Parse(mockServer.URL + "/assertions/")
c.Assert(err, IsNil)
cfg := Config{
@@ -3209,7 +3272,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryAssertion500(c *C) {
c.Assert(mockServer, NotNil)
defer mockServer.Close()
- var err error
assertionsURI, err := url.Parse(mockServer.URL + "/assertions/")
c.Assert(err, IsNil)
cfg := Config{
@@ -3279,7 +3341,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreDecorateOrders(c *C) {
c.Assert(mockPurchasesServer, NotNil)
defer mockPurchasesServer.Close()
- var err error
ordersURI, err := url.Parse(mockPurchasesServer.URL + ordersPath)
c.Assert(err, IsNil)
@@ -3328,7 +3389,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreDecorateOrdersFailedAccess(c *C) {
c.Assert(mockPurchasesServer, NotNil)
defer mockPurchasesServer.Close()
- var err error
ordersURI, err := url.Parse(mockPurchasesServer.URL + ordersPath)
c.Assert(err, IsNil)
cfg := Config{
@@ -3446,7 +3506,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreDecorateOrdersSingle(c *C) {
c.Assert(mockPurchasesServer, NotNil)
defer mockPurchasesServer.Close()
- var err error
ordersURI, err := url.Parse(mockPurchasesServer.URL + ordersPath)
c.Assert(err, IsNil)
@@ -3496,7 +3555,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreDecorateOrdersSingleNotFound(c *C)
c.Assert(mockPurchasesServer, NotNil)
defer mockPurchasesServer.Close()
- var err error
ordersURI, err := url.Parse(mockPurchasesServer.URL + ordersPath)
c.Assert(err, IsNil)
@@ -3531,7 +3589,6 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreDecorateOrdersTokenExpired(c *C) {
c.Assert(mockPurchasesServer, NotNil)
defer mockPurchasesServer.Close()
- var err error
ordersURI, err := url.Parse(mockPurchasesServer.URL + ordersPath)
c.Assert(err, IsNil)
diff --git a/tests/lib/prepare-project.sh b/tests/lib/prepare-project.sh
index 2011e3801b..8f0c350430 100644
--- a/tests/lib/prepare-project.sh
+++ b/tests/lib/prepare-project.sh
@@ -32,21 +32,26 @@ build_deb(){
cp ../*.deb $GOPATH
}
-download_from_ppa(){
- local ppa_version="$1"
+download_from_published(){
+ local published_version="$1"
- # we need to install snap-confine and ubunntu-core-launcher for versions < 2.23
+ curl -s -o pkg_page "https://launchpad.net/ubuntu/+source/snapd/$published_version"
+
+ arch=$(dpkg --print-architecture)
+ build_id=$(sed -n 's|<a href="/ubuntu/+source/snapd/'"$published_version"'/+build/\(.*\)">'"$arch"'</a>|\1|p' pkg_page | sed -e 's/^[[:space:]]*//')
+
+ # we need to download snap-confine and ubuntu-core-launcher for versions < 2.23
for pkg in snapd snap-confine ubuntu-core-launcher; do
- file="${pkg}_${ppa_version}_$(dpkg --print-architecture).deb"
- curl -L -o "$GOPATH/$file" "https://launchpad.net/~snappy-dev/+archive/ubuntu/snapd-${ppa_version}/+files/$file"
+ file="${pkg}_${published_version}_${arch}.deb"
+ curl -L -o "$GOPATH/$file" "https://launchpad.net/ubuntu/+source/snapd/${published_version}/+build/${build_id}/+files/${file}"
done
}
-install_dependencies_from_ppa(){
- local ppa_version="$1"
+install_dependencies_from_published(){
+ local published_version="$1"
for dep in snap-confine ubuntu-core-launcher; do
- dpkg -i "${GOPATH}/${dep}_${ppa_version}_$(dpkg --print-architecture).deb"
+ dpkg -i "${GOPATH}/${dep}_${published_version}_$(dpkg --print-architecture).deb"
done
}
@@ -129,12 +134,11 @@ if [ "$(which govendor)" = "" ]; then
fi
quiet govendor sync
-if [ -z "$SNAPD_PPA_VERSION" ]; then
+if [ -z "$SNAPD_PUBLISHED_VERSION" ]; then
build_deb
else
- download_from_ppa "$SNAPD_PPA_VERSION"
-
- install_dependencies_from_ppa "$SNAPD_PPA_VERSION"
+ download_from_published "$SNAPD_PUBLISHED_VERSION"
+ install_dependencies_from_published "$SNAPD_PUBLISHED_VERSION"
fi
# Build snapbuild.
diff --git a/tests/lib/prepare.sh b/tests/lib/prepare.sh
index cc73322214..108a4854b7 100755
--- a/tests/lib/prepare.sh
+++ b/tests/lib/prepare.sh
@@ -25,16 +25,17 @@ update_core_snap_for_classic_reexec() {
# Now unpack the core, inject the new snap-exec/snapctl into it
unsquashfs "$snap"
- cp /usr/lib/snapd/snap-exec squashfs-root/usr/lib/snapd/
- cp /usr/bin/snapctl squashfs-root/usr/bin/
+ cp -a /usr/lib/snapd/snap-exec squashfs-root/usr/lib/snapd/
+ cp -a /usr/bin/snapctl squashfs-root/usr/bin/
# also inject new version of snap-confine and snap-scard-ns
- cp /usr/lib/snapd/snap-discard-ns squashfs-root/usr/lib/snapd/
- cp /usr/lib/snapd/snap-confine squashfs-root/usr/lib/snapd/
+ cp -a /usr/lib/snapd/snap-discard-ns squashfs-root/usr/lib/snapd/
+ cp -a /usr/lib/snapd/snap-confine squashfs-root/usr/lib/snapd/
+ cp -a /etc/apparmor.d/usr.lib.snapd.snap-confine* squashfs-root/etc/apparmor.d/usr.lib.snapd.snap-confine.real
# also add snap/snapd because we re-exec by default and want to test
# this version
- cp /usr/lib/snapd/snapd squashfs-root/usr/lib/snapd/
- cp /usr/lib/snapd/info squashfs-root/usr/lib/snapd/
- cp /usr/bin/snap squashfs-root/usr/bin/snap
+ cp -a /usr/lib/snapd/snapd squashfs-root/usr/lib/snapd/
+ cp -a /usr/lib/snapd/info squashfs-root/usr/lib/snapd/
+ cp -a /usr/bin/snap squashfs-root/usr/bin/snap
# repack, cheating to speed things up (4sec vs 1.5min)
mv "$snap" "${snap}.orig"
mksnap_fast "squashfs-root" "$snap"
@@ -52,6 +53,16 @@ update_core_snap_for_classic_reexec() {
done
}
+upgrade_snapd_from_proposed(){
+ apt install -y snapd
+ cp /etc/apt/sources.list sources.list.back
+ echo "deb http://archive.ubuntu.com/ubuntu/ $(lsb_release -c -s)-proposed restricted main multiverse universe" | tee /etc/apt/sources.list -a
+ apt update
+ apt install -y --only-upgrade snapd
+ mv sources.list.back /etc/apt/sources.list
+ apt update
+}
+
prepare_each_classic() {
mkdir -p /etc/systemd/system/snapd.service.d
if [ -z "${SNAP_REEXEC:-}" ]; then
@@ -69,7 +80,11 @@ EOF
}
prepare_classic() {
- apt_install_local ${GOPATH}/snapd_*.deb
+ if [ "$SRU_VALIDATION" = "1" ]; then
+ upgrade_snapd_from_proposed
+ else
+ apt_install_local ${GOPATH}/snapd_*.deb
+ fi
if snap --version |MATCH unknown; then
echo "Package build incorrect, 'snap --version' mentions 'unknown'"
snap --version
diff --git a/tests/lib/snaps/test-snapd-kernel-module-control-consumer/snapcraft.yaml b/tests/lib/snaps/test-snapd-kernel-module-control-consumer/snapcraft.yaml
new file mode 100644
index 0000000000..b8a138caeb
--- /dev/null
+++ b/tests/lib/snaps/test-snapd-kernel-module-control-consumer/snapcraft.yaml
@@ -0,0 +1,24 @@
+name: test-snapd-kernel-module-control-consumer
+version: 1.0
+summary: Basic kernel-module-control consumer snap
+description: A basic snap declaring a plug on kernel-module-control
+
+apps:
+ lsmod:
+ command: bin/lsmod
+ plugs: [kernel-module-control]
+ insmod:
+ command: sbin/insmod
+ plugs: [kernel-module-control]
+ rmmod:
+ command: sbin/rmmod
+ plugs: [kernel-module-control]
+parts:
+ build:
+ plugin: autotools
+ build-packages: [docbook-utils]
+ source: https://github.com/vadmium/module-init-tools
+ source-type: git
+ snap:
+ - bin/
+ - sbin/
diff --git a/tests/main/alias/task.yaml b/tests/main/alias/task.yaml
index 85ad118230..a106aa0266 100644
--- a/tests/main/alias/task.yaml
+++ b/tests/main/alias/task.yaml
@@ -1,10 +1,16 @@
summary: Check snap alias and snap unalias
prepare: |
+ echo "New semantics are WIP, skipping for now"
+ exit 0
+
. $TESTSLIB/snaps.sh
install_local aliases
execute: |
+ echo "New semantics are WIP, skipping for now"
+ exit 0
+
echo "Sanity check"
aliases.cmd1|MATCH "ok command 1"
aliases.cmd2|MATCH "ok command 2"
diff --git a/tests/main/change-errors/task.yaml b/tests/main/change-errors/task.yaml
index 7832ec397a..e6b86c0590 100644
--- a/tests/main/change-errors/task.yaml
+++ b/tests/main/change-errors/task.yaml
@@ -1,7 +1,10 @@
-summary: Checks for cli errors of the change command.
+summary: Checks for cli errors of the tasks / change command.
execute: |
- echo "When an invalid ID is given to the change command it shows an error"
+ echo "When an invalid ID is given to the tasks command it shows an error"
+ if snap tasks 10000000; then
+ echo "Expected error when trying change on invalid ID" && exit 1
+ fi
if snap change 10000000; then
echo "Expected error when trying change on invalid ID" && exit 1
fi
diff --git a/tests/main/classic-ubuntu-core-transition-auth/task.yaml b/tests/main/classic-ubuntu-core-transition-auth/task.yaml
index a1a3625975..509e1d66a4 100644
--- a/tests/main/classic-ubuntu-core-transition-auth/task.yaml
+++ b/tests/main/classic-ubuntu-core-transition-auth/task.yaml
@@ -10,14 +10,18 @@ execute: |
. "$TESTSLIB/apt.sh"
echo "Ensure core is gone and we have ubuntu-core instead"
dpkg --purge snapd
- apt_install_local ${GOPATH}/snapd_*.deb
+ if [ "$SRU_VALIDATION" = "1" ]; then
+ upgrade_snapd_from_proposed
+ else
+ apt_install_local ${GOPATH}/snapd_*.deb
+ fi
snap install --${CORE_CHANNEL} ubuntu-core
mkdir -p /root/.snap/
echo '{}' > /root/.snap/auth.json
mkdir -p /home/test/.snap/
echo '{}' > /home/test/.snap/auth.json
-
+
echo "Ensure transition is triggered"
snap debug ensure-state-soon
@@ -30,3 +34,6 @@ execute: |
echo "ubuntu-core still installed, transition failed"
exit 1
fi
+
+ echo "Ensure interfaces are connected"
+ snap interfaces | MATCH ":core-support.*core:core-support-plug"
diff --git a/tests/main/classic-ubuntu-core-transition-two-cores/task.yaml b/tests/main/classic-ubuntu-core-transition-two-cores/task.yaml
index dadb13d6a5..8a5a17e761 100644
--- a/tests/main/classic-ubuntu-core-transition-two-cores/task.yaml
+++ b/tests/main/classic-ubuntu-core-transition-two-cores/task.yaml
@@ -45,3 +45,6 @@ execute: |
exit 1
fi
snap interfaces |MATCH ":network.*test-snapd-python-webserver"
+
+ echo "Ensure interfaces are connected"
+ snap interfaces | MATCH ":core-support.*core:core-support-plug"
diff --git a/tests/main/classic-ubuntu-core-transition/task.yaml b/tests/main/classic-ubuntu-core-transition/task.yaml
index 92da7b3b09..3e2bbc3ef1 100644
--- a/tests/main/classic-ubuntu-core-transition/task.yaml
+++ b/tests/main/classic-ubuntu-core-transition/task.yaml
@@ -30,7 +30,11 @@ execute: |
. "$TESTSLIB/apt.sh"
echo "Ensure core is gone and we have ubuntu-core instead"
dpkg --purge snapd
- apt_install_local ${GOPATH}/snapd_*.deb
+ if [ "$SRU_VALIDATION" = "1" ]; then
+ upgrade_snapd_from_proposed
+ else
+ apt_install_local ${GOPATH}/snapd_*.deb
+ fi
# modify daemon state to set ubuntu-core-transition-last-retry-time to the
# current time to prevent the ubuntu-core transition before the test snap is
@@ -43,7 +47,8 @@ execute: |
snap install --${CORE_CHANNEL} ubuntu-core
snap install test-snapd-python-webserver
- snap interfaces |MATCH ":network.*test-snapd-python-webserver"
+ snap interfaces | MATCH ":network +test-snapd-python-webserver"
+ snap interfaces | MATCH ":network-bind +test-snapd-python-webserver"
echo "Ensure the webserver is working"
wait_for_service snap.test-snapd-python-webserver.test-snapd-python-webserver
@@ -69,7 +74,8 @@ execute: |
echo "ubuntu-core still installed, transition failed"
exit 1
fi
- snap interfaces |MATCH ":network.*test-snapd-python-webserver"
+ snap interfaces | MATCH ":network +test-snapd-python-webserver"
+ snap interfaces | MATCH ":network-bind +test-snapd-python-webserver"
echo "Ensure the webserver is still working"
wait_for_service snap.test-snapd-python-webserver.test-snapd-python-webserver
curl http://localhost | MATCH "XKCD rocks"
@@ -78,3 +84,14 @@ execute: |
wait_for_service snap.test-snapd-python-webserver.test-snapd-python-webserver
echo "Ensure the webserver is working after a snap restart"
curl http://localhost | MATCH "XKCD rocks"
+
+ echo "Ensure interfaces are connected"
+ snap interfaces | MATCH ":core-support.*core:core-support-plug"
+
+ echo "Ensure snap set core works"
+ snap set core system.power-key-action=ignore
+ if [ "$(snap get core system.power-key-action)" != "ignore" ]; then
+ echo "snap get did not return the expected result: "
+ snap get core system.power-key-action
+ exit 1
+ fi
diff --git a/tests/main/interfaces-kernel-module-control/task.yaml b/tests/main/interfaces-kernel-module-control/task.yaml
new file mode 100644
index 0000000000..f07e7202c8
--- /dev/null
+++ b/tests/main/interfaces-kernel-module-control/task.yaml
@@ -0,0 +1,96 @@
+summary: Ensure that the kernel-module-control interface works.
+
+details: |
+ The kernel-module-control interface allows insertion, removal and querying
+ of modules.
+
+ A snap which defines a kernel-module-control plug must be shown in the
+ interfaces list. The plug must not be autoconnected on install and, as
+ usual, must be able to be reconnected.
+
+ A snap declaring a plug on this interface must be able to list the modules
+ loaded, insert and remove a module. For the test we use the binfmt_misc module.
+
+prepare: |
+ echo "Given a snap declaring a plug on the kernel-module-control interface is installed"
+ snap install --edge test-snapd-kernel-module-control-consumer
+
+restore: |
+ rm -f list.error remove.error
+ if lsmod | MATCH binfmt_misc && ! -f module_present; then
+ rmmod binfmt_misc
+ elif [ -f module_present ]; then
+ insmod /lib/modules/$(uname -r)/kernel/fs/binfmt_misc.ko
+ fi
+ if [ -f package_present ]; then
+ apt install -y binfmt-support
+ fi
+ rm -f module_present package_present
+
+debug: |
+ lsmod
+
+execute: |
+ CONNECTED_PATTERN=":kernel-module-control +test-snapd-kernel-module-control-consumer"
+ DISCONNECTED_PATTERN="\- +test-snapd-kernel-module-control-consumer:kernel-module-control"
+
+ echo "The plug is disconnected by default"
+ snap interfaces | MATCH "$DISCONNECTED_PATTERN"
+
+ echo "==================================="
+
+ echo "When the plug is connected"
+ snap connect test-snapd-kernel-module-control-consumer:kernel-module-control
+ snap interfaces | MATCH "$CONNECTED_PATTERN"
+
+ echo "Then the snap is able to list the existing modules"
+ [ $(su -l -c "test-snapd-kernel-module-control-consumer.lsmod" test | wc -l) -gt 2 ]
+
+ echo "And the snap is able to insert a module"
+ if lsmod | MATCH binfmt_misc; then
+ touch module_present
+ if ! rmmod binfmt_misc; then
+ # the module is being used
+ if apt list --installed binfmt-support | MATCH binfmt-support; then
+ touch package_present
+ apt remove -y binfmt-support
+ rmmod binfmt_misc
+ fi
+ fi
+ fi
+ lsmod | MATCH -v binfmt_misc
+ test-snapd-kernel-module-control-consumer.insmod /lib/modules/$(uname -r)/kernel/fs/binfmt_misc.ko
+
+ echo "And the snap is able to remove a module"
+ test-snapd-kernel-module-control-consumer.rmmod binfmt_misc
+ lsmod | MATCH -v binfmt_misc
+
+ echo "==================================="
+
+ echo "When the plug is disconnected"
+ snap disconnect test-snapd-kernel-module-control-consumer:kernel-module-control
+ snap interfaces | MATCH "$DISCONNECTED_PATTERN"
+
+ echo "Then the snap is not able to list modules"
+ if su -l -c "test-snapd-kernel-module-control-consumer.lsmod 2>${PWD}/list.error" test; then
+ echo "Expected permission error listing modules with disconnected plug"
+ exit 1
+ fi
+ cat list.error | MATCH "Permission denied"
+
+ echo "And the snap is not able to insert a module"
+ if test-snapd-kernel-module-control-consumer.insmod /lib/modules/$(uname -r)/kernel/fs/binfmt_misc.ko; then
+ echo "Expected permission error inserting module with disconnected plug"
+ exit 1
+ fi
+
+ echo "And the snap is not able to remove a module"
+ # first we need to insert the module
+ lsmod | MATCH -v binfmt_misc
+ insmod /lib/modules/$(uname -r)/kernel/fs/binfmt_misc.ko
+ lsmod | MATCH binfmt_misc
+ if test-snapd-kernel-module-control-consumer.rmmod binfmt_misc 2>${PWD}/remove.error; then
+ echo "Expected permission error removing module with disconnected plug"
+ exit 1
+ fi
+ cat remove.error | MATCH "Permission denied"
diff --git a/tests/main/interfaces-network-bind/task.yaml b/tests/main/interfaces-network-bind/task.yaml
index c96a09fbae..653043e34b 100644
--- a/tests/main/interfaces-network-bind/task.yaml
+++ b/tests/main/interfaces-network-bind/task.yaml
@@ -35,7 +35,7 @@ restore: |
execute: |
CONNECTED_PATTERN="(?s)Slot +Plug\n\
.*?\n\
- :network-bind +core,$SNAP_NAME"
+ :network-bind +$SNAP_NAME"
DISCONNECTED_PATTERN="(?s)Slot +Plug\n\
.*?\n\
- +$SNAP_NAME:network-bind"
diff --git a/tests/main/refresh-all-undo/task.yaml b/tests/main/refresh-all-undo/task.yaml
index d8df39a9f4..b3e0bd8d5c 100644
--- a/tests/main/refresh-all-undo/task.yaml
+++ b/tests/main/refresh-all-undo/task.yaml
@@ -64,3 +64,6 @@ execute: |
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}\""
diff --git a/tests/main/security-setuid-root/task.yaml b/tests/main/security-setuid-root/task.yaml
index e823dfe8ba..57e71e78e7 100644
--- a/tests/main/security-setuid-root/task.yaml
+++ b/tests/main/security-setuid-root/task.yaml
@@ -7,6 +7,15 @@ details: |
prepare: |
. $TESTSLIB/snaps.sh
install_local test-snapd-tools
+ echo "Ensure the snap-confine profiles on core are not loaded"
+ for p in /etc/apparmor.d/snap.core.*.usr.lib.snapd.snap-confine; do
+ apparmor_parser -R "$p"
+ done
+restore: |
+ echo "Ensure the snap-confine profiles are restored"
+ for p in /etc/apparmor.d/snap.core.*.usr.lib.snapd.snap-confine; do
+ apparmor_parser -r $p
+ done
execute: |
# 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
diff --git a/tests/main/snap-confine-from-core/task.yaml b/tests/main/snap-confine-from-core/task.yaml
new file mode 100644
index 0000000000..d59b65c5ef
--- /dev/null
+++ b/tests/main/snap-confine-from-core/task.yaml
@@ -0,0 +1,27 @@
+summary: Test that snap-confine is run from core on re-exec
+
+systems: [-ubuntu-core-16-*]
+
+prepare: |
+ echo "Installing test-snapd-tools"
+ snap install test-snapd-tools
+ echo "Breaking host snap-confine"
+ chmod 0755 /usr/lib/snapd/snap-confine
+
+restore: |
+ echo "Restoring host snap-confine"
+ chmod 4755 /usr/lib/snapd/snap-confine
+
+execute: |
+ if [ "${SNAP_REEXEC:-}" = "0" ]; then
+ echo "skipping test when SNAP_REEXEC is disabled"
+ exit 0
+ fi
+
+ echo "Ensure we re-exec by default"
+ snap list
+ journalctl | MATCH "DEBUG: restarting into"
+
+ echo "Ensure snap-confine from the core snap is run"
+ # do not use "strace -f" for unknown reasons that hangs
+ test-snapd-tools.echo hello
diff --git a/tests/main/snap-update-ns/task.yaml b/tests/main/snap-update-ns/task.yaml
new file mode 100644
index 0000000000..6a0e8e1312
--- /dev/null
+++ b/tests/main/snap-update-ns/task.yaml
@@ -0,0 +1,28 @@
+summary: smoke test for snap-update-ns
+details: |
+ Snapd is growing a new executable, snap-update-ns, to modify an existing
+ mount namespace. This is further documented on the forum here
+ https://forum.snapcraft.io/t/fixing-live-propagation-of-mount-changes/23
+
+ While the implementation matures this test checks that we call setns
+ correctly (and it doesn't fail) enough that we reach the "not implemented"
+ message that is currently in snap-updates-ns
+environment:
+ # This is needed to enable the C part of snap-update-ns to work.
+ SNAPD_INTERNAL: x-switch-namespace=1,
+prepare: |
+ . $TESTSLIB/snaps.sh
+ install_local test-snapd-tools
+execute: |
+ # Ensure there is no (stale) mount namespace
+ rm -f /run/snapd/ns/test-snapd-tools.mnt
+ # Run snap-update-ns to see that we try to open the namespace file but fail (because it is not there yet).
+ /usr/lib/snapd/snap-update-ns test-snapd-tools | MATCH "cannot update snap namespace: cannot open mount namespace file: no such file or directory"
+ # Run a trivial command to build and preserve a mount namespace.
+ test-snapd-tools.cmd true
+ # Run snap-update-ns to see that setns part worked and we got to the 'not implemented' code.
+ /usr/lib/snapd/snap-update-ns test-snapd-tools | MATCH "cannot update snap namespace: not implemented"
+ # Run snap-discard-ns to discard the namespace we were working with.
+ /usr/lib/snapd/snap-discard-ns test-snapd-tools
+ # Run snap-update-ns to see that setns part fails because what we open is no longer a namespace.
+ /usr/lib/snapd/snap-update-ns test-snapd-tools | MATCH "cannot update snap namespace: cannot switch mount namespace: invalid argument"
diff --git a/tests/nightly/unity/task.yaml b/tests/nightly/unity/task.yaml
index a760793e99..51604543b1 100644
--- a/tests/nightly/unity/task.yaml
+++ b/tests/nightly/unity/task.yaml
@@ -6,12 +6,19 @@ environment:
systems: [ubuntu-16.04-64]
prepare: |
+ # the file /etc/init/tty1.conf is present in the default images, upstart
+ # (which is installed as a dependency of the required packages) ships it
+ # and doesn't install cleanly if that file is in place
+ mv /etc/init/tty1.conf /etc/init/tty1.conf.back
+
apt install -y --no-install-recommends x11-utils xvfb unity
disabled_restore: |
systemctl stop unity-app
apt autoremove -y --purge x11-utils xvfb unity
+ mv /etc/init/tty1.conf.back /etc/init/tty1.conf
+
execute: |
echo "Given a unity snap is installed"
snap install ubuntu-clock-app