-
Notifications
You must be signed in to change notification settings - Fork 0
/
list.go
347 lines (304 loc) · 9.92 KB
/
list.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
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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
package graph
import (
"fmt"
"path"
"sort"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/parsers/filters"
"github.com/docker/docker/registry"
"github.com/docker/docker/utils"
)
var acceptedImageFilterTags = map[string]struct{}{
"dangling": {},
"label": {},
}
type ImagesConfig struct {
Filters string
Filter string
All bool
}
type ByCreated []*types.Image
func (r ByCreated) Len() int { return len(r) }
func (r ByCreated) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r ByCreated) Less(i, j int) bool { return r[i].Created < r[j].Created }
type byTagName []*types.RepositoryTag
func (r byTagName) Len() int { return len(r) }
func (r byTagName) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r byTagName) Less(i, j int) bool { return r[i].Tag < r[j].Tag }
type byAPIVersion []registry.APIEndpoint
func (r byAPIVersion) Len() int { return len(r) }
func (r byAPIVersion) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r byAPIVersion) Less(i, j int) bool {
if r[i].Version < r[j].Version {
return true
}
if r[i].Version == r[j].Version && strings.HasPrefix(r[i].URL, "https://") && !strings.HasPrefix(r[j].URL, "https://") {
return true
}
return false
}
// RemoteTagsConfig allows to specify transport paramater for remote ta listing.
type RemoteTagsConfig struct {
MetaHeaders map[string][]string
AuthConfig *cliconfig.AuthConfig
}
// TagLister allows to list tags of remote repository.
type TagLister interface {
ListTags() (tagList []*types.RepositoryTag, fallback bool, err error)
}
// NewTagLister creates a specific tag lister for given endpoint.
func NewTagLister(s *TagStore, endpoint registry.APIEndpoint, repoInfo *registry.RepositoryInfo, config *RemoteTagsConfig) (TagLister, error) {
switch endpoint.Version {
case registry.APIVersion2:
return &v2TagLister{
TagStore: s,
endpoint: endpoint,
config: config,
repoInfo: repoInfo,
}, nil
case registry.APIVersion1:
return &v1TagLister{
TagStore: s,
endpoint: endpoint,
config: config,
repoInfo: repoInfo,
}, nil
}
return nil, fmt.Errorf("unknown version %d for registry %s", endpoint.Version, endpoint.URL)
}
func (s *TagStore) Images(config *ImagesConfig) ([]*types.Image, error) {
var (
allImages map[string]*image.Image
err error
filtTagged = true
filtLabel = false
)
imageFilters, err := filters.FromParam(config.Filters)
if err != nil {
return nil, err
}
for name := range imageFilters {
if _, ok := acceptedImageFilterTags[name]; !ok {
return nil, fmt.Errorf("Invalid filter '%s'", name)
}
}
if i, ok := imageFilters["dangling"]; ok {
for _, value := range i {
if strings.ToLower(value) == "true" {
filtTagged = false
}
}
}
_, filtLabel = imageFilters["label"]
if config.All && filtTagged {
allImages = s.graph.Map()
} else {
allImages = s.graph.Heads()
}
// try to match filter against all repositories from additional registries
// when dealing with short name
repoNameFilters := make([]string, 1, 1+len(registry.RegistryList))
repoNameFilters[0] = config.Filter
if strings.IndexByte(config.Filter, '/') == -1 {
for _, r := range registry.RegistryList {
repoNameFilters = append(repoNameFilters, r+"/"+config.Filter)
}
}
lookup := make(map[string]*types.Image)
s.Lock()
for repoName, repository := range s.Repositories {
if repoNameFilters[0] != "" {
match := false
for _, filter := range repoNameFilters {
if match, _ = path.Match(filter, repoName); match {
break
}
}
if !match {
continue
}
}
for ref, id := range repository {
imgRef := utils.ImageReference(repoName, ref)
image, err := s.graph.Get(id)
if err != nil {
logrus.Warnf("couldn't load %s from %s: %s", id, imgRef, err)
continue
}
if lImage, exists := lookup[id]; exists {
if filtTagged {
if utils.DigestReference(ref) {
lImage.RepoDigests = append(lImage.RepoDigests, imgRef)
} else { // Tag Ref.
lImage.RepoTags = append(lImage.RepoTags, imgRef)
}
}
} else {
// get the boolean list for if only the untagged images are requested
delete(allImages, id)
if !imageFilters.MatchKVList("label", image.ContainerConfig.Labels) {
continue
}
if filtTagged {
newImage := new(types.Image)
newImage.ParentId = image.Parent
newImage.ID = image.ID
newImage.Created = int(image.Created.Unix())
newImage.Size = int(image.Size)
newImage.VirtualSize = int(s.graph.GetParentsSize(image, 0) + image.Size)
newImage.Labels = image.ContainerConfig.Labels
if utils.DigestReference(ref) {
newImage.RepoTags = []string{}
newImage.RepoDigests = []string{imgRef}
} else {
newImage.RepoTags = []string{imgRef}
newImage.RepoDigests = []string{}
}
lookup[id] = newImage
}
}
}
}
s.Unlock()
images := []*types.Image{}
for _, value := range lookup {
images = append(images, value)
}
// Display images which aren't part of a repository/tag
if config.Filter == "" || filtLabel {
for _, image := range allImages {
if !imageFilters.MatchKVList("label", image.ContainerConfig.Labels) {
continue
}
newImage := new(types.Image)
newImage.ParentId = image.Parent
newImage.RepoTags = []string{"<none>:<none>"}
newImage.RepoDigests = []string{"<none>@<none>"}
newImage.ID = image.ID
newImage.Created = int(image.Created.Unix())
newImage.Size = int(image.Size)
newImage.VirtualSize = int(s.graph.GetParentsSize(image, 0) + image.Size)
newImage.Labels = image.ContainerConfig.Labels
images = append(images, newImage)
}
}
sort.Sort(sort.Reverse(ByCreated(images)))
return images, nil
}
// Tags returns a tag list for given local repository.
func (s *TagStore) Tags(name string) (*types.RepositoryTagList, error) {
var tagList *types.RepositoryTagList
// Resolve the Repository name from fqn to RepositoryInfo
repos := s.getRepositoryList(name)
if len(repos) < 1 {
return nil, fmt.Errorf("no such repository %q", name)
}
for repoName, repo := range repos[0] {
tagList = &types.RepositoryTagList{
Name: repoName,
TagList: make([]*types.RepositoryTag, 0, len(repo)),
}
for ref, id := range repo {
tagList.TagList = append(tagList.TagList, &types.RepositoryTag{
Tag: ref,
ImageID: id,
})
}
}
sort.Sort(byTagName(tagList.TagList))
return tagList, nil
}
// RemoteTags fetches a tag list from remote repository
func (s *TagStore) RemoteTags(name string, config *RemoteTagsConfig) (*types.RepositoryTagList, error) {
var (
tagList *types.RepositoryTagList
err error
)
// Unless the index name is specified, iterate over all registries until
// the matching image is found.
if registry.RepositoryNameHasIndex(name) {
return s.getRemoteTagList(name, config)
}
if len(registry.RegistryList) == 0 {
return nil, fmt.Errorf("No configured registry to pull from.")
}
for _, r := range registry.RegistryList {
// Prepend the index name to the image name.
if tagList, err = s.getRemoteTagList(fmt.Sprintf("%s/%s", r, name), config); err == nil {
return tagList, nil
}
}
return tagList, err
}
func (s *TagStore) getRemoteTagList(name string, config *RemoteTagsConfig) (*types.RepositoryTagList, error) {
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := s.registryService.ResolveRepository(name)
if err != nil {
return nil, err
}
if err := validateRepoName(repoInfo.LocalName); err != nil {
return nil, err
}
endpoints, err := s.registryService.LookupPullEndpoints(repoInfo.CanonicalName)
if err != nil {
return nil, err
}
// Prefer v1 versions which provide also image ids
sort.Sort(byAPIVersion(endpoints))
var (
lastErr error
// discardNoSupportErrors is used to track whether an endpoint encountered an error of type registry.ErrNoSupport
// By default it is false, which means that if a ErrNoSupport error is encountered, it will be saved in lastErr.
// As soon as another kind of error is encountered, discardNoSupportErrors is set to true, avoiding the saving of
// any subsequent ErrNoSupport errors in lastErr.
// It's needed for pull-by-digest on v1 endpoints: if there are only v1 endpoints configured, the error should be
// returned and displayed, but if there was a v2 endpoint which supports pull-by-digest, then the last relevant
// error is the ones from v2 endpoints not v1.
discardNoSupportErrors bool
tagList = &types.RepositoryTagList{Name: repoInfo.CanonicalName}
)
for _, endpoint := range endpoints {
logrus.Debugf("Trying to fetch tag list of %s repository from %s %s", repoInfo.CanonicalName, endpoint.URL, endpoint.Version)
fallback := false
if !endpoint.Mirror && (endpoint.Official || endpoint.Version == registry.APIVersion2) {
if repoInfo.Official {
s.trustService.UpdateBase()
}
}
tagLister, err := NewTagLister(s, endpoint, repoInfo, config)
if err != nil {
lastErr = err
continue
}
tagList.TagList, fallback, err = tagLister.ListTags()
if err != nil {
// We're querying v1 registries first. Let's ignore errors until
// the first v2 registry.
if fallback || endpoint.Version == registry.APIVersion1 {
if _, ok := err.(registry.ErrNoSupport); !ok {
// Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors.
discardNoSupportErrors = true
// save the current error
lastErr = err
} else if !discardNoSupportErrors {
// Save the ErrNoSupport error, because it's either the first error or all encountered errors
// were also ErrNoSupport errors.
lastErr = err
}
continue
}
logrus.Debugf("Not continuing with error: %v", err)
return nil, err
}
sort.Sort(byTagName(tagList.TagList))
return tagList, nil
}
if lastErr == nil {
lastErr = fmt.Errorf("no endpoints found for %s", repoInfo.Index.Name)
}
return nil, lastErr
}