diff --git a/api/swagger.yaml b/api/swagger.yaml index 3ee7e50b81..741b1ebe66 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -8075,6 +8075,7 @@ paths: - `label=key` or `label="key=value"` of an image label - `reference`=(`[:]`) - `since`=(`[:]`, `` or ``) + - `until=` type: "string" - name: "shared-size" in: "query" diff --git a/daemon/images/image_list.go b/daemon/images/image_list.go index 8da13be112..e7e6e6ab53 100644 --- a/daemon/images/image_list.go +++ b/daemon/images/image_list.go @@ -10,6 +10,7 @@ import ( "github.com/distribution/reference" "github.com/docker/docker/api/types" imagetypes "github.com/docker/docker/api/types/image" + timetypes "github.com/docker/docker/api/types/time" "github.com/docker/docker/container" "github.com/docker/docker/image" "github.com/docker/docker/layer" @@ -21,6 +22,7 @@ var acceptedImageFilterTags = map[string]bool{ "before": true, "since": true, "reference": true, + "until": true, } // byCreated is a temporary type used to sort a list of images by creation @@ -59,6 +61,25 @@ func (i *ImageService) Images(ctx context.Context, opts types.ImageListOptions) return nil, err } + err = opts.Filters.WalkValues("until", func(value string) error { + ts, err := timetypes.GetTimestamp(value, time.Now()) + if err != nil { + return err + } + seconds, nanoseconds, err := timetypes.ParseTimestamps(ts, 0) + if err != nil { + return err + } + timestamp := time.Unix(seconds, nanoseconds) + if beforeFilter.IsZero() || beforeFilter.After(timestamp) { + beforeFilter = timestamp + } + return nil + }) + if err != nil { + return nil, err + } + err = opts.Filters.WalkValues("since", func(value string) error { img, err := i.GetImage(ctx, value, imagetypes.GetImageOpts{}) if err != nil { diff --git a/docs/api/version-history.md b/docs/api/version-history.md index ddef65e155..3bf92febe5 100644 --- a/docs/api/version-history.md +++ b/docs/api/version-history.md @@ -17,6 +17,11 @@ keywords: "API, Docker, rcli, REST, documentation" [Docker Engine API v1.44](https://docs.docker.com/engine/api/v1.44/) documentation +* GET `/images/json` now accepts an `until` filter. This accepts a timestamp and + lists all images created before it. The `` can be Unix timestamps, + date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`) + computed relative to the daemon machine’s time. This change is not versioned, + and affects all API versions if the daemon has this patch. * The `VirtualSize` field in the `GET /images/{name}/json`, `GET /images/json`, and `GET /system/df` responses is now omitted. Use the `Size` field instead, which contains the same information. diff --git a/integration/image/list_test.go b/integration/image/list_test.go index 2d6fd4973f..a7aa04f90f 100644 --- a/integration/image/list_test.go +++ b/integration/image/list_test.go @@ -57,6 +57,50 @@ func TestImagesFilterMultiReference(t *testing.T) { } } +func TestImagesFilterUntil(t *testing.T) { + ctx := setupTest(t) + + client := testEnv.APIClient() + + name := strings.ToLower(t.Name()) + ctr := container.Create(ctx, t, client, container.WithName(name)) + + imgs := make([]string, 5) + for i := range imgs { + if i > 0 { + // Make really really sure each image has a distinct timestamp. + time.Sleep(time.Millisecond) + } + id, err := client.ContainerCommit(ctx, ctr, containertypes.CommitOptions{Reference: fmt.Sprintf("%s:v%d", name, i)}) + assert.NilError(t, err) + imgs[i] = id.ID + } + + olderImage, _, err := client.ImageInspectWithRaw(ctx, imgs[2]) + assert.NilError(t, err) + olderUntil := olderImage.Created + + laterImage, _, err := client.ImageInspectWithRaw(ctx, imgs[3]) + assert.NilError(t, err) + laterUntil := laterImage.Created + + filter := filters.NewArgs( + filters.Arg("since", imgs[0]), + filters.Arg("until", olderUntil), + filters.Arg("until", laterUntil), + filters.Arg("before", imgs[len(imgs)-1]), + ) + list, err := client.ImageList(ctx, types.ImageListOptions{Filters: filter}) + assert.NilError(t, err) + + var listedIDs []string + for _, i := range list { + t.Logf("ImageList: ID=%v RepoTags=%v", i.ID, i.RepoTags) + listedIDs = append(listedIDs, i.ID) + } + assert.DeepEqual(t, listedIDs, imgs[1:2], cmpopts.SortSlices(func(a, b string) bool { return a < b })) +} + func TestImagesFilterBeforeSince(t *testing.T) { ctx := setupTest(t)