From 4d8122605961cc7347e0c01ede2e569bf560332f Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Tue, 9 Jun 2020 10:31:42 +0200 Subject: [PATCH 01/18] snap: add snap export meta-data Snaps can export content to the host. Exports are described by a map of paths, as seen from a fixed location on the host, to files and directories relative to the mount point of a specific snap revision. Currently exports cannot be declared by YAML and are reserved for the snapd snap. Signed-off-by: Zygmunt Krynicki --- snap/info.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/snap/info.go b/snap/info.go index 2a4aa17349f..8fc642cd883 100644 --- a/snap/info.go +++ b/snap/info.go @@ -311,6 +311,8 @@ type Info struct { // List of system users (usernames) this snap may use. The group of the same // name must also exist. SystemUsernames map[string]*SystemUsernameInfo + + Export map[string]*Export } // StoreAccount holds information about a store account, for example of snap @@ -336,6 +338,13 @@ type Layout struct { Symlink string `json:"symlink,omitempty"` } +// Export describes a file or directory exported from a snap to the host system. +type Export struct { + Snap *Info + Path string // Path relative to the snap mount directory. + Method string // symlink, copy, bind mount ... +} + // String returns a simple textual representation of a layout. func (l *Layout) String() string { var buf bytes.Buffer From b98f964649864fe55ad19f86cee6e2d866d2aa00 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Tue, 9 Jun 2020 19:19:32 +0200 Subject: [PATCH 02/18] fixup Export idea --- snap/info.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/snap/info.go b/snap/info.go index 8fc642cd883..d1591ed8749 100644 --- a/snap/info.go +++ b/snap/info.go @@ -312,7 +312,10 @@ type Info struct { // name must also exist. SystemUsernames map[string]*SystemUsernameInfo - Export map[string]*Export + // List of files to export to the host. + HostExports []*Export + // List of files to export to snap mount namespaces. + NamespaceExports []*Export } // StoreAccount holds information about a store account, for example of snap @@ -338,11 +341,18 @@ type Layout struct { Symlink string `json:"symlink,omitempty"` } -// Export describes a file or directory exported from a snap to the host system. +type ExportMethod string + +const ( + ExportMethodSymlink ExportMethod = "symlink" +) + +// Export describes a file or directory exported from a snap to the host system or the snap mount namespace. type Export struct { - Snap *Info - Path string // Path relative to the snap mount directory. - Method string // symlink, copy, bind mount ... + Snap *Info + PrivatePath string // path relative to snap mount point in the appropriate mount namespace. + PublicPath string // path relative to /var/lib/snapd/exports/ + Method ExportMethod // symlink, copy, bind mount ... } // String returns a simple textual representation of a layout. From 9daef3b741e6c0ae4028d269b183a0676236acd3 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Tue, 9 Jun 2020 12:19:44 +0200 Subject: [PATCH 03/18] snap: add implicit exports to snapd/core snaps The snapd.snap and core.snap declare implicit exports specific files from internal tools directory "$SNAP/usr/lib/snapd/$file" as "/var/lib/snapd/export/{core,snapd}/tools/$file" This will later on be picked up by snap-confine, as the source of the internal tools. Note that this design allows mounting "tools" and not having to perform any mount operations for other updates. Signed-off-by: Zygmunt Krynicki --- snap/info.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/snap/info.go b/snap/info.go index d1591ed8749..7fe24e6601d 100644 --- a/snap/info.go +++ b/snap/info.go @@ -1183,6 +1183,29 @@ func ReadInfo(name string, si *SideInfo) (*Info, error) { info.Size = st.Size() } + // Inject implicit exports to snapd and snapd snaps. + if info.SnapID == naming.WellKnownSnapID("snapd") || info.SnapID == naming.WellKnownSnapID("core") { + exportedFiles := []string{ + "snap-exec", // invoked inside the mount namespace + "snap-confine", // invoked inside the snap namespace, in special cases + "snap-update-ns", // invoked inside the mount namespace + "snapctl", // invoked inside the mount namespace + "snap-gdb-shim", // invoked inside the mount namespace + "snap-device-helper", // invoked inside the mount namespace + "etelpmoc.sh", // sourced inside the mount namespace + "info", // read inside the mount namespace by other tools + } + info.Export = make(map[string]*Export, len(exportedFiles)) + for _, fname := range exportedFiles { + publicName := fmt.Sprintf("%s/tools/%s", info.SnapName(), fname) + privateName := fmt.Sprintf("usr/lib/snapd/%s", fname) + info.Export[publicName] = &Export{ + Path: privateName, + Method: "symlink", + } + } + } + return info, nil } From fad953298625f8213b3c848e2589bda1c15b2206 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Tue, 9 Jun 2020 19:19:40 +0200 Subject: [PATCH 04/18] fixup exports for core/snapd --- snap/info.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/snap/info.go b/snap/info.go index 7fe24e6601d..7e8a5c70bbe 100644 --- a/snap/info.go +++ b/snap/info.go @@ -1195,14 +1195,14 @@ func ReadInfo(name string, si *SideInfo) (*Info, error) { "etelpmoc.sh", // sourced inside the mount namespace "info", // read inside the mount namespace by other tools } - info.Export = make(map[string]*Export, len(exportedFiles)) + info.NamespaceExports = make([]*Export, 0, len(exportedFiles)) for _, fname := range exportedFiles { - publicName := fmt.Sprintf("%s/tools/%s", info.SnapName(), fname) - privateName := fmt.Sprintf("usr/lib/snapd/%s", fname) - info.Export[publicName] = &Export{ - Path: privateName, - Method: "symlink", - } + info.NamespaceExports = append(info.NamespaceExports, &Export{ + Snap: info, + PublicPath: fmt.Sprintf("%s/tools/%s", info.SnapName(), fname), + PrivatePath: fmt.Sprintf("usr/lib/snapd/%s", fname), + Method: ExportMethodSymlink, + }) } } From 12d42cf6a06877f66e239afaff2e7890b4a216b3 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Tue, 9 Jun 2020 19:19:56 +0200 Subject: [PATCH 05/18] dirs: add SnapMountDirInsideNs Signed-off-by: Zygmunt Krynicki --- dirs/dirs.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dirs/dirs.go b/dirs/dirs.go index fcad5b8610d..a5840e9c17b 100644 --- a/dirs/dirs.go +++ b/dirs/dirs.go @@ -33,6 +33,9 @@ var ( GlobalRootDir string SnapMountDir string + // This is like SnapMountDir but always gives value true only + // from inside the snap mount namespace. + SnapMountDirInsideNs string DistroLibExecDir string @@ -270,6 +273,7 @@ func SetRootDir(rootdir string) { } else { SnapMountDir = filepath.Join(rootdir, defaultSnapMountDir) } + SnapMountDirInsideNs = filepath.Join(rootdir, defaultSnapMountDir) SnapDataDir = filepath.Join(rootdir, "/var/snap") SnapDataHomeGlob = filepath.Join(rootdir, "/home/*/", UserHomeSnapDir) From 69d069e9363aaa41fa8b9c4200e6a7ce914e3de7 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Tue, 9 Jun 2020 11:28:37 +0200 Subject: [PATCH 06/18] overlord: add stub export manager The export manager is responsible for exporting content from snaps to a designated location on the host. The host software must actively reach out to look for the exported content, so exporting content is safe as the burden is shifted on the party performing the integration. Signed-off-by: Zygmunt Krynicki --- overlord/exportstate/exportmgr.go | 98 +++++++++++++++++++++++++++++ overlord/exportstate/exportstate.go | 22 +++++++ overlord/overlord.go | 3 + 3 files changed, 123 insertions(+) create mode 100644 overlord/exportstate/exportmgr.go create mode 100644 overlord/exportstate/exportstate.go diff --git a/overlord/exportstate/exportmgr.go b/overlord/exportstate/exportmgr.go new file mode 100644 index 00000000000..402062bc3da --- /dev/null +++ b/overlord/exportstate/exportmgr.go @@ -0,0 +1,98 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2020 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 exportstate + +import ( + "gopkg.in/tomb.v2" + + "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/snap" +) + +type ExportManager struct { + state *state.State + runner *state.TaskRunner +} + +// Manager returns a new ExportManager. +func Manager(s *state.State, runner *state.TaskRunner) *ExportManager { + manager := &ExportManager{state: s, runner: runner} + runner.AddHandler("export-content", manager.doExportContent, manager.undoExportContent) + runner.AddHandler("unexport-content", manager.doUnexportContent, manager.undoUnexportContent) + return manager +} + +// Ensure implements StateManager.Ensure. +func (m *ExportManager) Ensure() error { + return nil +} + +func (m *ExportManager) readInfo(task *state.Task) (*snap.Info, error) { + st := task.State() + st.Lock() + defer st.Unlock() + + snapsup, err := snapstate.TaskSnapSetup(task) + if err != nil { + return nil, err + } + return snapstate.Info(st, snapsup.InstanceName(), snapsup.Revision()) +} + +func (m *ExportManager) doExportContent(task *state.Task, tomb *tomb.Tomb) error { + info, err := m.readInfo(task) + if err != nil { + return err + } + return m.exportContent(task, info) +} + +func (m *ExportManager) undoExportContent(task *state.Task, tomb *tomb.Tomb) error { + info, err := m.readInfo(task) + if err != nil { + return err + } + return m.unexportContent(task, info) +} + +func (m *ExportManager) doUnexportContent(task *state.Task, tomb *tomb.Tomb) error { + info, err := m.readInfo(task) + if err != nil { + return err + } + return m.unexportContent(task, info) +} + +func (m *ExportManager) undoUnexportContent(task *state.Task, tomb *tomb.Tomb) error { + info, err := m.readInfo(task) + if err != nil { + return err + } + return m.exportContent(task, info) +} + +func (m *ExportManager) exportContent(task *state.Task, info *snap.Info) error { + return nil +} + +func (m *ExportManager) unexportContent(task *state.Task, info *snap.Info) error { + return nil +} diff --git a/overlord/exportstate/exportstate.go b/overlord/exportstate/exportstate.go new file mode 100644 index 00000000000..1d5cd032124 --- /dev/null +++ b/overlord/exportstate/exportstate.go @@ -0,0 +1,22 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2020 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 exportstate implements the manager and state aspects responsible +// for exporting content from snaps to the host system. +package exportstate diff --git a/overlord/overlord.go b/overlord/overlord.go index aafef0d500d..280b63a8423 100644 --- a/overlord/overlord.go +++ b/overlord/overlord.go @@ -41,6 +41,7 @@ import ( "github.com/snapcore/snapd/overlord/configstate" "github.com/snapcore/snapd/overlord/configstate/proxyconf" "github.com/snapcore/snapd/overlord/devicestate" + "github.com/snapcore/snapd/overlord/exportstate" "github.com/snapcore/snapd/overlord/healthstate" "github.com/snapcore/snapd/overlord/hookstate" "github.com/snapcore/snapd/overlord/ifacestate" @@ -99,6 +100,7 @@ type Overlord struct { deviceMgr *devicestate.DeviceManager cmdMgr *cmdstate.CommandManager shotMgr *snapshotstate.SnapshotManager + xprtMgr *exportstate.ExportManager // proxyConf mediates the http proxy config proxyConf func(req *http.Request) (*url.URL, error) } @@ -177,6 +179,7 @@ func New(restartBehavior RestartBehavior) (*Overlord, error) { o.addManager(cmdstate.Manager(s, o.runner)) o.addManager(snapshotstate.Manager(s, o.runner)) + o.addManager(exportstate.Manager(s, o.runner)) if err := configstateInit(s, hookMgr); err != nil { return nil, err From fe5d5b576365d77f452eed871f9c72e783016f98 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Tue, 9 Jun 2020 11:59:52 +0200 Subject: [PATCH 07/18] overlord/exportstate: implement export/unexport logic Only the symlink method is currently implemented. The symbolic link uses absolute path to /snap/$SNAP_REVISION/$SNAP_NAME/$private_name, so that it works inside the mount namespace. Signed-off-by: Zygmunt Krynicki --- overlord/exportstate/exportmgr.go | 42 +++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/overlord/exportstate/exportmgr.go b/overlord/exportstate/exportmgr.go index 402062bc3da..d7df09d5b31 100644 --- a/overlord/exportstate/exportmgr.go +++ b/overlord/exportstate/exportmgr.go @@ -20,13 +20,28 @@ package exportstate import ( + "fmt" + "os" + "path/filepath" + "gopkg.in/tomb.v2" + "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/snap" ) +const defaultExportDir = "/var/lib/snapd/export" + +var ExportDir = defaultExportDir + +func init() { + dirs.AddRootDirCallback(func(root string) { + ExportDir = filepath.Join(root, defaultExportDir) + }) +} + type ExportManager struct { state *state.State runner *state.TaskRunner @@ -90,9 +105,36 @@ func (m *ExportManager) undoUnexportContent(task *state.Task, tomb *tomb.Tomb) e } func (m *ExportManager) exportContent(task *state.Task, info *snap.Info) error { + for path, export := range info.Export { + // XXX: This is is not aware of /snap vs /var/lib/snapd/snap + privateName := filepath.Join(info.MountDir(), export.Path) + publicName := filepath.Join(ExportDir, path) + if err := os.MkdirAll(filepath.Dir(publicName), 0755); err != nil { + return err + } + switch export.Method { + case "symlink": + if err := os.Symlink(privateName, publicName); err != nil { + return fmt.Errorf("cannot export %q as %q: %v", privateName, publicName, err) + } + default: + return fmt.Errorf("cannot export %q as %q, unsupported export method %s", privateName, publicName, export.Method) + } + } return nil } func (m *ExportManager) unexportContent(task *state.Task, info *snap.Info) error { + for path, export := range info.Export { + publicName := filepath.Join(ExportDir, path) + switch export.Method { + case "symlink": + if err := os.Remove(publicName); err != nil { + return fmt.Errorf("cannot unexport %q: %v", publicName, err) + } + default: + return fmt.Errorf("cannot unexport %q, unsupported export method %s", publicName, export.Method) + } + } return nil } From 37fba64301820119af478f0e694a730fffb3f12e Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Tue, 9 Jun 2020 12:25:12 +0200 Subject: [PATCH 08/18] overlord/exportstate: move handlers to handlers.go Signed-off-by: Zygmunt Krynicki --- overlord/exportstate/exportmgr.go | 34 ------------------ overlord/exportstate/handlers.go | 58 +++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 34 deletions(-) create mode 100644 overlord/exportstate/handlers.go diff --git a/overlord/exportstate/exportmgr.go b/overlord/exportstate/exportmgr.go index d7df09d5b31..680d3dd828c 100644 --- a/overlord/exportstate/exportmgr.go +++ b/overlord/exportstate/exportmgr.go @@ -24,8 +24,6 @@ import ( "os" "path/filepath" - "gopkg.in/tomb.v2" - "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" @@ -72,38 +70,6 @@ func (m *ExportManager) readInfo(task *state.Task) (*snap.Info, error) { return snapstate.Info(st, snapsup.InstanceName(), snapsup.Revision()) } -func (m *ExportManager) doExportContent(task *state.Task, tomb *tomb.Tomb) error { - info, err := m.readInfo(task) - if err != nil { - return err - } - return m.exportContent(task, info) -} - -func (m *ExportManager) undoExportContent(task *state.Task, tomb *tomb.Tomb) error { - info, err := m.readInfo(task) - if err != nil { - return err - } - return m.unexportContent(task, info) -} - -func (m *ExportManager) doUnexportContent(task *state.Task, tomb *tomb.Tomb) error { - info, err := m.readInfo(task) - if err != nil { - return err - } - return m.unexportContent(task, info) -} - -func (m *ExportManager) undoUnexportContent(task *state.Task, tomb *tomb.Tomb) error { - info, err := m.readInfo(task) - if err != nil { - return err - } - return m.exportContent(task, info) -} - func (m *ExportManager) exportContent(task *state.Task, info *snap.Info) error { for path, export := range info.Export { // XXX: This is is not aware of /snap vs /var/lib/snapd/snap diff --git a/overlord/exportstate/handlers.go b/overlord/exportstate/handlers.go new file mode 100644 index 00000000000..439ad193c8f --- /dev/null +++ b/overlord/exportstate/handlers.go @@ -0,0 +1,58 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2020 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 exportstate + +import ( + "gopkg.in/tomb.v2" + + "github.com/snapcore/snapd/overlord/state" +) + +func (m *ExportManager) doExportContent(task *state.Task, tomb *tomb.Tomb) error { + info, err := m.readInfo(task) + if err != nil { + return err + } + return m.exportContent(task, info) +} + +func (m *ExportManager) undoExportContent(task *state.Task, tomb *tomb.Tomb) error { + info, err := m.readInfo(task) + if err != nil { + return err + } + return m.unexportContent(task, info) +} + +func (m *ExportManager) doUnexportContent(task *state.Task, tomb *tomb.Tomb) error { + info, err := m.readInfo(task) + if err != nil { + return err + } + return m.unexportContent(task, info) +} + +func (m *ExportManager) undoUnexportContent(task *state.Task, tomb *tomb.Tomb) error { + info, err := m.readInfo(task) + if err != nil { + return err + } + return m.exportContent(task, info) +} From 9f7f183894cdc08b5a5434920c160535db830397 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Tue, 9 Jun 2020 19:20:10 +0200 Subject: [PATCH 09/18] fixup export mgr --- overlord/exportstate/exportmgr.go | 96 +++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 23 deletions(-) diff --git a/overlord/exportstate/exportmgr.go b/overlord/exportstate/exportmgr.go index 680d3dd828c..9b2a5b80956 100644 --- a/overlord/exportstate/exportmgr.go +++ b/overlord/exportstate/exportmgr.go @@ -32,11 +32,11 @@ import ( const defaultExportDir = "/var/lib/snapd/export" -var ExportDir = defaultExportDir +var exportDir = defaultExportDir func init() { dirs.AddRootDirCallback(func(root string) { - ExportDir = filepath.Join(root, defaultExportDir) + exportDir = filepath.Join(root, defaultExportDir) }) } @@ -71,36 +71,86 @@ func (m *ExportManager) readInfo(task *state.Task) (*snap.Info, error) { } func (m *ExportManager) exportContent(task *state.Task, info *snap.Info) error { - for path, export := range info.Export { - // XXX: This is is not aware of /snap vs /var/lib/snapd/snap - privateName := filepath.Join(info.MountDir(), export.Path) - publicName := filepath.Join(ExportDir, path) - if err := os.MkdirAll(filepath.Dir(publicName), 0755); err != nil { - return err + // TODO: store each exported file into the state. + for _, export := range info.NamespaceExports { + if err := m.exportOne(dirs.SnapMountDirInsideNs, export); err != nil { + return fmt.Errorf("cannot export %q from snap %s, to mount namespace, as %q: %v", + export.PrivatePath, info.SnapName(), export.PublicPath, err) } - switch export.Method { - case "symlink": - if err := os.Symlink(privateName, publicName); err != nil { - return fmt.Errorf("cannot export %q as %q: %v", privateName, publicName, err) - } - default: - return fmt.Errorf("cannot export %q as %q, unsupported export method %s", privateName, publicName, export.Method) + } + for _, export := range info.HostExports { + if err := m.exportOne(dirs.SnapMountDir, export); err != nil { + return fmt.Errorf("cannot export %q from snap %s, to the host, as %q: %v", + export.PrivatePath, info.SnapName(), export.PublicPath, err) } } return nil } - func (m *ExportManager) unexportContent(task *state.Task, info *snap.Info) error { - for path, export := range info.Export { - publicName := filepath.Join(ExportDir, path) - switch export.Method { - case "symlink": + // TODO: use the state to know what to unexport from the given snap name. + for _, export := range info.NamespaceExports { + if err := m.unexportOne(export); err != nil { + return fmt.Errorf("cannot unexport %q from snap %s: %v", + export.PrivatePath, info.SnapName(), err) + } + } + for _, export := range info.HostExports { + if err := m.unexportOne(export); err != nil { + return fmt.Errorf("cannot unexport %q from snap %s: %v", + export.PrivatePath, info.SnapName(), err) + } + } + return nil +} + +func (m *ExportManager) exportOne(baseDir string, export *snap.Export) error { + info := export.Snap + privateName := filepath.Join(baseDir, info.InstanceName(), info.Revision.String(), export.PrivatePath) + publicName := filepath.Join(exportDir, export.PublicPath) + if err := os.MkdirAll(filepath.Dir(publicName), 0755); err != nil { + return err + } + switch export.Method { + case snap.ExportMethodSymlink: + // Do we have an existing file? + fi, err := os.Stat(publicName) + if err != nil && !os.IsNotExist(err) { + return err + } + // Verify existing symlink. + if fi != nil && fi.Mode()&os.ModeSymlink != 0 { + target, err := os.Readlink(publicName) + if err != nil { + return err + } + if target == privateName { + // Symlink is up-to-date. + return nil + } + } + // Remove existing file. + // XXX: This should never happen if we modeled the exported state. + if fi != nil { if err := os.Remove(publicName); err != nil { - return fmt.Errorf("cannot unexport %q: %v", publicName, err) + return err } - default: - return fmt.Errorf("cannot unexport %q, unsupported export method %s", publicName, export.Method) } + // Export the current version. + return os.Symlink(privateName, publicName) + default: + return fmt.Errorf("unsupported export method %s", export.Method) + } +} + +func (m *ExportManager) unexportOne(export *snap.Export) error { + publicPath := filepath.Join(exportDir, export.PublicPath) + switch export.Method { + case snap.ExportMethodSymlink: + if err := os.Remove(publicPath); err != nil { + return err + } + default: + return fmt.Errorf("unsupported export method %s", export.Method) } return nil } From 3f985ad9ef3cb2a03badf285dd8614a4644fe128 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Tue, 9 Jun 2020 13:42:21 +0200 Subject: [PATCH 10/18] overlord: use export-content in doInstall Bulk of the change is the incredibly duplicated and fragile test changes to accommodate the new task in the chain. Signed-off-by: Zygmunt Krynicki --- .../devicestate/firstboot_preseed_test.go | 1 + overlord/hookstate/ctlcmd/services_test.go | 26 +++--- overlord/managers_test.go | 4 + overlord/snapstate/snapstate.go | 5 ++ overlord/snapstate/snapstate_install_test.go | 79 ++++++++++++++++--- overlord/snapstate/snapstate_test.go | 76 ++++++++++++++++-- overlord/snapstate/snapstate_try_test.go | 1 + 7 files changed, 165 insertions(+), 27 deletions(-) diff --git a/overlord/devicestate/firstboot_preseed_test.go b/overlord/devicestate/firstboot_preseed_test.go index 3b045f680ef..b52552dda3c 100644 --- a/overlord/devicestate/firstboot_preseed_test.go +++ b/overlord/devicestate/firstboot_preseed_test.go @@ -73,6 +73,7 @@ func checkPreseedTaskStates(c *C, st *state.State) { "prerequisites": true, "prepare-snap": true, "link-snap": true, + "export-content": true, "mount-snap": true, "setup-profiles": true, "copy-snap-data": true, diff --git a/overlord/hookstate/ctlcmd/services_test.go b/overlord/hookstate/ctlcmd/services_test.go index a17131ab68d..af1f0f8cb7d 100644 --- a/overlord/hookstate/ctlcmd/services_test.go +++ b/overlord/hookstate/ctlcmd/services_test.go @@ -315,6 +315,7 @@ var ( "copy-snap-data", "setup-profiles", "link-snap", + "export-content", "auto-connect", "set-auto-aliases", "setup-aliases", @@ -336,6 +337,7 @@ var ( "copy-snap-data", "setup-profiles", "link-snap", + "export-content", "auto-connect", "set-auto-aliases", "setup-aliases", @@ -386,10 +388,10 @@ func (s *servicectlSuite) TestQueuedCommands(c *C) { for i := 1; i <= 2; i++ { laneTasks := chg.LaneTasks(i) c.Assert(taskKinds(laneTasks), DeepEquals, expectedTaskKinds) - c.Check(laneTasks[12].Summary(), Matches, `Run configure hook of .* snap if present`) - c.Check(laneTasks[14].Summary(), Equals, "stop of [test-snap.test-service]") - c.Check(laneTasks[15].Summary(), Equals, "start of [test-snap.test-service]") - c.Check(laneTasks[16].Summary(), Equals, "restart of [test-snap.test-service]") + c.Check(laneTasks[13].Summary(), Matches, `Run configure hook of .* snap if present`) + c.Check(laneTasks[15].Summary(), Equals, "stop of [test-snap.test-service]") + c.Check(laneTasks[16].Summary(), Equals, "start of [test-snap.test-service]") + c.Check(laneTasks[17].Summary(), Equals, "restart of [test-snap.test-service]") } } @@ -499,10 +501,10 @@ func (s *servicectlSuite) TestQueuedCommandsUpdateMany(c *C) { for i := 1; i <= 2; i++ { laneTasks := chg.LaneTasks(i) c.Assert(taskKinds(laneTasks), DeepEquals, expectedTaskKinds) - c.Check(laneTasks[17].Summary(), Matches, `Run configure hook of .* snap if present`) - c.Check(laneTasks[19].Summary(), Equals, "stop of [test-snap.test-service]") - c.Check(laneTasks[20].Summary(), Equals, "start of [test-snap.test-service]") - c.Check(laneTasks[21].Summary(), Equals, "restart of [test-snap.test-service]") + c.Check(laneTasks[18].Summary(), Matches, `Run configure hook of .* snap if present`) + c.Check(laneTasks[20].Summary(), Equals, "stop of [test-snap.test-service]") + c.Check(laneTasks[21].Summary(), Equals, "start of [test-snap.test-service]") + c.Check(laneTasks[22].Summary(), Equals, "restart of [test-snap.test-service]") } } @@ -537,10 +539,10 @@ func (s *servicectlSuite) TestQueuedCommandsSingleLane(c *C) { laneTasks := chg.LaneTasks(0) c.Assert(taskKinds(laneTasks), DeepEquals, append(installTaskKinds, "exec-command", "exec-command", "exec-command")) - c.Check(laneTasks[12].Summary(), Matches, `Run configure hook of .* snap if present`) - c.Check(laneTasks[14].Summary(), Equals, "stop of [test-snap.test-service]") - c.Check(laneTasks[15].Summary(), Equals, "start of [test-snap.test-service]") - c.Check(laneTasks[16].Summary(), Equals, "restart of [test-snap.test-service]") + c.Check(laneTasks[13].Summary(), Matches, `Run configure hook of .* snap if present`) + c.Check(laneTasks[15].Summary(), Equals, "stop of [test-snap.test-service]") + c.Check(laneTasks[16].Summary(), Equals, "start of [test-snap.test-service]") + c.Check(laneTasks[17].Summary(), Equals, "restart of [test-snap.test-service]") } func (s *servicectlSuite) TestTwoServices(c *C) { diff --git a/overlord/managers_test.go b/overlord/managers_test.go index ecb09579c46..d913ad549cf 100644 --- a/overlord/managers_test.go +++ b/overlord/managers_test.go @@ -3859,6 +3859,8 @@ func validateInstallTasks(c *C, tasks []*state.Task, name, revno string, flags i i++ c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Make snap "%s" (%s) available to the system`, name, revno)) i++ + c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Export content from snap "%s" (%s) to the system`, name, revno)) + i++ c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Automatically connect eligible plugs and slots of snap "%s"`, name)) i++ c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Set automatic aliases for snap "%s"`, name)) @@ -3901,6 +3903,8 @@ func validateRefreshTasks(c *C, tasks []*state.Task, name, revno string, flags i i++ c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Make snap "%s" (%s) available to the system`, name, revno)) i++ + c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Export content from snap "%s" (%s) to the system`, name, revno)) + i++ c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Automatically connect eligible plugs and slots of snap "%s"`, name)) i++ c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Set automatic aliases for snap "%s"`, name)) diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index 4a74c3296bc..4dc558641b4 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -247,6 +247,11 @@ func doInstall(st *state.State, snapst *SnapState, snapsup *SnapSetup, flags int addTask(linkSnap) prev = linkSnap + // export content + exportContent := st.NewTask("export-content", fmt.Sprintf(i18n.G("Export content from snap %q%s to the system"), snapsup.InstanceName(), revisionStr)) + addTask(exportContent) + prev = exportContent + // auto-connections autoConnect := st.NewTask("auto-connect", fmt.Sprintf(i18n.G("Automatically connect eligible plugs and slots of snap %q"), snapsup.InstanceName())) addTask(autoConnect) diff --git a/overlord/snapstate/snapstate_install_test.go b/overlord/snapstate/snapstate_install_test.go index 1f481f9d27b..2e2f4c4d4ec 100644 --- a/overlord/snapstate/snapstate_install_test.go +++ b/overlord/snapstate/snapstate_install_test.go @@ -73,6 +73,7 @@ func verifyInstallTasks(c *C, opts, discards int, ts *state.TaskSet, st *state.S "copy-snap-data", "setup-profiles", "link-snap", + "export-content", ) expected = append(expected, "auto-connect", @@ -875,6 +876,11 @@ func (s *snapmgrTestSuite) TestInstallRunThrough(c *C) { op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), }, + { + op: "export-content:Doing", + name: "some-snap", + revno: snap.R(11), + }, { op: "auto-connect:Doing", name: "some-snap", @@ -902,14 +908,14 @@ func (s *snapmgrTestSuite) TestInstallRunThrough(c *C) { c.Check(task.Summary(), Equals, `Download snap "some-snap" (11) from channel "some-channel"`) // check install-record present - mountTask := ta[len(ta)-11] + mountTask := ta[len(ta)-12] c.Check(mountTask.Kind(), Equals, "mount-snap") var installRecord backend.InstallRecord c.Assert(mountTask.Get("install-record", &installRecord), IsNil) c.Check(installRecord.TargetSnapExisted, Equals, false) // check link/start snap summary - linkTask := ta[len(ta)-8] + linkTask := ta[len(ta)-9] c.Check(linkTask.Summary(), Equals, `Make snap "some-snap" (11) available to the system`) startTask := ta[len(ta)-3] c.Check(startTask.Summary(), Equals, `Start snap "some-snap" (11) services`) @@ -1052,6 +1058,11 @@ func (s *snapmgrTestSuite) TestParallelInstanceInstallRunThrough(c *C) { op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/11"), }, + { + op: "export-content:Doing", + name: "some-snap_instance", + revno: snap.R(11), + }, { op: "auto-connect:Doing", name: "some-snap_instance", @@ -1079,7 +1090,7 @@ func (s *snapmgrTestSuite) TestParallelInstanceInstallRunThrough(c *C) { c.Check(task.Summary(), Equals, `Download snap "some-snap_instance" (11) from channel "some-channel"`) // check link/start snap summary - linkTask := ta[len(ta)-8] + linkTask := ta[len(ta)-9] c.Check(linkTask.Summary(), Equals, `Make snap "some-snap_instance" (11) available to the system`) startTask := ta[len(ta)-3] c.Check(startTask.Summary(), Equals, `Start snap "some-snap_instance" (11) services`) @@ -1160,7 +1171,7 @@ func (s *snapmgrTestSuite) TestInstallUndoRunThroughJustOneSnap(c *C) { s.settle(c) s.state.Lock() - mountTask := tasks[len(tasks)-11] + mountTask := tasks[len(tasks)-12] c.Assert(mountTask.Kind(), Equals, "mount-snap") var installRecord backend.InstallRecord c.Assert(mountTask.Get("install-record", &installRecord), IsNil) @@ -1239,6 +1250,11 @@ func (s *snapmgrTestSuite) TestInstallUndoRunThroughJustOneSnap(c *C) { op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), }, + { + op: "export-content:Doing", + name: "some-snap", + revno: snap.R(11), + }, { op: "auto-connect:Doing", name: "some-snap", @@ -1385,6 +1401,11 @@ func (s *snapmgrTestSuite) TestInstallWithCohortRunThrough(c *C) { op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "some-snap/666"), }, + { + op: "export-content:Doing", + name: "some-snap", + revno: snap.R(666), + }, { op: "auto-connect:Doing", name: "some-snap", @@ -1412,7 +1433,7 @@ func (s *snapmgrTestSuite) TestInstallWithCohortRunThrough(c *C) { c.Check(task.Summary(), Equals, `Download snap "some-snap" (666) from channel "some-channel"`) // check link/start snap summary - linkTask := ta[len(ta)-8] + linkTask := ta[len(ta)-9] c.Check(linkTask.Summary(), Equals, `Make snap "some-snap" (666) available to the system`) startTask := ta[len(ta)-3] c.Check(startTask.Summary(), Equals, `Start snap "some-snap" (666) services`) @@ -1548,6 +1569,11 @@ func (s *snapmgrTestSuite) TestInstallWithRevisionRunThrough(c *C) { op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "some-snap/42"), }, + { + op: "export-content:Doing", + name: "some-snap", + revno: snap.R(42), + }, { op: "auto-connect:Doing", name: "some-snap", @@ -1575,7 +1601,7 @@ func (s *snapmgrTestSuite) TestInstallWithRevisionRunThrough(c *C) { c.Check(task.Summary(), Equals, `Download snap "some-snap" (42) from channel "some-channel"`) // check link/start snap summary - linkTask := ta[len(ta)-8] + linkTask := ta[len(ta)-9] c.Check(linkTask.Summary(), Equals, `Make snap "some-snap" (42) available to the system`) startTask := ta[len(ta)-3] c.Check(startTask.Summary(), Equals, `Start snap "some-snap" (42) services`) @@ -1720,6 +1746,11 @@ version: 1.0`) op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "mock/x1"), }, + { + op: "export-content:Doing", + name: "mock", + revno: snap.R("x1"), + }, { op: "auto-connect:Doing", name: "mock", @@ -1839,6 +1870,11 @@ epoch: 1* op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "mock/x3"), }, + { + op: "export-content:Doing", + name: "mock", + revno: snap.R("x3"), + }, { op: "auto-connect:Doing", name: "mock", @@ -1961,6 +1997,11 @@ epoch: 1* op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "mock/x1"), }, + { + op: "export-content:Doing", + name: "mock", + revno: snap.R("x1"), + }, { op: "auto-connect:Doing", name: "mock", @@ -2020,7 +2061,7 @@ version: 1.0`) s.state.Lock() // ensure only local install was run, i.e. first actions are pseudo-action current - c.Assert(s.fakeBackend.ops.Ops(), HasLen, 9) + c.Assert(s.fakeBackend.ops.Ops(), HasLen, 10) c.Check(s.fakeBackend.ops[0].op, Equals, "current") c.Check(s.fakeBackend.ops[0].old, Equals, "") // and setup-snap @@ -2204,6 +2245,11 @@ func (s *snapmgrTestSuite) TestInstallWithoutCoreRunThrough1(c *C) { op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "core/11"), }, + { + op: "export-content:Doing", + name: "core", + revno: snap.R(11), + }, { op: "auto-connect:Doing", name: "core", @@ -2263,6 +2309,11 @@ func (s *snapmgrTestSuite) TestInstallWithoutCoreRunThrough1(c *C) { op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "some-snap/42"), }, + { + op: "export-content:Doing", + name: "some-snap", + revno: snap.R(42), + }, { op: "auto-connect:Doing", name: "some-snap", @@ -2345,8 +2396,8 @@ func (s *snapmgrTestSuite) TestInstallWithoutCoreTwoSnapsRunThrough(c *C) { if len(chg1.Tasks()) < len(chg2.Tasks()) { chg1, chg2 = chg2, chg1 } - c.Assert(taskKinds(chg1.Tasks()), HasLen, 28) - c.Assert(taskKinds(chg2.Tasks()), HasLen, 14) + c.Assert(taskKinds(chg1.Tasks()), HasLen, 30) + c.Assert(taskKinds(chg2.Tasks()), HasLen, 15) // FIXME: add helpers and do a DeepEquals here for the operations } @@ -2657,6 +2708,10 @@ func (s *snapmgrTestSuite) TestInstallDefaultProviderRunThrough(c *C) { }, { op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "snap-content-slot/11"), + }, { + op: "export-content:Doing", + name: "snap-content-slot", + revno: snap.R(11), }, { op: "auto-connect:Doing", name: "snap-content-slot", @@ -2704,6 +2759,10 @@ func (s *snapmgrTestSuite) TestInstallDefaultProviderRunThrough(c *C) { }, { op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "snap-content-plug/42"), + }, { + op: "export-content:Doing", + name: "snap-content-plug", + revno: snap.R(42), }, { op: "auto-connect:Doing", name: "snap-content-plug", @@ -2996,7 +3055,7 @@ func (s *snapmgrTestSuite) TestInstallUndoRunThroughUndoContextOptional(c *C) { s.settle(c) s.state.Lock() - mountTask := tasks[len(tasks)-11] + mountTask := tasks[len(tasks)-12] c.Assert(mountTask.Kind(), Equals, "mount-snap") var installRecord backend.InstallRecord c.Assert(mountTask.Get("install-record", &installRecord), Equals, state.ErrNoState) diff --git a/overlord/snapstate/snapstate_test.go b/overlord/snapstate/snapstate_test.go index 829e4edc1c8..6a8cb35a4c9 100644 --- a/overlord/snapstate/snapstate_test.go +++ b/overlord/snapstate/snapstate_test.go @@ -243,6 +243,10 @@ func AddForeignTaskHandlers(runner *state.TaskRunner, tracker ForeignTaskTracker runner.AddHandler("transition-ubuntu-core", fakeHandler, nil) runner.AddHandler("transition-to-snapd-snap", fakeHandler, nil) + // Add fake handlers for tasks handled by the export manager + runner.AddHandler("export-content", fakeHandler, nil) + runner.AddHandler("unexport-content", fakeHandler, nil) + // Add handler to test full aborting of changes erroringHandler := func(task *state.Task, _ *tomb.Tomb) error { return errors.New("error out") @@ -414,6 +418,7 @@ func verifyUpdateTasks(c *C, opts, discards int, ts *state.TaskSet, st *state.St "copy-snap-data", "setup-profiles", "link-snap", + "export-content", ) if opts&maybeCore != 0 { expected = append(expected, "setup-profiles") @@ -553,6 +558,7 @@ func (s *snapmgrTestSuite) testRevertTasksFullFlags(flags fullFlags, c *C) { "unlink-current-snap", "setup-profiles", "link-snap", + "export-content", "auto-connect", "set-auto-aliases", "setup-aliases", @@ -1240,6 +1246,7 @@ func (s *snapmgrTestSuite) TestRevertCreatesNoGCTasks(c *C) { "unlink-current-snap", "setup-profiles", "link-snap", + "export-content", "auto-connect", "set-auto-aliases", "setup-aliases", @@ -2705,6 +2712,7 @@ func (s *snapmgrTestSuite) TestUpdateAmendRunThrough(c *C) { "setup-profiles:Doing", "candidate", "link-snap", + "export-content:Doing", "auto-connect:Doing", "update-aliases", "cleanup-trash", @@ -2752,7 +2760,7 @@ func (s *snapmgrTestSuite) TestUpdateAmendRunThrough(c *C) { verifyStopReason(c, ts, "refresh") // check post-refresh hook - task = ts.Tasks()[14] + task = ts.Tasks()[15] c.Assert(task.Kind(), Equals, "run-hook") c.Assert(task.Summary(), Matches, `Run post-refresh hook of "some-snap" snap if present`) @@ -2913,6 +2921,11 @@ func (s *snapmgrTestSuite) TestUpdateRunThrough(c *C) { op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "services-snap/11"), }, + { + op: "export-content:Doing", + name: "services-snap", + revno: snap.R(11), + }, { op: "auto-connect:Doing", name: "services-snap", @@ -2978,7 +2991,7 @@ func (s *snapmgrTestSuite) TestUpdateRunThrough(c *C) { verifyStopReason(c, ts, "refresh") // check post-refresh hook - task = ts.Tasks()[14] + task = ts.Tasks()[15] c.Assert(task.Kind(), Equals, "run-hook") c.Assert(task.Summary(), Matches, `Run post-refresh hook of "services-snap" snap if present`) @@ -3139,6 +3152,11 @@ func (s *snapmgrTestSuite) TestParallelInstanceUpdateRunThrough(c *C) { op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "services-snap_instance/11"), }, + { + op: "export-content:Doing", + name: "services-snap_instance", + revno: snap.R(11), + }, { op: "auto-connect:Doing", name: "services-snap_instance", @@ -3204,7 +3222,7 @@ func (s *snapmgrTestSuite) TestParallelInstanceUpdateRunThrough(c *C) { verifyStopReason(c, ts, "refresh") // check post-refresh hook - task = ts.Tasks()[14] + task = ts.Tasks()[15] c.Assert(task.Kind(), Equals, "run-hook") c.Assert(task.Summary(), Matches, `Run post-refresh hook of "services-snap_instance" snap if present`) @@ -4322,6 +4340,11 @@ func (s *snapmgrTestSuite) TestUpdateTotalUndoRunThrough(c *C) { op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), }, + { + op: "export-content:Doing", + name: "some-snap", + revno: snap.R(11), + }, { op: "auto-connect:Doing", name: "some-snap", @@ -5650,7 +5673,7 @@ func (s *snapmgrTestSuite) TestUpdateOneAutoAliasesScenarios(c *C) { } if scenario.update { first := tasks[j] - j += 19 + j += 20 c.Check(first.Kind(), Equals, "prerequisites") wait := false if expectedPruned["other-snap"]["aliasA"] { @@ -6920,6 +6943,11 @@ func (s *snapmgrTestSuite) TestUpdateDoesGC(c *C) { op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), }, + { + op: "export-content:Doing", + name: "some-snap", + revno: snap.R(11), + }, { op: "auto-connect:Doing", name: "some-snap", @@ -7106,6 +7134,11 @@ func (s *snapmgrTestSuite) TestRevertRunThrough(c *C) { op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "some-snap/2"), }, + { + op: "export-content:Doing", + name: "some-snap", + revno: snap.R(2), + }, { op: "auto-connect:Doing", name: "some-snap", @@ -7215,6 +7248,11 @@ func (s *snapmgrTestSuite) TestRevertWithBaseRunThrough(c *C) { op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "some-snap-with-base/2"), }, + { + op: "export-content:Doing", + name: "some-snap-with-base", + revno: snap.R(2), + }, { op: "auto-connect:Doing", name: "some-snap-with-base", @@ -7301,6 +7339,11 @@ func (s *snapmgrTestSuite) TestParallelInstanceRevertRunThrough(c *C) { op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/2"), }, + { + op: "export-content:Doing", + name: "some-snap_instance", + revno: snap.R(2), + }, { op: "auto-connect:Doing", name: "some-snap_instance", @@ -7373,7 +7416,7 @@ func (s *snapmgrTestSuite) TestRevertWithLocalRevisionRunThrough(c *C) { s.settle(c) s.state.Lock() - c.Assert(s.fakeBackend.ops.Ops(), HasLen, 7) + c.Assert(s.fakeBackend.ops.Ops(), HasLen, 8) // verify that LocalRevision is still -7 var snapst snapstate.SnapState @@ -7439,6 +7482,11 @@ func (s *snapmgrTestSuite) TestRevertToRevisionNewVersion(c *C) { op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, + { + op: "export-content:Doing", + name: "some-snap", + revno: snap.R(7), + }, { op: "auto-connect:Doing", name: "some-snap", @@ -7528,6 +7576,11 @@ func (s *snapmgrTestSuite) TestRevertTotalUndoRunThrough(c *C) { op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "some-snap/1"), }, + { + op: "export-content:Doing", + name: "some-snap", + revno: snap.R(1), + }, { op: "auto-connect:Doing", name: "some-snap", @@ -8231,6 +8284,7 @@ func (s *snapmgrTestSuite) testUpdateScenario(c *C, desc string, t switchScenari "setup-profiles:Doing", "candidate", "link-snap", + "export-content:Doing", "auto-connect:Doing", "update-aliases", "cleanup-trash", @@ -8390,6 +8444,7 @@ validate-snap: Done link-snap: Error INFO unlink ERROR fail +export-content: Hold auto-connect: Hold set-auto-aliases: Hold setup-aliases: Hold @@ -8406,6 +8461,7 @@ validate-snap: Done link-snap: Error INFO unlink ERROR fail +export-content: Hold auto-connect: Hold set-auto-aliases: Hold setup-aliases: Hold @@ -10202,6 +10258,11 @@ func (s *snapmgrTestSuite) TestUpdateCanDoBackwards(c *C) { op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, + { + op: "export-content:Doing", + name: "some-snap", + revno: snap.R(7), + }, { op: "auto-connect:Doing", name: "some-snap", @@ -10808,6 +10869,11 @@ func (s *snapmgrTestSuite) TestTransitionCoreRunThrough(c *C) { op: "link-snap", path: filepath.Join(dirs.SnapMountDir, "core/11"), }, + { + op: "export-content:Doing", + name: "core", + revno: snap.R(11), + }, { op: "auto-connect:Doing", name: "core", diff --git a/overlord/snapstate/snapstate_try_test.go b/overlord/snapstate/snapstate_try_test.go index 94f4153d64c..794b385171e 100644 --- a/overlord/snapstate/snapstate_try_test.go +++ b/overlord/snapstate/snapstate_try_test.go @@ -97,6 +97,7 @@ func (s *snapmgrTestSuite) testTrySetsTryMode(flags snapstate.Flags, c *C, extra "copy-snap-data", "setup-profiles", "link-snap", + "export-content", "auto-connect", "set-auto-aliases", "setup-aliases", From 211b2c21304c03978e225ea7edd6e2d8ad9d3566 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Tue, 9 Jun 2020 14:17:18 +0200 Subject: [PATCH 11/18] overlord/snapstate: use export-content in Enable Signed-off-by: Zygmunt Krynicki --- overlord/snapstate/snapstate.go | 9 ++++++--- overlord/snapstate/snapstate_test.go | 11 +++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index 4dc558641b4..d2c5b0371aa 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -1700,16 +1700,19 @@ func Enable(st *state.State, name string) (*state.TaskSet, error) { linkSnap.Set("snap-setup-task", prepareSnap.ID()) linkSnap.WaitFor(setupProfiles) - // setup aliases + exportContent := st.NewTask("export-content", fmt.Sprintf(i18n.G("Export content from snap %q%s to the system"), snapsup.InstanceName(), snapst.Current)) + exportContent.Set("snap-setup-task", prepareSnap.ID()) + exportContent.WaitFor(linkSnap) + setupAliases := st.NewTask("setup-aliases", fmt.Sprintf(i18n.G("Setup snap %q aliases"), snapsup.InstanceName())) setupAliases.Set("snap-setup-task", prepareSnap.ID()) - setupAliases.WaitFor(linkSnap) + setupAliases.WaitFor(exportContent) startSnapServices := st.NewTask("start-snap-services", fmt.Sprintf(i18n.G("Start snap %q (%s) services"), snapsup.InstanceName(), snapst.Current)) startSnapServices.Set("snap-setup-task", prepareSnap.ID()) startSnapServices.WaitFor(setupAliases) - return state.NewTaskSet(prepareSnap, setupProfiles, linkSnap, setupAliases, startSnapServices), nil + return state.NewTaskSet(prepareSnap, setupProfiles, linkSnap, exportContent, setupAliases, startSnapServices), nil } // Disable sets a snap to the inactive state diff --git a/overlord/snapstate/snapstate_test.go b/overlord/snapstate/snapstate_test.go index 6a8cb35a4c9..a57ec761a92 100644 --- a/overlord/snapstate/snapstate_test.go +++ b/overlord/snapstate/snapstate_test.go @@ -1276,6 +1276,7 @@ func (s *snapmgrTestSuite) TestEnableTasks(c *C) { "prepare-snap", "setup-profiles", "link-snap", + "export-content", "setup-aliases", "start-snap-services", }) @@ -7796,6 +7797,11 @@ func (s *snapmgrTestSuite) TestEnableRunThrough(c *C) { name: "some-snap", revno: snap.R(7), }, + { + op: "export-content:Doing", + name: "some-snap", + revno: snap.R(7), + }, { op: "update-aliases", }, @@ -7960,6 +7966,11 @@ func (s *snapmgrTestSuite) TestParallelInstanceEnableRunThrough(c *C) { name: "some-snap_instance", revno: snap.R(7), }, + { + op: "export-content:Doing", + name: "some-snap_instance", + revno: snap.R(7), + }, { op: "update-aliases", }, From 7f3ee45e01bbc831041e892ddc092ccd3aeaae53 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Tue, 9 Jun 2020 14:39:38 +0200 Subject: [PATCH 12/18] overlord/snapstate: use unexport-content in Disable Signed-off-by: Zygmunt Krynicki --- overlord/snapstate/snapstate.go | 8 ++++++-- overlord/snapstate/snapstate_test.go | 11 +++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index d2c5b0371aa..48a2e10bf44 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -1759,15 +1759,19 @@ func Disable(st *state.State, name string) (*state.TaskSet, error) { removeAliases.Set("snap-setup-task", stopSnapServices.ID()) removeAliases.WaitFor(stopSnapServices) + unexportContent := st.NewTask("unexport-content", fmt.Sprintf(i18n.G("Make snap %q (%s) unavailable to the system"), snapsup.InstanceName(), snapst.Current)) + unexportContent.Set("snap-setup-task", stopSnapServices.ID()) + unexportContent.WaitFor(removeAliases) + unlinkSnap := st.NewTask("unlink-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) unavailable to the system"), snapsup.InstanceName(), snapst.Current)) unlinkSnap.Set("snap-setup-task", stopSnapServices.ID()) - unlinkSnap.WaitFor(removeAliases) + unlinkSnap.WaitFor(unexportContent) removeProfiles := st.NewTask("remove-profiles", fmt.Sprintf(i18n.G("Remove security profiles of snap %q"), snapsup.InstanceName())) removeProfiles.Set("snap-setup-task", stopSnapServices.ID()) removeProfiles.WaitFor(unlinkSnap) - return state.NewTaskSet(stopSnapServices, removeAliases, unlinkSnap, removeProfiles), nil + return state.NewTaskSet(stopSnapServices, removeAliases, unexportContent, unlinkSnap, removeProfiles), nil } // canDisable verifies that a snap can be deactivated. diff --git a/overlord/snapstate/snapstate_test.go b/overlord/snapstate/snapstate_test.go index a57ec761a92..a05eba3edbe 100644 --- a/overlord/snapstate/snapstate_test.go +++ b/overlord/snapstate/snapstate_test.go @@ -1478,6 +1478,7 @@ func (s *snapmgrTestSuite) TestDisableTasks(c *C) { c.Assert(taskKinds(ts.Tasks()), DeepEquals, []string{ "stop-snap-services", "remove-aliases", + "unexport-content", "unlink-snap", "remove-profiles", }) @@ -7866,6 +7867,11 @@ func (s *snapmgrTestSuite) TestDisableRunThrough(c *C) { op: "remove-snap-aliases", name: "some-snap", }, + { + op: "unexport-content:Doing", + name: "some-snap", + revno: snap.R(7), + }, { op: "unlink-snap", path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), @@ -8037,6 +8043,11 @@ func (s *snapmgrTestSuite) TestParallelInstanceDisableRunThrough(c *C) { op: "remove-snap-aliases", name: "some-snap_instance", }, + { + op: "unexport-content:Doing", + name: "some-snap_instance", + revno: snap.R(7), + }, { op: "unlink-snap", path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"), From cf50fdc2ce7d33a656e151e3ec97e0ba8d95cb56 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Tue, 9 Jun 2020 14:52:00 +0200 Subject: [PATCH 13/18] overlord/snapstate: use unexport-content in Remove Signed-off-by: Zygmunt Krynicki --- overlord/snapstate/snapstate.go | 8 ++++-- overlord/snapstate/snapstate_test.go | 37 +++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index 48a2e10bf44..9e4de2bcaf0 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -1929,15 +1929,19 @@ func Remove(st *state.State, name string, revision snap.Revision, flags *RemoveF removeAliases.WaitFor(prev) // prev is not needed beyond here removeAliases.Set("snap-setup-task", stopSnapServices.ID()) + unexportContent := st.NewTask("unexport-content", fmt.Sprintf(i18n.G("Remove content exposed by snap %q"), name)) + unexportContent.WaitFor(removeAliases) + unexportContent.Set("snap-setup-task", stopSnapServices.ID()) + unlink := st.NewTask("unlink-snap", fmt.Sprintf(i18n.G("Make snap %q unavailable to the system"), name)) unlink.Set("snap-setup-task", stopSnapServices.ID()) - unlink.WaitFor(removeAliases) + unlink.WaitFor(unexportContent) removeSecurity := st.NewTask("remove-profiles", fmt.Sprintf(i18n.G("Remove security profile for snap %q (%s)"), name, revision)) removeSecurity.WaitFor(unlink) removeSecurity.Set("snap-setup-task", stopSnapServices.ID()) - tasks = append(tasks, removeAliases, unlink, removeSecurity) + tasks = append(tasks, removeAliases, unexportContent, unlink, removeSecurity) addNext(state.NewTaskSet(tasks...)) } diff --git a/overlord/snapstate/snapstate_test.go b/overlord/snapstate/snapstate_test.go index a05eba3edbe..2d0e27ba79c 100644 --- a/overlord/snapstate/snapstate_test.go +++ b/overlord/snapstate/snapstate_test.go @@ -469,6 +469,7 @@ func verifyRemoveTasks(c *C, ts *state.TaskSet) { "auto-disconnect", "save-snapshot", "remove-aliases", + "unexport-content", "unlink-snap", "remove-profiles", "clear-snap", @@ -483,6 +484,7 @@ func verifyCoreRemoveTasks(c *C, ts *state.TaskSet) { "run-hook[remove]", "auto-disconnect", "remove-aliases", + "unexport-content", "unlink-snap", "remove-profiles", "clear-snap", @@ -1998,6 +2000,7 @@ func (s *snapmgrTestSuite) TestRemoveTasksAutoSnapshotDisabled(c *C) { "run-hook[remove]", "auto-disconnect", "remove-aliases", + "unexport-content", "unlink-snap", "remove-profiles", "clear-snap", @@ -2026,6 +2029,7 @@ func (s *snapmgrTestSuite) TestRemoveTasksAutoSnapshotDisabledByPurgeFlag(c *C) "run-hook[remove]", "auto-disconnect", "remove-aliases", + "unexport-content", "unlink-snap", "remove-profiles", "clear-snap", @@ -5871,6 +5875,11 @@ func (s *snapmgrTestSuite) TestRemoveRunThrough(c *C) { op: "remove-snap-aliases", name: "some-snap", }, + { + op: "unexport-content:Doing", + name: "some-snap", + revno: snap.R(7), + }, { op: "unlink-snap", path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), @@ -6008,6 +6017,11 @@ func (s *snapmgrTestSuite) TestParallelInstanceRemoveRunThrough(c *C) { op: "remove-snap-aliases", name: "some-snap_instance", }, + { + op: "unexport-content:Doing", + name: "some-snap_instance", + revno: snap.R(7), + }, { op: "unlink-snap", path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"), @@ -6151,6 +6165,11 @@ func (s *snapmgrTestSuite) TestParallelInstanceRemoveRunThroughOtherInstances(c op: "remove-snap-aliases", name: "some-snap_instance", }, + { + op: "unexport-content:Doing", + name: "some-snap_instance", + revno: snap.R(7), + }, { op: "unlink-snap", path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"), @@ -6254,6 +6273,11 @@ func (s *snapmgrTestSuite) TestRemoveWithManyRevisionsRunThrough(c *C) { op: "remove-snap-aliases", name: "some-snap", }, + { + op: "unexport-content:Doing", + name: "some-snap", + revno: snap.R(7), + }, { op: "unlink-snap", path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), @@ -10356,13 +10380,14 @@ func (s *snapmgrTestSuite) TestRemoveMany(c *C) { c.Assert(tts, HasLen, 2) c.Check(removed, DeepEquals, []string{"one", "two"}) - c.Assert(s.state.TaskCount(), Equals, 8*2) + c.Assert(s.state.TaskCount(), Equals, 9*2) for i, ts := range tts { c.Assert(taskKinds(ts.Tasks()), DeepEquals, []string{ "stop-snap-services", "run-hook[remove]", "auto-disconnect", "remove-aliases", + "unexport-content", "unlink-snap", "remove-profiles", "clear-snap", @@ -10917,6 +10942,11 @@ func (s *snapmgrTestSuite) TestTransitionCoreRunThrough(c *C) { op: "remove-snap-aliases", name: "ubuntu-core", }, + { + op: "unexport-content:Doing", + name: "ubuntu-core", + revno: snap.R(1), + }, { op: "unlink-snap", path: filepath.Join(dirs.SnapMountDir, "ubuntu-core/1"), @@ -11013,6 +11043,11 @@ func (s *snapmgrTestSuite) TestTransitionCoreRunThroughWithCore(c *C) { op: "remove-snap-aliases", name: "ubuntu-core", }, + { + op: "unexport-content:Doing", + name: "ubuntu-core", + revno: snap.R(1), + }, { op: "unlink-snap", path: filepath.Join(dirs.SnapMountDir, "ubuntu-core/1"), From 2a64bb50de7e564767932a27907037a2007d5fe1 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Tue, 9 Jun 2020 16:11:16 +0200 Subject: [PATCH 14/18] cmd/snap-confine: mount tools from exports Signed-off-by: Zygmunt Krynicki --- cmd/snap-confine/mount-support.c | 73 +++++++++-------------- cmd/snap-confine/snap-confine.apparmor.in | 5 +- interfaces/apparmor/template.go | 1 + 3 files changed, 30 insertions(+), 49 deletions(-) diff --git a/cmd/snap-confine/mount-support.c b/cmd/snap-confine/mount-support.c index 7359e36a3d5..83da67c431c 100644 --- a/cmd/snap-confine/mount-support.c +++ b/cmd/snap-confine/mount-support.c @@ -378,54 +378,35 @@ static void sc_bootstrap_mount_namespace(const struct sc_mount_config *config) sc_do_mount("none", dst, NULL, MS_SLAVE, NULL); } } - // The "core" base snap is special as it contains snapd and friends. - // Other base snaps do not, so whenever a base snap other than core is - // in use we need extra provisions for setting up internal tooling to - // be available. - // - // However on a core18 (and similar) system the core snap is not - // a special base anymore and we should map our own tooling in. - if (config->distro == SC_DISTRO_CORE_OTHER - || !sc_streq(config->base_snap_name, "core")) { - // when bases are used we need to bind-mount the libexecdir - // (that contains snap-exec) into /usr/lib/snapd of the - // base snap so that snap-exec is available for the snaps - // (base snaps do not ship snapd) - - // dst is always /usr/lib/snapd as this is where snapd - // assumes to find snap-exec - sc_must_snprintf(dst, sizeof dst, "%s/usr/lib/snapd", - scratch_dir); - - // bind mount the current $ROOT/usr/lib/snapd path, - // where $ROOT is either "/" or the "/snap/{core,snapd}/current" - // that we are re-execing from - char *src = NULL; - char self[PATH_MAX + 1] = { 0 }; - ssize_t nread; - nread = readlink("/proc/self/exe", self, sizeof self - 1); - if (nread < 0) { - die("cannot read /proc/self/exe"); - } - // Though we initialized self to NULs and passed one less to - // readlink, therefore guaranteeing that self is - // zero-terminated, perform an explicit assignment to make - // Coverity happy. - self[nread] = '\0'; - // this cannot happen except when the kernel is buggy - if (strstr(self, "/snap-confine") == NULL) { - die("cannot use result from readlink: %s", self); + /* XXX: Put tools exported by snapd over /usr/lib/snapd. */ + { + /* TODO: Use a fixed export and have the export manager put the + * appropriate tools inside, this will automatically handle all + * possible transitions. */ + static const char *providers[] = {"snapd", "core", NULL}; + bool tools_mounted = false; + sc_must_snprintf(dst, sizeof dst, "%s/usr/lib/snapd", scratch_dir); + for (const char **provider = providers; *provider != NULL; ++provider) { + struct stat sb; + sc_must_snprintf(src, sizeof src, "/var/lib/snapd/export/%s/tools/snap-exec", *provider); + /* Check that snap-exec exists, the tools directory may be empty. */ + if (stat(src, &sb) != 0) { + continue; + } + /* Mount the entire tools directory as /usr/lib/snapd inside the mount namespace. */ + sc_must_snprintf(src, sizeof src, "/var/lib/snapd/export/%s/tools", *provider); + sc_do_mount(src, dst, NULL, MS_BIND | MS_RDONLY, NULL); + sc_do_mount("none", dst, NULL, MS_SLAVE, NULL); + tools_mounted = true; + break; } - src = dirname(self); - // dirname(path) might return '.' depending on path. - // /proc/self/exe should always point - // to an absolute path, but let's guarantee that. - if (src[0] != '/') { - die("cannot use the result of dirname(): %s", src); + if (!tools_mounted) { + /* XXX: We didn't find any exported tools. We could mount a + * snapshot of the host tools but since they would not be able to + * update, we should instead export the host tools with hostfs + * symlinks. */ + die("cannot find snapd tools"); } - - sc_do_mount(src, dst, NULL, MS_BIND | MS_RDONLY, NULL); - sc_do_mount("none", dst, NULL, MS_SLAVE, NULL); } // Bind mount the directory where all snaps are mounted. The location of // the this directory on the host filesystem may not match the location in diff --git a/cmd/snap-confine/snap-confine.apparmor.in b/cmd/snap-confine/snap-confine.apparmor.in index b7d5958e013..1e38eac0210 100644 --- a/cmd/snap-confine/snap-confine.apparmor.in +++ b/cmd/snap-confine/snap-confine.apparmor.in @@ -213,10 +213,9 @@ mount options=(rw bind) @LIBEXECDIR@/ -> /tmp/snap.rootfs_*/usr/lib/snapd/, mount options=(rw slave) -> /tmp/snap.rootfs_*/usr/lib/snapd/, - # allow making re-execed host snap-exec available inside base snaps - mount options=(ro bind) @SNAP_MOUNT_DIR@/core/*/usr/lib/snapd/ -> /tmp/snap.rootfs_*/usr/lib/snapd/, # allow making snapd snap tools available inside base snaps - mount options=(ro bind) @SNAP_MOUNT_DIR@/snapd/*/usr/lib/snapd/ -> /tmp/snap.rootfs_*/usr/lib/snapd/, + mount options=(rw bind) /var/lib/snapd/export/{core,snapd}/tools/ -> /tmp/snap.rootfs_*/usr/lib/snapd/, + mount options=(remount ro bind) -> /tmp/snap.rootfs_*/usr/lib/snapd/, mount options=(rw bind) /usr/bin/snapctl -> /tmp/snap.rootfs_*/usr/bin/snapctl, mount options=(rw slave) -> /tmp/snap.rootfs_*/usr/bin/snapctl, diff --git a/interfaces/apparmor/template.go b/interfaces/apparmor/template.go index 72d346deeab..809d72f471c 100644 --- a/interfaces/apparmor/template.go +++ b/interfaces/apparmor/template.go @@ -194,6 +194,7 @@ var templateCommon = ` # snapctl and its requirements /usr/bin/snapctl ixr, /usr/lib/snapd/snapctl ixr, + /snap/{core,snapd}/*/usr/lib/snapd/snapctl ixr, @{PROC}/sys/net/core/somaxconn r, /run/snapd-snap.socket rw, From ac4f70240cdb5c4ddf09a9c12fe25e2bf495dff4 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Tue, 9 Jun 2020 19:37:16 +0200 Subject: [PATCH 15/18] fixup apparmor --- interfaces/apparmor/template.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/interfaces/apparmor/template.go b/interfaces/apparmor/template.go index 809d72f471c..546dbbf65c7 100644 --- a/interfaces/apparmor/template.go +++ b/interfaces/apparmor/template.go @@ -165,14 +165,15 @@ var templateCommon = ` /etc/libnl-3/{classid,pktloc} r, # apps that use libnl # For snappy reexec on 4.8+ kernels - /usr/lib/snapd/snap-exec m, + /{,snap/{core,snapd}/*/,var/lib/snapd/snap/{core,snapd}/*/}usr/lib/snapd/snap-exec m, + /{,snap/{core,snapd}/*/,var/lib/snapd/snap/{core,snapd}/*/}usr/lib/snapd/snap-exec ixr, # For gdb support - /usr/lib/snapd/snap-gdb-shim ixr, + /{,snap/{core,snapd}/*/,var/lib/snapd/snap/{core,snapd}/*/}usr/lib/snapd/snap-gdb-shim ixr, # For in-snap tab completion /etc/bash_completion.d/{,*} r, - /usr/lib/snapd/etelpmoc.sh ixr, # marshaller (see complete.sh for out-of-snap unmarshal) + /{,snap/{core,snapd}/*/,var/lib/snapd/snap/{core,snapd}/*/}usr/lib/snapd/etelpmoc.sh ixr, # marshaller (see complete.sh for out-of-snap unmarshal) /usr/share/bash-completion/bash_completion r, # user-provided completions (run in-snap) may use functions from here # uptime @@ -193,8 +194,7 @@ var templateCommon = ` # snapctl and its requirements /usr/bin/snapctl ixr, - /usr/lib/snapd/snapctl ixr, - /snap/{core,snapd}/*/usr/lib/snapd/snapctl ixr, + /{,snap/{core,snapd}/*/,var/lib/snapd/snap/{core,snapd}/*/}usr/lib/snapd/snapctl ixr, @{PROC}/sys/net/core/somaxconn r, /run/snapd-snap.socket rw, From 9dda2882e729b6a12a95e95bd647821d32ab4914 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Tue, 9 Jun 2020 20:22:35 +0200 Subject: [PATCH 16/18] fixup fedora --- cmd/snap-confine/mount-support.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/snap-confine/mount-support.c b/cmd/snap-confine/mount-support.c index 83da67c431c..8b6b019a1b3 100644 --- a/cmd/snap-confine/mount-support.c +++ b/cmd/snap-confine/mount-support.c @@ -390,7 +390,7 @@ static void sc_bootstrap_mount_namespace(const struct sc_mount_config *config) struct stat sb; sc_must_snprintf(src, sizeof src, "/var/lib/snapd/export/%s/tools/snap-exec", *provider); /* Check that snap-exec exists, the tools directory may be empty. */ - if (stat(src, &sb) != 0) { + if (lstat(src, &sb) != 0) { continue; } /* Mount the entire tools directory as /usr/lib/snapd inside the mount namespace. */ From b09d377806765617feefc47d65132be819cc6f55 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Tue, 9 Jun 2020 20:25:03 +0200 Subject: [PATCH 17/18] fixup store snap info Signed-off-by: Zygmunt Krynicki --- store/details_v2_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/store/details_v2_test.go b/store/details_v2_test.go index f241149d2c6..ac9ba086ec6 100644 --- a/store/details_v2_test.go +++ b/store/details_v2_test.go @@ -307,6 +307,8 @@ func (s *detailsV2Suite) TestInfoFromStoreSnap(c *C) { "SideInfo.Channel", "DownloadInfo.AnonDownloadURL", // TODO: going away at some point "SystemUsernames", + "HostExports", + "NamespaceExports", } var checker func(string, reflect.Value) checker = func(pfx string, x reflect.Value) { From 4b8f405fdbc714324a9335b651af14219bacae73 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Wed, 10 Jun 2020 09:40:17 +0200 Subject: [PATCH 18/18] fixup symlink Signed-off-by: Zygmunt Krynicki --- overlord/exportstate/exportmgr.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overlord/exportstate/exportmgr.go b/overlord/exportstate/exportmgr.go index 9b2a5b80956..d34c6ade0e6 100644 --- a/overlord/exportstate/exportmgr.go +++ b/overlord/exportstate/exportmgr.go @@ -113,7 +113,7 @@ func (m *ExportManager) exportOne(baseDir string, export *snap.Export) error { switch export.Method { case snap.ExportMethodSymlink: // Do we have an existing file? - fi, err := os.Stat(publicName) + fi, err := os.Lstat(publicName) if err != nil && !os.IsNotExist(err) { return err }