瀏覽代碼

Implement docker pull with standalone client lib.

Signed-off-by: David Calavera <david.calavera@gmail.com>
David Calavera 9 年之前
父節點
當前提交
e78f02c4db

+ 2 - 0
api/client/client.go

@@ -7,6 +7,7 @@ package client
 import (
 	"io"
 
+	"github.com/docker/docker/api/client/lib"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/cliconfig"
 	"github.com/docker/docker/pkg/parsers/filters"
@@ -48,6 +49,7 @@ type apiClient interface {
 	ImageImport(options types.ImageImportOptions) (io.ReadCloser, error)
 	ImageList(options types.ImageListOptions) ([]types.Image, error)
 	ImageLoad(input io.Reader) (io.ReadCloser, error)
+	ImagePull(options types.ImagePullOptions, privilegeFunc lib.RequestPrivilegeFunc) (io.ReadCloser, error)
 	ImageRemove(options types.ImageRemoveOptions) ([]types.ImageDelete, error)
 	ImageSave(imageIDs []string) (io.ReadCloser, error)
 	ImageTag(options types.ImageTagOptions) error

+ 2 - 5
api/client/create.go

@@ -1,8 +1,6 @@
 package client
 
 import (
-	"encoding/base64"
-	"encoding/json"
 	"fmt"
 	"io"
 	"os"
@@ -45,8 +43,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
 	}
 
 	// Resolve the Auth config relevant for this server
-	authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
-	buf, err := json.Marshal(authConfig)
+	encodedAuth, err := cli.encodeRegistryAuth(repoInfo.Index)
 	if err != nil {
 		return err
 	}
@@ -54,7 +51,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
 	options := types.ImageCreateOptions{
 		Parent:       ref.Name(),
 		Tag:          tag,
-		RegistryAuth: base64.URLEncoding.EncodeToString(buf),
+		RegistryAuth: encodedAuth,
 	}
 
 	responseBody, err := cli.client.ImageCreate(options)

+ 6 - 3
api/client/lib/image_create.go

@@ -13,11 +13,14 @@ func (cli *Client) ImageCreate(options types.ImageCreateOptions) (io.ReadCloser,
 	query := url.Values{}
 	query.Set("fromImage", options.Parent)
 	query.Set("tag", options.Tag)
-
-	headers := map[string][]string{"X-Registry-Auth": {options.RegistryAuth}}
-	resp, err := cli.post("/images/create", query, nil, headers)
+	resp, err := cli.tryImageCreate(query, options.RegistryAuth)
 	if err != nil {
 		return nil, err
 	}
 	return resp.body, nil
 }
+
+func (cli *Client) tryImageCreate(query url.Values, registryAuth string) (*serverResponse, error) {
+	headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
+	return cli.post("/images/create", query, nil, headers)
+}

+ 34 - 0
api/client/lib/image_pull.go

@@ -0,0 +1,34 @@
+package lib
+
+import (
+	"io"
+	"net/http"
+	"net/url"
+
+	"github.com/docker/docker/api/types"
+)
+
+// ImagePull request 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(options types.ImagePullOptions, privilegeFunc RequestPrivilegeFunc) (io.ReadCloser, error) {
+	query := url.Values{}
+	query.Set("fromImage", options.ImageID)
+	if options.Tag != "" {
+		query.Set("tag", options.Tag)
+	}
+
+	resp, err := cli.tryImageCreate(query, options.RegistryAuth)
+	if resp.statusCode == http.StatusUnauthorized {
+		newAuthHeader, privilegeErr := privilegeFunc()
+		if privilegeErr != nil {
+			return nil, privilegeErr
+		}
+		resp, err = cli.tryImageCreate(query, newAuthHeader)
+	}
+	if err != nil {
+		return nil, err
+	}
+	return resp.body, nil
+}

+ 9 - 0
api/client/lib/privileged.go

@@ -0,0 +1,9 @@
+package lib
+
+// 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)

+ 29 - 7
api/client/pull.go

@@ -3,10 +3,13 @@ package client
 import (
 	"errors"
 	"fmt"
-	"net/url"
 
 	"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"
 	tagpkg "github.com/docker/docker/tag"
@@ -62,15 +65,34 @@ func (cli *DockerCli) CmdPull(args ...string) error {
 		return err
 	}
 
+	authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
+	requestPrivilege := cli.registryAuthenticationPrivilegedFunc(repoInfo.Index, "pull")
+
 	if isTrusted() && !ref.HasDigest() {
 		// Check if tag is digest
-		authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
-		return cli.trustedPull(repoInfo, ref, authConfig)
+		return cli.trustedPull(repoInfo, ref, authConfig, requestPrivilege)
+	}
+
+	return cli.imagePullPrivileged(authConfig, distributionRef.String(), "", requestPrivilege)
+}
+
+func (cli *DockerCli) imagePullPrivileged(authConfig cliconfig.AuthConfig, imageID, tag string, requestPrivilege lib.RequestPrivilegeFunc) error {
+
+	encodedAuth, err := authConfig.EncodeToBase64()
+	if err != nil {
+		return err
+	}
+	options := types.ImagePullOptions{
+		ImageID:      imageID,
+		Tag:          tag,
+		RegistryAuth: encodedAuth,
 	}
 
-	v := url.Values{}
-	v.Set("fromImage", distributionRef.String())
+	responseBody, err := cli.client.ImagePull(options, requestPrivilege)
+	if err != nil {
+		return err
+	}
+	defer responseBody.Close()
 
-	_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
-	return err
+	return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut)
 }

+ 4 - 9
api/client/trust.go

@@ -22,6 +22,7 @@ import (
 	"github.com/docker/distribution/reference"
 	"github.com/docker/distribution/registry/client/auth"
 	"github.com/docker/distribution/registry/client/transport"
+	"github.com/docker/docker/api/client/lib"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/cliconfig"
 	"github.com/docker/docker/pkg/ansiescape"
@@ -278,11 +279,8 @@ func notaryError(err error) error {
 	return err
 }
 
-func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registry.Reference, authConfig cliconfig.AuthConfig) error {
-	var (
-		v    = url.Values{}
-		refs = []target{}
-	)
+func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registry.Reference, authConfig cliconfig.AuthConfig, requestPrivilege lib.RequestPrivilegeFunc) error {
+	var refs []target
 
 	notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)
 	if err != nil {
@@ -317,17 +315,14 @@ func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registr
 		refs = append(refs, r)
 	}
 
-	v.Set("fromImage", repoInfo.LocalName.Name())
 	for i, r := range refs {
 		displayTag := r.reference.String()
 		if displayTag != "" {
 			displayTag = ":" + displayTag
 		}
 		fmt.Fprintf(cli.out, "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), repoInfo.LocalName, displayTag, r.digest)
-		v.Set("tag", r.digest.String())
 
-		_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
-		if err != nil {
+		if err := cli.imagePullPrivileged(authConfig, repoInfo.LocalName.Name(), r.digest.String(), requestPrivilege); err != nil {
 			return err
 		}
 

+ 15 - 0
api/client/utils.go

@@ -160,6 +160,21 @@ func (cli *DockerCli) cmdAttempt(authConfig cliconfig.AuthConfig, method, path s
 	return serverResp.body, serverResp.statusCode, err
 }
 
+func (cli *DockerCli) encodeRegistryAuth(index *registry.IndexInfo) (string, error) {
+	authConfig := registry.ResolveAuthConfig(cli.configFile, index)
+	return authConfig.EncodeToBase64()
+}
+
+func (cli *DockerCli) registryAuthenticationPrivilegedFunc(index *registry.IndexInfo, cmdName string) lib.RequestPrivilegeFunc {
+	return func() (string, error) {
+		fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
+		if err := cli.CmdLogin(index.GetAuthConfigKey()); err != nil {
+			return "", err
+		}
+		return cli.encodeRegistryAuth(index)
+	}
+}
+
 func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) {
 
 	// Resolve the Auth config relevant for this server

+ 8 - 0
api/types/client.go

@@ -179,6 +179,14 @@ type ImageListOptions struct {
 	Filters   filters.Args
 }
 
+// ImagePullOptions holds information to pull images.
+type ImagePullOptions struct {
+	ImageID string
+	Tag     string
+	// RegistryAuth is the base64 encoded credentials for this server
+	RegistryAuth string
+}
+
 // ImageRemoveOptions holds parameters to remove images.
 type ImageRemoveOptions struct {
 	ImageID       string

+ 9 - 0
cliconfig/config.go

@@ -54,6 +54,15 @@ type AuthConfig struct {
 	RegistryToken string `json:"registrytoken,omitempty"`
 }
 
+// EncodeToBase64 serializes the auth configuration as JSON base64 payload
+func (a AuthConfig) EncodeToBase64() (string, error) {
+	buf, err := json.Marshal(a)
+	if err != nil {
+		return "", err
+	}
+	return base64.URLEncoding.EncodeToString(buf), nil
+}
+
 // ConfigFile ~/.docker/config.json file info
 type ConfigFile struct {
 	AuthConfigs map[string]AuthConfig `json:"auths"`