[AIR-137] Add logic to isogen subcommand
* Add add default values for isogen subcommand keys * Introduce container interface * Implement docker driver * Add stdin support to container interface * Implement volume mount for container Change-Id: Ide0ecd474b1ccce358bdc9c85ef0006f230490b5
This commit is contained in:
parent
91bf9ab6e0
commit
b37b6f3703
2
go.mod
2
go.mod
@ -19,7 +19,7 @@ require (
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
|
||||
github.com/daviddengcn/go-colortext v0.0.0-20180409174941-186a3d44e920 // indirect
|
||||
github.com/docker/distribution v0.0.0-20170726174610-edc3ab29cdff // indirect
|
||||
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0 // indirect
|
||||
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0
|
||||
github.com/docker/go-connections v0.3.0 // indirect
|
||||
github.com/docker/go-units v0.3.3 // indirect
|
||||
github.com/docker/libnetwork v0.0.0-20180830151422-a9cd636e3789 // indirect
|
||||
|
@ -1,28 +1,62 @@
|
||||
package isogen
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/container"
|
||||
"opendev.org/airship/airshipctl/pkg/errors"
|
||||
"opendev.org/airship/airshipctl/pkg/util"
|
||||
)
|
||||
|
||||
// ErrNotImplemented returned for not implemented features
|
||||
var ErrNotImplemented = errors.New("Error. Not implemented")
|
||||
|
||||
// GenerateBootstrapIso will generate data for cloud init and start ISO builder container
|
||||
func GenerateBootstrapIso(settings *Settings, args []string, out io.Writer) error {
|
||||
if settings.IsogenConfigFile == "" {
|
||||
fmt.Fprintln(out, "Reading config file location from global settings is not supported")
|
||||
return ErrNotImplemented
|
||||
return errors.ErrNotImplemented{}
|
||||
}
|
||||
|
||||
cfg := Config{}
|
||||
ctx := context.Background()
|
||||
cfg := &Config{}
|
||||
|
||||
if err := util.ReadYAMLFile(settings.IsogenConfigFile, &cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Under construction")
|
||||
return nil
|
||||
|
||||
fmt.Fprintln(out, "Creating ISO builder container")
|
||||
builder, err := container.NewContainer(
|
||||
&ctx, cfg.Container.ContainerRuntime,
|
||||
cfg.Container.Image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return generateBootstrapIso(builder, cfg, out, settings.Debug)
|
||||
}
|
||||
|
||||
func generateBootstrapIso(builder container.Container, cfg *Config, out io.Writer, debug bool) error {
|
||||
vols := []string{cfg.Container.Volume}
|
||||
fmt.Fprintf(out, "Running default container command. Mounted dir: %s\n", vols)
|
||||
if err := builder.RunCommand(
|
||||
[]string{},
|
||||
nil,
|
||||
vols,
|
||||
[]string{},
|
||||
debug,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintln(out, "ISO successfully built.")
|
||||
if debug {
|
||||
fmt.Fprintf(
|
||||
out,
|
||||
"Debug flag is set. Container %s stopped but not deleted.\n",
|
||||
builder.GetId(),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintln(out, "Removing container.")
|
||||
return builder.RmContainer()
|
||||
}
|
||||
|
98
pkg/bootstrap/isogen/command_test.go
Normal file
98
pkg/bootstrap/isogen/command_test.go
Normal file
@ -0,0 +1,98 @@
|
||||
package isogen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type mockContainer struct {
|
||||
imagePull func() error
|
||||
runCommand func() error
|
||||
runCommandOutput func() (io.ReadCloser, error)
|
||||
rmContainer func() error
|
||||
getId func() string
|
||||
}
|
||||
|
||||
func (mc *mockContainer) ImagePull() error {
|
||||
return mc.imagePull()
|
||||
}
|
||||
|
||||
func (mc *mockContainer) RunCommand([]string, io.Reader, []string, []string, bool) error {
|
||||
return mc.runCommand()
|
||||
}
|
||||
|
||||
func (mc *mockContainer) RunCommandOutput([]string, io.Reader, []string, []string) (io.ReadCloser, error) {
|
||||
return mc.runCommandOutput()
|
||||
}
|
||||
|
||||
func (mc *mockContainer) RmContainer() error {
|
||||
return mc.rmContainer()
|
||||
}
|
||||
|
||||
func (mc *mockContainer) GetId() string {
|
||||
return mc.getId()
|
||||
}
|
||||
|
||||
func TestBootstrapIso(t *testing.T) {
|
||||
testErr := fmt.Errorf("TestErr")
|
||||
expOut := []string{
|
||||
"Running default container command. Mounted dir: []\n",
|
||||
"ISO successfully built.\n",
|
||||
"Debug flag is set. Container TESTID stopped but not deleted.\n",
|
||||
"Removing container.\n",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
builder *mockContainer
|
||||
cfg *Config
|
||||
debug bool
|
||||
expectedOut string
|
||||
expectdErr error
|
||||
}{
|
||||
{
|
||||
builder: &mockContainer{
|
||||
runCommand: func() error { return testErr },
|
||||
},
|
||||
cfg: &Config{},
|
||||
debug: false,
|
||||
expectedOut: expOut[0],
|
||||
expectdErr: testErr,
|
||||
},
|
||||
{
|
||||
builder: &mockContainer{
|
||||
runCommand: func() error { return nil },
|
||||
getId: func() string { return "TESTID" },
|
||||
},
|
||||
cfg: &Config{},
|
||||
debug: true,
|
||||
expectedOut: expOut[0] + expOut[1] + expOut[2],
|
||||
expectdErr: nil,
|
||||
},
|
||||
{
|
||||
builder: &mockContainer{
|
||||
runCommand: func() error { return nil },
|
||||
getId: func() string { return "TESTID" },
|
||||
rmContainer: func() error { return testErr },
|
||||
},
|
||||
cfg: &Config{},
|
||||
debug: false,
|
||||
expectedOut: expOut[0] + expOut[1] + expOut[3],
|
||||
expectdErr: testErr,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
actualOut := bytes.NewBufferString("")
|
||||
actualErr := generateBootstrapIso(tt.builder, tt.cfg, actualOut, tt.debug)
|
||||
|
||||
errS := fmt.Sprintf("generateBootstrapIso should have return error %s, got %s", tt.expectdErr, actualErr)
|
||||
assert.Equal(t, actualErr, tt.expectdErr, errS)
|
||||
|
||||
errS = fmt.Sprintf("generateBootstrapIso should have print %s, got %s", tt.expectedOut, actualOut)
|
||||
assert.Equal(t, actualOut.String(), tt.expectedOut, errS)
|
||||
}
|
||||
}
|
28
pkg/bootstrap/isogen/config_test.go
Normal file
28
pkg/bootstrap/isogen/config_test.go
Normal file
@ -0,0 +1,28 @@
|
||||
package isogen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestToYaml(t *testing.T) {
|
||||
expectedBytes := []byte(`builder: {}
|
||||
container:
|
||||
containerRuntime: docker
|
||||
`)
|
||||
cnf := &Config{
|
||||
Container: Container{
|
||||
ContainerRuntime: "docker",
|
||||
},
|
||||
}
|
||||
|
||||
actualBytes, _ := cnf.ToYAML()
|
||||
errS := fmt.Sprintf(
|
||||
"Call ToYAML should have returned %s, got %s",
|
||||
expectedBytes,
|
||||
actualBytes,
|
||||
)
|
||||
assert.Equal(t, actualBytes, expectedBytes, errS)
|
||||
}
|
37
pkg/container/container.go
Normal file
37
pkg/container/container.go
Normal file
@ -0,0 +1,37 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Container interface abstracion for continer.
|
||||
// Particular implementation depends on container runtime environment (CRE). Interface
|
||||
// defines methods that must be implemented for CRE (e.g. docker, containerd or CRI-O)
|
||||
type Container interface {
|
||||
ImagePull() error
|
||||
RunCommand([]string, io.Reader, []string, []string, bool) error
|
||||
RunCommandOutput([]string, io.Reader, []string, []string) (io.ReadCloser, error)
|
||||
RmContainer() error
|
||||
GetId() string
|
||||
}
|
||||
|
||||
// NewContainer returns instance of Container interface implemented by particular driver
|
||||
// Returned instance type (i.e. implementation) depends on driver sceified via function
|
||||
// arguments (e.g. "docker").
|
||||
// Supported drivers:
|
||||
// * docker
|
||||
func NewContainer(ctx *context.Context, driver string, url string) (Container, error) {
|
||||
switch driver {
|
||||
case "docker":
|
||||
cli, err := NewDockerClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewDockerContainer(ctx, url, cli)
|
||||
default:
|
||||
|
||||
return nil, ErrContainerDrvNotSupported{Driver: driver}
|
||||
}
|
||||
|
||||
}
|
300
pkg/container/container_docker.go
Normal file
300
pkg/container/container_docker.go
Normal file
@ -0,0 +1,300 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"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/network"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
// DockerClient interface that represents abstract Docker client object
|
||||
// Interface used as a wrapper for upstream docker client object
|
||||
type DockerClient interface {
|
||||
// ImageInspectWithRaw returns the image information and its raw
|
||||
// representation.
|
||||
ImageInspectWithRaw(
|
||||
context.Context,
|
||||
string,
|
||||
) (types.ImageInspect, []byte, error)
|
||||
// ImageList returns a list of images in the docker host.
|
||||
ImageList(
|
||||
context.Context,
|
||||
types.ImageListOptions,
|
||||
) ([]types.ImageSummary, error)
|
||||
// ImagePull requests the docker host to pull an image from a remote registry.
|
||||
ImagePull(
|
||||
context.Context,
|
||||
string,
|
||||
types.ImagePullOptions,
|
||||
) (io.ReadCloser, error)
|
||||
// ContainerCreate creates a new container based in the given configuration.
|
||||
ContainerCreate(
|
||||
context.Context,
|
||||
*container.Config,
|
||||
*container.HostConfig,
|
||||
*network.NetworkingConfig,
|
||||
string,
|
||||
) (container.ContainerCreateCreatedBody, error)
|
||||
// ContainerAttach attaches a connection to a container in the server.
|
||||
ContainerAttach(
|
||||
context.Context,
|
||||
string,
|
||||
types.ContainerAttachOptions,
|
||||
) (types.HijackedResponse, error)
|
||||
//ContainerStart sends a request to the docker daemon to start a container.
|
||||
ContainerStart(context.Context, string, types.ContainerStartOptions) error
|
||||
// ContainerWait waits until the specified container is in a certain state
|
||||
// indicated by the given condition, either "not-running" (default),
|
||||
// "next-exit", or "removed".
|
||||
ContainerWait(
|
||||
context.Context,
|
||||
string,
|
||||
container.WaitCondition,
|
||||
) (<-chan container.ContainerWaitOKBody, <-chan error)
|
||||
// ContainerLogs returns the logs generated by a continer in an
|
||||
// io.ReadCloser.
|
||||
ContainerLogs(
|
||||
context.Context,
|
||||
string,
|
||||
types.ContainerLogsOptions,
|
||||
) (io.ReadCloser, error)
|
||||
// ContainerRemove kills and removes a container from the docker host.
|
||||
ContainerRemove(
|
||||
context.Context,
|
||||
string,
|
||||
types.ContainerRemoveOptions,
|
||||
) error
|
||||
}
|
||||
|
||||
// DockerContainer docker container object wrapper
|
||||
type DockerContainer struct {
|
||||
tag string
|
||||
imageUrl string
|
||||
id string
|
||||
dockerClient DockerClient
|
||||
ctx *context.Context
|
||||
}
|
||||
|
||||
// NewDockerClient returns instance of DockerClient.
|
||||
// Function essentially returns new Docker API client with default values
|
||||
func NewDockerClient(ctx *context.Context) (DockerClient, error) {
|
||||
cli, err := client.NewClientWithOpts(client.FromEnv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cli.NegotiateAPIVersion(*ctx)
|
||||
return cli, nil
|
||||
}
|
||||
|
||||
// NewDockerContainer returns instance of DockerContainer object wrapper.
|
||||
// Function gets container image url, pointer to execution context and
|
||||
// DockerClient instance.
|
||||
//
|
||||
// url format: <image_path>:<tag>. If tag is not specified "latest" is used
|
||||
// as default value
|
||||
func NewDockerContainer(ctx *context.Context, url string, cli DockerClient) (*DockerContainer, error) {
|
||||
t := "latest"
|
||||
nameTag := strings.Split(url, ":")
|
||||
if len(nameTag) == 2 {
|
||||
t = nameTag[1]
|
||||
}
|
||||
|
||||
cnt := &DockerContainer{
|
||||
tag: t,
|
||||
imageUrl: url,
|
||||
id: "",
|
||||
dockerClient: cli,
|
||||
ctx: ctx,
|
||||
}
|
||||
if err := cnt.ImagePull(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cnt, nil
|
||||
}
|
||||
|
||||
// getCmd identifies container command. Accepts list of strings each element
|
||||
// represents command part (e.g "sample cmd --key" should be transformed to
|
||||
// []string{"sample", "command", "--key"})
|
||||
//
|
||||
// If input parameter is NOT empty list method returns input parameter
|
||||
// immediately
|
||||
//
|
||||
// If input parameter is empty list method identifies container image and
|
||||
// tries to extract Cmd option from this image description (i.e. tries to
|
||||
// identify default command specified in Dockerfile)
|
||||
func (c *DockerContainer) getCmd(cmd []string) ([]string, error) {
|
||||
if len(cmd) > 0 {
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
id, err := c.getImageId(c.imageUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
insp, _, err := c.dockerClient.ImageInspectWithRaw(*c.ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config := *insp.Config
|
||||
return config.Cmd, nil
|
||||
}
|
||||
|
||||
// getConfig creates configuration structures for Docker API client.
|
||||
func (c *DockerContainer) getConfig(
|
||||
cmd []string,
|
||||
volumeMounts []string,
|
||||
envVars []string,
|
||||
) (container.Config, container.HostConfig) {
|
||||
cCfg := container.Config{
|
||||
Image: c.imageUrl,
|
||||
Cmd: cmd,
|
||||
AttachStdin: true,
|
||||
OpenStdin: true,
|
||||
Env: envVars,
|
||||
}
|
||||
hCfg := container.HostConfig{
|
||||
Binds: volumeMounts,
|
||||
}
|
||||
return cCfg, hCfg
|
||||
}
|
||||
|
||||
// getImageId return ID of continer image specified by URL. Method executes
|
||||
// ImageList function supplied with "reference" filter
|
||||
func (c *DockerContainer) getImageId(url string) (string, error) {
|
||||
kv := filters.KeyValuePair{
|
||||
Key: "reference",
|
||||
Value: url,
|
||||
}
|
||||
filter := filters.NewArgs(kv)
|
||||
opts := types.ImageListOptions{
|
||||
All: false,
|
||||
Filters: filter,
|
||||
}
|
||||
img, err := c.dockerClient.ImageList(*c.ctx, opts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(img) == 0 {
|
||||
return "", ErrEmptyImageList{}
|
||||
}
|
||||
|
||||
return img[0].ID, nil
|
||||
}
|
||||
|
||||
func (c *DockerContainer) GetId() string {
|
||||
return c.id
|
||||
}
|
||||
|
||||
// ImagePull downloads image for container
|
||||
func (c *DockerContainer) ImagePull() error {
|
||||
// TODO (D. Ukov) add logic for searching among local images
|
||||
// to avoid image download on each execution
|
||||
resp, err := c.dockerClient.ImagePull(*c.ctx, c.imageUrl, types.ImagePullOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Wait for image is downloaded
|
||||
if _, err := ioutil.ReadAll(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunCommand executes specified command in Docker container. Method handles
|
||||
// container STDIN and volume binds
|
||||
func (c *DockerContainer) RunCommand(
|
||||
cmd []string,
|
||||
containerInput io.Reader,
|
||||
volumeMounts []string,
|
||||
envVars []string,
|
||||
// TODO (D. Ukov) add debug logic
|
||||
debug bool,
|
||||
) error {
|
||||
realCmd, err := c.getCmd(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
containerConfig, hostConfig := c.getConfig(realCmd, volumeMounts, envVars)
|
||||
resp, err := c.dockerClient.ContainerCreate(
|
||||
*c.ctx,
|
||||
&containerConfig,
|
||||
&hostConfig,
|
||||
nil,
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.id = resp.ID
|
||||
|
||||
if containerInput != nil {
|
||||
conn, attachErr := c.dockerClient.ContainerAttach(*c.ctx, c.id, types.ContainerAttachOptions{
|
||||
Stream: true,
|
||||
Stdin: true,
|
||||
})
|
||||
if attachErr != nil {
|
||||
return attachErr
|
||||
}
|
||||
if _, err = io.Copy(conn.Conn, containerInput); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = c.dockerClient.ContainerStart(*c.ctx, c.id, types.ContainerStartOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
statusCh, errCh := c.dockerClient.ContainerWait(*c.ctx, c.id, container.WaitConditionNotRunning)
|
||||
select {
|
||||
case err = <-errCh:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case retCode := <-statusCh:
|
||||
if retCode.StatusCode != 0 {
|
||||
logsCmd := fmt.Sprintf("docker logs %s", c.id)
|
||||
return ErrRunContainerCommand{Cmd: logsCmd}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunCommandOutput executes specified command in Docker container and
|
||||
// returns command output as ReadCloser object. RunCommand debug option is
|
||||
// set to false explicitly
|
||||
func (c *DockerContainer) RunCommandOutput(
|
||||
cmd []string,
|
||||
continerInput io.Reader,
|
||||
volumeMounts []string,
|
||||
envVars []string,
|
||||
) (io.ReadCloser, error) {
|
||||
if err := c.RunCommand(cmd, continerInput, volumeMounts, envVars, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.dockerClient.ContainerLogs(*c.ctx, c.id, types.ContainerLogsOptions{ShowStdout: true})
|
||||
}
|
||||
|
||||
// RmContainer kills and removes a container from the docker host.
|
||||
func (c *DockerContainer) RmContainer() error {
|
||||
return c.dockerClient.ContainerRemove(
|
||||
*c.ctx,
|
||||
c.id,
|
||||
types.ContainerRemoveOptions{
|
||||
Force: true,
|
||||
},
|
||||
)
|
||||
}
|
501
pkg/container/container_docker_test.go
Normal file
501
pkg/container/container_docker_test.go
Normal file
@ -0,0 +1,501 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
)
|
||||
|
||||
type mockConn struct {
|
||||
WData []byte
|
||||
}
|
||||
|
||||
func (mc mockConn) Read(b []byte) (n int, err error) { return len(b), nil }
|
||||
func (mc mockConn) Write(b []byte) (n int, err error) {
|
||||
copy(mc.WData, b)
|
||||
return len(b), nil
|
||||
}
|
||||
func (mc mockConn) Close() error { return nil }
|
||||
func (mc mockConn) LocalAddr() net.Addr { return nil }
|
||||
func (mc mockConn) RemoteAddr() net.Addr { return nil }
|
||||
func (mc mockConn) SetDeadline(t time.Time) error { return nil }
|
||||
func (mc mockConn) SetReadDeadline(t time.Time) error { return nil }
|
||||
func (mc mockConn) SetWriteDeadline(t time.Time) error { return nil }
|
||||
|
||||
type mockDockerClient struct {
|
||||
imageInspectWithRaw func() (types.ImageInspect, []byte, error)
|
||||
imageList func() ([]types.ImageSummary, error)
|
||||
containerAttach func() (types.HijackedResponse, error)
|
||||
imagePull func() (io.ReadCloser, error)
|
||||
containerStart func() error
|
||||
containerWait func() (<-chan container.ContainerWaitOKBody, <-chan error)
|
||||
containerLogs func() (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
func (mdc *mockDockerClient) ImageInspectWithRaw(context.Context, string) (types.ImageInspect, []byte, error) {
|
||||
return mdc.imageInspectWithRaw()
|
||||
}
|
||||
func (mdc *mockDockerClient) ImageList(context.Context, types.ImageListOptions) ([]types.ImageSummary, error) {
|
||||
return mdc.imageList()
|
||||
}
|
||||
func (mdc *mockDockerClient) ImagePull(
|
||||
context.Context,
|
||||
string,
|
||||
types.ImagePullOptions,
|
||||
) (io.ReadCloser, error) {
|
||||
return mdc.imagePull()
|
||||
}
|
||||
func (mdc *mockDockerClient) ContainerCreate(
|
||||
context.Context,
|
||||
*container.Config,
|
||||
*container.HostConfig,
|
||||
*network.NetworkingConfig,
|
||||
string,
|
||||
) (container.ContainerCreateCreatedBody, error) {
|
||||
return container.ContainerCreateCreatedBody{ID: "testID"}, nil
|
||||
}
|
||||
func (mdc *mockDockerClient) ContainerAttach(
|
||||
context.Context,
|
||||
string,
|
||||
types.ContainerAttachOptions,
|
||||
) (types.HijackedResponse, error) {
|
||||
return mdc.containerAttach()
|
||||
}
|
||||
func (mdc *mockDockerClient) ContainerStart(context.Context, string, types.ContainerStartOptions) error {
|
||||
if mdc.containerStart != nil {
|
||||
return mdc.containerStart()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (mdc *mockDockerClient) ContainerWait(
|
||||
context.Context,
|
||||
string,
|
||||
container.WaitCondition,
|
||||
) (<-chan container.ContainerWaitOKBody, <-chan error) {
|
||||
if mdc.containerWait != nil {
|
||||
return mdc.containerWait()
|
||||
}
|
||||
|
||||
resC := make(chan container.ContainerWaitOKBody)
|
||||
go func() {
|
||||
resC <- container.ContainerWaitOKBody{StatusCode: 0}
|
||||
}()
|
||||
return resC, nil
|
||||
}
|
||||
func (mdc *mockDockerClient) ContainerLogs(context.Context, string, types.ContainerLogsOptions) (io.ReadCloser, error) {
|
||||
if mdc.containerLogs != nil {
|
||||
return mdc.containerLogs()
|
||||
}
|
||||
return ioutil.NopCloser(strings.NewReader("")), nil
|
||||
}
|
||||
|
||||
func (mdc *mockDockerClient) ContainerRemove(context.Context, string, types.ContainerRemoveOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDockerContainerMock(mdc mockDockerClient) *DockerContainer {
|
||||
ctx := context.Background()
|
||||
cnt := &DockerContainer{
|
||||
dockerClient: &mdc,
|
||||
ctx: &ctx,
|
||||
}
|
||||
return cnt
|
||||
}
|
||||
|
||||
func TestGetCmd(t *testing.T) {
|
||||
testError := fmt.Errorf("Inspect error")
|
||||
tests := []struct {
|
||||
cmd []string
|
||||
mockDockerClient mockDockerClient
|
||||
expectedResult []string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
cmd: []string{"testCmd"},
|
||||
mockDockerClient: mockDockerClient{},
|
||||
expectedResult: []string{"testCmd"},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
cmd: []string{},
|
||||
mockDockerClient: mockDockerClient{
|
||||
imageList: func() ([]types.ImageSummary, error) {
|
||||
return []types.ImageSummary{{ID: "imgid"}}, nil
|
||||
},
|
||||
imageInspectWithRaw: func() (types.ImageInspect, []byte, error) {
|
||||
return types.ImageInspect{
|
||||
Config: &container.Config{
|
||||
Cmd: []string{"testCmd"},
|
||||
},
|
||||
}, nil, nil
|
||||
|
||||
},
|
||||
},
|
||||
expectedResult: []string{"testCmd"},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
cmd: []string{},
|
||||
mockDockerClient: mockDockerClient{
|
||||
imageList: func() ([]types.ImageSummary, error) {
|
||||
return []types.ImageSummary{{ID: "imgid"}}, nil
|
||||
},
|
||||
imageInspectWithRaw: func() (types.ImageInspect, []byte, error) {
|
||||
return types.ImageInspect{}, nil, testError
|
||||
},
|
||||
},
|
||||
expectedResult: nil,
|
||||
expectedErr: testError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
cnt := getDockerContainerMock(tt.mockDockerClient)
|
||||
actualRes, actualErr := cnt.getCmd(tt.cmd)
|
||||
|
||||
assert.Equal(t, tt.expectedErr, actualErr)
|
||||
assert.Equal(t, tt.expectedResult, actualRes)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetImageId(t *testing.T) {
|
||||
testError := fmt.Errorf("Img List Error")
|
||||
tests := []struct {
|
||||
url string
|
||||
mockDockerClient mockDockerClient
|
||||
expectedResult string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
url: "test:latest",
|
||||
mockDockerClient: mockDockerClient{
|
||||
imageList: func() ([]types.ImageSummary, error) {
|
||||
return nil, testError
|
||||
},
|
||||
},
|
||||
expectedResult: "",
|
||||
expectedErr: testError,
|
||||
},
|
||||
{
|
||||
url: "test:latest",
|
||||
mockDockerClient: mockDockerClient{
|
||||
imageList: func() ([]types.ImageSummary, error) {
|
||||
return []types.ImageSummary{}, nil
|
||||
},
|
||||
},
|
||||
expectedResult: "",
|
||||
expectedErr: ErrEmptyImageList{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
cnt := getDockerContainerMock(tt.mockDockerClient)
|
||||
actualRes, actualErr := cnt.getImageId(tt.url)
|
||||
|
||||
assert.Equal(t, tt.expectedErr, actualErr)
|
||||
assert.Equal(t, tt.expectedResult, actualRes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImagePull(t *testing.T) {
|
||||
testError := fmt.Errorf("Image Pull Error")
|
||||
tests := []struct {
|
||||
mockDockerClient mockDockerClient
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
mockDockerClient: mockDockerClient{
|
||||
imagePull: func() (io.ReadCloser, error) {
|
||||
return ioutil.NopCloser(strings.NewReader("test")), nil
|
||||
},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
mockDockerClient: mockDockerClient{
|
||||
imagePull: func() (io.ReadCloser, error) {
|
||||
return nil, testError
|
||||
},
|
||||
},
|
||||
expectedErr: testError,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
cnt := getDockerContainerMock(tt.mockDockerClient)
|
||||
actualErr := cnt.ImagePull()
|
||||
|
||||
assert.Equal(t, tt.expectedErr, actualErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetId(t *testing.T) {
|
||||
cnt := getDockerContainerMock(mockDockerClient{})
|
||||
cnt.RunCommand([]string{"testCmd"}, nil, nil, []string{}, false)
|
||||
actialId := cnt.GetId()
|
||||
|
||||
assert.Equal(t, "testID", actialId)
|
||||
}
|
||||
|
||||
func TestRunCommand(t *testing.T) {
|
||||
imageListeError := fmt.Errorf("Image List Error")
|
||||
attachError := fmt.Errorf("Attach error")
|
||||
containerStartError := fmt.Errorf("Container Start error")
|
||||
containerWaitError := fmt.Errorf("Container Wait Error")
|
||||
tests := []struct {
|
||||
cmd []string
|
||||
containerInput io.Reader
|
||||
volumeMounts []string
|
||||
debug bool
|
||||
mockDockerClient mockDockerClient
|
||||
expectedErr error
|
||||
assertF func(*testing.T)
|
||||
}{
|
||||
{
|
||||
cmd: []string{"testCmd"},
|
||||
containerInput: nil,
|
||||
volumeMounts: nil,
|
||||
debug: false,
|
||||
mockDockerClient: mockDockerClient{},
|
||||
expectedErr: nil,
|
||||
assertF: func(t *testing.T) {},
|
||||
},
|
||||
{
|
||||
cmd: []string{},
|
||||
containerInput: nil,
|
||||
volumeMounts: nil,
|
||||
debug: false,
|
||||
mockDockerClient: mockDockerClient{
|
||||
imageList: func() ([]types.ImageSummary, error) {
|
||||
return nil, imageListeError
|
||||
},
|
||||
},
|
||||
expectedErr: imageListeError,
|
||||
assertF: func(t *testing.T) {},
|
||||
},
|
||||
{
|
||||
cmd: []string{"testCmd"},
|
||||
containerInput: strings.NewReader("testInput"),
|
||||
volumeMounts: nil,
|
||||
debug: false,
|
||||
mockDockerClient: mockDockerClient{
|
||||
containerAttach: func() (types.HijackedResponse, error) {
|
||||
conn := types.HijackedResponse{
|
||||
Conn: mockConn{WData: make([]byte, len([]byte("testInput")))},
|
||||
}
|
||||
return conn, nil
|
||||
},
|
||||
},
|
||||
expectedErr: nil,
|
||||
assertF: func(t *testing.T) {},
|
||||
},
|
||||
{
|
||||
cmd: []string{"testCmd"},
|
||||
containerInput: strings.NewReader("testInput"),
|
||||
volumeMounts: nil,
|
||||
debug: false,
|
||||
mockDockerClient: mockDockerClient{
|
||||
containerAttach: func() (types.HijackedResponse, error) {
|
||||
return types.HijackedResponse{}, attachError
|
||||
},
|
||||
},
|
||||
expectedErr: attachError,
|
||||
assertF: func(t *testing.T) {},
|
||||
},
|
||||
{
|
||||
cmd: []string{"testCmd"},
|
||||
containerInput: nil,
|
||||
volumeMounts: nil,
|
||||
debug: false,
|
||||
mockDockerClient: mockDockerClient{
|
||||
containerStart: func() error {
|
||||
return containerStartError
|
||||
},
|
||||
},
|
||||
expectedErr: containerStartError,
|
||||
assertF: func(t *testing.T) {},
|
||||
},
|
||||
{
|
||||
cmd: []string{"testCmd"},
|
||||
containerInput: nil,
|
||||
volumeMounts: nil,
|
||||
debug: false,
|
||||
mockDockerClient: mockDockerClient{
|
||||
containerWait: func() (<-chan container.ContainerWaitOKBody, <-chan error) {
|
||||
|
||||
errC := make(chan error)
|
||||
go func() {
|
||||
errC <- containerWaitError
|
||||
}()
|
||||
return nil, errC
|
||||
},
|
||||
},
|
||||
expectedErr: containerWaitError,
|
||||
assertF: func(t *testing.T) {},
|
||||
},
|
||||
{
|
||||
cmd: []string{"testCmd"},
|
||||
containerInput: nil,
|
||||
volumeMounts: nil,
|
||||
debug: false,
|
||||
mockDockerClient: mockDockerClient{
|
||||
containerWait: func() (<-chan container.ContainerWaitOKBody, <-chan error) {
|
||||
|
||||
resC := make(chan container.ContainerWaitOKBody)
|
||||
go func() {
|
||||
resC <- container.ContainerWaitOKBody{StatusCode: 1}
|
||||
}()
|
||||
return resC, nil
|
||||
},
|
||||
},
|
||||
expectedErr: ErrRunContainerCommand{Cmd: "docker logs testID"},
|
||||
assertF: func(t *testing.T) {},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
cnt := getDockerContainerMock(tt.mockDockerClient)
|
||||
actualErr := cnt.RunCommand(tt.cmd, tt.containerInput, tt.volumeMounts, []string{}, tt.debug)
|
||||
|
||||
assert.Equal(t, tt.expectedErr, actualErr)
|
||||
|
||||
tt.assertF(t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCommandOutput(t *testing.T) {
|
||||
testError := fmt.Errorf("Img List Error")
|
||||
tests := []struct {
|
||||
cmd []string
|
||||
containerInput io.Reader
|
||||
volumeMounts []string
|
||||
mockDockerClient mockDockerClient
|
||||
expectedResult string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
cmd: []string{"testCmd"},
|
||||
containerInput: nil,
|
||||
volumeMounts: nil,
|
||||
mockDockerClient: mockDockerClient{},
|
||||
expectedResult: "",
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
cmd: []string{},
|
||||
containerInput: nil,
|
||||
volumeMounts: nil,
|
||||
mockDockerClient: mockDockerClient{
|
||||
imageList: func() ([]types.ImageSummary, error) {
|
||||
return nil, testError
|
||||
},
|
||||
},
|
||||
expectedResult: "",
|
||||
expectedErr: testError,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
cnt := getDockerContainerMock(tt.mockDockerClient)
|
||||
actualRes, actualErr := cnt.RunCommandOutput(tt.cmd, tt.containerInput, tt.volumeMounts, []string{})
|
||||
|
||||
assert.Equal(t, tt.expectedErr, actualErr)
|
||||
|
||||
var actualResBytes []byte
|
||||
if actualRes != nil {
|
||||
actualResBytes, _ = ioutil.ReadAll(actualRes)
|
||||
} else {
|
||||
actualResBytes = []byte{}
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.expectedResult, string(actualResBytes))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewDockerContainer(t *testing.T) {
|
||||
testError := fmt.Errorf("Image Pull Error")
|
||||
type resultStruct struct {
|
||||
tag string
|
||||
imageUrl string
|
||||
id string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
url string
|
||||
ctx context.Context
|
||||
cli mockDockerClient
|
||||
expectedErr error
|
||||
expectedResult resultStruct
|
||||
}{
|
||||
{
|
||||
url: "testPrefix/testImage:testTag",
|
||||
ctx: context.Background(),
|
||||
cli: mockDockerClient{
|
||||
imagePull: func() (io.ReadCloser, error) {
|
||||
return ioutil.NopCloser(strings.NewReader("test")), nil
|
||||
},
|
||||
},
|
||||
expectedErr: nil,
|
||||
expectedResult: resultStruct{
|
||||
tag: "testTag",
|
||||
imageUrl: "testPrefix/testImage:testTag",
|
||||
id: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
url: "testPrefix/testImage:testTag",
|
||||
ctx: context.Background(),
|
||||
cli: mockDockerClient{
|
||||
imagePull: func() (io.ReadCloser, error) {
|
||||
return nil, testError
|
||||
},
|
||||
},
|
||||
expectedErr: testError,
|
||||
expectedResult: resultStruct{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
actualRes, actualErr := NewDockerContainer(&(tt.ctx), tt.url, &(tt.cli))
|
||||
|
||||
assert.Equal(t, tt.expectedErr, actualErr)
|
||||
|
||||
var actualResStuct resultStruct
|
||||
if actualRes == nil {
|
||||
actualResStuct = resultStruct{}
|
||||
} else {
|
||||
actualResStuct = resultStruct{
|
||||
tag: actualRes.tag,
|
||||
imageUrl: actualRes.imageUrl,
|
||||
id: actualRes.id,
|
||||
}
|
||||
}
|
||||
assert.Equal(t, tt.expectedResult, actualResStuct)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRmContainer(t *testing.T) {
|
||||
tests := []struct {
|
||||
mockDockerClient mockDockerClient
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
mockDockerClient: mockDockerClient{},
|
||||
expectedErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
cnt := getDockerContainerMock(tt.mockDockerClient)
|
||||
actualErr := cnt.RmContainer()
|
||||
assert.Equal(t, tt.expectedErr, actualErr)
|
||||
}
|
||||
}
|
21
pkg/container/container_test.go
Normal file
21
pkg/container/container_test.go
Normal file
@ -0,0 +1,21 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewContainer(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
_, actualErr := NewContainer(&ctx, "test_drv", "")
|
||||
expectedErr := ErrContainerDrvNotSupported{Driver: "test_drv"}
|
||||
errS := fmt.Sprintf(
|
||||
"Call NewContainer should have returned error %s, got %s",
|
||||
expectedErr,
|
||||
actualErr,
|
||||
)
|
||||
assert.Equal(t, actualErr, expectedErr, errS)
|
||||
}
|
32
pkg/container/errors.go
Normal file
32
pkg/container/errors.go
Normal file
@ -0,0 +1,32 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrEmptyImageList returned if no image defined in filter found
|
||||
type ErrEmptyImageList struct {
|
||||
}
|
||||
|
||||
func (e ErrEmptyImageList) Error() string {
|
||||
return "Image List Error. No images filetered"
|
||||
}
|
||||
|
||||
// ErrRunContainerCommand returned if container command
|
||||
// exited with non-zero code
|
||||
type ErrRunContainerCommand struct {
|
||||
Cmd string
|
||||
}
|
||||
|
||||
func (e ErrRunContainerCommand) Error() string {
|
||||
return fmt.Sprintf("Command error. run '%s' for details", e.Cmd)
|
||||
}
|
||||
|
||||
// ErrContainerDrvNotSupported returned if desired CRI is not supported
|
||||
type ErrContainerDrvNotSupported struct {
|
||||
Driver string
|
||||
}
|
||||
|
||||
func (e ErrContainerDrvNotSupported) Error() string {
|
||||
return fmt.Sprintf("Driver %s is not supported", e.Driver)
|
||||
}
|
9
pkg/errors/common.go
Normal file
9
pkg/errors/common.go
Normal file
@ -0,0 +1,9 @@
|
||||
package errors
|
||||
|
||||
// ErrNotImplemented returned for not implemented features
|
||||
type ErrNotImplemented struct {
|
||||
}
|
||||
|
||||
func (e ErrNotImplemented) Error() string {
|
||||
return "Error. Not implemented"
|
||||
}
|
Loading…
Reference in New Issue
Block a user