github.com/containerd/nerdctl@v1.7.7/pkg/imgutil/filtering.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package imgutil
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"regexp"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/containerd/containerd"
    27  	"github.com/containerd/containerd/images"
    28  	"github.com/containerd/log"
    29  	"github.com/containerd/nerdctl/pkg/referenceutil"
    30  	dockerreference "github.com/distribution/reference"
    31  )
    32  
    33  // Filter types supported to filter images.
    34  var (
    35  	FilterBeforeType    = "before"
    36  	FilterSinceType     = "since"
    37  	FilterLabelType     = "label"
    38  	FilterReferenceType = "reference"
    39  	FilterDanglingType  = "dangling"
    40  )
    41  
    42  // Filters contains all types of filters to filter images.
    43  type Filters struct {
    44  	Before    []string
    45  	Since     []string
    46  	Labels    map[string]string
    47  	Reference []string
    48  	Dangling  *bool
    49  }
    50  
    51  // ParseFilters parse filter strings.
    52  func ParseFilters(filters []string) (*Filters, error) {
    53  	f := &Filters{Labels: make(map[string]string)}
    54  	for _, filter := range filters {
    55  		tempFilterToken := strings.Split(filter, "=")
    56  		switch len(tempFilterToken) {
    57  		case 1:
    58  			return nil, fmt.Errorf("invalid filter %q", filter)
    59  		case 2:
    60  			if tempFilterToken[0] == FilterDanglingType {
    61  				var isDangling bool
    62  				if tempFilterToken[1] == "true" {
    63  					isDangling = true
    64  				} else if tempFilterToken[1] == "false" {
    65  					isDangling = false
    66  				} else {
    67  					return nil, fmt.Errorf("invalid filter %q", filter)
    68  				}
    69  				f.Dangling = &isDangling
    70  			} else if tempFilterToken[0] == FilterBeforeType {
    71  				canonicalRef, err := referenceutil.ParseAny(tempFilterToken[1])
    72  				if err != nil {
    73  					return nil, err
    74  				}
    75  
    76  				f.Before = append(f.Before, fmt.Sprintf("name==%s", canonicalRef.String()))
    77  				f.Before = append(f.Before, fmt.Sprintf("name==%s", tempFilterToken[1]))
    78  			} else if tempFilterToken[0] == FilterSinceType {
    79  				canonicalRef, err := referenceutil.ParseAny(tempFilterToken[1])
    80  				if err != nil {
    81  					return nil, err
    82  				}
    83  				f.Since = append(f.Since, fmt.Sprintf("name==%s", canonicalRef.String()))
    84  				f.Since = append(f.Since, fmt.Sprintf("name==%s", tempFilterToken[1]))
    85  			} else if tempFilterToken[0] == FilterLabelType {
    86  				// To support filtering labels by keys.
    87  				f.Labels[tempFilterToken[1]] = ""
    88  			} else if tempFilterToken[0] == FilterReferenceType {
    89  				f.Reference = append(f.Reference, tempFilterToken[1])
    90  			} else {
    91  				return nil, fmt.Errorf("invalid filter %q", filter)
    92  			}
    93  		case 3:
    94  			if tempFilterToken[0] == FilterLabelType {
    95  				f.Labels[tempFilterToken[1]] = tempFilterToken[2]
    96  			} else {
    97  				return nil, fmt.Errorf("invalid filter %q", filter)
    98  			}
    99  		default:
   100  			return nil, fmt.Errorf("invalid filter %q", filter)
   101  		}
   102  	}
   103  	return f, nil
   104  }
   105  
   106  // FilterImages returns images in `labelImages` that are created
   107  // before MAX(beforeImages.CreatedAt) and after MIN(sinceImages.CreatedAt).
   108  func FilterImages(labelImages []images.Image, beforeImages []images.Image, sinceImages []images.Image) []images.Image {
   109  	var filteredImages []images.Image
   110  	maxTime := time.Now()
   111  	minTime := time.Date(1970, time.Month(1), 1, 0, 0, 0, 0, time.UTC)
   112  	if len(beforeImages) > 0 {
   113  		maxTime = beforeImages[0].CreatedAt
   114  		for _, value := range beforeImages {
   115  			if value.CreatedAt.After(maxTime) {
   116  				maxTime = value.CreatedAt
   117  			}
   118  		}
   119  	}
   120  	if len(sinceImages) > 0 {
   121  		minTime = sinceImages[0].CreatedAt
   122  		for _, value := range sinceImages {
   123  			if value.CreatedAt.Before(minTime) {
   124  				minTime = value.CreatedAt
   125  			}
   126  		}
   127  	}
   128  	for _, image := range labelImages {
   129  		if image.CreatedAt.After(minTime) && image.CreatedAt.Before(maxTime) {
   130  			filteredImages = append(filteredImages, image)
   131  		}
   132  	}
   133  	return filteredImages
   134  }
   135  
   136  // FilterByReference filters images using references given in `filters`.
   137  func FilterByReference(imageList []images.Image, filters []string) ([]images.Image, error) {
   138  	var filteredImageList []images.Image
   139  	log.L.Debug(filters)
   140  	for _, image := range imageList {
   141  		log.L.Debug(image.Name)
   142  		var matches int
   143  		for _, f := range filters {
   144  			var ref dockerreference.Reference
   145  			var err error
   146  			ref, err = dockerreference.ParseAnyReference(image.Name)
   147  			if err != nil {
   148  				return nil, fmt.Errorf("unable to parse image name: %s while filtering by reference because of %s", image.Name, err.Error())
   149  			}
   150  
   151  			familiarMatch, err := dockerreference.FamiliarMatch(f, ref)
   152  			if err != nil {
   153  				return nil, err
   154  			}
   155  			regexpMatch, err := regexp.MatchString(f, image.Name)
   156  			if err != nil {
   157  				return nil, err
   158  			}
   159  			if familiarMatch || regexpMatch {
   160  				matches++
   161  			}
   162  		}
   163  		if matches == len(filters) {
   164  			filteredImageList = append(filteredImageList, image)
   165  		}
   166  	}
   167  	return filteredImageList, nil
   168  }
   169  
   170  // FilterDangling filters dangling images (or keeps if `dangling` == false).
   171  func FilterDangling(imageList []images.Image, dangling bool) []images.Image {
   172  	var filtered []images.Image
   173  	for _, image := range imageList {
   174  		_, tag := ParseRepoTag(image.Name)
   175  
   176  		if dangling && tag == "" {
   177  			filtered = append(filtered, image)
   178  		}
   179  		if !dangling && tag != "" {
   180  			filtered = append(filtered, image)
   181  		}
   182  	}
   183  	return filtered
   184  }
   185  
   186  // FilterByLabel filters images based on labels given in `filters`.
   187  func FilterByLabel(ctx context.Context, client *containerd.Client, imageList []images.Image, filters map[string]string) ([]images.Image, error) {
   188  	for lk, lv := range filters {
   189  		var imageLabels []images.Image
   190  		for _, img := range imageList {
   191  			ci := containerd.NewImage(client, img)
   192  			cfg, _, err := ReadImageConfig(ctx, ci)
   193  			if err != nil {
   194  				return nil, err
   195  			}
   196  			if val, ok := cfg.Config.Labels[lk]; ok {
   197  				if val == lv || lv == "" {
   198  					imageLabels = append(imageLabels, img)
   199  				}
   200  			}
   201  		}
   202  		imageList = imageLabels
   203  	}
   204  	return imageList, nil
   205  }