client: Remove ImageCreate in favor of ImagePull/ImageImport

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
Paweł Gronowski
2025-10-31 14:48:54 +01:00
parent fd1593c067
commit 73455ce01a
9 changed files with 32 additions and 209 deletions

View File

@@ -96,7 +96,6 @@ type ImageAPIClient interface {
ImageBuild(ctx context.Context, context io.Reader, options ImageBuildOptions) (ImageBuildResult, error)
BuildCachePrune(ctx context.Context, opts BuildCachePruneOptions) (BuildCachePruneResult, error)
BuildCancel(ctx context.Context, id string, opts BuildCancelOptions) (BuildCancelResult, error)
ImageCreate(ctx context.Context, parentReference string, options ImageCreateOptions) (ImageCreateResult, error)
ImageImport(ctx context.Context, source ImageImportSource, ref string, options ImageImportOptions) (ImageImportResult, error)
ImageList(ctx context.Context, options ImageListOptions) (ImageListResult, error)

View File

@@ -1,50 +0,0 @@
package client
import (
"context"
"net/http"
"net/url"
cerrdefs "github.com/containerd/errdefs"
"github.com/distribution/reference"
"github.com/moby/moby/api/types/registry"
)
// ImageCreate creates a new image based on the parent options.
// It returns the JSON content in the response body.
func (cli *Client) ImageCreate(ctx context.Context, parentReference string, options ImageCreateOptions) (ImageCreateResult, error) {
ref, err := reference.ParseNormalizedNamed(parentReference)
if err != nil {
return ImageCreateResult{}, err
}
query := url.Values{}
query.Set("fromImage", ref.Name())
query.Set("tag", getAPITagFromNamedRef(ref))
if len(options.Platforms) > 0 {
if len(options.Platforms) > 1 {
// TODO(thaJeztah): update API spec and add equivalent check on the daemon. We need this still for older daemons, which would ignore it.
return ImageCreateResult{}, cerrdefs.ErrInvalidArgument.WithMessage("specifying multiple platforms is not yet supported")
}
query.Set("platform", formatPlatform(options.Platforms[0]))
}
resp, err := cli.tryImageCreate(ctx, query, staticAuth(options.RegistryAuth))
if err != nil {
return ImageCreateResult{}, err
}
return ImageCreateResult{Body: resp.Body}, nil
}
func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, resolveAuth registry.RequestAuthConfig) (*http.Response, error) {
hdr := http.Header{}
if resolveAuth != nil {
registryAuth, err := resolveAuth(ctx)
if err != nil {
return nil, err
}
if registryAuth != "" {
hdr.Set(registry.AuthHeader, registryAuth)
}
}
return cli.post(ctx, "/images/create", query, nil, hdr)
}

View File

@@ -1,21 +0,0 @@
package client
import (
"io"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ImageCreateOptions holds information to create images.
type ImageCreateOptions struct {
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry.
// Platforms specifies the platforms to platform of the image if it needs
// to be pulled from the registry. Multiple platforms can be provided
// if the daemon supports multi-platform pulls.
Platforms []ocispec.Platform
}
// ImageCreateResult holds the response body returned by the daemon for image create.
type ImageCreateResult struct {
Body io.ReadCloser
}

View File

@@ -1,65 +0,0 @@
package client
import (
"context"
"fmt"
"io"
"net/http"
"testing"
cerrdefs "github.com/containerd/errdefs"
"github.com/moby/moby/api/types/registry"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestImageCreateError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageCreate(context.Background(), "reference", ImageCreateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
func TestImageCreate(t *testing.T) {
const (
expectedURL = "/images/create"
expectedImage = "docker.io/test/my_image"
expectedTag = "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
specifiedReference = "test/my_image:latest@" + expectedTag
expectedRegistryAuth = "eyJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOnsiYXV0aCI6ImRHOTBid289IiwiZW1haWwiOiJqb2huQGRvZS5jb20ifX0="
)
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
if err := assertRequest(req, http.MethodPost, expectedURL); err != nil {
return nil, err
}
registryAuth := req.Header.Get(registry.AuthHeader)
if registryAuth != expectedRegistryAuth {
return nil, fmt.Errorf("%s header not properly set in the request. Expected '%s', got %s", registry.AuthHeader, expectedRegistryAuth, registryAuth)
}
query := req.URL.Query()
fromImage := query.Get("fromImage")
if fromImage != expectedImage {
return nil, fmt.Errorf("fromImage not set in URL query properly. Expected '%s', got %s", expectedImage, fromImage)
}
tag := query.Get("tag")
if tag != expectedTag {
return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", expectedTag, tag)
}
return mockResponse(http.StatusOK, nil, "body")(req)
}))
assert.NilError(t, err)
createResult, err := client.ImageCreate(context.Background(), specifiedReference, ImageCreateOptions{
RegistryAuth: expectedRegistryAuth,
})
assert.NilError(t, err)
response, err := io.ReadAll(createResult.Body)
assert.NilError(t, err)
err = createResult.Body.Close()
assert.NilError(t, err)
assert.Check(t, is.Equal(string(response), "body"))
}

View File

@@ -4,11 +4,13 @@ import (
"context"
"io"
"iter"
"net/http"
"net/url"
cerrdefs "github.com/containerd/errdefs"
"github.com/distribution/reference"
"github.com/moby/moby/api/types/jsonstream"
"github.com/moby/moby/api/types/registry"
"github.com/moby/moby/client/internal"
)
@@ -75,3 +77,17 @@ func getAPITagFromNamedRef(ref reference.Named) string {
}
return ""
}
func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, resolveAuth registry.RequestAuthConfig) (*http.Response, error) {
hdr := http.Header{}
if resolveAuth != nil {
registryAuth, err := resolveAuth(ctx)
if err != nil {
return nil, err
}
if registryAuth != "" {
hdr.Set(registry.AuthHeader, registryAuth)
}
}
return cli.post(ctx, "/images/create", query, nil, hdr)
}

View File

@@ -96,7 +96,6 @@ type ImageAPIClient interface {
ImageBuild(ctx context.Context, context io.Reader, options ImageBuildOptions) (ImageBuildResult, error)
BuildCachePrune(ctx context.Context, opts BuildCachePruneOptions) (BuildCachePruneResult, error)
BuildCancel(ctx context.Context, id string, opts BuildCancelOptions) (BuildCancelResult, error)
ImageCreate(ctx context.Context, parentReference string, options ImageCreateOptions) (ImageCreateResult, error)
ImageImport(ctx context.Context, source ImageImportSource, ref string, options ImageImportOptions) (ImageImportResult, error)
ImageList(ctx context.Context, options ImageListOptions) (ImageListResult, error)

View File

@@ -1,50 +0,0 @@
package client
import (
"context"
"net/http"
"net/url"
cerrdefs "github.com/containerd/errdefs"
"github.com/distribution/reference"
"github.com/moby/moby/api/types/registry"
)
// ImageCreate creates a new image based on the parent options.
// It returns the JSON content in the response body.
func (cli *Client) ImageCreate(ctx context.Context, parentReference string, options ImageCreateOptions) (ImageCreateResult, error) {
ref, err := reference.ParseNormalizedNamed(parentReference)
if err != nil {
return ImageCreateResult{}, err
}
query := url.Values{}
query.Set("fromImage", ref.Name())
query.Set("tag", getAPITagFromNamedRef(ref))
if len(options.Platforms) > 0 {
if len(options.Platforms) > 1 {
// TODO(thaJeztah): update API spec and add equivalent check on the daemon. We need this still for older daemons, which would ignore it.
return ImageCreateResult{}, cerrdefs.ErrInvalidArgument.WithMessage("specifying multiple platforms is not yet supported")
}
query.Set("platform", formatPlatform(options.Platforms[0]))
}
resp, err := cli.tryImageCreate(ctx, query, staticAuth(options.RegistryAuth))
if err != nil {
return ImageCreateResult{}, err
}
return ImageCreateResult{Body: resp.Body}, nil
}
func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, resolveAuth registry.RequestAuthConfig) (*http.Response, error) {
hdr := http.Header{}
if resolveAuth != nil {
registryAuth, err := resolveAuth(ctx)
if err != nil {
return nil, err
}
if registryAuth != "" {
hdr.Set(registry.AuthHeader, registryAuth)
}
}
return cli.post(ctx, "/images/create", query, nil, hdr)
}

View File

@@ -1,21 +0,0 @@
package client
import (
"io"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ImageCreateOptions holds information to create images.
type ImageCreateOptions struct {
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry.
// Platforms specifies the platforms to platform of the image if it needs
// to be pulled from the registry. Multiple platforms can be provided
// if the daemon supports multi-platform pulls.
Platforms []ocispec.Platform
}
// ImageCreateResult holds the response body returned by the daemon for image create.
type ImageCreateResult struct {
Body io.ReadCloser
}

View File

@@ -4,11 +4,13 @@ import (
"context"
"io"
"iter"
"net/http"
"net/url"
cerrdefs "github.com/containerd/errdefs"
"github.com/distribution/reference"
"github.com/moby/moby/api/types/jsonstream"
"github.com/moby/moby/api/types/registry"
"github.com/moby/moby/client/internal"
)
@@ -75,3 +77,17 @@ func getAPITagFromNamedRef(ref reference.Named) string {
}
return ""
}
func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, resolveAuth registry.RequestAuthConfig) (*http.Response, error) {
hdr := http.Header{}
if resolveAuth != nil {
registryAuth, err := resolveAuth(ctx)
if err != nil {
return nil, err
}
if registryAuth != "" {
hdr.Set(registry.AuthHeader, registryAuth)
}
}
return cli.post(ctx, "/images/create", query, nil, hdr)
}