Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

many: modify snap run to understand component hooks #13976

Merged
merged 25 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1c1eff0
snap, s/snaptest: add function for reading the ComponentInfo of the c…
andrewphelpsj Apr 15, 2024
0ec7028
s/snapenv, c/snap: add support for component hooks to ExtendEnvForRun
andrewphelpsj Feb 29, 2024
f7a47d9
c/snap: update run to be able to run component hooks
andrewphelpsj Feb 26, 2024
22c0394
c/snap: refactor runSnapConfine to operate on a runnable that can rep…
andrewphelpsj Feb 29, 2024
01b9ca4
c/snap-exec: handle running component hooks in snap-exec
andrewphelpsj Feb 29, 2024
89f0c73
c/snap-exec: move parsing of snap-exec target into execHook and execApp
andrewphelpsj Jun 18, 2024
d915e47
snap: make error message when failing to parse current component revi…
andrewphelpsj Jun 18, 2024
d9f2e7b
c/snap: add IsHook method to runnable type for easier checking
andrewphelpsj Jun 18, 2024
29ed42f
s/snaptest: use os.Symlink rather than atomic variant in test code
andrewphelpsj Jun 18, 2024
82193ed
snap, s/snapdir, c/snap: fix import cycle issue with hook from snapdi…
andrewphelpsj Jun 18, 2024
61dc73b
c/snap, c/snap-exec: docs and panicking default for NewContainerFromDir
andrewphelpsj Jun 20, 2024
d3b470f
c/snap, c/snap-exec: set up hook for snap.NewContainerFromDir
andrewphelpsj Jun 20, 2024
699801b
c/snap: remove TODO about getting component revision
andrewphelpsj Jun 20, 2024
db0e0f9
c/snap, c/snap-exec: use _ imports rather than initializing hook manu…
andrewphelpsj Jun 20, 2024
60cbe40
s/naming: add ParseComponentRef function
andrewphelpsj Jun 20, 2024
746b8a9
snap, o/s/backend, daemon: replace ComponentLinkPath and ComponentIns…
andrewphelpsj Jun 20, 2024
8da36fb
snap: use ComponentLinkPath helper in ComponentLinkPath
andrewphelpsj Jun 20, 2024
1f1d108
s/snapdir: add doc comment for NewContainerForDir
andrewphelpsj Jun 21, 2024
c467eec
Revert "snap: use ComponentLinkPath helper in ComponentLinkPath"
andrewphelpsj Jun 21, 2024
0a372fd
Revert "snap, o/s/backend, daemon: replace ComponentLinkPath and Comp…
andrewphelpsj Jun 21, 2024
a31cd6f
Revert "s/naming: add ParseComponentRef function"
andrewphelpsj Jun 21, 2024
0bfe8d5
snap: use ComponentLinkPath helper in ComponentLinkPath
andrewphelpsj Jun 21, 2024
9b74ba1
snap: remove whitespace
andrewphelpsj Jun 24, 2024
163cc7c
snap: update doc comment on ComponentLinkPath to mention usage constr…
andrewphelpsj Jun 24, 2024
715fed6
snap: replace NOTE with TODO
andrewphelpsj Jun 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 42 additions & 13 deletions cmd/snap-exec/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ import (
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snapenv"

// sets up the snap.NewContainerFromDir hook from snapdir
_ "github.com/snapcore/snapd/snap/snapdir"
)

// for the tests
Expand Down Expand Up @@ -81,7 +84,7 @@ func parseArgs(args []string) (app string, appArgs []string, err error) {
}

func run() error {
snapApp, extraArgs, err := parseArgs(os.Args[1:])
snapTarget, extraArgs, err := parseArgs(os.Args[1:])
if err != nil {
return err
}
Expand All @@ -93,10 +96,10 @@ func run() error {

// Now actually handle the dispatching
if opts.Hook != "" {
return execHook(snapApp, revision, opts.Hook)
return execHook(snapTarget, revision, opts.Hook)
}

return execApp(snapApp, revision, opts.Command, extraArgs)
return execApp(snapTarget, revision, opts.Command, extraArgs)
}

const defaultShell = "/bin/bash"
Expand Down Expand Up @@ -128,12 +131,10 @@ func findCommand(app *snap.AppInfo, command string) (string, error) {
return cmd, nil
}

func absoluteCommandChain(snapInfo *snap.Info, commandChain []string) []string {
func absoluteCommandChain(mountDir string, commandChain []string) []string {
chain := make([]string, 0, len(commandChain))
snapMountDir := snapInfo.MountDir()

for _, element := range commandChain {
chain = append(chain, filepath.Join(snapMountDir, element))
chain = append(chain, filepath.Join(mountDir, element))
}

return chain
Expand Down Expand Up @@ -162,13 +163,17 @@ func completionHelper() (string, error) {
return filepath.Join(filepath.Dir(exe), "etelpmoc.sh"), nil
}

func execApp(snapApp, revision, command string, args []string) error {
func execApp(snapTarget, revision, command string, args []string) error {
if strings.ContainsRune(snapTarget, '+') {
return fmt.Errorf("snap-exec cannot run a snap component without a hook specified (use --hook)")
}

rev, err := snap.ParseRevision(revision)
if err != nil {
return fmt.Errorf("cannot parse revision %q: %s", revision, err)
}

snapName, appName := snap.SplitSnapApp(snapApp)
snapName, appName := snap.SplitSnapApp(snapTarget)
info, err := snap.ReadInfo(snapName, &snap.SideInfo{
Revision: rev,
})
Expand Down Expand Up @@ -245,7 +250,7 @@ func execApp(snapApp, revision, command string, args []string) error {
fullCmd = append(fullCmd, cmdArgs...)
fullCmd = append(fullCmd, args...)

fullCmd = append(absoluteCommandChain(app.Snap, app.CommandChain), fullCmd...)
fullCmd = append(absoluteCommandChain(app.Snap.MountDir(), app.CommandChain), fullCmd...)

logger.StartupStageTimestamp("snap-exec to app")
if err := syscallExec(fullCmd[0], fullCmd, env.ForExec()); err != nil {
Expand All @@ -255,7 +260,13 @@ func execApp(snapApp, revision, command string, args []string) error {
return nil
}

func execHook(snapName, revision, hookName string) error {
func getComponentInfo(name string, snapInfo *snap.Info) (*snap.ComponentInfo, error) {
return snap.ReadCurrentComponentInfo(name, snapInfo)
}

func execHook(snapTarget string, revision, hookName string) error {
snapName, componentName := snap.SplitSnapComponentInstanceName(snapTarget)

rev, err := snap.ParseRevision(revision)
if err != nil {
return err
Expand All @@ -268,7 +279,23 @@ func execHook(snapName, revision, hookName string) error {
return err
}

hook := info.Hooks[hookName]
var (
hook *snap.HookInfo
mountDir string
)

if componentName == "" {
hook = info.Hooks[hookName]
mountDir = info.MountDir()
} else {
component, err := getComponentInfo(componentName, info)
if err != nil {
return err
}
hook = component.Hooks[hookName]
mountDir = snap.ComponentMountDir(component.Component.ComponentName, component.Revision, info.InstanceName())
}

if hook == nil {
return fmt.Errorf("cannot find hook %q in %q", hookName, snapName)
}
Expand All @@ -285,7 +312,9 @@ func execHook(snapName, revision, hookName string) error {
env.ExtendWithExpanded(eenv)
}

hookPath := filepath.Join(mountDir, "meta", "hooks", hookName)

// run the hook
cmd := append(absoluteCommandChain(hook.Snap, hook.CommandChain), filepath.Join(hook.Snap.HooksDir(), hook.Name))
cmd := append(absoluteCommandChain(mountDir, hook.CommandChain), hookPath)
return syscallExec(cmd[0], cmd, env.ForExec())
}
77 changes: 77 additions & 0 deletions cmd/snap-exec/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,20 @@ apps:
command-chain: [chain1, chain2]
nostop:
command: nostop
components:
comp:
type: test
hooks:
install:
`)

var mockClassicYaml = append([]byte("confinement: classic\n"), mockYaml...)

var mockComponentYaml = []byte(`component: snapname+comp
type: test
version: 1.0
`)

var mockHookYaml = []byte(`name: snapname
version: 1.0
hooks:
Expand All @@ -91,6 +101,12 @@ version: 1.0
hooks:
configure:
command-chain: [chain1, chain2]
components:
comp:
type: test
hooks:
install:
command-chain: [chain3, chain4]
`)

var binaryTemplate = `#!/bin/sh
Expand Down Expand Up @@ -663,3 +679,64 @@ func (s *snapExecSuite) TestSnapExecCompleteClassicNoReexec(c *C) {
c.Check(execEnv, testutil.Contains, "SNAP_DATA=/var/snap/snapname/42")
c.Check(execEnv, testutil.Contains, "TMPDIR=/var/tmp99")
}

func (s *snapExecSuite) TestSnapExecComponentHookIntegration(c *C) {
dirs.SetRootDir(c.MkDir())
snapInfo := snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{
Revision: snap.R(42),
})
snaptest.MockComponentCurrent(c, string(mockComponentYaml), snapInfo, snap.ComponentSideInfo{
Revision: snap.R(21),
})

execArgv0 := ""
execArgs := []string{}
restore := snapExec.MockSyscallExec(func(argv0 string, argv []string, env []string) error {
execArgv0 = argv0
execArgs = argv
return nil
})
defer restore()

hookPath := filepath.Join(dirs.SnapMountDir, "/snapname/components/mnt/comp/21/meta/hooks/install")

err := snapExec.ExecHook("snapname+comp", "42", "install")
c.Assert(err, IsNil)
c.Check(execArgv0, Equals, hookPath)
c.Check(execArgs, DeepEquals, []string{execArgv0})
}

func (s *snapExecSuite) TestSnapExecComponentHookCommandChainIntegration(c *C) {
dirs.SetRootDir(c.MkDir())
snapInfo := snaptest.MockSnap(c, string(mockHookCommandChainYaml), &snap.SideInfo{
Revision: snap.R(42),
})
snaptest.MockComponentCurrent(c, string(mockComponentYaml), snapInfo, snap.ComponentSideInfo{
Revision: snap.R(21),
})

execArgv0 := ""
execArgs := []string{}
restore := snapExec.MockSyscallExec(func(argv0 string, argv []string, env []string) error {
execArgv0 = argv0
execArgs = argv
return nil
})
defer restore()

chain3Path := filepath.Join(dirs.SnapMountDir, "/snapname/components/mnt/comp/21/chain3")
chain4Path := filepath.Join(dirs.SnapMountDir, "/snapname/components/mnt/comp/21/chain4")
hookPath := filepath.Join(dirs.SnapMountDir, "/snapname/components/mnt/comp/21/meta/hooks/install")

err := snapExec.ExecHook("snapname+comp", "42", "install")
c.Assert(err, IsNil)
c.Check(execArgv0, Equals, chain3Path)
c.Check(execArgs, DeepEquals, []string{chain3Path, chain4Path, hookPath})
}

func (s *snapExecSuite) TestSnapExecComponentWithoutHookError(c *C) {
dirs.SetRootDir(c.MkDir())

err := snapExec.ExecApp("snapname+comp", "42", "complete", []string{"foo"})
c.Assert(err, ErrorMatches, `snap-exec cannot run a snap component without a hook specified \(use --hook\)`)
}
Loading
Loading