Files
docker-cli/cli/command/manifest/annotate.go
Sebastiaan van Stijn 0adaf6be3b verify that DisableFlagsInUseLine is set for all commands
This replaces the visitAll recursive function with a test that verifies that
the option is set for all commands and subcommands, so that it doesn't have
to be modified at runtime.

We currently still have to loop over all functions for the setValidateArgs
call, but that can be looked at separately.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-01 09:39:46 +02:00

139 lines
4.5 KiB
Go

package manifest
import (
"context"
"fmt"
"path/filepath"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/manifest/store"
"github.com/docker/cli/internal/registryclient"
"github.com/moby/moby/api/types/registry"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/cobra"
)
type annotateOptions struct {
target string // the target manifest list name (also transaction ID)
image string // the manifest to annotate within the list
variant string // an architecture variant
os string
arch string
osFeatures []string
osVersion string
}
// manifestStoreProvider is used in tests to provide a dummy store.
type manifestStoreProvider interface {
// ManifestStore returns a store for local manifests
ManifestStore() store.Store
RegistryClient(bool) registryclient.RegistryClient
}
// newManifestStore returns a store for local manifests
func newManifestStore(dockerCLI command.Cli) store.Store {
if msp, ok := dockerCLI.(manifestStoreProvider); ok {
// manifestStoreProvider is used in tests to provide a dummy store.
return msp.ManifestStore()
}
// TODO: support override default location from config file
return store.NewStore(filepath.Join(config.Dir(), "manifests"))
}
// newRegistryClient returns a client for communicating with a Docker distribution
// registry
func newRegistryClient(dockerCLI command.Cli, allowInsecure bool) registryclient.RegistryClient {
if msp, ok := dockerCLI.(manifestStoreProvider); ok {
// manifestStoreProvider is used in tests to provide a dummy store.
return msp.RegistryClient(allowInsecure)
}
resolver := func(ctx context.Context, index *registry.IndexInfo) registry.AuthConfig {
return command.ResolveAuthConfig(dockerCLI.ConfigFile(), index)
}
return registryclient.NewRegistryClient(resolver, command.UserAgent(), allowInsecure)
}
// NewAnnotateCommand creates a new `docker manifest annotate` command
func newAnnotateCommand(dockerCLI command.Cli) *cobra.Command {
var opts annotateOptions
cmd := &cobra.Command{
Use: "annotate [OPTIONS] MANIFEST_LIST MANIFEST",
Short: "Add additional information to a local image manifest",
Args: cli.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
opts.target = args[0]
opts.image = args[1]
return runManifestAnnotate(dockerCLI, opts)
},
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
flags.StringVar(&opts.os, "os", "", "Set operating system")
flags.StringVar(&opts.arch, "arch", "", "Set architecture")
flags.StringVar(&opts.osVersion, "os-version", "", "Set operating system version")
flags.StringSliceVar(&opts.osFeatures, "os-features", []string{}, "Set operating system feature")
flags.StringVar(&opts.variant, "variant", "", "Set architecture variant")
return cmd
}
func runManifestAnnotate(dockerCLI command.Cli, opts annotateOptions) error {
targetRef, err := normalizeReference(opts.target)
if err != nil {
return fmt.Errorf("annotate: error parsing name for manifest list %s: %w", opts.target, err)
}
imgRef, err := normalizeReference(opts.image)
if err != nil {
return fmt.Errorf("annotate: error parsing name for manifest %s: %w", opts.image, err)
}
manifestStore := newManifestStore(dockerCLI)
imageManifest, err := manifestStore.Get(targetRef, imgRef)
switch {
case store.IsNotFound(err):
return fmt.Errorf("manifest for image %s does not exist in %s", opts.image, opts.target)
case err != nil:
return err
}
// Update the mf
if imageManifest.Descriptor.Platform == nil {
imageManifest.Descriptor.Platform = new(ocispec.Platform)
}
if opts.os != "" {
imageManifest.Descriptor.Platform.OS = opts.os
}
if opts.arch != "" {
imageManifest.Descriptor.Platform.Architecture = opts.arch
}
for _, osFeature := range opts.osFeatures {
imageManifest.Descriptor.Platform.OSFeatures = appendIfUnique(imageManifest.Descriptor.Platform.OSFeatures, osFeature)
}
if opts.variant != "" {
imageManifest.Descriptor.Platform.Variant = opts.variant
}
if opts.osVersion != "" {
imageManifest.Descriptor.Platform.OSVersion = opts.osVersion
}
if !isValidOSArch(imageManifest.Descriptor.Platform.OS, imageManifest.Descriptor.Platform.Architecture) {
return fmt.Errorf("manifest entry for image has unsupported os/arch combination: %s/%s", opts.os, opts.arch)
}
return manifestStore.Save(targetRef, imgRef, imageManifest)
}
func appendIfUnique(list []string, str string) []string {
for _, s := range list {
if s == str {
return list
}
}
return append(list, str)
}