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 }