From 971c81acdb104ec7cfe78f3081eca6a985bfec30 Mon Sep 17 00:00:00 2001 From: Kostiantyn Kalynovskyi Date: Sun, 7 Feb 2021 23:37:37 +0000 Subject: [PATCH] Extend container interface with mounts get log opts This commit allows to specify options to get container logs, such as stderr, stdout and if logs should be followed. Also extends RunCommandOptions with ability to add mounts in addtion to binds Relates-To: #458 Change-Id: I83507f2f7ca6ea596f52f5d3e9f868467458b6a3 --- pkg/bootstrap/ephemeral/command.go | 4 +-- pkg/bootstrap/isogen/command.go | 4 +-- pkg/container/container.go | 37 +++++++++++++++++----- pkg/container/container_docker.go | 43 ++++++++++++++++++++------ pkg/container/container_docker_test.go | 26 +++++++++++----- testutil/container/container.go | 2 +- 6 files changed, 87 insertions(+), 29 deletions(-) diff --git a/pkg/bootstrap/ephemeral/command.go b/pkg/bootstrap/ephemeral/command.go index ef569a126..dd9281c94 100644 --- a/pkg/bootstrap/ephemeral/command.go +++ b/pkg/bootstrap/ephemeral/command.go @@ -123,7 +123,7 @@ func (options *BootstrapContainerOptions) GetContainerStatus() (container.Status var exitCode int exitCode = state.ExitCode if exitCode > 0 { - reader, err := options.Container.GetContainerLogs() + reader, err := options.Container.GetContainerLogs(container.GetLogOptions{Stderr: true, Follow: true}) if err != nil { log.Printf("Error while trying to retrieve the container logs") return BootNullString, err @@ -197,7 +197,7 @@ func (options *BootstrapContainerOptions) CreateBootstrapContainer() error { fmt.Sprintf("%s=%s", envBootstrapVolume, containerVolMount), } - err := options.Container.RunCommand(container.RunCommandOptions{EnvVars: envVars, VolumeMounts: vols}) + err := options.Container.RunCommand(container.RunCommandOptions{EnvVars: envVars, Binds: vols}) if err != nil { return err } diff --git a/pkg/bootstrap/isogen/command.go b/pkg/bootstrap/isogen/command.go index b10306395..fda3c0a8c 100644 --- a/pkg/bootstrap/isogen/command.go +++ b/pkg/bootstrap/isogen/command.go @@ -134,7 +134,7 @@ func (opts BootstrapIsoOptions) CreateBootstrapIso() error { fmt.Sprintf("NO_PROXY=%s", os.Getenv("NO_PROXY")), } - err = opts.Builder.RunCommand(container.RunCommandOptions{EnvVars: envVars, VolumeMounts: vols}) + err = opts.Builder.RunCommand(container.RunCommandOptions{EnvVars: envVars, Binds: vols}) if err != nil { return err } @@ -143,7 +143,7 @@ func (opts BootstrapIsoOptions) CreateBootstrapIso() error { if log.DebugEnabled() { var cLogs io.ReadCloser - cLogs, err = opts.Builder.GetContainerLogs() + cLogs, err = opts.Builder.GetContainerLogs(container.GetLogOptions{Stderr: true, Follow: true}) if err != nil { log.Printf("failed to read container logs %s", err) } else { diff --git a/pkg/container/container.go b/pkg/container/container.go index 332c78886..cdc03488e 100644 --- a/pkg/container/container.go +++ b/pkg/container/container.go @@ -19,6 +19,11 @@ import ( "io" ) +const ( + // ContainerDriverDocker indicates that docker driver should be used in container constructor + ContainerDriverDocker = "docker" +) + // Status type provides container status type Status string @@ -36,7 +41,7 @@ type State struct { type Container interface { ImagePull() error RunCommand(RunCommandOptions) error - GetContainerLogs() (io.ReadCloser, error) + GetContainerLogs(GetLogOptions) (io.ReadCloser, error) InspectContainer() (State, error) WaitUntilFinished() error RmContainer() error @@ -45,13 +50,31 @@ type Container interface { // RunCommandOptions options for RunCommand type RunCommandOptions struct { - Privileged bool + Privileged bool + HostNewtork bool - Cmd []string - EnvVars []string - VolumeMounts []string + Cmd []string + EnvVars []string + Binds []string - Input io.Reader + Mounts []Mount + Input io.Reader +} + +// Mount describes mount settings +type Mount struct { + ReadOnly bool + Type string + Dst string + Src string +} + +// GetLogOptions options for getting logs +// If both Stderr and Stdout are specified the logs will contain both stderr and stdout +type GetLogOptions struct { + Stderr bool + Stdout bool + Follow bool } // NewContainer returns instance of Container interface implemented by particular driver @@ -63,7 +86,7 @@ func NewContainer(ctx context.Context, driver string, url string) (Container, er switch driver { case "": return nil, ErrNoContainerDriver{} - case "docker": + case ContainerDriverDocker: cli, err := NewDockerClient(ctx) if err != nil { return nil, err diff --git a/pkg/container/container_docker.go b/pkg/container/container_docker.go index 55070ce62..694d876f5 100644 --- a/pkg/container/container_docker.go +++ b/pkg/container/container_docker.go @@ -24,6 +24,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/network" "github.com/docker/docker/client" @@ -174,18 +175,36 @@ func (c *DockerContainer) getConfig(opts RunCommandOptions) (container.Config, c if err != nil { return container.Config{}, container.HostConfig{}, err } + + mounts := []mount.Mount{} + for _, mnt := range opts.Mounts { + mounts = append(mounts, mount.Mount{ + Type: mount.Type(mnt.Type), + Source: mnt.Src, + Target: mnt.Dst, + ReadOnly: mnt.ReadOnly, + }) + } + cCfg := container.Config{ - Image: c.imageURL, - Cmd: cmd, - AttachStdin: true, - OpenStdin: true, - Env: opts.EnvVars, - Tty: true, + Image: c.imageURL, + Cmd: cmd, + + AttachStdin: true, + StdinOnce: true, + OpenStdin: true, + AttachStderr: true, + AttachStdout: true, + Env: opts.EnvVars, } hCfg := container.HostConfig{ - Binds: opts.VolumeMounts, + Binds: opts.Binds, + Mounts: mounts, Privileged: opts.Privileged, } + if opts.HostNewtork { + hCfg.NetworkMode = "host" + } return cCfg, hCfg, nil } @@ -268,6 +287,8 @@ func (c *DockerContainer) RunCommand(opts RunCommandOptions) (err error) { if attachErr != nil { return attachErr } + defer conn.Close() + if _, err = io.Copy(conn.Conn, opts.Input); err != nil { return err } @@ -282,8 +303,12 @@ func (c *DockerContainer) RunCommand(opts RunCommandOptions) (err error) { } // GetContainerLogs returns logs from the container as io.ReadCloser -func (c *DockerContainer) GetContainerLogs() (io.ReadCloser, error) { - return c.dockerClient.ContainerLogs(c.ctx, c.id, types.ContainerLogsOptions{ShowStdout: true, Follow: true}) +func (c *DockerContainer) GetContainerLogs(opts GetLogOptions) (io.ReadCloser, error) { + return c.dockerClient.ContainerLogs(c.ctx, c.id, types.ContainerLogsOptions{ + ShowStderr: opts.Stderr, + Follow: opts.Follow, + ShowStdout: opts.Stdout, + }) } // RmContainer kills and removes a container from the docker host. diff --git a/pkg/container/container_docker_test.go b/pkg/container/container_docker_test.go index e3d53c75b..b28eab930 100644 --- a/pkg/container/container_docker_test.go +++ b/pkg/container/container_docker_test.go @@ -286,6 +286,7 @@ func TestRunCommand(t *testing.T) { cmd []string containerInput io.Reader volumeMounts []string + mounts []Mount debug bool mockDockerClient mockDockerClient expectedRunErr error @@ -329,7 +330,15 @@ func TestRunCommand(t *testing.T) { return conn, nil }, }, - expectedRunErr: nil, + expectedRunErr: nil, + mounts: []Mount{ + { + ReadOnly: true, + Type: "bind", + Dst: "/dev/vda0", + Src: "/dev/vd3", + }, + }, expectedWaitErr: nil, assertF: func(t *testing.T) {}, }, @@ -422,9 +431,10 @@ func TestRunCommand(t *testing.T) { for _, tt := range tests { cnt := getDockerContainerMock(tt.mockDockerClient) actualErr := cnt.RunCommand(RunCommandOptions{ - Input: tt.containerInput, - Cmd: tt.cmd, - VolumeMounts: tt.volumeMounts, + Input: tt.containerInput, + Cmd: tt.cmd, + Binds: tt.volumeMounts, + Mounts: tt.mounts, }) assert.Equal(t, tt.expectedRunErr, actualErr) actualErr = cnt.WaitUntilFinished() @@ -468,12 +478,12 @@ func TestRunCommandOutput(t *testing.T) { for _, tt := range tests { cnt := getDockerContainerMock(tt.mockDockerClient) actualErr := cnt.RunCommand(RunCommandOptions{ - Input: tt.containerInput, - Cmd: tt.cmd, - VolumeMounts: tt.volumeMounts, + Input: tt.containerInput, + Cmd: tt.cmd, + Binds: tt.volumeMounts, }) assert.Equal(t, tt.expectedErr, actualErr) - actualRes, actualErr := cnt.GetContainerLogs() + actualRes, actualErr := cnt.GetContainerLogs(GetLogOptions{Stdout: true, Follow: true}) require.NoError(t, actualErr) var actualResBytes []byte diff --git a/testutil/container/container.go b/testutil/container/container.go index dd01b8077..06ade22b4 100755 --- a/testutil/container/container.go +++ b/testutil/container/container.go @@ -42,7 +42,7 @@ func (mc *MockContainer) RunCommand(container.RunCommandOptions) error { } // GetContainerLogs Container interface implementation for unit test purposes -func (mc *MockContainer) GetContainerLogs() (io.ReadCloser, error) { +func (mc *MockContainer) GetContainerLogs(container.GetLogOptions) (io.ReadCloser, error) { return mc.MockGetContainerLogs() }