mirror of https://github.com/docker/cli.git
Merge pull request #4984 from vvoland/c8d-multiplatform-push
cli/push: Add `platform` switch
This commit is contained in:
commit
52eddcf4e4
|
@ -2,18 +2,25 @@ package image
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/containerd/platforms"
|
||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/completion"
|
"github.com/docker/cli/cli/command/completion"
|
||||||
"github.com/docker/cli/cli/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
|
"github.com/docker/docker/api/types/auxprogress"
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/docker/docker/api/types/image"
|
||||||
registrytypes "github.com/docker/docker/api/types/registry"
|
registrytypes "github.com/docker/docker/api/types/registry"
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
|
"github.com/moby/term"
|
||||||
|
"github.com/morikuni/aec"
|
||||||
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -23,6 +30,7 @@ type pushOptions struct {
|
||||||
remote string
|
remote string
|
||||||
untrusted bool
|
untrusted bool
|
||||||
quiet bool
|
quiet bool
|
||||||
|
platform string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPushCommand creates a new `docker push` command
|
// NewPushCommand creates a new `docker push` command
|
||||||
|
@ -48,12 +56,33 @@ func NewPushCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
flags.BoolVarP(&opts.all, "all-tags", "a", false, "Push all tags of an image to the repository")
|
flags.BoolVarP(&opts.all, "all-tags", "a", false, "Push all tags of an image to the repository")
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress verbose output")
|
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress verbose output")
|
||||||
command.AddTrustSigningFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled())
|
command.AddTrustSigningFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled())
|
||||||
|
flags.StringVar(&opts.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"),
|
||||||
|
`Push a platform-specific manifest as a single-platform image to the registry.
|
||||||
|
'os[/arch[/variant]]': Explicit platform (eg. linux/amd64)`)
|
||||||
|
flags.SetAnnotation("platform", "version", []string{"1.46"})
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunPush performs a push against the engine based on the specified options
|
// RunPush performs a push against the engine based on the specified options
|
||||||
|
//
|
||||||
|
//nolint:gocyclo
|
||||||
func RunPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error {
|
func RunPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error {
|
||||||
|
var platform *ocispec.Platform
|
||||||
|
if opts.platform != "" {
|
||||||
|
p, err := platforms.Parse(opts.platform)
|
||||||
|
if err != nil {
|
||||||
|
_, _ = fmt.Fprintf(dockerCli.Err(), "Invalid platform %s", opts.platform)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
platform = &p
|
||||||
|
|
||||||
|
printNote(dockerCli, `Selecting a single platform will only push one matching image manifest from a multi-platform image index.
|
||||||
|
This means that any other components attached to the multi-platform image index (like Buildkit attestations) won't be pushed.
|
||||||
|
If you want to only push a single platform image while preserving the attestations, please use 'docker convert\n'
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
ref, err := reference.ParseNormalizedNamed(opts.remote)
|
ref, err := reference.ParseNormalizedNamed(opts.remote)
|
||||||
switch {
|
switch {
|
||||||
case err != nil:
|
case err != nil:
|
||||||
|
@ -84,6 +113,7 @@ func RunPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error
|
||||||
All: opts.all,
|
All: opts.all,
|
||||||
RegistryAuth: encodedAuth,
|
RegistryAuth: encodedAuth,
|
||||||
PrivilegeFunc: requestPrivilege,
|
PrivilegeFunc: requestPrivilege,
|
||||||
|
Platform: platform,
|
||||||
}
|
}
|
||||||
|
|
||||||
responseBody, err := dockerCli.Client().ImagePush(ctx, reference.FamiliarString(ref), options)
|
responseBody, err := dockerCli.Client().ImagePush(ctx, reference.FamiliarString(ref), options)
|
||||||
|
@ -91,6 +121,13 @@ func RunPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
for _, note := range notes {
|
||||||
|
fmt.Fprintln(dockerCli.Err(), "")
|
||||||
|
printNote(dockerCli, note)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
defer responseBody.Close()
|
defer responseBody.Close()
|
||||||
if !opts.untrusted {
|
if !opts.untrusted {
|
||||||
// TODO PushTrustedReference currently doesn't respect `--quiet`
|
// TODO PushTrustedReference currently doesn't respect `--quiet`
|
||||||
|
@ -98,11 +135,51 @@ func RunPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.quiet {
|
if opts.quiet {
|
||||||
err = jsonmessage.DisplayJSONMessagesToStream(responseBody, streams.NewOut(io.Discard), nil)
|
err = jsonmessage.DisplayJSONMessagesToStream(responseBody, streams.NewOut(io.Discard), handleAux(dockerCli))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Fprintln(dockerCli.Out(), ref.String())
|
fmt.Fprintln(dockerCli.Out(), ref.String())
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil)
|
return jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), handleAux(dockerCli))
|
||||||
|
}
|
||||||
|
|
||||||
|
var notes []string
|
||||||
|
|
||||||
|
func handleAux(dockerCli command.Cli) func(jm jsonmessage.JSONMessage) {
|
||||||
|
return func(jm jsonmessage.JSONMessage) {
|
||||||
|
b := []byte(*jm.Aux)
|
||||||
|
|
||||||
|
var stripped auxprogress.ManifestPushedInsteadOfIndex
|
||||||
|
err := json.Unmarshal(b, &stripped)
|
||||||
|
if err == nil && stripped.ManifestPushedInsteadOfIndex {
|
||||||
|
note := fmt.Sprintf("Not all multiplatform-content is present and only the available single-platform image was pushed\n%s -> %s",
|
||||||
|
aec.RedF.Apply(stripped.OriginalIndex.Digest.String()),
|
||||||
|
aec.GreenF.Apply(stripped.SelectedManifest.Digest.String()),
|
||||||
|
)
|
||||||
|
notes = append(notes, note)
|
||||||
|
}
|
||||||
|
|
||||||
|
var missing auxprogress.ContentMissing
|
||||||
|
err = json.Unmarshal(b, &missing)
|
||||||
|
if err == nil && missing.ContentMissing {
|
||||||
|
note := `You're trying to push a manifest list/index which
|
||||||
|
references multiple platform specific manifests, but not all of them are available locally
|
||||||
|
or available to the remote repository.
|
||||||
|
|
||||||
|
Make sure you have all the referenced content and try again.
|
||||||
|
|
||||||
|
You can also push only a single platform specific manifest directly by specifying the platform you want to push with the --platform flag.`
|
||||||
|
notes = append(notes, note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printNote(dockerCli command.Cli, format string, args ...any) {
|
||||||
|
if _, isTTY := term.GetFdInfo(dockerCli.Err()); isTTY {
|
||||||
|
_, _ = fmt.Fprint(dockerCli.Err(), aec.WhiteF.Apply(aec.CyanB.Apply("[ NOTE ]"))+" ")
|
||||||
|
} else {
|
||||||
|
_, _ = fmt.Fprint(dockerCli.Err(), "[ NOTE ] ")
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintf(dockerCli.Err(), aec.Bold.Apply(format)+"\n", args...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,12 @@ Upload an image to a registry
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|:---------------------------------------------|:-------|:--------|:--------------------------------------------|
|
|:---------------------------------------------|:---------|:--------|:--------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| [`-a`](#all-tags), [`--all-tags`](#all-tags) | | | Push all tags of an image to the repository |
|
| [`-a`](#all-tags), [`--all-tags`](#all-tags) | | | Push all tags of an image to the repository |
|
||||||
| `--disable-content-trust` | `bool` | `true` | Skip image signing |
|
| `--disable-content-trust` | `bool` | `true` | Skip image signing |
|
||||||
| `-q`, `--quiet` | | | Suppress verbose output |
|
| `--platform` | `string` | | Push a platform-specific manifest as a single-platform image to the registry.<br>'os[/arch[/variant]]': Explicit platform (eg. linux/amd64) |
|
||||||
|
| `-q`, `--quiet` | | | Suppress verbose output |
|
||||||
|
|
||||||
|
|
||||||
<!---MARKER_GEN_END-->
|
<!---MARKER_GEN_END-->
|
||||||
|
|
|
@ -9,11 +9,12 @@ Upload an image to a registry
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|:--------------------------|:-------|:--------|:--------------------------------------------|
|
|:--------------------------|:---------|:--------|:--------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `-a`, `--all-tags` | | | Push all tags of an image to the repository |
|
| `-a`, `--all-tags` | | | Push all tags of an image to the repository |
|
||||||
| `--disable-content-trust` | `bool` | `true` | Skip image signing |
|
| `--disable-content-trust` | `bool` | `true` | Skip image signing |
|
||||||
| `-q`, `--quiet` | | | Suppress verbose output |
|
| `--platform` | `string` | | Push a platform-specific manifest as a single-platform image to the registry.<br>'os[/arch[/variant]]': Explicit platform (eg. linux/amd64) |
|
||||||
|
| `-q`, `--quiet` | | | Suppress verbose output |
|
||||||
|
|
||||||
|
|
||||||
<!---MARKER_GEN_END-->
|
<!---MARKER_GEN_END-->
|
||||||
|
|
|
@ -12,7 +12,7 @@ require (
|
||||||
github.com/creack/pty v1.1.21
|
github.com/creack/pty v1.1.21
|
||||||
github.com/distribution/reference v0.6.0
|
github.com/distribution/reference v0.6.0
|
||||||
github.com/docker/distribution v2.8.3+incompatible
|
github.com/docker/distribution v2.8.3+incompatible
|
||||||
github.com/docker/docker v26.1.1-0.20240610145149-a736d0701c41+incompatible // master (v27.0.0-dev)
|
github.com/docker/docker v26.1.1-0.20240610201418-9d9488468fe2+incompatible // master (v27.0.0-dev)
|
||||||
github.com/docker/docker-credential-helpers v0.8.2
|
github.com/docker/docker-credential-helpers v0.8.2
|
||||||
github.com/docker/go-connections v0.5.0
|
github.com/docker/go-connections v0.5.0
|
||||||
github.com/docker/go-units v0.5.0
|
github.com/docker/go-units v0.5.0
|
||||||
|
|
|
@ -59,8 +59,8 @@ github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5
|
||||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/docker v26.1.1-0.20240610145149-a736d0701c41+incompatible h1:Kraon288jb3POkrmM5w6Xo979z2rrCtFzHycAjafRes=
|
github.com/docker/docker v26.1.1-0.20240610201418-9d9488468fe2+incompatible h1:k63BdhjySkwvmdeofOsBElcuVrWaDBrI7FQgnyoVnnM=
|
||||||
github.com/docker/docker v26.1.1-0.20240610145149-a736d0701c41+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v26.1.1-0.20240610201418-9d9488468fe2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
|
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
|
||||||
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
||||||
|
|
|
@ -1368,7 +1368,8 @@ definitions:
|
||||||
|
|
||||||
<p><br /></p>
|
<p><br /></p>
|
||||||
|
|
||||||
> **Note**: this field is always empty and must not be used.
|
> **Deprecated**: this field is not part of the image specification and is
|
||||||
|
> always empty. It must not be used, and will be removed in API v1.47.
|
||||||
type: "string"
|
type: "string"
|
||||||
example: ""
|
example: ""
|
||||||
Domainname:
|
Domainname:
|
||||||
|
@ -1377,7 +1378,8 @@ definitions:
|
||||||
|
|
||||||
<p><br /></p>
|
<p><br /></p>
|
||||||
|
|
||||||
> **Note**: this field is always empty and must not be used.
|
> **Deprecated**: this field is not part of the image specification and is
|
||||||
|
> always empty. It must not be used, and will be removed in API v1.47.
|
||||||
type: "string"
|
type: "string"
|
||||||
example: ""
|
example: ""
|
||||||
User:
|
User:
|
||||||
|
@ -1390,7 +1392,8 @@ definitions:
|
||||||
|
|
||||||
<p><br /></p>
|
<p><br /></p>
|
||||||
|
|
||||||
> **Note**: this field is always false and must not be used.
|
> **Deprecated**: this field is not part of the image specification and is
|
||||||
|
> always false. It must not be used, and will be removed in API v1.47.
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
default: false
|
default: false
|
||||||
example: false
|
example: false
|
||||||
|
@ -1400,7 +1403,8 @@ definitions:
|
||||||
|
|
||||||
<p><br /></p>
|
<p><br /></p>
|
||||||
|
|
||||||
> **Note**: this field is always false and must not be used.
|
> **Deprecated**: this field is not part of the image specification and is
|
||||||
|
> always false. It must not be used, and will be removed in API v1.47.
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
default: false
|
default: false
|
||||||
example: false
|
example: false
|
||||||
|
@ -1410,7 +1414,8 @@ definitions:
|
||||||
|
|
||||||
<p><br /></p>
|
<p><br /></p>
|
||||||
|
|
||||||
> **Note**: this field is always false and must not be used.
|
> **Deprecated**: this field is not part of the image specification and is
|
||||||
|
> always false. It must not be used, and will be removed in API v1.47.
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
default: false
|
default: false
|
||||||
example: false
|
example: false
|
||||||
|
@ -1436,7 +1441,8 @@ definitions:
|
||||||
|
|
||||||
<p><br /></p>
|
<p><br /></p>
|
||||||
|
|
||||||
> **Note**: this field is always false and must not be used.
|
> **Deprecated**: this field is not part of the image specification and is
|
||||||
|
> always false. It must not be used, and will be removed in API v1.47.
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
default: false
|
default: false
|
||||||
example: false
|
example: false
|
||||||
|
@ -1446,7 +1452,8 @@ definitions:
|
||||||
|
|
||||||
<p><br /></p>
|
<p><br /></p>
|
||||||
|
|
||||||
> **Note**: this field is always false and must not be used.
|
> **Deprecated**: this field is not part of the image specification and is
|
||||||
|
> always false. It must not be used, and will be removed in API v1.47.
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
default: false
|
default: false
|
||||||
example: false
|
example: false
|
||||||
|
@ -1456,7 +1463,8 @@ definitions:
|
||||||
|
|
||||||
<p><br /></p>
|
<p><br /></p>
|
||||||
|
|
||||||
> **Note**: this field is always false and must not be used.
|
> **Deprecated**: this field is not part of the image specification and is
|
||||||
|
> always false. It must not be used, and will be removed in API v1.47.
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
default: false
|
default: false
|
||||||
example: false
|
example: false
|
||||||
|
@ -1492,7 +1500,8 @@ definitions:
|
||||||
|
|
||||||
<p><br /></p>
|
<p><br /></p>
|
||||||
|
|
||||||
> **Note**: this field is always empty and must not be used.
|
> **Deprecated**: this field is not part of the image specification and is
|
||||||
|
> always empty. It must not be used, and will be removed in API v1.47.
|
||||||
type: "string"
|
type: "string"
|
||||||
default: ""
|
default: ""
|
||||||
example: ""
|
example: ""
|
||||||
|
@ -1530,7 +1539,8 @@ definitions:
|
||||||
|
|
||||||
<p><br /></p>
|
<p><br /></p>
|
||||||
|
|
||||||
> **Note**: this field is always omitted and must not be used.
|
> **Deprecated**: this field is not part of the image specification and is
|
||||||
|
> always omitted. It must not be used, and will be removed in API v1.47.
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
default: false
|
default: false
|
||||||
example: false
|
example: false
|
||||||
|
@ -1541,7 +1551,8 @@ definitions:
|
||||||
|
|
||||||
<p><br /></p>
|
<p><br /></p>
|
||||||
|
|
||||||
> **Deprecated**: this field is deprecated in API v1.44 and up. It is always omitted.
|
> **Deprecated**: this field is not part of the image specification and is
|
||||||
|
> always omitted. It must not be used, and will be removed in API v1.47.
|
||||||
type: "string"
|
type: "string"
|
||||||
default: ""
|
default: ""
|
||||||
example: ""
|
example: ""
|
||||||
|
@ -1574,7 +1585,8 @@ definitions:
|
||||||
|
|
||||||
<p><br /></p>
|
<p><br /></p>
|
||||||
|
|
||||||
> **Note**: this field is always omitted and must not be used.
|
> **Deprecated**: this field is not part of the image specification and is
|
||||||
|
> always omitted. It must not be used, and will be removed in API v1.47.
|
||||||
type: "integer"
|
type: "integer"
|
||||||
default: 10
|
default: 10
|
||||||
x-nullable: true
|
x-nullable: true
|
||||||
|
@ -2115,6 +2127,7 @@ definitions:
|
||||||
format: "dateTime"
|
format: "dateTime"
|
||||||
example: "2022-02-28T14:40:02.623929178Z"
|
example: "2022-02-28T14:40:02.623929178Z"
|
||||||
x-nullable: true
|
x-nullable: true
|
||||||
|
|
||||||
ImageSummary:
|
ImageSummary:
|
||||||
type: "object"
|
type: "object"
|
||||||
x-go-name: "Summary"
|
x-go-name: "Summary"
|
||||||
|
@ -9023,6 +9036,11 @@ paths:
|
||||||
details.
|
details.
|
||||||
type: "string"
|
type: "string"
|
||||||
required: true
|
required: true
|
||||||
|
- name: "platform"
|
||||||
|
in: "query"
|
||||||
|
description: "Select a platform-specific manifest to be pushed. OCI platform (JSON encoded)"
|
||||||
|
type: "string"
|
||||||
|
x-nullable: true
|
||||||
tags: ["Image"]
|
tags: ["Image"]
|
||||||
/images/{name}/tag:
|
/images/{name}/tag:
|
||||||
post:
|
post:
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package auxprogress
|
||||||
|
|
||||||
|
import (
|
||||||
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ManifestPushedInsteadOfIndex is a note that is sent when a manifest is pushed
|
||||||
|
// instead of an index. It is sent when the pushed image is an multi-platform
|
||||||
|
// index, but the whole index couldn't be pushed.
|
||||||
|
type ManifestPushedInsteadOfIndex struct {
|
||||||
|
ManifestPushedInsteadOfIndex bool `json:"manifestPushedInsteadOfIndex"` // Always true
|
||||||
|
|
||||||
|
// OriginalIndex is the descriptor of the original image index.
|
||||||
|
OriginalIndex ocispec.Descriptor `json:"originalIndex"`
|
||||||
|
|
||||||
|
// SelectedManifest is the descriptor of the manifest that was pushed instead.
|
||||||
|
SelectedManifest ocispec.Descriptor `json:"selectedManifest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentMissing is a note that is sent when push fails because the content is missing.
|
||||||
|
type ContentMissing struct {
|
||||||
|
ContentMissing bool `json:"contentMissing"` // Always true
|
||||||
|
|
||||||
|
// Desc is the descriptor of the root object that was attempted to be pushed.
|
||||||
|
Desc ocispec.Descriptor `json:"desc"`
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ImportSource holds source information for ImageImport
|
// ImportSource holds source information for ImageImport
|
||||||
|
@ -43,7 +44,23 @@ type PullOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushOptions holds information to push images.
|
// PushOptions holds information to push images.
|
||||||
type PushOptions PullOptions
|
type PushOptions struct {
|
||||||
|
All bool
|
||||||
|
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry
|
||||||
|
|
||||||
|
// PrivilegeFunc is a function that clients can supply to retry operations
|
||||||
|
// after getting an authorization error. This function returns the registry
|
||||||
|
// authentication header value in base64 encoded format, or an error if the
|
||||||
|
// privilege request fails.
|
||||||
|
//
|
||||||
|
// Also see [github.com/docker/docker/api/types.RequestPrivilegeFunc].
|
||||||
|
PrivilegeFunc func(context.Context) (string, error)
|
||||||
|
|
||||||
|
// Platform is an optional field that selects a specific platform to push
|
||||||
|
// when the image is a multi-platform image.
|
||||||
|
// Using this will only push a single platform-specific manifest.
|
||||||
|
Platform *ocispec.Platform `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// ListOptions holds parameters to list images with.
|
// ListOptions holds parameters to list images with.
|
||||||
type ListOptions struct {
|
type ListOptions struct {
|
||||||
|
|
|
@ -2,7 +2,9 @@ package client // import "github.com/docker/docker/client"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -36,6 +38,20 @@ func (cli *Client) ImagePush(ctx context.Context, image string, options image.Pu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if options.Platform != nil {
|
||||||
|
if err := cli.NewVersionError(ctx, "1.46", "platform"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p := *options.Platform
|
||||||
|
pJson, err := json.Marshal(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid platform: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
query.Set("platform", string(pJson))
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := cli.tryImagePush(ctx, name, query, options.RegistryAuth)
|
resp, err := cli.tryImagePush(ctx, name, query, options.RegistryAuth)
|
||||||
if errdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil {
|
if errdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil {
|
||||||
newAuthHeader, privilegeErr := options.PrivilegeFunc(ctx)
|
newAuthHeader, privilegeErr := options.PrivilegeFunc(ctx)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/pkg/userns"
|
||||||
"github.com/docker/docker/pkg/system"
|
"github.com/docker/docker/pkg/system"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
@ -35,13 +36,18 @@ func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi.Mode()&os.ModeDir != 0 {
|
if fi.Mode()&os.ModeDir != 0 {
|
||||||
|
opaqueXattrName := "trusted.overlay.opaque"
|
||||||
|
if userns.RunningInUserNS() {
|
||||||
|
opaqueXattrName = "user.overlay.opaque"
|
||||||
|
}
|
||||||
|
|
||||||
// convert opaque dirs to AUFS format by writing an empty file with the prefix
|
// convert opaque dirs to AUFS format by writing an empty file with the prefix
|
||||||
opaque, err := system.Lgetxattr(path, "trusted.overlay.opaque")
|
opaque, err := system.Lgetxattr(path, opaqueXattrName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(opaque) == 1 && opaque[0] == 'y' {
|
if len(opaque) == 1 && opaque[0] == 'y' {
|
||||||
delete(hdr.PAXRecords, paxSchilyXattr+"trusted.overlay.opaque")
|
delete(hdr.PAXRecords, paxSchilyXattr+opaqueXattrName)
|
||||||
|
|
||||||
// create a header for the whiteout file
|
// create a header for the whiteout file
|
||||||
// it should inherit some properties from the parent, but be a regular file
|
// it should inherit some properties from the parent, but be a regular file
|
||||||
|
@ -69,9 +75,14 @@ func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (boo
|
||||||
|
|
||||||
// if a directory is marked as opaque by the AUFS special file, we need to translate that to overlay
|
// if a directory is marked as opaque by the AUFS special file, we need to translate that to overlay
|
||||||
if base == WhiteoutOpaqueDir {
|
if base == WhiteoutOpaqueDir {
|
||||||
err := unix.Setxattr(dir, "trusted.overlay.opaque", []byte{'y'}, 0)
|
opaqueXattrName := "trusted.overlay.opaque"
|
||||||
|
if userns.RunningInUserNS() {
|
||||||
|
opaqueXattrName = "user.overlay.opaque"
|
||||||
|
}
|
||||||
|
|
||||||
|
err := unix.Setxattr(dir, opaqueXattrName, []byte{'y'}, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.Wrapf(err, "setxattr(%q, trusted.overlay.opaque=y)", dir)
|
return false, errors.Wrapf(err, "setxattr(%q, %s=y)", dir, opaqueXattrName)
|
||||||
}
|
}
|
||||||
// don't write the file itself
|
// don't write the file itself
|
||||||
return false, err
|
return false, err
|
||||||
|
|
|
@ -56,10 +56,11 @@ github.com/docker/distribution/registry/client/transport
|
||||||
github.com/docker/distribution/registry/storage/cache
|
github.com/docker/distribution/registry/storage/cache
|
||||||
github.com/docker/distribution/registry/storage/cache/memory
|
github.com/docker/distribution/registry/storage/cache/memory
|
||||||
github.com/docker/distribution/uuid
|
github.com/docker/distribution/uuid
|
||||||
# github.com/docker/docker v26.1.1-0.20240610145149-a736d0701c41+incompatible
|
# github.com/docker/docker v26.1.1-0.20240610201418-9d9488468fe2+incompatible
|
||||||
## explicit
|
## explicit
|
||||||
github.com/docker/docker/api
|
github.com/docker/docker/api
|
||||||
github.com/docker/docker/api/types
|
github.com/docker/docker/api/types
|
||||||
|
github.com/docker/docker/api/types/auxprogress
|
||||||
github.com/docker/docker/api/types/blkiodev
|
github.com/docker/docker/api/types/blkiodev
|
||||||
github.com/docker/docker/api/types/checkpoint
|
github.com/docker/docker/api/types/checkpoint
|
||||||
github.com/docker/docker/api/types/container
|
github.com/docker/docker/api/types/container
|
||||||
|
|
Loading…
Reference in New Issue