diff --git a/cmd/snap-exec/main.go b/cmd/snap-exec/main.go index bcb3a0682edb..65b89e3ec2ec 100644 --- a/cmd/snap-exec/main.go +++ b/cmd/snap-exec/main.go @@ -141,16 +141,34 @@ func snapExecApp(snapApp, revision, command string, args []string) error { if err != nil { return err } - // strings.Split() is ok here because we validate all app fields - // and the whitelist is pretty strict (see - // snap/validate.go:appContentWhitelist) - cmdArgv := strings.Split(cmdAndArgs, " ") - cmd := cmdArgv[0] - cmdArgs := cmdArgv[1:] // build the environment from the yaml env := append(os.Environ(), osutil.SubstituteEnv(app.Env())...) + // strings.Split() is ok here because we validate all app fields + // and the whitelist is pretty strict (see + // snap/validate.go:appContentWhitelist) + tmpCmdArgv := strings.Split(cmdAndArgs, " ") + cmd := tmpCmdArgv[0] + + cmdArgs := make([]string, 0, len(tmpCmdArgv[1:])) + for _, arg := range tmpCmdArgv[1:] { + maybeExpanded := os.Expand(arg, func(k string) string { + for _, kv := range env { + l := strings.SplitN(kv, "=", 2) + if len(l) == 2 { + if k == l[0] { + return l[1] + } + } + } + return "" + }) + if maybeExpanded != "" { + cmdArgs = append(cmdArgs, maybeExpanded) + } + } + // run the command fullCmd := filepath.Join(app.Snap.MountDir(), cmd) switch command { diff --git a/cmd/snap-exec/main_test.go b/cmd/snap-exec/main_test.go index 0bf1fbd34389..3eb570f00ad1 100644 --- a/cmd/snap-exec/main_test.go +++ b/cmd/snap-exec/main_test.go @@ -58,7 +58,7 @@ var mockYaml = []byte(`name: snapname version: 1.0 apps: app: - command: run-app cmd-arg1 + command: run-app cmd-arg1 $SNAP_DATA stop-command: stop-app post-stop-command: post-stop-app environment: @@ -104,7 +104,7 @@ func (s *snapExecSuite) TestFindCommand(c *C) { cmd string expected string }{ - {cmd: "", expected: `run-app cmd-arg1`}, + {cmd: "", expected: `run-app cmd-arg1 $SNAP_DATA`}, {cmd: "stop", expected: "stop-app"}, {cmd: "post-stop", expected: "post-stop-app"}, } { @@ -313,3 +313,33 @@ func (s *snapExecSuite) TestSnapExecShellIntegration(c *C) { c.Check(execArgs, DeepEquals, []string{execArgv0, "-c", "echo foo"}) c.Check(execEnv, testutil.Contains, "LD_LIBRARY_PATH=/some/path/lib") } + +func (s *snapExecSuite) TestSnapExecAppIntegrationWithVars(c *C) { + dirs.SetRootDir(c.MkDir()) + snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ + Revision: snap.R("42"), + }) + + execArgv0 := "" + execArgs := []string{} + execEnv := []string{} + syscallExec = func(argv0 string, argv []string, env []string) error { + execArgv0 = argv0 + execArgs = argv + execEnv = env + return nil + } + + // setup env + os.Setenv("SNAP_DATA", "/var/snap/snapname/42") + defer os.Unsetenv("SNAP_DATA") + + // launch and verify its run the right way + err := snapExecApp("snapname.app", "42", "", []string{"user-arg1"}) + c.Assert(err, IsNil) + c.Check(execArgv0, Equals, fmt.Sprintf("%s/snapname/42/run-app", dirs.SnapMountDir)) + c.Check(execArgs, DeepEquals, []string{execArgv0, "cmd-arg1", "/var/snap/snapname/42", "user-arg1"}) + c.Check(execEnv, testutil.Contains, "BASE_PATH=/some/path") + c.Check(execEnv, testutil.Contains, "LD_LIBRARY_PATH=/some/path/lib") + c.Check(execEnv, testutil.Contains, fmt.Sprintf("MY_PATH=%s", os.Getenv("PATH"))) +} diff --git a/snap/validate.go b/snap/validate.go index a3816e43cc51..1dc1de8ffb4e 100644 --- a/snap/validate.go +++ b/snap/validate.go @@ -165,7 +165,7 @@ func validateField(name, cont string, whitelist *regexp.Regexp) error { // appContentWhitelist is the whitelist of legal chars in the "apps" // section of snap.yaml -var appContentWhitelist = regexp.MustCompile(`^[A-Za-z0-9/. _#:-]*$`) +var appContentWhitelist = regexp.MustCompile(`^[A-Za-z0-9/. _#:$-]*$`) var validAppName = regexp.MustCompile("^[a-zA-Z0-9](?:-?[a-zA-Z0-9])*$") // ValidateApp verifies the content in the app info. diff --git a/snap/validate_test.go b/snap/validate_test.go index 4d7b283be8e4..644656c3ea26 100644 --- a/snap/validate_test.go +++ b/snap/validate_test.go @@ -155,6 +155,12 @@ func (s *ValidateSuite) TestAppWhitelistSimple(c *C) { c.Check(ValidateApp(&AppInfo{Name: "foo", PostStopCommand: "foo"}), IsNil) } +func (s *ValidateSuite) TestAppWhitelistWithVars(c *C) { + c.Check(ValidateApp(&AppInfo{Name: "foo", Command: "foo $SNAP_DATA"}), IsNil) + c.Check(ValidateApp(&AppInfo{Name: "foo", StopCommand: "foo $SNAP_DATA"}), IsNil) + c.Check(ValidateApp(&AppInfo{Name: "foo", PostStopCommand: "foo $SNAP_DATA"}), IsNil) +} + func (s *ValidateSuite) TestAppWhitelistIllegal(c *C) { c.Check(ValidateApp(&AppInfo{Name: "x\n"}), NotNil) c.Check(ValidateApp(&AppInfo{Name: "test!me"}), NotNil) @@ -190,7 +196,7 @@ func (s *ValidateSuite) TestAppDaemonValue(c *C) { func (s *ValidateSuite) TestAppWhitelistError(c *C) { err := ValidateApp(&AppInfo{Name: "foo", Command: "x\n"}) c.Assert(err, NotNil) - c.Check(err.Error(), Equals, `app description field 'command' contains illegal "x\n" (legal: '^[A-Za-z0-9/. _#:-]*$')`) + c.Check(err.Error(), Equals, `app description field 'command' contains illegal "x\n" (legal: '^[A-Za-z0-9/. _#:$-]*$')`) } // Validate diff --git a/tests/lib/snaps/basic-run/meta/snap.yaml b/tests/lib/snaps/basic-run/meta/snap.yaml new file mode 100644 index 000000000000..f310e9a68e77 --- /dev/null +++ b/tests/lib/snaps/basic-run/meta/snap.yaml @@ -0,0 +1,6 @@ +name: basic-run +version: 1.0 +apps: + echo-data: + command: echo $SNAP_DATA + diff --git a/tests/main/snap-run-hook/task.yaml b/tests/main/snap-run-hook/task.yaml index 5346e94d497c..2fdf94b0d85e 100644 --- a/tests/main/snap-run-hook/task.yaml +++ b/tests/main/snap-run-hook/task.yaml @@ -15,9 +15,6 @@ prepare: | restore: | rm basic-hooks_1.0_all.snap -restore: | - rm basic-hooks_1.0_all.snap - execute: | # Note that `snap run` doesn't exit non-zero if the hook is missing, so we # check the output instead. diff --git a/tests/main/snap-run/task.yaml b/tests/main/snap-run/task.yaml new file mode 100644 index 000000000000..dd1f029a1fad --- /dev/null +++ b/tests/main/snap-run/task.yaml @@ -0,0 +1,12 @@ +summary: Check that `snap run` runs + +prepare: | + . $TESTSLIB/snaps.sh + install_local "$TESTSLIB/snaps/basic-run" + +restore: | + rm basic-run_1.0_all.snap + +execute: | + echo "Test that snap run use environments" + basic-run.echo | MATCH ^/var/snap