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
This commit is contained in:
Kostiantyn Kalynovskyi 2021-02-07 23:37:37 +00:00
parent 4671ea7f74
commit 971c81acdb
6 changed files with 87 additions and 29 deletions

View File

@ -123,7 +123,7 @@ func (options *BootstrapContainerOptions) GetContainerStatus() (container.Status
var exitCode int var exitCode int
exitCode = state.ExitCode exitCode = state.ExitCode
if exitCode > 0 { if exitCode > 0 {
reader, err := options.Container.GetContainerLogs() reader, err := options.Container.GetContainerLogs(container.GetLogOptions{Stderr: true, Follow: true})
if err != nil { if err != nil {
log.Printf("Error while trying to retrieve the container logs") log.Printf("Error while trying to retrieve the container logs")
return BootNullString, err return BootNullString, err
@ -197,7 +197,7 @@ func (options *BootstrapContainerOptions) CreateBootstrapContainer() error {
fmt.Sprintf("%s=%s", envBootstrapVolume, containerVolMount), 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 { if err != nil {
return err return err
} }

View File

@ -134,7 +134,7 @@ func (opts BootstrapIsoOptions) CreateBootstrapIso() error {
fmt.Sprintf("NO_PROXY=%s", os.Getenv("NO_PROXY")), 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 { if err != nil {
return err return err
} }
@ -143,7 +143,7 @@ func (opts BootstrapIsoOptions) CreateBootstrapIso() error {
if log.DebugEnabled() { if log.DebugEnabled() {
var cLogs io.ReadCloser var cLogs io.ReadCloser
cLogs, err = opts.Builder.GetContainerLogs() cLogs, err = opts.Builder.GetContainerLogs(container.GetLogOptions{Stderr: true, Follow: true})
if err != nil { if err != nil {
log.Printf("failed to read container logs %s", err) log.Printf("failed to read container logs %s", err)
} else { } else {

View File

@ -19,6 +19,11 @@ import (
"io" "io"
) )
const (
// ContainerDriverDocker indicates that docker driver should be used in container constructor
ContainerDriverDocker = "docker"
)
// Status type provides container status // Status type provides container status
type Status string type Status string
@ -36,7 +41,7 @@ type State struct {
type Container interface { type Container interface {
ImagePull() error ImagePull() error
RunCommand(RunCommandOptions) error RunCommand(RunCommandOptions) error
GetContainerLogs() (io.ReadCloser, error) GetContainerLogs(GetLogOptions) (io.ReadCloser, error)
InspectContainer() (State, error) InspectContainer() (State, error)
WaitUntilFinished() error WaitUntilFinished() error
RmContainer() error RmContainer() error
@ -45,13 +50,31 @@ type Container interface {
// RunCommandOptions options for RunCommand // RunCommandOptions options for RunCommand
type RunCommandOptions struct { type RunCommandOptions struct {
Privileged bool Privileged bool
HostNewtork bool
Cmd []string Cmd []string
EnvVars []string EnvVars []string
VolumeMounts []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 // 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 { switch driver {
case "": case "":
return nil, ErrNoContainerDriver{} return nil, ErrNoContainerDriver{}
case "docker": case ContainerDriverDocker:
cli, err := NewDockerClient(ctx) cli, err := NewDockerClient(ctx)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -24,6 +24,7 @@ import (
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters" "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/api/types/network"
"github.com/docker/docker/client" "github.com/docker/docker/client"
@ -174,18 +175,36 @@ func (c *DockerContainer) getConfig(opts RunCommandOptions) (container.Config, c
if err != nil { if err != nil {
return container.Config{}, container.HostConfig{}, err 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{ cCfg := container.Config{
Image: c.imageURL, Image: c.imageURL,
Cmd: cmd, Cmd: cmd,
AttachStdin: true,
OpenStdin: true, AttachStdin: true,
Env: opts.EnvVars, StdinOnce: true,
Tty: true, OpenStdin: true,
AttachStderr: true,
AttachStdout: true,
Env: opts.EnvVars,
} }
hCfg := container.HostConfig{ hCfg := container.HostConfig{
Binds: opts.VolumeMounts, Binds: opts.Binds,
Mounts: mounts,
Privileged: opts.Privileged, Privileged: opts.Privileged,
} }
if opts.HostNewtork {
hCfg.NetworkMode = "host"
}
return cCfg, hCfg, nil return cCfg, hCfg, nil
} }
@ -268,6 +287,8 @@ func (c *DockerContainer) RunCommand(opts RunCommandOptions) (err error) {
if attachErr != nil { if attachErr != nil {
return attachErr return attachErr
} }
defer conn.Close()
if _, err = io.Copy(conn.Conn, opts.Input); err != nil { if _, err = io.Copy(conn.Conn, opts.Input); err != nil {
return err return err
} }
@ -282,8 +303,12 @@ func (c *DockerContainer) RunCommand(opts RunCommandOptions) (err error) {
} }
// GetContainerLogs returns logs from the container as io.ReadCloser // GetContainerLogs returns logs from the container as io.ReadCloser
func (c *DockerContainer) GetContainerLogs() (io.ReadCloser, error) { func (c *DockerContainer) GetContainerLogs(opts GetLogOptions) (io.ReadCloser, error) {
return c.dockerClient.ContainerLogs(c.ctx, c.id, types.ContainerLogsOptions{ShowStdout: true, Follow: true}) 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. // RmContainer kills and removes a container from the docker host.

View File

@ -286,6 +286,7 @@ func TestRunCommand(t *testing.T) {
cmd []string cmd []string
containerInput io.Reader containerInput io.Reader
volumeMounts []string volumeMounts []string
mounts []Mount
debug bool debug bool
mockDockerClient mockDockerClient mockDockerClient mockDockerClient
expectedRunErr error expectedRunErr error
@ -329,7 +330,15 @@ func TestRunCommand(t *testing.T) {
return conn, nil return conn, nil
}, },
}, },
expectedRunErr: nil, expectedRunErr: nil,
mounts: []Mount{
{
ReadOnly: true,
Type: "bind",
Dst: "/dev/vda0",
Src: "/dev/vd3",
},
},
expectedWaitErr: nil, expectedWaitErr: nil,
assertF: func(t *testing.T) {}, assertF: func(t *testing.T) {},
}, },
@ -422,9 +431,10 @@ func TestRunCommand(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
cnt := getDockerContainerMock(tt.mockDockerClient) cnt := getDockerContainerMock(tt.mockDockerClient)
actualErr := cnt.RunCommand(RunCommandOptions{ actualErr := cnt.RunCommand(RunCommandOptions{
Input: tt.containerInput, Input: tt.containerInput,
Cmd: tt.cmd, Cmd: tt.cmd,
VolumeMounts: tt.volumeMounts, Binds: tt.volumeMounts,
Mounts: tt.mounts,
}) })
assert.Equal(t, tt.expectedRunErr, actualErr) assert.Equal(t, tt.expectedRunErr, actualErr)
actualErr = cnt.WaitUntilFinished() actualErr = cnt.WaitUntilFinished()
@ -468,12 +478,12 @@ func TestRunCommandOutput(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
cnt := getDockerContainerMock(tt.mockDockerClient) cnt := getDockerContainerMock(tt.mockDockerClient)
actualErr := cnt.RunCommand(RunCommandOptions{ actualErr := cnt.RunCommand(RunCommandOptions{
Input: tt.containerInput, Input: tt.containerInput,
Cmd: tt.cmd, Cmd: tt.cmd,
VolumeMounts: tt.volumeMounts, Binds: tt.volumeMounts,
}) })
assert.Equal(t, tt.expectedErr, actualErr) assert.Equal(t, tt.expectedErr, actualErr)
actualRes, actualErr := cnt.GetContainerLogs() actualRes, actualErr := cnt.GetContainerLogs(GetLogOptions{Stdout: true, Follow: true})
require.NoError(t, actualErr) require.NoError(t, actualErr)
var actualResBytes []byte var actualResBytes []byte

View File

@ -42,7 +42,7 @@ func (mc *MockContainer) RunCommand(container.RunCommandOptions) error {
} }
// GetContainerLogs Container interface implementation for unit test purposes // 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() return mc.MockGetContainerLogs()
} }