Add `Platform` parameter that allows to select a specific platform to
show the history for.
This is a breaking change to the Go client as it changes the signature
of `ImageHistory`.
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
| ... | ... |
@@ -23,7 +23,7 @@ type Backend interface {
|
| 23 | 23 |
|
| 24 | 24 |
type imageBackend interface {
|
| 25 | 25 |
ImageDelete(ctx context.Context, imageRef string, force, prune bool) ([]image.DeleteResponse, error) |
| 26 |
- ImageHistory(ctx context.Context, imageName string) ([]*image.HistoryResponseItem, error) |
|
| 26 |
+ ImageHistory(ctx context.Context, imageName string, platform *ocispec.Platform) ([]*image.HistoryResponseItem, error) |
|
| 27 | 27 |
Images(ctx context.Context, opts image.ListOptions) ([]*image.Summary, error) |
| 28 | 28 |
GetImage(ctx context.Context, refOrID string, options backend.GetImageOpts) (*dockerimage.Image, error) |
| 29 | 29 |
ImageInspect(ctx context.Context, refOrID string, options backend.ImageInspectOpts) (*image.InspectResponse, error) |
| ... | ... |
@@ -398,7 +398,21 @@ func (ir *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter, |
| 398 | 398 |
} |
| 399 | 399 |
|
| 400 | 400 |
func (ir *imageRouter) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
| 401 |
- history, err := ir.backend.ImageHistory(ctx, vars["name"]) |
|
| 401 |
+ if err := httputils.ParseForm(r); err != nil {
|
|
| 402 |
+ return err |
|
| 403 |
+ } |
|
| 404 |
+ |
|
| 405 |
+ var platform *ocispec.Platform |
|
| 406 |
+ if versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.48") {
|
|
| 407 |
+ if formPlatform := r.Form.Get("platform"); formPlatform != "" {
|
|
| 408 |
+ p, err := httputils.DecodePlatform(formPlatform) |
|
| 409 |
+ if err != nil {
|
|
| 410 |
+ return err |
|
| 411 |
+ } |
|
| 412 |
+ platform = p |
|
| 413 |
+ } |
|
| 414 |
+ } |
|
| 415 |
+ history, err := ir.backend.ImageHistory(ctx, vars["name"], platform) |
|
| 402 | 416 |
if err != nil {
|
| 403 | 417 |
return err |
| 404 | 418 |
} |
| ... | ... |
@@ -9202,6 +9202,15 @@ paths: |
| 9202 | 9202 |
description: "Image name or ID" |
| 9203 | 9203 |
type: "string" |
| 9204 | 9204 |
required: true |
| 9205 |
+ - name: "platform" |
|
| 9206 |
+ type: "string" |
|
| 9207 |
+ in: "query" |
|
| 9208 |
+ description: | |
|
| 9209 |
+ JSON encoded OCI platform describing platform to show the history for. |
|
| 9210 |
+ If not provided, the host platform will be used. If it's not |
|
| 9211 |
+ available, any present platform will be picked. |
|
| 9212 |
+ |
|
| 9213 |
+ Example: `{"os": "linux", "architecture": "arm", "variant": "v5"}`
|
|
| 9205 | 9214 |
tags: ["Image"] |
| 9206 | 9215 |
/images/{name}/push:
|
| 9207 | 9216 |
post: |
| ... | ... |
@@ -86,3 +86,9 @@ type RemoveOptions struct {
|
| 86 | 86 |
Force bool |
| 87 | 87 |
PruneChildren bool |
| 88 | 88 |
} |
| 89 |
+ |
|
| 90 |
+// HistoryOptions holds parameters to get image history. |
|
| 91 |
+type HistoryOptions struct {
|
|
| 92 |
+ // Platform from the manifest list to use for history. |
|
| 93 |
+ Platform *ocispec.Platform |
|
| 94 |
+} |
| ... | ... |
@@ -3,15 +3,29 @@ package client // import "github.com/docker/docker/client" |
| 3 | 3 |
import ( |
| 4 | 4 |
"context" |
| 5 | 5 |
"encoding/json" |
| 6 |
+ "fmt" |
|
| 6 | 7 |
"net/url" |
| 7 | 8 |
|
| 8 | 9 |
"github.com/docker/docker/api/types/image" |
| 9 | 10 |
) |
| 10 | 11 |
|
| 11 | 12 |
// ImageHistory returns the changes in an image in history format. |
| 12 |
-func (cli *Client) ImageHistory(ctx context.Context, imageID string) ([]image.HistoryResponseItem, error) {
|
|
| 13 |
+func (cli *Client) ImageHistory(ctx context.Context, imageID string, opts image.HistoryOptions) ([]image.HistoryResponseItem, error) {
|
|
| 14 |
+ values := url.Values{}
|
|
| 15 |
+ if opts.Platform != nil {
|
|
| 16 |
+ if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil {
|
|
| 17 |
+ return nil, err |
|
| 18 |
+ } |
|
| 19 |
+ |
|
| 20 |
+ p, err := json.Marshal(*opts.Platform) |
|
| 21 |
+ if err != nil {
|
|
| 22 |
+ return nil, fmt.Errorf("invalid platform: %v", err)
|
|
| 23 |
+ } |
|
| 24 |
+ values.Set("platform", string(p))
|
|
| 25 |
+ } |
|
| 26 |
+ |
|
| 13 | 27 |
var history []image.HistoryResponseItem |
| 14 |
- serverResp, err := cli.get(ctx, "/images/"+imageID+"/history", url.Values{}, nil)
|
|
| 28 |
+ serverResp, err := cli.get(ctx, "/images/"+imageID+"/history", values, nil) |
|
| 15 | 29 |
defer ensureReaderClosed(serverResp) |
| 16 | 30 |
if err != nil {
|
| 17 | 31 |
return history, err |
| ... | ... |
@@ -20,7 +20,7 @@ func TestImageHistoryError(t *testing.T) {
|
| 20 | 20 |
client := &Client{
|
| 21 | 21 |
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")), |
| 22 | 22 |
} |
| 23 |
- _, err := client.ImageHistory(context.Background(), "nothing") |
|
| 23 |
+ _, err := client.ImageHistory(context.Background(), "nothing", image.HistoryOptions{})
|
|
| 24 | 24 |
assert.Check(t, is.ErrorType(err, errdefs.IsSystem)) |
| 25 | 25 |
} |
| 26 | 26 |
|
| ... | ... |
@@ -51,7 +51,7 @@ func TestImageHistory(t *testing.T) {
|
| 51 | 51 |
}, nil |
| 52 | 52 |
}), |
| 53 | 53 |
} |
| 54 |
- imageHistories, err := client.ImageHistory(context.Background(), "image_id") |
|
| 54 |
+ imageHistories, err := client.ImageHistory(context.Background(), "image_id", image.HistoryOptions{})
|
|
| 55 | 55 |
if err != nil {
|
| 56 | 56 |
t.Fatal(err) |
| 57 | 57 |
} |
| ... | ... |
@@ -91,7 +91,7 @@ type ImageAPIClient interface {
|
| 91 | 91 |
BuildCachePrune(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error) |
| 92 | 92 |
BuildCancel(ctx context.Context, id string) error |
| 93 | 93 |
ImageCreate(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error) |
| 94 |
- ImageHistory(ctx context.Context, image string) ([]image.HistoryResponseItem, error) |
|
| 94 |
+ ImageHistory(ctx context.Context, image string, opts image.HistoryOptions) ([]image.HistoryResponseItem, error) |
|
| 95 | 95 |
ImageImport(ctx context.Context, source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error) |
| 96 | 96 |
ImageInspectWithRaw(ctx context.Context, image string) (image.InspectResponse, []byte, error) |
| 97 | 97 |
ImageList(ctx context.Context, options image.ListOptions) ([]image.Summary, error) |
| ... | ... |
@@ -18,15 +18,14 @@ import ( |
| 18 | 18 |
|
| 19 | 19 |
// ImageHistory returns a slice of HistoryResponseItem structures for the |
| 20 | 20 |
// specified image name by walking the image lineage. |
| 21 |
-func (i *ImageService) ImageHistory(ctx context.Context, name string) ([]*imagetype.HistoryResponseItem, error) {
|
|
| 21 |
+func (i *ImageService) ImageHistory(ctx context.Context, name string, platform *ocispec.Platform) ([]*imagetype.HistoryResponseItem, error) {
|
|
| 22 | 22 |
start := time.Now() |
| 23 | 23 |
img, err := i.resolveImage(ctx, name) |
| 24 | 24 |
if err != nil {
|
| 25 | 25 |
return nil, err |
| 26 | 26 |
} |
| 27 | 27 |
|
| 28 |
- // TODO: pass platform in from the CLI |
|
| 29 |
- pm := matchAllWithPreference(platforms.Default()) |
|
| 28 |
+ pm := i.matchRequestedOrDefault(platforms.Only, platform) |
|
| 30 | 29 |
|
| 31 | 30 |
im, err := i.getBestPresentImageManifest(ctx, img, pm) |
| 32 | 31 |
if err != nil {
|
| ... | ... |
@@ -40,7 +40,7 @@ type ImageService interface {
|
| 40 | 40 |
ImportImage(ctx context.Context, ref reference.Named, platform *ocispec.Platform, msg string, layerReader io.Reader, changes []string) (image.ID, error) |
| 41 | 41 |
TagImage(ctx context.Context, imageID image.ID, newTag reference.Named) error |
| 42 | 42 |
GetImage(ctx context.Context, refOrID string, options backend.GetImageOpts) (*image.Image, error) |
| 43 |
- ImageHistory(ctx context.Context, name string) ([]*imagetype.HistoryResponseItem, error) |
|
| 43 |
+ ImageHistory(ctx context.Context, name string, platform *ocispec.Platform) ([]*imagetype.HistoryResponseItem, error) |
|
| 44 | 44 |
CommitImage(ctx context.Context, c backend.CommitConfig) (image.ID, error) |
| 45 | 45 |
SquashImage(id, parent string) (string, error) |
| 46 | 46 |
ImageInspect(ctx context.Context, refOrID string, opts backend.ImageInspectOpts) (*imagetype.InspectResponse, error) |
| ... | ... |
@@ -9,13 +9,14 @@ import ( |
| 9 | 9 |
"github.com/docker/docker/api/types/backend" |
| 10 | 10 |
"github.com/docker/docker/api/types/image" |
| 11 | 11 |
"github.com/docker/docker/layer" |
| 12 |
+ ocispec "github.com/opencontainers/image-spec/specs-go/v1" |
|
| 12 | 13 |
) |
| 13 | 14 |
|
| 14 | 15 |
// ImageHistory returns a slice of ImageHistory structures for the specified image |
| 15 | 16 |
// name by walking the image lineage. |
| 16 |
-func (i *ImageService) ImageHistory(ctx context.Context, name string) ([]*image.HistoryResponseItem, error) {
|
|
| 17 |
+func (i *ImageService) ImageHistory(ctx context.Context, name string, platform *ocispec.Platform) ([]*image.HistoryResponseItem, error) {
|
|
| 17 | 18 |
start := time.Now() |
| 18 |
- img, err := i.GetImage(ctx, name, backend.GetImageOpts{})
|
|
| 19 |
+ img, err := i.GetImage(ctx, name, backend.GetImageOpts{Platform: platform})
|
|
| 19 | 20 |
if err != nil {
|
| 20 | 21 |
return nil, err |
| 21 | 22 |
} |
| ... | ... |
@@ -17,6 +17,10 @@ keywords: "API, Docker, rcli, REST, documentation" |
| 17 | 17 |
|
| 18 | 18 |
[Docker Engine API v1.48](https://docs.docker.com/engine/api/v1.48/) documentation |
| 19 | 19 |
|
| 20 |
+* `GET /images/{name}/history` now supports a `platform` parameter (JSON
|
|
| 21 |
+ encoded OCI Platform type) that allows to specify a platform to show the |
|
| 22 |
+ history of. |
|
| 23 |
+ |
|
| 20 | 24 |
## v1.47 API changes |
| 21 | 25 |
|
| 22 | 26 |
[Docker Engine API v1.47](https://docs.docker.com/engine/api/v1.47/) documentation |
| ... | ... |
@@ -73,7 +73,7 @@ func (s *DockerAPISuite) TestAPIImagesHistory(c *testing.T) {
|
| 73 | 73 |
buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nENV FOO bar"))
|
| 74 | 74 |
id := getIDByName(c, name) |
| 75 | 75 |
|
| 76 |
- historydata, err := apiClient.ImageHistory(testutil.GetContext(c), id) |
|
| 76 |
+ historydata, err := apiClient.ImageHistory(testutil.GetContext(c), id, image.HistoryOptions{})
|
|
| 77 | 77 |
assert.NilError(c, err) |
| 78 | 78 |
|
| 79 | 79 |
assert.Assert(c, len(historydata) != 0) |
| ... | ... |
@@ -8,6 +8,7 @@ import ( |
| 8 | 8 |
|
| 9 | 9 |
"github.com/docker/docker/api/types" |
| 10 | 10 |
containertypes "github.com/docker/docker/api/types/container" |
| 11 |
+ "github.com/docker/docker/api/types/image" |
|
| 11 | 12 |
dclient "github.com/docker/docker/client" |
| 12 | 13 |
"github.com/docker/docker/integration/internal/container" |
| 13 | 14 |
"github.com/docker/docker/pkg/stdcopy" |
| ... | ... |
@@ -105,9 +106,9 @@ func TestBuildSquashParent(t *testing.T) {
|
| 105 | 105 |
container.WithCmd("/bin/sh", "-c", `[ "$(echo $HELLO)" = "world" ]`),
|
| 106 | 106 |
) |
| 107 | 107 |
|
| 108 |
- origHistory, err := client.ImageHistory(ctx, origID) |
|
| 108 |
+ origHistory, err := client.ImageHistory(ctx, origID, image.HistoryOptions{})
|
|
| 109 | 109 |
assert.NilError(t, err) |
| 110 |
- testHistory, err := client.ImageHistory(ctx, name) |
|
| 110 |
+ testHistory, err := client.ImageHistory(ctx, name, image.HistoryOptions{})
|
|
| 111 | 111 |
assert.NilError(t, err) |
| 112 | 112 |
|
| 113 | 113 |
inspect, _, err = client.ImageInspectWithRaw(ctx, name) |