7
7
"errors"
8
8
"fmt"
9
9
"path"
10
+ "slices"
10
11
"strconv"
11
12
"strings"
12
13
"time"
@@ -21,13 +22,24 @@ import (
21
22
// indicates that the image matches the criteria.
22
23
type filterFunc func (* Image , * layerTree ) (bool , error )
23
24
24
- type compiledFilters map [string ][]filterFunc
25
+ // referenceFilterFunc is a prototype for a filter that returns a list of
26
+ // references. The first return value indicates whether the image matches the
27
+ // criteria. The second return value is a list of names that match the
28
+ // criteria. The third return value is an error.
29
+ type referenceFilterFunc func (* Image ) (bool , []string , error )
30
+
31
+ type compiledFilters struct {
32
+ filters map [string ][]filterFunc
33
+ referenceFilter referenceFilterFunc
34
+ needsLayerTree bool
35
+ }
25
36
26
37
// Apply the specified filters. All filters of each key must apply.
27
38
// tree must be provided if compileImageFilters indicated it is necessary.
28
- func (i * Image ) applyFilters (ctx context.Context , filters compiledFilters , tree * layerTree ) (bool , error ) {
29
- for key := range filters {
30
- for _ , filter := range filters [key ] {
39
+ // WARNING: Application of referenceFilter sets the image names to matched names, but this only affects the values in memory, they are not written to storage.
40
+ func (i * Image ) applyFilters (ctx context.Context , f * compiledFilters , tree * layerTree ) (bool , error ) {
41
+ for key := range f .filters {
42
+ for _ , filter := range f .filters [key ] {
31
43
matches , err := filter (i , tree )
32
44
if err != nil {
33
45
// Some images may have been corrupted in the
@@ -45,13 +57,33 @@ func (i *Image) applyFilters(ctx context.Context, filters compiledFilters, tree
45
57
}
46
58
}
47
59
}
60
+ if f .referenceFilter != nil {
61
+ referenceMatch , names , err := f .referenceFilter (i )
62
+ if err != nil {
63
+ // Some images may have been corrupted in the
64
+ // meantime, so do an extra check and make the
65
+ // error non-fatal (see containers/podman/issues/12582).
66
+ if errCorrupted := i .isCorrupted (ctx , "" ); errCorrupted != nil {
67
+ logrus .Error (errCorrupted .Error ())
68
+ return false , nil
69
+ }
70
+ return false , err
71
+ }
72
+ if ! referenceMatch {
73
+ return false , nil
74
+ }
75
+ if len (names ) > 0 {
76
+ i .setEphemeralNames (names )
77
+ }
78
+ }
48
79
return true , nil
49
80
}
50
81
51
82
// filterImages returns a slice of images which are passing all specified
52
83
// filters.
53
84
// tree must be provided if compileImageFilters indicated it is necessary.
54
- func (r * Runtime ) filterImages (ctx context.Context , images []* Image , filters compiledFilters , tree * layerTree ) ([]* Image , error ) {
85
+ // WARNING: Application of referenceFilter sets the image names to matched names, but this only affects the values in memory, they are not written to storage.
86
+ func (r * Runtime ) filterImages (ctx context.Context , images []* Image , filters * compiledFilters , tree * layerTree ) ([]* Image , error ) {
55
87
result := []* Image {}
56
88
for i := range images {
57
89
match , err := images [i ].applyFilters (ctx , filters , tree )
@@ -71,17 +103,19 @@ func (r *Runtime) filterImages(ctx context.Context, images []*Image, filters com
71
103
// after, since, before, containers, dangling, id, label, readonly, reference, intermediate
72
104
//
73
105
// compileImageFilters returns: compiled filters, if LayerTree is needed, error
74
- func (r * Runtime ) compileImageFilters (ctx context.Context , options * ListImagesOptions ) (compiledFilters , bool , error ) {
106
+ func (r * Runtime ) compileImageFilters (ctx context.Context , options * ListImagesOptions ) (* compiledFilters , error ) {
75
107
logrus .Tracef ("Parsing image filters %s" , options .Filters )
76
108
if len (options .Filters ) == 0 {
77
- return nil , false , nil
109
+ return & compiledFilters {} , nil
78
110
}
79
111
80
112
filterInvalidValue := `invalid image filter %q: must be in the format "filter=value or filter!=value"`
81
113
82
114
var wantedReferenceMatches , unwantedReferenceMatches []string
83
- filters := compiledFilters {}
84
- needsLayerTree := false
115
+ cf := compiledFilters {
116
+ filters : map [string ][]filterFunc {},
117
+ needsLayerTree : false ,
118
+ }
85
119
duplicate := map [string ]string {}
86
120
for _ , f := range options .Filters {
87
121
var key , value string
@@ -93,7 +127,7 @@ func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOp
93
127
} else {
94
128
split = strings .SplitN (f , "=" , 2 )
95
129
if len (split ) != 2 {
96
- return nil , false , fmt .Errorf (filterInvalidValue , f )
130
+ return nil , fmt .Errorf (filterInvalidValue , f )
97
131
}
98
132
}
99
133
@@ -103,30 +137,30 @@ func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOp
103
137
case "after" , "since" :
104
138
img , err := r .time (key , value )
105
139
if err != nil {
106
- return nil , false , err
140
+ return nil , err
107
141
}
108
142
key = "since"
109
143
filter = filterAfter (img .Created ())
110
144
111
145
case "before" :
112
146
img , err := r .time (key , value )
113
147
if err != nil {
114
- return nil , false , err
148
+ return nil , err
115
149
}
116
150
filter = filterBefore (img .Created ())
117
151
118
152
case "containers" :
119
153
if err := r .containers (duplicate , key , value , options .IsExternalContainerFunc ); err != nil {
120
- return nil , false , err
154
+ return nil , err
121
155
}
122
156
filter = filterContainers (value , options .IsExternalContainerFunc )
123
157
124
158
case "dangling" :
125
159
dangling , err := r .bool (duplicate , key , value )
126
160
if err != nil {
127
- return nil , false , err
161
+ return nil , err
128
162
}
129
- needsLayerTree = true
163
+ cf . needsLayerTree = true
130
164
filter = filterDangling (ctx , dangling )
131
165
132
166
case "id" :
@@ -135,31 +169,31 @@ func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOp
135
169
case "digest" :
136
170
f , err := filterDigest (value )
137
171
if err != nil {
138
- return nil , false , err
172
+ return nil , err
139
173
}
140
174
filter = f
141
175
142
176
case "intermediate" :
143
177
intermediate , err := r .bool (duplicate , key , value )
144
178
if err != nil {
145
- return nil , false , err
179
+ return nil , err
146
180
}
147
- needsLayerTree = true
181
+ cf . needsLayerTree = true
148
182
filter = filterIntermediate (ctx , intermediate )
149
183
150
184
case "label" :
151
185
filter = filterLabel (ctx , value )
152
186
case "readonly" :
153
187
readOnly , err := r .bool (duplicate , key , value )
154
188
if err != nil {
155
- return nil , false , err
189
+ return nil , err
156
190
}
157
191
filter = filterReadOnly (readOnly )
158
192
159
193
case "manifest" :
160
194
manifest , err := r .bool (duplicate , key , value )
161
195
if err != nil {
162
- return nil , false , err
196
+ return nil , err
163
197
}
164
198
filter = filterManifest (ctx , manifest )
165
199
@@ -174,25 +208,23 @@ func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOp
174
208
case "until" :
175
209
until , err := r .until (value )
176
210
if err != nil {
177
- return nil , false , err
211
+ return nil , err
178
212
}
179
213
filter = filterBefore (until )
180
214
181
215
default :
182
- return nil , false , fmt .Errorf (filterInvalidValue , key )
216
+ return nil , fmt .Errorf (filterInvalidValue , key )
183
217
}
184
218
if negate {
185
219
filter = negateFilter (filter )
186
220
}
187
- filters [key ] = append (filters [key ], filter )
221
+ cf . filters [key ] = append (cf . filters [key ], filter )
188
222
}
189
223
190
224
// reference filters is a special case as it does an OR for positive matches
191
- // and an AND logic for negative matches
192
- filter := filterReferences (r , wantedReferenceMatches , unwantedReferenceMatches )
193
- filters ["reference" ] = append (filters ["reference" ], filter )
194
-
195
- return filters , needsLayerTree , nil
225
+ // and an AND logic for negative matches and the filter function type is different.
226
+ cf .referenceFilter = filterReferences (r , wantedReferenceMatches , unwantedReferenceMatches )
227
+ return & cf , nil
196
228
}
197
229
198
230
func negateFilter (f filterFunc ) filterFunc {
@@ -265,62 +297,100 @@ func filterManifest(ctx context.Context, value bool) filterFunc {
265
297
266
298
// filterReferences creates a reference filter for matching the specified wantedReferenceMatches value (OR logic)
267
299
// and for matching the unwantedReferenceMatches values (AND logic)
268
- func filterReferences (r * Runtime , wantedReferenceMatches , unwantedReferenceMatches []string ) filterFunc {
269
- return func (img * Image , _ * layerTree ) (bool , error ) {
300
+ func filterReferences (r * Runtime , wantedReferenceMatches , unwantedReferenceMatches []string ) referenceFilterFunc {
301
+ return func (img * Image ) (bool , [] string , error ) {
270
302
// Empty reference filters, return true
271
303
if len (wantedReferenceMatches ) == 0 && len (unwantedReferenceMatches ) == 0 {
272
- return true , nil
304
+ return true , nil , nil
273
305
}
274
306
275
- unwantedMatched := false
276
307
// Go through the unwanted matches first
308
+ // TODO 6.0 podman: remove unwanted matches from the output names. https://github.com/containers/common/pull/2413#discussion_r2031749013
277
309
for _ , value := range unwantedReferenceMatches {
278
- matches , err := imageMatchesReferenceFilter (r , img , value )
310
+ names , err := getMatchedImageNames (r , img , value )
279
311
if err != nil {
280
- return false , err
312
+ return false , nil , err
281
313
}
282
- if matches {
283
- unwantedMatched = true
314
+ if len ( names ) > 0 {
315
+ return false , nil , nil
284
316
}
285
317
}
286
318
287
- // If there are no wanted match filters, then return false for the image
288
- // that matched the unwanted value otherwise return true
319
+ namesThatMatch := slices .Clone (img .Names ())
320
+ // If there are no wanted match filters, then return true for the image
321
+ // that don't march matched the unwanted filters.
289
322
if len (wantedReferenceMatches ) == 0 {
290
- return ! unwantedMatched , nil
323
+ return true , namesThatMatch , nil
291
324
}
292
325
293
- // Go through the wanted matches
294
326
// If an image matches the wanted filter but it also matches the unwanted
295
327
// filter, don't add it to the output
328
+ matchedNames := map [string ]struct {}{}
329
+
330
+ // If the wanted reference is RepoDigest and image match. All names of image are returned.
331
+ isRepoDigest := false
332
+
296
333
for _ , value := range wantedReferenceMatches {
297
- matches , err := imageMatchesReferenceFilter (r , img , value )
334
+ names , err := getMatchedImageNames (r , img , value )
298
335
if err != nil {
299
- return false , err
336
+ return false , nil , err
337
+ }
338
+
339
+ for name := range names {
340
+ repoDigests , err := img .RepoDigests ()
341
+ if err != nil {
342
+ return false , nil , err
343
+ }
344
+ for _ , repoDigest := range repoDigests {
345
+ if name == repoDigest {
346
+ isRepoDigest = true
347
+ break
348
+ }
349
+ }
350
+
351
+ if isRepoDigest {
352
+ break
353
+ }
354
+ matchedNames [name ] = struct {}{}
300
355
}
301
- if matches && ! unwantedMatched {
302
- return true , nil
356
+
357
+ if isRepoDigest {
358
+ break
303
359
}
304
360
}
305
361
306
- return false , nil
362
+ if isRepoDigest {
363
+ return true , namesThatMatch , nil
364
+ }
365
+
366
+ if len (matchedNames ) > 0 {
367
+ // Removes non-compliant names from image names
368
+ namesThatMatch = slices .DeleteFunc (namesThatMatch , func (name string ) bool {
369
+ _ , ok := matchedNames [name ]
370
+ return ! ok
371
+ })
372
+ return true , namesThatMatch , nil
373
+ }
374
+
375
+ return false , nil , nil
307
376
}
308
377
}
309
378
310
- // imageMatchesReferenceFilter returns true if an image matches the filter value given
311
- func imageMatchesReferenceFilter (r * Runtime , img * Image , value string ) (bool , error ) {
312
- lookedUp , _ , _ := r .LookupImage (value , nil )
379
+ // getMatchedImageNames returns a set of matching image names that match the specified filter value, or an empty list if the image does not match the filter.
380
+ func getMatchedImageNames (r * Runtime , img * Image , value string ) (map [ string ] struct {} , error ) {
381
+ lookedUp , resolvedName , _ := r .LookupImage (value , nil )
313
382
if lookedUp != nil {
314
383
if lookedUp .ID () == img .ID () {
315
- return true , nil
384
+ return map [ string ] struct {}{ resolvedName : {}} , nil
316
385
}
317
386
}
318
387
319
388
refs , err := img .NamesReferences ()
320
389
if err != nil {
321
- return false , err
390
+ return nil , err
322
391
}
323
392
393
+ resolvedNames := map [string ]struct {}{}
324
394
for _ , ref := range refs {
325
395
refString := ref .String () // FQN with tag/digest
326
396
candidates := []string {refString }
@@ -348,12 +418,12 @@ func imageMatchesReferenceFilter(r *Runtime, img *Image, value string) (bool, er
348
418
// path.Match() is also used by Docker's reference.FamiliarMatch().
349
419
matched , _ := path .Match (value , candidate )
350
420
if matched {
351
- return true , nil
421
+ resolvedNames [refString ] = struct {}{}
422
+ break
352
423
}
353
424
}
354
425
}
355
-
356
- return false , nil
426
+ return resolvedNames , nil
357
427
}
358
428
359
429
// filterLabel creates a label for matching the specified value.
0 commit comments