From 42670e30eef7023d2df9c6c8900041bc9e1546e0 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 6 Dec 2015 15:37:54 -0500 Subject: [PATCH] Implement docker push with standalone client lib. Signed-off-by: David Calavera --- api/client/client.go | 1 + api/client/lib/image_push.go | 36 ++++++++++++++++++++++++++++++++++++ api/client/push.go | 35 ++++++++++++++++++++++++++++------- api/client/trust.go | 10 ++++------ api/types/client.go | 3 +++ 5 files changed, 72 insertions(+), 13 deletions(-) create mode 100644 api/client/lib/image_push.go diff --git a/api/client/client.go b/api/client/client.go index c71617ac2a..26aabc00e4 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -50,6 +50,7 @@ type apiClient interface { ImageList(options types.ImageListOptions) ([]types.Image, error) ImageLoad(input io.Reader) (io.ReadCloser, error) ImagePull(options types.ImagePullOptions, privilegeFunc lib.RequestPrivilegeFunc) (io.ReadCloser, error) + ImagePush(options types.ImagePushOptions, privilegeFunc lib.RequestPrivilegeFunc) (io.ReadCloser, error) ImageRemove(options types.ImageRemoveOptions) ([]types.ImageDelete, error) ImageSave(imageIDs []string) (io.ReadCloser, error) ImageTag(options types.ImageTagOptions) error diff --git a/api/client/lib/image_push.go b/api/client/lib/image_push.go new file mode 100644 index 0000000000..14c369afac --- /dev/null +++ b/api/client/lib/image_push.go @@ -0,0 +1,36 @@ +package lib + +import ( + "io" + "net/http" + "net/url" + + "github.com/docker/docker/api/types" +) + +// ImagePush request 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(options types.ImagePushOptions, privilegeFunc RequestPrivilegeFunc) (io.ReadCloser, error) { + query := url.Values{} + query.Set("tag", options.Tag) + + resp, err := cli.tryImagePush(options.ImageID, query, options.RegistryAuth) + if resp.statusCode == http.StatusUnauthorized { + newAuthHeader, privilegeErr := privilegeFunc() + if privilegeErr != nil { + return nil, privilegeErr + } + resp, err = cli.tryImagePush(options.ImageID, query, newAuthHeader) + } + if err != nil { + return nil, err + } + return resp.body, nil +} + +func (cli *Client) tryImagePush(imageID string, query url.Values, registryAuth string) (*serverResponse, error) { + headers := map[string][]string{"X-Registry-Auth": {registryAuth}} + return cli.post("/images/"+imageID+"/push", query, nil, headers) +} diff --git a/api/client/push.go b/api/client/push.go index 760c97ef8b..4230eea838 100644 --- a/api/client/push.go +++ b/api/client/push.go @@ -3,10 +3,14 @@ package client import ( "errors" "fmt" - "net/url" + "io" "github.com/docker/distribution/reference" + "github.com/docker/docker/api/client/lib" + "github.com/docker/docker/api/types" Cli "github.com/docker/docker/cli" + "github.com/docker/docker/cliconfig" + "github.com/docker/docker/pkg/jsonmessage" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/registry" ) @@ -53,13 +57,30 @@ func (cli *DockerCli) CmdPush(args ...string) error { return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository to / (ex: %s/%s)", username, repoInfo.LocalName) } + requestPrivilege := cli.registryAuthenticationPrivilegedFunc(repoInfo.Index, "push") if isTrusted() { - return cli.trustedPush(repoInfo, tag, authConfig) + return cli.trustedPush(repoInfo, tag, authConfig, requestPrivilege) } - v := url.Values{} - v.Set("tag", tag) - - _, _, err = cli.clientRequestAttemptLogin("POST", "/images/"+ref.Name()+"/push?"+v.Encode(), nil, cli.out, repoInfo.Index, "push") - return err + return cli.imagePushPrivileged(authConfig, ref.Name(), tag, cli.out, requestPrivilege) +} + +func (cli *DockerCli) imagePushPrivileged(authConfig cliconfig.AuthConfig, imageID, tag string, outputStream io.Writer, requestPrivilege lib.RequestPrivilegeFunc) error { + encodedAuth, err := authConfig.EncodeToBase64() + if err != nil { + return err + } + options := types.ImagePushOptions{ + ImageID: imageID, + Tag: tag, + RegistryAuth: encodedAuth, + } + + responseBody, err := cli.client.ImagePush(options, requestPrivilege) + if err != nil { + return err + } + defer responseBody.Close() + + return jsonmessage.DisplayJSONMessagesStream(responseBody, outputStream, cli.outFd, cli.isTerminalOut) } diff --git a/api/client/trust.go b/api/client/trust.go index 1bc4f8fe1f..cb7c8ef10b 100644 --- a/api/client/trust.go +++ b/api/client/trust.go @@ -380,20 +380,18 @@ func targetStream(in io.Writer) (io.WriteCloser, <-chan []target) { return ioutils.NewWriteCloserWrapper(out, w.Close), targetChan } -func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string, authConfig cliconfig.AuthConfig) error { +func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string, authConfig cliconfig.AuthConfig, requestPrivilege lib.RequestPrivilegeFunc) error { streamOut, targetChan := targetStream(cli.out) - v := url.Values{} - v.Set("tag", tag) + reqError := cli.imagePushPrivileged(authConfig, repoInfo.LocalName.Name(), tag, streamOut, requestPrivilege) - _, _, err := cli.clientRequestAttemptLogin("POST", "/images/"+repoInfo.LocalName.Name()+"/push?"+v.Encode(), nil, streamOut, repoInfo.Index, "push") // Close stream channel to finish target parsing if err := streamOut.Close(); err != nil { return err } // Check error from request - if err != nil { - return err + if reqError != nil { + return reqError } // Get target results diff --git a/api/types/client.go b/api/types/client.go index 10f40a8cd8..698dc48543 100644 --- a/api/types/client.go +++ b/api/types/client.go @@ -187,6 +187,9 @@ type ImagePullOptions struct { RegistryAuth string } +//ImagePushOptions holds information to push images. +type ImagePushOptions ImagePullOptions + // ImageRemoveOptions holds parameters to remove images. type ImageRemoveOptions struct { ImageID string