From 413b3773a0459dbb657fccf05227f43290c94466 Mon Sep 17 00:00:00 2001 From: Spencer Brower Date: Tue, 14 Apr 2020 20:32:21 -0400 Subject: [PATCH] feat: Added ability to use `exec` on filesystems. Currently works only for the docker plugin. --- 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