cli/push: Print aux notes

Print note when the multi-platform image was reduced to a single
manifest.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
Paweł Gronowski 2024-06-10 19:17:50 +02:00
parent 966fa7c475
commit 32ac7a08f8
No known key found for this signature in database
GPG Key ID: B85EFCFE26DEF92A
3 changed files with 76 additions and 7 deletions

View File

@ -2,6 +2,7 @@ package image
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
@ -12,11 +13,13 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/streams"
"github.com/docker/docker/api/types/auxprogress"
"github.com/docker/docker/api/types/image"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/pkg/jsonmessage"
"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/spf13/cobra"
@ -62,6 +65,8 @@ func NewPushCommand(dockerCli command.Cli) *cobra.Command {
}
// 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 {
var platform *ocispec.Platform
if opts.platform != "" {
@ -74,9 +79,8 @@ func RunPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error
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 build an image with only that platform and push it instead.
Example: echo "FROM %s" | docker build - --platform %s -t <NEW-TAG>
`, opts.remote, opts.platform)
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)
@ -117,6 +121,13 @@ Example: echo "FROM %s" | docker build - --platform %s -t <NEW-TAG>
return err
}
defer func() {
for _, note := range notes {
fmt.Fprintln(dockerCli.Err(), "")
printNote(dockerCli, note)
}
}()
defer responseBody.Close()
if !opts.untrusted {
// TODO PushTrustedReference currently doesn't respect `--quiet`
@ -124,20 +135,51 @@ Example: echo "FROM %s" | docker build - --platform %s -t <NEW-TAG>
}
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 {
fmt.Fprintln(dockerCli.Out(), ref.String())
}
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(), "\x1b[1;37m\x1b[1;46m[ NOTE ]\x1b[0m\x1b[0m ")
_, _ = fmt.Fprint(dockerCli.Err(), aec.WhiteF.Apply(aec.CyanB.Apply("[ NOTE ]"))+" ")
} else {
_, _ = fmt.Fprint(dockerCli.Err(), "[ NOTE ] ")
}
_, _ = fmt.Fprintf(dockerCli.Err(), format+"\n\n", args...)
_, _ = fmt.Fprintf(dockerCli.Err(), aec.Bold.Apply(format)+"\n", args...)
}

View File

@ -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"`
}

1
vendor/modules.txt vendored
View File

@ -60,6 +60,7 @@ github.com/docker/distribution/uuid
## explicit
github.com/docker/docker/api
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/checkpoint
github.com/docker/docker/api/types/container