1
0
mirror of https://github.com/openshift/source-to-image.git synced 2026-02-05 12:44:54 +01:00

s2i switch to k8s/engine-api

This commit is contained in:
gabemontero
2016-07-25 12:38:15 -04:00
parent ada1c5fff8
commit 7f8cd156d0
15 changed files with 1130 additions and 582 deletions

4
Godeps/Godeps.json generated
View File

@@ -18,8 +18,8 @@
},
{
"ImportPath": "github.com/Sirupsen/logrus/formatters/logstash",
"Comment": "v0.7.3-4-gaaf92c9",
"Rev": "aaf92c95712104318fc35409745f1533aa5ff327"
"Comment": "v0.10.0-21-g32055c3",
"Rev": "32055c351ea8b00b96d70f28db48d9840feaf0ec"
},
{
"ImportPath": "github.com/beorn7/perks/quantile",

View File

@@ -353,6 +353,7 @@ _s2i()
flags+=("--ca=")
flags+=("--cert=")
flags+=("--key=")
flags+=("--log-flush-frequency=")
flags+=("--loglevel=")
flags+=("--url=")
two_word_flags+=("-U")

View File

@@ -206,6 +206,10 @@ EOF
fi
fi
# For any tools that expect this to be set (it is default in golang 1.6),
# force vendor experiment.
export GO15VENDOREXPERIMENT=1
GOPATH=${S2I_GOPATH}
# Append S2I_EXTRA_GOPATH to the GOPATH if it is defined.

View File

@@ -14,6 +14,7 @@ function time_now()
mkdir -p /tmp/sti
WORK_DIR=$(mktemp -d /tmp/sti/test-work.XXXX)
mkdir -p ${WORK_DIR}
NEEDKILL="yes"
S2I_PID=""
function cleanup()
@@ -24,7 +25,7 @@ function cleanup()
echo "Cleaning up working dir ${WORK_DIR}"
else
echo "Dumping logs since did not run successfully before cleanup of ${WORK_DIR} ..."
cat /tmp/test-work*/*
cat ${WORK_DIR}/*.log
fi
rm -rf ${WORK_DIR}
# use sigint so that s2i post processing will remove docker container
@@ -74,24 +75,24 @@ check_result $? "${WORK_DIR}/s2i-git-clone.log"
test_debug "s2i build with relative path without file://"
s2i build cakephp-ex openshift/php-55-centos7 test &> "${WORK_DIR}/s2i-rel-noproto.log"
s2i build cakephp-ex openshift/php-55-centos7 test --loglevel=5 &> "${WORK_DIR}/s2i-rel-noproto.log"
check_result $? "${WORK_DIR}/s2i-rel-noproto.log"
test_debug "s2i build with relative path with file://"
s2i build file://./cakephp-ex openshift/php-55-centos7 test &> "${WORK_DIR}/s2i-rel-proto.log"
s2i build file://./cakephp-ex openshift/php-55-centos7 test --loglevel=5 &> "${WORK_DIR}/s2i-rel-proto.log"
check_result $? "${WORK_DIR}/s2i-rel-proto.log"
popd
test_debug "s2i build with absolute path with file://"
s2i build "file://${WORK_DIR}/cakephp-ex" openshift/php-55-centos7 test &> "${WORK_DIR}/s2i-abs-proto.log"
s2i build "file://${WORK_DIR}/cakephp-ex" openshift/php-55-centos7 test --loglevel=5 &> "${WORK_DIR}/s2i-abs-proto.log"
check_result $? "${WORK_DIR}/s2i-abs-proto.log"
test_debug "s2i build with absolute path without file://"
s2i build "${WORK_DIR}/cakephp-ex" openshift/php-55-centos7 test &> "${WORK_DIR}/s2i-abs-noproto.log"
s2i build "${WORK_DIR}/cakephp-ex" openshift/php-55-centos7 test --loglevel=5 &> "${WORK_DIR}/s2i-abs-noproto.log"
check_result $? "${WORK_DIR}/s2i-abs-noproto.log"
## don't do ssh tests here because credentials are needed (even for the git user), which
@@ -100,7 +101,7 @@ check_result $? "${WORK_DIR}/s2i-abs-noproto.log"
test_debug "s2i build with non-git repo file location"
rm -rf "${WORK_DIR}/cakephp-ex/.git"
s2i build "${WORK_DIR}/cakephp-ex" openshift/php-55-centos7 test --loglevel=5 &> "${WORK_DIR}/s2i-non-repo.log"
s2i build "${WORK_DIR}/cakephp-ex" openshift/php-55-centos7 test --loglevel=5 --loglevel=5 &> "${WORK_DIR}/s2i-non-repo.log"
check_result $? ""
grep "Copying sources" "${WORK_DIR}/s2i-non-repo.log"
check_result $? "${WORK_DIR}/s2i-non-repo.log"
@@ -113,18 +114,18 @@ check_result $? "${WORK_DIR}/s2i-rebuild.log"
test_debug "s2i usage"
s2i usage openshift/ruby-20-centos7 &> "${WORK_DIR}/s2i-usage.log"
s2i usage openshift/ruby-20-centos7 --loglevel=5 &> "${WORK_DIR}/s2i-usage.log"
check_result $? ""
grep "Sample invocation" "${WORK_DIR}/s2i-usage.log"
check_result $? "${WORK_DIR}/s2i-usage.log"
test_debug "s2i build with git proto"
s2i build git://github.com/openshift/cakephp-ex openshift/php-55-centos7 test --run=true &> "${WORK_DIR}/s2i-git-proto.log" &
s2i build git://github.com/openshift/cakephp-ex openshift/php-55-centos7 test --run=true --loglevel=5 &> "${WORK_DIR}/s2i-git-proto.log" &
check_result $? "${WORK_DIR}/s2i-git-proto.log"
test_debug "s2i build with --run==true option"
s2i build git://github.com/bparees/openshift-jee-sample openshift/wildfly-90-centos7 test-jee-app --run=true &> "${WORK_DIR}/s2i-run.log" &
s2i build git://github.com/bparees/openshift-jee-sample openshift/wildfly-90-centos7 test-jee-app --run=true --loglevel=5 &> "${WORK_DIR}/s2i-run.log" &
S2I_PID=$!
TIME_SEC=1000
TIME_MIN=$((60 * $TIME_SEC))

View File

@@ -6,7 +6,6 @@ import (
"path/filepath"
"strings"
docker "github.com/fsouza/go-dockerclient"
utilglog "github.com/openshift/source-to-image/pkg/util/glog"
"github.com/openshift/source-to-image/pkg/util/user"
@@ -74,7 +73,7 @@ type Config struct {
// RuntimeAuthentication holds the authentication information for pulling the
// runtime Docker images from private repositories.
RuntimeAuthentication docker.AuthConfiguration
RuntimeAuthentication AuthConfig
// RuntimeArtifacts specifies a list of source/destination pairs that will
// be copied from builder to a runtime image. Source can be a file or
@@ -94,11 +93,11 @@ type Config struct {
// PullAuthentication holds the authentication information for pulling the
// Docker images from private repositories
PullAuthentication docker.AuthConfiguration
PullAuthentication AuthConfig
// IncrementalAuthentication holds the authentication information for pulling the
// previous image from private repositories
IncrementalAuthentication docker.AuthConfiguration
IncrementalAuthentication AuthConfig
// DockerNetworkMode is used to set the docker network setting to --net=container:<id>
// when the builder is invoked from a container.
@@ -283,6 +282,30 @@ type DockerConfig struct {
CAFile string
}
// AuthConfig is our abstraction of the Registry authorization information for whatever
// docker client we happen to be based on
type AuthConfig struct {
Username string
Password string
Email string
ServerAddress string
}
// ContainerConfig is the abstraction of the docker client provider (formerly go-dockerclient, now either
// engine-api or kube docker client) container.Config type that is leveraged by s2i or origin
type ContainerConfig struct {
Labels map[string]string
Env []string
}
// Image is the abstraction of the docker client provider (formerly go-dockerclient, now either
// engine-api or kube docker client) Image type that is leveraged by s2i or origin
type Image struct {
ID string
*ContainerConfig
Config *ContainerConfig
}
// Result structure contains information from build process.
type Result struct {

View File

@@ -331,7 +331,12 @@ func (step *startRuntimeImageAndUploadFilesStep) execute(ctx *postExecutorStepCo
step.builder.postExecutorStage++
err = step.docker.RunContainer(opts)
// close so we avoid data race condition on errOutput
errReader.Close()
if e, ok := err.(errors.ContainerError); ok {
// even with deferred close above, close errReader now so we avoid data race condition on errOutput;
// closing will cause StreamContainerIO to exit, thus releasing the writer in the equation
errReader.Close()
return errors.NewContainerError(image, e.ErrorCode, errOutput)
}

View File

@@ -437,6 +437,9 @@ func (builder *STI) Save(config *api.Config) (err error) {
go dockerpkg.StreamContainerIO(errReader, nil, func(a ...interface{}) { glog.Info(a...) })
err = builder.docker.RunContainer(opts)
if e, ok := err.(errors.ContainerError); ok {
// even with deferred close above, close errReader now so we avoid data race condition on errOutput;
// closing will cause StreamContainerIO to exit, thus releasing the writer in the equation
errReader.Close()
return errors.NewSaveArtifactsError(image, e.Output, err)
}
return err
@@ -598,6 +601,9 @@ func (builder *STI) Execute(command string, user string, config *api.Config) err
wg.Done()
}
if e, ok := err.(errors.ContainerError); ok {
// even with deferred close above, close errReader now so we avoid data race condition on errOutput;
// closing will cause StreamContainerIO to exit, thus releasing the writer in the equation
errReader.Close()
return errors.NewContainerError(config.BuilderImage, e.ErrorCode, errOutput)
}
return err

View File

@@ -1,18 +1,33 @@
package docker
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"math"
"math/rand"
"net"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"runtime"
"strings"
"sync"
"syscall"
"time"
docker "github.com/fsouza/go-dockerclient"
dockerstdcopy "github.com/docker/docker/pkg/stdcopy"
dockerapi "github.com/docker/engine-api/client"
dockertypes "github.com/docker/engine-api/types"
dockercontainer "github.com/docker/engine-api/types/container"
dockerstrslice "github.com/docker/engine-api/types/strslice"
"github.com/docker/go-connections/tlsconfig"
"golang.org/x/net/context"
"k8s.io/kubernetes/pkg/kubelet/dockertools"
k8snet "k8s.io/kubernetes/pkg/util/net"
"github.com/openshift/source-to-image/pkg/api"
"github.com/openshift/source-to-image/pkg/errors"
@@ -54,6 +69,9 @@ const (
// DefaultDockerTimeout specifies a timeout for Docker API calls. When this
// timeout is reached, certain Docker API calls might error out.
DefaultDockerTimeout = 20 * time.Second
// DefaultShmSize is the default shared memory size to use (in bytes) if not specified.
DefaultShmSize = int64(1024 * 1024 * 64)
)
// containerNamePrefix prefixes the name of containers launched by S2I. We
@@ -75,7 +93,7 @@ func containerName(image string) string {
return fmt.Sprintf("%s_%s_%s", containerNamePrefix, image, uid)
}
// Docker is the interface between STI and the Docker client
// Docker is the interface between STI and the k8s abstraction around docker engine-api.
// It contains higher level operations called from the STI
// build or usage commands
type Docker interface {
@@ -90,9 +108,9 @@ type Docker interface {
GetImageWorkdir(name string) (string, error)
CommitContainer(opts CommitContainerOptions) (string, error)
RemoveImage(name string) error
CheckImage(name string) (*docker.Image, error)
PullImage(name string) (*docker.Image, error)
CheckAndPullImage(name string) (*docker.Image, error)
CheckImage(name string) (*api.Image, error)
PullImage(name string) (*api.Image, error)
CheckAndPullImage(name string) (*api.Image, error)
BuildImage(opts BuildImageOptions) error
GetImageUser(name string) (string, error)
GetImageEntrypoint(name string) ([]string, error)
@@ -103,29 +121,23 @@ type Docker interface {
Ping() error
}
// Client contains all methods called on the go Docker
// client.
// Client contains all methods used when interacting directly with docker engine-api instead of the k8s abstraction around docker engine-api
type Client interface {
RemoveImage(name string) error
InspectImage(name string) (*docker.Image, error)
PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error
CreateContainer(opts docker.CreateContainerOptions) (*docker.Container, error)
AttachToContainerNonBlocking(opts docker.AttachToContainerOptions) (docker.CloseWaiter, error)
StartContainer(id string, hostConfig *docker.HostConfig) error
WaitContainer(id string) (int, error)
UploadToContainer(id string, opts docker.UploadToContainerOptions) error
DownloadFromContainer(id string, opts docker.DownloadFromContainerOptions) error
RemoveContainer(opts docker.RemoveContainerOptions) error
CommitContainer(opts docker.CommitContainerOptions) (*docker.Image, error)
CopyFromContainer(opts docker.CopyFromContainerOptions) error
BuildImage(opts docker.BuildImageOptions) error
InspectContainer(id string) (*docker.Container, error)
Ping() error
ContainerAttach(ctx context.Context, container string, options dockertypes.ContainerAttachOptions) (dockertypes.HijackedResponse, error)
ContainerWait(ctx context.Context, containerID string) (int, error)
ContainerCommit(ctx context.Context, container string, options dockertypes.ContainerCommitOptions) (dockertypes.ContainerCommitResponse, error)
CopyToContainer(ctx context.Context, container, path string, content io.Reader, opts dockertypes.CopyToContainerOptions) error
CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, dockertypes.ContainerPathStat, error)
ImageBuild(ctx context.Context, buildContext io.Reader, options dockertypes.ImageBuildOptions) (dockertypes.ImageBuildResponse, error)
}
type stiDocker struct {
client Client
pullAuth docker.AuthConfiguration
kubeDockerClient dockertools.DockerInterface
client Client
httpClient *http.Client
dialer *net.Dialer
pullAuth dockertypes.AuthConfig
endpoint string
}
type PostExecutor interface {
@@ -134,14 +146,14 @@ type PostExecutor interface {
type PullResult struct {
OnBuild bool
Image *docker.Image
Image *api.Image
}
// RunContainerOptions are options passed in to the RunContainer method
type RunContainerOptions struct {
Image string
PullImage bool
PullAuth docker.AuthConfiguration
PullAuth api.AuthConfig
ExternalScripts bool
ScriptsURL string
Destination string
@@ -175,13 +187,13 @@ type RunContainerOptions struct {
}
// asDockerConfig converts a RunContainerOptions into a Config understood by the
// go-dockerclient library.
func (rco RunContainerOptions) asDockerConfig() docker.Config {
return docker.Config{
// docker client
func (rco RunContainerOptions) asDockerConfig() dockercontainer.Config {
return dockercontainer.Config{
Image: getImageName(rco.Image),
User: rco.User,
Env: rco.Env,
Entrypoint: rco.Entrypoint,
Entrypoint: dockerstrslice.StrSlice(rco.Entrypoint),
OpenStdin: rco.Stdin != nil,
StdinOnce: rco.Stdin != nil,
AttachStdout: rco.Stdout != nil,
@@ -189,30 +201,30 @@ func (rco RunContainerOptions) asDockerConfig() docker.Config {
}
// asDockerHostConfig converts a RunContainerOptions into a HostConfig
// understood by the go-dockerclient library.
func (rco RunContainerOptions) asDockerHostConfig() docker.HostConfig {
hostConfig := docker.HostConfig{
// understood by the docker client
func (rco RunContainerOptions) asDockerHostConfig() dockercontainer.HostConfig {
hostConfig := dockercontainer.HostConfig{
CapDrop: rco.CapDrop,
PublishAllPorts: rco.TargetImage,
NetworkMode: rco.NetworkMode,
NetworkMode: dockercontainer.NetworkMode(rco.NetworkMode),
Binds: rco.Binds,
}
if rco.CGroupLimits != nil {
hostConfig.Memory = rco.CGroupLimits.MemoryLimitBytes
hostConfig.MemorySwap = rco.CGroupLimits.MemorySwap
hostConfig.CPUShares = rco.CGroupLimits.CPUShares
hostConfig.CPUQuota = rco.CGroupLimits.CPUQuota
hostConfig.CPUPeriod = rco.CGroupLimits.CPUPeriod
hostConfig.Resources.Memory = rco.CGroupLimits.MemoryLimitBytes
hostConfig.Resources.MemorySwap = rco.CGroupLimits.MemorySwap
hostConfig.Resources.CPUShares = rco.CGroupLimits.CPUShares
hostConfig.Resources.CPUQuota = rco.CGroupLimits.CPUQuota
hostConfig.Resources.CPUPeriod = rco.CGroupLimits.CPUPeriod
}
return hostConfig
}
// asDockerCreateContainerOptions converts a RunContainerOptions into a
// CreateContainerOptions understood by the go-dockerclient library.
func (rco RunContainerOptions) asDockerCreateContainerOptions() docker.CreateContainerOptions {
// ContainerCreateConfig understood by the docker client
func (rco RunContainerOptions) asDockerCreateContainerOptions() dockertypes.ContainerCreateConfig {
config := rco.asDockerConfig()
hostConfig := rco.asDockerHostConfig()
return docker.CreateContainerOptions{
return dockertypes.ContainerCreateConfig{
Name: containerName(rco.Image),
Config: &config,
HostConfig: &hostConfig,
@@ -220,22 +232,26 @@ func (rco RunContainerOptions) asDockerCreateContainerOptions() docker.CreateCon
}
// asDockerAttachToContainerOptions converts a RunContainerOptions into a
// AttachToContainerOptions understood by the go-dockerclient library.
func (rco RunContainerOptions) asDockerAttachToContainerOptions() docker.AttachToContainerOptions {
return docker.AttachToContainerOptions{
InputStream: rco.Stdin,
OutputStream: rco.Stdout,
ErrorStream: rco.Stderr,
// ContainerAttachOptions understood by the docker client
func (rco RunContainerOptions) asDockerAttachToContainerOptions() dockertypes.ContainerAttachOptions {
return dockertypes.ContainerAttachOptions{
Stdin: rco.Stdin != nil,
Stdout: rco.Stdout != nil,
Stderr: rco.Stderr != nil,
Logs: rco.Stdout != nil,
Stream: rco.Stdout != nil,
}
}
// asDockerAttachToStreamOptions converts RunContainerOptions into a
// StreamOptions understood by the docker client
func (rco RunContainerOptions) asDockerAttachToStreamOptions() dockertools.StreamOptions {
return dockertools.StreamOptions{
InputStream: rco.Stdin,
OutputStream: rco.Stdout,
ErrorStream: rco.Stderr,
}
}
// CommitContainerOptions are options passed in to the CommitContainer method
type CommitContainerOptions struct {
ContainerID string
@@ -256,35 +272,58 @@ type BuildImageOptions struct {
}
// New creates a new implementation of the STI Docker interface
func New(config *api.DockerConfig, auth docker.AuthConfiguration) (Docker, error) {
var client *docker.Client
var err error
func New(config *api.DockerConfig, auth api.AuthConfig) (Docker, error) {
var client *dockerapi.Client
var httpClient *http.Client
if config.CertFile != "" && config.KeyFile != "" && config.CAFile != "" {
client, err = docker.NewTLSClient(
config.Endpoint,
config.CertFile,
config.KeyFile,
config.CAFile)
} else {
client, err = docker.NewClient(config.Endpoint)
tlscOptions := tlsconfig.Options{
CAFile: config.CAFile,
CertFile: config.CertFile,
KeyFile: config.KeyFile,
}
tlsc, tlsErr := tlsconfig.Client(tlscOptions)
if tlsErr != nil {
return nil, tlsErr
}
httpClient = &http.Client{
Transport: k8snet.SetTransportDefaults(&http.Transport{
TLSClientConfig: tlsc,
}),
}
}
client, err := dockerapi.NewClient(config.Endpoint, "", httpClient, nil)
if err != nil {
return nil, err
}
k8sDocker := dockertools.ConnectToDockerOrDie(config.Endpoint)
return &stiDocker{
client: client,
pullAuth: auth,
kubeDockerClient: k8sDocker,
client: client,
httpClient: httpClient,
dialer: &net.Dialer{},
pullAuth: dockertypes.AuthConfig{
Username: auth.Username,
Password: auth.Password,
Email: auth.Email,
ServerAddress: auth.ServerAddress,
},
endpoint: config.Endpoint,
}, nil
}
func getDefaultContext(timeout time.Duration) (context.Context, context.CancelFunc) {
return context.WithTimeout(context.Background(), timeout)
}
// GetImageWorkdir returns the WORKDIR property for the given image name.
// When the WORKDIR is not set or empty, return "/" instead.
func (d *stiDocker) GetImageWorkdir(name string) (string, error) {
image, err := d.client.InspectImage(name)
resp, err := d.kubeDockerClient.InspectImage(name)
if err != nil {
return "", err
}
workdir := image.Config.WorkingDir
workdir := resp.Config.WorkingDir
if len(workdir) == 0 {
// This is a default destination used by UploadToContainer when the WORKDIR
// is not set or it is empty. To show user where the injections will end up,
@@ -296,13 +335,65 @@ func (d *stiDocker) GetImageWorkdir(name string) (string, error) {
// GetImageEntrypoint returns the ENTRYPOINT property for the given image name.
func (d *stiDocker) GetImageEntrypoint(name string) ([]string, error) {
image, err := d.client.InspectImage(name)
image, err := d.kubeDockerClient.InspectImage(name)
if err != nil {
return nil, err
}
return image.Config.Entrypoint, nil
}
// do is snippets of code borrowed from go-dockerclient and engine-api for basic HTTP Rest flows;
// minimally used for the Ping operation, but could be used for POST's as well
// if ever useful for debug
func (d *stiDocker) do(method, path string, body io.Reader) (*http.Response, error) {
//TODO - for now, we are forgoing the version check and specific version requests that exist in go-dockerclient;
// moving foward, keep an eye on whether this is a valid decision
uri, err := url.Parse(d.endpoint)
if err != nil {
return nil, err
}
urlStr := strings.TrimRight(uri.String(), "/")
if uri.Scheme == "unix" {
urlStr = ""
}
urlStr = urlStr + path
req, err := http.NewRequest(method, urlStr, body)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", "openshift-s2i")
if method == "POST" {
req.Header.Set("Content-Type", "application/json")
}
var resp *http.Response
if uri.Scheme == "unix" {
dial, err := d.dialer.Dial(uri.Scheme, uri.Path)
if err != nil {
return nil, err
}
defer dial.Close()
breader := bufio.NewReader(dial)
err = req.Write(dial)
if err != nil {
return nil, err
}
if resp, err = http.ReadResponse(breader, req); err != nil {
return nil, err
}
} else {
if resp, err = d.httpClient.Do(req); err != nil {
return nil, err
}
}
if method == "GET" {
defer resp.Body.Close()
}
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
return nil, fmt.Errorf("http response code %d", resp.StatusCode)
}
return resp, nil
}
// UploadToContainer uploads artifacts to the container.
func (d *stiDocker) UploadToContainer(src, dest, container string) error {
makeFileWorldWritable := func(path string, info os.FileInfo, err error) error {
@@ -358,48 +449,57 @@ func (d *stiDocker) UploadToContainerWithCallback(src, dest, container string, w
}()
}
glog.V(3).Infof("Uploading %q to %q ...", src, path)
opts := docker.UploadToContainerOptions{Path: path, InputStream: r}
return d.client.UploadToContainer(container, opts)
ctx, cancel := getDefaultContext(DefaultDockerTimeout)
defer cancel()
return d.client.CopyToContainer(ctx, container, path, r, dockertypes.CopyToContainerOptions{})
}
// DownloadFromContainer downloads file (or directory) from the container.
func (d *stiDocker) DownloadFromContainer(containerPath string, w io.Writer, container string) error {
opts := docker.DownloadFromContainerOptions{Path: containerPath, OutputStream: w}
return d.client.DownloadFromContainer(container, opts)
ctx, cancel := getDefaultContext(DefaultDockerTimeout)
defer cancel()
readCloser, _, err := d.client.CopyFromContainer(ctx, container, containerPath)
if err != nil {
return err
}
defer readCloser.Close()
_, err = io.Copy(w, readCloser)
return err
}
// IsImageInLocalRegistry determines whether the supplied image is in the local registry.
func (d *stiDocker) IsImageInLocalRegistry(name string) (bool, error) {
name = getImageName(name)
image, err := d.client.InspectImage(name)
if image != nil {
resp, err := d.kubeDockerClient.InspectImage(name)
if resp != nil {
return true, nil
} else if err == docker.ErrNoSuchImage {
return false, nil
}
return false, err
if err != nil && !dockerapi.IsErrImageNotFound(err) {
return false, errors.NewInspectImageError(name, err)
}
return false, nil
}
// GetImageUser finds and retrieves the user associated with
// an image if one has been specified
func (d *stiDocker) GetImageUser(name string) (string, error) {
name = getImageName(name)
image, err := d.client.InspectImage(name)
resp, err := d.kubeDockerClient.InspectImage(name)
if err != nil {
glog.V(4).Infof("error inspecting image %s: %v", name, err)
return "", errors.NewInspectImageError(name, err)
}
user := image.ContainerConfig.User
user := resp.ContainerConfig.User
if len(user) == 0 {
user = image.Config.User
user = resp.Config.User
}
return user, nil
}
// Ping determines if the Docker daemon is reachable
func (d *stiDocker) Ping() error {
return d.client.Ping()
_, err := d.do("GET", "/_ping", nil)
return err
}
// IsImageOnBuild provides information about whether the Docker image has
@@ -413,16 +513,17 @@ func (d *stiDocker) IsImageOnBuild(name string) bool {
// for the given image
func (d *stiDocker) GetOnBuild(name string) ([]string, error) {
name = getImageName(name)
image, err := d.client.InspectImage(name)
resp, err := d.kubeDockerClient.InspectImage(name)
if err != nil {
return nil, err
glog.V(4).Infof("error inspecting image %s: %v", name, err)
return nil, errors.NewInspectImageError(name, err)
}
return image.Config.OnBuild, nil
return resp.Config.OnBuild, nil
}
// CheckAndPullImage pulls an image into the local registry if not present
// and returns the image metadata
func (d *stiDocker) CheckAndPullImage(name string) (*docker.Image, error) {
func (d *stiDocker) CheckAndPullImage(name string) (*api.Image, error) {
name = getImageName(name)
displayName := name
@@ -439,7 +540,7 @@ func (d *stiDocker) CheckAndPullImage(name string) (*docker.Image, error) {
}
image, err := d.CheckImage(name)
if err != nil && err.(errors.Error).Details != docker.ErrNoSuchImage {
if err != nil && !strings.Contains(err.(errors.Error).Details.Error(), "no such image") {
return nil, err
}
if image == nil {
@@ -452,58 +553,81 @@ func (d *stiDocker) CheckAndPullImage(name string) (*docker.Image, error) {
}
// CheckImage checks image from the local registry.
func (d *stiDocker) CheckImage(name string) (*docker.Image, error) {
func (d *stiDocker) CheckImage(name string) (*api.Image, error) {
name = getImageName(name)
image, err := d.client.InspectImage(name)
inspect, err := d.kubeDockerClient.InspectImage(name)
if err != nil {
glog.V(4).Infof("error inspecting image %s: %v", name, err)
return nil, errors.NewInspectImageError(name, err)
}
return image, nil
if inspect != nil {
image := &api.Image{}
updateImageWithInspect(image, inspect)
return image, nil
}
return nil, nil
}
// PullImage pulls an image into the local registry
func (d *stiDocker) PullImage(name string) (*docker.Image, error) {
func (d *stiDocker) PullImage(name string) (*api.Image, error) {
name = getImageName(name)
glog.V(1).Infof("Pulling Docker image %s ...", name)
// TODO: Add authentication support
if err := d.client.PullImage(docker.PullImageOptions{Repository: name}, d.pullAuth); err != nil {
glog.V(3).Infof("An error was received from the PullImage call: %v", err)
err := d.kubeDockerClient.PullImage(name, d.pullAuth, dockertypes.ImagePullOptions{})
if err != nil {
return nil, errors.NewPullImageError(name, err)
}
image, err := d.client.InspectImage(name)
resp, err := d.kubeDockerClient.InspectImage(name)
if err != nil {
glog.V(4).Infof("error inspecting image %s: %v", name, err)
return nil, errors.NewInspectImageError(name, err)
return nil, errors.NewPullImageError(name, err)
}
if resp != nil {
image := &api.Image{}
updateImageWithInspect(image, resp)
return image, nil
}
return nil, nil
}
func updateImageWithInspect(image *api.Image, inspect *dockertypes.ImageInspect) {
image.ID = inspect.ID
if inspect.Config != nil {
image.Config = &api.ContainerConfig{
Labels: inspect.Config.Labels,
Env: inspect.Config.Env,
}
}
if inspect.ContainerConfig != nil {
image.ContainerConfig = &api.ContainerConfig{
Labels: inspect.ContainerConfig.Labels,
Env: inspect.ContainerConfig.Env,
}
}
return image, nil
}
// RemoveContainer removes a container and its associated volumes.
func (d *stiDocker) RemoveContainer(id string) error {
opts := docker.RemoveContainerOptions{
ID: id,
opts := dockertypes.ContainerRemoveOptions{
RemoveVolumes: true,
Force: true,
}
return d.client.RemoveContainer(opts)
return d.kubeDockerClient.RemoveContainer(id, opts)
}
// GetLabels retrieves the labels of the given image.
func (d *stiDocker) GetLabels(name string) (map[string]string, error) {
name = getImageName(name)
image, err := d.client.InspectImage(name)
resp, err := d.kubeDockerClient.InspectImage(name)
if err != nil {
glog.V(4).Infof("error inspecting image %s: %v", name, err)
return nil, errors.NewInspectImageError(name, err)
}
return image.Config.Labels, nil
return resp.Config.Labels, nil
}
// getImageName checks the image name and adds DefaultTag if none is specified
func getImageName(name string) string {
_, tag := docker.ParseRepositoryTag(name)
_, tag, _ := parseRepositoryTag(name)
if len(tag) == 0 {
return strings.Join([]string{name, DefaultTag}, ":")
}
@@ -512,19 +636,18 @@ func getImageName(name string) string {
}
// getLabel gets label's value from the image metadata
func getLabel(image *docker.Image, name string) string {
func getLabel(image *api.Image, name string) string {
if value, ok := image.Config.Labels[name]; ok {
return value
}
if value, ok := image.ContainerConfig.Labels[name]; ok {
return value
}
return ""
}
// getVariable gets environment variable's value from the image metadata
func getVariable(image *docker.Image, name string) string {
func getVariable(image *api.Image, name string) string {
envName := name + "="
env := append(image.ContainerConfig.Env, image.Config.Env...)
for _, v := range env {
@@ -563,7 +686,10 @@ func (d *stiDocker) GetAssembleInputFiles(image string) (string, error) {
}
// getScriptsURL finds a scripts url label in the image metadata
func getScriptsURL(image *docker.Image) string {
func getScriptsURL(image *api.Image) string {
if image == nil {
return ""
}
scriptsURL := getLabel(image, ScriptsURLLabel)
// For backward compatibility, support the old label schema
@@ -591,7 +717,7 @@ func getScriptsURL(image *docker.Image) string {
}
// getDestination finds a destination label in the image metadata
func getDestination(image *docker.Image) string {
func getDestination(image *api.Image) string {
if val := getLabel(image, DestinationLabel); len(val) != 0 {
return val
}
@@ -611,7 +737,7 @@ func getDestination(image *docker.Image) string {
return DefaultDestination
}
func constructCommand(opts RunContainerOptions, imageMetadata *docker.Image, tarDestination string) []string {
func constructCommand(opts RunContainerOptions, imageMetadata *api.Image, tarDestination string) []string {
// base directory for all S2I commands
commandBaseDir := determineCommandBaseDir(opts, imageMetadata, tarDestination)
@@ -634,14 +760,14 @@ func constructCommand(opts RunContainerOptions, imageMetadata *docker.Image, tar
return []string{binaryToRun}
}
func determineTarDestinationDir(opts RunContainerOptions, imageMetadata *docker.Image) string {
func determineTarDestinationDir(opts RunContainerOptions, imageMetadata *api.Image) string {
if len(opts.Destination) != 0 {
return opts.Destination
}
return getDestination(imageMetadata)
}
func determineCommandBaseDir(opts RunContainerOptions, imageMetadata *docker.Image, tarDestination string) string {
func determineCommandBaseDir(opts RunContainerOptions, imageMetadata *api.Image, tarDestination string) string {
if opts.ExternalScripts {
// for external scripts we must always append 'scripts' because this is
// the default subdirectory inside tar for them
@@ -665,24 +791,78 @@ func determineCommandBaseDir(opts RunContainerOptions, imageMetadata *docker.Ima
}
// dumpContainerInfo dumps information about a running container (port/IP/etc).
func dumpContainerInfo(container *docker.Container, d *stiDocker, image string) {
cont, icerr := d.client.InspectContainer(container.ID)
liveports := "\n\nPort Bindings: "
if icerr == nil {
// Ports is of the following type: map[docker.Port][]docker.PortBinding
for port, bindings := range cont.NetworkSettings.Ports {
liveports = liveports + "\n Container Port: " + port.Port()
liveports = liveports + "\n Protocol: " + port.Proto()
liveports = liveports + "\n Public Host / Port Mappings:"
for _, binding := range bindings {
liveports = liveports + "\n IP: " + binding.HostIP + " Port: " + binding.HostPort
}
}
liveports = liveports + "\n"
func dumpContainerInfo(container *dockertypes.ContainerCreateResponse, d *stiDocker, image string) {
containerJSON, err := d.kubeDockerClient.InspectContainer(container.ID)
if err != nil {
return
}
liveports := "\n\nPort Bindings: "
for port, bindings := range containerJSON.NetworkSettings.NetworkSettingsBase.Ports {
liveports = liveports + "\n Container Port: " + string(port)
liveports = liveports + "\n Public Host / Port Mappings:"
for _, binding := range bindings {
liveports = liveports + "\n IP: " + binding.HostIP + " Port: " + binding.HostPort
}
}
liveports = liveports + "\n"
glog.V(0).Infof("\n\n\n\n\nThe image %s has been started in container %s as a result of the --run=true option. The container's stdout/stderr will be redirected to this command's glog output to help you validate its behavior. You can also inspect the container with docker commands if you like. If the container is set up to stay running, you will have to Ctrl-C to exit this command, which should also stop the container %s. This particular invocation attempts to run with the port mappings %+v \n\n\n\n\n", image, container.ID, container.ID, liveports)
}
// begin block copy of two methods from kube_docker_client.go
// ... essentially, if that code could give us a way to inspect the error from engin-api's ContainerAttach call prior
// to blocking on the IO, we could just leverage the kube_docker_client.go code entirely ... essentially a "non-blocking" attach like go-dockerclient provided
// redirectResponseToOutputStream redirect the response stream to stdout and stderr. When tty is true, all stream will
// only be redirected to stdout.
func (d *stiDocker) redirectResponseToOutputStream(tty bool, outputStream, errorStream io.Writer, resp io.Reader) error {
if outputStream == nil {
outputStream = ioutil.Discard
}
if errorStream == nil {
errorStream = ioutil.Discard
}
var err error
if tty {
_, err = io.Copy(outputStream, resp)
} else {
_, err = dockerstdcopy.StdCopy(outputStream, errorStream, resp)
}
return err
}
// holdHijackedConnection hold the HijackedResponse, redirect the inputStream to the connection, and redirect the response
// stream to stdout and stderr. NOTE: If needed, we could also add context in this function.
func (d *stiDocker) holdHijackedConnection(tty bool, inputStream io.Reader, outputStream, errorStream io.Writer, resp dockertypes.HijackedResponse) error {
receiveStdout := make(chan error)
if outputStream != nil || errorStream != nil {
go func() {
receiveStdout <- d.redirectResponseToOutputStream(tty, outputStream, errorStream, resp.Reader)
}()
}
stdinDone := make(chan struct{})
go func() {
if inputStream != nil {
io.Copy(resp.Conn, inputStream)
}
resp.CloseWrite()
close(stdinDone)
}()
select {
case err := <-receiveStdout:
return err
case <-stdinDone:
if outputStream != nil || errorStream != nil {
return <-receiveStdout
}
}
return nil
}
// end block copy of two methods from kube_docker_client.go
// RunContainer creates and starts a container using the image specified in opts
// with the ability to stream input and/or output.
func (d *stiDocker) RunContainer(opts RunContainerOptions) error {
@@ -690,14 +870,13 @@ func (d *stiDocker) RunContainer(opts RunContainerOptions) error {
// get info about the specified image
image := createOpts.Config.Image
var (
imageMetadata *docker.Image
err error
)
if opts.PullImage {
imageMetadata, err = d.CheckAndPullImage(image)
} else {
imageMetadata, err = d.client.InspectImage(image)
inspect, err := d.kubeDockerClient.InspectImage(image)
imageMetadata := &api.Image{}
if err == nil {
updateImageWithInspect(imageMetadata, inspect)
if opts.PullImage {
_, err = d.CheckAndPullImage(image)
}
}
// if the original image has no entrypoint, and the run invocation
@@ -739,10 +918,13 @@ func (d *stiDocker) RunContainer(opts RunContainerOptions) error {
// Create a new container.
glog.V(2).Infof("Creating container with options {Name:%q Config:%+v HostConfig:%+v} ...", createOpts.Name, createOpts.Config, createOpts.HostConfig)
var container *docker.Container
var container *dockertypes.ContainerCreateResponse
if err = util.TimeoutAfter(DefaultDockerTimeout, "timeout after waiting %v for Docker to create container", func() error {
var createErr error
container, createErr = d.client.CreateContainer(createOpts)
if createOpts.HostConfig != nil && createOpts.HostConfig.ShmSize <= 0 {
createOpts.HostConfig.ShmSize = DefaultShmSize
}
container, createErr = d.kubeDockerClient.CreateContainer(createOpts)
return createErr
}); err != nil {
return err
@@ -768,19 +950,53 @@ func (d *stiDocker) RunContainer(opts RunContainerOptions) error {
os.Exit(2)
}
return interrupt.New(dumpStack, removeContainer).Run(func() error {
// Attach to the container.
// Attach to the container on go thread (different than with go-dockerclient, since it provided a non-blocking attach which we don't seem to have with k8s/engine-api)
// Attach to the container on go thread to mimic blocking behavior we had with go-dockerclient (k8s wrapper blocks); then use borrowed code
// from k8s to dump logs via return
// still preserve the flow of attaching before starting to handle various timing issues encountered in the past, as well as allow for --run option
glog.V(2).Infof("Attaching to container %q ...", containerName)
attachOpts := opts.asDockerAttachToContainerOptions()
attachOpts.Container = container.ID
if _, err = d.client.AttachToContainerNonBlocking(attachOpts); err != nil {
glog.V(0).Infof("error: Unable to attach to container %q with options %+v: %v", containerName, attachOpts, err)
return err
errorChannel := make(chan error)
timeoutTimer := time.NewTimer(DefaultDockerTimeout)
var attachLoggingError error
// unit tests found a DATA RACE on attachLoggingError; a simple mutex sufficed for proper coordination; note, the holdHijacedConnection will exit
// if the container wait exits
var mutex sync.Mutex
go func() {
ctx, cancel := getDefaultContext(DefaultDockerTimeout)
defer cancel()
resp, err := d.client.ContainerAttach(ctx, container.ID, opts.asDockerAttachToContainerOptions())
errorChannel <- err
if err != nil {
glog.V(0).Infof("error: Unable to attach to container %q: %v", containerName, err)
return
}
defer resp.Close()
sopts := opts.asDockerAttachToStreamOptions()
mutex.Lock()
defer mutex.Unlock()
attachLoggingError = d.holdHijackedConnection(sopts.RawTerminal, sopts.InputStream, sopts.OutputStream, sopts.ErrorStream, resp)
}()
// this error check should handle the result from the d.client.ContainerAttach call ... we progress to start when that occurs
select {
case err := <-errorChannel:
// in non-error scenarios, temporary tracing confirmed that
// unless the container starts, then exits, the attach blocks and
// never returns either a nil for success or whatever err it might
// return if the attach failed
if err != nil {
return err
}
break
case <-timeoutTimer.C:
return fmt.Errorf("timed out waiting to attach to container %s ", containerName)
}
// Start the container.
// Start the container
glog.V(2).Infof("Starting container %q ...", containerName)
if err := util.TimeoutAfter(DefaultDockerTimeout, "timeout after waiting %v for Docker to start container", func() error {
return d.client.StartContainer(container.ID, nil)
return d.kubeDockerClient.StartContainer(container.ID)
}); err != nil {
return err
}
@@ -800,15 +1016,18 @@ func (d *stiDocker) RunContainer(opts RunContainerOptions) error {
// the container exits normally). dump port/etc information for the user.
dumpContainerInfo(container, d, image)
}
// Return an error if the exit code of the container is
// non-zero.
glog.V(4).Infof("Waiting for container %q to stop ...", containerName)
exitCode, err := d.client.WaitContainer(container.ID)
ctx, cancel := getDefaultContext(math.MaxInt64 * time.Nanosecond) // infinite duration ... go does not expose max duration constant
defer cancel()
exitCode, err := d.client.ContainerWait(ctx, container.ID)
if err != nil {
return fmt.Errorf("waiting for container %q to stop: %v", containerName, err)
}
if exitCode != 0 {
return errors.NewContainerError(container.Name, exitCode, "")
return errors.NewContainerError(container.ID, exitCode, "")
}
// FIXME: If Stdout or Stderr can be closed, close it to notify that
@@ -837,24 +1056,21 @@ func (d *stiDocker) RunContainer(opts RunContainerOptions) error {
}
}
return nil
mutex.Lock()
defer mutex.Unlock()
return attachLoggingError
})
}
// containerName returns the name of a container or its ID if the name is empty.
// Useful for identifying a container in logs.
func containerNameOrID(c *docker.Container) string {
if c.Name != "" {
return c.Name
}
func containerNameOrID(c *dockertypes.ContainerCreateResponse) string {
return c.ID
}
// GetImageID retrieves the ID of the image identified by name
func (d *stiDocker) GetImageID(name string) (string, error) {
name = getImageName(name)
image, err := d.client.InspectImage(name)
image, err := d.kubeDockerClient.InspectImage(name)
if err != nil {
return "", err
}
@@ -864,54 +1080,62 @@ func (d *stiDocker) GetImageID(name string) (string, error) {
// CommitContainer commits a container to an image with a specific tag.
// The new image ID is returned
func (d *stiDocker) CommitContainer(opts CommitContainerOptions) (string, error) {
repository, tag := docker.ParseRepositoryTag(opts.Repository)
dockerOpts := docker.CommitContainerOptions{
Container: opts.ContainerID,
Repository: repository,
Tag: tag,
ctx, cancel := getDefaultContext(DefaultDockerTimeout)
defer cancel()
dockerOpts := dockertypes.ContainerCommitOptions{
Reference: opts.Repository,
}
if opts.Command != nil || opts.Entrypoint != nil {
config := docker.Config{
config := dockercontainer.Config{
Cmd: opts.Command,
Entrypoint: opts.Entrypoint,
Env: opts.Env,
Labels: opts.Labels,
User: opts.User,
}
dockerOpts.Run = &config
dockerOpts.Config = &config
glog.V(2).Infof("Committing container with dockerOpts: %+v, config: %+v", dockerOpts, config)
}
image, err := d.client.CommitContainer(dockerOpts)
if err == nil && image != nil {
return image.ID, nil
resp, err := d.client.ContainerCommit(ctx, opts.ContainerID, dockerOpts)
if err == nil {
return resp.ID, nil
}
return "", err
}
// RemoveImage removes the image with specified ID
func (d *stiDocker) RemoveImage(imageID string) error {
return d.client.RemoveImage(imageID)
_, err := d.kubeDockerClient.RemoveImage(imageID, dockertypes.ImageRemoveOptions{})
return err
}
// BuildImage builds the image according to specified options
func (d *stiDocker) BuildImage(opts BuildImageOptions) error {
dockerOpts := docker.BuildImageOptions{
Name: opts.Name,
NoCache: true,
SuppressOutput: false,
RmTmpContainer: true,
ForceRmTmpContainer: true,
InputStream: opts.Stdin,
OutputStream: opts.Stdout,
dockerOpts := dockertypes.ImageBuildOptions{
Tags: []string{opts.Name},
NoCache: true,
SuppressOutput: false,
Remove: true,
ForceRemove: true,
}
if opts.CGroupLimits != nil {
dockerOpts.Memory = opts.CGroupLimits.MemoryLimitBytes
dockerOpts.Memswap = opts.CGroupLimits.MemorySwap
dockerOpts.MemorySwap = opts.CGroupLimits.MemorySwap
dockerOpts.CPUShares = opts.CGroupLimits.CPUShares
dockerOpts.CPUPeriod = opts.CGroupLimits.CPUPeriod
dockerOpts.CPUQuota = opts.CGroupLimits.CPUQuota
}
glog.V(2).Infof("Building container using config: %+v", dockerOpts)
return d.client.BuildImage(dockerOpts)
ctx, cancel := getDefaultContext(((1<<63 - 1) * time.Nanosecond)) // infinite duration ... go does not expost max duration constant
defer cancel()
resp, err := d.client.ImageBuild(ctx, opts.Stdin, dockerOpts)
if err != nil {
return err
}
defer resp.Body.Close()
// since can't pass in output stream to engine-api, need to copy contents of
// the output stream they create into our output stream
_, err = io.Copy(opts.Stdout, resp.Body)
return err
}

View File

@@ -1,17 +1,23 @@
package docker
import (
"bytes"
"fmt"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/openshift/source-to-image/pkg/api"
"github.com/openshift/source-to-image/pkg/docker/test"
"github.com/openshift/source-to-image/pkg/errors"
docker "github.com/fsouza/go-dockerclient"
dockertypes "github.com/docker/engine-api/types"
dockercontainer "github.com/docker/engine-api/types/container"
dockerstrslice "github.com/docker/engine-api/types/strslice"
"k8s.io/kubernetes/pkg/kubelet/dockertools"
)
func TestContainerName(t *testing.T) {
@@ -24,119 +30,127 @@ func TestContainerName(t *testing.T) {
}
func getDocker(client Client) *stiDocker {
//k8s has its own fake docker client mechanism
k8sDocker := dockertools.ConnectToDockerOrDie("fake://")
return &stiDocker{
client: client,
pullAuth: docker.AuthConfiguration{},
kubeDockerClient: k8sDocker,
client: client,
pullAuth: dockertypes.AuthConfig{},
}
}
//NOTE, neither k8s kube client nor engine api make their error types public, so we cannot access / instantiate them like we did for the analogous
// ones in go-dockerclient.
// Hence, we test with errors created in these tests and set on the fake client vs. those actually defined in k8s/engine-api.
// Also, the fake client does not save the parameters passed in; but instead, you can confirm that it was called via AssertCalls
func TestIsImageInLocalRegistry(t *testing.T) {
type testDef struct {
imageName string
docker test.FakeDockerClient
expectedImageName string
expectedResult bool
expectedError error
imageName string
docker test.FakeDockerClient
expectedResult bool
expectedError string
}
otherError := fmt.Errorf("Other")
tests := map[string]testDef{
"ImageFound": {"a_test_image", test.FakeDockerClient{Image: &docker.Image{}}, "a_test_image:latest", true, nil},
"ImageNotFound": {"a_test_image:sometag", test.FakeDockerClient{InspectImageErr: []error{docker.ErrNoSuchImage}}, "a_test_image:sometag", false, nil},
"ErrorOccurred": {"a_test_image", test.FakeDockerClient{InspectImageErr: []error{otherError}}, "a_test_image:latest", false, otherError},
"ImageFound": {"a_test_image", test.FakeDockerClient{}, true, ""},
"ImageNotFound": {"a_test_image:sometag", test.FakeDockerClient{}, false, "unable to get metadata for a_test_image:sometag"},
}
for test, def := range tests {
dh := getDocker(&def.docker)
fake := dh.kubeDockerClient.(*dockertools.FakeDockerClient)
if def.expectedResult {
fake.Image = &dockertypes.ImageInspect{ID: def.imageName}
}
result, err := dh.IsImageInLocalRegistry(def.imageName)
if e := fake.AssertCalls([]string{"inspect_image"}); e != nil {
t.Errorf("%+v", e)
}
if result != def.expectedResult {
t.Errorf("Test - %s: Expected result: %v. Got: %v", test, def.expectedResult, result)
}
if err != def.expectedError {
t.Errorf("Test - %s: Expected error: %v. Got: %v", test, def.expectedError, err)
}
if def.docker.InspectImageName[0] != def.expectedImageName {
t.Errorf("Docker inspect called with unexpected image name: %s",
def.docker.InspectImageName)
if err != nil && len(def.expectedError) > 0 && !strings.Contains(err.Error(), def.expectedError) {
t.Errorf("Test - %s: Expected error: Got: %+v", test, err)
}
}
}
func TestCheckAndPullImage(t *testing.T) {
// in addition to the deltas mentioned up top, kube fake client can't distinguish between exists and successful pull, and
// pull options with engine-api don't reference repo name like go-dockerclient did ... removed tests related to that
type testDef struct {
imageName string
docker test.FakeDockerClient
expectedImage *docker.Image
expectedError int
expectedPullOptions docker.PullImageOptions
imageName string
docker test.FakeDockerClient
calls []string
errorStage string
expectedError string
maskedError string
}
image := &docker.Image{}
imageExistsTest := testDef{
imageName: "test_image",
docker: test.FakeDockerClient{
InspectImageErr: []error{nil},
InspectImageResult: []*docker.Image{image},
},
expectedImage: image,
docker: test.FakeDockerClient{},
calls: []string{"inspect_image"},
}
imagePulledTest := testDef{
imageName: "test_image",
docker: test.FakeDockerClient{
InspectImageErr: []error{docker.ErrNoSuchImage, nil},
InspectImageResult: []*docker.Image{nil, image},
},
expectedImage: image,
expectedPullOptions: docker.PullImageOptions{Repository: "test_image:" + DefaultTag},
imageDoesNotExistsTest := testDef{
imageName: "test_image",
docker: test.FakeDockerClient{},
calls: []string{"inspect_image", "pull", "inspect_image"},
errorStage: "inspect_image",
maskedError: "no such image",
}
inspectErrorTest := testDef{
imageName: "test_image",
docker: test.FakeDockerClient{
InspectImageErr: []error{docker.ErrConnectionRefused},
InspectImageResult: []*docker.Image{nil},
},
expectedImage: nil,
expectedError: errors.InspectImageError,
imageName: "test_image",
docker: test.FakeDockerClient{},
calls: []string{"inspect_image"},
errorStage: "inspect_image",
expectedError: "unable to get metadata for test_image:latest",
}
pullErrorTest := testDef{
imageName: "test_image",
docker: test.FakeDockerClient{
PullImageErr: docker.ErrConnectionRefused,
InspectImageErr: []error{nil},
InspectImageResult: []*docker.Image{nil},
},
expectedImage: nil,
expectedError: errors.PullImageError,
expectedPullOptions: docker.PullImageOptions{Repository: "test_image:" + DefaultTag},
}
errorAfterPullTest := testDef{
imageName: "test_image:testtag",
docker: test.FakeDockerClient{
InspectImageErr: []error{docker.ErrNoSuchImage, docker.ErrNoSuchImage},
InspectImageResult: []*docker.Image{nil, image},
},
expectedImage: nil,
expectedError: errors.InspectImageError,
expectedPullOptions: docker.PullImageOptions{Repository: "test_image:testtag"},
imageName: "test_image",
docker: test.FakeDockerClient{},
calls: []string{"inspect_image", "pull"},
errorStage: "pull",
expectedError: "unable to get test_image:latest",
}
tests := map[string]testDef{
"ImageExists": imageExistsTest,
"ImagePulled": imagePulledTest,
"InspectError": inspectErrorTest,
"PullError": pullErrorTest,
"ErrorAfterPull": errorAfterPullTest,
"ImageExists": imageExistsTest,
"ImageDoesNotExist": imageDoesNotExistsTest,
"InspectError": inspectErrorTest,
"PullError": pullErrorTest,
}
for test, def := range tests {
dh := getDocker(&def.docker)
resultImage, resultErr := dh.CheckAndPullImage(def.imageName)
if resultImage != def.expectedImage {
t.Errorf("%s: Unexpected image result -- %v", test, resultImage)
fake := dh.kubeDockerClient.(*dockertools.FakeDockerClient)
if len(def.maskedError) > 0 {
fake.ClearErrors()
fake.InjectError(def.errorStage, fmt.Errorf(def.maskedError))
fake.Image = nil
} else if len(def.expectedError) > 0 {
fake.ClearErrors()
fake.InjectError(def.errorStage, fmt.Errorf(def.expectedError))
fake.Image = nil
} else {
fake.Image = &dockertypes.ImageInspect{ID: def.imageName}
}
if e, ok := resultErr.(errors.Error); def.expectedError != 0 && (!ok || e.ErrorCode != def.expectedError) {
resultImage, resultErr := dh.CheckAndPullImage(def.imageName)
if e := fake.AssertCalls(def.calls); e != nil {
t.Errorf("%s %+v", test, e)
}
if len(def.maskedError) > 0 && resultErr != nil {
t.Errorf("%s: Unexpected error -- %v", test, resultErr)
}
if len(def.expectedError) > 0 && (resultErr == nil || resultErr.Error() != def.expectedError) {
t.Errorf("%s: Unexpected error result -- %v", test, resultErr)
}
pullOpts := def.docker.PullImageOpts
if !reflect.DeepEqual(pullOpts, def.expectedPullOptions) {
t.Errorf("%s: Unexpected pull image opts -- %v", test, pullOpts)
if fake.Image != nil && (resultImage == nil || resultImage.ID != def.imageName) {
t.Errorf("%s: Unexpected image result -- %+v instead of %+v", test, resultImage, fake.Image)
}
}
}
@@ -144,81 +158,235 @@ func TestCheckAndPullImage(t *testing.T) {
func TestRemoveContainer(t *testing.T) {
fakeDocker := &test.FakeDockerClient{}
dh := getDocker(fakeDocker)
fake := dh.kubeDockerClient.(*dockertools.FakeDockerClient)
containerID := "testContainerId"
expectedOpts := docker.RemoveContainerOptions{
ID: containerID,
RemoveVolumes: true,
Force: true,
fakeContainer := dockertools.FakeContainer{ID: containerID}
fake.SetFakeContainers([]*dockertools.FakeContainer{&fakeContainer})
err := dh.RemoveContainer(containerID)
if err != nil {
t.Errorf("%+v", err)
}
dh.RemoveContainer(containerID)
if !reflect.DeepEqual(expectedOpts, fakeDocker.RemoveContainerOpts) {
t.Errorf("Unexpected removeContainerOpts. Expected: %#v, Got: %#v",
expectedOpts, fakeDocker.RemoveContainerOpts)
err = fake.AssertCalls([]string{"remove"})
if err != nil {
t.Errorf("%v+v", err)
}
}
func TestCommitContainer(t *testing.T) {
expectedImageID := "test-1234"
fakeDocker := &test.FakeDockerClient{Image: &docker.Image{ID: expectedImageID}}
dh := getDocker(fakeDocker)
containerID := "test-container-id"
containerTag := "test-container-tag"
opt := CommitContainerOptions{
ContainerID: containerID,
Repository: containerTag,
type commit_test struct {
containerID string
containerTag string
expectedImageID string
expectedError error
}
imageID, err := dh.CommitContainer(opt)
if err != nil {
t.Errorf("Unexpected error returned: %v", err)
tests := map[string]commit_test{
"valid": {
containerID: "test-container-id",
containerTag: "test-container-tag",
expectedImageID: "test-container-tag",
},
"error": {
containerID: "test-container-id",
containerTag: "test-container-tag",
expectedImageID: "test-container-tag",
expectedError: fmt.Errorf("Test error"),
},
}
if imageID != expectedImageID {
t.Errorf("Did not return the correct image id: %s", imageID)
for desc, tst := range tests {
opt := CommitContainerOptions{
ContainerID: tst.containerID,
Repository: tst.containerTag,
}
param := dockertypes.ContainerCommitOptions{
Reference: tst.containerTag,
}
resp := dockertypes.ContainerCommitResponse{
ID: tst.expectedImageID,
}
fakeDocker := &test.FakeDockerClient{
ContainerCommitResponse: resp,
ContainerCommitErr: tst.expectedError,
}
dh := getDocker(fakeDocker)
imageID, err := dh.CommitContainer(opt)
if err != tst.expectedError {
t.Errorf("test case %s: Unexpected error returned: %v", desc, err)
}
if tst.containerID != fakeDocker.ContainerCommitID {
t.Errorf("test case %s: Commit container called with unexpected container id: %s and %+v", desc, tst.containerID, fakeDocker.ContainerCommitID)
}
if !reflect.DeepEqual(param, fakeDocker.ContainerCommitOptions) {
t.Errorf("test case %s: Commit container called with unexpected parameters: %+v and %+v", desc, param, fakeDocker.ContainerCommitOptions)
}
if tst.expectedError == nil && imageID != tst.expectedImageID {
t.Errorf("test case %s: Did not return the correct image id: %s", desc, imageID)
}
}
}
func TestCommitContainerError(t *testing.T) {
expectedErr := fmt.Errorf("Test error")
fakeDocker := &test.FakeDockerClient{CommitContainerErr: expectedErr}
dh := getDocker(fakeDocker)
containerID := "test-container-id"
containerTag := "test-container-tag"
opt := CommitContainerOptions{
ContainerID: containerID,
Repository: containerTag,
func TestCopyToContainer(t *testing.T) {
type copyto_test struct {
containerID string
src string
destPath string
}
_, err := dh.CommitContainer(opt)
tests := map[string]copyto_test{
"valid": {
containerID: "test-container-id",
src: "foo",
},
"error": {
containerID: "test-container-id",
},
}
expectedOpts := docker.CommitContainerOptions{
Container: containerID,
Repository: containerTag,
for desc, tst := range tests {
var tempDir, fileName string
var err error
var file *os.File
if len(tst.src) > 0 {
tempDir, err = ioutil.TempDir("", tst.src)
defer os.RemoveAll(tempDir)
fileName = filepath.Join(tempDir, "bar")
if err = os.MkdirAll(filepath.Dir(fileName), 0700); err == nil {
file, err = os.Create(fileName)
if err == nil {
defer file.Close()
file.WriteString("asdf")
}
}
}
if err != nil {
t.Fatalf("Error creating src test files: %v", err)
}
fakeDocker := &test.FakeDockerClient{
CopyToContainerContent: file,
}
dh := getDocker(fakeDocker)
err = dh.UploadToContainer(fileName, fileName, tst.containerID)
// the error we are inducing will prevent call into engine-api
if len(tst.src) > 0 {
if err != nil {
t.Errorf("test case %s: Unexpected error returned: %v", desc, err)
}
if tst.containerID != fakeDocker.CopyToContainerID {
t.Errorf("test case %s: copy to container called with unexpected id: %s and %s", desc, tst.containerID, fakeDocker.CopyToContainerID)
}
} else {
if err == nil {
t.Errorf("test case %s: Unexpected error returned: %v", desc, err)
}
if len(fakeDocker.CopyToContainerID) > 0 {
t.Errorf("test case %s: copy to container called with unexpected id: %s and %s", desc, tst.containerID, fakeDocker.CopyToContainerID)
}
}
// the directory of our file gets passed down to the engine-api method
if tempDir != fakeDocker.CopyToContainerPath {
t.Errorf("test case %s: copy to container called with unexpected path: %s and %s", desc, tempDir, fakeDocker.CopyToContainerPath)
}
// reflect.DeepEqual does not help here cause the reader is transformed prior to calling the engine-api stack, so just make sure it is no nil
if file != nil && fakeDocker.CopyToContainerContent == nil {
t.Errorf("test case %s: copy to container content was not passed through", desc)
}
}
if !reflect.DeepEqual(expectedOpts, fakeDocker.CommitContainerOpts) {
t.Errorf("Commit container called with unexpected parameters: %#v", fakeDocker.CommitContainerOpts)
}
func TestCopyFromContainer(t *testing.T) {
type copyfrom_test struct {
containerID string
srcPath string
expectedError error
}
if err != expectedErr {
t.Errorf("Unexpected error returned: %v", err)
tests := map[string]copyfrom_test{
"valid": {
containerID: "test-container-id",
srcPath: "/foo/bar",
},
"error": {
containerID: "test-container-id",
srcPath: "/foo/bar",
expectedError: fmt.Errorf("Test error"),
},
}
for desc, tst := range tests {
buffer := bytes.NewBuffer([]byte(""))
fakeDocker := &test.FakeDockerClient{
CopyFromContainerErr: tst.expectedError,
}
dh := getDocker(fakeDocker)
err := dh.DownloadFromContainer(tst.srcPath, buffer, tst.containerID)
if err != tst.expectedError {
t.Errorf("test case %s: Unexpected error returned: %v", desc, err)
}
if fakeDocker.CopyFromContainerID != tst.containerID {
t.Errorf("test case %s: Unexpected container id: %s and %s", desc, tst.containerID, fakeDocker.CopyFromContainerID)
}
if fakeDocker.CopyFromContainerPath != tst.srcPath {
t.Errorf("test case %s: Unexpected container id: %s and %s", desc, tst.srcPath, fakeDocker.CopyFromContainerPath)
}
}
}
func TestImageBuild(t *testing.T) {
type wait_test struct {
imageID string
expectedError error
}
tests := map[string]wait_test{
"valid": {
imageID: "test-container-id",
},
"error": {
imageID: "test-container-id",
expectedError: fmt.Errorf("Test error"),
},
}
for desc, tst := range tests {
fakeDocker := &test.FakeDockerClient{
BuildImageErr: tst.expectedError,
}
dh := getDocker(fakeDocker)
opts := BuildImageOptions{
Name: tst.imageID,
}
err := dh.BuildImage(opts)
if err != tst.expectedError {
t.Errorf("test case %s: Unexpected error returned: %v", desc, err)
}
if len(fakeDocker.BuildImageOpts.Tags) != 1 || fakeDocker.BuildImageOpts.Tags[0] != tst.imageID {
t.Errorf("test case %s: Unexpected container id: %s and %+v", desc, tst.imageID, fakeDocker.BuildImageOpts.Tags)
}
}
}
func TestGetScriptsURL(t *testing.T) {
type urltest struct {
image docker.Image
image dockertypes.ImageInspect
result string
calls []string
inspectErr error
errExpected bool
}
tests := map[string]urltest{
"not present": {
image: docker.Image{
ContainerConfig: docker.Config{
calls: []string{"inspect_image"},
image: dockertypes.ImageInspect{
ContainerConfig: &dockercontainer.Config{
Env: []string{"Env1=value1"},
Labels: map[string]string{},
},
Config: &docker.Config{
Config: &dockercontainer.Config{
Env: []string{"Env2=value2"},
Labels: map[string]string{},
},
@@ -227,19 +395,21 @@ func TestGetScriptsURL(t *testing.T) {
},
"env in containerConfig": {
image: docker.Image{
ContainerConfig: docker.Config{
calls: []string{"inspect_image"},
image: dockertypes.ImageInspect{
ContainerConfig: &dockercontainer.Config{
Env: []string{"Env1=value1", ScriptsURLEnvironment + "=test_url_value"},
},
Config: &docker.Config{},
Config: &dockercontainer.Config{},
},
result: "test_url_value",
},
"env in image config": {
image: docker.Image{
ContainerConfig: docker.Config{},
Config: &docker.Config{
calls: []string{"inspect_image"},
image: dockertypes.ImageInspect{
ContainerConfig: &dockercontainer.Config{},
Config: &dockercontainer.Config{
Env: []string{
"Env1=value1",
ScriptsURLEnvironment + "=test_url_value_2",
@@ -251,19 +421,21 @@ func TestGetScriptsURL(t *testing.T) {
},
"label in containerConfig": {
image: docker.Image{
ContainerConfig: docker.Config{
calls: []string{"inspect_image"},
image: dockertypes.ImageInspect{
ContainerConfig: &dockercontainer.Config{
Labels: map[string]string{ScriptsURLLabel: "test_url_value"},
},
Config: &docker.Config{},
Config: &dockercontainer.Config{},
},
result: "test_url_value",
},
"label in image config": {
image: docker.Image{
ContainerConfig: docker.Config{},
Config: &docker.Config{
calls: []string{"inspect_image"},
image: dockertypes.ImageInspect{
ContainerConfig: &dockercontainer.Config{},
Config: &dockercontainer.Config{
Labels: map[string]string{ScriptsURLLabel: "test_url_value_2"},
},
},
@@ -271,26 +443,33 @@ func TestGetScriptsURL(t *testing.T) {
},
"inspect error": {
image: docker.Image{},
inspectErr: fmt.Errorf("Inspect error"),
errExpected: true,
calls: []string{"inspect_image", "pull"},
image: dockertypes.ImageInspect{},
inspectErr: fmt.Errorf("Inspect error"),
},
}
for desc, tst := range tests {
fakeDocker := &test.FakeDockerClient{
InspectImageResult: []*docker.Image{&tst.image},
}
if tst.inspectErr != nil {
fakeDocker.InspectImageErr = []error{tst.inspectErr}
}
fakeDocker := &test.FakeDockerClient{}
dh := getDocker(fakeDocker)
fake := dh.kubeDockerClient.(*dockertools.FakeDockerClient)
if tst.inspectErr != nil {
fake.ClearErrors()
fake.InjectError("pull", tst.inspectErr)
fake.Image = nil
} else {
fake.Image = &tst.image
}
url, err := dh.GetScriptsURL("test/image")
if err != nil && !tst.errExpected {
if e := fake.AssertCalls(tst.calls); e != nil {
t.Errorf("%s: %+v", desc, e)
}
if err != nil && tst.inspectErr == nil {
t.Errorf("%s: Unexpected error returned: %v", desc, err)
} else if err == nil && tst.errExpected {
t.Errorf("%s: Expected error. Did not get one.", desc)
}
if !tst.errExpected && url != tst.result {
if tst.inspectErr == nil && url != tst.result {
t.Errorf("%s: Unexpected result. Expected: %s Actual: %s",
desc, tst.result, url)
}
@@ -299,7 +478,8 @@ func TestGetScriptsURL(t *testing.T) {
func TestRunContainer(t *testing.T) {
type runtest struct {
image docker.Image
calls []string
image dockertypes.ImageInspect
cmd string
externalScripts bool
paramScriptsURL string
@@ -309,18 +489,20 @@ func TestRunContainer(t *testing.T) {
tests := map[string]runtest{
"default": {
image: docker.Image{
ContainerConfig: docker.Config{},
Config: &docker.Config{},
calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"},
image: dockertypes.ImageInspect{
ContainerConfig: &dockercontainer.Config{},
Config: &dockercontainer.Config{},
},
cmd: api.Assemble,
externalScripts: true,
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /tmp/scripts/%s", api.Assemble)},
},
"paramDestination": {
image: docker.Image{
ContainerConfig: docker.Config{},
Config: &docker.Config{},
calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"},
image: dockertypes.ImageInspect{
ContainerConfig: &dockercontainer.Config{},
Config: &dockercontainer.Config{},
},
cmd: api.Assemble,
externalScripts: true,
@@ -328,9 +510,10 @@ func TestRunContainer(t *testing.T) {
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt/test -xf - && /opt/test/scripts/%s", api.Assemble)},
},
"paramDestination&paramScripts": {
image: docker.Image{
ContainerConfig: docker.Config{},
Config: &docker.Config{},
calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"},
image: dockertypes.ImageInspect{
ContainerConfig: &dockercontainer.Config{},
Config: &dockercontainer.Config{},
},
cmd: api.Assemble,
externalScripts: true,
@@ -339,33 +522,36 @@ func TestRunContainer(t *testing.T) {
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt/test -xf - && /opt/test/scripts/%s", api.Assemble)},
},
"scriptsInsideImageEnvironment": {
image: docker.Image{
ContainerConfig: docker.Config{
calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"},
image: dockertypes.ImageInspect{
ContainerConfig: &dockercontainer.Config{
Env: []string{ScriptsURLEnvironment + "=image:///opt/bin/"},
},
Config: &docker.Config{},
Config: &dockercontainer.Config{},
},
cmd: api.Assemble,
externalScripts: false,
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /opt/bin/%s", api.Assemble)},
},
"scriptsInsideImageLabel": {
image: docker.Image{
ContainerConfig: docker.Config{
calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"},
image: dockertypes.ImageInspect{
ContainerConfig: &dockercontainer.Config{
Labels: map[string]string{ScriptsURLLabel: "image:///opt/bin/"},
},
Config: &docker.Config{},
Config: &dockercontainer.Config{},
},
cmd: api.Assemble,
externalScripts: false,
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /opt/bin/%s", api.Assemble)},
},
"scriptsInsideImageEnvironmentWithParamDestination": {
image: docker.Image{
ContainerConfig: docker.Config{
calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"},
image: dockertypes.ImageInspect{
ContainerConfig: &dockercontainer.Config{
Env: []string{ScriptsURLEnvironment + "=image:///opt/bin"},
},
Config: &docker.Config{},
Config: &dockercontainer.Config{},
},
cmd: api.Assemble,
externalScripts: false,
@@ -373,11 +559,12 @@ func TestRunContainer(t *testing.T) {
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt/sti -xf - && /opt/bin/%s", api.Assemble)},
},
"scriptsInsideImageLabelWithParamDestination": {
image: docker.Image{
ContainerConfig: docker.Config{
calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"},
image: dockertypes.ImageInspect{
ContainerConfig: &dockercontainer.Config{
Labels: map[string]string{ScriptsURLLabel: "image:///opt/bin"},
},
Config: &docker.Config{},
Config: &dockercontainer.Config{},
},
cmd: api.Assemble,
externalScripts: false,
@@ -385,40 +572,44 @@ func TestRunContainer(t *testing.T) {
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt/sti -xf - && /opt/bin/%s", api.Assemble)},
},
"paramDestinationFromImageEnvironment": {
image: docker.Image{
ContainerConfig: docker.Config{
calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"},
image: dockertypes.ImageInspect{
ContainerConfig: &dockercontainer.Config{
Env: []string{LocationEnvironment + "=/opt", ScriptsURLEnvironment + "=http://my.test.url/test?param=one"},
},
Config: &docker.Config{},
Config: &dockercontainer.Config{},
},
cmd: api.Assemble,
externalScripts: true,
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt -xf - && /opt/scripts/%s", api.Assemble)},
},
"paramDestinationFromImageLabel": {
image: docker.Image{
ContainerConfig: docker.Config{
calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"},
image: dockertypes.ImageInspect{
ContainerConfig: &dockercontainer.Config{
Labels: map[string]string{DestinationLabel: "/opt", ScriptsURLLabel: "http://my.test.url/test?param=one"},
},
Config: &docker.Config{},
Config: &dockercontainer.Config{},
},
cmd: api.Assemble,
externalScripts: true,
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt -xf - && /opt/scripts/%s", api.Assemble)},
},
"usageCommand": {
image: docker.Image{
ContainerConfig: docker.Config{},
Config: &docker.Config{},
calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"},
image: dockertypes.ImageInspect{
ContainerConfig: &dockercontainer.Config{},
Config: &dockercontainer.Config{},
},
cmd: api.Usage,
externalScripts: true,
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /tmp/scripts/%s", api.Usage)},
},
"otherCommand": {
image: docker.Image{
ContainerConfig: docker.Config{},
Config: &docker.Config{},
calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"},
image: dockertypes.ImageInspect{
ContainerConfig: &dockercontainer.Config{},
Config: &dockercontainer.Config{},
},
cmd: api.Run,
externalScripts: true,
@@ -427,13 +618,17 @@ func TestRunContainer(t *testing.T) {
}
for desc, tst := range tests {
fakeDocker := &test.FakeDockerClient{
InspectImageResult: []*docker.Image{&tst.image},
Container: &docker.Container{
ID: "12345-test",
},
}
fakeDocker := &test.FakeDockerClient{}
dh := getDocker(fakeDocker)
fake := dh.kubeDockerClient.(*dockertools.FakeDockerClient)
tst.image.ID = "test/image"
fake.Image = &tst.image
if len(fake.ContainerMap) > 0 {
t.Errorf("newly created fake client should have empty container map: %+v", fake.ContainerMap)
}
//NOTE: the combo of the fake k8s client, go 1.6, and using os.Stderr/os.Stdout caused what appeared to be go test crashes
// when we tried to call their closers in RunContainer
err := dh.RunContainer(RunContainerOptions{
Image: "test/image",
PullImage: true,
@@ -443,41 +638,46 @@ func TestRunContainer(t *testing.T) {
Command: tst.cmd,
Env: []string{"Key1=Value1", "Key2=Value2"},
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stdout,
//Stdout: os.Stdout,
//Stderr: os.Stdout,
})
if err != nil {
t.Errorf("%s: Unexpected error: %v", desc, err)
}
// Validate the CreateContainer parameters
createConfig := fakeDocker.CreateContainerOpts.Config
if createConfig.Image != "test/image:latest" {
t.Errorf("%s: Unexpected create config image: %s", desc, createConfig.Image)
}
if !reflect.DeepEqual(createConfig.Cmd, tst.cmdExpected) {
t.Errorf("%s: Unexpected create config command: %#v", desc, createConfig.Cmd)
}
if !reflect.DeepEqual(createConfig.Env, []string{"Key1=Value1", "Key2=Value2"}) {
t.Errorf("%s: Unexpected create config env: %#v", desc, createConfig.Env)
}
if !createConfig.OpenStdin || !createConfig.StdinOnce {
t.Errorf("%s: Unexpected stdin flags for createConfig: OpenStdin - %v"+
" StdinOnce - %v", desc, createConfig.OpenStdin, createConfig.StdinOnce)
// container ID will be random, so don't look up directly ... just get the 1 entry which should be there
if len(fake.ContainerMap) != 1 {
t.Errorf("fake container map should only have 1 entry: %+v", fake.ContainerMap)
}
// Verify that remove container was called
if fakeDocker.RemoveContainerOpts.ID != "12345-test" {
t.Errorf("%s: RemoveContainer was not called with the expected container ID", desc)
for _, container := range fake.ContainerMap {
// Validate the Container parameters
if container.Config == nil {
t.Errorf("%s: container config not set: %+v", desc, container)
}
if container.Config.Image != "test/image:latest" {
t.Errorf("%s: Unexpected create config image: %s", desc, container.Config.Image)
}
if !reflect.DeepEqual(container.Config.Cmd, dockerstrslice.StrSlice(tst.cmdExpected)) {
t.Errorf("%s: Unexpected create config command: %#v instead of %q", desc, container.Config.Cmd, strings.Join(tst.cmdExpected, " "))
}
if !reflect.DeepEqual(container.Config.Env, []string{"Key1=Value1", "Key2=Value2"}) {
t.Errorf("%s: Unexpected create config env: %#v", desc, container.Config.Env)
}
}
}
}
func TestGetImageID(t *testing.T) {
fakeDocker := &test.FakeDockerClient{
InspectImageResult: []*docker.Image{{ID: "test-abcd"}},
}
fakeDocker := &test.FakeDockerClient{}
dh := getDocker(fakeDocker)
fake := dh.kubeDockerClient.(*dockertools.FakeDockerClient)
fake.Image = &dockertypes.ImageInspect{ID: "test-abcd"}
id, err := dh.GetImageID("test/image")
if e := fake.AssertCalls([]string{"inspect_image"}); e != nil {
t.Errorf("%+v", e)
}
if err != nil {
t.Errorf("Unexpected error returned: %v", err)
} else if id != "test-abcd" {
@@ -487,11 +687,15 @@ func TestGetImageID(t *testing.T) {
func TestGetImageIDError(t *testing.T) {
expected := fmt.Errorf("Image Error")
fakeDocker := &test.FakeDockerClient{
InspectImageErr: []error{expected},
}
fakeDocker := &test.FakeDockerClient{}
dh := getDocker(fakeDocker)
fake := dh.kubeDockerClient.(*dockertools.FakeDockerClient)
fake.Image = &dockertypes.ImageInspect{ID: "test-abcd"}
fake.InjectError("inspect_image", expected)
id, err := dh.GetImageID("test/image")
if e := fake.AssertCalls([]string{"inspect_image"}); e != nil {
t.Errorf("%+v", e)
}
if err != expected {
t.Errorf("Unexpected error returned: %v", err)
}
@@ -503,13 +707,12 @@ func TestGetImageIDError(t *testing.T) {
func TestRemoveImage(t *testing.T) {
fakeDocker := &test.FakeDockerClient{}
dh := getDocker(fakeDocker)
err := dh.RemoveImage("test-image-id")
fake := dh.kubeDockerClient.(*dockertools.FakeDockerClient)
fake.Images = []dockertypes.Image{{ID: "test-abcd"}}
err := dh.RemoveImage("test-abcd")
if err != nil {
t.Errorf("Unexpected error removing image: %s", err)
}
if fakeDocker.RemoveImageName != "test-image-id" {
t.Errorf("Unexpected image removed: %s", fakeDocker.RemoveImageName)
}
}
func TestGetImageName(t *testing.T) {

View File

@@ -2,10 +2,9 @@ package docker
import (
"errors"
"github.com/openshift/source-to-image/pkg/api"
"io"
"path/filepath"
dockerclient "github.com/fsouza/go-dockerclient"
)
// FakeDocker provides a fake docker interface
@@ -157,22 +156,22 @@ func (f *FakeDocker) RemoveImage(name string) error {
}
// CheckImage checks image in local registry
func (f *FakeDocker) CheckImage(name string) (*dockerclient.Image, error) {
func (f *FakeDocker) CheckImage(name string) (*api.Image, error) {
return nil, nil
}
// PullImage pulls a fake docker image
func (f *FakeDocker) PullImage(imageName string) (*dockerclient.Image, error) {
func (f *FakeDocker) PullImage(imageName string) (*api.Image, error) {
if f.PullResult {
return &dockerclient.Image{}, nil
return &api.Image{}, nil
}
return nil, f.PullError
}
// CheckAndPullImage pulls a fake docker image
func (f *FakeDocker) CheckAndPullImage(name string) (*dockerclient.Image, error) {
func (f *FakeDocker) CheckAndPullImage(name string) (*api.Image, error) {
if f.PullResult {
return &dockerclient.Image{}, nil
return &api.Image{}, nil
}
return nil, f.PullError
}

View File

@@ -1,146 +1,120 @@
package test
import (
"errors"
"github.com/fsouza/go-dockerclient"
"bytes"
dockertypes "github.com/docker/engine-api/types"
"golang.org/x/net/context"
"io"
"io/ioutil"
"net"
"time"
)
// FakeDockerClient provides a Fake client for Docker testing
type FakeDockerClient struct {
Image *docker.Image
InspectImageResult []*docker.Image
Container *docker.Container
RemoveImageErr error
InspectImageErr []error
PullImageErr error
CreateContainerErr error
AttachToContainerErr error
StartContainerErr error
WaitContainerResult int
WaitContainerErr error
RemoveContainerErr error
CommitContainerErr error
CopyFromContainerErr error
BuildImageErr error
RemoveImageName string
InspectImageName []string
PullImageOpts docker.PullImageOptions
PullImageAuth docker.AuthConfiguration
CreateContainerOpts docker.CreateContainerOptions
AttachToContainerOpts []docker.AttachToContainerOptions
StartContainerID string
StartContainerHostConfig *docker.HostConfig
WaitContainerID string
RemoveContainerOpts docker.RemoveContainerOptions
CommitContainerOpts docker.CommitContainerOptions
CopyFromContainerOpts docker.CopyFromContainerOptions
BuildImageOpts docker.BuildImageOptions
type FakeDockerAddr struct {
}
// RemoveImage removes an image from the fake client
func (d *FakeDockerClient) RemoveImage(name string) error {
d.RemoveImageName = name
return d.RemoveImageErr
func (a FakeDockerAddr) Network() string {
return ""
}
func (a FakeDockerAddr) String() string {
return ""
}
type FakeDockerConn struct {
}
func (c FakeDockerConn) Read(b []byte) (n int, err error) {
return 0, nil
}
func (c FakeDockerConn) Write(b []byte) (n int, err error) {
return 0, nil
}
func (c FakeDockerConn) Close() error {
return nil
}
func (c FakeDockerConn) LocalAddr() net.Addr {
return FakeDockerAddr{}
}
func (c FakeDockerConn) RemoteAddr() net.Addr {
return FakeDockerAddr{}
}
func (c FakeDockerConn) SetDeadline(t time.Time) error {
return nil
}
func (c FakeDockerConn) SetReadDeadline(t time.Time) error {
return nil
}
func (c FakeDockerConn) SetWriteDeadline(t time.Time) error {
return nil
}
// FakeDockerClient provides a Fake client for Docker testing, but for our direct access to the engine-api client;
// we leverage the FakeDockerClient defined in k8s when we leverage the k8s layer
type FakeDockerClient struct {
CopyToContainerID string
CopyToContainerPath string
CopyToContainerContent io.Reader
CopyFromContainerID string
CopyFromContainerPath string
CopyFromContainerErr error
WaitContainerID string
WaitContainerResult int
WaitContainerErr error
ContainerCommitID string
ContainerCommitOptions dockertypes.ContainerCommitOptions
ContainerCommitResponse dockertypes.ContainerCommitResponse
ContainerCommitErr error
BuildImageOpts dockertypes.ImageBuildOptions
BuildImageErr error
}
func (d *FakeDockerClient) Ping() error {
return nil
}
// InspectImage inspects the fake image
func (d *FakeDockerClient) InspectImage(name string) (*docker.Image, error) {
d.InspectImageName = append(d.InspectImageName, name)
i := len(d.InspectImageName) - 1
var img *docker.Image
if i >= len(d.InspectImageResult) {
img = d.Image
} else {
img = d.InspectImageResult[i]
}
var err error
if i >= len(d.InspectImageErr) {
err = nil
} else {
err = d.InspectImageErr[i]
}
return img, err
}
// PullImage pulls the fake image
func (d *FakeDockerClient) PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error {
d.PullImageOpts = opts
d.PullImageAuth = auth
return d.PullImageErr
}
// CreateContainer creates a fake container
func (d *FakeDockerClient) CreateContainer(opts docker.CreateContainerOptions) (*docker.Container, error) {
d.CreateContainerOpts = opts
return d.Container, d.CreateContainerErr
}
// StartContainer starts the fake container
func (d *FakeDockerClient) StartContainer(id string, hostConfig *docker.HostConfig) error {
d.StartContainerID = id
d.StartContainerHostConfig = hostConfig
return d.StartContainerErr
}
func (d *FakeDockerClient) UploadToContainer(id string, opts docker.UploadToContainerOptions) error {
func (d *FakeDockerClient) CopyToContainer(ctx context.Context, container, path string, content io.Reader, opts dockertypes.CopyToContainerOptions) error {
d.CopyToContainerID = container
d.CopyToContainerPath = path
d.CopyToContainerContent = content
return nil
}
// DownloadFromContainer downloads file (or directory) from the container.
func (d *FakeDockerClient) DownloadFromContainer(id string, opts docker.DownloadFromContainerOptions) error {
return errors.New("not implemented")
func (d *FakeDockerClient) CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, dockertypes.ContainerPathStat, error) {
d.CopyFromContainerID = container
d.CopyFromContainerPath = srcPath
return ioutil.NopCloser(bytes.NewReader([]byte(""))), dockertypes.ContainerPathStat{}, d.CopyFromContainerErr
}
// WaitContainer waits for a fake container to finish
func (d *FakeDockerClient) WaitContainer(id string) (int, error) {
d.WaitContainerID = id
func (d *FakeDockerClient) ContainerWait(ctx context.Context, containerID string) (int, error) {
d.WaitContainerID = containerID
return d.WaitContainerResult, d.WaitContainerErr
}
// RemoveContainer removes the fake container
func (d *FakeDockerClient) RemoveContainer(opts docker.RemoveContainerOptions) error {
d.RemoveContainerOpts = opts
return d.RemoveContainerErr
func (d *FakeDockerClient) ContainerCommit(ctx context.Context, container string, options dockertypes.ContainerCommitOptions) (dockertypes.ContainerCommitResponse, error) {
d.ContainerCommitID = container
d.ContainerCommitOptions = options
return d.ContainerCommitResponse, d.ContainerCommitErr
}
// CommitContainer commits the fake container
func (d *FakeDockerClient) CommitContainer(opts docker.CommitContainerOptions) (*docker.Image, error) {
d.CommitContainerOpts = opts
return d.Image, d.CommitContainerErr
func (d *FakeDockerClient) ContainerAttach(ctx context.Context, container string, options dockertypes.ContainerAttachOptions) (dockertypes.HijackedResponse, error) {
return dockertypes.HijackedResponse{Conn: FakeDockerConn{}}, nil
}
// CopyFromContainer copies from the fake container
func (d *FakeDockerClient) CopyFromContainer(opts docker.CopyFromContainerOptions) error {
d.CopyFromContainerOpts = opts
return d.CopyFromContainerErr
}
// BuildImage builds image
func (d *FakeDockerClient) BuildImage(opts docker.BuildImageOptions) error {
d.BuildImageOpts = opts
return d.BuildImageErr
}
func (d *FakeDockerClient) InspectContainer(id string) (*docker.Container, error) {
return nil, d.BuildImageErr
}
func (d *FakeDockerClient) AttachToContainerNonBlocking(opts docker.AttachToContainerOptions) (docker.CloseWaiter, error) {
return fakeCloseWait{}, nil
}
type fakeCloseWait struct{}
func (cw fakeCloseWait) Close() error {
return nil
}
func (cw fakeCloseWait) Wait() error {
return nil
func (d *FakeDockerClient) ImageBuild(ctx context.Context, buildContext io.Reader, options dockertypes.ImageBuildOptions) (dockertypes.ImageBuildResponse, error) {
d.BuildImageOpts = options
return dockertypes.ImageBuildResponse{
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}, d.BuildImageErr
}

View File

@@ -2,6 +2,9 @@ package docker
import (
"bufio"
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"os"
@@ -9,8 +12,6 @@ import (
"regexp"
"strings"
client "github.com/fsouza/go-dockerclient"
"github.com/openshift/source-to-image/pkg/api"
"github.com/openshift/source-to-image/pkg/errors"
utilglog "github.com/openshift/source-to-image/pkg/util/glog"
@@ -30,6 +31,15 @@ type ImageReference struct {
ID string
}
type AuthConfigurations struct {
Configs map[string]api.AuthConfig
}
type dockerConfig struct {
Auth string `json:"auth"`
Email string `json:"email"`
}
const (
// maxErrorOutput is the maximum length of the error output saved for processing
maxErrorOutput = 1024
@@ -38,12 +48,12 @@ const (
// GetImageRegistryAuth retrieves the appropriate docker client authentication object for a given
// image name and a given set of client authentication objects.
func GetImageRegistryAuth(auths *client.AuthConfigurations, imageName string) client.AuthConfiguration {
func GetImageRegistryAuth(auths *AuthConfigurations, imageName string) api.AuthConfig {
glog.V(5).Infof("Getting docker credentials for %s", imageName)
spec, err := ParseImageReference(imageName)
if err != nil {
glog.V(0).Infof("error: Failed to parse docker reference %s", imageName)
return client.AuthConfiguration{}
return api.AuthConfig{}
}
if auth, ok := auths.Configs[spec.Registry]; ok {
@@ -54,13 +64,13 @@ func GetImageRegistryAuth(auths *client.AuthConfigurations, imageName string) cl
glog.V(5).Infof("Using %s credentials for pulling %s", auth.Email, imageName)
return auth
}
return client.AuthConfiguration{}
return api.AuthConfig{}
}
// LoadImageRegistryAuth loads and returns the set of client auth objects from a docker config
// json file.
func LoadImageRegistryAuth(dockerCfg io.Reader) *client.AuthConfigurations {
auths, err := client.NewAuthConfigurations(dockerCfg)
func LoadImageRegistryAuth(dockerCfg io.Reader) *AuthConfigurations {
auths, err := NewAuthConfigurations(dockerCfg)
if err != nil {
glog.V(0).Infof("error: Unable to load docker config")
return nil
@@ -68,13 +78,78 @@ func LoadImageRegistryAuth(dockerCfg io.Reader) *client.AuthConfigurations {
return auths
}
// begin next 3 methods borrowed from go-dockerclient
// NewAuthConfigurations finishes creating the auth config array s2i pulls
// from any auth config file it is pointed to when started from the command line
func NewAuthConfigurations(r io.Reader) (*AuthConfigurations, error) {
var auth *AuthConfigurations
confs, err := parseDockerConfig(r)
if err != nil {
return nil, err
}
auth, err = authConfigs(confs)
if err != nil {
return nil, err
}
return auth, nil
}
// parseDockerConfig does the json unmarshalling of the data we read from the file
func parseDockerConfig(r io.Reader) (map[string]dockerConfig, error) {
buf := new(bytes.Buffer)
buf.ReadFrom(r)
byteData := buf.Bytes()
confsWrapper := struct {
Auths map[string]dockerConfig `json:"auths"`
}{}
if err := json.Unmarshal(byteData, &confsWrapper); err == nil {
if len(confsWrapper.Auths) > 0 {
return confsWrapper.Auths, nil
}
}
var confs map[string]dockerConfig
if err := json.Unmarshal(byteData, &confs); err != nil {
return nil, err
}
return confs, nil
}
// authConfigs converts a dockerConfigs map to a AuthConfigurations object.
func authConfigs(confs map[string]dockerConfig) (*AuthConfigurations, error) {
c := &AuthConfigurations{
Configs: make(map[string]api.AuthConfig),
}
for reg, conf := range confs {
data, err := base64.StdEncoding.DecodeString(conf.Auth)
if err != nil {
return nil, err
}
userpass := strings.SplitN(string(data), ":", 2)
if len(userpass) != 2 {
return nil, fmt.Errorf("cannot parse username/password from %s", userpass)
}
c.Configs[reg] = api.AuthConfig{
Email: conf.Email,
Username: userpass[0],
Password: userpass[1],
ServerAddress: reg,
}
}
return c, nil
}
// end block of 3 methods borrowed from go-dockerclient
// LoadAndGetImageRegistryAuth loads the set of client auth objects from a docker config file
// and returns the appropriate client auth object for a given image name.
func LoadAndGetImageRegistryAuth(dockerCfg io.Reader, imageName string) client.AuthConfiguration {
auths, err := client.NewAuthConfigurations(dockerCfg)
func LoadAndGetImageRegistryAuth(dockerCfg io.Reader, imageName string) api.AuthConfig {
auths, err := NewAuthConfigurations(dockerCfg)
if err != nil {
glog.V(0).Infof("error: Unable to load docker config")
return client.AuthConfiguration{}
return api.AuthConfig{}
}
return GetImageRegistryAuth(auths, imageName)
}
@@ -90,7 +165,7 @@ func StreamContainerIO(errStream io.Reader, errOutput *string, log func(...inter
// we're ignoring ErrClosedPipe, as this is information
// the docker container ended streaming logs
if glog.Is(2) && err != io.ErrClosedPipe && err != io.EOF {
glog.V(0).Infof("error: Error reading docker stderr, %v", err)
glog.V(0).Infof("error: Error reading docker stderr, %#v", err)
}
break
}
@@ -180,7 +255,7 @@ func PullImage(name string, d Docker, policy api.PullPolicy, force bool) (*PullR
}
var (
image *client.Image
image *api.Image
err error
)
switch policy {

View File

@@ -6,7 +6,6 @@ import (
"path/filepath"
"strings"
dockerClient "github.com/fsouza/go-dockerclient"
"github.com/openshift/source-to-image/pkg/api"
"github.com/openshift/source-to-image/pkg/docker"
"github.com/openshift/source-to-image/pkg/errors"
@@ -169,7 +168,7 @@ type DefaultScriptSourceManager struct {
ScriptsURL string
download Downloader
docker docker.Docker
dockerAuth dockerClient.AuthConfiguration
dockerAuth api.AuthConfig
sources []ScriptHandler
fs util.FileSystem
}
@@ -183,7 +182,7 @@ func (m *DefaultScriptSourceManager) Add(s ScriptHandler) {
}
// NewInstaller returns a new instance of the default Installer implementation
func NewInstaller(image string, scriptsURL string, proxyConfig *api.ProxyConfig, docker docker.Docker, auth dockerClient.AuthConfiguration) Installer {
func NewInstaller(image string, scriptsURL string, proxyConfig *api.ProxyConfig, docker docker.Docker, auth api.AuthConfig) Installer {
m := DefaultScriptSourceManager{
Image: image,
ScriptsURL: scriptsURL,

View File

@@ -7,7 +7,6 @@ import (
"strings"
"testing"
dockerClient "github.com/fsouza/go-dockerclient"
"github.com/openshift/source-to-image/pkg/api"
dockerpkg "github.com/openshift/source-to-image/pkg/docker"
"github.com/openshift/source-to-image/pkg/test"
@@ -263,7 +262,7 @@ func TestInstallRequiredFromInvalidURL(t *testing.T) {
func TestNewInstaller(t *testing.T) {
docker := &dockerpkg.FakeDocker{DefaultURLResult: "image://docker"}
inst := NewInstaller("test-image", "http://foo.bar", nil, docker, dockerClient.AuthConfiguration{})
inst := NewInstaller("test-image", "http://foo.bar", nil, docker, api.AuthConfig{})
sources := inst.(*DefaultScriptSourceManager).sources
firstHandler, ok := sources[0].(*URLScriptHandler)
if !ok {

View File

@@ -3,7 +3,6 @@
package integration
import (
"bytes"
"encoding/json"
"flag"
"io/ioutil"
@@ -15,11 +14,18 @@ import (
"testing"
"time"
"github.com/fsouza/go-dockerclient"
dockerapi "github.com/docker/engine-api/client"
dockertypes "github.com/docker/engine-api/types"
dockercontainer "github.com/docker/engine-api/types/container"
dockerstrslice "github.com/docker/engine-api/types/strslice"
"github.com/docker/go-connections/tlsconfig"
"github.com/golang/glog"
"github.com/openshift/source-to-image/pkg/api"
"github.com/openshift/source-to-image/pkg/build/strategies"
"github.com/openshift/source-to-image/pkg/util"
"golang.org/x/net/context"
"k8s.io/kubernetes/pkg/kubelet/dockertools"
k8snet "k8s.io/kubernetes/pkg/util/net"
)
const (
@@ -71,7 +77,8 @@ func TestInjectionBuild(t *testing.T) {
type integrationTest struct {
t *testing.T
dockerClient *docker.Client
dockerClient dockertools.DockerInterface
engineClient dockerapi.Client
setupComplete bool
}
@@ -93,15 +100,32 @@ func dockerConfig() *api.DockerConfig {
return cfg
}
func dockerClient(config *api.DockerConfig) (*docker.Client, error) {
func dockerClient(config *api.DockerConfig) (dockertools.DockerInterface, dockerapi.Client, error) {
var client *dockerapi.Client
var httpClient *http.Client
if config.CertFile != "" && config.KeyFile != "" && config.CAFile != "" {
return docker.NewTLSClient(
config.Endpoint,
config.CertFile,
config.KeyFile,
config.CAFile)
tlscOptions := tlsconfig.Options{
CAFile: config.CAFile,
CertFile: config.CertFile,
KeyFile: config.KeyFile,
}
tlsc, tlsErr := tlsconfig.Client(tlscOptions)
if tlsErr != nil {
return nil, dockerapi.Client{}, tlsErr
}
httpClient = &http.Client{
Transport: k8snet.SetTransportDefaults(&http.Transport{
TLSClientConfig: tlsc,
}),
}
}
return docker.NewClient(config.Endpoint)
client, err := dockerapi.NewClient(config.Endpoint, "", httpClient, nil)
if err != nil {
return nil, dockerapi.Client{}, err
}
k8sDocker := dockertools.ConnectToDockerOrDie(config.Endpoint)
return k8sDocker, *client, nil
}
func getLogLevel() (level int) {
@@ -115,7 +139,12 @@ func getLogLevel() (level int) {
// setup sets up integration tests
func (i *integrationTest) setup() {
i.dockerClient, _ = dockerClient(dockerConfig())
var err error
i.dockerClient, i.engineClient, err = dockerClient(dockerConfig())
if err != nil {
i.t.Errorf("%+v", err)
return
}
if !i.setupComplete {
// get the full path to this .go file so we can construct the file url
// using this file's dirname
@@ -124,7 +153,7 @@ func (i *integrationTest) setup() {
FakeScriptsFileURL = "file://" + filepath.Join(testImagesDir, ".s2i", "bin")
for _, image := range []string{TagCleanBuild, TagCleanBuildUser, TagIncrementalBuild, TagIncrementalBuildUser} {
i.dockerClient.RemoveImage(image)
i.dockerClient.RemoveImage(image, dockertypes.ImageRemoveOptions{})
}
go http.ListenAndServe(":23456", http.FileServer(http.Dir(testImagesDir)))
@@ -499,20 +528,22 @@ func (i *integrationTest) checkForImage(tag string) {
}
func (i *integrationTest) createContainer(image string) string {
config := docker.Config{Image: image, AttachStdout: false, AttachStdin: false}
container, err := i.dockerClient.CreateContainer(docker.CreateContainerOptions{Name: "", Config: &config})
opts := dockertypes.ContainerCreateConfig{Name: "", Config: &dockercontainer.Config{Image: image}}
container, err := i.dockerClient.CreateContainer(opts)
if err != nil {
i.t.Errorf("Couldn't create container from image %s", image)
i.t.Errorf("Couldn't create container from image %s with error %+v", image, err)
return ""
}
err = i.dockerClient.StartContainer(container.ID, &docker.HostConfig{})
err = i.dockerClient.StartContainer(container.ID)
if err != nil {
i.t.Errorf("Couldn't start container: %s", container.ID)
i.t.Errorf("Couldn't start container: %s with error %+v", container.ID, err)
return ""
}
exitCode, err := i.dockerClient.WaitContainer(container.ID)
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
exitCode, err := i.engineClient.ContainerWait(ctx, container.ID)
if exitCode != 0 {
i.t.Errorf("Bad exit code from container: %d", exitCode)
return ""
@@ -522,17 +553,20 @@ func (i *integrationTest) createContainer(image string) string {
}
func (i *integrationTest) runInContainer(image string, command []string) int {
config := docker.Config{Image: image, AttachStdout: false, Cmd: command, AttachStdin: false}
container, err := i.dockerClient.CreateContainer(docker.CreateContainerOptions{Name: "", Config: &config})
if err != nil {
i.t.Errorf("Couldn't create container from image %s", image)
opts := dockertypes.ContainerCreateConfig{Name: "", Config: &dockercontainer.Config{Image: image, AttachStdout: false, AttachStdin: false, Cmd: dockerstrslice.StrSlice(command)}}
container, err := i.dockerClient.CreateContainer(opts)
if err != nil || container == nil {
i.t.Errorf("Couldn't create container from image %s err %+v", image, err)
return -1
}
err = i.dockerClient.StartContainer(container.ID, &docker.HostConfig{})
err = i.dockerClient.StartContainer(container.ID)
if err != nil {
i.t.Errorf("Couldn't start container: %s", container.ID)
}
exitCode, err := i.dockerClient.WaitContainer(container.ID)
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
exitCode, err := i.engineClient.ContainerWait(ctx, container.ID)
if err != nil {
i.t.Errorf("Couldn't start container: %s", container.ID)
}
@@ -540,11 +574,11 @@ func (i *integrationTest) runInContainer(image string, command []string) int {
}
func (i *integrationTest) removeContainer(cID string) {
i.dockerClient.RemoveContainer(docker.RemoveContainerOptions{cID, true, true})
i.dockerClient.RemoveContainer(cID, dockertypes.ContainerRemoveOptions{true, true, true})
}
func (i *integrationTest) fileExists(cID string, filePath string) {
res := fileExistsInContainer(i.dockerClient, cID, filePath)
res := i.fileExistsInContainer(cID, filePath)
if !res {
i.t.Errorf("Couldn't find file %s in container %s", filePath, cID)
@@ -552,7 +586,7 @@ func (i *integrationTest) fileExists(cID string, filePath string) {
}
func (i *integrationTest) fileNotExists(cID string, filePath string) {
res := fileExistsInContainer(i.dockerClient, cID, filePath)
res := i.fileExistsInContainer(cID, filePath)
if res {
i.t.Errorf("Unexpected file %s in container %s", filePath, cID)
@@ -583,12 +617,13 @@ func (i *integrationTest) checkIncrementalBuildState(cID string, workingDir stri
}
}
func fileExistsInContainer(d *docker.Client, cID string, filePath string) bool {
var buf []byte
writer := bytes.NewBuffer(buf)
err := d.CopyFromContainer(docker.CopyFromContainerOptions{writer, cID, filePath})
content := writer.String()
return ((err == nil) && ("" != content))
func (i *integrationTest) fileExistsInContainer(cID string, filePath string) bool {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
rdr, stats, err := i.engineClient.CopyFromContainer(ctx, cID, filePath)
if err != nil {
return false
}
defer rdr.Close()
return "" != stats.Name
}