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:
4
Godeps/Godeps.json
generated
4
Godeps/Godeps.json
generated
@@ -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",
|
||||
|
||||
@@ -353,6 +353,7 @@ _s2i()
|
||||
flags+=("--ca=")
|
||||
flags+=("--cert=")
|
||||
flags+=("--key=")
|
||||
flags+=("--log-flush-frequency=")
|
||||
flags+=("--loglevel=")
|
||||
flags+=("--url=")
|
||||
two_word_flags+=("-U")
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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¶mScripts": {
|
||||
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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user