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
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
|
package registry
import (
"crypto/tls"
"fmt"
"math"
"net/http"
"strings"
"sync"
"time"
"github.com/argoproj-labs/argocd-image-updater/pkg/cache"
"github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/log"
"go.uber.org/ratelimit"
)
// TagListSort defines how the registry returns the list of tags
type TagListSort int
const (
TagListSortUnknown TagListSort = -1
TagListSortUnsorted TagListSort = 0
TagListSortLatestFirst TagListSort = 1
TagListSortLatestLast TagListSort = 2
TagListSortUnsortedString string = "unsorted"
TagListSortLatestFirstString string = "latest-first"
TagListSortLatestLastString string = "latest-last"
TagListSortUnknownString string = "unknown"
)
const (
RateLimitNone = math.MaxInt32
RateLimitDefault = 10
)
// IsTimeSorted returns whether a tag list is time sorted
func (tls TagListSort) IsTimeSorted() bool {
return tls == TagListSortLatestFirst || tls == TagListSortLatestLast
}
// TagListSortFromString gets the TagListSort value from a given string
func TagListSortFromString(tls string) TagListSort {
switch strings.ToLower(tls) {
case "latest-first":
return TagListSortLatestFirst
case "latest-last":
return TagListSortLatestLast
case "none", "":
return TagListSortUnsorted
default:
log.Warnf("unknown tag list sort mode: %s", tls)
return TagListSortUnknown
}
}
// String returns the string representation of a TagListSort value
func (tls TagListSort) String() string {
switch tls {
case TagListSortLatestFirst:
return TagListSortLatestFirstString
case TagListSortLatestLast:
return TagListSortLatestLastString
case TagListSortUnsorted:
return TagListSortUnsortedString
}
return TagListSortUnknownString
}
// RegistryEndpoint holds information on how to access any specific registry API
// endpoint.
type RegistryEndpoint struct {
RegistryName string
RegistryPrefix string
RegistryAPI string
Username string
Password string
Ping bool
Credentials string
Insecure bool
DefaultNS string
CredsExpire time.Duration
CredsUpdated time.Time
TagListSort TagListSort
Cache cache.ImageTagCache
Limiter ratelimit.Limiter
IsDefault bool
lock sync.RWMutex
limit int
}
// registryTweaks should contain a list of registries whose settings cannot be
// inferred by just looking at the image prefix. Prominent example here is the
// Docker Hub registry, which is referred to as docker.io from the image, but
// its API endpoint is https://registry-1.docker.io (and not https://docker.io)
var registryTweaks map[string]*RegistryEndpoint = map[string]*RegistryEndpoint{
"docker.io": {
RegistryName: "Docker Hub",
RegistryPrefix: "docker.io",
RegistryAPI: "https://registry-1.docker.io",
Ping: true,
Insecure: false,
DefaultNS: "library",
Cache: cache.NewMemCache(),
Limiter: ratelimit.New(RateLimitDefault),
IsDefault: true,
},
}
var registries map[string]*RegistryEndpoint = make(map[string]*RegistryEndpoint)
// Default registry points to the registry that is to be used as the default,
// e.g. when no registry prefix is given for a certain image.
var defaultRegistry *RegistryEndpoint
// Simple RW mutex for concurrent access to registries map
var registryLock sync.RWMutex
func AddRegistryEndpointFromConfig(epc RegistryConfiguration) error {
ep := NewRegistryEndpoint(epc.Prefix, epc.Name, epc.ApiURL, epc.Credentials, epc.DefaultNS, epc.Insecure, TagListSortFromString(epc.TagSortMode), epc.Limit, epc.CredsExpire)
return AddRegistryEndpoint(ep)
}
// NewRegistryEndpoint returns an endpoint object with the given configuration
// pre-populated and a fresh cache.
func NewRegistryEndpoint(prefix, name, apiUrl, credentials, defaultNS string, insecure bool, tagListSort TagListSort, limit int, credsExpire time.Duration) *RegistryEndpoint {
if limit <= 0 {
limit = RateLimitNone
}
ep := &RegistryEndpoint{
RegistryName: name,
RegistryPrefix: prefix,
RegistryAPI: strings.TrimSuffix(apiUrl, "/"),
Credentials: credentials,
CredsExpire: credsExpire,
Cache: cache.NewMemCache(),
Insecure: insecure,
DefaultNS: defaultNS,
TagListSort: tagListSort,
Limiter: ratelimit.New(limit),
limit: limit,
}
return ep
}
// AddRegistryEndpoint adds registry endpoint information with the given details
func AddRegistryEndpoint(ep *RegistryEndpoint) error {
prefix := ep.RegistryPrefix
registryLock.Lock()
// If the endpoint is supposed to be the default endpoint, make sure that
// any previously set default endpoint is unset.
if ep.IsDefault {
if dep := GetDefaultRegistry(); dep != nil {
dep.IsDefault = false
}
SetDefaultRegistry(ep)
}
registries[prefix] = ep
registryLock.Unlock()
logCtx := log.WithContext()
logCtx.AddField("registry", ep.RegistryAPI)
logCtx.AddField("prefix", ep.RegistryPrefix)
if ep.limit != RateLimitNone {
logCtx.Debugf("setting rate limit to %d requests per second", ep.limit)
} else {
logCtx.Debugf("rate limiting is disabled")
}
return nil
}
// inferRegistryEndpointFromPrefix returns a registry endpoint with the API
// URL inferred from the prefix and adds it to the list of the configured
// registries.
func inferRegistryEndpointFromPrefix(prefix string) *RegistryEndpoint {
apiURL := "https://" + prefix
return NewRegistryEndpoint(prefix, prefix, apiURL, "", "", false, TagListSortUnsorted, 20, 0)
}
// GetRegistryEndpoint retrieves the endpoint information for the given prefix
func GetRegistryEndpoint(prefix string) (*RegistryEndpoint, error) {
if prefix == "" {
if defaultRegistry == nil {
return nil, fmt.Errorf("no default endpoint configured")
} else {
return defaultRegistry, nil
}
}
registryLock.RLock()
registry, ok := registries[prefix]
registryLock.RUnlock()
if ok {
return registry, nil
} else {
var err error
ep := inferRegistryEndpointFromPrefix(prefix)
if ep != nil {
err = AddRegistryEndpoint(ep)
} else {
err = fmt.Errorf("could not infer registry configuration from prefix %s", prefix)
}
if err == nil {
log.Debugf("Inferred registry from prefix %s to use API %s", prefix, ep.RegistryAPI)
}
return ep, err
}
}
// SetDefaultRegistry sets a given registry endpoint as the default
func SetDefaultRegistry(ep *RegistryEndpoint) {
log.Debugf("Setting default registry endpoint to %s", ep.RegistryPrefix)
ep.IsDefault = true
if defaultRegistry != nil {
log.Debugf("Previous default registry was %s", defaultRegistry.RegistryPrefix)
defaultRegistry.IsDefault = false
}
defaultRegistry = ep
}
// GetDefaultRegistry returns the registry endpoint that is set as default,
// or nil if no default registry endpoint is set
func GetDefaultRegistry() *RegistryEndpoint {
if defaultRegistry != nil {
log.Debugf("Getting default registry endpoint: %s", defaultRegistry.RegistryPrefix)
} else {
log.Debugf("No default registry defined.")
}
return defaultRegistry
}
// SetRegistryEndpointCredentials allows to change the credentials used for
// endpoint access for existing RegistryEndpoint configuration
func SetRegistryEndpointCredentials(prefix, credentials string) error {
registry, err := GetRegistryEndpoint(prefix)
if err != nil {
return err
}
registry.lock.Lock()
registry.Credentials = credentials
registry.lock.Unlock()
return nil
}
// ConfiguredEndpoints returns a list of prefixes that are configured
func ConfiguredEndpoints() []string {
registryLock.RLock()
defer registryLock.RUnlock()
r := make([]string, 0, len(registries))
for _, v := range registries {
r = append(r, v.RegistryPrefix)
}
return r
}
// DeepCopy copies the endpoint to a new object, but creating a new Cache
func (ep *RegistryEndpoint) DeepCopy() *RegistryEndpoint {
ep.lock.RLock()
newEp := &RegistryEndpoint{}
newEp.RegistryAPI = ep.RegistryAPI
newEp.RegistryName = ep.RegistryName
newEp.RegistryPrefix = ep.RegistryPrefix
newEp.Credentials = ep.Credentials
newEp.Ping = ep.Ping
newEp.TagListSort = ep.TagListSort
newEp.Cache = cache.NewMemCache()
newEp.Insecure = ep.Insecure
newEp.DefaultNS = ep.DefaultNS
newEp.Limiter = ep.Limiter
newEp.CredsExpire = ep.CredsExpire
newEp.CredsUpdated = ep.CredsUpdated
newEp.IsDefault = ep.IsDefault
newEp.limit = ep.limit
ep.lock.RUnlock()
return newEp
}
// GetTransport returns a transport object for this endpoint
func (ep *RegistryEndpoint) GetTransport() *http.Transport {
tlsC := &tls.Config{}
if ep.Insecure {
tlsC.InsecureSkipVerify = true
}
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: tlsC,
}
}
// init initializes the registry configuration
func init() {
for k, v := range registryTweaks {
registries[k] = v.DeepCopy()
if v.IsDefault {
if defaultRegistry == nil {
defaultRegistry = v
} else {
panic("only one default registry can be configured")
}
}
}
}
|