[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/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
|
||||||
github.com/daviddengcn/go-colortext v0.0.0-20180409174941-186a3d44e920 // 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/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-connections v0.3.0 // indirect
|
||||||
github.com/docker/go-units v0.3.3 // indirect
|
github.com/docker/go-units v0.3.3 // indirect
|
||||||
github.com/docker/libnetwork v0.0.0-20180830151422-a9cd636e3789 // indirect
|
github.com/docker/libnetwork v0.0.0-20180830151422-a9cd636e3789 // indirect
|
||||||
|
@ -1,28 +1,62 @@
|
|||||||
package isogen
|
package isogen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"opendev.org/airship/airshipctl/pkg/container"
|
||||||
|
"opendev.org/airship/airshipctl/pkg/errors"
|
||||||
"opendev.org/airship/airshipctl/pkg/util"
|
"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
|
// GenerateBootstrapIso will generate data for cloud init and start ISO builder container
|
||||||
func GenerateBootstrapIso(settings *Settings, args []string, out io.Writer) error {
|
func GenerateBootstrapIso(settings *Settings, args []string, out io.Writer) error {
|
||||||
if settings.IsogenConfigFile == "" {
|
if settings.IsogenConfigFile == "" {
|
||||||
fmt.Fprintln(out, "Reading config file location from global settings is not supported")
|
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 {
|
if err := util.ReadYAMLFile(settings.IsogenConfigFile, &cfg); err != nil {
|
||||||
return err
|
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