Skip to content

Commit 4a0b532

Browse files
committed
feat(image): introduce WithCredentials* to select how pull credentials are retrieved
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent 461793d commit 4a0b532

File tree

3 files changed

+60
-32
lines changed

3 files changed

+60
-32
lines changed

image/options.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ package image
22

33
import (
44
"errors"
5+
"fmt"
56
"io"
67

78
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
89

910
"github.com/docker/docker/api/types/build"
1011
"github.com/docker/docker/api/types/image"
1112
"github.com/docker/go-sdk/client"
13+
"github.com/docker/go-sdk/config"
1214
)
1315

1416
// BuildOption is a function that configures the build options.
@@ -40,9 +42,39 @@ func WithBuildOptions(options build.ImageBuildOptions) BuildOption {
4042
type PullOption func(*pullOptions) error
4143

4244
type pullOptions struct {
43-
client client.SDKClient
44-
pullOptions image.PullOptions
45-
pullHandler func(r io.ReadCloser) error
45+
client client.SDKClient
46+
pullOptions image.PullOptions
47+
pullHandler func(r io.ReadCloser) error
48+
credentialsFn func(string) (string, string, error)
49+
}
50+
51+
// WithCredentialsFn sets the function to retrieve credentials for an image to be pulled
52+
func WithCredentialsFn(credentialsFn func(string) (string, string, error)) PullOption {
53+
return func(opts *pullOptions) error {
54+
opts.credentialsFn = credentialsFn
55+
return nil
56+
}
57+
}
58+
59+
// WithCredentialsFromConfig configures pull to retrieve credentials from the CLI config
60+
func WithCredentialsFromConfig(opts *pullOptions) error {
61+
opts.credentialsFn = func(imageName string) (string, string, error) {
62+
authConfigs, err := config.AuthConfigs(imageName)
63+
if err != nil {
64+
return "", "", err
65+
}
66+
67+
// there must be only one auth config for the image
68+
if len(authConfigs) > 1 {
69+
return "", "", fmt.Errorf("multiple auth configs found for image %s, expected only one", imageName)
70+
}
71+
72+
for _, ac := range authConfigs {
73+
return ac.Username, ac.Password, nil
74+
}
75+
return "", "", nil
76+
}
77+
return nil
4678
}
4779

4880
// WithPullClient sets the pull client used to pull the image.

image/pull.go

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -46,34 +46,30 @@ func Pull(ctx context.Context, imageName string, opts ...PullOption) error {
4646
pullOpts.client = sdk
4747
}
4848

49+
if pullOpts.credentialsFn == nil {
50+
if err := WithCredentialsFromConfig(pullOpts); err != nil {
51+
return fmt.Errorf("set credentials for pull option: %w", err)
52+
}
53+
}
54+
4955
if imageName == "" {
5056
return errors.New("image name is not set")
5157
}
5258

53-
authConfigs, err := config.AuthConfigs(imageName)
59+
username, password, err := pullOpts.credentialsFn(imageName)
5460
if err != nil {
55-
pullOpts.client.Logger().Warn("failed to get image auth, setting empty credentials for the image", "image", imageName, "error", err)
56-
} else {
57-
// there must be only one auth config for the image
58-
if len(authConfigs) > 1 {
59-
return fmt.Errorf("multiple auth configs found for image %s, expected only one", imageName)
60-
}
61-
62-
var tmp config.AuthConfig
63-
for _, ac := range authConfigs {
64-
tmp = ac
65-
}
61+
return fmt.Errorf("failed to retrieve registry credentials for %s: %w", imageName, err)
62+
}
6663

67-
authConfig := config.AuthConfig{
68-
Username: tmp.Username,
69-
Password: tmp.Password,
70-
}
71-
encodedJSON, err := json.Marshal(authConfig)
72-
if err != nil {
73-
pullOpts.client.Logger().Warn("failed to marshal image auth, setting empty credentials for the image", "image", imageName, "error", err)
74-
} else {
75-
pullOpts.pullOptions.RegistryAuth = base64.URLEncoding.EncodeToString(encodedJSON)
76-
}
64+
authConfig := config.AuthConfig{
65+
Username: username,
66+
Password: password,
67+
}
68+
encodedJSON, err := json.Marshal(authConfig)
69+
if err != nil {
70+
pullOpts.client.Logger().Warn("failed to marshal image auth, setting empty credentials for the image", "image", imageName, "error", err)
71+
} else {
72+
pullOpts.pullOptions.RegistryAuth = base64.URLEncoding.EncodeToString(encodedJSON)
7773
}
7874

7975
var pull io.ReadCloser

image/pull_unit_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,35 +58,35 @@ func TestPull(t *testing.T) {
5858
})
5959

6060
t.Run("success/no-retry", func(t *testing.T) {
61-
testPull(t, "someTag", defaultPullOpts, &errMockCli{err: nil}, false)
61+
testPull(t, "some_tag", defaultPullOpts, &errMockCli{err: nil}, false)
6262
})
6363

6464
t.Run("not-available/no-retry", func(t *testing.T) {
65-
testPull(t, "someTag", defaultPullOpts, &errMockCli{err: errdefs.ErrNotFound.WithMessage("not available")}, false)
65+
testPull(t, "some_tag", defaultPullOpts, &errMockCli{err: errdefs.ErrNotFound.WithMessage("not available")}, false)
6666
})
6767

6868
t.Run("invalid-parameters/no-retry", func(t *testing.T) {
69-
testPull(t, "someTag", defaultPullOpts, &errMockCli{err: errdefs.ErrInvalidArgument.WithMessage("invalid")}, false)
69+
testPull(t, "some_tag", defaultPullOpts, &errMockCli{err: errdefs.ErrInvalidArgument.WithMessage("invalid")}, false)
7070
})
7171

7272
t.Run("unauthorized/retry", func(t *testing.T) {
73-
testPull(t, "someTag", defaultPullOpts, &errMockCli{err: errdefs.ErrUnauthenticated.WithMessage("not authorized")}, false)
73+
testPull(t, "some_tag", defaultPullOpts, &errMockCli{err: errdefs.ErrUnauthenticated.WithMessage("not authorized")}, false)
7474
})
7575

7676
t.Run("forbidden/retry", func(t *testing.T) {
77-
testPull(t, "someTag", defaultPullOpts, &errMockCli{err: errdefs.ErrPermissionDenied.WithMessage("forbidden")}, false)
77+
testPull(t, "some_tag", defaultPullOpts, &errMockCli{err: errdefs.ErrPermissionDenied.WithMessage("forbidden")}, false)
7878
})
7979

8080
t.Run("not-implemented/retry", func(t *testing.T) {
81-
testPull(t, "someTag", defaultPullOpts, &errMockCli{err: errdefs.ErrNotImplemented.WithMessage("unknown method")}, false)
81+
testPull(t, "some_tag", defaultPullOpts, &errMockCli{err: errdefs.ErrNotImplemented.WithMessage("unknown method")}, false)
8282
})
8383

8484
t.Run("non-permanent-error/retry", func(t *testing.T) {
8585
mockCliWithLogger := &errMockCli{
8686
err: errors.New("whoops"),
8787
}
8888

89-
out := testPull(t, "someTag", defaultPullOpts, mockCliWithLogger, true)
89+
out := testPull(t, "some_tag", defaultPullOpts, mockCliWithLogger, true)
9090
require.Contains(t, out, "failed to pull image, will retry")
9191
})
9292
}

0 commit comments

Comments
 (0)