Skip to content

Commit 52eddcf

Browse files
authored
Merge pull request #4984 from vvoland/c8d-multiplatform-push
cli/push: Add `platform` switch
2 parents 0022fe7 + 32ac7a0 commit 52eddcf

File tree

11 files changed

+201
-33
lines changed

11 files changed

+201
-33
lines changed

cli/command/image/push.go

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,25 @@ package image
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
67
"io"
8+
"os"
79

10+
"github.com/containerd/platforms"
811
"github.com/distribution/reference"
912
"github.com/docker/cli/cli"
1013
"github.com/docker/cli/cli/command"
1114
"github.com/docker/cli/cli/command/completion"
1215
"github.com/docker/cli/cli/streams"
16+
"github.com/docker/docker/api/types/auxprogress"
1317
"github.com/docker/docker/api/types/image"
1418
registrytypes "github.com/docker/docker/api/types/registry"
1519
"github.com/docker/docker/pkg/jsonmessage"
1620
"github.com/docker/docker/registry"
21+
"github.com/moby/term"
22+
"github.com/morikuni/aec"
23+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
1724
"github.com/pkg/errors"
1825
"github.com/spf13/cobra"
1926
)
@@ -23,6 +30,7 @@ type pushOptions struct {
2330
remote string
2431
untrusted bool
2532
quiet bool
33+
platform string
2634
}
2735

2836
// NewPushCommand creates a new `docker push` command
@@ -48,12 +56,33 @@ func NewPushCommand(dockerCli command.Cli) *cobra.Command {
4856
flags.BoolVarP(&opts.all, "all-tags", "a", false, "Push all tags of an image to the repository")
4957
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress verbose output")
5058
command.AddTrustSigningFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled())
59+
flags.StringVar(&opts.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"),
60+
`Push a platform-specific manifest as a single-platform image to the registry.
61+
'os[/arch[/variant]]': Explicit platform (eg. linux/amd64)`)
62+
flags.SetAnnotation("platform", "version", []string{"1.46"})
5163

5264
return cmd
5365
}
5466

5567
// RunPush performs a push against the engine based on the specified options
68+
//
69+
//nolint:gocyclo
5670
func RunPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error {
71+
var platform *ocispec.Platform
72+
if opts.platform != "" {
73+
p, err := platforms.Parse(opts.platform)
74+
if err != nil {
75+
_, _ = fmt.Fprintf(dockerCli.Err(), "Invalid platform %s", opts.platform)
76+
return err
77+
}
78+
platform = &p
79+
80+
printNote(dockerCli, `Selecting a single platform will only push one matching image manifest from a multi-platform image index.
81+
This means that any other components attached to the multi-platform image index (like Buildkit attestations) won't be pushed.
82+
If you want to only push a single platform image while preserving the attestations, please use 'docker convert\n'
83+
`)
84+
}
85+
5786
ref, err := reference.ParseNormalizedNamed(opts.remote)
5887
switch {
5988
case err != nil:
@@ -84,25 +113,73 @@ func RunPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error
84113
All: opts.all,
85114
RegistryAuth: encodedAuth,
86115
PrivilegeFunc: requestPrivilege,
116+
Platform: platform,
87117
}
88118

89119
responseBody, err := dockerCli.Client().ImagePush(ctx, reference.FamiliarString(ref), options)
90120
if err != nil {
91121
return err
92122
}
93123

124+
defer func() {
125+
for _, note := range notes {
126+
fmt.Fprintln(dockerCli.Err(), "")
127+
printNote(dockerCli, note)
128+
}
129+
}()
130+
94131
defer responseBody.Close()
95132
if !opts.untrusted {
96133
// TODO PushTrustedReference currently doesn't respect `--quiet`
97134
return PushTrustedReference(dockerCli, repoInfo, ref, authConfig, responseBody)
98135
}
99136

100137
if opts.quiet {
101-
err = jsonmessage.DisplayJSONMessagesToStream(responseBody, streams.NewOut(io.Discard), nil)
138+
err = jsonmessage.DisplayJSONMessagesToStream(responseBody, streams.NewOut(io.Discard), handleAux(dockerCli))
102139
if err == nil {
103140
fmt.Fprintln(dockerCli.Out(), ref.String())
104141
}
105142
return err
106143
}
107-
return jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil)
144+
return jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), handleAux(dockerCli))
145+
}
146+
147+
var notes []string
148+
149+
func handleAux(dockerCli command.Cli) func(jm jsonmessage.JSONMessage) {
150+
return func(jm jsonmessage.JSONMessage) {
151+
b := []byte(*jm.Aux)
152+
153+
var stripped auxprogress.ManifestPushedInsteadOfIndex
154+
err := json.Unmarshal(b, &stripped)
155+
if err == nil && stripped.ManifestPushedInsteadOfIndex {
156+
note := fmt.Sprintf("Not all multiplatform-content is present and only the available single-platform image was pushed\n%s -> %s",
157+
aec.RedF.Apply(stripped.OriginalIndex.Digest.String()),
158+
aec.GreenF.Apply(stripped.SelectedManifest.Digest.String()),
159+
)
160+
notes = append(notes, note)
161+
}
162+
163+
var missing auxprogress.ContentMissing
164+
err = json.Unmarshal(b, &missing)
165+
if err == nil && missing.ContentMissing {
166+
note := `You're trying to push a manifest list/index which
167+
references multiple platform specific manifests, but not all of them are available locally
168+
or available to the remote repository.
169+
170+
Make sure you have all the referenced content and try again.
171+
172+
You can also push only a single platform specific manifest directly by specifying the platform you want to push with the --platform flag.`
173+
notes = append(notes, note)
174+
}
175+
}
176+
}
177+
178+
func printNote(dockerCli command.Cli, format string, args ...any) {
179+
if _, isTTY := term.GetFdInfo(dockerCli.Err()); isTTY {
180+
_, _ = fmt.Fprint(dockerCli.Err(), aec.WhiteF.Apply(aec.CyanB.Apply("[ NOTE ]"))+" ")
181+
} else {
182+
_, _ = fmt.Fprint(dockerCli.Err(), "[ NOTE ] ")
183+
}
184+
_, _ = fmt.Fprintf(dockerCli.Err(), aec.Bold.Apply(format)+"\n", args...)
108185
}

docs/reference/commandline/image_push.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ Upload an image to a registry
99

1010
### Options
1111

12-
| Name | Type | Default | Description |
13-
|:---------------------------------------------|:-------|:--------|:--------------------------------------------|
14-
| [`-a`](#all-tags), [`--all-tags`](#all-tags) | | | Push all tags of an image to the repository |
15-
| `--disable-content-trust` | `bool` | `true` | Skip image signing |
16-
| `-q`, `--quiet` | | | Suppress verbose output |
12+
| Name | Type | Default | Description |
13+
|:---------------------------------------------|:---------|:--------|:--------------------------------------------------------------------------------------------------------------------------------------------|
14+
| [`-a`](#all-tags), [`--all-tags`](#all-tags) | | | Push all tags of an image to the repository |
15+
| `--disable-content-trust` | `bool` | `true` | Skip image signing |
16+
| `--platform` | `string` | | Push a platform-specific manifest as a single-platform image to the registry.<br>'os[/arch[/variant]]': Explicit platform (eg. linux/amd64) |
17+
| `-q`, `--quiet` | | | Suppress verbose output |
1718

1819

1920
<!---MARKER_GEN_END-->

docs/reference/commandline/push.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ Upload an image to a registry
99

1010
### Options
1111

12-
| Name | Type | Default | Description |
13-
|:--------------------------|:-------|:--------|:--------------------------------------------|
14-
| `-a`, `--all-tags` | | | Push all tags of an image to the repository |
15-
| `--disable-content-trust` | `bool` | `true` | Skip image signing |
16-
| `-q`, `--quiet` | | | Suppress verbose output |
12+
| Name | Type | Default | Description |
13+
|:--------------------------|:---------|:--------|:--------------------------------------------------------------------------------------------------------------------------------------------|
14+
| `-a`, `--all-tags` | | | Push all tags of an image to the repository |
15+
| `--disable-content-trust` | `bool` | `true` | Skip image signing |
16+
| `--platform` | `string` | | Push a platform-specific manifest as a single-platform image to the registry.<br>'os[/arch[/variant]]': Explicit platform (eg. linux/amd64) |
17+
| `-q`, `--quiet` | | | Suppress verbose output |
1718

1819

1920
<!---MARKER_GEN_END-->

vendor.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ require (
1212
github.com/creack/pty v1.1.21
1313
github.com/distribution/reference v0.6.0
1414
github.com/docker/distribution v2.8.3+incompatible
15-
github.com/docker/docker v26.1.1-0.20240610145149-a736d0701c41+incompatible // master (v27.0.0-dev)
15+
github.com/docker/docker v26.1.1-0.20240610201418-9d9488468fe2+incompatible // master (v27.0.0-dev)
1616
github.com/docker/docker-credential-helpers v0.8.2
1717
github.com/docker/go-connections v0.5.0
1818
github.com/docker/go-units v0.5.0

vendor.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5
5959
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
6060
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
6161
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
62-
github.com/docker/docker v26.1.1-0.20240610145149-a736d0701c41+incompatible h1:Kraon288jb3POkrmM5w6Xo979z2rrCtFzHycAjafRes=
63-
github.com/docker/docker v26.1.1-0.20240610145149-a736d0701c41+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
62+
github.com/docker/docker v26.1.1-0.20240610201418-9d9488468fe2+incompatible h1:k63BdhjySkwvmdeofOsBElcuVrWaDBrI7FQgnyoVnnM=
63+
github.com/docker/docker v26.1.1-0.20240610201418-9d9488468fe2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
6464
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
6565
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
6666
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=

vendor/github.com/docker/docker/api/swagger.yaml

Lines changed: 30 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/docker/docker/api/types/auxprogress/push.go

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/docker/docker/api/types/image/opts.go

Lines changed: 18 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)