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 }