diff -Nru snapd-2.32.3.2/boot/kernel_os.go snapd-2.32.9/boot/kernel_os.go
--- snapd-2.32.3.2/boot/kernel_os.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/boot/kernel_os.go 2018-05-16 08:20:08.000000000 +0000
@@ -128,11 +128,22 @@
// check if we actually need to do anything, i.e. the exact same
// kernel/core revision got installed again (e.g. firstboot)
- m, err := bootloader.GetBootVars(goodBoot)
+ // and we are not in any special boot mode
+ m, err := bootloader.GetBootVars("snap_mode", goodBoot)
if err != nil {
return err
}
if m[goodBoot] == blobName {
+ // If we were in "try" mode before and now switch to
+ // the good core/kernel again, make sure to clean the
+ // snap_mode here. This also mitigates
+ // https://forum.snapcraft.io/t//5253
+ if m["snap_mode"] != "" {
+ return bootloader.SetBootVars(map[string]string{
+ "snap_mode": "",
+ nextBoot: "",
+ })
+ }
return nil
}
diff -Nru snapd-2.32.3.2/boot/kernel_os_test.go snapd-2.32.9/boot/kernel_os_test.go
--- snapd-2.32.3.2/boot/kernel_os_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/boot/kernel_os_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -217,12 +217,16 @@
info.Revision = snap.R(40)
s.bootloader.BootVars["snap_kernel"] = "krnl_40.snap"
+ s.bootloader.BootVars["snap_try_kernel"] = "krnl_99.snap"
+ s.bootloader.BootVars["snap_mode"] = "try"
err := boot.SetNextBoot(info)
c.Assert(err, IsNil)
c.Assert(s.bootloader.BootVars, DeepEquals, map[string]string{
- "snap_kernel": "krnl_40.snap",
+ "snap_kernel": "krnl_40.snap",
+ "snap_try_kernel": "",
+ "snap_mode": "",
})
}
diff -Nru snapd-2.32.3.2/client/client.go snapd-2.32.9/client/client.go
--- snapd-2.32.3.2/client/client.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/client/client.go 2018-05-16 08:20:08.000000000 +0000
@@ -377,6 +377,8 @@
ErrorKindNetworkTimeout = "network-timeout"
ErrorKindInterfacesUnchanged = "interfaces-unchanged"
+
+ ErrorKindConfigNoSuchOption = "option-not-found"
)
// IsTwoFactorError returns whether the given error is due to problems
diff -Nru snapd-2.32.3.2/cmd/libsnap-confine-private/locking-test.c snapd-2.32.9/cmd/libsnap-confine-private/locking-test.c
--- snapd-2.32.3.2/cmd/libsnap-confine-private/locking-test.c 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/cmd/libsnap-confine-private/locking-test.c 2018-05-16 08:20:08.000000000 +0000
@@ -32,6 +32,12 @@
sc_lock_dir = dir;
}
+// A variant of unsetenv that is compatible with GDestroyNotify
+static void my_unsetenv(const char *k)
+{
+ unsetenv(k);
+}
+
// Use temporary directory for locking.
//
// The directory is automatically reset to the real value at the end of the
@@ -50,7 +56,7 @@
g_test_queue_free(lock_dir);
g_assert_cmpint(setenv("SNAP_CONFINE_LOCK_DIR", lock_dir, 0),
==, 0);
- g_test_queue_destroy((GDestroyNotify) unsetenv,
+ g_test_queue_destroy((GDestroyNotify) my_unsetenv,
"SNAP_CONFINE_LOCK_DIR");
g_test_queue_destroy((GDestroyNotify) rm_rf_tmp, lock_dir);
}
diff -Nru snapd-2.32.3.2/cmd/libsnap-confine-private/utils-test.c snapd-2.32.9/cmd/libsnap-confine-private/utils-test.c
--- snapd-2.32.3.2/cmd/libsnap-confine-private/utils-test.c 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/cmd/libsnap-confine-private/utils-test.c 2018-05-16 08:20:08.000000000 +0000
@@ -99,6 +99,22 @@
g_test_trap_assert_stderr("death message: Operation not permitted\n");
}
+// A variant of rmdir that is compatible with GDestroyNotify
+static void my_rmdir(const char *path)
+{
+ if (rmdir(path) != 0) {
+ die("cannot rmdir %s", path);
+ }
+}
+
+// A variant of chdir that is compatible with GDestroyNotify
+static void my_chdir(const char *path)
+{
+ if (chdir(path) != 0) {
+ die("cannot change dir to %s", path);
+ }
+}
+
/**
* Perform the rest of testing in a ephemeral directory.
*
@@ -114,9 +130,9 @@
g_assert_cmpint(err, ==, 0);
g_test_queue_free(temp_dir);
- g_test_queue_destroy((GDestroyNotify) rmdir, temp_dir);
+ g_test_queue_destroy((GDestroyNotify) my_rmdir, temp_dir);
g_test_queue_free(orig_dir);
- g_test_queue_destroy((GDestroyNotify) chdir, orig_dir);
+ g_test_queue_destroy((GDestroyNotify) my_chdir, orig_dir);
}
/**
@@ -130,7 +146,7 @@
G_FILE_TEST_IS_DIR));
// Use sc_nonfatal_mkpath to create the directory and ensure that it worked
// as expected.
- g_test_queue_destroy((GDestroyNotify) rmdir, (char *)dirname);
+ g_test_queue_destroy((GDestroyNotify) my_rmdir, (char *)dirname);
int err = sc_nonfatal_mkpath(dirname, 0755);
g_assert_cmpint(err, ==, 0);
g_assert_cmpint(errno, ==, 0);
@@ -143,7 +159,7 @@
g_assert_cmpint(errno, ==, EEXIST);
// Now create a sub-directory of the original directory and observe the
// results. We should no longer see errno of EEXIST!
- g_test_queue_destroy((GDestroyNotify) rmdir, (char *)subdirname);
+ g_test_queue_destroy((GDestroyNotify) my_rmdir, (char *)subdirname);
err = sc_nonfatal_mkpath(subdirname, 0755);
g_assert_cmpint(err, ==, 0);
g_assert_cmpint(errno, ==, 0);
diff -Nru snapd-2.32.3.2/cmd/snap/cmd_info.go snapd-2.32.9/cmd/snap/cmd_info.go
--- snapd-2.32.3.2/cmd/snap/cmd_info.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/cmd/snap/cmd_info.go 2018-05-16 08:20:08.000000000 +0000
@@ -298,6 +298,10 @@
if i > 0 {
fmt.Fprintln(w, "---")
}
+ if snapName == "system" {
+ fmt.Fprintln(w, "system: You can't have it.")
+ continue
+ }
if tryDirect(w, snapName, x.Verbose) {
noneOK = false
diff -Nru snapd-2.32.3.2/cmd/snap/cmd_run.go snapd-2.32.9/cmd/snap/cmd_run.go
--- snapd-2.32.3.2/cmd/snap/cmd_run.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/cmd/snap/cmd_run.go 2018-05-16 08:20:08.000000000 +0000
@@ -233,28 +233,16 @@
return actualApp, argsOut
}
-func getSnapInfo(snapName string, revision snap.Revision) (*snap.Info, error) {
+func getSnapInfo(snapName string, revision snap.Revision) (info *snap.Info, err error) {
if revision.Unset() {
- curFn := filepath.Join(dirs.SnapMountDir, snapName, "current")
- realFn, err := os.Readlink(curFn)
- if err != nil {
- return nil, fmt.Errorf("cannot find current revision for snap %s: %s", snapName, err)
- }
- rev := filepath.Base(realFn)
- revision, err = snap.ParseRevision(rev)
- if err != nil {
- return nil, fmt.Errorf("cannot read revision %s: %s", rev, err)
- }
+ info, err = snap.ReadCurrentInfo(snapName)
+ } else {
+ info, err = snap.ReadInfo(snapName, &snap.SideInfo{
+ Revision: revision,
+ })
}
- info, err := snap.ReadInfo(snapName, &snap.SideInfo{
- Revision: revision,
- })
- if err != nil {
- return nil, err
- }
-
- return info, nil
+ return info, err
}
func createOrUpdateUserDataSymlink(info *snap.Info, usr *user.User) error {
diff -Nru snapd-2.32.3.2/cmd/snap/cmd_snap_op.go snapd-2.32.9/cmd/snap/cmd_snap_op.go
--- snapd-2.32.3.2/cmd/snap/cmd_snap_op.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/cmd/snap/cmd_snap_op.go 2018-05-16 08:20:08.000000000 +0000
@@ -373,7 +373,7 @@
default:
fmt.Fprintf(Stdout, "internal error: unknown op %q", op)
}
- if snap.TrackingChannel != snap.Channel {
+ if snap.TrackingChannel != snap.Channel && snap.Channel != "" {
// TRANSLATORS: first %s is a snap name, following %s is a channel name
fmt.Fprintf(Stdout, i18n.G("Snap %s is no longer tracking %s.\n"), snap.Name, snap.TrackingChannel)
}
diff -Nru snapd-2.32.3.2/cmd/snap/cmd_userd.go snapd-2.32.9/cmd/snap/cmd_userd.go
--- snapd-2.32.3.2/cmd/snap/cmd_userd.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/cmd/snap/cmd_userd.go 2018-05-16 08:20:08.000000000 +0000
@@ -33,6 +33,8 @@
type cmdUserd struct {
userd userd.Userd
+
+ Autostart bool `long:"autostart"`
}
var shortUserdHelp = i18n.G("Start the userd service")
@@ -44,10 +46,9 @@
longUserdHelp,
func() flags.Commander {
return &cmdUserd{}
- },
- nil,
- []argDesc{},
- )
+ }, map[string]string{
+ "autostart": i18n.G("Autostart user applications"),
+ }, nil)
cmd.hidden = true
}
@@ -56,6 +57,10 @@
return ErrExtraArgs
}
+ if x.Autostart {
+ return x.runAutostart()
+ }
+
if err := x.userd.Init(); err != nil {
return err
}
@@ -72,3 +77,10 @@
return x.userd.Stop()
}
+
+func (x *cmdUserd) runAutostart() error {
+ if err := userd.AutostartSessionApps(); err != nil {
+ return fmt.Errorf("autostart failed for the following apps:\n%v", err)
+ }
+ return nil
+}
diff -Nru snapd-2.32.3.2/cmd/snap/cmd_wait.go snapd-2.32.9/cmd/snap/cmd_wait.go
--- snapd-2.32.3.2/cmd/snap/cmd_wait.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/cmd/snap/cmd_wait.go 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,134 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2018 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package main
+
+import (
+ "encoding/json"
+ "time"
+
+ "github.com/jessevdk/go-flags"
+
+ "github.com/snapcore/snapd/client"
+ "github.com/snapcore/snapd/i18n"
+)
+
+type cmdWait struct {
+ Positional struct {
+ Snap installedSnapName `required:"yes"`
+ Key string
+ } `positional-args:"yes"`
+}
+
+func init() {
+ addCommand("wait",
+ "Wait for configuration.",
+ "The wait command waits until a configration becomes true.",
+ func() flags.Commander {
+ return &cmdWait{}
+ }, nil, []argDesc{
+ {
+ name: "",
+ // TRANSLATORS: This should probably not start with a lowercase letter.
+ desc: i18n.G("The snap for which configuration will be checked"),
+ }, {
+ // TRANSLATORS: This needs to be wrapped in <>s.
+ name: i18n.G(""),
+ // TRANSLATORS: This should probably not start with a lowercase letter.
+ desc: i18n.G("Key of interest within the configuration"),
+ },
+ })
+}
+
+var waitConfTimeout = 500 * time.Millisecond
+
+func isNoOption(err error) bool {
+ if e, ok := err.(*client.Error); ok && e.Kind == client.ErrorKindConfigNoSuchOption {
+ return true
+ }
+ return false
+}
+
+func trueish(vi interface{}) bool {
+ switch v := vi.(type) {
+ case bool:
+ if v == true {
+ return true
+ }
+ case int:
+ if v > 0 {
+ return true
+ }
+ case int64:
+ if v > 0 {
+ return true
+ }
+ case float32:
+ if v > 0 {
+ return true
+ }
+ case float64:
+ if v > 0 {
+ return true
+ }
+ case json.Number:
+ if i, err := v.Int64(); err == nil && i > 0 {
+ return true
+ }
+ if f, err := v.Float64(); err == nil && f != 0.0 {
+ return true
+ }
+ case string:
+ if v != "" {
+ return true
+ }
+ case []interface{}:
+ if len(v) > 0 {
+ return true
+ }
+ case map[string]interface{}:
+ if len(v) > 0 {
+ return true
+ }
+ }
+ return false
+}
+
+func (x *cmdWait) Execute(args []string) error {
+ if len(args) > 0 {
+ return ErrExtraArgs
+ }
+
+ snapName := string(x.Positional.Snap)
+ confKey := x.Positional.Key
+
+ cli := Client()
+ for {
+ conf, err := cli.Conf(snapName, []string{confKey})
+ if err != nil && !isNoOption(err) {
+ return err
+ }
+ if trueish(conf[confKey]) {
+ break
+ }
+ time.Sleep(waitConfTimeout)
+ }
+
+ return nil
+}
diff -Nru snapd-2.32.3.2/cmd/snap/cmd_wait_test.go snapd-2.32.9/cmd/snap/cmd_wait_test.go
--- snapd-2.32.3.2/cmd/snap/cmd_wait_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/cmd/snap/cmd_wait_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,59 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2018 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package main_test
+
+import (
+ "fmt"
+ "net/http"
+ "time"
+
+ . "gopkg.in/check.v1"
+
+ snap "github.com/snapcore/snapd/cmd/snap"
+)
+
+func (s *SnapSuite) TestCmdWait(c *C) {
+ var seeded bool
+
+ restore := snap.MockWaitConfTimeout(10 * time.Millisecond)
+ defer restore()
+
+ go func() {
+ time.Sleep(50 * time.Millisecond)
+ seeded = true
+ }()
+ n := 0
+ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
+ c.Check(r.Method, Equals, "GET")
+ c.Check(r.URL.Path, Equals, "/v2/snaps/system/conf")
+
+ fmt.Fprintln(w, fmt.Sprintf(`{"type":"sync", "status-code": 200, "result": {"seed.loaded":%v}}`, seeded))
+ n++
+ })
+
+ _, err := snap.Parser().ParseArgs([]string{"wait", "system", "seed.loaded"})
+ c.Assert(err, IsNil)
+
+ // ensure we retried a bit but make the check not overly precise
+ // because this will run in super busy build hosts that where a
+ // 10 millisecond sleep actually takes much longer until the kernel
+ // hands control back to the process
+ c.Check(n > 2, Equals, true)
+}
diff -Nru snapd-2.32.3.2/cmd/snap/export_test.go snapd-2.32.9/cmd/snap/export_test.go
--- snapd-2.32.3.2/cmd/snap/export_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/cmd/snap/export_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -145,3 +145,11 @@
timeNow = oldTimeNow
}
}
+
+func MockWaitConfTimeout(d time.Duration) (restore func()) {
+ oldWaitConfTimeout := d
+ waitConfTimeout = d
+ return func() {
+ waitConfTimeout = oldWaitConfTimeout
+ }
+}
diff -Nru snapd-2.32.3.2/cmd/snap-confine/ns-support-test.c snapd-2.32.9/cmd/snap-confine/ns-support-test.c
--- snapd-2.32.3.2/cmd/snap-confine/ns-support-test.c 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/cmd/snap-confine/ns-support-test.c 2018-05-16 08:20:08.000000000 +0000
@@ -35,6 +35,12 @@
sc_ns_dir = dir;
}
+// A variant of unsetenv that is compatible with GDestroyNotify
+static void my_unsetenv(const char *k)
+{
+ unsetenv(k);
+}
+
// Use temporary directory for namespace groups.
//
// The directory is automatically reset to the real value at the end of the
@@ -53,7 +59,7 @@
g_test_queue_free(ns_dir);
g_assert_cmpint(setenv("SNAP_CONFINE_NS_DIR", ns_dir, 0), ==,
0);
- g_test_queue_destroy((GDestroyNotify) unsetenv,
+ g_test_queue_destroy((GDestroyNotify) my_unsetenv,
"SNAP_CONFINE_NS_DIR");
g_test_queue_destroy((GDestroyNotify) rm_rf_tmp, ns_dir);
}
diff -Nru snapd-2.32.3.2/daemon/api.go snapd-2.32.9/daemon/api.go
--- snapd-2.32.3.2/daemon/api.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/daemon/api.go 2018-05-16 08:20:08.000000000 +0000
@@ -1165,7 +1165,13 @@
var snapName string
switch err {
- case store.ErrSnapNotFound:
+ case store.ErrSnapNotFound, store.ErrRevisionNotAvailable:
+ // TODO: treating ErrRevisionNotAvailable the same as
+ // ErrSnapNotFound preserves the old error handling
+ // behavior of the REST API and the snap command. We
+ // should revisit this once the store returns more
+ // precise errors and makes it possible to distinguish
+ // the why a revision wasn't available.
switch len(inst.Snaps) {
case 1:
return SnapNotFound(inst.Snaps[0], err)
@@ -1593,7 +1599,7 @@
func getSnapConf(c *Command, r *http.Request, user *auth.UserState) Response {
vars := muxVars(r)
- snapName := vars["name"]
+ snapName := systemCoreSnapUnalias(vars["name"])
keys := splitQS(r.URL.Query().Get("keys"))
@@ -1616,7 +1622,15 @@
currentConfValues = make(map[string]interface{})
break
}
- return BadRequest("%v", err)
+ return SyncResponse(&resp{
+ Type: ResponseTypeError,
+ Result: &errorResult{
+ Message: err.Error(),
+ Kind: errorKindConfigNoSuchOption,
+ Value: err,
+ },
+ Status: 400,
+ }, nil)
} else {
return InternalError("%v", err)
}
@@ -1636,7 +1650,7 @@
func setSnapConf(c *Command, r *http.Request, user *auth.UserState) Response {
vars := muxVars(r)
- snapName := vars["name"]
+ snapName := systemCoreSnapUnalias(vars["name"])
var patchValues map[string]interface{}
if err := jsonutil.DecodeWithNumber(r.Body, &patchValues); err != nil {
@@ -2799,3 +2813,10 @@
st.EnsureBefore(0)
return AsyncResponse(nil, &Meta{Change: chg.ID()})
}
+
+func systemCoreSnapUnalias(name string) string {
+ if name == "system" {
+ return "core"
+ }
+ return name
+}
diff -Nru snapd-2.32.3.2/daemon/api_test.go snapd-2.32.9/daemon/api_test.go
--- snapd-2.32.3.2/daemon/api_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/daemon/api_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -86,7 +86,8 @@
d *Daemon
user *auth.UserState
restoreBackends func()
- refreshCandidates []*store.RefreshCandidate
+ currentSnaps []*store.CurrentSnap
+ actions []*store.SnapAction
buyOptions *store.BuyOptions
buyResult *store.BuyResult
storeSigning *assertstest.StoreStack
@@ -126,18 +127,12 @@
return s.rsnaps, s.err
}
-func (s *apiBaseSuite) LookupRefresh(snap *store.RefreshCandidate, user *auth.UserState) (*snap.Info, error) {
- s.refreshCandidates = []*store.RefreshCandidate{snap}
- s.user = user
-
- return s.rsnaps[0], s.err
-}
-
-func (s *apiBaseSuite) ListRefresh(ctx context.Context, snaps []*store.RefreshCandidate, user *auth.UserState, flags *store.RefreshOptions) ([]*snap.Info, error) {
+func (s *apiBaseSuite) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, user *auth.UserState, opts *store.RefreshOptions) ([]*snap.Info, error) {
if ctx == nil {
panic("context required")
}
- s.refreshCandidates = snaps
+ s.currentSnaps = currentSnaps
+ s.actions = actions
s.user = user
return s.rsnaps, s.err
@@ -232,7 +227,8 @@
s.vars = nil
s.user = nil
s.d = nil
- s.refreshCandidates = nil
+ s.currentSnaps = nil
+ s.actions = nil
// Disable real security backends for all API tests
s.restoreBackends = ifacestate.MockSecurityBackends(nil)
@@ -1502,7 +1498,8 @@
c.Check(rsp.SuggestedCurrency, check.Equals, "EUR")
c.Check(s.storeSearch, check.DeepEquals, store.Search{Query: "hi"})
- c.Check(s.refreshCandidates, check.HasLen, 0)
+ c.Check(s.currentSnaps, check.HasLen, 0)
+ c.Check(s.actions, check.HasLen, 0)
}
func (s *apiSuite) TestFindRefreshes(c *check.C) {
@@ -1525,7 +1522,8 @@
snaps := snapList(rsp.Result)
c.Assert(snaps, check.HasLen, 1)
c.Assert(snaps[0]["name"], check.Equals, "store")
- c.Check(s.refreshCandidates, check.HasLen, 1)
+ c.Check(s.currentSnaps, check.HasLen, 1)
+ c.Check(s.actions, check.HasLen, 1)
}
func (s *apiSuite) TestFindRefreshSideloaded(c *check.C) {
@@ -1561,9 +1559,9 @@
rsp := searchStore(findCmd, req, nil).(*resp)
snaps := snapList(rsp.Result)
- c.Assert(snaps, check.HasLen, 1)
- c.Assert(snaps[0]["name"], check.Equals, "store")
- c.Check(s.refreshCandidates, check.HasLen, 0)
+ c.Assert(snaps, check.HasLen, 0)
+ c.Check(s.currentSnaps, check.HasLen, 0)
+ c.Check(s.actions, check.HasLen, 0)
}
func (s *apiSuite) TestFindPrivate(c *check.C) {
@@ -2547,9 +2545,9 @@
return chg.Summary()
}
-func (s *apiSuite) runGetConf(c *check.C, keys []string, statusCode int) map[string]interface{} {
- s.vars = map[string]string{"name": "test-snap"}
- req, err := http.NewRequest("GET", "/v2/snaps/test-snap/conf?keys="+strings.Join(keys, ","), nil)
+func (s *apiSuite) runGetConf(c *check.C, snapName string, keys []string, statusCode int) map[string]interface{} {
+ s.vars = map[string]string{"name": snapName}
+ req, err := http.NewRequest("GET", "/v2/snaps/"+snapName+"/conf?keys="+strings.Join(keys, ","), nil)
c.Check(err, check.IsNil)
rec := httptest.NewRecorder()
snapConfCmd.GET(snapConfCmd, req, nil).ServeHTTP(rec, req)
@@ -2572,16 +2570,40 @@
tr.Commit()
d.overlord.State().Unlock()
- result := s.runGetConf(c, []string{"test-key1"}, 200)
+ result := s.runGetConf(c, "test-snap", []string{"test-key1"}, 200)
c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1"})
- result = s.runGetConf(c, []string{"test-key1", "test-key2"}, 200)
+ result = s.runGetConf(c, "test-snap", []string{"test-key1", "test-key2"}, 200)
c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1", "test-key2": "test-value2"})
}
+func (s *apiSuite) TestGetConfCoreSystemAlias(c *check.C) {
+ d := s.daemon(c)
+
+ // Set a config that we'll get in a moment
+ d.overlord.State().Lock()
+ tr := config.NewTransaction(d.overlord.State())
+ tr.Set("core", "test-key1", "test-value1")
+ tr.Commit()
+ d.overlord.State().Unlock()
+
+ result := s.runGetConf(c, "core", []string{"test-key1"}, 200)
+ c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1"})
+
+ result = s.runGetConf(c, "system", []string{"test-key1"}, 200)
+ c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1"})
+}
+
func (s *apiSuite) TestGetConfMissingKey(c *check.C) {
- result := s.runGetConf(c, []string{"test-key2"}, 400)
- c.Check(result, check.DeepEquals, map[string]interface{}{"message": `snap "test-snap" has no "test-key2" configuration option`})
+ result := s.runGetConf(c, "test-snap", []string{"test-key2"}, 400)
+ c.Check(result, check.DeepEquals, map[string]interface{}{
+ "value": map[string]interface{}{
+ "SnapName": "test-snap",
+ "Key": "test-key2",
+ },
+ "message": `snap "test-snap" has no "test-key2" configuration option`,
+ "kind": "option-not-found",
+ })
}
func (s *apiSuite) TestGetRootDocument(c *check.C) {
@@ -2593,13 +2615,13 @@
tr.Commit()
d.overlord.State().Unlock()
- result := s.runGetConf(c, nil, 200)
+ result := s.runGetConf(c, "test-snap", nil, 200)
c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1", "test-key2": "test-value2"})
}
func (s *apiSuite) TestGetConfBadKey(c *check.C) {
// TODO: this one in particular should really be a 400 also
- result := s.runGetConf(c, []string{"."}, 500)
+ result := s.runGetConf(c, "test-snap", []string{"."}, 500)
c.Check(result, check.DeepEquals, map[string]interface{}{"message": `invalid option name: ""`})
}
@@ -2651,6 +2673,57 @@
}})
}
+func (s *apiSuite) TestSetConfCoreSystemAlias(c *check.C) {
+ d := s.daemon(c)
+ s.mockSnap(c, `
+name: core
+version: 1
+`)
+ // Mock the hook runner
+ hookRunner := testutil.MockCommand(c, "snap", "")
+ defer hookRunner.Restore()
+
+ d.overlord.Loop()
+ defer d.overlord.Stop()
+
+ text, err := json.Marshal(map[string]interface{}{"key": "value"})
+ c.Assert(err, check.IsNil)
+
+ buffer := bytes.NewBuffer(text)
+ req, err := http.NewRequest("PUT", "/v2/snaps/system/conf", buffer)
+ c.Assert(err, check.IsNil)
+
+ s.vars = map[string]string{"name": "system"}
+
+ rec := httptest.NewRecorder()
+ snapConfCmd.PUT(snapConfCmd, req, nil).ServeHTTP(rec, req)
+ c.Check(rec.Code, check.Equals, 202)
+
+ var body map[string]interface{}
+ err = json.Unmarshal(rec.Body.Bytes(), &body)
+ c.Assert(err, check.IsNil)
+ id := body["change"].(string)
+
+ st := d.overlord.State()
+ st.Lock()
+ chg := st.Change(id)
+ st.Unlock()
+ c.Assert(chg, check.NotNil)
+
+ <-chg.Ready()
+
+ st.Lock()
+ err = chg.Err()
+ tr := config.NewTransaction(st)
+ st.Unlock()
+ c.Assert(err, check.IsNil)
+
+ var value string
+ tr.Get("core", "key", &value)
+ c.Assert(value, check.Equals, "value")
+
+}
+
func (s *apiSuite) TestSetConfNumber(c *check.C) {
d := s.daemon(c)
s.mockSnap(c, configYaml)
@@ -6471,6 +6544,7 @@
si := &snapInstruction{Action: "frobble"}
errors := []error{
store.ErrSnapNotFound,
+ store.ErrRevisionNotAvailable,
store.ErrNoUpdateAvailable,
store.ErrLocalSnap,
&snap.AlreadyInstalledError{Snap: "foo"},
diff -Nru snapd-2.32.3.2/daemon/response.go snapd-2.32.9/daemon/response.go
--- snapd-2.32.3.2/daemon/response.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/daemon/response.go 2018-05-16 08:20:08.000000000 +0000
@@ -147,6 +147,8 @@
errorKindNetworkTimeout = errorKind("network-timeout")
errorKindInterfacesUnchanged = errorKind("interfaces-unchanged")
+
+ errorKindConfigNoSuchOption = errorKind("option-not-found")
)
type errorValue interface{}
diff -Nru snapd-2.32.3.2/data/desktop/Makefile snapd-2.32.9/data/desktop/Makefile
--- snapd-2.32.3.2/data/desktop/Makefile 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/data/desktop/Makefile 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,38 @@
+#
+# Copyright (C) 2018 Canonical Ltd
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+BINDIR = /usr/bin
+SYSCONFXDGAUTOSTARTDIR = /etc/xdg/autostart
+
+SOURCES = snap-userd-autostart.desktop.in
+DESKTOP_FILES = $(SOURCES:.in=)
+
+.PHONY: all
+all: $(DESKTOP_FILES)
+
+.PHONY: install
+install: $(DESKTOP_FILES)
+ # NOTE: old (e.g. 14.04) GNU coreutils doesn't -D with -t
+ install -d -m 0755 $(DESTDIR)/$(SYSCONFXDGAUTOSTARTDIR)
+ install -m 0644 -t $(DESTDIR)/$(SYSCONFXDGAUTOSTARTDIR) $^
+
+.PHONY: clean
+clean:
+ rm -f $(DESKTOP_FILES)
+
+%: %.in
+ cat $< | \
+ sed s:@bindir@:$(BINDIR):g | \
+ cat > $@
diff -Nru snapd-2.32.3.2/data/desktop/snap-userd-autostart.desktop.in snapd-2.32.9/data/desktop/snap-userd-autostart.desktop.in
--- snapd-2.32.3.2/data/desktop/snap-userd-autostart.desktop.in 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/data/desktop/snap-userd-autostart.desktop.in 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,5 @@
+[Desktop Entry]
+Name=Snap user application autostart helper
+Comment=Helper program for launching snap applications that are configured to start automatically.
+Exec=@bindir@/snap userd --autostart
+Type=Application
diff -Nru snapd-2.32.3.2/data/Makefile snapd-2.32.9/data/Makefile
--- snapd-2.32.3.2/data/Makefile 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/data/Makefile 2018-05-16 08:20:08.000000000 +0000
@@ -2,3 +2,4 @@
$(MAKE) -C systemd $@
$(MAKE) -C dbus $@
$(MAKE) -C env $@
+ $(MAKE) -C desktop $@
diff -Nru snapd-2.32.3.2/data/systemd/snapd.core-fixup.service.in snapd-2.32.9/data/systemd/snapd.core-fixup.service.in
--- snapd-2.32.3.2/data/systemd/snapd.core-fixup.service.in 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/data/systemd/snapd.core-fixup.service.in 2018-05-16 08:20:08.000000000 +0000
@@ -3,7 +3,6 @@
Before=snapd.service
# don't run on classic
ConditionKernelCommandLine=snap_core
-ConditionPathExists=!/var/lib/snapd/device/ownership-change.after
Documentation=man:snap(1)
[Service]
diff -Nru snapd-2.32.3.2/data/systemd/snapd.core-fixup.sh snapd-2.32.9/data/systemd/snapd.core-fixup.sh
--- snapd-2.32.3.2/data/systemd/snapd.core-fixup.sh 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/data/systemd/snapd.core-fixup.sh 2018-05-16 08:20:08.000000000 +0000
@@ -11,6 +11,38 @@
exit 0
fi
+# Workaround https://forum.snapcraft.io/t/5253
+#
+# We see sometimes corrupted uboot.env files created by fsck.vfat.
+# On the fat filesystem they are indistinguishable because one
+# has a fat16 name UBOOT.ENV (and not lfn (long-file-name)) but
+# the other has a "uboot.env" lfn name and a FSCK0000.000 FAT16
+# name. The only known workaround is to remove all dupes and put
+# one file back in place.
+if [ $(ls /boot/uboot | grep ^uboot.env$ | wc -l) -gt 1 ]; then
+ echo "Corrupted uboot.env file detected"
+ # ensure we have one uboot.env to go back to
+ cp -a /boot/uboot/uboot.env /boot/uboot/uboot.env.save
+ # now delete all dupes
+ while ls /boot/uboot/uboot.env 2>/dev/null; do
+ rm -f /boot/uboot/uboot.env
+ done
+ # and move the saved one into place
+ mv /boot/uboot/uboot.env.save /boot/uboot/uboot.env
+
+ # ensure we sync the fs
+ sync
+fi
+
+
+# The code below deals with incorrect permissions that happened on
+# some buggy ubuntu-image versions.
+#
+# This needs to run only once so we can exit here.
+if [ -f /var/lib/snapd/device/ownership-change.after ]; then
+ exit 0
+fi
+
# store important data in case we need it later
if [ ! -f /var/lib/snapd/device/ownership-change.before ]; then
mkdir -p /var/lib/snapd/device
diff -Nru snapd-2.32.3.2/data/systemd/snapd.seeded.service.in snapd-2.32.9/data/systemd/snapd.seeded.service.in
--- snapd-2.32.3.2/data/systemd/snapd.seeded.service.in 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/data/systemd/snapd.seeded.service.in 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,12 @@
+[Unit]
+Description=Wait until snapd is fully seeded
+After=snapd.service snapd.socket
+
+[Service]
+Type=oneshot
+ExecStart=@bindir@/snap wait system seed.loaded
+RemainAfterExit=true
+
+[Install]
+WantedBy=multi-user.target cloud-final.service
+
diff -Nru snapd-2.32.3.2/debian/changelog snapd-2.32.9/debian/changelog
--- snapd-2.32.3.2/debian/changelog 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/debian/changelog 2018-05-16 08:20:08.000000000 +0000
@@ -1,3 +1,66 @@
+snapd (2.32.9) xenial; urgency=medium
+
+ * New upstream release, LP: #1767833
+ - tests: run all spread tests inside GCE
+ - tests: build spread in the autopkgtests with a more recent go
+
+ -- Michael Vogt Wed, 16 May 2018 10:20:08 +0200
+
+snapd (2.32.8) xenial; urgency=medium
+
+ * New upstream release, LP: #1767833
+
+ -- Michael Vogt Fri, 11 May 2018 14:36:16 +0200
+
+snapd (2.32.7) xenial; urgency=medium
+
+ * New upstream release, LP: #1767833
+ - many: add wait command and seeded target (2
+ - snapd.core-fixup.sh: add workaround for corrupted uboot.env
+ - boot: clear "snap_mode" when needed
+ - cmd/libsnap: fix compile error on more restrictive gcc
+ - tests: cherry-pick commits to move spread to google backend
+ - spread.yaml: add cosmic (18.10) to autopkgtest/qemu
+ - userd: set up journal logging streams for autostarted apps
+
+ -- Michael Vogt Fri, 11 May 2018 13:09:32 +0200
+
+snapd (2.32.6) xenial; urgency=medium
+
+ * New upstream release, LP: #1767833
+ - snap: do not use overly short timeout in `snap
+ {start,stop,restart}`
+ - interfaces/apparmor: fix incorrect apparmor profile glob
+ - tests: detect kernel oops during tests and abort tests in this
+ case
+ - tests: run interfaces-boradcom-asic-control early
+ - tests: skip interfaces-content test on core devices
+
+ -- Michael Vogt Sun, 29 Apr 2018 19:21:53 +0200
+
+snapd (2.32.5) xenial; urgency=medium
+
+ * New upstream release, LP: #1765090
+ - many: add "stop-mode: sig{term,hup,usr[12]}{,-all}" instead of
+ conflating that with refresh-mode
+ - overlord/snapstate: poll for up to 10s if a snap is unexpectedly
+ not mounted in doMountSnap
+ - daemon: support 'system' as nickname of the core snap
+
+ -- Michael Vogt Mon, 16 Apr 2018 11:41:48 +0200
+
+snapd (2.32.4) xenial; urgency=medium
+
+ * New upstream release, LP: #1756173
+ - cmd/snap: user session application autostart
+ - overlord/snapstate: introduce envvars to control the channels for
+ bases and prereqs
+ - overlord/snapstate: on multi-snap refresh make sure bases and core
+ are finished before dependent snaps
+ - many: use the new install/refresh /v2/snaps/refresh store API
+
+ -- Michael Vogt Wed, 11 Apr 2018 16:30:45 +0200
+
snapd (2.32.3.2) xenial; urgency=medium
* New upstream release, LP: #1756173
diff -Nru snapd-2.32.3.2/debian/tests/integrationtests snapd-2.32.9/debian/tests/integrationtests
--- snapd-2.32.3.2/debian/tests/integrationtests 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/debian/tests/integrationtests 2018-05-16 08:20:08.000000000 +0000
@@ -36,11 +36,14 @@
export SPREAD_CORE_CHANNEL=stable
fi
+# Spread will only buid with recent go
+snap install --classic go
+
# and now run spread against localhost
# shellcheck disable=SC1091
. /etc/os-release
export GOPATH=/tmp/go
-go get -u github.com/snapcore/spread/cmd/spread
+/snap/bin/go get -u github.com/snapcore/spread/cmd/spread
/tmp/go/bin/spread -v "autopkgtest:${ID}-${VERSION_ID}-$(dpkg --print-architecture)"
# store journal info for inspectsion
diff -Nru snapd-2.32.3.2/dirs/dirs.go snapd-2.32.9/dirs/dirs.go
--- snapd-2.32.3.2/dirs/dirs.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/dirs/dirs.go 2018-05-16 08:20:08.000000000 +0000
@@ -113,6 +113,9 @@
// are in the snap confinement environment.
CoreLibExecDir = "/usr/lib/snapd"
CoreSnapMountDir = "/snap"
+
+ // Directory with snap data inside user's home
+ UserHomeSnapDir = "snap"
)
var (
@@ -179,7 +182,7 @@
}
SnapDataDir = filepath.Join(rootdir, "/var/snap")
- SnapDataHomeGlob = filepath.Join(rootdir, "/home/*/snap/")
+ SnapDataHomeGlob = filepath.Join(rootdir, "/home/*/", UserHomeSnapDir)
SnapAppArmorDir = filepath.Join(rootdir, snappyDir, "apparmor", "profiles")
SnapConfineAppArmorDir = filepath.Join(rootdir, snappyDir, "apparmor", "snap-confine")
AppArmorCacheDir = filepath.Join(rootdir, "/var/cache/apparmor")
diff -Nru snapd-2.32.3.2/interfaces/apparmor/backend.go snapd-2.32.9/interfaces/apparmor/backend.go
--- snapd-2.32.3.2/interfaces/apparmor/backend.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/interfaces/apparmor/backend.go 2018-05-16 08:20:08.000000000 +0000
@@ -259,6 +259,21 @@
return nil
}
+// nsProfile returns name of the apparmor profile for snap-update-ns for a given snap.
+func nsProfile(snapName string) string {
+ return fmt.Sprintf("snap-update-ns.%s", snapName)
+}
+
+// profileGlobs returns a list of globs that describe the apparmor profiles of
+// a given snap.
+//
+// Currently the list is just a pair. The first glob describes profiles for all
+// apps and hooks while the second profile describes the snap-update-ns profile
+// for the whole snap.
+func profileGlobs(snapName string) []string {
+ return []string{interfaces.SecurityTagGlob(snapName), nsProfile(snapName)}
+}
+
// Setup creates and loads apparmor profiles specific to a given snap.
// The snap can be in developer mode to make security violations non-fatal to
// the offending application process.
@@ -308,13 +323,12 @@
return fmt.Errorf("cannot obtain expected security files for snap %q: %s", snapName, err)
}
dir := dirs.SnapAppArmorDir
- glob1 := fmt.Sprintf("snap*.%s*", snapInfo.Name())
- glob2 := fmt.Sprintf("snap-update-ns.%s", snapInfo.Name())
+ globs := profileGlobs(snapInfo.Name())
cache := dirs.AppArmorCacheDir
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("cannot create directory for apparmor profiles %q: %s", dir, err)
}
- _, removed, errEnsure := osutil.EnsureDirStateGlobs(dir, []string{glob1, glob2}, content)
+ _, removed, errEnsure := osutil.EnsureDirStateGlobs(dir, globs, content)
// NOTE: load all profiles instead of just the changed profiles. We're
// relying on apparmor cache to make this efficient. This gives us
// certainty that each call to Setup ends up with working profiles.
@@ -337,10 +351,9 @@
// Remove removes and unloads apparmor profiles of a given snap.
func (b *Backend) Remove(snapName string) error {
dir := dirs.SnapAppArmorDir
- glob1 := fmt.Sprintf("snap*.%s*", snapName)
- glob2 := fmt.Sprintf("snap-update-ns.%s", snapName)
+ globs := profileGlobs(snapName)
cache := dirs.AppArmorCacheDir
- _, removed, errEnsure := osutil.EnsureDirStateGlobs(dir, []string{glob1, glob2}, nil)
+ _, removed, errEnsure := osutil.EnsureDirStateGlobs(dir, globs, nil)
errUnload := unloadProfiles(removed, cache)
if errEnsure != nil {
return fmt.Errorf("cannot synchronize security files for snap %q: %s", snapName, errEnsure)
@@ -397,7 +410,7 @@
})
// Ensure that the snap-update-ns profile is on disk.
- profileName := fmt.Sprintf("snap-update-ns.%s", snapInfo.Name())
+ profileName := nsProfile(snapInfo.Name())
content[profileName] = &osutil.FileState{
Content: []byte(policy),
Mode: 0644,
diff -Nru snapd-2.32.3.2/interfaces/apparmor/backend_test.go snapd-2.32.9/interfaces/apparmor/backend_test.go
--- snapd-2.32.3.2/interfaces/apparmor/backend_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/interfaces/apparmor/backend_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -352,6 +352,63 @@
}
}
+const snapcraftPrYaml = `name: snapcraft-pr
+version: 1
+apps:
+ snapcraft-pr:
+ cmd: snapcraft-pr
+`
+
+const snapcraftYaml = `name: snapcraft
+version: 1
+apps:
+ snapcraft:
+ cmd: snapcraft
+`
+
+func (s *backendSuite) TestInstallingSnapDoesntBreakSnapsWithPrefixName(c *C) {
+ snapcraftProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.snapcraft.snapcraft")
+ snapcraftPrProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.snapcraft-pr.snapcraft-pr")
+ // Install snapcraft-pr and check that its profile was created.
+ s.InstallSnap(c, interfaces.ConfinementOptions{}, snapcraftPrYaml, 1)
+ _, err := os.Stat(snapcraftPrProfile)
+ c.Check(err, IsNil)
+
+ // Install snapcraft (sans the -pr suffix) and check that its profile was created.
+ // Check that this didn't remove the profile of snapcraft-pr installed earlier.
+ s.InstallSnap(c, interfaces.ConfinementOptions{}, snapcraftYaml, 1)
+ _, err = os.Stat(snapcraftProfile)
+ c.Check(err, IsNil)
+ _, err = os.Stat(snapcraftPrProfile)
+ c.Check(err, IsNil)
+}
+
+func (s *backendSuite) TestRemovingSnapDoesntBreakSnapsWIthPrefixName(c *C) {
+ snapcraftProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.snapcraft.snapcraft")
+ snapcraftPrProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.snapcraft-pr.snapcraft-pr")
+
+ // Install snapcraft-pr and check that its profile was created.
+ s.InstallSnap(c, interfaces.ConfinementOptions{}, snapcraftPrYaml, 1)
+ _, err := os.Stat(snapcraftPrProfile)
+ c.Check(err, IsNil)
+
+ // Install snapcraft (sans the -pr suffix) and check that its profile was created.
+ // Check that this didn't remove the profile of snapcraft-pr installed earlier.
+ snapInfo := s.InstallSnap(c, interfaces.ConfinementOptions{}, snapcraftYaml, 1)
+ _, err = os.Stat(snapcraftProfile)
+ c.Check(err, IsNil)
+ _, err = os.Stat(snapcraftPrProfile)
+ c.Check(err, IsNil)
+
+ // Remove snapcraft (sans the -pr suffix) and check that its profile was removed.
+ // Check that this didn't remove the profile of snapcraft-pr installed earlier.
+ s.RemoveSnap(c, snapInfo)
+ _, err = os.Stat(snapcraftProfile)
+ c.Check(os.IsNotExist(err), Equals, true)
+ _, err = os.Stat(snapcraftPrProfile)
+ c.Check(err, IsNil)
+}
+
func (s *backendSuite) TestRealDefaultTemplateIsNormallyUsed(c *C) {
restore := release.MockAppArmorLevel(release.FullAppArmor)
defer restore()
@@ -1224,3 +1281,12 @@
s.RemoveSnap(c, snapInfo)
}
}
+
+func (s *backendSuite) TestProfileGlobs(c *C) {
+ globs := apparmor.ProfileGlobs("foo")
+ c.Assert(globs, DeepEquals, []string{"snap.foo.*", "snap-update-ns.foo"})
+}
+
+func (s *backendSuite) TestNsProfile(c *C) {
+ c.Assert(apparmor.NsProfile("foo"), Equals, "snap-update-ns.foo")
+}
diff -Nru snapd-2.32.3.2/interfaces/apparmor/export_test.go snapd-2.32.9/interfaces/apparmor/export_test.go
--- snapd-2.32.3.2/interfaces/apparmor/export_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/interfaces/apparmor/export_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -27,6 +27,8 @@
var (
SnapConfineFromCoreProfile = snapConfineFromCoreProfile
+ ProfileGlobs = profileGlobs
+ NsProfile = nsProfile
)
// MockIsRootWritableOverlay mocks the real implementation of osutil.IsRootWritableOverlay
diff -Nru snapd-2.32.3.2/overlord/cmdstate/cmdmgr.go snapd-2.32.9/overlord/cmdstate/cmdmgr.go
--- snapd-2.32.3.2/overlord/cmdstate/cmdmgr.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/cmdstate/cmdmgr.go 2018-05-16 08:20:08.000000000 +0000
@@ -61,19 +61,28 @@
m.runner.Stop()
}
-var execTimeout = 5 * time.Second
+var defaultExecTimeout = 5 * time.Second
func doExec(t *state.Task, tomb *tomb.Tomb) error {
var argv []string
+ var tout time.Duration
+
st := t.State()
st.Lock()
- err := t.Get("argv", &argv)
+ err1 := t.Get("argv", &argv)
+ err2 := t.Get("timeout", &tout)
st.Unlock()
- if err != nil {
- return err
+ if err1 != nil {
+ return err1
+ }
+ if err2 != nil && err2 != state.ErrNoState {
+ return err2
+ }
+ if err2 == state.ErrNoState {
+ tout = defaultExecTimeout
}
- if buf, err := osutil.RunAndWait(argv, nil, execTimeout, tomb); err != nil {
+ if buf, err := osutil.RunAndWait(argv, nil, tout, tomb); err != nil {
st.Lock()
t.Errorf("# %s\n%s", strings.Join(argv, " "), buf)
st.Unlock()
diff -Nru snapd-2.32.3.2/overlord/cmdstate/cmdstate.go snapd-2.32.9/overlord/cmdstate/cmdstate.go
--- snapd-2.32.3.2/overlord/cmdstate/cmdstate.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/cmdstate/cmdstate.go 2018-05-16 08:20:08.000000000 +0000
@@ -22,12 +22,16 @@
package cmdstate
import (
+ "time"
+
"github.com/snapcore/snapd/overlord/state"
)
-// Exec creates a task that will execute the given command.
-func Exec(st *state.State, summary string, argv []string) *state.TaskSet {
+// ExecWithTimeout creates a task that will execute the given command
+// with the given timeout.
+func ExecWithTimeout(st *state.State, summary string, argv []string, timeout time.Duration) *state.TaskSet {
t := st.NewTask("exec-command", summary)
t.Set("argv", argv)
+ t.Set("timeout", timeout)
return state.NewTaskSet(t)
}
diff -Nru snapd-2.32.3.2/overlord/cmdstate/cmdstate_test.go snapd-2.32.9/overlord/cmdstate/cmdstate_test.go
--- snapd-2.32.3.2/overlord/cmdstate/cmdstate_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/cmdstate/cmdstate_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -71,7 +71,7 @@
s.rootdir = d
s.state = state.New(nil)
s.manager = cmdstate.Manager(s.state)
- s.restore = cmdstate.MockExecTimeout(time.Second / 10)
+ s.restore = cmdstate.MockDefaultExecTimeout(time.Second / 10)
}
func (s *cmdSuite) TearDownTest(c *check.C) {
@@ -88,7 +88,7 @@
s.state.Lock()
defer s.state.Unlock()
argvIn := []string{"/bin/echo", "hello"}
- tasks := cmdstate.Exec(s.state, "this is the summary", argvIn).Tasks()
+ tasks := cmdstate.ExecWithTimeout(s.state, "this is the summary", argvIn, time.Second/10).Tasks()
c.Assert(tasks, check.HasLen, 1)
task := tasks[0]
c.Check(task.Kind(), check.Equals, "exec-command")
@@ -103,7 +103,7 @@
defer s.state.Unlock()
fn := filepath.Join(s.rootdir, "flag")
- ts := cmdstate.Exec(s.state, "Doing the thing", []string{"touch", fn})
+ ts := cmdstate.ExecWithTimeout(s.state, "Doing the thing", []string{"touch", fn}, time.Second/10)
chg := s.state.NewChange("do-the-thing", "Doing the thing")
chg.AddAll(ts)
@@ -117,7 +117,7 @@
s.state.Lock()
defer s.state.Unlock()
- ts := cmdstate.Exec(s.state, "Doing the thing", []string{"sh", "-c", "echo hello; false"})
+ ts := cmdstate.ExecWithTimeout(s.state, "Doing the thing", []string{"sh", "-c", "echo hello; false"}, time.Second/10)
chg := s.state.NewChange("do-the-thing", "Doing the thing")
chg.AddAll(ts)
@@ -130,7 +130,7 @@
s.state.Lock()
defer s.state.Unlock()
- ts := cmdstate.Exec(s.state, "Doing the thing", []string{"sleep", "1h"})
+ ts := cmdstate.ExecWithTimeout(s.state, "Doing the thing", []string{"sleep", "1h"}, time.Second/10)
chg := s.state.NewChange("do-the-thing", "Doing the thing")
chg.AddAll(ts)
@@ -152,7 +152,7 @@
s.state.Lock()
defer s.state.Unlock()
- ts := cmdstate.Exec(s.state, "Doing the thing", []string{"sleep", "1h"})
+ ts := cmdstate.ExecWithTimeout(s.state, "Doing the thing", []string{"sleep", "1h"}, time.Second/10)
chg := s.state.NewChange("do-the-thing", "Doing the thing")
chg.AddAll(ts)
@@ -170,7 +170,7 @@
s.state.Lock()
defer s.state.Unlock()
- ts := cmdstate.Exec(s.state, "Doing the thing", []string{"sleep", "1m"})
+ ts := cmdstate.ExecWithTimeout(s.state, "Doing the thing", []string{"sleep", "1m"}, time.Second/10)
chg := s.state.NewChange("do-the-thing", "Doing the thing")
chg.AddAll(ts)
diff -Nru snapd-2.32.3.2/overlord/cmdstate/export_test.go snapd-2.32.9/overlord/cmdstate/export_test.go
--- snapd-2.32.3.2/overlord/cmdstate/export_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/cmdstate/export_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -23,10 +23,10 @@
"time"
)
-func MockExecTimeout(t time.Duration) func() {
- ot := execTimeout
- execTimeout = t
+func MockDefaultExecTimeout(t time.Duration) func() {
+ ot := defaultExecTimeout
+ defaultExecTimeout = t
return func() {
- execTimeout = ot
+ defaultExecTimeout = ot
}
}
diff -Nru snapd-2.32.3.2/overlord/configstate/configcore/corecfg.go snapd-2.32.9/overlord/configstate/configcore/corecfg.go
--- snapd-2.32.3.2/overlord/configstate/configcore/corecfg.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/configstate/configcore/corecfg.go 2018-05-16 08:20:08.000000000 +0000
@@ -57,6 +57,7 @@
if err := validateRefreshSchedule(tr); err != nil {
return err
}
+ // FIXME: ensure the user cannot set "core seed.done"
// capture cloud information
if err := setCloudInfoWhenSeeding(tr); err != nil {
diff -Nru snapd-2.32.3.2/overlord/devicestate/devicemgr.go snapd-2.32.9/overlord/devicestate/devicemgr.go
--- snapd-2.32.3.2/overlord/devicestate/devicemgr.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/devicestate/devicemgr.go 2018-05-16 08:20:08.000000000 +0000
@@ -31,6 +31,7 @@
"github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/overlord/assertstate"
"github.com/snapcore/snapd/overlord/auth"
+ "github.com/snapcore/snapd/overlord/configstate/config"
"github.com/snapcore/snapd/overlord/hookstate"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
@@ -49,6 +50,8 @@
bootOkRan bool
bootRevisionsUpdated bool
+ ensureSeedInConfigRan bool
+
lastBecomeOperationalAttempt time.Time
becomeOperationalBackoff time.Duration
}
@@ -342,6 +345,48 @@
return nil
}
+func markSeededInConfig(st *state.State) error {
+ var seedDone bool
+ tr := config.NewTransaction(st)
+ if err := tr.Get("core", "seed.loaded", &seedDone); err != nil && !config.IsNoOption(err) {
+ return err
+ }
+ if !seedDone {
+ if err := tr.Set("core", "seed.loaded", true); err != nil {
+ return err
+ }
+ tr.Commit()
+ }
+ return nil
+}
+
+func (m *DeviceManager) ensureSeedInConfig() error {
+ m.state.Lock()
+ defer m.state.Unlock()
+
+ if !m.ensureSeedInConfigRan {
+ // get global seeded option
+ var seeded bool
+ if err := m.state.Get("seeded", &seeded); err != nil && err != state.ErrNoState {
+ return err
+ }
+ if !seeded {
+ // wait for ensure again, this is fine because
+ // doMarkSeeded will run "EnsureBefore(0)"
+ return nil
+ }
+
+ // sync seeding with the configuration state
+ if err := markSeededInConfig(m.state); err != nil {
+ return err
+ }
+ m.ensureSeedInConfigRan = true
+ }
+
+ return nil
+
+}
+
type ensureError struct {
errs []error
}
@@ -376,6 +421,10 @@
errs = append(errs, err)
}
+ if err := m.ensureSeedInConfig(); err != nil {
+ errs = append(errs, err)
+ }
+
m.runner.Ensure()
if len(errs) > 0 {
diff -Nru snapd-2.32.3.2/overlord/hookstate/ctlcmd/services_test.go snapd-2.32.9/overlord/hookstate/ctlcmd/services_test.go
--- snapd-2.32.3.2/overlord/hookstate/ctlcmd/services_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/hookstate/ctlcmd/services_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -47,18 +47,18 @@
storetest.Store
}
-func (f *fakeStore) SnapInfo(spec store.SnapSpec, user *auth.UserState) (*snap.Info, error) {
- return &snap.Info{
- SideInfo: snap.SideInfo{
- RealName: spec.Name,
- Revision: snap.R(2),
- },
- Publisher: "foo",
- Architectures: []string{"all"},
- }, nil
-}
+func (f *fakeStore) SnapAction(_ context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, user *auth.UserState, opts *store.RefreshOptions) ([]*snap.Info, error) {
+ if len(actions) == 1 && actions[0].Action == "install" {
+ return []*snap.Info{{
+ SideInfo: snap.SideInfo{
+ RealName: actions[0].Name,
+ Revision: snap.R(2),
+ },
+ Publisher: "foo",
+ Architectures: []string{"all"},
+ }}, nil
+ }
-func (f *fakeStore) ListRefresh(_ context.Context, cand []*store.RefreshCandidate, user *auth.UserState, opt *store.RefreshOptions) ([]*snap.Info, error) {
return []*snap.Info{{
SideInfo: snap.SideInfo{
RealName: "test-snap",
diff -Nru snapd-2.32.3.2/overlord/managers_test.go snapd-2.32.9/overlord/managers_test.go
--- snapd-2.32.3.2/overlord/managers_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/managers_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -360,6 +360,29 @@
"summary": "Foo",
"version": "@VERSION@"
}`
+
+ snapV2 = `{
+ "architectures": [
+ "all"
+ ],
+ "download": {
+ "url": "@URL@"
+ },
+ "type": "app",
+ "name": "@NAME@",
+ "revision": @REVISION@,
+ "snap-id": "@SNAPID@",
+ "summary": "Foo",
+ "description": "this is a description",
+ "version": "@VERSION@",
+ "publisher": {
+ "id": "devdevdev",
+ "name": "bar"
+ },
+ "media": [
+ {"type": "icon", "url": "@ICON@"}
+ ]
+}`
)
var fooSnapID = fakeSnapID("foo")
@@ -416,7 +439,7 @@
func (ms *mgrsSuite) mockStore(c *C) *httptest.Server {
var baseURL *url.URL
- fillHit := func(name string) string {
+ fillHit := func(hitTemplate, name string) string {
snapf, err := snap.Open(ms.serveSnapPath[name])
if err != nil {
panic(err)
@@ -425,7 +448,7 @@
if err != nil {
panic(err)
}
- hit := strings.Replace(searchHit, "@URL@", baseURL.String()+"/api/v1/snaps/download/"+name, -1)
+ hit := strings.Replace(hitTemplate, "@URL@", baseURL.String()+"/api/v1/snaps/download/"+name, -1)
hit = strings.Replace(hit, "@NAME@", name, -1)
hit = strings.Replace(hit, "@SNAPID@", fakeSnapID(name), -1)
hit = strings.Replace(hit, "@ICON@", baseURL.String()+"/icon", -1)
@@ -435,13 +458,25 @@
}
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- // all URLS are /api/v1/snaps/... so check the url is sane and discard
- // the common prefix to simplify indexing into the comps slice.
+ // all URLS are /api/v1/snaps/... or /v2/snaps/... so
+ // check the url is sane and discard the common prefix
+ // to simplify indexing into the comps slice.
comps := strings.Split(r.URL.Path, "/")
- if len(comps) <= 4 {
+ if len(comps) < 2 {
panic("unexpected url path: " + r.URL.Path)
}
- comps = comps[4:]
+ if comps[1] == "api" { //v1
+ if len(comps) <= 4 {
+ panic("unexpected url path: " + r.URL.Path)
+ }
+ comps = comps[4:]
+ } else { // v2
+ if len(comps) <= 3 {
+ panic("unexpected url path: " + r.URL.Path)
+ }
+ comps = comps[3:]
+ comps[0] = "v2:" + comps[0]
+ }
switch comps[0] {
case "assertions":
@@ -465,7 +500,7 @@
return
case "details":
w.WriteHeader(200)
- io.WriteString(w, fillHit(comps[1]))
+ io.WriteString(w, fillHit(searchHit, comps[1]))
case "metadata":
dec := json.NewDecoder(r.Body)
var input struct {
@@ -484,7 +519,7 @@
if snap.R(s.Revision) == snap.R(ms.serveRevision[name]) {
continue
}
- hits = append(hits, json.RawMessage(fillHit(name)))
+ hits = append(hits, json.RawMessage(fillHit(searchHit, name)))
}
w.WriteHeader(200)
output, err := json.Marshal(map[string]interface{}{
@@ -506,6 +541,50 @@
panic(err)
}
io.Copy(w, snapR)
+ case "v2:refresh":
+ dec := json.NewDecoder(r.Body)
+ var input struct {
+ Actions []struct {
+ Action string `json:"action"`
+ SnapID string `json:"snap-id"`
+ Name string `json:"name"`
+ } `json:"actions"`
+ }
+ if err := dec.Decode(&input); err != nil {
+ panic(err)
+ }
+ type resultJSON struct {
+ Result string `json:"result"`
+ SnapID string `json:"snap-id"`
+ Name string `json:"name"`
+ Snap json.RawMessage `json:"snap"`
+ }
+ var results []resultJSON
+ for _, a := range input.Actions {
+ name := ms.serveIDtoName[a.SnapID]
+ if a.Action == "install" {
+ name = a.Name
+ }
+ if ms.serveSnapPath[name] == "" {
+ // no match
+ continue
+ }
+ results = append(results, resultJSON{
+ Result: a.Action,
+ SnapID: a.SnapID,
+ Name: name,
+ Snap: json.RawMessage(fillHit(snapV2, name)),
+ })
+ }
+ w.WriteHeader(200)
+ output, err := json.Marshal(map[string]interface{}{
+ "results": results,
+ })
+ if err != nil {
+ panic(err)
+ }
+ w.Write(output)
+
default:
panic("unexpected url path: " + r.URL.Path)
}
diff -Nru snapd-2.32.3.2/overlord/servicestate/servicestate.go snapd-2.32.9/overlord/servicestate/servicestate.go
--- snapd-2.32.3.2/overlord/servicestate/servicestate.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/servicestate/servicestate.go 2018-05-16 08:20:08.000000000 +0000
@@ -21,6 +21,7 @@
import (
"fmt"
+ "time"
"github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/overlord/cmdstate"
@@ -107,5 +108,10 @@
return nil, &ServiceActionConflictError{err}
}
- return cmdstate.Exec(st, desc, argv), nil
+ // Give the systemctl a maximum time of 61 for now.
+ //
+ // Longer term we need to refactor this code and
+ // reuse the snapd/systemd and snapd/wrapper packages
+ // to control the timeout in a single place.
+ return cmdstate.ExecWithTimeout(st, desc, argv, 61*time.Second), nil
}
diff -Nru snapd-2.32.3.2/overlord/snapstate/autorefresh_test.go snapd-2.32.9/overlord/snapstate/autorefresh_test.go
--- snapd-2.32.3.2/overlord/snapstate/autorefresh_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/snapstate/autorefresh_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2017 Canonical Ltd
+ * Copyright (C) 2017-2018 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -54,6 +54,22 @@
}
r.ops = append(r.ops, "list-refresh")
return nil, r.listRefreshErr
+}
+
+func (r *autoRefreshStore) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, user *auth.UserState, opts *store.RefreshOptions) ([]*snap.Info, error) {
+ if ctx == nil || !auth.IsEnsureContext(ctx) {
+ panic("Ensure marked context required")
+ }
+ if len(currentSnaps) != len(actions) || len(currentSnaps) == 0 {
+ panic("expected in test one action for each current snaps, and at least one snap")
+ }
+ for _, a := range actions {
+ if a.Action != "refresh" {
+ panic("expected refresh actions")
+ }
+ }
+ r.ops = append(r.ops, "list-refresh")
+ return nil, r.listRefreshErr
}
type autoRefreshTestSuite struct {
diff -Nru snapd-2.32.3.2/overlord/snapstate/backend/setup.go snapd-2.32.9/overlord/snapstate/backend/setup.go
--- snapd-2.32.3.2/overlord/snapstate/backend/setup.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/snapstate/backend/setup.go 2018-05-16 08:20:08.000000000 +0000
@@ -30,12 +30,12 @@
)
// SetupSnap does prepare and mount the snap for further processing.
-func (b Backend) SetupSnap(snapFilePath string, sideInfo *snap.SideInfo, meter progress.Meter) (err error) {
+func (b Backend) SetupSnap(snapFilePath string, sideInfo *snap.SideInfo, meter progress.Meter) (snapType snap.Type, err error) {
// This assumes that the snap was already verified or --dangerous was used.
s, snapf, oErr := OpenSnapFile(snapFilePath, sideInfo)
if oErr != nil {
- return oErr
+ return snapType, oErr
}
instdir := s.MountDir()
@@ -50,25 +50,25 @@
}()
if err := os.MkdirAll(instdir, 0755); err != nil {
- return err
+ return snapType, err
}
if err := snapf.Install(s.MountFile(), instdir); err != nil {
- return err
+ return snapType, err
}
// generate the mount unit for the squashfs
if err := addMountUnit(s, meter); err != nil {
- return err
+ return snapType, err
}
if s.Type == snap.TypeKernel {
if err := boot.ExtractKernelAssets(s, snapf); err != nil {
- return fmt.Errorf("cannot install kernel: %s", err)
+ return snapType, fmt.Errorf("cannot install kernel: %s", err)
}
}
- return err
+ return s.Type, err
}
// RemoveSnapFiles removes the snap files from the disk after unmounting the snap.
diff -Nru snapd-2.32.3.2/overlord/snapstate/backend/setup_test.go snapd-2.32.9/overlord/snapstate/backend/setup_test.go
--- snapd-2.32.3.2/overlord/snapstate/backend/setup_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/snapstate/backend/setup_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -80,8 +80,9 @@
Revision: snap.R(14),
}
- err := s.be.SetupSnap(snapPath, &si, progress.Null)
+ snapType, err := s.be.SetupSnap(snapPath, &si, progress.Null)
c.Assert(err, IsNil)
+ c.Check(snapType, Equals, snap.TypeApp)
// after setup the snap file is in the right dir
c.Assert(osutil.FileExists(filepath.Join(dirs.SnapBlobDir, "hello_14.snap")), Equals, true)
@@ -131,8 +132,9 @@
Revision: snap.R(140),
}
- err := s.be.SetupSnap(snapPath, &si, progress.Null)
+ snapType, err := s.be.SetupSnap(snapPath, &si, progress.Null)
c.Assert(err, IsNil)
+ c.Check(snapType, Equals, snap.TypeKernel)
l, _ := filepath.Glob(filepath.Join(bootloader.Dir(), "*"))
c.Assert(l, HasLen, 1)
@@ -175,11 +177,11 @@
Revision: snap.R(140),
}
- err := s.be.SetupSnap(snapPath, &si, progress.Null)
+ _, err := s.be.SetupSnap(snapPath, &si, progress.Null)
c.Assert(err, IsNil)
// retry run
- err = s.be.SetupSnap(snapPath, &si, progress.Null)
+ _, err = s.be.SetupSnap(snapPath, &si, progress.Null)
c.Assert(err, IsNil)
minInfo := snap.MinimalPlaceInfo("kernel", snap.R(140))
@@ -224,7 +226,7 @@
Revision: snap.R(140),
}
- err := s.be.SetupSnap(snapPath, &si, progress.Null)
+ _, err := s.be.SetupSnap(snapPath, &si, progress.Null)
c.Assert(err, IsNil)
minInfo := snap.MinimalPlaceInfo("kernel", snap.R(140))
@@ -264,7 +266,7 @@
})
defer r()
- err := s.be.SetupSnap(snapPath, &si, progress.Null)
+ _, err := s.be.SetupSnap(snapPath, &si, progress.Null)
c.Assert(err, ErrorMatches, "failed")
// everything is gone
diff -Nru snapd-2.32.3.2/overlord/snapstate/backend.go snapd-2.32.9/overlord/snapstate/backend.go
--- snapd-2.32.3.2/overlord/snapstate/backend.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/snapstate/backend.go 2018-05-16 08:20:08.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2016-2017 Canonical Ltd
+ * Copyright (C) 2016-2018 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -37,9 +37,13 @@
SnapInfo(spec store.SnapSpec, user *auth.UserState) (*snap.Info, error)
Find(search *store.Search, user *auth.UserState) ([]*snap.Info, error)
LookupRefresh(*store.RefreshCandidate, *auth.UserState) (*snap.Info, error)
+
ListRefresh(context.Context, []*store.RefreshCandidate, *auth.UserState, *store.RefreshOptions) ([]*snap.Info, error)
+ SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, user *auth.UserState, opts *store.RefreshOptions) ([]*snap.Info, error)
+
Sections(ctx context.Context, user *auth.UserState) ([]string, error)
WriteCatalogs(ctx context.Context, names io.Writer, adder store.SnapAdder) error
+
Download(context.Context, string, string, *snap.DownloadInfo, progress.Meter, *auth.UserState) error
Assertion(assertType *asserts.AssertionType, primaryKey []string, user *auth.UserState) (asserts.Assertion, error)
@@ -51,7 +55,7 @@
type managerBackend interface {
// install releated
- SetupSnap(snapFilePath string, si *snap.SideInfo, meter progress.Meter) error
+ SetupSnap(snapFilePath string, si *snap.SideInfo, meter progress.Meter) (snap.Type, error)
CopySnapData(newSnap, oldSnap *snap.Info, meter progress.Meter) error
LinkSnap(info *snap.Info) error
StartServices(svcs []*snap.AppInfo, meter progress.Meter) error
diff -Nru snapd-2.32.3.2/overlord/snapstate/backend_test.go snapd-2.32.9/overlord/snapstate/backend_test.go
--- snapd-2.32.3.2/overlord/snapstate/backend_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/snapstate/backend_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2016-2017 Canonical Ltd
+ * Copyright (C) 2016-2018 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -47,7 +47,9 @@
revno snap.Revision
sinfo snap.SideInfo
stype snap.Type
- cand store.RefreshCandidate
+
+ curSnaps []store.CurrentSnap
+ action store.SnapAction
old string
@@ -93,6 +95,29 @@
macaroon string
}
+type byName []store.CurrentSnap
+
+func (bna byName) Len() int { return len(bna) }
+func (bna byName) Swap(i, j int) { bna[i], bna[j] = bna[j], bna[i] }
+func (bna byName) Less(i, j int) bool {
+ return bna[i].Name < bna[j].Name
+}
+
+type byAction []*store.SnapAction
+
+func (ba byAction) Len() int { return len(ba) }
+func (ba byAction) Swap(i, j int) { ba[i], ba[j] = ba[j], ba[i] }
+func (ba byAction) Less(i, j int) bool {
+ if ba[i].Action == ba[j].Action {
+ if ba[i].Action == "refresh" {
+ return ba[i].SnapID < ba[j].SnapID
+ } else {
+ return ba[i].Name < ba[j].Name
+ }
+ }
+ return ba[i].Action < ba[j].Action
+}
+
type fakeStore struct {
storetest.Store
@@ -114,6 +139,18 @@
func (f *fakeStore) SnapInfo(spec store.SnapSpec, user *auth.UserState) (*snap.Info, error) {
f.pokeStateLock()
+ info, err := f.snapInfo(spec, user)
+
+ userID := 0
+ if user != nil {
+ userID = user.ID
+ }
+ f.fakeBackend.ops = append(f.fakeBackend.ops, fakeOp{op: "storesvc-snap", name: spec.Name, revno: info.Revision, userID: userID})
+
+ return info, err
+}
+
+func (f *fakeStore) snapInfo(spec store.SnapSpec, user *auth.UserState) (*snap.Info, error) {
if spec.Revision.Unset() {
spec.Revision = snap.R(11)
if spec.Channel == "channel-for-7" {
@@ -128,6 +165,10 @@
typ = snap.TypeOS
}
+ if spec.Name == "snap-unknown" {
+ return nil, store.ErrSnapNotFound
+ }
+
info := &snap.Info{
Architectures: []string{"all"},
SideInfo: snap.SideInfo{
@@ -163,27 +204,24 @@
}
}
- userID := 0
- if user != nil {
- userID = user.ID
- }
- f.fakeBackend.ops = append(f.fakeBackend.ops, fakeOp{op: "storesvc-snap", name: spec.Name, revno: spec.Revision, userID: userID})
-
return info, nil
}
-func (f *fakeStore) LookupRefresh(cand *store.RefreshCandidate, user *auth.UserState) (*snap.Info, error) {
- f.pokeStateLock()
-
- if cand == nil {
- panic("LookupRefresh called with no candidate")
- }
+type refreshCand struct {
+ snapID string
+ channel string
+ revision snap.Revision
+ block []snap.Revision
+ ignoreValidation bool
+}
+func (f *fakeStore) lookupRefresh(cand refreshCand) (*snap.Info, error) {
var name string
- switch cand.SnapID {
+ typ := snap.TypeApp
+ switch cand.snapID {
case "":
- return nil, store.ErrLocalSnap
+ panic("store refresh APIs expect snap-ids")
case "other-snap-id":
return nil, store.ErrNoUpdateAvailable
case "fakestore-please-error-on-refresh":
@@ -194,18 +232,26 @@
name = "some-snap"
case "core-snap-id":
name = "core"
+ typ = snap.TypeOS
case "snap-with-snapd-control-id":
name = "snap-with-snapd-control"
+ case "producer-id":
+ name = "producer"
+ case "consumer-id":
+ name = "consumer"
+ case "some-base-id":
+ name = "some-base"
+ typ = snap.TypeBase
default:
- panic(fmt.Sprintf("ListRefresh: unknown snap-id: %s", cand.SnapID))
+ panic(fmt.Sprintf("refresh: unknown snap-id: %s", cand.snapID))
}
revno := snap.R(11)
- if r := f.refreshRevnos[cand.SnapID]; !r.Unset() {
+ if r := f.refreshRevnos[cand.snapID]; !r.Unset() {
revno = r
}
confinement := snap.StrictConfinement
- switch cand.Channel {
+ switch cand.channel {
case "channel-for-7":
revno = snap.R(7)
case "channel-for-classic":
@@ -215,10 +261,11 @@
}
info := &snap.Info{
+ Type: typ,
SideInfo: snap.SideInfo{
RealName: name,
- Channel: cand.Channel,
- SnapID: cand.SnapID,
+ Channel: cand.channel,
+ SnapID: cand.snapID,
Revision: revno,
},
Version: name,
@@ -228,7 +275,7 @@
Confinement: confinement,
Architectures: []string{"all"},
}
- switch cand.Channel {
+ switch cand.channel {
case "channel-for-layout":
info.Layout = map[string]*snap.Layout{
"/usr": {
@@ -237,26 +284,21 @@
Symlink: "$SNAP/usr",
},
}
+ case "channel-for-base":
+ info.Base = "some-base"
}
var hit snap.Revision
- if cand.Revision != revno {
+ if cand.revision != revno {
hit = revno
}
- for _, blocked := range cand.Block {
+ for _, blocked := range cand.block {
if blocked == revno {
hit = snap.Revision{}
break
}
}
- userID := 0
- if user != nil {
- userID = user.ID
- }
- // TODO: move this back to ListRefresh
- f.fakeBackend.ops = append(f.fakeBackend.ops, fakeOp{op: "storesvc-list-refresh", cand: *cand, revno: hit, userID: userID})
-
if !hit.Unset() {
return info, nil
}
@@ -264,28 +306,136 @@
return nil, store.ErrNoUpdateAvailable
}
-func (f *fakeStore) ListRefresh(ctx context.Context, cands []*store.RefreshCandidate, user *auth.UserState, flags *store.RefreshOptions) ([]*snap.Info, error) {
+func (f *fakeStore) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, user *auth.UserState, opts *store.RefreshOptions) ([]*snap.Info, error) {
if ctx == nil {
panic("context required")
}
f.pokeStateLock()
- if len(cands) == 0 {
+ if len(currentSnaps) == 0 && len(actions) == 0 {
return nil, nil
}
- if len(cands) > 3 {
- panic("fake ListRefresh unexpectedly called with more than 3 candidates")
+ if len(actions) > 3 {
+ panic("fake SnapAction unexpectedly called with more than 3 actions")
+ }
+
+ curByID := make(map[string]*store.CurrentSnap, len(currentSnaps))
+ curSnaps := make(byName, len(currentSnaps))
+ for i, cur := range currentSnaps {
+ if cur.Name == "" || cur.SnapID == "" || cur.Revision.Unset() {
+ return nil, fmt.Errorf("internal error: incomplete current snap info")
+ }
+ curByID[cur.SnapID] = cur
+ curSnaps[i] = *cur
+ }
+ sort.Sort(curSnaps)
+
+ userID := 0
+ if user != nil {
+ userID = user.ID
+ }
+ if len(curSnaps) == 0 {
+ curSnaps = nil
}
+ f.fakeBackend.ops = append(f.fakeBackend.ops, fakeOp{op: "storesvc-snap-action", curSnaps: curSnaps, userID: userID})
+ sorted := make(byAction, len(actions))
+ copy(sorted, actions)
+ sort.Sort(sorted)
+
+ refreshErrors := make(map[string]error)
+ installErrors := make(map[string]error)
var res []*snap.Info
- for _, cand := range cands {
- info, err := f.LookupRefresh(cand, user)
- if err == store.ErrLocalSnap || err == store.ErrNoUpdateAvailable {
+ for _, a := range sorted {
+ if a.Action != "install" && a.Action != "refresh" {
+ panic("not supported")
+ }
+
+ if a.Action == "install" {
+ spec := store.SnapSpec{
+ Name: a.Name,
+ Channel: a.Channel,
+ Revision: a.Revision,
+ }
+ info, err := f.snapInfo(spec, user)
+ if err != nil {
+ installErrors[a.Name] = err
+ continue
+ }
+ f.fakeBackend.ops = append(f.fakeBackend.ops, fakeOp{
+ op: "storesvc-snap-action:action",
+ action: *a,
+ revno: info.Revision,
+ userID: userID,
+ })
+ if !a.Revision.Unset() {
+ info.Channel = ""
+ }
+ res = append(res, info)
continue
}
+
+ // refresh
+
+ cur := curByID[a.SnapID]
+ channel := a.Channel
+ if channel == "" {
+ channel = cur.TrackingChannel
+ }
+ ignoreValidation := cur.IgnoreValidation
+ if a.Flags&store.SnapActionIgnoreValidation != 0 {
+ ignoreValidation = true
+ } else if a.Flags&store.SnapActionEnforceValidation != 0 {
+ ignoreValidation = false
+ }
+ cand := refreshCand{
+ snapID: a.SnapID,
+ channel: channel,
+ revision: cur.Revision,
+ block: cur.Block,
+ ignoreValidation: ignoreValidation,
+ }
+ info, err := f.lookupRefresh(cand)
+ var hit snap.Revision
+ if info != nil {
+ if !a.Revision.Unset() {
+ info.Revision = a.Revision
+ }
+ hit = info.Revision
+ }
+ f.fakeBackend.ops = append(f.fakeBackend.ops, fakeOp{
+ op: "storesvc-snap-action:action",
+ action: *a,
+ revno: hit,
+ userID: userID,
+ })
+ if err == store.ErrNoUpdateAvailable {
+ refreshErrors[cur.Name] = err
+ continue
+ }
+ if err != nil {
+ return nil, err
+ }
+ if !a.Revision.Unset() {
+ info.Channel = ""
+ }
res = append(res, info)
}
+ if len(refreshErrors)+len(installErrors) > 0 || len(res) == 0 {
+ if len(refreshErrors) == 0 {
+ refreshErrors = nil
+ }
+ if len(installErrors) == 0 {
+ installErrors = nil
+ }
+ return res, &store.SnapActionError{
+ NoResults: len(refreshErrors)+len(installErrors)+len(res) == 0,
+ Refresh: refreshErrors,
+ Install: installErrors,
+ }
+ }
+
return res, nil
}
@@ -365,7 +515,7 @@
return &snap.Info{SuggestedName: name, Architectures: []string{"all"}}, f.emptyContainer, nil
}
-func (f *fakeSnappyBackend) SetupSnap(snapFilePath string, si *snap.SideInfo, p progress.Meter) error {
+func (f *fakeSnappyBackend) SetupSnap(snapFilePath string, si *snap.SideInfo, p progress.Meter) (snap.Type, error) {
p.Notify("setup-snap")
revno := snap.R(0)
if si != nil {
@@ -376,13 +526,23 @@
name: snapFilePath,
revno: revno,
})
- return nil
+ snapType := snap.TypeApp
+ switch si.RealName {
+ case "core":
+ snapType = snap.TypeOS
+ case "gadget":
+ snapType = snap.TypeGadget
+ }
+ return snapType, nil
}
func (f *fakeSnappyBackend) ReadInfo(name string, si *snap.SideInfo) (*snap.Info, error) {
- if name == "borken" {
+ if name == "borken" && si.Revision == snap.R(2) {
return nil, errors.New(`cannot read info for "borken" snap`)
}
+ if name == "not-there" && si.Revision == snap.R(2) {
+ return nil, &snap.NotFoundError{Snap: name, Revision: si.Revision}
+ }
// naive emulation for now, always works
info := &snap.Info{
SuggestedName: name,
diff -Nru snapd-2.32.3.2/overlord/snapstate/export_test.go snapd-2.32.9/overlord/snapstate/export_test.go
--- snapd-2.32.3.2/overlord/snapstate/export_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/snapstate/export_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -21,6 +21,7 @@
import (
"errors"
+ "sort"
"time"
"gopkg.in/tomb.v2"
@@ -83,10 +84,25 @@
m.runner.AddHandler(adhoc, do, undo)
}
-func MockReadInfo(mock func(name string, si *snap.SideInfo) (*snap.Info, error)) (restore func()) {
- old := readInfo
- readInfo = mock
- return func() { readInfo = old }
+func MockSnapReadInfo(mock func(name string, si *snap.SideInfo) (*snap.Info, error)) (restore func()) {
+ old := snapReadInfo
+ snapReadInfo = mock
+ return func() { snapReadInfo = old }
+}
+
+func MockMountPollInterval(intv time.Duration) (restore func()) {
+ old := mountPollInterval
+ mountPollInterval = intv
+ return func() { mountPollInterval = old }
+}
+
+func MockRevisionDate(mock func(info *snap.Info) time.Time) (restore func()) {
+ old := revisionDate
+ if mock == nil {
+ mock = revisionDateImpl
+ }
+ revisionDate = mock
+ return func() { revisionDate = old }
}
func MockOpenSnapFile(mock func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error)) (restore func()) {
@@ -163,3 +179,8 @@
refreshRetryDelay = origRefreshRetryDelay
}
}
+
+func ByKindOrder(snaps ...*snap.Info) []*snap.Info {
+ sort.Sort(byKind(snaps))
+ return snaps
+}
diff -Nru snapd-2.32.3.2/overlord/snapstate/handlers_discard_test.go snapd-2.32.9/overlord/snapstate/handlers_discard_test.go
--- snapd-2.32.3.2/overlord/snapstate/handlers_discard_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/snapstate/handlers_discard_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -53,7 +53,7 @@
snapstate.SetSnapManagerBackend(s.snapmgr, s.fakeBackend)
- s.reset = snapstate.MockReadInfo(s.fakeBackend.ReadInfo)
+ s.reset = snapstate.MockSnapReadInfo(s.fakeBackend.ReadInfo)
}
func (s *discardSnapSuite) TearDownTest(c *C) {
diff -Nru snapd-2.32.3.2/overlord/snapstate/handlers_download_test.go snapd-2.32.9/overlord/snapstate/handlers_download_test.go
--- snapd-2.32.3.2/overlord/snapstate/handlers_download_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/snapstate/handlers_download_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -25,6 +25,7 @@
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/store"
)
type downloadSnapSuite struct {
@@ -58,7 +59,7 @@
snapstate.SetSnapManagerBackend(s.snapmgr, s.fakeBackend)
- s.reset = snapstate.MockReadInfo(s.fakeBackend.ReadInfo)
+ s.reset = snapstate.MockSnapReadInfo(s.fakeBackend.ReadInfo)
}
func (s *downloadSnapSuite) TearDownTest(c *C) {
@@ -90,8 +91,15 @@
// the compat code called the store "Snap" endpoint
c.Assert(s.fakeBackend.ops, DeepEquals, fakeOps{
{
- op: "storesvc-snap",
- name: "foo",
+ op: "storesvc-snap-action",
+ },
+ {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "install",
+ Name: "foo",
+ Channel: "some-channel",
+ },
revno: snap.R(11),
},
{
diff -Nru snapd-2.32.3.2/overlord/snapstate/handlers.go snapd-2.32.9/overlord/snapstate/handlers.go
--- snapd-2.32.3.2/overlord/snapstate/handlers.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/snapstate/handlers.go 2018-05-16 08:20:08.000000000 +0000
@@ -37,7 +37,6 @@
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
- "github.com/snapcore/snapd/store"
)
// TaskSnapSetup returns the SnapSetup with task params hold by or referred to by the the task.
@@ -114,7 +113,22 @@
*/
const defaultCoreSnapName = "core"
-const defaultBaseSnapsChannel = "stable"
+
+func defaultBaseSnapsChannel() string {
+ channel := os.Getenv("SNAPD_BASES_CHANNEL")
+ if channel == "" {
+ return "stable"
+ }
+ return channel
+}
+
+func defaultPrereqSnapsChannel() string {
+ channel := os.Getenv("SNAPD_PREREQS_CHANNEL")
+ if channel == "" {
+ return "stable"
+ }
+ return channel
+}
func linkSnapInFlight(st *state.State, snapName string) (bool, error) {
for _, chg := range st.Changes() {
@@ -183,7 +197,7 @@
return nil
}
-func (m *SnapManager) installOneBaseOrRequired(st *state.State, snapName string, onInFlight error, userID int) (*state.TaskSet, error) {
+func (m *SnapManager) installOneBaseOrRequired(st *state.State, snapName, channel string, onInFlight error, userID int) (*state.TaskSet, error) {
// installed already?
isInstalled, err := isInstalled(st, snapName)
if err != nil {
@@ -202,7 +216,7 @@
}
// not installed, nor queued for install -> install it
- ts, err := Install(st, snapName, defaultBaseSnapsChannel, snap.R(0), userID, Flags{})
+ ts, err := Install(st, snapName, channel, snap.R(0), userID, Flags{})
// something might have triggered an explicit install while
// the state was unlocked -> deal with that here by simply
// retrying the operation.
@@ -224,7 +238,7 @@
var tss []*state.TaskSet
for _, prereqName := range prereq {
var onInFlightErr error = nil
- ts, err := m.installOneBaseOrRequired(st, prereqName, onInFlightErr, userID)
+ ts, err := m.installOneBaseOrRequired(st, prereqName, defaultPrereqSnapsChannel(), onInFlightErr, userID)
if err != nil {
return err
}
@@ -237,7 +251,7 @@
// for base snaps we need to wait until the change is done
// (either finished or failed)
onInFlightErr := &state.Retry{After: prerequisitesRetryTimeout}
- tsBase, err := m.installOneBaseOrRequired(st, base, onInFlightErr, userID)
+ tsBase, err := m.installOneBaseOrRequired(st, base, defaultBaseSnapsChannel(), onInFlightErr, userID)
if err != nil {
return err
}
@@ -363,6 +377,12 @@
return nil
}
+func installInfoUnlocked(st *state.State, snapsup *SnapSetup) (*snap.Info, error) {
+ st.Lock()
+ defer st.Unlock()
+ return installInfo(st, snapsup.Name(), snapsup.Channel, snapsup.Revision(), snapsup.UserID)
+}
+
func (m *SnapManager) doDownloadSnap(t *state.Task, tomb *tomb.Tomb) error {
st := t.State()
st.Lock()
@@ -387,12 +407,7 @@
// COMPATIBILITY - this task was created from an older version
// of snapd that did not store the DownloadInfo in the state
// yet.
- spec := store.SnapSpec{
- Name: snapsup.Name(),
- Channel: snapsup.Channel,
- Revision: snapsup.Revision(),
- }
- storeInfo, err = theStore.SnapInfo(spec, user)
+ storeInfo, err = installInfoUnlocked(st, snapsup)
if err != nil {
return err
}
@@ -415,6 +430,10 @@
return nil
}
+var (
+ mountPollInterval = 1 * time.Second
+)
+
func (m *SnapManager) doMountSnap(t *state.Task, _ *tomb.Tomb) error {
t.State().Lock()
snapsup, snapst, err := snapSetupAndState(t)
@@ -436,18 +455,41 @@
pb := NewTaskProgressAdapterUnlocked(t)
// TODO Use snapsup.Revision() to obtain the right info to mount
// instead of assuming the candidate is the right one.
- if err := m.backend.SetupSnap(snapsup.SnapPath, snapsup.SideInfo, pb); err != nil {
+ snapType, err := m.backend.SetupSnap(snapsup.SnapPath, snapsup.SideInfo, pb)
+ if err != nil {
return err
}
- // set snapst type for undoMountSnap
- newInfo, err := readInfo(snapsup.Name(), snapsup.SideInfo)
- if err != nil {
- return err
+ // double check that the snap is mounted
+ var readInfoErr error
+ for i := 0; i < 10; i++ {
+ _, readInfoErr = readInfo(snapsup.Name(), snapsup.SideInfo, errorOnBroken)
+ if readInfoErr == nil {
+ break
+ }
+ if _, ok := readInfoErr.(*snap.NotFoundError); !ok {
+ break
+ }
+ // snap not found, seems is not mounted yet
+ msg := fmt.Sprintf("expected snap %q revision %v to be mounted but is not", snapsup.Name(), snapsup.Revision())
+ readInfoErr = fmt.Errorf("cannot proceed, %s", msg)
+ if i == 0 {
+ logger.Noticef(msg)
+ }
+ time.Sleep(mountPollInterval)
+ }
+ if readInfoErr != nil {
+ if err := m.backend.UndoSetupSnap(snapsup.placeInfo(), snapType, pb); err != nil {
+ t.State().Lock()
+ t.Errorf("cannot undo partial setup snap %q: %v", snapsup.Name(), err)
+ t.State().Unlock()
+ }
+ return readInfoErr
}
+ // set snapst type for undoMountSnap
t.State().Lock()
- t.Set("snap-type", newInfo.Type)
+ t.Set("snap-type", snapType)
t.State().Unlock()
if snapsup.Flags.RemoveSnapPath {
@@ -554,7 +596,7 @@
return err
}
- newInfo, err := readInfo(snapsup.Name(), snapsup.SideInfo)
+ newInfo, err := readInfo(snapsup.Name(), snapsup.SideInfo, 0)
if err != nil {
return err
}
@@ -576,7 +618,7 @@
return err
}
- newInfo, err := readInfo(snapsup.Name(), snapsup.SideInfo)
+ newInfo, err := readInfo(snapsup.Name(), snapsup.SideInfo, 0)
if err != nil {
return err
}
@@ -676,7 +718,7 @@
}
}
- newInfo, err := readInfo(snapsup.Name(), cand)
+ newInfo, err := readInfo(snapsup.Name(), cand, 0)
if err != nil {
return err
}
@@ -850,7 +892,7 @@
snapst.JailMode = oldJailMode
snapst.Classic = oldClassic
- newInfo, err := readInfo(snapsup.Name(), snapsup.SideInfo)
+ newInfo, err := readInfo(snapsup.Name(), snapsup.SideInfo, 0)
if err != nil {
return err
}
diff -Nru snapd-2.32.3.2/overlord/snapstate/handlers_link_test.go snapd-2.32.9/overlord/snapstate/handlers_link_test.go
--- snapd-2.32.3.2/overlord/snapstate/handlers_link_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/snapstate/handlers_link_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -77,7 +77,7 @@
snapstate.SetSnapManagerBackend(s.snapmgr, s.fakeBackend)
- resetReadInfo := snapstate.MockReadInfo(s.fakeBackend.ReadInfo)
+ resetReadInfo := snapstate.MockSnapReadInfo(s.fakeBackend.ReadInfo)
s.reset = func() {
resetReadInfo()
dirs.SetRootDir("/")
diff -Nru snapd-2.32.3.2/overlord/snapstate/handlers_mount_test.go snapd-2.32.9/overlord/snapstate/handlers_mount_test.go
--- snapd-2.32.3.2/overlord/snapstate/handlers_mount_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/snapstate/handlers_mount_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -21,6 +21,7 @@
import (
"path/filepath"
+ "time"
. "gopkg.in/check.v1"
@@ -57,7 +58,7 @@
snapstate.SetSnapManagerBackend(s.snapmgr, s.fakeBackend)
- reset1 := snapstate.MockReadInfo(s.fakeBackend.ReadInfo)
+ reset1 := snapstate.MockSnapReadInfo(s.fakeBackend.ReadInfo)
s.reset = func() {
reset1()
dirs.SetRootDir(oldDir)
@@ -153,3 +154,189 @@
})
}
+
+func (s *mountSnapSuite) TestDoMountSnapError(c *C) {
+ v1 := "name: borken\nversion: 1.0\n"
+ testSnap := snaptest.MakeTestSnapWithFiles(c, v1, nil)
+
+ s.state.Lock()
+ defer s.state.Unlock()
+ si1 := &snap.SideInfo{
+ RealName: "borken",
+ Revision: snap.R(1),
+ }
+ si2 := &snap.SideInfo{
+ RealName: "borken",
+ Revision: snap.R(2),
+ }
+ snapstate.Set(s.state, "borken", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{si1},
+ Current: si1.Revision,
+ SnapType: "app",
+ })
+
+ t := s.state.NewTask("mount-snap", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: si2,
+ SnapPath: testSnap,
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ s.state.Unlock()
+
+ for i := 0; i < 3; i++ {
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+ }
+
+ s.state.Lock()
+
+ c.Check(chg.Err(), ErrorMatches, `(?s).*cannot read info for "borken" snap.*`)
+
+ c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
+ {
+ op: "current",
+ old: filepath.Join(dirs.SnapMountDir, "borken/1"),
+ },
+ {
+ op: "setup-snap",
+ name: testSnap,
+ revno: snap.R(2),
+ },
+ {
+ op: "undo-setup-snap",
+ name: filepath.Join(dirs.SnapMountDir, "borken/2"),
+ stype: "app",
+ },
+ })
+}
+
+func (s *mountSnapSuite) TestDoMountSnapErrorNotFound(c *C) {
+ r := snapstate.MockMountPollInterval(10 * time.Millisecond)
+ defer r()
+
+ v1 := "name: not-there\nversion: 1.0\n"
+ testSnap := snaptest.MakeTestSnapWithFiles(c, v1, nil)
+
+ s.state.Lock()
+ defer s.state.Unlock()
+ si1 := &snap.SideInfo{
+ RealName: "not-there",
+ Revision: snap.R(1),
+ }
+ si2 := &snap.SideInfo{
+ RealName: "not-there",
+ Revision: snap.R(2),
+ }
+ snapstate.Set(s.state, "not-there", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{si1},
+ Current: si1.Revision,
+ SnapType: "app",
+ })
+
+ t := s.state.NewTask("mount-snap", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: si2,
+ SnapPath: testSnap,
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ s.state.Unlock()
+
+ for i := 0; i < 3; i++ {
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+ }
+
+ s.state.Lock()
+
+ c.Check(chg.Err(), ErrorMatches, `(?s).*cannot proceed, expected snap "not-there" revision 2 to be mounted but is not.*`)
+
+ c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
+ {
+ op: "current",
+ old: filepath.Join(dirs.SnapMountDir, "not-there/1"),
+ },
+ {
+ op: "setup-snap",
+ name: testSnap,
+ revno: snap.R(2),
+ },
+ {
+ op: "undo-setup-snap",
+ name: filepath.Join(dirs.SnapMountDir, "not-there/2"),
+ stype: "app",
+ },
+ })
+}
+
+func (s *mountSnapSuite) TestDoMountNotMountedRetryRetry(c *C) {
+ r := snapstate.MockMountPollInterval(10 * time.Millisecond)
+ defer r()
+ n := 0
+ slowMountedReadInfo := func(name string, si *snap.SideInfo) (*snap.Info, error) {
+ n++
+ if n < 3 {
+ return nil, &snap.NotFoundError{Snap: "not-there", Revision: si.Revision}
+ }
+ return &snap.Info{
+ SideInfo: *si,
+ }, nil
+ }
+
+ r1 := snapstate.MockSnapReadInfo(slowMountedReadInfo)
+ defer r1()
+
+ v1 := "name: not-there\nversion: 1.0\n"
+ testSnap := snaptest.MakeTestSnapWithFiles(c, v1, nil)
+
+ s.state.Lock()
+ defer s.state.Unlock()
+ si1 := &snap.SideInfo{
+ RealName: "not-there",
+ Revision: snap.R(1),
+ }
+ si2 := &snap.SideInfo{
+ RealName: "not-there",
+ Revision: snap.R(2),
+ }
+ snapstate.Set(s.state, "not-there", &snapstate.SnapState{
+ Sequence: []*snap.SideInfo{si1},
+ Current: si1.Revision,
+ SnapType: "app",
+ })
+
+ t := s.state.NewTask("mount-snap", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: si2,
+ SnapPath: testSnap,
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+
+ s.state.Unlock()
+
+ for i := 0; i < 3; i++ {
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+ }
+
+ s.state.Lock()
+
+ c.Check(chg.IsReady(), Equals, true)
+ c.Check(chg.Err(), IsNil)
+
+ c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
+ {
+ op: "current",
+ old: filepath.Join(dirs.SnapMountDir, "not-there/1"),
+ },
+ {
+ op: "setup-snap",
+ name: testSnap,
+ revno: snap.R(2),
+ },
+ })
+}
diff -Nru snapd-2.32.3.2/overlord/snapstate/handlers_prepare_test.go snapd-2.32.9/overlord/snapstate/handlers_prepare_test.go
--- snapd-2.32.3.2/overlord/snapstate/handlers_prepare_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/snapstate/handlers_prepare_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -52,7 +52,7 @@
snapstate.SetSnapManagerBackend(s.snapmgr, s.fakeBackend)
- reset1 := snapstate.MockReadInfo(s.fakeBackend.ReadInfo)
+ reset1 := snapstate.MockSnapReadInfo(s.fakeBackend.ReadInfo)
s.reset = func() {
dirs.SetRootDir("/")
reset1()
diff -Nru snapd-2.32.3.2/overlord/snapstate/handlers_prereq_test.go snapd-2.32.9/overlord/snapstate/handlers_prereq_test.go
--- snapd-2.32.3.2/overlord/snapstate/handlers_prereq_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/snapstate/handlers_prereq_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -20,6 +20,7 @@
package snapstate_test
import (
+ "os"
"time"
. "gopkg.in/check.v1"
@@ -29,6 +30,7 @@
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/store"
)
type prereqSuite struct {
@@ -62,7 +64,7 @@
s.snapmgr.AddForeignTaskHandlers(s.fakeBackend)
snapstate.SetSnapManagerBackend(s.snapmgr, s.fakeBackend)
- s.reset = snapstate.MockReadInfo(s.fakeBackend.ReadInfo)
+ s.reset = snapstate.MockSnapReadInfo(s.fakeBackend.ReadInfo)
}
func (s *prereqSuite) TearDownTest(c *C) {
@@ -123,18 +125,39 @@
defer s.state.Unlock()
c.Assert(s.fakeBackend.ops, DeepEquals, fakeOps{
{
- op: "storesvc-snap",
- name: "prereq1",
+ op: "storesvc-snap-action",
+ },
+ {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "install",
+ Name: "prereq1",
+ Channel: "stable",
+ },
revno: snap.R(11),
},
{
- op: "storesvc-snap",
- name: "prereq2",
+ op: "storesvc-snap-action",
+ },
+ {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "install",
+ Name: "prereq2",
+ Channel: "stable",
+ },
revno: snap.R(11),
},
{
- op: "storesvc-snap",
- name: "some-base",
+ op: "storesvc-snap-action",
+ },
+ {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "install",
+ Name: "some-base",
+ Channel: "stable",
+ },
revno: snap.R(11),
},
})
@@ -236,3 +259,69 @@
c.Check(t.Status(), Equals, state.DoneStatus)
}
+
+func (s *prereqSuite) TestDoPrereqChannelEnvvars(c *C) {
+ os.Setenv("SNAPD_BASES_CHANNEL", "edge")
+ defer os.Unsetenv("SNAPD_BASES_CHANNEL")
+ os.Setenv("SNAPD_PREREQS_CHANNEL", "candidate")
+ defer os.Unsetenv("SNAPD_PREREQS_CHANNEL")
+ s.state.Lock()
+ t := s.state.NewTask("prerequisites", "test")
+ t.Set("snap-setup", &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{
+ RealName: "foo",
+ Revision: snap.R(33),
+ },
+ Channel: "beta",
+ Base: "some-base",
+ Prereq: []string{"prereq1", "prereq2"},
+ })
+ chg := s.state.NewChange("dummy", "...")
+ chg.AddTask(t)
+ s.state.Unlock()
+
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+
+ s.state.Lock()
+ defer s.state.Unlock()
+ c.Assert(s.fakeBackend.ops, DeepEquals, fakeOps{
+ {
+ op: "storesvc-snap-action",
+ },
+ {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "install",
+ Name: "prereq1",
+ Channel: "candidate",
+ },
+ revno: snap.R(11),
+ },
+ {
+ op: "storesvc-snap-action",
+ },
+ {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "install",
+ Name: "prereq2",
+ Channel: "candidate",
+ },
+ revno: snap.R(11),
+ },
+ {
+ op: "storesvc-snap-action",
+ },
+ {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "install",
+ Name: "some-base",
+ Channel: "edge",
+ },
+ revno: snap.R(11),
+ },
+ })
+ c.Check(t.Status(), Equals, state.DoneStatus)
+}
diff -Nru snapd-2.32.3.2/overlord/snapstate/refreshhints_test.go snapd-2.32.9/overlord/snapstate/refreshhints_test.go
--- snapd-2.32.3.2/overlord/snapstate/refreshhints_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/snapstate/refreshhints_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2017 Canonical Ltd
+ * Copyright (C) 2017-2018 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -47,6 +47,22 @@
}
r.ops = append(r.ops, "list-refresh")
return nil, nil
+}
+
+func (r *recordingStore) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, user *auth.UserState, opts *store.RefreshOptions) ([]*snap.Info, error) {
+ if ctx == nil || !auth.IsEnsureContext(ctx) {
+ panic("Ensure marked context required")
+ }
+ if len(currentSnaps) != len(actions) || len(currentSnaps) == 0 {
+ panic("expected in test one action for each current snaps, and at least one snap")
+ }
+ for _, a := range actions {
+ if a.Action != "refresh" {
+ panic("expected refresh actions")
+ }
+ }
+ r.ops = append(r.ops, "list-refresh")
+ return nil, nil
}
type refreshHintsTestSuite struct {
diff -Nru snapd-2.32.3.2/overlord/snapstate/snapmgr.go snapd-2.32.9/overlord/snapstate/snapmgr.go
--- snapd-2.32.3.2/overlord/snapstate/snapmgr.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/snapstate/snapmgr.go 2018-05-16 08:20:08.000000000 +0000
@@ -212,10 +212,18 @@
var ErrNoCurrent = errors.New("snap has no current revision")
// Retrieval functions
-var readInfo = readInfoAnyway
-func readInfoAnyway(name string, si *snap.SideInfo) (*snap.Info, error) {
- info, err := snap.ReadInfo(name, si)
+const (
+ errorOnBroken = 1 << iota
+)
+
+var snapReadInfo = snap.ReadInfo
+
+func readInfo(name string, si *snap.SideInfo, flags int) (*snap.Info, error) {
+ info, err := snapReadInfo(name, si)
+ if err != nil && flags&errorOnBroken != 0 {
+ return nil, err
+ }
if err != nil {
logger.Noticef("cannot read snap info of snap %q at revision %s: %s", name, si.Revision, err)
}
@@ -234,13 +242,24 @@
return info, err
}
+var revisionDate = revisionDateImpl
+
+// revisionDate returns a good approximation of when a revision reached the system.
+func revisionDateImpl(info *snap.Info) time.Time {
+ fi, err := os.Lstat(info.MountFile())
+ if err != nil {
+ return time.Time{}
+ }
+ return fi.ModTime()
+}
+
// CurrentInfo returns the information about the current active revision or the last active revision (if the snap is inactive). It returns the ErrNoCurrent error if snapst.Current is unset.
func (snapst *SnapState) CurrentInfo() (*snap.Info, error) {
cur := snapst.CurrentSideInfo()
if cur == nil {
return nil, ErrNoCurrent
}
- return readInfo(cur.RealName, cur)
+ return readInfo(cur.RealName, cur, 0)
}
func revisionInSequence(snapst *SnapState, needle snap.Revision) bool {
diff -Nru snapd-2.32.3.2/overlord/snapstate/snapstate.go snapd-2.32.9/overlord/snapstate/snapstate.go
--- snapd-2.32.3.2/overlord/snapstate/snapstate.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/snapstate/snapstate.go 2018-05-16 08:20:08.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2016-2017 Canonical Ltd
+ * Copyright (C) 2016-2018 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -24,6 +24,7 @@
"encoding/json"
"fmt"
"reflect"
+ "sort"
"strings"
"golang.org/x/net/context"
@@ -64,6 +65,9 @@
}
func doInstall(st *state.State, snapst *SnapState, snapsup *SnapSetup, flags int) (*state.TaskSet, error) {
+ if snapsup.Name() == "system" {
+ return nil, fmt.Errorf("cannot install reserved snap name 'system'")
+ }
if snapst.IsInstalled() && !snapst.Active {
return nil, fmt.Errorf("cannot update disabled snap %q", snapsup.Name())
}
@@ -607,7 +611,7 @@
return nil, &snap.AlreadyInstalledError{Snap: name}
}
- info, err := snapInfo(st, name, channel, revision, userID)
+ info, err := installInfo(st, name, channel, revision, userID)
if err != nil {
return nil, err
}
@@ -637,6 +641,7 @@
func InstallMany(st *state.State, names []string, userID int) ([]string, []*state.TaskSet, error) {
installed := make([]string, 0, len(names))
tasksets := make([]*state.TaskSet, 0, len(names))
+ // TODO: this could be reorged to do one single store call
for _, name := range names {
ts, err := Install(st, name, "", snap.R(0), userID, Flags{})
// FIXME: is this expected behavior?
@@ -740,6 +745,18 @@
reportUpdated[snapName] = true
}
+ // first core, bases, then rest
+ sort.Sort(byKind(updates))
+ prereqs := make(map[string]*state.TaskSet)
+ waitPrereq := func(ts *state.TaskSet, prereqName string) {
+ preTs := prereqs[prereqName]
+ if preTs != nil {
+ ts.WaitAll(preTs)
+ }
+ }
+
+ // updates is sorted by kind so this will process first core
+ // and bases and then other snaps
for _, update := range updates {
channel, flags, snapst := params(update)
@@ -782,6 +799,24 @@
}
ts.JoinLane(st.NewLane())
+ // because of the sorting of updates we fill prereqs
+ // first (if branch) and only then use it to setup
+ // waits (else branch)
+ if update.Type == snap.TypeOS || update.Type == snap.TypeBase {
+ // prereq types come first in updates, we
+ // also assume bases don't have hooks, otherwise
+ // they would need to wait on core
+ prereqs[update.Name()] = ts
+ } else {
+ // prereqs were processed already, wait for
+ // them as necessary for the other kind of
+ // snaps
+ waitPrereq(ts, defaultCoreSnapName)
+ if update.Base != "" {
+ waitPrereq(ts, update.Base)
+ }
+ }
+
scheduleUpdate(update.Name(), ts)
tasksets = append(tasksets, ts)
}
@@ -802,6 +837,22 @@
return updated, tasksets, nil
}
+type byKind []*snap.Info
+
+func (bk byKind) Len() int { return len(bk) }
+func (bk byKind) Swap(i, j int) { bk[i], bk[j] = bk[j], bk[i] }
+
+var kindRevOrder = map[snap.Type]int{
+ snap.TypeOS: 2,
+ snap.TypeBase: 1,
+}
+
+func (bk byKind) Less(i, j int) bool {
+ iRevOrd := kindRevOrder[bk[i].Type]
+ jRevOrd := kindRevOrder[bk[j].Type]
+ return iRevOrd >= jRevOrd
+}
+
func applyAutoAliasesDelta(st *state.State, delta map[string][]string, op string, refreshAll bool, linkTs func(snapName string, ts *state.TaskSet)) (*state.TaskSet, error) {
applyTs := state.NewTaskSet()
kind := "refresh-aliases"
@@ -1077,11 +1128,11 @@
}
if sideInfo == nil {
// refresh from given revision from store
- return updateToRevisionInfo(st, snapst, channel, revision, userID)
+ return updateToRevisionInfo(st, snapst, revision, userID)
}
- // refresh-to-local
- return readInfo(name, sideInfo)
+ // refresh-to-local, this assumes the snap revision is mounted
+ return readInfo(name, sideInfo, errorOnBroken)
}
// AutoRefreshAssertions allows to hook fetching of important assertions
@@ -1514,12 +1565,6 @@
return nil, fmt.Errorf("cannot transition snap %q: not installed", oldName)
}
- var userID int
- newInfo, err := snapInfo(st, newName, oldSnapst.Channel, snap.R(0), userID)
- if err != nil {
- return nil, err
- }
-
var all []*state.TaskSet
// install new core (if not already installed)
err = Get(st, newName, &newSnapst)
@@ -1527,6 +1572,12 @@
return nil, err
}
if !newSnapst.IsInstalled() {
+ var userID int
+ newInfo, err := installInfo(st, newName, oldSnapst.Channel, snap.R(0), userID)
+ if err != nil {
+ return nil, err
+ }
+
// start by instaling the new snap
tsInst, err := doInstall(st, &newSnapst, &SnapSetup{
Channel: oldSnapst.Channel,
@@ -1595,7 +1646,7 @@
for i := len(snapst.Sequence) - 1; i >= 0; i-- {
if si := snapst.Sequence[i]; si.Revision == revision {
- return readInfo(name, si)
+ return readInfo(name, si, 0)
}
}
diff -Nru snapd-2.32.3.2/overlord/snapstate/snapstate_test.go snapd-2.32.9/overlord/snapstate/snapstate_test.go
--- snapd-2.32.3.2/overlord/snapstate/snapstate_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/snapstate/snapstate_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2016 Canonical Ltd
+ * Copyright (C) 2016-2018 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -78,6 +78,8 @@
var _ = Suite(&snapmgrTestSuite{})
+var fakeRevDateEpoch = time.Date(2018, 1, 0, 0, 0, 0, 0, time.UTC)
+
func (s *snapmgrTestSuite) SetUpTest(c *C) {
s.BaseTest.SetUpTest(c)
dirs.SetRootDir(c.MkDir())
@@ -114,8 +116,16 @@
s.o.AddManager(s.snapmgr)
- s.BaseTest.AddCleanup(snapstate.MockReadInfo(s.fakeBackend.ReadInfo))
+ s.BaseTest.AddCleanup(snapstate.MockSnapReadInfo(s.fakeBackend.ReadInfo))
s.BaseTest.AddCleanup(snapstate.MockOpenSnapFile(s.fakeBackend.OpenSnapFile))
+ revDate := func(info *snap.Info) time.Time {
+ if info.Revision.Local() {
+ panic("no local revision should reach revisionDate")
+ }
+ // for convenience a date derived from the revision
+ return fakeRevDateEpoch.AddDate(0, 0, info.Revision.N)
+ }
+ s.BaseTest.AddCleanup(snapstate.MockRevisionDate(revDate))
s.BaseTest.AddCleanup(func() {
snapstate.SetupInstallHook = oldSetupInstallHook
@@ -657,6 +667,13 @@
c.Assert(err, ErrorMatches, `cannot update disabled snap "some-snap"`)
}
+func (s snapmgrTestSuite) TestInstallFailsOnSystem(c *C) {
+ snapsup := &snapstate.SnapSetup{SideInfo: &snap.SideInfo{RealName: "system", SnapID: "some-snap-id", Revision: snap.R(1)}}
+ _, err := snapstate.DoInstall(s.state, nil, snapsup, 0)
+ c.Assert(err, NotNil)
+ c.Assert(err, ErrorMatches, `cannot install reserved snap name 'system'`)
+}
+
func (s *snapmgrTestSuite) TestUpdateCreatesDiscardAfterCurrentTasks(c *C) {
s.state.Lock()
defer s.state.Unlock()
@@ -812,6 +829,85 @@
c.Check(updates, HasLen, 0)
}
+func (s *snapmgrTestSuite) TestByKindOrder(c *C) {
+ core := &snap.Info{Type: snap.TypeOS}
+ base := &snap.Info{Type: snap.TypeBase}
+ app := &snap.Info{Type: snap.TypeApp}
+
+ c.Check(snapstate.ByKindOrder(base, core), DeepEquals, []*snap.Info{core, base})
+ c.Check(snapstate.ByKindOrder(app, core), DeepEquals, []*snap.Info{core, app})
+ c.Check(snapstate.ByKindOrder(app, base), DeepEquals, []*snap.Info{base, app})
+ c.Check(snapstate.ByKindOrder(app, base, core), DeepEquals, []*snap.Info{core, base, app})
+ c.Check(snapstate.ByKindOrder(app, core, base), DeepEquals, []*snap.Info{core, base, app})
+}
+
+func (s *snapmgrTestSuite) TestUpdateManyWaitForBases(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "core", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{
+ {RealName: "core", SnapID: "core-snap-id", Revision: snap.R(1)},
+ },
+ Current: snap.R(1),
+ SnapType: "os",
+ })
+
+ snapstate.Set(s.state, "some-base", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{
+ {RealName: "some-base", SnapID: "some-base-id", Revision: snap.R(1)},
+ },
+ Current: snap.R(1),
+ SnapType: "base",
+ })
+
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{
+ {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)},
+ },
+ Current: snap.R(1),
+ SnapType: "app",
+ Channel: "channel-for-base",
+ })
+
+ updates, tts, err := snapstate.UpdateMany(context.TODO(), s.state, []string{"some-snap", "core", "some-base"}, 0)
+ c.Assert(err, IsNil)
+ c.Assert(tts, HasLen, 3)
+ c.Check(updates, HasLen, 3)
+
+ // to make TaskSnapSetup work
+ chg := s.state.NewChange("refresh", "...")
+ for _, ts := range tts {
+ chg.AddAll(ts)
+ }
+
+ prereqTotal := len(tts[0].Tasks()) + len(tts[1].Tasks())
+ prereqs := map[string]bool{}
+ for i, task := range tts[2].Tasks() {
+ waitTasks := task.WaitTasks()
+ if i == 0 {
+ c.Check(len(waitTasks), Equals, prereqTotal)
+ } else if task.Kind() == "link-snap" {
+ c.Check(len(waitTasks), Equals, prereqTotal+1)
+ for _, pre := range waitTasks {
+ if pre.Kind() == "link-snap" {
+ snapsup, err := snapstate.TaskSnapSetup(pre)
+ c.Assert(err, IsNil)
+ prereqs[snapsup.Name()] = true
+ }
+ }
+ }
+ }
+
+ c.Check(prereqs, DeepEquals, map[string]bool{
+ "core": true,
+ "some-base": true,
+ })
+}
+
func (s *snapmgrTestSuite) TestUpdateManyValidateRefreshes(c *C) {
s.state.Lock()
defer s.state.Unlock()
@@ -1127,7 +1223,7 @@
state *state.State
}
-func (s sneakyStore) SnapInfo(spec store.SnapSpec, user *auth.UserState) (*snap.Info, error) {
+func (s sneakyStore) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, user *auth.UserState, opts *store.RefreshOptions) ([]*snap.Info, error) {
s.state.Lock()
snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
Active: true,
@@ -1137,7 +1233,7 @@
SnapType: "app",
})
s.state.Unlock()
- return s.fakeStore.SnapInfo(spec, user)
+ return s.fakeStore.SnapAction(ctx, currentSnaps, actions, user, opts)
}
func (s *snapmgrTestSuite) TestInstallStateConflict(c *C) {
@@ -1567,6 +1663,163 @@
defer s.state.Unlock()
chg := s.state.NewChange("install", "install a snap")
+ ts, err := snapstate.Install(s.state, "some-snap", "some-channel", snap.R(0), s.user.ID, snapstate.Flags{})
+ c.Assert(err, IsNil)
+ chg.AddAll(ts)
+
+ s.state.Unlock()
+ defer s.snapmgr.Stop()
+ s.settle(c)
+ s.state.Lock()
+
+ // ensure all our tasks ran
+ c.Assert(chg.Err(), IsNil)
+ c.Assert(chg.IsReady(), Equals, true)
+ c.Check(snapstate.Installing(s.state), Equals, false)
+ c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{{
+ macaroon: s.user.StoreMacaroon,
+ name: "some-snap",
+ }})
+ expected := fakeOps{
+ {
+ op: "storesvc-snap-action",
+ userID: 1,
+ },
+ {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "install",
+ Name: "some-snap",
+ Channel: "some-channel",
+ },
+ revno: snap.R(11),
+ userID: 1,
+ },
+ {
+ op: "storesvc-download",
+ name: "some-snap",
+ },
+ {
+ op: "validate-snap:Doing",
+ name: "some-snap",
+ revno: snap.R(11),
+ },
+ {
+ op: "current",
+ old: "",
+ },
+ {
+ op: "open-snap-file",
+ name: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"),
+ sinfo: snap.SideInfo{
+ RealName: "some-snap",
+ SnapID: "some-snap-id",
+ Channel: "some-channel",
+ Revision: snap.R(11),
+ },
+ },
+ {
+ op: "setup-snap",
+ name: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"),
+ revno: snap.R(11),
+ },
+ {
+ op: "copy-data",
+ name: filepath.Join(dirs.SnapMountDir, "some-snap/11"),
+ old: "",
+ },
+ {
+ op: "setup-profiles:Doing",
+ name: "some-snap",
+ revno: snap.R(11),
+ },
+ {
+ op: "candidate",
+ sinfo: snap.SideInfo{
+ RealName: "some-snap",
+ SnapID: "some-snap-id",
+ Channel: "some-channel",
+ Revision: snap.R(11),
+ },
+ },
+ {
+ op: "link-snap",
+ name: filepath.Join(dirs.SnapMountDir, "some-snap/11"),
+ },
+ {
+ op: "auto-connect:Doing",
+ name: "some-snap",
+ revno: snap.R(11),
+ },
+ {
+ op: "update-aliases",
+ },
+ {
+ op: "cleanup-trash",
+ name: "some-snap",
+ revno: snap.R(11),
+ },
+ }
+ // start with an easier-to-read error if this fails:
+ c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
+ c.Assert(s.fakeBackend.ops, DeepEquals, expected)
+
+ // check progress
+ ta := ts.Tasks()
+ task := ta[1]
+ _, cur, total := task.Progress()
+ c.Assert(cur, Equals, s.fakeStore.fakeCurrentProgress)
+ c.Assert(total, Equals, s.fakeStore.fakeTotalProgress)
+ c.Check(task.Summary(), Equals, `Download snap "some-snap" (11) from channel "some-channel"`)
+
+ // check link/start snap summary
+ linkTask := ta[len(ta)-7]
+ c.Check(linkTask.Summary(), Equals, `Make snap "some-snap" (11) available to the system`)
+ startTask := ta[len(ta)-2]
+ c.Check(startTask.Summary(), Equals, `Start snap "some-snap" (11) services`)
+
+ // verify snap-setup in the task state
+ var snapsup snapstate.SnapSetup
+ err = task.Get("snap-setup", &snapsup)
+ c.Assert(err, IsNil)
+ c.Assert(snapsup, DeepEquals, snapstate.SnapSetup{
+ Channel: "some-channel",
+ UserID: s.user.ID,
+ SnapPath: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"),
+ DownloadInfo: &snap.DownloadInfo{
+ DownloadURL: "https://some-server.com/some/path.snap",
+ },
+ SideInfo: snapsup.SideInfo,
+ })
+ c.Assert(snapsup.SideInfo, DeepEquals, &snap.SideInfo{
+ RealName: "some-snap",
+ Channel: "some-channel",
+ Revision: snap.R(11),
+ SnapID: "some-snap-id",
+ })
+
+ // verify snaps in the system state
+ var snaps map[string]*snapstate.SnapState
+ err = s.state.Get("snaps", &snaps)
+ c.Assert(err, IsNil)
+
+ snapst := snaps["some-snap"]
+ c.Assert(snapst.Active, Equals, true)
+ c.Assert(snapst.Channel, Equals, "some-channel")
+ c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{
+ RealName: "some-snap",
+ SnapID: "some-snap-id",
+ Channel: "some-channel",
+ Revision: snap.R(11),
+ })
+ c.Assert(snapst.Required, Equals, false)
+}
+
+func (s *snapmgrTestSuite) TestInstallWithRevisionRunThrough(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ chg := s.state.NewChange("install", "install a snap")
ts, err := snapstate.Install(s.state, "some-snap", "some-channel", snap.R(42), s.user.ID, snapstate.Flags{})
c.Assert(err, IsNil)
chg.AddAll(ts)
@@ -1586,8 +1839,16 @@
}})
expected := fakeOps{
{
- op: "storesvc-snap",
- name: "some-snap",
+ op: "storesvc-snap-action",
+ userID: 1,
+ },
+ {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "install",
+ Name: "some-snap",
+ Revision: snap.R(42),
+ },
revno: snap.R(42),
userID: 1,
},
@@ -1609,7 +1870,6 @@
name: filepath.Join(dirs.SnapBlobDir, "some-snap_42.snap"),
sinfo: snap.SideInfo{
RealName: "some-snap",
- Channel: "some-channel",
SnapID: "some-snap-id",
Revision: snap.R(42),
},
@@ -1633,7 +1893,6 @@
op: "candidate",
sinfo: snap.SideInfo{
RealName: "some-snap",
- Channel: "some-channel",
SnapID: "some-snap-id",
Revision: snap.R(42),
},
@@ -1690,7 +1949,6 @@
c.Assert(snapsup.SideInfo, DeepEquals, &snap.SideInfo{
RealName: "some-snap",
Revision: snap.R(42),
- Channel: "some-channel",
SnapID: "some-snap-id",
})
@@ -1704,7 +1962,6 @@
c.Assert(snapst.Channel, Equals, "some-channel")
c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{
RealName: "some-snap",
- Channel: "some-channel",
SnapID: "some-snap-id",
Revision: snap.R(42),
})
@@ -1732,6 +1989,13 @@
Revision: snap.R(7),
SnapID: "services-snap-id",
}
+ snaptest.MockSnap(c, `name: services-snap`, &si)
+ fi, err := os.Stat(snap.MountFile("services-snap", si.Revision))
+ c.Assert(err, IsNil)
+ refreshedDate := fi.ModTime()
+ // look at disk
+ r := snapstate.MockRevisionDate(nil)
+ defer r()
s.state.Lock()
defer s.state.Unlock()
@@ -1741,6 +2005,7 @@
Sequence: []*snap.SideInfo{&si},
Current: si.Revision,
SnapType: "app",
+ Channel: "stable",
})
chg := s.state.NewChange("refresh", "refresh a snap")
@@ -1755,11 +2020,23 @@
expected := fakeOps{
{
- op: "storesvc-list-refresh",
- cand: store.RefreshCandidate{
- Channel: "some-channel",
- SnapID: "services-snap-id",
- Revision: snap.R(7),
+ op: "storesvc-snap-action",
+ curSnaps: []store.CurrentSnap{{
+ Name: "services-snap",
+ SnapID: "services-snap-id",
+ Revision: snap.R(7),
+ TrackingChannel: "stable",
+ RefreshedDate: refreshedDate,
+ }},
+ userID: 1,
+ },
+ {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "refresh",
+ SnapID: "services-snap-id",
+ Channel: "some-channel",
+ Flags: store.SnapActionEnforceValidation,
},
revno: snap.R(11),
userID: 1,
@@ -1940,7 +2217,7 @@
for _, op := range s.fakeBackend.ops {
switch op.op {
- case "storesvc-list-refresh":
+ case "storesvc-snap-action":
c.Check(op.userID, Equals, 1)
case "storesvc-download":
snapName := op.name
@@ -1981,7 +2258,7 @@
for _, op := range s.fakeBackend.ops {
switch op.op {
- case "storesvc-snap":
+ case "storesvc-snap-action:action":
c.Check(op.userID, Equals, 1)
case "storesvc-download":
snapName := op.name
@@ -2052,11 +2329,19 @@
userID int
}
seen := make(map[snapIDuserID]bool)
+ ir := 0
di := 0
for _, op := range s.fakeBackend.ops {
switch op.op {
- case "storesvc-list-refresh":
- snapID := op.cand.SnapID
+ case "storesvc-snap-action":
+ ir++
+ c.Check(op.curSnaps, DeepEquals, []store.CurrentSnap{
+ {Name: "core", SnapID: "core-snap-id", Revision: snap.R(1), RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 1)},
+ {Name: "services-snap", SnapID: "services-snap-id", Revision: snap.R(2), RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 2)},
+ {Name: "some-snap", SnapID: "some-snap-id", Revision: snap.R(5), RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 5)},
+ })
+ case "storesvc-snap-action:action":
+ snapID := op.action.SnapID
seen[snapIDuserID{snapID: snapID, userID: op.userID}] = true
case "storesvc-download":
snapName := op.name
@@ -2067,13 +2352,11 @@
di++
}
}
+ c.Check(ir, Equals, 3)
// we check all snaps with each user
c.Check(seen, DeepEquals, map[snapIDuserID]bool{
- {snapID: "core-snap-id", userID: 1}: true,
+ {snapID: "core-snap-id", userID: 0}: true,
{snapID: "some-snap-id", userID: 1}: true,
- {snapID: "services-snap-id", userID: 1}: true,
- {snapID: "core-snap-id", userID: 2}: true,
- {snapID: "some-snap-id", userID: 2}: true,
{snapID: "services-snap-id", userID: 2}: true,
})
}
@@ -2137,11 +2420,19 @@
userID int
}
seen := make(map[snapIDuserID]bool)
+ ir := 0
di := 0
for _, op := range s.fakeBackend.ops {
switch op.op {
- case "storesvc-list-refresh":
- snapID := op.cand.SnapID
+ case "storesvc-snap-action":
+ ir++
+ c.Check(op.curSnaps, DeepEquals, []store.CurrentSnap{
+ {Name: "core", SnapID: "core-snap-id", Revision: snap.R(1), RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 1)},
+ {Name: "services-snap", SnapID: "services-snap-id", Revision: snap.R(2), RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 2)},
+ {Name: "some-snap", SnapID: "some-snap-id", Revision: snap.R(5), RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 5)},
+ })
+ case "storesvc-snap-action:action":
+ snapID := op.action.SnapID
seen[snapIDuserID{snapID: snapID, userID: op.userID}] = true
case "storesvc-download":
snapName := op.name
@@ -2152,13 +2443,11 @@
di++
}
}
+ c.Check(ir, Equals, 2)
// we check all snaps with each user
c.Check(seen, DeepEquals, map[snapIDuserID]bool{
- {snapID: "core-snap-id", userID: 1}: true,
- {snapID: "some-snap-id", userID: 1}: true,
- {snapID: "services-snap-id", userID: 1}: true,
{snapID: "core-snap-id", userID: 2}: true,
- {snapID: "some-snap-id", userID: 2}: true,
+ {snapID: "some-snap-id", userID: 1}: true,
{snapID: "services-snap-id", userID: 2}: true,
})
@@ -2208,11 +2497,22 @@
expected := fakeOps{
{
- op: "storesvc-list-refresh",
- cand: store.RefreshCandidate{
- Channel: "some-channel",
- SnapID: "some-snap-id",
- Revision: snap.R(7),
+ op: "storesvc-snap-action",
+ curSnaps: []store.CurrentSnap{{
+ Name: "some-snap",
+ SnapID: "some-snap-id",
+ Revision: snap.R(7),
+ RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 7),
+ }},
+ userID: 1,
+ },
+ {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "refresh",
+ SnapID: "some-snap-id",
+ Channel: "some-channel",
+ Flags: store.SnapActionEnforceValidation,
},
revno: snap.R(11),
userID: 1,
@@ -2368,11 +2668,23 @@
expected := fakeOps{
{
- op: "storesvc-list-refresh",
- cand: store.RefreshCandidate{
- Channel: "some-channel",
- SnapID: "some-snap-id",
- Revision: snap.R(7),
+ op: "storesvc-snap-action",
+ curSnaps: []store.CurrentSnap{{
+ Name: "some-snap",
+ SnapID: "some-snap-id",
+ Revision: snap.R(7),
+ TrackingChannel: "stable",
+ RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 7),
+ }},
+ userID: 1,
+ },
+ {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "refresh",
+ SnapID: "some-snap-id",
+ Channel: "some-channel",
+ Flags: store.SnapActionEnforceValidation,
},
revno: snap.R(11),
userID: 1,
@@ -2523,6 +2835,41 @@
c.Assert(err, Equals, store.ErrNoUpdateAvailable)
}
+// A noResultsStore returns no results for install/refresh requests
+type noResultsStore struct {
+ *fakeStore
+}
+
+func (n noResultsStore) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, user *auth.UserState, opts *store.RefreshOptions) ([]*snap.Info, error) {
+ return nil, &store.SnapActionError{NoResults: true}
+}
+
+func (s *snapmgrTestSuite) TestUpdateNoStoreResults(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.ReplaceStore(s.state, noResultsStore{fakeStore: s.fakeStore})
+
+ // this is an atypical case in which the store didn't return
+ // an error nor a result, we are defensive and return
+ // a reasonable error
+ si := snap.SideInfo{
+ RealName: "some-snap",
+ SnapID: "some-snap-id",
+ Revision: snap.R(7),
+ }
+
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{&si},
+ Channel: "channel-for-7",
+ Current: si.Revision,
+ })
+
+ _, err := snapstate.Update(s.state, "some-snap", "channel-for-7", snap.R(0), s.user.ID, snapstate.Flags{})
+ c.Assert(err, Equals, store.ErrNoUpdateAvailable)
+}
+
func (s *snapmgrTestSuite) TestUpdateSameRevisionSwitchesChannel(c *C) {
si := snap.SideInfo{
RealName: "some-snap",
@@ -2601,15 +2948,28 @@
s.state.Lock()
expected := fakeOps{
- // we just expect the "storesvc-list-refresh" op, we
+ // we just expect the "storesvc-snap-action" ops, we
// don't have a fakeOp for switchChannel because it has
// not a backend method, it just manipulates the state
{
- op: "storesvc-list-refresh",
- cand: store.RefreshCandidate{
- Channel: "channel-for-7",
- SnapID: "some-snap-id",
- Revision: snap.R(7),
+ op: "storesvc-snap-action",
+ curSnaps: []store.CurrentSnap{{
+ Name: "some-snap",
+ SnapID: "some-snap-id",
+ Revision: snap.R(7),
+ TrackingChannel: "other-channel",
+ RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 7),
+ }},
+ userID: 1,
+ },
+
+ {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "refresh",
+ SnapID: "some-snap-id",
+ Channel: "channel-for-7",
+ Flags: store.SnapActionEnforceValidation,
},
userID: 1,
},
@@ -2864,13 +3224,24 @@
c.Assert(err, IsNil)
c.Check(s.fakeBackend.ops[0], DeepEquals, fakeOp{
- op: "storesvc-list-refresh",
- revno: snap.R(11),
- cand: store.RefreshCandidate{
+ op: "storesvc-snap-action",
+ curSnaps: []store.CurrentSnap{{
+ Name: "some-snap",
SnapID: "some-snap-id",
Revision: snap.R(7),
- Channel: "stable",
- IgnoreValidation: true,
+ IgnoreValidation: false,
+ RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 7),
+ }},
+ userID: 1,
+ })
+ c.Check(s.fakeBackend.ops[1], DeepEquals, fakeOp{
+ op: "storesvc-snap-action:action",
+ revno: snap.R(11),
+ action: store.SnapAction{
+ Action: "refresh",
+ SnapID: "some-snap-id",
+ Channel: "stable",
+ Flags: store.SnapActionIgnoreValidation,
},
userID: 1,
})
@@ -2899,13 +3270,24 @@
c.Check(tts, HasLen, 1)
c.Check(s.fakeBackend.ops[0], DeepEquals, fakeOp{
- op: "storesvc-list-refresh",
- revno: snap.R(12),
- cand: store.RefreshCandidate{
+ op: "storesvc-snap-action",
+ curSnaps: []store.CurrentSnap{{
+ Name: "some-snap",
SnapID: "some-snap-id",
Revision: snap.R(11),
- Channel: "stable",
+ TrackingChannel: "stable",
IgnoreValidation: true,
+ RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 11),
+ }},
+ userID: 1,
+ })
+ c.Check(s.fakeBackend.ops[1], DeepEquals, fakeOp{
+ op: "storesvc-snap-action:action",
+ revno: snap.R(12),
+ action: store.SnapAction{
+ Action: "refresh",
+ SnapID: "some-snap-id",
+ Flags: 0,
},
userID: 1,
})
@@ -2939,13 +3321,25 @@
c.Assert(err, IsNil)
c.Check(s.fakeBackend.ops[0], DeepEquals, fakeOp{
- op: "storesvc-list-refresh",
- revno: snap.R(11),
- cand: store.RefreshCandidate{
+ op: "storesvc-snap-action",
+ curSnaps: []store.CurrentSnap{{
+ Name: "some-snap",
SnapID: "some-snap-id",
Revision: snap.R(12),
- Channel: "stable",
- IgnoreValidation: false,
+ TrackingChannel: "stable",
+ IgnoreValidation: true,
+ RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 12),
+ }},
+ userID: 1,
+ })
+ c.Check(s.fakeBackend.ops[1], DeepEquals, fakeOp{
+ op: "storesvc-snap-action:action",
+ revno: snap.R(11),
+ action: store.SnapAction{
+ Action: "refresh",
+ SnapID: "some-snap-id",
+ Channel: "stable",
+ Flags: store.SnapActionEnforceValidation,
},
userID: 1,
})
@@ -3012,7 +3406,26 @@
err = tasks[1].Get("snap-setup", &snapsup)
c.Assert(err, IsNil)
c.Check(snapsup.Revision(), Equals, snap.R(7))
+}
+func (s *snapmgrTestSuite) TestUpdateAmendSnapNotFound(c *C) {
+ si := snap.SideInfo{
+ RealName: "snap-unknown",
+ Revision: snap.R("x1"),
+ }
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "snap-unknown", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{&si},
+ Channel: "stable",
+ Current: si.Revision,
+ })
+
+ _, err := snapstate.Update(s.state, "snap-unknown", "stable", snap.R(0), s.user.ID, snapstate.Flags{Amend: true})
+ c.Assert(err, Equals, store.ErrSnapNotFound)
}
func (s *snapmgrTestSuite) TestSingleUpdateBlockedRevision(c *C) {
@@ -3041,18 +3454,17 @@
_, err := snapstate.Update(s.state, "some-snap", "some-channel", snap.R(0), s.user.ID, snapstate.Flags{})
c.Assert(err, IsNil)
- c.Assert(s.fakeBackend.ops, HasLen, 1)
+ c.Assert(s.fakeBackend.ops, HasLen, 2)
c.Check(s.fakeBackend.ops[0], DeepEquals, fakeOp{
- op: "storesvc-list-refresh",
- revno: snap.R(11),
- cand: store.RefreshCandidate{
- SnapID: "some-snap-id",
- Revision: snap.R(7),
- Channel: "some-channel",
- },
+ op: "storesvc-snap-action",
+ curSnaps: []store.CurrentSnap{{
+ Name: "some-snap",
+ SnapID: "some-snap-id",
+ Revision: snap.R(7),
+ RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 7),
+ }},
userID: 1,
})
-
}
func (s *snapmgrTestSuite) TestMultiUpdateBlockedRevision(c *C) {
@@ -3082,17 +3494,17 @@
c.Assert(err, IsNil)
c.Check(updates, DeepEquals, []string{"some-snap"})
- c.Assert(s.fakeBackend.ops, HasLen, 1)
+ c.Assert(s.fakeBackend.ops, HasLen, 2)
c.Check(s.fakeBackend.ops[0], DeepEquals, fakeOp{
- op: "storesvc-list-refresh",
- revno: snap.R(11),
- cand: store.RefreshCandidate{
- SnapID: "some-snap-id",
- Revision: snap.R(7),
- },
+ op: "storesvc-snap-action",
+ curSnaps: []store.CurrentSnap{{
+ Name: "some-snap",
+ SnapID: "some-snap-id",
+ Revision: snap.R(7),
+ RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 7),
+ }},
userID: 1,
})
-
}
func (s *snapmgrTestSuite) TestAllUpdateBlockedRevision(c *C) {
@@ -3121,17 +3533,18 @@
c.Check(err, IsNil)
c.Check(updates, HasLen, 0)
- c.Assert(s.fakeBackend.ops, HasLen, 1)
+ c.Assert(s.fakeBackend.ops, HasLen, 2)
c.Check(s.fakeBackend.ops[0], DeepEquals, fakeOp{
- op: "storesvc-list-refresh",
- cand: store.RefreshCandidate{
- SnapID: "some-snap-id",
- Revision: snap.R(7),
- Block: []snap.Revision{snap.R(11)},
- },
+ op: "storesvc-snap-action",
+ curSnaps: []store.CurrentSnap{{
+ Name: "some-snap",
+ SnapID: "some-snap-id",
+ Revision: snap.R(7),
+ RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 7),
+ Block: []snap.Revision{snap.R(11)},
+ }},
userID: 1,
})
-
}
var orthogonalAutoAliasesScenarios = []struct {
@@ -5351,8 +5764,16 @@
expected := fakeOps{
{
- op: "storesvc-snap",
- name: "some-snap",
+ op: "storesvc-snap-action",
+ userID: 1,
+ },
+ {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "install",
+ Name: "some-snap",
+ Channel: "some-channel",
+ },
revno: snap.R(11),
userID: 1,
},
@@ -7138,7 +7559,7 @@
defer r()
// using MockSnap, we want to read the bits on disk
- snapstate.MockReadInfo(snap.ReadInfo)
+ snapstate.MockSnapReadInfo(snap.ReadInfo)
s.state.Lock()
defer s.state.Unlock()
@@ -7230,7 +7651,7 @@
makeInstalledMockCoreSnap(c)
// using MockSnap, we want to read the bits on disk
- snapstate.MockReadInfo(snap.ReadInfo)
+ snapstate.MockSnapReadInfo(snap.ReadInfo)
s.state.Lock()
defer s.state.Unlock()
@@ -7260,7 +7681,7 @@
makeInstalledMockCoreSnap(c)
// using MockSnap, we want to read the bits on disk
- snapstate.MockReadInfo(snap.ReadInfo)
+ snapstate.MockSnapReadInfo(snap.ReadInfo)
s.state.Lock()
defer s.state.Unlock()
@@ -7283,7 +7704,7 @@
makeInstalledMockCoreSnap(c)
// using MockSnap, we want to read the bits on disk
- snapstate.MockReadInfo(snap.ReadInfo)
+ snapstate.MockSnapReadInfo(snap.ReadInfo)
s.state.Lock()
defer s.state.Unlock()
@@ -7402,6 +7823,7 @@
Sequence: []*snap.SideInfo{{RealName: "ubuntu-core", SnapID: "ubuntu-core-snap-id", Revision: snap.R(1)}},
Current: snap.R(1),
SnapType: "os",
+ Channel: "beta",
})
chg := s.state.NewChange("transition-ubuntu-core", "...")
@@ -7426,8 +7848,18 @@
}})
expected := fakeOps{
{
- op: "storesvc-snap",
- name: "core",
+ op: "storesvc-snap-action",
+ curSnaps: []store.CurrentSnap{
+ {Name: "ubuntu-core", SnapID: "ubuntu-core-snap-id", Revision: snap.R(1), TrackingChannel: "beta", RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 1)},
+ },
+ },
+ {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "install",
+ Name: "core",
+ Channel: "beta",
+ },
revno: snap.R(11),
},
{
@@ -7449,6 +7881,7 @@
sinfo: snap.SideInfo{
RealName: "core",
SnapID: "core-id",
+ Channel: "beta",
Revision: snap.R(11),
},
},
@@ -7472,6 +7905,7 @@
sinfo: snap.SideInfo{
RealName: "core",
SnapID: "core-id",
+ Channel: "beta",
Revision: snap.R(11),
},
},
@@ -7540,6 +7974,7 @@
c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
c.Assert(s.fakeBackend.ops, DeepEquals, expected)
}
+
func (s *snapmgrTestSuite) TestTransitionCoreRunThroughWithCore(c *C) {
s.state.Lock()
defer s.state.Unlock()
@@ -7549,12 +7984,14 @@
Sequence: []*snap.SideInfo{{RealName: "ubuntu-core", SnapID: "ubuntu-core-snap-id", Revision: snap.R(1)}},
Current: snap.R(1),
SnapType: "os",
+ Channel: "stable",
})
snapstate.Set(s.state, "core", &snapstate.SnapState{
Active: true,
Sequence: []*snap.SideInfo{{RealName: "core", SnapID: "core-snap-id", Revision: snap.R(1)}},
Current: snap.R(1),
SnapType: "os",
+ Channel: "stable",
})
chg := s.state.NewChange("transition-ubuntu-core", "...")
@@ -7575,11 +8012,6 @@
c.Check(s.fakeStore.downloads, HasLen, 0)
expected := fakeOps{
{
- op: "storesvc-snap",
- name: "core",
- revno: snap.R(11),
- },
- {
op: "transition-ubuntu-core:Doing",
name: "ubuntu-core",
},
@@ -7621,7 +8053,6 @@
// start with an easier-to-read error if this fails:
c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
c.Assert(s.fakeBackend.ops, DeepEquals, expected)
-
}
func (s *snapmgrTestSuite) TestTransitionCoreStartsAutomatically(c *C) {
@@ -8053,16 +8484,32 @@
expected := fakeOps{
// we check the snap
{
- op: "storesvc-snap",
- name: "some-snap",
+ op: "storesvc-snap-action",
+ userID: 1,
+ },
+ {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "install",
+ Name: "some-snap",
+ Revision: snap.R(42),
+ },
revno: snap.R(42),
userID: 1,
},
// then we check core because its not installed already
// and continue with that
{
- op: "storesvc-snap",
- name: "core",
+ op: "storesvc-snap-action",
+ userID: 1,
+ },
+ {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "install",
+ Name: "core",
+ Channel: "stable",
+ },
revno: snap.R(11),
userID: 1,
},
@@ -8144,7 +8591,6 @@
name: filepath.Join(dirs.SnapBlobDir, "some-snap_42.snap"),
sinfo: snap.SideInfo{
RealName: "some-snap",
- Channel: "some-channel",
SnapID: "some-snap-id",
Revision: snap.R(42),
},
@@ -8168,7 +8614,6 @@
op: "candidate",
sinfo: snap.SideInfo{
RealName: "some-snap",
- Channel: "some-channel",
SnapID: "some-snap-id",
Revision: snap.R(42),
},
@@ -8320,7 +8765,6 @@
// ensure we have both core and snap2
var snapst snapstate.SnapState
-
err = snapstate.Get(s.state, "core", &snapst)
c.Assert(err, IsNil)
c.Assert(snapst.Active, Equals, true)
@@ -8332,14 +8776,15 @@
Revision: snap.R(11),
})
- err = snapstate.Get(s.state, "snap2", &snapst)
+ var snapst2 snapstate.SnapState
+ err = snapstate.Get(s.state, "snap2", &snapst2)
c.Assert(err, IsNil)
- c.Assert(snapst.Active, Equals, true)
- c.Assert(snapst.Sequence, HasLen, 1)
- c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{
+ c.Assert(snapst2.Active, Equals, true)
+ c.Assert(snapst2.Sequence, HasLen, 1)
+ c.Assert(snapst2.Sequence[0], DeepEquals, &snap.SideInfo{
RealName: "snap2",
SnapID: "snap2-id",
- Channel: "some-other-channel",
+ Channel: "",
Revision: snap.R(21),
})
@@ -8355,8 +8800,8 @@
chg *state.Change
}
-func (s behindYourBackStore) SnapInfo(spec store.SnapSpec, user *auth.UserState) (*snap.Info, error) {
- if spec.Name == "core" {
+func (s behindYourBackStore) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, user *auth.UserState, opts *store.RefreshOptions) ([]*snap.Info, error) {
+ if len(actions) == 1 && actions[0].Action == "install" && actions[0].Name == "core" {
s.state.Lock()
if !s.coreInstallRequested {
s.coreInstallRequested = true
@@ -8390,7 +8835,7 @@
s.state.Unlock()
}
- return s.fakeStore.SnapInfo(spec, user)
+ return s.fakeStore.SnapAction(ctx, currentSnaps, actions, user, opts)
}
// this test the scenario that some-snap gets installed and during the
@@ -8412,7 +8857,7 @@
// now install a snap that will pull in core
chg := s.state.NewChange("install", "install a snap on a system without core")
- ts, err := snapstate.Install(s.state, "some-snap", "some-channel", snap.R(42), s.user.ID, snapstate.Flags{})
+ ts, err := snapstate.Install(s.state, "some-snap", "some-channel", snap.R(0), s.user.ID, snapstate.Flags{})
c.Assert(err, IsNil)
chg.AddAll(ts)
@@ -8469,7 +8914,7 @@
RealName: "some-snap",
SnapID: "some-snap-id",
Channel: "some-channel",
- Revision: snap.R(42),
+ Revision: snap.R(11),
})
}
@@ -8478,9 +8923,13 @@
state *state.State
}
-func (s contentStore) SnapInfo(spec store.SnapSpec, user *auth.UserState) (*snap.Info, error) {
- info, err := s.fakeStore.SnapInfo(spec, user)
- switch spec.Name {
+func (s contentStore) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, user *auth.UserState, opts *store.RefreshOptions) ([]*snap.Info, error) {
+ snaps, err := s.fakeStore.SnapAction(ctx, currentSnaps, actions, user, opts)
+ if len(snaps) != 1 {
+ panic("expected to be queried for install of only one snap at a time")
+ }
+ info := snaps[0]
+ switch info.Name() {
case "snap-content-plug":
info.Plugs = map[string]*snap.PlugInfo{
"some-plug": {
@@ -8562,7 +9011,7 @@
}
}
- return info, err
+ return []*snap.Info{info}, err
}
func (s *snapmgrTestSuite) TestInstallDefaultProviderRunThrough(c *C) {
@@ -8575,7 +9024,7 @@
ifacerepo.Replace(s.state, repo)
chg := s.state.NewChange("install", "install a snap")
- ts, err := snapstate.Install(s.state, "snap-content-plug", "some-channel", snap.R(42), s.user.ID, snapstate.Flags{})
+ ts, err := snapstate.Install(s.state, "snap-content-plug", "stable", snap.R(42), s.user.ID, snapstate.Flags{})
c.Assert(err, IsNil)
chg.AddAll(ts)
@@ -8588,13 +9037,27 @@
c.Assert(chg.Err(), IsNil)
c.Assert(chg.IsReady(), Equals, true)
expected := fakeOps{{
- op: "storesvc-snap",
- name: "snap-content-plug",
+ op: "storesvc-snap-action",
+ userID: 1,
+ }, {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "install",
+ Name: "snap-content-plug",
+ Revision: snap.R(42),
+ },
revno: snap.R(42),
userID: 1,
}, {
- op: "storesvc-snap",
- name: "snap-content-slot",
+ op: "storesvc-snap-action",
+ userID: 1,
+ }, {
+ op: "storesvc-snap-action:action",
+ action: store.SnapAction{
+ Action: "install",
+ Name: "snap-content-slot",
+ Channel: "stable",
+ },
revno: snap.R(11),
userID: 1,
}, {
@@ -8660,7 +9123,6 @@
name: filepath.Join(dirs.SnapBlobDir, "snap-content-plug_42.snap"),
sinfo: snap.SideInfo{
RealName: "snap-content-plug",
- Channel: "some-channel",
SnapID: "snap-content-plug-id",
Revision: snap.R(42),
},
@@ -8680,7 +9142,6 @@
op: "candidate",
sinfo: snap.SideInfo{
RealName: "snap-content-plug",
- Channel: "some-channel",
SnapID: "snap-content-plug-id",
Revision: snap.R(42),
},
@@ -8707,7 +9168,7 @@
// do a simple c.Check(ops, DeepEquals, fakeOps{...})
c.Check(len(s.fakeBackend.ops), Equals, len(expected))
for _, op := range expected {
- c.Check(s.fakeBackend.ops, testutil.DeepContains, op)
+ c.Assert(s.fakeBackend.ops, testutil.DeepContains, op)
}
}
diff -Nru snapd-2.32.3.2/overlord/snapstate/storehelpers.go snapd-2.32.9/overlord/snapstate/storehelpers.go
--- snapd-2.32.3.2/overlord/snapstate/storehelpers.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/overlord/snapstate/storehelpers.go 2018-05-16 08:20:08.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2016-2017 Canonical Ltd
+ * Copyright (C) 2016-2018 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -25,6 +25,7 @@
"golang.org/x/net/context"
+ "github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/overlord/auth"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/snap"
@@ -90,29 +91,39 @@
return fallbackUser, nil
}
-func snapNameToID(st *state.State, name string, user *auth.UserState) (string, error) {
- theStore := Store(st)
- st.Unlock()
- info, err := theStore.SnapInfo(store.SnapSpec{Name: name}, user)
- st.Lock()
- return info.SnapID, err
-}
+func installInfo(st *state.State, name, channel string, revision snap.Revision, userID int) (*snap.Info, error) {
+ // TODO: support ignore-validation?
+
+ curSnaps, err := currentSnaps(st)
+ if err != nil {
+ return nil, err
+ }
-func snapInfo(st *state.State, name, channel string, revision snap.Revision, userID int) (*snap.Info, error) {
user, err := userFromUserID(st, userID)
if err != nil {
return nil, err
}
- theStore := Store(st)
- st.Unlock() // calls to the store should be done without holding the state lock
- spec := store.SnapSpec{
- Name: name,
- Channel: channel,
+
+ // cannot specify both with the API
+ if !revision.Unset() {
+ channel = ""
+ }
+
+ action := &store.SnapAction{
+ Action: "install",
+ Name: name,
+ // the desired channel
+ Channel: channel,
+ // the desired revision
Revision: revision,
}
- snap, err := theStore.SnapInfo(spec, user)
+
+ theStore := Store(st)
+ st.Unlock() // calls to the store should be done without holding the state lock
+ res, err := theStore.SnapAction(context.TODO(), curSnaps, []*store.SnapAction{action}, user, nil)
st.Lock()
- return snap, err
+
+ return singleActionResult(name, action.Action, res, err)
}
func updateInfo(st *state.State, snapst *SnapState, opts *updateInfoOpts, userID int) (*snap.Info, error) {
@@ -120,26 +131,42 @@
opts = &updateInfoOpts{}
}
+ curSnaps, err := currentSnaps(st)
+ if err != nil {
+ return nil, err
+ }
+
curInfo, user, err := preUpdateInfo(st, snapst, opts.amend, userID)
if err != nil {
return nil, err
}
- refreshCand := &store.RefreshCandidate{
+ var flags store.SnapActionFlags
+ if opts.ignoreValidation {
+ flags = store.SnapActionIgnoreValidation
+ } else {
+ flags = store.SnapActionEnforceValidation
+ }
+
+ action := &store.SnapAction{
+ Action: "refresh",
+ SnapID: curInfo.SnapID,
// the desired channel
- Channel: opts.channel,
- SnapID: curInfo.SnapID,
- Revision: curInfo.Revision,
- Epoch: curInfo.Epoch,
- IgnoreValidation: opts.ignoreValidation,
- Amend: opts.amend,
+ Channel: opts.channel,
+ Flags: flags,
+ }
+
+ if curInfo.SnapID == "" { // amend
+ action.Action = "install"
+ action.Name = curInfo.Name()
}
theStore := Store(st)
st.Unlock() // calls to the store should be done without holding the state lock
- res, err := theStore.LookupRefresh(refreshCand, user)
+ res, err := theStore.SnapAction(context.TODO(), curSnaps, []*store.SnapAction{action}, user, nil)
st.Lock()
- return res, err
+
+ return singleActionResult(curInfo.Name(), action.Action, res, err)
}
func preUpdateInfo(st *state.State, snapst *SnapState, amend bool, userID int) (*snap.Info, *auth.UserState, error) {
@@ -157,39 +184,130 @@
if !amend {
return nil, nil, store.ErrLocalSnap
}
+ }
- // in amend mode we need to move to the store rev
- id, err := snapNameToID(st, curInfo.Name(), user)
- if err != nil {
- return nil, nil, fmt.Errorf("cannot get snap ID for %q: %v", curInfo.Name(), err)
+ return curInfo, user, nil
+}
+
+func singleActionResult(name, action string, results []*snap.Info, e error) (info *snap.Info, err error) {
+ if len(results) > 1 {
+ return nil, fmt.Errorf("internal error: multiple store results for a single snap op")
+ }
+ if len(results) > 0 {
+ // TODO: if we also have an error log/warn about it
+ return results[0], nil
+ }
+
+ if saErr, ok := e.(*store.SnapActionError); ok {
+ if len(saErr.Other) != 0 {
+ return nil, saErr
+ }
+
+ var snapErr error
+ switch action {
+ case "refresh":
+ snapErr = saErr.Refresh[name]
+ case "install":
+ snapErr = saErr.Install[name]
+ }
+ if snapErr != nil {
+ return nil, snapErr
+ }
+
+ // no result, atypical case
+ if saErr.NoResults {
+ switch action {
+ case "refresh":
+ return nil, store.ErrNoUpdateAvailable
+ case "install":
+ return nil, store.ErrSnapNotFound
+ }
}
- curInfo.SnapID = id
- // set revision to "unknown"
- curInfo.Revision = snap.R(0)
}
- return curInfo, user, nil
+ return nil, e
}
-func updateToRevisionInfo(st *state.State, snapst *SnapState, channel string, revision snap.Revision, userID int) (*snap.Info, error) {
+func updateToRevisionInfo(st *state.State, snapst *SnapState, revision snap.Revision, userID int) (*snap.Info, error) {
+ // TODO: support ignore-validation?
+
+ curSnaps, err := currentSnaps(st)
+ if err != nil {
+ return nil, err
+ }
+
curInfo, user, err := preUpdateInfo(st, snapst, false, userID)
if err != nil {
return nil, err
}
- theStore := Store(st)
- st.Unlock() // calls to the store should be done without holding the state lock
- spec := store.SnapSpec{
- Name: curInfo.Name(),
- Channel: channel,
+ action := &store.SnapAction{
+ Action: "refresh",
+ SnapID: curInfo.SnapID,
+ // the desired revision
Revision: revision,
}
- snap, err := theStore.SnapInfo(spec, user)
+
+ theStore := Store(st)
+ st.Unlock() // calls to the store should be done without holding the state lock
+ res, err := theStore.SnapAction(context.TODO(), curSnaps, []*store.SnapAction{action}, user, nil)
st.Lock()
- return snap, err
+
+ return singleActionResult(curInfo.Name(), action.Action, res, err)
+}
+
+func currentSnaps(st *state.State) ([]*store.CurrentSnap, error) {
+ snapStates, err := All(st)
+ if err != nil {
+ return nil, err
+ }
+
+ curSnaps := collectCurrentSnaps(snapStates, nil)
+ return curSnaps, nil
+}
+
+func collectCurrentSnaps(snapStates map[string]*SnapState, consider func(*store.CurrentSnap, *SnapState)) (curSnaps []*store.CurrentSnap) {
+ curSnaps = make([]*store.CurrentSnap, 0, len(snapStates))
+
+ for snapName, snapst := range snapStates {
+ if snapst.TryMode {
+ // try mode snaps are completely local and
+ // irrelevant for the operation
+ continue
+ }
+
+ snapInfo, err := snapst.CurrentInfo()
+ if err != nil {
+ continue
+ }
+
+ if snapInfo.SnapID == "" {
+ // the store won't be able to tell what this
+ // is and so cannot include it in the
+ // operation
+ continue
+ }
+
+ installed := &store.CurrentSnap{
+ Name: snapName,
+ SnapID: snapInfo.SnapID,
+ // the desired channel (not snapInfo.Channel!)
+ TrackingChannel: snapst.Channel,
+ Revision: snapInfo.Revision,
+ RefreshedDate: revisionDate(snapInfo),
+ IgnoreValidation: snapst.IgnoreValidation,
+ }
+ curSnaps = append(curSnaps, installed)
+
+ if consider != nil {
+ consider(installed, snapst)
+ }
+ }
+
+ return curSnaps
}
-func refreshCandidates(ctx context.Context, st *state.State, names []string, user *auth.UserState, flags *store.RefreshOptions) ([]*snap.Info, map[string]*SnapState, map[string]bool, error) {
+func refreshCandidates(ctx context.Context, st *state.State, names []string, user *auth.UserState, opts *store.RefreshOptions) ([]*snap.Info, map[string]*SnapState, map[string]bool, error) {
snapStates, err := All(st)
if err != nil {
return nil, nil, nil, err
@@ -204,93 +322,69 @@
sort.Strings(names)
+ actionsByUserID := make(map[int][]*store.SnapAction)
stateByID := make(map[string]*SnapState, len(snapStates))
- candidatesInfo := make([]*store.RefreshCandidate, 0, len(snapStates))
ignoreValidation := make(map[string]bool)
- userIDs := make(map[int]bool)
- for _, snapst := range snapStates {
- if len(names) == 0 && (snapst.TryMode || snapst.DevMode) {
- // no auto-refresh for trymode nor devmode
- continue
- }
+ fallbackID := idForUser(user)
+ nCands := 0
+ addCand := func(installed *store.CurrentSnap, snapst *SnapState) {
// FIXME: snaps that are not active are skipped for now
// until we know what we want to do
if !snapst.Active {
- continue
- }
-
- snapInfo, err := snapst.CurrentInfo()
- if err != nil {
- // log something maybe?
- continue
+ return
}
- if snapInfo.SnapID == "" {
- // no refresh for sideloaded
- continue
+ if len(names) == 0 && snapst.DevMode {
+ // no auto-refresh for devmode
+ return
}
- if len(names) > 0 && !strutil.SortedListContains(names, snapInfo.Name()) {
- continue
+ if len(names) > 0 && !strutil.SortedListContains(names, installed.Name) {
+ return
}
- stateByID[snapInfo.SnapID] = snapst
-
- // get confinement preference from the snapstate
- candidateInfo := &store.RefreshCandidate{
- // the desired channel (not info.Channel!)
- Channel: snapst.Channel,
- SnapID: snapInfo.SnapID,
- Revision: snapInfo.Revision,
- Epoch: snapInfo.Epoch,
- IgnoreValidation: snapst.IgnoreValidation,
- }
+ stateByID[installed.SnapID] = snapst
if len(names) == 0 {
- candidateInfo.Block = snapst.Block()
+ installed.Block = snapst.Block()
}
- candidatesInfo = append(candidatesInfo, candidateInfo)
- if snapst.UserID != 0 {
- userIDs[snapst.UserID] = true
+ userID := snapst.UserID
+ if userID == 0 {
+ userID = fallbackID
}
+ actionsByUserID[userID] = append(actionsByUserID[userID], &store.SnapAction{
+ Action: "refresh",
+ SnapID: installed.SnapID,
+ })
if snapst.IgnoreValidation {
- ignoreValidation[snapInfo.SnapID] = true
+ ignoreValidation[installed.SnapID] = true
}
+ nCands++
}
+ // determine current snaps and collect candidates for refresh
+ curSnaps := collectCurrentSnaps(snapStates, addCand)
theStore := Store(st)
- // TODO: we query for all snaps for each user so that the
- // store can take into account validation constraints, we can
- // do better with coming APIs
- updatesInfo := make(map[string]*snap.Info, len(candidatesInfo))
- fallbackUsed := false
- fallbackID := idForUser(user)
- if len(userIDs) == 0 {
- // none of the snaps had an installed user set, just
- // use the fallbackID
- userIDs[fallbackID] = true
- }
- for userID := range userIDs {
+ updatesInfo := make(map[string]*snap.Info, nCands)
+ for userID, actions := range actionsByUserID {
u, err := userFromUserIDOrFallback(st, userID, user)
if err != nil {
return nil, nil, nil, err
}
- // consider the fallback user at most once
- if idForUser(u) == fallbackID {
- if fallbackUsed {
- continue
- }
- fallbackUsed = true
- }
st.Unlock()
- updatesForUser, err := theStore.ListRefresh(ctx, candidatesInfo, u, flags)
+ updatesForUser, err := theStore.SnapAction(ctx, curSnaps, actions, u, opts)
st.Lock()
if err != nil {
- return nil, nil, nil, err
+ saErr, ok := err.(*store.SnapActionError)
+ if !ok {
+ return nil, nil, nil, err
+ }
+ // TODO: use the warning infra here when we have it
+ logger.Noticef("%v", saErr)
}
for _, snapInfo := range updatesForUser {
diff -Nru snapd-2.32.3.2/packaging/arch/PKGBUILD snapd-2.32.9/packaging/arch/PKGBUILD
--- snapd-2.32.3.2/packaging/arch/PKGBUILD 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/packaging/arch/PKGBUILD 2018-05-16 08:20:08.000000000 +0000
@@ -5,7 +5,7 @@
pkgbase=snapd
pkgname=snapd-git
-pkgver=2.32.3.2
+pkgver=2.32.9
pkgrel=1
arch=('i686' 'x86_64')
url="https://github.com/snapcore/snapd"
diff -Nru snapd-2.32.3.2/packaging/fedora/snapd.spec snapd-2.32.9/packaging/fedora/snapd.spec
--- snapd-2.32.3.2/packaging/fedora/snapd.spec 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/packaging/fedora/snapd.spec 2018-05-16 08:20:08.000000000 +0000
@@ -50,7 +50,7 @@
%global provider_prefix %{provider}.%{provider_tld}/%{project}/%{repo}
%global import_path %{provider_prefix}
-%global snappy_svcs snapd.service snapd.socket snapd.autoimport.service
+%global snappy_svcs snapd.service snapd.socket snapd.autoimport.service snapd.seeded.service
# Until we have a way to add more extldflags to gobuild macro...
%if 0%{?fedora} >= 26
@@ -70,7 +70,7 @@
%endif
Name: snapd
-Version: 2.32.3.2
+Version: 2.32.9
Release: 0%{?dist}
Summary: A transactional software package manager
Group: System Environment/Base
@@ -604,8 +604,10 @@
%{_unitdir}/snapd.socket
%{_unitdir}/snapd.service
%{_unitdir}/snapd.autoimport.service
+%{_unitdir}/snapd.seeded.service
%{_datadir}/dbus-1/services/io.snapcraft.Launcher.service
%{_datadir}/polkit-1/actions/io.snapcraft.snapd.policy
+%{_sysconfdir}/xdg/autostart/snap-userd-autostart.desktop
%config(noreplace) %{_sysconfdir}/sysconfig/snapd
%dir %{_sharedstatedir}/snapd
%dir %{_sharedstatedir}/snapd/assertions
@@ -710,6 +712,52 @@
%changelog
+* Tue May 16 2018 Michael Vogt
+- New upstream release 2.32.9
+ - tests: run all spread tests inside GCE
+ - tests: build spread in the autopkgtests with a more recent go
+
+* Fri May 11 2018 Michael Vogt
+- New upstream release 2.32.8
+ - snapd.core-fixup.sh: add workaround for corrupted uboot.env
+
+* Fri May 11 2018 Michael Vogt
+- New upstream release 2.32.7
+ - many: add wait command and seeded target (2
+ - snapd.core-fixup.sh: add workaround for corrupted uboot.env
+ - boot: clear "snap_mode" when needed
+ - cmd/libsnap: fix compile error on more restrictive gcc
+ - tests: cherry-pick commits to move spread to google backend
+ - spread.yaml: add cosmic (18.10) to autopkgtest/qemu
+ - userd: set up journal logging streams for autostarted apps
+
+* Sun Apr 29 2018 Michael Vogt
+- New upstream release 2.32.6
+ - snap: do not use overly short timeout in `snap
+ {start,stop,restart}`
+ - interfaces/apparmor: fix incorrect apparmor profile glob
+ - tests: detect kernel oops during tests and abort tests in this
+ case
+ - tests: run interfaces-boradcom-asic-control early
+ - tests: skip interfaces-content test on core devices
+
+* Mon Apr 16 2018 Michael Vogt
+- New upstream release 2.32.5
+ - many: add "stop-mode: sig{term,hup,usr[12]}{,-all}" instead of
+ conflating that with refresh-mode
+ - overlord/snapstate: poll for up to 10s if a snap is unexpectedly
+ not mounted in doMountSnap
+ - daemon: support 'system' as nickname of the core snap
+
+* Wed Apr 11 2018 Michael Vogt
+- New upstream release 2.32.4
+ - cmd/snap: user session application autostart
+ - overlord/snapstate: introduce envvars to control the channels for
+ bases and prereqs
+ - overlord/snapstate: on multi-snap refresh make sure bases and core
+ are finished before dependent snaps
+ - many: use the new install/refresh /v2/snaps/refresh store API
+
* Wed Apr 11 2018 Michael Vogt
- New upstream release 2.32.3.2
- errtracker: make TestJournalErrorSilentError work on
diff -Nru snapd-2.32.3.2/packaging/fedora-25/snapd.spec snapd-2.32.9/packaging/fedora-25/snapd.spec
--- snapd-2.32.3.2/packaging/fedora-25/snapd.spec 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/packaging/fedora-25/snapd.spec 2018-05-16 08:20:08.000000000 +0000
@@ -50,7 +50,7 @@
%global provider_prefix %{provider}.%{provider_tld}/%{project}/%{repo}
%global import_path %{provider_prefix}
-%global snappy_svcs snapd.service snapd.socket snapd.autoimport.service
+%global snappy_svcs snapd.service snapd.socket snapd.autoimport.service snapd.seeded.service
# Until we have a way to add more extldflags to gobuild macro...
%if 0%{?fedora} >= 26
@@ -70,7 +70,7 @@
%endif
Name: snapd
-Version: 2.32.3.2
+Version: 2.32.9
Release: 0%{?dist}
Summary: A transactional software package manager
Group: System Environment/Base
@@ -604,8 +604,10 @@
%{_unitdir}/snapd.socket
%{_unitdir}/snapd.service
%{_unitdir}/snapd.autoimport.service
+%{_unitdir}/snapd.seeded.service
%{_datadir}/dbus-1/services/io.snapcraft.Launcher.service
%{_datadir}/polkit-1/actions/io.snapcraft.snapd.policy
+%{_sysconfdir}/xdg/autostart/snap-userd-autostart.desktop
%config(noreplace) %{_sysconfdir}/sysconfig/snapd
%dir %{_sharedstatedir}/snapd
%dir %{_sharedstatedir}/snapd/assertions
@@ -710,6 +712,52 @@
%changelog
+* Tue May 16 2018 Michael Vogt
+- New upstream release 2.32.9
+ - tests: run all spread tests inside GCE
+ - tests: build spread in the autopkgtests with a more recent go
+
+* Fri May 11 2018 Michael Vogt
+- New upstream release 2.32.8
+ - snapd.core-fixup.sh: add workaround for corrupted uboot.env
+
+* Fri May 11 2018 Michael Vogt
+- New upstream release 2.32.7
+ - many: add wait command and seeded target (2
+ - snapd.core-fixup.sh: add workaround for corrupted uboot.env
+ - boot: clear "snap_mode" when needed
+ - cmd/libsnap: fix compile error on more restrictive gcc
+ - tests: cherry-pick commits to move spread to google backend
+ - spread.yaml: add cosmic (18.10) to autopkgtest/qemu
+ - userd: set up journal logging streams for autostarted apps
+
+* Sun Apr 29 2018 Michael Vogt
+- New upstream release 2.32.6
+ - snap: do not use overly short timeout in `snap
+ {start,stop,restart}`
+ - interfaces/apparmor: fix incorrect apparmor profile glob
+ - tests: detect kernel oops during tests and abort tests in this
+ case
+ - tests: run interfaces-boradcom-asic-control early
+ - tests: skip interfaces-content test on core devices
+
+* Mon Apr 16 2018 Michael Vogt
+- New upstream release 2.32.5
+ - many: add "stop-mode: sig{term,hup,usr[12]}{,-all}" instead of
+ conflating that with refresh-mode
+ - overlord/snapstate: poll for up to 10s if a snap is unexpectedly
+ not mounted in doMountSnap
+ - daemon: support 'system' as nickname of the core snap
+
+* Wed Apr 11 2018 Michael Vogt
+- New upstream release 2.32.4
+ - cmd/snap: user session application autostart
+ - overlord/snapstate: introduce envvars to control the channels for
+ bases and prereqs
+ - overlord/snapstate: on multi-snap refresh make sure bases and core
+ are finished before dependent snaps
+ - many: use the new install/refresh /v2/snaps/refresh store API
+
* Wed Apr 11 2018 Michael Vogt
- New upstream release 2.32.3.2
- errtracker: make TestJournalErrorSilentError work on
diff -Nru snapd-2.32.3.2/packaging/fedora-26/snapd.spec snapd-2.32.9/packaging/fedora-26/snapd.spec
--- snapd-2.32.3.2/packaging/fedora-26/snapd.spec 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/packaging/fedora-26/snapd.spec 2018-05-16 08:20:08.000000000 +0000
@@ -50,7 +50,7 @@
%global provider_prefix %{provider}.%{provider_tld}/%{project}/%{repo}
%global import_path %{provider_prefix}
-%global snappy_svcs snapd.service snapd.socket snapd.autoimport.service
+%global snappy_svcs snapd.service snapd.socket snapd.autoimport.service snapd.seeded.service
# Until we have a way to add more extldflags to gobuild macro...
%if 0%{?fedora} >= 26
@@ -70,7 +70,7 @@
%endif
Name: snapd
-Version: 2.32.3.2
+Version: 2.32.9
Release: 0%{?dist}
Summary: A transactional software package manager
Group: System Environment/Base
@@ -604,8 +604,10 @@
%{_unitdir}/snapd.socket
%{_unitdir}/snapd.service
%{_unitdir}/snapd.autoimport.service
+%{_unitdir}/snapd.seeded.service
%{_datadir}/dbus-1/services/io.snapcraft.Launcher.service
%{_datadir}/polkit-1/actions/io.snapcraft.snapd.policy
+%{_sysconfdir}/xdg/autostart/snap-userd-autostart.desktop
%config(noreplace) %{_sysconfdir}/sysconfig/snapd
%dir %{_sharedstatedir}/snapd
%dir %{_sharedstatedir}/snapd/assertions
@@ -710,6 +712,52 @@
%changelog
+* Tue May 16 2018 Michael Vogt
+- New upstream release 2.32.9
+ - tests: run all spread tests inside GCE
+ - tests: build spread in the autopkgtests with a more recent go
+
+* Fri May 11 2018 Michael Vogt
+- New upstream release 2.32.8
+ - snapd.core-fixup.sh: add workaround for corrupted uboot.env
+
+* Fri May 11 2018 Michael Vogt
+- New upstream release 2.32.7
+ - many: add wait command and seeded target (2
+ - snapd.core-fixup.sh: add workaround for corrupted uboot.env
+ - boot: clear "snap_mode" when needed
+ - cmd/libsnap: fix compile error on more restrictive gcc
+ - tests: cherry-pick commits to move spread to google backend
+ - spread.yaml: add cosmic (18.10) to autopkgtest/qemu
+ - userd: set up journal logging streams for autostarted apps
+
+* Sun Apr 29 2018 Michael Vogt
+- New upstream release 2.32.6
+ - snap: do not use overly short timeout in `snap
+ {start,stop,restart}`
+ - interfaces/apparmor: fix incorrect apparmor profile glob
+ - tests: detect kernel oops during tests and abort tests in this
+ case
+ - tests: run interfaces-boradcom-asic-control early
+ - tests: skip interfaces-content test on core devices
+
+* Mon Apr 16 2018 Michael Vogt
+- New upstream release 2.32.5
+ - many: add "stop-mode: sig{term,hup,usr[12]}{,-all}" instead of
+ conflating that with refresh-mode
+ - overlord/snapstate: poll for up to 10s if a snap is unexpectedly
+ not mounted in doMountSnap
+ - daemon: support 'system' as nickname of the core snap
+
+* Wed Apr 11 2018 Michael Vogt
+- New upstream release 2.32.4
+ - cmd/snap: user session application autostart
+ - overlord/snapstate: introduce envvars to control the channels for
+ bases and prereqs
+ - overlord/snapstate: on multi-snap refresh make sure bases and core
+ are finished before dependent snaps
+ - many: use the new install/refresh /v2/snaps/refresh store API
+
* Wed Apr 11 2018 Michael Vogt
- New upstream release 2.32.3.2
- errtracker: make TestJournalErrorSilentError work on
diff -Nru snapd-2.32.3.2/packaging/fedora-27/snapd.spec snapd-2.32.9/packaging/fedora-27/snapd.spec
--- snapd-2.32.3.2/packaging/fedora-27/snapd.spec 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/packaging/fedora-27/snapd.spec 2018-05-16 08:20:08.000000000 +0000
@@ -50,7 +50,7 @@
%global provider_prefix %{provider}.%{provider_tld}/%{project}/%{repo}
%global import_path %{provider_prefix}
-%global snappy_svcs snapd.service snapd.socket snapd.autoimport.service
+%global snappy_svcs snapd.service snapd.socket snapd.autoimport.service snapd.seeded.service
# Until we have a way to add more extldflags to gobuild macro...
%if 0%{?fedora} >= 26
@@ -70,7 +70,7 @@
%endif
Name: snapd
-Version: 2.32.3.2
+Version: 2.32.9
Release: 0%{?dist}
Summary: A transactional software package manager
Group: System Environment/Base
@@ -604,8 +604,10 @@
%{_unitdir}/snapd.socket
%{_unitdir}/snapd.service
%{_unitdir}/snapd.autoimport.service
+%{_unitdir}/snapd.seeded.service
%{_datadir}/dbus-1/services/io.snapcraft.Launcher.service
%{_datadir}/polkit-1/actions/io.snapcraft.snapd.policy
+%{_sysconfdir}/xdg/autostart/snap-userd-autostart.desktop
%config(noreplace) %{_sysconfdir}/sysconfig/snapd
%dir %{_sharedstatedir}/snapd
%dir %{_sharedstatedir}/snapd/assertions
@@ -710,6 +712,52 @@
%changelog
+* Tue May 16 2018 Michael Vogt
+- New upstream release 2.32.9
+ - tests: run all spread tests inside GCE
+ - tests: build spread in the autopkgtests with a more recent go
+
+* Fri May 11 2018 Michael Vogt
+- New upstream release 2.32.8
+ - snapd.core-fixup.sh: add workaround for corrupted uboot.env
+
+* Fri May 11 2018 Michael Vogt
+- New upstream release 2.32.7
+ - many: add wait command and seeded target (2
+ - snapd.core-fixup.sh: add workaround for corrupted uboot.env
+ - boot: clear "snap_mode" when needed
+ - cmd/libsnap: fix compile error on more restrictive gcc
+ - tests: cherry-pick commits to move spread to google backend
+ - spread.yaml: add cosmic (18.10) to autopkgtest/qemu
+ - userd: set up journal logging streams for autostarted apps
+
+* Sun Apr 29 2018 Michael Vogt
+- New upstream release 2.32.6
+ - snap: do not use overly short timeout in `snap
+ {start,stop,restart}`
+ - interfaces/apparmor: fix incorrect apparmor profile glob
+ - tests: detect kernel oops during tests and abort tests in this
+ case
+ - tests: run interfaces-boradcom-asic-control early
+ - tests: skip interfaces-content test on core devices
+
+* Mon Apr 16 2018 Michael Vogt
+- New upstream release 2.32.5
+ - many: add "stop-mode: sig{term,hup,usr[12]}{,-all}" instead of
+ conflating that with refresh-mode
+ - overlord/snapstate: poll for up to 10s if a snap is unexpectedly
+ not mounted in doMountSnap
+ - daemon: support 'system' as nickname of the core snap
+
+* Wed Apr 11 2018 Michael Vogt
+- New upstream release 2.32.4
+ - cmd/snap: user session application autostart
+ - overlord/snapstate: introduce envvars to control the channels for
+ bases and prereqs
+ - overlord/snapstate: on multi-snap refresh make sure bases and core
+ are finished before dependent snaps
+ - many: use the new install/refresh /v2/snaps/refresh store API
+
* Wed Apr 11 2018 Michael Vogt
- New upstream release 2.32.3.2
- errtracker: make TestJournalErrorSilentError work on
diff -Nru snapd-2.32.3.2/packaging/fedora-rawhide/snapd.spec snapd-2.32.9/packaging/fedora-rawhide/snapd.spec
--- snapd-2.32.3.2/packaging/fedora-rawhide/snapd.spec 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/packaging/fedora-rawhide/snapd.spec 2018-05-16 08:20:08.000000000 +0000
@@ -50,7 +50,7 @@
%global provider_prefix %{provider}.%{provider_tld}/%{project}/%{repo}
%global import_path %{provider_prefix}
-%global snappy_svcs snapd.service snapd.socket snapd.autoimport.service
+%global snappy_svcs snapd.service snapd.socket snapd.autoimport.service snapd.seeded.service
# Until we have a way to add more extldflags to gobuild macro...
%if 0%{?fedora} >= 26
@@ -70,7 +70,7 @@
%endif
Name: snapd
-Version: 2.32.3.2
+Version: 2.32.9
Release: 0%{?dist}
Summary: A transactional software package manager
Group: System Environment/Base
@@ -604,8 +604,10 @@
%{_unitdir}/snapd.socket
%{_unitdir}/snapd.service
%{_unitdir}/snapd.autoimport.service
+%{_unitdir}/snapd.seeded.service
%{_datadir}/dbus-1/services/io.snapcraft.Launcher.service
%{_datadir}/polkit-1/actions/io.snapcraft.snapd.policy
+%{_sysconfdir}/xdg/autostart/snap-userd-autostart.desktop
%config(noreplace) %{_sysconfdir}/sysconfig/snapd
%dir %{_sharedstatedir}/snapd
%dir %{_sharedstatedir}/snapd/assertions
@@ -710,6 +712,52 @@
%changelog
+* Tue May 16 2018 Michael Vogt
+- New upstream release 2.32.9
+ - tests: run all spread tests inside GCE
+ - tests: build spread in the autopkgtests with a more recent go
+
+* Fri May 11 2018 Michael Vogt
+- New upstream release 2.32.8
+ - snapd.core-fixup.sh: add workaround for corrupted uboot.env
+
+* Fri May 11 2018 Michael Vogt
+- New upstream release 2.32.7
+ - many: add wait command and seeded target (2
+ - snapd.core-fixup.sh: add workaround for corrupted uboot.env
+ - boot: clear "snap_mode" when needed
+ - cmd/libsnap: fix compile error on more restrictive gcc
+ - tests: cherry-pick commits to move spread to google backend
+ - spread.yaml: add cosmic (18.10) to autopkgtest/qemu
+ - userd: set up journal logging streams for autostarted apps
+
+* Sun Apr 29 2018 Michael Vogt
+- New upstream release 2.32.6
+ - snap: do not use overly short timeout in `snap
+ {start,stop,restart}`
+ - interfaces/apparmor: fix incorrect apparmor profile glob
+ - tests: detect kernel oops during tests and abort tests in this
+ case
+ - tests: run interfaces-boradcom-asic-control early
+ - tests: skip interfaces-content test on core devices
+
+* Mon Apr 16 2018 Michael Vogt
+- New upstream release 2.32.5
+ - many: add "stop-mode: sig{term,hup,usr[12]}{,-all}" instead of
+ conflating that with refresh-mode
+ - overlord/snapstate: poll for up to 10s if a snap is unexpectedly
+ not mounted in doMountSnap
+ - daemon: support 'system' as nickname of the core snap
+
+* Wed Apr 11 2018 Michael Vogt
+- New upstream release 2.32.4
+ - cmd/snap: user session application autostart
+ - overlord/snapstate: introduce envvars to control the channels for
+ bases and prereqs
+ - overlord/snapstate: on multi-snap refresh make sure bases and core
+ are finished before dependent snaps
+ - many: use the new install/refresh /v2/snaps/refresh store API
+
* Wed Apr 11 2018 Michael Vogt
- New upstream release 2.32.3.2
- errtracker: make TestJournalErrorSilentError work on
diff -Nru snapd-2.32.3.2/packaging/opensuse-42.1/snapd.changes snapd-2.32.9/packaging/opensuse-42.1/snapd.changes
--- snapd-2.32.3.2/packaging/opensuse-42.1/snapd.changes 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/packaging/opensuse-42.1/snapd.changes 2018-05-16 08:20:08.000000000 +0000
@@ -1,4 +1,34 @@
-------------------------------------------------------------------
+Wed May 16 10:20:08 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.9
+
+-------------------------------------------------------------------
+Fri May 11 14:36:16 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.8
+
+-------------------------------------------------------------------
+Fri May 11 13:09:32 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.7
+
+-------------------------------------------------------------------
+Sun Apr 29 19:21:53 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.6
+
+-------------------------------------------------------------------
+Mon Apr 16 11:41:48 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.5
+
+-------------------------------------------------------------------
+Wed Apr 11 16:30:45 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.4
+
+-------------------------------------------------------------------
Wed Apr 11 12:40:09 UTC 2018 - mvo@fastmail.fm
- Update to upstream release 2.32.3.2
diff -Nru snapd-2.32.3.2/packaging/opensuse-42.1/snapd.spec snapd-2.32.9/packaging/opensuse-42.1/snapd.spec
--- snapd-2.32.3.2/packaging/opensuse-42.1/snapd.spec 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/packaging/opensuse-42.1/snapd.spec 2018-05-16 08:20:08.000000000 +0000
@@ -32,7 +32,7 @@
%define systemd_services_list snapd.socket snapd.service
Name: snapd
-Version: 2.32.3.2
+Version: 2.32.9
Release: 0
Summary: Tools enabling systems to work with .snap files
License: GPL-3.0
@@ -293,6 +293,7 @@
%{_mandir}/man5/snap-discard-ns.5.gz
%{_unitdir}/snapd.service
%{_unitdir}/snapd.socket
+%{_unitdir}/snapd.seeded.service
/usr/bin/snap
/usr/bin/snapctl
/usr/sbin/rcsnapd
@@ -313,6 +314,7 @@
%{_mandir}/man1/snap.1.gz
/usr/share/dbus-1/services/io.snapcraft.Launcher.service
/usr/share/dbus-1/services/io.snapcraft.Settings.service
+%{_sysconfdir}/xdg/autostart/snap-userd-autostart.desktop
%changelog
diff -Nru snapd-2.32.3.2/packaging/opensuse-42.2/snapd.changes snapd-2.32.9/packaging/opensuse-42.2/snapd.changes
--- snapd-2.32.3.2/packaging/opensuse-42.2/snapd.changes 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/packaging/opensuse-42.2/snapd.changes 2018-05-16 08:20:08.000000000 +0000
@@ -1,4 +1,34 @@
-------------------------------------------------------------------
+Wed May 16 10:20:08 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.9
+
+-------------------------------------------------------------------
+Fri May 11 14:36:16 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.8
+
+-------------------------------------------------------------------
+Fri May 11 13:09:32 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.7
+
+-------------------------------------------------------------------
+Sun Apr 29 19:21:53 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.6
+
+-------------------------------------------------------------------
+Mon Apr 16 11:41:48 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.5
+
+-------------------------------------------------------------------
+Wed Apr 11 16:30:45 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.4
+
+-------------------------------------------------------------------
Wed Apr 11 12:40:09 UTC 2018 - mvo@fastmail.fm
- Update to upstream release 2.32.3.2
diff -Nru snapd-2.32.3.2/packaging/opensuse-42.2/snapd.spec snapd-2.32.9/packaging/opensuse-42.2/snapd.spec
--- snapd-2.32.3.2/packaging/opensuse-42.2/snapd.spec 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/packaging/opensuse-42.2/snapd.spec 2018-05-16 08:20:08.000000000 +0000
@@ -32,7 +32,7 @@
%define systemd_services_list snapd.socket snapd.service
Name: snapd
-Version: 2.32.3.2
+Version: 2.32.9
Release: 0
Summary: Tools enabling systems to work with .snap files
License: GPL-3.0
@@ -293,6 +293,7 @@
%{_mandir}/man5/snap-discard-ns.5.gz
%{_unitdir}/snapd.service
%{_unitdir}/snapd.socket
+%{_unitdir}/snapd.seeded.service
/usr/bin/snap
/usr/bin/snapctl
/usr/sbin/rcsnapd
@@ -313,6 +314,7 @@
%{_mandir}/man1/snap.1.gz
/usr/share/dbus-1/services/io.snapcraft.Launcher.service
/usr/share/dbus-1/services/io.snapcraft.Settings.service
+%{_sysconfdir}/xdg/autostart/snap-userd-autostart.desktop
%changelog
diff -Nru snapd-2.32.3.2/packaging/opensuse-42.3/permissions snapd-2.32.9/packaging/opensuse-42.3/permissions
--- snapd-2.32.3.2/packaging/opensuse-42.3/permissions 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/packaging/opensuse-42.3/permissions 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1 @@
+/usr/lib/snapd/snap-confine root:root 4755
diff -Nru snapd-2.32.3.2/packaging/opensuse-42.3/permissions.easy snapd-2.32.9/packaging/opensuse-42.3/permissions.easy
--- snapd-2.32.3.2/packaging/opensuse-42.3/permissions.easy 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/packaging/opensuse-42.3/permissions.easy 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1 @@
+/usr/lib/snapd/snap-confine root:root 4755
diff -Nru snapd-2.32.3.2/packaging/opensuse-42.3/permissions.paranoid snapd-2.32.9/packaging/opensuse-42.3/permissions.paranoid
--- snapd-2.32.3.2/packaging/opensuse-42.3/permissions.paranoid 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/packaging/opensuse-42.3/permissions.paranoid 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1 @@
+/usr/lib/snapd/snap-confine root:root 755
diff -Nru snapd-2.32.3.2/packaging/opensuse-42.3/permissions.secure snapd-2.32.9/packaging/opensuse-42.3/permissions.secure
--- snapd-2.32.3.2/packaging/opensuse-42.3/permissions.secure 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/packaging/opensuse-42.3/permissions.secure 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1 @@
+/usr/lib/snapd/snap-confine root:root 4755
diff -Nru snapd-2.32.3.2/packaging/opensuse-42.3/snapd.changes snapd-2.32.9/packaging/opensuse-42.3/snapd.changes
--- snapd-2.32.3.2/packaging/opensuse-42.3/snapd.changes 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/packaging/opensuse-42.3/snapd.changes 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,228 @@
+-------------------------------------------------------------------
+Wed May 16 10:20:08 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.9
+
+-------------------------------------------------------------------
+Fri May 11 14:36:16 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.8
+
+-------------------------------------------------------------------
+Fri May 11 13:09:32 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.7
+
+-------------------------------------------------------------------
+Sun Apr 29 19:21:53 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.6
+
+-------------------------------------------------------------------
+Mon Apr 16 11:41:48 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.5
+
+-------------------------------------------------------------------
+Wed Apr 11 16:30:45 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.4
+
+-------------------------------------------------------------------
+Wed Apr 11 12:40:09 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.3.2
+
+-------------------------------------------------------------------
+Wed Apr 11 10:34:00 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.3.1
+
+-------------------------------------------------------------------
+Thu Apr 05 22:35:35 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.3
+
+-------------------------------------------------------------------
+Sat Mar 31 21:09:29 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.2
+
+-------------------------------------------------------------------
+Mon Mar 26 21:03:02 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.1
+
+-------------------------------------------------------------------
+Sat Mar 24 08:50:11 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32
+
+-------------------------------------------------------------------
+Tue Feb 20 17:32:42 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.31.1
+
+-------------------------------------------------------------------
+Tue Feb 06 09:46:22 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.31
+
+-------------------------------------------------------------------
+Mon Dev 18 15:31:24 UTC 2017 - mvo@fastmail.fm
+
+- Update to upstream release 2.30
+
+-------------------------------------------------------------------
+Fri Nov 17 22:56:09 UTC 2017 - mvo@fastmail.fm
+
+- Update to upstream release 2.29.4
+
+-------------------------------------------------------------------
+Thu Nov 09 19:16:29 UTC 2017 - mvo@fastmail.fm
+
+- Update to upstream release 2.29.3
+
+-------------------------------------------------------------------
+Fri Nov 03 17:26:14:17 UTC 2017 - mvo@fastmail.fm
+
+- Update to upstream release 2.29.2
+
+-------------------------------------------------------------------
+Fri Nov 03 07:27:08:17 UTC 2017 - mvo@fastmail.fm
+
+- Update to upstream release 2.29.1
+
+-------------------------------------------------------------------
+Mon Oct 30 16:24:08 UTC 2017 - mvo@fastmail.fm
+
+- Update to upstream release 2.29
+
+-------------------------------------------------------------------
+Fri Oct 13 19:46:37 UTC 2017 - mvo@fastmail.fm
+
+- Update to upstream release 2.28.4
+
+-------------------------------------------------------------------
+Wed Oct 11 19:46:37 UTC 2017 - mvo@fastmail.fm
+
+- Update to upstream release 2.28.4
+
+-------------------------------------------------------------------
+Wed Oct 11 08:23:47 UTC 2017 - mvo@fastmail.fm
+
+- Update to upstream release 2.28.3
+
+-------------------------------------------------------------------
+Tue Oct 10 18:42:45 UTC 2017 - mvo@fastmail.fm
+
+- Update to upstream release 2.28.2
+
+-------------------------------------------------------------------
+Mon Sep 27 22:04:59 UTC 2017 - mvo@fastmail.fm
+
+- Update to upstream release 2.28.1
+
+-------------------------------------------------------------------
+Mon Sep 25 16:09:15 UTC 2017 - mvo@fastmail.fm
+
+- Update to upstream release 2.28
+
+-------------------------------------------------------------------
+Thu Sep 07 10:32:21 UTC 2017 - mvo@fastmail.fm
+
+- Update to upstream release 2.27.6
+
+-------------------------------------------------------------------
+Wed Aug 30 07:45:01 UTC 2017 - mvo@fastmail.fm
+
+- Update to upstream release 2.27.5
+
+-------------------------------------------------------------------
+Thu Aug 24 09:08:32 UTC 2017 - mvo@fastmail.fm
+
+- Update to upstream release 2.27.4
+
+-------------------------------------------------------------------
+Fri Aug 18 15:51:22 UTC 2017 - mvo@fastmail.fm
+
+- Update to upstream release 2.27.3
+
+-------------------------------------------------------------------
+Wed Aug 16 12:16:01 UTC 2017 - mvo@fastmail.fm
+
+- Update to upstream release 2.27.2
+
+-------------------------------------------------------------------
+Mon Aug 14 08:07:21 UTC 2017 - mvo@fastmail.fm
+
+- Update to upstream release 2.27.1
+
+-------------------------------------------------------------------
+Thu Aug 10 11:25:11 UTC 2017 - mvo@fastmail.fm
+
+- Update to upstream release 2.27
+
+-------------------------------------------------------------------
+Wed May 19 14:35:29 UTC 2017 - morphis@gravedo.de
+
+- Add bind() syscall to default seccomp policy to allow execution
+ of snap hooks.
+- Do not share /etc/ssl with the host but use the one from the core
+ snap.
+
+-------------------------------------------------------------------
+Wed May 10 12:24:44 UTC 2017 - morphis@gravedo.de
+
+- Update to upstream release 2.25
+
+-------------------------------------------------------------------
+Thu Apr 13 14:06:13 UTC 2017 - morphis@gravedo.de
+
+- Update to upstream release 2.24
+
+-------------------------------------------------------------------
+Thu Mar 30 14:14:44 UTC 2017 - morphis@gravedo.de
+
+- Update to upstream release 2.23.6
+
+-------------------------------------------------------------------
+Thu Mar 23 08:53:37 UTC 2017 - morphis@gravedo.de
+
+- Update to upstream release 2.23.5
+- Disable seccomp support to work around bugs in snap-confine
+ (see https://bugs.launchpad.net/snappy/+bug/1674193 for details)
+
+-------------------------------------------------------------------
+Wed Mar 8 16:09:03 UTC 2017 - me@zygoon.pl
+
+- Fix log-out prompt to be displayed only when really necessary.
+- Fix installation of /usr/lib/snapd/info (version information)
+- Install bash completion for "snap"
+
+-------------------------------------------------------------------
+Wed Mar 8 15:53:06 UTC 2017 - me@zygoon.pl
+
+- New upstream release.
+ More details are available at https://github.com/snapcore/snapd/releases/tag/2.23.1
+
+-------------------------------------------------------------------
+Tue Mar 7 23:00:34 UTC 2017 - me@zygoon.pl
+
+- Add PATH integration and post-install message asking the user to logout to
+ see PATH changes.
+
+-------------------------------------------------------------------
+Tue Mar 7 00:45:12 UTC 2017 - me@zygoon.pl
+
+- (hacky) Disable shellcheck as it is missing on Leap 42.1
+
+-------------------------------------------------------------------
+Tue Mar 7 00:43:58 UTC 2017 - me@zygoon.pl
+
+- (hacky) fix the 32bit build
+
+-------------------------------------------------------------------
+Mon Mar 6 18:08:04 UTC 2017 - me@zygoon.pl
+
+- Initial package based on fully vendorized source tarball
diff -Nru snapd-2.32.3.2/packaging/opensuse-42.3/snapd-rpmlintrc snapd-2.32.9/packaging/opensuse-42.3/snapd-rpmlintrc
--- snapd-2.32.3.2/packaging/opensuse-42.3/snapd-rpmlintrc 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/packaging/opensuse-42.3/snapd-rpmlintrc 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1 @@
+setBadness('permissions-unauthorized-file', 222)
diff -Nru snapd-2.32.3.2/packaging/opensuse-42.3/snapd.spec snapd-2.32.9/packaging/opensuse-42.3/snapd.spec
--- snapd-2.32.3.2/packaging/opensuse-42.3/snapd.spec 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/packaging/opensuse-42.3/snapd.spec 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,320 @@
+# spec file for package snapd
+#
+# Copyright (c) 2017 Zygmunt Krynicki
+#
+# All modifications and additions to the file contributed by third parties
+# remain the property of their copyright owners, unless otherwise agreed
+# upon. The license for this file, and modifications and additions to the
+# file, is the same license as for the pristine package itself (unless the
+# license for the pristine package is not an Open Source License, in which
+# case the license is the MIT License). An "Open Source License" is a
+# license that conforms to the Open Source Definition (Version 1.9)
+# published by the Open Source Initiative.
+
+# Please submit bugfixes or comments via http://bugs.opensuse.org/
+
+%bcond_with testkeys
+
+%global provider github
+%global provider_tld com
+%global project snapcore
+%global repo snapd
+%global provider_prefix %{provider}.%{provider_tld}/%{project}/%{repo}
+%global import_path %{provider_prefix}
+
+%global with_test_keys 0
+
+%if %{with testkeys}
+%global with_test_keys 1
+%else
+%global with_test_keys 0
+%endif
+
+%define systemd_services_list snapd.socket snapd.service
+Name: snapd
+Version: 2.32.9
+Release: 0
+Summary: Tools enabling systems to work with .snap files
+License: GPL-3.0
+Group: System/Packages
+Url: https://%{import_path}
+Source0: https://github.com/snapcore/snapd/releases/download/%{version}/%{name}_%{version}.vendor.tar.xz
+Source1: snapd-rpmlintrc
+# TODO: make this enabled only on Leap 42.2+
+# BuildRequires: ShellCheck
+BuildRequires: autoconf
+BuildRequires: automake
+BuildRequires: glib2-devel
+BuildRequires: glibc-devel-static
+BuildRequires: golang-packaging
+BuildRequires: gpg2
+BuildRequires: indent
+BuildRequires: libapparmor-devel
+BuildRequires: libcap-devel
+BuildRequires: libseccomp-devel
+BuildRequires: libtool
+BuildRequires: libudev-devel
+BuildRequires: libuuid-devel
+BuildRequires: make
+BuildRequires: openssh
+BuildRequires: pkg-config
+BuildRequires: python-docutils
+BuildRequires: python3-docutils
+BuildRequires: squashfs
+BuildRequires: timezone
+BuildRequires: udev
+BuildRequires: xfsprogs-devel
+BuildRequires: xz
+
+# Make sure we are on Leap 42.2/SLE 12 SP2 or higher
+%if 0%{?sle_version} >= 120200
+BuildRequires: systemd-rpm-macros
+%endif
+
+PreReq: permissions
+
+Requires(post): permissions
+Requires: apparmor-parser
+Requires: gpg2
+Requires: openssh
+Requires: squashfs
+
+%systemd_requires
+
+BuildRoot: %{_tmppath}/%{name}-%{version}-build
+
+# TODO strip the C executables but don't strip the go executables
+# as that breaks the world in some ways.
+# reenable {go_nostrip}
+%{go_provides}
+
+%description
+This package contains that snapd daemon and the snap command line tool.
+Together they can be used to install, refresh (update), remove and configure
+snap packages on a system. Snap packages are a novel format based on simple
+principles. Bundle your dependencies, run in a predictable environment, use
+moder kernel features for setting up the execution environment and security.
+The same binary snap package can be installed and used on many diverse systems
+such as Debian, Fedora and OpenSUSE as well as their multiple derivatives.
+.
+This package contains the official build, endorsed by snapd developers. It is
+updated as soon as new upstream releases are made and is designed to live in
+the system:snappy repository.
+
+%prep
+%setup -q -n %{name}-%{version}
+
+# Set the version that is compiled into the various executables
+./mkversion.sh %{version}-%{release}
+
+# Generate autotools build system files
+cd cmd && autoreconf -i -f
+
+# Enable hardening; We can't use -pie here as this conflicts with
+# our build of static binaries for snap-confine. Also see
+# https://bugzilla.redhat.com/show_bug.cgi?id=1343892
+CFLAGS="$RPM_OPT_FLAGS -fPIC -Wl,-z,relro -Wl,-z,now"
+CXXFLAGS="$RPM_OPT_FLAGS -fPIC -Wl,-z,relro -Wl,-z,now"
+export CFLAGS
+export CXXFLAGS
+
+# NOTE: until snapd and snap-confine have the improved communication mechanism
+# we need to disable apparmor as snapd doesn't yet support the version of
+# apparmor kernel available in SUSE and Debian. The generated apparmor profiles
+# cannot be loaded into a vanilla kernel. As a temporary measure we just switch
+# it all off.
+%configure --disable-apparmor --libexecdir=%{_libexecdir}/snapd
+
+%build
+# Build golang executables
+%goprep %{import_path}
+
+%if 0%{?with_test_keys}
+# The gobuild macro doesn't allow us to pass any additional parameters
+# so we we have to invoke `go install` here manually.
+export GOPATH=%{_builddir}/go:%{_libdir}/go/contrib
+export GOBIN=%{_builddir}/go/bin
+# Options used are the same as the gobuild macro does but as it
+# doesn't allow us to amend new flags we have to repeat them here:
+# -s: tell long running tests to shorten their build time
+# -v: be verbose
+# -p 4: allow parallel execution of tests
+# -x: print commands
+go install -s -v -p 4 -x -tags withtestkeys github.com/snapcore/snapd/cmd/snapd
+%else
+%gobuild cmd/snapd
+%endif
+
+%gobuild cmd/snap
+%gobuild cmd/snapctl
+# build snap-exec and snap-update-ns completely static for base snaps
+CGO_ENABLED=0 %gobuild cmd/snap-exec
+# gobuild --ldflags '-extldflags "-static"' bin/snap-update-ns
+# FIXME: ^ this doesn't work yet, it's going to be fixed with another PR.
+%gobuild cmd/snap-update-ns
+
+# This is ok because snap-seccomp only requires static linking when it runs from the core-snap via re-exec.
+sed -e "s/-Bstatic -lseccomp/-Bstatic/g" -i %{_builddir}/go/src/%{provider_prefix}/cmd/snap-seccomp/main.go
+# build snap-seccomp
+%gobuild cmd/snap-seccomp
+
+# Build C executables
+make %{?_smp_mflags} -C cmd
+
+%check
+%{gotest} %{import_path}/...
+make %{?_smp_mflags} -C cmd check
+
+%install
+# Install all the go stuff
+%goinstall
+# TODO: instead of removing it move this to a dedicated golang package
+rm -rf %{buildroot}%{_libexecdir}64/go
+rm -rf %{buildroot}%{_libexecdir}/go
+find %{buildroot}
+# Move snapd, snap-exec, snap-seccomp and snap-update-ns into %{_libexecdir}/snapd
+install -m 755 -d %{buildroot}%{_libexecdir}/snapd
+mv %{buildroot}/usr/bin/snapd %{buildroot}%{_libexecdir}/snapd/snapd
+mv %{buildroot}/usr/bin/snap-exec %{buildroot}%{_libexecdir}/snapd/snap-exec
+mv %{buildroot}/usr/bin/snap-update-ns %{buildroot}%{_libexecdir}/snapd/snap-update-ns
+mv %{buildroot}/usr/bin/snap-seccomp %{buildroot}%{_libexecdir}/snapd/snap-seccomp
+# Install profile.d-based PATH integration for /snap/bin
+# and XDG_DATA_DIRS for /var/lib/snapd/desktop
+make -C data/env install DESTDIR=%{buildroot}
+
+# Generate and install man page for snap command
+install -m 755 -d %{buildroot}%{_mandir}/man1
+%{buildroot}/usr/bin/snap help --man > %{buildroot}%{_mandir}/man1/snap.1
+
+# TODO: enable gosrc
+# TODO: enable gofilelist
+
+# Install all the C executables
+%{make_install} -C cmd
+# Undo special permissions of the void directory
+chmod 755 %{?buildroot}/var/lib/snapd/void
+# Remove traces of ubuntu-core-launcher. It is a phased-out executable that is
+# still partially present in the tree but should be removed in the subsequent
+# release.
+rm -f %{?buildroot}/usr/bin/ubuntu-core-launcher
+# NOTE: we don't want to ship system-shutdown helper, it is just a helper on
+# ubuntu-core systems that exclusively use snaps. It is used during the
+# shutdown process and thus can be left out of the distribution package.
+rm -f %{?buildroot}%{_libexecdir}/snapd/system-shutdown
+# Install the directories that snapd creates by itself so that they can be a part of the package
+install -d %buildroot/var/lib/snapd/{assertions,desktop/applications,device,hostfs,mount,apparmor/profiles,seccomp/bpf,snaps}
+
+install -d %buildroot/var/lib/snapd/{lib/gl,lib/gl32,lib/vulkan}
+install -d %buildroot/var/cache/snapd
+install -d %buildroot/snap/bin
+# Install local permissions policy for snap-confine. This should be removed
+# once snap-confine is added to the permissions package. This is done following
+# the recommendations on
+# https://en.opensuse.org/openSUSE:Package_security_guidelines
+install -m 644 -D packaging/opensuse-42.2/permissions %buildroot/%{_sysconfdir}/permissions.d/snapd
+install -m 644 -D packaging/opensuse-42.2/permissions.paranoid %buildroot/%{_sysconfdir}/permissions.d/snapd.paranoid
+# Install the systemd units
+make -C data install DESTDIR=%{buildroot} SYSTEMDSYSTEMUNITDIR=%{_unitdir}
+for s in snapd.autoimport.service snapd.system-shutdown.service snapd.snap-repair.timer snapd.snap-repair.service snapd.core-fixup.service; do
+ rm -f %buildroot/%{_unitdir}/$s
+done
+# Remove snappy core specific scripts
+rm -f %buildroot%{_libexecdir}/snapd/snapd.core-fixup.sh
+
+# See https://en.opensuse.org/openSUSE:Packaging_checks#suse-missing-rclink for details
+install -d %{buildroot}/usr/sbin
+ln -sf %{_sbindir}/service %{buildroot}/%{_sbindir}/rcsnapd
+ln -sf %{_sbindir}/service %{buildroot}/%{_sbindir}/rcsnapd.refresh
+# Install the "info" data file with snapd version
+install -m 644 -D data/info %{buildroot}%{_libexecdir}/snapd/info
+# Install bash completion for "snap"
+install -m 644 -D data/completion/snap %{buildroot}/usr/share/bash-completion/completions/snap
+install -m 644 -D data/completion/complete.sh %{buildroot}%{_libexecdir}/snapd
+install -m 644 -D data/completion/etelpmoc.sh %{buildroot}%{_libexecdir}/snapd
+# move snapd-generator
+install -m 755 -d %{buildroot}/lib/systemd/system-generators/
+mv %{buildroot}%{_libexecdir}/snapd/snapd-generator %{buildroot}/lib/systemd/system-generators/
+
+%verifyscript
+%verify_permissions -e %{_libexecdir}/snapd/snap-confine
+
+%pre
+%service_add_pre %{systemd_services_list}
+
+%post
+%set_permissions %{_libexecdir}/snapd/snap-confine
+%service_add_post %{systemd_services_list}
+case ":$PATH:" in
+ *:/snap/bin:*)
+ ;;
+ *)
+ echo "Please reboot, logout/login or source /etc/profile to have /snap/bin added to PATH."
+ ;;
+esac
+
+%preun
+%service_del_preun %{systemd_services_list}
+if [ $1 -eq 0 ]; then
+ %{_libexecdir}/snapd/snap-mgmt --purge || :
+fi
+
+%postun
+%service_del_postun %{systemd_services_list}
+
+%files
+%defattr(-,root,root)
+%config %{_sysconfdir}/permissions.d/snapd
+%config %{_sysconfdir}/permissions.d/snapd.paranoid
+%config %{_sysconfdir}/profile.d/snapd.sh
+%dir %attr(0000,root,root) /var/lib/snapd/void
+%dir /snap
+%dir /snap/bin
+%dir %{_libexecdir}/snapd
+%dir /var/lib/snapd
+%dir /var/lib/snapd/apparmor
+%dir /var/lib/snapd/apparmor/profiles
+%dir /var/lib/snapd/apparmor/snap-confine
+%dir /var/lib/snapd/assertions
+%dir /var/lib/snapd/desktop
+%dir /var/lib/snapd/desktop/applications
+%dir /var/lib/snapd/device
+%dir /var/lib/snapd/hostfs
+%dir /var/lib/snapd/mount
+%dir /var/lib/snapd/seccomp
+%dir /var/lib/snapd/seccomp/bpf
+%dir /var/lib/snapd/snaps
+%dir /var/lib/snapd/lib
+%dir /var/lib/snapd/lib/gl
+%dir /var/lib/snapd/lib/gl32
+%dir /var/lib/snapd/lib/vulkan
+%dir /var/cache/snapd
+%verify(not user group mode) %attr(06755,root,root) %{_libexecdir}/snapd/snap-confine
+%{_mandir}/man1/snap-confine.1.gz
+%{_mandir}/man5/snap-discard-ns.5.gz
+%{_unitdir}/snapd.service
+%{_unitdir}/snapd.socket
+%{_unitdir}/snapd.seeded.service
+/usr/bin/snap
+/usr/bin/snapctl
+/usr/sbin/rcsnapd
+/usr/sbin/rcsnapd.refresh
+%{_libexecdir}/snapd/info
+%{_libexecdir}/snapd/snap-discard-ns
+%{_libexecdir}/snapd/snap-update-ns
+%{_libexecdir}/snapd/snap-exec
+%{_libexecdir}/snapd/snap-seccomp
+%{_libexecdir}/snapd/snapd
+%{_libexecdir}/snapd/snap-mgmt
+%{_libexecdir}/snapd/snap-gdb-shim
+%{_libexecdir}/snapd/snap-device-helper
+/usr/share/bash-completion/completions/snap
+%{_libexecdir}/snapd/complete.sh
+%{_libexecdir}/snapd/etelpmoc.sh
+/lib/systemd/system-generators/snapd-generator
+%{_mandir}/man1/snap.1.gz
+/usr/share/dbus-1/services/io.snapcraft.Launcher.service
+/usr/share/dbus-1/services/io.snapcraft.Settings.service
+%{_sysconfdir}/xdg/autostart/snap-userd-autostart.desktop
+
+%changelog
+
diff -Nru snapd-2.32.3.2/packaging/ubuntu-14.04/changelog snapd-2.32.9/packaging/ubuntu-14.04/changelog
--- snapd-2.32.3.2/packaging/ubuntu-14.04/changelog 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/packaging/ubuntu-14.04/changelog 2018-05-16 08:20:08.000000000 +0000
@@ -1,3 +1,67 @@
+snapd (2.32.9~14.04) trusty; urgency=medium
+
+ * New upstream release, LP: #1767833
+ - tests: run all spread tests inside GCE
+ - tests: build spread in the autopkgtests with a more recent go
+
+ -- Michael Vogt Wed, 16 May 2018 10:20:08 +0200
+
+snapd (2.32.8~14.04) trusty; urgency=medium
+
+ * New upstream release, LP: #1767833
+ - snapd.core-fixup.sh: add workaround for corrupted uboot.env
+
+ -- Michael Vogt Fri, 11 May 2018 14:36:16 +0200
+
+snapd (2.32.7~14.04) trusty; urgency=medium
+
+ * New upstream release, LP: #1767833
+ - many: add wait command and seeded target (2
+ - snapd.core-fixup.sh: add workaround for corrupted uboot.env
+ - boot: clear "snap_mode" when needed
+ - cmd/libsnap: fix compile error on more restrictive gcc
+ - tests: cherry-pick commits to move spread to google backend
+ - spread.yaml: add cosmic (18.10) to autopkgtest/qemu
+ - userd: set up journal logging streams for autostarted apps
+
+ -- Michael Vogt Fri, 11 May 2018 13:09:32 +0200
+
+snapd (2.32.6~14.04) trusty; urgency=medium
+
+ * New upstream release, LP: #1767833
+ - snap: do not use overly short timeout in `snap
+ {start,stop,restart}`
+ - interfaces/apparmor: fix incorrect apparmor profile glob
+ - tests: detect kernel oops during tests and abort tests in this
+ case
+ - tests: run interfaces-boradcom-asic-control early
+ - tests: skip interfaces-content test on core devices
+
+ -- Michael Vogt Sun, 29 Apr 2018 19:21:53 +0200
+
+snapd (2.32.5~14.04) trusty; urgency=medium
+
+ * New upstream release, LP: #1756173
+ - many: add "stop-mode: sig{term,hup,usr[12]}{,-all}" instead of
+ conflating that with refresh-mode
+ - overlord/snapstate: poll for up to 10s if a snap is unexpectedly
+ not mounted in doMountSnap
+ - daemon: support 'system' as nickname of the core snap
+
+ -- Michael Vogt Mon, 16 Apr 2018 11:41:48 +0200
+
+snapd (2.32.4~14.04) trusty; urgency=medium
+
+ * New upstream release, LP: #1756173
+ - cmd/snap: user session application autostart
+ - overlord/snapstate: introduce envvars to control the channels for
+ bases and prereqs
+ - overlord/snapstate: on multi-snap refresh make sure bases and core
+ are finished before dependent snaps
+ - many: use the new install/refresh /v2/snaps/refresh store API
+
+ -- Michael Vogt Wed, 11 Apr 2018 16:30:45 +0200
+
snapd (2.32.3.2~14.04) trusty; urgency=medium
* New upstream release, LP: #1756173
diff -Nru snapd-2.32.3.2/packaging/ubuntu-16.04/changelog snapd-2.32.9/packaging/ubuntu-16.04/changelog
--- snapd-2.32.3.2/packaging/ubuntu-16.04/changelog 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/packaging/ubuntu-16.04/changelog 2018-05-16 08:20:08.000000000 +0000
@@ -1,3 +1,66 @@
+snapd (2.32.9) xenial; urgency=medium
+
+ * New upstream release, LP: #1767833
+ - tests: run all spread tests inside GCE
+ - tests: build spread in the autopkgtests with a more recent go
+
+ -- Michael Vogt Wed, 16 May 2018 10:20:08 +0200
+
+snapd (2.32.8) xenial; urgency=medium
+
+ * New upstream release, LP: #1767833
+
+ -- Michael Vogt Fri, 11 May 2018 14:36:16 +0200
+
+snapd (2.32.7) xenial; urgency=medium
+
+ * New upstream release, LP: #1767833
+ - many: add wait command and seeded target (2
+ - snapd.core-fixup.sh: add workaround for corrupted uboot.env
+ - boot: clear "snap_mode" when needed
+ - cmd/libsnap: fix compile error on more restrictive gcc
+ - tests: cherry-pick commits to move spread to google backend
+ - spread.yaml: add cosmic (18.10) to autopkgtest/qemu
+ - userd: set up journal logging streams for autostarted apps
+
+ -- Michael Vogt Fri, 11 May 2018 13:09:32 +0200
+
+snapd (2.32.6) xenial; urgency=medium
+
+ * New upstream release, LP: #1767833
+ - snap: do not use overly short timeout in `snap
+ {start,stop,restart}`
+ - interfaces/apparmor: fix incorrect apparmor profile glob
+ - tests: detect kernel oops during tests and abort tests in this
+ case
+ - tests: run interfaces-boradcom-asic-control early
+ - tests: skip interfaces-content test on core devices
+
+ -- Michael Vogt Sun, 29 Apr 2018 19:21:53 +0200
+
+snapd (2.32.5) xenial; urgency=medium
+
+ * New upstream release, LP: #1765090
+ - many: add "stop-mode: sig{term,hup,usr[12]}{,-all}" instead of
+ conflating that with refresh-mode
+ - overlord/snapstate: poll for up to 10s if a snap is unexpectedly
+ not mounted in doMountSnap
+ - daemon: support 'system' as nickname of the core snap
+
+ -- Michael Vogt Mon, 16 Apr 2018 11:41:48 +0200
+
+snapd (2.32.4) xenial; urgency=medium
+
+ * New upstream release, LP: #1756173
+ - cmd/snap: user session application autostart
+ - overlord/snapstate: introduce envvars to control the channels for
+ bases and prereqs
+ - overlord/snapstate: on multi-snap refresh make sure bases and core
+ are finished before dependent snaps
+ - many: use the new install/refresh /v2/snaps/refresh store API
+
+ -- Michael Vogt Wed, 11 Apr 2018 16:30:45 +0200
+
snapd (2.32.3.2) xenial; urgency=medium
* New upstream release, LP: #1756173
diff -Nru snapd-2.32.3.2/packaging/ubuntu-16.04/tests/integrationtests snapd-2.32.9/packaging/ubuntu-16.04/tests/integrationtests
--- snapd-2.32.3.2/packaging/ubuntu-16.04/tests/integrationtests 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/packaging/ubuntu-16.04/tests/integrationtests 2018-05-16 08:20:08.000000000 +0000
@@ -36,11 +36,14 @@
export SPREAD_CORE_CHANNEL=stable
fi
+# Spread will only buid with recent go
+snap install --classic go
+
# and now run spread against localhost
# shellcheck disable=SC1091
. /etc/os-release
export GOPATH=/tmp/go
-go get -u github.com/snapcore/spread/cmd/spread
+/snap/bin/go get -u github.com/snapcore/spread/cmd/spread
/tmp/go/bin/spread -v "autopkgtest:${ID}-${VERSION_ID}-$(dpkg --print-architecture)"
# store journal info for inspectsion
diff -Nru snapd-2.32.3.2/packaging/ubuntu-16.10/changelog snapd-2.32.9/packaging/ubuntu-16.10/changelog
--- snapd-2.32.3.2/packaging/ubuntu-16.10/changelog 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/packaging/ubuntu-16.10/changelog 2018-05-16 08:20:08.000000000 +0000
@@ -1,3 +1,66 @@
+snapd (2.32.9) xenial; urgency=medium
+
+ * New upstream release, LP: #1767833
+ - tests: run all spread tests inside GCE
+ - tests: build spread in the autopkgtests with a more recent go
+
+ -- Michael Vogt Wed, 16 May 2018 10:20:08 +0200
+
+snapd (2.32.8) xenial; urgency=medium
+
+ * New upstream release, LP: #1767833
+
+ -- Michael Vogt Fri, 11 May 2018 14:36:16 +0200
+
+snapd (2.32.7) xenial; urgency=medium
+
+ * New upstream release, LP: #1767833
+ - many: add wait command and seeded target (2
+ - snapd.core-fixup.sh: add workaround for corrupted uboot.env
+ - boot: clear "snap_mode" when needed
+ - cmd/libsnap: fix compile error on more restrictive gcc
+ - tests: cherry-pick commits to move spread to google backend
+ - spread.yaml: add cosmic (18.10) to autopkgtest/qemu
+ - userd: set up journal logging streams for autostarted apps
+
+ -- Michael Vogt Fri, 11 May 2018 13:09:32 +0200
+
+snapd (2.32.6) xenial; urgency=medium
+
+ * New upstream release, LP: #1767833
+ - snap: do not use overly short timeout in `snap
+ {start,stop,restart}`
+ - interfaces/apparmor: fix incorrect apparmor profile glob
+ - tests: detect kernel oops during tests and abort tests in this
+ case
+ - tests: run interfaces-boradcom-asic-control early
+ - tests: skip interfaces-content test on core devices
+
+ -- Michael Vogt Sun, 29 Apr 2018 19:21:53 +0200
+
+snapd (2.32.5) xenial; urgency=medium
+
+ * New upstream release, LP: #1765090
+ - many: add "stop-mode: sig{term,hup,usr[12]}{,-all}" instead of
+ conflating that with refresh-mode
+ - overlord/snapstate: poll for up to 10s if a snap is unexpectedly
+ not mounted in doMountSnap
+ - daemon: support 'system' as nickname of the core snap
+
+ -- Michael Vogt Mon, 16 Apr 2018 11:41:48 +0200
+
+snapd (2.32.4) xenial; urgency=medium
+
+ * New upstream release, LP: #1756173
+ - cmd/snap: user session application autostart
+ - overlord/snapstate: introduce envvars to control the channels for
+ bases and prereqs
+ - overlord/snapstate: on multi-snap refresh make sure bases and core
+ are finished before dependent snaps
+ - many: use the new install/refresh /v2/snaps/refresh store API
+
+ -- Michael Vogt Wed, 11 Apr 2018 16:30:45 +0200
+
snapd (2.32.3.2) xenial; urgency=medium
* New upstream release, LP: #1756173
diff -Nru snapd-2.32.3.2/packaging/ubuntu-16.10/tests/integrationtests snapd-2.32.9/packaging/ubuntu-16.10/tests/integrationtests
--- snapd-2.32.3.2/packaging/ubuntu-16.10/tests/integrationtests 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/packaging/ubuntu-16.10/tests/integrationtests 2018-05-16 08:20:08.000000000 +0000
@@ -36,11 +36,14 @@
export SPREAD_CORE_CHANNEL=stable
fi
+# Spread will only buid with recent go
+snap install --classic go
+
# and now run spread against localhost
# shellcheck disable=SC1091
. /etc/os-release
export GOPATH=/tmp/go
-go get -u github.com/snapcore/spread/cmd/spread
+/snap/bin/go get -u github.com/snapcore/spread/cmd/spread
/tmp/go/bin/spread -v "autopkgtest:${ID}-${VERSION_ID}-$(dpkg --print-architecture)"
# store journal info for inspectsion
diff -Nru snapd-2.32.3.2/packaging/ubuntu-17.04/changelog snapd-2.32.9/packaging/ubuntu-17.04/changelog
--- snapd-2.32.3.2/packaging/ubuntu-17.04/changelog 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/packaging/ubuntu-17.04/changelog 2018-05-16 08:20:08.000000000 +0000
@@ -1,3 +1,66 @@
+snapd (2.32.9) xenial; urgency=medium
+
+ * New upstream release, LP: #1767833
+ - tests: run all spread tests inside GCE
+ - tests: build spread in the autopkgtests with a more recent go
+
+ -- Michael Vogt Wed, 16 May 2018 10:20:08 +0200
+
+snapd (2.32.8) xenial; urgency=medium
+
+ * New upstream release, LP: #1767833
+
+ -- Michael Vogt Fri, 11 May 2018 14:36:16 +0200
+
+snapd (2.32.7) xenial; urgency=medium
+
+ * New upstream release, LP: #1767833
+ - many: add wait command and seeded target (2
+ - snapd.core-fixup.sh: add workaround for corrupted uboot.env
+ - boot: clear "snap_mode" when needed
+ - cmd/libsnap: fix compile error on more restrictive gcc
+ - tests: cherry-pick commits to move spread to google backend
+ - spread.yaml: add cosmic (18.10) to autopkgtest/qemu
+ - userd: set up journal logging streams for autostarted apps
+
+ -- Michael Vogt Fri, 11 May 2018 13:09:32 +0200
+
+snapd (2.32.6) xenial; urgency=medium
+
+ * New upstream release, LP: #1767833
+ - snap: do not use overly short timeout in `snap
+ {start,stop,restart}`
+ - interfaces/apparmor: fix incorrect apparmor profile glob
+ - tests: detect kernel oops during tests and abort tests in this
+ case
+ - tests: run interfaces-boradcom-asic-control early
+ - tests: skip interfaces-content test on core devices
+
+ -- Michael Vogt Sun, 29 Apr 2018 19:21:53 +0200
+
+snapd (2.32.5) xenial; urgency=medium
+
+ * New upstream release, LP: #1765090
+ - many: add "stop-mode: sig{term,hup,usr[12]}{,-all}" instead of
+ conflating that with refresh-mode
+ - overlord/snapstate: poll for up to 10s if a snap is unexpectedly
+ not mounted in doMountSnap
+ - daemon: support 'system' as nickname of the core snap
+
+ -- Michael Vogt Mon, 16 Apr 2018 11:41:48 +0200
+
+snapd (2.32.4) xenial; urgency=medium
+
+ * New upstream release, LP: #1756173
+ - cmd/snap: user session application autostart
+ - overlord/snapstate: introduce envvars to control the channels for
+ bases and prereqs
+ - overlord/snapstate: on multi-snap refresh make sure bases and core
+ are finished before dependent snaps
+ - many: use the new install/refresh /v2/snaps/refresh store API
+
+ -- Michael Vogt Wed, 11 Apr 2018 16:30:45 +0200
+
snapd (2.32.3.2) xenial; urgency=medium
* New upstream release, LP: #1756173
diff -Nru snapd-2.32.3.2/packaging/ubuntu-17.04/tests/integrationtests snapd-2.32.9/packaging/ubuntu-17.04/tests/integrationtests
--- snapd-2.32.3.2/packaging/ubuntu-17.04/tests/integrationtests 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/packaging/ubuntu-17.04/tests/integrationtests 2018-05-16 08:20:08.000000000 +0000
@@ -36,11 +36,14 @@
export SPREAD_CORE_CHANNEL=stable
fi
+# Spread will only buid with recent go
+snap install --classic go
+
# and now run spread against localhost
# shellcheck disable=SC1091
. /etc/os-release
export GOPATH=/tmp/go
-go get -u github.com/snapcore/spread/cmd/spread
+/snap/bin/go get -u github.com/snapcore/spread/cmd/spread
/tmp/go/bin/spread -v "autopkgtest:${ID}-${VERSION_ID}-$(dpkg --print-architecture)"
# store journal info for inspectsion
diff -Nru snapd-2.32.3.2/run-checks snapd-2.32.9/run-checks
--- snapd-2.32.3.2/run-checks 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/run-checks 2018-05-16 08:20:08.000000000 +0000
@@ -248,7 +248,7 @@
export PATH=$TMP_SPREAD:$PATH
( cd "$TMP_SPREAD" && curl -s -O https://niemeyer.s3.amazonaws.com/spread-amd64.tar.gz && tar xzvf spread-amd64.tar.gz )
- spread -v linode:
+ spread google: linode:
# cleanup the debian-ubuntu-14.04
rm -rf debian-ubuntu-14.04
diff -Nru snapd-2.32.3.2/snap/info.go snapd-2.32.9/snap/info.go
--- snapd-2.32.3.2/snap/info.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/snap/info.go 2018-05-16 08:20:08.000000000 +0000
@@ -304,12 +304,12 @@
// UserDataDir returns the user-specific data directory of the snap.
func (s *Info) UserDataDir(home string) string {
- return filepath.Join(home, "snap", s.Name(), s.Revision.String())
+ return filepath.Join(home, dirs.UserHomeSnapDir, s.Name(), s.Revision.String())
}
// UserCommonDataDir returns the user-specific data directory common across revision of the snap.
func (s *Info) UserCommonDataDir(home string) string {
- return filepath.Join(home, "snap", s.Name(), "common")
+ return filepath.Join(home, dirs.UserHomeSnapDir, s.Name(), "common")
}
// CommonDataDir returns the data directory common across revisions of the snap.
@@ -537,6 +537,33 @@
Timer string
}
+// StopModeType is the type for the "stop-mode:" of a snap app
+type StopModeType string
+
+// KillAll returns if the stop-mode means all processes should be killed
+// when the service is stopped or just the main process.
+func (st StopModeType) KillAll() bool {
+ return string(st) == "" || strings.HasSuffix(string(st), "-all")
+}
+
+// KillSignal returns the signal that should be used to kill the process
+// (or an empty string if no signal is needed)
+func (st StopModeType) KillSignal() string {
+ if st.Validate() != nil || st == "" {
+ return ""
+ }
+ return strings.ToUpper(strings.TrimSuffix(string(st), "-all"))
+}
+
+func (st StopModeType) Validate() error {
+ switch st {
+ case "", "sigterm", "sigterm-all", "sighup", "sighup-all", "sigusr1", "sigusr1-all", "sigusr2", "sigusr2-all":
+ // valid
+ return nil
+ }
+ return fmt.Errorf(`"stop-mode" field contains invalid value %q`, st)
+}
+
// AppInfo provides information about a app.
type AppInfo struct {
Snap *Info
@@ -553,6 +580,7 @@
RestartCond RestartCondition
Completer string
RefreshMode string
+ StopMode StopModeType
// TODO: this should go away once we have more plumbing and can change
// things vs refactor
@@ -571,6 +599,8 @@
Before []string
Timer *TimerInfo
+
+ Autostart string
}
// ScreenshotInfo provides information about a screenshot.
@@ -762,6 +792,22 @@
return info, nil
}
+// ReadCurrentInfo reads the snap information from the installed snap in 'current' revision
+func ReadCurrentInfo(snapName string) (*Info, error) {
+ curFn := filepath.Join(dirs.SnapMountDir, snapName, "current")
+ realFn, err := os.Readlink(curFn)
+ if err != nil {
+ return nil, fmt.Errorf("cannot find current revision for snap %s: %s", snapName, err)
+ }
+ rev := filepath.Base(realFn)
+ revision, err := ParseRevision(rev)
+ if err != nil {
+ return nil, fmt.Errorf("cannot read revision %s: %s", rev, err)
+ }
+
+ return ReadInfo(snapName, &SideInfo{Revision: revision})
+}
+
// ReadInfoFromSnapFile reads the snap information from the given File
// and completes it with the given side-info if this is not nil.
func ReadInfoFromSnapFile(snapf Container, si *SideInfo) (*Info, error) {
diff -Nru snapd-2.32.3.2/snap/info_snap_yaml.go snapd-2.32.9/snap/info_snap_yaml.go
--- snapd-2.32.3.2/snap/info_snap_yaml.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/snap/info_snap_yaml.go 2018-05-16 08:20:08.000000000 +0000
@@ -68,6 +68,7 @@
StopTimeout timeout.Timeout `yaml:"stop-timeout,omitempty"`
Completer string `yaml:"completer,omitempty"`
RefreshMode string `yaml:"refresh-mode,omitempty"`
+ StopMode StopModeType `yaml:"stop-mode,omitempty"`
RestartCond RestartCondition `yaml:"restart-condition,omitempty"`
SlotNames []string `yaml:"slots,omitempty"`
@@ -83,6 +84,8 @@
Before []string `yaml:"before,omitempty"`
Timer string `yaml:"timer,omitempty"`
+
+ Autostart string `yaml:"autostart,omitempty"`
}
type hookYaml struct {
@@ -298,8 +301,10 @@
Environment: yApp.Environment,
Completer: yApp.Completer,
RefreshMode: yApp.RefreshMode,
+ StopMode: yApp.StopMode,
Before: yApp.Before,
After: yApp.After,
+ Autostart: yApp.Autostart,
}
if len(y.Plugs) > 0 || len(yApp.PlugNames) > 0 {
app.Plugs = make(map[string]*PlugInfo)
diff -Nru snapd-2.32.3.2/snap/info_snap_yaml_test.go snapd-2.32.9/snap/info_snap_yaml_test.go
--- snapd-2.32.3.2/snap/info_snap_yaml_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/snap/info_snap_yaml_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -1670,3 +1670,30 @@
app := info.Apps["foo"]
c.Check(app.Timer, DeepEquals, &snap.TimerInfo{App: app, Timer: "mon,10:00-12:00"})
}
+
+func (s *YamlSuite) TestSnapYamlAppAutostart(c *C) {
+ yAutostart := []byte(`name: wat
+version: 42
+apps:
+ foo:
+ command: bin/foo
+ autostart: foo.desktop
+
+`)
+ info, err := snap.InfoFromSnapYaml(yAutostart)
+ c.Assert(err, IsNil)
+ app := info.Apps["foo"]
+ c.Check(app.Autostart, Equals, "foo.desktop")
+
+ yNoAutostart := []byte(`name: wat
+version: 42
+apps:
+ foo:
+ command: bin/foo
+
+`)
+ info, err = snap.InfoFromSnapYaml(yNoAutostart)
+ c.Assert(err, IsNil)
+ app = info.Apps["foo"]
+ c.Check(app.Autostart, Equals, "")
+}
diff -Nru snapd-2.32.3.2/snap/info_test.go snapd-2.32.9/snap/info_test.go
--- snapd-2.32.3.2/snap/info_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/snap/info_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -195,6 +195,23 @@
c.Check(snapInfo2, DeepEquals, snapInfo1)
}
+func (s *infoSuite) TestReadCurrentInfo(c *C) {
+ si := &snap.SideInfo{Revision: snap.R(42)}
+
+ snapInfo1 := snaptest.MockSnapCurrent(c, sampleYaml, si)
+
+ snapInfo2, err := snap.ReadCurrentInfo("sample")
+ c.Assert(err, IsNil)
+
+ c.Check(snapInfo2.Name(), Equals, "sample")
+ c.Check(snapInfo2.Revision, Equals, snap.R(42))
+ c.Check(snapInfo2, DeepEquals, snapInfo1)
+
+ snapInfo3, err := snap.ReadCurrentInfo("not-sample")
+ c.Check(snapInfo3, IsNil)
+ c.Assert(err, ErrorMatches, `cannot find current revision for snap not-sample:.*`)
+}
+
func (s *infoSuite) TestInstallDate(c *C) {
si := &snap.SideInfo{Revision: snap.R(1)}
info := snaptest.MockSnap(c, sampleYaml, si)
@@ -910,3 +927,41 @@
c.Assert(info.ExpandSnapVariables("$SNAP_COMMON/stuff"), Equals, "/var/snap/foo/common/stuff")
c.Assert(info.ExpandSnapVariables("$GARBAGE/rocks"), Equals, "/rocks")
}
+
+func (s *infoSuite) TestStopModeTypeKillMode(c *C) {
+ for _, t := range []struct {
+ stopMode string
+ killall bool
+ }{
+ {"", true},
+ {"sigterm", false},
+ {"sigterm-all", true},
+ {"sighup", false},
+ {"sighup-all", true},
+ {"sigusr1", false},
+ {"sigusr1-all", true},
+ {"sigusr2", false},
+ {"sigusr2-all", true},
+ } {
+ c.Check(snap.StopModeType(t.stopMode).KillAll(), Equals, t.killall, Commentf("wrong KillAll for %v", t.stopMode))
+ }
+}
+
+func (s *infoSuite) TestStopModeTypeKillSignal(c *C) {
+ for _, t := range []struct {
+ stopMode string
+ killSig string
+ }{
+ {"", ""},
+ {"sigterm", "SIGTERM"},
+ {"sigterm-all", "SIGTERM"},
+ {"sighup", "SIGHUP"},
+ {"sighup-all", "SIGHUP"},
+ {"sigusr1", "SIGUSR1"},
+ {"sigusr1-all", "SIGUSR1"},
+ {"sigusr2", "SIGUSR2"},
+ {"sigusr2-all", "SIGUSR2"},
+ } {
+ c.Check(snap.StopModeType(t.stopMode).KillSignal(), Equals, t.killSig)
+ }
+}
diff -Nru snapd-2.32.3.2/snap/snaptest/snaptest.go snapd-2.32.9/snap/snaptest/snaptest.go
--- snapd-2.32.3.2/snap/snaptest/snaptest.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/snap/snaptest/snaptest.go 2018-05-16 08:20:08.000000000 +0000
@@ -68,6 +68,18 @@
return snapInfo
}
+// MockSnapCurrent does the same as MockSnap but additionally creates the
+// 'current' symlink.
+//
+// The caller is responsible for mocking root directory with dirs.SetRootDir()
+// and for altering the overlord state if required.
+func MockSnapCurrent(c *check.C, yamlText string, sideInfo *snap.SideInfo) *snap.Info {
+ si := MockSnap(c, yamlText, sideInfo)
+ err := os.Symlink(si.MountDir(), filepath.Join(si.MountDir(), "../current"))
+ c.Assert(err, check.IsNil)
+ return si
+}
+
// MockInfo parses the given snap.yaml text and returns a validated snap.Info object including the optional SideInfo.
//
// The result is just kept in memory, there is nothing kept on disk. If that is
diff -Nru snapd-2.32.3.2/snap/snaptest/snaptest_test.go snapd-2.32.9/snap/snaptest/snaptest_test.go
--- snapd-2.32.3.2/snap/snaptest/snaptest_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/snap/snaptest/snaptest_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -72,6 +72,20 @@
c.Check(snapInfo.Plugs["network"].Interface, Equals, "network")
}
+func (s *snapTestSuite) TestMockSnapCurrent(c *C) {
+ snapInfo := snaptest.MockSnapCurrent(c, sampleYaml, &snap.SideInfo{Revision: snap.R(42)})
+ // Data from YAML is used
+ c.Check(snapInfo.Name(), Equals, "sample")
+ // Data from SideInfo is used
+ c.Check(snapInfo.Revision, Equals, snap.R(42))
+ // The YAML is placed on disk
+ c.Check(filepath.Join(dirs.SnapMountDir, "sample", "42", "meta", "snap.yaml"),
+ testutil.FileEquals, sampleYaml)
+ link, err := os.Readlink(filepath.Join(dirs.SnapMountDir, "sample", "current"))
+ c.Check(err, IsNil)
+ c.Check(link, Equals, filepath.Join(dirs.SnapMountDir, "sample", "42"))
+}
+
func (s *snapTestSuite) TestMockInfo(c *C) {
snapInfo := snaptest.MockInfo(c, sampleYaml, &snap.SideInfo{Revision: snap.R(42)})
// Data from YAML is used
diff -Nru snapd-2.32.3.2/snap/validate.go snapd-2.32.9/snap/validate.go
--- snapd-2.32.3.2/snap/validate.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/snap/validate.go 2018-05-16 08:20:08.000000000 +0000
@@ -534,13 +534,20 @@
return err
}
- // validate refresh-mode
+ // validate stop-mode
+ if err := app.StopMode.Validate(); err != nil {
+ return err
+ }
+ // validate stop-mode
switch app.RefreshMode {
- case "", "endure", "restart", "sigterm", "sigterm-all", "sighup", "sighup-all", "sigusr1", "sigusr1-all", "sigusr2", "sigusr2-all":
+ case "", "endure", "restart":
// valid
default:
return fmt.Errorf(`"refresh-mode" field contains invalid value %q`, app.RefreshMode)
}
+ if app.StopMode != "" && app.Daemon == "" {
+ return fmt.Errorf(`"stop-mode" cannot be used for %q, only for services`, app.Name)
+ }
if app.RefreshMode != "" && app.Daemon == "" {
return fmt.Errorf(`"refresh-mode" cannot be used for %q, only for services`, app.Name)
}
diff -Nru snapd-2.32.3.2/snap/validate_test.go snapd-2.32.9/snap/validate_test.go
--- snapd-2.32.3.2/snap/validate_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/snap/validate_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -415,16 +415,14 @@
}
}
-func (s *ValidateSuite) TestAppRefreshMode(c *C) {
+func (s *ValidateSuite) TestAppStopMode(c *C) {
// check services
for _, t := range []struct {
- refresh string
- ok bool
+ stopMode string
+ ok bool
}{
// good
{"", true},
- {"endure", true},
- {"restart", true},
{"sigterm", true},
{"sigterm-all", true},
{"sighup", true},
@@ -437,9 +435,34 @@
{"invalid-thing", false},
} {
if t.ok {
- c.Check(ValidateApp(&AppInfo{Name: "foo", Daemon: "simple", RefreshMode: t.refresh}), IsNil)
+ c.Check(ValidateApp(&AppInfo{Name: "foo", Daemon: "simple", StopMode: StopModeType(t.stopMode)}), IsNil)
+ } else {
+ c.Check(ValidateApp(&AppInfo{Name: "foo", Daemon: "simple", StopMode: StopModeType(t.stopMode)}), ErrorMatches, fmt.Sprintf(`"stop-mode" field contains invalid value %q`, t.stopMode))
+ }
+ }
+
+ // non-services cannot have a stop-mode
+ err := ValidateApp(&AppInfo{Name: "foo", Daemon: "", StopMode: "sigterm"})
+ c.Check(err, ErrorMatches, `"stop-mode" cannot be used for "foo", only for services`)
+}
+
+func (s *ValidateSuite) TestAppRefreshMode(c *C) {
+ // check services
+ for _, t := range []struct {
+ refreshMode string
+ ok bool
+ }{
+ // good
+ {"", true},
+ {"endure", true},
+ {"restart", true},
+ // bad
+ {"invalid-thing", false},
+ } {
+ if t.ok {
+ c.Check(ValidateApp(&AppInfo{Name: "foo", Daemon: "simple", RefreshMode: t.refreshMode}), IsNil)
} else {
- c.Check(ValidateApp(&AppInfo{Name: "foo", Daemon: "simple", RefreshMode: t.refresh}), ErrorMatches, fmt.Sprintf(`"refresh-mode" field contains invalid value %q`, t.refresh))
+ c.Check(ValidateApp(&AppInfo{Name: "foo", Daemon: "simple", RefreshMode: t.refreshMode}), ErrorMatches, fmt.Sprintf(`"refresh-mode" field contains invalid value %q`, t.refreshMode))
}
}
diff -Nru snapd-2.32.3.2/spread.yaml snapd-2.32.9/spread.yaml
--- snapd-2.32.3.2/spread.yaml 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/spread.yaml 2018-05-16 08:20:08.000000000 +0000
@@ -44,45 +44,50 @@
PRE_CACHE_SNAPS: core ubuntu-core test-snapd-tools
backends:
- linode:
- key: "$(HOST: echo $SPREAD_LINODE_KEY)"
- plan: 4GB
- location: Fremont
- halt-timeout: 2h
- environment:
- # Using proxy can help to accelerate testing in local conditions
- # but it is unlikely anyone has a proxy that is addressable from
- # Linode network. As such, don't honor host's SPREAD_HTTP_PROXY
- # that was set globally above.
- HTTP_PROXY: null
- HTTPS_PROXY: null
+ google:
+ key: "$(HOST: echo $SPREAD_GOOGLE_KEY)"
+ location: computeengine/us-east1-b
systems:
- ubuntu-16.04-64:
- kernel: GRUB 2
workers: 8
- ubuntu-16.04-32:
- kernel: GRUB 2
workers: 6
- ubuntu-14.04-64:
- kernel: GRUB 2
workers: 6
- ubuntu-core-16-64:
- kernel: Direct Disk
image: ubuntu-16.04-64
workers: 6
- debian-9-64:
- kernel: GRUB 2
workers: 6
- debian-sid-64:
- kernel: GRUB 2
workers: 6
manual: true
+ - opensuse-42.2-64:
+ image: opensuse-leap-42-2
+ workers: 4
+ manual: true
+ - opensuse-42.3-64:
+ image: opensuse-cloud/opensuse-leap-42-3-v20180116
+ workers: 4
+
+ linode:
+ key: "$(HOST: echo $SPREAD_LINODE_KEY)"
+ plan: 4GB
+ location: Fremont
+ halt-timeout: 2h
+ environment:
+ # Using proxy can help to accelerate testing in local conditions
+ # but it is unlikely anyone has a proxy that is addressable from
+ # Linode network. As such, don't honor host's SPREAD_HTTP_PROXY
+ # that was set globally above.
+ HTTP_PROXY: null
+ HTTPS_PROXY: null
+ systems:
- fedora-27-64:
workers: 4
- # Fedora CI disabled because of unexplained golang bug breaking every build.
manual: true
- fedora-26-64:
workers: 4
@@ -91,8 +96,6 @@
workers: 4
manual: true
- - opensuse-42.2-64:
- workers: 4
qemu:
systems:
- ubuntu-14.04-32:
@@ -120,6 +123,12 @@
- ubuntu-18.04-32:
username: ubuntu
password: ubuntu
+ - ubuntu-18.10-64:
+ username: ubuntu
+ password: ubuntu
+ - ubuntu-18.10-32:
+ username: ubuntu
+ password: ubuntu
- debian-sid-64:
username: debian
password: debian
@@ -206,6 +215,25 @@
- ubuntu-18.04-arm64:
username: ubuntu
password: ubuntu
+ # Cosmic
+ - ubuntu-18.10-amd64:
+ username: ubuntu
+ password: ubuntu
+ - ubuntu-18.10-i386:
+ username: ubuntu
+ password: ubuntu
+ - ubuntu-18.10-ppc64el:
+ username: ubuntu
+ password: ubuntu
+ - ubuntu-18.10-armhf:
+ username: ubuntu
+ password: ubuntu
+ - ubuntu-18.10-s390x:
+ username: ubuntu
+ password: ubuntu
+ - ubuntu-18.10-arm64:
+ username: ubuntu
+ password: ubuntu
external:
type: adhoc
environment:
@@ -334,6 +362,7 @@
dnf install --refresh -y xdelta curl &> "$tf" || (cat "$tf"; exit 1)
;;
opensuse-*)
+ zypper -q --gpg-auto-import-keys refresh
zypper -q install -y xdelta3 curl &> "$tf" || (cat "$tf"; exit 1)
;;
esac
diff -Nru snapd-2.32.3.2/store/details_v2.go snapd-2.32.9/store/details_v2.go
--- snapd-2.32.3.2/store/details_v2.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/store/details_v2.go 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,177 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2018 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package store
+
+import (
+ "fmt"
+ "strconv"
+
+ "github.com/snapcore/snapd/snap"
+)
+
+// storeSnap holds the information sent as JSON by the store for a snap.
+type storeSnap struct {
+ Architectures []string `json:"architectures"`
+ Base string `json:"base"`
+ Confinement string `json:"confinement"`
+ Contact string `json:"contact"`
+ CreatedAt string `json:"created-at"` // revision timestamp
+ Description string `json:"description"`
+ Download storeSnapDownload `json:"download"`
+ Epoch snap.Epoch `json:"epoch"`
+ License string `json:"license"`
+ Name string `json:"name"`
+ Prices map[string]string `json:"prices"` // currency->price, free: {"USD": "0"}
+ Private bool `json:"private"`
+ Publisher storeAccount `json:"publisher"`
+ Revision int `json:"revision"` // store revisions are ints starting at 1
+ SnapID string `json:"snap-id"`
+ SnapYAML string `json:"snap-yaml"` // optional
+ Summary string `json:"summary"`
+ Title string `json:"title"`
+ Type snap.Type `json:"type"`
+ Version string `json:"version"`
+
+ // TODO: not yet defined: channel map
+
+ // media
+ Media []storeSnapMedia `json:"media"`
+}
+
+type storeSnapDownload struct {
+ Sha3_384 string `json:"sha3-384"`
+ Size int64 `json:"size"`
+ URL string `json:"url"`
+ Deltas []storeSnapDelta `json:"deltas"`
+}
+
+type storeSnapDelta struct {
+ Format string `json:"format"`
+ Sha3_384 string `json:"sha3-384"`
+ Size int64 `json:"size"`
+ Source int `json:"source"`
+ Target int `json:"target"`
+ URL string `json:"url"`
+}
+
+type storeAccount struct {
+ ID string `json:"id"`
+ Username string `json:"username"`
+ DisplayName string `json:"display-name"`
+}
+
+type storeSnapMedia struct {
+ Type string `json:"type"` // icon/screenshot
+ URL string `json:"url"`
+ Width int64 `json:"width"`
+ Height int64 `json:"height"`
+}
+
+func infoFromStoreSnap(d *storeSnap) (*snap.Info, error) {
+ info := &snap.Info{}
+ info.RealName = d.Name
+ info.Revision = snap.R(d.Revision)
+ info.SnapID = d.SnapID
+ info.EditedTitle = d.Title
+ info.EditedSummary = d.Summary
+ info.EditedDescription = d.Description
+ info.Private = d.Private
+ info.Contact = d.Contact
+ info.Architectures = d.Architectures
+ info.Type = d.Type
+ info.Version = d.Version
+ info.Epoch = d.Epoch
+ info.Confinement = snap.ConfinementType(d.Confinement)
+ info.Base = d.Base
+ info.License = d.License
+ info.PublisherID = d.Publisher.ID
+ info.Publisher = d.Publisher.Username
+ info.DownloadURL = d.Download.URL
+ info.Size = d.Download.Size
+ info.Sha3_384 = d.Download.Sha3_384
+ if len(d.Download.Deltas) > 0 {
+ deltas := make([]snap.DeltaInfo, len(d.Download.Deltas))
+ for i, d := range d.Download.Deltas {
+ deltas[i] = snap.DeltaInfo{
+ FromRevision: d.Source,
+ ToRevision: d.Target,
+ Format: d.Format,
+ DownloadURL: d.URL,
+ Size: d.Size,
+ Sha3_384: d.Sha3_384,
+ }
+ }
+ info.Deltas = deltas
+ }
+
+ // fill in the plug/slot data
+ if rawYamlInfo, err := snap.InfoFromSnapYaml([]byte(d.SnapYAML)); err == nil {
+ if info.Plugs == nil {
+ info.Plugs = make(map[string]*snap.PlugInfo)
+ }
+ for k, v := range rawYamlInfo.Plugs {
+ info.Plugs[k] = v
+ info.Plugs[k].Snap = info
+ }
+ if info.Slots == nil {
+ info.Slots = make(map[string]*snap.SlotInfo)
+ }
+ for k, v := range rawYamlInfo.Slots {
+ info.Slots[k] = v
+ info.Slots[k].Snap = info
+ }
+ }
+
+ // convert prices
+ if len(d.Prices) > 0 {
+ prices := make(map[string]float64, len(d.Prices))
+ for currency, priceStr := range d.Prices {
+ price, err := strconv.ParseFloat(priceStr, 64)
+ if err != nil {
+ return nil, fmt.Errorf("cannot parse snap price: %v", err)
+ }
+ prices[currency] = price
+ }
+ info.Paid = true
+ info.Prices = prices
+ }
+
+ // media
+ screenshots := make([]snap.ScreenshotInfo, 0, len(d.Media))
+ for _, mediaObj := range d.Media {
+ switch mediaObj.Type {
+ case "icon":
+ if info.IconURL == "" {
+ info.IconURL = mediaObj.URL
+ }
+ case "screenshot":
+ screenshots = append(screenshots, snap.ScreenshotInfo{
+ URL: mediaObj.URL,
+ Width: mediaObj.Width,
+ Height: mediaObj.Height,
+ })
+ }
+ }
+ if len(screenshots) > 0 {
+ info.Screenshots = screenshots
+ }
+
+ return info, nil
+}
diff -Nru snapd-2.32.3.2/store/details_v2_test.go snapd-2.32.9/store/details_v2_test.go
--- snapd-2.32.3.2/store/details_v2_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/store/details_v2_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,305 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2018 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package store
+
+import (
+ "reflect"
+
+ "encoding/json"
+ "strings"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/testutil"
+)
+
+type detailsV2Suite struct {
+ testutil.BaseTest
+}
+
+var _ = Suite(&detailsV2Suite{})
+
+const (
+ coreStoreJSON = `{
+ "architectures": [
+ "amd64"
+ ],
+ "base": null,
+ "confinement": "strict",
+ "contact": "mailto:snappy-canonical-storeaccount@canonical.com",
+ "created-at": "2018-01-22T07:49:19.440720+00:00",
+ "description": "The core runtime environment for snapd",
+ "download": {
+ "sha3-384": "b691f6dde3d8022e4db563840f0ef82320cb824b6292ffd027dbc838535214dac31c3512c619beaf73f1aeaf35ac62d5",
+ "size": 85291008,
+ "url": "https://api.snapcraft.io/api/v1/snaps/download/99T7MUlRhtI3U0QFgl5mXXESAiSwt776_3887.snap",
+ "deltas": []
+ },
+ "epoch": {
+ "read": [0],
+ "write": [0]
+ },
+ "license": null,
+ "name": "core",
+ "prices": {},
+ "private": false,
+ "publisher": {
+ "id": "canonical",
+ "username": "canonical",
+ "display-name": "Canonical"
+ },
+ "revision": 3887,
+ "snap-id": "99T7MUlRhtI3U0QFgl5mXXESAiSwt776",
+ "summary": "snapd runtime environment",
+ "title": "core",
+ "type": "os",
+ "version": "16-2.30",
+ "media": []
+}`
+
+ thingyStoreJSON = `{
+ "architectures": [
+ "amd64"
+ ],
+ "base": "base-18",
+ "confinement": "strict",
+ "contact": "https://thingy.com",
+ "created-at": "2018-01-26T11:38:35.536410+00:00",
+ "description": "Useful thingy for thinging",
+ "download": {
+ "sha3-384": "a29f8d894c92ad19bb943764eb845c6bd7300f555ee9b9dbb460599fecf712775c0f3e2117b5c56b08fcb9d78fc8ae4d",
+ "size": 10000021,
+ "url": "https://api.snapcraft.io/api/v1/snaps/download/XYZEfjn4WJYnm0FzDKwqqRZZI77awQEV_21.snap",
+ "deltas": [
+ {
+ "format": "xdelta3",
+ "source": 19,
+ "target": 21,
+ "url": "https://api.snapcraft.io/api/v1/snaps/download/XYZEfjn4WJYnm0FzDKwqqRZZI77awQEV_19_21_xdelta3.delta",
+ "size": 9999,
+ "sha3-384": "29f8d894c92ad19bb943764eb845c6bd7300f555ee9b9dbb460599fecf712775c0f3e2117b5c56b08fcb9d78fc8ae4df"
+ }
+ ]
+ },
+ "epoch": {
+ "read": [0,1],
+ "write": [1]
+ },
+ "license": "Proprietary",
+ "name": "thingy",
+ "prices": {"USD": "9.99"},
+ "private": false,
+ "publisher": {
+ "id": "ZvtzsxbsHivZLdvzrt0iqW529riGLfXJ",
+ "username": "thingyinc",
+ "display-name": "Thingy Inc."
+ },
+ "revision": 21,
+ "snap-id": "XYZEfjn4WJYnm0FzDKwqqRZZI77awQEV",
+ "snap-yaml": "name: test-snapd-content-plug\nversion: 1.0\napps:\n content-plug:\n command: bin/content-plug\n plugs: [shared-content-plug]\nplugs:\n shared-content-plug:\n interface: content\n target: import\n content: mylib\n default-provider: test-snapd-content-slot\nslots:\n shared-content-slot:\n interface: content\n content: mylib\n read:\n - /\n",
+ "summary": "useful thingy",
+ "title": "thingy",
+ "type": "app",
+ "version": "9.50",
+ "media": [
+ {"type": "icon", "url": "https://dashboard.snapcraft.io/site_media/appmedia/2017/12/Thingy.png"},
+ {"type": "screenshot", "url": "https://dashboard.snapcraft.io/site_media/appmedia/2018/01/Thingy_01.png"},
+ {"type": "screenshot", "url": "https://dashboard.snapcraft.io/site_media/appmedia/2018/01/Thingy_02.png", "width": 600, "height": 200}
+ ]
+}`
+)
+
+func (s *detailsV2Suite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+ s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
+}
+
+func (s *detailsV2Suite) TearDownTest(c *C) {
+ s.BaseTest.TearDownTest(c)
+}
+
+func (s *detailsV2Suite) TestInfoFromStoreSnapSimple(c *C) {
+ var snp storeSnap
+ err := json.Unmarshal([]byte(coreStoreJSON), &snp)
+ c.Assert(err, IsNil)
+
+ info, err := infoFromStoreSnap(&snp)
+ c.Assert(err, IsNil)
+ c.Check(snap.Validate(info), IsNil)
+
+ c.Check(info, DeepEquals, &snap.Info{
+ Architectures: []string{"amd64"},
+ SideInfo: snap.SideInfo{
+ RealName: "core",
+ SnapID: "99T7MUlRhtI3U0QFgl5mXXESAiSwt776",
+ Revision: snap.R(3887),
+ Contact: "mailto:snappy-canonical-storeaccount@canonical.com",
+ EditedTitle: "core",
+ EditedSummary: "snapd runtime environment",
+ EditedDescription: "The core runtime environment for snapd",
+ Private: false,
+ Paid: false,
+ },
+ Epoch: *snap.E("0"),
+ Type: snap.TypeOS,
+ Version: "16-2.30",
+ Confinement: snap.StrictConfinement,
+ PublisherID: "canonical",
+ Publisher: "canonical",
+ DownloadInfo: snap.DownloadInfo{
+ DownloadURL: "https://api.snapcraft.io/api/v1/snaps/download/99T7MUlRhtI3U0QFgl5mXXESAiSwt776_3887.snap",
+ Sha3_384: "b691f6dde3d8022e4db563840f0ef82320cb824b6292ffd027dbc838535214dac31c3512c619beaf73f1aeaf35ac62d5",
+ Size: 85291008,
+ },
+ Plugs: make(map[string]*snap.PlugInfo),
+ Slots: make(map[string]*snap.SlotInfo),
+ })
+}
+
+func (s *detailsV2Suite) TestInfoFromStoreSnap(c *C) {
+ var snp storeSnap
+ // base, prices, media
+ err := json.Unmarshal([]byte(thingyStoreJSON), &snp)
+ c.Assert(err, IsNil)
+
+ info, err := infoFromStoreSnap(&snp)
+ c.Assert(err, IsNil)
+ c.Check(snap.Validate(info), IsNil)
+
+ info2 := *info
+ // clear recursive bits
+ info2.Plugs = nil
+ info2.Slots = nil
+ c.Check(&info2, DeepEquals, &snap.Info{
+ Architectures: []string{"amd64"},
+ Base: "base-18",
+ SideInfo: snap.SideInfo{
+ RealName: "thingy",
+ SnapID: "XYZEfjn4WJYnm0FzDKwqqRZZI77awQEV",
+ Revision: snap.R(21),
+ Contact: "https://thingy.com",
+ EditedTitle: "thingy",
+ EditedSummary: "useful thingy",
+ EditedDescription: "Useful thingy for thinging",
+ Private: false,
+ Paid: true,
+ },
+ Epoch: snap.Epoch{
+ Read: []uint32{0, 1},
+ Write: []uint32{1},
+ },
+ Type: snap.TypeApp,
+ Version: "9.50",
+ Confinement: snap.StrictConfinement,
+ License: "Proprietary",
+ PublisherID: "ZvtzsxbsHivZLdvzrt0iqW529riGLfXJ",
+ Publisher: "thingyinc",
+ DownloadInfo: snap.DownloadInfo{
+ DownloadURL: "https://api.snapcraft.io/api/v1/snaps/download/XYZEfjn4WJYnm0FzDKwqqRZZI77awQEV_21.snap",
+ Sha3_384: "a29f8d894c92ad19bb943764eb845c6bd7300f555ee9b9dbb460599fecf712775c0f3e2117b5c56b08fcb9d78fc8ae4d",
+ Size: 10000021,
+ Deltas: []snap.DeltaInfo{
+ {
+ Format: "xdelta3",
+ FromRevision: 19,
+ ToRevision: 21,
+ DownloadURL: "https://api.snapcraft.io/api/v1/snaps/download/XYZEfjn4WJYnm0FzDKwqqRZZI77awQEV_19_21_xdelta3.delta",
+ Size: 9999,
+ Sha3_384: "29f8d894c92ad19bb943764eb845c6bd7300f555ee9b9dbb460599fecf712775c0f3e2117b5c56b08fcb9d78fc8ae4df",
+ },
+ },
+ },
+ Prices: map[string]float64{
+ "USD": 9.99,
+ },
+ IconURL: "https://dashboard.snapcraft.io/site_media/appmedia/2017/12/Thingy.png",
+ Screenshots: []snap.ScreenshotInfo{
+ {URL: "https://dashboard.snapcraft.io/site_media/appmedia/2018/01/Thingy_01.png"},
+ {URL: "https://dashboard.snapcraft.io/site_media/appmedia/2018/01/Thingy_02.png", Width: 600, Height: 200},
+ },
+ })
+
+ // validate the plugs/slots
+ c.Assert(info.Plugs, HasLen, 1)
+ plug := info.Plugs["shared-content-plug"]
+ c.Check(plug.Name, Equals, "shared-content-plug")
+ c.Check(plug.Snap, Equals, info)
+ c.Check(plug.Apps, HasLen, 1)
+ c.Check(plug.Apps["content-plug"].Command, Equals, "bin/content-plug")
+
+ c.Assert(info.Slots, HasLen, 1)
+ slot := info.Slots["shared-content-slot"]
+ c.Check(slot.Name, Equals, "shared-content-slot")
+ c.Check(slot.Snap, Equals, info)
+ c.Check(slot.Apps, HasLen, 1)
+ c.Check(slot.Apps["content-plug"].Command, Equals, "bin/content-plug")
+
+ // private
+ err = json.Unmarshal([]byte(strings.Replace(thingyStoreJSON, `"private": false`, `"private": true`, 1)), &snp)
+ c.Assert(err, IsNil)
+
+ info, err = infoFromStoreSnap(&snp)
+ c.Assert(err, IsNil)
+ c.Check(snap.Validate(info), IsNil)
+
+ c.Check(info.Private, Equals, true)
+
+ // check that up to few exceptions info is filled
+ expectedZeroFields := []string{
+ "SuggestedName",
+ "Assumes",
+ "OriginalTitle",
+ "OriginalSummary",
+ "OriginalDescription",
+ "Environment",
+ "LicenseAgreement", // XXX go away?
+ "LicenseVersion", // XXX go away?
+ "Apps",
+ "LegacyAliases",
+ "Hooks",
+ "BadInterfaces",
+ "Broken",
+ "MustBuy",
+ "Channels", // TODO: support coming later
+ "Tracks", // TODO: support coming later
+ "Layout",
+ "SideInfo.Channel",
+ "DownloadInfo.AnonDownloadURL", // TODO: going away at some point
+ }
+ var checker func(string, reflect.Value)
+ checker = func(pfx string, x reflect.Value) {
+ t := x.Type()
+ for i := 0; i < x.NumField(); i++ {
+ f := t.Field(i)
+ v := x.Field(i)
+ if f.Anonymous {
+ checker(pfx+f.Name+".", v)
+ continue
+ }
+ if reflect.DeepEqual(v.Interface(), reflect.Zero(f.Type).Interface()) {
+ name := pfx + f.Name
+ c.Check(expectedZeroFields, testutil.Contains, name, Commentf("%s not set", name))
+ }
+ }
+ }
+ x := reflect.ValueOf(info).Elem()
+ checker("", x)
+}
diff -Nru snapd-2.32.3.2/store/errors.go snapd-2.32.9/store/errors.go
--- snapd-2.32.3.2/store/errors.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/store/errors.go 2018-05-16 08:20:08.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2014-2015 Canonical Ltd
+ * Copyright (C) 2014-2018 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -61,6 +61,9 @@
// ErrNoUpdateAvailable is returned when an update is attempetd for a snap that has no update available.
ErrNoUpdateAvailable = errors.New("snap has no updates available")
+
+ // ErrRevisionNotAvailable is returned when an install is attempted for a snap but the/a revision is not available (given install constraints)
+ ErrRevisionNotAvailable = errors.New("no snap revision given constraints")
)
// DownloadError represents a download error
@@ -107,3 +110,86 @@
// (empirically this checks out)
return strings.Join(es, " ")
}
+
+// SnapActionError conveys errors that were reported on otherwise overall successful snap action (install/refresh) request.
+type SnapActionError struct {
+ // NoResults is set if the there were no results in the response
+ NoResults bool
+ // Refresh errors by snap name.
+ Refresh map[string]error
+ // Install errors by snap name.
+ Install map[string]error
+ // Other errors.
+ Other []error
+}
+
+func (e SnapActionError) Error() string {
+ nRefresh := len(e.Refresh)
+ nInstall := len(e.Install)
+ nOther := len(e.Other)
+
+ // single error
+ if nRefresh+nInstall+nOther == 1 {
+ if nOther == 0 {
+ op := "refresh"
+ errs := e.Refresh
+ if nInstall > 0 {
+ op = "install"
+ errs = e.Install
+ }
+ for name, e := range errs {
+ return fmt.Sprintf("cannot %s snap %q: %v", op, name, e)
+ }
+ } else {
+ return fmt.Sprintf("cannot refresh or install: %v", e.Other[0])
+ }
+ }
+
+ header := "cannot refresh:"
+ if nInstall > 0 {
+ header = "cannot install:"
+ }
+ if nOther > 0 || (nInstall > 0 && nRefresh > 0) {
+ header = "cannot refresh or install:"
+ }
+ es := []string{header}
+
+ for name, e := range e.Refresh {
+ es = append(es, fmt.Sprintf("snap %q: %v", name, e))
+ }
+
+ for name, e := range e.Install {
+ es = append(es, fmt.Sprintf("snap %q: %v", name, e))
+ }
+
+ for _, e := range e.Other {
+ es = append(es, fmt.Sprintf("* %v", e))
+ }
+
+ if e.NoResults && len(es) == 1 {
+ // this is an atypical result
+ return "no install/refresh information results from the store"
+ }
+ return strings.Join(es, "\n")
+}
+
+// Authorization soft-expiry errors that get handled automatically.
+var (
+ errUserAuthorizationNeedsRefresh = errors.New("soft-expired user authorization needs refresh")
+ errDeviceAuthorizationNeedsRefresh = errors.New("soft-expired device authorization needs refresh")
+)
+
+func translateSnapActionError(action, code, message string) error {
+ switch code {
+ case "revision-not-found":
+ return ErrRevisionNotAvailable
+ case "id-not-found", "name-not-found":
+ return ErrSnapNotFound
+ case "user-authorization-needs-refresh":
+ return errUserAuthorizationNeedsRefresh
+ case "device-authorization-needs-refresh":
+ return errDeviceAuthorizationNeedsRefresh
+ default:
+ return fmt.Errorf("%v", message)
+ }
+}
diff -Nru snapd-2.32.3.2/store/store.go snapd-2.32.9/store/store.go
--- snapd-2.32.3.2/store/store.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/store/store.go 2018-05-16 08:20:08.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2014-2017 Canonical Ltd
+ * Copyright (C) 2014-2018 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -333,6 +333,7 @@
func storeURL(api *url.URL) (*url.URL, error) {
var override string
var overrideName string
+ // XXX: time to drop FORCE_CPI support
// XXX: Deprecated but present for backward-compatibility: this used
// to be "Click Package Index". Remove this once people have got
// used to SNAPPY_FORCE_API_URL instead.
@@ -489,6 +490,8 @@
customersMeEndpPath = "api/v1/snaps/purchases/customers/me"
sectionsEndpPath = "api/v1/snaps/sections"
commandsEndpPath = "api/v1/snaps/names"
+ // v2
+ snapActionEndpPath = "v2/snaps/refresh"
deviceNonceEndpPath = "api/v1/snaps/auth/nonces"
deviceSessionEndpPath = "api/v1/snaps/auth/sessions"
@@ -693,13 +696,13 @@
}
// authenticateDevice will add the store expected Macaroon X-Device-Authorization header for device
-func authenticateDevice(r *http.Request, device *auth.DeviceState) {
+func authenticateDevice(r *http.Request, device *auth.DeviceState, apiLevel apiLevel) {
if device.SessionMacaroon != "" {
- r.Header.Set("X-Device-Authorization", fmt.Sprintf(`Macaroon root="%s"`, device.SessionMacaroon))
+ r.Header.Set(hdrSnapDeviceAuthorization[apiLevel], fmt.Sprintf(`Macaroon root="%s"`, device.SessionMacaroon))
}
}
-func (s *Store) setStoreID(r *http.Request) (customStore bool) {
+func (s *Store) setStoreID(r *http.Request, apiLevel apiLevel) (customStore bool) {
storeID := s.fallbackStoreID
if s.authContext != nil {
cand, err := s.authContext.StoreID(storeID)
@@ -710,12 +713,27 @@
}
}
if storeID != "" {
- r.Header.Set("X-Ubuntu-Store", storeID)
+ r.Header.Set(hdrSnapDeviceStore[apiLevel], storeID)
return true
}
return false
}
+type apiLevel int
+
+const (
+ apiV1Endps apiLevel = 0 // api/v1 endpoints
+ apiV2Endps apiLevel = 1 // v2 endpoints
+)
+
+var (
+ hdrSnapDeviceAuthorization = []string{"X-Device-Authorization", "Snap-Device-Authorization"}
+ hdrSnapDeviceStore = []string{"X-Ubuntu-Store", "Snap-Device-Store"}
+ hdrSnapDeviceSeries = []string{"X-Ubuntu-Series", "Snap-Device-Series"}
+ hdrSnapDeviceArchitecture = []string{"X-Ubuntu-Architecture", "Snap-Device-Architecture"}
+ hdrSnapClassic = []string{"X-Ubuntu-Classic", "Snap-Classic"}
+)
+
type deviceAuthNeed int
const (
@@ -729,6 +747,7 @@
URL *url.URL
Accept string
ContentType string
+ APILevel apiLevel
ExtraHeaders map[string]string
Data []byte
@@ -874,18 +893,15 @@
// 4 tries: 2 tries for each in case both user
// and device need refreshing
var refreshNeed authRefreshNeed
- refresh := false
if user != nil && strings.Contains(wwwAuth, "needs_refresh=1") {
// refresh user
refreshNeed.user = true
- refresh = true
}
if strings.Contains(wwwAuth, "refresh_device_session=1") {
// refresh device session
refreshNeed.device = true
- refresh = true
}
- if refresh {
+ if refreshNeed.needed() {
err := s.refreshAuth(user, refreshNeed)
if err != nil {
return nil, err
@@ -901,6 +917,41 @@
}
}
+type authRefreshNeed struct {
+ device bool
+ user bool
+}
+
+func (rn *authRefreshNeed) needed() bool {
+ return rn.device || rn.user
+}
+
+func (s *Store) refreshAuth(user *auth.UserState, need authRefreshNeed) error {
+ if need.user {
+ // refresh user
+ err := s.refreshUser(user)
+ if err != nil {
+ return err
+ }
+ }
+ if need.device {
+ // refresh device session
+ if s.authContext == nil {
+ return fmt.Errorf("internal error: no authContext")
+ }
+ device, err := s.authContext.Device()
+ if err != nil {
+ return err
+ }
+
+ err = s.refreshDeviceSession(device)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
// build a new http.Request with headers for the store
func (s *Store) newRequest(reqOptions *requestOptions, user *auth.UserState) (*http.Request, error) {
var body io.Reader
@@ -913,7 +964,7 @@
return nil, err
}
- customStore := s.setStoreID(req)
+ customStore := s.setStoreID(req, reqOptions.APILevel)
if s.authContext != nil && (customStore || reqOptions.DeviceAuthNeed != deviceAuthCustomStoreOnly) {
device, err := s.authContext.Device()
@@ -932,7 +983,7 @@
return nil, err
}
}
- authenticateDevice(req, device)
+ authenticateDevice(req, device, reqOptions.APILevel)
}
// only set user authentication if user logged in to the store
@@ -942,16 +993,11 @@
req.Header.Set("User-Agent", httputil.UserAgent())
req.Header.Set("Accept", reqOptions.Accept)
- req.Header.Set("X-Ubuntu-Architecture", s.architecture)
- req.Header.Set("X-Ubuntu-Series", s.series)
- req.Header.Set("X-Ubuntu-Classic", strconv.FormatBool(release.OnClassic))
- req.Header.Set("X-Ubuntu-Wire-Protocol", UbuntuCoreWireProtocol)
- // still send this for now
- req.Header.Set("X-Ubuntu-No-CDN", strconv.FormatBool(s.noCDN))
- // TODO: do this only for download
- err = s.cdnHeader(req, reqOptions)
- if err != nil {
- return nil, err
+ req.Header.Set(hdrSnapDeviceArchitecture[reqOptions.APILevel], s.architecture)
+ req.Header.Set(hdrSnapDeviceSeries[reqOptions.APILevel], s.series)
+ req.Header.Set(hdrSnapClassic[reqOptions.APILevel], strconv.FormatBool(release.OnClassic))
+ if reqOptions.APILevel == apiV1Endps {
+ req.Header.Set("X-Ubuntu-Wire-Protocol", UbuntuCoreWireProtocol)
}
if reqOptions.ContentType != "" {
@@ -965,14 +1011,13 @@
return req, nil
}
-func (s *Store) cdnHeader(req *http.Request, reqOptions *requestOptions) error {
+func (s *Store) cdnHeader() (string, error) {
if s.noCDN {
- req.Header.Set("Snap-CDN", "none")
- return nil
+ return "none", nil
}
if s.authContext == nil {
- return nil
+ return "", nil
}
// set Snap-CDN from cloud instance information
@@ -985,7 +1030,7 @@
cloudInfo, err := s.authContext.CloudInfo()
if err != nil {
- return err
+ return "", err
}
if cloudInfo != nil {
@@ -997,40 +1042,10 @@
cdnParams = append(cdnParams, fmt.Sprintf("availability-zone=%q", cloudInfo.AvailabilityZone))
}
- req.Header.Set("Snap-CDN", strings.Join(cdnParams, " "))
+ return strings.Join(cdnParams, " "), nil
}
- return nil
-}
-
-type authRefreshNeed struct {
- device bool
- user bool
-}
-
-func (s *Store) refreshAuth(user *auth.UserState, need authRefreshNeed) error {
- if need.user {
- // refresh user
- err := s.refreshUser(user)
- if err != nil {
- return err
- }
- }
- if need.device {
- // refresh device session
- if s.authContext == nil {
- return fmt.Errorf("internal error: no authContext")
- }
- device, err := s.authContext.Device()
- if err != nil {
- return err
- }
- err = s.refreshDeviceSession(device)
- if err != nil {
- return err
- }
- }
- return nil
+ return "", nil
}
func (s *Store) extractSuggestedCurrency(resp *http.Response) {
@@ -1674,21 +1689,29 @@
return err
}
+ cdnHeader, err := s.cdnHeader()
+ if err != nil {
+ return err
+ }
+
var finalErr error
startTime := time.Now()
for attempt := retry.Start(defaultRetryStrategy, nil); attempt.Next(); {
reqOptions := &requestOptions{
- Method: "GET",
- URL: storeURL,
+ Method: "GET",
+ URL: storeURL,
+ ExtraHeaders: make(map[string]string),
+ }
+ if cdnHeader != "" {
+ reqOptions.ExtraHeaders["Snap-CDN"] = cdnHeader
}
+
httputil.MaybeLogRetryAttempt(reqOptions.URL.String(), attempt, startTime)
h := crypto.SHA3_384.New()
if resume > 0 {
- reqOptions.ExtraHeaders = map[string]string{
- "Range": fmt.Sprintf("bytes=%d-", resume),
- }
+ reqOptions.ExtraHeaders["Range"] = fmt.Sprintf("bytes=%d-", resume)
// seed the sha3 with the already local file
if _, err := w.Seek(0, os.SEEK_SET); err != nil {
return err
@@ -2143,3 +2166,296 @@
s.cacher = &nullCache{}
}
}
+
+// snap action: install/refresh
+
+type CurrentSnap struct {
+ Name string
+ SnapID string
+ Revision snap.Revision
+ TrackingChannel string
+ RefreshedDate time.Time
+ IgnoreValidation bool
+ Block []snap.Revision
+}
+
+type currentSnapV2JSON struct {
+ SnapID string `json:"snap-id"`
+ InstanceKey string `json:"instance-key"`
+ Revision int `json:"revision"`
+ TrackingChannel string `json:"tracking-channel"`
+ RefreshedDate *time.Time `json:"refreshed-date,omitempty"`
+ IgnoreValidation bool `json:"ignore-validation,omitempty"`
+}
+
+type SnapActionFlags int
+
+const (
+ SnapActionIgnoreValidation SnapActionFlags = 1 << iota
+ SnapActionEnforceValidation
+)
+
+type SnapAction struct {
+ Action string
+ Name string
+ SnapID string
+ Channel string
+ Revision snap.Revision
+ Flags SnapActionFlags
+ Epoch *snap.Epoch
+}
+
+type snapActionJSON struct {
+ Action string `json:"action"`
+ InstanceKey string `json:"instance-key"`
+ Name string `json:"name,omitempty"`
+ SnapID string `json:"snap-id,omitempty"`
+ Channel string `json:"channel,omitempty"`
+ Revision int `json:"revision,omitempty"`
+ Epoch *snap.Epoch `json:"epoch,omitempty"`
+ IgnoreValidation *bool `json:"ignore-validation,omitempty"`
+}
+
+type snapActionResult struct {
+ Result string `json:"result"`
+ InstanceKey string `json:"instance-key"`
+ SnapID string `json:"snap-id,omitempy"`
+ Name string `json:"name,omitempty"`
+ Snap storeSnap `json:"snap"`
+ EffectiveChannel string `json:"effective-channel,omitempty"`
+ Error struct {
+ Code string `json:"code"`
+ Message string `json:"message"`
+ } `json:"error"`
+}
+
+type snapActionRequest struct {
+ Context []*currentSnapV2JSON `json:"context"`
+ Actions []*snapActionJSON `json:"actions"`
+ Fields []string `json:"fields"`
+}
+
+type snapActionResultList struct {
+ Results []*snapActionResult `json:"results"`
+ ErrorList []struct {
+ Code string `json:"code"`
+ Message string `json:"message"`
+ } `json:"error-list"`
+}
+
+var snapActionFields = getStructFields(storeSnap{})
+
+// SnapAction queries the store for snap information for the given
+// install/refresh actions, given the context information about
+// current installed snaps in currentSnaps. If the request was overall
+// successul (200) but there were reported errors it will return both
+// the snap infos and an SnapActionError.
+func (s *Store) SnapAction(ctx context.Context, currentSnaps []*CurrentSnap, actions []*SnapAction, user *auth.UserState, opts *RefreshOptions) ([]*snap.Info, error) {
+ if opts == nil {
+ opts = &RefreshOptions{}
+ }
+
+ if len(currentSnaps) == 0 && len(actions) == 0 {
+ // nothing to do
+ return nil, &SnapActionError{NoResults: true}
+ }
+
+ authRefreshes := 0
+ for {
+ snaps, err := s.snapAction(ctx, currentSnaps, actions, user, opts)
+
+ if saErr, ok := err.(*SnapActionError); ok && authRefreshes < 2 && len(saErr.Other) > 0 {
+ // do we need to try to refresh auths?, 2 tries
+ var refreshNeed authRefreshNeed
+ for _, otherErr := range saErr.Other {
+ switch otherErr {
+ case errUserAuthorizationNeedsRefresh:
+ refreshNeed.user = true
+ case errDeviceAuthorizationNeedsRefresh:
+ refreshNeed.device = true
+ }
+ }
+ if refreshNeed.needed() {
+ err := s.refreshAuth(user, refreshNeed)
+ if err != nil {
+ // best effort
+ logger.Noticef("cannot refresh soft-expired authorisation: %v", err)
+ }
+ authRefreshes++
+ // TODO: we could avoid retrying here
+ // if refreshAuth gave no error we got
+ // as many non-error results from the
+ // store as actions anyway
+ continue
+ }
+ }
+
+ return snaps, err
+ }
+}
+
+func (s *Store) snapAction(ctx context.Context, currentSnaps []*CurrentSnap, actions []*SnapAction, user *auth.UserState, opts *RefreshOptions) ([]*snap.Info, error) {
+
+ // TODO: the store already requires instance-key but doesn't
+ // yet support repeating in context or sending actions for the
+ // same snap-id, for now we keep instance-key handling internal
+
+ curSnaps := make(map[string]*CurrentSnap, len(currentSnaps))
+ curSnapJSONs := make([]*currentSnapV2JSON, len(currentSnaps))
+ for i, curSnap := range currentSnaps {
+ if curSnap.SnapID == "" || curSnap.Name == "" || curSnap.Revision.Unset() {
+ return nil, fmt.Errorf("internal error: invalid current snap information")
+ }
+ curSnaps[curSnap.SnapID] = curSnap
+ channel := curSnap.TrackingChannel
+ if channel == "" {
+ channel = "stable"
+ }
+ var refreshedDate *time.Time
+ if !curSnap.RefreshedDate.IsZero() {
+ refreshedDate = &curSnap.RefreshedDate
+ }
+ curSnapJSONs[i] = ¤tSnapV2JSON{
+ SnapID: curSnap.SnapID,
+ InstanceKey: curSnap.SnapID,
+ Revision: curSnap.Revision.N,
+ TrackingChannel: channel,
+ IgnoreValidation: curSnap.IgnoreValidation,
+ RefreshedDate: refreshedDate,
+ }
+ }
+
+ installNum := 0
+ installKeys := make(map[string]bool, len(actions))
+ actionJSONs := make([]*snapActionJSON, len(actions))
+ for i, a := range actions {
+ var ignoreValidation *bool
+ if a.Flags&SnapActionIgnoreValidation != 0 {
+ var t = true
+ ignoreValidation = &t
+ } else if a.Flags&SnapActionEnforceValidation != 0 {
+ var f = false
+ ignoreValidation = &f
+ }
+
+ instanceKey := a.SnapID
+ if a.Action == "install" {
+ installNum++
+ instanceKey = fmt.Sprintf("install-%d", installNum)
+ installKeys[instanceKey] = true
+ }
+
+ aJSON := &snapActionJSON{
+ Action: a.Action,
+ InstanceKey: instanceKey,
+ SnapID: a.SnapID,
+ Name: a.Name,
+ Channel: a.Channel,
+ Revision: a.Revision.N,
+ Epoch: a.Epoch,
+ IgnoreValidation: ignoreValidation,
+ }
+ actionJSONs[i] = aJSON
+ }
+
+ // build input for the install/refresh endpoint
+ jsonData, err := json.Marshal(snapActionRequest{
+ Context: curSnapJSONs,
+ Actions: actionJSONs,
+ Fields: snapActionFields,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ reqOptions := &requestOptions{
+ Method: "POST",
+ URL: s.endpointURL(snapActionEndpPath, nil),
+ Accept: jsonContentType,
+ ContentType: jsonContentType,
+ Data: jsonData,
+ APILevel: apiV2Endps,
+ }
+
+ if useDeltas() {
+ logger.Debugf("Deltas enabled. Adding header Snap-Accept-Delta-Format: %v", s.deltaFormat)
+ reqOptions.addHeader("Snap-Accept-Delta-Format", s.deltaFormat)
+ }
+ if opts.RefreshManaged {
+ reqOptions.addHeader("Snap-Refresh-Managed", "true")
+ }
+
+ var results snapActionResultList
+ resp, err := s.retryRequestDecodeJSON(ctx, reqOptions, user, &results, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ if resp.StatusCode != 200 {
+ return nil, respToError(resp, "query the store for updates")
+ }
+
+ s.extractSuggestedCurrency(resp)
+
+ refreshErrors := make(map[string]error)
+ installErrors := make(map[string]error)
+ var otherErrors []error
+
+ var snaps []*snap.Info
+ for _, res := range results.Results {
+ if res.Result == "error" {
+ if installKeys[res.InstanceKey] {
+ if res.Name != "" {
+ installErrors[res.Name] = translateSnapActionError("install", res.Error.Code, res.Error.Message)
+ continue
+ }
+ } else {
+ if cur := curSnaps[res.InstanceKey]; cur != nil {
+ refreshErrors[cur.Name] = translateSnapActionError("refresh", res.Error.Code, res.Error.Message)
+ continue
+ }
+ }
+ otherErrors = append(otherErrors, translateSnapActionError("-", res.Error.Code, res.Error.Message))
+ continue
+ }
+ snapInfo, err := infoFromStoreSnap(&res.Snap)
+ if err != nil {
+ return nil, fmt.Errorf("unexpected invalid install/refresh API result: %v", err)
+ }
+ snapInfo.Channel = res.EffectiveChannel
+ if res.Result == "refresh" {
+ cur := curSnaps[res.SnapID]
+ if cur == nil {
+ return nil, fmt.Errorf("unexpected invalid install/refresh API result: unexpected refresh")
+ }
+ rrev := snap.R(res.Snap.Revision)
+ if rrev == cur.Revision || findRev(rrev, cur.Block) {
+ refreshErrors[cur.Name] = ErrNoUpdateAvailable
+ continue
+ }
+ }
+ snaps = append(snaps, snapInfo)
+ }
+
+ for _, errObj := range results.ErrorList {
+ otherErrors = append(otherErrors, translateSnapActionError("-", errObj.Code, errObj.Message))
+ }
+
+ if len(refreshErrors)+len(installErrors) != 0 || len(results.Results) == 0 || len(otherErrors) != 0 {
+ // normalize empty maps
+ if len(refreshErrors) == 0 {
+ refreshErrors = nil
+ }
+ if len(installErrors) == 0 {
+ installErrors = nil
+ }
+ return snaps, &SnapActionError{
+ NoResults: len(results.Results) == 0,
+ Refresh: refreshErrors,
+ Install: installErrors,
+ Other: otherErrors,
+ }
+ }
+
+ return snaps, nil
+}
diff -Nru snapd-2.32.3.2/store/storetest/storetest.go snapd-2.32.9/store/storetest/storetest.go
--- snapd-2.32.3.2/store/storetest/storetest.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/store/storetest/storetest.go 2018-05-16 08:20:08.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2014-2017 Canonical Ltd
+ * Copyright (C) 2014-2018 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -57,6 +57,10 @@
panic("Store.ListRefresh not expected")
}
+func (Store) SnapAction(context.Context, []*store.CurrentSnap, []*store.SnapAction, *auth.UserState, *store.RefreshOptions) ([]*snap.Info, error) {
+ panic("Store.SnapAction not expected")
+}
+
func (Store) Download(context.Context, string, string, *snap.DownloadInfo, progress.Meter, *auth.UserState) error {
panic("Store.Download not expected")
}
diff -Nru snapd-2.32.3.2/store/store_test.go snapd-2.32.9/store/store_test.go
--- snapd-2.32.3.2/store/store_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/store/store_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -131,6 +131,8 @@
ordersPath = "/api/v1/snaps/purchases/orders"
searchPath = "/api/v1/snaps/search"
sectionsPath = "/api/v1/snaps/sections"
+ // v2
+ snapActionPath = "/v2/snaps/refresh"
)
// Build details path for a snap name.
@@ -816,6 +818,7 @@
func (s *storeTestSuite) TestActualDownload(c *C) {
n := 0
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ c.Check(r.Header.Get("Snap-CDN"), Equals, "")
n++
io.WriteString(w, "response-data")
}))
@@ -832,6 +835,64 @@
c.Check(n, Equals, 1)
}
+func (s *storeTestSuite) TestActualDownloadNoCDN(c *C) {
+ os.Setenv("SNAPPY_STORE_NO_CDN", "1")
+ defer os.Unsetenv("SNAPPY_STORE_NO_CDN")
+
+ mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ c.Check(r.Header.Get("Snap-CDN"), Equals, "none")
+ io.WriteString(w, "response-data")
+ }))
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ theStore := New(&Config{}, nil)
+ var buf SillyBuffer
+ // keep tests happy
+ sha3 := ""
+ err := download(context.TODO(), "foo", sha3, mockServer.URL, nil, theStore, &buf, 0, nil)
+ c.Assert(err, IsNil)
+ c.Check(buf.String(), Equals, "response-data")
+}
+
+func (s *storeTestSuite) TestActualDownloadFullCloudInfoFromAuthContext(c *C) {
+ mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ c.Check(r.Header.Get("Snap-CDN"), Equals, `cloud-name="aws" region="us-east-1" availability-zone="us-east-1c"`)
+
+ io.WriteString(w, "response-data")
+ }))
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ theStore := New(&Config{}, &testAuthContext{c: c, device: s.device, cloudInfo: &auth.CloudInfo{Name: "aws", Region: "us-east-1", AvailabilityZone: "us-east-1c"}})
+
+ var buf SillyBuffer
+ // keep tests happy
+ sha3 := ""
+ err := download(context.TODO(), "foo", sha3, mockServer.URL, nil, theStore, &buf, 0, nil)
+ c.Assert(err, IsNil)
+ c.Check(buf.String(), Equals, "response-data")
+}
+
+func (s *storeTestSuite) TestActualDownloadLessDetailedCloudInfoFromAuthContext(c *C) {
+ mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ c.Check(r.Header.Get("Snap-CDN"), Equals, `cloud-name="openstack" availability-zone="nova"`)
+
+ io.WriteString(w, "response-data")
+ }))
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ theStore := New(&Config{}, &testAuthContext{c: c, device: s.device, cloudInfo: &auth.CloudInfo{Name: "openstack", Region: "", AvailabilityZone: "nova"}})
+
+ var buf SillyBuffer
+ // keep tests happy
+ sha3 := ""
+ err := download(context.TODO(), "foo", sha3, mockServer.URL, nil, theStore, &buf, 0, nil)
+ c.Assert(err, IsNil)
+ c.Check(buf.String(), Equals, "response-data")
+}
+
func (s *storeTestSuite) TestDownloadCancellation(c *C) {
// the channel used by mock server to request cancellation from the test
syncCh := make(chan struct{})
@@ -2303,8 +2364,6 @@
func (s *storeTestSuite) TestNonDefaults(c *C) {
restore := release.MockOnClassic(true)
defer restore()
- os.Setenv("SNAPPY_STORE_NO_CDN", "1")
- defer os.Unsetenv("SNAPPY_STORE_NO_CDN")
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assertRequest(c, r, "GET", detailsPathPattern)
@@ -2318,9 +2377,6 @@
c.Check(r.Header.Get("X-Ubuntu-Series"), Equals, "21")
c.Check(r.Header.Get("X-Ubuntu-Architecture"), Equals, "archXYZ")
c.Check(r.Header.Get("X-Ubuntu-Classic"), Equals, "true")
- // for now we have both
- c.Check(r.Header.Get("X-Ubuntu-No-CDN"), Equals, "true")
- c.Check(r.Header.Get("Snap-CDN"), Equals, "none")
w.WriteHeader(200)
io.WriteString(w, MockDetailsJSON)
@@ -2380,68 +2436,6 @@
c.Check(result.Name(), Equals, "hello-world")
}
-func (s *storeTestSuite) TestFullCloudInfoFromAuthContext(c *C) {
- mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- assertRequest(c, r, "GET", detailsPathPattern)
- c.Check(r.Header.Get("Snap-CDN"), Equals, `cloud-name="aws" region="us-east-1" availability-zone="us-east-1c"`)
-
- w.WriteHeader(200)
- io.WriteString(w, MockDetailsJSON)
- }))
-
- c.Assert(mockServer, NotNil)
- defer mockServer.Close()
-
- mockServerURL, _ := url.Parse(mockServer.URL)
- cfg := DefaultConfig()
- cfg.StoreBaseURL = mockServerURL
- cfg.Series = "21"
- cfg.Architecture = "archXYZ"
- cfg.StoreID = "fallback"
- sto := New(cfg, &testAuthContext{c: c, device: s.device, cloudInfo: &auth.CloudInfo{Name: "aws", Region: "us-east-1", AvailabilityZone: "us-east-1c"}})
-
- // the actual test
- spec := SnapSpec{
- Name: "hello-world",
- Channel: "edge",
- Revision: snap.R(0),
- }
- result, err := sto.SnapInfo(spec, nil)
- c.Assert(err, IsNil)
- c.Check(result.Name(), Equals, "hello-world")
-}
-
-func (s *storeTestSuite) TestLessDetailedCloudInfoFromAuthContext(c *C) {
- mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- assertRequest(c, r, "GET", detailsPathPattern)
- c.Check(r.Header.Get("Snap-CDN"), Equals, `cloud-name="openstack" availability-zone="nova"`)
-
- w.WriteHeader(200)
- io.WriteString(w, MockDetailsJSON)
- }))
-
- c.Assert(mockServer, NotNil)
- defer mockServer.Close()
-
- mockServerURL, _ := url.Parse(mockServer.URL)
- cfg := DefaultConfig()
- cfg.StoreBaseURL = mockServerURL
- cfg.Series = "21"
- cfg.Architecture = "archXYZ"
- cfg.StoreID = "fallback"
- sto := New(cfg, &testAuthContext{c: c, device: s.device, cloudInfo: &auth.CloudInfo{Name: "openstack", Region: "", AvailabilityZone: "nova"}})
-
- // the actual test
- spec := SnapSpec{
- Name: "hello-world",
- Channel: "edge",
- Revision: snap.R(0),
- }
- result, err := sto.SnapInfo(spec, nil)
- c.Assert(err, IsNil)
- c.Check(result.Name(), Equals, "hello-world")
-}
-
func (s *storeTestSuite) TestProxyStoreFromAuthContext(c *C) {
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assertRequest(c, r, "GET", detailsPathPattern)
@@ -5361,3 +5355,1566 @@
c.Check(obs.gets, DeepEquals, []string{fmt.Sprintf("the-snaps-sha3_384:%s", path)})
c.Check(obs.puts, DeepEquals, []string{fmt.Sprintf("the-snaps-sha3_384:%s", path)})
}
+
+var (
+ helloRefreshedDateStr = "2018-02-27T11:00:00Z"
+ helloRefreshedDate time.Time
+)
+
+func init() {
+ t, err := time.Parse(time.RFC3339, helloRefreshedDateStr)
+ if err != nil {
+ panic(err)
+ }
+ helloRefreshedDate = t
+}
+
+func (s *storeTestSuite) TestSnapAction(c *C) {
+ restore := release.MockOnClassic(false)
+ defer restore()
+
+ mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assertRequest(c, r, "POST", snapActionPath)
+ // check device authorization is set, implicitly checking doRequest was used
+ c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
+
+ c.Check(r.Header.Get("Snap-Refresh-Managed"), Equals, "")
+
+ // no store ID by default
+ storeID := r.Header.Get("Snap-Device-Store")
+ c.Check(storeID, Equals, "")
+
+ c.Check(r.Header.Get("Snap-Device-Series"), Equals, release.Series)
+ c.Check(r.Header.Get("Snap-Device-Architecture"), Equals, arch.UbuntuArchitecture())
+ c.Check(r.Header.Get("Snap-Classic"), Equals, "false")
+
+ jsonReq, err := ioutil.ReadAll(r.Body)
+ c.Assert(err, IsNil)
+ var req struct {
+ Context []map[string]interface{} `json:"context"`
+ Fields []string `json:"fields"`
+ Actions []map[string]interface{} `json:"actions"`
+ }
+
+ err = json.Unmarshal(jsonReq, &req)
+ c.Assert(err, IsNil)
+
+ c.Check(req.Fields, DeepEquals, snapActionFields)
+
+ c.Assert(req.Context, HasLen, 1)
+ c.Assert(req.Context[0], DeepEquals, map[string]interface{}{
+ "snap-id": helloWorldSnapID,
+ "instance-key": helloWorldSnapID,
+ "revision": float64(1),
+ "tracking-channel": "beta",
+ "refreshed-date": helloRefreshedDateStr,
+ })
+ c.Assert(req.Actions, HasLen, 1)
+ c.Assert(req.Actions[0], DeepEquals, map[string]interface{}{
+ "action": "refresh",
+ "instance-key": helloWorldSnapID,
+ "snap-id": helloWorldSnapID,
+ })
+
+ io.WriteString(w, `{
+ "results": [{
+ "result": "refresh",
+ "instance-key": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "snap": {
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "revision": 26,
+ "version": "6.1",
+ "publisher": {
+ "id": "canonical",
+ "username": "canonical",
+ "display-name": "Canonical"
+ }
+ }
+ }]
+}`)
+ }))
+
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ mockServerURL, _ := url.Parse(mockServer.URL)
+ cfg := Config{
+ StoreBaseURL: mockServerURL,
+ }
+ authContext := &testAuthContext{c: c, device: s.device}
+ sto := New(&cfg, authContext)
+
+ results, err := sto.SnapAction(context.TODO(), []*CurrentSnap{
+ {
+ Name: "hello-world",
+ SnapID: helloWorldSnapID,
+ TrackingChannel: "beta",
+ Revision: snap.R(1),
+ RefreshedDate: helloRefreshedDate,
+ },
+ }, []*SnapAction{
+ {
+ Action: "refresh",
+ SnapID: helloWorldSnapID,
+ },
+ }, nil, nil)
+ c.Assert(err, IsNil)
+ c.Assert(results, HasLen, 1)
+ c.Assert(results[0].Name(), Equals, "hello-world")
+ c.Assert(results[0].Revision, Equals, snap.R(26))
+ c.Assert(results[0].Version, Equals, "6.1")
+ c.Assert(results[0].SnapID, Equals, helloWorldSnapID)
+ c.Assert(results[0].PublisherID, Equals, helloWorldDeveloperID)
+ c.Assert(results[0].Deltas, HasLen, 0)
+}
+
+func (s *storeTestSuite) TestSnapActionNoResults(c *C) {
+ restore := release.MockOnClassic(false)
+ defer restore()
+
+ mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assertRequest(c, r, "POST", snapActionPath)
+ // check device authorization is set, implicitly checking doRequest was used
+ c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
+
+ jsonReq, err := ioutil.ReadAll(r.Body)
+ c.Assert(err, IsNil)
+ var req struct {
+ Context []map[string]interface{} `json:"context"`
+ Actions []map[string]interface{} `json:"actions"`
+ }
+
+ err = json.Unmarshal(jsonReq, &req)
+ c.Assert(err, IsNil)
+
+ c.Assert(req.Context, HasLen, 1)
+ c.Assert(req.Context[0], DeepEquals, map[string]interface{}{
+ "snap-id": helloWorldSnapID,
+ "instance-key": helloWorldSnapID,
+ "revision": float64(1),
+ "tracking-channel": "beta",
+ "refreshed-date": helloRefreshedDateStr,
+ })
+ c.Assert(req.Actions, HasLen, 0)
+ io.WriteString(w, `{
+ "results": []
+}`)
+ }))
+
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ mockServerURL, _ := url.Parse(mockServer.URL)
+ cfg := Config{
+ StoreBaseURL: mockServerURL,
+ }
+ authContext := &testAuthContext{c: c, device: s.device}
+ sto := New(&cfg, authContext)
+
+ results, err := sto.SnapAction(context.TODO(), []*CurrentSnap{
+ {
+ Name: "hello-world",
+ SnapID: helloWorldSnapID,
+ TrackingChannel: "beta",
+ Revision: snap.R(1),
+ RefreshedDate: helloRefreshedDate,
+ },
+ }, nil, nil, nil)
+ c.Check(results, HasLen, 0)
+ c.Check(err, DeepEquals, &SnapActionError{NoResults: true})
+
+ // local no-op
+ results, err = sto.SnapAction(context.TODO(), nil, nil, nil, nil)
+ c.Check(results, HasLen, 0)
+ c.Check(err, DeepEquals, &SnapActionError{NoResults: true})
+
+ c.Check(err.Error(), Equals, "no install/refresh information results from the store")
+}
+
+func (s *storeTestSuite) TestSnapActionRefreshedDateIsOptional(c *C) {
+ restore := release.MockOnClassic(false)
+ defer restore()
+
+ mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assertRequest(c, r, "POST", snapActionPath)
+ // check device authorization is set, implicitly checking doRequest was used
+ c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
+
+ jsonReq, err := ioutil.ReadAll(r.Body)
+ c.Assert(err, IsNil)
+ var req struct {
+ Context []map[string]interface{} `json:"context"`
+ Actions []map[string]interface{} `json:"actions"`
+ }
+
+ err = json.Unmarshal(jsonReq, &req)
+ c.Assert(err, IsNil)
+
+ c.Assert(req.Context, HasLen, 1)
+ c.Assert(req.Context[0], DeepEquals, map[string]interface{}{
+ "snap-id": helloWorldSnapID,
+ "instance-key": helloWorldSnapID,
+
+ "revision": float64(1),
+ "tracking-channel": "beta",
+ })
+ c.Assert(req.Actions, HasLen, 0)
+ io.WriteString(w, `{
+ "results": []
+}`)
+ }))
+
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ mockServerURL, _ := url.Parse(mockServer.URL)
+ cfg := Config{
+ StoreBaseURL: mockServerURL,
+ }
+ authContext := &testAuthContext{c: c, device: s.device}
+ sto := New(&cfg, authContext)
+
+ results, err := sto.SnapAction(context.TODO(), []*CurrentSnap{
+ {
+ Name: "hello-world",
+ SnapID: helloWorldSnapID,
+ TrackingChannel: "beta",
+ Revision: snap.R(1),
+ },
+ }, nil, nil, nil)
+ c.Check(results, HasLen, 0)
+ c.Check(err, DeepEquals, &SnapActionError{NoResults: true})
+}
+
+func (s *storeTestSuite) TestSnapActionSkipBlocked(c *C) {
+ mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assertRequest(c, r, "POST", snapActionPath)
+ // check device authorization is set, implicitly checking doRequest was used
+ c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
+
+ jsonReq, err := ioutil.ReadAll(r.Body)
+ c.Assert(err, IsNil)
+ var req struct {
+ Context []map[string]interface{} `json:"context"`
+ Actions []map[string]interface{} `json:"actions"`
+ }
+
+ err = json.Unmarshal(jsonReq, &req)
+ c.Assert(err, IsNil)
+
+ c.Assert(req.Context, HasLen, 1)
+ c.Assert(req.Context[0], DeepEquals, map[string]interface{}{
+ "snap-id": helloWorldSnapID,
+ "instance-key": helloWorldSnapID,
+ "revision": float64(1),
+ "tracking-channel": "stable",
+ "refreshed-date": helloRefreshedDateStr,
+ })
+ c.Assert(req.Actions, HasLen, 1)
+ c.Assert(req.Actions[0], DeepEquals, map[string]interface{}{
+ "action": "refresh",
+ "instance-key": helloWorldSnapID,
+ "snap-id": helloWorldSnapID,
+ "channel": "stable",
+ })
+
+ io.WriteString(w, `{
+ "results": [{
+ "result": "refresh",
+ "instance-key": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "snap": {
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "revision": 26,
+ "version": "6.1",
+ "publisher": {
+ "id": "canonical",
+ "username": "canonical",
+ "display-name": "Canonical"
+ }
+ }
+ }]
+}`)
+ }))
+
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ mockServerURL, _ := url.Parse(mockServer.URL)
+ cfg := Config{
+ StoreBaseURL: mockServerURL,
+ }
+ authContext := &testAuthContext{c: c, device: s.device}
+ sto := New(&cfg, authContext)
+
+ results, err := sto.SnapAction(context.TODO(), []*CurrentSnap{
+ {
+ Name: "hello-world",
+ SnapID: helloWorldSnapID,
+ TrackingChannel: "stable",
+ Revision: snap.R(1),
+ RefreshedDate: helloRefreshedDate,
+ Block: []snap.Revision{snap.R(26)},
+ },
+ }, []*SnapAction{
+ {
+ Action: "refresh",
+ SnapID: helloWorldSnapID,
+ Channel: "stable",
+ },
+ }, nil, nil)
+ c.Assert(results, HasLen, 0)
+ c.Check(err, DeepEquals, &SnapActionError{
+ Refresh: map[string]error{
+ "hello-world": ErrNoUpdateAvailable,
+ },
+ })
+}
+
+func (s *storeTestSuite) TestSnapActionSkipCurrent(c *C) {
+ mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assertRequest(c, r, "POST", snapActionPath)
+ // check device authorization is set, implicitly checking doRequest was used
+ c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
+
+ jsonReq, err := ioutil.ReadAll(r.Body)
+ c.Assert(err, IsNil)
+ var req struct {
+ Context []map[string]interface{} `json:"context"`
+ Actions []map[string]interface{} `json:"actions"`
+ }
+
+ err = json.Unmarshal(jsonReq, &req)
+ c.Assert(err, IsNil)
+
+ c.Assert(req.Context, HasLen, 1)
+ c.Assert(req.Context[0], DeepEquals, map[string]interface{}{
+ "snap-id": helloWorldSnapID,
+ "instance-key": helloWorldSnapID,
+ "revision": float64(26),
+ "tracking-channel": "stable",
+ "refreshed-date": helloRefreshedDateStr,
+ })
+ c.Assert(req.Actions, HasLen, 1)
+ c.Assert(req.Actions[0], DeepEquals, map[string]interface{}{
+ "action": "refresh",
+ "instance-key": helloWorldSnapID,
+ "snap-id": helloWorldSnapID,
+ "channel": "stable",
+ })
+
+ io.WriteString(w, `{
+ "results": [{
+ "result": "refresh",
+ "instance-key": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "snap": {
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "revision": 26,
+ "version": "6.1",
+ "publisher": {
+ "id": "canonical",
+ "username": "canonical",
+ "display-name": "Canonical"
+ }
+ }
+ }]
+}`)
+ }))
+
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ mockServerURL, _ := url.Parse(mockServer.URL)
+ cfg := Config{
+ StoreBaseURL: mockServerURL,
+ }
+ authContext := &testAuthContext{c: c, device: s.device}
+ sto := New(&cfg, authContext)
+
+ results, err := sto.SnapAction(context.TODO(), []*CurrentSnap{
+ {
+ Name: "hello-world",
+ SnapID: helloWorldSnapID,
+ TrackingChannel: "stable",
+ Revision: snap.R(26),
+ RefreshedDate: helloRefreshedDate,
+ },
+ }, []*SnapAction{
+ {
+ Action: "refresh",
+ SnapID: helloWorldSnapID,
+ Channel: "stable",
+ },
+ }, nil, nil)
+ c.Assert(results, HasLen, 0)
+ c.Check(err, DeepEquals, &SnapActionError{
+ Refresh: map[string]error{
+ "hello-world": ErrNoUpdateAvailable,
+ },
+ })
+}
+
+func (s *storeTestSuite) TestSnapActionRetryOnEOF(c *C) {
+ n := 0
+ var mockServer *httptest.Server
+ mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assertRequest(c, r, "POST", snapActionPath)
+ n++
+ if n < 4 {
+ io.WriteString(w, "{")
+ mockServer.CloseClientConnections()
+ return
+ }
+
+ var req struct {
+ Context []map[string]interface{} `json:"context"`
+ Actions []map[string]interface{} `json:"actions"`
+ }
+
+ err := json.NewDecoder(r.Body).Decode(&req)
+ c.Assert(err, IsNil)
+ c.Assert(req.Context, HasLen, 1)
+ c.Assert(req.Actions, HasLen, 1)
+ io.WriteString(w, `{
+ "results": [{
+ "result": "refresh",
+ "instance-key": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "snap": {
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "revision": 26,
+ "version": "6.1",
+ "publisher": {
+ "id": "canonical",
+ "username": "canonical",
+ "display-name": "Canonical"
+ }
+ }
+ }]
+}`)
+ }))
+
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ mockServerURL, _ := url.Parse(mockServer.URL)
+ cfg := Config{
+ StoreBaseURL: mockServerURL,
+ }
+ authContext := &testAuthContext{c: c, device: s.device}
+ sto := New(&cfg, authContext)
+
+ results, err := sto.SnapAction(context.TODO(), []*CurrentSnap{
+ {
+ Name: "hello-world",
+ SnapID: helloWorldSnapID,
+ TrackingChannel: "stable",
+ Revision: snap.R(1),
+ },
+ }, []*SnapAction{
+ {
+ Action: "refresh",
+ SnapID: helloWorldSnapID,
+ Channel: "stable",
+ },
+ }, nil, nil)
+ c.Assert(err, IsNil)
+ c.Assert(n, Equals, 4)
+ c.Assert(results, HasLen, 1)
+ c.Assert(results[0].Name(), Equals, "hello-world")
+}
+
+func (s *storeTestSuite) TestSnapActionIgnoreValidation(c *C) {
+ mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assertRequest(c, r, "POST", snapActionPath)
+ // check device authorization is set, implicitly checking doRequest was used
+ c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
+
+ jsonReq, err := ioutil.ReadAll(r.Body)
+ c.Assert(err, IsNil)
+ var req struct {
+ Context []map[string]interface{} `json:"context"`
+ Actions []map[string]interface{} `json:"actions"`
+ }
+
+ err = json.Unmarshal(jsonReq, &req)
+ c.Assert(err, IsNil)
+
+ c.Assert(req.Context, HasLen, 1)
+ c.Assert(req.Context[0], DeepEquals, map[string]interface{}{
+ "snap-id": helloWorldSnapID,
+ "instance-key": helloWorldSnapID,
+ "revision": float64(1),
+ "tracking-channel": "stable",
+ "refreshed-date": helloRefreshedDateStr,
+ "ignore-validation": true,
+ })
+ c.Assert(req.Actions, HasLen, 1)
+ c.Assert(req.Actions[0], DeepEquals, map[string]interface{}{
+ "action": "refresh",
+ "instance-key": helloWorldSnapID,
+ "snap-id": helloWorldSnapID,
+ "channel": "stable",
+ "ignore-validation": false,
+ })
+
+ io.WriteString(w, `{
+ "results": [{
+ "result": "refresh",
+ "instance-key": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "snap": {
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "revision": 26,
+ "version": "6.1",
+ "publisher": {
+ "id": "canonical",
+ "username": "canonical",
+ "display-name": "Canonical"
+ }
+ }
+ }]
+}`)
+ }))
+
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ mockServerURL, _ := url.Parse(mockServer.URL)
+ cfg := Config{
+ StoreBaseURL: mockServerURL,
+ }
+ authContext := &testAuthContext{c: c, device: s.device}
+ sto := New(&cfg, authContext)
+
+ results, err := sto.SnapAction(context.TODO(), []*CurrentSnap{
+ {
+ Name: "hello-world",
+ SnapID: helloWorldSnapID,
+ TrackingChannel: "stable",
+ Revision: snap.R(1),
+ RefreshedDate: helloRefreshedDate,
+ IgnoreValidation: true,
+ },
+ }, []*SnapAction{
+ {
+ Action: "refresh",
+ SnapID: helloWorldSnapID,
+ Channel: "stable",
+ Flags: SnapActionEnforceValidation,
+ },
+ }, nil, nil)
+ c.Assert(err, IsNil)
+ c.Assert(results, HasLen, 1)
+ c.Assert(results[0].Name(), Equals, "hello-world")
+ c.Assert(results[0].Revision, Equals, snap.R(26))
+}
+
+func (s *storeTestSuite) TestInstallFallbackChannelIsStable(c *C) {
+ mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assertRequest(c, r, "POST", snapActionPath)
+ // check device authorization is set, implicitly checking doRequest was used
+ c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
+
+ jsonReq, err := ioutil.ReadAll(r.Body)
+ c.Assert(err, IsNil)
+ var req struct {
+ Context []map[string]interface{} `json:"context"`
+ Actions []map[string]interface{} `json:"actions"`
+ }
+
+ err = json.Unmarshal(jsonReq, &req)
+ c.Assert(err, IsNil)
+
+ c.Assert(req.Context, HasLen, 1)
+ c.Assert(req.Context[0], DeepEquals, map[string]interface{}{
+ "snap-id": helloWorldSnapID,
+ "instance-key": helloWorldSnapID,
+ "revision": float64(1),
+ "tracking-channel": "stable",
+ "refreshed-date": helloRefreshedDateStr,
+ })
+ c.Assert(req.Actions, HasLen, 1)
+ c.Assert(req.Actions[0], DeepEquals, map[string]interface{}{
+ "action": "refresh",
+ "instance-key": helloWorldSnapID,
+ "snap-id": helloWorldSnapID,
+ })
+
+ io.WriteString(w, `{
+ "results": [{
+ "result": "refresh",
+ "instance-key": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "snap": {
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "revision": 26,
+ "version": "6.1",
+ "publisher": {
+ "id": "canonical",
+ "username": "canonical",
+ "display-name": "Canonical"
+ }
+ }
+ }]
+}`)
+ }))
+
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ mockServerURL, _ := url.Parse(mockServer.URL)
+ cfg := Config{
+ StoreBaseURL: mockServerURL,
+ }
+ authContext := &testAuthContext{c: c, device: s.device}
+ sto := New(&cfg, authContext)
+
+ results, err := sto.SnapAction(context.TODO(), []*CurrentSnap{
+ {
+ Name: "hello-world",
+ SnapID: helloWorldSnapID,
+ RefreshedDate: helloRefreshedDate,
+ Revision: snap.R(1),
+ },
+ }, []*SnapAction{
+ {
+ Action: "refresh",
+ SnapID: helloWorldSnapID,
+ },
+ }, nil, nil)
+ c.Assert(err, IsNil)
+ c.Assert(results, HasLen, 1)
+ c.Assert(results[0].Name(), Equals, "hello-world")
+ c.Assert(results[0].Revision, Equals, snap.R(26))
+ c.Assert(results[0].SnapID, Equals, helloWorldSnapID)
+}
+
+func (s *storeTestSuite) TestSnapActionNonDefaultsHeaders(c *C) {
+ restore := release.MockOnClassic(true)
+ defer restore()
+
+ mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assertRequest(c, r, "POST", snapActionPath)
+ // check device authorization is set, implicitly checking doRequest was used
+ c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
+
+ storeID := r.Header.Get("Snap-Device-Store")
+ c.Check(storeID, Equals, "foo")
+
+ c.Check(r.Header.Get("Snap-Device-Series"), Equals, "21")
+ c.Check(r.Header.Get("Snap-Device-Architecture"), Equals, "archXYZ")
+ c.Check(r.Header.Get("Snap-Classic"), Equals, "true")
+
+ jsonReq, err := ioutil.ReadAll(r.Body)
+ c.Assert(err, IsNil)
+ var req struct {
+ Context []map[string]interface{} `json:"context"`
+ Actions []map[string]interface{} `json:"actions"`
+ }
+
+ err = json.Unmarshal(jsonReq, &req)
+ c.Assert(err, IsNil)
+
+ c.Assert(req.Context, HasLen, 1)
+ c.Assert(req.Context[0], DeepEquals, map[string]interface{}{
+ "snap-id": helloWorldSnapID,
+ "instance-key": helloWorldSnapID,
+ "revision": float64(1),
+ "tracking-channel": "beta",
+ "refreshed-date": helloRefreshedDateStr,
+ })
+ c.Assert(req.Actions, HasLen, 1)
+ c.Assert(req.Actions[0], DeepEquals, map[string]interface{}{
+ "action": "refresh",
+ "instance-key": helloWorldSnapID,
+ "snap-id": helloWorldSnapID,
+ })
+
+ io.WriteString(w, `{
+ "results": [{
+ "result": "refresh",
+ "instance-key": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "snap": {
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "revision": 26,
+ "version": "6.1",
+ "publisher": {
+ "id": "canonical",
+ "username": "canonical",
+ "display-name": "Canonical"
+ }
+ }
+ }]
+}`)
+ }))
+
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ mockServerURL, _ := url.Parse(mockServer.URL)
+ cfg := DefaultConfig()
+ cfg.StoreBaseURL = mockServerURL
+ cfg.Series = "21"
+ cfg.Architecture = "archXYZ"
+ cfg.StoreID = "foo"
+ authContext := &testAuthContext{c: c, device: s.device}
+ sto := New(cfg, authContext)
+
+ results, err := sto.SnapAction(context.TODO(), []*CurrentSnap{
+ {
+ Name: "hello-world",
+ SnapID: helloWorldSnapID,
+ TrackingChannel: "beta",
+ RefreshedDate: helloRefreshedDate,
+ Revision: snap.R(1),
+ },
+ }, []*SnapAction{
+ {
+ Action: "refresh",
+ SnapID: helloWorldSnapID,
+ },
+ }, nil, nil)
+ c.Assert(err, IsNil)
+ c.Assert(results, HasLen, 1)
+ c.Assert(results[0].Name(), Equals, "hello-world")
+ c.Assert(results[0].Revision, Equals, snap.R(26))
+ c.Assert(results[0].Version, Equals, "6.1")
+ c.Assert(results[0].SnapID, Equals, helloWorldSnapID)
+ c.Assert(results[0].PublisherID, Equals, helloWorldDeveloperID)
+ c.Assert(results[0].Deltas, HasLen, 0)
+}
+
+func (s *storeTestSuite) TestSnapActionWithDeltas(c *C) {
+ origUseDeltas := os.Getenv("SNAPD_USE_DELTAS_EXPERIMENTAL")
+ defer os.Setenv("SNAPD_USE_DELTAS_EXPERIMENTAL", origUseDeltas)
+ c.Assert(os.Setenv("SNAPD_USE_DELTAS_EXPERIMENTAL", "1"), IsNil)
+
+ mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assertRequest(c, r, "POST", snapActionPath)
+ // check device authorization is set, implicitly checking doRequest was used
+ c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
+
+ c.Check(r.Header.Get("Snap-Accept-Delta-Format"), Equals, "xdelta3")
+ jsonReq, err := ioutil.ReadAll(r.Body)
+ c.Assert(err, IsNil)
+ var req struct {
+ Context []map[string]interface{} `json:"context"`
+ Actions []map[string]interface{} `json:"actions"`
+ }
+
+ err = json.Unmarshal(jsonReq, &req)
+ c.Assert(err, IsNil)
+
+ c.Assert(req.Context, HasLen, 1)
+ c.Assert(req.Context[0], DeepEquals, map[string]interface{}{
+ "snap-id": helloWorldSnapID,
+ "instance-key": helloWorldSnapID,
+ "revision": float64(1),
+ "tracking-channel": "beta",
+ "refreshed-date": helloRefreshedDateStr,
+ })
+ c.Assert(req.Actions, HasLen, 1)
+ c.Assert(req.Actions[0], DeepEquals, map[string]interface{}{
+ "action": "refresh",
+ "instance-key": helloWorldSnapID,
+ "snap-id": helloWorldSnapID,
+ })
+
+ io.WriteString(w, `{
+ "results": [{
+ "result": "refresh",
+ "instance-key": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "snap": {
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "revision": 26,
+ "version": "6.1",
+ "publisher": {
+ "id": "canonical",
+ "username": "canonical",
+ "display-name": "Canonical"
+ }
+ }
+ }]
+}`)
+ }))
+
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ mockServerURL, _ := url.Parse(mockServer.URL)
+ cfg := Config{
+ StoreBaseURL: mockServerURL,
+ }
+ authContext := &testAuthContext{c: c, device: s.device}
+ sto := New(&cfg, authContext)
+
+ results, err := sto.SnapAction(context.TODO(), []*CurrentSnap{
+ {
+ Name: "hello-world",
+ SnapID: helloWorldSnapID,
+ TrackingChannel: "beta",
+ Revision: snap.R(1),
+ RefreshedDate: helloRefreshedDate,
+ },
+ }, []*SnapAction{
+ {
+ Action: "refresh",
+ SnapID: helloWorldSnapID,
+ },
+ }, nil, nil)
+ c.Assert(err, IsNil)
+ c.Assert(results, HasLen, 1)
+ c.Assert(results[0].Name(), Equals, "hello-world")
+ c.Assert(results[0].Revision, Equals, snap.R(26))
+}
+
+func (s *storeTestSuite) TestSnapActionOptions(c *C) {
+ mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assertRequest(c, r, "POST", snapActionPath)
+ // check device authorization is set, implicitly checking doRequest was used
+ c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
+
+ c.Check(r.Header.Get("Snap-Refresh-Managed"), Equals, "true")
+
+ jsonReq, err := ioutil.ReadAll(r.Body)
+ c.Assert(err, IsNil)
+ var req struct {
+ Context []map[string]interface{} `json:"context"`
+ Actions []map[string]interface{} `json:"actions"`
+ }
+
+ err = json.Unmarshal(jsonReq, &req)
+ c.Assert(err, IsNil)
+
+ c.Assert(req.Context, HasLen, 1)
+ c.Assert(req.Context[0], DeepEquals, map[string]interface{}{
+ "snap-id": helloWorldSnapID,
+ "instance-key": helloWorldSnapID,
+ "revision": float64(1),
+ "tracking-channel": "stable",
+ "refreshed-date": helloRefreshedDateStr,
+ })
+ c.Assert(req.Actions, HasLen, 1)
+ c.Assert(req.Actions[0], DeepEquals, map[string]interface{}{
+ "action": "refresh",
+ "instance-key": helloWorldSnapID,
+ "snap-id": helloWorldSnapID,
+ "channel": "stable",
+ })
+
+ io.WriteString(w, `{
+ "results": [{
+ "result": "refresh",
+ "instance-key": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "snap": {
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "revision": 26,
+ "version": "6.1",
+ "publisher": {
+ "id": "canonical",
+ "username": "canonical",
+ "display-name": "Canonical"
+ }
+ }
+ }]
+}`)
+ }))
+
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ mockServerURL, _ := url.Parse(mockServer.URL)
+ cfg := Config{
+ StoreBaseURL: mockServerURL,
+ }
+ authContext := &testAuthContext{c: c, device: s.device}
+ sto := New(&cfg, authContext)
+
+ results, err := sto.SnapAction(context.TODO(), []*CurrentSnap{
+ {
+ Name: "hello-world",
+ SnapID: helloWorldSnapID,
+ TrackingChannel: "stable",
+ Revision: snap.R(1),
+ RefreshedDate: helloRefreshedDate,
+ },
+ }, []*SnapAction{
+ {
+ Action: "refresh",
+ SnapID: helloWorldSnapID,
+ Channel: "stable",
+ },
+ }, nil, &RefreshOptions{RefreshManaged: true})
+ c.Assert(err, IsNil)
+ c.Assert(results, HasLen, 1)
+ c.Assert(results[0].Name(), Equals, "hello-world")
+ c.Assert(results[0].Revision, Equals, snap.R(26))
+}
+
+func (s *storeTestSuite) TestSnapActionInstall(c *C) {
+ restore := release.MockOnClassic(false)
+ defer restore()
+
+ mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assertRequest(c, r, "POST", snapActionPath)
+ // check device authorization is set, implicitly checking doRequest was used
+ c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
+
+ c.Check(r.Header.Get("Snap-Refresh-Managed"), Equals, "")
+
+ // no store ID by default
+ storeID := r.Header.Get("Snap-Device-Store")
+ c.Check(storeID, Equals, "")
+
+ c.Check(r.Header.Get("Snap-Device-Series"), Equals, release.Series)
+ c.Check(r.Header.Get("Snap-Device-Architecture"), Equals, arch.UbuntuArchitecture())
+ c.Check(r.Header.Get("Snap-Classic"), Equals, "false")
+
+ jsonReq, err := ioutil.ReadAll(r.Body)
+ c.Assert(err, IsNil)
+ var req struct {
+ Context []map[string]interface{} `json:"context"`
+ Actions []map[string]interface{} `json:"actions"`
+ }
+
+ err = json.Unmarshal(jsonReq, &req)
+ c.Assert(err, IsNil)
+
+ c.Assert(req.Context, HasLen, 0)
+ c.Assert(req.Actions, HasLen, 1)
+ c.Assert(req.Actions[0], DeepEquals, map[string]interface{}{
+ "action": "install",
+ "instance-key": "install-1",
+ "name": "hello-world",
+ "channel": "beta",
+ })
+
+ io.WriteString(w, `{
+ "results": [{
+ "result": "install",
+ "instance-key": "install-1",
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "effective-channel": "candidate",
+ "snap": {
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "revision": 26,
+ "version": "6.1",
+ "publisher": {
+ "id": "canonical",
+ "username": "canonical",
+ "display-name": "Canonical"
+ }
+ }
+ }]
+}`)
+ }))
+
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ mockServerURL, _ := url.Parse(mockServer.URL)
+ cfg := Config{
+ StoreBaseURL: mockServerURL,
+ }
+ authContext := &testAuthContext{c: c, device: s.device}
+ sto := New(&cfg, authContext)
+
+ results, err := sto.SnapAction(context.TODO(), nil,
+ []*SnapAction{
+ {
+ Action: "install",
+ Name: "hello-world",
+ Channel: "beta",
+ },
+ }, nil, nil)
+ c.Assert(err, IsNil)
+ c.Assert(results, HasLen, 1)
+ c.Assert(results[0].Name(), Equals, "hello-world")
+ c.Assert(results[0].Revision, Equals, snap.R(26))
+ c.Assert(results[0].Version, Equals, "6.1")
+ c.Assert(results[0].SnapID, Equals, helloWorldSnapID)
+ c.Assert(results[0].PublisherID, Equals, helloWorldDeveloperID)
+ c.Assert(results[0].Deltas, HasLen, 0)
+ // effective-channel
+ c.Assert(results[0].Channel, Equals, "candidate")
+}
+
+func (s *storeTestSuite) TestSnapActionInstallWithRevision(c *C) {
+ restore := release.MockOnClassic(false)
+ defer restore()
+
+ mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assertRequest(c, r, "POST", snapActionPath)
+ // check device authorization is set, implicitly checking doRequest was used
+ c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
+
+ c.Check(r.Header.Get("Snap-Refresh-Managed"), Equals, "")
+
+ // no store ID by default
+ storeID := r.Header.Get("Snap-Device-Store")
+ c.Check(storeID, Equals, "")
+
+ c.Check(r.Header.Get("Snap-Device-Series"), Equals, release.Series)
+ c.Check(r.Header.Get("Snap-Device-Architecture"), Equals, arch.UbuntuArchitecture())
+ c.Check(r.Header.Get("Snap-Classic"), Equals, "false")
+
+ jsonReq, err := ioutil.ReadAll(r.Body)
+ c.Assert(err, IsNil)
+ var req struct {
+ Context []map[string]interface{} `json:"context"`
+ Actions []map[string]interface{} `json:"actions"`
+ }
+
+ err = json.Unmarshal(jsonReq, &req)
+ c.Assert(err, IsNil)
+
+ c.Assert(req.Context, HasLen, 0)
+ c.Assert(req.Actions, HasLen, 1)
+ c.Assert(req.Actions[0], DeepEquals, map[string]interface{}{
+ "action": "install",
+ "instance-key": "install-1",
+ "name": "hello-world",
+ "revision": float64(28),
+ })
+
+ io.WriteString(w, `{
+ "results": [{
+ "result": "install",
+ "instance-key": "install-1",
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "snap": {
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "revision": 28,
+ "version": "6.1",
+ "publisher": {
+ "id": "canonical",
+ "username": "canonical",
+ "display-name": "Canonical"
+ }
+ }
+ }]
+}`)
+ }))
+
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ mockServerURL, _ := url.Parse(mockServer.URL)
+ cfg := Config{
+ StoreBaseURL: mockServerURL,
+ }
+ authContext := &testAuthContext{c: c, device: s.device}
+ sto := New(&cfg, authContext)
+
+ results, err := sto.SnapAction(context.TODO(), nil,
+ []*SnapAction{
+ {
+ Action: "install",
+ Name: "hello-world",
+ Revision: snap.R(28),
+ },
+ }, nil, nil)
+ c.Assert(err, IsNil)
+ c.Assert(results, HasLen, 1)
+ c.Assert(results[0].Name(), Equals, "hello-world")
+ c.Assert(results[0].Revision, Equals, snap.R(28))
+ c.Assert(results[0].Version, Equals, "6.1")
+ c.Assert(results[0].SnapID, Equals, helloWorldSnapID)
+ c.Assert(results[0].PublisherID, Equals, helloWorldDeveloperID)
+ c.Assert(results[0].Deltas, HasLen, 0)
+ // effective-channel is not set
+ c.Assert(results[0].Channel, Equals, "")
+}
+
+func (s *storeTestSuite) TestSnapActionRevisionNotAvailable(c *C) {
+ mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assertRequest(c, r, "POST", snapActionPath)
+ // check device authorization is set, implicitly checking doRequest was used
+ c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
+
+ jsonReq, err := ioutil.ReadAll(r.Body)
+ c.Assert(err, IsNil)
+ var req struct {
+ Context []map[string]interface{} `json:"context"`
+ Actions []map[string]interface{} `json:"actions"`
+ }
+
+ err = json.Unmarshal(jsonReq, &req)
+ c.Assert(err, IsNil)
+
+ c.Assert(req.Context, HasLen, 1)
+ c.Assert(req.Context[0], DeepEquals, map[string]interface{}{
+ "snap-id": helloWorldSnapID,
+ "instance-key": helloWorldSnapID,
+ "revision": float64(26),
+ "tracking-channel": "stable",
+ "refreshed-date": helloRefreshedDateStr,
+ })
+ c.Assert(req.Actions, HasLen, 2)
+ c.Assert(req.Actions[0], DeepEquals, map[string]interface{}{
+ "action": "refresh",
+ "instance-key": helloWorldSnapID,
+ "snap-id": helloWorldSnapID,
+ "channel": "stable",
+ })
+ c.Assert(req.Actions[1], DeepEquals, map[string]interface{}{
+ "action": "install",
+ "instance-key": "install-1",
+ "name": "foo",
+ "channel": "stable",
+ })
+
+ io.WriteString(w, `{
+ "results": [{
+ "result": "error",
+ "instance-key": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "error": {
+ "code": "revision-not-found",
+ "message": "msg1"
+ }
+ }, {
+ "result": "error",
+ "instance-key": "install-1",
+ "snap-id": "foo-id",
+ "name": "foo",
+ "error": {
+ "code": "revision-not-found",
+ "message": "msg2"
+ }
+ }]
+}`)
+ }))
+
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ mockServerURL, _ := url.Parse(mockServer.URL)
+ cfg := Config{
+ StoreBaseURL: mockServerURL,
+ }
+ authContext := &testAuthContext{c: c, device: s.device}
+ sto := New(&cfg, authContext)
+
+ results, err := sto.SnapAction(context.TODO(), []*CurrentSnap{
+ {
+ Name: "hello-world",
+ SnapID: helloWorldSnapID,
+ TrackingChannel: "stable",
+ Revision: snap.R(26),
+ RefreshedDate: helloRefreshedDate,
+ },
+ }, []*SnapAction{
+ {
+ Action: "refresh",
+ SnapID: helloWorldSnapID,
+ Channel: "stable",
+ }, {
+ Action: "install",
+ Name: "foo",
+ Channel: "stable",
+ },
+ }, nil, nil)
+ c.Assert(results, HasLen, 0)
+ c.Check(err, DeepEquals, &SnapActionError{
+ Refresh: map[string]error{
+ "hello-world": ErrRevisionNotAvailable,
+ },
+ Install: map[string]error{
+ "foo": ErrRevisionNotAvailable,
+ },
+ })
+}
+
+func (s *storeTestSuite) TestSnapActionSnapNotFound(c *C) {
+ mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assertRequest(c, r, "POST", snapActionPath)
+ // check device authorization is set, implicitly checking doRequest was used
+ c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
+
+ jsonReq, err := ioutil.ReadAll(r.Body)
+ c.Assert(err, IsNil)
+ var req struct {
+ Context []map[string]interface{} `json:"context"`
+ Actions []map[string]interface{} `json:"actions"`
+ }
+
+ err = json.Unmarshal(jsonReq, &req)
+ c.Assert(err, IsNil)
+
+ c.Assert(req.Context, HasLen, 1)
+ c.Assert(req.Context[0], DeepEquals, map[string]interface{}{
+ "snap-id": helloWorldSnapID,
+ "instance-key": helloWorldSnapID,
+ "revision": float64(26),
+ "tracking-channel": "stable",
+ "refreshed-date": helloRefreshedDateStr,
+ })
+ c.Assert(req.Actions, HasLen, 2)
+ c.Assert(req.Actions[0], DeepEquals, map[string]interface{}{
+ "action": "refresh",
+ "instance-key": helloWorldSnapID,
+ "snap-id": helloWorldSnapID,
+ "channel": "stable",
+ })
+ c.Assert(req.Actions[1], DeepEquals, map[string]interface{}{
+ "action": "install",
+ "instance-key": "install-1",
+ "name": "foo",
+ "channel": "stable",
+ })
+
+ io.WriteString(w, `{
+ "results": [{
+ "result": "error",
+ "instance-key": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "error": {
+ "code": "id-not-found",
+ "message": "msg1"
+ }
+ }, {
+ "result": "error",
+ "instance-key": "install-1",
+ "name": "foo",
+ "error": {
+ "code": "name-not-found",
+ "message": "msg2"
+ }
+ }]
+}`)
+ }))
+
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ mockServerURL, _ := url.Parse(mockServer.URL)
+ cfg := Config{
+ StoreBaseURL: mockServerURL,
+ }
+ authContext := &testAuthContext{c: c, device: s.device}
+ sto := New(&cfg, authContext)
+
+ results, err := sto.SnapAction(context.TODO(), []*CurrentSnap{
+ {
+ Name: "hello-world",
+ SnapID: helloWorldSnapID,
+ TrackingChannel: "stable",
+ Revision: snap.R(26),
+ RefreshedDate: helloRefreshedDate,
+ },
+ }, []*SnapAction{
+ {
+ Action: "refresh",
+ SnapID: helloWorldSnapID,
+ Channel: "stable",
+ }, {
+ Action: "install",
+ Name: "foo",
+ Channel: "stable",
+ },
+ }, nil, nil)
+ c.Assert(results, HasLen, 0)
+ c.Check(err, DeepEquals, &SnapActionError{
+ Refresh: map[string]error{
+ "hello-world": ErrSnapNotFound,
+ },
+ Install: map[string]error{
+ "foo": ErrSnapNotFound,
+ },
+ })
+}
+
+func (s *storeTestSuite) TestSnapActionOtherErrors(c *C) {
+ mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assertRequest(c, r, "POST", snapActionPath)
+ // check device authorization is set, implicitly checking doRequest was used
+ c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
+
+ jsonReq, err := ioutil.ReadAll(r.Body)
+ c.Assert(err, IsNil)
+ var req struct {
+ Context []map[string]interface{} `json:"context"`
+ Actions []map[string]interface{} `json:"actions"`
+ }
+
+ err = json.Unmarshal(jsonReq, &req)
+ c.Assert(err, IsNil)
+
+ c.Assert(req.Context, HasLen, 0)
+ c.Assert(req.Actions, HasLen, 1)
+ c.Assert(req.Actions[0], DeepEquals, map[string]interface{}{
+ "action": "install",
+ "instance-key": "install-1",
+ "name": "foo",
+ "channel": "stable",
+ })
+
+ io.WriteString(w, `{
+ "results": [{
+ "result": "error",
+ "error": {
+ "code": "other1",
+ "message": "other error one"
+ }
+ }],
+ "error-list": [
+ {"code": "global-error", "message": "global error"}
+ ]
+}`)
+ }))
+
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ mockServerURL, _ := url.Parse(mockServer.URL)
+ cfg := Config{
+ StoreBaseURL: mockServerURL,
+ }
+ authContext := &testAuthContext{c: c, device: s.device}
+ sto := New(&cfg, authContext)
+
+ results, err := sto.SnapAction(context.TODO(), nil, []*SnapAction{
+ {
+ Action: "install",
+ Name: "foo",
+ Channel: "stable",
+ },
+ }, nil, nil)
+ c.Assert(results, HasLen, 0)
+ c.Check(err, DeepEquals, &SnapActionError{
+ Other: []error{
+ fmt.Errorf("other error one"),
+ fmt.Errorf("global error"),
+ },
+ })
+}
+
+func (s *storeTestSuite) TestSnapActionErrorError(c *C) {
+ e := &SnapActionError{Refresh: map[string]error{
+ "foo": fmt.Errorf("sad refresh"),
+ }}
+ c.Check(e.Error(), Equals, `cannot refresh snap "foo": sad refresh`)
+
+ e = &SnapActionError{Refresh: map[string]error{
+ "foo": fmt.Errorf("sad refresh 1"),
+ "bar": fmt.Errorf("sad refresh 2"),
+ }}
+ errMsg := e.Error()
+ c.Check(strings.HasPrefix(errMsg, "cannot refresh:\n"), Equals, true)
+ c.Check(errMsg, testutil.Contains, "\nsnap \"foo\": sad refresh 1")
+ c.Check(errMsg, testutil.Contains, "\nsnap \"bar\": sad refresh 2")
+
+ e = &SnapActionError{Install: map[string]error{
+ "foo": fmt.Errorf("sad install"),
+ }}
+ c.Check(e.Error(), Equals, `cannot install snap "foo": sad install`)
+
+ e = &SnapActionError{Install: map[string]error{
+ "foo": fmt.Errorf("sad install 1"),
+ "bar": fmt.Errorf("sad install 2"),
+ }}
+ errMsg = e.Error()
+ c.Check(strings.HasPrefix(errMsg, "cannot install:\n"), Equals, true)
+ c.Check(errMsg, testutil.Contains, "\nsnap \"foo\": sad install 1")
+ c.Check(errMsg, testutil.Contains, "\nsnap \"bar\": sad install 2")
+
+ e = &SnapActionError{Refresh: map[string]error{
+ "foo": fmt.Errorf("sad refresh 1"),
+ },
+ Install: map[string]error{
+ "bar": fmt.Errorf("sad install 2"),
+ }}
+ c.Check(e.Error(), Equals, `cannot refresh or install:
+snap "foo": sad refresh 1
+snap "bar": sad install 2`)
+
+ e = &SnapActionError{
+ NoResults: true,
+ Other: []error{fmt.Errorf("other error")},
+ }
+ c.Check(e.Error(), Equals, `cannot refresh or install: other error`)
+
+ e = &SnapActionError{
+ Other: []error{fmt.Errorf("other error 1"), fmt.Errorf("other error 2")},
+ }
+ c.Check(e.Error(), Equals, `cannot refresh or install:
+* other error 1
+* other error 2`)
+
+ e = &SnapActionError{
+ Install: map[string]error{
+ "bar": fmt.Errorf("sad install"),
+ },
+ Other: []error{fmt.Errorf("other error 1"), fmt.Errorf("other error 2")},
+ }
+ c.Check(e.Error(), Equals, `cannot refresh or install:
+snap "bar": sad install
+* other error 1
+* other error 2`)
+
+ e = &SnapActionError{
+ NoResults: true,
+ }
+ c.Check(e.Error(), Equals, "no install/refresh information results from the store")
+}
+
+func (s *storeTestSuite) TestSnapActionRefreshesBothAuths(c *C) {
+ // snap action (install/refresh) has is its own custom way to
+ // signal macaroon refreshes that allows to do a best effort
+ // with the available results
+
+ refresh, err := makeTestRefreshDischargeResponse()
+ c.Assert(err, IsNil)
+ c.Check(s.user.StoreDischarges[0], Not(Equals), refresh)
+
+ // mock refresh response
+ refreshDischargeEndpointHit := false
+ mockSSOServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ io.WriteString(w, fmt.Sprintf(`{"discharge_macaroon": "%s"}`, refresh))
+ refreshDischargeEndpointHit = true
+ }))
+ defer mockSSOServer.Close()
+ UbuntuoneRefreshDischargeAPI = mockSSOServer.URL + "/tokens/refresh"
+
+ refreshSessionRequested := false
+ expiredAuth := `Macaroon root="expired-session-macaroon"`
+ n := 0
+ // mock store response
+ mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ c.Check(r.UserAgent(), Equals, userAgent)
+
+ switch r.URL.Path {
+ case snapActionPath:
+ n++
+ type errObj struct {
+ Code string `json:"code"`
+ Message string `json:"message"`
+ }
+ var errors []errObj
+
+ authorization := r.Header.Get("Authorization")
+ c.Check(authorization, Equals, s.expectedAuthorization(c, s.user))
+ if s.user.StoreDischarges[0] != refresh {
+ errors = append(errors, errObj{Code: "user-authorization-needs-refresh"})
+ }
+
+ devAuthorization := r.Header.Get("Snap-Device-Authorization")
+ if devAuthorization == "" {
+ c.Fatalf("device authentication missing")
+ } else if devAuthorization == expiredAuth {
+ errors = append(errors, errObj{Code: "device-authorization-needs-refresh"})
+ } else {
+ c.Check(devAuthorization, Equals, `Macaroon root="refreshed-session-macaroon"`)
+ }
+
+ errorsJSON, err := json.Marshal(errors)
+ c.Assert(err, IsNil)
+
+ io.WriteString(w, fmt.Sprintf(`{
+ "results": [{
+ "result": "refresh",
+ "instance-key": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "snap": {
+ "snap-id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
+ "name": "hello-world",
+ "revision": 26,
+ "version": "6.1",
+ "publisher": {
+ "id": "canonical",
+ "name": "canonical",
+ "title": "Canonical"
+ }
+ }
+ }],
+ "error-list": %s
+}`, errorsJSON))
+ case authNoncesPath:
+ io.WriteString(w, `{"nonce": "1234567890:9876543210"}`)
+ case authSessionPath:
+ // sanity of request
+ jsonReq, err := ioutil.ReadAll(r.Body)
+ c.Assert(err, IsNil)
+ var req map[string]string
+ err = json.Unmarshal(jsonReq, &req)
+ c.Assert(err, IsNil)
+ c.Check(strings.HasPrefix(req["device-session-request"], "type: device-session-request\n"), Equals, true)
+ c.Check(strings.HasPrefix(req["serial-assertion"], "type: serial\n"), Equals, true)
+ c.Check(strings.HasPrefix(req["model-assertion"], "type: model\n"), Equals, true)
+
+ authorization := r.Header.Get("X-Device-Authorization")
+ if authorization == "" {
+ c.Fatalf("expecting only refresh")
+ } else {
+ c.Check(authorization, Equals, expiredAuth)
+ io.WriteString(w, `{"macaroon": "refreshed-session-macaroon"}`)
+ refreshSessionRequested = true
+ }
+ default:
+ c.Fatalf("unexpected path %q", r.URL.Path)
+ }
+ }))
+ c.Assert(mockServer, NotNil)
+ defer mockServer.Close()
+
+ mockServerURL, _ := url.Parse(mockServer.URL)
+
+ // make sure device session is expired
+ s.device.SessionMacaroon = "expired-session-macaroon"
+ authContext := &testAuthContext{c: c, device: s.device, user: s.user}
+ sto := New(&Config{
+ StoreBaseURL: mockServerURL,
+ }, authContext)
+
+ results, err := sto.SnapAction(context.TODO(), []*CurrentSnap{
+ {
+ Name: "hello-world",
+ SnapID: helloWorldSnapID,
+ TrackingChannel: "beta",
+ Revision: snap.R(1),
+ RefreshedDate: helloRefreshedDate,
+ },
+ }, []*SnapAction{
+ {
+ Action: "refresh",
+ SnapID: helloWorldSnapID,
+ },
+ }, s.user, nil)
+ c.Assert(err, IsNil)
+ c.Assert(results, HasLen, 1)
+ c.Assert(results[0].Name(), Equals, "hello-world")
+ c.Check(refreshDischargeEndpointHit, Equals, true)
+ c.Check(refreshSessionRequested, Equals, true)
+ c.Check(n, Equals, 2)
+}
diff -Nru snapd-2.32.3.2/strutil/shlex/shlex.go snapd-2.32.9/strutil/shlex/shlex.go
--- snapd-2.32.3.2/strutil/shlex/shlex.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/strutil/shlex/shlex.go 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,417 @@
+/*
+Copyright 2012 Google Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+/*
+Package shlex implements a simple lexer which splits input in to tokens using
+shell-style rules for quoting and commenting.
+
+The basic use case uses the default ASCII lexer to split a string into sub-strings:
+
+ shlex.Split("one \"two three\" four") -> []string{"one", "two three", "four"}
+
+To process a stream of strings:
+
+ l := NewLexer(os.Stdin)
+ for ; token, err := l.Next(); err != nil {
+ // process token
+ }
+
+To access the raw token stream (which includes tokens for comments):
+
+ t := NewTokenizer(os.Stdin)
+ for ; token, err := t.Next(); err != nil {
+ // process token
+ }
+
+*/
+package shlex
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "strings"
+)
+
+// TokenType is a top-level token classification: A word, space, comment, unknown.
+type TokenType int
+
+// runeTokenClass is the type of a UTF-8 character classification: A quote, space, escape.
+type runeTokenClass int
+
+// the internal state used by the lexer state machine
+type lexerState int
+
+// Token is a (type, value) pair representing a lexographical token.
+type Token struct {
+ tokenType TokenType
+ value string
+}
+
+// Equal reports whether tokens a, and b, are equal.
+// Two tokens are equal if both their types and values are equal. A nil token can
+// never be equal to another token.
+func (a *Token) Equal(b *Token) bool {
+ if a == nil || b == nil {
+ return false
+ }
+ if a.tokenType != b.tokenType {
+ return false
+ }
+ return a.value == b.value
+}
+
+// Named classes of UTF-8 runes
+const (
+ spaceRunes = " \t\r\n"
+ escapingQuoteRunes = `"`
+ nonEscapingQuoteRunes = "'"
+ escapeRunes = `\`
+ commentRunes = "#"
+)
+
+// Classes of rune token
+const (
+ unknownRuneClass runeTokenClass = iota
+ spaceRuneClass
+ escapingQuoteRuneClass
+ nonEscapingQuoteRuneClass
+ escapeRuneClass
+ commentRuneClass
+ eofRuneClass
+)
+
+// Classes of lexographic token
+const (
+ UnknownToken TokenType = iota
+ WordToken
+ SpaceToken
+ CommentToken
+)
+
+// Lexer state machine states
+const (
+ startState lexerState = iota // no runes have been seen
+ inWordState // processing regular runes in a word
+ escapingState // we have just consumed an escape rune; the next rune is literal
+ escapingQuotedState // we have just consumed an escape rune within a quoted string
+ quotingEscapingState // we are within a quoted string that supports escaping ("...")
+ quotingState // we are within a string that does not support escaping ('...')
+ commentState // we are within a comment (everything following an unquoted or unescaped #
+)
+
+// tokenClassifier is used for classifying rune characters.
+type tokenClassifier map[rune]runeTokenClass
+
+func (typeMap tokenClassifier) addRuneClass(runes string, tokenType runeTokenClass) {
+ for _, runeChar := range runes {
+ typeMap[runeChar] = tokenType
+ }
+}
+
+// newDefaultClassifier creates a new classifier for ASCII characters.
+func newDefaultClassifier() tokenClassifier {
+ t := tokenClassifier{}
+ t.addRuneClass(spaceRunes, spaceRuneClass)
+ t.addRuneClass(escapingQuoteRunes, escapingQuoteRuneClass)
+ t.addRuneClass(nonEscapingQuoteRunes, nonEscapingQuoteRuneClass)
+ t.addRuneClass(escapeRunes, escapeRuneClass)
+ t.addRuneClass(commentRunes, commentRuneClass)
+ return t
+}
+
+// ClassifyRune classifiees a rune
+func (t tokenClassifier) ClassifyRune(runeVal rune) runeTokenClass {
+ return t[runeVal]
+}
+
+// Lexer turns an input stream into a sequence of tokens. Whitespace and comments are skipped.
+type Lexer Tokenizer
+
+// NewLexer creates a new lexer from an input stream.
+func NewLexer(r io.Reader) *Lexer {
+
+ return (*Lexer)(NewTokenizer(r))
+}
+
+// Next returns the next word, or an error. If there are no more words,
+// the error will be io.EOF.
+func (l *Lexer) Next() (string, error) {
+ for {
+ token, err := (*Tokenizer)(l).Next()
+ if err != nil {
+ return "", err
+ }
+ switch token.tokenType {
+ case WordToken:
+ return token.value, nil
+ case CommentToken:
+ // skip comments
+ default:
+ return "", fmt.Errorf("Unknown token type: %v", token.tokenType)
+ }
+ }
+}
+
+// Tokenizer turns an input stream into a sequence of typed tokens
+type Tokenizer struct {
+ input bufio.Reader
+ classifier tokenClassifier
+}
+
+// NewTokenizer creates a new tokenizer from an input stream.
+func NewTokenizer(r io.Reader) *Tokenizer {
+ input := bufio.NewReader(r)
+ classifier := newDefaultClassifier()
+ return &Tokenizer{
+ input: *input,
+ classifier: classifier}
+}
+
+// scanStream scans the stream for the next token using the internal state machine.
+// It will panic if it encounters a rune which it does not know how to handle.
+func (t *Tokenizer) scanStream() (*Token, error) {
+ state := startState
+ var tokenType TokenType
+ var value []rune
+ var nextRune rune
+ var nextRuneType runeTokenClass
+ var err error
+
+ for {
+ nextRune, _, err = t.input.ReadRune()
+ nextRuneType = t.classifier.ClassifyRune(nextRune)
+
+ if err == io.EOF {
+ nextRuneType = eofRuneClass
+ err = nil
+ } else if err != nil {
+ return nil, err
+ }
+
+ switch state {
+ case startState: // no runes read yet
+ {
+ switch nextRuneType {
+ case eofRuneClass:
+ {
+ return nil, io.EOF
+ }
+ case spaceRuneClass:
+ {
+ }
+ case escapingQuoteRuneClass:
+ {
+ tokenType = WordToken
+ state = quotingEscapingState
+ }
+ case nonEscapingQuoteRuneClass:
+ {
+ tokenType = WordToken
+ state = quotingState
+ }
+ case escapeRuneClass:
+ {
+ tokenType = WordToken
+ state = escapingState
+ }
+ case commentRuneClass:
+ {
+ tokenType = CommentToken
+ state = commentState
+ }
+ default:
+ {
+ tokenType = WordToken
+ value = append(value, nextRune)
+ state = inWordState
+ }
+ }
+ }
+ case inWordState: // in a regular word
+ {
+ switch nextRuneType {
+ case eofRuneClass:
+ {
+ token := &Token{
+ tokenType: tokenType,
+ value: string(value)}
+ return token, err
+ }
+ case spaceRuneClass:
+ {
+ t.input.UnreadRune()
+ token := &Token{
+ tokenType: tokenType,
+ value: string(value)}
+ return token, err
+ }
+ case escapingQuoteRuneClass:
+ {
+ state = quotingEscapingState
+ }
+ case nonEscapingQuoteRuneClass:
+ {
+ state = quotingState
+ }
+ case escapeRuneClass:
+ {
+ state = escapingState
+ }
+ default:
+ {
+ value = append(value, nextRune)
+ }
+ }
+ }
+ case escapingState: // the rune after an escape character
+ {
+ switch nextRuneType {
+ case eofRuneClass:
+ {
+ err = fmt.Errorf("EOF found after escape character")
+ token := &Token{
+ tokenType: tokenType,
+ value: string(value)}
+ return token, err
+ }
+ default:
+ {
+ state = inWordState
+ value = append(value, nextRune)
+ }
+ }
+ }
+ case escapingQuotedState: // the next rune after an escape character, in double quotes
+ {
+ switch nextRuneType {
+ case eofRuneClass:
+ {
+ err = fmt.Errorf("EOF found after escape character")
+ token := &Token{
+ tokenType: tokenType,
+ value: string(value)}
+ return token, err
+ }
+ default:
+ {
+ state = quotingEscapingState
+ value = append(value, nextRune)
+ }
+ }
+ }
+ case quotingEscapingState: // in escaping double quotes
+ {
+ switch nextRuneType {
+ case eofRuneClass:
+ {
+ err = fmt.Errorf("EOF found when expecting closing quote")
+ token := &Token{
+ tokenType: tokenType,
+ value: string(value)}
+ return token, err
+ }
+ case escapingQuoteRuneClass:
+ {
+ state = inWordState
+ }
+ case escapeRuneClass:
+ {
+ state = escapingQuotedState
+ }
+ default:
+ {
+ value = append(value, nextRune)
+ }
+ }
+ }
+ case quotingState: // in non-escaping single quotes
+ {
+ switch nextRuneType {
+ case eofRuneClass:
+ {
+ err = fmt.Errorf("EOF found when expecting closing quote")
+ token := &Token{
+ tokenType: tokenType,
+ value: string(value)}
+ return token, err
+ }
+ case nonEscapingQuoteRuneClass:
+ {
+ state = inWordState
+ }
+ default:
+ {
+ value = append(value, nextRune)
+ }
+ }
+ }
+ case commentState: // in a comment
+ {
+ switch nextRuneType {
+ case eofRuneClass:
+ {
+ token := &Token{
+ tokenType: tokenType,
+ value: string(value)}
+ return token, err
+ }
+ case spaceRuneClass:
+ {
+ if nextRune == '\n' {
+ state = startState
+ token := &Token{
+ tokenType: tokenType,
+ value: string(value)}
+ return token, err
+ } else {
+ value = append(value, nextRune)
+ }
+ }
+ default:
+ {
+ value = append(value, nextRune)
+ }
+ }
+ }
+ default:
+ {
+ return nil, fmt.Errorf("Unexpected state: %v", state)
+ }
+ }
+ }
+}
+
+// Next returns the next token in the stream.
+func (t *Tokenizer) Next() (*Token, error) {
+ return t.scanStream()
+}
+
+// Split partitions a string into a slice of strings.
+func Split(s string) ([]string, error) {
+ l := NewLexer(strings.NewReader(s))
+ subStrings := make([]string, 0)
+ for {
+ word, err := l.Next()
+ if err != nil {
+ if err == io.EOF {
+ return subStrings, nil
+ }
+ return subStrings, err
+ }
+ subStrings = append(subStrings, word)
+ }
+}
diff -Nru snapd-2.32.3.2/strutil/shlex/shlex_test.go snapd-2.32.9/strutil/shlex/shlex_test.go
--- snapd-2.32.3.2/strutil/shlex/shlex_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/strutil/shlex/shlex_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,101 @@
+/*
+Copyright 2012 Google Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package shlex
+
+import (
+ "strings"
+ "testing"
+)
+
+var (
+ // one two "three four" "five \"six\"" seven#eight # nine # ten
+ // eleven 'twelve\'
+ testString = "one two \"three four\" \"five \\\"six\\\"\" seven#eight # nine # ten\n eleven 'twelve\\' thirteen=13 fourteen/14"
+)
+
+func TestClassifier(t *testing.T) {
+ classifier := newDefaultClassifier()
+ tests := map[rune]runeTokenClass{
+ ' ': spaceRuneClass,
+ '"': escapingQuoteRuneClass,
+ '\'': nonEscapingQuoteRuneClass,
+ '#': commentRuneClass}
+ for runeChar, want := range tests {
+ got := classifier.ClassifyRune(runeChar)
+ if got != want {
+ t.Errorf("ClassifyRune(%v) -> %v. Want: %v", runeChar, got, want)
+ }
+ }
+}
+
+func TestTokenizer(t *testing.T) {
+ testInput := strings.NewReader(testString)
+ expectedTokens := []*Token{
+ {WordToken, "one"},
+ {WordToken, "two"},
+ {WordToken, "three four"},
+ {WordToken, "five \"six\""},
+ {WordToken, "seven#eight"},
+ {CommentToken, " nine # ten"},
+ {WordToken, "eleven"},
+ {WordToken, "twelve\\"},
+ {WordToken, "thirteen=13"},
+ {WordToken, "fourteen/14"}}
+
+ tokenizer := NewTokenizer(testInput)
+ for i, want := range expectedTokens {
+ got, err := tokenizer.Next()
+ if err != nil {
+ t.Error(err)
+ }
+ if !got.Equal(want) {
+ t.Errorf("Tokenizer.Next()[%v] of %q -> %v. Want: %v", i, testString, got, want)
+ }
+ }
+}
+
+func TestLexer(t *testing.T) {
+ testInput := strings.NewReader(testString)
+ expectedStrings := []string{"one", "two", "three four", "five \"six\"", "seven#eight", "eleven", "twelve\\", "thirteen=13", "fourteen/14"}
+
+ lexer := NewLexer(testInput)
+ for i, want := range expectedStrings {
+ got, err := lexer.Next()
+ if err != nil {
+ t.Error(err)
+ }
+ if got != want {
+ t.Errorf("Lexer.Next()[%v] of %q -> %v. Want: %v", i, testString, got, want)
+ }
+ }
+}
+
+func TestSplit(t *testing.T) {
+ want := []string{"one", "two", "three four", "five \"six\"", "seven#eight", "eleven", "twelve\\", "thirteen=13", "fourteen/14"}
+ got, err := Split(testString)
+ if err != nil {
+ t.Error(err)
+ }
+ if len(want) != len(got) {
+ t.Errorf("Split(%q) -> %v. Want: %v", testString, got, want)
+ }
+ for i := range got {
+ if got[i] != want[i] {
+ t.Errorf("Split(%q)[%v] -> %v. Want: %v", testString, i, got[i], want[i])
+ }
+ }
+}
diff -Nru snapd-2.32.3.2/systemd/export_test.go snapd-2.32.9/systemd/export_test.go
--- snapd-2.32.3.2/systemd/export_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/systemd/export_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -50,3 +50,11 @@
osutilStreamCommand = f
return func() { osutilStreamCommand = old }
}
+
+func MockJournalStdoutPath(path string) func() {
+ oldPath := journalStdoutPath
+ journalStdoutPath = path
+ return func() {
+ journalStdoutPath = oldPath
+ }
+}
diff -Nru snapd-2.32.3.2/systemd/journal.go snapd-2.32.9/systemd/journal.go
--- snapd-2.32.3.2/systemd/journal.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/systemd/journal.go 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,72 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2018 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package systemd
+
+import (
+ "bytes"
+ "fmt"
+ "log/syslog"
+ "net"
+ "os"
+)
+
+var journalStdoutPath = "/run/systemd/journal/stdout"
+
+// NewJournalStreamFile creates log stream file descriptor to the journal. The
+// semantics is identical to that of sd_journal_stream_fd(3) call.
+func NewJournalStreamFile(identifier string, priority syslog.Priority, levelPrefix bool) (*os.File, error) {
+ conn, err := net.DialUnix("unix", nil, &net.UnixAddr{Name: journalStdoutPath})
+ if err != nil {
+ return nil, err
+ }
+ // does not affect *os.File created through conn.File() later on
+ defer conn.Close()
+
+ if err := conn.CloseRead(); err != nil {
+ return nil, err
+ }
+
+ // header contents taken from the original systemd code:
+ // https://github.com/systemd/systemd/blob/97a33b126c845327a3a19d6e66f05684823868fb/src/journal/journal-send.c#L395
+ header := bytes.Buffer{}
+ header.WriteString(identifier)
+ header.WriteByte('\n')
+ header.WriteByte('\n')
+ header.WriteByte(byte('0') + byte(priority))
+ header.WriteByte('\n')
+ var prefix int
+ if levelPrefix {
+ prefix = 1
+ }
+ header.WriteByte(byte('0') + byte(prefix))
+ header.WriteByte('\n')
+ header.WriteByte('0')
+ header.WriteByte('\n')
+ header.WriteByte('0')
+ header.WriteByte('\n')
+ header.WriteByte('0')
+ header.WriteByte('\n')
+
+ if _, err := conn.Write(header.Bytes()); err != nil {
+ return nil, fmt.Errorf("failed to write header: %v", err)
+ }
+
+ return conn.File()
+}
diff -Nru snapd-2.32.3.2/systemd/journal_test.go snapd-2.32.9/systemd/journal_test.go
--- snapd-2.32.3.2/systemd/journal_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/systemd/journal_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,89 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2018 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package systemd_test
+
+import (
+ "log/syslog"
+ "net"
+ "path"
+
+ . "gopkg.in/check.v1"
+
+ . "github.com/snapcore/snapd/systemd"
+)
+
+type journalTestSuite struct{}
+
+var _ = Suite(&journalTestSuite{})
+
+func (j *journalTestSuite) TestStreamFileErrorNoPath(c *C) {
+ restore := MockJournalStdoutPath(path.Join(c.MkDir(), "fake-journal"))
+ defer restore()
+
+ jout, err := NewJournalStreamFile("foobar", syslog.LOG_INFO, false)
+ c.Assert(err, ErrorMatches, ".*no such file or directory")
+ c.Assert(jout, IsNil)
+}
+
+func (j *journalTestSuite) TestStreamFileHeader(c *C) {
+ fakePath := path.Join(c.MkDir(), "fake-journal")
+ restore := MockJournalStdoutPath(fakePath)
+ defer restore()
+
+ listener, err := net.ListenUnix("unix", &net.UnixAddr{Name: fakePath})
+ c.Assert(err, IsNil)
+ defer listener.Close()
+
+ doneCh := make(chan struct{}, 1)
+
+ go func() {
+ defer func() { close(doneCh) }()
+
+ // see https://github.com/systemd/systemd/blob/97a33b126c845327a3a19d6e66f05684823868fb/src/journal/journal-send.c#L424
+ conn, err := listener.AcceptUnix()
+ c.Assert(err, IsNil)
+ defer conn.Close()
+
+ expectedHdrLen := len("foobar") + 1 + 1 + 2 + 2 + 2 + 2 + 2
+ hdrBuf := make([]byte, expectedHdrLen)
+ hdrLen, err := conn.Read(hdrBuf)
+ c.Assert(err, IsNil)
+ c.Assert(hdrLen, Equals, expectedHdrLen)
+ c.Check(hdrBuf, DeepEquals, []byte("foobar\n\n6\n0\n0\n0\n0\n"))
+
+ data := make([]byte, 4096)
+ sz, err := conn.Read(data)
+ c.Assert(err, IsNil)
+ c.Assert(sz > 0, Equals, true)
+ c.Check(data[0:sz], DeepEquals, []byte("hello from unit tests"))
+
+ doneCh <- struct{}{}
+ }()
+
+ jout, err := NewJournalStreamFile("foobar", syslog.LOG_INFO, false)
+ c.Assert(err, IsNil)
+ c.Assert(jout, NotNil)
+
+ _, err = jout.WriteString("hello from unit tests")
+ c.Assert(err, IsNil)
+ defer jout.Close()
+
+ <-doneCh
+}
diff -Nru snapd-2.32.3.2/tests/lib/fakestore/store/store.go snapd-2.32.9/tests/lib/fakestore/store/store.go
--- snapd-2.32.3.2/tests/lib/fakestore/store/store.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/tests/lib/fakestore/store/store.go 2018-05-16 08:20:08.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2016 Canonical Ltd
+ * Copyright (C) 2016-2018 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -99,6 +99,8 @@
mux.HandleFunc("/api/v1/snaps/metadata", store.bulkEndpoint)
mux.Handle("/download/", http.StripPrefix("/download/", http.FileServer(http.Dir(topDir))))
mux.HandleFunc("/api/v1/snaps/assertions/", store.assertionsEndpoint)
+ // v2
+ mux.HandleFunc("/v2/snaps/refresh", store.snapActionEndpoint)
return store
}
@@ -185,19 +187,19 @@
info, err := snap.ReadInfoFromSnapFile(snapFile, nil)
if err != nil {
- http.Error(w, fmt.Sprintf("can get info for: %v: %v", fn, err), 400)
+ http.Error(w, fmt.Sprintf("cannot get info for: %v: %v", fn, err), 400)
return nil, errInfo
}
snapDigest, size, err := asserts.SnapFileSHA3_384(fn)
if err != nil {
- http.Error(w, fmt.Sprintf("can get digest for: %v: %v", fn, err), 400)
+ http.Error(w, fmt.Sprintf("cannot get digest for: %v: %v", fn, err), 400)
return nil, errInfo
}
snapRev, devAcct, err := findSnapRevision(snapDigest, bs)
if err != nil && !asserts.IsNotFound(err) {
- http.Error(w, fmt.Sprintf("can get info for: %v: %v", fn, err), 400)
+ http.Error(w, fmt.Sprintf("cannot get info for: %v: %v", fn, err), 400)
return nil, errInfo
}
@@ -407,7 +409,7 @@
name := snapIDtoName[pkg.SnapID]
if name == "" {
- http.Error(w, fmt.Sprintf("unknown snapid: %q", pkg.SnapID), 400)
+ http.Error(w, fmt.Sprintf("unknown snap-id: %q", pkg.SnapID), 400)
return
}
@@ -439,7 +441,7 @@
// should look nice
out, err := json.MarshalIndent(replyData, "", " ")
if err != nil {
- http.Error(w, fmt.Sprintf("can marshal: %v: %v", replyData, err), 400)
+ http.Error(w, fmt.Sprintf("cannot marshal: %v: %v", replyData, err), 400)
return
}
w.Write(out)
@@ -482,6 +484,153 @@
return bs, nil
}
+type currentSnap struct {
+ SnapID string `json:"snap-id"`
+ InstanceKey string `json:"instance-key"`
+}
+
+type snapAction struct {
+ Action string `json:"action"`
+ InstanceKey string `json:"instance-key"`
+ SnapID string `json:"snap-id"`
+ Name string `json:"name"`
+}
+
+type snapActionRequest struct {
+ Context []currentSnap `json:"context"`
+ Fields []string `json:"fields"`
+ Actions []snapAction `json:"actions"`
+}
+
+type snapActionResult struct {
+ Result string `json:"result"`
+ InstanceKey string `json:"instance-key"`
+ SnapID string `json:"snap-id"`
+ Name string `json:"name"`
+ Snap detailsResultV2 `json:"snap"`
+}
+
+type snapActionResultList struct {
+ Results []*snapActionResult `json:"results"`
+}
+
+type detailsResultV2 struct {
+ Architectures []string `json:"architectures"`
+ SnapID string `json:"snap-id"`
+ Name string `json:"name"`
+ Publisher struct {
+ ID string `json:"id"`
+ Username string `json:"username"`
+ } `json:"publisher"`
+ Download struct {
+ URL string `json:"url"`
+ Sha3_384 string `json:"sha3-384"`
+ Size uint64 `json:"size"`
+ } `json:"download"`
+ Version string `json:"version"`
+ Revision int `json:"revision"`
+}
+
+func (s *Store) snapActionEndpoint(w http.ResponseWriter, req *http.Request) {
+ var reqData snapActionRequest
+ var replyData snapActionResultList
+
+ decoder := json.NewDecoder(req.Body)
+ if err := decoder.Decode(&reqData); err != nil {
+ http.Error(w, fmt.Sprintf("cannot decode request body: %v", err), 400)
+ return
+ }
+
+ bs, err := s.collectAssertions()
+ if err != nil {
+ http.Error(w, fmt.Sprintf("internal error collecting assertions: %v", err), 500)
+ return
+ }
+
+ 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), 500)
+ return
+ }
+
+ snaps, err := s.collectSnaps()
+ if err != nil {
+ http.Error(w, fmt.Sprintf("internal error collecting snaps: %v", err), 500)
+ return
+ }
+
+ actions := reqData.Actions
+ if len(actions) == 1 && actions[0].Action == "refresh-all" {
+ actions = make([]snapAction, len(reqData.Context))
+ for i, s := range reqData.Context {
+ actions[i] = snapAction{
+ Action: "refresh",
+ SnapID: s.SnapID,
+ InstanceKey: s.InstanceKey,
+ }
+ }
+ }
+
+ // check if we have downloadable snap of the given SnapID or name
+ for _, a := range actions {
+ name := a.Name
+ snapID := a.SnapID
+ if a.Action == "refresh" {
+ name = snapIDtoName[snapID]
+ }
+
+ if name == "" {
+ http.Error(w, fmt.Sprintf("unknown snap-id: %q", snapID), 400)
+ return
+ }
+
+ if fn, ok := snaps[name]; ok {
+ essInfo, err := snapEssentialInfo(w, fn, snapID, bs)
+ if essInfo == nil {
+ if err != errInfo {
+ panic(err)
+ }
+ return
+ }
+
+ res := &snapActionResult{
+ Result: a.Action,
+ InstanceKey: a.InstanceKey,
+ SnapID: essInfo.SnapID,
+ Name: essInfo.Name,
+ Snap: detailsResultV2{
+ Architectures: []string{"all"},
+ SnapID: essInfo.SnapID,
+ Name: essInfo.Name,
+ Version: essInfo.Version,
+ Revision: essInfo.Revision,
+ },
+ }
+ res.Snap.Publisher.ID = essInfo.DeveloperID
+ res.Snap.Publisher.Username = essInfo.DevelName
+ res.Snap.Download.URL = fmt.Sprintf("%s/download/%s", s.URL(), filepath.Base(fn))
+ res.Snap.Download.Sha3_384 = hexify(essInfo.Digest)
+ res.Snap.Download.Size = essInfo.Size
+ replyData.Results = append(replyData.Results, res)
+ }
+ }
+
+ // use indent because this is a development tool, output
+ // should look nice
+ out, err := json.MarshalIndent(replyData, "", " ")
+ if err != nil {
+ http.Error(w, fmt.Sprintf("cannot marshal: %v: %v", replyData, err), 400)
+ return
+ }
+ w.Write(out)
+}
+
func (s *Store) retrieveAssertion(bs asserts.Backstore, assertType *asserts.AssertionType, primaryKey []string) (asserts.Assertion, error) {
a, err := bs.Get(assertType, primaryKey, assertType.MaxSupportedFormat())
if asserts.IsNotFound(err) && s.assertFallback {
diff -Nru snapd-2.32.3.2/tests/lib/fakestore/store/store_test.go snapd-2.32.9/tests/lib/fakestore/store/store_test.go
--- snapd-2.32.3.2/tests/lib/fakestore/store/store_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/tests/lib/fakestore/store/store_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2014-2015 Canonical Ltd
+ * Copyright (C) 2014-2018 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -49,12 +49,12 @@
var defaultAddr = "localhost:23321"
-func getSha(fn string) string {
- snapDigest, _, err := asserts.SnapFileSHA3_384(fn)
+func getSha(fn string) (string, uint64) {
+ snapDigest, size, err := asserts.SnapFileSHA3_384(fn)
if err != nil {
panic(err)
}
- return hexify(snapDigest)
+ return hexify(snapDigest), size
}
func (s *storeTestSuite) SetUpTest(c *C) {
@@ -127,6 +127,7 @@
var body map[string]interface{}
c.Assert(json.NewDecoder(resp.Body).Decode(&body), IsNil)
+ sha3_384, _ := getSha(snapFn)
c.Check(body, DeepEquals, map[string]interface{}{
"architecture": []interface{}{"all"},
"snap_id": "xidididididididididididididididid",
@@ -137,7 +138,7 @@
"download_url": s.store.URL() + "/download/foo_7_all.snap",
"version": "7",
"revision": float64(77),
- "download_sha3_384": getSha(snapFn),
+ "download_sha3_384": sha3_384,
})
}
@@ -151,6 +152,7 @@
var body map[string]interface{}
c.Assert(json.NewDecoder(resp.Body).Decode(&body), IsNil)
+ sha3_384, _ := getSha(snapFn)
c.Check(body, DeepEquals, map[string]interface{}{
"architecture": []interface{}{"all"},
"snap_id": "",
@@ -161,7 +163,7 @@
"download_url": s.store.URL() + "/download/foo_1_all.snap",
"version": "1",
"revision": float64(424242),
- "download_sha3_384": getSha(snapFn),
+ "download_sha3_384": sha3_384,
})
}
@@ -183,6 +185,7 @@
} `json:"_embedded"`
}
c.Assert(json.NewDecoder(resp.Body).Decode(&body), IsNil)
+ sha3_384, _ := getSha(snapFn)
c.Check(body.Top.Cat, DeepEquals, []map[string]interface{}{{
"architecture": []interface{}{"all"},
"snap_id": "eFe8BTR5L5V9F7yHeMAPxkEr2NdUXMtw",
@@ -193,7 +196,7 @@
"download_url": s.store.URL() + "/download/test-snapd-tools_1_all.snap",
"version": "1",
"revision": float64(424242),
- "download_sha3_384": getSha(snapFn),
+ "download_sha3_384": sha3_384,
}})
}
@@ -214,6 +217,7 @@
} `json:"_embedded"`
}
c.Assert(json.NewDecoder(resp.Body).Decode(&body), IsNil)
+ sha3_384, _ := getSha(snapFn)
c.Check(body.Top.Cat, DeepEquals, []map[string]interface{}{{
"architecture": []interface{}{"all"},
"snap_id": "xidididididididididididididididid",
@@ -224,7 +228,7 @@
"download_url": s.store.URL() + "/download/foo_10_all.snap",
"version": "10",
"revision": float64(99),
- "download_sha3_384": getSha(snapFn),
+ "download_sha3_384": sha3_384,
}})
}
@@ -390,3 +394,169 @@
c.Assert(err, IsNil)
c.Check(respObj["status"], Equals, float64(404))
}
+
+func (s *storeTestSuite) TestSnapActionEndpoint(c *C) {
+ snapFn := s.makeTestSnap(c, "name: test-snapd-tools\nversion: 1")
+
+ resp, err := s.StorePostJSON("/v2/snaps/refresh", []byte(`{
+"context": [{"instance-key":"eFe8BTR5L5V9F7yHeMAPxkEr2NdUXMtw","snap-id":"eFe8BTR5L5V9F7yHeMAPxkEr2NdUXMtw","tracking-channel":"stable","revision":1}],
+"actions": [{"action":"refresh","instance-key":"eFe8BTR5L5V9F7yHeMAPxkEr2NdUXMtw","snap-id":"eFe8BTR5L5V9F7yHeMAPxkEr2NdUXMtw"}]
+}`))
+ c.Assert(err, IsNil)
+ defer resp.Body.Close()
+
+ c.Assert(resp.StatusCode, Equals, 200)
+ var body struct {
+ Results []map[string]interface{}
+ }
+ c.Assert(json.NewDecoder(resp.Body).Decode(&body), IsNil)
+ c.Check(body.Results, HasLen, 1)
+ sha3_384, size := getSha(snapFn)
+ c.Check(body.Results[0], DeepEquals, map[string]interface{}{
+ "result": "refresh",
+ "instance-key": "eFe8BTR5L5V9F7yHeMAPxkEr2NdUXMtw",
+ "snap-id": "eFe8BTR5L5V9F7yHeMAPxkEr2NdUXMtw",
+ "name": "test-snapd-tools",
+ "snap": map[string]interface{}{
+ "architectures": []interface{}{"all"},
+ "snap-id": "eFe8BTR5L5V9F7yHeMAPxkEr2NdUXMtw",
+ "name": "test-snapd-tools",
+ "publisher": map[string]interface{}{
+ "username": "canonical",
+ "id": "canonical",
+ },
+ "download": map[string]interface{}{
+ "url": s.store.URL() + "/download/test-snapd-tools_1_all.snap",
+ "sha3-384": sha3_384,
+ "size": float64(size),
+ },
+ "version": "1",
+ "revision": float64(424242),
+ },
+ })
+}
+
+func (s *storeTestSuite) TestSnapActionEndpointWithAssertions(c *C) {
+ snapFn := s.makeTestSnap(c, "name: foo\nversion: 10")
+ s.makeAssertions(c, snapFn, "foo", "xidididididididididididididididid", "foo-devel", "foo-devel-id", 99)
+
+ resp, err := s.StorePostJSON("/v2/snaps/refresh", []byte(`{
+"context": [{"instance-key":"eFe8BTR5L5V9F7yHeMAPxkEr2NdUXMtw","snap-id":"xidididididididididididididididid","tracking-channel":"stable","revision":1}],
+"actions": [{"action":"refresh","instance-key":"eFe8BTR5L5V9F7yHeMAPxkEr2NdUXMtw","snap-id":"xidididididididididididididididid"}]
+}`))
+ c.Assert(err, IsNil)
+ defer resp.Body.Close()
+
+ c.Assert(resp.StatusCode, Equals, 200)
+ var body struct {
+ Results []map[string]interface{}
+ }
+ c.Assert(json.NewDecoder(resp.Body).Decode(&body), IsNil)
+ c.Check(body.Results, HasLen, 1)
+ sha3_384, size := getSha(snapFn)
+ c.Check(body.Results[0], DeepEquals, map[string]interface{}{
+ "result": "refresh",
+ "instance-key": "eFe8BTR5L5V9F7yHeMAPxkEr2NdUXMtw",
+ "snap-id": "xidididididididididididididididid",
+ "name": "foo",
+ "snap": map[string]interface{}{
+ "architectures": []interface{}{"all"},
+ "snap-id": "xidididididididididididididididid",
+ "name": "foo",
+ "publisher": map[string]interface{}{
+ "username": "foo-devel",
+ "id": "foo-devel-id",
+ },
+ "download": map[string]interface{}{
+ "url": s.store.URL() + "/download/foo_10_all.snap",
+ "sha3-384": sha3_384,
+ "size": float64(size),
+ },
+ "version": "10",
+ "revision": float64(99),
+ },
+ })
+}
+
+func (s *storeTestSuite) TestSnapActionEndpointRefreshAll(c *C) {
+ snapFn := s.makeTestSnap(c, "name: test-snapd-tools\nversion: 1")
+
+ resp, err := s.StorePostJSON("/v2/snaps/refresh", []byte(`{
+"context": [{"instance-key":"eFe8BTR5L5V9F7yHeMAPxkEr2NdUXMtw","snap-id":"eFe8BTR5L5V9F7yHeMAPxkEr2NdUXMtw","tracking-channel":"stable","revision":1}],
+"actions": [{"action":"refresh-all"}]
+}`))
+ c.Assert(err, IsNil)
+ defer resp.Body.Close()
+
+ c.Assert(resp.StatusCode, Equals, 200)
+ var body struct {
+ Results []map[string]interface{}
+ }
+ c.Assert(json.NewDecoder(resp.Body).Decode(&body), IsNil)
+ c.Check(body.Results, HasLen, 1)
+ sha3_384, size := getSha(snapFn)
+ c.Check(body.Results[0], DeepEquals, map[string]interface{}{
+ "result": "refresh",
+ "instance-key": "eFe8BTR5L5V9F7yHeMAPxkEr2NdUXMtw",
+ "snap-id": "eFe8BTR5L5V9F7yHeMAPxkEr2NdUXMtw",
+ "name": "test-snapd-tools",
+ "snap": map[string]interface{}{
+ "architectures": []interface{}{"all"},
+ "snap-id": "eFe8BTR5L5V9F7yHeMAPxkEr2NdUXMtw",
+ "name": "test-snapd-tools",
+ "publisher": map[string]interface{}{
+ "username": "canonical",
+ "id": "canonical",
+ },
+ "download": map[string]interface{}{
+ "url": s.store.URL() + "/download/test-snapd-tools_1_all.snap",
+ "sha3-384": sha3_384,
+ "size": float64(size),
+ },
+ "version": "1",
+ "revision": float64(424242),
+ },
+ })
+}
+
+func (s *storeTestSuite) TestSnapActionEndpointWithAssertionsInstall(c *C) {
+ snapFn := s.makeTestSnap(c, "name: foo\nversion: 10")
+ s.makeAssertions(c, snapFn, "foo", "xidididididididididididididididid", "foo-devel", "foo-devel-id", 99)
+
+ resp, err := s.StorePostJSON("/v2/snaps/refresh", []byte(`{
+"context": [],
+"actions": [{"action":"install","instance-key":"foo","name":"foo"}]
+}`))
+ c.Assert(err, IsNil)
+ defer resp.Body.Close()
+
+ c.Assert(resp.StatusCode, Equals, 200)
+ var body struct {
+ Results []map[string]interface{}
+ }
+ c.Assert(json.NewDecoder(resp.Body).Decode(&body), IsNil)
+ c.Check(body.Results, HasLen, 1)
+ sha3_384, size := getSha(snapFn)
+ c.Check(body.Results[0], DeepEquals, map[string]interface{}{
+ "result": "install",
+ "instance-key": "foo",
+ "snap-id": "xidididididididididididididididid",
+ "name": "foo",
+ "snap": map[string]interface{}{
+ "architectures": []interface{}{"all"},
+ "snap-id": "xidididididididididididididididid",
+ "name": "foo",
+ "publisher": map[string]interface{}{
+ "username": "foo-devel",
+ "id": "foo-devel-id",
+ },
+ "download": map[string]interface{}{
+ "url": s.store.URL() + "/download/foo_10_all.snap",
+ "sha3-384": sha3_384,
+ "size": float64(size),
+ },
+ "version": "10",
+ "revision": float64(99),
+ },
+ })
+}
diff -Nru snapd-2.32.3.2/tests/lib/pkgdb.sh snapd-2.32.9/tests/lib/pkgdb.sh
--- snapd-2.32.3.2/tests/lib/pkgdb.sh 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/tests/lib/pkgdb.sh 2018-05-16 08:20:08.000000000 +0000
@@ -122,7 +122,7 @@
quiet dnf -y install "$@"
;;
opensuse-*)
- quiet zypper install -y "$@"
+ quiet rpm -i "$@"
;;
*)
echo "ERROR: Unsupported distribution $SPREAD_SYSTEM"
@@ -245,7 +245,7 @@
quiet dnf makecache
;;
opensuse-*)
- quiet zypper refresh
+ quiet zypper --gpg-auto-import-keys refresh
;;
*)
echo "ERROR: Unsupported distribution $SPREAD_SYSTEM"
@@ -378,6 +378,7 @@
libglib2.0-dev
libseccomp-dev
libudev-dev
+ man
netcat-openbsd
pkg-config
python3-docutils
@@ -423,9 +424,14 @@
xvfb
"
;;
+ ubuntu-17.10-64)
+ echo "
+ linux-image-extra-4.13.0-16-generic
+ "
+ ;;
ubuntu-*)
echo "
- linux-image-extra-$(uname -r)
+ squashfs-tools
"
;;
debian-*)
@@ -451,6 +457,7 @@
git
golang
jq
+ man
mock
redhat-lsb-core
rpm-build
@@ -460,12 +467,14 @@
pkg_dependencies_opensuse(){
echo "
+ apparmor-profiles
curl
expect
git
golang-packaging
jq
lsb-release
+ man
netcat-openbsd
osc
uuidd
diff -Nru snapd-2.32.3.2/tests/lib/prepare-restore.sh snapd-2.32.9/tests/lib/prepare-restore.sh
--- snapd-2.32.3.2/tests/lib/prepare-restore.sh 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/tests/lib/prepare-restore.sh 2018-05-16 08:20:08.000000000 +0000
@@ -359,6 +359,14 @@
cat /proc/meminfo
exit 1
fi
+
+ # Check for kernel oops during the tests
+ if dmesg|grep "Oops: "; then
+ echo "A kernel oops happened during the tests, test results will be unreliable"
+ echo "Dmesg debug output:"
+ dmesg
+ exit 1
+ fi
}
restore_project() {
diff -Nru snapd-2.32.3.2/tests/lib/reset.sh snapd-2.32.9/tests/lib/reset.sh
--- snapd-2.32.3.2/tests/lib/reset.sh 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/tests/lib/reset.sh 2018-05-16 08:20:08.000000000 +0000
@@ -5,24 +5,15 @@
# shellcheck source=tests/lib/dirs.sh
. "$TESTSLIB/dirs.sh"
+# shellcheck source=tests/lib/systemd.sh
+. "$TESTSLIB/systemd.sh"
+
reset_classic() {
# Reload all service units as in some situations the unit might
# have changed on the disk.
systemctl daemon-reload
- echo "Ensure the service is active before stopping it"
- retries=20
- systemctl status snapd.service snapd.socket || true
- while systemctl status snapd.service snapd.socket | grep "Active: activating"; do
- if [ $retries -eq 0 ]; then
- echo "snapd service or socket not active"
- exit 1
- fi
- retries=$(( $retries - 1 ))
- sleep 1
- done
-
- systemctl stop snapd.service snapd.socket
+ systemd_stop_units snapd.service snapd.socket
case "$SPREAD_SYSTEM" in
ubuntu-*|debian-*)
diff -Nru snapd-2.32.3.2/tests/lib/snaps/test-snapd-service/bin/reload snapd-2.32.9/tests/lib/snaps/test-snapd-service/bin/reload
--- snapd-2.32.3.2/tests/lib/snaps/test-snapd-service/bin/reload 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/tests/lib/snaps/test-snapd-service/bin/reload 2018-05-16 08:20:08.000000000 +0000
@@ -5,4 +5,4 @@
exit 1
fi
echo "reloading main PID: $MAINPID"
-kill -HUP $MAINPID
+kill -HUP "$MAINPID"
diff -Nru snapd-2.32.3.2/tests/lib/snaps/test-snapd-service/bin/start-refresh-mode snapd-2.32.9/tests/lib/snaps/test-snapd-service/bin/start-refresh-mode
--- snapd-2.32.3.2/tests/lib/snaps/test-snapd-service/bin/start-refresh-mode 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/tests/lib/snaps/test-snapd-service/bin/start-refresh-mode 1970-01-01 00:00:00.000000000 +0000
@@ -1,11 +0,0 @@
-#!/bin/bash
-
-trap "echo got sigusr1" USR1
-trap "echo got sigusr2" USR2
-trap "echo got sighup" HUP
-
-while true; do
- echo "running $1 process"
- sleep 1
-done
-
diff -Nru snapd-2.32.3.2/tests/lib/snaps/test-snapd-service/bin/start-stop-mode snapd-2.32.9/tests/lib/snaps/test-snapd-service/bin/start-stop-mode
--- snapd-2.32.3.2/tests/lib/snaps/test-snapd-service/bin/start-stop-mode 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/tests/lib/snaps/test-snapd-service/bin/start-stop-mode 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+SIG=0
+trap "echo got sigusr1; SIG=1" USR1
+trap "echo got sigusr2; SIG=1" USR2
+trap "echo got sighup; SIG=1" HUP
+
+while [ "$SIG" = "0" ]; do
+ echo "running $1 process"
+ sleep 1
+done
diff -Nru snapd-2.32.3.2/tests/lib/snaps/test-snapd-service/bin/start-stop-mode-sigterm snapd-2.32.9/tests/lib/snaps/test-snapd-service/bin/start-stop-mode-sigterm
--- snapd-2.32.3.2/tests/lib/snaps/test-snapd-service/bin/start-stop-mode-sigterm 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/tests/lib/snaps/test-snapd-service/bin/start-stop-mode-sigterm 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+set -e
+
+echo "start-refresh-mode-sigkill"
+
+echo "running a process"
+sleep 3133731337
diff -Nru snapd-2.32.3.2/tests/lib/snaps/test-snapd-service/bin/stop snapd-2.32.9/tests/lib/snaps/test-snapd-service/bin/stop
--- snapd-2.32.3.2/tests/lib/snaps/test-snapd-service/bin/stop 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/tests/lib/snaps/test-snapd-service/bin/stop 2018-05-16 08:20:08.000000000 +0000
@@ -1,3 +1,7 @@
#!/bin/sh
echo "stop service"
+
+if [ -n "$1" ]; then
+ sleep "$1"
+fi
diff -Nru snapd-2.32.3.2/tests/lib/snaps/test-snapd-service/bin/stop-refresh-mode snapd-2.32.9/tests/lib/snaps/test-snapd-service/bin/stop-refresh-mode
--- snapd-2.32.3.2/tests/lib/snaps/test-snapd-service/bin/stop-refresh-mode 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/tests/lib/snaps/test-snapd-service/bin/stop-refresh-mode 1970-01-01 00:00:00.000000000 +0000
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-echo "stop $1 process"
diff -Nru snapd-2.32.3.2/tests/lib/snaps/test-snapd-service/bin/stop-stop-mode snapd-2.32.9/tests/lib/snaps/test-snapd-service/bin/stop-stop-mode
--- snapd-2.32.3.2/tests/lib/snaps/test-snapd-service/bin/stop-stop-mode 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/tests/lib/snaps/test-snapd-service/bin/stop-stop-mode 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo "stop $1 process"
diff -Nru snapd-2.32.3.2/tests/lib/snaps/test-snapd-service/meta/snap.yaml snapd-2.32.9/tests/lib/snaps/test-snapd-service/meta/snap.yaml
--- snapd-2.32.3.2/tests/lib/snaps/test-snapd-service/meta/snap.yaml 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/tests/lib/snaps/test-snapd-service/meta/snap.yaml 2018-05-16 08:20:08.000000000 +0000
@@ -9,48 +9,51 @@
test-snapd-other-service:
command: bin/start-other
daemon: simple
- test-snapd-endure-service:
- command: bin/start-refresh-mode endure
- stop-command: bin/stop-refresh-mode endure
- daemon: simple
- refresh-mode: endure
- test-snapd-sigterm-service:
- command: bin/start-refresh-mode sigterm
- stop-command: bin/stop-refresh-mode sigterm
- daemon: simple
- refresh-mode: sigterm
- test-snapd-sigterm-all-service:
- command: bin/start-refresh-mode sigterm-all
- stop-command: bin/stop-refresh-mode sigterm-all
- daemon: simple
- refresh-mode: sigterm
test-snapd-sighup-service:
- command: bin/start-refresh-mode sighup
- stop-command: bin/stop-refresh-mode sighup
+ command: bin/start-stop-mode sighup
+ stop-command: bin/stop-stop-mode sighup
daemon: simple
- refresh-mode: sighup
+ stop-mode: sighup
test-snapd-sighup-all-service:
- command: bin/start-refresh-mode sighup-all
- stop-command: bin/stop-refresh-mode sighup-all
+ command: bin/start-stop-mode sighup-all
+ stop-command: bin/stop-stop-mode sighup-all
daemon: simple
- refresh-mode: sighup-all
+ stop-mode: sighup-all
test-snapd-sigusr1-service:
- command: bin/start-refresh-mode sigusr1
- stop-command: bin/stop-refresh-mode sigusr1
+ command: bin/start-stop-mode sigusr1
+ stop-command: bin/stop-stop-mode sigusr1
daemon: simple
- refresh-mode: sigusr1
+ stop-mode: sigusr1
test-snapd-sigusr1-all-service:
- command: bin/start-refresh-mode sigusr1-all
- stop-command: bin/stop-refresh-mode sigusr1-all
+ command: bin/start-stop-mode sigusr1-all
+ stop-command: bin/stop-stop-mode sigusr1-all
daemon: simple
- refresh-mode: sigusr1-all
+ stop-mode: sigusr1-all
test-snapd-sigusr2-service:
- command: bin/start-refresh-mode sigusr2
- stop-command: bin/stop-refresh-mode sigusr2
+ command: bin/start-stop-mode sigusr2
+ stop-command: bin/stop-stop-mode sigusr2
daemon: simple
- refresh-mode: sigusr2
+ stop-mode: sigusr2
test-snapd-sigusr2-all-service:
- command: bin/start-refresh-mode sigusr2-all
- stop-command: bin/stop-refresh-mode sigusr2-all
+ command: bin/start-stop-mode sigusr2-all
+ stop-command: bin/stop-stop-mode sigusr2-all
+ daemon: simple
+ stop-mode: sigusr2-all
+ test-snapd-sigterm-service:
+ command: bin/start-stop-mode-sigterm
+ daemon: simple
+ stop-mode: sigterm
+ test-snapd-sigterm-all-service:
+ command: bin/start-stop-mode-sigterm
+ daemon: simple
+ stop-mode: sigterm-all
+ test-snapd-endure-service:
+ command: bin/start-stop-mode endure
+ stop-command: bin/stop-stop-mode endure
+ daemon: simple
+ refresh-mode: endure
+ test-snapd-service-refuses-to-stop:
+ command: bin/start
daemon: simple
- refresh-mode: sigusr2-all
+ stop-command: bin/stop 60
+ stop-timeout: 10s
diff -Nru snapd-2.32.3.2/tests/lib/snaps/test-snapd-xdg-autostart/bin/foobar snapd-2.32.9/tests/lib/snaps/test-snapd-xdg-autostart/bin/foobar
--- snapd-2.32.3.2/tests/lib/snaps/test-snapd-xdg-autostart/bin/foobar 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/tests/lib/snaps/test-snapd-xdg-autostart/bin/foobar 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+dump_autostart() {
+ dfpath="$SNAP_USER_DATA/.config/autostart/foo.desktop"
+
+ test -e "$dfpath" && return
+
+ echo "generating autostart file $dfpath"
+
+ mkdir -p $SNAP_USER_DATA/.config/autostart
+ cat < $dfpath
+[Desktop Entry]
+Name=foo autostart
+Exec=/foo/bar/baz/bin/foobar --autostart a b c
+EOF
+}
+
+case "$1" in
+ --autostart)
+ echo "autostarting with args '$@'" | tee $SNAP_USER_DATA/foo-autostarted
+ ;;
+ *)
+ echo "regular run with args '$@'"
+ dump_autostart
+ ;;
+esac
diff -Nru snapd-2.32.3.2/tests/lib/snaps/test-snapd-xdg-autostart/meta/snap.yaml snapd-2.32.9/tests/lib/snaps/test-snapd-xdg-autostart/meta/snap.yaml
--- snapd-2.32.3.2/tests/lib/snaps/test-snapd-xdg-autostart/meta/snap.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/tests/lib/snaps/test-snapd-xdg-autostart/meta/snap.yaml 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,6 @@
+name: test-snapd-xdg-autostart
+version: 1.0
+apps:
+ foo:
+ command: bin/foobar
+ autostart: foo.desktop
diff -Nru snapd-2.32.3.2/tests/lib/systemd.sh snapd-2.32.9/tests/lib/systemd.sh
--- snapd-2.32.3.2/tests/lib/systemd.sh 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/tests/lib/systemd.sh 2018-05-16 08:20:08.000000000 +0000
@@ -36,3 +36,23 @@
echo "service $service_name did not start"
exit 1
}
+
+systemd_stop_units() {
+ for unit in "$@"; do
+ if systemctl is-active "$unit"; then
+ echo "Ensure the service is active before stopping it"
+ retries=20
+ systemctl status "$unit" || true
+ while systemctl status "$unit" | grep "Active: activating"; do
+ if [ $retries -eq 0 ]; then
+ echo "$unit unit not active"
+ exit 1
+ fi
+ retries=$(( $retries - 1 ))
+ sleep 1
+ done
+
+ systemctl stop "$unit"
+ fi
+ done
+}
diff -Nru snapd-2.32.3.2/tests/main/completion/task.yaml snapd-2.32.9/tests/main/completion/task.yaml
--- snapd-2.32.3.2/tests/main/completion/task.yaml 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/tests/main/completion/task.yaml 2018-05-16 08:20:08.000000000 +0000
@@ -9,7 +9,9 @@
NAMES: /var/cache/snapd/names
prepare: |
- systemctl stop snapd.service snapd.socket
+ . "$TESTSLIB/systemd.sh"
+
+ systemd_stop_units snapd.service snapd.socket
[ -e "$NAMES" ] && mv "$NAMES" "$NAMES.orig"
cat >"$NAMES" <test.img
+ mount -t vfat test.img /boot/uboot
+ n=$(ls /boot/uboot | grep ^uboot.env$ | wc -l)
+ if [ "$n" != "2" ]; then
+ echo "Image not broken in the right way, expected two uboot.env files"
+ ls /boot/uboot
+ exit 1
+ fi
+
+ echo "Trigger cleanup"
+ systemctl restart snapd.core-fixup.service
+
+ n=$(ls /boot/uboot | grep ^uboot.env$ | wc -l)
+ if [ "$n" != "1" ]; then
+ echo "Image not repaired"
+ ls /boot/uboot
+ exit 1
+ fi
Binary files /tmp/tmp4OuFJA/YuzmSqdfjj/snapd-2.32.3.2/tests/main/snap-core-fixup/test.img.xz and /tmp/tmp4OuFJA/pdkogafPOb/snapd-2.32.9/tests/main/snap-core-fixup/test.img.xz differ
diff -Nru snapd-2.32.3.2/tests/main/snap-service/task.yaml snapd-2.32.9/tests/main/snap-service/task.yaml
--- snapd-2.32.3.2/tests/main/snap-service/task.yaml 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/tests/main/snap-service/task.yaml 2018-05-16 08:20:08.000000000 +0000
@@ -17,3 +17,12 @@
while ! systemctl status snap.test-snapd-service.test-snapd-service|grep "reloading reloading reloading"; do
sleep 1
done
+
+ echo "A snap that refuses to stop is killed eventually"
+ snap stop test-snapd-service.test-snapd-service-refuses-to-stop
+ # systemd in 14.04 does not provide the "Result: timeout" information
+ if [[ "$SPREAD_SYSTEM" == ubuntu-14.04-* ]]; then
+ systemctl status snap.test-snapd-service.test-snapd-service-refuses-to-stop|MATCH "code=killed"
+ else
+ systemctl status snap.test-snapd-service.test-snapd-service-refuses-to-stop|MATCH "Result: timeout"
+ fi
diff -Nru snapd-2.32.3.2/tests/main/snap-service-refresh-mode/task.yaml snapd-2.32.9/tests/main/snap-service-refresh-mode/task.yaml
--- snapd-2.32.3.2/tests/main/snap-service-refresh-mode/task.yaml 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/tests/main/snap-service-refresh-mode/task.yaml 2018-05-16 08:20:08.000000000 +0000
@@ -1,46 +1,32 @@
summary: "Check that refresh-modes works"
-kill-timeout: 3m
+kill-timeout: 5m
debug: |
- journalctl -u snap.test-snapd-service.test-snapd-endure-service
+ grep -n '' *.pid || true
+ systemctl status snap.test-snapd-service.test-snapd-endure-service || true
execute: |
echo "When the service snap is installed"
+
. $TESTSLIB/snaps.sh
install_local test-snapd-service
echo "We can see it running"
systemctl status snap.test-snapd-service.test-snapd-endure-service|MATCH "running"
+ systemctl show -p MainPID snap.test-snapd-service.test-snapd-endure-service > old-main.pid
echo "When it is re-installed"
install_local test-snapd-service
- # note that we do not spread test sigterm{,-all} because catching this
- # signal in the service means the stop will timeout with a 90s delay
- refresh_modes="endure sighup sighup-all sigusr1 sigusr1-all sigusr2 sigusr2-all"
- for s in $refresh_modes; do
- echo "We can still see it running"
- systemctl status snap.test-snapd-service.test-snapd-${s}-service|MATCH "running"
-
- echo "And it is not re-started"
- if journalctl -u snap.test-snapd-service.test-snapd-${s}-service | grep "stop ${s}"; then
- echo "reinstall did stop a service that shouldn't"
- exit 1
- fi
-
- if [[ "$s" == sig* ]]; then
- echo "checking that the right signal was sent"
- sleep 1
- journalctl -u snap.test-snapd-service.test-snapd-${s}-service | MATCH "got ${s%%-all}"
- fi
- done
+ echo "We can still see it running with the same PID"
+ systemctl show -p MainPID snap.test-snapd-service.test-snapd-endure-service > new-main.pid
- echo "But regular services are restarted"
- journalctl -u snap.test-snapd-service.test-snapd-service | MATCH "stop service"
+ test "$(cat new-endure.pid)" = "$(cat old-endure.pid)"
echo "Once the snap is removed, the service is stopped"
snap remove test-snapd-service
- for s in $refresh_modes; do
- journalctl | MATCH "stop ${s}"
- done
+ journalctl | MATCH "stop endure"
+
+restore:
+ rm -f *.pid || true
diff -Nru snapd-2.32.3.2/tests/main/snap-service-stop-mode/task.yaml snapd-2.32.9/tests/main/snap-service-stop-mode/task.yaml
--- snapd-2.32.3.2/tests/main/snap-service-stop-mode/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/tests/main/snap-service-stop-mode/task.yaml 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,54 @@
+summary: "Check that stop-modes works"
+
+kill-timeout: 5m
+
+# journald in ubuntu-14.04 not reliable
+systems: [-ubuntu-14.04-*]
+
+debug: |
+ stop_modes="sighup sighup-all sigusr1 sigusr1-all sigusr2 sigusr2-all"
+ for s in $stop_modes; do
+ systemctl status snap.test-snapd-service.test-snapd-${s}-service || true
+ done
+
+execute: |
+ echo "When the service snap is installed"
+ . $TESTSLIB/snaps.sh
+ install_local test-snapd-service
+
+ echo "We can see it running"
+ systemctl status snap.test-snapd-service.test-snapd-service|MATCH "running"
+
+ stop_modes="sighup sighup-all sigusr1 sigusr1-all sigusr2 sigusr2-all"
+ for s in $stop_modes; do
+ systemctl show -p ActiveState snap.test-snapd-service.test-snapd-${s}-service | MATCH "ActiveState=active"
+ done
+
+ echo "When it is re-installed"
+ install_local test-snapd-service
+
+ # note that sigterm{,-all} is tested separately
+ for s in $stop_modes; do
+ echo "We can see it is running"
+ systemctl show -p ActiveState snap.test-snapd-service.test-snapd-${s}-service | MATCH "ActiveState=active"
+
+ echo "and it got the right signal"
+ echo "checking that the right signal was sent"
+ sleep 1
+ journalctl -u snap.test-snapd-service.test-snapd-${s}-service | MATCH "got ${s%%-all}"
+ done
+
+ echo "Regular services are restarted normally"
+ journalctl -u snap.test-snapd-service.test-snapd-service | MATCH "stop service"
+
+ echo "Once the snap is removed, all services are stopped"
+ snap remove test-snapd-service
+ for s in $stop_modes; do
+ journalctl | MATCH "stop ${s}"
+ done
+
+restore: |
+ rm -f *.pid || true
+ # remove to ensure all services are stopped
+ snap remove test-snapd-service || true
+ killall sleep || true
diff -Nru snapd-2.32.3.2/tests/main/snap-service-stop-mode-sigkill/task.yaml snapd-2.32.9/tests/main/snap-service-stop-mode-sigkill/task.yaml
--- snapd-2.32.3.2/tests/main/snap-service-stop-mode-sigkill/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/tests/main/snap-service-stop-mode-sigkill/task.yaml 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,37 @@
+summary: "Check that refresh-modes sigkill works"
+
+kill-timeout: 5m
+
+restore: |
+ # remove to ensure all services are stopped
+ snap remove test-snapd-service || true
+ killall sleep || true
+
+debug: |
+ ps afx
+
+execute: |
+ echo "Ensure no stray sleep processes are around"
+ killall sleep || true
+
+ echo "When the service snap is installed"
+ . $TESTSLIB/snaps.sh
+ install_local test-snapd-service
+
+ refresh_modes="sigterm sigterm-all"
+ for s in $refresh_modes; do
+ systemctl show -p ActiveState snap.test-snapd-service.test-snapd-${s}-service | MATCH "ActiveState=active"
+ done
+
+ echo "we expect two sleep processes (children) from the two sigterm services"
+ n=$(ps afx | grep [3]133731337 | grep -v grep | wc -l)
+ [ "$n" = "2" ]
+
+ echo "When it is re-installed one process uses sigterm, the other sigterm-all"
+ install_local test-snapd-service
+
+ echo "After reinstall the sigterm-all service and all children got killed"
+ echo "but the sigterm service only got a kill for the main process "
+ echo "and one sleep is still alive"
+ n=$(ps afx | grep [3]133731337 | wc -l)
+ [ "$n" = "3" ]
diff -Nru snapd-2.32.3.2/tests/main/snap-userd-desktop-app-autostart/task.yaml snapd-2.32.9/tests/main/snap-userd-desktop-app-autostart/task.yaml
--- snapd-2.32.3.2/tests/main/snap-userd-desktop-app-autostart/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/tests/main/snap-userd-desktop-app-autostart/task.yaml 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,42 @@
+summary: Check that snap userd can autostart user session applications
+
+execute: |
+ echo "When the snap is installed"
+ . $TESTSLIB/snaps.sh
+ install_local test-snapd-xdg-autostart
+
+ # run the app directly, it will dump a *.desktop file
+ snap run test-snapd-xdg-autostart.foo
+
+ echo "And snap application autostart file exists"
+ test -e ~/snap/test-snapd-xdg-autostart/current/.config/autostart/foo.desktop
+
+ echo "Applications can be automatically started by snap userd --autostart"
+
+ test ! -e ~/snap/test-xdg-snap-autostart/current/foo-autostarted
+ if [[ "$SPREAD_SYSTEM" != ubuntu-14.04-* ]]; then
+ # 14.04 journalctl does not have cursor support
+ cursor=$(journalctl --show-cursor -n 0 |grep cursor | cut -f3 -d' ')
+ fi
+ snap userd --autostart > autostart.log 2>&1
+
+ # when app is autostarted it dumps a file at $SNAP_USER_DATA/foo-autostarted,
+ # applications are forked by userd, but userd does not wait for them
+ for i in `seq 20`; do
+ test -e ~/snap/test-snapd-xdg-autostart/current/foo-autostarted && break
+ sleep 1
+ done
+ test -e ~/snap/test-snapd-xdg-autostart/current/foo-autostarted
+
+ if [[ "$SPREAD_SYSTEM" != ubuntu-14.04-* ]]; then
+ journalctl --flush
+ test "$(journalctl -t foo.desktop --after-cursor=$cursor | wc -l)" -eq 1
+ else
+ # 14.04 journalctl does not have cursor, neither --identifier support,
+ # nor --flush
+ test "$(journalctl | grep foo.desktop | wc -l)" -eq 1
+ fi
+
+restore: |
+ rm -f ~/snap/test-snapd-xdg-autostart/current/foo-autostarted
+ rm -f ~/snap/test-snapd-xdg-autostart/current/.config/autostart/foo.desktop
diff -Nru snapd-2.32.3.2/tests/main/snap-wait/task.yaml snapd-2.32.9/tests/main/snap-wait/task.yaml
--- snapd-2.32.3.2/tests/main/snap-wait/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/tests/main/snap-wait/task.yaml 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,26 @@
+summary: Check that `snap wait` works
+
+kill-timeout: 2m
+
+prepare: |
+ . "$TESTSLIB/snaps.sh"
+ install_local basic-hooks
+
+execute: |
+ echo "Ensure snap wait for seeding works"
+ snap wait system seed.loaded
+
+ echo "Ensure snap wait for arbitrary stuff works"
+ # set to a false value
+ snap set basic-hooks foo=0
+ # keep track
+ start=$(date +%s)
+ # ensure we wait 3s before the false value becomes true
+ ((sleep 3; snap set basic-hooks foo=1)&)
+ snap wait basic-hooks foo
+ end=$(date +%s)
+ # ensure we waited
+ if [ $((end-start)) -lt 2 ]; then
+ echo "snap wait returned too early"
+ exit 1
+ fi
diff -Nru snapd-2.32.3.2/tests/main/system-core-alias/task.yaml snapd-2.32.9/tests/main/system-core-alias/task.yaml
--- snapd-2.32.3.2/tests/main/system-core-alias/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/tests/main/system-core-alias/task.yaml 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,19 @@
+summary: Verify that 'system' can be used as an alias for 'core'
+
+execute: |
+ echo "When a configuration is set for the core snap"
+ snap set core foo.bar=true
+ snap get core foo.bar | MATCH "true"
+
+ echo "It can be retrieved using system alias"
+ snap get system foo.bar | MATCH "true"
+
+ echo "When a configuration is set using the system alias"
+ snap set system foo.bar=baz
+ snap get system foo.bar | MATCH "baz"
+
+ echo "It is also visible through the core snap"
+ snap get core foo.bar | MATCH "baz"
+debug: |
+ snap get core -d || true
+ snap get system -d || true
diff -Nru snapd-2.32.3.2/.travis.yml snapd-2.32.9/.travis.yml
--- snapd-2.32.3.2/.travis.yml 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/.travis.yml 2018-05-16 08:20:08.000000000 +0000
@@ -10,6 +10,8 @@
- secure: "LjqfvJ2xz/7cxt1Cywaw5l8gaj5jOhUsf502UeaH+rOnj+9tCdWTtyP8U4nOjjQwiJ0xuygba+FgdnXEyxV+THeXHOF69SRF/1N8JIc3i9G6JK/CqDfFTRMqiRaCf5u7KuOrYZ0ssYNBXyZ8X4Ahls3uFu2DgEuAim1J6wOVSgIoUkduLVrbsn6uB9G5Uuc+C4NMA3TH21IJ6ct35t3T+/EjvoGUHcKtoOsPXdBZvz96xw5mKGIBaLpZdy5WxmhPUsz3MIlZgvi4DR3YIa/9u+QoGNU05f8upJRhwdwkuu9vJwqekXNXDJi/ZGlpkkAPx0feJbyhtz68551Pn1TtmA3TS5JtuMeMZWxCL9SudA7/C3oBRNGnKI3LwvP20pPjdlEYMOCq/oHlxoJylGVdpynZXTtaFS+s4Qhnr+WuNcG3zFa9bJvXPyy1vxPKcjI2DojneTrCTW/L6zg7tBIVQGzTxmC7QWsbTvOQzu+YICyeeS3g+iJ+QyP6+/oTyER3a3vmZCtXqsBJTznesS0SL5AkK+8moBGct96S6kT55XCDVgThWV0OGH6l4LwVSOjPioNzXNhVLZ8GKkXrMZXKSaWAeYptzWl4Gfz0Y4nFCu3aqIOyie7janPPgeEL0E2ZjndIs+ZigtN1LCol+GJN7fXzUFy8Fichqhhwvb3YLyE="
# SPREAD_STORE_PASSWORD
- secure: "Le4CMhklfadi4aBQIEaEMbsFIB608GOvSHjVUxkDxkkUAVwl/Ov4Dni5d0Tn4e/xcxPkcm+pPg64dn0Jxzwx6XfWlxhWC10vYh+/GjpZW1znahtb/Gf9CNZOJJEy5LSeI7/uJ3LYcFd0FU0EJSerNeQJc5d8jmJH8UnuqObHOk29YD//XILiLRa1XALEimwXeQyGQePBmDTxPQQv1VLFjgfaJa5Xy55Us7AKTML2V7lhaeKCSEIp3x9liLAtnKlJhyXaXO/e4b3ZJTgXwYh+vENK1E2pxalpjBNPaJNkvtbsjFtYNXJoXca+hBVs5Sq1PCBhkEGxqFUsD8VLQd+MEXp4MYOF5fBhxIa3qOSjtuR+WmZ9G6fEysEBV6Y3F3D6HYWTpNkcHNXJCwdtOM+n92zNEBDIrufwzTPpyJXpoxZCCXrk3HHRdyDktvJYLrHdn1bM19mgYguesMZHTC5xMD6ifwdRoylmApjImXOvVxf2HdQiNvNLDqvaHgmYwNfl0+KbaVz+O2EDPCRnT5wOCpSeSUet47EPITdjr5OnTwLpOVaY+iSvn90EUB/8+ZU01TRYgc+6VNPHokLVjuiQJSrE4yTx/c2MnY9eRaOosVXngYfoS/L3XwDwZiQoeLZs04bScvxzGQIGCJ+CBzNPENtZ4AUh55Yl/vVNReZJeaY="
+ # SPREAD_GOOGLE_KEY
+ - secure: "dIA2HrartowFL2Gl5jXiVMd9hIJyIeummYwxeBL9MzO48E/BIJyIGHudEOo8oCnZ5a0yb8TqYgND2FCgJU1V5I2LyxH6T9kizHjtmIGgeM4qlEGKRlptb2v7DFkaHeW4Mpp4gLk8hYIeWyq9OR+SlK6f0Jj049LLKfQoX6GzTPug5+MMEQOJs55OJ6f6gvCv2o3oj6WFybaohMCO4GbNYQSPLwheyTSkT0efnW9QqTN0w62pDMqscVURO90/CUeZyCcXw2uOBegwPNTBoo/+4+nZsfSNeupV8wX4vVYL0ZFL6IO3mViDoZBD4SGTNF/9x8Lc1WeKm9HlELzy5krdLqsvdV/fQSWhBzwkdykKVA3Aae5dAMIGRt7e5bJaUg+/HdtOgA5jr+qey/c/BN11MyaSOMNPNGjRuv9NAcEjxoN2JkiDXfpA3lE9kjd7TBTexGe4RJGJLJjT9s8XxdKufBfruC/yhVGdVkRoc2tsAJPZ72Ds9qH0FH28zNFAgAitCLDfInjhPMPvZJhb3Bqx5P/0DE5zUbduE9kYK0iiZRJ4AaytQy+R4nJCXE42mWv5cxoE84opVqO9cBu1TPCC8gTRQFWpJt1rP+DvwjaFiswvptG8obxNpHmkhcItPGmRVN9P9Yjd9nHvegS83tsbrd2KOyMmCk3/1KWhLufisHE="
git:
quiet: true
diff -Nru snapd-2.32.3.2/userd/autostart.go snapd-2.32.9/userd/autostart.go
--- snapd-2.32.3.2/userd/autostart.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/userd/autostart.go 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,225 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2018 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package userd
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "log/syslog"
+ "os"
+ "os/exec"
+ "os/user"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/logger"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/strutil/shlex"
+ "github.com/snapcore/snapd/systemd"
+)
+
+// expandDesktopFields processes the input string and expands any %
+// patterns. '%%' expands to '%', all other patterns expand to empty strings.
+func expandDesktopFields(in string) string {
+ raw := []rune(in)
+ out := make([]rune, 0, len(raw))
+
+ var hasKey bool
+ for _, r := range raw {
+ if hasKey {
+ hasKey = false
+ // only allow %% -> % expansion, drop other keys
+ if r == '%' {
+ out = append(out, r)
+ }
+ continue
+ } else if r == '%' {
+ hasKey = true
+ continue
+ }
+ out = append(out, r)
+ }
+ return string(out)
+}
+
+// Note: consider wrappers/desktop.go if we start parsing more than Exec line
+func findExec(desktopFileContent []byte) (string, error) {
+ scanner := bufio.NewScanner(bytes.NewBuffer(desktopFileContent))
+ execCmd := ""
+ for scanner.Scan() {
+ bline := scanner.Bytes()
+
+ if !bytes.HasPrefix(bline, []byte("Exec=")) {
+ continue
+ }
+
+ full := string(bline[len("Exec="):])
+ execCmd = expandDesktopFields(full)
+ break
+ }
+ if err := scanner.Err(); err != nil {
+ return "", err
+ }
+
+ execCmd = strings.TrimSpace(execCmd)
+ if execCmd == "" {
+ return "", fmt.Errorf("Exec not found or invalid")
+ }
+ return execCmd, nil
+}
+
+func autostartCmd(snapName, desktopFilePath string) (*exec.Cmd, error) {
+ desktopFile := filepath.Base(desktopFilePath)
+
+ info, err := snap.ReadCurrentInfo(snapName)
+ if err != nil {
+ return nil, err
+ }
+
+ var app *snap.AppInfo
+ for _, candidate := range info.Apps {
+ if candidate.Autostart == desktopFile {
+ app = candidate
+ break
+ }
+ }
+ if app == nil {
+ return nil, fmt.Errorf("cannot match desktop file with snap %s applications", snapName)
+ }
+
+ content, err := ioutil.ReadFile(desktopFilePath)
+ if err != nil {
+ return nil, err
+ }
+
+ // NOTE: Ignore all fields and just look for Exec=..., this also means
+ // that fields with meaning such as TryExec, X-GNOME-Autostart and so on
+ // are ignored
+
+ command, err := findExec(content)
+ if err != nil {
+ return nil, fmt.Errorf("cannot determine startup command for application %s in snap %s: %v", app.Name, snapName, err)
+ }
+ logger.Debugf("exec line: %v", command)
+
+ split, err := shlex.Split(command)
+ if err != nil {
+ return nil, fmt.Errorf("invalid application startup command: %v", err)
+ }
+
+ // NOTE: Ignore the actual argv[0] in Exec=.. line and replace it with a
+ // command of the snap application. Any arguments passed in the Exec=..
+ // line to the original command are preserved.
+ cmd := exec.Command(app.WrapperPath(), split[1:]...)
+ return cmd, nil
+}
+
+// failedAutostartError keeps track of errors that occurred when starting an
+// application for a specific desktop file, desktop file name is as a key
+type failedAutostartError map[string]error
+
+func (f failedAutostartError) Error() string {
+ var out bytes.Buffer
+
+ dfiles := make([]string, 0, len(f))
+ for desktopFile := range f {
+ dfiles = append(dfiles, desktopFile)
+ }
+ sort.Strings(dfiles)
+ for _, desktopFile := range dfiles {
+ fmt.Fprintf(&out, "- %q: %v\n", desktopFile, f[desktopFile])
+ }
+ return out.String()
+}
+
+func makeStdStreams(identifier string) (stdout *os.File, stderr *os.File) {
+ var err error
+
+ stdout, err = systemd.NewJournalStreamFile(identifier, syslog.LOG_INFO, false)
+ if err != nil {
+ logger.Noticef("failed to set up stdout journal stream for %q: %v", identifier, err)
+ stdout = os.Stdout
+ }
+
+ stderr, err = systemd.NewJournalStreamFile(identifier, syslog.LOG_WARNING, false)
+ if err != nil {
+ logger.Noticef("failed to set up stderr journal stream for %q: %v", identifier, err)
+ stderr = os.Stderr
+ }
+
+ return stdout, stderr
+}
+
+var userCurrent = user.Current
+
+// AutostartSessionApps starts applications which have placed their desktop
+// files in $SNAP_USER_DATA/.config/autostart
+//
+// NOTE: By the spec, the actual path is $SNAP_USER_DATA/${XDG_CONFIG_DIR}/autostart
+func AutostartSessionApps() error {
+ usr, err := userCurrent()
+ if err != nil {
+ return err
+ }
+
+ usrSnapDir := filepath.Join(usr.HomeDir, dirs.UserHomeSnapDir)
+
+ glob := filepath.Join(usrSnapDir, "*/current/.config/autostart/*.desktop")
+ matches, err := filepath.Glob(glob)
+ if err != nil {
+ return err
+ }
+
+ failedApps := make(failedAutostartError)
+ for _, desktopFilePath := range matches {
+ desktopFile := filepath.Base(desktopFilePath)
+ logger.Debugf("autostart desktop file %v", desktopFile)
+
+ // /home/foo/snap/some-snap/current/.config/autostart/some-app.desktop ->
+ // some-snap/current/.config/autostart/some-app.desktop
+ noHomePrefix := strings.TrimPrefix(desktopFilePath, usrSnapDir+"/")
+ // some-snap/current/.config/autostart/some-app.desktop -> some-snap
+ snapName := noHomePrefix[0:strings.IndexByte(noHomePrefix, '/')]
+
+ logger.Debugf("snap name: %q", snapName)
+
+ cmd, err := autostartCmd(snapName, desktopFilePath)
+ if err != nil {
+ failedApps[desktopFile] = err
+ continue
+ }
+
+ // similarly to gnome-session, use the desktop file name as
+ // identifier, see:
+ // https://github.com/GNOME/gnome-session/blob/099c19099de8e351f6cc0f2110ad27648780a0fe/gnome-session/gsm-autostart-app.c#L948
+ cmd.Stdout, cmd.Stderr = makeStdStreams(desktopFile)
+ if err := cmd.Start(); err != nil {
+ failedApps[desktopFile] = fmt.Errorf("cannot autostart %q: %v", desktopFile, err)
+ }
+ }
+ if len(failedApps) > 0 {
+ return failedApps
+ }
+ return nil
+}
diff -Nru snapd-2.32.3.2/userd/autostart_test.go snapd-2.32.9/userd/autostart_test.go
--- snapd-2.32.3.2/userd/autostart_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.32.9/userd/autostart_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -0,0 +1,244 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2018 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package userd_test
+
+import (
+ "io/ioutil"
+ "os"
+ "os/user"
+ "path"
+ "path/filepath"
+ "strings"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/snaptest"
+ "github.com/snapcore/snapd/testutil"
+ "github.com/snapcore/snapd/userd"
+)
+
+type autostartSuite struct {
+ dir string
+ autostartDir string
+ userDir string
+ userCurrentRestore func()
+}
+
+var _ = Suite(&autostartSuite{})
+
+func (s *autostartSuite) SetUpTest(c *C) {
+ s.dir = c.MkDir()
+ dirs.SetRootDir(s.dir)
+ snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})
+
+ s.userDir = path.Join(s.dir, "home")
+ s.autostartDir = path.Join(s.userDir, ".config", "autostart")
+ s.userCurrentRestore = userd.MockUserCurrent(func() (*user.User, error) {
+ return &user.User{HomeDir: s.userDir}, nil
+ })
+
+ err := os.MkdirAll(s.autostartDir, 0755)
+ c.Assert(err, IsNil)
+}
+
+func (s *autostartSuite) TearDownTest(c *C) {
+ s.dir = c.MkDir()
+ dirs.SetRootDir("/")
+ if s.userCurrentRestore != nil {
+ s.userCurrentRestore()
+ }
+}
+
+func (s *autostartSuite) TestFindExec(c *C) {
+ allGood := `[Desktop Entry]
+Exec=foo --bar
+`
+ allGoodWithFlags := `[Desktop Entry]
+Exec=foo --bar "%%p" %U %D +%s %%
+`
+ noExec := `[Desktop Entry]
+Type=Application
+`
+ emptyExec := `[Desktop Entry]
+Exec=
+`
+ onlySpacesExec := `[Desktop Entry]
+Exec=
+`
+ for i, tc := range []struct {
+ in string
+ out string
+ err string
+ }{{
+ in: allGood,
+ out: "foo --bar",
+ }, {
+ in: noExec,
+ err: "Exec not found or invalid",
+ }, {
+ in: emptyExec,
+ err: "Exec not found or invalid",
+ }, {
+ in: onlySpacesExec,
+ err: "Exec not found or invalid",
+ }, {
+ in: allGoodWithFlags,
+ out: `foo --bar "%p" + %`,
+ }} {
+ c.Logf("tc %d", i)
+
+ cmd, err := userd.FindExec([]byte(tc.in))
+ if tc.err != "" {
+ c.Check(cmd, Equals, "")
+ c.Check(err, ErrorMatches, tc.err)
+ } else {
+ c.Check(err, IsNil)
+ c.Check(cmd, Equals, tc.out)
+ }
+ }
+}
+
+var mockYaml = `name: snapname
+version: 1.0
+apps:
+ foo:
+ command: run-app
+ autostart: foo-stable.desktop
+`
+
+func (s *autostartSuite) TestTryAutostartAppValid(c *C) {
+ si := snaptest.MockSnapCurrent(c, mockYaml, &snap.SideInfo{Revision: snap.R("x2")})
+
+ appWrapperPath := si.Apps["foo"].WrapperPath()
+ err := os.MkdirAll(filepath.Dir(appWrapperPath), 0755)
+ c.Assert(err, IsNil)
+
+ appCmd := testutil.MockCommand(c, appWrapperPath, "")
+ defer appCmd.Restore()
+
+ fooDesktopFile := filepath.Join(s.autostartDir, "foo-stable.desktop")
+ writeFile(c, fooDesktopFile,
+ []byte(`[Desktop Entry]
+Exec=this-is-ignored -a -b --foo="a b c" -z "dev"
+`))
+
+ cmd, err := userd.AutostartCmd("snapname", fooDesktopFile)
+ c.Assert(err, IsNil)
+ c.Assert(cmd.Path, Equals, appWrapperPath)
+
+ err = cmd.Start()
+ c.Assert(err, IsNil)
+ cmd.Wait()
+
+ c.Assert(appCmd.Calls(), DeepEquals,
+ [][]string{
+ {
+ filepath.Base(appWrapperPath),
+ "-a",
+ "-b",
+ "--foo=a b c",
+ "-z",
+ "dev",
+ },
+ })
+}
+
+func (s *autostartSuite) TestTryAutostartAppNoMatchingApp(c *C) {
+ snaptest.MockSnapCurrent(c, mockYaml, &snap.SideInfo{Revision: snap.R("x2")})
+
+ fooDesktopFile := filepath.Join(s.autostartDir, "foo-no-match.desktop")
+ writeFile(c, fooDesktopFile,
+ []byte(`[Desktop Entry]
+Exec=this-is-ignored -a -b --foo="a b c" -z "dev"
+`))
+
+ cmd, err := userd.AutostartCmd("snapname", fooDesktopFile)
+ c.Assert(cmd, IsNil)
+ c.Assert(err, ErrorMatches, `cannot match desktop file with snap snapname applications`)
+}
+
+func (s *autostartSuite) TestTryAutostartAppNoSnap(c *C) {
+ fooDesktopFile := filepath.Join(s.autostartDir, "foo-stable.desktop")
+ writeFile(c, fooDesktopFile,
+ []byte(`[Desktop Entry]
+Exec=this-is-ignored -a -b --foo="a b c" -z "dev"
+`))
+
+ cmd, err := userd.AutostartCmd("snapname", fooDesktopFile)
+ c.Assert(cmd, IsNil)
+ c.Assert(err, ErrorMatches, `cannot find current revision for snap snapname.*`)
+}
+
+func (s *autostartSuite) TestTryAutostartAppBadExec(c *C) {
+ snaptest.MockSnapCurrent(c, mockYaml, &snap.SideInfo{Revision: snap.R("x2")})
+
+ fooDesktopFile := filepath.Join(s.autostartDir, "foo-stable.desktop")
+ writeFile(c, fooDesktopFile,
+ []byte(`[Desktop Entry]
+Foo=bar
+`))
+
+ cmd, err := userd.AutostartCmd("snapname", fooDesktopFile)
+ c.Assert(cmd, IsNil)
+ c.Assert(err, ErrorMatches, `cannot determine startup command for application foo in snap snapname: Exec not found or invalid`)
+}
+
+func writeFile(c *C, path string, content []byte) {
+ err := os.MkdirAll(filepath.Dir(path), 0755)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(path, content, 0644)
+ c.Assert(err, IsNil)
+}
+
+func (s *autostartSuite) TestTryAutostartMany(c *C) {
+ var mockYamlTemplate = `name: {snap}
+version: 1.0
+apps:
+ foo:
+ command: run-app
+ autostart: foo-stable.desktop
+`
+
+ snaptest.MockSnapCurrent(c, strings.Replace(mockYamlTemplate, "{snap}", "a-foo", -1),
+ &snap.SideInfo{Revision: snap.R("x2")})
+ snaptest.MockSnapCurrent(c, strings.Replace(mockYamlTemplate, "{snap}", "b-foo", -1),
+ &snap.SideInfo{Revision: snap.R("x2")})
+ writeFile(c, filepath.Join(s.userDir, "snap/a-foo/current/.config/autostart/foo-stable.desktop"),
+ []byte(`[Desktop Entry]
+Foo=bar
+`))
+ writeFile(c, filepath.Join(s.userDir, "snap/b-foo/current/.config/autostart/no-match.desktop"),
+ []byte(`[Desktop Entry]
+Exec=no-snap
+`))
+ writeFile(c, filepath.Join(s.userDir, "snap/c-foo/current/.config/autostart/no-snap.desktop"),
+ []byte(`[Desktop Entry]
+Exec=no-snap
+`))
+
+ err := userd.AutostartSessionApps()
+ c.Assert(err, NotNil)
+ c.Check(err, ErrorMatches, `- "foo-stable.desktop": cannot determine startup command for application foo in snap a-foo: Exec not found or invalid
+- "no-match.desktop": cannot match desktop file with snap b-foo applications
+- "no-snap.desktop": cannot find current revision for snap c-foo: readlink.*no such file or directory
+`)
+}
diff -Nru snapd-2.32.3.2/userd/export_test.go snapd-2.32.9/userd/export_test.go
--- snapd-2.32.3.2/userd/export_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/userd/export_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -20,11 +20,16 @@
package userd
import (
+ "os/user"
+
"github.com/godbus/dbus"
)
var (
SnapFromPid = snapFromPid
+
+ FindExec = findExec
+ AutostartCmd = autostartCmd
)
func MockSnapFromSender(f func(*dbus.Conn, dbus.Sender) (string, error)) func() {
@@ -34,3 +39,11 @@
snapFromSender = origSnapFromSender
}
}
+
+func MockUserCurrent(f func() (*user.User, error)) func() {
+ origUserCurrent := userCurrent
+ userCurrent = f
+ return func() {
+ userCurrent = origUserCurrent
+ }
+}
diff -Nru snapd-2.32.3.2/wrappers/services_gen_test.go snapd-2.32.9/wrappers/services_gen_test.go
--- snapd-2.32.3.2/wrappers/services_gen_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/wrappers/services_gen_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -333,3 +333,44 @@
c.Logf("service: \n%v\n", string(generatedWrapper))
c.Assert(string(generatedWrapper), Equals, expectedService)
}
+
+func (s *servicesWrapperGenSuite) TestKillModeSig(c *C) {
+ for _, rm := range []string{"sigterm", "sighup", "sigusr1", "sigusr2"} {
+ service := &snap.AppInfo{
+ Snap: &snap.Info{
+ SuggestedName: "snap",
+ Version: "0.3.4",
+ SideInfo: snap.SideInfo{Revision: snap.R(44)},
+ },
+ Name: "app",
+ Command: "bin/foo start",
+ Daemon: "simple",
+ StopMode: snap.StopModeType(rm),
+ }
+
+ generatedWrapper, err := wrappers.GenerateSnapServiceFile(service)
+ c.Assert(err, IsNil)
+
+ c.Check(string(generatedWrapper), Equals, fmt.Sprintf(`[Unit]
+# Auto-generated, DO NOT EDIT
+Description=Service for snap application snap.app
+Requires=%s-snap-44.mount
+Wants=network-online.target
+After=%s-snap-44.mount network-online.target
+X-Snappy=yes
+
+[Service]
+ExecStart=/usr/bin/snap run snap.app
+SyslogIdentifier=snap.app
+Restart=on-failure
+WorkingDirectory=/var/snap/snap/44
+TimeoutStopSec=30
+Type=simple
+KillMode=process
+KillSignal=%s
+
+[Install]
+WantedBy=multi-user.target
+`, mountUnitPrefix, mountUnitPrefix, strings.ToUpper(rm)))
+ }
+}
diff -Nru snapd-2.32.3.2/wrappers/services.go snapd-2.32.9/wrappers/services.go
--- snapd-2.32.3.2/wrappers/services.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/wrappers/services.go 2018-05-16 08:20:08.000000000 +0000
@@ -233,42 +233,27 @@
// Skip stop on refresh when refresh mode is set to something
// other than "restart" (or "" which is the same)
if reason == snap.StopReasonRefresh {
- logger.Debugf(" %s refresh-mode: %v", app.Name, app.RefreshMode)
+ logger.Debugf(" %s refresh-mode: %v", app.Name, app.StopMode)
switch app.RefreshMode {
case "endure":
// skip this service
continue
- case "sigterm":
- sysd.Kill(app.ServiceName(), "TERM", "main")
- continue
- case "sigterm-all":
- sysd.Kill(app.ServiceName(), "TERM", "all")
- continue
- case "sighup":
- sysd.Kill(app.ServiceName(), "HUP", "main")
- continue
- case "sighup-all":
- sysd.Kill(app.ServiceName(), "HUP", "all")
- continue
- case "sigusr1":
- sysd.Kill(app.ServiceName(), "USR1", "main")
- continue
- case "sigusr1-all":
- sysd.Kill(app.ServiceName(), "USR1", "all")
- continue
- case "sigusr2":
- sysd.Kill(app.ServiceName(), "USR2", "main")
- continue
- case "sigusr2-all":
- sysd.Kill(app.ServiceName(), "USR2", "all")
- continue
- case "", "restart":
- // do nothing here, the default below to stop
}
}
if err := stopService(sysd, app, inter); err != nil {
return err
}
+
+ // ensure the service is really stopped on remove regardless
+ // of stop-mode
+ if reason == snap.StopReasonRemove && !app.StopMode.KillAll() {
+ // FIXME: make this smarter and avoid the killWait
+ // delay if not needed (i.e. if all processes
+ // have died)
+ sysd.Kill(app.ServiceName(), "TERM", "all")
+ time.Sleep(killWait)
+ sysd.Kill(app.ServiceName(), "KILL", "")
+ }
}
return nil
@@ -367,6 +352,12 @@
{{- if .App.BusName}}
BusName={{.App.BusName}}
{{- end}}
+{{- if .KillMode}}
+KillMode={{.KillMode}}
+{{- end}}
+{{- if .KillSignal}}
+KillSignal={{.KillSignal}}
+{{- end}}
{{- if not .App.Sockets}}
[Install]
@@ -391,6 +382,10 @@
remain = "yes"
}
}
+ var killMode string
+ if !appInfo.StopMode.KillAll() {
+ killMode = "process"
+ }
wrapperData := struct {
App *snap.AppInfo
@@ -401,6 +396,8 @@
PrerequisiteTarget string
MountUnit string
Remain string
+ KillMode string
+ KillSignal string
Before []string
After []string
@@ -415,8 +412,11 @@
PrerequisiteTarget: systemd.PrerequisiteTarget,
MountUnit: filepath.Base(systemd.MountUnitPath(appInfo.Snap.MountDir())),
Remain: remain,
- Before: genServiceNames(appInfo.Snap, appInfo.Before),
- After: genServiceNames(appInfo.Snap, appInfo.After),
+ KillMode: killMode,
+ KillSignal: appInfo.StopMode.KillSignal(),
+
+ Before: genServiceNames(appInfo.Snap, appInfo.Before),
+ After: genServiceNames(appInfo.Snap, appInfo.After),
// systemd runs as PID 1 so %h will not work.
Home: "/root",
diff -Nru snapd-2.32.3.2/wrappers/services_test.go snapd-2.32.9/wrappers/services_test.go
--- snapd-2.32.3.2/wrappers/services_test.go 2018-04-11 10:40:09.000000000 +0000
+++ snapd-2.32.9/wrappers/services_test.go 2018-05-16 08:20:08.000000000 +0000
@@ -652,6 +652,9 @@
})
defer r()
+ r = wrappers.MockKillWait(1 * time.Millisecond)
+ defer r()
+
survivorFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.survive-snap.srv.service")
for _, t := range []struct {
mode string
@@ -672,7 +675,7 @@
apps:
srv:
command: bin/survivor
- refresh-mode: %s
+ stop-mode: %s
daemon: simple
`, t.mode)
info := snaptest.MockSnap(c, surviveYaml, &snap.SideInfo{Revision: snap.R(1)})
@@ -689,16 +692,29 @@
err = wrappers.StopServices(info.Services(), snap.StopReasonRefresh, progress.Null)
c.Assert(err, IsNil)
c.Check(sysdLog, DeepEquals, [][]string{
- {"kill", filepath.Base(survivorFile), "-s", t.expectedSig, "--kill-who=" + t.expectedWho},
+ {"stop", filepath.Base(survivorFile)},
+ {"show", "--property=ActiveState", "snap.survive-snap.srv.service"},
}, Commentf("failure in %s", t.mode))
sysdLog = nil
err = wrappers.StopServices(info.Services(), snap.StopReasonRemove, progress.Null)
c.Assert(err, IsNil)
- c.Check(sysdLog, DeepEquals, [][]string{
- {"stop", filepath.Base(survivorFile)},
- {"show", "--property=ActiveState", "snap.survive-snap.srv.service"},
- })
+ switch t.expectedWho {
+ case "all":
+ c.Check(sysdLog, DeepEquals, [][]string{
+ {"stop", filepath.Base(survivorFile)},
+ {"show", "--property=ActiveState", "snap.survive-snap.srv.service"},
+ })
+ case "main":
+ c.Check(sysdLog, DeepEquals, [][]string{
+ {"stop", filepath.Base(survivorFile)},
+ {"show", "--property=ActiveState", "snap.survive-snap.srv.service"},
+ {"kill", filepath.Base(survivorFile), "-s", "TERM", "--kill-who=all"},
+ {"kill", filepath.Base(survivorFile), "-s", "KILL", "--kill-who=all"},
+ })
+ default:
+ panic("not reached")
+ }
}
}