diff options
| author | Michael Vogt <mvo@ubuntu.com> | 2017-05-17 07:51:34 +0200 |
|---|---|---|
| committer | Michael Vogt <mvo@ubuntu.com> | 2017-05-17 07:51:34 +0200 |
| commit | 72b6f480e3d396ead2888062b29b06c58b5541d7 (patch) | |
| tree | b43244bcb8691c30b8241964e8b494dc5d8a5824 | |
| parent | dac697f7ba99c67f143abc8a31ac7389b77acbf9 (diff) | |
| parent | 20a059ce553d257a357070928593188845e9e4e4 (diff) | |
Merge remote-tracking branch 'upstream/master' into release/2.26.2release/2.26.2
283 files changed, 2105 insertions, 1733 deletions
diff --git a/.travis.yml b/.travis.yml index 64bcf08361..00b451b058 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,4 +19,5 @@ install: script: - ./run-checks --static || travis_terminate 1 + - ./run-checks --unit || travis_terminate 1 - ./run-checks --spread @@ -1,6 +1,7 @@ [![Build Status][travis-image]][travis-url] [![Go Report Card][goreportcard-image]][goreportcard-url] - +[![codecov][codecov-image]][codecov-url] + ## Snaps Package any app for every Linux desktop, server, cloud or device. @@ -40,3 +41,6 @@ Get news and stay up to date on [Twitter](https://twitter.com/snapcraftio), [coveralls-image]: https://coveralls.io/repos/snapcore/snapd/badge.svg?branch=master&service=github [coveralls-url]: https://coveralls.io/github/snapcore/snapd?branch=master + +[codecov-url]: https://codecov.io/gh/snapcore/snapd +[codecov-image]: https://codecov.io/gh/snapcore/snapd/branch/master/graph/badge.svg \ No newline at end of file diff --git a/cmd/cmd.go b/cmd/cmd.go index c681b390b2..87228b77ce 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -26,6 +26,7 @@ import ( "regexp" "syscall" + "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/release" @@ -36,7 +37,7 @@ import ( // will attempt to re-exec itself from inside an ubuntu-core snap // present on the system. If not present in the environ it's assumed // to be set to 1 (do re-exec); that is: set it to 0 to disable. -const key = "SNAP_REEXEC" +const reExecKey = "SNAP_REEXEC" // newCore is the place to look for the core snap; everything in this // location will be new enough to re-exec into. @@ -46,37 +47,119 @@ const newCore = "/snap/core/current" // newer than minOldRevno will be ok to re-exec into. const oldCore = "/snap/ubuntu-core/current" -// ExecInCoreSnap makes sure you're executing the binary that ships in -// the core snap. -func ExecInCoreSnap() { +// distroSupportsReExec returns true if the distribution we are running on can use re-exec. +// +// This is true by default except for a "core/all" snap system where it makes +// no sense and in certain distributions that we don't want to enable re-exec +// yet because of missing validation or other issues. +func distroSupportsReExec() bool { if !release.OnClassic { - // you're already the real deal, natch - return + return false + } + switch release.ReleaseInfo.ID { + case "fedora", "centos", "rhel", "opensuse", "suse", "poky": + logger.Debugf("re-exec not supported on distro %q yet", release.ReleaseInfo.ID) + return false + } + return true +} + +// coreSupportsReExec returns true if the given core snap should be used as re-exec target. +// +// Ensure we do not use older version of snapd, look for info file and ignore +// version of core that do not yet have it. +func coreSupportsReExec(corePath string) bool { + fullInfo := filepath.Join(corePath, "/usr/lib/snapd/info") + if !osutil.FileExists(fullInfo) { + return false + } + content, err := ioutil.ReadFile(fullInfo) + if err != nil { + logger.Noticef("cannot read snapd info file %q: %s", fullInfo, err) + return false + } + ver := regexp.MustCompile("(?m)^VERSION=(.*)$").FindStringSubmatch(string(content)) + if len(ver) != 2 { + logger.Noticef("cannot find snapd version information in %q", content) + return false + } + // > 0 means our Version is bigger than the version of snapd in core + res, err := strutil.VersionCompare(Version, ver[1]) + if err != nil { + logger.Debugf("cannot version compare %q and %q: %s", Version, ver[1], res) + return false + } + if res > 0 { + logger.Debugf("core snap (at %q) is older (%q) than distribution package (%q)", corePath, ver[1], Version) + return false } + return true +} - // should we re-exec? no option in the environment means yes - if !osutil.GetenvBool(key, true) { +// InternalToolPath returns the path of the internal snapd tool. +// +// The return value is either the path of the tool in the current distribution +// or in the core snap (or the ubuntu-core snap). This handles spiritual +// "re-exec" where we run the tool from the core snap if the environment allows +// us to do so. +func InternalToolPath(tool string) string { + distroTool := filepath.Join(dirs.DistroLibExecDir, tool) + + // If we are asked not to re-execute use distribution packages. This is + // "spiritual" re-exec so use the same environment variable to decide. + if !osutil.GetenvBool(reExecKey, true) { logger.Debugf("re-exec disabled by user") - return + return distroTool } - // can we re-exec? some distributions will need extra work before re-exec really works. - switch release.ReleaseInfo.ID { - case "fedora", "centos", "rhel", "opensuse", "suse", "poky": - logger.Debugf("re-exec not supported on distro %q yet", release.ReleaseInfo.ID) + // If the distribution doesn't support re-exec or run-from-core then don't do it. + if !distroSupportsReExec() { + return distroTool + } + + // Is the tool we are after present in the core snap? + corePath := newCore + coreTool := filepath.Join(newCore, "/usr/lib/snapd", tool) + if !osutil.FileExists(coreTool) { + corePath = oldCore + coreTool = filepath.Join(oldCore, "/usr/lib/snapd", tool) + } + + // If the core snap doesn't support re-exec or run-from-core then don't do it. + if !coreSupportsReExec(corePath) { + return distroTool + } + + return coreTool +} + +// ExecInCoreSnap makes sure you're executing the binary that ships in +// the core snap. +func ExecInCoreSnap() { + // If we are asked not to re-execute use distribution packages. This is + // "spiritual" re-exec so use the same environment variable to decide. + if !osutil.GetenvBool(reExecKey, true) { + logger.Debugf("re-exec disabled by user") return } - // did we already re-exec? + // Did we already re-exec? if osutil.GetenvBool("SNAP_DID_REEXEC") { return } + // If the distribution doesn't support re-exec or run-from-core then don't do it. + if !distroSupportsReExec() { + return + } + + // Which executable are we? exe, err := os.Readlink("/proc/self/exe") if err != nil { return } + // Is this executable in the core snap too? corePath := newCore full := filepath.Join(newCore, exe) if !osutil.FileExists(full) { @@ -87,36 +170,12 @@ func ExecInCoreSnap() { } } - // ensure we do not re-exec into an older version of snapd, look - // for info file and ignore version of core that do not yet have - // it - fullInfo := filepath.Join(corePath, "/usr/lib/snapd/info") - if !osutil.FileExists(fullInfo) { - logger.Debugf("not restarting into %q (no version info): older than %q (%s)", full, exe, Version) - return - } - content, err := ioutil.ReadFile(fullInfo) - if err != nil { - logger.Noticef("cannot read info file %q: %s", fullInfo, err) - return - } - ver := regexp.MustCompile("(?m)^VERSION=(.*)$").FindStringSubmatch(string(content)) - if len(ver) != 2 { - logger.Noticef("cannot find version information in %q", content) - } - // > 0 means our Version is bigger than the version of snapd in core - res, err := strutil.VersionCompare(Version, ver[1]) - if err != nil { - logger.Debugf("cannot version compare %q and %q: %s", Version, ver[1], res) - return - } - if res > 0 { - logger.Debugf("not restarting into %q (%s): older than %q (%s)", full, ver, exe, Version) + // If the core snap doesn't support re-exec or run-from-core then don't do it. + if !coreSupportsReExec(corePath) { return } logger.Debugf("restarting into %q", full) - env := append(os.Environ(), "SNAP_DID_REEXEC=1") panic(syscall.Exec(full, os.Args, env)) } diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go new file mode 100644 index 0000000000..c39fc7d806 --- /dev/null +++ b/cmd/cmd_test.go @@ -0,0 +1,54 @@ +// -*- 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 cmd_test + +import ( + "testing" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/cmd" + "github.com/snapcore/snapd/release" +) + +func Test(t *testing.T) { TestingT(t) } + +type cmdSuite struct{} + +var _ = Suite(&cmdSuite{}) + +func (s *cmdSuite) TestDistroSupportsReExec(c *C) { + restore := release.MockOnClassic(true) + defer restore() + + // Some distributions don't support re-execution yet. + for _, id := range []string{"fedora", "centos", "rhel", "opensuse", "suse", "poky"} { + restore = release.MockReleaseInfo(&release.OS{ID: id}) + defer restore() + c.Assert(cmd.DistroSupportsReExec(), Equals, false, Commentf("ID: %q", id)) + } + + // While others do. + for _, id := range []string{"debian", "ubuntu"} { + restore = release.MockReleaseInfo(&release.OS{ID: id}) + defer restore() + c.Assert(cmd.DistroSupportsReExec(), Equals, true, Commentf("ID: %q", id)) + } +} diff --git a/cmd/export_test.go b/cmd/export_test.go new file mode 100644 index 0000000000..42d04d5f84 --- /dev/null +++ b/cmd/export_test.go @@ -0,0 +1,24 @@ +// -*- 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 cmd + +var ( + DistroSupportsReExec = distroSupportsReExec +) diff --git a/cmd/snap-confine/mount-support.c b/cmd/snap-confine/mount-support.c index 6bf38e1014..ee63c19dca 100644 --- a/cmd/snap-confine/mount-support.c +++ b/cmd/snap-confine/mount-support.c @@ -341,20 +341,28 @@ static void sc_bootstrap_mount_namespace(const struct sc_mount_config *config) sc_do_mount("none", dst, NULL, MS_REC | MS_SLAVE, NULL); } } - // Since we mounted /etc from the host filesystem to the scratch directory, - // we may need to put /etc/alternatives from the desired root filesystem - // (e.g. the core snap) back. This way the behavior of running snaps is not - // affected by the alternatives directory from the host, if one exists. - // - // https://bugs.launchpad.net/snap-confine/+bug/1580018 - const char *etc_alternatives = "/etc/alternatives"; - if (access(etc_alternatives, F_OK) == 0) { - sc_must_snprintf(src, sizeof src, "%s%s", config->rootfs_dir, - etc_alternatives); - sc_must_snprintf(dst, sizeof dst, "%s%s", scratch_dir, - etc_alternatives); - sc_do_mount(src, dst, NULL, MS_BIND, NULL); - sc_do_mount("none", dst, NULL, MS_SLAVE, NULL); + if (config->on_classic_distro) { + // Since we mounted /etc from the host filesystem to the scratch directory, + // we may need to put certain directories from the desired root filesystem + // (e.g. the core snap) back. This way the behavior of running snaps is not + // affected by the alternatives directory from the host, if one exists. + // + // Fixes the following bugs: + // - https://bugs.launchpad.net/snap-confine/+bug/1580018 + // - https://bugzilla.opensuse.org/show_bug.cgi?id=1028568 + const char *dirs_from_core[] = + { "/etc/alternatives", "/etc/ssl", NULL }; + for (const char **dirs = dirs_from_core; *dirs != NULL; dirs++) { + const char *dir = *dirs; + if (access(dir, F_OK) == 0) { + sc_must_snprintf(src, sizeof src, "%s%s", + config->rootfs_dir, dir); + sc_must_snprintf(dst, sizeof dst, "%s%s", + scratch_dir, dir); + sc_do_mount(src, dst, NULL, MS_BIND, NULL); + sc_do_mount("none", dst, NULL, MS_SLAVE, NULL); + } + } } // Bind mount the directory where all snaps are mounted. The location of // the this directory on the host filesystem may not match the location in diff --git a/cmd/snap-confine/snap-confine.apparmor.in b/cmd/snap-confine/snap-confine.apparmor.in index a999f462ee..9e8064f7a3 100644 --- a/cmd/snap-confine/snap-confine.apparmor.in +++ b/cmd/snap-confine/snap-confine.apparmor.in @@ -160,9 +160,11 @@ mount options=(rw rslave) -> /tmp/snap.rootfs_*/usr/src/, # /etc/alternatives (classic) mount options=(rw bind) @SNAP_MOUNT_DIR@/{,ubuntu-}core/*/etc/alternatives/ -> /tmp/snap.rootfs_*/etc/alternatives/, + mount options=(rw bind) @SNAP_MOUNT_DIR@/{,ubuntu-}core/*/etc/ssl/ -> /tmp/snap.rootfs_*/etc/ssl/, # /etc/alternatives (core) mount options=(rw bind) /etc/alternatives/ -> /tmp/snap.rootfs_*/etc/alternatives/, mount options=(rw slave) -> /tmp/snap.rootfs_*/etc/alternatives/, + mount options=(rw slave) -> /tmp/snap.rootfs_*/etc/ssl/, # the /snap directory mount options=(rw rbind) @SNAP_MOUNT_DIR@/ -> /tmp/snap.rootfs_*/snap/, mount options=(rw rslave) -> /tmp/snap.rootfs_*/snap/, diff --git a/cmd/snap-confine/snap-confine.c b/cmd/snap-confine/snap-confine.c index eb08ec6962..0db96380f2 100644 --- a/cmd/snap-confine/snap-confine.c +++ b/cmd/snap-confine/snap-confine.c @@ -105,12 +105,6 @@ int main(int argc, char **argv) #endif // ifdef HAVE_SECCOMP if (geteuid() == 0) { - // ensure that "/" or "/snap" is mounted with the - // "shared" option, see LP:#1668659 - int global_lock_fd = sc_lock_global(); - sc_ensure_shared_snap_mount(); - sc_unlock_global(global_lock_fd); - if (classic_confinement) { /* 'classic confinement' is designed to run without the sandbox * inside the shared namespace. Specifically: @@ -139,10 +133,12 @@ int main(int argc, char **argv) sc_reassociate_with_pid1_mount_ns(); // Do global initialization: int global_lock_fd = sc_lock_global(); + // ensure that "/" or "/snap" is mounted with the + // "shared" option, see LP:#1668659 + debug("ensuring that snap mount directory is shared"); + sc_ensure_shared_snap_mount(); debug("unsharing snap namespace directory"); sc_initialize_ns_groups(); - // TODO: implement this. - debug("share snap directory here..."); sc_unlock_global(global_lock_fd); // Do per-snap initialization. diff --git a/cmd/snap-update-ns/bootstrap.go b/cmd/snap-update-ns/bootstrap.go index b7239c0b03..6adad9618f 100644 --- a/cmd/snap-update-ns/bootstrap.go +++ b/cmd/snap-update-ns/bootstrap.go @@ -37,17 +37,27 @@ __attribute__((constructor)) static void init(void) { import "C" import ( + "errors" "fmt" "syscall" "unsafe" ) -// Error returns error (if any) encountered in pre-main C code. +var ( + // ErrNoNamespace is returned when a snap namespace does not exist. + ErrNoNamespace = errors.New("cannot update mount namespace that was not created yet") +) + +// BootstrapError 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) + // Translate EINVAL from setns or ENOENT from open into a dedicated error. + if errno == syscall.EINVAL || errno == syscall.ENOENT { + return ErrNoNamespace + } if errno != 0 { return fmt.Errorf("%s: %s", C.GoString(C.bootstrap_msg), errno) } diff --git a/cmd/snap-update-ns/main.go b/cmd/snap-update-ns/main.go index e153baa554..ec530605eb 100644 --- a/cmd/snap-update-ns/main.go +++ b/cmd/snap-update-ns/main.go @@ -25,6 +25,9 @@ import ( "github.com/jessevdk/go-flags" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/interfaces/mount" + "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/snap" ) @@ -53,12 +56,72 @@ 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 { + // If there is no mount namespace to transition to let's just quit + // instantly without any errors as there is nothing to do anymore. + if err == ErrNoNamespace { + return nil + } return err } - // TODO: implement this - return fmt.Errorf("not implemented") + snapName := opts.Positionals.SnapName + + // Lock the mount namespace so that any concurrently attempted invocations + // of snap-confine are synchronized and will see consistent state. + lock, err := mount.OpenLock(snapName) + if err != nil { + return fmt.Errorf("cannot open lock file for mount namespace of snap %q: %s", snapName, err) + } + defer lock.Close() + if err := lock.Lock(); err != nil { + return fmt.Errorf("cannot lock mount namespace of snap %q: %s", snapName, err) + } + + // Read the desired and current mount profiles. Note that missing files + // count as empty profiles so that we can gracefully handle a mount + // interface connection/disconnection. + desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName) + desired, err := mount.LoadProfile(desiredProfilePath) + if err != nil { + return fmt.Errorf("cannot load desired mount profile of snap %q: %s", snapName, err) + } + + currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName) + currentBefore, err := mount.LoadProfile(currentProfilePath) + if err != nil { + return fmt.Errorf("cannot load current mount profile of snap %q: %s", snapName, err) + } + + // Compute the needed changes and perform each change if needed, collecting + // those that we managed to perform or that were performed already. + changesNeeded := mount.NeededChanges(currentBefore, desired) + var changesMade []mount.Change + for _, change := range changesNeeded { + if change.Action == mount.Keep { + changesMade = append(changesMade, change) + continue + } + if err := change.Perform(); err != nil { + logger.Noticef("cannot change mount namespace of snap %q according to change %s: %s", snapName, change, err) + continue + } + changesMade = append(changesMade, change) + } + + // Compute the new current profile so that it contains only changes that were made + // and save it back for next runs. + var currentAfter mount.Profile + for _, change := range changesMade { + if change.Action == mount.Mount || change.Action == mount.Keep { + currentAfter.Entries = append(currentAfter.Entries, change.Entry) + } + } + if err := currentAfter.Save(currentProfilePath); err != nil { + return fmt.Errorf("cannot save current mount profile of snap %q: %s", snapName, err) + } + return nil } diff --git a/cmd/snap/cmd_snap_op.go b/cmd/snap/cmd_snap_op.go index da2260dc8c..a40e71765e 100644 --- a/cmd/snap/cmd_snap_op.go +++ b/cmd/snap/cmd_snap_op.go @@ -331,6 +331,12 @@ func (mx *channelMixin) setChannelFromCommandline() error { mx.Channel = ch.chName } + if !strings.Contains(mx.Channel, "/") && mx.Channel != "" && mx.Channel != "edge" && mx.Channel != "beta" && mx.Channel != "candidate" && mx.Channel != "stable" { + // shortcut to jump to a different track, e.g. + // snap install foo --channel=3.4 # implies 3.4/stable + mx.Channel += "/stable" + } + return nil } diff --git a/cmd/snap/cmd_snap_op_test.go b/cmd/snap/cmd_snap_op_test.go index 15f7178c96..e29aa33d86 100644 --- a/cmd/snap/cmd_snap_op_test.go +++ b/cmd/snap/cmd_snap_op_test.go @@ -172,16 +172,57 @@ func (s *SnapOpSuite) TestInstall(c *check.C) { c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ "action": "install", - "channel": "chan", + "channel": "candidate", }) - s.srv.channel = "chan" + s.srv.channel = "candidate" } s.RedirectClientToTestServer(s.srv.handle) - rest, err := snap.Parser().ParseArgs([]string{"install", "--channel", "chan", "foo"}) + rest, err := snap.Parser().ParseArgs([]string{"install", "--channel", "candidate", "foo"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) - c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(chan\) 1.0 from 'bar' installed`) + c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(candidate\) 1.0 from 'bar' installed`) + c.Check(s.Stderr(), check.Equals, "") + // ensure that the fake server api was actually hit + c.Check(s.srv.n, check.Equals, s.srv.total) +} + +func (s *SnapOpSuite) TestInstallFromTrack(c *check.C) { + s.srv.checker = func(r *http.Request) { + c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") + c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ + "action": "install", + "channel": "3.4/stable", + }) + s.srv.channel = "3.4/stable" + } + + s.RedirectClientToTestServer(s.srv.handle) + // snap install --channel=3.4 means 3.4/stable, this is what we test here + rest, err := snap.Parser().ParseArgs([]string{"install", "--channel", "3.4", "foo"}) + c.Assert(err, check.IsNil) + c.Assert(rest, check.DeepEquals, []string{}) + c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(3.4/stable\) 1.0 from 'bar' installed`) + c.Check(s.Stderr(), check.Equals, "") + // ensure that the fake server api was actually hit + c.Check(s.srv.n, check.Equals, s.srv.total) +} + +func (s *SnapOpSuite) TestInstallFromBranch(c *check.C) { + s.srv.checker = func(r *http.Request) { + c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") + c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ + "action": "install", + "channel": "3.4/hotfix-1", + }) + s.srv.channel = "3.4/hotfix-1" + } + + s.RedirectClientToTestServer(s.srv.handle) + rest, err := snap.Parser().ParseArgs([]string{"install", "--channel", "3.4/hotfix-1", "foo"}) + c.Assert(err, check.IsNil) + c.Assert(rest, check.DeepEquals, []string{}) + c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(3.4/hotfix-1\) 1.0 from 'bar' installed`) c.Check(s.Stderr(), check.Equals, "") // ensure that the fake server api was actually hit c.Check(s.srv.n, check.Equals, s.srv.total) @@ -193,16 +234,16 @@ func (s *SnapOpSuite) TestInstallDevMode(c *check.C) { c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ "action": "install", "devmode": true, - "channel": "chan", + "channel": "beta", }) - s.srv.channel = "chan" + s.srv.channel = "beta" } s.RedirectClientToTestServer(s.srv.handle) - rest, err := snap.Parser().ParseArgs([]string{"install", "--channel", "chan", "--devmode", "foo"}) + rest, err := snap.Parser().ParseArgs([]string{"install", "--channel", "beta", "--devmode", "foo"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) - c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(chan\) 1.0 from 'bar' installed`) + c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(beta\) 1.0 from 'bar' installed`) c.Check(s.Stderr(), check.Equals, "") // ensure that the fake server api was actually hit c.Check(s.srv.n, check.Equals, s.srv.total) diff --git a/daemon/api.go b/daemon/api.go index a3dffd5c81..7c0a31e12e 100644 --- a/daemon/api.go +++ b/daemon/api.go @@ -32,6 +32,7 @@ import ( "os/user" "path/filepath" "regexp" + "sort" "strconv" "strings" "time" @@ -823,7 +824,7 @@ var errNothingToInstall = errors.New("nothing to install") const oldDefaultSnapCoreName = "ubuntu-core" const defaultCoreSnapName = "core" -func ensureUbuntuCore(st *state.State, targetSnap string, userID int) (*state.TaskSet, error) { +func ensureCore(st *state.State, targetSnap string, userID int) (*state.TaskSet, error) { if targetSnap == defaultCoreSnapName || targetSnap == oldDefaultSnapCoreName { return nil, errNothingToInstall } @@ -836,8 +837,8 @@ func ensureUbuntuCore(st *state.State, targetSnap string, userID int) (*state.Ta return snapstateInstall(st, defaultCoreSnapName, "stable", snap.R(0), userID, snapstate.Flags{}) } -func withEnsureUbuntuCore(st *state.State, targetSnap string, userID int, install func() (*state.TaskSet, error)) ([]*state.TaskSet, error) { - ubuCoreTs, err := ensureUbuntuCore(st, targetSnap, userID) +func withEnsureCore(st *state.State, targetSnap string, userID int, install func() (*state.TaskSet, error)) ([]*state.TaskSet, error) { + ubuCoreTs, err := ensureCore(st, targetSnap, userID) if err != nil && err != errNothingToInstall { return nil, err } @@ -910,6 +911,21 @@ func snapUpdateMany(inst *snapInstruction, st *state.State) (msg string, updated return msg, updated, tasksets, nil } +func verifySnapInstructions(inst *snapInstruction) error { + switch inst.Action { + case "install": + for _, snapName := range inst.Snaps { + // FIXME: alternatively we could simply mutate *inst + // and s/ubuntu-core/core/ ? + if snapName == "ubuntu-core" { + return fmt.Errorf(`cannot install "ubuntu-core", please use "core" instead`) + } + } + } + + return nil +} + func snapInstallMany(inst *snapInstruction, st *state.State) (msg string, installed []string, tasksets []*state.TaskSet, err error) { installed, tasksets, err = snapstateInstallMany(st, inst.Snaps, inst.userID) if err != nil { @@ -938,7 +954,7 @@ func snapInstall(inst *snapInstruction, st *state.State) (string, []*state.TaskS logger.Noticef("Installing snap %q revision %s", inst.Snaps[0], inst.Revision) - tsets, err := withEnsureUbuntuCore(st, inst.Snaps[0], inst.userID, + tsets, err := withEnsureCore(st, inst.Snaps[0], inst.userID, func() (*state.TaskSet, error) { return snapstateInstall(st, inst.Snaps[0], inst.Channel, inst.Revision, inst.userID, flags) }, @@ -1127,6 +1143,10 @@ func postSnap(c *Command, r *http.Request, user *auth.UserState) Response { vars := muxVars(r) inst.Snaps = []string{vars["name"]} + if err := verifySnapInstructions(&inst); err != nil { + return BadRequest("%s", err) + } + impl := inst.dispatch() if impl == nil { return BadRequest("unknown action %s", inst.Action) @@ -1189,7 +1209,7 @@ func trySnap(c *Command, r *http.Request, user *auth.UserState, trydir string, f if user != nil { userID = user.ID } - tsets, err := withEnsureUbuntuCore(st, info.Name(), userID, + tsets, err := withEnsureCore(st, info.Name(), userID, func() (*state.TaskSet, error) { return snapstateTryPath(st, info.Name(), trydir, flags) }, @@ -1415,7 +1435,7 @@ out: userID = user.ID } - tsets, err := withEnsureUbuntuCore(st, snapName, userID, + tsets, err := withEnsureCore(st, snapName, userID, func() (*state.TaskSet, error) { return snapstateInstallPath(st, sideInfo, tempPath, "", flags) }, @@ -1565,6 +1585,20 @@ type interfaceAction struct { Slots []slotJSON `json:"slots,omitempty"` } +func snapNamesFromConns(conns []interfaces.ConnRef) []string { + m := make(map[string]bool) + for _, conn := range conns { + m[conn.PlugRef.Snap] = true + m[conn.SlotRef.Snap] = true + } + l := make([]string, 0, len(m)) + for name := range m { + l = append(l, name) + } + sort.Strings(l) + return l +} + // changeInterfaces controls the interfaces system. // Plugs can be connected to and disconnected from slots. // When enableInternalInterfaceActions is true plugs and slots can also be @@ -1592,12 +1626,14 @@ func changeInterfaces(c *Command, r *http.Request, user *auth.UserState) Respons } var summary string - var taskset *state.TaskSet var err error - state := c.d.overlord.State() - state.Lock() - defer state.Unlock() + var tasksets []*state.TaskSet + var affected []string + + st := c.d.overlord.State() + st.Lock() + defer st.Unlock() switch a.Action { case "connect": @@ -1605,22 +1641,37 @@ func changeInterfaces(c *Command, r *http.Request, user *auth.UserState) Respons repo := c.d.overlord.InterfaceManager().Repository() connRef, err = repo.ResolveConnect(a.Plugs[0].Snap, a.Plugs[0].Name, a.Slots[0].Snap, a.Slots[0].Name) if err == nil { + var ts *state.TaskSet summary = fmt.Sprintf("Connect %s:%s to %s:%s", connRef.PlugRef.Snap, connRef.PlugRef.Name, connRef.SlotRef.Snap, connRef.SlotRef.Name) - taskset, err = ifacestate.Connect(state, connRef.PlugRef.Snap, connRef.PlugRef.Name, connRef.SlotRef.Snap, connRef.SlotRef.Name) + ts, err = ifacestate.Connect(st, connRef.PlugRef.Snap, connRef.PlugRef.Name, connRef.SlotRef.Snap, connRef.SlotRef.Name) + tasksets = append(tasksets, ts) + affected = snapNamesFromConns([]interfaces.ConnRef{connRef}) } case "disconnect": + var conns []interfaces.ConnRef + repo := c.d.overlord.InterfaceManager().Repository() summary = fmt.Sprintf("Disconnect %s:%s from %s:%s", a.Plugs[0].Snap, a.Plugs[0].Name, a.Slots[0].Snap, a.Slots[0].Name) - taskset, err = ifacestate.Disconnect(state, a.Plugs[0].Snap, a.Plugs[0].Name, a.Slots[0].Snap, a.Slots[0].Name) + conns, err = repo.ResolveDisconnect(a.Plugs[0].Snap, a.Plugs[0].Name, a.Slots[0].Snap, a.Slots[0].Name) + if err == nil { + for _, connRef := range conns { + var ts *state.TaskSet + ts, err = ifacestate.Disconnect(st, connRef.PlugRef.Snap, connRef.PlugRef.Name, connRef.SlotRef.Snap, connRef.SlotRef.Name) + if err != nil { + break + } + ts.JoinLane(st.NewLane()) + tasksets = append(tasksets, ts) + } + affected = snapNamesFromConns(conns) + } } if err != nil { return BadRequest("%v", err) } - change := state.NewChange(a.Action+"-snap", summary) - change.Set("snap-names", []string{a.Plugs[0].Snap, a.Slots[0].Snap}) - change.AddAll(taskset) + change := newChange(st, a.Action+"-snap", summary, tasksets, affected) - state.EnsureBefore(0) + st.EnsureBefore(0) return AsyncResponse(nil, &Meta{Change: change.ID()}) } diff --git a/daemon/api_test.go b/daemon/api_test.go index b90be7966a..7200e7b2c2 100644 --- a/daemon/api_test.go +++ b/daemon/api_test.go @@ -1702,6 +1702,23 @@ func (s *apiSuite) TestPostSnap(c *check.C) { c.Check(soon, check.Equals, 1) } +func (s *apiSuite) TestPostSnapVerfySnapInstruction(c *check.C) { + d := s.daemon(c) + d.overlord.Loop() + defer d.overlord.Stop() + + buf := bytes.NewBufferString(`{"action": "install"}`) + req, err := http.NewRequest("POST", "/v2/snaps/ubuntu-core", buf) + c.Assert(err, check.IsNil) + s.vars = map[string]string{"name": "ubuntu-core"} + + rsp := postSnap(snapCmd, req, nil).(*resp) + + c.Check(rsp.Type, check.Equals, ResponseTypeError) + c.Check(rsp.Status, check.Equals, http.StatusBadRequest) + c.Check(rsp.Result.(*errorResult).Message, testutil.Contains, `cannot install "ubuntu-core", please use "core" instead`) +} + func (s *apiSuite) TestPostSnapSetsUser(c *check.C) { d := s.daemon(c) ensureStateSoon = func(st *state.State) {} @@ -3405,7 +3422,7 @@ func (s *apiSuite) TestConnectPlugFailureNoSuchSlot(c *check.C) { c.Assert(plug.Connections, check.HasLen, 0) } -func (s *apiSuite) TestDisconnectPlugSuccess(c *check.C) { +func (s *apiSuite) testDisconnect(c *check.C, plugSnap, plugName, slotSnap, slotName string) { d := s.daemon(c) s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"}) @@ -3424,8 +3441,8 @@ func (s *apiSuite) TestDisconnectPlugSuccess(c *check.C) { action := &interfaceAction{ Action: "disconnect", - Plugs: []plugJSON{{Snap: "consumer", Name: "plug"}}, - Slots: []slotJSON{{Snap: "producer", Name: "slot"}}, + Plugs: []plugJSON{{Snap: plugSnap, Name: plugName}}, + Slots: []slotJSON{{Snap: slotSnap, Name: slotName}}, } text, err := json.Marshal(action) c.Assert(err, check.IsNil) @@ -3459,6 +3476,18 @@ func (s *apiSuite) TestDisconnectPlugSuccess(c *check.C) { c.Assert(slot.Connections, check.HasLen, 0) } +func (s *apiSuite) TestDisconnectPlugSuccess(c *check.C) { + s.testDisconnect(c, "consumer", "plug", "producer", "slot") +} + +func (s *apiSuite) TestDisconnectPlugSuccessWithEmptyPlug(c *check.C) { + s.testDisconnect(c, "", "", "producer", "slot") +} + +func (s *apiSuite) TestDisconnectPlugSuccessWithEmptySlot(c *check.C) { + s.testDisconnect(c, "consumer", "plug", "", "") +} + func (s *apiSuite) TestDisconnectPlugFailureNoSuchPlug(c *check.C) { d := s.daemon(c) @@ -3481,30 +3510,18 @@ func (s *apiSuite) TestDisconnectPlugFailureNoSuchPlug(c *check.C) { c.Assert(err, check.IsNil) rec := httptest.NewRecorder() interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req) - c.Check(rec.Code, check.Equals, 202) + c.Check(rec.Code, check.Equals, 400) 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() - err = chg.Err() - st.Unlock() - c.Assert(err, check.NotNil) - c.Check(err.Error(), check.Equals, `cannot perform the following tasks: -- Disconnect consumer:plug from producer:slot (snap "consumer" has no plug named "plug")`) - - repo := d.overlord.InterfaceManager().Repository() - slot := repo.Slot("producer", "slot") - c.Assert(slot.Connections, check.HasLen, 0) + c.Check(body, check.DeepEquals, map[string]interface{}{ + "result": map[string]interface{}{ + "message": "snap \"consumer\" has no plug named \"plug\"", + }, + "status": "Bad Request", + "status-code": 400.0, + "type": "error", + }) } func (s *apiSuite) TestDisconnectPlugFailureNoSuchSlot(c *check.C) { @@ -3529,30 +3546,19 @@ func (s *apiSuite) TestDisconnectPlugFailureNoSuchSlot(c *check.C) { c.Assert(err, check.IsNil) rec := httptest.NewRecorder() interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req) - c.Check(rec.Code, check.Equals, 202) + + c.Check(rec.Code, check.Equals, 400) 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() - err = chg.Err() - st.Unlock() - c.Assert(err, check.NotNil) - c.Check(err.Error(), check.Equals, `cannot perform the following tasks: -- Disconnect consumer:plug from producer:slot (snap "producer" has no slot named "slot")`) - - repo := d.overlord.InterfaceManager().Repository() - plug := repo.Plug("consumer", "plug") - c.Assert(plug.Connections, check.HasLen, 0) + c.Check(body, check.DeepEquals, map[string]interface{}{ + "result": map[string]interface{}{ + "message": "snap \"producer\" has no slot named \"slot\"", + }, + "status": "Bad Request", + "status-code": 400.0, + "type": "error", + }) } func (s *apiSuite) TestDisconnectPlugFailureNotConnected(c *check.C) { @@ -3577,32 +3583,19 @@ func (s *apiSuite) TestDisconnectPlugFailureNotConnected(c *check.C) { c.Assert(err, check.IsNil) rec := httptest.NewRecorder() interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req) - c.Check(rec.Code, check.Equals, 202) + + c.Check(rec.Code, check.Equals, 400) 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() - err = chg.Err() - st.Unlock() - c.Assert(err, check.NotNil) - c.Check(err.Error(), check.Equals, `cannot perform the following tasks: -- Disconnect consumer:plug from producer:slot (cannot disconnect consumer:plug from producer:slot, it is not connected)`) - - repo := d.overlord.InterfaceManager().Repository() - plug := repo.Plug("consumer", "plug") - slot := repo.Slot("producer", "slot") - c.Assert(plug.Connections, check.HasLen, 0) - c.Assert(slot.Connections, check.HasLen, 0) + c.Check(body, check.DeepEquals, map[string]interface{}{ + "result": map[string]interface{}{ + "message": "cannot disconnect consumer:plug from producer:slot, it is not connected", + }, + "status": "Bad Request", + "status-code": 400.0, + "type": "error", + }) } func (s *apiSuite) TestUnsupportedInterfaceRequest(c *check.C) { diff --git a/dirs/dirs.go b/dirs/dirs.go index 4139d9bd52..e8aa916c92 100644 --- a/dirs/dirs.go +++ b/dirs/dirs.go @@ -49,6 +49,7 @@ var ( SnapSocket string SnapRunDir string SnapRunNsDir string + SnapRunLockDir string SnapSeedDir string SnapDeviceDir string @@ -143,6 +144,7 @@ func SetRootDir(rootdir string) { SnapDesktopFilesDir = filepath.Join(rootdir, snappyDir, "desktop", "applications") SnapRunDir = filepath.Join(rootdir, "/run/snapd") SnapRunNsDir = filepath.Join(SnapRunDir, "/ns") + SnapRunLockDir = filepath.Join(SnapRunDir, "/lock") // keep in sync with the debian/snapd.socket file: SnapdSocket = filepath.Join(rootdir, "/run/snapd.socket") diff --git a/get-deps.sh b/get-deps.sh index d699591607..d14f2e5b2b 100755 --- a/get-deps.sh +++ b/get-deps.sh @@ -10,3 +10,11 @@ export PATH=$PATH:$GOPATH/bin echo Obtaining dependencies govendor sync + +unused="$(govendor list +unused)" +if [ "$unused" != "" ]; then + echo "Found unused ./vendor packages:" + echo "$unused" + echo "Please fix via 'govendor remove +unused'" + exit 1 +fi diff --git a/interfaces/builtin/account_control.go b/interfaces/builtin/account_control.go index c456ecf5f9..759c427e1d 100644 --- a/interfaces/builtin/account_control.go +++ b/interfaces/builtin/account_control.go @@ -19,10 +19,6 @@ package builtin -import ( - "github.com/snapcore/snapd/interfaces" -) - const accountControlConnectedPlugAppArmor = ` # Allow creating, modifying and deleting non-system users and account password. /{,usr/}sbin/chpasswd ixr, @@ -63,16 +59,11 @@ bind socket AF_NETLINK - NETLINK_AUDIT ` -// Interface which allows to handle the user accounts. -func NewAccountControlInterface() interfaces.Interface { - return &commonInterface{ +func init() { + registerIface(&commonInterface{ name: "account-control", connectedPlugAppArmor: accountControlConnectedPlugAppArmor, connectedPlugSecComp: accountControlConnectedPlugSecComp, reservedForOS: true, - } -} - -func init() { - registerIface(NewAccountControlInterface()) + }) } diff --git a/interfaces/builtin/account_control_test.go b/interfaces/builtin/account_control_test.go index da52c53c84..1b762709f3 100644 --- a/interfaces/builtin/account_control_test.go +++ b/interfaces/builtin/account_control_test.go @@ -37,7 +37,9 @@ type AccountControlSuite struct { plug *interfaces.Plug } -var _ = Suite(&AccountControlSuite{}) +var _ = Suite(&AccountControlSuite{ + iface: builtin.MustInterface("account-control"), +}) const accountCtlMockPlugSnapInfo = `name: other version: 1.0 @@ -48,7 +50,6 @@ apps: ` func (s *AccountControlSuite) SetUpTest(c *C) { - s.iface = builtin.NewAccountControlInterface() s.slot = &interfaces.Slot{ SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, diff --git a/interfaces/builtin/all.go b/interfaces/builtin/all.go index 2b895d268a..6582df7203 100644 --- a/interfaces/builtin/all.go +++ b/interfaces/builtin/all.go @@ -20,29 +20,35 @@ package builtin import ( + "fmt" "sort" "github.com/snapcore/snapd/interfaces" ) var ( - allInterfaces []interfaces.Interface - sorted bool + allInterfaces map[string]interfaces.Interface ) // Interfaces returns all of the built-in interfaces. func Interfaces() []interfaces.Interface { - if !sorted { - sort.Sort(byIfaceName(allInterfaces)) - sorted = true + ifaces := make([]interfaces.Interface, 0, len(allInterfaces)) + for _, iface := range allInterfaces { + ifaces = append(ifaces, iface) } - return allInterfaces + sort.Sort(byIfaceName(ifaces)) + return ifaces } // registerIface appends the given interface into the list of all known interfaces. func registerIface(iface interfaces.Interface) { - allInterfaces = append(allInterfaces, iface) - sorted = false + if allInterfaces[iface.Name()] != nil { + panic(fmt.Errorf("cannot register duplicate interface %q", iface.Name())) + } + if allInterfaces == nil { + allInterfaces = make(map[string]interfaces.Interface) + } + allInterfaces[iface.Name()] = iface } type byIfaceName []interfaces.Interface diff --git a/interfaces/builtin/all_test.go b/interfaces/builtin/all_test.go index c457cf2017..36443f092a 100644 --- a/interfaces/builtin/all_test.go +++ b/interfaces/builtin/all_test.go @@ -26,6 +26,7 @@ import ( "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/builtin" "github.com/snapcore/snapd/interfaces/dbus" + "github.com/snapcore/snapd/interfaces/ifacetest" "github.com/snapcore/snapd/interfaces/kmod" "github.com/snapcore/snapd/interfaces/mount" "github.com/snapcore/snapd/interfaces/seccomp" @@ -293,3 +294,16 @@ func (s *AllSuite) TestNoInterfaceImplementsOldBackendMethods(c *C) { } } } + +func (s *AllSuite) TestRegisterIface(c *C) { + restore := builtin.MockInterfaces(nil) + defer restore() + + // Registering an interface works correctly. + iface := &ifacetest.TestInterface{InterfaceName: "foo"} + builtin.RegisterIface(iface) + c.Assert(builtin.Interface("foo"), DeepEquals, iface) + + // Duplicates are detected. + c.Assert(func() { builtin.RegisterIface(iface) }, PanicMatches, `cannot register duplicate interface "foo"`) +} diff --git a/interfaces/builtin/alsa.go b/interfaces/builtin/alsa.go index 3c0715e60e..f4c152c41a 100644 --- a/interfaces/builtin/alsa.go +++ b/interfaces/builtin/alsa.go @@ -19,8 +19,6 @@ package builtin -import "github.com/snapcore/snapd/interfaces" - const alsaConnectedPlugAppArmor = ` # Description: Allow access to raw ALSA devices. @@ -33,14 +31,10 @@ const alsaConnectedPlugAppArmor = ` /var/lib/alsa/{,*} r, ` -func NewAlsaInterface() interfaces.Interface { - return &commonInterface{ +func init() { + registerIface(&commonInterface{ name: "alsa", connectedPlugAppArmor: alsaConnectedPlugAppArmor, reservedForOS: true, - } -} - -func init() { - registerIface(NewAlsaInterface()) + }) } diff --git a/interfaces/builtin/alsa_test.go b/interfaces/builtin/alsa_test.go index 8a455c0fc0..6f08f54b18 100644 --- a/interfaces/builtin/alsa_test.go +++ b/interfaces/builtin/alsa_test.go @@ -36,7 +36,9 @@ type AlsaInterfaceSuite struct { plug *interfaces.Plug } -var _ = Suite(&AlsaInterfaceSuite{}) +var _ = Suite(&AlsaInterfaceSuite{ + iface: builtin.MustInterface("alsa"), +}) func (s *AlsaInterfaceSuite) SetUpTest(c *C) { var mockPlugSnapInfoYaml = `name: other @@ -46,7 +48,6 @@ apps: command: foo plugs: [alsa] ` - s.iface = builtin.NewAlsaInterface() s.slot = &interfaces.Slot{ SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, @@ -56,6 +57,7 @@ apps: } snapInfo := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["alsa"]} + c.Assert(s.iface, NotNil) } func (s *AlsaInterfaceSuite) TestName(c *C) { diff --git a/interfaces/builtin/autopilot.go b/interfaces/builtin/autopilot.go index d3ba5d7361..dddbff9622 100644 --- a/interfaces/builtin/autopilot.go +++ b/interfaces/builtin/autopilot.go @@ -19,10 +19,6 @@ package builtin -import ( - "github.com/snapcore/snapd/interfaces" -) - const autopilotIntrospectionPlugAppArmor = ` # Description: Allows an application to be introspected and export its ui # status over DBus @@ -56,17 +52,11 @@ sendmsg sendto ` -// NewAutopilotIntrospectionInterface returns a new "autopilot-introspection" -// interface. -func NewAutopilotIntrospectionInterface() interfaces.Interface { - return &commonInterface{ +func init() { + registerIface(&commonInterface{ name: "autopilot-introspection", connectedPlugAppArmor: autopilotIntrospectionPlugAppArmor, connectedPlugSecComp: autopilotIntrospectionPlugSecComp, reservedForOS: true, - } -} - -func init() { - registerIface(NewAutopilotIntrospectionInterface()) + }) } diff --git a/interfaces/builtin/autopilot_test.go b/interfaces/builtin/autopilot_test.go index 655c8953d1..e61d46a264 100644 --- a/interfaces/builtin/autopilot_test.go +++ b/interfaces/builtin/autopilot_test.go @@ -45,10 +45,11 @@ apps: plugs: [autopilot-introspection] ` -var _ = Suite(&AutopilotInterfaceSuite{}) +var _ = Suite(&AutopilotInterfaceSuite{ + iface: builtin.MustInterface("autopilot-introspection"), +}) func (s *AutopilotInterfaceSuite) SetUpTest(c *C) { - s.iface = builtin.NewAutopilotIntrospectionInterface() s.slot = &interfaces.Slot{ SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, diff --git a/interfaces/builtin/avahi_observe.go b/interfaces/builtin/avahi_observe.go index e66a500def..c44c5827c1 100644 --- a/interfaces/builtin/avahi_observe.go +++ b/interfaces/builtin/avahi_observe.go @@ -19,8 +19,6 @@ package builtin -import "github.com/snapcore/snapd/interfaces" - const avahiObserveConnectedPlugAppArmor = ` # Description: allows domain browsing, service browsing and service resolving @@ -113,14 +111,10 @@ dbus (receive) peer=(label=unconfined), ` -func NewAvahiObserveInterface() interfaces.Interface { - return &commonInterface{ +func init() { + registerIface(&commonInterface{ name: "avahi-observe", connectedPlugAppArmor: avahiObserveConnectedPlugAppArmor, reservedForOS: true, - } -} - -func init() { - registerIface(NewAvahiObserveInterface()) + }) } diff --git a/interfaces/builtin/avahi_observe_test.go b/interfaces/builtin/avahi_observe_test.go index ebf3f91152..ae571405df 100644 --- a/interfaces/builtin/avahi_observe_test.go +++ b/interfaces/builtin/avahi_observe_test.go @@ -36,7 +36,9 @@ type AvahiObserveInterfaceSuite struct { plug *interfaces.Plug } -var _ = Suite(&AvahiObserveInterfaceSuite{}) +var _ = Suite(&AvahiObserveInterfaceSuite{ + iface: builtin.MustInterface("avahi-observe"), +}) func (s *AvahiObserveInterfaceSuite) SetUpTest(c *C) { var mockPlugSnapInfoYaml = `name: other @@ -46,7 +48,6 @@ apps: command: foo plugs: [avahi-observe] ` - s.iface = builtin.NewAvahiObserveInterface() s.slot = &interfaces.Slot{ SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, diff --git a/interfaces/builtin/bluetooth_control.go b/interfaces/builtin/bluetooth_control.go index 2ae7d682d1..55fbebeeff 100644 --- a/interfaces/builtin/bluetooth_control.go +++ b/interfaces/builtin/bluetooth_control.go @@ -19,10 +19,6 @@ package builtin -import ( - "github.com/snapcore/snapd/interfaces" -) - const bluetoothControlConnectedPlugAppArmor = ` # Description: Allow managing the kernel side Bluetooth stack. Reserved # because this gives privileged access to the system. @@ -50,15 +46,11 @@ const bluetoothControlConnectedPlugSecComp = ` bind ` -func NewBluetoothControlInterface() interfaces.Interface { - return &commonInterface{ +func init() { + registerIface(&commonInterface{ name: "bluetooth-control", connectedPlugAppArmor: bluetoothControlConnectedPlugAppArmor, connectedPlugSecComp: bluetoothControlConnectedPlugSecComp, reservedForOS: true, - } -} - -func init() { - registerIface(NewBluetoothControlInterface()) + }) } diff --git a/interfaces/builtin/bluetooth_control_test.go b/interfaces/builtin/bluetooth_control_test.go index e93232bfd3..3ab834d80e 100644 --- a/interfaces/builtin/bluetooth_control_test.go +++ b/interfaces/builtin/bluetooth_control_test.go @@ -45,10 +45,11 @@ apps: plugs: [bluetooth-control] ` -var _ = Suite(&BluetoothControlInterfaceSuite{}) +var _ = Suite(&BluetoothControlInterfaceSuite{ + iface: builtin.MustInterface("bluetooth-control"), +}) func (s *BluetoothControlInterfaceSuite) SetUpTest(c *C) { - s.iface = builtin.NewBluetoothControlInterface() s.slot = &interfaces.Slot{ SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, diff --git a/interfaces/builtin/bluez.go b/interfaces/builtin/bluez.go
|
