-
Notifications
You must be signed in to change notification settings - Fork 6
/
puller.go
121 lines (101 loc) · 3.04 KB
/
puller.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package aceptadora
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"sync"
"testing"
"time"
"github.com/distribution/reference"
imagetype "github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/client"
"github.com/stretchr/testify/require"
)
// ImagePullerConfig configures the pulling options for different image repositories
type ImagePullerConfig struct {
Repo []RepositoryConfig
}
// RepositoryConfig provides the details of access to a docker repository.
type RepositoryConfig struct {
// Domain is used to specify which domain this config applies to, like `docker.io`
Domain string
// SkipPulling can be specified if images from this domain are not intended to be pulled
// Useful for images previously built locally, or for local testing when repository credentials are not passed to the test
SkipPulling bool
// Auth provides the default docker library's field to authenticate and will be used for pulling.
// Usually Username & Password fields should be filled.
Auth registry.AuthConfig
}
func NewImagePuller(t *testing.T, cfg ImagePullerConfig) *ImagePullerImpl {
repos := make(map[string]RepositoryConfig, len(cfg.Repo))
for _, repo := range cfg.Repo {
repos[repo.Domain] = repo
}
return &ImagePullerImpl{
t: t,
require: require.New(t),
cfg: cfg,
repos: repos,
}
}
type ImagePuller interface {
Pull(ctx context.Context, imageName string)
}
type ImagePullerImpl struct {
t *testing.T
require *require.Assertions
images sync.Map
cfg ImagePullerConfig
repos map[string]RepositoryConfig
}
func (i *ImagePullerImpl) Pull(ctx context.Context, imageName string) {
imi, _ := i.images.LoadOrStore(imageName, &image{})
im := imi.(*image)
im.Do(func() {
im.err = i.tryPullImage(ctx, imageName)
})
i.require.NoError(im.err, "Can't pull image %q: %s", imageName, im.err)
}
type image struct {
sync.Once
err error
}
func (i *ImagePullerImpl) tryPullImage(ctx context.Context, imageName string) error {
t0 := time.Now()
ref, err := reference.ParseNamed(imageName)
if err != nil {
return fmt.Errorf("can't parse image name %q: %w", imageName, err)
}
domain := reference.Domain(ref)
repoCfg, ok := i.repos[domain]
if repoCfg.SkipPulling {
i.t.Logf("Not pulling %s: disabled by config for domain %s", imageName, domain)
return nil
}
var authStr string
if ok {
encodedJSON, err := json.Marshal(repoCfg.Auth)
if err != nil {
return fmt.Errorf("encoding JSON auth: %v", err)
}
authStr = base64.URLEncoding.EncodeToString(encodedJSON)
}
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
return fmt.Errorf("creating docker client: %v", err)
}
out, err := cli.ImagePull(ctx, imageName, imagetype.PullOptions{RegistryAuth: authStr})
if err != nil {
return fmt.Errorf("can't pull image %s: %v", imageName, err)
}
defer out.Close()
i.t.Logf("Pulled image %q in %s", imageName, time.Since(t0))
_, _ = io.Copy(
testLogsWriter{i.t, fmt.Sprintf("Image %q puller", imageName)},
out,
)
return nil
}