Implement docker pull with standalone client lib.
Signed-off-by: David Calavera <david.calavera@gmail.com>
This commit is contained in:
parent
e59d54bd99
commit
e78f02c4db
10 changed files with 119 additions and 25 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
api/client/lib/image_pull.go
Normal file
34
api/client/lib/image_pull.go
Normal 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
|
||||
}
|
9
api/client/lib/privileged.go
Normal file
9
api/client/lib/privileged.go
Normal 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)
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"`
|
||||
|
|
Loading…
Reference in a new issue