diff --git a/Godeps/.license_file_state b/Godeps/.license_file_state index 756336cd1f9..84014291fea 100644 --- a/Godeps/.license_file_state +++ b/Godeps/.license_file_state @@ -372,3 +372,11 @@ raw.githubusercontent.com/Microsoft/go-winio/master/NOTICE raw.githubusercontent.com/Microsoft/go-winio/master/NOTICE.txt raw.githubusercontent.com/Microsoft/go-winio/master/README raw.githubusercontent.com/Microsoft/go-winio/master/README.md +raw.githubusercontent.com/docker/distribution/master/NOTICE +raw.githubusercontent.com/docker/distribution/master/NOTICE.txt +raw.githubusercontent.com/docker/distribution/master/README +raw.githubusercontent.com/docker/distribution/master/README.md +raw.githubusercontent.com/golang/mock/master/NOTICE +raw.githubusercontent.com/golang/mock/master/NOTICE.txt +raw.githubusercontent.com/golang/mock/master/README +raw.githubusercontent.com/golang/mock/master/README.md diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index d28e5118f98..2d6d37ae22d 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -414,13 +414,13 @@ }, { "ImportPath": "github.com/docker/distribution/digest", - "Comment": "v2.4.0-rc.1-22-g55f1b76", - "Rev": "55f1b7651f6242617133312ff8af5c2e4e3628ea" + "Comment": "v2.4.0-rc.1-38-gcd27f17", + "Rev": "cd27f179f2c10c5d300e6d09025b538c475b0d51" }, { "ImportPath": "github.com/docker/distribution/reference", - "Comment": "v2.4.0-rc.1-22-g55f1b76", - "Rev": "55f1b7651f6242617133312ff8af5c2e4e3628ea" + "Comment": "v2.4.0-rc.1-38-gcd27f17", + "Rev": "cd27f179f2c10c5d300e6d09025b538c475b0d51" }, { "ImportPath": "github.com/docker/docker/pkg/jsonmessage", @@ -459,13 +459,13 @@ }, { "ImportPath": "github.com/docker/engine-api/client", - "Comment": "v0.2.2-173-g26cdffe", - "Rev": "26cdffeca716ae4df98070051a852b3198d7d153" + "Comment": "v0.3.1-62-g3d72d39", + "Rev": "3d72d392d07bece8d7d7b2a3b6b2e57c2df376a2" }, { "ImportPath": "github.com/docker/engine-api/types", - "Comment": "v0.2.2-173-g26cdffe", - "Rev": "26cdffeca716ae4df98070051a852b3198d7d153" + "Comment": "v0.3.1-62-g3d72d39", + "Rev": "3d72d392d07bece8d7d7b2a3b6b2e57c2df376a2" }, { "ImportPath": "github.com/docker/go-connections/nat", diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/client.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/client.go index 13aecc1be7c..8c8c6fd1828 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/client.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/client.go @@ -21,7 +21,7 @@ type Client struct { addr string // basePath holds the path to prepend to the requests. basePath string - // transport is the interface to sends request with, it implements transport.Client. + // transport is the interface to send request with, it implements transport.Client. transport transport.Client // version of the server to talk to. version string @@ -97,10 +97,14 @@ func (cli *Client) getAPIPath(p string, query url.Values) string { } else { apiPath = fmt.Sprintf("%s%s", cli.basePath, p) } - if len(query) > 0 { - apiPath += "?" + query.Encode() + + u := &url.URL{ + Path: apiPath, } - return apiPath + if len(query) > 0 { + u.RawQuery = query.Encode() + } + return u.String() } // ClientVersion returns the version string associated with this @@ -110,6 +114,12 @@ func (cli *Client) ClientVersion() string { return cli.version } +// UpdateClientVersion updates the version string associated with this +// instance of the Client. +func (cli *Client) UpdateClientVersion(v string) { + cli.version = v +} + // ParseHost verifies that the given host strings is valid. func ParseHost(host string) (string, string, string, error) { protoAddrParts := strings.SplitN(host, "://", 2) diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/client_unix.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/client_unix.go index a6d5390a7c0..572c5f87a78 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/client_unix.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/client_unix.go @@ -1,4 +1,4 @@ -// +build linux freebsd solaris +// +build linux freebsd solaris openbsd package client diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/container_attach.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/container_attach.go index d87fc655c29..1b616bf0385 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/container_attach.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/container_attach.go @@ -11,7 +11,7 @@ import ( // It returns a types.HijackedConnection with the hijacked connection // and the a reader to get output. It's up to the called to close // the hijacked connection by calling types.HijackedResponse.Close. -func (cli *Client) ContainerAttach(ctx context.Context, options types.ContainerAttachOptions) (types.HijackedResponse, error) { +func (cli *Client) ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error) { query := url.Values{} if options.Stream { query.Set("stream", "1") @@ -30,5 +30,5 @@ func (cli *Client) ContainerAttach(ctx context.Context, options types.ContainerA } headers := map[string][]string{"Content-Type": {"text/plain"}} - return cli.postHijacked(ctx, "/containers/"+options.ContainerID+"/attach", query, nil, headers) + return cli.postHijacked(ctx, "/containers/"+container+"/attach", query, nil, headers) } diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/container_commit.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/container_commit.go index 8a6c89935a9..d5c47499066 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/container_commit.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/container_commit.go @@ -2,18 +2,36 @@ package client import ( "encoding/json" + "errors" "net/url" + distreference "github.com/docker/distribution/reference" "github.com/docker/engine-api/types" + "github.com/docker/engine-api/types/reference" "golang.org/x/net/context" ) // ContainerCommit applies changes into a container and creates a new tagged image. -func (cli *Client) ContainerCommit(ctx context.Context, options types.ContainerCommitOptions) (types.ContainerCommitResponse, error) { +func (cli *Client) ContainerCommit(ctx context.Context, container string, options types.ContainerCommitOptions) (types.ContainerCommitResponse, error) { + var repository, tag string + if options.Reference != "" { + distributionRef, err := distreference.ParseNamed(options.Reference) + if err != nil { + return types.ContainerCommitResponse{}, err + } + + if _, isCanonical := distributionRef.(distreference.Canonical); isCanonical { + return types.ContainerCommitResponse{}, errors.New("refusing to create a tag with a digest reference") + } + + tag = reference.GetTagFromNamedRef(distributionRef) + repository = distributionRef.Name() + } + query := url.Values{} - query.Set("container", options.ContainerID) - query.Set("repo", options.RepositoryName) - query.Set("tag", options.Tag) + query.Set("container", container) + query.Set("repo", repository) + query.Set("tag", tag) query.Set("comment", options.Comment) query.Set("author", options.Author) for _, change := range options.Changes { diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/container_copy.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/container_copy.go index aaf1f775554..d3dd0b116c0 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/container_copy.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/container_copy.go @@ -30,17 +30,17 @@ func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path stri } // CopyToContainer copies content into the container filesystem. -func (cli *Client) CopyToContainer(ctx context.Context, options types.CopyToContainerOptions) error { +func (cli *Client) CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error { query := url.Values{} - query.Set("path", filepath.ToSlash(options.Path)) // Normalize the paths used in the API. + query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API. // Do not allow for an existing directory to be overwritten by a non-directory and vice versa. if !options.AllowOverwriteDirWithFile { query.Set("noOverwriteDirNonDir", "true") } - path := fmt.Sprintf("/containers/%s/archive", options.ContainerID) + apiPath := fmt.Sprintf("/containers/%s/archive", container) - response, err := cli.putRaw(ctx, path, query, options.Content, nil) + response, err := cli.putRaw(ctx, apiPath, query, content, nil) if err != nil { return err } @@ -53,13 +53,13 @@ func (cli *Client) CopyToContainer(ctx context.Context, options types.CopyToCont return nil } -// CopyFromContainer get the content from the container and return it as a Reader +// CopyFromContainer gets the content from the container and returns it as a Reader // to manipulate it in the host. It's up to the caller to close the reader. -func (cli *Client) CopyFromContainer(ctx context.Context, containerID, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) { +func (cli *Client) CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) { query := make(url.Values, 1) query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API. - apiPath := fmt.Sprintf("/containers/%s/archive", containerID) + apiPath := fmt.Sprintf("/containers/%s/archive", container) response, err := cli.get(ctx, apiPath, query, nil) if err != nil { return nil, types.ContainerPathStat{}, err diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/container_exec.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/container_exec.go index 159c9dfdfdb..ff7e1a9d059 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/container_exec.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/container_exec.go @@ -8,9 +8,9 @@ import ( ) // ContainerExecCreate creates a new exec configuration to run an exec process. -func (cli *Client) ContainerExecCreate(ctx context.Context, config types.ExecConfig) (types.ContainerExecCreateResponse, error) { +func (cli *Client) ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.ContainerExecCreateResponse, error) { var response types.ContainerExecCreateResponse - resp, err := cli.post(ctx, "/containers/"+config.Container+"/exec", nil, config, nil) + resp, err := cli.post(ctx, "/containers/"+container+"/exec", nil, config, nil) if err != nil { return response, err } @@ -19,7 +19,7 @@ func (cli *Client) ContainerExecCreate(ctx context.Context, config types.ExecCon return response, err } -// ContainerExecStart starts an exec process already create in the docker host. +// ContainerExecStart starts an exec process already created in the docker host. func (cli *Client) ContainerExecStart(ctx context.Context, execID string, config types.ExecStartCheck) error { resp, err := cli.post(ctx, "/exec/"+execID+"/start", nil, config, nil) ensureReaderClosed(resp) diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/container_export.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/container_export.go index 1925113ec3e..52194f3d342 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/container_export.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/container_export.go @@ -8,7 +8,7 @@ import ( ) // ContainerExport retrieves the raw contents of a container -// and returns them as a io.ReadCloser. It's up to the caller +// and returns them as an io.ReadCloser. It's up to the caller // to close the stream. func (cli *Client) ContainerExport(ctx context.Context, containerID string) (io.ReadCloser, error) { serverResp, err := cli.get(ctx, "/containers/"+containerID+"/export", url.Values{}, nil) diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/container_list.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/container_list.go index 573f41d5c60..87f7333dc7b 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/container_list.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/container_list.go @@ -35,7 +35,8 @@ func (cli *Client) ContainerList(ctx context.Context, options types.ContainerLis } if options.Filter.Len() > 0 { - filterJSON, err := filters.ToParam(options.Filter) + filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filter) + if err != nil { return nil, err } diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/container_logs.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/container_logs.go index 47c60ee3c41..9699ac7dde9 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/container_logs.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/container_logs.go @@ -13,7 +13,7 @@ import ( // ContainerLogs returns the logs generated by a container in an io.ReadCloser. // It's up to the caller to close the stream. -func (cli *Client) ContainerLogs(ctx context.Context, options types.ContainerLogsOptions) (io.ReadCloser, error) { +func (cli *Client) ContainerLogs(ctx context.Context, container string, options types.ContainerLogsOptions) (io.ReadCloser, error) { query := url.Values{} if options.ShowStdout { query.Set("stdout", "1") @@ -40,7 +40,7 @@ func (cli *Client) ContainerLogs(ctx context.Context, options types.ContainerLog } query.Set("tail", options.Tail) - resp, err := cli.get(ctx, "/containers/"+options.ContainerID+"/logs", query, nil) + resp, err := cli.get(ctx, "/containers/"+container+"/logs", query, nil) if err != nil { return nil, err } diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/container_remove.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/container_remove.go index 56796231f42..cef4b812208 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/container_remove.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/container_remove.go @@ -8,7 +8,7 @@ import ( ) // ContainerRemove kills and removes a container from the docker host. -func (cli *Client) ContainerRemove(ctx context.Context, options types.ContainerRemoveOptions) error { +func (cli *Client) ContainerRemove(ctx context.Context, containerID string, options types.ContainerRemoveOptions) error { query := url.Values{} if options.RemoveVolumes { query.Set("v", "1") @@ -21,7 +21,7 @@ func (cli *Client) ContainerRemove(ctx context.Context, options types.ContainerR query.Set("force", "1") } - resp, err := cli.delete(ctx, "/containers/"+options.ContainerID, query, nil) + resp, err := cli.delete(ctx, "/containers/"+containerID, query, nil) ensureReaderClosed(resp) return err } diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/container_resize.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/container_resize.go index 0782017497d..b95d26b335a 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/container_resize.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/container_resize.go @@ -9,13 +9,13 @@ import ( ) // ContainerResize changes the size of the tty for a container. -func (cli *Client) ContainerResize(ctx context.Context, options types.ResizeOptions) error { - return cli.resize(ctx, "/containers/"+options.ID, options.Height, options.Width) +func (cli *Client) ContainerResize(ctx context.Context, containerID string, options types.ResizeOptions) error { + return cli.resize(ctx, "/containers/"+containerID, options.Height, options.Width) } // ContainerExecResize changes the size of the tty for an exec process running inside a container. -func (cli *Client) ContainerExecResize(ctx context.Context, options types.ResizeOptions) error { - return cli.resize(ctx, "/exec/"+options.ID, options.Height, options.Width) +func (cli *Client) ContainerExecResize(ctx context.Context, execID string, options types.ResizeOptions) error { + return cli.resize(ctx, "/exec/"+execID, options.Height, options.Width) } func (cli *Client) resize(ctx context.Context, basePath string, height, width int) error { diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/container_wait.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/container_wait.go index ca8c443bd04..c26ff3f3786 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/container_wait.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/container_wait.go @@ -8,7 +8,7 @@ import ( "github.com/docker/engine-api/types" ) -// ContainerWait pauses execution util a container is exits. +// ContainerWait pauses execution until a container exits. // It returns the API status code as response of its readiness. func (cli *Client) ContainerWait(ctx context.Context, containerID string) (int, error) { resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", nil, nil, nil) diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/errors.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/errors.go index 9bcc78eb443..17828bb7168 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/errors.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/errors.go @@ -5,7 +5,7 @@ import ( "fmt" ) -// ErrConnectionFailed is a error raised when the connection between the client and the server failed. +// ErrConnectionFailed is an error raised when the connection between the client and the server failed. var ErrConnectionFailed = errors.New("Cannot connect to the Docker daemon. Is the docker daemon running on this host?") // imageNotFoundError implements an error returned when an image is not in the docker host. @@ -30,7 +30,7 @@ type containerNotFoundError struct { containerID string } -// Error returns a string representation of an containerNotFoundError +// Error returns a string representation of a containerNotFoundError func (e containerNotFoundError) Error() string { return fmt.Sprintf("Error: No such container: %s", e.containerID) } @@ -47,7 +47,7 @@ type networkNotFoundError struct { networkID string } -// Error returns a string representation of an networkNotFoundError +// Error returns a string representation of a networkNotFoundError func (e networkNotFoundError) Error() string { return fmt.Sprintf("Error: No such network: %s", e.networkID) } @@ -64,7 +64,7 @@ type volumeNotFoundError struct { volumeID string } -// Error returns a string representation of an networkNotFoundError +// Error returns a string representation of a networkNotFoundError func (e volumeNotFoundError) Error() string { return fmt.Sprintf("Error: No such volume: %s", e.volumeID) } @@ -87,7 +87,7 @@ func (u unauthorizedError) Error() string { } // IsErrUnauthorized returns true if the error is caused -// when an the remote registry authentication fails +// when a remote registry authentication fails func IsErrUnauthorized(err error) bool { _, ok := err.(unauthorizedError) return ok diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/hijack.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/hijack.go index 8102d481b4d..dbd91ef6299 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/hijack.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/hijack.go @@ -46,8 +46,7 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu req.Header.Set("Connection", "Upgrade") req.Header.Set("Upgrade", "tcp") - tlsConfig := cli.transport.TLSConfig() - conn, err := dial(cli.proto, cli.addr, tlsConfig) + conn, err := dial(cli.proto, cli.addr, cli.transport.TLSConfig()) if err != nil { if strings.Contains(err.Error(), "connection refused") { return types.HijackedResponse{}, fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?") @@ -69,11 +68,11 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu defer clientconn.Close() // Server hijacks the connection, error 'connection closed' expected - clientconn.Do(req) + _, err = clientconn.Do(req) rwc, br := clientconn.Hijack() - return types.HijackedResponse{Conn: rwc, Reader: br}, nil + return types.HijackedResponse{Conn: rwc, Reader: br}, err } func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) { @@ -126,6 +125,21 @@ func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Con tcpConn.SetKeepAlivePeriod(30 * time.Second) } + colonPos := strings.LastIndex(addr, ":") + if colonPos == -1 { + colonPos = len(addr) + } + hostname := addr[:colonPos] + + // If no ServerName is set, infer the ServerName + // from the hostname we're connecting to. + if config.ServerName == "" { + // Make a copy to avoid polluting argument or default. + c := *config + c.ServerName = hostname + config = &c + } + conn := tls.Client(rawConn, config) if timeout == 0 { diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/image_build.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/image_build.go index d5f96cbd544..4165c4e9a84 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/image_build.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/image_build.go @@ -3,6 +3,7 @@ package client import ( "encoding/base64" "encoding/json" + "io" "net/http" "net/url" "regexp" @@ -20,7 +21,7 @@ var headerRegexp = regexp.MustCompile(`\ADocker/.+\s\((.+)\)\z`) // ImageBuild sends request to the daemon to build images. // The Body in the response implement an io.ReadCloser and it's up to the caller to // close it. -func (cli *Client) ImageBuild(ctx context.Context, options types.ImageBuildOptions) (types.ImageBuildResponse, error) { +func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) { query, err := imageBuildOptionsToQuery(options) if err != nil { return types.ImageBuildResponse{}, err @@ -34,7 +35,7 @@ func (cli *Client) ImageBuild(ctx context.Context, options types.ImageBuildOptio headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf)) headers.Set("Content-Type", "application/tar") - serverResp, err := cli.postRaw(ctx, "/build", query, options.Context, headers) + serverResp, err := cli.postRaw(ctx, "/build", query, buildContext, headers) if err != nil { return types.ImageBuildResponse{}, err } @@ -101,6 +102,11 @@ func imageBuildOptionsToQuery(options types.ImageBuildOptions) (url.Values, erro } query.Set("buildargs", string(buildArgsJSON)) + labelsJSON, err := json.Marshal(options.Labels) + if err != nil { + return query, err + } + query.Set("labels", string(labelsJSON)) return query, nil } diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/image_create.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/image_create.go index 1ec1f9d09b5..6dfc0391c00 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/image_create.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/image_create.go @@ -7,14 +7,20 @@ import ( "golang.org/x/net/context" "github.com/docker/engine-api/types" + "github.com/docker/engine-api/types/reference" ) // ImageCreate creates a new image based in the parent options. // It returns the JSON content in the response body. -func (cli *Client) ImageCreate(ctx context.Context, options types.ImageCreateOptions) (io.ReadCloser, error) { +func (cli *Client) ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) { + repository, tag, err := reference.Parse(parentReference) + if err != nil { + return nil, err + } + query := url.Values{} - query.Set("fromImage", options.Parent) - query.Set("tag", options.Tag) + query.Set("fromImage", repository) + query.Set("tag", tag) resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth) if err != nil { return nil, err diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/image_import.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/image_import.go index 48e2c951a47..4e8749a01d5 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/image_import.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/image_import.go @@ -6,22 +6,30 @@ import ( "golang.org/x/net/context" + "github.com/docker/distribution/reference" "github.com/docker/engine-api/types" ) // ImageImport creates a new image based in the source options. // It returns the JSON content in the response body. -func (cli *Client) ImageImport(ctx context.Context, options types.ImageImportOptions) (io.ReadCloser, error) { +func (cli *Client) ImageImport(ctx context.Context, source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) { + if ref != "" { + //Check if the given image name can be resolved + if _, err := reference.ParseNamed(ref); err != nil { + return nil, err + } + } + query := url.Values{} - query.Set("fromSrc", options.SourceName) - query.Set("repo", options.RepositoryName) + query.Set("fromSrc", source.SourceName) + query.Set("repo", ref) query.Set("tag", options.Tag) query.Set("message", options.Message) for _, change := range options.Changes { query.Add("changes", change) } - resp, err := cli.postRaw(ctx, "/images/create", query, options.Source, nil) + resp, err := cli.postRaw(ctx, "/images/create", query, source.Source, nil) if err != nil { return nil, err } diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/image_inspect.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/image_inspect.go index 761a994c580..859ba640869 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/image_inspect.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/image_inspect.go @@ -11,7 +11,7 @@ import ( "golang.org/x/net/context" ) -// ImageInspectWithRaw returns the image information and it's raw representation. +// ImageInspectWithRaw returns the image information and its raw representation. func (cli *Client) ImageInspectWithRaw(ctx context.Context, imageID string, getSize bool) (types.ImageInspect, []byte, error) { query := url.Values{} if getSize { diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/image_pull.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/image_pull.go index 09044376507..0584f00bd40 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/image_pull.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/image_pull.go @@ -8,22 +8,32 @@ import ( "golang.org/x/net/context" "github.com/docker/engine-api/types" + "github.com/docker/engine-api/types/reference" ) -// ImagePull request the docker host to pull an image from a remote registry. +// ImagePull requests the docker host to pull an image from a remote registry. // It executes the privileged function if the operation is unauthorized // and it tries one more time. // It's up to the caller to handle the io.ReadCloser and close it properly. -func (cli *Client) ImagePull(ctx context.Context, options types.ImagePullOptions, privilegeFunc RequestPrivilegeFunc) (io.ReadCloser, error) { +// +// FIXME(vdemeester): there is currently used in a few way in docker/docker +// - if not in trusted content, ref is used to pass the whole reference, and tag is empty +// - if in trusted content, ref is used to pass the reference name, and tag for the digest +func (cli *Client) ImagePull(ctx context.Context, ref string, options types.ImagePullOptions) (io.ReadCloser, error) { + repository, tag, err := reference.Parse(ref) + if err != nil { + return nil, err + } + query := url.Values{} - query.Set("fromImage", options.ImageID) - if options.Tag != "" { - query.Set("tag", options.Tag) + query.Set("fromImage", repository) + if tag != "" { + query.Set("tag", tag) } resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth) if resp.statusCode == http.StatusUnauthorized { - newAuthHeader, privilegeErr := privilegeFunc() + newAuthHeader, privilegeErr := options.PrivilegeFunc() if privilegeErr != nil { return nil, privilegeErr } diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/image_push.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/image_push.go index ca2cb43b7f4..8134f8018cf 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/image_push.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/image_push.go @@ -1,30 +1,44 @@ package client import ( + "errors" "io" "net/http" "net/url" "golang.org/x/net/context" + distreference "github.com/docker/distribution/reference" "github.com/docker/engine-api/types" + "github.com/docker/engine-api/types/reference" ) -// ImagePush request the docker host to push an image to a remote registry. +// ImagePush requests the docker host to push an image to a remote registry. // It executes the privileged function if the operation is unauthorized // and it tries one more time. // It's up to the caller to handle the io.ReadCloser and close it properly. -func (cli *Client) ImagePush(ctx context.Context, options types.ImagePushOptions, privilegeFunc RequestPrivilegeFunc) (io.ReadCloser, error) { - query := url.Values{} - query.Set("tag", options.Tag) +func (cli *Client) ImagePush(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error) { + distributionRef, err := distreference.ParseNamed(ref) + if err != nil { + return nil, err + } - resp, err := cli.tryImagePush(ctx, options.ImageID, query, options.RegistryAuth) + if _, isCanonical := distributionRef.(distreference.Canonical); isCanonical { + return nil, errors.New("cannot push a digest reference") + } + + tag := reference.GetTagFromNamedRef(distributionRef) + + query := url.Values{} + query.Set("tag", tag) + + resp, err := cli.tryImagePush(ctx, distributionRef.Name(), query, options.RegistryAuth) if resp.statusCode == http.StatusUnauthorized { - newAuthHeader, privilegeErr := privilegeFunc() + newAuthHeader, privilegeErr := options.PrivilegeFunc() if privilegeErr != nil { return nil, privilegeErr } - resp, err = cli.tryImagePush(ctx, options.ImageID, query, newAuthHeader) + resp, err = cli.tryImagePush(ctx, distributionRef.Name(), query, newAuthHeader) } if err != nil { return nil, err diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/image_remove.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/image_remove.go index d7e71c89ced..47224326e0c 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/image_remove.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/image_remove.go @@ -9,7 +9,7 @@ import ( ) // ImageRemove removes an image from the docker host. -func (cli *Client) ImageRemove(ctx context.Context, options types.ImageRemoveOptions) ([]types.ImageDelete, error) { +func (cli *Client) ImageRemove(ctx context.Context, imageID string, options types.ImageRemoveOptions) ([]types.ImageDelete, error) { query := url.Values{} if options.Force { @@ -19,7 +19,7 @@ func (cli *Client) ImageRemove(ctx context.Context, options types.ImageRemoveOpt query.Set("noprune", "1") } - resp, err := cli.delete(ctx, "/images/"+options.ImageID, query, nil) + resp, err := cli.delete(ctx, "/images/"+imageID, query, nil) if err != nil { return nil, err } diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/image_save.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/image_save.go index c0feb3ede5c..ecac880a32c 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/image_save.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/image_save.go @@ -7,7 +7,7 @@ import ( "golang.org/x/net/context" ) -// ImageSave retrieves one or more images from the docker host as a io.ReadCloser. +// ImageSave retrieves one or more images from the docker host as an io.ReadCloser. // It's up to the caller to store the images and close the stream. func (cli *Client) ImageSave(ctx context.Context, imageIDs []string) (io.ReadCloser, error) { query := url.Values{ diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/image_search.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/image_search.go index ebe9dc0b6ef..3528bda6bd8 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/image_search.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/image_search.go @@ -12,14 +12,14 @@ import ( // ImageSearch makes the docker host to search by a term in a remote registry. // The list of results is not sorted in any fashion. -func (cli *Client) ImageSearch(ctx context.Context, options types.ImageSearchOptions, privilegeFunc RequestPrivilegeFunc) ([]registry.SearchResult, error) { +func (cli *Client) ImageSearch(ctx context.Context, term string, options types.ImageSearchOptions) ([]registry.SearchResult, error) { var results []registry.SearchResult query := url.Values{} - query.Set("term", options.Term) + query.Set("term", term) resp, err := cli.tryImageSearch(ctx, query, options.RegistryAuth) if resp.statusCode == http.StatusUnauthorized { - newAuthHeader, privilegeErr := privilegeFunc() + newAuthHeader, privilegeErr := options.PrivilegeFunc() if privilegeErr != nil { return results, privilegeErr } diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/image_tag.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/image_tag.go index 20feda38c1d..490de4e5fea 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/image_tag.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/image_tag.go @@ -1,22 +1,38 @@ package client import ( + "errors" + "fmt" "net/url" - "github.com/docker/engine-api/types" "golang.org/x/net/context" + + distreference "github.com/docker/distribution/reference" + "github.com/docker/engine-api/types" + "github.com/docker/engine-api/types/reference" ) // ImageTag tags an image in the docker host -func (cli *Client) ImageTag(ctx context.Context, options types.ImageTagOptions) error { +func (cli *Client) ImageTag(ctx context.Context, imageID, ref string, options types.ImageTagOptions) error { + distributionRef, err := distreference.ParseNamed(ref) + if err != nil { + return fmt.Errorf("Error parsing reference: %q is not a valid repository/tag", ref) + } + + if _, isCanonical := distributionRef.(distreference.Canonical); isCanonical { + return errors.New("refusing to create a tag with a digest reference") + } + + tag := reference.GetTagFromNamedRef(distributionRef) + query := url.Values{} - query.Set("repo", options.RepositoryName) - query.Set("tag", options.Tag) + query.Set("repo", distributionRef.Name()) + query.Set("tag", tag) if options.Force { query.Set("force", "1") } - resp, err := cli.post(ctx, "/images/"+options.ImageID+"/tag", query, nil, nil) + resp, err := cli.post(ctx, "/images/"+imageID+"/tag", query, nil, nil) ensureReaderClosed(resp) return err } diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/interface.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/interface.go index e95ed555677..2c6872f534b 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/interface.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/interface.go @@ -15,59 +15,60 @@ import ( // APIClient is an interface that clients that talk with a docker server must implement. type APIClient interface { ClientVersion() string - ContainerAttach(ctx context.Context, options types.ContainerAttachOptions) (types.HijackedResponse, error) - ContainerCommit(ctx context.Context, options types.ContainerCommitOptions) (types.ContainerCommitResponse, error) + ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error) + ContainerCommit(ctx context.Context, container string, options types.ContainerCommitOptions) (types.ContainerCommitResponse, error) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (types.ContainerCreateResponse, error) - ContainerDiff(ctx context.Context, ontainerID string) ([]types.ContainerChange, error) + ContainerDiff(ctx context.Context, container string) ([]types.ContainerChange, error) ContainerExecAttach(ctx context.Context, execID string, config types.ExecConfig) (types.HijackedResponse, error) - ContainerExecCreate(ctx context.Context, config types.ExecConfig) (types.ContainerExecCreateResponse, error) + ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.ContainerExecCreateResponse, error) ContainerExecInspect(ctx context.Context, execID string) (types.ContainerExecInspect, error) - ContainerExecResize(ctx context.Context, options types.ResizeOptions) error + ContainerExecResize(ctx context.Context, execID string, options types.ResizeOptions) error ContainerExecStart(ctx context.Context, execID string, config types.ExecStartCheck) error - ContainerExport(ctx context.Context, containerID string) (io.ReadCloser, error) - ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) - ContainerInspectWithRaw(ctx context.Context, containerID string, getSize bool) (types.ContainerJSON, []byte, error) - ContainerKill(ctx context.Context, containerID, signal string) error + ContainerExport(ctx context.Context, container string) (io.ReadCloser, error) + ContainerInspect(ctx context.Context, container string) (types.ContainerJSON, error) + ContainerInspectWithRaw(ctx context.Context, container string, getSize bool) (types.ContainerJSON, []byte, error) + ContainerKill(ctx context.Context, container, signal string) error ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) - ContainerLogs(ctx context.Context, options types.ContainerLogsOptions) (io.ReadCloser, error) - ContainerPause(ctx context.Context, containerID string) error - ContainerRemove(ctx context.Context, options types.ContainerRemoveOptions) error - ContainerRename(ctx context.Context, containerID, newContainerName string) error - ContainerResize(ctx context.Context, options types.ResizeOptions) error - ContainerRestart(ctx context.Context, containerID string, timeout int) error - ContainerStatPath(ctx context.Context, containerID, path string) (types.ContainerPathStat, error) - ContainerStats(ctx context.Context, containerID string, stream bool) (io.ReadCloser, error) - ContainerStart(ctx context.Context, containerID string) error - ContainerStop(ctx context.Context, containerID string, timeout int) error - ContainerTop(ctx context.Context, containerID string, arguments []string) (types.ContainerProcessList, error) - ContainerUnpause(ctx context.Context, containerID string) error - ContainerUpdate(ctx context.Context, containerID string, updateConfig container.UpdateConfig) error - ContainerWait(ctx context.Context, containerID string) (int, error) - CopyFromContainer(ctx context.Context, containerID, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) - CopyToContainer(ctx context.Context, options types.CopyToContainerOptions) error + ContainerLogs(ctx context.Context, container string, options types.ContainerLogsOptions) (io.ReadCloser, error) + ContainerPause(ctx context.Context, container string) error + ContainerRemove(ctx context.Context, container string, options types.ContainerRemoveOptions) error + ContainerRename(ctx context.Context, container, newContainerName string) error + ContainerResize(ctx context.Context, container string, options types.ResizeOptions) error + ContainerRestart(ctx context.Context, container string, timeout int) error + ContainerStatPath(ctx context.Context, container, path string) (types.ContainerPathStat, error) + ContainerStats(ctx context.Context, container string, stream bool) (io.ReadCloser, error) + ContainerStart(ctx context.Context, container string) error + ContainerStop(ctx context.Context, container string, timeout int) error + ContainerTop(ctx context.Context, container string, arguments []string) (types.ContainerProcessList, error) + ContainerUnpause(ctx context.Context, container string) error + ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) error + ContainerWait(ctx context.Context, container string) (int, error) + CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) + CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error Events(ctx context.Context, options types.EventsOptions) (io.ReadCloser, error) - ImageBuild(ctx context.Context, options types.ImageBuildOptions) (types.ImageBuildResponse, error) - ImageCreate(ctx context.Context, options types.ImageCreateOptions) (io.ReadCloser, error) - ImageHistory(ctx context.Context, imageID string) ([]types.ImageHistory, error) - ImageImport(ctx context.Context, options types.ImageImportOptions) (io.ReadCloser, error) - ImageInspectWithRaw(ctx context.Context, imageID string, getSize bool) (types.ImageInspect, []byte, error) + ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) + ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) + ImageHistory(ctx context.Context, image string) ([]types.ImageHistory, error) + ImageImport(ctx context.Context, source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) + ImageInspectWithRaw(ctx context.Context, image string, getSize bool) (types.ImageInspect, []byte, error) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.Image, error) ImageLoad(ctx context.Context, input io.Reader, quiet bool) (types.ImageLoadResponse, error) - ImagePull(ctx context.Context, options types.ImagePullOptions, privilegeFunc RequestPrivilegeFunc) (io.ReadCloser, error) - ImagePush(ctx context.Context, options types.ImagePushOptions, privilegeFunc RequestPrivilegeFunc) (io.ReadCloser, error) - ImageRemove(ctx context.Context, options types.ImageRemoveOptions) ([]types.ImageDelete, error) - ImageSearch(ctx context.Context, options types.ImageSearchOptions, privilegeFunc RequestPrivilegeFunc) ([]registry.SearchResult, error) - ImageSave(ctx context.Context, imageIDs []string) (io.ReadCloser, error) - ImageTag(ctx context.Context, options types.ImageTagOptions) error + ImagePull(ctx context.Context, ref string, options types.ImagePullOptions) (io.ReadCloser, error) + ImagePush(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error) + ImageRemove(ctx context.Context, image string, options types.ImageRemoveOptions) ([]types.ImageDelete, error) + ImageSearch(ctx context.Context, term string, options types.ImageSearchOptions) ([]registry.SearchResult, error) + ImageSave(ctx context.Context, images []string) (io.ReadCloser, error) + ImageTag(ctx context.Context, image, ref string, options types.ImageTagOptions) error Info(ctx context.Context) (types.Info, error) - NetworkConnect(ctx context.Context, networkID, containerID string, config *network.EndpointSettings) error - NetworkCreate(ctx context.Context, options types.NetworkCreate) (types.NetworkCreateResponse, error) - NetworkDisconnect(ctx context.Context, networkID, containerID string, force bool) error + NetworkConnect(ctx context.Context, networkID, container string, config *network.EndpointSettings) error + NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) + NetworkDisconnect(ctx context.Context, networkID, container string, force bool) error NetworkInspect(ctx context.Context, networkID string) (types.NetworkResource, error) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) NetworkRemove(ctx context.Context, networkID string) error RegistryLogin(ctx context.Context, auth types.AuthConfig) (types.AuthResponse, error) ServerVersion(ctx context.Context) (types.Version, error) + UpdateClientVersion(v string) VolumeCreate(ctx context.Context, options types.VolumeCreateRequest) (types.Volume, error) VolumeInspect(ctx context.Context, volumeID string) (types.Volume, error) VolumeList(ctx context.Context, filter filters.Args) (types.VolumesListResponse, error) diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/network_create.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/network_create.go index 2c41ad7ec46..c9c0b9fde77 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/network_create.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/network_create.go @@ -8,9 +8,13 @@ import ( ) // NetworkCreate creates a new network in the docker host. -func (cli *Client) NetworkCreate(ctx context.Context, options types.NetworkCreate) (types.NetworkCreateResponse, error) { +func (cli *Client) NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) { + networkCreateRequest := types.NetworkCreateRequest{ + NetworkCreate: options, + Name: name, + } var response types.NetworkCreateResponse - serverResp, err := cli.post(ctx, "/networks/create", nil, options, nil) + serverResp, err := cli.post(ctx, "/networks/create", nil, networkCreateRequest, nil) if err != nil { return response, err } diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/privileged.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/privileged.go deleted file mode 100644 index 945f18cef56..00000000000 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/privileged.go +++ /dev/null @@ -1,9 +0,0 @@ -package client - -// RequestPrivilegeFunc is a function interface that -// clients can supply to retry operations after -// getting an authorization error. -// This function returns the registry authentication -// header value in base 64 format, or an error -// if the privilege request fails. -type RequestPrivilegeFunc func() (string, error) diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/request.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/request.go index f45182399c4..cdbb0975bd2 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/request.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/request.go @@ -56,12 +56,14 @@ func (cli *Client) delete(ctx context.Context, path string, query url.Values, he } func (cli *Client) sendRequest(ctx context.Context, method, path string, query url.Values, obj interface{}, headers map[string][]string) (*serverResponse, error) { - body, err := encodeData(obj) - if err != nil { - return nil, err - } + var body io.Reader - if body != nil { + if obj != nil { + var err error + body, err = encodeData(obj) + if err != nil { + return nil, err + } if headers == nil { headers = make(map[string][]string) } @@ -83,6 +85,11 @@ func (cli *Client) sendClientRequest(ctx context.Context, method, path string, q } req, err := cli.newRequest(method, path, query, body, headers) + if cli.proto == "unix" || cli.proto == "npipe" { + // For local communications, it doesn't matter what the host is. We just + // need a valid and meaningful host name. (See #189) + req.Host = "docker" + } req.URL.Host = cli.addr req.URL.Scheme = cli.transport.Scheme() diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/transport/cancellable/LICENSE b/Godeps/_workspace/src/github.com/docker/engine-api/client/transport/cancellable/LICENSE new file mode 100644 index 00000000000..6a66aea5eaf --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/transport/cancellable/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/client/transport/transport.go b/Godeps/_workspace/src/github.com/docker/engine-api/client/transport/transport.go index 9e0095f3e42..ff28af1855f 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/client/transport/transport.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/client/transport/transport.go @@ -4,7 +4,6 @@ package transport import ( "fmt" "net/http" - "strings" "github.com/docker/go-connections/sockets" ) @@ -35,10 +34,6 @@ func NewTransportWithHTTP(proto, addr string, client *http.Client) (Client, erro } } - if transport.TLSClientConfig != nil && transport.TLSClientConfig.ServerName == "" { - transport.TLSClientConfig.ServerName = hostname(addr) - } - return &apiTransport{ Client: client, tlsInfo: &tlsInfo{transport.TLSClientConfig}, @@ -59,12 +54,4 @@ func defaultTransport(proto, addr string) *http.Transport { return tr } -func hostname(addr string) string { - colonPos := strings.LastIndex(addr, ":") - if colonPos == -1 { - return addr - } - return addr[:colonPos] -} - var _ Client = &apiTransport{} diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/types/blkiodev/blkio.go b/Godeps/_workspace/src/github.com/docker/engine-api/types/blkiodev/blkio.go index 458a9c96bbb..931ae10ab1e 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/types/blkiodev/blkio.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/types/blkiodev/blkio.go @@ -2,7 +2,7 @@ package blkiodev import "fmt" -// WeightDevice is a structure that hold device:weight pair +// WeightDevice is a structure that holds device:weight pair type WeightDevice struct { Path string Weight uint16 @@ -12,7 +12,7 @@ func (w *WeightDevice) String() string { return fmt.Sprintf("%s:%d", w.Path, w.Weight) } -// ThrottleDevice is a structure that hold device:rate_per_second pair +// ThrottleDevice is a structure that holds device:rate_per_second pair type ThrottleDevice struct { Path string Rate uint64 diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/types/client.go b/Godeps/_workspace/src/github.com/docker/engine-api/types/client.go index 48801403672..a3453414901 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/types/client.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/types/client.go @@ -12,24 +12,21 @@ import ( // ContainerAttachOptions holds parameters to attach to a container. type ContainerAttachOptions struct { - ContainerID string - Stream bool - Stdin bool - Stdout bool - Stderr bool - DetachKeys string + Stream bool + Stdin bool + Stdout bool + Stderr bool + DetachKeys string } // ContainerCommitOptions holds parameters to commit changes into a container. type ContainerCommitOptions struct { - ContainerID string - RepositoryName string - Tag string - Comment string - Author string - Changes []string - Pause bool - Config *container.Config + Reference string + Comment string + Author string + Changes []string + Pause bool + Config *container.Config } // ContainerExecInspect holds information returned by exec inspect. @@ -54,18 +51,16 @@ type ContainerListOptions struct { // ContainerLogsOptions holds parameters to filter logs with. type ContainerLogsOptions struct { - ContainerID string - ShowStdout bool - ShowStderr bool - Since string - Timestamps bool - Follow bool - Tail string + ShowStdout bool + ShowStderr bool + Since string + Timestamps bool + Follow bool + Tail string } // ContainerRemoveOptions holds parameters to remove containers. type ContainerRemoveOptions struct { - ContainerID string RemoveVolumes bool RemoveLinks bool Force bool @@ -74,9 +69,6 @@ type ContainerRemoveOptions struct { // CopyToContainerOptions holds information // about files to copy into a container type CopyToContainerOptions struct { - ContainerID string - Path string - Content io.Reader AllowOverwriteDirWithFile bool } @@ -103,7 +95,7 @@ func (h *HijackedResponse) Close() { h.Conn.Close() } -// CloseWriter is an interface that implement structs +// CloseWriter is an interface that implements structs // that close input streams to prevent from writing. type CloseWriter interface { CloseWrite() error @@ -142,6 +134,7 @@ type ImageBuildOptions struct { BuildArgs map[string]string AuthConfigs map[string]AuthConfig Context io.Reader + Labels map[string]string } // ImageBuildResponse holds information @@ -154,19 +147,20 @@ type ImageBuildResponse struct { // ImageCreateOptions holds information to create images. type ImageCreateOptions struct { - Parent string // Parent is the name of the image to pull - Tag string // Tag is the name to tag this image with RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry } +// ImageImportSource holds source information for ImageImport +type ImageImportSource struct { + Source io.Reader // Source is the data to send to the server to create this image from (mutually exclusive with SourceName) + SourceName string // SourceName is the name of the image to pull (mutually exclusive with Source) +} + // ImageImportOptions holds information to import images from the client host. type ImageImportOptions struct { - Source io.Reader // Source is the data to send to the server to create this image from (mutually exclusive with SourceName) - SourceName string // SourceName is the name of the image to pull (mutually exclusive with Source) - RepositoryName string // RepositoryName is the name of the repository to import this image into - Message string // Message is the message to tag the image with - Tag string // Tag is the name to tag this image with - Changes []string // Changes are the raw changes to apply to this image + Tag string // Tag is the name to tag this image with. This attribute is deprecated. + Message string // Message is the message to tag the image with + Changes []string // Changes are the raw changes to apply to this image } // ImageListOptions holds parameters to filter the list of images with. @@ -184,40 +178,42 @@ type ImageLoadResponse struct { // ImagePullOptions holds information to pull images. type ImagePullOptions struct { - ImageID string // ImageID is the name of the image to pull - Tag string // Tag is the name of the tag to be pulled - RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry + RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry + PrivilegeFunc RequestPrivilegeFunc } +// RequestPrivilegeFunc is a function interface that +// clients can supply to retry operations after +// getting an authorization error. +// This function returns the registry authentication +// header value in base 64 format, or an error +// if the privilege request fails. +type RequestPrivilegeFunc func() (string, error) + //ImagePushOptions holds information to push images. type ImagePushOptions ImagePullOptions // ImageRemoveOptions holds parameters to remove images. type ImageRemoveOptions struct { - ImageID string Force bool PruneChildren bool } // ImageSearchOptions holds parameters to search images with. type ImageSearchOptions struct { - Term string - RegistryAuth string + RegistryAuth string + PrivilegeFunc RequestPrivilegeFunc } // ImageTagOptions holds parameters to tag an image type ImageTagOptions struct { - ImageID string - RepositoryName string - Tag string - Force bool + Force bool } // ResizeOptions holds parameters to resize a tty. // It can be used to resize container ttys and // exec process ttys too. type ResizeOptions struct { - ID string Height int Width int } @@ -228,7 +224,7 @@ type VersionResponse struct { Server *Version } -// ServerOK return true when the client could connect to the docker server +// ServerOK returns true when the client could connect to the docker server // and parse the information received. It returns false otherwise. func (v VersionResponse) ServerOK() bool { return v.Server != nil diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/types/configs.go b/Godeps/_workspace/src/github.com/docker/engine-api/types/configs.go index 6874a037df1..7d4fcb343e3 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/types/configs.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/types/configs.go @@ -38,13 +38,12 @@ type ContainerCommitConfig struct { Config *container.Config } -// ExecConfig is a small subset of the Config struct that hold the configuration +// ExecConfig is a small subset of the Config struct that holds the configuration // for the exec feature of docker. type ExecConfig struct { User string // User that will run the command Privileged bool // Is the container in privileged mode Tty bool // Attach standard streams to a tty. - Container string // Name of the container (to execute in) AttachStdin bool // Attach the standard input, makes possible user interaction AttachStderr bool // Attach the standard output AttachStdout bool // Attach the standard error diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/types/container/config.go b/Godeps/_workspace/src/github.com/docker/engine-api/types/container/config.go index b8747a50874..1dfc4083480 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/types/container/config.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/types/container/config.go @@ -19,7 +19,6 @@ type Config struct { AttachStdout bool // Attach the standard output AttachStderr bool // Attach the standard error ExposedPorts map[nat.Port]struct{} `json:",omitempty"` // List of exposed ports - PublishService string `json:",omitempty"` // Name of the network service exposed by the container Tty bool // Attach standard streams to a tty, including stdin if it is not closed. OpenStdin bool // Open stdin StdinOnce bool // If true, close stdin after the 1 attached client disconnects. diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/types/container/host_config.go b/Godeps/_workspace/src/github.com/docker/engine-api/types/container/host_config.go index a1b503f8625..39f6a225169 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/types/container/host_config.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/types/container/host_config.go @@ -25,7 +25,7 @@ func (i Isolation) IsDefault() bool { // IpcMode represents the container ipc stack. type IpcMode string -// IsPrivate indicates whether the container uses it's private ipc stack. +// IsPrivate indicates whether the container uses its private ipc stack. func (n IpcMode) IsPrivate() bool { return !(n.IsHost() || n.IsContainer()) } @@ -89,14 +89,16 @@ func (n UsernsMode) Valid() bool { return true } -// Cgroup Spec represents the cgroup to use for the container. +// CgroupSpec represents the cgroup to use for the container. type CgroupSpec string +// IsContainer indicates whether the container is using another container cgroup func (c CgroupSpec) IsContainer() bool { parts := strings.SplitN(string(c), ":", 2) return len(parts) > 1 && parts[0] == "container" } +// Valid indicates whether the cgroup spec is valid. func (c CgroupSpec) Valid() bool { return c.IsContainer() || c == "" } @@ -113,7 +115,7 @@ func (c CgroupSpec) Container() string { // UTSMode represents the UTS namespace of the container. type UTSMode string -// IsPrivate indicates whether the container uses it's private UTS namespace. +// IsPrivate indicates whether the container uses its private UTS namespace. func (n UTSMode) IsPrivate() bool { return !(n.IsHost()) } @@ -137,7 +139,7 @@ func (n UTSMode) Valid() bool { // PidMode represents the pid stack of the container. type PidMode string -// IsPrivate indicates whether the container uses it's private pid stack. +// IsPrivate indicates whether the container uses its private pid stack. func (n PidMode) IsPrivate() bool { return !(n.IsHost()) } @@ -184,7 +186,7 @@ func (rp *RestartPolicy) IsAlways() bool { } // IsOnFailure indicates whether the container has the "on-failure" restart policy. -// This means the contain will automatically restart of exiting with a non-zero exit status. +// This means the container will automatically restart of exiting with a non-zero exit status. func (rp *RestartPolicy) IsOnFailure() bool { return rp.Name == "on-failure" } @@ -236,11 +238,11 @@ type Resources struct { Ulimits []*units.Ulimit // List of ulimits to be set in the container // Applicable to Windows - CPUCount int64 `json:"CpuCount"` // CPU count - CPUPercent int64 `json:"CpuPercent"` // CPU percent - BlkioIOps uint64 // Maximum IOps for the container system drive - BlkioBps uint64 // Maximum Bytes per second for the container system drive - SandboxSize uint64 // System drive will be expanded to at least this size (in bytes) + CPUCount int64 `json:"CpuCount"` // CPU count + CPUPercent int64 `json:"CpuPercent"` // CPU percent + IOMaximumIOps uint64 // Maximum IOps for the container system drive + IOMaximumBandwidth uint64 // Maximum IO in bytes per second for the container system drive + NetworkMaximumBandwidth uint64 // Maximum bandwidth of the network endpoint in bytes per second } // UpdateConfig holds the mutable attributes of a Container. diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/types/container/hostconfig_windows.go b/Godeps/_workspace/src/github.com/docker/engine-api/types/container/hostconfig_windows.go index 5726a77e0d4..0ee332ba689 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/types/container/hostconfig_windows.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/types/container/hostconfig_windows.go @@ -32,7 +32,7 @@ func (n NetworkMode) IsHost() bool { return false } -// IsPrivate indicates whether container uses it's private network stack. +// IsPrivate indicates whether container uses its private network stack. func (n NetworkMode) IsPrivate() bool { return !(n.IsHost() || n.IsContainer()) } diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/types/filters/parse.go b/Godeps/_workspace/src/github.com/docker/engine-api/types/filters/parse.go index 9c80b1eddbe..0e0d7e38054 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/types/filters/parse.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/types/filters/parse.go @@ -8,12 +8,14 @@ import ( "fmt" "regexp" "strings" + + "github.com/docker/engine-api/types/versions" ) // Args stores filter arguments as map key:{map key: bool}. -// It contains a aggregation of the map of arguments (which are in the form -// of -f 'key=value') based on the key, and store values for the same key -// in an map with string keys and boolean values. +// It contains an aggregation of the map of arguments (which are in the form +// of -f 'key=value') based on the key, and stores values for the same key +// in a map with string keys and boolean values. // e.g given -f 'label=label1=1' -f 'label=label2=2' -f 'image.name=ubuntu' // the args will be {"image.name":{"ubuntu":true},"label":{"label1=1":true,"label2=2":true}} type Args struct { @@ -54,7 +56,7 @@ func ParseFlag(arg string, prev Args) (Args, error) { // ErrBadFormat is an error returned in case of bad format for a filter. var ErrBadFormat = errors.New("bad format of filter (expected name=value)") -// ToParam packs the Args into an string for easy transport from client to server. +// ToParam packs the Args into a string for easy transport from client to server. func ToParam(a Args) (string, error) { // this way we don't URL encode {}, just empty space if a.Len() == 0 { @@ -68,6 +70,28 @@ func ToParam(a Args) (string, error) { return string(buf), nil } +// ToParamWithVersion packs the Args into a string for easy transport from client to server. +// The generated string will depend on the specified version (corresponding to the API version). +func ToParamWithVersion(version string, a Args) (string, error) { + // this way we don't URL encode {}, just empty space + if a.Len() == 0 { + return "", nil + } + + // for daemons older than v1.10, filter must be of the form map[string][]string + buf := []byte{} + err := errors.New("") + if version != "" && versions.LessThan(version, "1.22") { + buf, err = json.Marshal(convertArgsToSlice(a.fields)) + } else { + buf, err = json.Marshal(a.fields) + } + if err != nil { + return "", err + } + return string(buf), nil +} + // FromParam unpacks the filter Args. func FromParam(p string) (Args, error) { if len(p) == 0 { @@ -190,7 +214,7 @@ func (filters Args) ExactMatch(field, source string) bool { return true } - // try to march full name value to avoid O(N) regular expression matching + // try to match full name value to avoid O(N) regular expression matching if fieldValues[source] { return true } @@ -255,3 +279,17 @@ func deprecatedArgs(d map[string][]string) map[string]map[string]bool { } return m } + +func convertArgsToSlice(f map[string]map[string]bool) map[string][]string { + m := map[string][]string{} + for k, v := range f { + values := []string{} + for kk := range v { + if v[kk] { + values = append(values, kk) + } + } + m[k] = values + } + return m +} diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/types/reference/image_reference.go b/Godeps/_workspace/src/github.com/docker/engine-api/types/reference/image_reference.go new file mode 100644 index 00000000000..74201582060 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/engine-api/types/reference/image_reference.go @@ -0,0 +1,32 @@ +package reference + +import ( + distreference "github.com/docker/distribution/reference" +) + +// Parse parses the given references and returns the repository and +// tag (if present) from it. If there is an error during parsing, it will +// return an error. +func Parse(ref string) (string, string, error) { + distributionRef, err := distreference.ParseNamed(ref) + if err != nil { + return "", "", err + } + + tag := GetTagFromNamedRef(distributionRef) + return distributionRef.Name(), tag, nil +} + +// GetTagFromNamedRef returns a tag from the specified reference. +// This function is necessary as long as the docker "server" api makes the distinction between repository +// and tags. +func GetTagFromNamedRef(ref distreference.Named) string { + var tag string + switch x := ref.(type) { + case distreference.Digested: + tag = x.Digest().String() + case distreference.NamedTagged: + tag = x.Tag() + } + return tag +} diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/types/registry/registry.go b/Godeps/_workspace/src/github.com/docker/engine-api/types/registry/registry.go index 4fcf986e7b9..8a6fe70ea7d 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/types/registry/registry.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/types/registry/registry.go @@ -82,7 +82,7 @@ type SearchResult struct { IsOfficial bool `json:"is_official"` // Name is the name of the repository Name string `json:"name"` - // IsOfficial indicates whether the result is trusted + // IsTrusted indicates whether the result is trusted IsTrusted bool `json:"is_trusted"` // IsAutomated indicates whether the result is automated IsAutomated bool `json:"is_automated"` diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/types/stats.go b/Godeps/_workspace/src/github.com/docker/engine-api/types/stats.go index 55081ae4d2f..b420ebe7f6a 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/types/stats.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/types/stats.go @@ -8,7 +8,7 @@ import "time" type ThrottlingData struct { // Number of periods with throttling active Periods uint64 `json:"periods"` - // Number of periods when the container hit its throttling limit. + // Number of periods when the container hits its throttling limit. ThrottledPeriods uint64 `json:"throttled_periods"` // Aggregate time the container was throttled for in nanoseconds. ThrottledTime uint64 `json:"throttled_time"` @@ -91,6 +91,9 @@ type NetworkStats struct { type PidsStats struct { // Current is the number of pids in the cgroup Current uint64 `json:"current,omitempty"` + // Limit is the hard limit on the number of pids in the cgroup. + // A "Limit" of 0 means that there is no limit. + Limit uint64 `json:"limit,omitempty"` } // Stats is Ultimate struct aggregating all types of stats of one container diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/types/types.go b/Godeps/_workspace/src/github.com/docker/engine-api/types/types.go index 0b6494aa501..406b561a6e5 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/types/types.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/types/types.go @@ -103,6 +103,13 @@ type GraphDriverData struct { Data map[string]string } +// RootFS returns Image's RootFS description including the layer IDs. +type RootFS struct { + Type string + Layers []string `json:",omitempty"` + BaseLayer string `json:",omitempty"` +} + // ImageInspect contains response of Remote API: // GET "/images/{name:.*}/json" type ImageInspect struct { @@ -122,6 +129,7 @@ type ImageInspect struct { Size int64 VirtualSize int64 GraphDriver GraphDriverData + RootFS RootFS } // Port stores open ports info of container @@ -243,6 +251,7 @@ type Info struct { ServerVersion string ClusterStore string ClusterAdvertise string + SecurityOptions []string } // PluginsInfo is a temp struct holding Plugins name @@ -281,6 +290,18 @@ type ContainerState struct { FinishedAt string } +// ContainerNode stores information about the node that a container +// is running on. It's only available in Docker Swarm +type ContainerNode struct { + ID string + IPAddress string `json:"IP"` + Addr string + Name string + Cpus int + Memory int + Labels map[string]string +} + // ContainerJSONBase contains response of Remote API: // GET "/containers/{name:.*}/json" type ContainerJSONBase struct { @@ -294,6 +315,7 @@ type ContainerJSONBase struct { HostnamePath string HostsPath string LogPath string + Node *ContainerNode `json:",omitempty"` Name string RestartCount int Driver string @@ -368,9 +390,11 @@ type MountPoint struct { // Volume represents the configuration of a volume for the remote API type Volume struct { - Name string // Name is the name of the volume - Driver string // Driver is the Driver name used to create the volume - Mountpoint string // Mountpoint is the location on disk of the volume + Name string // Name is the name of the volume + Driver string // Driver is the Driver name used to create the volume + Mountpoint string // Mountpoint is the location on disk of the volume + Status map[string]interface{} `json:",omitempty"` // Status provides low-level status information about the volume + Labels map[string]string // Labels is metadata specific to the volume } // VolumesListResponse contains the response for the remote API: @@ -386,6 +410,7 @@ type VolumeCreateRequest struct { Name string // Name is the requested name of the volume Driver string // Driver is the name of the driver that should be used to create the volume DriverOpts map[string]string // DriverOpts holds the driver specific options to use for when creating the volume. + Labels map[string]string // Labels holds metadata specific to the volume being created. } // NetworkResource is the body of the "get network" http response message @@ -399,6 +424,7 @@ type NetworkResource struct { Internal bool Containers map[string]EndpointResource Options map[string]string + Labels map[string]string } // EndpointResource contains network resources allocated and used for a container in a network @@ -412,13 +438,19 @@ type EndpointResource struct { // NetworkCreate is the expected body of the "create network" http request message type NetworkCreate struct { - Name string CheckDuplicate bool Driver string EnableIPv6 bool IPAM network.IPAM Internal bool Options map[string]string + Labels map[string]string +} + +// NetworkCreateRequest is the request message sent to the server for network create call. +type NetworkCreateRequest struct { + NetworkCreate + Name string } // NetworkCreateResponse is the response message sent by the server for network create call diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/types/versions/compare.go b/Godeps/_workspace/src/github.com/docker/engine-api/types/versions/compare.go new file mode 100644 index 00000000000..611d4fed66e --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/engine-api/types/versions/compare.go @@ -0,0 +1,62 @@ +package versions + +import ( + "strconv" + "strings" +) + +// compare compares two version strings +// returns -1 if v1 < v2, 1 if v1 > v2, 0 otherwise. +func compare(v1, v2 string) int { + var ( + currTab = strings.Split(v1, ".") + otherTab = strings.Split(v2, ".") + ) + + max := len(currTab) + if len(otherTab) > max { + max = len(otherTab) + } + for i := 0; i < max; i++ { + var currInt, otherInt int + + if len(currTab) > i { + currInt, _ = strconv.Atoi(currTab[i]) + } + if len(otherTab) > i { + otherInt, _ = strconv.Atoi(otherTab[i]) + } + if currInt > otherInt { + return 1 + } + if otherInt > currInt { + return -1 + } + } + return 0 +} + +// LessThan checks if a version is less than another +func LessThan(v, other string) bool { + return compare(v, other) == -1 +} + +// LessThanOrEqualTo checks if a version is less than or equal to another +func LessThanOrEqualTo(v, other string) bool { + return compare(v, other) <= 0 +} + +// GreaterThan checks if a version is greater than another +func GreaterThan(v, other string) bool { + return compare(v, other) == 1 +} + +// GreaterThanOrEqualTo checks if a version is greater than or equal to another +func GreaterThanOrEqualTo(v, other string) bool { + return compare(v, other) >= 0 +} + +// Equal checks if a version is equal to another +func Equal(v, other string) bool { + return compare(v, other) == 0 +} diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/types/versions/v1p20/types.go b/Godeps/_workspace/src/github.com/docker/engine-api/types/versions/v1p20/types.go index ed800061fae..5736efad003 100644 --- a/Godeps/_workspace/src/github.com/docker/engine-api/types/versions/v1p20/types.go +++ b/Godeps/_workspace/src/github.com/docker/engine-api/types/versions/v1p20/types.go @@ -27,7 +27,7 @@ type ContainerConfig struct { VolumeDriver string } -// StatsJSON is a backcompatibility struct used in Stats for API prior to 1.21 +// StatsJSON is a backcompatibility struct used in Stats for APIs prior to 1.21 type StatsJSON struct { types.Stats Network types.NetworkStats `json:"network,omitempty"` diff --git a/hack/verify-godeps.sh b/hack/verify-godeps.sh index 61dddca370a..dbf4c47369b 100755 --- a/hack/verify-godeps.sh +++ b/hack/verify-godeps.sh @@ -95,13 +95,4 @@ if ! _out="$(diff -Naupr --ignore-matching-lines='^\s*\"GoVersion\":' --ignore-m exit 1 fi -# Godeps/_workstapces/src/github.com/fsouza/go-dockerclient/testing/data/symlink' -# is an intentionally broken symlink. Linux can use --no-dereference. OS X cannot. -# So we --exclude='symlink' so diff -r doesn't die following a bad symlink. -if ! _out="$(diff -Naupr --exclude='symlink' ${KUBE_ROOT}/Godeps/_workspace/src ${_kubetmp}/Godeps/_workspace/src)"; then - echo "Your godeps changes are not reproducible" - echo "${_out}" - exit 1 -fi - # ex: ts=2 sw=2 et filetype=sh diff --git a/pkg/api/testing/fuzzer.go b/pkg/api/testing/fuzzer.go index fcc79e06635..052ce3f4957 100644 --- a/pkg/api/testing/fuzzer.go +++ b/pkg/api/testing/fuzzer.go @@ -23,7 +23,6 @@ import ( "strconv" "testing" - docker "github.com/fsouza/go-dockerclient" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/testapi" @@ -191,21 +190,6 @@ func FuzzerFor(t *testing.T, version unversioned.GroupVersion, src rand.Source) *j = t } }, - func(pb map[docker.Port][]docker.PortBinding, c fuzz.Continue) { - // This is necessary because keys with nil values get omitted. - // TODO: Is this a bug? - pb[docker.Port(c.RandString())] = []docker.PortBinding{ - {c.RandString(), c.RandString()}, - {c.RandString(), c.RandString()}, - } - }, - func(pm map[string]docker.PortMapping, c fuzz.Continue) { - // This is necessary because keys with nil values get omitted. - // TODO: Is this a bug? - pm[c.RandString()] = docker.PortMapping{ - c.RandString(): c.RandString(), - } - }, func(q *api.ResourceRequirements, c fuzz.Continue) { randomQuantity := func() resource.Quantity { var q resource.Quantity diff --git a/pkg/api/v1/defaults.go b/pkg/api/v1/defaults.go index ac25b800831..938df8ec8f5 100644 --- a/pkg/api/v1/defaults.go +++ b/pkg/api/v1/defaults.go @@ -92,7 +92,7 @@ func SetDefaults_ContainerPort(obj *ContainerPort) { func SetDefaults_Container(obj *Container) { if obj.ImagePullPolicy == "" { // Ignore error and assume it has been validated elsewhere - _, tag, _ := parsers.ParseImageName(obj.Image) + _, tag, _, _ := parsers.ParseImageName(obj.Image) // Check image tag diff --git a/pkg/kubelet/dockertools/docker.go b/pkg/kubelet/dockertools/docker.go index 157fd1b4606..d265db51250 100644 --- a/pkg/kubelet/dockertools/docker.go +++ b/pkg/kubelet/dockertools/docker.go @@ -24,6 +24,7 @@ import ( "strconv" "strings" + dockerref "github.com/docker/distribution/reference" "github.com/docker/docker/pkg/jsonmessage" dockerapi "github.com/docker/engine-api/client" dockertypes "github.com/docker/engine-api/types" @@ -55,7 +56,7 @@ const ( minQuotaPerod = 1000 ) -// DockerInterface is an abstract interface for testability. It abstracts the interface of docker.Client. +// DockerInterface is an abstract interface for testability. It abstracts the interface of docker client. type DockerInterface interface { ListContainers(options dockertypes.ContainerListOptions) ([]dockertypes.Container, error) InspectContainer(id string) (*dockertypes.ContainerJSON, error) @@ -144,9 +145,28 @@ func filterHTTPError(err error, image string) error { } } +// applyDefaultImageTag parses a docker image string, if it doesn't contain any tag or digest, +// a default tag will be applied. +func applyDefaultImageTag(image string) (string, error) { + named, err := dockerref.ParseNamed(image) + if err != nil { + return "", fmt.Errorf("couldn't parse image reference %q: %v", image, err) + } + _, isTagged := named.(dockerref.Tagged) + _, isDigested := named.(dockerref.Digested) + if !isTagged && !isDigested { + named, err := dockerref.WithTag(named, parsers.DefaultImageTag) + if err != nil { + return "", fmt.Errorf("failed to apply default image tag %q: %v", image, err) + } + image = named.String() + } + return image, nil +} + func (p dockerPuller) Pull(image string, secrets []api.Secret) error { - // If no tag was specified, use the default "latest". - imageID, tag, err := parsers.ParseImageName(image) + // If the image contains no tag or digest, a default tag should be applied. + image, err := applyDefaultImageTag(image) if err != nil { return err } @@ -156,15 +176,14 @@ func (p dockerPuller) Pull(image string, secrets []api.Secret) error { return err } - opts := dockertypes.ImagePullOptions{ - Tag: tag, - } + // The only used image pull option RegistryAuth will be set in kube_docker_client + opts := dockertypes.ImagePullOptions{} - creds, haveCredentials := keyring.Lookup(imageID) + creds, haveCredentials := keyring.Lookup(image) if !haveCredentials { glog.V(1).Infof("Pulling image %s without credentials", image) - err := p.client.PullImage(imageID, dockertypes.AuthConfig{}, opts) + err := p.client.PullImage(image, dockertypes.AuthConfig{}, opts) if err == nil { // Sometimes PullImage failed with no error returned. exist, ierr := p.IsImagePresent(image) @@ -191,7 +210,7 @@ func (p dockerPuller) Pull(image string, secrets []api.Secret) error { var pullErrs []error for _, currentCreds := range creds { - err = p.client.PullImage(imageID, credentialprovider.LazyProvide(currentCreds), opts) + err = p.client.PullImage(image, credentialprovider.LazyProvide(currentCreds), opts) // If there was no error, return success if err == nil { return nil diff --git a/pkg/kubelet/dockertools/docker_test.go b/pkg/kubelet/dockertools/docker_test.go index 4e9e388a920..2bf72610fbc 100644 --- a/pkg/kubelet/dockertools/docker_test.go +++ b/pkg/kubelet/dockertools/docker_test.go @@ -40,7 +40,6 @@ import ( nettest "k8s.io/kubernetes/pkg/kubelet/network/testing" "k8s.io/kubernetes/pkg/types" hashutil "k8s.io/kubernetes/pkg/util/hash" - "k8s.io/kubernetes/pkg/util/parsers" ) func verifyCalls(t *testing.T, fakeDocker *FakeDockerClient, calls []string) { @@ -156,26 +155,20 @@ func TestContainerNaming(t *testing.T) { } } -func TestParseImageName(t *testing.T) { - tests := []struct { - imageName string - name string - tag string +func TestApplyDefaultImageTag(t *testing.T) { + for _, testCase := range []struct { + Input string + Output string }{ - {"ubuntu", "ubuntu", "latest"}, - {"ubuntu:2342", "ubuntu", "2342"}, - {"ubuntu:latest", "ubuntu", "latest"}, - {"foo/bar:445566", "foo/bar", "445566"}, - {"registry.example.com:5000/foobar", "registry.example.com:5000/foobar", "latest"}, - {"registry.example.com:5000/foobar:5342", "registry.example.com:5000/foobar", "5342"}, - {"registry.example.com:5000/foobar:latest", "registry.example.com:5000/foobar", "latest"}, - } - for _, test := range tests { - name, tag, err := parsers.ParseImageName(test.imageName) + {Input: "root", Output: "root:latest"}, + {Input: "root:tag", Output: "root:tag"}, + {Input: "root@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", Output: "root@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + } { + image, err := applyDefaultImageTag(testCase.Input) if err != nil { - t.Errorf("ParseImageName(%s) failed: %v", test.imageName, err) - } else if name != test.name || tag != test.tag { - t.Errorf("Expected name/tag: %s/%s, got %s/%s", test.name, test.tag, name, tag) + t.Errorf("applyDefaultTag(%s) failed: %v", testCase.Input, err) + } else if image != testCase.Output { + t.Errorf("Expected image reference: %q, got %q", testCase.Output, image) } } } diff --git a/pkg/kubelet/dockertools/fake_docker_client.go b/pkg/kubelet/dockertools/fake_docker_client.go index c0ea8ae0376..02f302fa03b 100644 --- a/pkg/kubelet/dockertools/fake_docker_client.go +++ b/pkg/kubelet/dockertools/fake_docker_client.go @@ -422,14 +422,14 @@ func (f *FakeDockerClient) Logs(id string, opts dockertypes.ContainerLogsOptions // PullImage is a test-spy implementation of DockerInterface.PullImage. // It adds an entry "pull" to the internal method call record. -func (f *FakeDockerClient) PullImage(imageID string, auth dockertypes.AuthConfig, opts dockertypes.ImagePullOptions) error { +func (f *FakeDockerClient) PullImage(image string, auth dockertypes.AuthConfig, opts dockertypes.ImagePullOptions) error { f.Lock() defer f.Unlock() f.called = append(f.called, "pull") err := f.popError("pull") if err == nil { authJson, _ := json.Marshal(auth) - f.pulled = append(f.pulled, fmt.Sprintf("%s:%s using %s", imageID, opts.Tag, string(authJson))) + f.pulled = append(f.pulled, fmt.Sprintf("%s using %s", image, string(authJson))) } return err } diff --git a/pkg/kubelet/dockertools/kube_docker_client.go b/pkg/kubelet/dockertools/kube_docker_client.go index ad656d54a66..76bb89e548e 100644 --- a/pkg/kubelet/dockertools/kube_docker_client.go +++ b/pkg/kubelet/dockertools/kube_docker_client.go @@ -29,7 +29,6 @@ import ( dockerstdcopy "github.com/docker/docker/pkg/stdcopy" dockerapi "github.com/docker/engine-api/client" dockertypes "github.com/docker/engine-api/types" - dockerfilters "github.com/docker/engine-api/types/filters" "golang.org/x/net/context" ) @@ -70,26 +69,6 @@ func getDefaultContext() context.Context { return context.Background() } -// convertType converts between different types with the same json format. -func convertType(src interface{}, dst interface{}) error { - data, err := json.Marshal(src) - if err != nil { - return err - } - return json.Unmarshal(data, dst) -} - -// convertFilters converts filters to the filter type in engine-api. -func convertFilters(filters map[string][]string) dockerfilters.Args { - args := dockerfilters.NewArgs() - for name, fields := range filters { - for _, field := range fields { - args.Add(name, field) - } - } - return args -} - func (k *kubeDockerClient) ListContainers(options dockertypes.ContainerListOptions) ([]dockertypes.Container, error) { containers, err := k.client.ContainerList(getDefaultContext(), options) if err != nil { @@ -136,8 +115,7 @@ func (d *kubeDockerClient) StopContainer(id string, timeout int) error { } func (d *kubeDockerClient) RemoveContainer(id string, opts dockertypes.ContainerRemoveOptions) error { - opts.ContainerID = id - return d.client.ContainerRemove(getDefaultContext(), opts) + return d.client.ContainerRemove(getDefaultContext(), id, opts) } func (d *kubeDockerClient) InspectImage(image string) (*dockertypes.ImageInspect, error) { @@ -177,9 +155,8 @@ func (d *kubeDockerClient) PullImage(image string, auth dockertypes.AuthConfig, if err != nil { return err } - opts.ImageID = image opts.RegistryAuth = base64Auth - resp, err := d.client.ImagePull(getDefaultContext(), opts, nil) + resp, err := d.client.ImagePull(getDefaultContext(), image, opts) if err != nil { return err } @@ -203,12 +180,11 @@ func (d *kubeDockerClient) PullImage(image string, auth dockertypes.AuthConfig, } func (d *kubeDockerClient) RemoveImage(image string, opts dockertypes.ImageRemoveOptions) ([]dockertypes.ImageDelete, error) { - return d.client.ImageRemove(getDefaultContext(), dockertypes.ImageRemoveOptions{ImageID: image}) + return d.client.ImageRemove(getDefaultContext(), image, opts) } func (d *kubeDockerClient) Logs(id string, opts dockertypes.ContainerLogsOptions, sopts StreamOptions) error { - opts.ContainerID = id - resp, err := d.client.ContainerLogs(getDefaultContext(), opts) + resp, err := d.client.ContainerLogs(getDefaultContext(), id, opts) if err != nil { return err } @@ -234,8 +210,7 @@ func (d *kubeDockerClient) Info() (*dockertypes.Info, error) { // TODO(random-liu): Add unit test for exec and attach functions, just like what go-dockerclient did. func (d *kubeDockerClient) CreateExec(id string, opts dockertypes.ExecConfig) (*dockertypes.ContainerExecCreateResponse, error) { - opts.Container = id - resp, err := d.client.ContainerExecCreate(getDefaultContext(), opts) + resp, err := d.client.ContainerExecCreate(getDefaultContext(), id, opts) if err != nil { return nil, err } @@ -266,8 +241,7 @@ func (d *kubeDockerClient) InspectExec(id string) (*dockertypes.ContainerExecIns } func (d *kubeDockerClient) AttachToContainer(id string, opts dockertypes.ContainerAttachOptions, sopts StreamOptions) error { - opts.ContainerID = id - resp, err := d.client.ContainerAttach(getDefaultContext(), opts) + resp, err := d.client.ContainerAttach(getDefaultContext(), id, opts) if err != nil { return err } diff --git a/pkg/kubelet/dockertools/manager.go b/pkg/kubelet/dockertools/manager.go index 544cd3b5e2c..730f56c27f7 100644 --- a/pkg/kubelet/dockertools/manager.go +++ b/pkg/kubelet/dockertools/manager.go @@ -33,6 +33,7 @@ import ( dockertypes "github.com/docker/engine-api/types" dockercontainer "github.com/docker/engine-api/types/container" dockerstrslice "github.com/docker/engine-api/types/strslice" + dockerversion "github.com/docker/engine-api/types/versions" dockernat "github.com/docker/go-connections/nat" "github.com/golang/glog" cadvisorapi "github.com/google/cadvisor/info/v1" @@ -894,40 +895,12 @@ func (v dockerVersion) String() string { } func (v dockerVersion) Compare(other string) (int, error) { - return compare(string(v), other), nil -} - -// compare is copied from engine-api, it compares two version strings, returns -1 if -// v1 < v2, 1 if v1 > v2, 0 otherwise. -// TODO(random-liu): Leveraging the version comparison in engine-api after bumping up -// the engine-api version. See #24076 -func compare(v1, v2 string) int { - var ( - currTab = strings.Split(v1, ".") - otherTab = strings.Split(v2, ".") - ) - - max := len(currTab) - if len(otherTab) > max { - max = len(otherTab) + if dockerversion.LessThan(string(v), other) { + return -1, nil + } else if dockerversion.GreaterThan(string(v), other) { + return 1, nil } - for i := 0; i < max; i++ { - var currInt, otherInt int - - if len(currTab) > i { - currInt, _ = strconv.Atoi(currTab[i]) - } - if len(otherTab) > i { - otherInt, _ = strconv.Atoi(otherTab[i]) - } - if currInt > otherInt { - return 1 - } - if otherInt > currInt { - return -1 - } - } - return 0 + return 0, nil } func (dm *DockerManager) Type() string { diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index c6be6894ad1..0467b17be1f 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -1415,7 +1415,7 @@ func (kl *Kubelet) getServiceEnvVarMap(ns string) (map[string]string, error) { // Make the environment variables for a pod in the given namespace. func (kl *Kubelet) makeEnvironmentVariables(pod *api.Pod, container *api.Container, podIP string) ([]kubecontainer.EnvVar, error) { var result []kubecontainer.EnvVar - // Note: These are added to the docker.Config, but are not included in the checksum computed + // Note: These are added to the docker Config, but are not included in the checksum computed // by dockertools.BuildDockerName(...). That way, we can still determine whether an // api.Container is already running by its hash. (We don't want to restart a container just // because some service changed.) diff --git a/pkg/kubelet/rkt/image.go b/pkg/kubelet/rkt/image.go index 47ea1a3627b..ae4d1554074 100644 --- a/pkg/kubelet/rkt/image.go +++ b/pkg/kubelet/rkt/image.go @@ -47,7 +47,7 @@ func (r *Runtime) PullImage(image kubecontainer.ImageSpec, pullSecrets []api.Sec img := image.Image // TODO(yifan): The credential operation is a copy from dockertools package, // Need to resolve the code duplication. - repoToPull, _, err := parsers.ParseImageName(img) + repoToPull, _, _, err := parsers.ParseImageName(img) if err != nil { return err } @@ -142,7 +142,7 @@ func (s sortByImportTime) Less(i, j int) bool { return s[i].ImportTimestamp < s[ // will return the result reversely sorted by the import time, so that the latest // image comes first. func (r *Runtime) listImages(image string, detail bool) ([]*rktapi.Image, error) { - repoToPull, tag, err := parsers.ParseImageName(image) + repoToPull, tag, _, err := parsers.ParseImageName(image) if err != nil { return nil, err } diff --git a/pkg/util/parsers/parsers.go b/pkg/util/parsers/parsers.go index c173be66340..a02f18d3eb1 100644 --- a/pkg/util/parsers/parsers.go +++ b/pkg/util/parsers/parsers.go @@ -19,37 +19,36 @@ package parsers import ( "fmt" - "github.com/docker/distribution/reference" + dockerref "github.com/docker/distribution/reference" ) const ( - defaultImageTag = "latest" + DefaultImageTag = "latest" ) -// parseImageName parses a docker image string into two parts: repo and tag. -// If tag is empty, return the defaultImageTag. -func ParseImageName(image string) (string, string, error) { - named, err := reference.ParseNamed(image) +// ParseImageName parses a docker image string into three parts: repo, tag and digest. +// If both tag and digest are empty, a default image tag will be returned. +func ParseImageName(image string) (string, string, string, error) { + named, err := dockerref.ParseNamed(image) if err != nil { - return "", "", fmt.Errorf("couldn't parse image name: %v", err) + return "", "", "", fmt.Errorf("couldn't parse image name: %v", err) } repoToPull := named.Name() - var tag string + var tag, digest string - tagged, ok := named.(reference.Tagged) + tagged, ok := named.(dockerref.Tagged) if ok { tag = tagged.Tag() } - digested, ok := named.(reference.Digested) + digested, ok := named.(dockerref.Digested) if ok { - tag = digested.Digest().String() + digest = digested.Digest().String() } - // If no tag was specified, use the default "latest". - if len(tag) == 0 { - tag = defaultImageTag + if len(tag) == 0 && len(digest) == 0 { + tag = DefaultImageTag } - return repoToPull, tag, nil + return repoToPull, tag, digest, nil } diff --git a/pkg/util/parsers/parsers_test.go b/pkg/util/parsers/parsers_test.go index 8c829682f20..371eee758a0 100644 --- a/pkg/util/parsers/parsers_test.go +++ b/pkg/util/parsers/parsers_test.go @@ -24,26 +24,28 @@ import ( // https://github.com/docker/docker/commit/4352da7803d182a6013a5238ce20a7c749db979a func TestParseImageName(t *testing.T) { testCases := []struct { - Input string - Repo string - Image string + Input string + Repo string + Tag string + Digest string }{ - {Input: "root", Repo: "root", Image: "latest"}, - {Input: "root:tag", Repo: "root", Image: "tag"}, - {Input: "root@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", Repo: "root", Image: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, - {Input: "user/repo", Repo: "user/repo", Image: "latest"}, - {Input: "user/repo:tag", Repo: "user/repo", Image: "tag"}, - {Input: "user/repo@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", Repo: "user/repo", Image: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, - {Input: "url:5000/repo", Repo: "url:5000/repo", Image: "latest"}, - {Input: "url:5000/repo:tag", Repo: "url:5000/repo", Image: "tag"}, - {Input: "url:5000/repo@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", Repo: "url:5000/repo", Image: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + {Input: "root", Repo: "root", Tag: "latest"}, + {Input: "root:tag", Repo: "root", Tag: "tag"}, + {Input: "root@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", Repo: "root", Digest: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + {Input: "user/repo", Repo: "user/repo", Tag: "latest"}, + {Input: "user/repo:tag", Repo: "user/repo", Tag: "tag"}, + {Input: "user/repo@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", Repo: "user/repo", Digest: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + {Input: "url:5000/repo", Repo: "url:5000/repo", Tag: "latest"}, + {Input: "url:5000/repo:tag", Repo: "url:5000/repo", Tag: "tag"}, + {Input: "url:5000/repo@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", Repo: "url:5000/repo", Digest: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, } for _, testCase := range testCases { - repo, image, err := ParseImageName(testCase.Input) + repo, tag, digest, err := ParseImageName(testCase.Input) if err != nil { t.Errorf("ParseImageName(%s) failed: %v", testCase.Input, err) - } else if repo != testCase.Repo || image != testCase.Image { - t.Errorf("Expected repo: '%s' and image: '%s', got '%s' and '%s'", "root", "", repo, image) + } else if repo != testCase.Repo || tag != testCase.Tag || digest != testCase.Digest { + t.Errorf("Expected repo: %q, tag: %q and digest: %q, got %q, %q and %q", testCase.Repo, testCase.Tag, testCase.Digest, + repo, tag, digest) } } }