Skip to content

Commit e78f02c

Browse files
committed
Implement docker pull with standalone client lib.
Signed-off-by: David Calavera <[email protected]>
1 parent e59d54b commit e78f02c

10 files changed

Lines changed: 118 additions & 24 deletions

File tree

api/client/client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package client
77
import (
88
"io"
99

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

api/client/create.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package client
22

33
import (
4-
"encoding/base64"
5-
"encoding/json"
64
"fmt"
75
"io"
86
"os"
@@ -45,16 +43,15 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
4543
}
4644

4745
// Resolve the Auth config relevant for this server
48-
authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
49-
buf, err := json.Marshal(authConfig)
46+
encodedAuth, err := cli.encodeRegistryAuth(repoInfo.Index)
5047
if err != nil {
5148
return err
5249
}
5350

5451
options := types.ImageCreateOptions{
5552
Parent: ref.Name(),
5653
Tag: tag,
57-
RegistryAuth: base64.URLEncoding.EncodeToString(buf),
54+
RegistryAuth: encodedAuth,
5855
}
5956

6057
responseBody, err := cli.client.ImageCreate(options)

api/client/lib/image_create.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@ func (cli *Client) ImageCreate(options types.ImageCreateOptions) (io.ReadCloser,
1313
query := url.Values{}
1414
query.Set("fromImage", options.Parent)
1515
query.Set("tag", options.Tag)
16-
17-
headers := map[string][]string{"X-Registry-Auth": {options.RegistryAuth}}
18-
resp, err := cli.post("/images/create", query, nil, headers)
16+
resp, err := cli.tryImageCreate(query, options.RegistryAuth)
1917
if err != nil {
2018
return nil, err
2119
}
2220
return resp.body, nil
2321
}
22+
23+
func (cli *Client) tryImageCreate(query url.Values, registryAuth string) (*serverResponse, error) {
24+
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
25+
return cli.post("/images/create", query, nil, headers)
26+
}

api/client/lib/image_pull.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package lib
2+
3+
import (
4+
"io"
5+
"net/http"
6+
"net/url"
7+
8+
"github.com/docker/docker/api/types"
9+
)
10+
11+
// ImagePull request the docker host to pull an image from a remote registry.
12+
// It executes the privileged function if the operation is unauthorized
13+
// and it tries one more time.
14+
// It's up to the caller to handle the io.ReadCloser and close it properly.
15+
func (cli *Client) ImagePull(options types.ImagePullOptions, privilegeFunc RequestPrivilegeFunc) (io.ReadCloser, error) {
16+
query := url.Values{}
17+
query.Set("fromImage", options.ImageID)
18+
if options.Tag != "" {
19+
query.Set("tag", options.Tag)
20+
}
21+
22+
resp, err := cli.tryImageCreate(query, options.RegistryAuth)
23+
if resp.statusCode == http.StatusUnauthorized {
24+
newAuthHeader, privilegeErr := privilegeFunc()
25+
if privilegeErr != nil {
26+
return nil, privilegeErr
27+
}
28+
resp, err = cli.tryImageCreate(query, newAuthHeader)
29+
}
30+
if err != nil {
31+
return nil, err
32+
}
33+
return resp.body, nil
34+
}

api/client/lib/privileged.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package lib
2+
3+
// RequestPrivilegeFunc is a function interface that
4+
// clients can supply to retry operations after
5+
// getting an authorization error.
6+
// This function returns the registry authentication
7+
// header value in base 64 format, or an error
8+
// if the privilege request fails.
9+
type RequestPrivilegeFunc func() (string, error)

api/client/pull.go

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ package client
33
import (
44
"errors"
55
"fmt"
6-
"net/url"
76

87
"github.com/docker/distribution/reference"
8+
"github.com/docker/docker/api/client/lib"
9+
"github.com/docker/docker/api/types"
910
Cli "github.com/docker/docker/cli"
11+
"github.com/docker/docker/cliconfig"
12+
"github.com/docker/docker/pkg/jsonmessage"
1013
flag "github.com/docker/docker/pkg/mflag"
1114
"github.com/docker/docker/registry"
1215
tagpkg "github.com/docker/docker/tag"
@@ -62,15 +65,34 @@ func (cli *DockerCli) CmdPull(args ...string) error {
6265
return err
6366
}
6467

68+
authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
69+
requestPrivilege := cli.registryAuthenticationPrivilegedFunc(repoInfo.Index, "pull")
70+
6571
if isTrusted() && !ref.HasDigest() {
6672
// Check if tag is digest
67-
authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
68-
return cli.trustedPull(repoInfo, ref, authConfig)
73+
return cli.trustedPull(repoInfo, ref, authConfig, requestPrivilege)
74+
}
75+
76+
return cli.imagePullPrivileged(authConfig, distributionRef.String(), "", requestPrivilege)
77+
}
78+
79+
func (cli *DockerCli) imagePullPrivileged(authConfig cliconfig.AuthConfig, imageID, tag string, requestPrivilege lib.RequestPrivilegeFunc) error {
80+
81+
encodedAuth, err := authConfig.EncodeToBase64()
82+
if err != nil {
83+
return err
84+
}
85+
options := types.ImagePullOptions{
86+
ImageID: imageID,
87+
Tag: tag,
88+
RegistryAuth: encodedAuth,
6989
}
7090

71-
v := url.Values{}
72-
v.Set("fromImage", distributionRef.String())
91+
responseBody, err := cli.client.ImagePull(options, requestPrivilege)
92+
if err != nil {
93+
return err
94+
}
95+
defer responseBody.Close()
7396

74-
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
75-
return err
97+
return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut)
7698
}

api/client/trust.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/docker/distribution/reference"
2323
"github.com/docker/distribution/registry/client/auth"
2424
"github.com/docker/distribution/registry/client/transport"
25+
"github.com/docker/docker/api/client/lib"
2526
"github.com/docker/docker/api/types"
2627
"github.com/docker/docker/cliconfig"
2728
"github.com/docker/docker/pkg/ansiescape"
@@ -278,11 +279,8 @@ func notaryError(err error) error {
278279
return err
279280
}
280281

281-
func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registry.Reference, authConfig cliconfig.AuthConfig) error {
282-
var (
283-
v = url.Values{}
284-
refs = []target{}
285-
)
282+
func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registry.Reference, authConfig cliconfig.AuthConfig, requestPrivilege lib.RequestPrivilegeFunc) error {
283+
var refs []target
286284

287285
notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)
288286
if err != nil {
@@ -317,17 +315,14 @@ func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registr
317315
refs = append(refs, r)
318316
}
319317

320-
v.Set("fromImage", repoInfo.LocalName.Name())
321318
for i, r := range refs {
322319
displayTag := r.reference.String()
323320
if displayTag != "" {
324321
displayTag = ":" + displayTag
325322
}
326323
fmt.Fprintf(cli.out, "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), repoInfo.LocalName, displayTag, r.digest)
327-
v.Set("tag", r.digest.String())
328324

329-
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
330-
if err != nil {
325+
if err := cli.imagePullPrivileged(authConfig, repoInfo.LocalName.Name(), r.digest.String(), requestPrivilege); err != nil {
331326
return err
332327
}
333328

api/client/utils.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,21 @@ func (cli *DockerCli) cmdAttempt(authConfig cliconfig.AuthConfig, method, path s
160160
return serverResp.body, serverResp.statusCode, err
161161
}
162162

163+
func (cli *DockerCli) encodeRegistryAuth(index *registry.IndexInfo) (string, error) {
164+
authConfig := registry.ResolveAuthConfig(cli.configFile, index)
165+
return authConfig.EncodeToBase64()
166+
}
167+
168+
func (cli *DockerCli) registryAuthenticationPrivilegedFunc(index *registry.IndexInfo, cmdName string) lib.RequestPrivilegeFunc {
169+
return func() (string, error) {
170+
fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
171+
if err := cli.CmdLogin(index.GetAuthConfigKey()); err != nil {
172+
return "", err
173+
}
174+
return cli.encodeRegistryAuth(index)
175+
}
176+
}
177+
163178
func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) {
164179

165180
// Resolve the Auth config relevant for this server

api/types/client.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,14 @@ type ImageListOptions struct {
179179
Filters filters.Args
180180
}
181181

182+
// ImagePullOptions holds information to pull images.
183+
type ImagePullOptions struct {
184+
ImageID string
185+
Tag string
186+
// RegistryAuth is the base64 encoded credentials for this server
187+
RegistryAuth string
188+
}
189+
182190
// ImageRemoveOptions holds parameters to remove images.
183191
type ImageRemoveOptions struct {
184192
ImageID string

cliconfig/config.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@ type AuthConfig struct {
5454
RegistryToken string `json:"registrytoken,omitempty"`
5555
}
5656

57+
// EncodeToBase64 serializes the auth configuration as JSON base64 payload
58+
func (a AuthConfig) EncodeToBase64() (string, error) {
59+
buf, err := json.Marshal(a)
60+
if err != nil {
61+
return "", err
62+
}
63+
return base64.URLEncoding.EncodeToString(buf), nil
64+
}
65+
5766
// ConfigFile ~/.docker/config.json file info
5867
type ConfigFile struct {
5968
AuthConfigs map[string]AuthConfig `json:"auths"`

0 commit comments

Comments
 (0)