github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/cmd/container/list.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  	"encoding/json"
    22  	"fmt"
    23  	"sort"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/containerd/containerd"
    28  	"github.com/containerd/containerd/containers"
    29  	"github.com/containerd/containerd/errdefs"
    30  	"github.com/containerd/containerd/pkg/progress"
    31  	"github.com/containerd/log"
    32  	"github.com/containerd/nerdctl/v2/pkg/api/types"
    33  	"github.com/containerd/nerdctl/v2/pkg/formatter"
    34  	"github.com/containerd/nerdctl/v2/pkg/imgutil"
    35  	"github.com/containerd/nerdctl/v2/pkg/labels"
    36  	"github.com/containerd/nerdctl/v2/pkg/labels/k8slabels"
    37  )
    38  
    39  // List prints containers according to `options`.
    40  func List(ctx context.Context, client *containerd.Client, options types.ContainerListOptions) ([]ListItem, error) {
    41  	containers, err := filterContainers(ctx, client, options.Filters, options.LastN, options.All)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  	return prepareContainers(ctx, client, containers, options)
    46  }
    47  
    48  // filterContainers returns containers matching the filters.
    49  //
    50  //   - Supported filters: https://github.com/containerd/nerdctl/blob/main/docs/command-reference.md#whale-blue_square-nerdctl-ps
    51  //   - all means showing all containers (default shows just running).
    52  //   - lastN means only showing n last created containers (includes all states). Non-positive values are ignored.
    53  //     In other words, if lastN is positive, all will be set to true.
    54  func filterContainers(ctx context.Context, client *containerd.Client, filters []string, lastN int, all bool) ([]containerd.Container, error) {
    55  	containers, err := client.Containers(ctx)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  	filterCtx, err := foldContainerFilters(ctx, containers, filters)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	containers = filterCtx.MatchesFilters(ctx)
    64  	if lastN > 0 {
    65  		all = true
    66  		sort.Slice(containers, func(i, j int) bool {
    67  			infoI, _ := containers[i].Info(ctx, containerd.WithoutRefreshedMetadata)
    68  			infoJ, _ := containers[j].Info(ctx, containerd.WithoutRefreshedMetadata)
    69  			return infoI.CreatedAt.After(infoJ.CreatedAt)
    70  		})
    71  		if lastN < len(containers) {
    72  			containers = containers[:lastN]
    73  		}
    74  	}
    75  
    76  	if all {
    77  		return containers, nil
    78  	}
    79  	var upContainers []containerd.Container
    80  	for _, c := range containers {
    81  		cStatus := formatter.ContainerStatus(ctx, c)
    82  		if strings.HasPrefix(cStatus, "Up") {
    83  			upContainers = append(upContainers, c)
    84  		}
    85  	}
    86  	return upContainers, nil
    87  }
    88  
    89  type ListItem struct {
    90  	Command   string
    91  	CreatedAt time.Time
    92  	ID        string
    93  	Image     string
    94  	Platform  string // nerdctl extension
    95  	Names     string
    96  	Ports     string
    97  	Status    string
    98  	Runtime   string // nerdctl extension
    99  	Size      string
   100  	Labels    map[string]string
   101  	// TODO: "LocalVolumes", "Mounts", "Networks", "RunningFor", "State"
   102  }
   103  
   104  func (x *ListItem) Label(s string) string {
   105  	return x.Labels[s]
   106  }
   107  
   108  func prepareContainers(ctx context.Context, client *containerd.Client, containers []containerd.Container, options types.ContainerListOptions) ([]ListItem, error) {
   109  	listItems := make([]ListItem, len(containers))
   110  	for i, c := range containers {
   111  		info, err := c.Info(ctx, containerd.WithoutRefreshedMetadata)
   112  		if err != nil {
   113  			if errdefs.IsNotFound(err) {
   114  				log.G(ctx).Warn(err)
   115  				continue
   116  			}
   117  			return nil, err
   118  		}
   119  		spec, err := c.Spec(ctx)
   120  		if err != nil {
   121  			if errdefs.IsNotFound(err) {
   122  				log.G(ctx).Warn(err)
   123  				continue
   124  			}
   125  			return nil, err
   126  		}
   127  		id := c.ID()
   128  		if options.Truncate && len(id) > 12 {
   129  			id = id[:12]
   130  		}
   131  		li := ListItem{
   132  			Command:   formatter.InspectContainerCommand(spec, options.Truncate, true),
   133  			CreatedAt: info.CreatedAt,
   134  			ID:        id,
   135  			Image:     info.Image,
   136  			Platform:  info.Labels[labels.Platform],
   137  			Names:     getContainerName(info.Labels),
   138  			Ports:     formatter.FormatPorts(info.Labels),
   139  			Status:    formatter.ContainerStatus(ctx, c),
   140  			Runtime:   info.Runtime.Name,
   141  			Labels:    info.Labels,
   142  		}
   143  		if options.Size {
   144  			containerSize, err := getContainerSize(ctx, client, c, info)
   145  			if err != nil {
   146  				return nil, err
   147  			}
   148  			li.Size = containerSize
   149  		}
   150  		listItems[i] = li
   151  	}
   152  	return listItems, nil
   153  }
   154  
   155  func getContainerName(containerLabels map[string]string) string {
   156  	if name, ok := containerLabels[labels.Name]; ok {
   157  		return name
   158  	}
   159  
   160  	if ns, ok := containerLabels[k8slabels.PodNamespace]; ok {
   161  		if podName, ok := containerLabels[k8slabels.PodName]; ok {
   162  			if containerName, ok := containerLabels[k8slabels.ContainerName]; ok {
   163  				// Container
   164  				return fmt.Sprintf("k8s://%s/%s/%s", ns, podName, containerName)
   165  			}
   166  			// Pod sandbox
   167  			return fmt.Sprintf("k8s://%s/%s", ns, podName)
   168  		}
   169  	}
   170  	return ""
   171  }
   172  
   173  func getContainerNetworks(containerLables map[string]string) []string {
   174  	var networks []string
   175  	if names, ok := containerLables[labels.Networks]; ok {
   176  		if err := json.Unmarshal([]byte(names), &networks); err != nil {
   177  			log.L.Warn(err)
   178  		}
   179  	}
   180  	return networks
   181  }
   182  
   183  func getContainerSize(ctx context.Context, client *containerd.Client, c containerd.Container, info containers.Container) (string, error) {
   184  	// get container snapshot size
   185  	snapshotKey := info.SnapshotKey
   186  	var containerSize int64
   187  
   188  	if snapshotKey != "" {
   189  		usage, err := client.SnapshotService(info.Snapshotter).Usage(ctx, snapshotKey)
   190  		if err != nil {
   191  			return "", err
   192  		}
   193  		containerSize = usage.Size
   194  	}
   195  
   196  	// get the image interface
   197  	image, err := c.Image(ctx)
   198  	if err != nil {
   199  		return "", err
   200  	}
   201  
   202  	sn := client.SnapshotService(info.Snapshotter)
   203  
   204  	imageSize, err := imgutil.UnpackedImageSize(ctx, sn, image)
   205  	if err != nil {
   206  		return "", err
   207  	}
   208  
   209  	return fmt.Sprintf("%s (virtual %s)", progress.Bytes(containerSize).String(), progress.Bytes(imageSize).String()), nil
   210  }