mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
Merge pull request #49586 from vvoland/image-inspect-platform
image/inspect: Add platform selection
This commit is contained in:
@@ -341,8 +341,22 @@ func (ir *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWrite
|
|||||||
manifests = httputils.BoolValue(r, "manifests")
|
manifests = httputils.BoolValue(r, "manifests")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var platform *ocispec.Platform
|
||||||
|
if r.Form.Get("platform") != "" && versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.49") {
|
||||||
|
p, err := httputils.DecodePlatform(r.Form.Get("platform"))
|
||||||
|
if err != nil {
|
||||||
|
return errdefs.InvalidParameter(err)
|
||||||
|
}
|
||||||
|
platform = p
|
||||||
|
}
|
||||||
|
|
||||||
|
if manifests && platform != nil {
|
||||||
|
return errdefs.InvalidParameter(errors.New("conflicting options: manifests and platform options cannot both be set"))
|
||||||
|
}
|
||||||
|
|
||||||
imageInspect, err := ir.backend.ImageInspect(ctx, vars["name"], backend.ImageInspectOpts{
|
imageInspect, err := ir.backend.ImageInspect(ctx, vars["name"], backend.ImageInspectOpts{
|
||||||
Manifests: manifests,
|
Manifests: manifests,
|
||||||
|
Platform: platform,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ type GetImageOpts struct {
|
|||||||
// ImageInspectOpts holds parameters to inspect an image.
|
// ImageInspectOpts holds parameters to inspect an image.
|
||||||
type ImageInspectOpts struct {
|
type ImageInspectOpts struct {
|
||||||
Manifests bool
|
Manifests bool
|
||||||
|
Platform *ocispec.Platform
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitConfig is the configuration for creating an image as part of a build.
|
// CommitConfig is the configuration for creating an image as part of a build.
|
||||||
|
|||||||
@@ -128,11 +128,12 @@ type InspectResponse struct {
|
|||||||
// compatibility.
|
// compatibility.
|
||||||
Descriptor *ocispec.Descriptor `json:"Descriptor,omitempty"`
|
Descriptor *ocispec.Descriptor `json:"Descriptor,omitempty"`
|
||||||
|
|
||||||
// Manifests is a list of image manifests available in this image. It
|
// Manifests is a list of image manifests available in this image. It
|
||||||
// provides a more detailed view of the platform-specific image manifests or
|
// provides a more detailed view of the platform-specific image manifests or
|
||||||
// other image-attached data like build attestations.
|
// other image-attached data like build attestations.
|
||||||
//
|
//
|
||||||
// Only available if the daemon provides a multi-platform image store.
|
// Only available if the daemon provides a multi-platform image store, the client
|
||||||
|
// requests manifests AND does not request a specific platform.
|
||||||
//
|
//
|
||||||
// WARNING: This is experimental and may change at any time without any backward
|
// WARNING: This is experimental and may change at any time without any backward
|
||||||
// compatibility.
|
// compatibility.
|
||||||
|
|||||||
@@ -106,6 +106,11 @@ type LoadOptions struct {
|
|||||||
type InspectOptions struct {
|
type InspectOptions struct {
|
||||||
// Manifests returns the image manifests.
|
// Manifests returns the image manifests.
|
||||||
Manifests bool
|
Manifests bool
|
||||||
|
|
||||||
|
// Platform selects the specific platform of a multi-platform image to inspect.
|
||||||
|
//
|
||||||
|
// This option is only available for API version 1.49 and up.
|
||||||
|
Platform *ocispec.Platform
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveOptions holds parameters to save images.
|
// SaveOptions holds parameters to save images.
|
||||||
|
|||||||
@@ -32,6 +32,17 @@ func (cli *Client) ImageInspect(ctx context.Context, imageID string, inspectOpts
|
|||||||
query.Set("manifests", "1")
|
query.Set("manifests", "1")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.apiOptions.Platform != nil {
|
||||||
|
if err := cli.NewVersionError(ctx, "1.49", "platform"); err != nil {
|
||||||
|
return image.InspectResponse{}, err
|
||||||
|
}
|
||||||
|
platform, err := encodePlatform(opts.apiOptions.Platform)
|
||||||
|
if err != nil {
|
||||||
|
return image.InspectResponse{}, err
|
||||||
|
}
|
||||||
|
query.Set("platform", platform)
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := cli.get(ctx, "/images/"+imageID+"/json", query, nil)
|
resp, err := cli.get(ctx, "/images/"+imageID+"/json", query, nil)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/docker/docker/api/types/image"
|
||||||
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ImageInspectOption is a type representing functional options for the image inspect operation.
|
// ImageInspectOption is a type representing functional options for the image inspect operation.
|
||||||
@@ -36,6 +37,17 @@ func ImageInspectWithManifests(manifests bool) ImageInspectOption {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ImageInspectWithPlatform sets platform API option for the image inspect operation.
|
||||||
|
// This option is only available for API version 1.49 and up.
|
||||||
|
// With this option set, the image inspect operation will return information for the
|
||||||
|
// specified platform variant of the multi-platform image.
|
||||||
|
func ImageInspectWithPlatform(platform *ocispec.Platform) ImageInspectOption {
|
||||||
|
return imageInspectOptionFunc(func(clientOpts *imageInspectOpts) error {
|
||||||
|
clientOpts.apiOptions.Platform = platform
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ImageInspectWithAPIOpts sets the API options for the image inspect operation.
|
// ImageInspectWithAPIOpts sets the API options for the image inspect operation.
|
||||||
func ImageInspectWithAPIOpts(opts image.InspectOptions) ImageInspectOption {
|
func ImageInspectWithAPIOpts(opts image.InspectOptions) ImageInspectOption {
|
||||||
return imageInspectOptionFunc(func(clientOpts *imageInspectOpts) error {
|
return imageInspectOptionFunc(func(clientOpts *imageInspectOpts) error {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/docker/docker/api/types/image"
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
)
|
)
|
||||||
@@ -79,3 +80,47 @@ func TestImageInspect(t *testing.T) {
|
|||||||
t.Fatalf("expected `%v`, got %v", expectedTags, imageInspect.RepoTags)
|
t.Fatalf("expected `%v`, got %v", expectedTags, imageInspect.RepoTags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImageInspectWithPlatform(t *testing.T) {
|
||||||
|
expectedURL := "/images/image_id/json"
|
||||||
|
requestedPlatform := &ocispec.Platform{
|
||||||
|
OS: "linux",
|
||||||
|
Architecture: "arm64",
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedPlatform, err := encodePlatform(requestedPlatform)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
client := &Client{
|
||||||
|
client: newMockClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
if !strings.HasPrefix(req.URL.Path, expectedURL) {
|
||||||
|
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if platform parameter is passed correctly
|
||||||
|
platform := req.URL.Query().Get("platform")
|
||||||
|
if platform != expectedPlatform {
|
||||||
|
return nil, fmt.Errorf("Expected platform '%s', got '%s'", expectedPlatform, platform)
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := json.Marshal(image.InspectResponse{
|
||||||
|
ID: "image_id",
|
||||||
|
Architecture: "arm64",
|
||||||
|
Os: "linux",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: io.NopCloser(bytes.NewReader(content)),
|
||||||
|
}, nil
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
imageInspect, err := client.ImageInspect(context.Background(), "image_id", ImageInspectWithPlatform(requestedPlatform))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, imageInspect.ID, "image_id")
|
||||||
|
assert.Equal(t, imageInspect.Architecture, "arm64")
|
||||||
|
assert.Equal(t, imageInspect.Os, "linux")
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (i *ImageService) ImageInspect(ctx context.Context, refOrID string, opts backend.ImageInspectOpts) (*imagetypes.InspectResponse, error) {
|
func (i *ImageService) ImageInspect(ctx context.Context, refOrID string, opts backend.ImageInspectOpts) (*imagetypes.InspectResponse, error) {
|
||||||
// TODO: Pass in opts
|
requestedPlatform := opts.Platform
|
||||||
var requestedPlatform *ocispec.Platform
|
|
||||||
|
|
||||||
c8dImg, err := i.resolveImage(ctx, refOrID)
|
c8dImg, err := i.resolveImage(ctx, refOrID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -60,7 +59,6 @@ func (i *ImageService) ImageInspect(ctx context.Context, refOrID string, opts ba
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:govet // TODO: requestedPlatform is always nil, but should be passed by the caller
|
|
||||||
if multi.Best == nil && requestedPlatform != nil {
|
if multi.Best == nil && requestedPlatform != nil {
|
||||||
return nil, &errPlatformNotFound{
|
return nil, &errPlatformNotFound{
|
||||||
imageRef: refOrID,
|
imageRef: refOrID,
|
||||||
@@ -87,6 +85,10 @@ func (i *ImageService) ImageInspect(ctx context.Context, refOrID string, opts ba
|
|||||||
|
|
||||||
repoTags, repoDigests := collectRepoTagsAndDigests(ctx, tagged)
|
repoTags, repoDigests := collectRepoTagsAndDigests(ctx, tagged)
|
||||||
|
|
||||||
|
if requestedPlatform != nil {
|
||||||
|
target = multi.Best.Target()
|
||||||
|
}
|
||||||
|
|
||||||
resp := &imagetypes.InspectResponse{
|
resp := &imagetypes.InspectResponse{
|
||||||
ID: target.Digest.String(),
|
ID: target.Digest.String(),
|
||||||
RepoTags: repoTags,
|
RepoTags: repoTags,
|
||||||
|
|||||||
@@ -61,4 +61,38 @@ func TestImageInspect(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("inspect image with platform parameter", func(t *testing.T) {
|
||||||
|
ctx := logtest.WithT(ctx, t)
|
||||||
|
service := fakeImageService(t, ctx, cs)
|
||||||
|
|
||||||
|
multiPlatformImage := toContainerdImage(t, func(dir string) (*ocispec.Index, error) {
|
||||||
|
idx, _, err := specialimage.MultiPlatform(dir, "multiplatform:latest", []ocispec.Platform{
|
||||||
|
{OS: "linux", Architecture: "amd64"},
|
||||||
|
{OS: "linux", Architecture: "arm64"},
|
||||||
|
})
|
||||||
|
return idx, err
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := service.images.Create(ctx, multiPlatformImage)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
// Test with amd64 platform
|
||||||
|
amd64Platform := &ocispec.Platform{OS: "linux", Architecture: "amd64"}
|
||||||
|
inspectAmd64, err := service.ImageInspect(ctx, multiPlatformImage.Name, backend.ImageInspectOpts{
|
||||||
|
Platform: amd64Platform,
|
||||||
|
})
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, inspectAmd64.Architecture, "amd64")
|
||||||
|
assert.Equal(t, inspectAmd64.Os, "linux")
|
||||||
|
|
||||||
|
// Test with arm64 platform
|
||||||
|
arm64Platform := &ocispec.Platform{OS: "linux", Architecture: "arm64"}
|
||||||
|
inspectArm64, err := service.ImageInspect(ctx, multiPlatformImage.Name, backend.ImageInspectOpts{
|
||||||
|
Platform: arm64Platform,
|
||||||
|
})
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, inspectArm64.Architecture, "arm64")
|
||||||
|
assert.Equal(t, inspectArm64.Os, "linux")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import (
|
|||||||
"github.com/docker/docker/layer"
|
"github.com/docker/docker/layer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (i *ImageService) ImageInspect(ctx context.Context, refOrID string, _ backend.ImageInspectOpts) (*imagetypes.InspectResponse, error) {
|
func (i *ImageService) ImageInspect(ctx context.Context, refOrID string, opts backend.ImageInspectOpts) (*imagetypes.InspectResponse, error) {
|
||||||
img, err := i.GetImage(ctx, refOrID, backend.GetImageOpts{})
|
img, err := i.GetImage(ctx, refOrID, backend.GetImageOpts{Platform: opts.Platform})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ keywords: "API, Docker, rcli, REST, documentation"
|
|||||||
|
|
||||||
[Docker Engine API v1.49](https://docs.docker.com/reference/api/engine/version/v1.49/) documentation
|
[Docker Engine API v1.49](https://docs.docker.com/reference/api/engine/version/v1.49/) documentation
|
||||||
|
|
||||||
|
* `GET /images/{name}/json` now supports a `platform` parameter (JSON
|
||||||
|
encoded OCI Platform type) allowing to specify a platform of the multi-platform
|
||||||
|
image to inspect.
|
||||||
|
This option is mutually exclusive with the `manifests` option.
|
||||||
|
|
||||||
## v1.48 API changes
|
## v1.48 API changes
|
||||||
|
|
||||||
[Docker Engine API v1.48](https://docs.docker.com/reference/api/engine/version/v1.48/) documentation
|
[Docker Engine API v1.48](https://docs.docker.com/reference/api/engine/version/v1.48/) documentation
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/docker/docker/api/types/image"
|
"github.com/docker/docker/api/types/image"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/internal/testutils/specialimage"
|
"github.com/docker/docker/internal/testutils/specialimage"
|
||||||
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
"gotest.tools/v3/skip"
|
"gotest.tools/v3/skip"
|
||||||
@@ -80,3 +81,123 @@ func TestImageInspectDescriptor(t *testing.T) {
|
|||||||
assert.Check(t, inspect.Descriptor.Digest.String() == inspect.ID)
|
assert.Check(t, inspect.Descriptor.Digest.String() == inspect.ID)
|
||||||
assert.Check(t, inspect.Descriptor.Size > 0)
|
assert.Check(t, inspect.Descriptor.Size > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImageInspectWithPlatform(t *testing.T) {
|
||||||
|
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "The test image is a Linux image")
|
||||||
|
ctx := setupTest(t)
|
||||||
|
|
||||||
|
apiClient := testEnv.APIClient()
|
||||||
|
|
||||||
|
nativePlatform := ocispec.Platform{
|
||||||
|
OS: testEnv.DaemonInfo.OSType,
|
||||||
|
Architecture: testEnv.DaemonInfo.Architecture,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a platform that does not match the host platform
|
||||||
|
differentOS := "linux"
|
||||||
|
if nativePlatform.OS == "linux" {
|
||||||
|
differentOS = "windows"
|
||||||
|
}
|
||||||
|
differentPlatform := ocispec.Platform{
|
||||||
|
OS: differentOS,
|
||||||
|
Architecture: "amd64",
|
||||||
|
}
|
||||||
|
|
||||||
|
imageID := specialimage.Load(ctx, t, apiClient, func(dir string) (*ocispec.Index, error) {
|
||||||
|
i, descs, err := specialimage.MultiPlatform(dir, "multiplatform:latest", []ocispec.Platform{nativePlatform, differentPlatform})
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
err = specialimage.LegacyManifest(dir, "multiplatform:latest", descs[0])
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
return i, err
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
requestedPlatform *ocispec.Platform
|
||||||
|
expectedPlatform *ocispec.Platform
|
||||||
|
expectedError string
|
||||||
|
withManifests bool
|
||||||
|
snapshotterOnly bool
|
||||||
|
graphdriverOnly bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default",
|
||||||
|
requestedPlatform: nil,
|
||||||
|
expectedPlatform: &nativePlatform,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "snapshotter/with-manifests",
|
||||||
|
requestedPlatform: nil,
|
||||||
|
expectedPlatform: &nativePlatform,
|
||||||
|
snapshotterOnly: true,
|
||||||
|
withManifests: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "native",
|
||||||
|
requestedPlatform: &nativePlatform,
|
||||||
|
expectedPlatform: &nativePlatform,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "different",
|
||||||
|
requestedPlatform: &differentPlatform,
|
||||||
|
expectedPlatform: &differentPlatform,
|
||||||
|
snapshotterOnly: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "different not supported on graphdriver",
|
||||||
|
requestedPlatform: &differentPlatform,
|
||||||
|
graphdriverOnly: true,
|
||||||
|
// image with reference multiplatform:latest was found but its platform (linux/aarch64) does not match the specified platform (windows/amd64)
|
||||||
|
expectedError: "image with reference multiplatform:latest was found but its platform",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
if tc.snapshotterOnly && !testEnv.UsingSnapshotter() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tc.graphdriverOnly && testEnv.UsingSnapshotter() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
var opts []client.ImageInspectOption
|
||||||
|
if tc.requestedPlatform != nil {
|
||||||
|
opts = append(opts, client.ImageInspectWithPlatform(tc.requestedPlatform))
|
||||||
|
}
|
||||||
|
if tc.withManifests {
|
||||||
|
opts = append(opts, client.ImageInspectWithManifests(true))
|
||||||
|
}
|
||||||
|
inspect, err := apiClient.ImageInspect(ctx, imageID, opts...)
|
||||||
|
if tc.expectedError != "" {
|
||||||
|
assert.Assert(t, is.ErrorContains(err, tc.expectedError))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
assert.Check(t, is.Equal(inspect.Architecture, tc.expectedPlatform.Architecture))
|
||||||
|
assert.Check(t, is.Equal(inspect.Os, tc.expectedPlatform.OS))
|
||||||
|
|
||||||
|
if testEnv.UsingSnapshotter() {
|
||||||
|
assert.Assert(t, inspect.Descriptor != nil)
|
||||||
|
if tc.requestedPlatform != nil {
|
||||||
|
if assert.Check(t, inspect.Descriptor.Platform != nil) {
|
||||||
|
assert.Check(t, is.DeepEqual(*inspect.Descriptor.Platform, *tc.expectedPlatform))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert.Check(t, inspect.Descriptor == nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.withManifests {
|
||||||
|
t.Run("has manifests", func(t *testing.T) {
|
||||||
|
assert.Check(t, is.Len(inspect.Manifests, 2))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
t.Run("has no manifests", func(t *testing.T) {
|
||||||
|
assert.Check(t, is.Nil(inspect.Manifests))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ func MultiPlatform(dir string, imageRef string, imagePlatforms []ocispec.Platfor
|
|||||||
|
|
||||||
for _, platform := range imagePlatforms {
|
for _, platform := range imagePlatforms {
|
||||||
ps := platforms.Format(platform)
|
ps := platforms.Format(platform)
|
||||||
manifestDesc, err := oneLayerPlatformManifest(dir, platform, FileInLayer{Path: "bash", Content: []byte("layer-" + ps)})
|
manifestDesc, _, err := oneLayerPlatformManifest(dir, platform, FileInLayer{Path: "bash", Content: []byte("layer-" + ps)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func PartialMultiPlatform(dir string, imageRef string, opts PartialOpts) (*ocisp
|
|||||||
|
|
||||||
for _, platform := range opts.Stored {
|
for _, platform := range opts.Stored {
|
||||||
ps := platforms.Format(platform)
|
ps := platforms.Format(platform)
|
||||||
manifestDesc, err := oneLayerPlatformManifest(dir, platform, FileInLayer{Path: "bash", Content: []byte("layer-" + ps)})
|
manifestDesc, _, err := oneLayerPlatformManifest(dir, platform, FileInLayer{Path: "bash", Content: []byte("layer-" + ps)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package specialimage
|
package specialimage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
@@ -75,3 +78,32 @@ func blobPaths(descriptors []ocispec.Descriptor) []string {
|
|||||||
}
|
}
|
||||||
return paths
|
return paths
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readJson(path string, v any) error {
|
||||||
|
content, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.Unmarshal(content, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LegacyManifest(dir string, imageRef string, mfstDesc ocispec.Descriptor) error {
|
||||||
|
legacyManifests := []manifestItem{}
|
||||||
|
|
||||||
|
var mfst ocispec.Manifest
|
||||||
|
if err := readJson(filepath.Join(dir, blobPath(mfstDesc)), &mfst); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
legacyManifests = append(legacyManifests, manifestItem{
|
||||||
|
Config: blobPath(mfst.Config),
|
||||||
|
RepoTags: []string{imageRef},
|
||||||
|
Layers: blobPaths(mfst.Layers),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := writeJson(legacyManifests, filepath.Join(dir, "manifest.json")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ func TwoPlatform(dir string) (*ocispec.Index, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
manifest1Desc, err := oneLayerPlatformManifest(dir, platforms.MustParse("linux/amd64"), FileInLayer{Path: "bash", Content: []byte("layer1")})
|
manifest1Desc, _, err := oneLayerPlatformManifest(dir, platforms.MustParse("linux/amd64"), FileInLayer{Path: "bash", Content: []byte("layer1")})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
manifest2Desc, err := oneLayerPlatformManifest(dir, platforms.MustParse("linux/arm64"), FileInLayer{Path: "bash", Content: []byte("layer2")})
|
manifest2Desc, _, err := oneLayerPlatformManifest(dir, platforms.MustParse("linux/arm64"), FileInLayer{Path: "bash", Content: []byte("layer2")})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -40,13 +40,13 @@ type FileInLayer struct {
|
|||||||
Content []byte
|
Content []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func oneLayerPlatformManifest(dir string, platform ocispec.Platform, f FileInLayer) (ocispec.Descriptor, error) {
|
func oneLayerPlatformManifest(dir string, platform ocispec.Platform, f FileInLayer) (ocispec.Descriptor, manifestItem, error) {
|
||||||
layerDesc, err := writeLayerWithOneFile(dir, f.Path, f.Content)
|
layerDesc, err := writeLayerWithOneFile(dir, f.Path, f.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ocispec.Descriptor{}, err
|
return ocispec.Descriptor{}, manifestItem{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
configDesc, err := writeJsonBlob(dir, ocispec.MediaTypeImageConfig, ocispec.Image{
|
img := ocispec.Image{
|
||||||
Platform: platform,
|
Platform: platform,
|
||||||
Config: ocispec.ImageConfig{
|
Config: ocispec.ImageConfig{
|
||||||
Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
|
Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
|
||||||
@@ -55,9 +55,11 @@ func oneLayerPlatformManifest(dir string, platform ocispec.Platform, f FileInLay
|
|||||||
Type: "layers",
|
Type: "layers",
|
||||||
DiffIDs: []digest.Digest{layerDesc.Digest},
|
DiffIDs: []digest.Digest{layerDesc.Digest},
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
|
|
||||||
|
configDesc, err := writeJsonBlob(dir, ocispec.MediaTypeImageConfig, img)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ocispec.Descriptor{}, err
|
return ocispec.Descriptor{}, manifestItem{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestDesc, err := writeJsonBlob(dir, ocispec.MediaTypeImageManifest, ocispec.Manifest{
|
manifestDesc, err := writeJsonBlob(dir, ocispec.MediaTypeImageManifest, ocispec.Manifest{
|
||||||
@@ -66,11 +68,14 @@ func oneLayerPlatformManifest(dir string, platform ocispec.Platform, f FileInLay
|
|||||||
Layers: []ocispec.Descriptor{layerDesc},
|
Layers: []ocispec.Descriptor{layerDesc},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ocispec.Descriptor{}, err
|
return ocispec.Descriptor{}, manifestItem{}, err
|
||||||
}
|
}
|
||||||
manifestDesc.Platform = &platform
|
manifestDesc.Platform = &platform
|
||||||
|
|
||||||
return manifestDesc, nil
|
return manifestDesc, manifestItem{
|
||||||
|
Config: blobPath(configDesc),
|
||||||
|
Layers: []string{blobPath(layerDesc)},
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func multiPlatformImage(dir string, ref reference.Named, target ocispec.Index) (*ocispec.Index, error) {
|
func multiPlatformImage(dir string, ref reference.Named, target ocispec.Index) (*ocispec.Index, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user