Repository: operator-framework/operator-registry
Pull request #1626: Containers image registry implementation with opt-in caching
Author: login: joelanford
Labels: name: approved
description: Indicates a PR has been approved by an approver from all required OWNERS files.
name: lgtm
description: Indicates that a PR is ready to be merged.
Created: 2025-03-25T18:09:36Z | Merged: 2025-04-24T14:46:38Z

<!--

Before making a PR, please read our contributing guidelines https://github.com/operator-framework/operator-lifecycle-manager/blob/master/CONTRIBUTING.md
Note: Make sure your branch is rebased to the latest upstream master.

-->

**Description of the change:**
This change adds a new `containers/image`-based registry client and updates various (non-deprecated) commands to use it. Specifically `render` (and other commands that use `action.Render`) and `alpha bundle validate`. Deprecated sqlite based commands are not updated.

The intent of the PR is to keep essentially full compatibility with the `containerd` implementation, and to additionally gain the features of containers/image-based clients.

This PR also includes the ability to opt into caching (and reusing) downloaded blobs (when `OLM_CACHE_DIR` is set). The behavior around the cache is essentially `--pull-policy=Always`, where existing layers do not need to be redownloaded. While this PR does garbage collect unused blobs in the cache after each image pull, specifically cleaning up the cache is (for now at least) left as an exercise for the user.

**Motivation for the change:**
Improves alignment of `opm` with the ecosystem of tools it is often used with. The main benefit here is that `opm` calls that use this client will now honor user's existing `containers/image`-based configuration, including `registries.conf` CAs and image mappings.

**Reviewer Checklist**
- [ ] Implementation matches the proposed design, or proposal is updated to match implementation
- [ ] Sufficient unit test coverage 
- [ ] Sufficient end-to-end test coverage
- [ ] Docs updated or added to `/docs` 
- [ ] Commit messages sensible and descriptive


<!--

Note: If this PR is fixing an issue make sure to add a note saying:
Closes #<ISSUE_NUMBER>

-->



Diff:
diff --git a/Makefile b/Makefile
index c2992c598..6ac2911ca 100644
--- a/Makefile
+++ b/Makefile
@@ -26,16 +26,14 @@ $(PROTOC):
 null  :=
 space := $(null) #
 comma := ,
-# default to json1 for sqlite3
-TAGS := -tags=json1
+# default to json1 for sqlite3 and containers_image_openpgp for containers/image
+TAGS := json1,containers_image_openpgp
 
 # Cluster to use for e2e testing
 CLUSTER ?= ""
 ifeq ($(CLUSTER), kind)
 # add kind to the list of tags
-TAGS += kind
-# convert tag format from space to comma list
-TAGS := $(subst $(space),$(comma),$(strip $(TAGS)))
+TAGS := $(TAGS),kind
 endif
 
 # -race is only supported on linux/amd64, linux/ppc64le, linux/arm64, freebsd/amd64, netbsd/amd64, darwin/amd64 and windows/amd64
@@ -49,12 +47,12 @@ endif
 all: clean test build
 
 $(CMDS):
-	$(extra_env) $(GO) build $(extra_flags) $(TAGS) -o $@ ./cmd/$(notdir $@)
+	$(extra_env) $(GO) build $(extra_flags) -tags=$(TAGS) -o $@ ./cmd/$(notdir $@)
 
 .PHONY: $(OPM)
 $(OPM): opm_version_flags=-ldflags "-X '$(PKG)/cmd/opm/version.gitCommit=$(GIT_COMMIT)' -X '$(PKG)/cmd/opm/version.opmVersion=$(OPM_VERSION)' -X '$(PKG)/cmd/opm/version.buildDate=$(BUILD_DATE)'"
 $(OPM):
-	$(extra_env) $(GO) build $(opm_version_flags) $(extra_flags) $(TAGS) -o $@ ./cmd/$(notdir $@)
+	$(extra_env) $(GO) build $(opm_version_flags) $(extra_flags) -tags=$(TAGS) -o $@ ./cmd/$(notdir $@)
 
 .PHONY: build
 build: clean $(CMDS) $(OPM)
@@ -63,8 +61,8 @@ build: clean $(CMDS) $(OPM)
 cross: opm_version_flags=-ldflags "-X '$(PKG)/cmd/opm/version.gitCommit=$(GIT_COMMIT)' -X '$(PKG)/cmd/opm/version.opmVersion=$(OPM_VERSION)' -X '$(PKG)/cmd/opm/version.buildDate=$(BUILD_DATE)'"
 cross:
 ifeq ($(shell go env GOARCH),amd64)
-	GOOS=darwin CC=o64-clang CXX=o64-clang++ CGO_ENABLED=1 $(GO) build $(opm_version_flags) $(TAGS) -o "bin/darwin-amd64-opm" --ldflags "-extld=o64-clang" ./cmd/opm
-	GOOS=windows CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ CGO_ENABLED=1 $(GO) build $(opm_version_flags) $(TAGS)  -o "bin/windows-amd64-opm" --ldflags "-extld=x86_64-w64-mingw32-gcc" -buildmode=exe ./cmd/opm
+	GOOS=darwin CC=o64-clang CXX=o64-clang++ CGO_ENABLED=1 $(GO) build $(opm_version_flags) -tags=$(TAGS) -o "bin/darwin-amd64-opm" --ldflags "-extld=o64-clang" ./cmd/opm
+	GOOS=windows CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ CGO_ENABLED=1 $(GO) build $(opm_version_flags) -tags=$(TAGS)  -o "bin/windows-amd64-opm" --ldflags "-extld=x86_64-w64-mingw32-gcc" -buildmode=exe ./cmd/opm
 endif
 
 .PHONY: static
@@ -73,7 +71,7 @@ static: build
 
 .PHONY: unit
 unit:
-	$(GO) test -coverprofile=coverage.out $(SPECIFIC_UNIT_TEST) $(SPECIFIC_SKIP_UNIT_TEST) $(TAGS) $(TEST_RACE) -count=1 ./pkg/... ./alpha/...
+	$(GO) test -coverprofile=coverage.out --coverpkg=./... $(SPECIFIC_UNIT_TEST) $(SPECIFIC_SKIP_UNIT_TEST) -tags=$(TAGS) $(TEST_RACE) -count=1 ./pkg/... ./alpha/...
 
 .PHONY: tidy
 tidy:
@@ -102,10 +100,8 @@ image-upstream:
 	docker build -f upstream-example.Dockerfile .
 
 .PHONY: lint
-#lint:
-#	find . -type f -name '*.go' ! -name '*.pb.go' -print0 | xargs -0 goimports -w
 lint: $(GOLANGCI_LINT)
-	$(GOLANGCI_LINT) run $(GOLANGCI_LINT_ARGS)
+	$(GOLANGCI_LINT) run --build-tags=$(TAGS) $(GOLANGCI_LINT_ARGS)
 
 .PHONY: fix-lint
 fix-lint: $(GOLANGCI_LINT)
@@ -133,7 +129,7 @@ clean:
 
 .PHONY: e2e
 e2e: $(GINKGO)
-	$(GINKGO) --v --randomize-all --progress --trace --randomize-suites --race $(if $(TEST),-focus '$(TEST)') $(TAGS) ./test/e2e -- $(if $(SKIPTLS),-skip-tls-verify true) $(if $(USEHTTP),-use-http true)
+	$(GINKGO) --v --randomize-all --progress --trace --randomize-suites --race $(if $(TEST),-focus '$(TEST)') -tags=$(TAGS) ./test/e2e -- $(if $(SKIPTLS),-skip-tls-verify true) $(if $(USEHTTP),-use-http true)
 
 .PHONY: release
 export OPM_IMAGE_REPO ?= quay.io/operator-framework/opm
diff --git a/alpha/action/render.go b/alpha/action/render.go
index ad7a066b4..a124c0f8a 100644
--- a/alpha/action/render.go
+++ b/alpha/action/render.go
@@ -22,9 +22,8 @@ import (
 	"github.com/operator-framework/operator-registry/alpha/property"
 	"github.com/operator-framework/operator-registry/pkg/containertools"
 	"github.com/operator-framework/operator-registry/pkg/image"
-	"github.com/operator-framework/operator-registry/pkg/image/containerdregistry"
+	"github.com/operator-framework/operator-registry/pkg/image/containersimageregistry"
 	"github.com/operator-framework/operator-registry/pkg/lib/bundle"
-	"github.com/operator-framework/operator-registry/pkg/lib/log"
 	"github.com/operator-framework/operator-registry/pkg/registry"
 	"github.com/operator-framework/operator-registry/pkg/sqlite"
 )
@@ -66,7 +65,7 @@ func (r Render) Run(ctx context.Context) (*declcfg.DeclarativeConfig, error) {
 		logDeprecationMessage.Do(func() {})
 	}
 	if r.Registry == nil {
-		reg, err := r.createRegistry()
+		reg, err := containersimageregistry.NewDefault()
 		if err != nil {
 			return nil, fmt.Errorf("create registry: %v", err)
 		}
@@ -101,26 +100,6 @@ func (r Render) Run(ctx context.Context) (*declcfg.DeclarativeConfig, error) {
 	return combineConfigs(cfgs), nil
 }
 
-func (r Render) createRegistry() (*containerdregistry.Registry, error) {
-	cacheDir, err := os.MkdirTemp("", "render-registry-")
-	if err != nil {
-		return nil, fmt.Errorf("create tempdir: %v", err)
-	}
-
-	reg, err := containerdregistry.NewRegistry(
-		containerdregistry.WithCacheDir(cacheDir),
-
-		// The containerd registry impl is somewhat verbose, even on the happy path,
-		// so discard all logger logs. Any important failures will be returned from
-		// registry methods and eventually logged as fatal errors.
-		containerdregistry.WithLog(log.Null()),
-	)
-	if err != nil {
-		return nil, err
-	}
-	return reg, nil
-}
-
 func (r Render) renderReference(ctx context.Context, ref string) (*declcfg.DeclarativeConfig, error) {
 	stat, err := os.Stat(ref)
 	if err != nil {
diff --git a/cmd/opm/alpha/bundle/validate.go b/cmd/opm/alpha/bundle/validate.go
index c1044c05c..069cfff9e 100644
--- a/cmd/opm/alpha/bundle/validate.go
+++ b/cmd/opm/alpha/bundle/validate.go
@@ -10,7 +10,7 @@ import (
 
 	"github.com/operator-framework/operator-registry/pkg/containertools"
 	"github.com/operator-framework/operator-registry/pkg/image"
-	"github.com/operator-framework/operator-registry/pkg/image/containerdregistry"
+	"github.com/operator-framework/operator-registry/pkg/image/containersimageregistry"
 	"github.com/operator-framework/operator-registry/pkg/image/execregistry"
 	"github.com/operator-framework/operator-registry/pkg/lib/bundle"
 )
@@ -69,7 +69,7 @@ func validateFunc(cmd *cobra.Command, _ []string) error {
 	case containertools.PodmanTool, containertools.DockerTool:
 		registry, err = execregistry.NewRegistry(tool, logger)
 	case containertools.NoneTool:
-		registry, err = containerdregistry.NewRegistry(containerdregistry.WithLog(logger))
+		registry, err = containersimageregistry.NewDefault()
 	default:
 		err = fmt.Errorf("unrecognized container-tool option: %s", containerTool)
 	}
diff --git a/cmd/opm/internal/util/util.go b/cmd/opm/internal/util/util.go
index e007caa48..35455b85b 100644
--- a/cmd/opm/internal/util/util.go
+++ b/cmd/opm/internal/util/util.go
@@ -7,8 +7,8 @@ import (
 
 	"github.com/spf13/cobra"
 
-	"github.com/operator-framework/operator-registry/pkg/image/containerdregistry"
-	"github.com/operator-framework/operator-registry/pkg/lib/log"
+	"github.com/operator-framework/operator-registry/pkg/image"
+	"github.com/operator-framework/operator-registry/pkg/image/containersimageregistry"
 )
 
 // GetTLSOptions validates and returns TLS options set by opm flags
@@ -45,27 +45,15 @@ func GetTLSOptions(cmd *cobra.Command) (bool, bool, error) {
 
 // This works in tandem with opm/index/cmd, which adds the relevant flags as persistent
 // as part of the root command (cmd/root/cmd) initialization
-func CreateCLIRegistry(cmd *cobra.Command) (*containerdregistry.Registry, error) {
+func CreateCLIRegistry(cmd *cobra.Command) (image.Registry, error) {
 	skipTLSVerify, useHTTP, err := GetTLSOptions(cmd)
 	if err != nil {
 		return nil, err
 	}
-
-	cacheDir, err := os.MkdirTemp("", "opm-registry-")
-	if err != nil {
-		return nil, err
-	}
-
-	reg, err := containerdregistry.NewRegistry(
-		containerdregistry.WithCacheDir(cacheDir),
-		containerdregistry.SkipTLSVerify(skipTLSVerify),
-		containerdregistry.WithPlainHTTP(useHTTP),
-		containerdregistry.WithLog(log.Null()),
+	return containersimageregistry.New(
+		containersimageregistry.DefaultSystemContext,
+		containersimageregistry.WithInsecureSkipTLSVerify(skipTLSVerify || useHTTP),
 	)
-	if err != nil {
-		return nil, err
-	}
-	return reg, nil
 }
 
 func OpenFileOrStdin(cmd *cobra.Command, args []string) (io.ReadCloser, string, error) {
diff --git a/go.mod b/go.mod
index ec5337a7a..f06a69ab4 100644
--- a/go.mod
+++ b/go.mod
@@ -28,7 +28,6 @@ require (
 	github.com/opencontainers/image-spec v1.1.1
 	github.com/operator-framework/api v0.30.0
 	github.com/otiai10/copy v1.14.1
-	github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
 	github.com/pkg/errors v0.9.1
 	github.com/sirupsen/logrus v1.9.3
 	github.com/spf13/cobra v1.9.1
@@ -51,6 +50,7 @@ require (
 	k8s.io/apimachinery v0.32.3
 	k8s.io/client-go v0.32.3
 	k8s.io/kubectl v0.32.0
+	oras.land/oras-go/v2 v2.5.0
 	sigs.k8s.io/controller-runtime v0.20.4
 	sigs.k8s.io/kind v0.27.0
 	sigs.k8s.io/yaml v1.4.0
@@ -65,10 +65,11 @@ require (
 	github.com/MakeNowJust/heredoc v1.0.0 // indirect
 	github.com/Microsoft/go-winio v0.6.2 // indirect
 	github.com/Microsoft/hcsshim v0.12.9 // indirect
+	github.com/VividCortex/ewma v1.2.0 // indirect
+	github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
 	github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
 	github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
-	github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect
 	github.com/cenkalti/backoff/v4 v4.3.0 // indirect
 	github.com/cespare/xxhash/v2 v2.3.0 // indirect
 	github.com/containerd/cgroups/v3 v3.0.5 // indirect
@@ -82,7 +83,7 @@ require (
 	github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
 	github.com/containers/ocicrypt v1.2.1 // indirect
 	github.com/containers/storage v1.58.0 // indirect
-	github.com/coreos/go-systemd/v22 v22.5.0 // indirect
+	github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 	github.com/docker/distribution v2.8.3+incompatible // indirect
@@ -102,15 +103,23 @@ require (
 	github.com/go-jose/go-jose/v4 v4.0.5 // indirect
 	github.com/go-logr/logr v1.4.2 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
+	github.com/go-openapi/analysis v0.23.0 // indirect
+	github.com/go-openapi/errors v0.22.1 // indirect
 	github.com/go-openapi/jsonpointer v0.21.0 // indirect
 	github.com/go-openapi/jsonreference v0.21.0 // indirect
+	github.com/go-openapi/loads v0.22.0 // indirect
+	github.com/go-openapi/runtime v0.28.0 // indirect
+	github.com/go-openapi/spec v0.21.0 // indirect
+	github.com/go-openapi/strfmt v0.23.0 // indirect
 	github.com/go-openapi/swag v0.23.1 // indirect
+	github.com/go-openapi/validate v0.24.0 // indirect
 	github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
 	github.com/golang/protobuf v1.5.4 // indirect
 	github.com/google/cel-go v0.22.1 // indirect
 	github.com/google/gnostic-models v0.6.8 // indirect
+	github.com/google/go-containerregistry v0.20.3 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
 	github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
 	github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 // indirect
@@ -118,7 +127,7 @@ require (
 	github.com/gorilla/handlers v1.5.2 // indirect
 	github.com/gorilla/mux v1.8.1 // indirect
 	github.com/gorilla/websocket v1.5.0 // indirect
-	github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect
+	github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect
 	github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect
 	github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
@@ -126,10 +135,15 @@ require (
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/klauspost/compress v1.18.0 // indirect
+	github.com/klauspost/pgzip v1.2.6 // indirect
+	github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect
 	github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
 	github.com/mailru/easyjson v0.9.0 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
+	github.com/mattn/go-runewidth v0.0.16 // indirect
+	github.com/miekg/pkcs11 v1.1.1 // indirect
 	github.com/mitchellh/go-wordwrap v1.0.1 // indirect
+	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/moby/locker v1.0.1 // indirect
 	github.com/moby/spdystream v0.5.0 // indirect
 	github.com/moby/sys/capability v0.4.0 // indirect
@@ -142,10 +156,12 @@ require (
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
+	github.com/oklog/ulid v1.3.1 // indirect
 	github.com/opencontainers/runtime-spec v1.2.1 // indirect
 	github.com/otiai10/mint v1.6.3 // indirect
 	github.com/pelletier/go-toml v1.9.5 // indirect
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+	github.com/proglottis/gpgme v0.1.4 // indirect
 	github.com/prometheus/client_golang v1.21.1 // indirect
 	github.com/prometheus/client_model v0.6.1 // indirect
 	github.com/prometheus/common v0.62.0 // indirect
@@ -153,11 +169,24 @@ require (
 	github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect
 	github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect
 	github.com/redis/go-redis/v9 v9.7.3 // indirect
+	github.com/rivo/uniseg v0.4.7 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
+	github.com/secure-systems-lab/go-securesystemslib v0.9.0 // indirect
+	github.com/sigstore/fulcio v1.6.6 // indirect
+	github.com/sigstore/protobuf-specs v0.4.1 // indirect
+	github.com/sigstore/rekor v1.3.10 // indirect
+	github.com/sigstore/sigstore v1.9.3 // indirect
+	github.com/smallstep/pkcs7 v0.1.1 // indirect
 	github.com/spiffe/go-spiffe/v2 v2.4.0 // indirect
+	github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 // indirect
 	github.com/stoewer/go-strcase v1.3.0 // indirect
+	github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
+	github.com/ulikunitz/xz v0.5.12 // indirect
+	github.com/vbatts/tar-split v0.12.1 // indirect
+	github.com/vbauerster/mpb/v8 v8.9.3 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
 	github.com/zeebo/errs v1.3.0 // indirect
+	go.mongodb.org/mongo-driver v1.14.0 // indirect
 	go.opencensus.io v0.24.0 // indirect
 	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
 	go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 // indirect
@@ -170,7 +199,7 @@ require (
 	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 // indirect
-	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 // indirect
+	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 // indirect
 	go.opentelemetry.io/otel/exporters/prometheus v0.54.0 // indirect
 	go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 // indirect
 	go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 // indirect
@@ -186,9 +215,9 @@ require (
 	golang.org/x/crypto v0.37.0 // indirect
 	golang.org/x/oauth2 v0.29.0 // indirect
 	golang.org/x/term v0.31.0 // indirect
-	golang.org/x/time v0.7.0 // indirect
+	golang.org/x/time v0.11.0 // indirect
 	golang.org/x/tools v0.31.0 // indirect
-	google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect
+	google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
 	gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
diff --git a/go.sum b/go.sum
index 4897fb488..38535c199 100644
--- a/go.sum
+++ b/go.sum
@@ -3,6 +3,8 @@ al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWt
 cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4=
 cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
+dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
 github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
@@ -16,6 +18,10 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
 github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
 github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg=
 github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y=
+github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
+github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
+github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
+github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
 github.com/akrylysov/pogreb v0.10.2 h1:e6PxmeyEhWyi2AKOBIJzAEi4HkiC+lKyCocRGlnDi78=
 github.com/akrylysov/pogreb v0.10.2/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@@ -32,8 +38,6 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
 github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
-github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70=
-github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
 github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
 github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
 github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
@@ -85,6 +89,8 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
 github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
 github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
 github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
+github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 h1:uX1JmpONuD549D73r6cgnxyUu18Zb7yHAy5AYU0Pm4Q=
+github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -141,16 +147,31 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
 github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
 github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
+github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
+github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
+github.com/go-openapi/errors v0.22.1 h1:kslMRRnK7NCb/CvR1q1VWuEQCEIsBGn5GgKD9e+HYhU=
+github.com/go-openapi/errors v0.22.1/go.mod h1:+n/5UdIqdVnLIJ6Q9Se8HNGUXYaY6CN8ImWzfi/Gzp0=
 github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
 github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
 github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
 github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
+github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco=
+github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs=
+github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ=
+github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc=
+github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
+github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
+github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
+github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
 github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
 github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
+github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
+github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
 github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
-github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
+github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
@@ -188,8 +209,11 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI=
+github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -210,10 +234,10 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
 github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0 h1:kQ0NI7W1B3HwiN5gAYtY+XFItDPbLBwYRxAqbFTyDes=
 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0/go.mod h1:zrT2dxOAjNFPRGjTUe2Xmb4q4YdUwVvQFV6xiCSf+z0=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99 h1:JYghRBlGCZyCF2wNUJ8W0cwaQdtpcssJ4CgC406g+WU=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
 github.com/grpc-ecosystem/grpc-health-probe v0.4.37 h1:x8IIa8JfqiXpBX3xtmm3jz5b9YMq4Lpc9/b+ZAfzRZI=
 github.com/grpc-ecosystem/grpc-health-probe v0.4.37/go.mod h1:uKUtQKoBUvaI4Ez1jaLCHxFYYYoYn/FYkqNgl+8hADw=
 github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
@@ -232,6 +256,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
+github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs=
+github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI=
 github.com/joelanford/ignore v0.1.1 h1:vKky5RDoPT+WbONrbQBgOn95VV/UPh4ejlyAbbzgnQk=
 github.com/joelanford/ignore v0.1.1/go.mod h1:8eho/D8fwQ3rIXrLwE23AaeaGDNXqLE9QJ3zJ4LIPCw=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -255,6 +281,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec h1:2tTW6cDth2TSgRbAhD7yjZzTQmcN25sDRPEeinR51yQ=
+github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec/go.mod h1:TmwEoGCwIti7BCeJ9hescZgRtatxRE+A72pCoPfmcfk=
 github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
 github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
@@ -263,13 +291,19 @@ github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4
 github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
+github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
 github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/maxbrunsfeld/counterfeiter/v6 v6.11.2 h1:yVCLo4+ACVroOEr4iFU1iH46Ldlzz2rTuu18Ra7M8sU=
 github.com/maxbrunsfeld/counterfeiter/v6 v6.11.2/go.mod h1:VzB2VoMh1Y32/QqDfg9ZJYHj99oM4LiGtqPZydTiQSQ=
+github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
+github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
 github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
 github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
 github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
 github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
@@ -298,6 +332,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
 github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
+github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
 github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
 github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
@@ -316,8 +352,6 @@ github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
 github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
 github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
 github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
-github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
-github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -326,6 +360,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
 github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
+github.com/proglottis/gpgme v0.1.4 h1:3nE7YNA70o2aLjcg63tXMOhPD7bplfE5CBdV+hLAm2M=
+github.com/proglottis/gpgme v0.1.4/go.mod h1:5LoXMgpE4bttgwwdv9bLs/vwqv3qV7F4glEEZ7mRKrM=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
@@ -352,21 +388,40 @@ github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnA
 github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
 github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
 github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
 github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
 github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=
+github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
 github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
 github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
+github.com/secure-systems-lab/go-securesystemslib v0.9.0 h1:rf1HIbL64nUpEIZnjLZ3mcNEL9NBPB0iuVjyxvq3LZc=
+github.com/secure-systems-lab/go-securesystemslib v0.9.0/go.mod h1:DVHKMcZ+V4/woA/peqr+L0joiRXbPpQ042GgJckkFgw=
+github.com/sigstore/fulcio v1.6.6 h1:XaMYX6TNT+8n7Npe8D94nyZ7/ERjEsNGFC+REdi/wzw=
+github.com/sigstore/fulcio v1.6.6/go.mod h1:BhQ22lwaebDgIxVBEYOOqLRcN5+xOV+C9bh/GUXRhOk=
+github.com/sigstore/protobuf-specs v0.4.1 h1:5SsMqZbdkcO/DNHudaxuCUEjj6x29tS2Xby1BxGU7Zc=
+github.com/sigstore/protobuf-specs v0.4.1/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc=
+github.com/sigstore/rekor v1.3.10 h1:/mSvRo4MZ/59ECIlARhyykAlQlkmeAQpvBPlmJtZOCU=
+github.com/sigstore/rekor v1.3.10/go.mod h1:JvryKJ40O0XA48MdzYUPu0y4fyvqt0C4iSY7ri9iu3A=
+github.com/sigstore/sigstore v1.9.3 h1:y2qlTj+vh+Or3ictKuR3JUFawZPdDxAjrWkeFhon0OQ=
+github.com/sigstore/sigstore v1.9.3/go.mod h1:VwYkiw0G0dRtwL25KSs04hCyVFF6CYMd/qvNeYrl7EQ=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
 github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/smallstep/pkcs7 v0.1.1 h1:x+rPdt2W088V9Vkjho4KtoggyktZJlMduZAtRHm68LU=
+github.com/smallstep/pkcs7 v0.1.1/go.mod h1:dL6j5AIz9GHjVEBTXtW+QliALcgM19RtXaTeyxI+AfA=
 github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
 github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
 github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
 github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spiffe/go-spiffe/v2 v2.4.0 h1:j/FynG7hi2azrBG5cvjRcnQ4sux/VNj8FAVc99Fl66c=
 github.com/spiffe/go-spiffe/v2 v2.4.0/go.mod h1:m5qJ1hGzjxjtrkGHZupoXHo/FDWwCB1MdSyBzfHugx0=
+github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 h1:pnnLyeX7o/5aX8qUQ69P/mLojDqwda8hFOCBTmP/6hw=
+github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M=
 github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
 github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -383,15 +438,20 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
 github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
+github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0=
+github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs=
 github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
 github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
 github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
 github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
+github.com/vbauerster/mpb/v8 v8.9.3 h1:PnMeF+sMvYv9u23l6DO6Q3+Mdj408mjLRXIzmUmU2Z8=
+github.com/vbauerster/mpb/v8 v8.9.3/go.mod h1:hxS8Hz4C6ijnppDSIX6LjG8FYJSoPo9iIOcE53Zik0c=
 github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
 github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs=
 github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
 go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
@@ -402,6 +462,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.16 h1:ZgY48uH6UvB+/7R9Yf4x574uCO3jIx0TRDyetSf
 go.etcd.io/etcd/client/pkg/v3 v3.5.16/go.mod h1:V8acl8pcEK0Y2g19YlOV9m9ssUe6MgiDSobSoaBAM0E=
 go.etcd.io/etcd/client/v3 v3.5.16 h1:sSmVYOAHeC9doqi0gv7v86oY/BTld0SEFGaxsU9eRhE=
 go.etcd.io/etcd/client/v3 v3.5.16/go.mod h1:X+rExSGkyqxvu276cr2OwPLBaeqFu1cIl4vmRjAD/50=
+go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
+go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
 go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
 go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
 go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
@@ -410,8 +472,8 @@ go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGh
 go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk=
 go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4=
 go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
 go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
@@ -428,8 +490,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx
 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0=
 go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU=
 go.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU=
 go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 h1:CHXNXwfKWfzS65yrlB2PVds1IBZcdsX8Vepy9of0iRU=
@@ -466,6 +528,11 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
 golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
 golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -477,6 +544,11 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
 golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -490,7 +562,14 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
 golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
 golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -503,6 +582,12 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
 golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -515,20 +600,44 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
 golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
+golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
 golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
 golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
 golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
 golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
-golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
-golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
+golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -538,6 +647,10 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
 golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
 golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -549,8 +662,8 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU=
-google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4=
+google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE=
+google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE=
 google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
 google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c=
@@ -617,6 +730,8 @@ k8s.io/kubectl v0.32.0 h1:rpxl+ng9qeG79YA4Em9tLSfX0G8W0vfaiPVrc/WR7Xw=
 k8s.io/kubectl v0.32.0/go.mod h1:qIjSX+QgPQUgdy8ps6eKsYNF+YmFOAO3WygfucIqFiE=
 k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
 k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c=
+oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 h1:CPT0ExVicCzcpeN4baWEV2ko2Z/AsiZgEdwgcfwLgMo=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
 sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU=
diff --git a/internal/testutil/image/registry.go b/internal/testutil/image/registry.go
new file mode 100644
index 000000000..736cb003d
--- /dev/null
+++ b/internal/testutil/image/registry.go
@@ -0,0 +1,35 @@
+package image
+
+import (
+	"context"
+	"net/http"
+	"net/http/httptest"
+
+	"github.com/distribution/distribution/v3/configuration"
+	"github.com/distribution/distribution/v3/registry/handlers"
+	_ "github.com/distribution/distribution/v3/registry/storage/driver/filesystem" // Driver for persisting docker image data to the filesystem.
+	_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"   // Driver for keeping docker image data in memory.
+)
+
+// RunDockerRegistry runs a docker registry on an available port and returns its host string if successful, otherwise it returns an error.
+// If the rootDir argument isn't empty, the registry is configured to use this as the root directory for persisting image data to the filesystem.
+// If the rootDir argument is empty, the registry is configured to keep image data in memory.
+func RunDockerRegistry(ctx context.Context, rootDir string, middlewares ...func(http.Handler) http.Handler) *httptest.Server {
+	config := &configuration.Configuration{}
+	config.Log.Level = "error"
+
+	if rootDir != "" {
+		config.Storage = map[string]configuration.Parameters{"filesystem": map[string]interface{}{
+			"rootdirectory": rootDir,
+		}}
+	} else {
+		config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}}
+	}
+
+	var dockerRegistryApp http.Handler = handlers.NewApp(ctx, config)
+	for _, m := range middlewares {
+		dockerRegistryApp = m(dockerRegistryApp)
+	}
+	server := httptest.NewTLSServer(dockerRegistryApp)
+	return server
+}
diff --git a/pkg/image/containersimageregistry/registry.go b/pkg/image/containersimageregistry/registry.go
new file mode 100644
index 000000000..f3452106d
--- /dev/null
+++ b/pkg/image/containersimageregistry/registry.go
@@ -0,0 +1,287 @@
+package containersimageregistry
+
+import (
+	"archive/tar"
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/containerd/containerd/archive"
+	"github.com/containers/common/pkg/auth"
+	"github.com/containers/image/v5/copy"
+	"github.com/containers/image/v5/docker"
+	"github.com/containers/image/v5/docker/reference"
+	"github.com/containers/image/v5/image"
+	"github.com/containers/image/v5/oci/layout"
+	"github.com/containers/image/v5/pkg/compression"
+	"github.com/containers/image/v5/pkg/docker/config"
+	"github.com/containers/image/v5/signature"
+	"github.com/containers/image/v5/types"
+	dockerconfig "github.com/docker/cli/cli/config"
+	"oras.land/oras-go/v2/content/oci"
+
+	orimage "github.com/operator-framework/operator-registry/pkg/image"
+)
+
+var _ orimage.Registry = (*Registry)(nil)
+
+type Registry struct {
+	sourceCtx *types.SystemContext
+	cache     *cacheConfig
+}
+
+var DefaultSystemContext = &types.SystemContext{OSChoice: "linux"}
+
+func New(sourceCtx *types.SystemContext, opts ...Option) (orimage.Registry, error) {
+	if sourceCtx == nil {
+		sourceCtx = &types.SystemContext{}
+	}
+	reg := &Registry{
+		sourceCtx: sourceCtx,
+	}
+
+	for _, opt := range opts {
+		if err := opt(reg); err != nil {
+			return nil, err
+		}
+	}
+
+	if reg.cache == nil {
+		var err error
+		reg.cache, err = getDefaultImageCache()
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return reg, nil
+}
+
+func NewDefault() (orimage.Registry, error) {
+	return New(DefaultSystemContext)
+}
+
+type cacheConfig struct {
+	baseDir  string
+	preserve bool
+}
+
+func (c *cacheConfig) ociLayoutDir() string {
+	return filepath.Join(c.baseDir, "oci-layout")
+}
+func (c *cacheConfig) blobInfoCacheDir() string {
+	return filepath.Join(c.baseDir, "blob-info-cache")
+}
+
+func (c *cacheConfig) getSystemContext() *types.SystemContext {
+	return &types.SystemContext{
+		BlobInfoCacheDir: c.blobInfoCacheDir(),
+	}
+}
+
+type Option func(*Registry) error
+
+func getDefaultImageCache() (*cacheConfig, error) {
+	if dir := os.Getenv("OLM_CACHE_DIR"); dir != "" {
+		return newCacheConfig(filepath.Join(dir, "images"), true), nil
+	}
+	return getTemporaryImageCache()
+}
+
+func getTemporaryImageCache() (*cacheConfig, error) {
+	tmpDir, err := os.MkdirTemp("", "opm-containers-image-cache-")
+	if err != nil {
+		return nil, err
+	}
+	return newCacheConfig(tmpDir, false), nil
+}
+
+func newCacheConfig(dir string, preserve bool) *cacheConfig {
+	return &cacheConfig{
+		baseDir:  dir,
+		preserve: preserve,
+	}
+}
+
+func WithTemporaryImageCache() Option {
+	return func(r *Registry) error {
+		var err error
+		r.cache, err = getTemporaryImageCache()
+		if err != nil {
+			return err
+		}
+		return nil
+	}
+}
+
+func WithInsecureSkipTLSVerify(insecureSkipTLSVerify bool) Option {
+	return func(r *Registry) error {
+		r.sourceCtx.DockerDaemonInsecureSkipTLSVerify = insecureSkipTLSVerify
+		r.sourceCtx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(insecureSkipTLSVerify)
+		r.sourceCtx.OCIInsecureSkipTLSVerify = insecureSkipTLSVerify
+		return nil
+	}
+}
+
+func (r *Registry) Pull(ctx context.Context, ref orimage.Reference) error {
+	namedRef, err := reference.ParseNamed(ref.String())
+	if err != nil {
+		return err
+	}
+	dockerRef, err := docker.NewReference(namedRef)
+	if err != nil {
+		return err
+	}
+
+	if err := os.MkdirAll(r.cache.ociLayoutDir(), 0700); err != nil {
+		return err
+	}
+	ociLayoutRef, err := layout.NewReference(r.cache.ociLayoutDir(), ref.String())
+	if err != nil {
+		return err
+	}
+
+	policy, err := signature.DefaultPolicy(r.sourceCtx)
+	if err != nil {
+		return err
+	}
+	policyContext, err := signature.NewPolicyContext(policy)
+	if err != nil {
+		return err
+	}
+
+	sourceCtx := r.sourceCtx
+	authFile := getAuthFile(r.sourceCtx, namedRef.String())
+	if authFile != "" {
+		sourceCtx.AuthFilePath = authFile
+	}
+
+	if _, err := copy.Image(ctx, policyContext, ociLayoutRef, dockerRef, &copy.Options{
+		SourceCtx:                             sourceCtx,
+		DestinationCtx:                        r.cache.getSystemContext(),
+		OptimizeDestinationImageAlreadyExists: true,
+	}); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (r *Registry) Unpack(ctx context.Context, ref orimage.Reference, unpackDir string) error {
+	ociLayoutRef, err := layout.NewReference(r.cache.ociLayoutDir(), ref.String())
+	if err != nil {
+		return fmt.Errorf("could not create oci layout reference: %w", err)
+	}
+
+	ociLayoutCtx := r.cache.getSystemContext()
+	imageSource, err := ociLayoutRef.NewImageSource(ctx, ociLayoutCtx)
+	if err != nil {
+		return fmt.Errorf("failed to create oci image source: %v", err)
+	}
+	defer imageSource.Close()
+
+	img, err := image.FromSource(ctx, ociLayoutCtx, imageSource)
+	if err != nil {
+		return fmt.Errorf("could not get image from oci image source: %v", err)
+	}
+
+	if err := os.MkdirAll(unpackDir, 0700); err != nil {
+		return err
+	}
+
+	for _, info := range img.LayerInfos() {
+		if err := func() error {
+			layer, _, err := imageSource.GetBlob(ctx, info, nil)
+			if err != nil {
+				return fmt.Errorf("failed to get blob: %v", err)
+			}
+			defer layer.Close()
+
+			decompressed, _, err := compression.AutoDecompress(layer)
+			if err != nil {
+				return fmt.Errorf("failed to decompress layer: %v", err)
+			}
+
+			if _, err := archive.Apply(ctx, unpackDir, decompressed, archive.WithFilter(func(th *tar.Header) (bool, error) {
+				th.PAXRecords = nil
+				th.Xattrs = nil //nolint:staticcheck
+				th.Uid = os.Getuid()
+				th.Gid = os.Getgid()
+				th.Mode = 0600
+				if th.FileInfo().IsDir() {
+					th.Mode = 0700
+				}
+				return true, nil
+			})); err != nil {
+				return fmt.Errorf("failed to apply layer: %v", err)
+			}
+			return nil
+		}(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (r *Registry) Labels(ctx context.Context, ref orimage.Reference) (map[string]string, error) {
+	ociLayoutRef, err := layout.NewReference(r.cache.ociLayoutDir(), ref.String())
+	if err != nil {
+		return nil, fmt.Errorf("could not create oci layout reference: %w", err)
+	}
+
+	ociLayoutCtx := r.cache.getSystemContext()
+	img, err := ociLayoutRef.NewImage(ctx, ociLayoutCtx)
+	if err != nil {
+		return nil, fmt.Errorf("could not load image from oci image reference: %v", err)
+	}
+	imgConfig, err := img.OCIConfig(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("could not get oci config from image: %v", err)
+	}
+	return imgConfig.Config.Labels, nil
+}
+
+func (r *Registry) Destroy() error {
+	if !r.cache.preserve {
+		return os.RemoveAll(r.cache.baseDir)
+	}
+
+	store, err := oci.NewWithContext(context.TODO(), r.cache.ociLayoutDir())
+	if err != nil {
+		return fmt.Errorf("open cache for garbage collection: %v", err)
+	}
+	if err := store.GC(context.TODO()); err != nil {
+		return fmt.Errorf("garbage collection failed: %v", err)
+	}
+	return nil
+}
+
+// This is a slight variation on the auth.GetDefaultAuthFile function provided by containers/image.
+// The reason for this variation is so that this image registry implementation can be used as a drop-in
+// replacement for our existing containerd-based image registry client, and remain compatible with current
+// behavior.
+func getAuthFile(sourceCtx *types.SystemContext, ref string) string {
+	// By default, we will use the docker config file in the standard docker config directory.
+	// However, if REGISTRY_AUTH_FILE or DOCKER_CONFIG environment variables are set, we will
+	// use those (in that order) instead to derive the auth config file.
+	authFile := filepath.Join(dockerconfig.Dir(), dockerconfig.ConfigFileName)
+	if defaultAuthFile := auth.GetDefaultAuthFile(); defaultAuthFile != "" {
+		authFile = defaultAuthFile
+	}
+
+	// In order to maintain backward-compatibility with the original credential getter from
+	// the containerd registry implementation, we will first try to get the credentials from
+	// the auth config file we derived above, if it exists. If we find a matching credential
+	// in this file, we'll use this file.
+	if stat, statErr := os.Stat(authFile); statErr == nil && stat.Mode().IsRegular() {
+		if _, err := config.GetCredentials(&types.SystemContext{AuthFilePath: authFile}, ref); err == nil {
+			return authFile
+		}
+	}
+	// If the auth file was unset, doesn't exist, or if we couldn't find credentials in it,
+	// we'll use system defaults from containers/image (podman/skopeo) to lookup the credentials.
+	if sourceCtx != nil && sourceCtx.AuthFilePath != "" {
+		return sourceCtx.AuthFilePath
+	}
+	return ""
+}
diff --git a/pkg/image/registry_test.go b/pkg/image/registry_test.go
index ac1df7ca4..4fc638c8f 100644
--- a/pkg/image/registry_test.go
+++ b/pkg/image/registry_test.go
@@ -4,53 +4,83 @@ import (
 	"context"
 	"crypto/rand"
 	"crypto/x509"
+	"encoding/pem"
 	"errors"
 	"fmt"
 	"io"
 	"math"
 	"math/big"
 	"net/http"
+	"net/url"
 	"os"
+	"path/filepath"
 	"sync"
 	"testing"
 
-	distribution "github.com/distribution/distribution/v3"
-	"github.com/distribution/distribution/v3/configuration"
-	repositorymiddleware "github.com/distribution/distribution/v3/registry/middleware/repository"
+	"github.com/containers/image/v5/types"
+	"github.com/distribution/distribution/v3"
 	"github.com/distribution/reference"
 	"github.com/opencontainers/go-digest"
 	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/mod/sumdb/dirhash"
 
+	libimage "github.com/operator-framework/operator-registry/internal/testutil/image"
 	"github.com/operator-framework/operator-registry/pkg/image"
 	"github.com/operator-framework/operator-registry/pkg/image/containerdregistry"
-	libimage "github.com/operator-framework/operator-registry/pkg/lib/image"
+	"github.com/operator-framework/operator-registry/pkg/image/containersimageregistry"
 )
 
 // cleanupFunc is a function that cleans up after some test infra.
 type cleanupFunc func()
 
 // newRegistryFunc is a function that creates and returns a new image.Registry to test its cleanupFunc.
-type newRegistryFunc func(t *testing.T, cafile string) (image.Registry, cleanupFunc)
+type newRegistryFunc func(t *testing.T, serverCert *x509.Certificate) (image.Registry, cleanupFunc)
 
-func poolForCertFile(t *testing.T, file string) *x509.CertPool {
-	rootCAs := x509.NewCertPool()
-	certs, err := os.ReadFile(file)
+func caDirForCert(t *testing.T, serverCert *x509.Certificate) string {
+	caDir, err := os.MkdirTemp("", "opm-registry-test-ca-")
+	require.NoError(t, err)
+	caFile, err := os.Create(filepath.Join(caDir, "ca.crt"))
 	require.NoError(t, err)
-	require.True(t, rootCAs.AppendCertsFromPEM(certs))
+
+	require.NoError(t, pem.Encode(caFile, &pem.Block{
+		Type:  "CERTIFICATE",
+		Bytes: serverCert.Raw,
+	}))
+	require.NoError(t, caFile.Close())
+	return caDir
+}
+
+func poolForCert(serverCert *x509.Certificate) *x509.CertPool {
+	rootCAs := x509.NewCertPool()
+	rootCAs.AddCert(serverCert)
 	return rootCAs
 }
 
 func TestRegistries(t *testing.T) {
 	registries := map[string]newRegistryFunc{
-		"containerd": func(t *testing.T, cafile string) (image.Registry, cleanupFunc) {
+		"containersimage": func(t *testing.T, serverCert *x509.Certificate) (image.Registry, cleanupFunc) {
+			caDir := caDirForCert(t, serverCert)
+			sourceCtx := &types.SystemContext{
+				OCICertPath:              caDir,
+				DockerCertPath:           caDir,
+				DockerPerHostCertDirPath: caDir,
+			}
+			r, err := containersimageregistry.New(sourceCtx, containersimageregistry.WithTemporaryImageCache())
+			require.NoError(t, err)
+			cleanup := func() {
+				require.NoError(t, os.RemoveAll(caDir))
+				require.NoError(t, r.Destroy())
+			}
+			return r, cleanup
+		},
+		"containerd": func(t *testing.T, serverCert *x509.Certificate) (image.Registry, cleanupFunc) {
 			val, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
 			require.NoError(t, err)
 			r, err := containerdregistry.NewRegistry(
 				containerdregistry.WithLog(logrus.New().WithField("test", t.Name())),
 				containerdregistry.WithCacheDir(fmt.Sprintf("cache-%x", val)),
-				containerdregistry.WithRootCAs(poolForCertFile(t, cafile)),
+				containerdregistry.WithRootCAs(poolForCert(serverCert)),
 			)
 			require.NoError(t, err)
 			cleanup := func() {
@@ -59,30 +89,6 @@ func TestRegistries(t *testing.T) {
 
 			return r, cleanup
 		},
-		// TODO: enable docker tests - currently blocked on a cross-platform way to configure either insecure registries
-		// or CA certs
-		//"docker": func(t *testing.T, cafile string) (image.Registry, cleanupFunc) {
-		//	r, err := execregistry.NewRegistry(containertools.DockerTool,
-		//		logrus.New().WithField("test", t.Name()),
-		//		cafile,
-		//	)
-		//	require.NoError(t, err)
-		//	cleanup := func() {
-		//		require.NoError(t, r.Destroy())
-		//	}
-		//
-		//	return r, cleanup
-		//},
-		// TODO: Enable buildah tests
-		// func(t *testing.T) image.Registry {
-		// 	r, err := buildahregistry.NewRegistry(
-		// 		buildahregistry.WithLog(logrus.New().WithField("test", t.Name())),
-		// 		buildahregistry.WithCacheDir(fmt.Sprintf("cache-%x", rand.Int())),
-		// 	)
-		// 	require.NoError(t, err)
-
-		// 	return r
-		// },
 	}
 
 	for name, registry := range registries {
@@ -90,6 +96,18 @@ func TestRegistries(t *testing.T) {
 	}
 }
 
+type httpError struct {
+	statusCode int
+	error      error
+}
+
+func (e *httpError) Error() string {
+	if e.error != nil {
+		return e.error.Error()
+	}
+	return http.StatusText(e.statusCode)
+}
+
 func testPullAndUnpack(t *testing.T, name string, newRegistry newRegistryFunc) {
 	type args struct {
 		dockerRootDir string
@@ -100,7 +118,18 @@ func testPullAndUnpack(t *testing.T, name string, newRegistry newRegistryFunc) {
 	type expected struct {
 		checksum      string
 		pullAssertion require.ErrorAssertionFunc
+		labels        map[string]string
 	}
+
+	expectedLabels := map[string]string{
+		"operators.operatorframework.io.bundle.mediatype.v1":       "registry+v1",
+		"operators.operatorframework.io.bundle.manifests.v1":       "manifests/",
+		"operators.operatorframework.io.bundle.metadata.v1":        "metadata/",
+		"operators.operatorframework.io.bundle.package.v1":         "kiali",
+		"operators.operatorframework.io.bundle.channels.v1":        "stable,alpha",
+		"operators.operatorframework.io.bundle.channel.default.v1": "stable",
+	}
+
 	tests := []struct {
 		description string
 		args        args
@@ -114,6 +143,7 @@ func testPullAndUnpack(t *testing.T, name string, newRegistry newRegistryFunc) {
 			},
 			expected: expected{
 				checksum:      dirChecksum(t, "testdata/golden/bundles/kiali"),
+				labels:        expectedLabels,
 				pullAssertion: require.NoError,
 			},
 		},
@@ -125,6 +155,7 @@ func testPullAndUnpack(t *testing.T, name string, newRegistry newRegistryFunc) {
 			},
 			expected: expected{
 				checksum:      dirChecksum(t, "testdata/golden/bundles/kiali"),
+				labels:        expectedLabels,
 				pullAssertion: require.NoError,
 			},
 		},
@@ -134,10 +165,11 @@ func testPullAndUnpack(t *testing.T, name string, newRegistry newRegistryFunc) {
 				dockerRootDir: "testdata/golden",
 				img:           "/olmtest/kiali:1.4.2",
 				pullErrCount:  1,
-				pullErr:       errors.New("dummy"),
+				pullErr:       &httpError{statusCode: http.StatusTooManyRequests},
 			},
 			expected: expected{
 				checksum:      dirChecksum(t, "testdata/golden/bundles/kiali"),
+				labels:        expectedLabels,
 				pullAssertion: require.NoError,
 			},
 		},
@@ -158,7 +190,7 @@ func testPullAndUnpack(t *testing.T, name string, newRegistry newRegistryFunc) {
 				dockerRootDir: "testdata/golden",
 				img:           "/olmtest/kiali:1.4.2",
 				pullErrCount:  math.MaxInt64,
-				pullErr:       errors.New("dummy"),
+				pullErr:       &httpError{statusCode: http.StatusTooManyRequests},
 			},
 			expected: expected{
 				pullAssertion: require.Error,
@@ -168,51 +200,43 @@ func testPullAndUnpack(t *testing.T, name string, newRegistry newRegistryFunc) {
 	for _, tt := range tests {
 		t.Run(tt.description, func(t *testing.T) {
 			logrus.SetLevel(logrus.DebugLevel)
-			ctx, close := context.WithCancel(context.Background())
-			defer close()
-
-			configOpts := []libimage.ConfigOpt{}
+			ctx, cancel := context.WithCancel(context.Background())
+			defer cancel()
 
+			var middlewares []func(next http.Handler) http.Handler
 			if tt.args.pullErrCount > 0 {
-				configOpts = append(configOpts, func(config *configuration.Configuration) {
-					if config.Middleware == nil {
-						config.Middleware = make(map[string][]configuration.Middleware)
-					}
-
-					mockRepo := &mockRepo{blobStore: &mockBlobStore{
-						maxCount: tt.args.pullErrCount,
-						err:      tt.args.pullErr,
-					}}
-					val, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
-					require.NoError(t, err)
-
-					middlewareName := fmt.Sprintf("test-%x", val)
-					require.NoError(t, repositorymiddleware.Register(middlewareName, mockRepo.init))
-					config.Middleware["repository"] = append(config.Middleware["repository"], configuration.Middleware{
-						Name: middlewareName,
-					})
-				})
+				middlewares = append(middlewares, failureMiddleware(tt.args.pullErrCount, tt.args.pullErr))
 			}
 
-			host, cafile, err := libimage.RunDockerRegistry(ctx, tt.args.dockerRootDir, configOpts...)
-			require.NoError(t, err)
+			dockerServer := libimage.RunDockerRegistry(ctx, tt.args.dockerRootDir, middlewares...)
+			defer dockerServer.Close()
 
-			r, cleanup := newRegistry(t, cafile)
+			r, cleanup := newRegistry(t, dockerServer.Certificate())
 			defer cleanup()
 
-			ref := image.SimpleReference(host + tt.args.img)
-			tt.expected.pullAssertion(t, r.Pull(ctx, ref))
+			url, err := url.Parse(dockerServer.URL)
+			require.NoError(t, err)
 
-			if tt.expected.checksum != "" {
-				// Copy golden manifests to a temp dir
-				dir := "kiali-unpacked"
-				require.NoError(t, r.Unpack(ctx, ref, dir))
+			ref := image.SimpleReference(fmt.Sprintf("%s%s", url.Host, tt.args.img))
+			t.Log("pulling image", ref)
+			pullErr := r.Pull(ctx, ref)
+			tt.expected.pullAssertion(t, pullErr)
+			if pullErr != nil {
+				return
+			}
 
-				checksum := dirChecksum(t, dir)
-				require.Equal(t, tt.expected.checksum, checksum)
+			labels, err := r.Labels(ctx, ref)
+			require.NoError(t, err)
+			require.Equal(t, tt.expected.labels, labels)
 
-				require.NoError(t, os.RemoveAll(dir))
-			}
+			// Copy golden manifests to a temp dir
+			dir := "kiali-unpacked"
+			require.NoError(t, r.Unpack(ctx, ref, dir))
+
+			checksum := dirChecksum(t, dir)
+			require.Equal(t, tt.expected.checksum, checksum)
+
+			require.NoError(t, os.RemoveAll(dir))
 		})
 	}
 }
@@ -303,3 +327,24 @@ func (f *mockBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter, r
 func (f *mockBlobStore) Delete(ctx context.Context, dgst digest.Digest) error {
 	return f.base.Delete(ctx, dgst)
 }
+
+func failureMiddleware(totalCount int, err error) func(next http.Handler) http.Handler {
+	return func(next http.Handler) http.Handler {
+		count := 0
+		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			if count >= totalCount {
+				next.ServeHTTP(w, r)
+				return
+			}
+			count++
+			statusCode := http.StatusInternalServerError
+
+			var httpErr *httpError
+			if errors.As(err, &httpErr) {
+				statusCode = httpErr.statusCode
+			}
+
+			http.Error(w, err.Error(), statusCode)
+		})
+	}
+}
diff --git a/pkg/lib/image/registry.go b/pkg/lib/image/registry.go
deleted file mode 100644
index c535379ae..000000000
--- a/pkg/lib/image/registry.go
+++ /dev/null
@@ -1,218 +0,0 @@
-package image
-
-import (
-	"bytes"
-	"context"
-	"crypto/ecdsa"
-	"crypto/elliptic"
-	"crypto/rand"
-	"crypto/tls"
-	"crypto/x509"
-	"crypto/x509/pkix"
-	"encoding/pem"
-	"fmt"
-	"io"
-	"math/big"
-	"net"
-	"net/http"
-	"os"
-	"time"
-
-	"github.com/distribution/distribution/v3/configuration"
-	"github.com/distribution/distribution/v3/registry"
-	_ "github.com/distribution/distribution/v3/registry/storage/driver/filesystem" // Driver for persisting docker image data to the filesystem.
-	_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"   // Driver for keeping docker image data in memory.
-	"github.com/phayes/freeport"
-	"k8s.io/apimachinery/pkg/util/wait"
-)
-
-type ConfigOpt func(*configuration.Configuration)
-
-// RunDockerRegistry runs a docker registry on an available port and returns its host string if successful, otherwise it returns an error.
-// If the rootDir argument isn't empty, the registry is configured to use this as the root directory for persisting image data to the filesystem.
-// If the rootDir argument is empty, the registry is configured to keep image data in memory.
-func RunDockerRegistry(ctx context.Context, rootDir string, configOpts ...ConfigOpt) (string, string, error) {
-	dockerPort, err := freeport.GetFreePort()
-	if err != nil {
-		return "", "", err
-	}
-	host := fmt.Sprintf("localhost:%d", dockerPort)
-	certPool := x509.NewCertPool()
-
-	cafile, err := os.CreateTemp("", "ca")
-	if err != nil {
-		return "", "", err
-	}
-	certfile, err := os.CreateTemp("", "cert")
-	if err != nil {
-		return "", "", err
-	}
-	keyfile, err := os.CreateTemp("", "key")
-	if err != nil {
-		return "", "", err
-	}
-	if err := GenerateCerts(cafile, certfile, keyfile, certPool); err != nil {
-		return "", "", err
-	}
-	if err := cafile.Close(); err != nil {
-		return "", "", err
-	}
-	if err := certfile.Close(); err != nil {
-		return "", "", err
-	}
-	if err := keyfile.Close(); err != nil {
-		return "", "", err
-	}
-
-	config := &configuration.Configuration{}
-	config.HTTP.Addr = host
-	config.HTTP.TLS.Certificate = certfile.Name()
-	config.HTTP.TLS.Key = keyfile.Name()
-	config.Log.Level = "error"
-
-	if rootDir != "" {
-		config.Storage = map[string]configuration.Parameters{"filesystem": map[string]interface{}{
-			"rootdirectory": rootDir,
-		}}
-	} else {
-		config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}}
-	}
-	config.HTTP.DrainTimeout = 2 * time.Second
-
-	for _, opt := range configOpts {
-		opt(config)
-	}
-
-	dockerRegistry, err := registry.NewRegistry(ctx, config)
-	if err != nil {
-		return "", "", err
-	}
-
-	go func() {
-		defer func() {
-			os.Remove(cafile.Name())
-			os.Remove(certfile.Name())
-			os.Remove(keyfile.Name())
-		}()
-		if err := dockerRegistry.ListenAndServe(); err != nil {
-			panic(fmt.Errorf("docker registry stopped listening: %v", err))
-		}
-	}()
-
-	// nolint:staticcheck
-	err = wait.Poll(100*time.Millisecond, 10*time.Second, func() (bool, error) {
-		tr := &http.Transport{TLSClientConfig: &tls.Config{
-			InsecureSkipVerify: false,
-			RootCAs:            certPool,
-			MinVersion:         tls.VersionTLS12,
-		}}
-		client := &http.Client{Transport: tr}
-		r, err := client.Get("https://" + host + "/v2/")
-		if err != nil {
-			return false, nil
-		}
-		defer r.Body.Close()
-		if r.StatusCode == http.StatusOK {
-			return true, nil
-		}
-		return false, nil
-	})
-	if err != nil {
-		return "", "", err
-	}
-
-	// Return the registry host string
-	return host, cafile.Name(), nil
-}
-
-func certToPem(der []byte) ([]byte, error) {
-	out := &bytes.Buffer{}
-	if err := pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: der}); err != nil {
-		return nil, err
-	}
-	return out.Bytes(), nil
-}
-
-func keyToPem(key *ecdsa.PrivateKey) ([]byte, error) {
-	b, err := x509.MarshalECPrivateKey(key)
-	if err != nil {
-		return nil, fmt.Errorf("unable to marshal private key: %v", err)
-	}
-	out := &bytes.Buffer{}
-	if err := pem.Encode(out, &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}); err != nil {
-		return nil, err
-	}
-	return out.Bytes(), nil
-}
-
-func GenerateCerts(caWriter, certWriter, keyWriter io.Writer, pool *x509.CertPool) error {
-	priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
-	if err != nil {
-		return err
-	}
-	ca := x509.Certificate{
-		SerialNumber: big.NewInt(1),
-		Subject: pkix.Name{
-			Organization: []string{"test ca"},
-		},
-		NotBefore: time.Now(),
-		NotAfter:  time.Now().Add(time.Hour),
-
-		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
-		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
-		BasicConstraintsValid: true,
-		IsCA:                  true,
-	}
-	cert := x509.Certificate{
-		SerialNumber: big.NewInt(2),
-		Subject: pkix.Name{
-			Organization: []string{"test cert"},
-		},
-		NotBefore: time.Now(),
-		NotAfter:  time.Now().Add(time.Hour),
-
-		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
-		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
-		BasicConstraintsValid: true,
-		IPAddresses: []net.IP{
-			net.ParseIP("127.0.0.1"),
-			net.ParseIP("::1"),
-		},
-		DNSNames: []string{"localhost"},
-	}
-
-	caBytes, err := x509.CreateCertificate(rand.Reader, &ca, &ca, &priv.PublicKey, priv)
-	if err != nil {
-		return err
-	}
-
-	caFile, err := certToPem(caBytes)
-	if err != nil {
-		return err
-	}
-	if _, err := caWriter.Write(caFile); err != nil {
-		return err
-	}
-	pool.AppendCertsFromPEM(caFile)
-
-	certBytes, err := x509.CreateCertificate(rand.Reader, &cert, &ca, &priv.PublicKey, priv)
-	if err != nil {
-		return err
-	}
-	certFile, err := certToPem(certBytes)
-	if err != nil {
-		return err
-	}
-	if _, err := certWriter.Write(certFile); err != nil {
-		return err
-	}
-
-	keyFile, err := keyToPem(priv)
-	if err != nil {
-		return err
-	}
-	if _, err := keyWriter.Write(keyFile); err != nil {
-		return err
-	}
-	return nil
-}
diff --git a/release/goreleaser.darwin.yaml b/release/goreleaser.darwin.yaml
index b60071040..d0d47ff79 100644
--- a/release/goreleaser.darwin.yaml
+++ b/release/goreleaser.darwin.yaml
@@ -10,7 +10,7 @@ builds:
       - CGO_ENABLED=1
     mod_timestamp: "{{ .CommitTimestamp }}"
     flags: &build-flags
-      - -tags=json1
+      - -tags=json1,containers_image_openpgp
     asmflags: &build-asmflags
       - all=-trimpath={{ .Env.PWD }}
     gcflags: &build-gcflags
diff --git a/release/goreleaser.linux.yaml b/release/goreleaser.linux.yaml
index 2f69f8dd5..2822d1d30 100644
--- a/release/goreleaser.linux.yaml
+++ b/release/goreleaser.linux.yaml
@@ -11,7 +11,7 @@ builds:
       - CGO_ENABLED=1
     mod_timestamp: "{{ .CommitTimestamp }}"
     flags: &build-flags
-      - -tags=json1,netgo,osusergo
+      - -tags=json1,netgo,osusergo,containers_image_openpgp
     asmflags: &build-asmflags
       - all=-trimpath={{ .Env.PWD }}
     gcflags: &build-gcflags
diff --git a/release/goreleaser.windows.yaml b/release/goreleaser.windows.yaml
index 4232f5eea..b35be4c7a 100644
--- a/release/goreleaser.windows.yaml
+++ b/release/goreleaser.windows.yaml
@@ -10,7 +10,7 @@ builds:
       - CGO_ENABLED=1
     mod_timestamp: "{{ .CommitTimestamp }}"
     flags: &build-flags
-      - -tags=json1,netgo,osusergo
+      - -tags=json1,netgo,osusergo,containers_image_openpgp
     asmflags: &build-asmflags
       - all=-trimpath={{ .Env.PWD }}
     gcflags: &build-gcflags
diff --git a/test/e2e/opm_bundle_test.go b/test/e2e/opm_bundle_test.go
index b1161fea6..f761261e8 100644
--- a/test/e2e/opm_bundle_test.go
+++ b/test/e2e/opm_bundle_test.go
@@ -3,7 +3,10 @@ package e2e_test
 import (
 	"bytes"
 	"context"
+	"encoding/pem"
 	"io"
+	"net/http/httptest"
+	"net/url"
 	"os"
 	"path/filepath"
 
@@ -12,7 +15,7 @@ import (
 
 	"golang.org/x/mod/sumdb/dirhash"
 
-	libimage "github.com/operator-framework/operator-registry/pkg/lib/image"
+	libimage "github.com/operator-framework/operator-registry/internal/testutil/image"
 )
 
 var _ = Describe("opm alpha bundle", func() {
@@ -32,29 +35,34 @@ var _ = Describe("opm alpha bundle", func() {
 			bundleChecksum string
 			tmpDir         string
 			rootCA         string
-			stopRegistry   func()
+			server         *httptest.Server
 		)
 
 		BeforeEach(func() {
 			ctx, cancel := context.WithCancel(context.Background())
-			stopRegistry = func() {
-				cancel()
-				<-ctx.Done()
-			}
+			defer cancel()
 
 			// Spin up an in-process docker registry with a set of preconfigured test images
 			var (
 				// Directory containing the docker registry filesystem
 				goldenFiles = "../../pkg/image/testdata/golden"
-
-				host string
-				err  error
 			)
-			host, rootCA, err = libimage.RunDockerRegistry(ctx, goldenFiles)
-			Expect(err).ToNot(HaveOccurred())
+			server := libimage.RunDockerRegistry(ctx, goldenFiles)
+			serverURL, err := url.Parse(server.URL)
+			Expect(err).NotTo(HaveOccurred())
+
+			caFile, err := os.CreateTemp("", "ca")
+			Expect(err).NotTo(HaveOccurred())
+
+			Expect(pem.Encode(caFile, &pem.Block{
+				Type:  "CERTIFICATE",
+				Bytes: server.Certificate().Raw,
+			})).To(Succeed())
+			rootCA = caFile.Name()
+			caFile.Close()
 
 			// Create a bundle ref using the local registry host name and the namespace/name of a bundle we already know the content of
-			bundleRef = host + "/" + imageDomain + "/kiali@sha256:a1bec450c104ceddbb25b252275eb59f1f1e6ca68e0ced76462042f72f7057d8"
+			bundleRef = serverURL.Host + imageDomain + "/kiali@sha256:a1bec450c104ceddbb25b252275eb59f1f1e6ca68e0ced76462042f72f7057d8"
 
 			// Generate a checksum of the expected content for the bundle under test
 			bundleChecksum, err = dirhash.HashDir(filepath.Join(goldenFiles, "bundles/kiali"), "", dirhash.DefaultHash)
@@ -66,12 +74,13 @@ var _ = Describe("opm alpha bundle", func() {
 		})
 
 		AfterEach(func() {
-			stopRegistry()
+			server.Close()
 			if CurrentGinkgoTestDescription().Failed {
 				// Skip additional cleanup
 				return
 			}
 
+			Expect(os.RemoveAll(rootCA)).To(Succeed())
 			Expect(os.RemoveAll(tmpDir)).To(Succeed())
 		})
 


Reviews:
user: login: grokspawn
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T14:31:41Z
user: login: joelanford
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T14:48:21Z
user: login: joelanford
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T13:20:48Z
user: login: grokspawn
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T18:57:20Z
user: login: grokspawn
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T18:57:59Z
user: login: grokspawn
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T19:47:43Z
user: login: grokspawn
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T19:51:21Z
user: login: grokspawn
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T20:33:22Z
user: login: grokspawn
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T20:39:38Z
user: login: joelanford
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T00:19:05Z
user: login: joelanford
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T00:24:04Z
user: login: joelanford
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T00:25:56Z
user: login: joelanford
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T00:30:28Z
user: login: joelanford
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T00:47:16Z
user: login: joelanford
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T00:50:33Z
user: login: grokspawn
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T14:41:24Z
user: login: grokspawn
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T14:41:41Z

Review comments:
diff_hunk: @@ -102,8 +100,7 @@ image-upstream:
 	docker build -f upstream-example.Dockerfile .
 
 .PHONY: lint
-#lint:
-#	find . -type f -name '*.go' ! -name '*.pb.go' -print0 | xargs -0 goimports -w
+GOLANGCI_LINT_ARGS := --build-tags=$(TAGS)
path: Makefile
user: login: grokspawn
body: When I was adding the linter to this repo I was making use of an externally-declared GOLANGCI_LINT_ARGS.  Can we either
```suggestion
GOLANGCI_LINT_ARGS += --build-tags=$(TAGS)
```
or
```suggestion
GOLANGCI_LINT_ARGS := "$(GOLANGCI_LINT_ARGS) --build-tags=$(TAGS)"
```
created_at: 2025-03-26T14:31:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -102,8 +100,7 @@ image-upstream:
 	docker build -f upstream-example.Dockerfile .
 
 .PHONY: lint
-#lint:
-#	find . -type f -name '*.go' ! -name '*.pb.go' -print0 | xargs -0 goimports -w
+GOLANGCI_LINT_ARGS := --build-tags=$(TAGS)
path: Makefile
user: login: joelanford
body: Ah, I was wondering why I wasn't seeing that anywhere. Yeah, I can make this change.
created_at: 2025-03-26T14:48:21Z
author_association: MEMBER
diff_hunk: @@ -102,8 +100,7 @@ image-upstream:
 	docker build -f upstream-example.Dockerfile .
 
 .PHONY: lint
-#lint:
-#	find . -type f -name '*.go' ! -name '*.pb.go' -print0 | xargs -0 goimports -w
+GOLANGCI_LINT_ARGS := --build-tags=$(TAGS)
path: Makefile
user: login: joelanford
body: Done!
created_at: 2025-03-27T13:20:48Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,290 @@
+package containersimageregistry
+
+import (
+	"archive/tar"
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/containerd/containerd/archive"
+	"github.com/containers/common/pkg/auth"
+	"github.com/containers/image/v5/copy"
+	"github.com/containers/image/v5/docker"
+	"github.com/containers/image/v5/docker/reference"
+	"github.com/containers/image/v5/image"
+	"github.com/containers/image/v5/oci/layout"
+	"github.com/containers/image/v5/pkg/compression"
+	"github.com/containers/image/v5/pkg/docker/config"
+	"github.com/containers/image/v5/signature"
+	"github.com/containers/image/v5/types"
+	dockerconfig "github.com/docker/cli/cli/config"
+	"oras.land/oras-go/v2/content/oci"
+
+	orimage "github.com/operator-framework/operator-registry/pkg/image"
+)
+
+var _ orimage.Registry = (*Registry)(nil)
+
+type Registry struct {
+	sourceCtx *types.SystemContext
+	cache     *cacheConfig
+}
+
+var DefaultSystemContext = &types.SystemContext{OSChoice: "linux"}
+
+func New(sourceCtx *types.SystemContext, opts ...Option) (orimage.Registry, error) {
+	reg := &Registry{
+		sourceCtx: sourceCtx,
+	}
+
+	for _, opt := range opts {
+		if err := opt(reg); err != nil {
+			return nil, err
+		}
+	}
+
+	if reg.cache == nil {
+		var err error
+		reg.cache, err = getDefaultImageCache()
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return reg, nil
+}
+
+func NewDefault() (orimage.Registry, error) {
+	return New(DefaultSystemContext)
+}
+
+type cacheConfig struct {
+	baseDir  string
+	preserve bool
+}
+
+func (c *cacheConfig) ociLayoutDir() string {
+	return filepath.Join(c.baseDir, "oci-layout")
+}
+func (c *cacheConfig) blobInfoCacheDir() string {
+	return filepath.Join(c.baseDir, "blob-info-cache")
+}
+
+func (c *cacheConfig) getSystemContext() *types.SystemContext {
+	return &types.SystemContext{
+		BlobInfoCacheDir: c.blobInfoCacheDir(),
+	}
+}
+
+type Option func(*Registry) error
+
+func getDefaultImageCache() (*cacheConfig, error) {
+	if dir := os.Getenv("OLM_CACHE_DIR"); dir != "" {
+		return newCacheConfig(filepath.Join(dir, "images"), true), nil
+	}
+	return getTemporaryImageCache()
+}
+
+func getTemporaryImageCache() (*cacheConfig, error) {
+	tmpDir, err := os.MkdirTemp("", "opm-containers-image-cache-")
+	if err != nil {
+		return nil, err
+	}
+	return newCacheConfig(tmpDir, false), nil
+}
+
+func newCacheConfig(dir string, preserve bool) *cacheConfig {
+	return &cacheConfig{
+		baseDir:  dir,
+		preserve: preserve,
+	}
+}
+
+func ForceTemporaryImageCache() Option {
path: pkg/image/containersimageregistry/registry.go
user: login: grokspawn
body: Why not the idiomatic `With$OPTION` syntax?
created_at: 2025-04-21T18:57:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,290 @@
+package containersimageregistry
+
+import (
+	"archive/tar"
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/containerd/containerd/archive"
+	"github.com/containers/common/pkg/auth"
+	"github.com/containers/image/v5/copy"
+	"github.com/containers/image/v5/docker"
+	"github.com/containers/image/v5/docker/reference"
+	"github.com/containers/image/v5/image"
+	"github.com/containers/image/v5/oci/layout"
+	"github.com/containers/image/v5/pkg/compression"
+	"github.com/containers/image/v5/pkg/docker/config"
+	"github.com/containers/image/v5/signature"
+	"github.com/containers/image/v5/types"
+	dockerconfig "github.com/docker/cli/cli/config"
+	"oras.land/oras-go/v2/content/oci"
+
+	orimage "github.com/operator-framework/operator-registry/pkg/image"
+)
+
+var _ orimage.Registry = (*Registry)(nil)
+
+type Registry struct {
+	sourceCtx *types.SystemContext
+	cache     *cacheConfig
+}
+
+var DefaultSystemContext = &types.SystemContext{OSChoice: "linux"}
+
+func New(sourceCtx *types.SystemContext, opts ...Option) (orimage.Registry, error) {
+	reg := &Registry{
+		sourceCtx: sourceCtx,
+	}
+
+	for _, opt := range opts {
+		if err := opt(reg); err != nil {
+			return nil, err
+		}
+	}
+
+	if reg.cache == nil {
+		var err error
+		reg.cache, err = getDefaultImageCache()
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return reg, nil
+}
+
+func NewDefault() (orimage.Registry, error) {
+	return New(DefaultSystemContext)
+}
+
+type cacheConfig struct {
+	baseDir  string
+	preserve bool
+}
+
+func (c *cacheConfig) ociLayoutDir() string {
+	return filepath.Join(c.baseDir, "oci-layout")
+}
+func (c *cacheConfig) blobInfoCacheDir() string {
+	return filepath.Join(c.baseDir, "blob-info-cache")
+}
+
+func (c *cacheConfig) getSystemContext() *types.SystemContext {
+	return &types.SystemContext{
+		BlobInfoCacheDir: c.blobInfoCacheDir(),
+	}
+}
+
+type Option func(*Registry) error
+
+func getDefaultImageCache() (*cacheConfig, error) {
+	if dir := os.Getenv("OLM_CACHE_DIR"); dir != "" {
+		return newCacheConfig(filepath.Join(dir, "images"), true), nil
+	}
+	return getTemporaryImageCache()
+}
+
+func getTemporaryImageCache() (*cacheConfig, error) {
+	tmpDir, err := os.MkdirTemp("", "opm-containers-image-cache-")
+	if err != nil {
+		return nil, err
+	}
+	return newCacheConfig(tmpDir, false), nil
+}
+
+func newCacheConfig(dir string, preserve bool) *cacheConfig {
+	return &cacheConfig{
+		baseDir:  dir,
+		preserve: preserve,
+	}
+}
+
+func ForceTemporaryImageCache() Option {
+	return func(r *Registry) error {
+		var err error
+		r.cache, err = getTemporaryImageCache()
+		if err != nil {
+			return err
+		}
+		return nil
+	}
+}
+
+func InsecureSkipTLSVerify(insecureSkipTLSVerify bool) Option {
+	return func(r *Registry) error {
+		if r.sourceCtx == nil {
+			r.sourceCtx = &types.SystemContext{}
+		}
+		r.sourceCtx.DockerDaemonInsecureSkipTLSVerify = insecureSkipTLSVerify
+		r.sourceCtx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(insecureSkipTLSVerify)
+		r.sourceCtx.OCIInsecureSkipTLSVerify = insecureSkipTLSVerify
+		return nil
+	}
+}
+
+func (r *Registry) Pull(ctx context.Context, ref orimage.Reference) error {
+	namedRef, err := reference.ParseNamed(ref.String())
+	if err != nil {
+		return err
+	}
+	dockerRef, err := docker.NewReference(namedRef)
+	if err != nil {
+		return err
+	}
+
+	if err := os.MkdirAll(r.cache.ociLayoutDir(), 0700); err != nil {
path: pkg/image/containersimageregistry/registry.go
user: login: grokspawn
body: I think the linter will complain about assuming anything greater than 0600 for dirs.

created_at: 2025-04-21T18:57:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,290 @@
+package containersimageregistry
+
+import (
+	"archive/tar"
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/containerd/containerd/archive"
+	"github.com/containers/common/pkg/auth"
+	"github.com/containers/image/v5/copy"
+	"github.com/containers/image/v5/docker"
+	"github.com/containers/image/v5/docker/reference"
+	"github.com/containers/image/v5/image"
+	"github.com/containers/image/v5/oci/layout"
+	"github.com/containers/image/v5/pkg/compression"
+	"github.com/containers/image/v5/pkg/docker/config"
+	"github.com/containers/image/v5/signature"
+	"github.com/containers/image/v5/types"
+	dockerconfig "github.com/docker/cli/cli/config"
+	"oras.land/oras-go/v2/content/oci"
+
+	orimage "github.com/operator-framework/operator-registry/pkg/image"
+)
+
+var _ orimage.Registry = (*Registry)(nil)
+
+type Registry struct {
+	sourceCtx *types.SystemContext
+	cache     *cacheConfig
+}
+
+var DefaultSystemContext = &types.SystemContext{OSChoice: "linux"}
path: pkg/image/containersimageregistry/registry.go
user: login: grokspawn
body: Do we need to default this, or follow the example of docker/podman and
1. derive from the invoking OS; and
2. allow overriding platform via option

?
created_at: 2025-04-21T19:47:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,290 @@
+package containersimageregistry
+
+import (
+	"archive/tar"
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/containerd/containerd/archive"
+	"github.com/containers/common/pkg/auth"
+	"github.com/containers/image/v5/copy"
+	"github.com/containers/image/v5/docker"
+	"github.com/containers/image/v5/docker/reference"
+	"github.com/containers/image/v5/image"
+	"github.com/containers/image/v5/oci/layout"
+	"github.com/containers/image/v5/pkg/compression"
+	"github.com/containers/image/v5/pkg/docker/config"
+	"github.com/containers/image/v5/signature"
+	"github.com/containers/image/v5/types"
+	dockerconfig "github.com/docker/cli/cli/config"
+	"oras.land/oras-go/v2/content/oci"
+
+	orimage "github.com/operator-framework/operator-registry/pkg/image"
+)
+
+var _ orimage.Registry = (*Registry)(nil)
+
+type Registry struct {
+	sourceCtx *types.SystemContext
+	cache     *cacheConfig
+}
+
+var DefaultSystemContext = &types.SystemContext{OSChoice: "linux"}
+
+func New(sourceCtx *types.SystemContext, opts ...Option) (orimage.Registry, error) {
+	reg := &Registry{
+		sourceCtx: sourceCtx,
+	}
+
+	for _, opt := range opts {
+		if err := opt(reg); err != nil {
+			return nil, err
+		}
+	}
+
+	if reg.cache == nil {
+		var err error
+		reg.cache, err = getDefaultImageCache()
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return reg, nil
+}
+
+func NewDefault() (orimage.Registry, error) {
+	return New(DefaultSystemContext)
+}
+
+type cacheConfig struct {
+	baseDir  string
+	preserve bool
+}
+
+func (c *cacheConfig) ociLayoutDir() string {
+	return filepath.Join(c.baseDir, "oci-layout")
+}
+func (c *cacheConfig) blobInfoCacheDir() string {
+	return filepath.Join(c.baseDir, "blob-info-cache")
+}
+
+func (c *cacheConfig) getSystemContext() *types.SystemContext {
+	return &types.SystemContext{
+		BlobInfoCacheDir: c.blobInfoCacheDir(),
+	}
+}
+
+type Option func(*Registry) error
+
+func getDefaultImageCache() (*cacheConfig, error) {
+	if dir := os.Getenv("OLM_CACHE_DIR"); dir != "" {
+		return newCacheConfig(filepath.Join(dir, "images"), true), nil
+	}
+	return getTemporaryImageCache()
+}
+
+func getTemporaryImageCache() (*cacheConfig, error) {
+	tmpDir, err := os.MkdirTemp("", "opm-containers-image-cache-")
+	if err != nil {
+		return nil, err
+	}
+	return newCacheConfig(tmpDir, false), nil
+}
+
+func newCacheConfig(dir string, preserve bool) *cacheConfig {
+	return &cacheConfig{
+		baseDir:  dir,
+		preserve: preserve,
+	}
+}
+
+func ForceTemporaryImageCache() Option {
+	return func(r *Registry) error {
+		var err error
+		r.cache, err = getTemporaryImageCache()
+		if err != nil {
+			return err
+		}
+		return nil
+	}
+}
+
+func InsecureSkipTLSVerify(insecureSkipTLSVerify bool) Option {
+	return func(r *Registry) error {
+		if r.sourceCtx == nil {
+			r.sourceCtx = &types.SystemContext{}
path: pkg/image/containersimageregistry/registry.go
user: login: grokspawn
body: We wouldn't use the `DefaultSystemContext` here?

created_at: 2025-04-21T19:51:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,290 @@
+package containersimageregistry
+
+import (
+	"archive/tar"
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/containerd/containerd/archive"
+	"github.com/containers/common/pkg/auth"
+	"github.com/containers/image/v5/copy"
+	"github.com/containers/image/v5/docker"
+	"github.com/containers/image/v5/docker/reference"
+	"github.com/containers/image/v5/image"
+	"github.com/containers/image/v5/oci/layout"
+	"github.com/containers/image/v5/pkg/compression"
+	"github.com/containers/image/v5/pkg/docker/config"
+	"github.com/containers/image/v5/signature"
+	"github.com/containers/image/v5/types"
+	dockerconfig "github.com/docker/cli/cli/config"
+	"oras.land/oras-go/v2/content/oci"
+
+	orimage "github.com/operator-framework/operator-registry/pkg/image"
+)
+
+var _ orimage.Registry = (*Registry)(nil)
+
+type Registry struct {
+	sourceCtx *types.SystemContext
+	cache     *cacheConfig
+}
+
+var DefaultSystemContext = &types.SystemContext{OSChoice: "linux"}
+
+func New(sourceCtx *types.SystemContext, opts ...Option) (orimage.Registry, error) {
+	reg := &Registry{
+		sourceCtx: sourceCtx,
+	}
+
+	for _, opt := range opts {
+		if err := opt(reg); err != nil {
+			return nil, err
+		}
+	}
+
+	if reg.cache == nil {
+		var err error
+		reg.cache, err = getDefaultImageCache()
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return reg, nil
+}
+
+func NewDefault() (orimage.Registry, error) {
+	return New(DefaultSystemContext)
+}
+
+type cacheConfig struct {
+	baseDir  string
+	preserve bool
+}
+
+func (c *cacheConfig) ociLayoutDir() string {
+	return filepath.Join(c.baseDir, "oci-layout")
+}
+func (c *cacheConfig) blobInfoCacheDir() string {
+	return filepath.Join(c.baseDir, "blob-info-cache")
+}
+
+func (c *cacheConfig) getSystemContext() *types.SystemContext {
+	return &types.SystemContext{
+		BlobInfoCacheDir: c.blobInfoCacheDir(),
+	}
+}
+
+type Option func(*Registry) error
+
+func getDefaultImageCache() (*cacheConfig, error) {
+	if dir := os.Getenv("OLM_CACHE_DIR"); dir != "" {
+		return newCacheConfig(filepath.Join(dir, "images"), true), nil
+	}
+	return getTemporaryImageCache()
+}
+
+func getTemporaryImageCache() (*cacheConfig, error) {
+	tmpDir, err := os.MkdirTemp("", "opm-containers-image-cache-")
+	if err != nil {
+		return nil, err
+	}
+	return newCacheConfig(tmpDir, false), nil
+}
+
+func newCacheConfig(dir string, preserve bool) *cacheConfig {
+	return &cacheConfig{
+		baseDir:  dir,
+		preserve: preserve,
+	}
+}
+
+func ForceTemporaryImageCache() Option {
+	return func(r *Registry) error {
+		var err error
+		r.cache, err = getTemporaryImageCache()
+		if err != nil {
+			return err
+		}
+		return nil
+	}
+}
+
+func InsecureSkipTLSVerify(insecureSkipTLSVerify bool) Option {
+	return func(r *Registry) error {
+		if r.sourceCtx == nil {
+			r.sourceCtx = &types.SystemContext{}
+		}
+		r.sourceCtx.DockerDaemonInsecureSkipTLSVerify = insecureSkipTLSVerify
+		r.sourceCtx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(insecureSkipTLSVerify)
+		r.sourceCtx.OCIInsecureSkipTLSVerify = insecureSkipTLSVerify
+		return nil
+	}
+}
+
+func (r *Registry) Pull(ctx context.Context, ref orimage.Reference) error {
+	namedRef, err := reference.ParseNamed(ref.String())
+	if err != nil {
+		return err
+	}
+	dockerRef, err := docker.NewReference(namedRef)
+	if err != nil {
+		return err
+	}
+
+	if err := os.MkdirAll(r.cache.ociLayoutDir(), 0700); err != nil {
+		return err
+	}
+	ociLayoutRef, err := layout.NewReference(r.cache.ociLayoutDir(), ref.String())
+	if err != nil {
+		return err
+	}
+
+	policy, err := signature.DefaultPolicy(r.sourceCtx)
+	if err != nil {
+		return err
+	}
+	policyContext, err := signature.NewPolicyContext(policy)
+	if err != nil {
+		return err
+	}
+
+	sourceCtx := r.sourceCtx
+	authFile := getAuthFile(r.sourceCtx, namedRef.String())
+	if authFile != "" {
+		if sourceCtx == nil {
+			sourceCtx = &types.SystemContext{}
path: pkg/image/containersimageregistry/registry.go
user: login: grokspawn
body: use default?
created_at: 2025-04-21T20:33:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,290 @@
+package containersimageregistry
+
+import (
+	"archive/tar"
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/containerd/containerd/archive"
+	"github.com/containers/common/pkg/auth"
+	"github.com/containers/image/v5/copy"
+	"github.com/containers/image/v5/docker"
+	"github.com/containers/image/v5/docker/reference"
+	"github.com/containers/image/v5/image"
+	"github.com/containers/image/v5/oci/layout"
+	"github.com/containers/image/v5/pkg/compression"
+	"github.com/containers/image/v5/pkg/docker/config"
+	"github.com/containers/image/v5/signature"
+	"github.com/containers/image/v5/types"
+	dockerconfig "github.com/docker/cli/cli/config"
+	"oras.land/oras-go/v2/content/oci"
+
+	orimage "github.com/operator-framework/operator-registry/pkg/image"
+)
+
+var _ orimage.Registry = (*Registry)(nil)
+
+type Registry struct {
+	sourceCtx *types.SystemContext
+	cache     *cacheConfig
+}
+
+var DefaultSystemContext = &types.SystemContext{OSChoice: "linux"}
+
+func New(sourceCtx *types.SystemContext, opts ...Option) (orimage.Registry, error) {
+	reg := &Registry{
+		sourceCtx: sourceCtx,
+	}
+
path: pkg/image/containersimageregistry/registry.go
user: login: grokspawn
body: ```suggestion
if sourceCtx == nil {
   reg.sourceCtx = &types.SystemContext{}
}
```
would avoid checks at [117](https://github.com/operator-framework/operator-registry/pull/1626/files#diff-310f611de22924cc6f62874dd8f06d7a32711c949627543d2e8c1727af584c9bR117) & [157](https://github.com/operator-framework/operator-registry/pull/1626/files#diff-310f611de22924cc6f62874dd8f06d7a32711c949627543d2e8c1727af584c9bR157).
created_at: 2025-04-21T20:39:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,290 @@
+package containersimageregistry
+
+import (
+	"archive/tar"
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/containerd/containerd/archive"
+	"github.com/containers/common/pkg/auth"
+	"github.com/containers/image/v5/copy"
+	"github.com/containers/image/v5/docker"
+	"github.com/containers/image/v5/docker/reference"
+	"github.com/containers/image/v5/image"
+	"github.com/containers/image/v5/oci/layout"
+	"github.com/containers/image/v5/pkg/compression"
+	"github.com/containers/image/v5/pkg/docker/config"
+	"github.com/containers/image/v5/signature"
+	"github.com/containers/image/v5/types"
+	dockerconfig "github.com/docker/cli/cli/config"
+	"oras.land/oras-go/v2/content/oci"
+
+	orimage "github.com/operator-framework/operator-registry/pkg/image"
+)
+
+var _ orimage.Registry = (*Registry)(nil)
+
+type Registry struct {
+	sourceCtx *types.SystemContext
+	cache     *cacheConfig
+}
+
+var DefaultSystemContext = &types.SystemContext{OSChoice: "linux"}
path: pkg/image/containersimageregistry/registry.go
user: login: joelanford
body: In our case, we are pulling catalog and bundle images, which are never meant to be OS or arch-specific. Also, with the exception of a some niche windows containers use cases, linux is the only OS that ever shows up in image indexes or manifest lists because linux is the only OS that actually supports containers.

This defaulting essentially guarantees that MacOS and windows users can natively interact with the current incarnation of OLM's OCI formats (which use standard images to carry the FBC and bundle filesystems) without having to always override the OS from darwin to linux.
created_at: 2025-04-24T00:19:05Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,290 @@
+package containersimageregistry
+
+import (
+	"archive/tar"
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/containerd/containerd/archive"
+	"github.com/containers/common/pkg/auth"
+	"github.com/containers/image/v5/copy"
+	"github.com/containers/image/v5/docker"
+	"github.com/containers/image/v5/docker/reference"
+	"github.com/containers/image/v5/image"
+	"github.com/containers/image/v5/oci/layout"
+	"github.com/containers/image/v5/pkg/compression"
+	"github.com/containers/image/v5/pkg/docker/config"
+	"github.com/containers/image/v5/signature"
+	"github.com/containers/image/v5/types"
+	dockerconfig "github.com/docker/cli/cli/config"
+	"oras.land/oras-go/v2/content/oci"
+
+	orimage "github.com/operator-framework/operator-registry/pkg/image"
+)
+
+var _ orimage.Registry = (*Registry)(nil)
+
+type Registry struct {
+	sourceCtx *types.SystemContext
+	cache     *cacheConfig
+}
+
+var DefaultSystemContext = &types.SystemContext{OSChoice: "linux"}
+
+func New(sourceCtx *types.SystemContext, opts ...Option) (orimage.Registry, error) {
+	reg := &Registry{
+		sourceCtx: sourceCtx,
+	}
+
+	for _, opt := range opts {
+		if err := opt(reg); err != nil {
+			return nil, err
+		}
+	}
+
+	if reg.cache == nil {
+		var err error
+		reg.cache, err = getDefaultImageCache()
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return reg, nil
+}
+
+func NewDefault() (orimage.Registry, error) {
+	return New(DefaultSystemContext)
+}
+
+type cacheConfig struct {
+	baseDir  string
+	preserve bool
+}
+
+func (c *cacheConfig) ociLayoutDir() string {
+	return filepath.Join(c.baseDir, "oci-layout")
+}
+func (c *cacheConfig) blobInfoCacheDir() string {
+	return filepath.Join(c.baseDir, "blob-info-cache")
+}
+
+func (c *cacheConfig) getSystemContext() *types.SystemContext {
+	return &types.SystemContext{
+		BlobInfoCacheDir: c.blobInfoCacheDir(),
+	}
+}
+
+type Option func(*Registry) error
+
+func getDefaultImageCache() (*cacheConfig, error) {
+	if dir := os.Getenv("OLM_CACHE_DIR"); dir != "" {
+		return newCacheConfig(filepath.Join(dir, "images"), true), nil
+	}
+	return getTemporaryImageCache()
+}
+
+func getTemporaryImageCache() (*cacheConfig, error) {
+	tmpDir, err := os.MkdirTemp("", "opm-containers-image-cache-")
+	if err != nil {
+		return nil, err
+	}
+	return newCacheConfig(tmpDir, false), nil
+}
+
+func newCacheConfig(dir string, preserve bool) *cacheConfig {
+	return &cacheConfig{
+		baseDir:  dir,
+		preserve: preserve,
+	}
+}
+
+func ForceTemporaryImageCache() Option {
+	return func(r *Registry) error {
+		var err error
+		r.cache, err = getTemporaryImageCache()
+		if err != nil {
+			return err
+		}
+		return nil
+	}
+}
+
+func InsecureSkipTLSVerify(insecureSkipTLSVerify bool) Option {
+	return func(r *Registry) error {
+		if r.sourceCtx == nil {
+			r.sourceCtx = &types.SystemContext{}
path: pkg/image/containersimageregistry/registry.go
user: login: joelanford
body: I don't think so, no. Because `InsecureSkipTLSVerify` will be called by `New`, which accepts an explicit `*types.SystemContext` from the caller. So if the user explicitly passes `nil` as the `*types.SystemContext`, we should not override that to the `DefaultSystemContext`.
created_at: 2025-04-24T00:24:04Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,290 @@
+package containersimageregistry
+
+import (
+	"archive/tar"
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/containerd/containerd/archive"
+	"github.com/containers/common/pkg/auth"
+	"github.com/containers/image/v5/copy"
+	"github.com/containers/image/v5/docker"
+	"github.com/containers/image/v5/docker/reference"
+	"github.com/containers/image/v5/image"
+	"github.com/containers/image/v5/oci/layout"
+	"github.com/containers/image/v5/pkg/compression"
+	"github.com/containers/image/v5/pkg/docker/config"
+	"github.com/containers/image/v5/signature"
+	"github.com/containers/image/v5/types"
+	dockerconfig "github.com/docker/cli/cli/config"
+	"oras.land/oras-go/v2/content/oci"
+
+	orimage "github.com/operator-framework/operator-registry/pkg/image"
+)
+
+var _ orimage.Registry = (*Registry)(nil)
+
+type Registry struct {
+	sourceCtx *types.SystemContext
+	cache     *cacheConfig
+}
+
+var DefaultSystemContext = &types.SystemContext{OSChoice: "linux"}
+
+func New(sourceCtx *types.SystemContext, opts ...Option) (orimage.Registry, error) {
+	reg := &Registry{
+		sourceCtx: sourceCtx,
+	}
+
+	for _, opt := range opts {
+		if err := opt(reg); err != nil {
+			return nil, err
+		}
+	}
+
+	if reg.cache == nil {
+		var err error
+		reg.cache, err = getDefaultImageCache()
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return reg, nil
+}
+
+func NewDefault() (orimage.Registry, error) {
+	return New(DefaultSystemContext)
+}
+
+type cacheConfig struct {
+	baseDir  string
+	preserve bool
+}
+
+func (c *cacheConfig) ociLayoutDir() string {
+	return filepath.Join(c.baseDir, "oci-layout")
+}
+func (c *cacheConfig) blobInfoCacheDir() string {
+	return filepath.Join(c.baseDir, "blob-info-cache")
+}
+
+func (c *cacheConfig) getSystemContext() *types.SystemContext {
+	return &types.SystemContext{
+		BlobInfoCacheDir: c.blobInfoCacheDir(),
+	}
+}
+
+type Option func(*Registry) error
+
+func getDefaultImageCache() (*cacheConfig, error) {
+	if dir := os.Getenv("OLM_CACHE_DIR"); dir != "" {
+		return newCacheConfig(filepath.Join(dir, "images"), true), nil
+	}
+	return getTemporaryImageCache()
+}
+
+func getTemporaryImageCache() (*cacheConfig, error) {
+	tmpDir, err := os.MkdirTemp("", "opm-containers-image-cache-")
+	if err != nil {
+		return nil, err
+	}
+	return newCacheConfig(tmpDir, false), nil
+}
+
+func newCacheConfig(dir string, preserve bool) *cacheConfig {
+	return &cacheConfig{
+		baseDir:  dir,
+		preserve: preserve,
+	}
+}
+
+func ForceTemporaryImageCache() Option {
+	return func(r *Registry) error {
+		var err error
+		r.cache, err = getTemporaryImageCache()
+		if err != nil {
+			return err
+		}
+		return nil
+	}
+}
+
+func InsecureSkipTLSVerify(insecureSkipTLSVerify bool) Option {
+	return func(r *Registry) error {
+		if r.sourceCtx == nil {
+			r.sourceCtx = &types.SystemContext{}
+		}
+		r.sourceCtx.DockerDaemonInsecureSkipTLSVerify = insecureSkipTLSVerify
+		r.sourceCtx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(insecureSkipTLSVerify)
+		r.sourceCtx.OCIInsecureSkipTLSVerify = insecureSkipTLSVerify
+		return nil
+	}
+}
+
+func (r *Registry) Pull(ctx context.Context, ref orimage.Reference) error {
+	namedRef, err := reference.ParseNamed(ref.String())
+	if err != nil {
+		return err
+	}
+	dockerRef, err := docker.NewReference(namedRef)
+	if err != nil {
+		return err
+	}
+
+	if err := os.MkdirAll(r.cache.ociLayoutDir(), 0700); err != nil {
+		return err
+	}
+	ociLayoutRef, err := layout.NewReference(r.cache.ociLayoutDir(), ref.String())
+	if err != nil {
+		return err
+	}
+
+	policy, err := signature.DefaultPolicy(r.sourceCtx)
+	if err != nil {
+		return err
+	}
+	policyContext, err := signature.NewPolicyContext(policy)
+	if err != nil {
+		return err
+	}
+
+	sourceCtx := r.sourceCtx
+	authFile := getAuthFile(r.sourceCtx, namedRef.String())
+	if authFile != "" {
+		if sourceCtx == nil {
+			sourceCtx = &types.SystemContext{}
path: pkg/image/containersimageregistry/registry.go
user: login: joelanford
body: No, with pretty much the same reasoning as above. At this point, the caller has already explicitly provided us with a `nil` SystemContext and has opted _not_ to use the `DefaultSystemContext`.
created_at: 2025-04-24T00:25:56Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,290 @@
+package containersimageregistry
+
+import (
+	"archive/tar"
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/containerd/containerd/archive"
+	"github.com/containers/common/pkg/auth"
+	"github.com/containers/image/v5/copy"
+	"github.com/containers/image/v5/docker"
+	"github.com/containers/image/v5/docker/reference"
+	"github.com/containers/image/v5/image"
+	"github.com/containers/image/v5/oci/layout"
+	"github.com/containers/image/v5/pkg/compression"
+	"github.com/containers/image/v5/pkg/docker/config"
+	"github.com/containers/image/v5/signature"
+	"github.com/containers/image/v5/types"
+	dockerconfig "github.com/docker/cli/cli/config"
+	"oras.land/oras-go/v2/content/oci"
+
+	orimage "github.com/operator-framework/operator-registry/pkg/image"
+)
+
+var _ orimage.Registry = (*Registry)(nil)
+
+type Registry struct {
+	sourceCtx *types.SystemContext
+	cache     *cacheConfig
+}
+
+var DefaultSystemContext = &types.SystemContext{OSChoice: "linux"}
+
+func New(sourceCtx *types.SystemContext, opts ...Option) (orimage.Registry, error) {
+	reg := &Registry{
+		sourceCtx: sourceCtx,
+	}
+
path: pkg/image/containersimageregistry/registry.go
user: login: joelanford
body: So technically, it is valid to send a `nil` SystemContext all the way into the `container/image` library calls. So the reason I have things setup this way is to only actually make a `types.SystemContext` when we actually _need_ to set a field. Otherwise, we roll with `nil` all the way through.

But I don't think there is a semantic difference between `&types.SystemContext{}` and `nil`, so I can get on board with this suggestion!
created_at: 2025-04-24T00:30:28Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,290 @@
+package containersimageregistry
+
+import (
+	"archive/tar"
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/containerd/containerd/archive"
+	"github.com/containers/common/pkg/auth"
+	"github.com/containers/image/v5/copy"
+	"github.com/containers/image/v5/docker"
+	"github.com/containers/image/v5/docker/reference"
+	"github.com/containers/image/v5/image"
+	"github.com/containers/image/v5/oci/layout"
+	"github.com/containers/image/v5/pkg/compression"
+	"github.com/containers/image/v5/pkg/docker/config"
+	"github.com/containers/image/v5/signature"
+	"github.com/containers/image/v5/types"
+	dockerconfig "github.com/docker/cli/cli/config"
+	"oras.land/oras-go/v2/content/oci"
+
+	orimage "github.com/operator-framework/operator-registry/pkg/image"
+)
+
+var _ orimage.Registry = (*Registry)(nil)
+
+type Registry struct {
+	sourceCtx *types.SystemContext
+	cache     *cacheConfig
+}
+
+var DefaultSystemContext = &types.SystemContext{OSChoice: "linux"}
+
+func New(sourceCtx *types.SystemContext, opts ...Option) (orimage.Registry, error) {
+	reg := &Registry{
+		sourceCtx: sourceCtx,
+	}
+
+	for _, opt := range opts {
+		if err := opt(reg); err != nil {
+			return nil, err
+		}
+	}
+
+	if reg.cache == nil {
+		var err error
+		reg.cache, err = getDefaultImageCache()
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return reg, nil
+}
+
+func NewDefault() (orimage.Registry, error) {
+	return New(DefaultSystemContext)
+}
+
+type cacheConfig struct {
+	baseDir  string
+	preserve bool
+}
+
+func (c *cacheConfig) ociLayoutDir() string {
+	return filepath.Join(c.baseDir, "oci-layout")
+}
+func (c *cacheConfig) blobInfoCacheDir() string {
+	return filepath.Join(c.baseDir, "blob-info-cache")
+}
+
+func (c *cacheConfig) getSystemContext() *types.SystemContext {
+	return &types.SystemContext{
+		BlobInfoCacheDir: c.blobInfoCacheDir(),
+	}
+}
+
+type Option func(*Registry) error
+
+func getDefaultImageCache() (*cacheConfig, error) {
+	if dir := os.Getenv("OLM_CACHE_DIR"); dir != "" {
+		return newCacheConfig(filepath.Join(dir, "images"), true), nil
+	}
+	return getTemporaryImageCache()
+}
+
+func getTemporaryImageCache() (*cacheConfig, error) {
+	tmpDir, err := os.MkdirTemp("", "opm-containers-image-cache-")
+	if err != nil {
+		return nil, err
+	}
+	return newCacheConfig(tmpDir, false), nil
+}
+
+func newCacheConfig(dir string, preserve bool) *cacheConfig {
+	return &cacheConfig{
+		baseDir:  dir,
+		preserve: preserve,
+	}
+}
+
+func ForceTemporaryImageCache() Option {
+	return func(r *Registry) error {
+		var err error
+		r.cache, err = getTemporaryImageCache()
+		if err != nil {
+			return err
+		}
+		return nil
+	}
+}
+
+func InsecureSkipTLSVerify(insecureSkipTLSVerify bool) Option {
+	return func(r *Registry) error {
+		if r.sourceCtx == nil {
+			r.sourceCtx = &types.SystemContext{}
+		}
+		r.sourceCtx.DockerDaemonInsecureSkipTLSVerify = insecureSkipTLSVerify
+		r.sourceCtx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(insecureSkipTLSVerify)
+		r.sourceCtx.OCIInsecureSkipTLSVerify = insecureSkipTLSVerify
+		return nil
+	}
+}
+
+func (r *Registry) Pull(ctx context.Context, ref orimage.Reference) error {
+	namedRef, err := reference.ParseNamed(ref.String())
+	if err != nil {
+		return err
+	}
+	dockerRef, err := docker.NewReference(namedRef)
+	if err != nil {
+		return err
+	}
+
+	if err := os.MkdirAll(r.cache.ociLayoutDir(), 0700); err != nil {
path: pkg/image/containersimageregistry/registry.go
user: login: joelanford
body: No complaints from the linter. I think `0700` is standard for directories because the execute bit for directories is what gives a user permission to traverse the directory.
created_at: 2025-04-24T00:47:15Z
author_association: MEMBER
path: internal/testutil/image/registry.go
user: login: joelanford
body: This used to be in `pkg/lib/image`, but it is only used by tests. Given that its implicit intention is as a test utility, I think it is reasonable to move this into `internal/testutil` to make that intention explicit.

This is technically a breaking change, but one that I am comfortable with because a reasonable person would not expect this to actually be part of the public API surface of operator-registry.
created_at: 2025-04-24T00:50:33Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,290 @@
+package containersimageregistry
+
+import (
+	"archive/tar"
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/containerd/containerd/archive"
+	"github.com/containers/common/pkg/auth"
+	"github.com/containers/image/v5/copy"
+	"github.com/containers/image/v5/docker"
+	"github.com/containers/image/v5/docker/reference"
+	"github.com/containers/image/v5/image"
+	"github.com/containers/image/v5/oci/layout"
+	"github.com/containers/image/v5/pkg/compression"
+	"github.com/containers/image/v5/pkg/docker/config"
+	"github.com/containers/image/v5/signature"
+	"github.com/containers/image/v5/types"
+	dockerconfig "github.com/docker/cli/cli/config"
+	"oras.land/oras-go/v2/content/oci"
+
+	orimage "github.com/operator-framework/operator-registry/pkg/image"
+)
+
+var _ orimage.Registry = (*Registry)(nil)
+
+type Registry struct {
+	sourceCtx *types.SystemContext
+	cache     *cacheConfig
+}
+
+var DefaultSystemContext = &types.SystemContext{OSChoice: "linux"}
path: pkg/image/containersimageregistry/registry.go
user: login: grokspawn
body: LOL.  I was going to point to https://github.com/operator-framework/operator-registry/issues/1591 and say that I was reviewing to evaluate similar goals ... but then I see you replied to that issue in a way I feel resolves the questions. 

created_at: 2025-04-24T14:41:24Z
author_association: CONTRIBUTOR

PR comments:
user: login: openshift-ci[bot]
created_at: 2025-03-25T18:09:41Z
body: Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with `/test all`
author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-03-25T18:18:40Z
body: ## [Codecov](https://app.codecov.io/gh/operator-framework/operator-registry/pull/1626?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=operator-framework) Report
Attention: Patch coverage is `53.62319%` with `96 lines` in your changes missing coverage. Please review.
> Project coverage is 55.15%. Comparing base [(`2d1087f`)](https://app.codecov.io/gh/operator-framework/operator-registry/commit/2d1087f5500191cb6900353c4d6ddcf689798fad?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=operator-framework) to head [(`3f5bbe0`)](https://app.codecov.io/gh/operator-framework/operator-registry/commit/3f5bbe068f3bb53a5b45556f91f4beba6b4b0ccd?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=operator-framework).
> Report is 1 commits behind head on master.

| [Files with missing lines](https://app.codecov.io/gh/operator-framework/operator-registry/pull/1626?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=operator-framework) | Patch % | Lines |
|---|---|---|
| [pkg/image/containersimageregistry/registry.go](https://app.codecov.io/gh/operator-framework/operator-registry/pull/1626?src=pr&el=tree&filepath=pkg%2Fimage%2Fcontainersimageregistry%2Fregistry.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=operator-framework#diff-cGtnL2ltYWdlL2NvbnRhaW5lcnNpbWFnZXJlZ2lzdHJ5L3JlZ2lzdHJ5Lmdv) | 50.79% | [69 Missing and 24 partials :warning: ](https://app.codecov.io/gh/operator-framework/operator-registry/pull/1626?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=operator-framework) |
| [internal/testutil/image/registry.go](https://app.codecov.io/gh/operator-framework/operator-registry/pull/1626?src=pr&el=tree&filepath=internal%2Ftestutil%2Fimage%2Fregistry.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=operator-framework#diff-aW50ZXJuYWwvdGVzdHV0aWwvaW1hZ2UvcmVnaXN0cnkuZ28=) | 82.35% | [2 Missing and 1 partial :warning: ](https://app.codecov.io/gh/operator-framework/operator-registry/pull/1626?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=operator-framework) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #1626      +/-   ##
==========================================
+ Coverage   46.79%   55.15%   +8.36%     
==========================================
  Files         135      136       +1     
  Lines       15888    15911      +23     
==========================================
+ Hits         7435     8776    +1341     
+ Misses       7412     5982    -1430     
- Partials     1041     1153     +112     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/operator-framework/operator-registry/pull/1626?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=operator-framework).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=operator-framework).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: openshift-ci[bot]
created_at: 2025-04-24T14:41:46Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/operator-framework/operator-registry/pull/1626#pullrequestreview-2791481016" title="Approved">grokspawn</a>*, *<a href="https://github.com/operator-framework/operator-registry/pull/1626#" title="Author self-approved">joelanford</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=operator-framework%2Foperator-registry).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/operator-framework/operator-registry/blob/master/OWNERS)~~ [grokspawn,joelanford]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR
user: login: grokspawn
created_at: 2025-04-24T14:42:07Z
body: /lgtm

author_association: CONTRIBUTOR