diff --git a/.precommit b/.precommit deleted file mode 100755 index d3456d61240..00000000000 --- a/.precommit +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh -set -e -echo "$@" -# put me in the project root, call me ".precommit". -# And put this here-document in ~/.bazaar/plugins/precommit_script.py: -<= 230 - - sudo systemctl stop snapd.service snapd.socket - sudo systemd-socket-activate -E SNAPD_DEBUG=1 -E SNAPD_DEBUG_HTTP=3 -l /run/snapd.socket -l /run/snapd-snap.socket ./snapd - -This will stop the installed snapd and activate the new one. Once it's -printed out something like `Listening on /run/snapd.socket as 3.` you -should then - - sudo chmod 0666 /run/snapd*.socket - -so the socket has the right permissions (otherwise you need `sudo` to -connect). + sudo SNAPD_DEBUG=1 SNAPD_DEBUG_HTTP=3 ./snapd To debug interaction with the snap store, you can set `SNAP_DEBUG_HTTP`. It is a bitfield: dump requests: 1, dump responses: 2, dump bodies: 4. diff --git a/cmd/autogen.sh b/cmd/autogen.sh new file mode 100755 index 00000000000..7b5449e7d26 --- /dev/null +++ b/cmd/autogen.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# Welcome to the Happy Maintainer's Utility Script +set -eux + +# We need the VERSION file to configure +if [ ! -e VERSION ]; then + ( cd .. && ./mkversion.sh ) +fi + +# Sanity check, are we in the right directory? +test -f configure.ac + +# Regenerate the build system +rm -f config.status +autoreconf -i -f + +# Configure the build +extra_opts= +. /etc/os-release +case "$ID" in + arch) + extra_opts="--libexecdir=/usr/lib/snapd --enable-nvidia-arch" + ;; + debian) + extra_opts="--libexecdir=/usr/lib/snapd" + ;; + ubuntu) + extra_opts="--libexecdir=/usr/lib/snapd --enable-nvidia-ubuntu" + ;; +esac + +./configure --enable-maintainer-mode --prefix=/usr $extra_opts diff --git a/cmd/cmd.go b/cmd/cmd.go index 44a6dcb258d..9f3eb2c7fef 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -56,6 +56,12 @@ func ExecInCoreSnap() { // should we re-exec? no option in the environment means yes if !osutil.GetenvBool(key, true) { + logger.Debugf("re-exec disabled by user") + return + } + + // did we already re-exec? + if osutil.GetenvBool("SNAP_DID_REEXEC") { return } @@ -104,6 +110,6 @@ func ExecInCoreSnap() { logger.Debugf("restarting into %q", full) - env := append(os.Environ(), key+"=0") + env := append(os.Environ(), "SNAP_DID_REEXEC=1") panic(syscall.Exec(full, os.Args, env)) } diff --git a/cmd/configure.ac b/cmd/configure.ac index abe85a72bf6..eef92875490 100644 --- a/cmd/configure.ac +++ b/cmd/configure.ac @@ -162,7 +162,7 @@ AS_IF([test "x$enable_caps_over_setuid" = "xyes"], [ AC_DEFINE([CAPS_OVER_SETUID], [1], [Use capabilities rather than setuid bit])]) -AC_PATH_PROG([HAVE_RST2MAN],[rst2man]) +AC_PATH_PROGS([HAVE_RST2MAN],[rst2man rst2man.py]) AS_IF([test "x$HAVE_RST2MAN" = "x"], [AC_MSG_ERROR(["cannot find the rst2man tool, install python-docutils or similar"])]) AC_CONFIG_FILES([Makefile snap-confine/Makefile snap-confine/tests/Makefile snap-confine/manpages/Makefile]) diff --git a/cmd/snap-confine/manpages/Makefile.am b/cmd/snap-confine/manpages/Makefile.am index e59b0743c8c..cf17dea44c7 100644 --- a/cmd/snap-confine/manpages/Makefile.am +++ b/cmd/snap-confine/manpages/Makefile.am @@ -4,7 +4,7 @@ CLEANFILES = snap-confine.5 snap-discard-ns.5 ubuntu-core-launcher.1 EXTRA_DIST = snap-confine.rst snap-discard-ns.rst ubuntu-core-launcher.rst %.5: %.rst - rst2man $^ > $@ + $(HAVE_RST2MAN) $^ > $@ ubuntu-core-launcher.1: ubuntu-core-launcher.rst - rst2man $^ > $@ + $(HAVE_RST2MAN) $^ > $@ diff --git a/debian/changelog b/debian/changelog index 1bfded46dc6..6c316c388bc 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,106 @@ +snapd (2.21) xenial; urgency=medium + + * New upstream release, LP: #1656382 + - daemon: re-enable reexec + - interfaces: allow reading installed files from previous revisions + by default + - daemon: make activation optional + - tests: run all snap-confine tests in c-unit-tests task + - many: fix abbreviated forms of disconnect + - tests: switch more tests to MATCH + - store: export userAgent. daemon: print store.UserAgent() on + startup. + - tests: test classic confinement `snap list` and `snap info` + output + - debian: skip snap-confine unit tests on nocheck + - overlord/snapstate: share code between Update and UpdateMany, so + that it deals with auto-aliases correctly + - interfaces: upower-observe: refactor to allow snaps to provide a + slot + - tests: add end-to-end store test for classic confinement + - overlord,overlord/snapstate: have UpdateMany retire/enable auto- + aliases even without new revision + - interfaces/browser-support: add @{PROC}/@{pid}/fd/[0-9] w and misc + /run/udev + - interfaces/builtin: add physical-memory-* and io-ports-control + - interfaces: allow getsockopt by default since it is so commonly + used + - cmd/snap, daemon, overlord/snapstate: tests and fixes for "snap + refresh" of a classic snap + - interfaces: allow read/write access of real-time clock with time- + control interface + - store: request no CDN via a header using SNAPPY_STORE_NO_CDN + envvar + - snap: add information about tracking channel (not just actual + channel) + - interfaces: use fewer dot imports + - overlord/snapstate: remove restrictions on ResetAliases + - overlord, store: move confinement filtering to the overlord (from + The Store) + - many: move interface test helpers to ifacetest package + - many: implement 'snap aliases' + - vet: fix for unkeyed fields error on aliases_test.go + - interfaces: miscellaneous policy updates for network-control, + unity7, pulseaudio, default and home + - tests: test for auto-aliases + - interface hooks: connect plug slot hooks (step 2) + - cmd/snap: fix internal naming in snap connect + - snap: use "size" as the json tag in snap.ChannelSnapInfo + - tests: restore the missing initialization of iface manager causing + race + - snap: fix missing sizes in `snap info ` + - tests: improve cleanup for c-unit-tests + - cmd/snap-confine: build non-installed libsnap-confine-private.a + - cmd/snap-confine: small tweaks to seccomp support code + - interfaces/docker-support: allow /run/shm/aufs.xeno for 14.04 + - many: obtain installed snaps developer/publisher username through + assertions + - store: setting of fields for details endpoint + - cmd/snap-confine: check for rst2man on configure + - snap: show `snap --help` output when just running `snap` + - interface/builtin: drop the obsolete checks in udisks2 + SanitizeSlot + - cmd/snap: remove currency switch following UX review + - spread: find top-level directory before running generate- + packaging-dir + - interface hooks: prepare plug slot hooks (step 1) + - i18n: use github.com/mvo5/gettext.go (pure go) for i18n to avoid + cgo + - many: put a marker in the User-Agent sent by snapd/snap when under + testingThe User-Agent will look like: + - tests: fix -reuse and -resend when govendor is missing + - snap: provide friendlier `snap find` message when no snaps are + found + - tests: fix mkversions.sh failure on zesty + - spread: install build-essential unconditionally + - spread: improve qemu ubuntu-14.04-{32,64} support + - overlord/snapstate,daemon: implement GET /v2/aliases handling + - store: retry user info request + - tests: port more snap-confine regression tests + - tests: cancel the scheduled reboot on ubuntu-core-upgrade-no-gc + and restore state + - tests: debug zesty autopkgtest failures + - overlord/snapstate: use keyed fields on literals + - tests: use MATCH in install-remove-multi + - tests: increase wait time for service to be up + - tests: make debug-each succeed if DENIED doesn't match + - tests: skip packaging dir generation for non-git based autopkgtest + runs + - tests: port refresh-all-undo to MATCH + - tests: improve snap connect test + - tests: port additional snap-confine regression tests + - tests: show --version when it matches unknown + - tests: optionally use apt proxy for qemu + - tests: add hello-classic test + - many: behave more consistently when pointed to staging and + possibly the fake store + - overlord/ifacestate: remove stale comments + - interfaces/apparmor: ignore snippets in classic confinement + - tests: port first regression test from snap-confine + - cmd/snap-confine: disable old tests + + -- Michael Vogt Fri, 13 Jan 2017 19:39:51 +0100 + snapd (2.20.1) xenial; urgency=medium * New upstream release, LP: #1648520 diff --git a/debian/control b/debian/control index aa03af3c2cf..37244b37fe4 100644 --- a/debian/control +++ b/debian/control @@ -13,6 +13,7 @@ Build-Depends: autoconf, dh-systemd, fakeroot, gettext, + grub-common, gnupg2, golang-any (>=2:1.6) | golang-1.6, indent, diff --git a/interfaces/apparmor/backend.go b/interfaces/apparmor/backend.go index 46e79a8ec8a..d5458714f27 100644 --- a/interfaces/apparmor/backend.go +++ b/interfaces/apparmor/backend.go @@ -55,8 +55,8 @@ import ( type Backend struct{} // Name returns the name of the backend. -func (b *Backend) Name() string { - return "apparmor" +func (b *Backend) Name() interfaces.SecuritySystem { + return interfaces.SecurityAppArmor } // Setup creates and loads apparmor profiles specific to a given snap. @@ -205,3 +205,7 @@ func unloadProfiles(profiles []string) error { } return nil } + +func (b *Backend) NewSpecification() interfaces.Specification { + panic(fmt.Errorf("%s is not using specifications yet", b.Name())) +} diff --git a/interfaces/apparmor/backend_test.go b/interfaces/apparmor/backend_test.go index dd2c44c7219..4faee868761 100644 --- a/interfaces/apparmor/backend_test.go +++ b/interfaces/apparmor/backend_test.go @@ -105,7 +105,7 @@ func (s *backendSuite) TearDownTest(c *C) { // Tests for Setup() and Remove() func (s *backendSuite) TestName(c *C) { - c.Check(s.Backend.Name(), Equals, "apparmor") + c.Check(s.Backend.Name(), Equals, interfaces.SecurityAppArmor) } func (s *backendSuite) TestInstallingSnapWritesAndLoadsProfiles(c *C) { diff --git a/interfaces/backend.go b/interfaces/backend.go index 3ea9977022f..d70b9c353a7 100644 --- a/interfaces/backend.go +++ b/interfaces/backend.go @@ -69,7 +69,7 @@ type ConfinementOptions struct { type SecurityBackend interface { // Name returns the name of the backend. // This is intended for diagnostic messages. - Name() string + Name() SecuritySystem // Setup creates and loads security artefacts specific to a given snap. // The snap can be in one of three kids onf confinement (strict mode, @@ -84,4 +84,7 @@ type SecurityBackend interface { // // This method should be called during the process of removing a snap. Remove(snapName string) error + + // NewSpecification returns a new specification associated with this backend. + NewSpecification() Specification } diff --git a/interfaces/core.go b/interfaces/core.go index a759ee6b824..0cd774f9440 100644 --- a/interfaces/core.go +++ b/interfaces/core.go @@ -157,6 +157,18 @@ type Interface interface { AutoConnect(plug *Plug, slot *Slot) bool } +// Specification describes interactions between backends and interfaces. +type Specification interface { + // AddPermanentSlot records side-effects of having a slot. + AddPermanentSlot(iface Interface, slot *Slot) error + // AddPermanentPlug records side-effects of having a plug. + AddPermanentPlug(iface Interface, plug *Plug) error + // AddConnectedSlot records side-effects of having a connected slot. + AddConnectedSlot(iface Interface, plug *Plug, slot *Slot) error + // AddConnectedPlug records side-effects of having a connected plug. + AddConnectedPlug(iface Interface, plug *Plug, slot *Slot) error +} + // SecuritySystem is a name of a security system. type SecuritySystem string diff --git a/interfaces/dbus/backend.go b/interfaces/dbus/backend.go index 9b1fc0be9b0..daf5c7832cc 100644 --- a/interfaces/dbus/backend.go +++ b/interfaces/dbus/backend.go @@ -42,7 +42,7 @@ import ( type Backend struct{} // Name returns the name of the backend. -func (b *Backend) Name() string { +func (b *Backend) Name() interfaces.SecuritySystem { return "dbus" } @@ -131,3 +131,7 @@ func addContent(securityTag string, executableSnippets [][]byte, content map[str Mode: 0644, } } + +func (b *Backend) NewSpecification() interfaces.Specification { + panic(fmt.Errorf("%s is not using specifications yet", b.Name())) +} diff --git a/interfaces/dbus/backend_test.go b/interfaces/dbus/backend_test.go index b3d2070061d..8f5d5873bbe 100644 --- a/interfaces/dbus/backend_test.go +++ b/interfaces/dbus/backend_test.go @@ -61,7 +61,7 @@ func (s *backendSuite) TearDownTest(c *C) { // Tests for Setup() and Remove() func (s *backendSuite) TestName(c *C) { - c.Check(s.Backend.Name(), Equals, "dbus") + c.Check(s.Backend.Name(), Equals, interfaces.SecurityDBus) } func (s *backendSuite) TestInstallingSnapWritesConfigFiles(c *C) { diff --git a/interfaces/ifacetest/testbackend.go b/interfaces/ifacetest/backend.go similarity index 93% rename from interfaces/ifacetest/testbackend.go rename to interfaces/ifacetest/backend.go index cebec14c540..648b70e6642 100644 --- a/interfaces/ifacetest/testbackend.go +++ b/interfaces/ifacetest/backend.go @@ -45,7 +45,7 @@ type TestSetupCall struct { } // Name returns the name of the security backend. -func (b *TestSecurityBackend) Name() string { +func (b *TestSecurityBackend) Name() interfaces.SecuritySystem { return "test" } @@ -66,3 +66,7 @@ func (b *TestSecurityBackend) Remove(snapName string) error { } return b.RemoveCallback(snapName) } + +func (b *TestSecurityBackend) NewSpecification() interfaces.Specification { + return &Specification{} +} diff --git a/interfaces/ifacetest/spec.go b/interfaces/ifacetest/spec.go new file mode 100644 index 00000000000..3a179b08bc4 --- /dev/null +++ b/interfaces/ifacetest/spec.go @@ -0,0 +1,80 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package ifacetest + +import ( + "github.com/snapcore/snapd/interfaces" +) + +// Specification is a specification intended for testing. +type Specification struct { + Snippets []string +} + +// AddSnippet appends a snippet to a list stored in the specification. +func (spec *Specification) AddSnippet(snippet string) { + spec.Snippets = append(spec.Snippets, snippet) +} + +// Implementation of methods required by interfaces.Specification + +// AddConnectedPlug records test side-effects of having a connected plug. +func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.Plug, slot *interfaces.Slot) error { + type definer interface { + TestConnectedPlug(spec *Specification, plug *interfaces.Plug, slot *interfaces.Slot) error + } + if iface, ok := iface.(definer); ok { + return iface.TestConnectedPlug(spec, plug, slot) + } + return nil +} + +// AddConnectedSlot records test side-effects of having a connected slot. +func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.Plug, slot *interfaces.Slot) error { + type definer interface { + TestConnectedSlot(spec *Specification, plug *interfaces.Plug, slot *interfaces.Slot) error + } + if iface, ok := iface.(definer); ok { + return iface.TestConnectedSlot(spec, plug, slot) + } + return nil +} + +// AddPermanentPlug records test side-effects of having a plug. +func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *interfaces.Plug) error { + type definer interface { + TestPermanentPlug(spec *Specification, plug *interfaces.Plug) error + } + if iface, ok := iface.(definer); ok { + return iface.TestPermanentPlug(spec, plug) + } + return nil +} + +// AddPermanentSlot records test side-effects of having a slot. +func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *interfaces.Slot) error { + type definer interface { + TestPermanentSlot(spec *Specification, slot *interfaces.Slot) error + } + if iface, ok := iface.(definer); ok { + return iface.TestPermanentSlot(spec, slot) + } + return nil +} diff --git a/interfaces/ifacetest/spec_test.go b/interfaces/ifacetest/spec_test.go new file mode 100644 index 00000000000..389d3e95430 --- /dev/null +++ b/interfaces/ifacetest/spec_test.go @@ -0,0 +1,93 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2015-2017 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package ifacetest_test + +import ( + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/ifacetest" + "github.com/snapcore/snapd/snap" +) + +type SpecificationSuite struct { + iface *ifacetest.TestInterface + spec *ifacetest.Specification + plug *interfaces.Plug + slot *interfaces.Slot +} + +var _ = Suite(&SpecificationSuite{ + iface: &ifacetest.TestInterface{ + InterfaceName: "test", + TestConnectedPlugCallback: func(spec *ifacetest.Specification, plug *interfaces.Plug, slot *interfaces.Slot) error { + spec.AddSnippet("connected-plug") + return nil + }, + TestConnectedSlotCallback: func(spec *ifacetest.Specification, plug *interfaces.Plug, slot *interfaces.Slot) error { + spec.AddSnippet("connected-slot") + return nil + }, + TestPermanentPlugCallback: func(spec *ifacetest.Specification, plug *interfaces.Plug) error { + spec.AddSnippet("permanent-plug") + return nil + }, + TestPermanentSlotCallback: func(spec *ifacetest.Specification, slot *interfaces.Slot) error { + spec.AddSnippet("permanent-slot") + return nil + }, + }, + plug: &interfaces.Plug{ + PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "snap"}, + Name: "name", + Interface: "test", + }, + }, + slot: &interfaces.Slot{ + SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "snap"}, + Name: "name", + Interface: "test", + }, + }, +}) + +func (s *SpecificationSuite) SetUpTest(c *C) { + s.spec = &ifacetest.Specification{} +} + +// AddSnippet is not broken +func (s *SpecificationSuite) TestAddSnippet(c *C) { + s.spec.AddSnippet("hello") + s.spec.AddSnippet("world") + c.Assert(s.spec.Snippets, DeepEquals, []string{"hello", "world"}) +} + +// The Specification can be used through the interfaces.Specification interface +func (s *SpecificationSuite) SpecificationIface(c *C) { + var r interfaces.Specification = s.spec + c.Assert(r.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(r.AddConnectedSlot(s.iface, s.plug, s.slot), IsNil) + c.Assert(r.AddPermanentPlug(s.iface, s.plug), IsNil) + c.Assert(r.AddPermanentSlot(s.iface, s.slot), IsNil) + c.Assert(s.spec.Snippets, DeepEquals, []string{ + "connected-plug", "connected-slot", "permanent-plug", "permanent-slot"}) +} diff --git a/interfaces/ifacetest/testtype.go b/interfaces/ifacetest/testiface.go similarity index 77% rename from interfaces/ifacetest/testtype.go rename to interfaces/ifacetest/testiface.go index e65aebdffb1..bb2364db749 100644 --- a/interfaces/ifacetest/testtype.go +++ b/interfaces/ifacetest/testiface.go @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2015 Canonical Ltd + * Copyright (C) 2015-2017 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -44,6 +44,13 @@ type TestInterface struct { PlugSnippetCallback func(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) // PermanentPlugSnippetCallback is the callback invoked inside PermanentPlugSnippet() PermanentPlugSnippetCallback func(plug *interfaces.Plug, securitySystem interfaces.SecuritySystem) ([]byte, error) + + // Support for interacting with the test backend. + + TestConnectedPlugCallback func(spec *Specification, plug *interfaces.Plug, slot *interfaces.Slot) error + TestConnectedSlotCallback func(spec *Specification, plug *interfaces.Plug, slot *interfaces.Slot) error + TestPermanentPlugCallback func(spec *Specification, plug *interfaces.Plug) error + TestPermanentSlotCallback func(spec *Specification, slot *interfaces.Slot) error } // String() returns the same value as Name(). @@ -123,3 +130,33 @@ func (t *TestInterface) AutoConnect(plug *interfaces.Plug, slot *interfaces.Slot } return true } + +// Support for interacting with the test backend. + +func (t *TestInterface) TestConnectedPlug(spec *Specification, plug *interfaces.Plug, slot *interfaces.Slot) error { + if t.TestConnectedPlugCallback != nil { + return t.TestConnectedPlugCallback(spec, plug, slot) + } + return nil +} + +func (t *TestInterface) TestConnectedSlot(spec *Specification, plug *interfaces.Plug, slot *interfaces.Slot) error { + if t.TestConnectedSlotCallback != nil { + return t.TestConnectedSlotCallback(spec, plug, slot) + } + return nil +} + +func (t *TestInterface) TestPermanentPlug(spec *Specification, plug *interfaces.Plug) error { + if t.TestPermanentPlugCallback != nil { + return t.TestPermanentPlugCallback(spec, plug) + } + return nil +} + +func (t *TestInterface) TestPermanentSlot(spec *Specification, slot *interfaces.Slot) error { + if t.TestPermanentSlotCallback != nil { + return t.TestPermanentSlotCallback(spec, slot) + } + return nil +} diff --git a/interfaces/ifacetest/testtype_test.go b/interfaces/ifacetest/testiface_test.go similarity index 99% rename from interfaces/ifacetest/testtype_test.go rename to interfaces/ifacetest/testiface_test.go index ec3b40c0429..74474ce026c 100644 --- a/interfaces/ifacetest/testtype_test.go +++ b/interfaces/ifacetest/testiface_test.go @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2015 Canonical Ltd + * Copyright (C) 2015-2017 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as diff --git a/interfaces/kmod/backend.go b/interfaces/kmod/backend.go index 9c74dd7f4ed..c8ce95c16e1 100644 --- a/interfaces/kmod/backend.go +++ b/interfaces/kmod/backend.go @@ -52,7 +52,7 @@ import ( type Backend struct{} // Name returns the name of the backend. -func (b *Backend) Name() string { +func (b *Backend) Name() interfaces.SecuritySystem { return "kmod" } @@ -150,3 +150,7 @@ func uniqueLines(lines []string) (deduplicated []string) { } return deduplicated } + +func (b *Backend) NewSpecification() interfaces.Specification { + panic(fmt.Errorf("%s is not using specifications yet", b.Name())) +} diff --git a/interfaces/kmod/backend_test.go b/interfaces/kmod/backend_test.go index e88ebcbde97..86aaec74deb 100644 --- a/interfaces/kmod/backend_test.go +++ b/interfaces/kmod/backend_test.go @@ -64,7 +64,7 @@ func (s *backendSuite) TearDownTest(c *C) { } func (s *backendSuite) TestName(c *C) { - c.Check(s.Backend.Name(), Equals, "kmod") + c.Check(s.Backend.Name(), Equals, interfaces.SecurityKMod) } func (s *backendSuite) TestUniqueLines(c *C) { diff --git a/interfaces/mount/backend.go b/interfaces/mount/backend.go index 0141de38633..70fd975de01 100644 --- a/interfaces/mount/backend.go +++ b/interfaces/mount/backend.go @@ -44,8 +44,8 @@ import ( type Backend struct{} // Name returns the name of the backend. -func (b *Backend) Name() string { - return "mount" +func (b *Backend) Name() interfaces.SecuritySystem { + return interfaces.SecurityMount } // Setup creates mount mount profile files specific to a given snap. @@ -128,3 +128,7 @@ func addContent(securityTag string, executableSnippets [][]byte, content map[str Mode: 0644, } } + +func (b *Backend) NewSpecification() interfaces.Specification { + panic(fmt.Errorf("%s is not using specifications yet", b.Name())) +} diff --git a/interfaces/mount/backend_test.go b/interfaces/mount/backend_test.go index 6e58828cab1..1843b900750 100644 --- a/interfaces/mount/backend_test.go +++ b/interfaces/mount/backend_test.go @@ -67,7 +67,7 @@ func (s *backendSuite) TearDownTest(c *C) { } func (s *backendSuite) TestName(c *C) { - c.Check(s.Backend.Name(), Equals, "mount") + c.Check(s.Backend.Name(), Equals, interfaces.SecurityMount) } func (s *backendSuite) TestRemove(c *C) { diff --git a/interfaces/repo.go b/interfaces/repo.go index 2653e296dae..0eaa3f6fc91 100644 --- a/interfaces/repo.go +++ b/interfaces/repo.go @@ -41,6 +41,7 @@ type Repository struct { slotPlugs map[*Slot]map[*Plug]bool // given a plug and a slot, are they connected? plugSlots map[*Plug]map[*Slot]bool + backends map[SecuritySystem]SecurityBackend } // NewRepository creates an empty plug repository. @@ -51,6 +52,7 @@ func NewRepository() *Repository { slots: make(map[string]map[string]*Slot), slotPlugs: make(map[*Slot]map[*Plug]bool), plugSlots: make(map[*Plug]map[*Slot]bool), + backends: make(map[SecuritySystem]SecurityBackend), } } @@ -78,6 +80,19 @@ func (r *Repository) AddInterface(i Interface) error { return nil } +// AddBackend adds the provided security backend to the repository. +func (r *Repository) AddBackend(backend SecurityBackend) error { + r.m.Lock() + defer r.m.Unlock() + + name := backend.Name() + if _, ok := r.backends[name]; ok { + return fmt.Errorf("cannot add backend %q, security system name is in use", name) + } + r.backends[name] = backend + return nil +} + // AllPlugs returns all plugs of the given interface. // If interfaceName is the empty string, all plugs are returned. func (r *Repository) AllPlugs(interfaceName string) []*Plug { @@ -699,6 +714,45 @@ func (r *Repository) securitySnippetsForSnap(snapName string, securitySystem Sec return snippets, nil } +// SnapSpecification returns the specification of a given snap in a given security system. +func (r *Repository) SnapSpecification(securitySystem SecuritySystem, snapName string) (Specification, error) { + r.m.Lock() + defer r.m.Unlock() + + backend := r.backends[securitySystem] + if backend == nil { + return nil, fmt.Errorf("cannot handle interfaces of snap %q, security system %q is not known", snapName, securitySystem) + } + + spec := backend.NewSpecification() + + // slot side + for _, slot := range r.slots[snapName] { + iface := r.ifaces[slot.Interface] + if err := spec.AddPermanentSlot(iface, slot); err != nil { + return nil, err + } + for plug := range r.slotPlugs[slot] { + if err := spec.AddConnectedSlot(iface, plug, slot); err != nil { + return nil, err + } + } + } + // plug side + for _, plug := range r.plugs[snapName] { + iface := r.ifaces[plug.Interface] + if err := spec.AddPermanentPlug(iface, plug); err != nil { + return nil, err + } + for slot := range r.plugSlots[plug] { + if err := spec.AddConnectedPlug(iface, plug, slot); err != nil { + return nil, err + } + } + } + return spec, nil +} + // BadInterfacesError is returned when some snap interfaces could not be registered. // Those interfaces not mentioned in the error were successfully registered. type BadInterfacesError struct { diff --git a/interfaces/repo_test.go b/interfaces/repo_test.go index fd8bf3f18aa..dd50f7849c6 100644 --- a/interfaces/repo_test.go +++ b/interfaces/repo_test.go @@ -135,6 +135,13 @@ func (s *RepositorySuite) TestAddInterfaceInvalidName(c *C) { c.Assert(s.emptyRepo.Interface(iface.Name()), IsNil) } +func (s *RepositorySuite) TestAddBackend(c *C) { + backend := &ifacetest.TestSecurityBackend{} + c.Assert(s.emptyRepo.AddBackend(backend), IsNil) + err := s.emptyRepo.AddBackend(backend) + c.Assert(err, ErrorMatches, `cannot add backend "test", security system name is in use`) +} + // Tests for Repository.Interface() func (s *RepositorySuite) TestInterface(c *C) { @@ -1241,8 +1248,9 @@ func (s *RepositorySuite) TestInterfacesSmokeTest(c *C) { } // Tests for Repository.SecuritySnippetsForSnap() +// and for SnapSpecification() -const testSecurity SecuritySystem = "security" +const testSecurity SecuritySystem = "test" var testInterface = &ifacetest.TestInterface{ InterfaceName: "interface", @@ -1270,6 +1278,22 @@ var testInterface = &ifacetest.TestInterface{ } return nil, nil }, + TestPermanentPlugCallback: func(spec *ifacetest.Specification, plug *Plug) error { + spec.AddSnippet("static plug snippet") + return nil + }, + TestConnectedPlugCallback: func(spec *ifacetest.Specification, plug *Plug, slot *Slot) error { + spec.AddSnippet("connection-specific plug snippet") + return nil + }, + TestPermanentSlotCallback: func(spec *ifacetest.Specification, slot *Slot) error { + spec.AddSnippet("static slot snippet") + return nil + }, + TestConnectedSlotCallback: func(spec *ifacetest.Specification, plug *Plug, slot *Slot) error { + spec.AddSnippet("connection-specific slot snippet") + return nil + }, } func (s *RepositorySuite) TestSlotSnippetsForSnapSuccess(c *C) { @@ -1323,6 +1347,43 @@ func (s *RepositorySuite) TestSlotSnippetsForSnapSuccess(c *C) { }) } +func (s *RepositorySuite) TestSnapSpecification(c *C) { + repo := s.emptyRepo + c.Assert(repo.AddBackend(&ifacetest.TestSecurityBackend{}), IsNil) + c.Assert(repo.AddInterface(testInterface), IsNil) + c.Assert(repo.AddPlug(s.plug), IsNil) + c.Assert(repo.AddSlot(s.slot), IsNil) + + // Snaps should get static security now + spec, err := repo.SnapSpecification(testSecurity, s.plug.Snap.Name()) + c.Assert(err, IsNil) + c.Check(spec.(*ifacetest.Specification).Snippets, DeepEquals, []string{"static plug snippet"}) + + spec, err = repo.SnapSpecification(testSecurity, s.slot.Snap.Name()) + c.Assert(err, IsNil) + c.Check(spec.(*ifacetest.Specification).Snippets, DeepEquals, []string{"static slot snippet"}) + + // Establish connection between plug and slot + connRef := ConnRef{PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()} + err = repo.Connect(connRef) + c.Assert(err, IsNil) + + // Snaps should get static and connection-specific security now + spec, err = repo.SnapSpecification(testSecurity, s.plug.Snap.Name()) + c.Assert(err, IsNil) + c.Check(spec.(*ifacetest.Specification).Snippets, DeepEquals, []string{ + "static plug snippet", + "connection-specific plug snippet", + }) + + spec, err = repo.SnapSpecification(testSecurity, s.slot.Snap.Name()) + c.Assert(err, IsNil) + c.Check(spec.(*ifacetest.Specification).Snippets, DeepEquals, []string{ + "static slot snippet", + "connection-specific slot snippet", + }) +} + func (s *RepositorySuite) TestOrphanInterfaces(c *C) { repo := s.emptyRepo snaps := addPlugsSlots(c, s.testRepo, ` diff --git a/interfaces/seccomp/backend.go b/interfaces/seccomp/backend.go index b4acc39f514..b70d7421e1e 100644 --- a/interfaces/seccomp/backend.go +++ b/interfaces/seccomp/backend.go @@ -47,8 +47,8 @@ import ( type Backend struct{} // Name returns the name of the backend. -func (b *Backend) Name() string { - return "seccomp" +func (b *Backend) Name() interfaces.SecuritySystem { + return interfaces.SecuritySecComp } // Setup creates seccomp profiles specific to a given snap. @@ -133,3 +133,7 @@ func addContent(securityTag string, opts interfaces.ConfinementOptions, snippets Mode: 0644, } } + +func (b *Backend) NewSpecification() interfaces.Specification { + panic(fmt.Errorf("%s is not using specifications yet", b.Name())) +} diff --git a/interfaces/seccomp/backend_test.go b/interfaces/seccomp/backend_test.go index 55ffb53e11c..bf51ec94224 100644 --- a/interfaces/seccomp/backend_test.go +++ b/interfaces/seccomp/backend_test.go @@ -63,7 +63,7 @@ func (s *backendSuite) TearDownTest(c *C) { // Tests for Setup() and Remove() func (s *backendSuite) TestName(c *C) { - c.Check(s.Backend.Name(), Equals, "seccomp") + c.Check(s.Backend.Name(), Equals, interfaces.SecuritySecComp) } func (s *backendSuite) TestInstallingSnapWritesProfiles(c *C) { diff --git a/interfaces/systemd/backend.go b/interfaces/systemd/backend.go index a202b671a9c..7beee08c8fc 100644 --- a/interfaces/systemd/backend.go +++ b/interfaces/systemd/backend.go @@ -43,8 +43,8 @@ import ( type Backend struct{} // Name returns the name of the backend. -func (b *Backend) Name() string { - return "systemd" +func (b *Backend) Name() interfaces.SecuritySystem { + return interfaces.SecuritySystemd } func disableRemovedServices(systemd sysd.Systemd, dir, glob string, content map[string]*osutil.FileState) error { @@ -191,3 +191,7 @@ type dummyReporter struct{} func (dr *dummyReporter) Notify(msg string) { } + +func (b *Backend) NewSpecification() interfaces.Specification { + panic(fmt.Errorf("%s is not using specifications yet", b.Name())) +} diff --git a/interfaces/systemd/backend_test.go b/interfaces/systemd/backend_test.go index 24988c4254b..fd99f0d2241 100644 --- a/interfaces/systemd/backend_test.go +++ b/interfaces/systemd/backend_test.go @@ -61,7 +61,7 @@ func (s *backendSuite) TearDownTest(c *C) { } func (s *backendSuite) TestName(c *C) { - c.Check(s.Backend.Name(), Equals, "systemd") + c.Check(s.Backend.Name(), Equals, interfaces.SecuritySystemd) } func (s *backendSuite) TestUnmarshalRawSnippetMap(c *C) { diff --git a/interfaces/udev/backend.go b/interfaces/udev/backend.go index 66b5c7b5428..b4541ed3710 100644 --- a/interfaces/udev/backend.go +++ b/interfaces/udev/backend.go @@ -41,8 +41,8 @@ import ( type Backend struct{} // Name returns the name of the backend. -func (b *Backend) Name() string { - return "udev" +func (b *Backend) Name() interfaces.SecuritySystem { + return interfaces.SecurityUDev } // snapRulesFileName returns the path of the snap udev rules file. @@ -180,3 +180,7 @@ func (b *Backend) combineSnippets(snapInfo *snap.Info, snippets map[string][][]b return combinedSnippets, nil } + +func (b *Backend) NewSpecification() interfaces.Specification { + panic(fmt.Errorf("%s is not using specifications yet", b.Name())) +} diff --git a/interfaces/udev/backend_test.go b/interfaces/udev/backend_test.go index 39918548f30..89455728ac1 100644 --- a/interfaces/udev/backend_test.go +++ b/interfaces/udev/backend_test.go @@ -79,7 +79,7 @@ func (s *backendSuite) TearDownTest(c *C) { // Tests for Setup() and Remove() func (s *backendSuite) TestName(c *C) { - c.Check(s.Backend.Name(), Equals, "udev") + c.Check(s.Backend.Name(), Equals, interfaces.SecurityUDev) } func (s *backendSuite) TestInstallingSnapWritesAndLoadsRules(c *C) { diff --git a/overlord/devicestate/firstboot.go b/overlord/devicestate/firstboot.go index ada39f5e69e..1add606f7ae 100644 --- a/overlord/devicestate/firstboot.go +++ b/overlord/devicestate/firstboot.go @@ -48,7 +48,8 @@ func populateStateFromSeedImpl(st *state.State) ([]*state.TaskSet, error) { } // ack all initial assertions - if err := importAssertionsFromSeed(st); err != nil { + model, err := importAssertionsFromSeed(st) + if err != nil { return nil, err } @@ -57,13 +58,25 @@ func populateStateFromSeedImpl(st *state.State) ([]*state.TaskSet, error) { return nil, err } + var required map[string]bool + reqSnaps := model.RequiredSnaps() + if len(reqSnaps) > 0 { + required = make(map[string]bool, len(reqSnaps)) + for _, snap := range reqSnaps { + required[snap] = true + } + } + tsAll := []*state.TaskSet{} for i, sn := range seed.Snaps { - var flags snapstate.Flags if sn.DevMode { flags.DevMode = true } + if required[sn.Name] { + flags.Required = true + } + path := filepath.Join(dirs.SnapSeedDir, "snaps", sn.File) var sideInfo snap.SideInfo @@ -113,22 +126,17 @@ func readAsserts(fn string, batch *assertstate.Batch) ([]*asserts.Ref, error) { return batch.AddStream(f) } -func importAssertionsFromSeed(st *state.State) error { +func importAssertionsFromSeed(st *state.State) (*asserts.Model, error) { device, err := auth.Device(st) if err != nil { - return err + return nil, err } // set device,model from the model assertion assertSeedDir := filepath.Join(dirs.SnapSeedDir, "assertions") dc, err := ioutil.ReadDir(assertSeedDir) if err != nil { - return fmt.Errorf("cannot read assert seed dir: %s", err) - } - - // FIXME: remove this check once asserts are mandatory - if len(dc) == 0 { - return nil + return nil, fmt.Errorf("cannot read assert seed dir: %s", err) } // collect @@ -138,12 +146,12 @@ func importAssertionsFromSeed(st *state.State) error { fn := filepath.Join(assertSeedDir, fi.Name()) refs, err := readAsserts(fn, batch) if err != nil { - return fmt.Errorf("cannot read assertions: %s", err) + return nil, fmt.Errorf("cannot read assertions: %s", err) } for _, ref := range refs { if ref.Type == asserts.ModelType { if modelRef != nil && modelRef.Unique() != ref.Unique() { - return fmt.Errorf("cannot add more than one model assertion") + return nil, fmt.Errorf("cannot add more than one model assertion") } modelRef = ref } @@ -151,16 +159,16 @@ func importAssertionsFromSeed(st *state.State) error { } // verify we have one model assertion if modelRef == nil { - return fmt.Errorf("need a model assertion") + return nil, fmt.Errorf("need a model assertion") } if err := batch.Commit(st); err != nil { - return err + return nil, err } a, err := modelRef.Resolve(assertstate.DB(st).Find) if err != nil { - return fmt.Errorf("internal error: cannot find just added assertion %v: %v", modelRef, err) + return nil, fmt.Errorf("internal error: cannot find just added assertion %v: %v", modelRef, err) } modelAssertion := a.(*asserts.Model) @@ -168,8 +176,8 @@ func importAssertionsFromSeed(st *state.State) error { device.Brand = modelAssertion.BrandID() device.Model = modelAssertion.Model() if err := auth.SetDevice(st, device); err != nil { - return err + return nil, err } - return nil + return modelAssertion, nil } diff --git a/overlord/devicestate/firstboot_test.go b/overlord/devicestate/firstboot_test.go index 63a05bf656f..4139b2a0329 100644 --- a/overlord/devicestate/firstboot_test.go +++ b/overlord/devicestate/firstboot_test.go @@ -164,7 +164,7 @@ version: 1.0` c.Assert(err, IsNil) // add a model assertion and its chain - assertsChain := s.makeModelAssertionChain(c) + assertsChain := s.makeModelAssertionChain(c, "foo") for i, as := range assertsChain { fn := filepath.Join(dirs.SnapSeedDir, "assertions", strconv.Itoa(i)) err := ioutil.WriteFile(fn, asserts.Encode(as), 0644) @@ -237,6 +237,7 @@ snaps: err = snapstate.Get(state, "foo", &snapst) c.Assert(err, IsNil) c.Assert(snapst.DevMode, Equals, true) + c.Assert(snapst.Required, Equals, true) // check local info, err = snapstate.CurrentInfo(state, "local") @@ -244,6 +245,11 @@ snaps: c.Assert(info.SnapID, Equals, "") c.Assert(info.Revision, Equals, snap.R("x1")) + var snapst2 snapstate.SnapState + err = snapstate.Get(state, "local", &snapst2) + c.Assert(err, IsNil) + c.Assert(snapst2.Required, Equals, false) + // and ensure state is now considered seeded var seeded bool err = state.Get("seeded", &seeded) @@ -402,7 +408,7 @@ snaps: c.Check(pubAcct.AccountID(), Equals, "developerid") } -func (s *FirstBootTestSuite) makeModelAssertion(c *C, modelStr string) *asserts.Model { +func (s *FirstBootTestSuite) makeModelAssertion(c *C, modelStr string, reqSnaps ...string) *asserts.Model { headers := map[string]interface{}{ "series": "16", "authority-id": "my-brand", @@ -414,12 +420,19 @@ func (s *FirstBootTestSuite) makeModelAssertion(c *C, modelStr string) *asserts. "kernel": "pc-kernel", "timestamp": time.Now().Format(time.RFC3339), } + if len(reqSnaps) != 0 { + reqs := make([]interface{}, len(reqSnaps)) + for i, req := range reqSnaps { + reqs[i] = req + } + headers["required-snaps"] = reqs + } model, err := s.brandSigning.Sign(asserts.ModelType, headers, nil, "") c.Assert(err, IsNil) return model.(*asserts.Model) } -func (s *FirstBootTestSuite) makeModelAssertionChain(c *C) []asserts.Assertion { +func (s *FirstBootTestSuite) makeModelAssertionChain(c *C, reqSnaps ...string) []asserts.Assertion { assertChain := []asserts.Assertion{} brandAcct := assertstest.NewAccount(s.storeSigning, "my-brand", map[string]interface{}{ @@ -431,7 +444,7 @@ func (s *FirstBootTestSuite) makeModelAssertionChain(c *C) []asserts.Assertion { brandAccKey := assertstest.NewAccountKey(s.storeSigning, brandAcct, nil, s.brandPrivKey.PublicKey(), "") assertChain = append(assertChain, brandAccKey) - model := s.makeModelAssertion(c, "my-model") + model := s.makeModelAssertion(c, "my-model", reqSnaps...) assertChain = append(assertChain, model) storeAccountKey := s.storeSigning.StoreAccountKey("") @@ -456,8 +469,9 @@ func (s *FirstBootTestSuite) TestImportAssertionsFromSeedHappy(c *C) { st.Lock() defer st.Unlock() - err = devicestate.ImportAssertionsFromSeed(st) + model, err := devicestate.ImportAssertionsFromSeed(st) c.Assert(err, IsNil) + c.Assert(model, NotNil) // verify that the model was added db := assertstate.DB(st) @@ -474,6 +488,9 @@ func (s *FirstBootTestSuite) TestImportAssertionsFromSeedHappy(c *C) { c.Assert(err, IsNil) c.Check(ds.Brand, Equals, "my-brand") c.Check(ds.Model, Equals, "my-model") + + c.Check(model.BrandID(), Equals, "my-brand") + c.Check(model.Model(), Equals, "my-model") } func (s *FirstBootTestSuite) TestImportAssertionsFromSeedMissingSig(c *C) { @@ -494,7 +511,7 @@ func (s *FirstBootTestSuite) TestImportAssertionsFromSeedMissingSig(c *C) { // try import and verify that its rejects because other assertions are // missing - err := devicestate.ImportAssertionsFromSeed(st) + _, err := devicestate.ImportAssertionsFromSeed(st) c.Assert(err, ErrorMatches, "cannot find account-key .*: assertion not found") } @@ -516,7 +533,7 @@ func (s *FirstBootTestSuite) TestImportAssertionsFromSeedTwoModelAsserts(c *C) { // try import and verify that its rejects because other assertions are // missing - err = devicestate.ImportAssertionsFromSeed(st) + _, err = devicestate.ImportAssertionsFromSeed(st) c.Assert(err, ErrorMatches, "cannot add more than one model assertion") } @@ -537,6 +554,6 @@ func (s *FirstBootTestSuite) TestImportAssertionsFromSeedNoModelAsserts(c *C) { // try import and verify that its rejects because other assertions are // missing - err := devicestate.ImportAssertionsFromSeed(st) + _, err := devicestate.ImportAssertionsFromSeed(st) c.Assert(err, ErrorMatches, "need a model assertion") } diff --git a/overlord/snapstate/flags.go b/overlord/snapstate/flags.go index 576155b67e2..23da8623231 100644 --- a/overlord/snapstate/flags.go +++ b/overlord/snapstate/flags.go @@ -41,6 +41,10 @@ type Flags struct { // IgnoreValidation is set when the user requested as one-off // to ignore refresh control validation. IgnoreValidation bool `json:"ignore-validation,omitempty"` + + // Required is set to mark that a snap is required + // and cannot be removed + Required bool `json:"required,omitempty"` } // DevModeAllowed returns whether a snap can be installed with devmode confinement (either set or overridden) diff --git a/overlord/snapstate/snapmgr.go b/overlord/snapstate/snapmgr.go index 3a2e28aeff2..7ded348abbd 100644 --- a/overlord/snapstate/snapmgr.go +++ b/overlord/snapstate/snapmgr.go @@ -857,6 +857,9 @@ func (m *SnapManager) doLinkSnap(t *state.Task, _ *tomb.Tomb) error { snapst.JailMode = snapsup.JailMode oldClassic := snapst.Classic snapst.Classic = snapsup.Classic + if snapsup.Required { // set only on install and left alone on refresh + snapst.Required = true + } newInfo, err := readInfo(snapsup.Name(), cand) if err != nil { diff --git a/overlord/snapstate/snapmgr_test.go b/overlord/snapstate/snapmgr_test.go index 96c3e88391e..625133471b4 100644 --- a/overlord/snapstate/snapmgr_test.go +++ b/overlord/snapstate/snapmgr_test.go @@ -1165,6 +1165,7 @@ func (s *snapmgrTestSuite) TestInstallRunThrough(c *C) { SnapID: "snapIDsnapidsnapidsnapidsnapidsn", Revision: snap.R(42), }) + c.Assert(snapst.Required, Equals, false) } func (s *snapmgrTestSuite) TestUpdateRunThrough(c *C) { @@ -2454,7 +2455,7 @@ version: 1.0`) SnapID: "snapIDsnapidsnapidsnapidsnapidsn", Revision: snap.R(42), } - ts, err := snapstate.InstallPath(s.state, si, someSnap, "", snapstate.Flags{}) + ts, err := snapstate.InstallPath(s.state, si, someSnap, "", snapstate.Flags{Required: true}) c.Assert(err, IsNil) chg.AddAll(ts) @@ -2487,6 +2488,9 @@ version: 1.0`) c.Assert(snapsup, DeepEquals, snapstate.SnapSetup{ SnapPath: someSnap, SideInfo: snapsup.SideInfo, + Flags: snapstate.Flags{ + Required: true, + }, }) c.Assert(snapsup.SideInfo, DeepEquals, si) @@ -2499,6 +2503,7 @@ version: 1.0`) c.Assert(snapst.Channel, Equals, "") c.Assert(snapst.Sequence[0], DeepEquals, si) c.Assert(snapst.LocalRevision().Unset(), Equals, true) + c.Assert(snapst.Required, Equals, true) } func (s *snapmgrTestSuite) TestRemoveRunThrough(c *C) { @@ -2974,6 +2979,27 @@ func (s *snapmgrTestSuite) TestRemoveRefused(c *C) { c.Check(err, ErrorMatches, `snap "gadget" is not removable`) } +func (s *snapmgrTestSuite) TestRemoveRefusedLastRevision(c *C) { + si := snap.SideInfo{ + RealName: "gadget", + Revision: snap.R(7), + } + + s.state.Lock() + defer s.state.Unlock() + + snapstate.Set(s.state, "gadget", &snapstate.SnapState{ + Active: false, + Sequence: []*snap.SideInfo{&si}, + Current: si.Revision, + SnapType: "app", + }) + + _, err := snapstate.Remove(s.state, "gadget", snap.R(7)) + + c.Check(err, ErrorMatches, `snap "gadget" is not removable`) +} + func (s *snapmgrTestSuite) TestUpdateDoesGC(c *C) { s.state.Lock() defer s.state.Unlock() @@ -4180,21 +4206,20 @@ func (s *canRemoveSuite) TestAppAreAlwaysOKToRemove(c *C) { } info.RealName = "foo" - c.Check(snapstate.CanRemove(info, false), Equals, true) - c.Check(snapstate.CanRemove(info, true), Equals, true) + c.Check(snapstate.CanRemove(info, &snapstate.SnapState{Active: true}, false), Equals, true) + c.Check(snapstate.CanRemove(info, &snapstate.SnapState{Active: true}, true), Equals, true) } -func (s *canRemoveSuite) TestActiveGadgetsAreNotOK(c *C) { +func (s *canRemoveSuite) TestLastGadgetsAreNotOK(c *C) { info := &snap.Info{ Type: snap.TypeGadget, } info.RealName = "foo" - c.Check(snapstate.CanRemove(info, false), Equals, true) - c.Check(snapstate.CanRemove(info, true), Equals, false) + c.Check(snapstate.CanRemove(info, &snapstate.SnapState{}, true), Equals, false) } -func (s *canRemoveSuite) TestActiveOSAndKernelAreNotOK(c *C) { +func (s *canRemoveSuite) TestLastOSAndKernelAreNotOK(c *C) { os := &snap.Info{ Type: snap.TypeOS, } @@ -4204,11 +4229,29 @@ func (s *canRemoveSuite) TestActiveOSAndKernelAreNotOK(c *C) { } kernel.RealName = "krnl" - c.Check(snapstate.CanRemove(os, false), Equals, true) - c.Check(snapstate.CanRemove(os, true), Equals, false) + c.Check(snapstate.CanRemove(os, &snapstate.SnapState{}, true), Equals, false) + + c.Check(snapstate.CanRemove(kernel, &snapstate.SnapState{}, true), Equals, false) +} + +func (s *canRemoveSuite) TestOneRevisionIsOK(c *C) { + info := &snap.Info{ + Type: snap.TypeGadget, + } + info.RealName = "foo" + + c.Check(snapstate.CanRemove(info, &snapstate.SnapState{Active: true}, false), Equals, true) +} + +func (s *canRemoveSuite) TestRequiredIsNotOK(c *C) { + info := &snap.Info{ + Type: snap.TypeApp, + } + info.RealName = "foo" - c.Check(snapstate.CanRemove(kernel, false), Equals, true) - c.Check(snapstate.CanRemove(kernel, true), Equals, false) + c.Check(snapstate.CanRemove(info, &snapstate.SnapState{Active: false, Flags: snapstate.Flags{Required: true}}, true), Equals, false) + c.Check(snapstate.CanRemove(info, &snapstate.SnapState{Active: true, Flags: snapstate.Flags{Required: true}}, true), Equals, false) + c.Check(snapstate.CanRemove(info, &snapstate.SnapState{Active: true, Flags: snapstate.Flags{Required: true}}, false), Equals, true) } func revs(seq []*snap.SideInfo) []int { diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index f36cb5e62d6..9f9f295e55e 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -874,16 +874,28 @@ func canDisable(st *snap.Info) bool { } // canRemove verifies that a snap can be removed. -func canRemove(si *snap.Info, active bool) bool { +func canRemove(si *snap.Info, snapst *SnapState, removeAll bool) bool { + // removing single revisions is generally allowed + if !removeAll { + return true + } + + // required snaps cannot be removed + if snapst.Required { + return false + } + + // TODO: use Required for these too // Gadget snaps should not be removed as they are a key - // building block for Gadgets. Pruning non active ones - // is acceptable. - if si.Type == snap.TypeGadget && active { + // building block for Gadgets. Do not remove their last + // revision left. + if si.Type == snap.TypeGadget { return false } - // You never want to remove an active kernel or OS - if (si.Type == snap.TypeKernel || si.Type == snap.TypeOS) && active { + // You never want to remove a kernel or OS Do not remove their + // last revision left. + if si.Type == snap.TypeKernel || si.Type == snap.TypeOS { return false } // TODO: on classic likely let remove core even if active if it's only snap left. @@ -916,11 +928,9 @@ func Remove(st *state.State, name string, revision snap.Revision) (*state.TaskSe active := snapst.Active var removeAll bool if revision.Unset() { - removeAll = true revision = snapst.Current + removeAll = true } else { - removeAll = false - if active { if revision == snapst.Current { msg := "cannot remove active revision %s of snap %q" @@ -935,6 +945,8 @@ func Remove(st *state.State, name string, revision snap.Revision) (*state.TaskSe if !revisionInSequence(&snapst, revision) { return nil, &snap.NotInstalledError{Snap: name, Rev: revision} } + + removeAll = len(snapst.Sequence) == 1 } info, err := Info(st, name, revision) @@ -943,7 +955,7 @@ func Remove(st *state.State, name string, revision snap.Revision) (*state.TaskSe } // check if this is something that can be removed - if !canRemove(info, active) { + if !canRemove(info, &snapst, removeAll) { return nil, fmt.Errorf("snap %q is not removable", name) } @@ -987,7 +999,7 @@ func Remove(st *state.State, name string, revision snap.Revision) (*state.TaskSe addNext(state.NewTaskSet(stopSnapServices, removeAliases, unlink, removeSecurity)) } - if removeAll || len(snapst.Sequence) == 1 { + if removeAll { seq := snapst.Sequence for i := len(seq) - 1; i >= 0; i-- { si := seq[i] diff --git a/partition/grub.go b/partition/grub.go index e2240321935..862aaedb70f 100644 --- a/partition/grub.go +++ b/partition/grub.go @@ -20,22 +20,15 @@ package partition import ( - "fmt" + "os" "path/filepath" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/osutil" - - "github.com/mvo5/goconfigparser" -) - -// var to make it testable -var ( - grubEnvCmd = "/usr/bin/grub-editenv" + "github.com/snapcore/snapd/partition/grubenv" ) -type grub struct { -} +type grub struct{} // newGrub create a new Grub bootloader object func newGrub() Bootloader { @@ -64,40 +57,27 @@ func (g *grub) envFile() string { } func (g *grub) GetBootVars(names ...string) (map[string]string, error) { - out := map[string]string{} + out := make(map[string]string) - // Grub doesn't provide a get verb, so retrieve all values and - // search for the required variable ourselves. - output, err := runCommand(grubEnvCmd, g.envFile(), "list") - if err != nil { - return nil, err - } - - cfg := goconfigparser.New() - cfg.AllowNoSectionHeader = true - if err := cfg.ReadString(output); err != nil { + env := grubenv.NewEnv(g.envFile()) + if err := env.Load(); err != nil { return nil, err } for _, name := range names { - v, err := cfg.Get("", name) - if err != nil { - return nil, err - } - out[name] = v + out[name] = env.Get(name) } return out, nil } func (g *grub) SetBootVars(values map[string]string) error { - // note that strings are not quoted since because - // runCommand does not use a shell and thus adding quotes - // stores them in the environment file (which is not desirable) - args := []string{grubEnvCmd, g.envFile(), "set"} + env := grubenv.NewEnv(g.envFile()) + if err := env.Load(); err != nil && !os.IsNotExist(err) { + return err + } for k, v := range values { - args = append(args, fmt.Sprintf("%s=%s", k, v)) + env.Set(k, v) } - _, err := runCommand(args...) - return err + return env.Save() } diff --git a/partition/grub_test.go b/partition/grub_test.go index c8934fa9a4e..b64d0b8a06b 100644 --- a/partition/grub_test.go +++ b/partition/grub_test.go @@ -22,29 +22,40 @@ package partition import ( "fmt" "io/ioutil" - "os" - "sort" - - "github.com/snapcore/snapd/dirs" + "path/filepath" + "github.com/mvo5/goconfigparser" . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/dirs" ) -func mockGrubEditenvList(cmd ...string) (string, error) { - mockGrubEditenvOutput := fmt.Sprintf("%s=regular", bootmodeVar) - return mockGrubEditenvOutput, nil +func grubEnvPath() string { + return filepath.Join(dirs.GlobalRootDir, "boot/grub/grubenv") +} + +func grubEditenvSet(c *C, key, value string) { + _, err := runCommand("/usr/bin/grub-editenv", grubEnvPath(), "set", fmt.Sprintf("%s=%s", key, value)) + c.Assert(err, IsNil) } -func mockGrubFile(c *C, newPath string, mode os.FileMode) { - err := ioutil.WriteFile(newPath, []byte(""), mode) +func grubEditenvGet(c *C, key string) string { + output, err := runCommand("/usr/bin/grub-editenv", grubEnvPath(), "list") + c.Assert(err, IsNil) + cfg := goconfigparser.New() + cfg.AllowNoSectionHeader = true + err = cfg.ReadString(output) + c.Assert(err, IsNil) + v, err := cfg.Get("", key) c.Assert(err, IsNil) + return v } func (s *PartitionTestSuite) makeFakeGrubEnv(c *C) { - // these files just needs to exist g := &grub{} - mockGrubFile(c, g.ConfigFile(), 0644) - mockGrubFile(c, g.envFile(), 0644) + err := ioutil.WriteFile(g.ConfigFile(), nil, 0644) + c.Assert(err, IsNil) + grubEditenvSet(c, "k", "v") } func (s *PartitionTestSuite) TestNewGrubNoGrubReturnsNil(c *C) { @@ -72,7 +83,7 @@ func (s *PartitionTestSuite) TestGetBootloaderWithGrub(c *C) { func (s *PartitionTestSuite) TestGetBootVer(c *C) { s.makeFakeGrubEnv(c) - runCommand = mockGrubEditenvList + grubEditenvSet(c, bootmodeVar, "regular") g := newGrub() v, err := g.GetBootVars(bootmodeVar) @@ -83,11 +94,6 @@ func (s *PartitionTestSuite) TestGetBootVer(c *C) { func (s *PartitionTestSuite) TestSetBootVer(c *C) { s.makeFakeGrubEnv(c) - cmds := [][]string{} - runCommand = func(cmd ...string) (string, error) { - cmds = append(cmds, cmd) - return "", nil - } g := newGrub() err := g.SetBootVars(map[string]string{ @@ -95,12 +101,7 @@ func (s *PartitionTestSuite) TestSetBootVer(c *C) { "k2": "v2", }) c.Assert(err, IsNil) - c.Check(cmds, HasLen, 1) - c.Check(cmds[0][0:3], DeepEquals, []string{ - "/usr/bin/grub-editenv", g.(*grub).envFile(), "set", - }) - // need to sort, its coming from a slice - kwargs := cmds[0][3:] - sort.Strings(kwargs) - c.Check(kwargs, DeepEquals, []string{"k1=v1", "k2=v2"}) + + c.Check(grubEditenvGet(c, "k1"), Equals, "v1") + c.Check(grubEditenvGet(c, "k2"), Equals, "v2") } diff --git a/partition/grubenv/grubenv.go b/partition/grubenv/grubenv.go new file mode 100644 index 00000000000..c8108da495f --- /dev/null +++ b/partition/grubenv/grubenv.go @@ -0,0 +1,123 @@ +// -*- 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 . + * + */ + +package grubenv + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" +) + +// FIXME: support for escaping (embedded \n in grubenv) missing +type Env struct { + env map[string]string + ordering []string + + path string +} + +func NewEnv(path string) *Env { + return &Env{ + env: make(map[string]string), + path: path, + } +} + +func (g *Env) Get(name string) string { + return g.env[name] +} + +func (g *Env) Set(key, value string) { + var contains = func(needle string, haystack []string) bool { + for _, k := range haystack { + if k == key { + return true + } + } + return false + } + if !contains(key, g.ordering) { + g.ordering = append(g.ordering, key) + } + + g.env[key] = value +} + +func (g *Env) Load() error { + buf, err := ioutil.ReadFile(g.path) + if err != nil { + return err + } + if len(buf) != 1024 { + return fmt.Errorf("grubenv %q must be exactly 1024 byte, got %d", g.path, len(buf)) + } + if !bytes.HasPrefix(buf, []byte("# GRUB Environment Block\n")) { + return fmt.Errorf("cannot find grubenv header in %q", g.path) + } + rawEnv := bytes.Split(buf, []byte("\n")) + for _, env := range rawEnv[1:] { + l := bytes.SplitN(env, []byte("="), 2) + // be liberal in what you accept + if len(l) < 2 { + continue + } + k := string(l[0]) + v := string(l[1]) + g.env[k] = v + g.ordering = append(g.ordering, k) + } + + return nil +} + +func (g *Env) Save() error { + w := bytes.NewBuffer(nil) + w.Grow(1024) + + fmt.Fprintf(w, "# GRUB Environment Block\n") + for _, k := range g.ordering { + if _, err := fmt.Fprintf(w, "%s=%s\n", k, g.env[k]); err != nil { + return err + } + } + if w.Len() > 1024 { + return fmt.Errorf("cannot write grubenv %q: bigger than 1024 bytes (%d)", g.path, w.Len()) + } + content := w.Bytes()[:w.Cap()] + for i := w.Len(); i < len(content); i++ { + content[i] = '#' + } + + // write in place to avoid the file moving on disk + // (thats what grubenv is also doing) + f, err := os.Create(g.path) + if err != nil { + return err + } + if _, err := f.Write(content); err != nil { + return err + } + if err := f.Sync(); err != nil { + return err + } + + return f.Close() +} diff --git a/partition/grubenv/grubenv_test.go b/partition/grubenv/grubenv_test.go new file mode 100644 index 00000000000..6da4bdfe43d --- /dev/null +++ b/partition/grubenv/grubenv_test.go @@ -0,0 +1,94 @@ +// -*- 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 . + * + */ + +package grubenv_test + +import ( + "fmt" + "io/ioutil" + "path/filepath" + "testing" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/partition/grubenv" +) + +// Hook up check.v1 into the "go test" runner +func Test(t *testing.T) { TestingT(t) } + +type grubenvTestSuite struct { + envPath string +} + +var _ = Suite(&grubenvTestSuite{}) + +func (g *grubenvTestSuite) SetUpTest(c *C) { + g.envPath = filepath.Join(c.MkDir(), "grubenv") +} + +func (g *grubenvTestSuite) TestSet(c *C) { + env := grubenv.NewEnv(g.envPath) + c.Check(env, NotNil) + + env.Set("key", "value") + c.Check(env.Get("key"), Equals, "value") +} + +func (g *grubenvTestSuite) TestSave(c *C) { + env := grubenv.NewEnv(g.envPath) + c.Check(env, NotNil) + + env.Set("key1", "value1") + env.Set("key2", "value2") + env.Set("key3", "value3") + env.Set("key4", "value4") + env.Set("key5", "value5") + env.Set("key6", "value6") + env.Set("key7", "value7") + // set "key1" again, ordering (position) does not change + env.Set("key1", "value1") + + err := env.Save() + c.Assert(err, IsNil) + + buf, err := ioutil.ReadFile(g.envPath) + c.Assert(err, IsNil) + c.Assert(buf, DeepEquals, []byte(`# GRUB Environment Block +key1=value1 +key2=value2 +key3=value3 +key4=value4 +key5=value5 +key6=value6 +key7=value7 +###################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################`)) +} + +func (g *grubenvTestSuite) TestSaveOverflow(c *C) { + env := grubenv.NewEnv(g.envPath) + c.Check(env, NotNil) + + for i := 0; i < 101; i++ { + env.Set(fmt.Sprintf("key%d", i), "foo") + } + + err := env.Save() + c.Assert(err, ErrorMatches, `cannot write grubenv .*: bigger than 1024 bytes \(1026\)`) +} diff --git a/spread.yaml b/spread.yaml index c0329a18bbb..a81157ad685 100644 --- a/spread.yaml +++ b/spread.yaml @@ -22,11 +22,15 @@ environment: # important to ensure adhoc and linode/qemu behave the same SUDO_USER: "" SUDO_UID: "" - TRUST_TEST_KEYS: "true" + TRUST_TEST_KEYS: "$(HOST: echo ${SPREAD_TRUST_TEST_KEYS:-true})" MANAGED_DEVICE: "false" CORE_CHANNEL: "$(HOST: echo ${SPREAD_CORE_CHANNEL:-edge})" # slight abuse GENERATE_14_04: "$(HOST: while [ `pwd` != / ] && [ ! -f spread.yaml ]; do cd ..; done && ./generate-packaging-dir ubuntu 14.04)" + REMOTE_STORE: "$(HOST: echo ${SPREAD_REMOTE_STORE:-production})" + SNAPPY_USE_STAGING_STORE: "$(HOST: if [ $SPREAD_REMOTE_STORE = staging ]; then echo 1; else echo 0; fi)" + DELTA_REF: 2.17 + DELTA_PREFIX: snapd-$DELTA_REF/ backends: linode: @@ -148,6 +152,12 @@ path: /home/gopath/src/github.com/snapcore/snapd exclude: - .git + - cmd/snap/snap + - cmd/snapd/snapd + - cmd/snapctl/snapctl + - cmd/snap-exec/snap-exec + - "*.o" + - "*.a" prepare-each: | # systemd on 14.04 does not know about --rotate @@ -165,122 +175,49 @@ debug-each: | journalctl -u snapd dmesg | grep DENIED || true -prepare: | - # this indicates that the server got reused, nothing to setup - [ "$REUSE_PROJECT" != 1 ] || exit 0 - echo "Running with SNAP_REEXEC: $SNAP_REEXEC" - # check that we are not updating - . "$TESTSLIB/boot.sh" - if [ "$(bootenv snap_mode)" = "try" ]; then - echo "Ongoing reboot upgrade process, please try again when finished" - exit 1 - fi - - if [ "$SPREAD_BACKEND" = external ]; then - # build test binaries - if [ ! -f $GOPATH/bin/snapbuild ]; then - mkdir -p $GOPATH/bin - snap install --devmode --edge classic - classic "sudo apt update && apt install -y git golang-go build-essential" - classic "GOPATH=$GOPATH go get ../..${PROJECT_PATH}/tests/lib/snapbuild" - snap remove classic - fi - # stop and disable autorefresh - systemctl disable --now snapd.refresh.timer - exit 0 - fi +rename: + # Move content into a directory, so that deltas computed by repack benefit + # from the content looking similar to codeload.github.com. + - s,^,$DELTA_PREFIX,S - if [ "$SPREAD_BACKEND" = qemu ]; then - # treat APT_PROXY as a location of apt-cacher-ng to use - if [ -d /etc/apt/apt.conf.d ] && [ -n "${APT_PROXY:-}" ]; then - printf 'Acquire::http::Proxy "%s";\n' "$APT_PROXY" > /etc/apt/apt.conf.d/99proxy - fi +repack: | + # For Linode, compute a delta based on a known git reference that can be + # obtained directly from GitHub. There's nothing special about that reference, + # other than it will often be in the local repository's history already. + # The more recent the reference, the smaller the delta. + if ! echo $SPREAD_BACKENDS | grep linode; then + cat <&3 >&4 + elif ! git show-ref $DELTA_REF > /dev/null; then + cat <&3 >&4 + else + trap "rm -f delta-ref.tar current.delta" EXIT + git archive -o delta-ref.tar --format=tar --prefix=$DELTA_PREFIX $DELTA_REF + xdelta3 -s delta-ref.tar <&3 > current.delta + tar c current.delta >&4 fi +prepare: | # 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-get update - - # XXX: This seems to be required. Otherwise package build - # fails with unmet dependency on "build-essential:native" - apt-get install -y build-essential - - apt-get install -y software-properties-common - - if [[ "$SPREAD_SYSTEM" == ubuntu-14.04-* ]]; then - if [ ! -d debian-ubuntu-14.04 ]; then - echo "no debian-ubuntu-14.04/ directory " - echo "broken test setup" - exit 1 - fi - - # 14.04 has its own packaging - rm -rf debian - mv debian-ubuntu-14.04 debian - - echo 'deb http://archive.ubuntu.com/ubuntu/ trusty-proposed main universe' >> /etc/apt/sources.list - apt-get update - - add-apt-repository ppa:snappy-dev/image - apt-get update - apt-get install -y --install-recommends linux-generic-lts-xenial - apt-get install -y --force-yes apparmor libapparmor1 seccomp libseccomp2 systemd cgroup-lite util-linux - - fi - - apt-get purge -y snapd snap-confine ubuntu-core-launcher - apt-get update - # utilities - apt-get install -y curl devscripts expect gdebi-core jq rng-tools git - - # in 16.04: apt build-dep -y ./ - apt-get install -y $(gdebi --quiet --apt-line ./debian/control) - - # update vendoring - if [ "$(which govendor)" = "" ]; then - rm -rf $GOPATH/src/github.com/kardianos/govendor - go get -u github.com/kardianos/govendor + # Unpack delta, or move content out of the prefixed directory (see rename and repack above). + # (needs to be in spread.yaml directly because there's nothing else on the filesystem yet) + if [ -f current.delta ]; then + tf=$(mktemp) + # poor-man's "quiet" + apt-get update >& "$tf" || ( cat "$tf"; exit 1 ) + apt-get install -y xdelta3 curl >& "$tf" || ( cat "$tf"; exit 1 ) + rm -f "$tf" + curl -sS -o - https://codeload.github.com/snapcore/snapd/tar.gz/$DELTA_REF | gunzip > delta-ref.tar + xdelta3 -q -d -s delta-ref.tar current.delta | tar x --strip-components=1 + rm -f delta-ref.tar current.delta + elif [ -d $DELTA_PREFIX ]; then + find $DELTA_PREFIX -mindepth 1 -maxdepth 1 -exec mv {} . \; + rmdir $DELTA_PREFIX fi - govendor sync - # increment version so upgrade can work, use "zzz" as version - # component to ensure that its higher than any "ubuntuN" version - # that might also be in the archive - dch -lzzz "testing build" - - if ! id test >& /dev/null; then - # manually setting the UID and GID to 12345 because we need to - # know the numbers match for when we set up the user inside - # the all-snap, which has its own user & group database. - # Nothing special about 12345 beyond it being high enough it's - # unlikely to ever clash with anything, and easy to remember. - addgroup --quiet --gid 12345 test - adduser --quiet --uid 12345 --gid 12345 --disabled-password --gecos '' test - fi - - owner=$( stat -c "%U:%G" /home/test ) - if [ "$owner" != "test:test" ]; then - echo "expected /home/test to be test:test but it's $owner" - exit 1 - fi - unset owner - - echo 'test ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers - chown test.test -R .. - su -l -c "cd $PWD && DEB_BUILD_OPTIONS='nocheck testkeys' dpkg-buildpackage -tc -b -Zgzip" test - # put our debs to a safe place - cp ../*.deb $GOPATH - - # Build snapbuild. - apt-get install -y git - go get ./tests/lib/snapbuild - - # Build fakestore. - go get ./tests/lib/fakestore/cmd/fakestore - # Build fakedevicesvc. - go get ./tests/lib/fakedevicesvc + . "$TESTSLIB/prepare-project.sh" restore: | if [ "$SPREAD_BACKEND" = external ]; then @@ -299,8 +236,13 @@ suites: else prepare_classic fi + prepare-each: | + . $TESTSLIB/prepare.sh + if [[ "$SPREAD_SYSTEM" != ubuntu-core-16-* ]]; then + prepare_each_classic + fi restore: | - $TESTSLIB/reset.sh + $TESTSLIB/reset.sh --store if [[ "$SPREAD_SYSTEM" != ubuntu-core-16-* ]]; then apt-get purge -y snapd snap-confine ubuntu-core-launcher fi @@ -327,8 +269,18 @@ suites: tests/upgrade/: summary: Tests for snapd upgrade - systems: [-ubuntu-core-16-64-fixme] - restore: + systems: [-ubuntu-core-16-64-fixme, -ubuntu-core-16-64, -ubuntu-core-16-arm-64, -ubuntu-core-16-arm-32] + restore: | + if [ "$REMOTE_STORE" = staging ]; then + echo "skip upgrade tests while talking to the staging store" + exit 0 + fi apt-get purge -y snapd snap-confine ubuntu-core-launcher restore-each: | + if [ "$REMOTE_STORE" = staging ]; then + echo "skip upgrade tests while talking to the staging store" + exit 0 + fi $TESTSLIB/reset.sh + +# vim:ts=4:sw=4:et diff --git a/tests/lib/assertions/pc-production.model b/tests/lib/assertions/pc-production.model new file mode 100644 index 00000000000..ce30b505b7f --- /dev/null +++ b/tests/lib/assertions/pc-production.model @@ -0,0 +1,21 @@ +type: model +authority-id: canonical +series: 16 +brand-id: canonical +model: pc +architecture: amd64 +gadget: pc +kernel: pc-kernel +timestamp: 2016-08-31T00:00:00.0Z +sign-key-sha3-384: 9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn + +AcLBXAQAAQoABgUCV8lRDQAKCRDgT5vottzAEg9GEACsSb+qXB34mwESsd7ns6VpM9BfAOOSstwB +KJlWOlcJ39M7is/fO+dxRH4XsI7Td6BI1WEf5188sJuld8APUsTPn8tPYN3JB5CJ8Edkr6p78YUW +f3Wo26USAE32ewjq9kHo6uBqIr4VixjTXfGUeDXc7tvKcduIMokSKjDLRHJRur1NC8LjkBn2ZPi8 +9d0BpJzr5y8wK0yFEyAhaS8H8LvL7VMjKG7/BkZcQ0a3jv69qh9jdmxnKDN2zcd1btRR1Giew3gw +VJ8lNtfxQSWi+nYNEuzDqwKdffo9sVyCzBC+vEH3xYYk8NpRx2QgCSzDCPMoxaJgLwhAeWz6mHQp +8EaGOsMZm7c85BXUcdJGEhZ5MpNGSzCb/ifgOKBB6zYzekiQh4TVLgi9Uk/acsLH75vNrI8Kwyl+ +r4Pahf///LbeWNwcEonaSV48S5fg3QqxEQeb42xcp6wPfRr7LN1LvQ9kRQTt42GDAlIva5HKlo0T +cUb5A4zz3IlBn/KQ4BS/2sBcixrH97tHInef4oA8IrBiBDGnIv/s4qyZ+gB5fX8Ohnn/a5bUgU5u +GmwRQ12Ix54YGJrzZocu1AiQINij4s6ZSoJAEJobI9VBK8WnV8PRmra6UJonV+qrJOiSKTJVCkAF ++RFartQL+pjF/H29FsyBkIEcPwhTslxWKUWajHsExw== diff --git a/tests/lib/assertions/pc-staging.model b/tests/lib/assertions/pc-staging.model new file mode 100644 index 00000000000..23c36f811c1 --- /dev/null +++ b/tests/lib/assertions/pc-staging.model @@ -0,0 +1,21 @@ +type: model +authority-id: canonical +series: 16 +brand-id: canonical +model: pc-amd64 +architecture: amd64 +gadget: pc +kernel: pc-kernel +timestamp: 2016-08-31T00:00:00.0Z +sign-key-sha3-384: 9BPWBFF1Xze9qNKUUjgsdJMoB_ckNMtTk6nxP3Q6jQrt5BUu-Wa7PdaWNIOaMhYI + +AcLBXAQAAQoABgUCWHizIwAKCRCv35n635X7gxtMD/9E6lmMXbeZ06LmuOj3eGEpMOxX0/w+qLvO +CCiQe/Q27AXzSqPV1q0CUFwUDqR+TuoSbo6Q46A86/BXdJaq98ndv9IqtcizIixkTCNPoMq3zHJ8 +nE2LwbOIXBfe1Y5G9mjwWknnDWxngCLgEVBbqVVU7HJWEyEMCT4bkVVAQwAVdG27IeK1AQNk0pWR +FkJcAk2/gYGF6PfaHpG5MzypnK4ZA9PbokcIz3+TabT0/fcnaRKnV2tm6L6qSkbGLqmmBm8z0i0U +6jty9xJpi/8Jyszbvm1oQCr82NrAMZf73Wjmncb3BLPwG8kIr0WqFrW+O3+LrVu1LpttwqIR2lii +4J4GoEkOt5b+O1IQy2H7u7VctSKA2elJn18PlEFSzO3qo0+GR12yfP6vOD/AZ/7dIgVEJJwf6dRa +OeybvIOxQpDlHAXCoJkqsOaF1v3mqWAG9UOEdAJP8wroQZ8kXBiQ3LWjh/tuAP31XJy92E0Tn1Ig +klaeXiXTAeVqqXBUUNsp6iTtZLc8tMxTgP6lcdTe2JcQxW2JpOO2gIQOnVFDrSGBC2T7Dx96b7Qo +wsEL6je9h2YRSDI9LNCgMK8jxroFDYHPJKV+8IaZ2MrrYidHfK1kIm28rdrBTwSDkFlF9dqkATK9 +e1trSFDNUQXC8+zIPK6U5FT4vfou9gn1QdF3Zbt/jg== diff --git a/tests/lib/fakestore/store/store.go b/tests/lib/fakestore/store/store.go index 2887f5f5a43..a1e3164c18e 100644 --- a/tests/lib/fakestore/store/store.go +++ b/tests/lib/fakestore/store/store.go @@ -36,6 +36,7 @@ import ( "github.com/snapcore/snapd/asserts" "github.com/snapcore/snapd/asserts/sysdb" "github.com/snapcore/snapd/asserts/systestkeys" + "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/store" ) @@ -340,14 +341,22 @@ type bulkReplyJSON struct { Payload payload `json:"_embedded"` } -var someSnapIDtoName = map[string]string{ - "b8X2psL1ryVrPt5WEmpYiqfr5emixTd7": "ubuntu-core", - "99T7MUlRhtI3U0QFgl5mXXESAiSwt776": "core", - "bul8uZn9U3Ll4ke6BMqvNVEZjuJCSQvO": "canonical-pc", - "SkKeDk2PRgBrX89DdgULk3pyY5DJo6Jk": "canonical-pc-linux", - "eFe8BTR5L5V9F7yHeMAPxkEr2NdUXMtw": "test-snapd-tools", - "Wcs8QL2iRQMjsPYQ4qz4V1uOlElZ1ZOb": "test-snapd-python-webserver", - "DVvhXhpa9oJjcm0rnxfxftH1oo5vTW1M": "test-snapd-go-webserver", +var someSnapIDtoName = map[string]map[string]string{ + "production": { + "b8X2psL1ryVrPt5WEmpYiqfr5emixTd7": "ubuntu-core", + "99T7MUlRhtI3U0QFgl5mXXESAiSwt776": "core", + "bul8uZn9U3Ll4ke6BMqvNVEZjuJCSQvO": "canonical-pc", + "SkKeDk2PRgBrX89DdgULk3pyY5DJo6Jk": "canonical-pc-linux", + "eFe8BTR5L5V9F7yHeMAPxkEr2NdUXMtw": "test-snapd-tools", + "Wcs8QL2iRQMjsPYQ4qz4V1uOlElZ1ZOb": "test-snapd-python-webserver", + "DVvhXhpa9oJjcm0rnxfxftH1oo5vTW1M": "test-snapd-go-webserver", + }, + "staging": { + "xMNMpEm0COPZy7jq9YRwWVLCD9q5peow": "core", + "02AHdOomTzby7gTaiLX3M3SGMmXDfLJp": "test-snapd-tools", + "uHjTANBWSXSiYzNOUXZNDnOSH3POSqWS": "test-snapd-python-webserver", + "edmdK5G9fP1q1bGyrjnaDXS4RkdjiTGV": "test-snapd-go-webserver", + }, } func (s *Store) bulkEndpoint(w http.ResponseWriter, req *http.Request) { @@ -366,7 +375,13 @@ func (s *Store) bulkEndpoint(w http.ResponseWriter, req *http.Request) { return } - snapIDtoName, err := addSnapIDs(bs, someSnapIDtoName) + var remoteStore string + if osutil.GetenvBool("SNAPPY_USE_STAGING_STORE") { + remoteStore = "staging" + } else { + remoteStore = "production" + } + snapIDtoName, err := addSnapIDs(bs, someSnapIDtoName[remoteStore]) if err != nil { http.Error(w, fmt.Sprintf("internal error collecting snapIDs: %v", err), http.StatusInternalServerError) return diff --git a/tests/lib/prepare-project.sh b/tests/lib/prepare-project.sh new file mode 100644 index 00000000000..178ce0de4ef --- /dev/null +++ b/tests/lib/prepare-project.sh @@ -0,0 +1,114 @@ +#!/bin/bash + +# Set REUSE_PROJECT to reuse the previous prepare when also reusing the server. +[ "$REUSE_PROJECT" != 1 ] || exit 0 +echo "Running with SNAP_REEXEC: $SNAP_REEXEC" + +# check that we are not updating +. "$TESTSLIB/boot.sh" +if [ "$(bootenv snap_mode)" = "try" ]; then + echo "Ongoing reboot upgrade process, please try again when finished" + exit 1 +fi + +# declare the "quiet" wrapper +. "$TESTSLIB/quiet.sh" + +if [ "$SPREAD_BACKEND" = external ]; then + # build test binaries + if [ ! -f $GOPATH/bin/snapbuild ]; then + mkdir -p $GOPATH/bin + snap install --devmode --edge classic + classic "sudo apt update && apt install -y git golang-go build-essential" + classic "GOPATH=$GOPATH go get ../..${PROJECT_PATH}/tests/lib/snapbuild" + snap remove classic + fi + # stop and disable autorefresh + systemctl disable --now snapd.refresh.timer + exit 0 +fi + +if [ "$SPREAD_BACKEND" = qemu ]; then + # treat APT_PROXY as a location of apt-cacher-ng to use + if [ -d /etc/apt/apt.conf.d ] && [ -n "${APT_PROXY:-}" ]; then + printf 'Acquire::http::Proxy "%s";\n' "$APT_PROXY" > /etc/apt/apt.conf.d/99proxy + fi +fi + +if [[ "$SPREAD_SYSTEM" == ubuntu-14.04-* ]]; then + if [ ! -d debian-ubuntu-14.04 ]; then + echo "no debian-ubuntu-14.04/ directory " + echo "broken test setup" + exit 1 + fi + + # 14.04 has its own packaging + rm -rf debian + mv debian-ubuntu-14.04 debian + + quiet apt-get update + quiet apt-get install -y software-properties-common + + echo 'deb http://archive.ubuntu.com/ubuntu/ trusty-proposed main universe' >> /etc/apt/sources.list + quiet add-apt-repository ppa:snappy-dev/image + quiet apt-get update + + quiet apt-get install -y --install-recommends linux-generic-lts-xenial + quiet apt-get install -y --force-yes apparmor libapparmor1 seccomp libseccomp2 systemd cgroup-lite util-linux +fi + +quiet apt-get purge -y snapd snap-confine ubuntu-core-launcher +# utilities +# XXX: build-essential seems to be required. Otherwise package build +# fails with unmet dependency on "build-essential:native" +quiet apt-get install -y build-essential curl devscripts expect gdebi-core jq rng-tools git + +# in 16.04: apt build-dep -y ./ +quiet apt-get install -y $(gdebi --quiet --apt-line ./debian/control) + +# update vendoring +if [ "$(which govendor)" = "" ]; then + rm -rf $GOPATH/src/github.com/kardianos/govendor + go get -u github.com/kardianos/govendor +fi +quiet govendor sync + +# increment version so upgrade can work, use "zzz" as version +# component to ensure that its higher than any "ubuntuN" version +# that might also be in the archive +dch -lzzz "testing build" + +if ! id test >& /dev/null; then + # manually setting the UID and GID to 12345 because we need to + # know the numbers match for when we set up the user inside + # the all-snap, which has its own user & group database. + # Nothing special about 12345 beyond it being high enough it's + # unlikely to ever clash with anything, and easy to remember. + addgroup --quiet --gid 12345 test + adduser --quiet --uid 12345 --gid 12345 --disabled-password --gecos '' test +fi + +owner=$( stat -c "%U:%G" /home/test ) +if [ "$owner" != "test:test" ]; then + echo "expected /home/test to be test:test but it's $owner" + exit 1 +fi +unset owner + +echo 'test ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers +chown test.test -R .. +quiet su -l -c "cd $PWD && DEB_BUILD_OPTIONS='nocheck testkeys' dpkg-buildpackage -tc -b -Zgzip" test +# put our debs to a safe place +cp ../*.deb $GOPATH + +# Build snapbuild. +go get ./tests/lib/snapbuild +# Build fakestore. + +fakestore_tags= +if [ "$REMOTE_STORE" = staging ]; then + fakestore_tags="-tags withstagingkeys" +fi +go get $fakestore_tags ./tests/lib/fakestore/cmd/fakestore +# Build fakedevicesvc. +go get ./tests/lib/fakedevicesvc diff --git a/tests/lib/prepare.sh b/tests/lib/prepare.sh index 9f893d92b6b..773a02e157f 100755 --- a/tests/lib/prepare.sh +++ b/tests/lib/prepare.sh @@ -54,6 +54,18 @@ update_core_snap_for_classic_reexec() { done } +prepare_each_classic() { + if [ -z "${SNAP_REEXEC:-}" ]; then + rm -f /etc/systemd/system/snapd.service.d/reexec.conf + else + cat < /etc/systemd/system/snapd.service.d/reexec.conf +[Service] +Environment=SNAP_REEXEC=$SNAP_REEXEC +EOF + fi + +} + prepare_classic() { apt_install_local ${GOPATH}/snap-confine*.deb ${GOPATH}/ubuntu-core-launcher_*.deb apt_install_local ${GOPATH}/snapd_*.deb @@ -70,19 +82,12 @@ prepare_classic() { exit 1 fi - # Disable burst limit so resetting the state quickly doesn't create - # problems. mkdir -p /etc/systemd/system/snapd.service.d - if [ -n "${SNAP_REEXEC:-}" ]; then - EXTRA_ENV="SNAP_REEXEC=$SNAP_REEXEC" - else - EXTRA_ENV="" - fi cat < /etc/systemd/system/snapd.service.d/local.conf [Unit] StartLimitInterval=0 [Service] -Environment=SNAPD_DEBUG_HTTP=7 SNAPPY_TESTING=1 $EXTRA_ENV +Environment=SNAPD_DEBUG_HTTP=7 SNAPD_DEBUG=1 SNAPPY_TESTING=1 EOF mkdir -p /etc/systemd/system/snapd.socket.d cat < /etc/systemd/system/snapd.socket.d/local.conf @@ -93,6 +98,10 @@ EOF # Snapshot the state including core. if [ ! -f $SPREAD_PATH/snapd-state.tar.gz ]; then ! snap list | grep core || exit 1 + if [ "$REMOTE_STORE" = staging ]; then + . $TESTSLIB/store.sh + setup_staging_store + fi # use parameterized core channel (defaults to edge) instead # of a fixed one and close to stable in order to detect defects # earlier @@ -196,29 +205,7 @@ EOF snapbuild $UNPACKD $IMAGE_HOME # FIXME: fetch directly once its in the assertion service - cat > $IMAGE_HOME/pc.model <&/dev/null + + # not strictly needed because it's a subshell, but good practice + local tf retval + + tf="$(tempfile)" + + set +e + "$@" >& "$tf" + retval=$? + set -e + + if [ "$retval" != "0" ]; then + echo "quiet: $*" >&2 + echo "quiet: exit status $retval. Output follows:" >&2 + cat "$tf" >&2 + echo "quiet: end of output." >&2 + fi + + rm -f -- "$tf" + + return $retval +) diff --git a/tests/lib/reset.sh b/tests/lib/reset.sh index 216e54b11d1..f9e9e4c02cd 100755 --- a/tests/lib/reset.sh +++ b/tests/lib/reset.sh @@ -16,7 +16,7 @@ reset_classic() { exit 1 fi - if [[ "$SPREAD_SYSTEM" == ubuntu-14.04-* ]]; then + if [[ "$SPREAD_SYSTEM" == ubuntu-14.04-* ]]; then systemctl start snap.mount.service fi @@ -66,3 +66,8 @@ if [[ "$SPREAD_SYSTEM" == ubuntu-core-16-* ]]; then else reset_classic "$@" fi + +if [ "$REMOTE_STORE" = staging ] && [ "$1" = "--store" ]; then + . $TESTSLIB/store.sh + teardown_staging_store +fi diff --git a/tests/lib/store.sh b/tests/lib/store.sh index 2f07e63309f..2cad8f280ed 100644 --- a/tests/lib/store.sh +++ b/tests/lib/store.sh @@ -4,8 +4,8 @@ STORE_CONFIG=/etc/systemd/system/snapd.service.d/store.conf . $TESTSLIB/systemd.sh _configure_store_backends(){ - systemctl stop snapd.service snapd.socket - mkdir -p $(dirname $STORE_CONFIG) + systemctl stop snapd.service snapd.socket snapd.refresh.timer + rm -rf $(dirname $STORE_CONFIG) && mkdir -p $(dirname $STORE_CONFIG) cat > $STORE_CONFIG </dev/null | grep -q ^basic; then + if snap list 2>/dev/null |grep -q -E "^basic" ; then break fi done - snap list | grep ^basic + echo "Verifying the imported assertions" + if ! snap known model|MATCH "type: model" ; then + echo "Model assertion was not imported on firstboot" + exit 1 + fi + + snap list |grep -q -E "^basic" test -f $SEED_DIR/snaps/basic.snap diff --git a/tests/main/local-install-w-metadata/task.yaml b/tests/main/local-install-w-metadata/task.yaml index a8bc3c6e40e..c5c8e7bf1b7 100644 --- a/tests/main/local-install-w-metadata/task.yaml +++ b/tests/main/local-install-w-metadata/task.yaml @@ -2,7 +2,7 @@ summary: Checks for local install with metadata from assertions # XXX we would need to bother with curl there atm systems: [-ubuntu-core-16-64, -ubuntu-core-16-arm-64, -ubuntu-core-16-arm-32] restore: | - rm -f test-snapd-tools_*.snap + rm -f test-snapd-tools_*.{snap,assert} execute: | echo "Get the snap" snap download test-snapd-tools @@ -10,15 +10,8 @@ execute: | echo "Try to install the snap without assertions" (snap install test-snapd-tools_*.snap 2>&1 || true) | grep -q 'cannot find signatures with metadata for snap "test-snapd-tools.*\.snap"' - echo "Get its assertions" - # XXX snap download should do this as well - curl -H "Accept: application/x.ubuntu.assertion" -o snap-decl.assertion https://assertions.ubuntu.com/v1/assertions/snap-declaration/16/eFe8BTR5L5V9F7yHeMAPxkEr2NdUXMtw - # XXX have a 'snap snap-digest' command? - digest=$(go run digest.go test-snapd-tools_*.snap) - curl -H "Accept: application/x.ubuntu.assertion" -o snap-rev.assertion https://assertions.ubuntu.com/v1/assertions/snap-revision/${digest} - # add them - snap ack snap-decl.assertion - snap ack snap-rev.assertion + echo "Add its assertions" + snap ack test-snapd-tools_*.assert echo "Installing the snap file will use the metadata from assertions" snap install test-snapd-tools_*.snap diff --git a/tests/main/manpages/task.yaml b/tests/main/manpages/task.yaml new file mode 100644 index 00000000000..94fabc11275 --- /dev/null +++ b/tests/main/manpages/task.yaml @@ -0,0 +1,11 @@ +summary: the essential manual pages are installed by the native package +# core systems don't ship man or manual pages +systems: [-ubuntu-core-16-64, -ubuntu-core-16-64-fixme, -ubuntu-core-16-arm-32, -ubuntu-core-16-arm-64] +execute: | + for manpage in snap snap-confine snap-discard-ns ubuntu-core-launcher; do + if ! man --what $manpage; then + echo "Expected to see manual page for $manpage" + exit 1 + fi + done +# TODO: add manual pages for snapctl, snap-exec and snapd diff --git a/tests/main/prepare-image-grub/task.yaml b/tests/main/prepare-image-grub/task.yaml index 176cc4253c8..054df81ac36 100644 --- a/tests/main/prepare-image-grub/task.yaml +++ b/tests/main/prepare-image-grub/task.yaml @@ -11,10 +11,10 @@ environment: UBUNTU_IMAGE_SKIP_COPY_UNVERIFIED_SNAPS: 1 prepare: | . $TESTSLIB/store.sh - setup_store fake $STORE_DIR + setup_fake_store $STORE_DIR restore: | . $TESTSLIB/store.sh - teardown_store fake $STORE_DIR + teardown_fake_store $STORE_DIR rm -rf $ROOT execute: | echo Expose the needed assertions through the fakestore @@ -24,7 +24,7 @@ execute: | export SNAPPY_FORCE_SAS_URL=http://$STORE_ADDR echo Running prepare-image - su -c "snap prepare-image --channel edge --extra-snaps snapweb $TESTSLIB/assertions/developer1-pc.model $ROOT" test + su -c "SNAPPY_USE_STAGING_STORE=$SNAPPY_USE_STAGING_STORE snap prepare-image --channel edge --extra-snaps snapweb $TESTSLIB/assertions/developer1-pc.model $ROOT" test echo Verifying the result ls -lR $IMAGE @@ -44,10 +44,17 @@ execute: | echo "Verify that we have valid looking seed.yaml" cat $IMAGE/var/lib/snapd/seed/seed.yaml + # snap-id of core - grep -q "snap-id: 99T7MUlRhtI3U0QFgl5mXXESAiSwt776" $IMAGE/var/lib/snapd/seed/seed.yaml + if [ "$REMOTE_STORE" = production ]; then + core_snap_id="99T7MUlRhtI3U0QFgl5mXXESAiSwt776" + else + core_snap_id="xMNMpEm0COPZy7jq9YRwWVLCD9q5peow" + fi + grep -q "snap-id: ${core_snap_id}" $IMAGE/var/lib/snapd/seed/seed.yaml + for snap in pc pc-kernel core; do - grep -q "name: $snap" $IMAGE/var/lib/snapd/seed/seed.yaml + grep -q "name: $snap" $IMAGE/var/lib/snapd/seed/seed.yaml done echo "Verify that we got some snap assertions" diff --git a/tests/main/prepare-image-uboot/task.yaml b/tests/main/prepare-image-uboot/task.yaml index bc8c2dee91d..f63f3505c8c 100644 --- a/tests/main/prepare-image-uboot/task.yaml +++ b/tests/main/prepare-image-uboot/task.yaml @@ -30,7 +30,7 @@ execute: | export UBUNTU_IMAGE_SKIP_COPY_UNVERIFIED_MODEL=1 echo Running prepare-image as a user - su -c "snap prepare-image --channel edge --extra-snaps snapweb $ROOT/model.assertion $ROOT" test + su -c "SNAPPY_USE_STAGING_STORE=$SNAPPY_USE_STAGING_STORE snap prepare-image --channel edge --extra-snaps snapweb $ROOT/model.assertion $ROOT" test echo Verifying the result ls -lR $IMAGE @@ -52,7 +52,13 @@ execute: | echo Verify that we have valid looking seed.yaml cat $IMAGE/var/lib/snapd/seed/seed.yaml # snap-id of core - grep -q "snap-id: 99T7MUlRhtI3U0QFgl5mXXESAiSwt776" $IMAGE/var/lib/snapd/seed/seed.yaml + if [ "$REMOTE_STORE" = staging ]; then + core_id="xMNMpEm0COPZy7jq9YRwWVLCD9q5peow" + else + core_id="99T7MUlRhtI3U0QFgl5mXXESAiSwt776" + fi + + grep -q "snap-id: $core_id" $IMAGE/var/lib/snapd/seed/seed.yaml for snap in pi2 pi2-kernel core; do grep -q "name: $snap" $IMAGE/var/lib/snapd/seed/seed.yaml done diff --git a/tests/main/refresh-all-undo/task.yaml b/tests/main/refresh-all-undo/task.yaml index e15cd045d25..0866660f1aa 100644 --- a/tests/main/refresh-all-undo/task.yaml +++ b/tests/main/refresh-all-undo/task.yaml @@ -16,15 +16,16 @@ prepare: | done echo "And the daemon is configured to point to the fake store" - setup_store fake $BLOB_DIR + setup_fake_store $BLOB_DIR restore: | . $TESTSLIB/store.sh - teardown_store fake $BLOB_DIR + teardown_fake_store $BLOB_DIR execute: | echo "When the store is configured to make them refreshable" - fakestore -make-refreshable $GOOD_SNAP,$BAD_SNAP -dir $BLOB_DIR + . $TESTSLIB/store.sh + init_fake_refreshes $GOOD_SNAP,$BAD_SNAP $BLOB_DIR echo "When a snap is broken" echo "i-am-broken-now" >> $BLOB_DIR/${BAD_SNAP}*fake1*.snap diff --git a/tests/main/refresh-all/task.yaml b/tests/main/refresh-all/task.yaml index a91e3f3c21f..4a8ff55f5d8 100644 --- a/tests/main/refresh-all/task.yaml +++ b/tests/main/refresh-all/task.yaml @@ -19,15 +19,16 @@ prepare: | done echo "And the daemon is configured to point to the fake store" - setup_store fake $BLOB_DIR + setup_fake_store $BLOB_DIR restore: | . $TESTSLIB/store.sh - teardown_store fake $BLOB_DIR + teardown_fake_store $BLOB_DIR execute: | echo "When the store is configured to make them refreshable" - fakestore -make-refreshable test-snapd-tools,test-snapd-python-webserver -dir $BLOB_DIR + . $TESTSLIB/store.sh + init_fake_refreshes test-snapd-tools,test-snapd-python-webserver $BLOB_DIR echo "And a refresh is performed" snap refresh diff --git a/tests/main/refresh-devmode/task.yaml b/tests/main/refresh-devmode/task.yaml index 133ac30541b..94925fd702a 100644 --- a/tests/main/refresh-devmode/task.yaml +++ b/tests/main/refresh-devmode/task.yaml @@ -1,9 +1,9 @@ summary: Check that the refresh command works. details: | These tests exercise the refresh command using different store backends. - The concrete store to be used is controlled with the STORE_TYPE environment - vairiable, if it is empty, a fake local store is used, if it has a value of - staginig or production the corresponding remote store is used. + The concrete store to be used is controlled with the STORE_TYPE variant, + the defined values are fake, for a local store, or remote, for the currently + configured remote store. When executing against the remote stores the tests rely in the existence of a given snap with an updatable version (version string like 2.0+fake1) in the edge channel. @@ -13,35 +13,34 @@ environment: SNAP_VERSION_PATTERN: \d+\.\d+\+fake1 BLOB_DIR: $(pwd)/fake-store-blobdir STORE_TYPE/fake: fake -# STORE_TYPE/staging: staging - STORE_TYPE/production: production + STORE_TYPE/remote: ${REMOTE_STORE} prepare: | if [[ "$STORE_TYPE" = "fake" ]] && [[ "$SPREAD_SYSTEM" =~ ubuntu-core-16-* ]]; then exit fi - if [ "$STORE_TYPE" = "fake" ]; then - echo "Given a snap is installed" - snap install --devmode test-snapd-tools - fi - - . $TESTSLIB/store.sh - setup_store $STORE_TYPE $BLOB_DIR + echo "Given a snap is installed" + snap install --devmode test-snapd-tools if [ "$STORE_TYPE" = "fake" ]; then + . $TESTSLIB/store.sh + setup_fake_store $BLOB_DIR + echo "And a new version of that snap put in the controlled store" - fakestore -dir $BLOB_DIR -make-refreshable test-snapd-tools + . $TESTSLIB/store.sh + init_fake_refreshes test-snapd-tools $BLOB_DIR fi restore: | - if [[ "$STORE_TYPE" = "fake" ]] && [[ "$SPREAD_SYSTEM" =~ ubuntu-core-16-* ]]; then - exit + if [ "$STORE_TYPE" = "fake" ]; then + if [[ "$SPREAD_SYSTEM" =~ ubuntu-core-16-* ]]; then + exit + fi + . $TESTSLIB/store.sh + teardown_fake_store $BLOB_DIR fi - . $TESTSLIB/store.sh - teardown_store $STORE_TYPE $BLOB_DIR - execute: | if [[ "$STORE_TYPE" = "fake" ]] && [[ "$SPREAD_SYSTEM" =~ ubuntu-core-16-* ]]; then exit diff --git a/tests/main/refresh/task.yaml b/tests/main/refresh/task.yaml index 5ee1e0481a8..82e489c856d 100644 --- a/tests/main/refresh/task.yaml +++ b/tests/main/refresh/task.yaml @@ -1,9 +1,9 @@ summary: Check that the refresh command works. details: | These tests exercise the refresh command using different store backends. - The concrete store to be used is controlled with the STORE_TYPE environment - vairiable, if it is empty, a fake local store is used, if it has a value of - staginig or production the corresponding remote store is used. + The concrete store to be used is controlled with the STORE_TYPE variant, + the defined values are fake, for a local store, or remote, for the currently + configured remote store. When executing against the remote stores the tests rely in the existence of a given snap with an updatable version (version string like 2.0+fake1) in the edge channel. @@ -13,35 +13,34 @@ environment: SNAP_VERSION_PATTERN: \d+\.\d+\+fake1 BLOB_DIR: $(pwd)/fake-store-blobdir STORE_TYPE/fake: fake -# STORE_TYPE/staging: staging - STORE_TYPE/production: production + STORE_TYPE/remote: ${REMOTE_STORE} prepare: | if [[ "$STORE_TYPE" = "fake" ]] && [[ "$SPREAD_SYSTEM" =~ ubuntu-core-16-* ]]; then exit fi - if [ "$STORE_TYPE" = "fake" ]; then - echo "Given a snap is installed" - snap install test-snapd-tools - fi - - . $TESTSLIB/store.sh - setup_store $STORE_TYPE $BLOB_DIR + echo "Given a snap is installed" + snap install test-snapd-tools if [ "$STORE_TYPE" = "fake" ]; then + . $TESTSLIB/store.sh + setup_fake_store $BLOB_DIR + echo "And a new version of that snap put in the controlled store" - fakestore -dir $BLOB_DIR -make-refreshable test-snapd-tools + . $TESTSLIB/store.sh + init_fake_refreshes test-snapd-tools $BLOB_DIR fi restore: | - if [[ "$STORE_TYPE" = "fake" ]] && [[ "$SPREAD_SYSTEM" =~ ubuntu-core-16-* ]]; then - exit + if [ "$STORE_TYPE" = "fake" ]; then + if [[ "$SPREAD_SYSTEM" =~ ubuntu-core-16-* ]]; then + exit + fi + . $TESTSLIB/store.sh + teardown_fake_store $BLOB_DIR fi - . $TESTSLIB/store.sh - teardown_store $STORE_TYPE $BLOB_DIR - execute: | if [[ "$STORE_TYPE" = "fake" ]] && [[ "$SPREAD_SYSTEM" =~ ubuntu-core-16-* ]]; then exit @@ -63,4 +62,4 @@ execute: | echo "When a snap is refreshed and has no update it exit 0" snap refresh $SNAP_NAME 2>stderr.out - cat stderr.out | MATCH "snap \"$SNAP_NAME\" has no updates available" \ No newline at end of file + cat stderr.out | MATCH "snap \"$SNAP_NAME\" has no updates available" diff --git a/tests/main/revert-devmode/task.yaml b/tests/main/revert-devmode/task.yaml index 63280972d7a..eb66b988bc1 100644 --- a/tests/main/revert-devmode/task.yaml +++ b/tests/main/revert-devmode/task.yaml @@ -1,8 +1,7 @@ summary: Check that revert of a snap in devmode restores devmode environment: STORE_TYPE/fake: fake -# STORE_TYPE/staging: staging - STORE_TYPE/production: production + STORE_TYPE/remote: ${REMOTE_STORE} BLOB_DIR: $(pwd)/fake-store-blobdir prepare: | @@ -10,27 +9,27 @@ prepare: | exit fi - if [ "$STORE_TYPE" = "fake" ]; then - echo "Given a snap is installed" - snap install --devmode test-snapd-tools - fi - - . $TESTSLIB/store.sh - setup_store $STORE_TYPE $BLOB_DIR + echo "Given a snap is installed" + snap install --devmode test-snapd-tools if [ "$STORE_TYPE" = "fake" ]; then + . $TESTSLIB/store.sh + setup_fake_store $BLOB_DIR + echo "And a new version of that snap put in the controlled store" - fakestore -dir $BLOB_DIR -make-refreshable test-snapd-tools + . $TESTSLIB/store.sh + init_fake_refreshes test-snapd-tools $BLOB_DIR fi restore: | - if [[ "$STORE_TYPE" = "fake" ]] && [[ "$SPREAD_SYSTEM" =~ ubuntu-core-16-* ]]; then - exit + if [ "$STORE_TYPE" = "fake" ]; then + if [[ "$SPREAD_SYSTEM" =~ ubuntu-core-16-* ]]; then + exit + fi + . $TESTSLIB/store.sh + teardown_fake_store $BLOB_DIR fi - . $TESTSLIB/store.sh - teardown_store $STORE_TYPE $BLOB_DIR - execute: | if [[ "$STORE_TYPE" = "fake" ]] && [[ "$SPREAD_SYSTEM" =~ ubuntu-core-16-* ]]; then exit diff --git a/tests/main/revert/task.yaml b/tests/main/revert/task.yaml index 3fc005d6c14..d2d52fb9144 100644 --- a/tests/main/revert/task.yaml +++ b/tests/main/revert/task.yaml @@ -2,8 +2,7 @@ summary: Check that revert works. environment: STORE_TYPE/fake: fake -# STORE_TYPE/staging: staging - STORE_TYPE/production: production + STORE_TYPE/remote: ${REMOTE_STORE} BLOB_DIR: $(pwd)/fake-store-blobdir prepare: | @@ -11,27 +10,27 @@ prepare: | exit fi - if [ "$STORE_TYPE" = "fake" ]; then - echo "Given a snap is installed" - snap install test-snapd-tools - fi - - . $TESTSLIB/store.sh - setup_store $STORE_TYPE $BLOB_DIR + echo "Given a snap is installed" + snap install test-snapd-tools if [ "$STORE_TYPE" = "fake" ]; then + . $TESTSLIB/store.sh + setup_fake_store $BLOB_DIR + echo "And a new version of that snap put in the controlled store" - fakestore -dir $BLOB_DIR -make-refreshable test-snapd-tools + . $TESTSLIB/store.sh + init_fake_refreshes test-snapd-tools $BLOB_DIR fi restore: | - if [[ "$STORE_TYPE" = "fake" ]] && [[ "$SPREAD_SYSTEM" =~ ubuntu-core-16-* ]]; then - exit + if [ "$STORE_TYPE" = "fake" ]; then + if [[ "$SPREAD_SYSTEM" =~ ubuntu-core-16-* ]]; then + exit + fi + . $TESTSLIB/store.sh + teardown_fake_store $BLOB_DIR fi - . $TESTSLIB/store.sh - teardown_store $STORE_TYPE $BLOB_DIR - execute: | if [[ "$STORE_TYPE" = "fake" ]] && [[ "$SPREAD_SYSTEM" =~ ubuntu-core-16-* ]]; then exit diff --git a/tests/main/snap-env/task.yaml b/tests/main/snap-env/task.yaml index 8ebf651a822..32a624ed21a 100644 --- a/tests/main/snap-env/task.yaml +++ b/tests/main/snap-env/task.yaml @@ -4,9 +4,11 @@ prepare: | snap install --dangerous test-snapd-tools_1.0_all.snap restore: | rm -f *.snap +debug: | + cat *-vars.txt execute: | echo "Collect SNAP and XDG environment variables" - test-snapd-tools.env | egrep '^SNAP_' | sort > snap-vars.txt + test-snapd-tools.env | egrep '^SNAP_' | egrep -v '^SNAP_DID_REEXEC'| sort > snap-vars.txt test-snapd-tools.env | egrep '^XDG_' | sort > xdg-vars.txt echo "Ensure that SNAP environment variables are what we expect" @@ -26,5 +28,3 @@ execute: | echo "Enure that XDG environment variables are what we expect" egrep -q '^XDG_RUNTIME_DIR=/run/user/0/snap.test-snapd-tools$' xdg-vars.txt test $(wc -l < xdg-vars.txt) -ge 1 -debug: | - cat *-vars.txt diff --git a/tests/main/snapd-reexec/task.yaml b/tests/main/snapd-reexec/task.yaml index f0fb2340c10..8635a403eb3 100644 --- a/tests/main/snapd-reexec/task.yaml +++ b/tests/main/snapd-reexec/task.yaml @@ -2,9 +2,6 @@ summary: Test that snapd reexecs itself into core systems: [-ubuntu-core-16-64, -ubuntu-core-16-arm-64, -ubuntu-core-16-arm-32] -environment: - SNAPD_CONF: /etc/systemd/system/snapd.service.d/local.conf - restore: | # extra cleanup in case something in this test went wrong rm -f /etc/systemd/system/snapd.service.d/no-reexec.conf @@ -17,6 +14,10 @@ restore: | fi rm -f /tmp/old-info +debug: | + ls /etc/systemd/system/snapd.service.d + cat /etc/systemd/system/snapd.service.d/* + execute: | if [ "${SNAP_REEXEC:-}" = "0" ]; then echo "skipping test when SNAP_REEXEC is disabled" @@ -41,7 +42,7 @@ execute: | umount /snap/core/current/usr/lib/snapd/info echo "Ensure SNAP_REEXEC=0 is honored for snapd" - cat > /etc/systemd/system/snapd.service.d/no-reexec.conf < /etc/systemd/system/snapd.service.d/reexec.conf <