diff --git a/cmd/nerdctl/images.go b/cmd/nerdctl/images.go index f51db89876..867af49868 100644 --- a/cmd/nerdctl/images.go +++ b/cmd/nerdctl/images.go @@ -119,6 +119,10 @@ func imagesAction(cmd *cobra.Command, args []string) error { return err } + if f.Dangling != nil { + imageList = filterDangling(imageList, *f.Dangling) + } + imageList, err = filterByLabel(ctx, client, imageList, f.Labels) if err != nil { return err @@ -182,6 +186,21 @@ func filterByReference(imageList []images.Image, filters []string) ([]images.Ima return filteredImageList, nil } +func filterDangling(imageList []images.Image, dangling bool) []images.Image { + var filtered []images.Image + for _, image := range imageList { + _, tag := imgutil.ParseRepoTag(image.Name) + + if dangling && tag == "" { + filtered = append(filtered, image) + } + if !dangling && tag != "" { + filtered = append(filtered, image) + } + } + return filtered +} + func filterByLabel(ctx context.Context, client *containerd.Client, imageList []images.Image, filters map[string]string) ([]images.Image, error) { for lk, lv := range filters { var imageLabels []images.Image diff --git a/cmd/nerdctl/images_test.go b/cmd/nerdctl/images_test.go index dbe7ba413c..15ea97c82b 100644 --- a/cmd/nerdctl/images_test.go +++ b/cmd/nerdctl/images_test.go @@ -116,3 +116,22 @@ LABEL version=0.1`, testutil.CommonImage) base.Cmd("images", "--filter", "reference=busy*:*libc*").AssertOutContains("glibc") base.Cmd("images", "--filter", "reference=busy*:*libc*").AssertOutContains("uclibc") } + +func TestImagesFilterDangling(t *testing.T) { + testutil.RequiresBuild(t) + base := testutil.NewBase(t) + base.Cmd("images", "prune", "--all").AssertOK() + + dockerfile := fmt.Sprintf(`FROM %s +CMD ["echo", "nerdctl-build-notag-string"] + `, testutil.CommonImage) + buildCtx, err := createBuildContext(dockerfile) + assert.NilError(t, err) + + defer os.RemoveAll(buildCtx) + base.Cmd("build", "-f", buildCtx+"/Dockerfile", buildCtx).AssertOK() + + // dangling image test + base.Cmd("images", "--filter", "dangling=true").AssertOutContains("") + base.Cmd("images", "--filter", "dangling=false").AssertOutNotContains("") +} diff --git a/pkg/imgutil/imgutil.go b/pkg/imgutil/imgutil.go index be37d076b1..f606722251 100644 --- a/pkg/imgutil/imgutil.go +++ b/pkg/imgutil/imgutil.go @@ -56,6 +56,7 @@ var ( FilterSinceType = "since" FilterLabelType = "label" FilterReferenceType = "reference" + FilterDanglingType = "dangling" ) // PullMode is either one of "always", "missing", "never" @@ -369,7 +370,7 @@ func ParseRepoTag(imgName string) (string, string) { ref, err := refdocker.ParseDockerRef(imgName) if err != nil { - logrus.WithError(err).Warnf("unparsable image name %q", imgName) + logrus.WithError(err).Debugf("unparsable image name %q", imgName) return "", "" } @@ -388,6 +389,7 @@ type Filters struct { Since []string Labels map[string]string Reference []string + Dangling *bool } func ParseFilters(filters []string) (*Filters, error) { @@ -398,7 +400,17 @@ func ParseFilters(filters []string) (*Filters, error) { case 1: return nil, fmt.Errorf("invalid filter %q", filter) case 2: - if tempFilterToken[0] == FilterBeforeType { + if tempFilterToken[0] == FilterDanglingType { + var isDangling bool + if tempFilterToken[1] == "true" { + isDangling = true + } else if tempFilterToken[1] == "false" { + isDangling = false + } else { + return nil, fmt.Errorf("invalid filter %q", filter) + } + f.Dangling = &isDangling + } else if tempFilterToken[0] == FilterBeforeType { canonicalRef, err := referenceutil.ParseAny(tempFilterToken[1]) if err != nil { return nil, err