summaryrefslogtreecommitdiff
diff options
authorMichael Vogt <mvo@ubuntu.com>2016-06-23 15:31:22 +0200
committerMichael Vogt <mvo@ubuntu.com>2016-06-23 15:31:22 +0200
commitcf08067479d445060122e74f7c388422c9293870 (patch)
treecadfa4bb0ecd866f75cb4a42fe427c9bdb842c5b
parent35cf41007035decc6e049c957a817b944e1b015b (diff)
parent521095116be94c414f9ebca25b2a60b0f00ac30f (diff)
Merge remote-tracking branch 'upstream/master' into feature/content-auto-connectfeature/content-auto-connect
-rw-r--r--asserts/asserts.go9
-rw-r--r--daemon/api_test.go2
-rw-r--r--daemon/daemon_test.go4
-rw-r--r--dirs/dirs.go11
-rw-r--r--docs/interfaces.md29
-rw-r--r--firstboot/firstboot.go80
-rw-r--r--firstboot/firstboot_test.go95
-rw-r--r--integration-tests/tests/home_interface_test.go5
-rw-r--r--integration-tests/tests/log_observe_interface_test.go51
-rw-r--r--integration-tests/tests/network_bind_interface_test.go58
-rw-r--r--integration-tests/tests/network_interface_test.go55
-rw-r--r--integration-tests/tests/snap_install_test.go66
-rw-r--r--interfaces/apparmor/backend.go50
-rw-r--r--interfaces/apparmor/backend_test.go81
-rw-r--r--interfaces/apparmor/template_vars.go7
-rw-r--r--interfaces/backendtest/backendtest.go20
-rw-r--r--interfaces/builtin/all.go3
-rw-r--r--interfaces/builtin/all_test.go3
-rw-r--r--interfaces/builtin/camera.go (renamed from integration-tests/tests/snap_interfaces_test.go)36
-rw-r--r--interfaces/builtin/home.go7
-rw-r--r--interfaces/builtin/home_test.go15
-rw-r--r--interfaces/builtin/mpris.go234
-rw-r--r--interfaces/builtin/mpris_test.go230
-rw-r--r--interfaces/builtin/optical_drive.go39
-rw-r--r--interfaces/dbus/backend.go5
-rw-r--r--interfaces/mount/backend.go5
-rw-r--r--interfaces/naming.go4
-rw-r--r--interfaces/repo.go24
-rw-r--r--interfaces/repo_test.go19
-rw-r--r--interfaces/seccomp/backend.go41
-rw-r--r--interfaces/seccomp/backend_test.go48
-rw-r--r--interfaces/seccomp/template.go5
-rw-r--r--interfaces/udev/backend.go5
-rw-r--r--overlord/export_test.go19
-rw-r--r--overlord/firstboot.go112
-rw-r--r--overlord/firstboot_test.go99
-rw-r--r--overlord/managers_test.go2
-rw-r--r--overlord/migrations.go96
-rw-r--r--overlord/overlord.go25
-rw-r--r--overlord/overlord_test.go155
-rw-r--r--overlord/snapstate/backend.go2
-rw-r--r--overlord/snapstate/backend_test.go2
-rw-r--r--overlord/snapstate/check_snap.go34
-rw-r--r--overlord/snapstate/check_snap_test.go14
-rw-r--r--overlord/snapstate/prepare_snap_test.go81
-rw-r--r--overlord/snapstate/snapmgr.go2
-rw-r--r--overlord/snapstate/snapstate.go63
-rw-r--r--snap/implicit.go2
-rw-r--r--snap/implicit_test.go2
-rw-r--r--snap/info.go19
-rw-r--r--snap/info_test.go34
-rw-r--r--snap/snaptest/snaptest.go22
-rw-r--r--snap/squashfs/squashfs.go5
-rw-r--r--snappy/errors.go4
-rw-r--r--snappy/firstboot.go163
-rw-r--r--snappy/firstboot_test.go169
-rw-r--r--snappy/install.go4
-rw-r--r--spread.yaml52
-rw-r--r--store/store.go19
-rw-r--r--store/store_test.go19
-rw-r--r--tests/abort/task.yaml12
-rw-r--r--tests/basics/task.yaml13
-rw-r--r--tests/gccgo/task.yaml9
-rw-r--r--tests/install-errors/task.yaml40
-rw-r--r--tests/install-sideload/task.yaml9
-rw-r--r--tests/install-store/task.yaml25
-rw-r--r--tests/interfaces-cli/task.yaml34
-rw-r--r--tests/interfaces-log-observe/task.yaml66
-rw-r--r--tests/interfaces-network-bind/task.yaml78
-rw-r--r--tests/interfaces-network/task.yaml76
-rwxr-xr-xtests/lib/reset.sh27
-rwxr-xr-xtests/lib/snaps/basic-binaries/bin/block (renamed from tests/fixtures/snaps/basic-binaries/bin/block)0
-rwxr-xr-xtests/lib/snaps/basic-binaries/bin/cat (renamed from tests/fixtures/snaps/basic-binaries/bin/cat)0
-rwxr-xr-xtests/lib/snaps/basic-binaries/bin/echo (renamed from tests/fixtures/snaps/basic-binaries/bin/echo)0
-rwxr-xr-xtests/lib/snaps/basic-binaries/bin/fail (renamed from tests/fixtures/snaps/basic-binaries/bin/fail)0
-rwxr-xr-xtests/lib/snaps/basic-binaries/bin/success (renamed from tests/fixtures/snaps/basic-binaries/bin/success)0
-rw-r--r--tests/lib/snaps/basic-binaries/meta/icon.png (renamed from tests/fixtures/snaps/basic-binaries/meta/icon.png)bin3371 -> 3371 bytes
-rw-r--r--tests/lib/snaps/basic-binaries/meta/snap.yaml (renamed from tests/fixtures/snaps/basic-binaries/meta/snap.yaml)0
-rwxr-xr-xtests/lib/snaps/basic-desktop/bin/echo (renamed from tests/fixtures/snaps/basic-desktop/bin/echo)0
-rw-r--r--tests/lib/snaps/basic-desktop/meta/gui/echo.desktop (renamed from tests/fixtures/snaps/basic-desktop/meta/gui/echo.desktop)0
-rw-r--r--tests/lib/snaps/basic-desktop/meta/gui/icon.png (renamed from tests/fixtures/snaps/basic-desktop/meta/gui/icon.png)bin3371 -> 3371 bytes
-rw-r--r--tests/lib/snaps/basic-desktop/meta/snap.yaml (renamed from tests/fixtures/snaps/basic-desktop/meta/snap.yaml)0
-rwxr-xr-xtests/lib/snaps/basic-hooks/meta/hooks/install3
-rwxr-xr-xtests/lib/snaps/basic-hooks/meta/hooks/upgrade3
-rw-r--r--tests/lib/snaps/basic-hooks/meta/icon.png (renamed from tests/fixtures/snaps/basic/meta/icon.png)bin3371 -> 3371 bytes
-rw-r--r--tests/lib/snaps/basic-hooks/meta/snap.yaml2
-rw-r--r--tests/lib/snaps/basic/meta/icon.pngbin0 -> 3371 bytes
-rw-r--r--tests/lib/snaps/basic/meta/snap.yaml (renamed from tests/fixtures/snaps/basic/meta/snap.yaml)0
-rwxr-xr-xtests/lib/snaps/log-observe-consumer/bin/consumer (renamed from integration-tests/data/snaps/log-observe-consumer/bin/consumer)1
-rw-r--r--tests/lib/snaps/log-observe-consumer/meta/snap.yaml (renamed from integration-tests/data/snaps/log-observe-consumer/meta/snap.yaml)0
-rwxr-xr-xtests/lib/snaps/network-bind-consumer/bin/consumer22
-rw-r--r--tests/lib/snaps/network-bind-consumer/meta/snap.yaml10
-rwxr-xr-xtests/lib/snaps/network-consumer/bin/consumer21
-rw-r--r--tests/lib/snaps/network-consumer/meta/snap.yaml9
-rw-r--r--tests/searching/task.yaml4
-rw-r--r--tests/security-profiles/task.yaml33
-rw-r--r--tests/server-snap/task.yaml7
-rw-r--r--tests/snap-run-symlink-error/task.yaml4
-rw-r--r--tests/snap-run-symlink/task.yaml23
99 files changed, 2247 insertions, 850 deletions
diff --git a/asserts/asserts.go b/asserts/asserts.go
index 13e01f575d..de556c1d88 100644
--- a/asserts/asserts.go
+++ b/asserts/asserts.go
@@ -227,11 +227,11 @@ func parseHeaders(head []byte) (map[string]string, error) {
// BODY can be arbitrary,
// SIGNATURE is the signature
//
-// A header entry for a single line value (no "\n" in it) looks like:
+// A header entry for a single line value (no '\n' in it) looks like:
//
// NAME ": " VALUE
//
-// A header entry for a multiline value (a value with "\n"s in it) looks like:
+// A header entry for a multiline value (a value with '\n's in it) looks like:
//
// NAME ":\n" 1-space indented VALUE
//
@@ -240,12 +240,17 @@ func parseHeaders(head []byte) (map[string]string, error) {
// type
// authority-id (the signer id)
//
+// Further for a given assertion type all the primary key headers
+// must be non empty and must not contain '/'.
+//
// The following headers expect integer values and if omitted
// otherwise are assumed to be 0:
//
// revision (a positive int)
// body-length (expected to be equal to the length of BODY)
//
+// Typically list values in headers are expected to be comma separated.
+// Times are expected to be in the RFC3339 format: "2006-01-02T15:04:05Z07:00".
func Decode(serializedAssertion []byte) (Assertion, error) {
// copy to get an independent backstorage that can't be mutated later
assertionSnapshot := make([]byte, len(serializedAssertion))
diff --git a/daemon/api_test.go b/daemon/api_test.go
index 6759747bc2..1b8ddc2a2f 100644
--- a/daemon/api_test.go
+++ b/daemon/api_test.go
@@ -67,7 +67,7 @@ type apiSuite struct {
var _ = check.Suite(&apiSuite{})
-func (s *apiSuite) Snap(name, channel string, auther store.Authenticator) (*snap.Info, error) {
+func (s *apiSuite) Snap(name, channel string, devmode bool, auther store.Authenticator) (*snap.Info, error) {
s.auther = auther
if len(s.rsnaps) > 0 {
return s.rsnaps[0], s.err
diff --git a/daemon/daemon_test.go b/daemon/daemon_test.go
index 15da96539c..c925107bc8 100644
--- a/daemon/daemon_test.go
+++ b/daemon/daemon_test.go
@@ -23,6 +23,8 @@ import (
"net"
"net/http"
"net/http/httptest"
+ "os"
+ "path/filepath"
"testing"
"time"
@@ -42,6 +44,8 @@ var _ = check.Suite(&daemonSuite{})
func (s *daemonSuite) SetUpTest(c *check.C) {
dirs.SetRootDir(c.MkDir())
+ err := os.MkdirAll(filepath.Dir(dirs.SnapStateFile), 0755)
+ c.Assert(err, check.IsNil)
}
func (s *daemonSuite) TearDownTest(c *check.C) {
diff --git a/dirs/dirs.go b/dirs/dirs.go
index 652cd4991a..79ed033f53 100644
--- a/dirs/dirs.go
+++ b/dirs/dirs.go
@@ -42,10 +42,13 @@ var (
SnapMetaDir string
SnapdSocket string
+ SnapSeedDir string
+
SnapAssertsDBDir string
SnapTrustedAccountKey string
- SnapStateFile string
+ SnapStateFile string
+ SnapFirstBootStamp string
SnapBinariesDir string
SnapServicesDir string
@@ -96,6 +99,12 @@ func SetRootDir(rootdir string) {
SnapStateFile = filepath.Join(rootdir, snappyDir, "state.json")
+ SnapSeedDir = filepath.Join(rootdir, snappyDir, "seed")
+
+ // NOTE: if you change stampFile, update the condition in
+ // snapd.firstboot.service to match
+ SnapFirstBootStamp = filepath.Join(rootdir, snappyDir, "firstboot", "stamp")
+
SnapBinariesDir = filepath.Join(SnapSnapsDir, "bin")
SnapServicesDir = filepath.Join(rootdir, "/etc/systemd/system")
SnapBusPolicyDir = filepath.Join(rootdir, "/etc/dbus-1/system.d")
diff --git a/docs/interfaces.md b/docs/interfaces.md
index d56e1f4a24..ade4610fe4 100644
--- a/docs/interfaces.md
+++ b/docs/interfaces.md
@@ -64,7 +64,8 @@ Auto-Connect: yes
Can access non-hidden files in user's `$HOME` to read/write/lock.
This is restricted because it gives file access to the user's
-`$HOME`.
+`$HOME`. This interface is auto-connected on classic systems and
+manually connected on non-classic.
Usage: reserved
Auto-Connect: yes
@@ -78,6 +79,32 @@ allows adjusting settings of other applications.
Usage: reserved
Auto-Connect: yes
+### optical-drive
+
+Can access the first optical drive in read-only mode. Suitable for CD/DVD playback.
+
+Usage: common
+Auto-Connect: yes
+
+### mpris
+
+Can access media players implementing the Media Player Remote Interfacing
+Specification (mpris) when the interface is specified as a plug.
+
+Media players implementing mpris can be accessed by connected clients when
+specified as a slot.
+
+Usage: common
+Auto-Connect: no
+
+### camera
+
+Can access the first video camera. Suitable for programs wanting to use the
+webcams.
+
+Usage: common
+Auto-Connect: no
+
## Supported Interfaces - Advanced
### cups-control
diff --git a/firstboot/firstboot.go b/firstboot/firstboot.go
new file mode 100644
index 0000000000..1ab05c65f8
--- /dev/null
+++ b/firstboot/firstboot.go
@@ -0,0 +1,80 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2014-2015 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 firstboot
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/osutil"
+)
+
+func HasRun() bool {
+ return osutil.FileExists(dirs.SnapFirstBootStamp)
+}
+
+func StampFirstBoot() error {
+ // filepath.Dir instead of firstbootDir directly to ease testing
+ stampDir := filepath.Dir(dirs.SnapFirstBootStamp)
+
+ if _, err := os.Stat(stampDir); os.IsNotExist(err) {
+ if err := os.MkdirAll(stampDir, 0755); err != nil {
+ return err
+ }
+ }
+
+ return osutil.AtomicWriteFile(dirs.SnapFirstBootStamp, []byte{}, 0644, 0)
+}
+
+var globs = []string{"/sys/class/net/eth*", "/sys/class/net/en*"}
+var ethdir = "/etc/network/interfaces.d"
+var ifup = "/sbin/ifup"
+
+func EnableFirstEther() error {
+ var eths []string
+ for _, glob := range globs {
+ eths, _ = filepath.Glob(glob)
+ if len(eths) != 0 {
+ break
+ }
+ }
+ if len(eths) == 0 {
+ return nil
+ }
+ eth := filepath.Base(eths[0])
+ ethfile := filepath.Join(ethdir, eth)
+ data := fmt.Sprintf("allow-hotplug %[1]s\niface %[1]s inet dhcp\n", eth)
+
+ if err := osutil.AtomicWriteFile(ethfile, []byte(data), 0644, 0); err != nil {
+ return err
+ }
+
+ ifup := exec.Command(ifup, eth)
+ ifup.Stdout = os.Stdout
+ ifup.Stderr = os.Stderr
+ if err := ifup.Run(); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/firstboot/firstboot_test.go b/firstboot/firstboot_test.go
new file mode 100644
index 0000000000..2205ad753c
--- /dev/null
+++ b/firstboot/firstboot_test.go
@@ -0,0 +1,95 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2015 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 firstboot
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/dirs"
+)
+
+func TestStore(t *testing.T) { TestingT(t) }
+
+type FirstBootTestSuite struct {
+ globs []string
+ ethdir string
+ ifup string
+ e error
+}
+
+var _ = Suite(&FirstBootTestSuite{})
+
+func (s *FirstBootTestSuite) SetUpTest(c *C) {
+ tempdir := c.MkDir()
+ dirs.SetRootDir(tempdir)
+
+ s.globs = globs
+ globs = nil
+ s.ethdir = ethdir
+ ethdir = c.MkDir()
+ s.ifup = ifup
+ ifup = "/bin/true"
+
+ s.e = nil
+}
+
+func (s *FirstBootTestSuite) TearDownTest(c *C) {
+ globs = s.globs
+ ethdir = s.ethdir
+ ifup = s.ifup
+}
+
+func (s *FirstBootTestSuite) TestEnableFirstEther(c *C) {
+ c.Check(EnableFirstEther(), IsNil)
+ fs, _ := filepath.Glob(filepath.Join(ethdir, "*"))
+ c.Assert(fs, HasLen, 0)
+}
+
+func (s *FirstBootTestSuite) TestEnableFirstEtherSomeEth(c *C) {
+ dir := c.MkDir()
+ _, err := os.Create(filepath.Join(dir, "eth42"))
+ c.Assert(err, IsNil)
+
+ globs = []string{filepath.Join(dir, "eth*")}
+ c.Check(EnableFirstEther(), IsNil)
+ fs, _ := filepath.Glob(filepath.Join(ethdir, "*"))
+ c.Assert(fs, HasLen, 1)
+ bs, err := ioutil.ReadFile(fs[0])
+ c.Assert(err, IsNil)
+ c.Check(string(bs), Equals, "allow-hotplug eth42\niface eth42 inet dhcp\n")
+
+}
+
+func (s *FirstBootTestSuite) TestEnableFirstEtherBadEthDir(c *C) {
+ dir := c.MkDir()
+ _, err := os.Create(filepath.Join(dir, "eth42"))
+ c.Assert(err, IsNil)
+
+ ethdir = "/no/such/thing"
+ globs = []string{filepath.Join(dir, "eth*")}
+ err = EnableFirstEther()
+ c.Check(err, NotNil)
+ c.Check(os.IsNotExist(err), Equals, true)
+}
diff --git a/integration-tests/tests/home_interface_test.go b/integration-tests/tests/home_interface_test.go
index adc9cd5845..64c7657564 100644
--- a/integration-tests/tests/home_interface_test.go
+++ b/integration-tests/tests/home_interface_test.go
@@ -27,6 +27,7 @@ import (
"github.com/snapcore/snapd/integration-tests/testutils/cli"
"github.com/snapcore/snapd/integration-tests/testutils/data"
+ "github.com/snapcore/snapd/release"
"gopkg.in/check.v1"
)
@@ -36,7 +37,9 @@ var _ = check.Suite(&homeInterfaceSuite{
sampleSnaps: []string{data.HomeConsumerSnapName},
slot: "home",
plug: "home-consumer",
- autoconnect: true}})
+ // we only auto-connect on classic
+ autoconnect: release.OnClassic,
+ }})
type homeInterfaceSuite struct {
interfaceSuite
diff --git a/integration-tests/tests/log_observe_interface_test.go b/integration-tests/tests/log_observe_interface_test.go
deleted file mode 100644
index 1021bbc5e6..0000000000
--- a/integration-tests/tests/log_observe_interface_test.go
+++ /dev/null
@@ -1,51 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-// +build !excludeintegration
-
-/*
- * Copyright (C) 2016 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 tests
-
-import (
- "gopkg.in/check.v1"
-
- "github.com/snapcore/snapd/integration-tests/testutils/cli"
- "github.com/snapcore/snapd/integration-tests/testutils/data"
-)
-
-var _ = check.Suite(&logObserveInterfaceSuite{
- interfaceSuite: interfaceSuite{
- sampleSnaps: []string{data.LogObserveConsumerSnapName},
- slot: "log-observe",
- plug: "log-observe-consumer"}})
-
-type logObserveInterfaceSuite struct {
- interfaceSuite
-}
-
-func (s *logObserveInterfaceSuite) TestConnectedPlugAllowsLogObserve(c *check.C) {
- cli.ExecCommand(c, "sudo", "snap", "connect",
- s.plug+":"+s.slot, "ubuntu-core:"+s.slot)
-
- output := cli.ExecCommand(c, "log-observe-consumer")
- c.Assert(output, check.Equals, "ok\n")
-}
-
-func (s *logObserveInterfaceSuite) TestDisconnectedPlugDisablesLogObserve(c *check.C) {
- output := cli.ExecCommand(c, "log-observe-consumer")
- c.Assert(output, check.Equals, "tail: cannot open '/var/log/syslog' for reading: Permission denied\nerror accessing log\n")
-}
diff --git a/integration-tests/tests/network_bind_interface_test.go b/integration-tests/tests/network_bind_interface_test.go
deleted file mode 100644
index a3fa4ec9b4..0000000000
--- a/integration-tests/tests/network_bind_interface_test.go
+++ /dev/null
@@ -1,58 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-// +build !excludeintegration
-
-/*
- * Copyright (C) 2016 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 tests
-
-import (
- "gopkg.in/check.v1"
-
- "github.com/snapcore/snapd/integration-tests/testutils/cli"
- "github.com/snapcore/snapd/integration-tests/testutils/data"
- "github.com/snapcore/snapd/integration-tests/testutils/wait"
-)
-
-const providerURL = "http://127.0.0.1:8081"
-
-var _ = check.Suite(&networkBindInterfaceSuite{
- interfaceSuite: interfaceSuite{
- sampleSnaps: []string{data.NetworkBindConsumerSnapName, data.NetworkConsumerSnapName},
- slot: "network-bind",
- plug: "network-bind-consumer",
- autoconnect: true}})
-
-type networkBindInterfaceSuite struct {
- interfaceSuite
-}
-
-func (s *networkBindInterfaceSuite) TestPlugDisconnectionDisablesClientConnection(c *check.C) {
- wait.ForActiveService(c, "snap.network-bind-consumer.network-consumer.service")
-
- output := cli.ExecCommand(c, "network-consumer", providerURL)
- c.Assert(output, check.Equals, "ok\n")
-
- cli.ExecCommand(c, "sudo", "snap", "disconnect",
- s.plug+":"+s.slot, "ubuntu-core:"+s.slot)
-
- output = cli.ExecCommand(c, "snap", "interfaces")
- c.Assert(output, check.Matches, disconnectedPattern(s.slot, s.plug))
-
- output, err := cli.ExecCommandErr("network-consumer", providerURL)
- c.Assert(err, check.NotNil)
-}
diff --git a/integration-tests/tests/network_interface_test.go b/integration-tests/tests/network_interface_test.go
deleted file mode 100644
index 80b6f315f2..0000000000
--- a/integration-tests/tests/network_interface_test.go
+++ /dev/null
@@ -1,55 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-// +build !excludeintegration
-
-/*
- * Copyright (C) 2016 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 tests
-
-import (
- "github.com/snapcore/snapd/integration-tests/testutils/cli"
- "github.com/snapcore/snapd/integration-tests/testutils/data"
-
- "gopkg.in/check.v1"
-)
-
-var _ = check.Suite(&networkInterfaceSuite{
- interfaceSuite: interfaceSuite{
- sampleSnaps: []string{data.NetworkBindConsumerSnapName, data.NetworkConsumerSnapName},
- slot: "network",
- plug: "network-consumer",
- autoconnect: true}})
-
-type networkInterfaceSuite struct {
- interfaceSuite
-}
-
-func (s *networkInterfaceSuite) TestPlugDisconnectionDisablesFunctionality(c *check.C) {
- providerURL := "http://127.0.0.1:8081"
-
- output := cli.ExecCommand(c, "network-consumer", providerURL)
- c.Assert(output, check.Equals, "ok\n")
-
- cli.ExecCommand(c, "sudo", "snap", "disconnect",
- s.plug+":"+s.slot, "ubuntu-core:"+s.slot)
-
- output = cli.ExecCommand(c, "snap", "interfaces")
- c.Assert(output, check.Matches, disconnectedPattern(s.slot, s.plug))
-
- output, err := cli.ExecCommandErr("network-consumer", providerURL)
- c.Check(err, check.NotNil)
-}
diff --git a/integration-tests/tests/snap_install_test.go b/integration-tests/tests/snap_install_test.go
deleted file mode 100644
index 49e83c9890..0000000000
--- a/integration-tests/tests/snap_install_test.go
+++ /dev/null
@@ -1,66 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-// +build !excludeintegration
-
-/*
- * Copyright (C) 2015, 2016 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 tests
-
-import (
- "fmt"
- "os"
-
- "github.com/snapcore/snapd/integration-tests/testutils/build"
- "github.com/snapcore/snapd/integration-tests/testutils/cli"
- "github.com/snapcore/snapd/integration-tests/testutils/common"
- "github.com/snapcore/snapd/integration-tests/testutils/data"
-
- "gopkg.in/check.v1"
-)
-
-var _ = check.Suite(&installSuite{})
-
-type installSuite struct {
- common.SnappySuite
-}
-
-func (s *installSuite) TestCallFailBinaryFromInstalledSnap(c *check.C) {
- c.Skip("port to snapd")
-
- snapPath, err := build.LocalSnap(c, data.BasicBinariesSnapName)
- defer os.Remove(snapPath)
- c.Assert(err, check.IsNil, check.Commentf("Error building local snap: %s", err))
- common.InstallSnap(c, snapPath)
- defer common.RemoveSnap(c, data.BasicBinariesSnapName)
-
- _, err = cli.ExecCommandErr("basic-binaries.fail")
- c.Assert(err, check.NotNil, check.Commentf("The binary did not fail"))
-}
-
-// SNAP_INSTALL_004: with already installed snap name and same version
-func (s *installSuite) TestInstallWithAlreadyInstalledSnapAndSameVersionMustFail(c *check.C) {
- snapName := "hello-world"
-
- common.InstallSnap(c, snapName)
- defer common.RemoveSnap(c, snapName)
-
- expected := fmt.Sprintf(`error: cannot install "%s": snap "%[1]s" already installed\n`, snapName)
- actual, err := cli.ExecCommandErr("sudo", "snap", "install", snapName)
-
- c.Assert(err, check.NotNil)
- c.Assert(actual, check.Matches, expected)
-}
diff --git a/interfaces/apparmor/backend.go b/interfaces/apparmor/backend.go
index 204b9b39bf..0e892814e6 100644
--- a/interfaces/apparmor/backend.go
+++ b/interfaces/apparmor/backend.go
@@ -127,33 +127,45 @@ var (
// backend delegates writing those files to higher layers.
func (b *Backend) combineSnippets(snapInfo *snap.Info, devMode bool, snippets map[string][][]byte) (content map[string]*osutil.FileState, err error) {
for _, appInfo := range snapInfo.Apps {
- policy := defaultTemplate
- if devMode {
- policy = attachPattern.ReplaceAll(policy, attachComplain)
- }
- policy = templatePattern.ReplaceAllFunc(policy, func(placeholder []byte) []byte {
- switch {
- case bytes.Equal(placeholder, placeholderVar):
- return templateVariables(appInfo)
- case bytes.Equal(placeholder, placeholderProfileAttach):
- return []byte(fmt.Sprintf("profile \"%s\"", appInfo.SecurityTag()))
- case bytes.Equal(placeholder, placeholderSnippets):
- return bytes.Join(snippets[appInfo.Name], []byte("\n"))
- }
- return nil
- })
if content == nil {
content = make(map[string]*osutil.FileState)
}
- fname := appInfo.SecurityTag()
- content[fname] = &osutil.FileState{
- Content: policy,
- Mode: 0644,
+ addContent(appInfo.SecurityTag(), snapInfo, devMode, snippets, content)
+ }
+
+ for _, hookInfo := range snapInfo.Hooks {
+ if content == nil {
+ content = make(map[string]*osutil.FileState)
}
+ addContent(hookInfo.SecurityTag(), snapInfo, devMode, snippets, content)
}
+
return content, nil
}
+func addContent(securityTag string, snapInfo *snap.Info, devMode bool, snippets map[string][][]byte, content map[string]*osutil.FileState) {
+ policy := defaultTemplate
+ if devMode {
+ policy = attachPattern.ReplaceAll(policy, attachComplain)
+ }
+ policy = templatePattern.ReplaceAllFunc(policy, func(placeholder []byte) []byte {
+ switch {
+ case bytes.Equal(placeholder, placeholderVar):
+ return templateVariables(snapInfo)
+ case bytes.Equal(placeholder, placeholderProfileAttach):
+ return []byte(fmt.Sprintf("profile \"%s\"", securityTag))
+ case bytes.Equal(placeholder, placeholderSnippets):
+ return bytes.Join(snippets[securityTag], []byte("\n"))
+ }
+ return nil
+ })
+
+ content[securityTag] = &osutil.FileState{
+ Content: policy,
+ Mode: 0644,
+ }
+}
+
func reloadProfiles(profiles []string) error {
for _, profile := range profiles {
fname := filepath.Join(dirs.SnapAppArmorDir, profile)
diff --git a/interfaces/apparmor/backend_test.go b/interfaces/apparmor/backend_test.go
index 5cae8caec8..d81ab4c9cf 100644
--- a/interfaces/apparmor/backend_test.go
+++ b/interfaces/apparmor/backend_test.go
@@ -114,6 +114,20 @@ func (s *backendSuite) TestInstallingSnapWritesAndLoadsProfiles(c *C) {
})
}
+func (s *backendSuite) TestInstallingSnapWithHookWritesAndLoadsProfiles(c *C) {
+ devMode := false
+ s.InstallSnap(c, devMode, backendtest.HookYaml, 1)
+ profile := filepath.Join(dirs.SnapAppArmorDir, "snap.foo.hook.test-hook")
+
+ // Verify that profile "snap.foo.hook.test-hook" was created
+ _, err := os.Stat(profile)
+ c.Check(err, IsNil)
+ // apparmor_parser was used to load that file
+ c.Check(s.parserCmd.Calls(), DeepEquals, [][]string{
+ {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), profile},
+ })
+}
+
func (s *backendSuite) TestProfilesAreAlwaysLoaded(c *C) {
for _, devMode := range []bool{true, false} {
snapInfo := s.InstallSnap(c, devMode, backendtest.SambaYamlV1, 1)
@@ -148,6 +162,26 @@ func (s *backendSuite) TestRemovingSnapRemovesAndUnloadsProfiles(c *C) {
}
}
+func (s *backendSuite) TestRemovingSnapWithHookRemovesAndUnloadsProfiles(c *C) {
+ for _, devMode := range []bool{true, false} {
+ snapInfo := s.InstallSnap(c, devMode, backendtest.HookYaml, 1)
+ s.parserCmd.ForgetCalls()
+ s.RemoveSnap(c, snapInfo)
+ profile := filepath.Join(dirs.SnapAppArmorDir, "snap.foo.hook.test-hook")
+ // file called "snap.foo.hook.test-hook" was removed
+ _, err := os.Stat(profile)
+ c.Check(os.IsNotExist(err), Equals, true)
+ // apparmor cache file was removed
+ cache := filepath.Join(dirs.AppArmorCacheDir, "snap.foo.hook.test-hook")
+ _, err = os.Stat(cache)
+ c.Check(os.IsNotExist(err), Equals, true)
+ // apparmor_parser was used to unload the profile
+ c.Check(s.parserCmd.Calls(), DeepEquals, [][]string{
+ {"apparmor_parser", "--remove", "snap.foo.hook.test-hook"},
+ })
+ }
+}
+
func (s *backendSuite) TestUpdatingSnapMakesNeccesaryChanges(c *C) {
for _, devMode := range []bool{true, false} {
snapInfo := s.InstallSnap(c, devMode, backendtest.SambaYamlV1, 1)
@@ -183,6 +217,29 @@ func (s *backendSuite) TestUpdatingSnapToOneWithMoreApps(c *C) {
}
}
+func (s *backendSuite) TestUpdatingSnapToOneWithMoreHooks(c *C) {
+ for _, devMode := range []bool{true, false} {
+ snapInfo := s.InstallSnap(c, devMode, backendtest.SambaYamlV1WithNmbd, 1)
+ s.parserCmd.ForgetCalls()
+ // NOTE: the revision is kept the same to just test on the new application being added
+ snapInfo = s.UpdateSnap(c, snapInfo, devMode, backendtest.SambaYamlWithHook, 1)
+ smbdProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd")
+ nmbdProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.nmbd")
+ hookProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.hook.test-hook")
+
+ // Verify that profile "snap.samba.hook.test-hook" was created
+ _, err := os.Stat(hookProfile)
+ c.Check(err, IsNil)
+ // apparmor_parser was used to load the both profiles
+ c.Check(s.parserCmd.Calls(), DeepEquals, [][]string{
+ {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), hookProfile},
+ {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), nmbdProfile},
+ {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), smbdProfile},
+ })
+ s.RemoveSnap(c, snapInfo)
+ }
+}
+
func (s *backendSuite) TestUpdatingSnapToOneWithFewerApps(c *C) {
for _, devMode := range []bool{true, false} {
snapInfo := s.InstallSnap(c, devMode, backendtest.SambaYamlV1WithNmbd, 1)
@@ -203,6 +260,29 @@ func (s *backendSuite) TestUpdatingSnapToOneWithFewerApps(c *C) {
}
}
+func (s *backendSuite) TestUpdatingSnapToOneWithFewerHooks(c *C) {
+ for _, devMode := range []bool{true, false} {
+ snapInfo := s.InstallSnap(c, devMode, backendtest.SambaYamlWithHook, 1)
+ s.parserCmd.ForgetCalls()
+ // NOTE: the revision is kept the same to just test on the application being removed
+ snapInfo = s.UpdateSnap(c, snapInfo, devMode, backendtest.SambaYamlV1WithNmbd, 1)
+ smbdProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd")
+ nmbdProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.nmbd")
+ hookProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.hook.test-hook")
+
+ // Verify profile "snap.samba.hook.test-hook" was removed
+ _, err := os.Stat(hookProfile)
+ c.Check(os.IsNotExist(err), Equals, true)
+ // apparmor_parser was used to remove the unused profile
+ c.Check(s.parserCmd.Calls(), DeepEquals, [][]string{
+ {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), nmbdProfile},
+ {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), smbdProfile},
+ {"apparmor_parser", "--remove", "snap.samba.hook.test-hook"},
+ })
+ s.RemoveSnap(c, snapInfo)
+ }
+}
+
func (s *backendSuite) TestRealDefaultTemplateIsNormallyUsed(c *C) {
snapInfo, err := snap.InfoFromSnapYaml([]byte(backendtest.SambaYamlV1))
c.Assert(err, IsNil)
@@ -230,7 +310,6 @@ type combineSnippetsScenario struct {
}
const commonPrefix = `
-@{APP_NAME}="smbd"
@{SNAP_NAME}="samba"
@{SNAP_REVISION}="1"
@{INSTALL_DIR}="/snap"`
diff --git a/interfaces/apparmor/template_vars.go b/interfaces/apparmor/template_vars.go
index d000649193..2a2eea02d1 100644
--- a/interfaces/apparmor/template_vars.go
+++ b/interfaces/apparmor/template_vars.go
@@ -28,11 +28,10 @@ import (
// templateVariables returns text defining apparmor variables that can be used in the
// apparmor template and by apparmor snippets.
-func templateVariables(appInfo *snap.AppInfo) []byte {
+func templateVariables(info *snap.Info) []byte {
var buf bytes.Buffer
- fmt.Fprintf(&buf, "@{APP_NAME}=\"%s\"\n", appInfo.Name)
- fmt.Fprintf(&buf, "@{SNAP_NAME}=\"%s\"\n", appInfo.Snap.Name())
- fmt.Fprintf(&buf, "@{SNAP_REVISION}=\"%s\"\n", appInfo.Snap.Revision)
+ fmt.Fprintf(&buf, "@{SNAP_NAME}=\"%s\"\n", info.Name())
+ fmt.Fprintf(&buf, "@{SNAP_REVISION}=\"%s\"\n", info.Revision)
fmt.Fprintf(&buf, "@{INSTALL_DIR}=\"/snap\"")
return buf.Bytes()
}
diff --git a/interfaces/backendtest/backendtest.go b/interfaces/backendtest/backendtest.go
index 33631ab59f..c0c1a95720 100644
--- a/interfaces/backendtest/backendtest.go
+++ b/interfaces/backendtest/backendtest.go
@@ -79,6 +79,26 @@ apps:
slots:
iface:
`
+const SambaYamlWithHook = `
+name: samba
+apps:
+ smbd:
+ nmbd:
+hooks:
+ test-hook:
+ plugs: [iface]
+slots:
+ iface:
+`
+const HookYaml = `
+name: foo
+version: 1
+developer: acme
+hooks:
+ test-hook:
+plugs:
+ iface:
+`
// Support code for tests
diff --git a/interfaces/builtin/all.go b/interfaces/builtin/all.go
index 6443759a63..e761acb486 100644
--- a/interfaces/builtin/all.go
+++ b/interfaces/builtin/all.go
@@ -29,6 +29,7 @@ var allInterfaces = []interfaces.Interface{
&LocationControlInterface{},
&LocationObserveInterface{},
&ModemManagerInterface{},
+ &MprisInterface{},
&NetworkManagerInterface{},
&PppInterface{},
&SerialPortInterface{},
@@ -51,6 +52,8 @@ var allInterfaces = []interfaces.Interface{
NewOpenglInterface(),
NewPulseAudioInterface(),
NewCupsControlInterface(),
+ NewOpticalDriveInterface(),
+ NewCameraInterface(),
}
// Interfaces returns all of the built-in interfaces.
diff --git a/interfaces/builtin/all_test.go b/interfaces/builtin/all_test.go
index 3290fcc245..f64ee88d22 100644
--- a/interfaces/builtin/all_test.go
+++ b/interfaces/builtin/all_test.go
@@ -36,6 +36,7 @@ func (s *AllSuite) TestInterfaces(c *C) {
c.Check(all, Contains, &builtin.BluezInterface{})
c.Check(all, Contains, &builtin.LocationControlInterface{})
c.Check(all, Contains, &builtin.LocationObserveInterface{})
+ c.Check(all, Contains, &builtin.MprisInterface{})
c.Check(all, Contains, &builtin.SerialPortInterface{})
c.Check(all, DeepContains, builtin.NewFirewallControlInterface())
c.Check(all, DeepContains, builtin.NewGsettingsInterface())
@@ -56,4 +57,6 @@ func (s *AllSuite) TestInterfaces(c *C) {
c.Check(all, DeepContains, builtin.NewOpenglInterface())
c.Check(all, DeepContains, builtin.NewPulseAudioInterface())
c.Check(all, DeepContains, builtin.NewCupsControlInterface())
+ c.Check(all, DeepContains, builtin.NewOpticalDriveInterface())
+ c.Check(all, DeepContains, builtin.NewCameraInterface())
}
diff --git a/integration-tests/tests/snap_interfaces_test.go b/interfaces/builtin/camera.go
index 7cee2099cb..c3cce4765e 100644
--- a/integration-tests/tests/snap_interfaces_test.go
+++ b/interfaces/builtin/camera.go
@@ -1,5 +1,4 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
-// +build !excludeintegration
/*
* Copyright (C) 2016 Canonical Ltd
@@ -18,30 +17,21 @@
*
*/
-package tests
+package builtin
import (
- "fmt"
-
- "gopkg.in/check.v1"
-
- "github.com/snapcore/snapd/integration-tests/testutils/cli"
- "github.com/snapcore/snapd/integration-tests/testutils/common"
+ "github.com/snapcore/snapd/interfaces"
)
-var _ = check.Suite(&interfacesCliTest{})
-
-type interfacesCliTest struct {
- common.SnappySuite
-}
-
-// SNAP_INTERFACES_006: snap interfaces -i=<slot>
-func (s *interfacesCliTest) TestFilterBySlot(c *check.C) {
- plug := "network"
-
- expected := fmt.Sprintf("Slot +Plug\n:%s +-\n", plug)
-
- actual := cli.ExecCommand(c, "snap", "interfaces", "-i", plug)
-
- c.Assert(actual, check.Matches, expected)
+const cameraConnectedPlugAppArmor = `
+/dev/video0 rw,
+`
+
+// NewCameraInterface returns a new "camera" interface.
+func NewCameraInterface() interfaces.Interface {
+ return &commonInterface{
+ name: "camera",
+ connectedPlugAppArmor: cameraConnectedPlugAppArmor,
+ reservedForOS: true,
+ }
}
diff --git a/interfaces/builtin/home.go b/interfaces/builtin/home.go
index ca33b2be75..7ffd61045d 100644
--- a/interfaces/builtin/home.go
+++ b/interfaces/builtin/home.go
@@ -21,6 +21,7 @@ package builtin
import (
"github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/release"
)
// http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/home
@@ -41,6 +42,10 @@ owner @{HOME}/sn[^a]** rwk,
owner @{HOME}/sna[^p]** rwk,
# allow creating a few files not caught above
owner @{HOME}/{s,sn,sna}{,/} rwk,
+
+# allow access to gvfs mounts (only allow writes to files, not mount point)
+owner /run/user/[0-9]*/gvfs/** r,
+owner /run/user/[0-9]*/gvfs/*/** w,
`
// NewHomeInterface returns a new "home" interface.
@@ -49,6 +54,6 @@ func NewHomeInterface() interfaces.Interface {
name: "home",
connectedPlugAppArmor: homeConnectedPlugAppArmor,
reservedForOS: true,
- autoConnect: true,
+ autoConnect: release.OnClassic,
}
}
diff --git a/interfaces/builtin/home_test.go b/interfaces/builtin/home_test.go
index 829523aca5..0cf03d7223 100644
--- a/interfaces/builtin/home_test.go
+++ b/interfaces/builtin/home_test.go
@@ -24,6 +24,7 @@ import (
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/builtin"
+ "github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
)
@@ -123,6 +124,16 @@ func (s *HomeInterfaceSuite) TestUnexpectedSecuritySystems(c *C) {
c.Assert(snippet, IsNil)
}
-func (s *HomeInterfaceSuite) TestAutoConnect(c *C) {
- c.Check(s.iface.AutoConnect(), Equals, true)
+func (s *HomeInterfaceSuite) TestAutoConnectOnClassic(c *C) {
+ restore := release.MockOnClassic(true)
+ defer restore()
+ iface := builtin.NewHomeInterface()
+ c.Check(iface.AutoConnect(), Equals, true)
+}
+
+func (s *HomeInterfaceSuite) TestAutoConnectOnCore(c *C) {
+ restore := release.MockOnClassic(false)
+ defer restore()
+ iface := builtin.NewHomeInterface()
+ c.Check(iface.AutoConnect(), Equals, false)
}
diff --git a/interfaces/builtin/mpris.go b/interfaces/builtin/mpris.go
new file mode 100644
index 0000000000..e7ba838d3d
--- /dev/null
+++ b/interfaces/builtin/mpris.go
@@ -0,0 +1,234 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016 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 (
+ "bytes"
+
+ "github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/release"
+)
+
+var mprisPermanentSlotAppArmor = []byte(`
+# Description: Allow operating as an MPRIS player.
+# Usage: common
+
+# DBus accesses
+#include <abstractions/dbus-session-strict>
+
+# https://specifications.freedesktop.org/mpris-spec/latest/
+# allow binding to the well-known DBus mpris interface based on the snap's name
+dbus (bind)
+ bus=session
+ name="org.mpris.MediaPlayer2.@{SNAP_NAME}{,.*}",
+
+# register as a player
+dbus (send)
+ bus=system
+ path=/org/freedesktop/DBus
+ interface=org.freedesktop.DBus
+ member="{Request,Release}Name"
+ peer=(name=org.freedesktop.DBus, label=unconfined),
+
+dbus (send)
+ bus=system
+ path=/org/freedesktop/DBus
+ interface=org.freedesktop.DBus
+ member="GetConnectionUnix{ProcessID,User}"
+ peer=(name=org.freedesktop.DBus, label=unconfined),
+
+dbus (send)
+ bus=session
+ path=/org/mpris/MediaPlayer2
+ interface=org.freedesktop.DBus.Properties
+ member="{GetAll,PropertiesChanged}"
+ peer=(name=org.freedesktop.DBus, label=unconfined),
+
+dbus (send)
+ bus=session
+ path=/org/mpris/MediaPlayer2
+ interface="org.mpris.MediaPlayer2{,.Player}"
+ peer=(name=org.freedesktop.DBus, label=unconfined),
+
+# we can always connect to ourselves
+dbus (receive)
+ bus=session
+ path=/org/mpris/MediaPlayer2
+ peer=(label=@{profile_name}),
+`)
+
+var mprisConnectedSlotAppArmor = []byte(`
+# Allow connected clients to interact with the player
+dbus (receive)
+ bus=session
+ interface=org.freedesktop.DBus.Properties
+ path=/org/mpris/MediaPlayer2
+ peer=(label=###PLUG_SECURITY_TAGS###),
+dbus (receive)
+ bus=session
+ interface=org.freedesktop.DBus.Introspectable
+ path="/{,org,org/mpris,org/mpris/MediaPlayer2}"
+ peer=(label=###PLUG_SECURITY_TAGS###),
+
+dbus (receive)
+ bus=session
+ interface="org.mpris.MediaPlayer2{,.*}"
+ path=/org/mpris/MediaPlayer2
+ peer=(label=###PLUG_SECURITY_TAGS###),
+`)
+
+var mprisConnectedSlotAppArmorClassic = []byte(`
+# Allow unconfined clients to interact with the player on classic
+dbus (receive)
+ bus=session
+ path=/org/mpris/MediaPlayer2
+ peer=(label=unconfined),
+`)
+
+var mprisConnectedPlugAppArmor = []byte(`
+# Description: Allow connecting to an MPRIS player.
+# Usage: common
+
+#include <abstractions/dbus-session-strict>
+
+# Find the mpris player
+dbus (send)
+ bus=session
+ path=/org/freedesktop/DBus
+ interface=org.freedesktop.DBus.Introspectable
+ peer=(name="org.freedesktop.DBus", label="unconfined"),
+dbus (send)
+ bus=session
+ path=/{,org,org/mpris,org/mpris/MediaPlayer2}
+ interface=org.freedesktop.DBus.Introspectable
+ peer=(name="org.freedesktop.DBus", label="unconfined"),
+# This reveals all names on the session bus
+dbus (send)
+ bus=session
+ path=/
+ interface=org.freedesktop.DBus
+ member=ListNames
+ peer=(name="org.freedesktop.DBus", label="unconfined"),
+
+# Communicate with the mpris player
+dbus (send)
+ bus=session
+ interface=org.freedesktop.DBus.Properties
+ path=/org/mpris/MediaPlayer2
+ peer=(label=###SLOT_SECURITY_TAGS###),
+dbus (send)
+ bus=session
+ path=/org/mpris/MediaPlayer2
+ peer=(label=###SLOT_SECURITY_TAGS###),
+`)
+
+var mprisPermanentSlotSecComp = []byte(`
+getsockname
+recvmsg
+sendmsg
+sendto
+`)
+
+var mprisConnectedPlugSecComp = []byte(`
+getsockname
+recvmsg
+sendmsg
+sendto
+`)
+
+type MprisInterface struct{}
+
+func (iface *MprisInterface) Name() string {
+ return "mpris"
+}
+
+func (iface *MprisInterface) PermanentPlugSnippet(plug *interfaces.Plug, securitySystem interfaces.SecuritySystem) ([]byte, error) {
+ switch securitySystem {
+ case interfaces.SecurityDBus, interfaces.SecurityAppArmor, interfaces.SecuritySecComp, interfaces.SecurityUDev, interfaces.SecurityMount:
+ return nil, nil
+ default:
+ return nil, interfaces.ErrUnknownSecurity
+ }
+}
+
+func (iface *MprisInterface) ConnectedPlugSnippet(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) {
+ switch securitySystem {
+ case interfaces.SecurityAppArmor:
+ old := []byte("###SLOT_SECURITY_TAGS###")
+ new := slotAppLabelExpr(slot)
+ snippet := bytes.Replace(mprisConnectedPlugAppArmor, old, new, -1)
+ return snippet, nil
+ case interfaces.SecurityDBus:
+ return nil, nil
+ case interfaces.SecuritySecComp:
+ return mprisConnectedPlugSecComp, nil
+ case interfaces.SecurityUDev:
+ return nil, nil
+ default:
+ return nil, interfaces.ErrUnknownSecurity
+ }
+}
+
+func (iface *MprisInterface) PermanentSlotSnippet(slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) {
+ switch securitySystem {
+ case interfaces.SecurityAppArmor:
+ snippet := mprisPermanentSlotAppArmor
+ // on classic, allow unconfined remotes to control the player
+ // (eg, indicator-sound)
+ if release.OnClassic {
+ snippet = append(snippet, mprisConnectedSlotAppArmorClassic...)
+ }
+ return snippet, nil
+ case interfaces.SecurityDBus:
+ return nil, nil
+ case interfaces.SecuritySecComp:
+ return mprisPermanentSlotSecComp, nil
+ case interfaces.SecurityUDev:
+ return nil, nil
+ default:
+ return nil, interfaces.ErrUnknownSecurity
+ }
+}
+
+func (iface *MprisInterface) ConnectedSlotSnippet(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) {
+ switch securitySystem {
+ case interfaces.SecurityAppArmor:
+ old := []byte("###PLUG_SECURITY_TAGS###")
+ new := plugAppLabelExpr(plug)
+ snippet := bytes.Replace(mprisConnectedSlotAppArmor, old, new, -1)
+ return snippet, nil
+ case interfaces.SecurityDBus, interfaces.SecuritySecComp, interfaces.SecurityUDev, interfaces.SecurityMount:
+ return nil, nil
+ default:
+ return nil, interfaces.ErrUnknownSecurity
+ }
+}
+
+func (iface *MprisInterface) SanitizePlug(slot *interfaces.Plug) error {
+ return nil
+}
+
+func (iface *MprisInterface) SanitizeSlot(slot *interfaces.Slot) error {
+ return nil
+}
+
+func (iface *MprisInterface) AutoConnect() bool {
+ return true
+}
diff --git a/interfaces/builtin/mpris_test.go b/interfaces/builtin/mpris_test.go
new file mode 100644
index 0000000000..29032d0a7e
--- /dev/null
+++ b/interfaces/builtin/mpris_test.go
@@ -0,0 +1,230 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016 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/builtin"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/testutil"
+)
+
+type MprisInterfaceSuite struct {
+ iface interfaces.Interface
+ slot *interfaces.Slot
+ plug *interfaces.Plug
+}
+
+var _ = Suite(&MprisInterfaceSuite{
+ iface: &builtin.MprisInterface{},
+ slot: &interfaces.Slot{
+ SlotInfo: &snap.SlotInfo{
+ Snap: &snap.Info{SuggestedName: "mpris"},
+ Name: "mpris-player",
+ Interface: "mpris",
+ },
+ },
+ plug: &interfaces.Plug{
+ PlugInfo: &snap.PlugInfo{
+ Snap: &snap.Info{SuggestedName: "mpris"},
+ Name: "mpris-client",
+ Interface: "mpris",
+ },
+ },
+})
+
+func (s *MprisInterfaceSuite) TestName(c *C) {
+ c.Assert(s.iface.Name(), Equals, "mpris")
+}
+
+// The label glob when all apps are bound to the mpris slot
+func (s *MprisInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelAll(c *C) {
+ app1 := &snap.AppInfo{Name: "app1"}
+ app2 := &snap.AppInfo{Name: "app2"}
+ slot := &interfaces.Slot{
+ SlotInfo: &snap.SlotInfo{
+ Snap: &snap.Info{
+ SuggestedName: "mpris",
+ Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2},
+ },
+ Name: "mpris",
+ Interface: "mpris",
+ Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2},
+ },
+ }
+ snippet, err := s.iface.ConnectedPlugSnippet(s.plug, slot, interfaces.SecurityAppArmor)
+ c.Assert(err, IsNil)
+ c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.mpris.*"),`)
+}
+
+// The label uses alternation when some, but not all, apps is bound to the mpris slot
+func (s *MprisInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelSome(c *C) {
+ app1 := &snap.AppInfo{Name: "app1"}
+ app2 := &snap.AppInfo{Name: "app2"}
+ app3 := &snap.AppInfo{Name: "app3"}
+ slot := &interfaces.Slot{
+ SlotInfo: &snap.SlotInfo{
+ Snap: &snap.Info{
+ SuggestedName: "mpris",
+ Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3},
+ },
+ Name: "mpris",
+ Interface: "mpris",
+ Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2},
+ },
+ }
+ snippet, err := s.iface.ConnectedPlugSnippet(s.plug, slot, interfaces.SecurityAppArmor)
+ c.Assert(err, IsNil)
+ c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.mpris.{app1,app2}"),`)
+}
+
+// The label uses short form when exactly one app is bound to the mpris slot
+func (s *MprisInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelOne(c *C) {
+ app := &snap.AppInfo{Name: "app"}
+ slot := &interfaces.Slot{
+ SlotInfo: &snap.SlotInfo{
+ Snap: &snap.Info{
+ SuggestedName: "mpris",
+ Apps: map[string]*snap.AppInfo{"app": app},
+ },
+ Name: "mpris",
+ Interface: "mpris",
+ Apps: map[string]*snap.AppInfo{"app": app},
+ },
+ }
+ snippet, err := s.iface.ConnectedPlugSnippet(s.plug, slot, interfaces.SecurityAppArmor)
+ c.Assert(err, IsNil)
+ c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.mpris.app"),`)
+}
+
+// The label glob when all apps are bound to the mpris plug
+func (s *MprisInterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelAll(c *C) {
+ app1 := &snap.AppInfo{Name: "app1"}
+ app2 := &snap.AppInfo{Name: "app2"}
+ plug := &interfaces.Plug{
+ PlugInfo: &snap.PlugInfo{
+ Snap: &snap.Info{
+ SuggestedName: "mpris",
+ Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2},
+ },
+ Name: "mpris",
+ Interface: "mpris",
+ Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2},
+ },
+ }
+ snippet, err := s.iface.ConnectedSlotSnippet(plug, s.slot, interfaces.SecurityAppArmor)
+ c.Assert(err, IsNil)
+ c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.mpris.*"),`)
+}
+
+// The label uses alternation when some, but not all, apps is bound to the mpris plug
+func (s *MprisInterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelSome(c *C) {
+ app1 := &snap.AppInfo{Name: "app1"}
+ app2 := &snap.AppInfo{Name: "app2"}
+ app3 := &snap.AppInfo{Name: "app3"}
+ plug := &interfaces.Plug{
+ PlugInfo: &snap.PlugInfo{
+ Snap: &snap.Info{
+ SuggestedName: "mpris",
+ Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3},
+ },
+ Name: "mpris",
+ Interface: "mpris",
+ Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2},
+ },
+ }
+ snippet, err := s.iface.ConnectedSlotSnippet(plug, s.slot, interfaces.SecurityAppArmor)
+ c.Assert(err, IsNil)
+ c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.mpris.{app1,app2}"),`)
+}
+
+// The label uses short form when exactly one app is bound to the mpris plug
+func (s *MprisInterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelOne(c *C) {
+ app := &snap.AppInfo{Name: "app"}
+ plug := &interfaces.Plug{
+ PlugInfo: &snap.PlugInfo{
+ Snap: &snap.Info{
+ SuggestedName: "mpris",
+ Apps: map[string]*snap.AppInfo{"app": app},
+ },
+ Name: "mpris",
+ Interface: "mpris",
+ Apps: map[string]*snap.AppInfo{"app": app},
+ },
+ }
+ snippet, err := s.iface.ConnectedSlotSnippet(plug, s.slot, interfaces.SecurityAppArmor)
+ c.Assert(err, IsNil)
+ c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.mpris.app"),`)
+}
+
+func (s *MprisInterfaceSuite) TestUnusedSecuritySystems(c *C) {
+ systems := [...]interfaces.SecuritySystem{interfaces.SecuritySecComp,
+ interfaces.SecurityDBus, interfaces.SecurityUDev}
+ for _, system := range systems {
+ snippet, err := s.iface.PermanentPlugSnippet(s.plug, system)
+ c.Assert(err, IsNil)
+ c.Assert(snippet, IsNil)
+ snippet, err = s.iface.ConnectedSlotSnippet(s.plug, s.slot, system)
+ c.Assert(err, IsNil)
+ c.Assert(snippet, IsNil)
+ }
+ snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityUDev)
+ c.Assert(err, IsNil)
+ c.Assert(snippet, IsNil)
+ snippet, err = s.iface.PermanentSlotSnippet(s.slot, interfaces.SecurityUDev)
+ c.Assert(err, IsNil)
+ c.Assert(snippet, IsNil)
+ snippet, err = s.iface.PermanentPlugSnippet(s.plug, interfaces.SecurityAppArmor)
+ c.Assert(err, IsNil)
+ c.Assert(snippet, IsNil)
+}
+
+func (s *MprisInterfaceSuite) TestUsedSecuritySystems(c *C) {
+ systems := [...]interfaces.SecuritySystem{interfaces.SecurityAppArmor,
+ interfaces.SecuritySecComp}
+ for _, system := range systems {
+ snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, system)
+ c.Assert(err, IsNil)
+ c.Assert(snippet, Not(IsNil))
+ snippet, err = s.iface.PermanentSlotSnippet(s.slot, system)
+ c.Assert(err, IsNil)
+ c.Assert(snippet, Not(IsNil))
+ }
+ snippet, err := s.iface.ConnectedSlotSnippet(s.plug, s.slot, interfaces.SecurityAppArmor)
+ c.Assert(err, IsNil)
+ c.Assert(snippet, Not(IsNil))
+}
+
+func (s *MprisInterfaceSuite) TestUnexpectedSecuritySystems(c *C) {
+ snippet, err := s.iface.PermanentPlugSnippet(s.plug, "foo")
+ c.Assert(err, Equals, interfaces.ErrUnknownSecurity)
+ c.Assert(snippet, IsNil)
+ snippet, err = s.iface.ConnectedPlugSnippet(s.plug, s.slot, "foo")
+ c.Assert(err, Equals, interfaces.ErrUnknownSecurity)
+ c.Assert(snippet, IsNil)
+ snippet, err = s.iface.PermanentSlotSnippet(s.slot, "foo")
+ c.Assert(err, Equals, interfaces.ErrUnknownSecurity)
+ c.Assert(snippet, IsNil)
+ snippet, err = s.iface.ConnectedSlotSnippet(s.plug, s.slot, "foo")
+ c.Assert(err, Equals, interfaces.ErrUnknownSecurity)
+ c.Assert(snippet, IsNil)
+}
diff --git a/interfaces/builtin/optical_drive.go b/interfaces/builtin/optical_drive.go
new file mode 100644
index 0000000000..5b9e5dbc7d
--- /dev/null
+++ b/interfaces/builtin/optical_drive.go
@@ -0,0 +1,39 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016 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 opticalDriveConnectedPlugAppArmor = `
+/dev/sr0 r,
+/dev/scd0 r,
+`
+
+// NewOpticalDriveInterface returns a new "optical-drive" interface.
+func NewOpticalDriveInterface() interfaces.Interface {
+ return &commonInterface{
+ name: "optical-drive",
+ connectedPlugAppArmor: opticalDriveConnectedPlugAppArmor,
+ reservedForOS: true,
+ autoConnect: true,
+ }
+}
diff --git a/interfaces/dbus/backend.go b/interfaces/dbus/backend.go
index 2f14041ea9..0f92166eeb 100644
--- a/interfaces/dbus/backend.go
+++ b/interfaces/dbus/backend.go
@@ -89,7 +89,8 @@ func (b *Backend) Remove(snapName string) error {
// affecting a given snap into a content map applicable to EnsureDirState.
func (b *Backend) combineSnippets(snapInfo *snap.Info, snippets map[string][][]byte) (content map[string]*osutil.FileState, err error) {
for _, appInfo := range snapInfo.Apps {
- appSnippets := snippets[appInfo.Name]
+ securityTag := appInfo.SecurityTag()
+ appSnippets := snippets[securityTag]
if len(appSnippets) == 0 {
continue
}
@@ -103,7 +104,7 @@ func (b *Backend) combineSnippets(snapInfo *snap.Info, snippets map[string][][]b
if content == nil {
content = make(map[string]*osutil.FileState)
}
- fname := fmt.Sprintf("%s.conf", appInfo.SecurityTag())
+ fname := fmt.Sprintf("%s.conf", securityTag)
content[fname] = &osutil.FileState{Content: buf.Bytes(), Mode: 0644}
}
return content, nil
diff --git a/interfaces/mount/backend.go b/interfaces/mount/backend.go
index 6e953d9a10..cea7f33ca7 100644
--- a/interfaces/mount/backend.go
+++ b/interfaces/mount/backend.go
@@ -89,7 +89,8 @@ func (b *Backend) Remove(snapName string) error {
// affecting a given snap into a content map applicable to EnsureDirState.
func (b *Backend) combineSnippets(snapInfo *snap.Info, snippets map[string][][]byte) (content map[string]*osutil.FileState, err error) {
for _, appInfo := range snapInfo.Apps {
- appSnippets := snippets[appInfo.Name]
+ securityTag := appInfo.SecurityTag()
+ appSnippets := snippets[securityTag]
if len(appSnippets) == 0 {
continue
}
@@ -101,7 +102,7 @@ func (b *Backend) combineSnippets(snapInfo *snap.Info, snippets map[string][][]b
if content == nil {
content = make(map[string]*osutil.FileState)
}
- fname := fmt.Sprintf("%s.fstab", appInfo.SecurityTag())
+ fname := fmt.Sprintf("%s.fstab", securityTag)
content[fname] = &osutil.FileState{Content: buf.Bytes(), Mode: 0644}
}
return content, nil
diff --git a/interfaces/naming.go b/interfaces/naming.go
index 7c41ad409b..e1f303ee91 100644
--- a/interfaces/naming.go
+++ b/interfaces/naming.go
@@ -20,11 +20,11 @@
package interfaces
import (
- "fmt"
+ "github.com/snapcore/snapd/snap"
)
// SecurityTagGlob returns a pattern that matches all security tags belonging to
// the same snap as the given app.
func SecurityTagGlob(snapName string) string {
- return fmt.Sprintf("snap.%s.%s", snapName, "*")
+ return snap.AppSecurityTag(snapName, "*")
}
diff --git a/interfaces/repo.go b/interfaces/repo.go
index 97f1ef704c..3f774e3978 100644
--- a/interfaces/repo.go
+++ b/interfaces/repo.go
@@ -451,8 +451,8 @@ func (r *Repository) Interfaces() *Interfaces {
}
// SecuritySnippetsForSnap collects all of the snippets of a given security
-// system that affect a given snap. The return value is indexed by app name
-// within that snap.
+// system that affect a given snap. The return value is indexed by app/hook
+// security tag within that snap.
func (r *Repository) SecuritySnippetsForSnap(snapName string, securitySystem SecuritySystem) (map[string][][]byte, error) {
r.m.Lock()
defer r.m.Unlock()
@@ -472,7 +472,8 @@ func (r *Repository) securitySnippetsForSnap(snapName string, securitySystem Sec
}
if snippet != nil {
for appName := range slot.Apps {
- snippets[appName] = append(snippets[appName], snippet)
+ securityTag := snap.AppSecurityTag(snapName, appName)
+ snippets[securityTag] = append(snippets[securityTag], snippet)
}
}
// Add connection-specific snippet specific to each plug
@@ -485,7 +486,8 @@ func (r *Repository) securitySnippetsForSnap(snapName string, securitySystem Sec
continue
}
for appName := range slot.Apps {
- snippets[appName] = append(snippets[appName], snippet)
+ securityTag := snap.AppSecurityTag(snapName, appName)
+ snippets[securityTag] = append(snippets[securityTag], snippet)
}
}
}
@@ -499,7 +501,12 @@ func (r *Repository) securitySnippetsForSnap(snapName string, securitySystem Sec
}
if snippet != nil {
for appName := range plug.Apps {
- snippets[appName] = append(snippets[appName], snippet)
+ securityTag := snap.AppSecurityTag(snapName, appName)
+ snippets[securityTag] = append(snippets[securityTag], snippet)
+ }
+ for hookName := range plug.Hooks {
+ securityTag := snap.HookSecurityTag(snapName, hookName)
+ snippets[securityTag] = append(snippets[securityTag], snippet)
}
}
// Add connection-specific snippet specific to each slot
@@ -512,7 +519,12 @@ func (r *Repository) securitySnippetsForSnap(snapName string, securitySystem Sec
continue
}
for appName := range plug.Apps {
- snippets[appName] = append(snippets[appName], snippet)
+ securityTag := snap.AppSecurityTag(snapName, appName)
+ snippets[securityTag] = append(snippets[securityTag], snippet)
+ }
+ for hookName := range plug.Hooks {
+ securityTag := snap.HookSecurityTag(snapName, hookName)
+ snippets[securityTag] = append(snippets[securityTag], snippet)
}
}
}
diff --git a/interfaces/repo_test.go b/interfaces/repo_test.go
index 88acba632d..3aa1a63c42 100644
--- a/interfaces/repo_test.go
+++ b/interfaces/repo_test.go
@@ -49,6 +49,8 @@ func (s *RepositorySuite) SetUpTest(c *C) {
name: consumer
apps:
app:
+hooks:
+ test-hook:
plugs:
plug:
interface: interface
@@ -61,6 +63,8 @@ plugs:
name: producer
apps:
app:
+hooks:
+ test-hook:
slots:
slot:
interface: interface
@@ -767,14 +771,17 @@ func (s *RepositorySuite) TestSlotSnippetsForSnapSuccess(c *C) {
snippets, err := repo.SecuritySnippetsForSnap(s.plug.Snap.Name(), testSecurity)
c.Assert(err, IsNil)
c.Check(snippets, DeepEquals, map[string][][]byte{
- "app": [][]byte{
+ "snap.consumer.app": [][]byte{
+ []byte(`static plug snippet`),
+ },
+ "snap.consumer.hook.test-hook": [][]byte{
[]byte(`static plug snippet`),
},
})
snippets, err = repo.SecuritySnippetsForSnap(s.slot.Snap.Name(), testSecurity)
c.Assert(err, IsNil)
c.Check(snippets, DeepEquals, map[string][][]byte{
- "app": [][]byte{
+ "snap.producer.app": [][]byte{
[]byte(`static slot snippet`),
},
})
@@ -784,7 +791,11 @@ func (s *RepositorySuite) TestSlotSnippetsForSnapSuccess(c *C) {
snippets, err = repo.SecuritySnippetsForSnap(s.plug.Snap.Name(), testSecurity)
c.Assert(err, IsNil)
c.Check(snippets, DeepEquals, map[string][][]byte{
- "app": [][]byte{
+ "snap.consumer.app": [][]byte{
+ []byte(`static plug snippet`),
+ []byte(`connection-specific plug snippet`),
+ },
+ "snap.consumer.hook.test-hook": [][]byte{
[]byte(`static plug snippet`),
[]byte(`connection-specific plug snippet`),
},
@@ -792,7 +803,7 @@ func (s *RepositorySuite) TestSlotSnippetsForSnapSuccess(c *C) {
snippets, err = repo.SecuritySnippetsForSnap(s.slot.Snap.Name(), testSecurity)
c.Assert(err, IsNil)
c.Check(snippets, DeepEquals, map[string][][]byte{
- "app": [][]byte{
+ "snap.producer.app": [][]byte{
[]byte(`static slot snippet`),
[]byte(`connection-specific slot snippet`),
},
diff --git a/interfaces/seccomp/backend.go b/interfaces/seccomp/backend.go
index 491f11f5bd..6d66c4d4b9 100644
--- a/interfaces/seccomp/backend.go
+++ b/interfaces/seccomp/backend.go
@@ -95,24 +95,37 @@ func (b *Backend) Remove(snapName string) error {
// affecting a given snap into a content map applicable to EnsureDirState.
func (b *Backend) combineSnippets(snapInfo *snap.Info, devMode bool, snippets map[string][][]byte) (content map[string]*osutil.FileState, err error) {
for _, appInfo := range snapInfo.Apps {
- var buf bytes.Buffer
- if devMode {
- // NOTE: This is going to be understood by ubuntu-core-launcher
- buf.WriteString("@complain\n")
- }
- buf.Write(defaultTemplate)
- for _, snippet := range snippets[appInfo.Name] {
- buf.Write(snippet)
- buf.WriteRune('\n')
- }
if content == nil {
content = make(map[string]*osutil.FileState)
}
- fname := appInfo.SecurityTag()
- content[fname] = &osutil.FileState{
- Content: buf.Bytes(),
- Mode: 0644,
+ addContent(appInfo.SecurityTag(), devMode, snippets, content)
+ }
+
+ for _, hookInfo := range snapInfo.Hooks {
+ if content == nil {
+ content = make(map[string]*osutil.FileState)
}
+ addContent(hookInfo.SecurityTag(), devMode, snippets, content)
}
+
return content, nil
}
+
+func addContent(securityTag string, devMode bool, snippets map[string][][]byte, content map[string]*osutil.FileState) {
+ var buffer bytes.Buffer
+ if devMode {
+ // NOTE: This is understood by ubuntu-core-launcher
+ buffer.WriteString("@complain\n")
+ }
+
+ buffer.Write(defaultTemplate)
+ for _, snippet := range snippets[securityTag] {
+ buffer.Write(snippet)
+ buffer.WriteRune('\n')
+ }
+
+ content[securityTag] = &osutil.FileState{
+ Content: buffer.Bytes(),
+ Mode: 0644,
+ }
+}
diff --git a/interfaces/seccomp/backend_test.go b/interfaces/seccomp/backend_test.go
index d30b8a903a..079bb9fd04 100644
--- a/interfaces/seccomp/backend_test.go
+++ b/interfaces/seccomp/backend_test.go
@@ -68,6 +68,16 @@ func (s *backendSuite) TestInstallingSnapWritesProfiles(c *C) {
c.Check(err, IsNil)
}
+func (s *backendSuite) TestInstallingSnapWritesHookProfiles(c *C) {
+ devMode := false
+ s.InstallSnap(c, devMode, backendtest.HookYaml, 0)
+ profile := filepath.Join(dirs.SnapSeccompDir, "snap.foo.hook.test-hook")
+
+ // Verify that profile named "snap.foo.hook.test-hook" was created.
+ _, err := os.Stat(profile)
+ c.Check(err, IsNil)
+}
+
func (s *backendSuite) TestRemovingSnapRemovesProfiles(c *C) {
for _, devMode := range []bool{true, false} {
snapInfo := s.InstallSnap(c, devMode, backendtest.SambaYamlV1, 0)
@@ -79,6 +89,18 @@ func (s *backendSuite) TestRemovingSnapRemovesProfiles(c *C) {
}
}
+func (s *backendSuite) TestRemovingSnapRemovesHookProfiles(c *C) {
+ for _, devMode := range []bool{true, false} {
+ snapInfo := s.InstallSnap(c, devMode, backendtest.HookYaml, 0)
+ s.RemoveSnap(c, snapInfo)
+ profile := filepath.Join(dirs.SnapSeccompDir, "snap.foo.hook.test-hook")
+
+ // Verify that profile "snap.foo.hook.test-hook" was removed.
+ _, err := os.Stat(profile)
+ c.Check(os.IsNotExist(err), Equals, true)
+ }
+}
+
func (s *backendSuite) TestUpdatingSnapToOneWithMoreApps(c *C) {
for _, devMode := range []bool{true, false} {
snapInfo := s.InstallSnap(c, devMode, backendtest.SambaYamlV1, 0)
@@ -91,6 +113,19 @@ func (s *backendSuite) TestUpdatingSnapToOneWithMoreApps(c *C) {
}
}
+func (s *backendSuite) TestUpdatingSnapToOneWithHooks(c *C) {
+ for _, devMode := range []bool{true, false} {
+ snapInfo := s.InstallSnap(c, devMode, backendtest.SambaYamlV1, 0)
+ snapInfo = s.UpdateSnap(c, snapInfo, devMode, backendtest.SambaYamlWithHook, 0)
+ profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.hook.test-hook")
+
+ // Verify that profile "snap.samba.hook.test-hook" was created.
+ _, err := os.Stat(profile)
+ c.Check(err, IsNil)
+ s.RemoveSnap(c, snapInfo)
+ }
+}
+
func (s *backendSuite) TestUpdatingSnapToOneWithFewerApps(c *C) {
for _, devMode := range []bool{true, false} {
snapInfo := s.InstallSnap(c, devMode, backendtest.SambaYamlV1WithNmbd, 0)
@@ -103,6 +138,19 @@ func (s *backendSuite) TestUpdatingSnapToOneWithFewerApps(c *C) {
}
}
+func (s *backendSuite) TestUpdatingSnapToOneWithNoHooks(c *C) {
+ for _, devMode := range []bool{true, false} {
+ snapInfo := s.InstallSnap(c, devMode, backendtest.SambaYamlWithHook, 0)
+ snapInfo = s.UpdateSnap(c, snapInfo, devMode, backendtest.SambaYamlV1, 0)
+ profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.hook.test-hook")
+
+ // Verify that profile snap.samba.hook.test-hook was removed.
+ _, err := os.Stat(profile)
+ c.Check(os.IsNotExist(err), Equals, true)
+ s.RemoveSnap(c, snapInfo)
+ }
+}
+
func (s *backendSuite) TestRealDefaultTemplateIsNormallyUsed(c *C) {
snapInfo, err := snap.InfoFromSnapYaml([]byte(backendtest.SambaYamlV1))
c.Assert(err, IsNil)
diff --git a/interfaces/seccomp/template.go b/interfaces/seccomp/template.go
index f5c5497df0..05d825edd1 100644
--- a/interfaces/seccomp/template.go
+++ b/interfaces/seccomp/template.go
@@ -479,4 +479,9 @@ writev
pwrite
pwrite64
pwritev
+
+# FIXME: remove this after LP: #1446748 is implemented
+# This is an older interface and single entry point that can be used instead
+# of socket(), bind(), connect(), etc individually.
+socketcall
`)
diff --git a/interfaces/udev/backend.go b/interfaces/udev/backend.go
index a230f80b99..9376a6f264 100644
--- a/interfaces/udev/backend.go
+++ b/interfaces/udev/backend.go
@@ -95,7 +95,8 @@ func ensureDirState(dir, glob string, content map[string]*osutil.FileState, snap
// affecting a given snap into a content map applicable to EnsureDirState.
func (b *Backend) combineSnippets(snapInfo *snap.Info, snippets map[string][][]byte) (content map[string]*osutil.FileState, err error) {
for _, appInfo := range snapInfo.Apps {
- appSnippets := snippets[appInfo.Name]
+ securityTag := appInfo.SecurityTag()
+ appSnippets := snippets[securityTag]
if len(appSnippets) == 0 {
continue
}
@@ -108,7 +109,7 @@ func (b *Backend) combineSnippets(snapInfo *snap.Info, snippets map[string][][]b
if content == nil {
content = make(map[string]*osutil.FileState)
}
- fname := fmt.Sprintf("70-%s.rules", appInfo.SecurityTag())
+ fname := fmt.Sprintf("70-%s.rules", securityTag)
content[fname] = &osutil.FileState{Content: buf.Bytes(), Mode: 0644}
}
return content, nil
diff --git a/overlord/export_test.go b/overlord/export_test.go
index fcde13c48d..ac4a4cdbf0 100644
--- a/overlord/export_test.go
+++ b/overlord/export_test.go
@@ -21,6 +21,8 @@ package overlord
import (
"time"
+
+ "github.com/snapcore/snapd/overlord/state"
)
// MockEnsureInterval sets the overlord ensure interval for tests.
@@ -53,3 +55,20 @@ func MockEnsureNext(o *Overlord, t time.Time) {
func (o *Overlord) Engine() *StateEngine {
return o.stateEng
}
+
+var (
+ PopulateStateFromInstalled = populateStateFromInstalled
+ Migrations = migrations
+)
+
+// MockPatches lets mock the current patch level and available migrations.
+func MockPatches(level int, m map[int]func(*state.State) error) (restore func()) {
+ prevLevel := patchLevel
+ prevMigrations := migrations
+ patchLevel = level
+ migrations = m
+ return func() {
+ patchLevel = prevLevel
+ migrations = prevMigrations
+ }
+}
diff --git a/overlord/firstboot.go b/overlord/firstboot.go
index 5616b5cccb..2936355a0c 100644
--- a/overlord/firstboot.go
+++ b/overlord/firstboot.go
@@ -20,53 +20,119 @@
package overlord
import (
+ "errors"
"fmt"
+ "path/filepath"
"github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/firstboot"
+ "github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
- "github.com/snapcore/snapd/snappy"
+ "github.com/snapcore/snapd/snap"
+)
+
+var (
+ // ErrNotFirstBoot is an error that indicates that the first boot has already
+ // run
+ ErrNotFirstBoot = errors.New("this is not your first boot")
)
func populateStateFromInstalled() error {
- all, err := (&snappy.Overlord{}).Installed()
+ if osutil.FileExists(dirs.SnapStateFile) {
+ return fmt.Errorf("cannot create state: state %q already exists", dirs.SnapStateFile)
+ }
+
+ ovld, err := New()
if err != nil {
return err
}
+ st := ovld.State()
- if osutil.FileExists(dirs.SnapStateFile) {
- return fmt.Errorf("cannot create state: state %q already exists", dirs.SnapStateFile)
+ all, err := filepath.Glob(filepath.Join(dirs.SnapSeedDir, "snaps", "*.snap"))
+ if err != nil {
+ return err
+ }
+
+ tsAll := []*state.TaskSet{}
+ for i, snapPath := range all {
+
+ fmt.Printf("Installing %s\n", snapPath)
+
+ st.Lock()
+
+ // XXX: needing to know the name here is too early
+
+ // everything will be sideloaded for now - that is
+ // ok, we will support adding assertions soon
+ snapf, err := snap.Open(snapPath)
+ if err != nil {
+ return err
+ }
+ info, err := snap.ReadInfoFromSnapFile(snapf, nil)
+ if err != nil {
+ return err
+ }
+ ts, err := snapstate.InstallPath(st, info.Name(), snapPath, "", 0)
+
+ if i > 0 {
+ ts.WaitAll(tsAll[i-1])
+ }
+ st.Unlock()
+
+ if err != nil {
+ return err
+ }
+
+ tsAll = append(tsAll, ts)
+ }
+ if len(tsAll) == 0 {
+ return nil
}
- st := state.New(&overlordStateBackend{
- path: dirs.SnapStateFile,
- })
st.Lock()
- defer st.Unlock()
+ msg := fmt.Sprintf("First boot seeding")
+ chg := st.NewChange("seed", msg)
+ for _, ts := range tsAll {
+ chg.AddAll(ts)
+ }
+ st.Unlock()
- for _, sn := range all {
- // no need to do a snapstate.Get() because this is firstboot
- info := sn.Info()
+ // do it and wait for ready
+ ovld.Loop()
+
+ st.EnsureBefore(0)
+ <-chg.Ready()
+
+ st.Lock()
+ status := chg.Status()
+ st.Unlock()
+ if status != state.DoneStatus {
+ ovld.Stop()
+ return fmt.Errorf("cannot run seed change: %s", chg.Err())
- var snapst snapstate.SnapState
- snapst.Sequence = append(snapst.Sequence, &info.SideInfo)
- snapst.Channel = info.Channel
- snapst.Active = sn.IsActive()
- snapstate.Set(st, sn.Name(), &snapst)
}
- return nil
+ return ovld.Stop()
}
-// FIXME:
-// This is not the final way we will do the state sync. This is just
-// an intermediate step to have working images again. We need to
-// figure out how we want first-boot to look like.
+// FirstBoot will do some initial boot setup and then sync the
+// state
func FirstBoot() error {
- if err := snappy.FirstBoot(); err != nil {
+ if firstboot.HasRun() {
+ return ErrNotFirstBoot
+ }
+ if err := firstboot.EnableFirstEther(); err != nil {
+ logger.Noticef("Failed to bring up ethernet: %s", err)
+ }
+
+ // snappy will be in a very unhappy state if this happens,
+ // because populateStateFromInstalled will error if there
+ // is a state file already
+ if err := populateStateFromInstalled(); err != nil {
return err
}
- return populateStateFromInstalled()
+ return firstboot.StampFirstBoot()
}
diff --git a/overlord/firstboot_test.go b/overlord/firstboot_test.go
new file mode 100644
index 0000000000..01353dbe49
--- /dev/null
+++ b/overlord/firstboot_test.go
@@ -0,0 +1,99 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2015 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 overlord_test
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/overlord"
+ "github.com/snapcore/snapd/snap/snaptest"
+ "github.com/snapcore/snapd/testutil"
+)
+
+type FirstBootTestSuite struct {
+ systemctl *testutil.MockCmd
+}
+
+var _ = Suite(&FirstBootTestSuite{})
+
+func (s *FirstBootTestSuite) SetUpTest(c *C) {
+ tempdir := c.MkDir()
+ dirs.SetRootDir(tempdir)
+
+ // mock the world!
+ err := os.MkdirAll(filepath.Join(dirs.SnapSeedDir, "snaps"), 0755)
+ c.Assert(err, IsNil)
+ err = os.MkdirAll(dirs.SnapServicesDir, 0755)
+ c.Assert(err, IsNil)
+ os.Setenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS", "1")
+ s.systemctl = testutil.MockCommand(c, "systemctl", "")
+}
+
+func (s *FirstBootTestSuite) TearDownTest(c *C) {
+ dirs.SetRootDir("/")
+ os.Unsetenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS")
+ s.systemctl.Restore()
+}
+
+func (s *FirstBootTestSuite) TestTwoRuns(c *C) {
+ c.Assert(overlord.FirstBoot(), IsNil)
+ _, err := os.Stat(dirs.SnapFirstBootStamp)
+ c.Assert(err, IsNil)
+
+ c.Assert(overlord.FirstBoot(), Equals, overlord.ErrNotFirstBoot)
+}
+
+func (s *FirstBootTestSuite) TestNoErrorWhenNoGadget(c *C) {
+ c.Assert(overlord.FirstBoot(), IsNil)
+ _, err := os.Stat(dirs.SnapFirstBootStamp)
+ c.Assert(err, IsNil)
+}
+
+func (s *FirstBootTestSuite) TestPopulateFromInstalledErrorsOnState(c *C) {
+ err := os.MkdirAll(filepath.Dir(dirs.SnapStateFile), 0755)
+ err = ioutil.WriteFile(dirs.SnapStateFile, nil, 0644)
+ c.Assert(err, IsNil)
+
+ err = overlord.PopulateStateFromInstalled()
+ c.Assert(err, ErrorMatches, "cannot create state: state .* already exists")
+}
+
+func (s *FirstBootTestSuite) TestPopulateFromInstalledSimpleNoSideInfo(c *C) {
+ // put a firstboot snap into the SnapBlobDir
+ snapYaml := `name: foo
+version: 1.0`
+ mockSnapFile := snaptest.MakeTestSnapWithFiles(c, snapYaml, nil)
+ targetSnapFile := filepath.Join(dirs.SnapSeedDir, "snaps", filepath.Base(mockSnapFile))
+ err := os.Rename(mockSnapFile, targetSnapFile)
+ c.Assert(err, IsNil)
+
+ // run the firstboot stuff
+ err = overlord.PopulateStateFromInstalled()
+ c.Assert(err, IsNil)
+
+ // and check the snap got correctly installed
+ c.Check(osutil.FileExists(filepath.Join(dirs.SnapSnapsDir, "foo", "x1", "meta", "snap.yaml")), Equals, true)
+}
diff --git a/overlord/managers_test.go b/overlord/managers_test.go
index a670dfb37b..3c87785301 100644
--- a/overlord/managers_test.go
+++ b/overlord/managers_test.go
@@ -63,6 +63,8 @@ var _ = Suite(&mgrsSuite{})
func (ms *mgrsSuite) SetUpTest(c *C) {
ms.tempdir = c.MkDir()
dirs.SetRootDir(ms.tempdir)
+ err := os.MkdirAll(filepath.Dir(dirs.SnapStateFile), 0755)
+ c.Assert(err, IsNil)
os.Setenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS", "1")
diff --git a/overlord/migrations.go b/overlord/migrations.go
new file mode 100644
index 0000000000..87eac8094f
--- /dev/null
+++ b/overlord/migrations.go
@@ -0,0 +1,96 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016 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 overlord
+
+import (
+ "fmt"
+
+ "github.com/snapcore/snapd/logger"
+ "github.com/snapcore/snapd/overlord/state"
+)
+
+// patchLevel is the current implemented patch level of the state format and content.
+var patchLevel = 0
+
+// PatchLevel returns the implemented patch level for state format and content.
+func PatchLevel() int {
+ return patchLevel
+}
+
+// initialize state at the current implemented patch level.
+func initialize(s *state.State) {
+ s.Lock()
+ defer s.Unlock()
+ s.Set("patch-level", patchLevel)
+}
+
+// migrate executes migrations that bridge state format changes as
+// identified by patch level increments that can be dealt with as
+// one-shot.
+func migrate(s *state.State) error {
+ var level int
+ s.Lock()
+ err := s.Get("patch-level", &level)
+ s.Unlock()
+ if err != nil && err != state.ErrNoState {
+ return err
+ }
+ if level == patchLevel {
+ // already at right level, nothing to do
+ return nil
+ }
+ if level > patchLevel {
+ return fmt.Errorf("cannot downgrade: snapd is too old for the current state patch level %d", level)
+ }
+
+ for level != patchLevel {
+ logger.Noticef("Running migration from state patch level %d to %d", level, level+1)
+ err := runMigration(s, level)
+ if err != nil {
+ logger.Noticef("Cannnot migrate: %v", err)
+ return fmt.Errorf("cannot migrate from state patch level %d to %d: %v", level, level+1, err)
+ }
+ level++
+ }
+
+ return nil
+}
+
+func runMigration(s *state.State, level int) error {
+ m := migrations[level]
+ if m == nil {
+ return fmt.Errorf("no supported migration")
+ }
+ s.Lock()
+ defer s.Unlock()
+
+ err := m(s)
+ if err != nil {
+ return err
+ }
+
+ s.Set("patch-level", level+1)
+
+ return nil
+}
+
+// migrations maps from patch level L to migration function for L to L+1.
+// Migration functions are run with the state lock held.
+var migrations = map[int]func(s *state.State) error{}
diff --git a/overlord/overlord.go b/overlord/overlord.go
index da94d2fba9..c8fcd6314c 100644
--- a/overlord/overlord.go
+++ b/overlord/overlord.go
@@ -23,6 +23,7 @@ package overlord
import (
"fmt"
"os"
+ "path/filepath"
"sync"
"time"
@@ -107,16 +108,34 @@ func New() (*Overlord, error) {
func loadState(backend state.Backend) (*state.State, error) {
if !osutil.FileExists(dirs.SnapStateFile) {
- return state.New(backend), nil
+ // fail fast, mostly interesting for tests, this dir is setup
+ // by the snapd package
+ stateDir := filepath.Dir(dirs.SnapStateFile)
+ if !osutil.IsDirectory(stateDir) {
+ return nil, fmt.Errorf("fatal: directory %q must be present", stateDir)
+ }
+ s := state.New(backend)
+ initialize(s)
+ return s, nil
}
r, err := os.Open(dirs.SnapStateFile)
if err != nil {
- return nil, fmt.Errorf("failed to read the state file: %s", err)
+ return nil, fmt.Errorf("cannot read the state file: %s", err)
}
defer r.Close()
- return state.ReadState(backend, r)
+ s, err := state.ReadState(backend, r)
+ if err != nil {
+ return nil, err
+ }
+
+ // one-shot migrations
+ err = migrate(s)
+ if err != nil {
+ return nil, err
+ }
+ return s, nil
}
func (o *Overlord) ensureTimerSetup() {
diff --git a/overlord/overlord_test.go b/overlord/overlord_test.go
index d6ca588ce2..23a6fa9c68 100644
--- a/overlord/overlord_test.go
+++ b/overlord/overlord_test.go
@@ -20,9 +20,12 @@
package overlord_test
import (
+ "encoding/json"
+ "fmt"
"io/ioutil"
"os"
"path/filepath"
+ "sort"
"syscall"
"testing"
"time"
@@ -54,6 +57,9 @@ func (ovs *overlordSuite) TearDownTest(c *C) {
}
func (ovs *overlordSuite) TestNew(c *C) {
+ restore := overlord.MockPatches(1, nil)
+ defer restore()
+
o, err := overlord.New()
c.Assert(err, IsNil)
c.Check(o, NotNil)
@@ -65,16 +71,22 @@ func (ovs *overlordSuite) TestNew(c *C) {
s := o.State()
c.Check(s, NotNil)
c.Check(o.Engine().State(), Equals, s)
+
+ s.Lock()
+ defer s.Unlock()
+ var patchLevel int
+ s.Get("patch-level", &patchLevel)
+ c.Check(patchLevel, Equals, 1)
}
func (ovs *overlordSuite) TestNewWithGoodState(c *C) {
- fakeState := []byte(`{"data":{"some":"data"},"changes":null,"tasks":null,"last-change-id":0,"last-task-id":0}`)
+ fakeState := []byte(fmt.Sprintf(`{"data":{"patch-level":%d,"some":"data"},"changes":null,"tasks":null,"last-change-id":0,"last-task-id":0}`, overlord.PatchLevel()))
err := ioutil.WriteFile(dirs.SnapStateFile, fakeState, 0600)
c.Assert(err, IsNil)
o, err := overlord.New()
-
c.Assert(err, IsNil)
+
state := o.State()
c.Assert(err, IsNil)
state.Lock()
@@ -82,7 +94,14 @@ func (ovs *overlordSuite) TestNewWithGoodState(c *C) {
d, err := state.MarshalJSON()
c.Assert(err, IsNil)
- c.Assert(string(d), DeepEquals, string(fakeState))
+
+ var got, expected map[string]interface{}
+ err = json.Unmarshal(d, &got)
+ c.Assert(err, IsNil)
+ err = json.Unmarshal(fakeState, &expected)
+ c.Assert(err, IsNil)
+
+ c.Check(got, DeepEquals, expected)
}
func (ovs *overlordSuite) TestNewWithInvalidState(c *C) {
@@ -94,6 +113,136 @@ func (ovs *overlordSuite) TestNewWithInvalidState(c *C) {
c.Assert(err, ErrorMatches, "EOF")
}
+func (ovs *overlordSuite) TestNewNoDowngrade(c *C) {
+ overlord.MockPatches(2, nil)
+
+ fakeState := []byte(fmt.Sprintf(`{"data":{"patch-level":%d,"some":"data"},"changes":null,"tasks":null,"last-change-id":0,"last-task-id":0}`, 3))
+ err := ioutil.WriteFile(dirs.SnapStateFile, fakeState, 0600)
+ c.Assert(err, IsNil)
+
+ _, err = overlord.New()
+ c.Assert(err, ErrorMatches, `cannot downgrade: snapd is too old for the current state patch level 3`)
+}
+
+func (ovs *overlordSuite) TestNewWithMigrations(c *C) {
+ m12 := func(s *state.State) error {
+ s.Set("m12", true)
+ return nil
+ }
+ m23 := func(s *state.State) error {
+ s.Set("m23", true)
+ return nil
+ }
+ overlord.MockPatches(3, map[int]func(*state.State) error{
+ 1: m12,
+ 2: m23,
+ })
+
+ fakeState := []byte(fmt.Sprintf(`{"data":{"patch-level":%d,"some":"data"},"changes":null,"tasks":null,"last-change-id":0,"last-task-id":0}`, 1))
+ err := ioutil.WriteFile(dirs.SnapStateFile, fakeState, 0600)
+ c.Assert(err, IsNil)
+
+ o, err := overlord.New()
+ c.Assert(err, IsNil)
+
+ state := o.State()
+ c.Assert(err, IsNil)
+ state.Lock()
+ defer state.Unlock()
+
+ var level int
+ var m12f, m23f bool
+ err = state.Get("patch-level", &level)
+ c.Assert(err, IsNil)
+ c.Check(level, Equals, 3)
+
+ err = state.Get("m12", &m12f)
+ c.Assert(err, IsNil)
+ c.Check(m12f, Equals, true)
+
+ err = state.Get("m23", &m23f)
+ c.Assert(err, IsNil)
+ c.Check(m12f, Equals, true)
+}
+
+func (ovs *overlordSuite) TestNewWithMissingMigrations(c *C) {
+ m23 := func(s *state.State) error {
+ s.Set("m23", true)
+ return nil
+ }
+ overlord.MockPatches(3, map[int]func(*state.State) error{
+ 2: m23,
+ })
+
+ fakeState := []byte(fmt.Sprintf(`{"data":{"patch-level":%d,"some":"data"},"changes":null,"tasks":null,"last-change-id":0,"last-task-id":0}`, 1))
+ err := ioutil.WriteFile(dirs.SnapStateFile, fakeState, 0600)
+ c.Assert(err, IsNil)
+
+ _, err = overlord.New()
+ c.Assert(err, ErrorMatches, `cannot migrate from state patch level 1 to 2: no supported migration`)
+}
+
+func (ovs *overlordSuite) TestNewWithMigrationError(c *C) {
+ m12 := func(s *state.State) error {
+ return fmt.Errorf("m12 failed")
+ }
+ m23 := func(s *state.State) error {
+ s.Set("m23", true)
+ return nil
+ }
+ overlord.MockPatches(3, map[int]func(*state.State) error{
+ 1: m12,
+ 2: m23,
+ })
+
+ fakeState := []byte(fmt.Sprintf(`{"data":{"patch-level":%d,"some":"data"},"changes":null,"tasks":null,"last-change-id":0,"last-task-id":0}`, 1))
+ err := ioutil.WriteFile(dirs.SnapStateFile, fakeState, 0600)
+ c.Assert(err, IsNil)
+
+ _, err = overlord.New()
+ c.Assert(err, ErrorMatches, `cannot migrate from state patch level 1 to 2: m12 failed`)
+
+ r, err := os.Open(dirs.SnapStateFile)
+ c.Assert(err, IsNil)
+ defer r.Close()
+
+ s, err := state.ReadState(nil, r)
+ c.Assert(err, IsNil)
+
+ s.Lock()
+ defer s.Unlock()
+
+ var level int
+ var m12f, m23f bool
+ err = s.Get("patch-level", &level)
+ c.Assert(err, IsNil)
+ c.Check(level, Equals, 1)
+
+ err = s.Get("m12", &m12f)
+ c.Assert(err, Equals, state.ErrNoState)
+
+ err = s.Get("m23", &m23f)
+ c.Assert(err, Equals, state.ErrNoState)
+}
+
+func (ovs *overlordSuite) TestMigrationsSanity(c *C) {
+ if overlord.PatchLevel() == 0 {
+ c.Assert(len(overlord.Migrations), Equals, 0)
+ c.Skip("patch level still at 0, no migrations")
+ }
+ from := make([]int, 0, len(overlord.Migrations))
+ for l, _ := range overlord.Migrations {
+ from = append(from, l)
+ }
+ sort.Ints(from)
+ // all steps present
+ for i := 1; i < len(from); i++ {
+ c.Check(from[i], Equals, from[i-1]+1)
+ }
+ // ends at previous of implemented patch level
+ c.Check(from[len(from)-1], Equals, overlord.PatchLevel()-1)
+}
+
type witnessManager struct {
state *state.State
expectedEnsure int
diff --git a/overlord/snapstate/backend.go b/overlord/snapstate/backend.go
index b12353571a..d3d69b3603 100644
--- a/overlord/snapstate/backend.go
+++ b/overlord/snapstate/backend.go
@@ -28,7 +28,7 @@ import (
// A StoreService can find, list available updates and download snaps.
type StoreService interface {
- Snap(name, channel string, auther store.Authenticator) (*snap.Info, error)
+ Snap(name, channel string, devmode bool, auther store.Authenticator) (*snap.Info, error)
Find(query, channel string, auther store.Authenticator) ([]*snap.Info, error)
ListRefresh([]*store.RefreshCandidate, store.Authenticator) ([]*snap.Info, error)
SuggestedCurrency() string
diff --git a/overlord/snapstate/backend_test.go b/overlord/snapstate/backend_test.go
index 64fbe8b8ad..b351b83a17 100644
--- a/overlord/snapstate/backend_test.go
+++ b/overlord/snapstate/backend_test.go
@@ -54,7 +54,7 @@ type fakeStore struct {
fakeTotalProgress int
}
-func (f *fakeStore) Snap(name, channel string, auther store.Authenticator) (*snap.Info, error) {
+func (f *fakeStore) Snap(name, channel string, devmode bool, auther store.Authenticator) (*snap.Info, error) {
revno := snap.R(11)
if channel == "channel-for-7" {
revno.N = 7
diff --git a/overlord/snapstate/check_snap.go b/overlord/snapstate/check_snap.go
index df2ad634b5..33aaeb6cda 100644
--- a/overlord/snapstate/check_snap.go
+++ b/overlord/snapstate/check_snap.go
@@ -24,6 +24,7 @@ import (
"strings"
"github.com/snapcore/snapd/arch"
+ "github.com/snapcore/snapd/firstboot"
"github.com/snapcore/snapd/overlord/snapstate/backend"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/release"
@@ -53,7 +54,7 @@ func checkAssumes(s *snap.Info) error {
var openSnapFile = backend.OpenSnapFile
// checkSnap ensures that the snap can be installed.
-func checkSnap(state *state.State, snapFilePath string, curInfo *snap.Info, flags Flags) error {
+func checkSnap(st *state.State, snapFilePath string, curInfo *snap.Info, flags Flags) error {
// XXX: actually verify snap before using content from it unless dev-mode
s, _, err := openSnapFile(snapFilePath, nil)
@@ -75,21 +76,28 @@ func checkSnap(state *state.State, snapFilePath string, curInfo *snap.Info, flag
if s.Type != snap.TypeGadget {
return nil
}
- state.Lock()
- defer state.Unlock()
- if currentGadget, err := GadgetInfo(state); err == nil {
- // TODO: actually compare snap ids, from current gadget and candidate
- if currentGadget.Name() == s.Name() {
- return nil
- }
-
- return fmt.Errorf("cannot replace gadget snap with a different one")
- } else if release.OnClassic {
+ // gadget specific checks
+ if release.OnClassic {
// for the time being
return fmt.Errorf("cannot install a gadget snap on classic")
}
- // there should always be a gadget snap on devices
- return fmt.Errorf("cannot find original gadget snap")
+ st.Lock()
+ defer st.Unlock()
+ currentGadget, err := GadgetInfo(st)
+ // in firstboot we have no gadget yet - that is ok
+ if err == state.ErrNoState && !firstboot.HasRun() {
+ return nil
+ }
+ if err != nil {
+ return fmt.Errorf("cannot find original gadget snap")
+ }
+
+ // TODO: actually compare snap ids, from current gadget and candidate
+ if currentGadget.Name() != s.Name() {
+ return fmt.Errorf("cannot replace gadget snap with a different one")
+ }
+
+ return nil
}
diff --git a/overlord/snapstate/check_snap_test.go b/overlord/snapstate/check_snap_test.go
index b848c9dbe9..8b8d55de13 100644
--- a/overlord/snapstate/check_snap_test.go
+++ b/overlord/snapstate/check_snap_test.go
@@ -21,6 +21,9 @@ package snapstate_test
import (
"fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
. "gopkg.in/check.v1"
@@ -107,6 +110,9 @@ assumes: [common-data-dir]`
}
func (s *checkSnapSuite) TestCheckSnapGadgetUpdate(c *C) {
+ reset := release.MockOnClassic(false)
+ defer reset()
+
st := state.New(nil)
st.Lock()
defer st.Unlock()
@@ -143,6 +149,9 @@ version: 2
}
func (s *checkSnapSuite) TestCheckSnapGadgetAdditionProhibited(c *C) {
+ reset := release.MockOnClassic(false)
+ defer reset()
+
st := state.New(nil)
st.Lock()
defer st.Unlock()
@@ -179,6 +188,11 @@ version: 2
}
func (s *checkSnapSuite) TestCheckSnapGadgetMissingPrior(c *C) {
+ err := os.MkdirAll(filepath.Dir(dirs.SnapFirstBootStamp), 0755)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(dirs.SnapFirstBootStamp, nil, 0644)
+ c.Assert(err, IsNil)
+
reset := release.MockOnClassic(false)
defer reset()
diff --git a/overlord/snapstate/prepare_snap_test.go b/overlord/snapstate/prepare_snap_test.go
new file mode 100644
index 0000000000..5275edb692
--- /dev/null
+++ b/overlord/snapstate/prepare_snap_test.go
@@ -0,0 +1,81 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016 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"
+
+ "github.com/snapcore/snapd/overlord/snapstate"
+ "github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/snap"
+)
+
+type prepareSnapSuite struct {
+ state *state.State
+ snapmgr *snapstate.SnapManager
+
+ fakeBackend *fakeSnappyBackend
+
+ reset func()
+}
+
+var _ = Suite(&prepareSnapSuite{})
+
+func (s *prepareSnapSuite) SetUpTest(c *C) {
+ s.fakeBackend = &fakeSnappyBackend{}
+ s.state = state.New(nil)
+
+ var err error
+ s.snapmgr, err = snapstate.Manager(s.state)
+ c.Assert(err, IsNil)
+ s.snapmgr.AddForeignTaskHandlers(s.fakeBackend)
+
+ snapstate.SetSnapManagerBackend(s.snapmgr, s.fakeBackend)
+
+ s.reset = snapstate.MockReadInfo(s.fakeBackend.ReadInfo)
+}
+
+func (s *prepareSnapSuite) TearDownTest(c *C) {
+ s.reset()
+}
+
+func (s *prepareSnapSuite) TestDoPrepareSnapSimple(c *C) {
+ s.state.Lock()
+ t := s.state.NewTask("prepare-snap", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ Name: "foo",
+ })
+ s.state.NewChange("dummy", "...").AddTask(t)
+
+ s.state.Unlock()
+
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+
+ s.state.Lock()
+ defer s.state.Unlock()
+ var snapst snapstate.SnapState
+ err := snapstate.Get(s.state, "foo", &snapst)
+ c.Assert(err, IsNil)
+ c.Check(snapst.Candidate, DeepEquals, &snap.SideInfo{
+ Revision: snap.R(-1),
+ })
+ c.Check(t.Status(), Equals, state.DoneStatus)
+}
diff --git a/overlord/snapstate/snapmgr.go b/overlord/snapstate/snapmgr.go
index fed1721338..eb7103d0fc 100644
--- a/overlord/snapstate/snapmgr.go
+++ b/overlord/snapstate/snapmgr.go
@@ -295,7 +295,7 @@ func (m *SnapManager) doDownloadSnap(t *state.Task, _ *tomb.Tomb) error {
auther = user.Authenticator()
}
- storeInfo, err := m.store.Snap(ss.Name, ss.Channel, auther)
+ storeInfo, err := m.store.Snap(ss.Name, ss.Channel, ss.DevMode(), auther)
if err != nil {
return err
}
diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go
index 05d8313d18..5cb4fd5517 100644
--- a/overlord/snapstate/snapstate.go
+++ b/overlord/snapstate/snapstate.go
@@ -57,28 +57,22 @@ const (
// 0x40000000 >> iota
)
-func doInstall(s *state.State, curActive bool, snapName, snapPath, channel string, userID int, flags Flags) (*state.TaskSet, error) {
- if err := checkChangeConflict(s, snapName); err != nil {
+func doInstall(s *state.State, curActive bool, ss *SnapSetup) (*state.TaskSet, error) {
+ if err := checkChangeConflict(s, ss.Name); err != nil {
return nil, err
}
- if snapPath == "" && channel == "" {
- channel = "stable"
+ if ss.SnapPath == "" && ss.Channel == "" {
+ ss.Channel = "stable"
}
var prepare *state.Task
- ss := SnapSetup{
- Channel: channel,
- UserID: userID,
- Flags: SnapSetupFlags(flags),
- }
- ss.Name = snapName
- ss.SnapPath = snapPath
- if snapPath != "" {
- prepare = s.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q"), snapPath))
+ if ss.SnapPath != "" {
+ prepare = s.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q"), ss.SnapPath))
} else {
- prepare = s.NewTask("download-snap", fmt.Sprintf(i18n.G("Download snap %q from channel %q"), snapName, channel))
+ prepare = s.NewTask("download-snap", fmt.Sprintf(i18n.G("Download snap %q from channel %q"), ss.Name, ss.Channel))
}
+
prepare.Set("snap-setup", ss)
tasks := []*state.Task{prepare}
@@ -88,31 +82,31 @@ func doInstall(s *state.State, curActive bool, snapName, snapPath, channel strin
}
// mount
- mount := s.NewTask("mount-snap", fmt.Sprintf(i18n.G("Mount snap %q"), snapName))
+ mount := s.NewTask("mount-snap", fmt.Sprintf(i18n.G("Mount snap %q"), ss.Name))
addTask(mount)
mount.WaitFor(prepare)
precopy := mount
if curActive {
// unlink-current-snap (will stop services for copy-data)
- unlink := s.NewTask("unlink-current-snap", fmt.Sprintf(i18n.G("Make current revision for snap %q unavailable"), snapName))
+ unlink := s.NewTask("unlink-current-snap", fmt.Sprintf(i18n.G("Make current revision for snap %q unavailable"), ss.Name))
addTask(unlink)
unlink.WaitFor(mount)
precopy = unlink
}
// copy-data (needs stopped services by unlink)
- copyData := s.NewTask("copy-snap-data", fmt.Sprintf(i18n.G("Copy snap %q data"), snapName))
+ copyData := s.NewTask("copy-snap-data", fmt.Sprintf(i18n.G("Copy snap %q data"), ss.Name))
addTask(copyData)
copyData.WaitFor(precopy)
// security
- setupSecurity := s.NewTask("setup-profiles", fmt.Sprintf(i18n.G("Setup snap %q security profiles"), snapName))
+ setupSecurity := s.NewTask("setup-profiles", fmt.Sprintf(i18n.G("Setup snap %q security profiles"), ss.Name))
addTask(setupSecurity)
setupSecurity.WaitFor(copyData)
// finalize (wrappers+current symlink)
- linkSnap := s.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q available to the system"), snapName))
+ linkSnap := s.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q available to the system"), ss.Name))
addTask(linkSnap)
linkSnap.WaitFor(setupSecurity)
@@ -148,7 +142,14 @@ func Install(s *state.State, name, channel string, userID int, flags Flags) (*st
return nil, fmt.Errorf("snap %q already installed", name)
}
- return doInstall(s, false, name, "", channel, userID, flags)
+ ss := &SnapSetup{
+ Name: name,
+ Channel: channel,
+ UserID: userID,
+ Flags: SnapSetupFlags(flags),
+ }
+
+ return doInstall(s, false, ss)
}
// InstallPath returns a set of tasks for installing snap from a file path.
@@ -160,7 +161,14 @@ func InstallPath(s *state.State, name, path, channel string, flags Flags) (*stat
return nil, err
}
- return doInstall(s, snapst.Active, name, path, channel, 0, flags)
+ ss := &SnapSetup{
+ Name: name,
+ SnapPath: path,
+ Channel: channel,
+ Flags: SnapSetupFlags(flags),
+ }
+
+ return doInstall(s, snapst.Active, ss)
}
// TryPath returns a set of tasks for trying a snap from a file path.
@@ -187,8 +195,14 @@ func Update(s *state.State, name, channel string, userID int, flags Flags) (*sta
channel = snapst.Channel
}
- // TODO: pass the right UserID
- return doInstall(s, snapst.Active, name, "", channel, userID, flags)
+ ss := &SnapSetup{
+ Name: name,
+ Channel: channel,
+ UserID: userID,
+ Flags: SnapSetupFlags(flags),
+ }
+
+ return doInstall(s, snapst.Active, ss)
}
func removeInactiveRevision(s *state.State, name string, revision snap.Revision) *state.TaskSet {
@@ -435,6 +449,9 @@ func GadgetInfo(s *state.State) (*snap.Info, error) {
return nil, err
}
for snapName, snapState := range stateMap {
+ if snapState.Current() == nil {
+ continue
+ }
snapInfo, err := readInfo(snapName, snapState.Current())
if err != nil {
logger.Noticef("cannot retrieve info for snap %q: %s", snapName, err)
diff --git a/snap/implicit.go b/snap/implicit.go
index 812b46df89..318a139ee7 100644
--- a/snap/implicit.go
+++ b/snap/implicit.go
@@ -53,6 +53,8 @@ var implicitClassicSlots = []string{
"unity7",
"x11",
"modem-manager",
+ "optical-drive",
+ "camera",
}
// AddImplicitSlots adds implicitly defined slots to a given snap.
diff --git a/snap/implicit_test.go b/snap/implicit_test.go
index b425d26225..75fb2d68ca 100644
--- a/snap/implicit_test.go
+++ b/snap/implicit_test.go
@@ -56,7 +56,7 @@ func (s *InfoSnapYamlTestSuite) TestAddImplicitSlotsOnClassic(c *C) {
c.Assert(info.Slots["unity7"].Interface, Equals, "unity7")
c.Assert(info.Slots["unity7"].Name, Equals, "unity7")
c.Assert(info.Slots["unity7"].Snap, Equals, info)
- c.Assert(info.Slots, HasLen, 22)
+ c.Assert(info.Slots, HasLen, 24)
}
func (s *InfoSnapYamlTestSuite) TestImplicitSlotsAreRealInterfaces(c *C) {
diff --git a/snap/info.go b/snap/info.go
index ddd08cedeb..2d4415c83e 100644
--- a/snap/info.go
+++ b/snap/info.go
@@ -68,6 +68,16 @@ func MountDir(name string, revision Revision) string {
return filepath.Join(dirs.SnapSnapsDir, name, revision.String())
}
+// AppSecurityTag returns the application-specific security tag.
+func AppSecurityTag(snapName, appName string) string {
+ return fmt.Sprintf("snap.%s.%s", snapName, appName)
+}
+
+// HookSecurityTag returns the hook-specific security tag.
+func HookSecurityTag(snapName, hookName string) string {
+ return fmt.Sprintf("snap.%s.hook.%s", snapName, hookName)
+}
+
// SideInfo holds snap metadata that is crucial for the tracking of
// snaps and for the working of the system offline and which is not
// included in snap.yaml or for which the store is the canonical
@@ -186,6 +196,11 @@ func (s *Info) CommonDataHomeDir() string {
return filepath.Join(dirs.SnapDataHomeGlob, s.Name(), "common")
}
+// NeedsDevMode retursn whether the snap needs devmode.
+func (s *Info) NeedsDevMode() bool {
+ return s.Confinement == DevmodeConfinement
+}
+
// sanity check that Info is a PlaceInfo
var _ PlaceInfo = (*Info)(nil)
@@ -253,7 +268,7 @@ type HookInfo struct {
// Security tags are used by various security subsystems as "profile names" and
// sometimes also as a part of the file name.
func (app *AppInfo) SecurityTag() string {
- return fmt.Sprintf("snap.%s.%s", app.Snap.Name(), app.Name)
+ return AppSecurityTag(app.Snap.Name(), app.Name)
}
// WrapperPath returns the path to wrapper invoking the app binary.
@@ -326,7 +341,7 @@ func (app *AppInfo) Env() []string {
// Security tags are used by various security subsystems as "profile names" and
// sometimes also as a part of the file name.
func (hook *HookInfo) SecurityTag() string {
- return fmt.Sprintf("snap.%s.hook.%s", hook.Snap.Name(), hook.Name)
+ return HookSecurityTag(hook.Snap.Name(), hook.Name)
}
func infoFromSnapYamlWithSideInfo(meta []byte, si *SideInfo) (*Info, error) {
diff --git a/snap/info_test.go b/snap/info_test.go
index 38fb2c5daf..642a8f4916 100644
--- a/snap/info_test.go
+++ b/snap/info_test.go
@@ -127,11 +127,8 @@ func (s *infoSuite) TestReadInfo(c *C) {
c.Check(snapInfo2, DeepEquals, snapInfo1)
}
+// makeTestSnap here can also be used to produce broken snaps (differently from snaptest.MakeTestSnapWithFiles)!
func makeTestSnap(c *C, yaml string) string {
- return makeTestSnapWithHooks(c, yaml, nil)
-}
-
-func makeTestSnapWithHooks(c *C, yaml string, hookNames []string) string {
tmp := c.MkDir()
snapSource := filepath.Join(tmp, "snapsrc")
@@ -141,18 +138,6 @@ func makeTestSnapWithHooks(c *C, yaml string, hookNames []string) string {
err = ioutil.WriteFile(filepath.Join(snapSource, "meta", "snap.yaml"), []byte(yaml), 0644)
c.Assert(err, IsNil)
- // make the requested hooks
- if len(hookNames) > 0 {
- hooksDir := filepath.Join(snapSource, "meta", "hooks")
- err := os.MkdirAll(filepath.Join(hooksDir), 0755)
- c.Assert(err, IsNil)
-
- for _, hookName := range hookNames {
- err = ioutil.WriteFile(filepath.Join(hooksDir, hookName), nil, 0644)
- c.Assert(err, IsNil)
- }
- }
-
dest := filepath.Join(tmp, "foo.snap")
snap := squashfs.New(dest)
err = snap.Build(snapSource)
@@ -161,6 +146,14 @@ func makeTestSnapWithHooks(c *C, yaml string, hookNames []string) string {
return dest
}
+// produce descrs for empty hooks suitable for snaptest.PopulateDir
+func emptyHooks(hookNames ...string) (emptyHooks [][]string) {
+ for _, hookName := range hookNames {
+ emptyHooks = append(emptyHooks, []string{filepath.Join("meta", "hooks", hookName), ""})
+ }
+ return
+}
+
func (s *infoSuite) TestReadInfoFromSnapFile(c *C) {
yaml := `name: foo
version: 1.0
@@ -349,7 +342,7 @@ hooks:
func (s *infoSuite) TestReadInfoFromSnapFileCatchesInvalidImplicitHook(c *C) {
yaml := `name: foo
version: 1.0`
- snapPath := makeTestSnapWithHooks(c, yaml, []string{"abc123"})
+ snapPath := snaptest.MakeTestSnapWithFiles(c, yaml, emptyHooks("abc123"))
snapf, err := snap.Open(snapPath)
c.Assert(err, IsNil)
@@ -361,13 +354,14 @@ version: 1.0`
func (s *infoSuite) checkInstalledSnapAndSnapFile(c *C, yaml string, hooks []string, checker func(c *C, info *snap.Info)) {
// First check installed snap
sideInfo := &snap.SideInfo{Revision: snap.R(42)}
- info := snaptest.MockSnapWithHooks(c, yaml, sideInfo, hooks)
- info, err := snap.ReadInfo(info.Name(), sideInfo)
+ info0 := snaptest.MockSnap(c, yaml, sideInfo)
+ snaptest.PopulateDir(info0.MountDir(), emptyHooks(hooks...))
+ info, err := snap.ReadInfo(info0.Name(), sideInfo)
c.Check(err, IsNil)
checker(c, info)
// Now check snap file
- snapPath := makeTestSnapWithHooks(c, yaml, hooks)
+ snapPath := snaptest.MakeTestSnapWithFiles(c, yaml, emptyHooks(hooks...))
snapf, err := snap.Open(snapPath)
c.Assert(err, IsNil)
info, err = snap.ReadInfoFromSnapFile(snapf, nil)
diff --git a/snap/snaptest/snaptest.go b/snap/snaptest/snaptest.go
index d27fcb65c3..b08df1ea8e 100644
--- a/snap/snaptest/snaptest.go
+++ b/snap/snaptest/snaptest.go
@@ -55,28 +55,6 @@ func MockSnap(c *check.C, yamlText string, sideInfo *snap.SideInfo) *snap.Info {
return snapInfo
}
-// MockSnapWithHooks puts a snap.yaml file on disk along with hooks so to mock an installed snap, based on the provided arguments.
-//
-// The caller is responsible for mocking root directory with dirs.SetRootDir()
-// and for altering the overlord state if required.
-func MockSnapWithHooks(c *check.C, yamlText string, sideInfo *snap.SideInfo, hookNames []string) *snap.Info {
- snapInfo := MockSnap(c, yamlText, sideInfo)
-
- // Now create the requested hooks (if any)
- if len(hookNames) > 0 {
- hooksDir := snapInfo.HooksDir()
- err := os.MkdirAll(filepath.Join(hooksDir), 0755)
- c.Assert(err, check.IsNil)
-
- for _, hookName := range hookNames {
- err = ioutil.WriteFile(filepath.Join(hooksDir, hookName), nil, 0644)
- c.Assert(err, check.IsNil)
- }
- }
-
- 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/squashfs/squashfs.go b/snap/squashfs/squashfs.go
index 4c9578aff5..062306b8df 100644
--- a/snap/squashfs/squashfs.go
+++ b/snap/squashfs/squashfs.go
@@ -72,6 +72,11 @@ func (s *Snap) Install(targetPath, mountDir string) error {
}
}
+ // nothing to do, happens on e.g. first-boot
+ if s.path == targetPath {
+ return nil
+ }
+
// FIXME: cp.CopyFile() has no preserve attribute flag yet
return runCommand("cp", "-a", s.path, targetPath)
}
diff --git a/snappy/errors.go b/snappy/errors.go
index bc78c199ca..0bd2fd96b5 100644
--- a/snappy/errors.go
+++ b/snappy/errors.go
@@ -95,10 +95,6 @@ var (
// accepting a license is required, but no license file is provided
ErrLicenseNotProvided = errors.New("snap.yaml requires license, but no license was provided")
- // ErrNotFirstBoot is an error that indicates that the first boot has already
- // run
- ErrNotFirstBoot = errors.New("this is not your first boot")
-
// ErrNotImplemented may be returned when an implementation of
// an interface is partial.
ErrNotImplemented = errors.New("not implemented")
diff --git a/snappy/firstboot.go b/snappy/firstboot.go
deleted file mode 100644
index aeee5c36a0..0000000000
--- a/snappy/firstboot.go
+++ /dev/null
@@ -1,163 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2014-2015 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 snappy
-
-import (
- "errors"
- "fmt"
- "os"
- "os/exec"
- "path/filepath"
-
- "github.com/snapcore/snapd/logger"
- "github.com/snapcore/snapd/osutil"
- "github.com/snapcore/snapd/progress"
-
- "gopkg.in/yaml.v2"
-)
-
-var (
- errNoSnapToConfig = errors.New("configuring an invalid snappy package")
- errNoSnapToActivate = errors.New("activating an invalid snappy package")
-)
-
-func wrapConfig(pkgName string, conf interface{}) ([]byte, error) {
- configWrap := map[string]map[string]interface{}{
- "config": map[string]interface{}{
- pkgName: conf,
- },
- }
-
- return yaml.Marshal(configWrap)
-}
-
-var newSnapMap = newSnapMapImpl
-
-func newSnapMapImpl() (map[string]*Snap, error) {
- all, err := (&Overlord{}).Installed()
- if err != nil {
- return nil, err
- }
-
- m := make(map[string]*Snap, 2*len(all))
- for _, snap := range all {
- info := snap.Info()
- m[FullName(info)] = snap
- m[BareName(info)] = snap
- }
-
- return m, nil
-}
-
-type activator interface {
- SetActive(sp *Snap, active bool, meter progress.Meter) error
-}
-
-var getActivator = func() activator {
- return &Overlord{}
-}
-
-// enableInstalledSnaps activates the installed preinstalled snaps
-// on the first boot
-func enableInstalledSnaps() error {
- all, err := (&Overlord{}).Installed()
- if err != nil {
- return nil
- }
-
- activator := getActivator()
- pb := progress.MakeProgressBar()
- for _, sn := range all {
- logger.Noticef("Acitvating %s", FullName(sn.Info()))
- if err := activator.SetActive(sn, true, pb); err != nil {
- // we don't want this to fail for now
- logger.Noticef("failed to activate %s: %s", FullName(sn.Info()), err)
- }
- }
-
- return nil
-}
-
-// FirstBoot checks whether it's the first boot, and if so enables the
-// first ethernet device and runs gadgetConfig (as well as flagging that
-// it run)
-func FirstBoot() error {
- if firstBootHasRun() {
- return ErrNotFirstBoot
- }
- defer stampFirstBoot()
- defer enableFirstEther()
-
- return enableInstalledSnaps()
-}
-
-// NOTE: if you change stampFile, update the condition in
-// snapd.firstboot.service to match
-var stampFile = "/var/lib/snapd/firstboot/stamp"
-
-func stampFirstBoot() error {
- // filepath.Dir instead of firstbootDir directly to ease testing
- stampDir := filepath.Dir(stampFile)
-
- if _, err := os.Stat(stampDir); os.IsNotExist(err) {
- if err := os.MkdirAll(stampDir, 0755); err != nil {
- return err
- }
- }
-
- return osutil.AtomicWriteFile(stampFile, []byte{}, 0644, 0)
-}
-
-var globs = []string{"/sys/class/net/eth*", "/sys/class/net/en*"}
-var ethdir = "/etc/network/interfaces.d"
-var ifup = "/sbin/ifup"
-
-func enableFirstEther() error {
- var eths []string
- for _, glob := range globs {
- eths, _ = filepath.Glob(glob)
- if len(eths) != 0 {
- break
- }
- }
- if len(eths) == 0 {
- return nil
- }
- eth := filepath.Base(eths[0])
- ethfile := filepath.Join(ethdir, eth)
- data := fmt.Sprintf("allow-hotplug %[1]s\niface %[1]s inet dhcp\n", eth)
-
- if err := osutil.AtomicWriteFile(ethfile, []byte(data), 0644, 0); err != nil {
- return err
- }
-
- ifup := exec.Command(ifup, eth)
- ifup.Stdout = os.Stdout
- ifup.Stderr = os.Stderr
- if err := ifup.Run(); err != nil {
- return err
- }
-
- return nil
-}
-
-func firstBootHasRun() bool {
- return osutil.FileExists(stampFile)
-}
diff --git a/snappy/firstboot_test.go b/snappy/firstboot_test.go
deleted file mode 100644
index 1d74750f1a..0000000000
--- a/snappy/firstboot_test.go
+++ /dev/null
@@ -1,169 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2015 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 snappy
-
-import (
- "io/ioutil"
- "os"
- "path/filepath"
-
- . "gopkg.in/check.v1"
-
- "github.com/snapcore/snapd/dirs"
- "github.com/snapcore/snapd/systemd"
-)
-
-type FirstBootTestSuite struct {
- gadgetConfig map[string]interface{}
- globs []string
- ethdir string
- ifup string
- e error
- snapMap map[string]*Snap
- snapMapErr error
-}
-
-var _ = Suite(&FirstBootTestSuite{})
-
-func (s *FirstBootTestSuite) SetUpTest(c *C) {
- tempdir := c.MkDir()
- dirs.SetRootDir(tempdir)
- os.MkdirAll(dirs.SnapSnapsDir, 0755)
- stampFile = filepath.Join(c.MkDir(), "stamp")
-
- // mock the world!
- systemd.SystemctlCmd = func(cmd ...string) ([]byte, error) {
- return []byte("ActiveState=inactive\n"), nil
- }
-
- err := os.MkdirAll(filepath.Join(tempdir, "etc", "systemd", "system", "multi-user.target.wants"), 0755)
- c.Assert(err, IsNil)
-
- s.globs = globs
- globs = nil
- s.ethdir = ethdir
- ethdir = c.MkDir()
- s.ifup = ifup
- ifup = "/bin/true"
- newSnapMap = s.newSnapMap
-
- s.e = nil
- s.snapMap = nil
- s.snapMapErr = nil
-}
-
-func (s *FirstBootTestSuite) TearDownTest(c *C) {
- globs = s.globs
- ethdir = s.ethdir
- ifup = s.ifup
- newSnapMap = newSnapMapImpl
-}
-
-func (s *FirstBootTestSuite) newSnapMap() (map[string]*Snap, error) {
- return s.snapMap, s.snapMapErr
-}
-
-func (s *FirstBootTestSuite) TestTwoRuns(c *C) {
- c.Assert(FirstBoot(), IsNil)
- _, err := os.Stat(stampFile)
- c.Assert(err, IsNil)
-
- c.Assert(FirstBoot(), Equals, ErrNotFirstBoot)
-}
-
-func (s *FirstBootTestSuite) TestNoErrorWhenNoGadget(c *C) {
- c.Assert(FirstBoot(), IsNil)
- _, err := os.Stat(stampFile)
- c.Assert(err, IsNil)
-}
-
-func (s *FirstBootTestSuite) TestEnableFirstEther(c *C) {
- c.Check(enableFirstEther(), IsNil)
- fs, _ := filepath.Glob(filepath.Join(ethdir, "*"))
- c.Assert(fs, HasLen, 0)
-}
-
-func (s *FirstBootTestSuite) TestEnableFirstEtherSomeEth(c *C) {
- dir := c.MkDir()
- _, err := os.Create(filepath.Join(dir, "eth42"))
- c.Assert(err, IsNil)
-
- globs = []string{filepath.Join(dir, "eth*")}
- c.Check(enableFirstEther(), IsNil)
- fs, _ := filepath.Glob(filepath.Join(ethdir, "*"))
- c.Assert(fs, HasLen, 1)
- bs, err := ioutil.ReadFile(fs[0])
- c.Assert(err, IsNil)
- c.Check(string(bs), Equals, "allow-hotplug eth42\niface eth42 inet dhcp\n")
-
-}
-
-func (s *FirstBootTestSuite) TestEnableFirstEtherBadEthDir(c *C) {
- dir := c.MkDir()
- _, err := os.Create(filepath.Join(dir, "eth42"))
- c.Assert(err, IsNil)
-
- ethdir = "/no/such/thing"
- globs = []string{filepath.Join(dir, "eth*")}
- err = enableFirstEther()
- c.Check(err, NotNil)
- c.Check(os.IsNotExist(err), Equals, true)
-}
-
-var mockOSYaml = `
-name: ubuntu-core
-version: 1.0
-type: os
-`
-
-var mockKernelYaml = `
-name: canonical-linux-pc
-version: 1.0
-type: kernel
-`
-
-func (s *FirstBootTestSuite) ensureSystemSnapIsEnabledOnFirstBoot(c *C, yaml string, expectActivated bool) {
- _, err := makeInstalledMockSnap(yaml, 11)
- c.Assert(err, IsNil)
-
- all, err := (&Overlord{}).Installed()
- c.Check(err, IsNil)
- c.Assert(all, HasLen, 1)
- c.Check(all[0].IsActive(), Equals, false)
-
- c.Assert(FirstBoot(), IsNil)
-
- all, err = (&Overlord{}).Installed()
- c.Check(err, IsNil)
- c.Assert(all, HasLen, 1)
- c.Check(all[0].IsActive(), Equals, expectActivated)
-}
-
-func (s *FirstBootTestSuite) TestSystemSnapsEnablesOS(c *C) {
- s.ensureSystemSnapIsEnabledOnFirstBoot(c, mockOSYaml, true)
-}
-
-func (s *FirstBootTestSuite) TestSystemSnapsEnablesKernel(c *C) {
- s.ensureSystemSnapIsEnabledOnFirstBoot(c, mockKernelYaml, true)
-}
-
-func (s *FirstBootTestSuite) TestSystemSnapsDoesEnableApps(c *C) {
- s.ensureSystemSnapIsEnabledOnFirstBoot(c, "", true)
-}
diff --git a/snappy/install.go b/snappy/install.go
index 328c3bf30d..8bc84d2b32 100644
--- a/snappy/install.go
+++ b/snappy/install.go
@@ -119,7 +119,9 @@ func doInstall(name, channel string, flags LegacyInstallFlags, meter progress.Me
return "", err
}
- snap, err := mStore.Snap(name, channel, nil)
+ // devmode false preserves the old behaviour but we might want
+ // it to be set from flags instead.
+ snap, err := mStore.Snap(name, channel, false, nil)
if err != nil {
return "", err
}
diff --git a/spread.yaml b/spread.yaml
index bed701af7e..9b8c69ace4 100644
--- a/spread.yaml
+++ b/spread.yaml
@@ -9,7 +9,8 @@ backends:
linode:
key: $(echo $SPREAD_LINODE_KEY)
systems:
- - ubuntu-16.04-grub
+ - ubuntu-16.04-64-grub
+ - ubuntu-16.04-32-grub
path: /gopath/src/github.com/snapcore/snapd
@@ -18,9 +19,15 @@ exclude:
prepare: |
[ "$REUSE_PROJECT" != 1 ] || exit 0
+
+ # apt update is hanging on security.ubuntu.com with IPv6.
+ sysctl -w net.ipv6.conf.all.disable_ipv6=1
+ trap "sysctl -w net.ipv6.conf.all.disable_ipv6=0" EXIT
+
apt purge -y snapd || true
apt update
apt build-dep -y ./
+
test -d /home/test || adduser --quiet --disabled-password --gecos '' test
chown test.test -R ..
sudo -i -u test /bin/sh -c "cd $PWD && DEB_BUILD_OPTIONS=nocheck dpkg-buildpackage -tc -b -Zgzip"
@@ -29,22 +36,41 @@ prepare: |
# Disable burst limit so resetting the state quickly doesn't create problems.
mkdir -p /etc/systemd/system/snapd.service.d
- echo "[Unit]\nStartLimitInterval=0" >> /etc/systemd/system/snapd.service.d/local.conf
+ cat <<EOF > /etc/systemd/system/snapd.service.d/local.conf
+ [Unit]
+ StartLimitInterval=0
+ [Service]
+ Environment=SNAPD_DEBUG_HTTP=7
+ EOF
- #snapbuild: get dependencies and build; we need to get the deps again because of the GOPATH redef
+ # Build snapbuild.
apt install -y git
- go get gopkg.in/check.v1 gopkg.in/yaml.v2
- go build -o $GOPATH/bin/snapbuild ./tests/lib/snapbuild
+ go get ./tests/lib/snapbuild
+
+ # Snapshot the state including core.
+ if [ ! -f snapd-state.tar.gz ]; then
+ ! snap list | grep core || exit 1
+ snap install hello-world
+ snap list | grep core
+ snap remove hello-world
+ rmdir /snap/hello-world # Should be done by snapd.
+
+ systemctl stop snapd
+ systemctl daemon-reload
+ mounts="$(systemctl list-unit-files | grep '^snap[-.].*\.mount' | cut -f1 -d ' ')"
+ services="$(systemctl list-unit-files | grep '^snap[-.].*\.service' | cut -f1 -d ' ')"
+ for unit in $services $mounts; do
+ systemctl stop $unit
+ done
+ tar czf snapd-state.tar.gz /var/lib/snapd /snap /etc/systemd/system/snap-*core*.mount
+ systemctl daemon-reload # Workaround for http://paste.ubuntu.com/17735820/
+ for unit in $mounts $services; do
+ systemctl start $unit
+ done
+ fi
suites:
tests/:
summary: Full-system tests for snapd
restore-each: |
- echo Resetting snapd state...
- systemctl stop snapd || true
- umount /var/lib/snapd/snaps/*.snap 2>&1 || true
- rm -rf /snap/*
- rm -rf /var/lib/snapd/*
- rm -f /etc/systemd/system/snap-*.{mount,service}
- rm -f /etc/systemd/system/multi-user.target.wants/snap-*.mount
- systemctl start snapd
+ $SPREAD_PATH/tests/lib/reset.sh --reuse-core
diff --git a/store/store.go b/store/store.go
index 51529d4a92..d53537345e 100644
--- a/store/store.go
+++ b/store/store.go
@@ -218,7 +218,7 @@ func NewUbuntuStoreSnapRepository(cfg *SnapUbuntuStoreConfig, storeID string) *S
}
// small helper that sets the correct http headers for the ubuntu store
-func (s *SnapUbuntuStoreRepository) setUbuntuStoreHeaders(req *http.Request, channel string, auther Authenticator) {
+func (s *SnapUbuntuStoreRepository) setUbuntuStoreHeaders(req *http.Request, channel string, devmode bool, auther Authenticator) {
if auther != nil {
auther.Authenticate(req)
}
@@ -232,6 +232,10 @@ func (s *SnapUbuntuStoreRepository) setUbuntuStoreHeaders(req *http.Request, cha
req.Header.Set("X-Ubuntu-Device-Channel", channel)
}
+ if devmode {
+ req.Header.Set("X-Ubuntu-Confinement", "devmode")
+ }
+
if s.storeID != "" {
req.Header.Set("X-Ubuntu-Store", s.storeID)
}
@@ -301,7 +305,7 @@ func (s *SnapUbuntuStoreRepository) getPurchasesFromURL(url *url.URL, channel st
return nil, err
}
- s.setUbuntuStoreHeaders(req, channel, auther)
+ s.setUbuntuStoreHeaders(req, channel, false, auther)
resp, err := s.client.Do(req)
if err != nil {
@@ -414,8 +418,7 @@ func mustBuy(prices map[string]float64, purchases []*purchase) bool {
}
// Snap returns the snap.Info for the store hosted snap with the given name or an error.
-func (s *SnapUbuntuStoreRepository) Snap(name, channel string, auther Authenticator) (*snap.Info, error) {
-
+func (s *SnapUbuntuStoreRepository) Snap(name, channel string, devmode bool, auther Authenticator) (*snap.Info, error) {
u := *s.searchURI // make a copy, so we can mutate it
q := u.Query()
@@ -429,7 +432,7 @@ func (s *SnapUbuntuStoreRepository) Snap(name, channel string, auther Authentica
}
// set headers
- s.setUbuntuStoreHeaders(req, channel, auther)
+ s.setUbuntuStoreHeaders(req, channel, devmode, auther)
resp, err := s.client.Do(req)
if err != nil {
@@ -498,7 +501,7 @@ func (s *SnapUbuntuStoreRepository) Find(searchTerm string, channel string, auth
}
// set headers
- s.setUbuntuStoreHeaders(req, channel, auther)
+ s.setUbuntuStoreHeaders(req, channel, false, auther)
resp, err := s.client.Do(req)
if err != nil {
@@ -616,7 +619,7 @@ func (s *SnapUbuntuStoreRepository) ListRefresh(installed []*RefreshCandidate, a
// set headers
// the updates call is a special snowflake right now
// (see LP: #1427155)
- s.setUbuntuStoreHeaders(req, "", auther)
+ s.setUbuntuStoreHeaders(req, "", false, auther)
resp, err := s.client.Do(req)
if err != nil {
@@ -672,7 +675,7 @@ func (s *SnapUbuntuStoreRepository) Download(remoteSnap *snap.Info, pbar progres
if err != nil {
return "", err
}
- s.setUbuntuStoreHeaders(req, "", auther)
+ s.setUbuntuStoreHeaders(req, "", remoteSnap.NeedsDevMode(), auther)
if err := download(remoteSnap.Name(), w, req, pbar); err != nil {
return "", err
diff --git a/store/store_test.go b/store/store_test.go
index 6ff660e97c..9787022b4e 100644
--- a/store/store_test.go
+++ b/store/store_test.go
@@ -172,15 +172,17 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryHeaders(c *C) {
req, err := http.NewRequest("GET", "http://example.com", nil)
c.Assert(err, IsNil)
- t.store.setUbuntuStoreHeaders(req, "", nil)
+ t.store.setUbuntuStoreHeaders(req, "", false, nil)
c.Check(req.Header.Get("X-Ubuntu-Release"), Equals, "16")
c.Check(req.Header.Get("X-Ubuntu-Device-Channel"), Equals, "")
+ c.Check(req.Header.Get("X-Ubuntu-Confinement"), Equals, "")
- t.store.setUbuntuStoreHeaders(req, "chan", nil)
+ t.store.setUbuntuStoreHeaders(req, "chan", true, nil)
c.Check(req.Header.Get("Authorization"), Equals, "")
c.Check(req.Header.Get("X-Ubuntu-Device-Channel"), Equals, "chan")
+ c.Check(req.Header.Get("X-Ubuntu-Confinement"), Equals, "devmode")
}
const (
@@ -308,6 +310,7 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryDetails(c *C) {
q := r.URL.Query()
c.Check(q.Get("q"), Equals, "package_name:\"hello-world\"")
c.Check(r.Header.Get("X-Ubuntu-Device-Channel"), Equals, "edge")
+ c.Check(r.Header.Get("X-Ubuntu-Confinement"), Equals, "devmode")
w.Header().Set("X-Suggested-Currency", "GBP")
w.WriteHeader(http.StatusOK)
@@ -327,7 +330,7 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryDetails(c *C) {
c.Assert(repo, NotNil)
// the actual test
- result, err := repo.Snap("hello-world", "edge", nil)
+ result, err := repo.Snap("hello-world", "edge", true, nil)
c.Assert(err, IsNil)
c.Check(result.Name(), Equals, "hello-world")
c.Check(result.Architectures, DeepEquals, []string{"all"})
@@ -388,7 +391,7 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryDetailsSetsAuth(c *C) {
c.Assert(repo, NotNil)
authenticator := &fakeAuthenticator{}
- snap, err := repo.Snap("hello-world", "edge", authenticator)
+ snap, err := repo.Snap("hello-world", "edge", false, authenticator)
c.Assert(snap, NotNil)
c.Assert(err, IsNil)
c.Check(snap.MustBuy, Equals, false)
@@ -420,7 +423,7 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryDetailsOopses(c *C) {
c.Assert(repo, NotNil)
// the actual test
- _, err = repo.Snap("hello-world", "edge", nil)
+ _, err = repo.Snap("hello-world", "edge", false, nil)
c.Assert(err, ErrorMatches, `Ubuntu CPI service returned unexpected HTTP status code 5.. while looking for snap "hello-world" in channel "edge" \[OOPS-[a-f0-9A-F]*\]`)
}
@@ -473,7 +476,7 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryNoDetails(c *C) {
c.Assert(repo, NotNil)
// the actual test
- result, err := repo.Snap("no-such-pkg", "edge", nil)
+ result, err := repo.Snap("no-such-pkg", "edge", false, nil)
c.Assert(err, NotNil)
c.Assert(result, IsNil)
}
@@ -1045,7 +1048,7 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositorySuggestedCurrency(c *C) {
c.Check(repo.SuggestedCurrency(), Equals, "USD")
// we should soon have a suggested currency
- result, err := repo.Snap("hello-world", "edge", nil)
+ result, err := repo.Snap("hello-world", "edge", false, nil)
c.Assert(err, IsNil)
c.Assert(result, NotNil)
c.Check(repo.SuggestedCurrency(), Equals, "GBP")
@@ -1053,7 +1056,7 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositorySuggestedCurrency(c *C) {
suggestedCurrency = "EUR"
// checking the currency updates
- result, err = repo.Snap("hello-world", "edge", nil)
+ result, err = repo.Snap("hello-world", "edge", false, nil)
c.Assert(err, IsNil)
c.Assert(result, NotNil)
c.Check(repo.SuggestedCurrency(), Equals, "EUR")
diff --git a/tests/abort/task.yaml b/tests/abort/task.yaml
index 516718d293..01b6176515 100644
--- a/tests/abort/task.yaml
+++ b/tests/abort/task.yaml
@@ -5,27 +5,27 @@ execute: |
echo "Abort with invalid id"
invalidID="10000000"
expected="error: cannot find change with id \"$invalidID\""
- actual=$(sudo snap abort $invalidID 2>&1) || EXPECTED_FAILED="abort-invalid"
+ actual=$(snap abort $invalidID 2>&1) || EXPECTED_FAILED="abort-invalid"
[ "$EXPECTED_FAILED" = "abort-invalid" ] || exit 1
echo "$actual" | grep -Pq "$expected" || exit 1
echo "Abort with valid id - error"
subdirPath="/snap/$SNAP_NAME/current/foo"
- sudo mkdir -p $subdirPath
+ mkdir -p $subdirPath
snap install $SNAP_NAME || EXPECTED_FAILED=install
[ "$EXPECTED_FAILED" = "install" ] || exit 1
id="1"
expected="error: cannot abort change $id with nothing pending"
- actual=$(sudo snap abort $id 2>&1) || EXPECTED_FAILED="abort-error"
+ actual=$(snap abort $id 2>&1) || EXPECTED_FAILED="abort-error"
[ "$EXPECTED_FAILED" = "abort-error" ] || exit 1
echo "$actual" | grep -Pq "$expected" || exit 1
- sudo rm -rf $subdirPath
+ rm -rf $subdirPath
echo "Abort with valid id - done"
- sudo snap install $SNAP_NAME
+ snap install $SNAP_NAME
id="2"
expected="error: cannot abort change $id with nothing pending"
- actual=$(sudo snap abort $id 2>&1) || EXPECTED_FAILED="abort-done"
+ actual=$(snap abort $id 2>&1) || EXPECTED_FAILED="abort-done"
[ "$EXPECTED_FAILED" = "abort-done" ] || exit 1
echo "$actual" | grep -Pq "$expected" || exit 1
diff --git a/tests/basics/task.yaml b/tests/basics/task.yaml
deleted file mode 100644
index f87109f34a..0000000000
--- a/tests/basics/task.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-summary: Check that basic interactions work
-execute: |
- echo Core is not there by default...
- snap list | grep core && exit 1
-
- echo Installing something pulls it...
- snap install hello-world
- snap list | grep core
-
- echo Test a few basic properties...
- hello-world.echo | grep Hello
- hello-world.env | grep SNAP_NAME=hello-world
- hello-world.evil && exit 1 || true
diff --git a/tests/gccgo/task.yaml b/tests/gccgo/task.yaml
index 68b221f48e..b023e0d0b4 100644
--- a/tests/gccgo/task.yaml
+++ b/tests/gccgo/task.yaml
@@ -1,14 +1,17 @@
summary: Check that snapd builds with gccgo
prepare: |
echo Installing gccgo-6 and pretending it is the default go
- sudo apt install -y gccgo-6
- sudo ln -s /usr/bin/go-6 /usr/local/bin/go
+ apt install -y gccgo-6
+ ln -s /usr/bin/go-6 /usr/local/bin/go
restore: |
rm -f /usr/local/bin/go
- sudo apt-get autoremove -y gccgo-6
+ apt-get autoremove -y gccgo-6
execute: |
echo Ensure we really build with gccgo
go version|grep gccgo
echo Build the deb with gccgo and run the tests as part of the build
sudo -i -u test /bin/sh -c "cd /gopath/src/github.com/snapcore/snapd && dpkg-buildpackage -tc -Zgzip"
+# Tests run during package build take a while.
+warn-timeout: 8m
+kill-timeout: 20m
diff --git a/tests/install-errors/task.yaml b/tests/install-errors/task.yaml
index 81ba770814..e58e1ebab1 100644
--- a/tests/install-errors/task.yaml
+++ b/tests/install-errors/task.yaml
@@ -1,4 +1,22 @@
summary: Checks for cli errors installing snaps
+environment:
+ SIDELOAD_SNAP_NAME: basic-binaries
+ STORE_SNAP_NAME: hello-world
+ SNAP_FILE: "./$[SIDELOAD_SNAP_NAME]_1.0_all.snap"
+
+prepare: |
+ echo "Given a snap with a failing command is installed"
+ snapbuild ../lib/snaps/$SIDELOAD_SNAP_NAME .
+ snap install $SNAP_FILE
+
+ echo "And a snap from the store is installed"
+ snap install $STORE_SNAP_NAME
+
+restore: |
+ snap remove $SIDELOAD_SNAP_NAME
+ snap remove $STORE_SNAP_NAME
+ rm -f $SNAP_FILE
+
execute: |
echo "Install unexisting snap prints error"
expected="(?s)error: cannot perform the following tasks:\n\
@@ -7,14 +25,36 @@ execute: |
[ "$EXPECTED_FAILURE" = "unexisting" ] || exit 1
echo "$actual" | grep -Pzq "$expected"
+ echo "============================================"
+
echo "Install without snap name shows error"
expected="(?s)error: the required argument \`<snap>\` was not provided\n"
actual=$(snap install 2>&1) || EXPECTED_FAILURE="nosnap"
[ "$EXPECTED_FAILURE" = "nosnap" ] || exit 1
echo "$actual" | grep -Pzq "$expected"
+ echo "============================================"
+
echo "Install points to login when not authenticated"
expected="snap login --help"
actual=$(sudo -i -u test /bin/sh -c "snap install hello-world 2>&1") || EXPECTED_FAILURE="unauthenticated"
[ "$EXPECTED_FAILURE" = "unauthenticated" ] || exit 1
echo "$actual" | grep -Pzq "$expected"
+
+ echo "============================================"
+
+ echo "When a failing command from a snap is called"
+ basic-binaries.fail || EXPECTED_FAILURE="command-failed"
+
+ echo "Then it must fail"
+ [ "$EXPECTED_FAILURE" = "command-failed" ] || exit 1
+
+ echo "============================================"
+
+ echo "When we try to install a snap already installed from the store"
+ snap install $STORE_SNAP_NAME || EXPECTED_FAILURE="install-failed"
+
+ echo "Then it must fail"
+ [ "$EXPECTED_FAILURE" = "install-failed" ] || exit 1
+
+ echo "============================================"
diff --git a/tests/install-sideload/task.yaml b/tests/install-sideload/task.yaml
index 722a5f9160..76e77e0e2c 100644
--- a/tests/install-sideload/task.yaml
+++ b/tests/install-sideload/task.yaml
@@ -2,12 +2,11 @@ summary: Checks for snap sideload install
prepare: |
for snap in basic basic-binaries basic-desktop
do
- snapbuild ./../fixtures/snaps/$snap .
+ snapbuild ../lib/snaps/$snap .
done
restore: |
for snap in basic basic-binaries basic-desktop
do
- sudo snap remove $snap
rm ./${snap}_1.0_all.snap
done
execute: |
@@ -15,16 +14,16 @@ execute: |
expected="(?s)Name +Version +Rev +Developer +Notes\n\
basic +.*? *\n\
.*"
- actual=$(sudo snap install ./basic_1.0_all.snap)
+ actual=$(snap install ./basic_1.0_all.snap)
echo "$actual" | grep -Pzq "$expected" || exit 1
echo "Sideloaded snap executes commands"
- sudo snap install ./basic-binaries_1.0_all.snap
+ snap install ./basic-binaries_1.0_all.snap
basic-binaries.success
[ "$(basic-binaries.echo)" = "From basic-binaries snap" ] || exit 1
echo "Sideload desktop snap"
- sudo snap install ./basic-desktop_1.0_all.snap
+ snap install ./basic-desktop_1.0_all.snap
expected="\[Desktop Entry\]\n\
Name=Echo\n\
Comment=It echos stuff\n\
diff --git a/tests/install-store/task.yaml b/tests/install-store/task.yaml
index 9b24b8f6bf..c7ee93a044 100644
--- a/tests/install-store/task.yaml
+++ b/tests/install-store/task.yaml
@@ -1,19 +1,36 @@
summary: Checks for special cases of snap install from the store
environment:
SNAP_NAME: hello-world
+ DEVMODE_SNAP: devmode-world
execute: |
echo "Install from different channels"
expected="(?s)Name +Version +Rev +Developer +Notes\n\
$SNAP_NAME .*? canonical +-\n"
for channel in edge beta candidate stable
do
- actual=$(sudo snap install $SNAP_NAME --channel=$channel)
+ actual=$(snap install $SNAP_NAME --channel=$channel)
echo "$actual" | grep -Pzq "$expected" || exit 1
- sudo snap remove $SNAP_NAME
+ snap remove $SNAP_NAME
done
- echo "Install with devmode option"
+ echo "Install non-devmode snap with devmode option"
expected="(?s)Name +Version +Rev +Developer +Notes\n\
$SNAP_NAME .*? canonical +devmode\n"
- actual=$(sudo snap install $SNAP_NAME --devmode)
+ actual=$(snap install $SNAP_NAME --devmode)
echo "$actual" | grep -Pzq "$expected" || exit 1
+
+ echo "Install devmode snap without devmode option"
+ expected="snap not found"
+ actual=$(snap install --channel beta $DEVMODE_SNAP 2>&1 && exit 1 || true)
+ echo "$actual" | grep -Pzq "$expected"
+
+ echo "Install devmode snap from stable"
+ expected="snap not found"
+ actual=$(snap install --devmode $DEVMODE_SNAP 2>&1 && exit 1 || true)
+ echo "$actual" | grep -Pzq "$expected"
+
+ echo "Install devmode snap from beta with devmode option"
+ expected="(?s)Name +Version +Rev +Developer +Notes\n\
+ $DEVMODE_SNAP .* +devmode"
+ actual=$(snap install --channel beta --devmode $DEVMODE_SNAP)
+ echo "$actual" | grep -Pzq "$expected"
diff --git a/tests/interfaces-cli/task.yaml b/tests/interfaces-cli/task.yaml
new file mode 100644
index 0000000000..38d069f966
--- /dev/null
+++ b/tests/interfaces-cli/task.yaml
@@ -0,0 +1,34 @@
+summary: Check the interfaces command
+
+environment:
+ SNAP_NAME: network-consumer
+ SNAP_FILE: "./$[SNAP_NAME]_1.0_all.snap"
+ PLUG: network
+
+prepare: |
+ echo "Given a snap with the $PLUG plug is installed"
+ snapbuild ../lib/snaps/$SNAP_NAME .
+ snap install $SNAP_FILE
+
+restore: |
+ rm -f $SNAP_FILE
+
+execute: |
+ expected="(?s)Slot +Plug\n\
+ :$PLUG +$SNAP_NAME"
+
+ echo "When the interfaces list is restricted by slot"
+ actual=$(snap interfaces -i $PLUG)
+
+ echo "Then only the requested slots are shown"
+ echo "$actual" | grep -Pzq "$expected"
+
+ echo "==============================================="
+
+ echo "When the interfaces list is restricted by slot and snap"
+ actual=$(snap interfaces -i $PLUG $SNAP_NAME)
+
+ echo "Then only the requested slots are shown"
+ echo "$actual" | grep -Pzq "$expected"
+
+ echo "==============================================="
diff --git a/tests/interfaces-log-observe/task.yaml b/tests/interfaces-log-observe/task.yaml
new file mode 100644
index 0000000000..d86f32035a
--- /dev/null
+++ b/tests/interfaces-log-observe/task.yaml
@@ -0,0 +1,66 @@
+summary: |
+
+ The log-observe interface allows a snap to read system logs and set kernel
+ log rate-limiting.
+
+ A snap which defines the log-observe 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.
+
+environment:
+ SNAP_NAME: log-observe-consumer
+ SNAP_FILE: "./$[SNAP_NAME]_1.0_all.snap"
+ PLUG: log-observe
+
+kill-wait: 1m
+
+prepare: |
+ echo "Given a snap declaring the $PLUG plug is installed"
+ snapbuild ../lib/snaps/$SNAP_NAME .
+ snap install $SNAP_FILE
+
+restore: |
+ rm -f $SNAP_FILE
+
+execute: |
+ CONNECTED_PATTERN="(?s)Slot +Plug\n\
+ .*?\n\
+ :$PLUG +$SNAP_NAME"
+ DISCONNECTED_PATTERN="(?s)Slot +Plug\n\
+ .*?\n\
+ - +$SNAP_NAME:$PLUG"
+
+ echo "Then the snap is not listed as connected"
+ echo "$(snap interfaces)" | grep -Pzq "$DISCONNECTED_PATTERN"
+
+ echo "============================================"
+
+ echo "When the plug is connected"
+ snap connect $SNAP_NAME:$PLUG ubuntu-core:$PLUG
+ echo "$(snap interfaces)" | grep -Pzq "$CONNECTED_PATTERN"
+
+ echo "Then the plug can be disconnected again"
+ snap disconnect $SNAP_NAME:$PLUG ubuntu-core:$PLUG
+ echo "$(snap interfaces)" | grep -Pzq "$DISCONNECTED_PATTERN"
+
+ echo "============================================"
+
+ echo "When the plug is connected"
+ snap connect $SNAP_NAME:$PLUG ubuntu-core:$PLUG
+ echo "$(snap interfaces)" | grep -Pzq "$CONNECTED_PATTERN"
+
+ echo "Then the snap is able to access the system logs"
+ response=$(log-observe-consumer)
+ echo "$response" | grep -Pqz "ok\n"
+
+ echo "============================================"
+
+ echo "When the plug is disconnected"
+ snap disconnect $SNAP_NAME:$PLUG ubuntu-core:$PLUG
+ echo "$(snap interfaces)" | grep -Pzq "$DISCONNECTED_PATTERN"
+
+ echo "Then snap can't access the system logs"
+ if log-observe-consumer; then
+ echo "System log shouldn't be accessible" && exit 1
+ fi
+ echo "============================================"
diff --git a/tests/interfaces-network-bind/task.yaml b/tests/interfaces-network-bind/task.yaml
new file mode 100644
index 0000000000..9cb054f09e
--- /dev/null
+++ b/tests/interfaces-network-bind/task.yaml
@@ -0,0 +1,78 @@
+summary: Ensure that the network-bind interface works
+
+details: |
+ The network-bind interface allows a daemon to access the network as a server.
+
+ A snap which defines the network-bind plug must be shown in the interfaces list.
+ The plug must be autoconnected on install and, as usual, must be able to be
+ reconnected.
+
+ A snap declaring a plug on this interface must be accessible by a network client.
+
+environment:
+ SNAP_NAME: network-bind-consumer
+ SNAP_FILE: ./$[SNAP_NAME]_1.0_all.snap
+ PORT: 8081
+ REQUEST_FILE: ./request.txt
+
+prepare: |
+ echo "Given a snap declaring the network-bind plug is installed"
+ snapbuild ../lib/snaps/$SNAP_NAME .
+ snap install $SNAP_FILE
+
+ echo "Given the snap's service is listening"
+ while ! netstat -lnt | grep -Pq "tcp.*?:$PORT +.*?LISTEN\n*"; do sleep 0.5; done
+
+ echo "Given we store a basic HTTP request"
+ cat > $REQUEST_FILE <<EOF
+ GET / HTTP/1.0
+
+ EOF
+
+restore: |
+ echo "FIXME: once the state is properly reset in the upper levels this remove can go away"
+ snap remove $SNAP_NAME
+ rm -f $SNAP_FILE $REQUEST_FILE
+
+execute: |
+ CONNECTED_PATTERN="(?s)Slot +Plug\n\
+ .*?\n\
+ :network-bind +$SNAP_NAME"
+ DISCONNECTED_PATTERN="(?s)Slot +Plug\n\
+ .*?\n\
+ - +$SNAP_NAME:network-bind"
+
+ echo "Then the snap is listed as connected"
+ echo "$(snap interfaces)" | grep -Pzq "$CONNECTED_PATTERN"
+
+ echo "============================================"
+
+ echo "When the plug is disconnected"
+ snap disconnect $SNAP_NAME:network-bind ubuntu-core:network-bind
+ echo "$(snap interfaces)" | grep -Pzq "$DISCONNECTED_PATTERN"
+
+ echo "Then the plug can be connected again"
+ snap connect $SNAP_NAME:network-bind ubuntu-core:network-bind
+ echo "$(snap interfaces)" | grep -Pzq "$CONNECTED_PATTERN"
+
+ echo "============================================"
+
+ echo "When the plug is connected"
+ snap connect $SNAP_NAME:network-bind ubuntu-core:network-bind
+ echo "$(snap interfaces)" | grep -Pzq "$CONNECTED_PATTERN"
+
+ echo "Then the service is accessible by a client"
+ response=$(nc -w 2 localhost "$PORT" < $REQUEST_FILE)
+ echo "$response" | grep -Pqz "ok\n"
+
+ echo "============================================"
+
+ echo "When the plug is disconnected"
+ snap disconnect $SNAP_NAME:network-bind ubuntu-core:network-bind
+ echo "$(snap interfaces)" | grep -Pzq "$DISCONNECTED_PATTERN"
+
+ echo "Then the service is not accessible by a client"
+ response=$(nc -w 2 localhost "$PORT" < $REQUEST_FILE)
+ [ "$response" = "" ] || exit 1
+
+ echo "============================================"
diff --git a/tests/interfaces-network/task.yaml b/tests/interfaces-network/task.yaml
new file mode 100644
index 0000000000..2511839e7a
--- /dev/null
+++ b/tests/interfaces-network/task.yaml
@@ -0,0 +1,76 @@
+summary: Ensure network interface works.
+
+details: |
+ The network interface allows a snap to access the network as a client.
+
+ A snap which defines the network plug must be shown in the interfaces list.
+ The plug must 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 access network services.
+
+environment:
+ SNAP_NAME: network-consumer
+ SNAP_FILE: "./$[SNAP_NAME]_1.0_all.snap"
+ PORT: 8081
+ SERVICE_FILE: "./service.sh"
+ SERVICE_NAME: "test-service"
+
+prepare: |
+ echo "Given a snap declaring the network plug is installed"
+ snapbuild ../lib/snaps/$SNAP_NAME .
+ snap install $SNAP_FILE
+
+ echo "And a service is listening"
+ echo "#!/bin/sh\nwhile true; do echo \"HTTP/1.1 200 OK\n\nok\n\" | nc -l -p $PORT -q 1; done" > $SERVICE_FILE
+ chmod a+x $SERVICE_FILE
+ systemd-run --unit $SERVICE_NAME $SERVICE_FILE
+ while ! netstat -lnt | grep -Pq "tcp.*?:$PORT +.*?LISTEN\n*"; do sleep 0.5; done
+
+restore: |
+ systemctl stop $SERVICE_NAME
+ rm -f $SNAP_FILE $SERVICE_FILE
+
+execute: |
+ CONNECTED_PATTERN="(?s)Slot +Plug\n\
+ .*?\n\
+ :network +$SNAP_NAME"
+ DISCONNECTED_PATTERN="(?s)Slot +Plug\n\
+ .*?\n\
+ - +$SNAP_NAME:network"
+
+ echo "Then the snap is listed as connected"
+ echo "$(snap interfaces)" | grep -Pzq "$CONNECTED_PATTERN"
+
+ echo "============================================"
+
+ echo "When the plug is disconnected"
+ snap disconnect $SNAP_NAME:network ubuntu-core:network
+ echo "$(snap interfaces)" | grep -Pzq "$DISCONNECTED_PATTERN"
+
+ echo "Then the plug can be connected again"
+ snap connect $SNAP_NAME:network ubuntu-core:network
+ echo "$(snap interfaces)" | grep -Pzq "$CONNECTED_PATTERN"
+
+ echo "============================================"
+
+ echo "When the plug is connected"
+ snap connect $SNAP_NAME:network ubuntu-core:network
+ echo "$(snap interfaces)" | grep -Pzq "$CONNECTED_PATTERN"
+
+ echo "Then the snap is able to access a network service"
+ response=$(network-consumer http://127.0.0.1:$PORT)
+ echo "$response" | grep -Pqz "ok\n"
+
+ echo "============================================"
+
+ echo "When the plug is disconnected"
+ snap disconnect $SNAP_NAME:network ubuntu-core:network
+ echo "$(snap interfaces)" | grep -Pzq "$DISCONNECTED_PATTERN"
+
+ echo "Then snap can't access a network service"
+ if network-consumer http://127.0.0.1:$PORT; then
+ echo "Network shouldn't be accessible" && exit 1
+ fi
+
+ echo "============================================"
diff --git a/tests/lib/reset.sh b/tests/lib/reset.sh
new file mode 100755
index 0000000000..e6298833d8
--- /dev/null
+++ b/tests/lib/reset.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+set -e -x
+
+systemctl stop snapd || true
+mounts="$(systemctl list-unit-files | grep '^snap[-.].*\.mount' | cut -f1 -d ' ')"
+services="$(systemctl list-unit-files | grep '^snap[-.].*\.service' | cut -f1 -d ' ')"
+for unit in $services $mounts; do
+ systemctl stop $unit || true
+done
+
+rm -f /tmp/ubuntu-core*
+rm -rf /snap/*
+rm -rf /var/lib/snapd/*
+rm -f /etc/systemd/system/snap[-.]*.{mount,service}
+rm -f /etc/systemd/system/multi-user.target.wants/snap[-.]*.{mount,service}
+
+if [ "$1" = "--reuse-core" ]; then
+ $(cd / && tar xzf $SPREAD_PATH/snapd-state.tar.gz)
+ mounts="$(systemctl list-unit-files | grep '^snap[-.].*\.mount' | cut -f1 -d ' ')"
+ services="$(systemctl list-unit-files | grep '^snap[-.].*\.service' | cut -f1 -d ' ')"
+ systemctl daemon-reload # Workaround for http://paste.ubuntu.com/17735820/
+ for unit in $mounts $services; do
+ systemctl start $unit
+ done
+fi
+systemctl start snapd
diff --git a/tests/fixtures/snaps/basic-binaries/bin/block b/tests/lib/snaps/basic-binaries/bin/block
index 2c68e4ef68..2c68e4ef68 100755
--- a/tests/fixtures/snaps/basic-binaries/bin/block
+++ b/tests/lib/snaps/basic-binaries/bin/block
diff --git a/tests/fixtures/snaps/basic-binaries/bin/cat b/tests/lib/snaps/basic-binaries/bin/cat
index a5ce91c395..a5ce91c395 100755
--- a/tests/fixtures/snaps/basic-binaries/bin/cat
+++ b/tests/lib/snaps/basic-binaries/bin/cat
diff --git a/tests/fixtures/snaps/basic-binaries/bin/echo b/tests/lib/snaps/basic-binaries/bin/echo
index c9ed1b045d..c9ed1b045d 100755
--- a/tests/fixtures/snaps/basic-binaries/bin/echo
+++ b/tests/lib/snaps/basic-binaries/bin/echo
diff --git a/tests/fixtures/snaps/basic-binaries/bin/fail b/tests/lib/snaps/basic-binaries/bin/fail
index 2bb8d868bd..2bb8d868bd 100755
--- a/tests/fixtures/snaps/basic-binaries/bin/fail
+++ b/tests/lib/snaps/basic-binaries/bin/fail
diff --git a/tests/fixtures/snaps/basic-binaries/bin/success b/tests/lib/snaps/basic-binaries/bin/success
index c52d3c26b3..c52d3c26b3 100755
--- a/tests/fixtures/snaps/basic-binaries/bin/success
+++ b/tests/lib/snaps/basic-binaries/bin/success
diff --git a/tests/fixtures/snaps/basic-binaries/meta/icon.png b/tests/lib/snaps/basic-binaries/meta/icon.png
index 1ec92f1241..1ec92f1241 100644
--- a/tests/fixtures/snaps/basic-binaries/meta/icon.png
+++ b/tests/lib/snaps/basic-binaries/meta/icon.png
Binary files differ
diff --git a/tests/fixtures/snaps/basic-binaries/meta/snap.yaml b/tests/lib/snaps/basic-binaries/meta/snap.yaml
index 437d0b8479..437d0b8479 100644
--- a/tests/fixtures/snaps/basic-binaries/meta/snap.yaml
+++ b/tests/lib/snaps/basic-binaries/meta/snap.yaml
diff --git a/tests/fixtures/snaps/basic-desktop/bin/echo b/tests/lib/snaps/basic-desktop/bin/echo
index ce4b3443f8..ce4b3443f8 100755
--- a/tests/fixtures/snaps/basic-desktop/bin/echo
+++ b/tests/lib/snaps/basic-desktop/bin/echo
diff --git a/tests/fixtures/snaps/basic-desktop/meta/gui/echo.desktop b/tests/lib/snaps/basic-desktop/meta/gui/echo.desktop
index 932e2e5ddb..932e2e5ddb 100644
--- a/tests/fixtures/snaps/basic-desktop/meta/gui/echo.desktop
+++ b/tests/lib/snaps/basic-desktop/meta/gui/echo.desktop
diff --git a/tests/fixtures/snaps/basic-desktop/meta/gui/icon.png b/tests/lib/snaps/basic-desktop/meta/gui/icon.png
index 1ec92f1241..1ec92f1241 100644
--- a/tests/fixtures/snaps/basic-desktop/meta/gui/icon.png
+++ b/tests/lib/snaps/basic-desktop/meta/gui/icon.png
Binary files differ
diff --git a/tests/fixtures/snaps/basic-desktop/meta/snap.yaml b/tests/lib/snaps/basic-desktop/meta/snap.yaml
index 0f9ebc962a..0f9ebc962a 100644
--- a/tests/fixtures/snaps/basic-desktop/meta/snap.yaml
+++ b/tests/lib/snaps/basic-desktop/meta/snap.yaml
diff --git a/tests/lib/snaps/basic-hooks/meta/hooks/install b/tests/lib/snaps/basic-hooks/meta/hooks/install
new file mode 100755
index 0000000000..69b2f9c96b
--- /dev/null
+++ b/tests/lib/snaps/basic-hooks/meta/hooks/install
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo "I'm the install hook"
diff --git a/tests/lib/snaps/basic-hooks/meta/hooks/upgrade b/tests/lib/snaps/basic-hooks/meta/hooks/upgrade
new file mode 100755
index 0000000000..d2ce0c6440
--- /dev/null
+++ b/tests/lib/snaps/basic-hooks/meta/hooks/upgrade
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo "I'm the upgrade hook"
diff --git a/tests/fixtures/snaps/basic/meta/icon.png b/tests/lib/snaps/basic-hooks/meta/icon.png
index 1ec92f1241..1ec92f1241 100644
--- a/tests/fixtures/snaps/basic/meta/icon.png
+++ b/tests/lib/snaps/basic-hooks/meta/icon.png
Binary files differ
diff --git a/tests/lib/snaps/basic-hooks/meta/snap.yaml b/tests/lib/snaps/basic-hooks/meta/snap.yaml
new file mode 100644
index 0000000000..0bb50350b5
--- /dev/null
+++ b/tests/lib/snaps/basic-hooks/meta/snap.yaml
@@ -0,0 +1,2 @@
+name: basic-hooks
+version: 1.0
diff --git a/tests/lib/snaps/basic/meta/icon.png b/tests/lib/snaps/basic/meta/icon.png
new file mode 100644
index 0000000000..1ec92f1241
--- /dev/null
+++ b/tests/lib/snaps/basic/meta/icon.png
Binary files differ
diff --git a/tests/fixtures/snaps/basic/meta/snap.yaml b/tests/lib/snaps/basic/meta/snap.yaml
index 7664ad2283..7664ad2283 100644
--- a/tests/fixtures/snaps/basic/meta/snap.yaml
+++ b/tests/lib/snaps/basic/meta/snap.yaml
diff --git a/integration-tests/data/snaps/log-observe-consumer/bin/consumer b/tests/lib/snaps/log-observe-consumer/bin/consumer
index a9f42be9af..3bfc478f3c 100755
--- a/integration-tests/data/snaps/log-observe-consumer/bin/consumer
+++ b/tests/lib/snaps/log-observe-consumer/bin/consumer
@@ -9,6 +9,7 @@ def run():
print("ok")
except Exception as e:
print("error accessing log")
+ raise
if __name__ == '__main__':
sys.exit(run())
diff --git a/integration-tests/data/snaps/log-observe-consumer/meta/snap.yaml b/tests/lib/snaps/log-observe-consumer/meta/snap.yaml
index 600ed778a6..600ed778a6 100644
--- a/integration-tests/data/snaps/log-observe-consumer/meta/snap.yaml
+++ b/tests/lib/snaps/log-observe-consumer/meta/snap.yaml
diff --git a/tests/lib/snaps/network-bind-consumer/bin/consumer b/tests/lib/snaps/network-bind-consumer/bin/consumer
new file mode 100755
index 0000000000..b1e5b290e2
--- /dev/null
+++ b/tests/lib/snaps/network-bind-consumer/bin/consumer
@@ -0,0 +1,22 @@
+#!/usr/bin/env python3
+
+import sys
+from http.server import BaseHTTPRequestHandler, HTTPServer
+
+class testRequestHandler(BaseHTTPRequestHandler):
+ def do_GET(self):
+ self.send_response(200)
+
+ self.send_header('Content-type', 'text/html')
+ self.end_headers()
+
+ message = b"<!doctype html>ok\n"
+ self.wfile.write(message)
+
+def run():
+ server_address = ('localhost', 8081)
+ httpd = HTTPServer(server_address, testRequestHandler)
+ httpd.serve_forever()
+
+if __name__ == '__main__':
+ sys.exit(run())
diff --git a/tests/lib/snaps/network-bind-consumer/meta/snap.yaml b/tests/lib/snaps/network-bind-consumer/meta/snap.yaml
new file mode 100644
index 0000000000..8019de827d
--- /dev/null
+++ b/tests/lib/snaps/network-bind-consumer/meta/snap.yaml
@@ -0,0 +1,10 @@
+name: network-bind-consumer
+version: 1.0
+summary: Basic network-bind consumer snap
+description: A basic snap declaring a plug on network-bind
+
+apps:
+ network-consumer:
+ command: bin/consumer
+ daemon: simple
+ plugs: [network-bind]
diff --git a/tests/lib/snaps/network-consumer/bin/consumer b/tests/lib/snaps/network-consumer/bin/consumer
new file mode 100755
index 0000000000..b09be979ba
--- /dev/null
+++ b/tests/lib/snaps/network-consumer/bin/consumer
@@ -0,0 +1,21 @@
+#! /usr/bin/env python3
+
+import sys
+from socket import timeout
+import urllib.request
+
+if len(sys.argv) > 1:
+ url = sys.argv[1]
+else:
+ url = 'http://www.ubuntu.com'
+
+try:
+ response = urllib.request.urlopen(url, timeout=3)
+ decoded_response = response.read().decode('utf-8')
+ print(decoded_response, end="")
+except urllib.error.URLError as e:
+ print("Error, reason: ", e.reason)
+ sys.exit(1)
+except timeout:
+ print("request timeout")
+ sys.exit(1)
diff --git a/tests/lib/snaps/network-consumer/meta/snap.yaml b/tests/lib/snaps/network-consumer/meta/snap.yaml
new file mode 100644
index 0000000000..de97ced217
--- /dev/null
+++ b/tests/lib/snaps/network-consumer/meta/snap.yaml
@@ -0,0 +1,9 @@
+name: network-consumer
+version: 1.0
+summary: Basic network consumer snap
+description: A basic snap declaring a plug on network
+
+apps:
+ network-consumer:
+ command: bin/consumer
+ plugs: [network]
diff --git a/tests/searching/task.yaml b/tests/searching/task.yaml
index 3f3edc2a18..2928c19ad9 100644
--- a/tests/searching/task.yaml
+++ b/tests/searching/task.yaml
@@ -5,13 +5,13 @@ execute: |
.*?\
hello-world +.*? *\n\
.*?\
- ubuntu-clock-app +.*? *\n\
+ xkcd-webserver +.*? *\n\
.*"
actual=$(snap find)
echo "$actual" | grep -Pzq "$expected" || exit 1
echo "Exact matches"
- for snapName in hello-world ubuntu-clock-app
+ for snapName in hello-world xkcd-webserver
do
expected="(?s)Name +Version +Developer +Notes +Summary *\n\
.*?\n\
diff --git a/tests/security-profiles/task.yaml b/tests/security-profiles/task.yaml
new file mode 100644
index 0000000000..373f8326e7
--- /dev/null
+++ b/tests/security-profiles/task.yaml
@@ -0,0 +1,33 @@
+summary: Check security profile generation for apps and hooks.
+prepare: |
+ for snap in basic-binaries basic-hooks
+ do
+ snapbuild ../lib/snaps/$snap .
+ done
+restore: |
+ for snap in basic-binaries basic-hooks
+ do
+ rm ${snap}_1.0_all.snap
+ done
+execute: |
+ seccomp_profile_directory="/var/lib/snapd/seccomp/profiles"
+
+ echo "Security profiles are generated and loaded for apps"
+ snap install basic-binaries_1.0_all.snap
+ loaded_profiles=$(cat /sys/kernel/security/apparmor/profiles)
+
+ for profile in snap.basic-binaries.block snap.basic-binaries.cat snap.basic-binaries.echo snap.basic-binaries.fail snap.basic-binaries.success
+ do
+ echo "$loaded_profiles" | grep -zq "$profile (enforce)" || exit 1
+ [ -f "$seccomp_profile_directory/$profile" ] || exit 1
+ done
+
+ echo "Security profiles are generated and loaded for hooks"
+ snap install basic-hooks_1.0_all.snap
+ loaded_profiles=$(cat /sys/kernel/security/apparmor/profiles)
+
+ for profile in snap.basic-hooks.hook.install snap.basic-hooks.hook.upgrade
+ do
+ echo "$loaded_profiles" | grep -zq "$profile (enforce)" || exit 1
+ [ -f "$seccomp_profile_directory/$profile" ] || exit 1
+ done
diff --git a/tests/server-snap/task.yaml b/tests/server-snap/task.yaml
index 2eaca8a11a..99c61b7a68 100644
--- a/tests/server-snap/task.yaml
+++ b/tests/server-snap/task.yaml
@@ -8,9 +8,9 @@ environment:
IP_VERSION/goServer: 6
PORT/goServer: 8081
TEXT/goServer: Hello World
-kill-timeout: 1m
+warn-timeout: 3m
prepare: |
- sudo snap install $SNAP_NAME
+ snap install $SNAP_NAME
cat > request.txt <<EOF
GET / HTTP/1.0
@@ -18,8 +18,7 @@ prepare: |
echo "Wait for the service to be listening, limited to the task kill-timeout"
while ! netstat -lnt | grep -Pq "tcp.*?:$PORT +.*?LISTEN\n*"; do sleep 0.5; done
restore: |
- sudo snap remove $SNAP_NAME
- rm request.txt
+ rm -f request.txt
execute: |
response=$(nc -"$IP_VERSION" localhost "$PORT" < request.txt)
diff --git a/tests/snap-run-symlink-error/task.yaml b/tests/snap-run-symlink-error/task.yaml
index 1d007df8e8..b2d1d5138f 100644
--- a/tests/snap-run-symlink-error/task.yaml
+++ b/tests/snap-run-symlink-error/task.yaml
@@ -12,8 +12,8 @@ execute: |
# FIXME: remove "SNAP_REEXEC" once we have `snap run` inside the os snap
export SNAP_REEXEC=0
echo Setting up incorrect symlink for snap run
- sudo mkdir -p /snap/bin
- sudo ln -s /usr/bin/snap /snap/bin/xxx
+ mkdir -p /snap/bin
+ ln -s /usr/bin/snap /snap/bin/xxx
echo Running unknown command
expected='internal error, please report: running "xxx" failed: cannot find snap "xxx"'
output="$(/snap/bin/xxx 2>&1 )" && exit 1
diff --git a/tests/snap-run-symlink/task.yaml b/tests/snap-run-symlink/task.yaml
index ac815ea701..5aee969419 100644
--- a/tests/snap-run-symlink/task.yaml
+++ b/tests/snap-run-symlink/task.yaml
@@ -1,30 +1,25 @@
summary: Check that symlinks to /usr/bin/snap trigger `snap run`
+
prepare: |
echo Ensure we have a os snap with snap run
- sudo snap install --channel=beta ubuntu-core
- sudo snap install hello-world
-restore: |
- echo Resetting snapd state...
- systemctl stop snapd || true
- umount /var/lib/snapd/snaps/*.snap 2>&1 || true
- rm -rf /snap/*
- rm -rf /var/lib/snapd/*
- rm -f /etc/systemd/system/snap-*.{mount,service}
- rm -f /etc/systemd/system/multi-user.target.wants/snap-*.mount
- systemctl start snapd
+ $SPREAD_PATH/tests/lib/reset.sh
+ snap install --channel=beta ubuntu-core
+ snap install hello-world
+
environment:
APP/helloworld: hello-world
APP/helloworldecho: hello-world.echo
+
execute: |
echo Testing that replacing the wrapper with a symlink works
$APP
$APP > orig.txt 2>&1
- sudo rm /snap/bin/$APP
- sudo ln -s /usr/bin/snap /snap/bin/$APP
+ rm /snap/bin/$APP
+ ln -s /usr/bin/snap /snap/bin/$APP
# FIXME: remove "SNAP_REEXEC" once we have `snap run` inside the os snap
SNAP_REEXEC=0 $APP
SNAP_REEXEC=0 $APP > new.txt 2>&1
- diff -u orig.txt new.txt \ No newline at end of file
+ diff -u orig.txt new.txt