Merge pull request #51227 from vvoland/client-image-opts

client/image: Wrap result and options in structs
This commit is contained in:
Austin Vazquez
2025-10-21 20:42:14 -05:00
committed by GitHub
52 changed files with 299 additions and 188 deletions

View File

@@ -7,13 +7,15 @@ import (
type BuildCancelOptions struct{}
type BuildCancelResult struct{}
// BuildCancel requests the daemon to cancel the ongoing build request
// with the given id.
func (cli *Client) BuildCancel(ctx context.Context, id string, _ BuildCancelOptions) error {
func (cli *Client) BuildCancel(ctx context.Context, id string, _ BuildCancelOptions) (BuildCancelResult, error) {
query := url.Values{}
query.Set("id", id)
resp, err := cli.post(ctx, "/build/cancel", query, nil, nil)
defer ensureReaderClosed(resp)
return err
return BuildCancelResult{}, err
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/moby/moby/api/types"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/events"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/api/types/plugin"
"github.com/moby/moby/api/types/registry"
@@ -105,18 +104,18 @@ type DistributionAPIClient interface {
// ImageAPIClient defines API client methods for the images
type ImageAPIClient interface {
ImageBuild(ctx context.Context, context io.Reader, options ImageBuildOptions) (ImageBuildResponse, error)
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) error
ImageCreate(ctx context.Context, parentReference string, options ImageCreateOptions) (io.ReadCloser, error)
ImageImport(ctx context.Context, source ImageImportSource, ref string, options ImageImportOptions) (io.ReadCloser, 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) ([]image.Summary, error)
ImageList(ctx context.Context, options ImageListOptions) (ImageListResult, error)
ImagePull(ctx context.Context, ref string, options ImagePullOptions) (ImagePullResponse, error)
ImagePush(ctx context.Context, ref string, options ImagePushOptions) (ImagePushResponse, error)
ImageRemove(ctx context.Context, image string, options ImageRemoveOptions) ([]image.DeleteResponse, error)
ImageSearch(ctx context.Context, term string, options ImageSearchOptions) ([]registry.SearchResult, error)
ImageTag(ctx context.Context, image, ref string) error
ImageRemove(ctx context.Context, image string, options ImageRemoveOptions) (ImageRemoveResult, error)
ImageSearch(ctx context.Context, term string, options ImageSearchOptions) (ImageSearchResult, error)
ImageTag(ctx context.Context, options ImageTagOptions) (ImageTagResult, error)
ImagesPrune(ctx context.Context, opts ImagePruneOptions) (ImagePruneResult, error)
ImageInspect(ctx context.Context, image string, _ ...ImageInspectOption) (ImageInspectResult, error)

View File

@@ -17,15 +17,15 @@ import (
// ImageBuild sends a request to the daemon to build images.
// The Body in the response implements an [io.ReadCloser] and it's up to the caller to
// close it.
func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, options ImageBuildOptions) (ImageBuildResponse, error) {
func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, options ImageBuildOptions) (ImageBuildResult, error) {
query, err := cli.imageBuildOptionsToQuery(ctx, options)
if err != nil {
return ImageBuildResponse{}, err
return ImageBuildResult{}, err
}
buf, err := json.Marshal(options.AuthConfigs)
if err != nil {
return ImageBuildResponse{}, err
return ImageBuildResult{}, err
}
headers := http.Header{}
@@ -34,10 +34,10 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio
resp, err := cli.postRaw(ctx, "/build", query, buildContext, headers)
if err != nil {
return ImageBuildResponse{}, err
return ImageBuildResult{}, err
}
return ImageBuildResponse{
return ImageBuildResult{
Body: resp.Body,
}, nil
}

View File

@@ -68,9 +68,9 @@ type ImageBuildOutput struct {
Attrs map[string]string
}
// ImageBuildResponse holds information
// ImageBuildResult holds information
// returned by a server after building
// an image.
type ImageBuildResponse struct {
type ImageBuildResult struct {
Body io.ReadCloser
}

View File

@@ -2,7 +2,6 @@ package client
import (
"context"
"io"
"net/http"
"net/url"
"strings"
@@ -13,10 +12,10 @@ import (
// 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) (io.ReadCloser, error) {
func (cli *Client) ImageCreate(ctx context.Context, parentReference string, options ImageCreateOptions) (ImageCreateResult, error) {
ref, err := reference.ParseNormalizedNamed(parentReference)
if err != nil {
return nil, err
return ImageCreateResult{}, err
}
query := url.Values{}
@@ -27,9 +26,9 @@ func (cli *Client) ImageCreate(ctx context.Context, parentReference string, opti
}
resp, err := cli.tryImageCreate(ctx, query, staticAuth(options.RegistryAuth))
if err != nil {
return nil, err
return ImageCreateResult{}, err
}
return resp.Body, nil
return ImageCreateResult{Body: resp.Body}, nil
}
func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, resolveAuth registry.RequestAuthConfig) (*http.Response, error) {

View File

@@ -1,7 +1,14 @@
package client
import "io"
// ImageCreateOptions holds information to create images.
type ImageCreateOptions struct {
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry.
Platform string // Platform is the target platform of the image if it needs to be pulled from the registry.
}
// ImageCreateResult holds the response body returned by the daemon for image create.
type ImageCreateResult struct {
Body io.ReadCloser
}

View File

@@ -57,13 +57,13 @@ func TestImageCreate(t *testing.T) {
}))
assert.NilError(t, err)
createResponse, err := client.ImageCreate(context.Background(), specifiedReference, ImageCreateOptions{
createResult, err := client.ImageCreate(context.Background(), specifiedReference, ImageCreateOptions{
RegistryAuth: expectedRegistryAuth,
})
assert.NilError(t, err)
response, err := io.ReadAll(createResponse)
response, err := io.ReadAll(createResult.Body)
assert.NilError(t, err)
err = createResponse.Close()
err = createResult.Body.Close()
assert.NilError(t, err)
assert.Check(t, is.Equal(string(response), "body"))
}

View File

@@ -2,7 +2,6 @@ package client
import (
"context"
"io"
"net/url"
"strings"
@@ -11,11 +10,11 @@ import (
// ImageImport creates a new image based on the source options.
// It returns the JSON content in the response body.
func (cli *Client) ImageImport(ctx context.Context, source ImageImportSource, ref string, options ImageImportOptions) (io.ReadCloser, error) {
func (cli *Client) ImageImport(ctx context.Context, source ImageImportSource, ref string, options ImageImportOptions) (ImageImportResult, error) {
if ref != "" {
// Check if the given image name can be resolved
if _, err := reference.ParseNormalizedNamed(ref); err != nil {
return nil, err
return ImageImportResult{}, err
}
}
@@ -41,7 +40,7 @@ func (cli *Client) ImageImport(ctx context.Context, source ImageImportSource, re
resp, err := cli.postRaw(ctx, "/images/create", query, source.Source, nil)
if err != nil {
return nil, err
return ImageImportResult{}, err
}
return resp.Body, nil
return ImageImportResult{body: resp.Body}, nil
}

View File

@@ -17,3 +17,19 @@ type ImageImportOptions struct {
Changes []string // Changes are the raw changes to apply to this image
Platform string // Platform is the target platform of the image
}
// ImageImportResult holds the response body returned by the daemon for image import.
type ImageImportResult struct {
body io.ReadCloser
}
func (r ImageImportResult) Read(p []byte) (n int, err error) {
return r.body.Read(p)
}
func (r ImageImportResult) Close() error {
if r.body == nil {
return nil
}
return r.body.Close()
}

View File

@@ -77,14 +77,14 @@ func TestImageImport(t *testing.T) {
}, nil
}))
assert.NilError(t, err)
resp, err := client.ImageImport(context.Background(), ImageImportSource{
result, err := client.ImageImport(context.Background(), ImageImportSource{
Source: strings.NewReader("source"),
SourceName: "image_source",
}, "repository_name:imported", tc.options)
assert.NilError(t, err)
defer assert.NilError(t, resp.Close())
defer assert.NilError(t, result.Close())
body, err := io.ReadAll(resp)
body, err := io.ReadAll(result)
assert.NilError(t, err)
assert.Check(t, is.Equal(string(body), expectedOutput))
})

View File

@@ -15,7 +15,7 @@ import (
// to include [image.Summary.Manifests] with information about image manifests.
// This is experimental and might change in the future without any backward
// compatibility.
func (cli *Client) ImageList(ctx context.Context, options ImageListOptions) ([]image.Summary, error) {
func (cli *Client) ImageList(ctx context.Context, options ImageListOptions) (ImageListResult, error) {
var images []image.Summary
query := url.Values{}
@@ -34,7 +34,7 @@ func (cli *Client) ImageList(ctx context.Context, options ImageListOptions) ([]i
// Normally, version-negotiation (if enabled) would not happen until
// the API request is made.
if err := cli.checkVersion(ctx); err != nil {
return images, err
return ImageListResult{}, err
}
if versions.GreaterThanOrEqualTo(cli.version, "1.47") {
@@ -45,9 +45,9 @@ func (cli *Client) ImageList(ctx context.Context, options ImageListOptions) ([]i
resp, err := cli.get(ctx, "/images/json", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return images, err
return ImageListResult{}, err
}
err = json.NewDecoder(resp.Body).Decode(&images)
return images, err
return ImageListResult{Items: images}, err
}

View File

@@ -1,5 +1,7 @@
package client
import "github.com/moby/moby/api/types/image"
// ImageListOptions holds parameters to list images with.
type ImageListOptions struct {
// All controls whether all images in the graph are filtered, or just
@@ -15,3 +17,8 @@ type ImageListOptions struct {
// Manifests indicates whether the image manifests should be returned.
Manifests bool
}
// ImageListResult holds the result from ImageList.
type ImageListResult struct {
Items []image.Summary
}

View File

@@ -108,7 +108,7 @@ func TestImageList(t *testing.T) {
images, err := client.ImageList(context.Background(), listCase.options)
assert.NilError(t, err)
assert.Check(t, is.Len(images, 2))
assert.Check(t, is.Len(images.Items, 2))
}
}

View File

@@ -9,7 +9,7 @@ import (
)
// ImageRemove removes an image from the docker host.
func (cli *Client) ImageRemove(ctx context.Context, imageID string, options ImageRemoveOptions) ([]image.DeleteResponse, error) {
func (cli *Client) ImageRemove(ctx context.Context, imageID string, options ImageRemoveOptions) (ImageRemoveResult, error) {
query := url.Values{}
if options.Force {
@@ -22,7 +22,7 @@ func (cli *Client) ImageRemove(ctx context.Context, imageID string, options Imag
if len(options.Platforms) > 0 {
p, err := encodePlatforms(options.Platforms...)
if err != nil {
return nil, err
return ImageRemoveResult{}, err
}
query["platforms"] = p
}
@@ -30,10 +30,10 @@ func (cli *Client) ImageRemove(ctx context.Context, imageID string, options Imag
resp, err := cli.delete(ctx, "/images/"+imageID, query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return nil, err
return ImageRemoveResult{}, err
}
var dels []image.DeleteResponse
err = json.NewDecoder(resp.Body).Decode(&dels)
return dels, err
return ImageRemoveResult{Deleted: dels}, err
}

View File

@@ -1,6 +1,9 @@
package client
import ocispec "github.com/opencontainers/image-spec/specs-go/v1"
import (
"github.com/moby/moby/api/types/image"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ImageRemoveOptions holds parameters to remove images.
type ImageRemoveOptions struct {
@@ -8,3 +11,8 @@ type ImageRemoveOptions struct {
Force bool
PruneChildren bool
}
// ImageRemoveResult holds the delete responses returned by the daemon.
type ImageRemoveResult struct {
Deleted []image.DeleteResponse
}

View File

@@ -108,6 +108,6 @@ func TestImageRemove(t *testing.T) {
imageDeletes, err := client.ImageRemove(context.Background(), "image_id", opts)
assert.NilError(t, err)
assert.Check(t, is.Len(imageDeletes, 2))
assert.Check(t, is.Len(imageDeletes.Deleted, 2))
}
}

View File

@@ -13,7 +13,7 @@ import (
// ImageSearch makes the docker host search by a term in a remote registry.
// The list of results is not sorted in any fashion.
func (cli *Client) ImageSearch(ctx context.Context, term string, options ImageSearchOptions) ([]registry.SearchResult, error) {
func (cli *Client) ImageSearch(ctx context.Context, term string, options ImageSearchOptions) (ImageSearchResult, error) {
var results []registry.SearchResult
query := url.Values{}
query.Set("term", term)
@@ -28,16 +28,16 @@ func (cli *Client) ImageSearch(ctx context.Context, term string, options ImageSe
if cerrdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil {
newAuthHeader, privilegeErr := options.PrivilegeFunc(ctx)
if privilegeErr != nil {
return results, privilegeErr
return ImageSearchResult{}, privilegeErr
}
resp, err = cli.tryImageSearch(ctx, query, newAuthHeader)
}
if err != nil {
return results, err
return ImageSearchResult{}, err
}
err = json.NewDecoder(resp.Body).Decode(&results)
return results, err
return ImageSearchResult{Items: results}, err
}
func (cli *Client) tryImageSearch(ctx context.Context, query url.Values, registryAuth string) (*http.Response, error) {

View File

@@ -2,8 +2,15 @@ package client
import (
"context"
"github.com/moby/moby/api/types/registry"
)
// ImageSearchResult wraps results returned by ImageSearch.
type ImageSearchResult struct {
Items []registry.SearchResult
}
// ImageSearchOptions holds parameters to search images with.
type ImageSearchOptions struct {
RegistryAuth string

View File

@@ -97,7 +97,7 @@ func TestImageSearchWithPrivilegedFuncNoError(t *testing.T) {
PrivilegeFunc: privilegeFunc,
})
assert.NilError(t, err)
assert.Check(t, is.Len(results, 1))
assert.Check(t, is.Len(results.Items, 1))
}
func TestImageSearchWithoutErrors(t *testing.T) {
@@ -135,5 +135,5 @@ func TestImageSearchWithoutErrors(t *testing.T) {
Filters: make(Filters).Add("is-automated", "true").Add("stars", "3"),
})
assert.NilError(t, err)
assert.Check(t, is.Len(results, 1))
assert.Check(t, is.Len(results.Items, 1))
}

View File

@@ -9,19 +9,29 @@ import (
"github.com/distribution/reference"
)
type ImageTagOptions struct {
Source string
Target string
}
type ImageTagResult struct{}
// ImageTag tags an image in the docker host
func (cli *Client) ImageTag(ctx context.Context, source, target string) error {
func (cli *Client) ImageTag(ctx context.Context, options ImageTagOptions) (ImageTagResult, error) {
source := options.Source
target := options.Target
if _, err := reference.ParseAnyReference(source); err != nil {
return fmt.Errorf("error parsing reference: %q is not a valid repository/tag: %w", source, err)
return ImageTagResult{}, fmt.Errorf("error parsing reference: %q is not a valid repository/tag: %w", source, err)
}
ref, err := reference.ParseNormalizedNamed(target)
if err != nil {
return fmt.Errorf("error parsing reference: %q is not a valid repository/tag: %w", target, err)
return ImageTagResult{}, fmt.Errorf("error parsing reference: %q is not a valid repository/tag: %w", target, err)
}
if _, ok := ref.(reference.Digested); ok {
return errors.New("refusing to create a tag with a digest reference")
return ImageTagResult{}, errors.New("refusing to create a tag with a digest reference")
}
ref = reference.TagNameOnly(ref)
@@ -34,5 +44,5 @@ func (cli *Client) ImageTag(ctx context.Context, source, target string) error {
resp, err := cli.post(ctx, "/images/"+source+"/tag", query, nil, nil)
defer ensureReaderClosed(resp)
return err
return ImageTagResult{}, err
}

View File

@@ -18,7 +18,7 @@ func TestImageTagError(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
err = client.ImageTag(context.Background(), "image_id", "repo:tag")
_, err = client.ImageTag(context.Background(), ImageTagOptions{Source: "image_id", Target: "repo:tag"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -28,7 +28,7 @@ func TestImageTagInvalidReference(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
err = client.ImageTag(context.Background(), "image_id", "aa/asdf$$^/aa")
_, err = client.ImageTag(context.Background(), ImageTagOptions{Source: "image_id", Target: "aa/asdf$$^/aa"})
assert.Check(t, is.Error(err, `error parsing reference: "aa/asdf$$^/aa" is not a valid repository/tag: invalid reference format`))
}
@@ -43,7 +43,7 @@ func TestImageTagInvalidSourceImageName(t *testing.T) {
for _, repo := range invalidRepos {
t.Run("invalidRepo/"+repo, func(t *testing.T) {
t.Parallel()
err := client.ImageTag(ctx, "busybox", repo)
_, err := client.ImageTag(ctx, ImageTagOptions{Source: "busybox", Target: repo})
assert.Check(t, is.ErrorContains(err, "not a valid repository/tag"))
})
}
@@ -53,26 +53,26 @@ func TestImageTagInvalidSourceImageName(t *testing.T) {
for _, repotag := range invalidTags {
t.Run("invalidTag/"+repotag, func(t *testing.T) {
t.Parallel()
err := client.ImageTag(ctx, "busybox", repotag)
_, err := client.ImageTag(ctx, ImageTagOptions{Source: "busybox", Target: repotag})
assert.Check(t, is.ErrorContains(err, "not a valid repository/tag"))
})
}
t.Run("test repository name begin with '-'", func(t *testing.T) {
t.Parallel()
err := client.ImageTag(ctx, "busybox:latest", "-busybox:test")
_, err := client.ImageTag(ctx, ImageTagOptions{Source: "busybox:latest", Target: "-busybox:test"})
assert.Check(t, is.ErrorContains(err, "error parsing reference"))
})
t.Run("test namespace name begin with '-'", func(t *testing.T) {
t.Parallel()
err := client.ImageTag(ctx, "busybox:latest", "-test/busybox:test")
_, err := client.ImageTag(ctx, ImageTagOptions{Source: "busybox:latest", Target: "-test/busybox:test"})
assert.Check(t, is.ErrorContains(err, "error parsing reference"))
})
t.Run("test index name begin with '-'", func(t *testing.T) {
t.Parallel()
err := client.ImageTag(ctx, "busybox:latest", "-index:5000/busybox:test")
_, err := client.ImageTag(ctx, ImageTagOptions{Source: "busybox:latest", Target: "-index:5000/busybox:test"})
assert.Check(t, is.ErrorContains(err, "error parsing reference"))
})
}
@@ -91,7 +91,7 @@ func TestImageTagHexSource(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusOK, "OK")))
assert.NilError(t, err)
err = client.ImageTag(context.Background(), "0d409d33b27e47423b049f7f863faa08655a8c901749c2b25b93ca67d01a470d", "repo:tag")
_, err = client.ImageTag(context.Background(), ImageTagOptions{Source: "0d409d33b27e47423b049f7f863faa08655a8c901749c2b25b93ca67d01a470d", Target: "repo:tag"})
assert.NilError(t, err)
}
@@ -169,7 +169,7 @@ func TestImageTag(t *testing.T) {
}, nil
}))
assert.NilError(t, err)
err = client.ImageTag(context.Background(), "image_id", tagCase.reference)
_, err = client.ImageTag(context.Background(), ImageTagOptions{Source: "image_id", Target: tagCase.reference})
assert.NilError(t, err)
}
}

View File

@@ -344,7 +344,7 @@ func (s *DockerRegistrySuite) TestBuildCopyFromForcePull(c *testing.T) {
repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
// tag the image to upload it to the private registry
ctx := testutil.GetContext(c)
err := apiClient.ImageTag(ctx, "busybox", repoName)
_, err := apiClient.ImageTag(ctx, client.ImageTagOptions{Source: "busybox", Target: repoName})
assert.Check(c, err)
// push the image to the registry
rc, err := apiClient.ImagePush(ctx, repoName, client.ImagePushOptions{RegistryAuth: "{}"})

View File

@@ -102,10 +102,10 @@ func (s *DockerAPISuite) TestAPIImagesSizeCompatibility(c *testing.T) {
apiclient := testEnv.APIClient()
defer apiclient.Close()
images, err := apiclient.ImageList(testutil.GetContext(c), client.ImageListOptions{})
imageList, err := apiclient.ImageList(testutil.GetContext(c), client.ImageListOptions{})
assert.NilError(c, err)
assert.Assert(c, len(images) != 0)
for _, img := range images {
assert.Assert(c, len(imageList.Items) != 0)
for _, img := range imageList.Items {
assert.Assert(c, img.Size != int64(-1))
}
@@ -115,8 +115,8 @@ func (s *DockerAPISuite) TestAPIImagesSizeCompatibility(c *testing.T) {
v124Images, err := apiclient.ImageList(testutil.GetContext(c), client.ImageListOptions{})
assert.NilError(c, err)
assert.Assert(c, len(v124Images) != 0)
for _, img := range v124Images {
assert.Assert(c, len(v124Images.Items) != 0)
for _, img := range v124Images.Items {
assert.Assert(c, img.Size != int64(-1))
}
}

View File

@@ -800,9 +800,9 @@ func TestBuildHistoryDoesNotPreventRemoval(t *testing.T) {
err := buildImage("history-a")
assert.NilError(t, err)
resp, err := apiClient.ImageRemove(ctx, "history-a", client.ImageRemoveOptions{})
res, err := apiClient.ImageRemove(ctx, "history-a", client.ImageRemoveOptions{})
assert.NilError(t, err)
assert.Check(t, slices.ContainsFunc(resp, func(r image.DeleteResponse) bool {
assert.Check(t, slices.ContainsFunc(res.Deleted, func(r image.DeleteResponse) bool {
return r.Deleted != ""
}))
}

View File

@@ -27,10 +27,10 @@ func TestExportContainerAndImportImage(t *testing.T) {
poll.WaitOn(t, container.IsStopped(ctx, apiClient, cID))
reference := "repo/" + strings.ToLower(t.Name()) + ":v1"
exportResp, err := apiClient.ContainerExport(ctx, cID)
exportRes, err := apiClient.ContainerExport(ctx, cID)
assert.NilError(t, err)
importResp, err := apiClient.ImageImport(ctx, client.ImageImportSource{
Source: exportResp,
importRes, err := apiClient.ImageImport(ctx, client.ImageImportSource{
Source: exportRes,
SourceName: "-",
}, reference, client.ImageImportOptions{})
assert.NilError(t, err)
@@ -38,7 +38,7 @@ func TestExportContainerAndImportImage(t *testing.T) {
// If the import is successfully, then the message output should contain
// the image ID and match with the output from `docker images`.
dec := json.NewDecoder(importResp)
dec := json.NewDecoder(importRes)
var jm jsonmessage.JSONMessage
err = dec.Decode(&jm)
assert.NilError(t, err)
@@ -47,7 +47,7 @@ func TestExportContainerAndImportImage(t *testing.T) {
Filters: make(client.Filters).Add("reference", reference),
})
assert.NilError(t, err)
assert.Check(t, is.Equal(jm.Status, images[0].ID))
assert.Check(t, is.Equal(jm.Status, images.Items[0].ID))
}
// TestExportContainerAfterDaemonRestart checks that a container

View File

@@ -136,7 +136,7 @@ func TestMigrateSaveLoad(t *testing.T) {
// Delete all images
list, err := apiClient.ImageList(ctx, client.ImageListOptions{})
assert.NilError(t, err)
for _, i := range list {
for _, i := range list.Items {
_, err = apiClient.ImageRemove(ctx, i.ID, client.ImageRemoveOptions{Force: true})
assert.NilError(t, err)
}

View File

@@ -51,7 +51,7 @@ func TestImageInspectUniqueRepoDigests(t *testing.T) {
for _, tag := range []string{"master", "newest"} {
imgName := "busybox:" + tag
err := apiClient.ImageTag(ctx, "busybox", imgName)
_, err := apiClient.ImageTag(ctx, client.ImageTagOptions{Source: "busybox", Target: imgName})
assert.NilError(t, err)
defer func() {
_, _ = apiClient.ImageRemove(ctx, imgName, client.ImageRemoveOptions{Force: true})

View File

@@ -36,19 +36,19 @@ func TestImagesFilterMultiReference(t *testing.T) {
}
for _, repoTag := range repoTags {
err := apiClient.ImageTag(ctx, "busybox:latest", repoTag)
_, err := apiClient.ImageTag(ctx, client.ImageTagOptions{Source: "busybox:latest", Target: repoTag})
assert.NilError(t, err)
}
options := client.ImageListOptions{
Filters: make(client.Filters).Add("reference", repoTags[:3]...),
}
images, err := apiClient.ImageList(ctx, options)
imageList, err := apiClient.ImageList(ctx, options)
assert.NilError(t, err)
assert.Assert(t, is.Len(images, 1))
assert.Check(t, is.Len(images[0].RepoTags, 3))
for _, repoTag := range images[0].RepoTags {
assert.Assert(t, is.Len(imageList.Items, 1))
assert.Check(t, is.Len(imageList.Items[0].RepoTags, 3))
for _, repoTag := range imageList.Items[0].RepoTags {
if repoTag != repoTags[0] && repoTag != repoTags[1] && repoTag != repoTags[2] {
t.Errorf("list images doesn't match any repoTag we expected, repoTag: %s", repoTag)
}
@@ -91,7 +91,7 @@ func TestImagesFilterUntil(t *testing.T) {
assert.NilError(t, err)
var listedIDs []string
for _, i := range list {
for _, i := range list.Items {
t.Logf("ImageList: ID=%v RepoTags=%v", i.ID, i.RepoTags)
listedIDs = append(listedIDs, i.ID)
}
@@ -124,7 +124,7 @@ func TestImagesFilterBeforeSince(t *testing.T) {
assert.NilError(t, err)
var listedIDs []string
for _, i := range list {
for _, i := range list.Items {
t.Logf("ImageList: ID=%v RepoTags=%v", i.ID, i.RepoTags)
listedIDs = append(listedIDs, i.ID)
}
@@ -140,7 +140,7 @@ func TestAPIImagesFilters(t *testing.T) {
apiClient := testEnv.APIClient()
for _, n := range []string{"utest:tag1", "utest/docker:tag2", "utest:5000/docker:tag3"} {
err := apiClient.ImageTag(ctx, "busybox:latest", n)
_, err := apiClient.ImageTag(ctx, client.ImageTagOptions{Source: "busybox:latest", Target: n})
assert.NilError(t, err)
}
@@ -181,12 +181,12 @@ func TestAPIImagesFilters(t *testing.T) {
t.Parallel()
ctx := testutil.StartSpan(ctx, t)
images, err := apiClient.ImageList(ctx, client.ImageListOptions{
imageList, err := apiClient.ImageList(ctx, client.ImageListOptions{
Filters: tc.filters,
})
assert.Check(t, err)
assert.Assert(t, is.Len(images, tc.expectedImages))
assert.Check(t, is.Len(images[0].RepoTags, tc.expectedRepoTags))
assert.Assert(t, is.Len(imageList.Items, tc.expectedImages))
assert.Check(t, is.Len(imageList.Items[0].RepoTags, tc.expectedRepoTags))
})
}
}
@@ -255,31 +255,31 @@ func TestAPIImagesListManifests(t *testing.T) {
// TODO: Remove when MinAPIVersion >= 1.47
c := d.NewClientT(t, client.WithVersion("1.46"))
images, err := c.ImageList(ctx, client.ImageListOptions{Manifests: true})
imageList, err := c.ImageList(ctx, client.ImageListOptions{Manifests: true})
assert.NilError(t, err)
assert.Assert(t, is.Len(images, 1))
assert.Check(t, is.Nil(images[0].Manifests))
assert.Assert(t, is.Len(imageList.Items, 1))
assert.Check(t, is.Nil(imageList.Items[0].Manifests))
})
api147 := d.NewClientT(t, client.WithVersion("1.47"))
t.Run("no manifests if not requested", func(t *testing.T) {
images, err := api147.ImageList(ctx, client.ImageListOptions{})
imageList, err := api147.ImageList(ctx, client.ImageListOptions{})
assert.NilError(t, err)
assert.Assert(t, is.Len(images, 1))
assert.Check(t, is.Nil(images[0].Manifests))
assert.Assert(t, is.Len(imageList.Items, 1))
assert.Check(t, is.Nil(imageList.Items[0].Manifests))
})
images, err := api147.ImageList(ctx, client.ImageListOptions{Manifests: true})
imageList, err := api147.ImageList(ctx, client.ImageListOptions{Manifests: true})
assert.NilError(t, err)
assert.Check(t, is.Len(images, 1))
assert.Check(t, images[0].Manifests != nil)
assert.Check(t, is.Len(images[0].Manifests, 3))
assert.Check(t, is.Len(imageList.Items, 1))
assert.Check(t, imageList.Items[0].Manifests != nil)
assert.Check(t, is.Len(imageList.Items[0].Manifests, 3))
for _, mfst := range images[0].Manifests {
for _, mfst := range imageList.Items[0].Manifests {
// All manifests should be image manifests
assert.Check(t, is.Equal(mfst.Kind, image.ManifestKindImage))

View File

@@ -29,7 +29,7 @@ func TestLoadDanglingImages(t *testing.T) {
})
// Should be one image.
images, err := apiClient.ImageList(ctx, client.ImageListOptions{})
imageList, err := apiClient.ImageList(ctx, client.ImageListOptions{})
assert.NilError(t, err)
findImageByName := func(images []image.Summary, imageName string) (image.Summary, error) {
@@ -42,7 +42,7 @@ func TestLoadDanglingImages(t *testing.T) {
return images[index], nil
}
oldImage, err := findImageByName(images, "namedimage:latest")
oldImage, err := findImageByName(imageList.Items, "namedimage:latest")
assert.NilError(t, err)
// Retain a copy of the old image and then replace it with a new one.
@@ -52,10 +52,10 @@ func TestLoadDanglingImages(t *testing.T) {
})
})
images, err = apiClient.ImageList(ctx, client.ImageListOptions{})
imageList, err = apiClient.ImageList(ctx, client.ImageListOptions{})
assert.NilError(t, err)
newImage, err := findImageByName(images, "namedimage:latest")
newImage, err := findImageByName(imageList.Items, "namedimage:latest")
assert.NilError(t, err)
// IDs should be different.
@@ -72,7 +72,7 @@ func TestLoadDanglingImages(t *testing.T) {
return images[index], nil
}
danglingImage, err := findImageById(images, oldImage.ID)
danglingImage, err := findImageById(imageList.Items, oldImage.ID)
assert.NilError(t, err)
assert.Check(t, is.Len(danglingImage.RepoTags, 0))
}

View File

@@ -74,10 +74,10 @@ func TestPruneLexographicalOrder(t *testing.T) {
tags := []string{"h", "a", "j", "o", "s", "q", "w", "e", "r", "t"}
for _, tag := range tags {
err = apiClient.ImageTag(ctx, id, "busybox:"+tag)
_, err = apiClient.ImageTag(ctx, client.ImageTagOptions{Source: id, Target: "busybox:" + tag})
assert.NilError(t, err)
}
err = apiClient.ImageTag(ctx, id, "busybox:z")
_, err = apiClient.ImageTag(ctx, client.ImageTagOptions{Source: id, Target: "busybox:z"})
assert.NilError(t, err)
_, err = apiClient.ImageRemove(ctx, "busybox:latest", client.ImageRemoveOptions{Force: true})
@@ -127,7 +127,8 @@ func TestPruneDontDeleteUsedImage(t *testing.T) {
// busybox:other tag pointing to the same image.
name: "two tags",
prepare: func(t *testing.T, d *daemon.Daemon, apiClient *client.Client) error {
return apiClient.ImageTag(ctx, "busybox:latest", "busybox:a")
_, err := apiClient.ImageTag(ctx, client.ImageTagOptions{Source: "busybox:latest", Target: "busybox:a"})
return err
},
check: func(t *testing.T, apiClient *client.Client, pruned image.PruneReport) {
if assert.Check(t, is.Len(pruned.ImagesDeleted, 1)) {

View File

@@ -216,7 +216,8 @@ func TestImagePullKeepOldAsDangling(t *testing.T) {
t.Log(inspect1)
assert.NilError(t, apiClient.ImageTag(ctx, "busybox:latest", "alpine:latest"))
_, err = apiClient.ImageTag(ctx, client.ImageTagOptions{Source: "busybox:latest", Target: "alpine:latest"})
assert.NilError(t, err)
_, err = apiClient.ImageRemove(ctx, "busybox:latest", client.ImageRemoveOptions{})
assert.NilError(t, err)

View File

@@ -71,7 +71,7 @@ func TestRemoveByDigest(t *testing.T) {
ctx := setupTest(t)
apiClient := testEnv.APIClient()
err := apiClient.ImageTag(ctx, "busybox", "test-remove-by-digest:latest")
_, err := apiClient.ImageTag(ctx, client.ImageTagOptions{Source: "busybox", Target: "test-remove-by-digest:latest"})
assert.NilError(t, err)
inspect, err := apiClient.ImageInspect(ctx, "test-remove-by-digest")
@@ -142,25 +142,25 @@ func TestRemoveWithPlatform(t *testing.T) {
{platform: &platformHost, deleted: descs[0]},
{platform: &someOtherPlatform, deleted: descs[3]},
} {
resp, err := apiClient.ImageRemove(ctx, imgName, client.ImageRemoveOptions{
res, err := apiClient.ImageRemove(ctx, imgName, client.ImageRemoveOptions{
Platforms: []ocispec.Platform{*tc.platform},
Force: true,
})
assert.NilError(t, err)
assert.Check(t, is.Len(resp, 1))
for _, r := range resp {
assert.Check(t, is.Len(res.Deleted, 1))
for _, r := range res.Deleted {
assert.Check(t, is.Equal(r.Untagged, ""), "No image should be untagged")
}
checkPlatformDeleted(t, imageIdx, resp, tc.deleted)
checkPlatformDeleted(t, imageIdx, res.Deleted, tc.deleted)
}
// Delete the rest
resp, err := apiClient.ImageRemove(ctx, imgName, client.ImageRemoveOptions{})
assert.NilError(t, err)
assert.Check(t, is.Len(resp, 2))
assert.Check(t, is.Equal(resp[0].Untagged, imgName))
assert.Check(t, is.Equal(resp[1].Deleted, imageIdx.Manifests[0].Digest.String()))
assert.Check(t, is.Len(resp.Deleted, 2))
assert.Check(t, is.Equal(resp.Deleted[0].Untagged, imgName))
assert.Check(t, is.Equal(resp.Deleted[1].Deleted, imageIdx.Manifests[0].Digest.String()))
// TODO(vvoland): Should it also include platform-specific manifests? https://github.com/moby/moby/pull/49982
}

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"testing"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
@@ -12,23 +13,23 @@ import (
func TestTagUnprefixedRepoByNameOrName(t *testing.T) {
ctx := setupTest(t)
client := testEnv.APIClient()
apiClient := testEnv.APIClient()
// By name
err := client.ImageTag(ctx, "busybox:latest", "testfoobarbaz")
_, err := apiClient.ImageTag(ctx, client.ImageTagOptions{Source: "busybox:latest", Target: "testfoobarbaz"})
assert.NilError(t, err)
// By ID
insp, err := client.ImageInspect(ctx, "busybox")
insp, err := apiClient.ImageInspect(ctx, "busybox")
assert.NilError(t, err)
err = client.ImageTag(ctx, insp.ID, "testfoobarbaz")
_, err = apiClient.ImageTag(ctx, client.ImageTagOptions{Source: insp.ID, Target: "testfoobarbaz"})
assert.NilError(t, err)
}
func TestTagUsingDigestAlgorithmAsName(t *testing.T) {
ctx := setupTest(t)
client := testEnv.APIClient()
err := client.ImageTag(ctx, "busybox:latest", "sha256:sometag")
apiClient := testEnv.APIClient()
_, err := apiClient.ImageTag(ctx, client.ImageTagOptions{Source: "busybox:latest", Target: "sha256:sometag"})
assert.Check(t, is.ErrorContains(err, "refusing to create an ambiguous tag using digest algorithm as name"))
}
@@ -36,14 +37,14 @@ func TestTagUsingDigestAlgorithmAsName(t *testing.T) {
func TestTagValidPrefixedRepo(t *testing.T) {
ctx := setupTest(t)
client := testEnv.APIClient()
apiClient := testEnv.APIClient()
validRepos := []string{"fooo/bar", "fooaa/test", "foooo:t", "HOSTNAME.DOMAIN.COM:443/foo/bar"}
for _, repo := range validRepos {
t.Run(repo, func(t *testing.T) {
t.Parallel()
err := client.ImageTag(ctx, "busybox", repo)
_, err := apiClient.ImageTag(ctx, client.ImageTagOptions{Source: "busybox", Target: repo})
assert.NilError(t, err)
})
}
@@ -52,9 +53,9 @@ func TestTagValidPrefixedRepo(t *testing.T) {
// tag an image with an existed tag name without -f option should work
func TestTagExistedNameWithoutForce(t *testing.T) {
ctx := setupTest(t)
client := testEnv.APIClient()
apiClient := testEnv.APIClient()
err := client.ImageTag(ctx, "busybox:latest", "busybox:test")
_, err := apiClient.ImageTag(ctx, client.ImageTagOptions{Source: "busybox:latest", Target: "busybox:test"})
assert.NilError(t, err)
}
@@ -62,7 +63,7 @@ func TestTagExistedNameWithoutForce(t *testing.T) {
// ensure all tags result in the same name
func TestTagOfficialNames(t *testing.T) {
ctx := setupTest(t)
client := testEnv.APIClient()
apiClient := testEnv.APIClient()
names := []string{
"docker.io/busybox",
@@ -74,16 +75,16 @@ func TestTagOfficialNames(t *testing.T) {
for _, name := range names {
t.Run("tag from busybox to "+name, func(t *testing.T) {
err := client.ImageTag(ctx, "busybox", name+":latest")
_, err := apiClient.ImageTag(ctx, client.ImageTagOptions{Source: "busybox", Target: name + ":latest"})
assert.NilError(t, err)
// ensure we don't have multiple tag names.
insp, err := client.ImageInspect(ctx, "busybox")
insp, err := apiClient.ImageInspect(ctx, "busybox")
assert.NilError(t, err)
// TODO(vvoland): Not sure what's actually being tested here. Is is still doing anything useful?
assert.Assert(t, !is.Contains(insp.RepoTags, name)().Success())
err = client.ImageTag(ctx, name+":latest", "test-tag-official-names/foobar:latest")
_, err = apiClient.ImageTag(ctx, client.ImageTagOptions{Source: name + ":latest", Target: "test-tag-official-names/foobar:latest"})
assert.NilError(t, err)
})
}
@@ -92,14 +93,14 @@ func TestTagOfficialNames(t *testing.T) {
// ensure tags can not match digests
func TestTagMatchesDigest(t *testing.T) {
ctx := setupTest(t)
client := testEnv.APIClient()
apiClient := testEnv.APIClient()
digest := "busybox@sha256:abcdef76720241213f5303bda7704ec4c2ef75613173910a56fb1b6e20251507"
// test setting tag fails
err := client.ImageTag(ctx, "busybox:latest", digest)
_, err := apiClient.ImageTag(ctx, client.ImageTagOptions{Source: "busybox:latest", Target: digest})
assert.Check(t, is.ErrorContains(err, "refusing to create a tag with a digest reference"))
// check that no new image matches the digest
_, err = client.ImageInspect(ctx, digest)
_, err = apiClient.ImageInspect(ctx, digest)
assert.Check(t, is.ErrorContains(err, fmt.Sprintf("No such image: %s", digest)))
}

View File

@@ -94,10 +94,10 @@ func getAllContainers(ctx context.Context, t testing.TB, apiClient client.Contai
func deleteAllImages(ctx context.Context, t testing.TB, apiclient client.ImageAPIClient, protectedImages map[string]struct{}) {
t.Helper()
images, err := apiclient.ImageList(ctx, client.ImageListOptions{})
imageList, err := apiclient.ImageList(ctx, client.ImageListOptions{})
assert.Check(t, err, "failed to list images")
for _, img := range images {
for _, img := range imageList.Items {
tags := tagsFromImageSummary(img)
if _, ok := protectedImages[img.ID]; ok {
continue

View File

@@ -205,7 +205,7 @@ func (e *Execution) HasExistingImage(t testing.TB, reference string) bool {
})
assert.NilError(t, err, "failed to list images")
return len(imageList) > 0
return len(imageList.Items) > 0
}
// EnsureFrozenImagesLinux loads frozen test images into the daemon

View File

@@ -121,7 +121,7 @@ func getExistingImages(ctx context.Context, t testing.TB, testEnv *Execution) []
assert.NilError(t, err, "failed to list images")
var images []string
for _, img := range imageList {
for _, img := range imageList.Items {
images = append(images, tagsFromImageSummary(img)...)
}
return images

View File

@@ -70,7 +70,7 @@ func FrozenImagesLinux(ctx context.Context, apiClient client.APIClient, images .
for _, img := range loadImages {
if img.srcName != img.destName {
if err := apiClient.ImageTag(ctx, img.srcName, img.destName); err != nil {
if _, err := apiClient.ImageTag(ctx, client.ImageTagOptions{Source: img.srcName, Target: img.destName}); err != nil {
return errors.Wrapf(err, "failed to tag %s as %s", img.srcName, img.destName)
}
if _, err := apiClient.ImageRemove(ctx, img.srcName, client.ImageRemoveOptions{}); err != nil {
@@ -171,7 +171,7 @@ func pullTagAndRemove(ctx context.Context, apiClient client.APIClient, ref strin
return err
}
if err := apiClient.ImageTag(ctx, ref, tag); err != nil {
if _, err := apiClient.ImageTag(ctx, client.ImageTagOptions{Source: ref, Target: tag}); err != nil {
return errors.Wrapf(err, "failed to tag %s as %s", ref, tag)
}
_, err = apiClient.ImageRemove(ctx, ref, client.ImageRemoveOptions{})

View File

@@ -7,13 +7,15 @@ import (
type BuildCancelOptions struct{}
type BuildCancelResult struct{}
// BuildCancel requests the daemon to cancel the ongoing build request
// with the given id.
func (cli *Client) BuildCancel(ctx context.Context, id string, _ BuildCancelOptions) error {
func (cli *Client) BuildCancel(ctx context.Context, id string, _ BuildCancelOptions) (BuildCancelResult, error) {
query := url.Values{}
query.Set("id", id)
resp, err := cli.post(ctx, "/build/cancel", query, nil, nil)
defer ensureReaderClosed(resp)
return err
return BuildCancelResult{}, err
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/moby/moby/api/types"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/events"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/api/types/plugin"
"github.com/moby/moby/api/types/registry"
@@ -105,18 +104,18 @@ type DistributionAPIClient interface {
// ImageAPIClient defines API client methods for the images
type ImageAPIClient interface {
ImageBuild(ctx context.Context, context io.Reader, options ImageBuildOptions) (ImageBuildResponse, error)
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) error
ImageCreate(ctx context.Context, parentReference string, options ImageCreateOptions) (io.ReadCloser, error)
ImageImport(ctx context.Context, source ImageImportSource, ref string, options ImageImportOptions) (io.ReadCloser, 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) ([]image.Summary, error)
ImageList(ctx context.Context, options ImageListOptions) (ImageListResult, error)
ImagePull(ctx context.Context, ref string, options ImagePullOptions) (ImagePullResponse, error)
ImagePush(ctx context.Context, ref string, options ImagePushOptions) (ImagePushResponse, error)
ImageRemove(ctx context.Context, image string, options ImageRemoveOptions) ([]image.DeleteResponse, error)
ImageSearch(ctx context.Context, term string, options ImageSearchOptions) ([]registry.SearchResult, error)
ImageTag(ctx context.Context, image, ref string) error
ImageRemove(ctx context.Context, image string, options ImageRemoveOptions) (ImageRemoveResult, error)
ImageSearch(ctx context.Context, term string, options ImageSearchOptions) (ImageSearchResult, error)
ImageTag(ctx context.Context, options ImageTagOptions) (ImageTagResult, error)
ImagesPrune(ctx context.Context, opts ImagePruneOptions) (ImagePruneResult, error)
ImageInspect(ctx context.Context, image string, _ ...ImageInspectOption) (ImageInspectResult, error)

View File

@@ -17,15 +17,15 @@ import (
// ImageBuild sends a request to the daemon to build images.
// The Body in the response implements an [io.ReadCloser] and it's up to the caller to
// close it.
func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, options ImageBuildOptions) (ImageBuildResponse, error) {
func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, options ImageBuildOptions) (ImageBuildResult, error) {
query, err := cli.imageBuildOptionsToQuery(ctx, options)
if err != nil {
return ImageBuildResponse{}, err
return ImageBuildResult{}, err
}
buf, err := json.Marshal(options.AuthConfigs)
if err != nil {
return ImageBuildResponse{}, err
return ImageBuildResult{}, err
}
headers := http.Header{}
@@ -34,10 +34,10 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio
resp, err := cli.postRaw(ctx, "/build", query, buildContext, headers)
if err != nil {
return ImageBuildResponse{}, err
return ImageBuildResult{}, err
}
return ImageBuildResponse{
return ImageBuildResult{
Body: resp.Body,
}, nil
}

View File

@@ -68,9 +68,9 @@ type ImageBuildOutput struct {
Attrs map[string]string
}
// ImageBuildResponse holds information
// ImageBuildResult holds information
// returned by a server after building
// an image.
type ImageBuildResponse struct {
type ImageBuildResult struct {
Body io.ReadCloser
}

View File

@@ -2,7 +2,6 @@ package client
import (
"context"
"io"
"net/http"
"net/url"
"strings"
@@ -13,10 +12,10 @@ import (
// 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) (io.ReadCloser, error) {
func (cli *Client) ImageCreate(ctx context.Context, parentReference string, options ImageCreateOptions) (ImageCreateResult, error) {
ref, err := reference.ParseNormalizedNamed(parentReference)
if err != nil {
return nil, err
return ImageCreateResult{}, err
}
query := url.Values{}
@@ -27,9 +26,9 @@ func (cli *Client) ImageCreate(ctx context.Context, parentReference string, opti
}
resp, err := cli.tryImageCreate(ctx, query, staticAuth(options.RegistryAuth))
if err != nil {
return nil, err
return ImageCreateResult{}, err
}
return resp.Body, nil
return ImageCreateResult{Body: resp.Body}, nil
}
func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, resolveAuth registry.RequestAuthConfig) (*http.Response, error) {

View File

@@ -1,7 +1,14 @@
package client
import "io"
// ImageCreateOptions holds information to create images.
type ImageCreateOptions struct {
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry.
Platform string // Platform is the target platform of the image if it needs to be pulled from the registry.
}
// ImageCreateResult holds the response body returned by the daemon for image create.
type ImageCreateResult struct {
Body io.ReadCloser
}

View File

@@ -2,7 +2,6 @@ package client
import (
"context"
"io"
"net/url"
"strings"
@@ -11,11 +10,11 @@ import (
// ImageImport creates a new image based on the source options.
// It returns the JSON content in the response body.
func (cli *Client) ImageImport(ctx context.Context, source ImageImportSource, ref string, options ImageImportOptions) (io.ReadCloser, error) {
func (cli *Client) ImageImport(ctx context.Context, source ImageImportSource, ref string, options ImageImportOptions) (ImageImportResult, error) {
if ref != "" {
// Check if the given image name can be resolved
if _, err := reference.ParseNormalizedNamed(ref); err != nil {
return nil, err
return ImageImportResult{}, err
}
}
@@ -41,7 +40,7 @@ func (cli *Client) ImageImport(ctx context.Context, source ImageImportSource, re
resp, err := cli.postRaw(ctx, "/images/create", query, source.Source, nil)
if err != nil {
return nil, err
return ImageImportResult{}, err
}
return resp.Body, nil
return ImageImportResult{body: resp.Body}, nil
}

View File

@@ -17,3 +17,19 @@ type ImageImportOptions struct {
Changes []string // Changes are the raw changes to apply to this image
Platform string // Platform is the target platform of the image
}
// ImageImportResult holds the response body returned by the daemon for image import.
type ImageImportResult struct {
body io.ReadCloser
}
func (r ImageImportResult) Read(p []byte) (n int, err error) {
return r.body.Read(p)
}
func (r ImageImportResult) Close() error {
if r.body == nil {
return nil
}
return r.body.Close()
}

View File

@@ -15,7 +15,7 @@ import (
// to include [image.Summary.Manifests] with information about image manifests.
// This is experimental and might change in the future without any backward
// compatibility.
func (cli *Client) ImageList(ctx context.Context, options ImageListOptions) ([]image.Summary, error) {
func (cli *Client) ImageList(ctx context.Context, options ImageListOptions) (ImageListResult, error) {
var images []image.Summary
query := url.Values{}
@@ -34,7 +34,7 @@ func (cli *Client) ImageList(ctx context.Context, options ImageListOptions) ([]i
// Normally, version-negotiation (if enabled) would not happen until
// the API request is made.
if err := cli.checkVersion(ctx); err != nil {
return images, err
return ImageListResult{}, err
}
if versions.GreaterThanOrEqualTo(cli.version, "1.47") {
@@ -45,9 +45,9 @@ func (cli *Client) ImageList(ctx context.Context, options ImageListOptions) ([]i
resp, err := cli.get(ctx, "/images/json", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return images, err
return ImageListResult{}, err
}
err = json.NewDecoder(resp.Body).Decode(&images)
return images, err
return ImageListResult{Items: images}, err
}

View File

@@ -1,5 +1,7 @@
package client
import "github.com/moby/moby/api/types/image"
// ImageListOptions holds parameters to list images with.
type ImageListOptions struct {
// All controls whether all images in the graph are filtered, or just
@@ -15,3 +17,8 @@ type ImageListOptions struct {
// Manifests indicates whether the image manifests should be returned.
Manifests bool
}
// ImageListResult holds the result from ImageList.
type ImageListResult struct {
Items []image.Summary
}

View File

@@ -9,7 +9,7 @@ import (
)
// ImageRemove removes an image from the docker host.
func (cli *Client) ImageRemove(ctx context.Context, imageID string, options ImageRemoveOptions) ([]image.DeleteResponse, error) {
func (cli *Client) ImageRemove(ctx context.Context, imageID string, options ImageRemoveOptions) (ImageRemoveResult, error) {
query := url.Values{}
if options.Force {
@@ -22,7 +22,7 @@ func (cli *Client) ImageRemove(ctx context.Context, imageID string, options Imag
if len(options.Platforms) > 0 {
p, err := encodePlatforms(options.Platforms...)
if err != nil {
return nil, err
return ImageRemoveResult{}, err
}
query["platforms"] = p
}
@@ -30,10 +30,10 @@ func (cli *Client) ImageRemove(ctx context.Context, imageID string, options Imag
resp, err := cli.delete(ctx, "/images/"+imageID, query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return nil, err
return ImageRemoveResult{}, err
}
var dels []image.DeleteResponse
err = json.NewDecoder(resp.Body).Decode(&dels)
return dels, err
return ImageRemoveResult{Deleted: dels}, err
}

View File

@@ -1,6 +1,9 @@
package client
import ocispec "github.com/opencontainers/image-spec/specs-go/v1"
import (
"github.com/moby/moby/api/types/image"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ImageRemoveOptions holds parameters to remove images.
type ImageRemoveOptions struct {
@@ -8,3 +11,8 @@ type ImageRemoveOptions struct {
Force bool
PruneChildren bool
}
// ImageRemoveResult holds the delete responses returned by the daemon.
type ImageRemoveResult struct {
Deleted []image.DeleteResponse
}

View File

@@ -13,7 +13,7 @@ import (
// ImageSearch makes the docker host search by a term in a remote registry.
// The list of results is not sorted in any fashion.
func (cli *Client) ImageSearch(ctx context.Context, term string, options ImageSearchOptions) ([]registry.SearchResult, error) {
func (cli *Client) ImageSearch(ctx context.Context, term string, options ImageSearchOptions) (ImageSearchResult, error) {
var results []registry.SearchResult
query := url.Values{}
query.Set("term", term)
@@ -28,16 +28,16 @@ func (cli *Client) ImageSearch(ctx context.Context, term string, options ImageSe
if cerrdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil {
newAuthHeader, privilegeErr := options.PrivilegeFunc(ctx)
if privilegeErr != nil {
return results, privilegeErr
return ImageSearchResult{}, privilegeErr
}
resp, err = cli.tryImageSearch(ctx, query, newAuthHeader)
}
if err != nil {
return results, err
return ImageSearchResult{}, err
}
err = json.NewDecoder(resp.Body).Decode(&results)
return results, err
return ImageSearchResult{Items: results}, err
}
func (cli *Client) tryImageSearch(ctx context.Context, query url.Values, registryAuth string) (*http.Response, error) {

View File

@@ -2,8 +2,15 @@ package client
import (
"context"
"github.com/moby/moby/api/types/registry"
)
// ImageSearchResult wraps results returned by ImageSearch.
type ImageSearchResult struct {
Items []registry.SearchResult
}
// ImageSearchOptions holds parameters to search images with.
type ImageSearchOptions struct {
RegistryAuth string

View File

@@ -9,19 +9,29 @@ import (
"github.com/distribution/reference"
)
type ImageTagOptions struct {
Source string
Target string
}
type ImageTagResult struct{}
// ImageTag tags an image in the docker host
func (cli *Client) ImageTag(ctx context.Context, source, target string) error {
func (cli *Client) ImageTag(ctx context.Context, options ImageTagOptions) (ImageTagResult, error) {
source := options.Source
target := options.Target
if _, err := reference.ParseAnyReference(source); err != nil {
return fmt.Errorf("error parsing reference: %q is not a valid repository/tag: %w", source, err)
return ImageTagResult{}, fmt.Errorf("error parsing reference: %q is not a valid repository/tag: %w", source, err)
}
ref, err := reference.ParseNormalizedNamed(target)
if err != nil {
return fmt.Errorf("error parsing reference: %q is not a valid repository/tag: %w", target, err)
return ImageTagResult{}, fmt.Errorf("error parsing reference: %q is not a valid repository/tag: %w", target, err)
}
if _, ok := ref.(reference.Digested); ok {
return errors.New("refusing to create a tag with a digest reference")
return ImageTagResult{}, errors.New("refusing to create a tag with a digest reference")
}
ref = reference.TagNameOnly(ref)
@@ -34,5 +44,5 @@ func (cli *Client) ImageTag(ctx context.Context, source, target string) error {
resp, err := cli.post(ctx, "/images/"+source+"/tag", query, nil, nil)
defer ensureReaderClosed(resp)
return err
return ImageTagResult{}, err
}