From ebe44320fa74aaec76a063f5c22eb7b6f564be87 Mon Sep 17 00:00:00 2001 From: Spencer Brower Date: Tue, 14 Apr 2020 20:32:21 -0400 Subject: [PATCH 1/3] feat: Added ability to use `exec` on filesystems. Currently works only for the docker plugin. Signed-off-by: Spencer Brower --- plugin/docker/container.go | 2 +- plugin/types.go | 3 ++ volume/core.go | 5 +++- volume/execable_dir.go | 59 ++++++++++++++++++++++++++++++++++++++ volume/fs.go | 6 ++++ 5 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 volume/execable_dir.go diff --git a/plugin/docker/container.go b/plugin/docker/container.go index 4992afd1e..351afbe45 100644 --- a/plugin/docker/container.go +++ b/plugin/docker/container.go @@ -105,7 +105,7 @@ func (c *container) Exec(ctx context.Context, cmd string, args []string, opts pl command := append([]string{cmd}, args...) activity.Record(ctx, "Exec %v on %v", command, c.Name()) - cfg := types.ExecConfig{Cmd: command, AttachStdout: true, AttachStderr: true, Tty: opts.Tty} + cfg := types.ExecConfig{Cmd: command, AttachStdout: true, AttachStderr: true, Tty: opts.Tty, WorkingDir: opts.WorkingDir} if opts.Stdin != nil || opts.Tty { cfg.AttachStdin = true } diff --git a/plugin/types.go b/plugin/types.go index 859fd51a4..b698b9611 100644 --- a/plugin/types.go +++ b/plugin/types.go @@ -141,6 +141,9 @@ type ExecOptions struct { // Elevate execution to run as a privileged user if not already running as a privileged user. Elevate bool `json:"elevate"` + + // WorkingDir is the directory in which to execute the command. + WorkingDir string } // ExecPacketType identifies the packet type. diff --git a/volume/core.go b/volume/core.go index 4659e1c52..d9cc697a5 100644 --- a/volume/core.go +++ b/volume/core.go @@ -53,7 +53,7 @@ type dirMap struct { // ChildSchemas returns a volume's child schema func ChildSchemas() []*plugin.EntrySchema { return []*plugin.EntrySchema{ - (&dir{}).Schema(), + (&execableDir{}).Schema(), (&file{}).Schema(), } } @@ -66,6 +66,9 @@ const RootPath = "" // Requests are cached against the supplied Interface using the VolumeListCB op. func List(ctx context.Context, impl Interface) ([]plugin.Entry, error) { // Start with the implementation as the cache key so we re-use data we get from it for subdirectory queries. + if exImpl, ok := impl.(execableInterface); ok { + return newExecDir("dummy", plugin.EntryAttributes{}, exImpl, RootPath).List(ctx) + } return newDir("dummy", plugin.EntryAttributes{}, impl, RootPath).List(ctx) } diff --git a/volume/execable_dir.go b/volume/execable_dir.go new file mode 100644 index 000000000..7dafdf470 --- /dev/null +++ b/volume/execable_dir.go @@ -0,0 +1,59 @@ +package volume + +import ( + "context" + "fmt" + + "github.com/puppetlabs/wash/plugin" +) + +type execableInterface interface { + Interface + VolumeExec(ctx context.Context, path string, cmd string, args []string, opts plugin.ExecOptions) (plugin.ExecCommand, error) +} + +// execableDir adds the exec action to dir. +type execableDir struct { + dir + impl execableInterface +} + +func newExecDir(name string, attr plugin.EntryAttributes, impl execableInterface, path string) *execableDir { + e := new(execableDir) + e.dir = *newDir(name, attr, impl, path) + e.impl = impl + return e +} + +func (v *execableDir) Exec(ctx context.Context, cmd string, args []string, opts plugin.ExecOptions) (plugin.ExecCommand, error) { + return v.impl.VolumeExec(ctx, v.path, cmd, args, opts) +} + +func (v *execableDir) generateChildren(dirmap *dirMap) []plugin.Entry { + entries := v.dir.generateChildren(dirmap) + + for i, entry := range entries { + dir, ok := entry.(*dir) + if ok { + fmt.Println(dir) + entries[i] = &execableDir{dir: *dir, impl: v.impl} + } + } + return entries +} + +// List lists the children of the directory. +func (v *execableDir) List(ctx context.Context) ([]plugin.Entry, error) { + if v.dirmap != nil { + // Children have been pre-populated by a source parent. + return v.generateChildren(v.dirmap), nil + } + + // Generate child hierarchy. Don't store it on this entry, but populate new dirs from it. + dirmap, err := v.impl.VolumeList(ctx, v.path) + if err != nil { + return nil, err + } + + return v.generateChildren(&dirMap{mp: dirmap}), nil +} diff --git a/volume/fs.go b/volume/fs.go index 16064586c..555139222 100644 --- a/volume/fs.go +++ b/volume/fs.go @@ -243,6 +243,12 @@ func (d *FS) loginShell() plugin.Shell { return plugin.POSIXShell } +// VolumeExec executes cmd in the directory at path. +func (d *FS) VolumeExec(ctx context.Context, path string, cmd string, args []string, opts plugin.ExecOptions) (plugin.ExecCommand, error) { + opts.WorkingDir = path + return d.executor.Exec(ctx, cmd, args, opts) +} + const fsDescription = ` This represents the root directory of a container/VM. It lets you navigate and interact with that container/VM's filesystem as if you were logged into From 79e2f60603f1520bb3f534292f4786ab1f25b146 Mon Sep 17 00:00:00 2001 From: Spencer Brower Date: Fri, 17 Apr 2020 22:26:10 -0400 Subject: [PATCH 2/3] fix: Fixed broken test. Signed-off-by: Spencer Brower --- plugin/external/pluginEntry_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/external/pluginEntry_test.go b/plugin/external/pluginEntry_test.go index 0828aff48..3089a4bf6 100644 --- a/plugin/external/pluginEntry_test.go +++ b/plugin/external/pluginEntry_test.go @@ -954,7 +954,7 @@ func (suite *ExternalPluginEntryTestSuite) TestExec() { ctx := context.Background() mockRunAndWait := func(cmd []string, startErr, waitErr error, exitCode int) { mockInv := &mockedInvocation{Command: NewCommand(ctx, "")} - args := []interface{}{ctx, "exec", entry, append([]string{`{"tty":false,"elevate":false,"stdin":false}`}, cmd...)} + args := []interface{}{ctx, "exec", entry, append([]string{`{"tty":false,"elevate":false,"WorkingDir":"","stdin":false}`}, cmd...)} mockScript.On("NewInvocation", args...).Return(mockInv).Once() mockInv.MockExec(startErr, waitErr, exitCode) } From 2ab37f3da1cea5af6956e54a677228e804f6e856 Mon Sep 17 00:00:00 2001 From: Spencer Brower Date: Fri, 17 Apr 2020 22:36:14 -0400 Subject: [PATCH 3/3] feat: Updated EntrySchema on `execableDir`. Signed-off-by: Spencer Brower --- volume/execable_dir.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/volume/execable_dir.go b/volume/execable_dir.go index 7dafdf470..90e0e5676 100644 --- a/volume/execable_dir.go +++ b/volume/execable_dir.go @@ -57,3 +57,7 @@ func (v *execableDir) List(ctx context.Context) ([]plugin.Entry, error) { return v.generateChildren(&dirMap{mp: dirmap}), nil } + +func (v *execableDir) Schema() *plugin.EntrySchema { + return plugin.NewEntrySchema(v, "execDir").SetDescription(dirDescription) +}