diff --git a/contrib/completions/bash/oc b/contrib/completions/bash/oc index 0be4c2c027..ca34212362 100644 --- a/contrib/completions/bash/oc +++ b/contrib/completions/bash/oc @@ -13576,6 +13576,8 @@ _oc_image_mirror() local_nonpersistent_flags+=("--from-dir=") flags+=("--insecure") local_nonpersistent_flags+=("--insecure") + flags+=("--keep-manifest-list") + local_nonpersistent_flags+=("--keep-manifest-list") flags+=("--max-per-registry=") two_word_flags+=("--max-per-registry") local_nonpersistent_flags+=("--max-per-registry=") diff --git a/contrib/completions/zsh/oc b/contrib/completions/zsh/oc index 2c60413b1f..8f2b57ea64 100644 --- a/contrib/completions/zsh/oc +++ b/contrib/completions/zsh/oc @@ -13718,6 +13718,8 @@ _oc_image_mirror() local_nonpersistent_flags+=("--from-dir=") flags+=("--insecure") local_nonpersistent_flags+=("--insecure") + flags+=("--keep-manifest-list") + local_nonpersistent_flags+=("--keep-manifest-list") flags+=("--max-per-registry=") two_word_flags+=("--max-per-registry") local_nonpersistent_flags+=("--max-per-registry=") diff --git a/pkg/cli/admin/catalog/mirror.go b/pkg/cli/admin/catalog/mirror.go index fc5aa5277f..f928d9aa23 100644 --- a/pkg/cli/admin/catalog/mirror.go +++ b/pkg/cli/admin/catalog/mirror.go @@ -182,7 +182,7 @@ func (o *MirrorCatalogOptions) Complete(cmd *cobra.Command, args []string) error a.SecurityOptions = o.SecurityOptions a.FilterOptions = o.FilterOptions a.ParallelOptions = o.ParallelOptions - a.ForceManifestList = true + a.KeepManifestList = true a.Mappings = []imgmirror.Mapping{{ Source: fromRef[0], Destination: toRef, diff --git a/pkg/cli/image/manifest/manifest.go b/pkg/cli/image/manifest/manifest.go index 7cea303b82..1505ed231c 100644 --- a/pkg/cli/image/manifest/manifest.go +++ b/pkg/cli/image/manifest/manifest.go @@ -149,6 +149,15 @@ func (o *FilterOptions) Complete(flags *pflag.FlagSet) error { return nil } +// IsWildcardFilter returns true if the filter regex is set to a wildcard +func (o *FilterOptions) IsWildcardFilter() bool { + wildcardFilter := ".*" + if o.FilterByOS == wildcardFilter { + return true + } + return false +} + // Include returns true if the provided manifest should be included, or the first image if the user didn't alter the // default selection and there is only one image. func (o *FilterOptions) Include(d *manifestlist.ManifestDescriptor, hasMultiple bool) bool { @@ -330,7 +339,7 @@ func ManifestToImageConfig(ctx context.Context, srcManifest distribution.Manifes } } -func ProcessManifestList(ctx context.Context, srcDigest digest.Digest, srcManifest distribution.Manifest, manifests distribution.ManifestService, ref imagereference.DockerImageReference, filterFn FilterFunc, forceManifestList bool) ([]distribution.Manifest, distribution.Manifest, digest.Digest, error) { +func ProcessManifestList(ctx context.Context, srcDigest digest.Digest, srcManifest distribution.Manifest, manifests distribution.ManifestService, ref imagereference.DockerImageReference, filterFn FilterFunc, keepManifestList bool) ([]distribution.Manifest, distribution.Manifest, digest.Digest, error) { var srcManifests []distribution.Manifest switch t := srcManifest.(type) { case *manifestlist.DeserializedManifestList: @@ -379,7 +388,7 @@ func ProcessManifestList(ctx context.Context, srcDigest digest.Digest, srcManife } switch { - case len(srcManifests) == 1 && !forceManifestList: + case len(srcManifests) == 1 && !keepManifestList: manifestDigest, err := registryclient.ContentDigestForManifest(srcManifests[0], srcDigest.Algorithm()) if err != nil { return nil, nil, "", err diff --git a/pkg/cli/image/mirror/mirror.go b/pkg/cli/image/mirror/mirror.go index 50e7a41685..faf2f355d2 100644 --- a/pkg/cli/image/mirror/mirror.go +++ b/pkg/cli/image/mirror/mirror.go @@ -100,7 +100,7 @@ type MirrorImageOptions struct { SkipMultipleScopes bool SkipMissing bool Force bool - ForceManifestList bool + KeepManifestList bool MaxRegistry int ParallelOptions imagemanifest.ParallelOptions @@ -145,14 +145,12 @@ func NewCmdMirrorImage(name string, streams genericclioptions.IOStreams) *cobra. o.FilterOptions.Bind(flag) o.ParallelOptions.Bind(flag) - // Always mirror manifestlist if available - o.ForceManifestList = true - flag.BoolVar(&o.DryRun, "dry-run", o.DryRun, "Print the actions that would be taken and exit without writing to the destinations.") flag.BoolVar(&o.SkipMissing, "skip-missing", o.SkipMissing, "If an input image is not found, skip them.") flag.BoolVar(&o.SkipMount, "skip-mount", o.SkipMount, "Always push layers instead of cross-mounting them") flag.BoolVar(&o.SkipMultipleScopes, "skip-multiple-scopes", o.SkipMultipleScopes, "Some registries do not support multiple scopes passed to the registry login.") flag.BoolVar(&o.Force, "force", o.Force, "Attempt to write all layers and manifests even if they exist in the remote repository.") + flag.BoolVar(&o.KeepManifestList, "keep-manifest-list", o.KeepManifestList, "If an image is part of a manifest list, always mirror the list even if only one image is found. The default is to mirror the specific image unless unless --filter-by-os is '.*'.") flag.IntVar(&o.MaxRegistry, "max-registry", o.MaxRegistry, "Number of concurrent registries to connect to at any one time.") flag.StringSliceVar(&o.AttemptS3BucketCopy, "s3-source-bucket", o.AttemptS3BucketCopy, "A list of bucket/path locations on S3 that may contain already uploaded blobs. Add [store] to the end to use the container image registry path convention.") flag.StringSliceVarP(&o.Filenames, "filename", "f", o.Filenames, "One or more files to read SRC=DST or SRC DST [DST ...] mappings from.") @@ -167,6 +165,10 @@ func (o *MirrorImageOptions) Complete(cmd *cobra.Command, args []string) error { return err } + if o.FilterOptions.IsWildcardFilter() { + o.KeepManifestList = true + } + registryContext, err := o.SecurityOptions.Context() if err != nil { return err @@ -466,7 +468,7 @@ func (o *MirrorImageOptions) plan() (*plan, error) { // filter or load manifest list as appropriate originalSrcDigest := srcDigest - srcManifests, srcManifest, srcDigest, err := imagemanifest.ProcessManifestList(ctx, srcDigest, srcManifest, manifests, src.ref.Ref, o.FilterOptions.IncludeAll, o.ForceManifestList) + srcManifests, srcManifest, srcDigest, err := imagemanifest.ProcessManifestList(ctx, srcDigest, srcManifest, manifests, src.ref.Ref, o.FilterOptions.IncludeAll, o.KeepManifestList) if err != nil { plan.AddError(retrieverError{src: src.ref, err: err}) return