Skip to content

Commit

Permalink
Support filter argument for nerdctl images command
Browse files Browse the repository at this point in the history
Signed-off-by: Zheao.Li <[email protected]>
  • Loading branch information
Zheaoli committed Aug 18, 2022
1 parent af6ac29 commit 2b43c9a
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 7 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -782,9 +782,16 @@ Flags:
- :nerd_face: `--format=wide`: Wide table
- :nerd_face: `--format=json`: Alias of `--format='{{json .}}'`
- :whale: `--digests`: Show digests (compatible with Docker, unlike ID)
- :whale: `-f, --filter`: Filter the images. For now, only 'before=<image:tag>' and 'since=<image:tag>' is supported.
- :whale: `--filter=before=<image:tag>`: Images created before given image (exclusive)
- :whale: `--filter=since=<image:tag>`: Images created after given image (exclusive)
- :nerd_face: `--names`: Show image names

Unimplemented `docker images` flags: `--filter`
Following arguments for `--filter` are not supported yet:

1. `--filter=label=<key>=<value>`: Filter images by label
2. `--filter=reference=<image:tag>`: Filter images by reference
3. `--filter=dangling=true`: Filter images by dangling

### :whale: :blue_square: nerdctl pull
Pull an image from a registry.
Expand Down
41 changes: 36 additions & 5 deletions cmd/nerdctl/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Properties:
imagesCommand.Flags().Bool("no-trunc", false, "Don't truncate output")
// Alias "-f" is reserved for "--filter"
imagesCommand.Flags().String("format", "", "Format the output using the given Go template, e.g, '{{json .}}', 'wide'")
imagesCommand.Flags().StringSliceP("filter", "f", []string{}, "Filter output based on conditions provided")
imagesCommand.RegisterFlagCompletionFunc("format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"json", "table", "wide"}, cobra.ShellCompDirectiveNoFileComp
})
Expand Down Expand Up @@ -103,13 +104,43 @@ func imagesAction(cmd *cobra.Command, args []string) error {
var (
imageStore = client.ImageService()
)
var imageList []images.Image

// To-do: Add support for --filter.
imageList, err := imageStore.List(ctx, filters...)
if err != nil {
return err
}
if !cmd.Flags().Changed("filter") {
imageList, err = imageStore.List(ctx, filters...)
if err != nil {
return err
}
} else {
inputFilters, err := cmd.Flags().GetStringSlice("filter")
if err != nil {
return err
}
beforeFilters, sinceFilters, err := imgutil.ParseFilters(inputFilters)
if err != nil {
return err
}
imageList, err = imageStore.List(ctx, filters...)
if err != nil {
return err
}
var beforeImages []images.Image
if len(beforeFilters) > 0 {
beforeImages, err = imageStore.List(ctx, beforeFilters...)
if err != nil {
return err
}
}
var afterImages []images.Image
if len(sinceFilters) > 0 {
afterImages, err = imageStore.List(ctx, sinceFilters...)
if err != nil {
return err
}
}

imageList = imgutil.FilterImages(imageList, beforeImages, afterImages)
}
return printImages(ctx, cmd, client, imageList)
}

Expand Down
21 changes: 21 additions & 0 deletions cmd/nerdctl/images_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package main

import (
"fmt"
"os"
"strings"
"testing"

Expand Down Expand Up @@ -74,3 +75,23 @@ func TestImages(t *testing.T) {
return nil
})
}

func TestImagesFilter(t *testing.T) {
t.Parallel()
base := testutil.NewBase(t)
tempName := testutil.Identifier(base.T)
tempName2 := testutil.Identifier(base.T)
base.Cmd("pull", testutil.CommonImage).AssertOK()

dockerfile := fmt.Sprintf(`FROM %s
CMD ["echo", "nerdctl-build-test-string"]`, testutil.CommonImage)

buildCtx, err := createBuildContext(dockerfile)
assert.NilError(t, err)
defer os.RemoveAll(buildCtx)
base.Cmd("build", "-t", tempName, "-f", buildCtx+"/Dockerfile", buildCtx).AssertOK()
base.Cmd("images", "--filter", fmt.Sprintf("before=%s:%s", tempName, "latest")).AssertOutContains(strings.Split(testutil.CommonImage, ":")[0])
base.Cmd("images", "--filter", fmt.Sprintf("before=%s:%s", tempName2, "latest")).AssertOutNotContains(tempName)
base.Cmd("images", "--filter", fmt.Sprintf("since=%s", testutil.CommonImage)).AssertOutContains(tempName)
base.Cmd("images", "--filter", fmt.Sprintf("since=%s", testutil.CommonImage)).AssertOutNotContains(strings.Split(testutil.CommonImage, ":")[0])
}
71 changes: 70 additions & 1 deletion pkg/imgutil/imgutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
"fmt"
"io"
"reflect"
"strings"
"time"

"github.com/containerd/containerd"
"github.com/containerd/containerd/content"
Expand All @@ -35,9 +37,9 @@ import (
"github.com/containerd/nerdctl/pkg/idutil/imagewalker"
"github.com/containerd/nerdctl/pkg/imgutil/dockerconfigresolver"
"github.com/containerd/nerdctl/pkg/imgutil/pull"
"github.com/containerd/nerdctl/pkg/referenceutil"
"github.com/docker/docker/errdefs"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"

"github.com/sirupsen/logrus"
)

Expand All @@ -49,6 +51,11 @@ type EnsuredImage struct {
Remote bool // true for stargz or overlaybd
}

var (
FilterBeforeType = "before"
FilterSinceType = "since"
)

// PullMode is either one of "always", "missing", "never"
type PullMode = string

Expand Down Expand Up @@ -373,3 +380,65 @@ func ParseRepoTag(imgName string) (string, string) {

return repository, tag
}

func ParseFilters(filters []string) ([]string, []string, error) {
var beforeFilters []string
var sinceFilters []string
for _, filter := range filters {
tempFilterToken := strings.Split(filter, "=")
switch len(tempFilterToken) {
case 1:
return nil, nil, fmt.Errorf("invalid filter %q", filter)
case 2:
if tempFilterToken[0] == FilterBeforeType {
canonicalRef, err := referenceutil.ParseAny(tempFilterToken[1])
if err != nil {
return nil, nil, err
}
beforeFilters = append(beforeFilters, fmt.Sprintf("name==%s", canonicalRef.String()))
beforeFilters = append(beforeFilters, fmt.Sprintf("name==%s", tempFilterToken[1]))
} else if tempFilterToken[0] == FilterSinceType {
canonicalRef, err := referenceutil.ParseAny(tempFilterToken[1])
if err != nil {
return nil, nil, err
}
sinceFilters = append(sinceFilters, fmt.Sprintf("name==%s", canonicalRef.String()))
sinceFilters = append(sinceFilters, fmt.Sprintf("name==%s", tempFilterToken[1]))
} else {
return nil, nil, fmt.Errorf("invalid filter %q", filter)
}
default:
return nil, nil, fmt.Errorf("invalid filter %q", filter)
}
}
return beforeFilters, sinceFilters, nil
}

func FilterImages(labelImages []images.Image, beforeImages []images.Image, sinceImages []images.Image) []images.Image {

var filteredImages []images.Image
maxTime := time.Now()
minTime := time.Date(1970, time.Month(1), 1, 0, 0, 0, 0, time.UTC)
if len(beforeImages) > 0 {
maxTime = beforeImages[0].CreatedAt
for _, value := range beforeImages {
if value.CreatedAt.After(maxTime) {
maxTime = value.CreatedAt
}
}
}
if len(sinceImages) > 0 {
minTime = sinceImages[0].CreatedAt
for _, value := range sinceImages {
if value.CreatedAt.Before(minTime) {
minTime = value.CreatedAt
}
}
}
for _, image := range labelImages {
if image.CreatedAt.After(minTime) && image.CreatedAt.Before(maxTime) {
filteredImages = append(filteredImages, image)
}
}
return filteredImages
}

0 comments on commit 2b43c9a

Please sign in to comment.