-
Notifications
You must be signed in to change notification settings - Fork 203
/
Copy pathfilters.go
456 lines (404 loc) · 13.2 KB
/
filters.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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
//go:build !remote
package libimage
import (
"context"
"errors"
"fmt"
"path"
"strconv"
"strings"
"time"
filtersPkg "github.com/containers/common/pkg/filters"
"github.com/containers/common/pkg/timetype"
"github.com/containers/image/v5/docker/reference"
"github.com/sirupsen/logrus"
)
// filterFunc is a prototype for a positive image filter. Returning `true`
// indicates that the image matches the criteria.
type filterFunc func(*Image, *layerTree) (bool, error)
type compiledFilters map[string][]filterFunc
// Apply the specified filters. All filters of each key must apply.
// tree must be provided if compileImageFilters indicated it is necessary.
func (i *Image) applyFilters(ctx context.Context, filters compiledFilters, tree *layerTree) (bool, error) {
for key := range filters {
for _, filter := range filters[key] {
matches, err := filter(i, tree)
if err != nil {
// Some images may have been corrupted in the
// meantime, so do an extra check and make the
// error non-fatal (see containers/podman/issues/12582).
if errCorrupted := i.isCorrupted(ctx, ""); errCorrupted != nil {
logrus.Error(errCorrupted.Error())
return false, nil
}
return false, err
}
// If any filter within a group doesn't match, return false
if !matches {
return false, nil
}
}
}
return true, nil
}
// filterImages returns a slice of images which are passing all specified
// filters.
// tree must be provided if compileImageFilters indicated it is necessary.
func (r *Runtime) filterImages(ctx context.Context, images []*Image, filters compiledFilters, tree *layerTree) ([]*Image, error) {
result := []*Image{}
for i := range images {
match, err := images[i].applyFilters(ctx, filters, tree)
if err != nil {
return nil, err
}
if match {
result = append(result, images[i])
}
}
return result, nil
}
// compileImageFilters creates `filterFunc`s for the specified filters. The
// required format is `key=value` with the following supported keys:
//
// after, since, before, containers, dangling, id, label, readonly, reference, intermediate
//
// compileImageFilters returns: compiled filters, if LayerTree is needed, error
func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOptions) (compiledFilters, bool, error) {
logrus.Tracef("Parsing image filters %s", options.Filters)
if len(options.Filters) == 0 {
return nil, false, nil
}
filterInvalidValue := `invalid image filter %q: must be in the format "filter=value or filter!=value"`
var wantedReferenceMatches, unwantedReferenceMatches []string
filters := compiledFilters{}
needsLayerTree := false
duplicate := map[string]string{}
for _, f := range options.Filters {
var key, value string
var filter filterFunc
negate := false
split := strings.SplitN(f, "!=", 2)
if len(split) == 2 {
negate = true
} else {
split = strings.SplitN(f, "=", 2)
if len(split) != 2 {
return nil, false, fmt.Errorf(filterInvalidValue, f)
}
}
key = split[0]
value = split[1]
switch key {
case "after", "since":
img, err := r.time(key, value)
if err != nil {
return nil, false, err
}
key = "since"
filter = filterAfter(img.Created())
case "before":
img, err := r.time(key, value)
if err != nil {
return nil, false, err
}
filter = filterBefore(img.Created())
case "containers":
if err := r.containers(duplicate, key, value, options.IsExternalContainerFunc); err != nil {
return nil, false, err
}
filter = filterContainers(value, options.IsExternalContainerFunc)
case "dangling":
dangling, err := r.bool(duplicate, key, value)
if err != nil {
return nil, false, err
}
needsLayerTree = true
filter = filterDangling(ctx, dangling)
case "id":
filter = filterID(value)
case "digest":
f, err := filterDigest(value)
if err != nil {
return nil, false, err
}
filter = f
case "intermediate":
intermediate, err := r.bool(duplicate, key, value)
if err != nil {
return nil, false, err
}
needsLayerTree = true
filter = filterIntermediate(ctx, intermediate)
case "label":
filter = filterLabel(ctx, value)
case "readonly":
readOnly, err := r.bool(duplicate, key, value)
if err != nil {
return nil, false, err
}
filter = filterReadOnly(readOnly)
case "manifest":
manifest, err := r.bool(duplicate, key, value)
if err != nil {
return nil, false, err
}
filter = filterManifest(ctx, manifest)
case "reference":
if negate {
unwantedReferenceMatches = append(unwantedReferenceMatches, value)
} else {
wantedReferenceMatches = append(wantedReferenceMatches, value)
}
continue
case "until":
until, err := r.until(value)
if err != nil {
return nil, false, err
}
filter = filterBefore(until)
default:
return nil, false, fmt.Errorf(filterInvalidValue, key)
}
if negate {
filter = negateFilter(filter)
}
filters[key] = append(filters[key], filter)
}
// reference filters is a special case as it does an OR for positive matches
// and an AND logic for negative matches
filter := filterReferences(r, wantedReferenceMatches, unwantedReferenceMatches)
filters["reference"] = append(filters["reference"], filter)
return filters, needsLayerTree, nil
}
func negateFilter(f filterFunc) filterFunc {
return func(img *Image, tree *layerTree) (bool, error) {
b, err := f(img, tree)
return !b, err
}
}
func (r *Runtime) containers(duplicate map[string]string, key, value string, externalFunc IsExternalContainerFunc) error {
if exists, ok := duplicate[key]; ok && exists != value {
return fmt.Errorf("specifying %q filter more than once with different values is not supported", key)
}
duplicate[key] = value
switch value {
case "false", "true":
case "external":
if externalFunc == nil {
return errors.New("libimage error: external containers filter without callback")
}
default:
return fmt.Errorf("unsupported value %q for containers filter", value)
}
return nil
}
func (r *Runtime) until(value string) (time.Time, error) {
var until time.Time
ts, err := timetype.GetTimestamp(value, time.Now())
if err != nil {
return until, err
}
seconds, nanoseconds, err := timetype.ParseTimestamps(ts, 0)
if err != nil {
return until, err
}
return time.Unix(seconds, nanoseconds), nil
}
func (r *Runtime) time(key, value string) (*Image, error) {
img, _, err := r.LookupImage(value, nil)
if err != nil {
return nil, fmt.Errorf("could not find local image for filter %q=%q: %w", key, value, err)
}
return img, nil
}
func (r *Runtime) bool(duplicate map[string]string, key, value string) (bool, error) {
if exists, ok := duplicate[key]; ok && exists != value {
return false, fmt.Errorf("specifying %q filter more than once with different values is not supported", key)
}
duplicate[key] = value
set, err := strconv.ParseBool(value)
if err != nil {
return false, fmt.Errorf("non-boolean value %q for %s filter: %w", key, value, err)
}
return set, nil
}
// filterManifest filters whether or not the image is a manifest list
func filterManifest(ctx context.Context, value bool) filterFunc {
return func(img *Image, _ *layerTree) (bool, error) {
isManifestList, err := img.IsManifestList(ctx)
if err != nil {
return false, err
}
return isManifestList == value, nil
}
}
// filterReferences creates a reference filter for matching the specified wantedReferenceMatches value (OR logic)
// and for matching the unwantedReferenceMatches values (AND logic)
func filterReferences(r *Runtime, wantedReferenceMatches, unwantedReferenceMatches []string) filterFunc {
return func(img *Image, _ *layerTree) (bool, error) {
// Empty reference filters, return true
if len(wantedReferenceMatches) == 0 && len(unwantedReferenceMatches) == 0 {
return true, nil
}
unwantedMatched := false
// Go through the unwanted matches first
for _, value := range unwantedReferenceMatches {
matches, err := imageMatchesReferenceFilter(r, img, value)
if err != nil {
return false, err
}
if matches {
unwantedMatched = true
}
}
// If there are no wanted match filters, then return false for the image
// that matched the unwanted value otherwise return true
if len(wantedReferenceMatches) == 0 {
return !unwantedMatched, nil
}
// Go through the wanted matches
// If an image matches the wanted filter but it also matches the unwanted
// filter, don't add it to the output
for _, value := range wantedReferenceMatches {
matches, err := imageMatchesReferenceFilter(r, img, value)
if err != nil {
return false, err
}
if matches && !unwantedMatched {
return true, nil
}
}
return false, nil
}
}
// imageMatchesReferenceFilter returns true if an image matches the filter value given
func imageMatchesReferenceFilter(r *Runtime, img *Image, value string) (bool, error) {
lookedUp, _, _ := r.LookupImage(value, nil)
if lookedUp != nil {
if lookedUp.ID() == img.ID() {
return true, nil
}
}
refs, err := img.NamesReferences()
if err != nil {
return false, err
}
for _, ref := range refs {
refString := ref.String() // FQN with tag/digest
candidates := []string{refString}
// Split the reference into 3 components (twice if digested/tagged):
// 1) Fully-qualified reference
// 2) Without domain
// 3) Without domain and path
if named, isNamed := ref.(reference.Named); isNamed {
candidates = append(candidates,
reference.Path(named), // path/name without tag/digest (Path() removes it)
refString[strings.LastIndex(refString, "/")+1:]) // name with tag/digest
trimmedString := reference.TrimNamed(named).String()
if refString != trimmedString {
tagOrDigest := refString[len(trimmedString):]
candidates = append(candidates,
trimmedString, // FQN without tag/digest
reference.Path(named)+tagOrDigest, // path/name with tag/digest
trimmedString[strings.LastIndex(trimmedString, "/")+1:]) // name without tag/digest
}
}
for _, candidate := range candidates {
// path.Match() is also used by Docker's reference.FamiliarMatch().
matched, _ := path.Match(value, candidate)
if matched {
return true, nil
}
}
}
return false, nil
}
// filterLabel creates a label for matching the specified value.
func filterLabel(ctx context.Context, value string) filterFunc {
return func(img *Image, _ *layerTree) (bool, error) {
labels, err := img.Labels(ctx)
if err != nil {
return false, err
}
return filtersPkg.MatchLabelFilters([]string{value}, labels), nil
}
}
// filterAfter creates an after filter for matching the specified value.
func filterAfter(value time.Time) filterFunc {
return func(img *Image, _ *layerTree) (bool, error) {
return img.Created().After(value), nil
}
}
// filterBefore creates a before filter for matching the specified value.
func filterBefore(value time.Time) filterFunc {
return func(img *Image, _ *layerTree) (bool, error) {
return img.Created().Before(value), nil
}
}
// filterReadOnly creates a readonly filter for matching the specified value.
func filterReadOnly(value bool) filterFunc {
return func(img *Image, _ *layerTree) (bool, error) {
return img.IsReadOnly() == value, nil
}
}
// filterContainers creates a container filter for matching the specified value.
func filterContainers(value string, fn IsExternalContainerFunc) filterFunc {
return func(img *Image, _ *layerTree) (bool, error) {
ctrs, err := img.Containers()
if err != nil {
return false, err
}
if value != "external" {
boolValue := value == "true"
return (len(ctrs) > 0) == boolValue, nil
}
// Check whether all associated containers are external ones.
for _, c := range ctrs {
isExternal, err := fn(c)
if err != nil {
return false, fmt.Errorf("checking if %s is an external container in filter: %w", c, err)
}
if !isExternal {
return isExternal, nil
}
}
return true, nil
}
}
// filterDangling creates a dangling filter for matching the specified value.
func filterDangling(ctx context.Context, value bool) filterFunc {
return func(img *Image, tree *layerTree) (bool, error) {
isDangling, err := img.isDangling(ctx, tree)
if err != nil {
return false, err
}
return isDangling == value, nil
}
}
// filterID creates an image-ID filter for matching the specified value.
func filterID(value string) filterFunc {
return func(img *Image, _ *layerTree) (bool, error) {
return strings.HasPrefix(img.ID(), value), nil
}
}
// filterDigest creates a digest filter for matching the specified value.
func filterDigest(value string) (filterFunc, error) {
if !strings.HasPrefix(value, "sha256:") {
return nil, fmt.Errorf("invalid value %q for digest filter", value)
}
return func(img *Image, _ *layerTree) (bool, error) {
return img.containsDigestPrefix(value), nil
}, nil
}
// filterIntermediate creates an intermediate filter for images. An image is
// considered to be an intermediate image if it is dangling (i.e., no tags) and
// has no children (i.e., no other image depends on it).
func filterIntermediate(ctx context.Context, value bool) filterFunc {
return func(img *Image, tree *layerTree) (bool, error) {
isIntermediate, err := img.isIntermediate(ctx, tree)
if err != nil {
return false, err
}
return isIntermediate == value, nil
}
}