diff --git a/pkg/docker/docker.go b/pkg/docker/docker.go index 61bd2c7b9..d48575853 100644 --- a/pkg/docker/docker.go +++ b/pkg/docker/docker.go @@ -1,15 +1,14 @@ package docker import ( - "bufio" + "bytes" + "encoding/base64" + "encoding/json" "fmt" "io" "io/ioutil" - "math" "math/rand" - "net" "net/http" - "net/url" "os" "path" "path/filepath" @@ -18,15 +17,14 @@ import ( "syscall" "time" + dockermessage "github.com/docker/docker/pkg/jsonmessage" 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" + dockernetwork "github.com/docker/engine-api/types/network" "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" @@ -67,7 +65,7 @@ const ( // DefaultDockerTimeout specifies a timeout for Docker API calls. When this // timeout is reached, certain Docker API calls might error out. - DefaultDockerTimeout = 60 * time.Second + DefaultDockerTimeout = 2 * time.Minute // DefaultShmSize is the default shared memory size to use (in bytes) if not specified. DefaultShmSize = int64(1024 * 1024 * 64) @@ -92,7 +90,7 @@ func containerName(image string) string { return fmt.Sprintf("%s_%s_%s", containerNamePrefix, image, uid) } -// Docker is the interface between STI and the k8s abstraction around docker engine-api. +// Docker is the interface between STI and the docker engine-api. // It contains higher level operations called from the STI // build or usage commands type Docker interface { @@ -117,36 +115,37 @@ type Docker interface { UploadToContainer(srcPath, destPath, container string) error UploadToContainerWithCallback(srcPath, destPath, container string, walkFn filepath.WalkFunc, modifyInplace bool) error DownloadFromContainer(containerPath string, w io.Writer, container string) error - Ping() error + Version() (dockertypes.Version, error) } -// Client contains all methods used when interacting directly with docker engine-api instead of the k8s abstraction around docker engine-api +// Client contains all methods used when interacting directly with docker engine-api type Client interface { 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) + ContainerCreate(ctx context.Context, config *dockercontainer.Config, hostConfig *dockercontainer.HostConfig, networkingConfig *dockernetwork.NetworkingConfig, containerName string) (dockertypes.ContainerCreateResponse, error) + ContainerInspect(ctx context.Context, containerID string) (dockertypes.ContainerJSON, error) + ContainerRemove(ctx context.Context, containerID string, options dockertypes.ContainerRemoveOptions) error + ContainerStart(ctx context.Context, containerID string) error + ContainerWait(ctx context.Context, containerID string) (int, 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) ImageInspectWithRaw(ctx context.Context, imageID string, getSize bool) (dockertypes.ImageInspect, []byte, error) + ImagePull(ctx context.Context, ref string, options dockertypes.ImagePullOptions) (io.ReadCloser, error) + ImageRemove(ctx context.Context, imageID string, options dockertypes.ImageRemoveOptions) ([]dockertypes.ImageDelete, error) + ServerVersion(ctx context.Context) (dockertypes.Version, error) } type stiDocker struct { - kubeDockerClient dockertools.DockerInterface - client Client - httpClient *http.Client - dialer *net.Dialer - pullAuth dockertypes.AuthConfig - endpoint string + client Client + pullAuth dockertypes.AuthConfig } -func (s stiDocker) InspectImage(name string) (*dockertypes.ImageInspect, error) { - ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute) - resp, _, err := s.client.ImageInspectWithRaw(ctx, name, true) +func (d stiDocker) InspectImage(name string) (*dockertypes.ImageInspect, error) { + ctx, cancel := getDefaultContext() + defer cancel() + resp, _, err := d.client.ImageInspectWithRaw(ctx, name, true) if err != nil { - if dockerapi.IsErrImageNotFound(err) { - return nil, fmt.Errorf("no such image :%q", name) - } return nil, err } return &resp, nil @@ -205,7 +204,7 @@ func (rco RunContainerOptions) asDockerConfig() dockercontainer.Config { Image: getImageName(rco.Image), User: rco.User, Env: rco.Env, - Entrypoint: dockerstrslice.StrSlice(rco.Entrypoint), + Entrypoint: rco.Entrypoint, OpenStdin: rco.Stdin != nil, StdinOnce: rco.Stdin != nil, AttachStdout: rco.Stdout != nil, @@ -254,16 +253,6 @@ func (rco RunContainerOptions) asDockerAttachToContainerOptions() dockertypes.Co } } -// 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 @@ -285,47 +274,47 @@ type BuildImageOptions struct { // New creates a new implementation of the STI Docker interface 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 != "" { tlscOptions := tlsconfig.Options{ - CAFile: config.CAFile, - CertFile: config.CertFile, - KeyFile: config.KeyFile, + CAFile: config.CAFile, + CertFile: config.CertFile, + KeyFile: config.KeyFile, + InsecureSkipVerify: os.Getenv("DOCKER_TLS_VERIFY") == "", } - tlsc, tlsErr := tlsconfig.Client(tlscOptions) - if tlsErr != nil { - return nil, tlsErr + tlsc, err := tlsconfig.Client(tlscOptions) + if err != nil { + return nil, err } + httpClient = &http.Client{ - Transport: k8snet.SetTransportDefaults(&http.Transport{ + Transport: &http.Transport{ TLSClientConfig: tlsc, - }), + }, } } - client, err := dockerapi.NewClient(config.Endpoint, "", httpClient, nil) + client, err := dockerapi.NewClient(config.Endpoint, os.Getenv("DOCKER_API_VERSION"), httpClient, nil) if err != nil { return nil, err } - k8sDocker := dockertools.ConnectToDockerOrDie(config.Endpoint, 0) return &stiDocker{ - kubeDockerClient: k8sDocker, - client: client, - httpClient: httpClient, - dialer: &net.Dialer{}, + client: client, 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) +func getDefaultContext() (context.Context, context.CancelFunc) { + // the intention is: all docker API calls with the exception of known long- + // running calls (ContainerWait, ImagePull, ImageBuild) must complete within a + // certain timeout otherwise we bail. + return context.WithTimeout(context.Background(), DefaultDockerTimeout) } // GetImageWorkdir returns the WORKDIR property for the given image name. @@ -354,58 +343,6 @@ func (d *stiDocker) GetImageEntrypoint(name string) ([]string, error) { 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 { @@ -461,14 +398,14 @@ func (d *stiDocker) UploadToContainerWithCallback(src, dest, container string, w }() } glog.V(3).Infof("Uploading %q to %q ...", src, path) - ctx, cancel := getDefaultContext(DefaultDockerTimeout) + ctx, cancel := getDefaultContext() 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 { - ctx, cancel := getDefaultContext(DefaultDockerTimeout) + ctx, cancel := getDefaultContext() defer cancel() readCloser, _, err := d.client.CopyFromContainer(ctx, container, containerPath) if err != nil { @@ -508,10 +445,11 @@ func (d *stiDocker) GetImageUser(name string) (string, error) { return user, nil } -// Ping determines if the Docker daemon is reachable -func (d *stiDocker) Ping() error { - _, err := d.do("GET", "/_ping", nil) - return err +// Version returns information of the docker client and server host +func (d *stiDocker) Version() (dockertypes.Version, error) { + ctx, cancel := getDefaultContext() + defer cancel() + return d.client.ServerVersion(ctx) } // IsImageOnBuild provides information about whether the Docker image has @@ -552,7 +490,7 @@ func (d *stiDocker) CheckAndPullImage(name string) (*api.Image, error) { } image, err := d.CheckImage(name) - if err != nil && !strings.Contains(err.(errors.Error).Details.Error(), "no such image") { + if err != nil && !strings.Contains(err.(errors.Error).Details.Error(), "No such image") { return nil, err } if image == nil { @@ -581,20 +519,66 @@ func (d *stiDocker) CheckImage(name string) (*api.Image, error) { return nil, nil } +func base64EncodeAuth(auth dockertypes.AuthConfig) (string, error) { + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(auth); err != nil { + return "", err + } + return base64.URLEncoding.EncodeToString(buf.Bytes()), nil +} + // PullImage pulls an image into the local registry func (d *stiDocker) PullImage(name string) (*api.Image, error) { name = getImageName(name) - err := d.kubeDockerClient.PullImage(name, d.pullAuth, dockertypes.ImagePullOptions{}) + + // RegistryAuth is the base64 encoded credentials for the registry + base64Auth, err := base64EncodeAuth(d.pullAuth) if err != nil { return nil, errors.NewPullImageError(name, err) } - resp, err := d.InspectImage(name) + + err = util.TimeoutAfter(DefaultDockerTimeout, fmt.Sprintf("pulling image %q", name), func(timer *time.Timer) error { + resp, pullErr := d.client.ImagePull(context.Background(), name, dockertypes.ImagePullOptions{RegistryAuth: base64Auth}) + if pullErr != nil { + return pullErr + } + defer resp.Close() + + decoder := json.NewDecoder(resp) + for { + if !timer.Stop() { + return &util.TimeoutError{} + } + timer.Reset(DefaultDockerTimeout) + + var msg dockermessage.JSONMessage + pullErr = decoder.Decode(&msg) + if pullErr == io.EOF { + return nil + } + if pullErr != nil { + return pullErr + } + + if msg.Error != nil { + return msg.Error + } + if msg.ProgressMessage != "" { + glog.V(4).Infof("pulling image %s: %s", name, msg.ProgressMessage) + } + } + }) if err != nil { return nil, errors.NewPullImageError(name, err) } - if resp != nil { + + inspectResp, err := d.InspectImage(name) + if err != nil { + return nil, errors.NewPullImageError(name, err) + } + if inspectResp != nil { image := &api.Image{} - updateImageWithInspect(image, resp) + updateImageWithInspect(image, inspectResp) return image, nil } return nil, nil @@ -618,12 +602,13 @@ func updateImageWithInspect(image *api.Image, inspect *dockertypes.ImageInspect) // RemoveContainer removes a container and its associated volumes. func (d *stiDocker) RemoveContainer(id string) error { + ctx, cancel := getDefaultContext() + defer cancel() opts := dockertypes.ContainerRemoveOptions{ RemoveVolumes: true, Force: true, } - return d.kubeDockerClient.RemoveContainer(id, opts) - + return d.client.ContainerRemove(ctx, id, opts) } // GetLabels retrieves the labels of the given image. @@ -805,8 +790,11 @@ func determineCommandBaseDir(opts RunContainerOptions, imageMetadata *api.Image, } // dumpContainerInfo dumps information about a running container (port/IP/etc). -func dumpContainerInfo(container *dockertypes.ContainerCreateResponse, d *stiDocker, image string) { - containerJSON, err := d.kubeDockerClient.InspectContainer(container.ID) +func dumpContainerInfo(container dockertypes.ContainerCreateResponse, d *stiDocker, image string) { + ctx, cancel := getDefaultContext() + defer cancel() + + containerJSON, err := d.client.ContainerInspect(ctx, container.ID) if err != nil { return } @@ -931,27 +919,23 @@ 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 *dockertypes.ContainerCreateResponse - if err = util.TimeoutAfter(DefaultDockerTimeout, "timeout after waiting %v for Docker to create container", func() error { - var createErr error - if createOpts.HostConfig != nil && createOpts.HostConfig.ShmSize <= 0 { - createOpts.HostConfig.ShmSize = DefaultShmSize - } - container, createErr = d.kubeDockerClient.CreateContainer(createOpts) - return createErr - }); err != nil { + ctx, cancel := getDefaultContext() + defer cancel() + if createOpts.HostConfig != nil && createOpts.HostConfig.ShmSize <= 0 { + createOpts.HostConfig.ShmSize = DefaultShmSize + } + container, err := d.client.ContainerCreate(ctx, createOpts.Config, createOpts.HostConfig, createOpts.NetworkingConfig, createOpts.Name) + if err != nil { return err } - containerName := containerNameOrID(container) - // Container was created, so we defer its removal, and also remove it if we get a SIGINT/SIGTERM/SIGQUIT/SIGHUP. removeContainer := func() { - glog.V(4).Infof("Removing container %q ...", containerName) - if err := d.RemoveContainer(container.ID); err != nil { - glog.V(0).Infof("warning: Failed to remove container %q: %v", containerName, err) + glog.V(4).Infof("Removing container %q ...", container.ID) + if removeErr := d.RemoveContainer(container.ID); removeErr != nil { + glog.V(0).Infof("warning: Failed to remove container %q: %v", container.ID, removeErr) } else { - glog.V(4).Infof("Removed container %q", containerName) + glog.V(4).Infof("Removed container %q", container.ID) } } dumpStack := func(signal os.Signal) { @@ -968,7 +952,7 @@ func (d *stiDocker) RunContainer(opts RunContainerOptions) error { // 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) + glog.V(2).Infof("Attaching to container %q ...", container.ID) errorChannel := make(chan error) timeoutTimer := time.NewTimer(DefaultDockerTimeout) var attachLoggingError error @@ -977,23 +961,22 @@ func (d *stiDocker) RunContainer(opts RunContainerOptions) error { // container exited in holdHijackedConnection, we'll using channel based signaling coupled with a time to avoid blocking forever attachExit := make(chan bool, 1) go func() { - ctx, cancel := getDefaultContext(DefaultDockerTimeout) + ctx, cancel := getDefaultContext() 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) + resp, attachErr := d.client.ContainerAttach(ctx, container.ID, opts.asDockerAttachToContainerOptions()) + errorChannel <- attachErr + if attachErr != nil { + glog.V(0).Infof("error: Unable to attach to container %q: %v", container.ID, attachErr) return } defer resp.Close() - sopts := opts.asDockerAttachToStreamOptions() - attachLoggingError = d.holdHijackedConnection(sopts.RawTerminal, sopts.InputStream, sopts.OutputStream, sopts.ErrorStream, resp) + attachLoggingError = d.holdHijackedConnection(false, opts.Stdin, opts.Stdout, opts.Stderr, resp) attachExit <- true }() // this error check should handle the result from the d.client.ContainerAttach call ... we progress to start when that occurs select { - case err := <-errorChannel: + 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 @@ -1003,14 +986,15 @@ func (d *stiDocker) RunContainer(opts RunContainerOptions) error { } break case <-timeoutTimer.C: - return fmt.Errorf("timed out waiting to attach to container %s ", containerName) + return fmt.Errorf("timed out waiting to attach to container %s ", container.ID) } // 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.kubeDockerClient.StartContainer(container.ID) - }); err != nil { + glog.V(2).Infof("Starting container %q ...", container.ID) + ctx, cancel := getDefaultContext() + defer cancel() + err = d.client.ContainerStart(ctx, container.ID) + if err != nil { return err } @@ -1032,12 +1016,10 @@ func (d *stiDocker) RunContainer(opts RunContainerOptions) error { // Return an error if the exit code of the container is // non-zero. - glog.V(4).Infof("Waiting for container %q to stop ...", containerName) - 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) + glog.V(4).Infof("Waiting for container %q to stop ...", container.ID) + exitCode, err := d.client.ContainerWait(context.Background(), container.ID) if err != nil { - return fmt.Errorf("waiting for container %q to stop: %v", containerName, err) + return fmt.Errorf("waiting for container %q to stop: %v", container.ID, err) } if exitCode != 0 { return errors.NewContainerError(container.ID, exitCode, "") @@ -1079,10 +1061,6 @@ func (d *stiDocker) RunContainer(opts RunContainerOptions) error { } -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) @@ -1096,7 +1074,7 @@ 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) { - ctx, cancel := getDefaultContext(DefaultDockerTimeout) + ctx, cancel := getDefaultContext() defer cancel() dockerOpts := dockertypes.ContainerCommitOptions{ Reference: opts.Repository, @@ -1122,7 +1100,9 @@ func (d *stiDocker) CommitContainer(opts CommitContainerOptions) (string, error) // RemoveImage removes the image with specified ID func (d *stiDocker) RemoveImage(imageID string) error { - _, err := d.kubeDockerClient.RemoveImage(imageID, dockertypes.ImageRemoveOptions{}) + ctx, cancel := getDefaultContext() + defer cancel() + _, err := d.client.ImageRemove(ctx, imageID, dockertypes.ImageRemoveOptions{}) return err } @@ -1143,9 +1123,7 @@ func (d *stiDocker) BuildImage(opts BuildImageOptions) error { dockerOpts.CPUQuota = opts.CGroupLimits.CPUQuota } glog.V(2).Infof("Building container using config: %+v", 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) + resp, err := d.client.ImageBuild(context.Background(), opts.Stdin, dockerOpts) if err != nil { return err } diff --git a/pkg/docker/docker_test.go b/pkg/docker/docker_test.go index 1f7b8db6f..ad98cf291 100644 --- a/pkg/docker/docker_test.go +++ b/pkg/docker/docker_test.go @@ -17,7 +17,6 @@ import ( 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) { @@ -30,29 +29,24 @@ func TestContainerName(t *testing.T) { } func getDocker(client Client) *stiDocker { - //k8s has its own fake docker client mechanism - k8sDocker := dockertools.ConnectToDockerOrDie("fake://", 0) return &stiDocker{ - kubeDockerClient: k8sDocker, - client: client, - pullAuth: dockertypes.AuthConfig{}, + client: client, + pullAuth: dockertypes.AuthConfig{}, } } func TestRemoveContainer(t *testing.T) { - fakeDocker := &test.FakeDockerClient{} + fakeDocker := test.NewFakeDockerClient() dh := getDocker(fakeDocker) - fake := dh.kubeDockerClient.(*dockertools.FakeDockerClient) containerID := "testContainerId" - fakeContainer := dockertools.FakeContainer{ID: containerID} - fake.SetFakeContainers([]*dockertools.FakeContainer{&fakeContainer}) + fakeDocker.Containers[containerID] = dockercontainer.Config{} err := dh.RemoveContainer(containerID) if err != nil { t.Errorf("%+v", err) } - err = fake.AssertCalls([]string{"remove"}) - if err != nil { - t.Errorf("%v+v", err) + expectedCalls := []string{"remove"} + if !reflect.DeepEqual(fakeDocker.Calls, expectedCalls) { + t.Errorf("Expected fakeDocker.Calls %v, got %v", expectedCalls, fakeDocker.Calls) } } @@ -256,15 +250,14 @@ func TestImageBuild(t *testing.T) { func TestGetScriptsURL(t *testing.T) { type urltest struct { - image dockertypes.ImageInspect - result string - calls []string - inspectErr error - errExpected bool + image dockertypes.ImageInspect + result string + calls []string + inspectErr error } tests := map[string]urltest{ "not present": { - calls: []string{}, + calls: []string{"inspect_image"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{ Env: []string{"Env1=value1"}, @@ -279,7 +272,7 @@ func TestGetScriptsURL(t *testing.T) { }, "env in containerConfig": { - calls: []string{}, + calls: []string{"inspect_image"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{ Env: []string{"Env1=value1", ScriptsURLEnvironment + "=test_url_value"}, @@ -290,7 +283,7 @@ func TestGetScriptsURL(t *testing.T) { }, "env in image config": { - calls: []string{}, + calls: []string{"inspect_image"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{}, Config: &dockercontainer.Config{ @@ -305,7 +298,7 @@ func TestGetScriptsURL(t *testing.T) { }, "label in containerConfig": { - calls: []string{}, + calls: []string{"inspect_image"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{ Labels: map[string]string{ScriptsURLLabel: "test_url_value"}, @@ -316,7 +309,7 @@ func TestGetScriptsURL(t *testing.T) { }, "label in image config": { - calls: []string{}, + calls: []string{"inspect_image"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{}, Config: &dockercontainer.Config{ @@ -327,31 +320,27 @@ func TestGetScriptsURL(t *testing.T) { }, "inspect error": { - calls: []string{"pull"}, + calls: []string{"inspect_image", "pull"}, image: dockertypes.ImageInspect{}, inspectErr: fmt.Errorf("Inspect error"), }, } for desc, tst := range tests { - fakeDocker := &test.FakeDockerClient{} + fakeDocker := test.NewFakeDockerClient() dh := getDocker(fakeDocker) - fake := dh.kubeDockerClient.(*dockertools.FakeDockerClient) + tst.image.ID = "test/image:latest" if tst.inspectErr != nil { - fake.ClearErrors() - fake.InjectError("pull", tst.inspectErr) - fakeDocker.Image = nil + fakeDocker.PullFail = tst.inspectErr } else { - fakeDocker.Image = &tst.image + fakeDocker.Images = map[string]dockertypes.ImageInspect{tst.image.ID: tst.image} } - url, err := dh.GetScriptsURL("test/image") + url, err := dh.GetScriptsURL(tst.image.ID) - if e := fake.AssertCalls(tst.calls); e != nil { - t.Errorf("%s: %+v", desc, e) + if !reflect.DeepEqual(fakeDocker.Calls, tst.calls) { + t.Errorf("%s: Expected fakeDocker.Calls %v, got %v", desc, tst.calls, fakeDocker.Calls) } 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.inspectErr == nil && url != tst.result { t.Errorf("%s: Unexpected result. Expected: %s Actual: %s", @@ -373,7 +362,7 @@ func TestRunContainer(t *testing.T) { tests := map[string]runtest{ "default": { - calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"}, + calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{}, Config: &dockercontainer.Config{}, @@ -383,7 +372,7 @@ func TestRunContainer(t *testing.T) { cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /tmp/scripts/%s", api.Assemble)}, }, "paramDestination": { - calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"}, + calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{}, Config: &dockercontainer.Config{}, @@ -394,7 +383,7 @@ 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": { - calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"}, + calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{}, Config: &dockercontainer.Config{}, @@ -406,7 +395,7 @@ func TestRunContainer(t *testing.T) { cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt/test -xf - && /opt/test/scripts/%s", api.Assemble)}, }, "scriptsInsideImageEnvironment": { - calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"}, + calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{ Env: []string{ScriptsURLEnvironment + "=image:///opt/bin/"}, @@ -418,7 +407,7 @@ func TestRunContainer(t *testing.T) { cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /opt/bin/%s", api.Assemble)}, }, "scriptsInsideImageLabel": { - calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"}, + calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{ Labels: map[string]string{ScriptsURLLabel: "image:///opt/bin/"}, @@ -430,7 +419,7 @@ func TestRunContainer(t *testing.T) { cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /opt/bin/%s", api.Assemble)}, }, "scriptsInsideImageEnvironmentWithParamDestination": { - calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"}, + calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{ Env: []string{ScriptsURLEnvironment + "=image:///opt/bin"}, @@ -443,7 +432,7 @@ func TestRunContainer(t *testing.T) { cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt/sti -xf - && /opt/bin/%s", api.Assemble)}, }, "scriptsInsideImageLabelWithParamDestination": { - calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"}, + calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{ Labels: map[string]string{ScriptsURLLabel: "image:///opt/bin"}, @@ -456,7 +445,7 @@ func TestRunContainer(t *testing.T) { cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt/sti -xf - && /opt/bin/%s", api.Assemble)}, }, "paramDestinationFromImageEnvironment": { - calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"}, + calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{ Env: []string{LocationEnvironment + "=/opt", ScriptsURLEnvironment + "=http://my.test.url/test?param=one"}, @@ -468,7 +457,7 @@ func TestRunContainer(t *testing.T) { cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt -xf - && /opt/scripts/%s", api.Assemble)}, }, "paramDestinationFromImageLabel": { - calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"}, + calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{ Labels: map[string]string{DestinationLabel: "/opt", ScriptsURLLabel: "http://my.test.url/test?param=one"}, @@ -480,7 +469,7 @@ func TestRunContainer(t *testing.T) { cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt -xf - && /opt/scripts/%s", api.Assemble)}, }, "usageCommand": { - calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"}, + calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{}, Config: &dockercontainer.Config{}, @@ -490,7 +479,7 @@ func TestRunContainer(t *testing.T) { cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /tmp/scripts/%s", api.Usage)}, }, "otherCommand": { - calls: []string{"inspect_image", "inspect_image", "create", "start", "remove", "attach"}, + calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{}, Config: &dockercontainer.Config{}, @@ -502,13 +491,12 @@ func TestRunContainer(t *testing.T) { } for desc, tst := range tests { - fakeDocker := &test.FakeDockerClient{} + fakeDocker := test.NewFakeDockerClient() dh := getDocker(fakeDocker) - fake := dh.kubeDockerClient.(*dockertools.FakeDockerClient) - tst.image.ID = "test/image" - fakeDocker.Image = &tst.image - if len(fake.ContainerMap) > 0 { - t.Errorf("newly created fake client should have empty container map: %+v", fake.ContainerMap) + tst.image.ID = "test/image:latest" + fakeDocker.Images = map[string]dockertypes.ImageInspect{tst.image.ID: tst.image} + if len(fakeDocker.Containers) > 0 { + t.Errorf("newly created fake client should have empty container map: %+v", fakeDocker.Containers) } //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 @@ -530,50 +518,50 @@ func TestRunContainer(t *testing.T) { } // 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) + if len(fakeDocker.Containers) != 1 { + t.Errorf("fake container map should only have 1 entry: %+v", fakeDocker.Containers) } - for _, container := range fake.ContainerMap { + for _, container := range fakeDocker.Containers { // Validate the Container parameters - if container.Config == nil { - t.Errorf("%s: container config not set: %+v", desc, container) + if container.Image != "test/image:latest" { + t.Errorf("%s: Unexpected create config image: %s", desc, container.Image) } - if container.Config.Image != "test/image:latest" { - t.Errorf("%s: Unexpected create config image: %s", desc, container.Config.Image) + if !reflect.DeepEqual(container.Cmd, dockerstrslice.StrSlice(tst.cmdExpected)) { + t.Errorf("%s: Unexpected create config command: %#v instead of %q", desc, container.Cmd, strings.Join(tst.cmdExpected, " ")) } - 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.Env, []string{"Key1=Value1", "Key2=Value2"}) { + t.Errorf("%s: Unexpected create config env: %#v", desc, container.Env) } - if !reflect.DeepEqual(container.Config.Env, []string{"Key1=Value1", "Key2=Value2"}) { - t.Errorf("%s: Unexpected create config env: %#v", desc, container.Config.Env) + if !reflect.DeepEqual(fakeDocker.Calls, tst.calls) { + t.Errorf("%s: Expected fakeDocker.Calls %v, got %v", desc, tst.calls, fakeDocker.Calls) } } - } } func TestGetImageID(t *testing.T) { - fakeDocker := &test.FakeDockerClient{} + fakeDocker := test.NewFakeDockerClient() dh := getDocker(fakeDocker) - fake := dh.kubeDockerClient.(*dockertools.FakeDockerClient) - fakeDocker.Image = &dockertypes.ImageInspect{ID: "test-abcd"} - id, err := dh.GetImageID("test/image") - if e := fake.AssertCalls([]string{}); e != nil { - t.Errorf("%+v", e) + image := dockertypes.ImageInspect{ID: "test-abcd:latest"} + fakeDocker.Images = map[string]dockertypes.ImageInspect{image.ID: image} + id, err := dh.GetImageID("test-abcd") + expectedCalls := []string{"inspect_image"} + if !reflect.DeepEqual(fakeDocker.Calls, expectedCalls) { + t.Errorf("Expected fakeDocker.Calls %v, got %v", expectedCalls, fakeDocker.Calls) } if err != nil { t.Errorf("Unexpected error returned: %v", err) - } else if id != "test-abcd" { + } else if id != image.ID { t.Errorf("Unexpected image id returned: %s", id) } } func TestRemoveImage(t *testing.T) { - fakeDocker := &test.FakeDockerClient{} + fakeDocker := test.NewFakeDockerClient() dh := getDocker(fakeDocker) - fake := dh.kubeDockerClient.(*dockertools.FakeDockerClient) - fake.Images = []dockertypes.Image{{ID: "test-abcd"}} + image := dockertypes.ImageInspect{ID: "test-abcd"} + fakeDocker.Images = map[string]dockertypes.ImageInspect{image.ID: image} err := dh.RemoveImage("test-abcd") if err != nil { t.Errorf("Unexpected error removing image: %s", err) diff --git a/pkg/docker/fake_docker.go b/pkg/docker/fake_docker.go index 11d1d6061..0923a6626 100644 --- a/pkg/docker/fake_docker.go +++ b/pkg/docker/fake_docker.go @@ -2,9 +2,11 @@ package docker import ( "errors" - "github.com/openshift/source-to-image/pkg/api" "io" "path/filepath" + + dockertypes "github.com/docker/engine-api/types" + "github.com/openshift/source-to-image/pkg/api" ) // FakeDocker provides a fake docker interface @@ -62,9 +64,9 @@ func (f *FakeDocker) IsImageOnBuild(imageName string) bool { return f.IsOnBuildResult } -// Ping tells id the Docker deamon is reachable -func (f *FakeDocker) Ping() error { - return nil +// Version returns information of the docker client and server host +func (f *FakeDocker) Version() (dockertypes.Version, error) { + return dockertypes.Version{}, nil } // GetImageWorkdir returns the workdir diff --git a/pkg/docker/test/client.go b/pkg/docker/test/client.go index cee0225de..9d2fa602b 100644 --- a/pkg/docker/test/client.go +++ b/pkg/docker/test/client.go @@ -2,13 +2,17 @@ package test import ( "bytes" + "errors" "fmt" - dockertypes "github.com/docker/engine-api/types" - "golang.org/x/net/context" "io" "io/ioutil" "net" "time" + + dockertypes "github.com/docker/engine-api/types" + dockercontainer "github.com/docker/engine-api/types/container" + dockernetwork "github.com/docker/engine-api/types/network" + "golang.org/x/net/context" ) type FakeDockerAddr struct { @@ -57,8 +61,7 @@ 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 +// FakeDockerClient provides a Fake client for Docker testing type FakeDockerClient struct { CopyToContainerID string CopyToContainerPath string @@ -79,18 +82,30 @@ type FakeDockerClient struct { BuildImageOpts dockertypes.ImageBuildOptions BuildImageErr error - Image *dockertypes.ImageInspect + Images map[string]dockertypes.ImageInspect + + Containers map[string]dockercontainer.Config + + PullFail error + + Calls []string +} + +func NewFakeDockerClient() *FakeDockerClient { + return &FakeDockerClient{ + Images: make(map[string]dockertypes.ImageInspect), + Containers: make(map[string]dockercontainer.Config), + Calls: make([]string, 0), + } } func (d *FakeDockerClient) ImageInspectWithRaw(ctx context.Context, imageID string, getSize bool) (dockertypes.ImageInspect, []byte, error) { - if d.Image != nil { - return *d.Image, nil, nil - } - return dockertypes.ImageInspect{}, nil, fmt.Errorf("no such image :%q", imageID) -} + d.Calls = append(d.Calls, "inspect_image") -func (d *FakeDockerClient) Ping() error { - return nil + if _, exists := d.Images[imageID]; exists { + return d.Images[imageID], nil, nil + } + return dockertypes.ImageInspect{}, nil, fmt.Errorf("No such image: %q", imageID) } func (d *FakeDockerClient) CopyToContainer(ctx context.Context, container, path string, content io.Reader, opts dockertypes.CopyToContainerOptions) error { @@ -118,6 +133,7 @@ func (d *FakeDockerClient) ContainerCommit(ctx context.Context, container string } func (d *FakeDockerClient) ContainerAttach(ctx context.Context, container string, options dockertypes.ContainerAttachOptions) (dockertypes.HijackedResponse, error) { + d.Calls = append(d.Calls, "attach") return dockertypes.HijackedResponse{Conn: FakeDockerConn{}}, nil } @@ -127,3 +143,54 @@ func (d *FakeDockerClient) ImageBuild(ctx context.Context, buildContext io.Reade Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), }, d.BuildImageErr } + +func (d *FakeDockerClient) ContainerCreate(ctx context.Context, config *dockercontainer.Config, hostConfig *dockercontainer.HostConfig, networkingConfig *dockernetwork.NetworkingConfig, containerName string) (dockertypes.ContainerCreateResponse, error) { + d.Calls = append(d.Calls, "create") + + d.Containers[containerName] = *config + return dockertypes.ContainerCreateResponse{}, nil +} + +func (d *FakeDockerClient) ContainerInspect(ctx context.Context, containerID string) (dockertypes.ContainerJSON, error) { + d.Calls = append(d.Calls, "inspect_container") + return dockertypes.ContainerJSON{}, nil +} + +func (d *FakeDockerClient) ContainerRemove(ctx context.Context, containerID string, options dockertypes.ContainerRemoveOptions) error { + d.Calls = append(d.Calls, "remove") + + if _, exists := d.Containers[containerID]; exists { + delete(d.Containers, containerID) + return nil + } + return errors.New("container does not exist") +} + +func (d *FakeDockerClient) ContainerStart(ctx context.Context, containerID string) error { + d.Calls = append(d.Calls, "start") + return nil +} + +func (d *FakeDockerClient) ImagePull(ctx context.Context, ref string, options dockertypes.ImagePullOptions) (io.ReadCloser, error) { + d.Calls = append(d.Calls, "pull") + + if d.PullFail != nil { + return nil, d.PullFail + } + + return ioutil.NopCloser(bytes.NewReader([]byte{})), nil +} + +func (d *FakeDockerClient) ImageRemove(ctx context.Context, imageID string, options dockertypes.ImageRemoveOptions) ([]dockertypes.ImageDelete, error) { + d.Calls = append(d.Calls, "remove_image") + + if _, exists := d.Images[imageID]; exists { + delete(d.Images, imageID) + return []dockertypes.ImageDelete{}, nil + } + return []dockertypes.ImageDelete{}, errors.New("image does not exist") +} + +func (d *FakeDockerClient) ServerVersion(ctx context.Context) (dockertypes.Version, error) { + return dockertypes.Version{}, nil +} diff --git a/pkg/docker/util.go b/pkg/docker/util.go index eb632149f..e028ee738 100644 --- a/pkg/docker/util.go +++ b/pkg/docker/util.go @@ -13,6 +13,7 @@ import ( "strings" "github.com/docker/distribution/reference" + "github.com/docker/engine-api/client" "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" @@ -287,7 +288,8 @@ func IsReachable(config *api.Config) bool { if err != nil { return false } - return d.Ping() == nil + _, err = d.Version() + return err == nil } func pullAndCheck(image string, docker Docker, pullPolicy api.PullPolicy, config *api.Config, forcePull bool) (*PullResult, error) { @@ -344,10 +346,9 @@ func GetRuntimeImage(config *api.Config, docker Docker) error { func GetDefaultDockerConfig() *api.DockerConfig { cfg := &api.DockerConfig{} if cfg.Endpoint = os.Getenv("DOCKER_HOST"); cfg.Endpoint == "" { - cfg.Endpoint = "unix:///var/run/docker.sock" + cfg.Endpoint = client.DefaultDockerHost } - if os.Getenv("DOCKER_TLS_VERIFY") == "1" { - certPath := os.Getenv("DOCKER_CERT_PATH") + if certPath := os.Getenv("DOCKER_CERT_PATH"); certPath != "" { cfg.CertFile = filepath.Join(certPath, "cert.pem") cfg.KeyFile = filepath.Join(certPath, "key.pem") cfg.CAFile = filepath.Join(certPath, "ca.pem") diff --git a/pkg/util/timeout.go b/pkg/util/timeout.go index ef22eeb55..68a3ae077 100644 --- a/pkg/util/timeout.go +++ b/pkg/util/timeout.go @@ -14,29 +14,38 @@ type TimeoutError struct { // Error implements the Go error interface. func (t *TimeoutError) Error() string { if len(t.message) > 0 { - return fmt.Sprintf(t.message, t.after) + return fmt.Sprintf("%s timed out after %v", t.message, t.after) } - return fmt.Sprintf("calling the function timeout after %v", t.after) + return fmt.Sprintf("function timed out after %v", t.after) } -// TimeoutAfter executes the provide function and return the TimeoutError in -// case when the execution time of the provided function is bigger than provided -// time duration. -func TimeoutAfter(t time.Duration, errorMsg string, fn func() error) error { +// TimeoutAfter executes the provided function and returns TimeoutError in the +// case that the execution time of the function exceeded the provided duration. +// The provided function is passed the timer in case it wishes to reset it. If +// so, the following pattern must be used: +// if !timer.Stop() { +// return &TimeoutError{} +// } +// timer.Reset(timeout) +func TimeoutAfter(t time.Duration, errorMsg string, f func(*time.Timer) error) error { c := make(chan error, 1) + timer := time.NewTimer(t) go func() { - defer close(c) - c <- fn() + err := f(timer) + if !IsTimeoutError(err) { + c <- err + } }() select { case err := <-c: + timer.Stop() return err - case <-time.After(t): + case <-timer.C: return &TimeoutError{after: t, message: errorMsg} } } -// IsTimeoutError checks if the provided error is timeout. +// IsTimeoutError checks if the provided error is a TimeoutError. func IsTimeoutError(e error) bool { _, ok := e.(*TimeoutError) return ok diff --git a/pkg/util/timeout_test.go b/pkg/util/timeout_test.go index d269412c5..e8d867316 100644 --- a/pkg/util/timeout_test.go +++ b/pkg/util/timeout_test.go @@ -9,30 +9,30 @@ import ( func TestTimeoutAfter(t *testing.T) { type testCase struct { - fn func() error + fn func(*time.Timer) error msg string timeout time.Duration expect interface{} } table := []testCase{ { - fn: func() error { time.Sleep(1 * time.Second); return nil }, + fn: func(timer *time.Timer) error { time.Sleep(1 * time.Second); return nil }, timeout: 50 * time.Millisecond, expect: &TimeoutError{after: 50 * time.Millisecond}, }, { - fn: func() error { return fmt.Errorf("foo") }, + fn: func(timer *time.Timer) error { return fmt.Errorf("foo") }, timeout: 50 * time.Millisecond, expect: fmt.Errorf("foo"), }, { - fn: func() error { time.Sleep(1 * time.Second); return fmt.Errorf("foo") }, - msg: "bar %v", + fn: func(timer *time.Timer) error { time.Sleep(1 * time.Second); return fmt.Errorf("foo") }, + msg: "bar", timeout: 50 * time.Millisecond, - expect: fmt.Errorf("bar 50ms"), + expect: fmt.Errorf("bar timed out after 50ms"), }, { - fn: func() error { return nil }, + fn: func(timer *time.Timer) error { return nil }, timeout: 50 * time.Millisecond, expect: nil, }, diff --git a/test/integration/integration_test.go b/test/integration/integration_test.go index 950dbb51d..527957207 100644 --- a/test/integration/integration_test.go +++ b/test/integration/integration_test.go @@ -25,8 +25,6 @@ import ( "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 ( @@ -68,6 +66,10 @@ const ( FakeScriptsHttpURL = "http://127.0.0.1:23456/.s2i/bin" ) +func getDefaultContext() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), 20*time.Second) +} + // TestInjectionBuild tests the build where we inject files to assemble script. func TestInjectionBuild(t *testing.T) { integration(t).exerciseInjectionBuild(TagCleanBuild, FakeBuilderImage, []string{ @@ -78,13 +80,13 @@ func TestInjectionBuild(t *testing.T) { type integrationTest struct { t *testing.T - dockerClient dockertools.DockerInterface - engineClient dockerapi.Client + engineClient *dockerapi.Client setupComplete bool } func (i integrationTest) InspectImage(name string) (*dockertypes.ImageInspect, error) { - ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute) + ctx, cancel := getDefaultContext() + defer cancel() resp, _, err := i.engineClient.ImageInspectWithRaw(ctx, name, true) if err != nil { if dockerapi.IsErrImageNotFound(err) { @@ -113,32 +115,33 @@ func dockerConfig() *api.DockerConfig { return cfg } -func dockerClient(config *api.DockerConfig) (dockertools.DockerInterface, dockerapi.Client, error) { - var client *dockerapi.Client +func dockerClient(config *api.DockerConfig) (*dockerapi.Client, error) { var httpClient *http.Client + if config.CertFile != "" && config.KeyFile != "" && config.CAFile != "" { tlscOptions := tlsconfig.Options{ - CAFile: config.CAFile, - CertFile: config.CertFile, - KeyFile: config.KeyFile, + CAFile: config.CAFile, + CertFile: config.CertFile, + KeyFile: config.KeyFile, + InsecureSkipVerify: os.Getenv("DOCKER_TLS_VERIFY") == "", } - tlsc, tlsErr := tlsconfig.Client(tlscOptions) - if tlsErr != nil { - return nil, dockerapi.Client{}, tlsErr + tlsc, err := tlsconfig.Client(tlscOptions) + if err != nil { + return nil, err } + httpClient = &http.Client{ - Transport: k8snet.SetTransportDefaults(&http.Transport{ + Transport: &http.Transport{ TLSClientConfig: tlsc, - }), + }, } } - client, err := dockerapi.NewClient(config.Endpoint, "", httpClient, nil) + client, err := dockerapi.NewClient(config.Endpoint, os.Getenv("DOCKER_API_VERSION"), httpClient, nil) if err != nil { - return nil, dockerapi.Client{}, err + return nil, err } - k8sDocker := dockertools.ConnectToDockerOrDie(config.Endpoint, 0) - return k8sDocker, *client, nil + return client, nil } func getLogLevel() (level int) { @@ -153,7 +156,7 @@ func getLogLevel() (level int) { // setup sets up integration tests func (i *integrationTest) setup() { var err error - i.dockerClient, i.engineClient, err = dockerClient(dockerConfig()) + i.engineClient, err = dockerClient(dockerConfig()) if err != nil { i.t.Errorf("%+v", err) return @@ -166,7 +169,9 @@ func (i *integrationTest) setup() { FakeScriptsFileURL = "file://" + filepath.Join(testImagesDir, ".s2i", "bin") for _, image := range []string{TagCleanBuild, TagCleanBuildUser, TagIncrementalBuild, TagIncrementalBuildUser} { - i.dockerClient.RemoveImage(image, dockertypes.ImageRemoveOptions{}) + ctx, cancel := getDefaultContext() + i.engineClient.ImageRemove(ctx, image, dockertypes.ImageRemoveOptions{}) + cancel() } go http.ListenAndServe(":23456", http.FileServer(http.Dir(testImagesDir))) @@ -541,22 +546,26 @@ func (i *integrationTest) checkForImage(tag string) { } func (i *integrationTest) createContainer(image string) string { + ctx, cancel := getDefaultContext() + defer cancel() opts := dockertypes.ContainerCreateConfig{Name: "", Config: &dockercontainer.Config{Image: image}} - container, err := i.dockerClient.CreateContainer(opts) + container, err := i.engineClient.ContainerCreate(ctx, opts.Config, opts.HostConfig, opts.NetworkingConfig, opts.Name) if err != nil { i.t.Errorf("Couldn't create container from image %s with error %+v", image, err) return "" } - err = i.dockerClient.StartContainer(container.ID) + ctx, cancel = getDefaultContext() + defer cancel() + err = i.engineClient.ContainerStart(ctx, container.ID) if err != nil { i.t.Errorf("Couldn't start container: %s with error %+v", container.ID, err) return "" } - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + ctx, cancel = getDefaultContext() defer cancel() - exitCode, err := i.engineClient.ContainerWait(ctx, container.ID) + exitCode, _ := i.engineClient.ContainerWait(ctx, container.ID) if exitCode != 0 { i.t.Errorf("Bad exit code from container: %d", exitCode) return "" @@ -566,18 +575,22 @@ func (i *integrationTest) createContainer(image string) string { } func (i *integrationTest) runInContainer(image string, command []string) int { + ctx, cancel := getDefaultContext() + defer cancel() 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 { + container, err := i.engineClient.ContainerCreate(ctx, opts.Config, opts.HostConfig, opts.NetworkingConfig, opts.Name) + if err != nil { i.t.Errorf("Couldn't create container from image %s err %+v", image, err) return -1 } - err = i.dockerClient.StartContainer(container.ID) + ctx, cancel = getDefaultContext() + defer cancel() + err = i.engineClient.ContainerStart(ctx, container.ID) if err != nil { i.t.Errorf("Couldn't start container: %s", container.ID) } - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + ctx, cancel = getDefaultContext() defer cancel() exitCode, err := i.engineClient.ContainerWait(ctx, container.ID) if err != nil { @@ -587,7 +600,14 @@ func (i *integrationTest) runInContainer(image string, command []string) int { } func (i *integrationTest) removeContainer(cID string) { - i.dockerClient.RemoveContainer(cID, dockertypes.ContainerRemoveOptions{true, true, true}) + ctx, cancel := getDefaultContext() + defer cancel() + removeOpts := dockertypes.ContainerRemoveOptions{ + RemoveVolumes: true, + RemoveLinks: true, + Force: true, + } + i.engineClient.ContainerRemove(ctx, cID, removeOpts) } func (i *integrationTest) fileExists(cID string, filePath string) { @@ -631,7 +651,7 @@ func (i *integrationTest) checkIncrementalBuildState(cID string, workingDir stri } func (i *integrationTest) fileExistsInContainer(cID string, filePath string) bool { - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + ctx, cancel := getDefaultContext() defer cancel() rdr, stats, err := i.engineClient.CopyFromContainer(ctx, cID, filePath) if err != nil {