Implement docker pull with standalone client lib.

Signed-off-by: David Calavera <david.calavera@gmail.com>
This commit is contained in:
David Calavera 2015-12-06 15:17:34 -05:00
parent e59d54bd99
commit e78f02c4db
10 changed files with 119 additions and 25 deletions

View file

@ -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

View file

@ -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)

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)

View file

@ -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)
}
v := url.Values{}
v.Set("fromImage", distributionRef.String())
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
return err
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,
}
responseBody, err := cli.client.ImagePull(options, requestPrivilege)
if err != nil {
return err
}
defer responseBody.Close()
return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut)
}

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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"`