This repository has been archived by the owner on Oct 27, 2022. It is now read-only.
forked from regclient/regclient
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathregclient.go
275 lines (242 loc) · 6.95 KB
/
regclient.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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
// Package regclient is used to access OCI registries
package regclient
import (
"embed"
"encoding/json"
"errors"
"io/fs"
"io/ioutil"
"time"
"fmt"
"github.com/regclient/regclient/config"
"github.com/regclient/regclient/internal/rwfs"
"github.com/regclient/regclient/scheme"
"github.com/regclient/regclient/scheme/ocidir"
"github.com/regclient/regclient/scheme/reg"
"github.com/sirupsen/logrus"
)
const (
// DefaultUserAgent sets the header on http requests
DefaultUserAgent = "regclient/regclient"
// DockerCertDir default location for docker certs
DockerCertDir = "/etc/docker/certs.d"
// DockerRegistry is the well known name of Docker Hub, "docker.io"
DockerRegistry = config.DockerRegistry
// DockerRegistryAuth is the name of Docker Hub seen in docker's config.json
DockerRegistryAuth = config.DockerRegistryAuth
// DockerRegistryDNS is the actual registry DNS name for Docker Hub
DockerRegistryDNS = config.DockerRegistryDNS
)
//go:embed embed/*
var embedFS embed.FS
var (
// VCSRef is populated from an embed at build time to the git reference
VCSRef = ""
// VCSTag is populated from an embed at build time to the git tag if defined
VCSTag = ""
)
func init() {
setupVCSVars()
}
// RegClient is used to access OCI distribution-spec registries
type RegClient struct {
hosts map[string]*config.Host
log *logrus.Logger
// mu sync.Mutex
regOpts []reg.Opts
schemes map[string]scheme.API
userAgent string
fs rwfs.RWFS
}
// Opt functions are used to configure NewRegClient
type Opt func(*RegClient)
// New returns a registry client
func New(opts ...Opt) *RegClient {
var rc = RegClient{
hosts: map[string]*config.Host{},
userAgent: DefaultUserAgent,
// logging is disabled by default
log: &logrus.Logger{Out: ioutil.Discard},
regOpts: []reg.Opts{},
schemes: map[string]scheme.API{},
fs: rwfs.OSNew(""),
}
if VCSTag != "" {
rc.userAgent = fmt.Sprintf("%s (%s)", rc.userAgent, VCSTag)
} else if VCSRef != "" {
rc.userAgent = fmt.Sprintf("%s (%s)", rc.userAgent, VCSRef)
}
// inject Docker Hub settings
rc.hostSet(*config.HostNewName(config.DockerRegistryAuth))
for _, opt := range opts {
opt(&rc)
}
// configure regOpts
hostList := []*config.Host{}
for _, h := range rc.hosts {
hostList = append(hostList, h)
}
rc.regOpts = append(rc.regOpts,
reg.WithConfigHosts(hostList),
reg.WithLog(rc.log),
reg.WithUserAgent(rc.userAgent),
)
// setup scheme's
rc.schemes["reg"] = reg.New(rc.regOpts...)
rc.schemes["ocidir"] = ocidir.New(
ocidir.WithLog(rc.log),
ocidir.WithFS(rc.fs),
)
rc.log.WithFields(logrus.Fields{
"VCSRef": VCSRef,
"VCSTag": VCSTag,
}).Debug("regclient initialized")
return &rc
}
// WithCertDir adds a path of certificates to trust similar to Docker's /etc/docker/certs.d
func WithCertDir(path ...string) Opt {
return func(rc *RegClient) {
rc.regOpts = append(rc.regOpts, reg.WithCertDirs(path))
}
}
// WithDockerCerts adds certificates trusted by docker in /etc/docker/certs.d
func WithDockerCerts() Opt {
return WithCertDir(DockerCertDir)
}
// WithDockerCreds adds configuration from users docker config with registry logins
// This changes the default value from the config file, and should be added after the config file is loaded
func WithDockerCreds() Opt {
return func(rc *RegClient) {
configHosts, err := config.DockerLoad()
if err != nil {
rc.log.WithFields(logrus.Fields{
"err": err,
}).Warn("Failed to load docker creds")
return
}
rc.hostLoad("docker", configHosts)
}
}
// WithConfigHosts adds a list of config host settings
func WithConfigHosts(configHosts []config.Host) Opt {
return func(rc *RegClient) {
rc.hostLoad("host", configHosts)
}
}
// WithConfigHost adds config host settings
func WithConfigHost(configHost config.Host) Opt {
return WithConfigHosts([]config.Host{configHost})
}
// WithBlobSize overrides default blob sizes
func WithBlobSize(chunk, max int64) Opt {
return func(rc *RegClient) {
rc.regOpts = append(rc.regOpts, reg.WithBlobSize(chunk, max))
}
}
// WithFS overrides the backing filesystem (used by ocidir)
func WithFS(fs rwfs.RWFS) Opt {
return func(rc *RegClient) {
rc.fs = fs
}
}
// WithLog overrides default logrus Logger
func WithLog(log *logrus.Logger) Opt {
return func(rc *RegClient) {
rc.log = log
}
}
// WithRetryDelay specifies the time permitted for retry delays
func WithRetryDelay(delayInit, delayMax time.Duration) Opt {
return func(rc *RegClient) {
rc.regOpts = append(rc.regOpts, reg.WithDelay(delayInit, delayMax))
}
}
// WithRetryLimit specifies the number of retries for non-fatal errors
func WithRetryLimit(retryLimit int) Opt {
return func(rc *RegClient) {
rc.regOpts = append(rc.regOpts, reg.WithRetryLimit(retryLimit))
}
}
// WithUserAgent specifies the User-Agent http header
func WithUserAgent(ua string) Opt {
return func(rc *RegClient) {
rc.userAgent = ua
}
}
func (rc *RegClient) hostLoad(src string, hosts []config.Host) {
for _, configHost := range hosts {
if configHost.Name == "" {
// TODO: should this error, warn, or fall back to hostname?
continue
}
if configHost.Name == DockerRegistry || configHost.Name == DockerRegistryDNS || configHost.Name == DockerRegistryAuth {
configHost.Name = DockerRegistry
if configHost.Hostname == "" || configHost.Hostname == DockerRegistry || configHost.Hostname == DockerRegistryAuth {
configHost.Hostname = DockerRegistryDNS
}
}
tls, _ := configHost.TLS.MarshalText()
rc.log.WithFields(logrus.Fields{
"name": configHost.Name,
"user": configHost.User,
"hostname": configHost.Hostname,
"helper": configHost.CredHelper,
"repoAuth": configHost.RepoAuth,
"tls": string(tls),
"pathPrefix": configHost.PathPrefix,
"mirrors": configHost.Mirrors,
"api": configHost.API,
"blobMax": configHost.BlobMax,
"blobChunk": configHost.BlobChunk,
}).Debugf("Loading %s config", src)
err := rc.hostSet(configHost)
if err != nil {
rc.log.WithFields(logrus.Fields{
"host": configHost.Name,
"user": configHost.User,
"error": err,
}).Warn("Failed to update host config")
}
}
}
func (rc *RegClient) hostSet(newHost config.Host) error {
name := newHost.Name
var err error
// hostSet should only run on New, which single threaded
// rc.mu.Lock()
// defer rc.mu.Unlock()
if _, ok := rc.hosts[name]; !ok {
// merge newHost with default host settings
rc.hosts[name] = config.HostNewName(name)
err = rc.hosts[name].Merge(newHost, nil)
} else {
// merge newHost with existing settings
err = rc.hosts[name].Merge(newHost, rc.log)
}
if err != nil {
return err
}
return nil
}
func setupVCSVars() {
verS := struct {
VCSRef string
VCSTag string
}{}
verB, err := embedFS.ReadFile("embed/version.json")
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return
}
if len(verB) > 0 {
err = json.Unmarshal(verB, &verS)
if err != nil {
return
}
}
if verS.VCSRef != "" {
VCSRef = verS.VCSRef
}
if verS.VCSTag != "" {
VCSTag = verS.VCSTag
}
}