Skip to content
This repository has been archived by the owner on Jun 2, 2022. It is now read-only.

Implement exec on Directories #772

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion plugin/docker/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion plugin/external/pluginEntry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
3 changes: 3 additions & 0 deletions plugin/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 4 additions & 1 deletion volume/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}
Expand All @@ -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)
}

Expand Down
63 changes: 63 additions & 0 deletions volume/execable_dir.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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
}

func (v *execableDir) Schema() *plugin.EntrySchema {
return plugin.NewEntrySchema(v, "execDir").SetDescription(dirDescription)
}
6 changes: 6 additions & 0 deletions volume/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down