Browse code

Implement docker push with standalone client lib.

Signed-off-by: David Calavera <david.calavera@gmail.com>

David Calavera authored on 2015/12/07 05:37:54
Showing 5 changed files
... ...
@@ -50,6 +50,7 @@ type apiClient interface {
50 50
 	ImageList(options types.ImageListOptions) ([]types.Image, error)
51 51
 	ImageLoad(input io.Reader) (io.ReadCloser, error)
52 52
 	ImagePull(options types.ImagePullOptions, privilegeFunc lib.RequestPrivilegeFunc) (io.ReadCloser, error)
53
+	ImagePush(options types.ImagePushOptions, privilegeFunc lib.RequestPrivilegeFunc) (io.ReadCloser, error)
53 54
 	ImageRemove(options types.ImageRemoveOptions) ([]types.ImageDelete, error)
54 55
 	ImageSave(imageIDs []string) (io.ReadCloser, error)
55 56
 	ImageTag(options types.ImageTagOptions) error
56 57
new file mode 100644
... ...
@@ -0,0 +1,36 @@
0
+package lib
1
+
2
+import (
3
+	"io"
4
+	"net/http"
5
+	"net/url"
6
+
7
+	"github.com/docker/docker/api/types"
8
+)
9
+
10
+// ImagePush request the docker host to push an image to a remote registry.
11
+// It executes the privileged function if the operation is unauthorized
12
+// and it tries one more time.
13
+// It's up to the caller to handle the io.ReadCloser and close it properly.
14
+func (cli *Client) ImagePush(options types.ImagePushOptions, privilegeFunc RequestPrivilegeFunc) (io.ReadCloser, error) {
15
+	query := url.Values{}
16
+	query.Set("tag", options.Tag)
17
+
18
+	resp, err := cli.tryImagePush(options.ImageID, query, options.RegistryAuth)
19
+	if resp.statusCode == http.StatusUnauthorized {
20
+		newAuthHeader, privilegeErr := privilegeFunc()
21
+		if privilegeErr != nil {
22
+			return nil, privilegeErr
23
+		}
24
+		resp, err = cli.tryImagePush(options.ImageID, query, newAuthHeader)
25
+	}
26
+	if err != nil {
27
+		return nil, err
28
+	}
29
+	return resp.body, nil
30
+}
31
+
32
+func (cli *Client) tryImagePush(imageID string, query url.Values, registryAuth string) (*serverResponse, error) {
33
+	headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
34
+	return cli.post("/images/"+imageID+"/push", query, nil, headers)
35
+}
... ...
@@ -3,10 +3,14 @@ package client
3 3
 import (
4 4
 	"errors"
5 5
 	"fmt"
6
-	"net/url"
6
+	"io"
7 7
 
8 8
 	"github.com/docker/distribution/reference"
9
+	"github.com/docker/docker/api/client/lib"
10
+	"github.com/docker/docker/api/types"
9 11
 	Cli "github.com/docker/docker/cli"
12
+	"github.com/docker/docker/cliconfig"
13
+	"github.com/docker/docker/pkg/jsonmessage"
10 14
 	flag "github.com/docker/docker/pkg/mflag"
11 15
 	"github.com/docker/docker/registry"
12 16
 )
... ...
@@ -53,13 +57,30 @@ func (cli *DockerCli) CmdPush(args ...string) error {
53 53
 		return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository to <user>/<repo> (ex: %s/%s)", username, repoInfo.LocalName)
54 54
 	}
55 55
 
56
+	requestPrivilege := cli.registryAuthenticationPrivilegedFunc(repoInfo.Index, "push")
56 57
 	if isTrusted() {
57
-		return cli.trustedPush(repoInfo, tag, authConfig)
58
+		return cli.trustedPush(repoInfo, tag, authConfig, requestPrivilege)
58 59
 	}
59 60
 
60
-	v := url.Values{}
61
-	v.Set("tag", tag)
61
+	return cli.imagePushPrivileged(authConfig, ref.Name(), tag, cli.out, requestPrivilege)
62
+}
63
+
64
+func (cli *DockerCli) imagePushPrivileged(authConfig cliconfig.AuthConfig, imageID, tag string, outputStream io.Writer, requestPrivilege lib.RequestPrivilegeFunc) error {
65
+	encodedAuth, err := authConfig.EncodeToBase64()
66
+	if err != nil {
67
+		return err
68
+	}
69
+	options := types.ImagePushOptions{
70
+		ImageID:      imageID,
71
+		Tag:          tag,
72
+		RegistryAuth: encodedAuth,
73
+	}
74
+
75
+	responseBody, err := cli.client.ImagePush(options, requestPrivilege)
76
+	if err != nil {
77
+		return err
78
+	}
79
+	defer responseBody.Close()
62 80
 
63
-	_, _, err = cli.clientRequestAttemptLogin("POST", "/images/"+ref.Name()+"/push?"+v.Encode(), nil, cli.out, repoInfo.Index, "push")
64
-	return err
81
+	return jsonmessage.DisplayJSONMessagesStream(responseBody, outputStream, cli.outFd, cli.isTerminalOut)
65 82
 }
... ...
@@ -380,20 +380,18 @@ func targetStream(in io.Writer) (io.WriteCloser, <-chan []target) {
380 380
 	return ioutils.NewWriteCloserWrapper(out, w.Close), targetChan
381 381
 }
382 382
 
383
-func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string, authConfig cliconfig.AuthConfig) error {
383
+func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string, authConfig cliconfig.AuthConfig, requestPrivilege lib.RequestPrivilegeFunc) error {
384 384
 	streamOut, targetChan := targetStream(cli.out)
385 385
 
386
-	v := url.Values{}
387
-	v.Set("tag", tag)
386
+	reqError := cli.imagePushPrivileged(authConfig, repoInfo.LocalName.Name(), tag, streamOut, requestPrivilege)
388 387
 
389
-	_, _, err := cli.clientRequestAttemptLogin("POST", "/images/"+repoInfo.LocalName.Name()+"/push?"+v.Encode(), nil, streamOut, repoInfo.Index, "push")
390 388
 	// Close stream channel to finish target parsing
391 389
 	if err := streamOut.Close(); err != nil {
392 390
 		return err
393 391
 	}
394 392
 	// Check error from request
395
-	if err != nil {
396
-		return err
393
+	if reqError != nil {
394
+		return reqError
397 395
 	}
398 396
 
399 397
 	// Get target results
... ...
@@ -187,6 +187,9 @@ type ImagePullOptions struct {
187 187
 	RegistryAuth string
188 188
 }
189 189
 
190
+//ImagePushOptions holds information to push images.
191
+type ImagePushOptions ImagePullOptions
192
+
190 193
 // ImageRemoveOptions holds parameters to remove images.
191 194
 type ImageRemoveOptions struct {
192 195
 	ImageID       string