github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/cmd/container/list_util.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 container
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strconv"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/containerd/containerd"
    27  	"github.com/containerd/containerd/containers"
    28  	"github.com/containerd/log"
    29  	"github.com/containerd/nerdctl/v2/pkg/containerutil"
    30  )
    31  
    32  func foldContainerFilters(ctx context.Context, containers []containerd.Container, filters []string) (*containerFilterContext, error) {
    33  	filterCtx := &containerFilterContext{containers: containers}
    34  	err := filterCtx.foldFilters(ctx, filters)
    35  	return filterCtx, err
    36  }
    37  
    38  type containerFilterContext struct {
    39  	containers []containerd.Container
    40  
    41  	idFilterFuncs      []func(string) bool
    42  	nameFilterFuncs    []func(string) bool
    43  	exitedFilterFuncs  []func(int) bool
    44  	beforeFilterFuncs  []func(t time.Time) bool
    45  	sinceFilterFuncs   []func(t time.Time) bool
    46  	statusFilterFuncs  []func(containerd.ProcessStatus) bool
    47  	labelFilterFuncs   []func(map[string]string) bool
    48  	volumeFilterFuncs  []func([]*containerutil.ContainerVolume) bool
    49  	networkFilterFuncs []func([]string) bool
    50  }
    51  
    52  func (cl *containerFilterContext) MatchesFilters(ctx context.Context) []containerd.Container {
    53  	matchesContainers := make([]containerd.Container, 0, len(cl.containers))
    54  	for _, container := range cl.containers {
    55  		if !cl.matchesInfoFilters(ctx, container) {
    56  			continue
    57  		}
    58  		if !cl.matchesTaskFilters(ctx, container) {
    59  			continue
    60  		}
    61  		matchesContainers = append(matchesContainers, container)
    62  	}
    63  	cl.containers = matchesContainers
    64  	return cl.containers
    65  }
    66  
    67  func (cl *containerFilterContext) foldFilters(ctx context.Context, filters []string) error {
    68  	folders := []struct {
    69  		filterType string
    70  		foldFunc   func(context.Context, string, string) error
    71  	}{
    72  		{"id", cl.foldIDFilter}, {"name", cl.foldNameFilter},
    73  		{"before", cl.foldBeforeFilter}, {"since", cl.foldSinceFilter},
    74  		{"network", cl.foldNetworkFilter}, {"label", cl.foldLabelFilter},
    75  		{"volume", cl.foldVolumeFilter}, {"status", cl.foldStatusFilter},
    76  		{"exited", cl.foldExitedFilter},
    77  	}
    78  	for _, filter := range filters {
    79  		invalidFilter := true
    80  		for _, folder := range folders {
    81  			if !strings.HasPrefix(filter, folder.filterType) {
    82  				continue
    83  			}
    84  			splited := strings.SplitN(filter, "=", 2)
    85  			if len(splited) != 2 {
    86  				return fmt.Errorf("invalid argument \"%s\" for \"-f, --filter\": bad format of filter (expected name=value)", folder.filterType)
    87  			}
    88  			if err := folder.foldFunc(ctx, filter, splited[1]); err != nil {
    89  				return err
    90  			}
    91  			invalidFilter = false
    92  			break
    93  		}
    94  		if invalidFilter {
    95  			return fmt.Errorf("invalid filter '%s'", filter)
    96  		}
    97  	}
    98  	return nil
    99  }
   100  
   101  func (cl *containerFilterContext) foldExitedFilter(_ context.Context, filter, value string) error {
   102  	exited, err := strconv.Atoi(value)
   103  	if err != nil {
   104  		return err
   105  	}
   106  	cl.exitedFilterFuncs = append(cl.exitedFilterFuncs, func(exitStatus int) bool {
   107  		return exited == exitStatus
   108  	})
   109  	return nil
   110  }
   111  
   112  func (cl *containerFilterContext) foldStatusFilter(_ context.Context, filter, value string) error {
   113  	status := containerd.ProcessStatus(value)
   114  	switch status {
   115  	case containerd.Running, containerd.Created, containerd.Stopped, containerd.Paused, containerd.Pausing, containerd.Unknown:
   116  		cl.statusFilterFuncs = append(cl.statusFilterFuncs, func(stats containerd.ProcessStatus) bool {
   117  			return status == stats
   118  		})
   119  	case containerd.ProcessStatus("exited"):
   120  		cl.statusFilterFuncs = append(cl.statusFilterFuncs, func(stats containerd.ProcessStatus) bool {
   121  			return containerd.Stopped == stats
   122  		})
   123  	case containerd.ProcessStatus("restarting"), containerd.ProcessStatus("removing"), containerd.ProcessStatus("dead"):
   124  		log.L.Warnf("%s is not supported and is ignored", filter)
   125  	default:
   126  		return fmt.Errorf("invalid filter '%s'", filter)
   127  	}
   128  	return nil
   129  }
   130  
   131  func (cl *containerFilterContext) foldBeforeFilter(ctx context.Context, filter, value string) error {
   132  	beforeC, err := idOrNameFilter(ctx, cl.containers, value)
   133  	if err == nil {
   134  		cl.beforeFilterFuncs = append(cl.beforeFilterFuncs, func(t time.Time) bool {
   135  			return t.Before(beforeC.CreatedAt)
   136  		})
   137  	}
   138  	return err
   139  }
   140  
   141  func (cl *containerFilterContext) foldSinceFilter(ctx context.Context, filter, value string) error {
   142  	sinceC, err := idOrNameFilter(ctx, cl.containers, value)
   143  	if err == nil {
   144  		cl.sinceFilterFuncs = append(cl.sinceFilterFuncs, func(t time.Time) bool {
   145  			return t.After(sinceC.CreatedAt)
   146  		})
   147  	}
   148  	return err
   149  }
   150  
   151  func (cl *containerFilterContext) foldIDFilter(_ context.Context, filter, value string) error {
   152  	cl.idFilterFuncs = append(cl.idFilterFuncs, func(id string) bool {
   153  		if value == "" {
   154  			return false
   155  		}
   156  		return strings.HasPrefix(id, value)
   157  	})
   158  	return nil
   159  }
   160  
   161  func (cl *containerFilterContext) foldNameFilter(_ context.Context, filter, value string) error {
   162  	cl.nameFilterFuncs = append(cl.nameFilterFuncs, func(name string) bool {
   163  		if value == "" {
   164  			return true
   165  		}
   166  		return strings.Contains(name, value)
   167  	})
   168  	return nil
   169  }
   170  
   171  func (cl *containerFilterContext) foldLabelFilter(_ context.Context, filter, value string) error {
   172  	k, v, hasValue := value, "", false
   173  	if subs := strings.SplitN(value, "=", 2); len(subs) == 2 {
   174  		hasValue = true
   175  		k, v = subs[0], subs[1]
   176  	}
   177  	cl.labelFilterFuncs = append(cl.labelFilterFuncs, func(labels map[string]string) bool {
   178  		if labels == nil {
   179  			return false
   180  		}
   181  		val, ok := labels[k]
   182  		if !ok || (hasValue && val != v) {
   183  			return false
   184  		}
   185  		return true
   186  	})
   187  	return nil
   188  }
   189  
   190  func (cl *containerFilterContext) foldVolumeFilter(_ context.Context, filter, value string) error {
   191  	cl.volumeFilterFuncs = append(cl.volumeFilterFuncs, func(vols []*containerutil.ContainerVolume) bool {
   192  		for _, vol := range vols {
   193  			if (vol.Source != "" && vol.Source == value) ||
   194  				(vol.Destination != "" && vol.Destination == value) ||
   195  				(vol.Name != "" && vol.Name == value) {
   196  				return true
   197  			}
   198  		}
   199  		return false
   200  	})
   201  	return nil
   202  }
   203  
   204  func (cl *containerFilterContext) foldNetworkFilter(_ context.Context, filter, value string) error {
   205  	cl.networkFilterFuncs = append(cl.networkFilterFuncs, func(networks []string) bool {
   206  		for _, network := range networks {
   207  			if network == value {
   208  				return true
   209  			}
   210  		}
   211  		return false
   212  	})
   213  	return nil
   214  }
   215  
   216  func (cl *containerFilterContext) matchesInfoFilters(ctx context.Context, container containerd.Container) bool {
   217  	if len(cl.idFilterFuncs)+len(cl.nameFilterFuncs)+len(cl.beforeFilterFuncs)+
   218  		len(cl.sinceFilterFuncs)+len(cl.labelFilterFuncs)+len(cl.volumeFilterFuncs)+len(cl.networkFilterFuncs) == 0 {
   219  		return true
   220  	}
   221  	info, _ := container.Info(ctx, containerd.WithoutRefreshedMetadata)
   222  	return cl.matchesIDFilter(info) && cl.matchesNameFilter(info) && cl.matchesBeforeFilter(info) &&
   223  		cl.matchesSinceFilter(info) && cl.matchesLabelFilter(info) && cl.matchesVolumeFilter(info) &&
   224  		cl.matchesNetworkFilter(info)
   225  }
   226  
   227  func (cl *containerFilterContext) matchesTaskFilters(ctx context.Context, container containerd.Container) bool {
   228  	if len(cl.exitedFilterFuncs)+len(cl.statusFilterFuncs) == 0 {
   229  		return true
   230  	}
   231  	ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
   232  	defer cancel()
   233  	task, err := container.Task(ctx, nil)
   234  	if err != nil {
   235  		log.G(ctx).Warn(err)
   236  		return false
   237  	}
   238  	status, err := task.Status(ctx)
   239  	if err != nil {
   240  		log.G(ctx).Warn(err)
   241  		return false
   242  	}
   243  	return cl.matchesExitedFilter(status) && cl.matchesStatusFilter(status)
   244  }
   245  
   246  func (cl *containerFilterContext) matchesExitedFilter(status containerd.Status) bool {
   247  	if len(cl.exitedFilterFuncs) == 0 {
   248  		return true
   249  	}
   250  	if status.Status != containerd.Stopped {
   251  		return false
   252  	}
   253  	for _, exitedFilterFunc := range cl.exitedFilterFuncs {
   254  		if !exitedFilterFunc(int(status.ExitStatus)) {
   255  			continue
   256  		}
   257  		return true
   258  	}
   259  	return false
   260  }
   261  
   262  func (cl *containerFilterContext) matchesStatusFilter(status containerd.Status) bool {
   263  	if len(cl.statusFilterFuncs) == 0 {
   264  		return true
   265  	}
   266  	for _, statusFilterFunc := range cl.statusFilterFuncs {
   267  		if !statusFilterFunc(status.Status) {
   268  			continue
   269  		}
   270  		return true
   271  	}
   272  	return false
   273  }
   274  
   275  func (cl *containerFilterContext) matchesIDFilter(info containers.Container) bool {
   276  	if len(cl.idFilterFuncs) == 0 {
   277  		return true
   278  	}
   279  	for _, idFilterFunc := range cl.idFilterFuncs {
   280  		if !idFilterFunc(info.ID) {
   281  			continue
   282  		}
   283  		return true
   284  	}
   285  	return false
   286  }
   287  
   288  func (cl *containerFilterContext) matchesNameFilter(info containers.Container) bool {
   289  	if len(cl.nameFilterFuncs) == 0 {
   290  		return true
   291  	}
   292  	cName := getContainerName(info.Labels)
   293  	for _, nameFilterFunc := range cl.nameFilterFuncs {
   294  		if !nameFilterFunc(cName) {
   295  			continue
   296  		}
   297  		return true
   298  	}
   299  	return false
   300  }
   301  
   302  func (cl *containerFilterContext) matchesSinceFilter(info containers.Container) bool {
   303  	if len(cl.sinceFilterFuncs) == 0 {
   304  		return true
   305  	}
   306  	for _, sinceFilterFunc := range cl.sinceFilterFuncs {
   307  		if !sinceFilterFunc(info.CreatedAt) {
   308  			continue
   309  		}
   310  		return true
   311  	}
   312  	return false
   313  }
   314  
   315  func (cl *containerFilterContext) matchesBeforeFilter(info containers.Container) bool {
   316  	if len(cl.beforeFilterFuncs) == 0 {
   317  		return true
   318  	}
   319  	for _, beforeFilterFunc := range cl.beforeFilterFuncs {
   320  		if !beforeFilterFunc(info.CreatedAt) {
   321  			continue
   322  		}
   323  		return true
   324  	}
   325  	return false
   326  }
   327  
   328  func (cl *containerFilterContext) matchesLabelFilter(info containers.Container) bool {
   329  	for _, labelFilterFunc := range cl.labelFilterFuncs {
   330  		if !labelFilterFunc(info.Labels) {
   331  			return false
   332  		}
   333  	}
   334  	return true
   335  }
   336  
   337  func (cl *containerFilterContext) matchesVolumeFilter(info containers.Container) bool {
   338  	if len(cl.volumeFilterFuncs) == 0 {
   339  		return true
   340  	}
   341  	vols := containerutil.GetContainerVolumes(info.Labels)
   342  	for _, volumeFilterFunc := range cl.volumeFilterFuncs {
   343  		if !volumeFilterFunc(vols) {
   344  			continue
   345  		}
   346  		return true
   347  	}
   348  	return false
   349  }
   350  
   351  func (cl *containerFilterContext) matchesNetworkFilter(info containers.Container) bool {
   352  	if len(cl.networkFilterFuncs) == 0 {
   353  		return true
   354  	}
   355  	networks := getContainerNetworks(info.Labels)
   356  	for _, networkFilterFunc := range cl.networkFilterFuncs {
   357  		if !networkFilterFunc(networks) {
   358  			continue
   359  		}
   360  		return true
   361  	}
   362  	return false
   363  }
   364  
   365  func idOrNameFilter(ctx context.Context, containers []containerd.Container, value string) (*containers.Container, error) {
   366  	for _, container := range containers {
   367  		info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)
   368  		if err != nil {
   369  			return nil, err
   370  		}
   371  		if strings.HasPrefix(info.ID, value) || strings.Contains(getContainerName(info.Labels), value) {
   372  			return &info, nil
   373  		}
   374  	}
   375  	return nil, fmt.Errorf("no such container %s", value)
   376  }