Skip to content

Commit

Permalink
Added exec command in sandbox (#122)
Browse files Browse the repository at this point in the history
* Added exec command

Signed-off-by: Yuvraj <[email protected]>
  • Loading branch information
yindia authored Jun 30, 2021
1 parent a462dc0 commit 8b1c844
Show file tree
Hide file tree
Showing 10 changed files with 348 additions and 12 deletions.
45 changes: 45 additions & 0 deletions flytectl/cmd/sandbox/exec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package sandbox

import (
"context"
"fmt"

cmdCore "github.com/flyteorg/flytectl/cmd/core"
"github.com/flyteorg/flytectl/pkg/docker"
)

const (
execShort = "Execute any command in sandbox"
execLong = `
Execute command will Will run non-interactive commands and return immediately with the output.
::
bin/flytectl sandbox exec -- ls -al
Usage`
)

func sandboxClusterExec(ctx context.Context, args []string, cmdCtx cmdCore.CommandContext) error {
cli, err := docker.GetDockerClient()
if err != nil {
return err
}
if len(args) > 0 {
return Execute(ctx, cli, args)
}
return fmt.Errorf("missing argument. Please check usage examples by running flytectl sandbox exec --help")
}

func Execute(ctx context.Context, cli docker.Docker, args []string) error {
c := docker.GetSandbox(ctx, cli)
if c != nil {
exec, err := docker.ExecCommend(ctx, cli, c.ID, args)
if err != nil {
return err
}
if err := docker.InspectExecResp(ctx, cli, exec.ID); err != nil {
return err
}
}
return nil
}
72 changes: 72 additions & 0 deletions flytectl/cmd/sandbox/exec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package sandbox

import (
"bufio"
"context"
"fmt"
"io"
"strings"
"testing"

cmdCore "github.com/flyteorg/flytectl/cmd/core"
"github.com/stretchr/testify/assert"

"github.com/docker/docker/api/types"
"github.com/flyteorg/flytectl/pkg/docker"
"github.com/flyteorg/flytectl/pkg/docker/mocks"
"github.com/stretchr/testify/mock"
)

func TestSandboxClusterExec(t *testing.T) {
mockDocker := &mocks.Docker{}
mockOutStream := new(io.Writer)
ctx := context.Background()
cmdCtx := cmdCore.NewCommandContext(nil, *mockOutStream)
reader := bufio.NewReader(strings.NewReader("test"))

mockDocker.OnContainerList(ctx, types.ContainerListOptions{All: true}).Return([]types.Container{
{
ID: docker.FlyteSandboxClusterName,
Names: []string{
docker.FlyteSandboxClusterName,
},
},
}, nil)
docker.ExecConfig.Cmd = []string{"ls -al"}
mockDocker.OnContainerExecCreateMatch(ctx, mock.Anything, docker.ExecConfig).Return(types.IDResponse{}, nil)
mockDocker.OnContainerExecInspectMatch(ctx, mock.Anything).Return(types.ContainerExecInspect{}, nil)
mockDocker.OnContainerExecAttachMatch(ctx, mock.Anything, types.ExecStartCheck{}).Return(types.HijackedResponse{
Reader: reader,
}, fmt.Errorf("Test"))
docker.Client = mockDocker
err := sandboxClusterExec(ctx, []string{"ls -al"}, cmdCtx)

assert.NotNil(t, err)
}

func TestSandboxClusterExecWithoutCmd(t *testing.T) {
mockDocker := &mocks.Docker{}
mockOutStream := new(io.Writer)
ctx := context.Background()
cmdCtx := cmdCore.NewCommandContext(nil, *mockOutStream)
reader := bufio.NewReader(strings.NewReader("test"))

mockDocker.OnContainerList(ctx, types.ContainerListOptions{All: true}).Return([]types.Container{
{
ID: docker.FlyteSandboxClusterName,
Names: []string{
docker.FlyteSandboxClusterName,
},
},
}, nil)
docker.ExecConfig.Cmd = []string{}
mockDocker.OnContainerExecCreateMatch(ctx, mock.Anything, docker.ExecConfig).Return(types.IDResponse{}, nil)
mockDocker.OnContainerExecInspectMatch(ctx, mock.Anything).Return(types.ContainerExecInspect{}, nil)
mockDocker.OnContainerExecAttachMatch(ctx, mock.Anything, types.ExecStartCheck{}).Return(types.HijackedResponse{
Reader: reader,
}, fmt.Errorf("Test"))
docker.Client = mockDocker
err := sandboxClusterExec(ctx, []string{}, cmdCtx)

assert.NotNil(t, err)
}
3 changes: 3 additions & 0 deletions flytectl/cmd/sandbox/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ func CreateSandboxCommand() *cobra.Command {
"status": {CmdFunc: sandboxClusterStatus, Aliases: []string{}, ProjectDomainNotRequired: true,
Short: statusShort,
Long: statusLong},
"exec": {CmdFunc: sandboxClusterExec, Aliases: []string{}, ProjectDomainNotRequired: true,
Short: execShort,
Long: execLong, PFlagProvider: sandboxConfig.DefaultConfig},
}

cmdcore.AddCommands(sandbox, sandboxResourcesFuncs)
Expand Down
24 changes: 14 additions & 10 deletions flytectl/cmd/sandbox/sandbox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,27 @@ func TestCreateSandboxCommand(t *testing.T) {
assert.Equal(t, sandboxCommand.Use, "sandbox")
assert.Equal(t, sandboxCommand.Short, "Used for testing flyte sandbox.")
fmt.Println(sandboxCommand.Commands())
assert.Equal(t, len(sandboxCommand.Commands()), 3)
assert.Equal(t, len(sandboxCommand.Commands()), 4)
cmdNouns := sandboxCommand.Commands()
// Sort by Use value.
sort.Slice(cmdNouns, func(i, j int) bool {
return cmdNouns[i].Use < cmdNouns[j].Use
})

assert.Equal(t, cmdNouns[0].Use, "start")
assert.Equal(t, cmdNouns[0].Short, startShort)
assert.Equal(t, cmdNouns[0].Long, startLong)
assert.Equal(t, cmdNouns[0].Use, "exec")
assert.Equal(t, cmdNouns[0].Short, execShort)
assert.Equal(t, cmdNouns[0].Long, execLong)

assert.Equal(t, cmdNouns[1].Use, "status")
assert.Equal(t, cmdNouns[1].Short, statusShort)
assert.Equal(t, cmdNouns[1].Long, statusLong)
assert.Equal(t, cmdNouns[1].Use, "start")
assert.Equal(t, cmdNouns[1].Short, startShort)
assert.Equal(t, cmdNouns[1].Long, startLong)

assert.Equal(t, cmdNouns[2].Use, "teardown")
assert.Equal(t, cmdNouns[2].Short, teardownShort)
assert.Equal(t, cmdNouns[2].Long, teardownLong)
assert.Equal(t, cmdNouns[2].Use, "status")
assert.Equal(t, cmdNouns[2].Short, statusShort)
assert.Equal(t, cmdNouns[2].Long, statusLong)

assert.Equal(t, cmdNouns[3].Use, "teardown")
assert.Equal(t, cmdNouns[3].Short, teardownShort)
assert.Equal(t, cmdNouns[3].Long, teardownLong)

}
2 changes: 2 additions & 0 deletions flytectl/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ require (
github.com/flyteorg/flytestdlib v0.3.24
github.com/ghodss/yaml v1.0.0
github.com/golang/protobuf v1.4.3
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/go-github v17.0.0+incompatible
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.2.0
github.com/gorilla/mux v1.8.0 // indirect
github.com/hashicorp/go-version v1.3.0
github.com/kataras/tablewriter v0.0.0-20180708051242-e063d29b7c23
github.com/kr/text v0.2.0 // indirect
Expand Down
6 changes: 4 additions & 2 deletions flytectl/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -438,8 +438,9 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
Expand Down Expand Up @@ -479,8 +480,9 @@ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
Expand Down
3 changes: 3 additions & 0 deletions flytectl/pkg/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ type Docker interface {
ContainerLogs(ctx context.Context, container string, options types.ContainerLogsOptions) (io.ReadCloser, error)
ContainerRemove(ctx context.Context, containerID string, options types.ContainerRemoveOptions) error
ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error)
ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.IDResponse, error)
ContainerExecAttach(ctx context.Context, execID string, config types.ExecStartCheck) (types.HijackedResponse, error)
ContainerExecInspect(ctx context.Context, execID string) (types.ContainerExecInspect, error)
}

type FlyteDocker struct {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ var (
Target: K3sDir,
},
}
ExecConfig = types.ExecConfig{
AttachStderr: true,
Tty: true,
WorkingDir: FlyteSnackDir,
AttachStdout: true,
Cmd: []string{},
}
StdWriterPrefixLen = 8
StartingBufLen = 32*1024 + StdWriterPrefixLen + 1
)

// SetupFlyteDir will create .flyte dir if not exist
Expand Down Expand Up @@ -192,3 +201,25 @@ func GetDockerClient() (Docker, error) {
}
return Client, nil
}

// ExecCommend will execute a command in container and returns an execution id
func ExecCommend(ctx context.Context, cli Docker, containerID string, command []string) (types.IDResponse, error) {
ExecConfig.Cmd = command
r, err := cli.ContainerExecCreate(ctx, containerID, ExecConfig)
if err != nil {
return types.IDResponse{}, err
}
return r, err
}

func InspectExecResp(ctx context.Context, cli Docker, containerID string) error {
resp, err := cli.ContainerExecAttach(ctx, containerID, types.ExecStartCheck{})
if err != nil {
return err
}
s := bufio.NewScanner(resp.Reader)
for s.Scan() {
fmt.Println(s.Text())
}
return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -311,5 +311,62 @@ func TestDockerClient(t *testing.T) {
assert.Nil(t, err)
assert.NotNil(t, cli)
})
}

func TestDockerExec(t *testing.T) {
t.Run("Successfully exec command in container", func(t *testing.T) {
ctx := context.Background()
mockDocker := &mocks.Docker{}
Client = mockDocker
c := ExecConfig
c.Cmd = []string{"ls"}
mockDocker.OnContainerExecCreateMatch(ctx, mock.Anything, c).Return(types.IDResponse{}, nil)
_, err := ExecCommend(ctx, mockDocker, "test", []string{"ls"})
assert.Nil(t, err)
})
t.Run("Failed exec command in container", func(t *testing.T) {
ctx := context.Background()
mockDocker := &mocks.Docker{}
Client = mockDocker
c := ExecConfig
c.Cmd = []string{"ls"}
mockDocker.OnContainerExecCreateMatch(ctx, mock.Anything, c).Return(types.IDResponse{}, fmt.Errorf("test"))
_, err := ExecCommend(ctx, mockDocker, "test", []string{"ls"})
assert.NotNil(t, err)
})
}

func TestInspectExecResp(t *testing.T) {
t.Run("Failed exec command in container", func(t *testing.T) {
ctx := context.Background()
mockDocker := &mocks.Docker{}
Client = mockDocker
c := ExecConfig
c.Cmd = []string{"ls"}
reader := bufio.NewReader(strings.NewReader("test"))

mockDocker.OnContainerExecInspectMatch(ctx, mock.Anything).Return(types.ContainerExecInspect{}, nil)
mockDocker.OnContainerExecAttachMatch(ctx, mock.Anything, types.ExecStartCheck{}).Return(types.HijackedResponse{
Reader: reader,
}, fmt.Errorf("err"))

err := InspectExecResp(ctx, mockDocker, "test")
assert.NotNil(t, err)
})
t.Run("Successfully exec command in container", func(t *testing.T) {
ctx := context.Background()
mockDocker := &mocks.Docker{}
Client = mockDocker
c := ExecConfig
c.Cmd = []string{"ls"}
reader := bufio.NewReader(strings.NewReader("test"))

mockDocker.OnContainerExecAttachMatch(ctx, mock.Anything, types.ExecStartCheck{}).Return(types.HijackedResponse{
Reader: reader,
}, nil)

err := InspectExecResp(ctx, mockDocker, "test")
assert.Nil(t, err)
})

}
Loading

0 comments on commit 8b1c844

Please sign in to comment.