Skip to content

Commit

Permalink
c/snap-exec: handle running component hooks in snap-exec
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewphelpsj committed Jun 13, 2024
1 parent b14e4c7 commit 4e1002a
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 14 deletions.
49 changes: 38 additions & 11 deletions cmd/snap-exec/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snapdir"
"github.com/snapcore/snapd/snap/snapenv"
)

Expand Down Expand Up @@ -81,22 +82,24 @@ 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
}

snapName, componentName := snap.SplitSnapComponentInstanceName(snapTarget)

// the SNAP_REVISION is set by `snap run` - we can not (easily)
// find it in `snap-exec` because `snap-exec` is run inside the
// confinement and (generally) can not talk to snapd
revision := os.Getenv("SNAP_REVISION")

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

return execApp(snapApp, revision, opts.Command, extraArgs)
return execApp(snapName, 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 @@ -245,7 +246,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 +256,15 @@ 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) {
container := func(p string) (snap.Container, error) {
return snapdir.New(p), nil
}

return snap.ReadCurrentComponentInfo(name, snapInfo, container)
}

func execHook(snapName, componentName, revision, hookName string) error {
rev, err := snap.ParseRevision(revision)
if err != nil {
return err
Expand All @@ -268,7 +277,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 +310,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())
}
76 changes: 73 additions & 3 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 @@ -287,7 +303,7 @@ func (s *snapExecSuite) TestSnapExecHookIntegration(c *C) {
defer restore()

// launch and verify it ran correctly
err := snapExec.ExecHook("snapname", "42", "configure")
err := snapExec.ExecHook("snapname", "", "42", "configure")
c.Assert(err, IsNil)
c.Check(execArgv0, Equals, fmt.Sprintf("%s/snapname/42/meta/hooks/configure", dirs.SnapMountDir))
c.Check(execArgs, DeepEquals, []string{execArgv0})
Expand All @@ -312,7 +328,7 @@ func (s *snapExecSuite) TestSnapExecHookCommandChainIntegration(c *C) {
chain2_path := fmt.Sprintf("%s/snapname/42/chain2", dirs.SnapMountDir)
hook_path := fmt.Sprintf("%s/snapname/42/meta/hooks/configure", dirs.SnapMountDir)

err := snapExec.ExecHook("snapname", "42", "configure")
err := snapExec.ExecHook("snapname", "", "42", "configure")
c.Assert(err, IsNil)
c.Check(execArgv0, Equals, chain1_path)
c.Check(execArgs, DeepEquals, []string{chain1_path, chain2_path, hook_path})
Expand All @@ -324,7 +340,7 @@ func (s *snapExecSuite) TestSnapExecHookMissingHookIntegration(c *C) {
Revision: snap.R("42"),
})

err := snapExec.ExecHook("snapname", "42", "missing-hook")
err := snapExec.ExecHook("snapname", "", "42", "missing-hook")
c.Assert(err, NotNil)
c.Assert(err, ErrorMatches, "cannot find hook \"missing-hook\" in \"snapname\"")
}
Expand Down Expand Up @@ -663,3 +679,57 @@ 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})
}

0 comments on commit 4e1002a

Please sign in to comment.