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
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
}

View File

@ -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 {

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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()
}