diff --git a/.goreleaser.yml b/.goreleaser.yml index 19358457a..57e9dd8b6 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -9,6 +9,8 @@ builds: asmflags: "{{ .Env.GO_BUILD_ASMFLAGS }}" gcflags: "{{ .Env.GO_BUILD_GCFLAGS }}" ldflags: "{{ .Env.GO_BUILD_LDFLAGS }}" + tags: + - "{{ .Env.GO_BUILD_TAGS }}" goos: - linux goarch: @@ -73,4 +75,4 @@ release: ```bash curl -L -s https://github.com/operator-framework/operator-controller/releases/download/{{ .Tag }}/install.sh | bash -s - ``` \ No newline at end of file + ``` diff --git a/Makefile b/Makefile index f36df3f91..f92abb7ca 100644 --- a/Makefile +++ b/Makefile @@ -91,7 +91,7 @@ help-extended: #HELP Display extended help. .PHONY: lint lint: $(GOLANGCI_LINT) #HELP Run golangci linter. - $(GOLANGCI_LINT) run $(GOLANGCI_LINT_ARGS) + $(GOLANGCI_LINT) run --build-tags $(GO_BUILD_TAGS) $(GOLANGCI_LINT_ARGS) .PHONY: tidy tidy: #HELP Update dependencies. @@ -111,7 +111,7 @@ verify: tidy fmt vet generate manifests crd-ref-docs #HELP Verify all generated .PHONY: fix-lint fix-lint: $(GOLANGCI_LINT) #EXHELP Fix lint issues - $(GOLANGCI_LINT) run --fix $(GOLANGCI_LINT_ARGS) + $(GOLANGCI_LINT) run --fix --build-tags $(GO_BUILD_TAGS) $(GOLANGCI_LINT_ARGS) .PHONY: fmt fmt: #EXHELP Formats code @@ -119,7 +119,7 @@ fmt: #EXHELP Formats code .PHONY: vet vet: #EXHELP Run go vet against code. - go vet ./... + go vet -tags '$(GO_BUILD_TAGS)' ./... .PHONY: bingo-upgrade bingo-upgrade: $(BINGO) #EXHELP Upgrade tools @@ -155,10 +155,17 @@ UNIT_TEST_DIRS := $(shell go list ./... | grep -v /test/) COVERAGE_UNIT_DIR := $(ROOT_DIR)/coverage/unit test-unit: $(SETUP_ENVTEST) #HELP Run the unit tests rm -rf $(COVERAGE_UNIT_DIR) && mkdir -p $(COVERAGE_UNIT_DIR) - eval $$($(SETUP_ENVTEST) use -p env $(ENVTEST_VERSION) $(SETUP_ENVTEST_BIN_DIR_OVERRIDE)) && CGO_ENABLED=1 go test -count=1 -race -short $(UNIT_TEST_DIRS) -cover -coverprofile ${ROOT_DIR}/coverage/unit.out -test.gocoverdir=$(ROOT_DIR)/coverage/unit - + eval $$($(SETUP_ENVTEST) use -p env $(ENVTEST_VERSION) $(SETUP_ENVTEST_BIN_DIR_OVERRIDE)) && \ + CGO_ENABLED=1 go test \ + -tags '$(GO_BUILD_TAGS)' \ + -cover -coverprofile ${ROOT_DIR}/coverage/unit.out \ + -count=1 -race -short \ + $(UNIT_TEST_DIRS) \ + -test.gocoverdir=$(ROOT_DIR)/coverage/unit + +E2E_REGISTRY_CERT_REF := ClusterIssuer/olmv1-ca # By default, we'll use a trusted CA for the registry. image-registry: ## Setup in-cluster image registry - ./hack/test/image-registry.sh $(E2E_REGISTRY_NAMESPACE) $(E2E_REGISTRY_NAME) + ./hack/test/image-registry.sh $(E2E_REGISTRY_NAMESPACE) $(E2E_REGISTRY_NAME) $(E2E_REGISTRY_CERT_REF) build-push-e2e-catalog: ## Build the testdata catalog used for e2e tests and push it to the image registry ./hack/test/build-push-e2e-catalog.sh $(E2E_REGISTRY_NAMESPACE) $(LOCAL_REGISTRY_HOST)/$(E2E_TEST_CATALOG_V1) @@ -173,6 +180,7 @@ build-push-e2e-catalog: ## Build the testdata catalog used for e2e tests and pus test-e2e: KIND_CLUSTER_NAME := operator-controller-e2e test-e2e: KUSTOMIZE_BUILD_DIR := config/overlays/e2e test-e2e: GO_BUILD_FLAGS := -cover +test-e2e: E2E_REGISTRY_CERT_REF := Issuer/selfsigned-issuer test-e2e: run image-registry build-push-e2e-catalog registry-load-bundles e2e e2e-coverage kind-clean #HELP Run e2e test suite on local kind cluster .PHONY: extension-developer-e2e @@ -243,6 +251,7 @@ export CGO_ENABLED export GIT_REPO := $(shell go list -m) export VERSION_PATH := ${GIT_REPO}/internal/version +export GO_BUILD_TAGS := containers_image_openpgp export GO_BUILD_ASMFLAGS := all=-trimpath=$(PWD) export GO_BUILD_GCFLAGS := all=-trimpath=$(PWD) export GO_BUILD_FLAGS := diff --git a/Tiltfile b/Tiltfile index 330212902..10c4362e1 100644 --- a/Tiltfile +++ b/Tiltfile @@ -18,6 +18,6 @@ repo = { for r in repos: if r == 'operator-controller': - deploy_repo('operator-controller', repo) + deploy_repo('operator-controller', repo, '-tags containers_image_openpgp') else: include('../{}/Tiltfile'.format(r)) diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 95813cf6c..db25c3ad0 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -25,6 +25,7 @@ import ( "path/filepath" "time" + "github.com/containers/image/v5/types" "github.com/spf13/pflag" "go.uber.org/zap/zapcore" apiextensionsv1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" @@ -194,14 +195,18 @@ func main() { setupLog.Error(err, "unable to create CA certificate pool") os.Exit(1) } - unpacker := &source.ImageRegistry{ - BaseCachePath: filepath.Join(cachePath, "unpack"), - CertPoolWatcher: certPoolWatcher, + + unpacker := &source.ContainersImageRegistry{ + BaseCachePath: filepath.Join(cachePath, "unpack"), + SourceContext: &types.SystemContext{ + DockerCertPath: caCertDir, + OCICertPath: caCertDir, + }, } clusterExtensionFinalizers := crfinalizer.NewFinalizers() if err := clusterExtensionFinalizers.Register(controllers.ClusterExtensionCleanupUnpackCacheFinalizer, finalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) { - return crfinalizer.Result{}, os.RemoveAll(filepath.Join(unpacker.BaseCachePath, obj.GetName())) + return crfinalizer.Result{}, unpacker.Cleanup(ctx, &source.BundleSource{Name: obj.GetName()}) })); err != nil { setupLog.Error(err, "unable to register finalizer", "finalizerKey", controllers.ClusterExtensionCleanupUnpackCacheFinalizer) os.Exit(1) diff --git a/config/components/registries-conf/kustomization.yaml b/config/components/registries-conf/kustomization.yaml new file mode 100644 index 000000000..e48262429 --- /dev/null +++ b/config/components/registries-conf/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +namespace: olmv1-system +resources: +- registries_conf_configmap.yaml +patches: +- path: manager_e2e_registries_conf_patch.yaml diff --git a/config/components/registries-conf/manager_e2e_registries_conf_patch.yaml b/config/components/registries-conf/manager_e2e_registries_conf_patch.yaml new file mode 100644 index 000000000..7530f9b08 --- /dev/null +++ b/config/components/registries-conf/manager_e2e_registries_conf_patch.yaml @@ -0,0 +1,18 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: kube-rbac-proxy + - name: manager + volumeMounts: + - name: e2e-registries-conf + mountPath: /etc/containers + volumes: + - name: e2e-registries-conf + configMap: + name: e2e-registries-conf diff --git a/config/components/registries-conf/registries_conf_configmap.yaml b/config/components/registries-conf/registries_conf_configmap.yaml new file mode 100644 index 000000000..df33edb41 --- /dev/null +++ b/config/components/registries-conf/registries_conf_configmap.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: e2e-registries-conf + namespace: system +data: + registries.conf: | + [[registry]] + prefix = "docker-registry.operator-controller-e2e.svc.cluster.local:5000" + insecure = true + location = "docker-registry.operator-controller-e2e.svc.cluster.local:5000" diff --git a/config/overlays/e2e/kustomization.yaml b/config/overlays/e2e/kustomization.yaml index 626ecb619..4a40576bd 100644 --- a/config/overlays/e2e/kustomization.yaml +++ b/config/overlays/e2e/kustomization.yaml @@ -7,5 +7,6 @@ resources: components: - ../../components/tls - ../../components/coverage +- ../../components/registries-conf # ca must be last or (tls|coverage) will overwrite the namespaces - ../../components/ca diff --git a/config/samples/olm_v1alpha1_clusterextension.yaml b/config/samples/olm_v1alpha1_clusterextension.yaml index 19bbf06b0..7536c3d90 100644 --- a/config/samples/olm_v1alpha1_clusterextension.yaml +++ b/config/samples/olm_v1alpha1_clusterextension.yaml @@ -49,20 +49,20 @@ rules: # Manage ArgoCD ClusterRoles and ClusterRoleBindings - apiGroups: [rbac.authorization.k8s.io] resources: [clusterroles] - verbs: [create] + verbs: [create, list, watch] - apiGroups: [rbac.authorization.k8s.io] resources: [clusterroles] - verbs: [get, list, watch, update, patch, delete] + verbs: [get, update, patch, delete] resourceNames: - argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx - argocd-operator-metrics-reader - argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3 - apiGroups: [rbac.authorization.k8s.io] resources: [clusterrolebindings] - verbs: [create] + verbs: [create, list, watch] - apiGroups: [rbac.authorization.k8s.io] resources: [clusterrolebindings] - verbs: [get, list, watch, update, patch, delete] + verbs: [get, update, patch, delete] resourceNames: - argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx - argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3 @@ -226,31 +226,31 @@ metadata: rules: - apiGroups: [""] resources: [serviceaccounts] - verbs: [create] + verbs: [create, list, watch] - apiGroups: [""] resources: [serviceaccounts] - verbs: [get, list, watch, update, patch, delete] + verbs: [get, update, patch, delete] resourceNames: [argocd-operator-controller-manager] - apiGroups: [""] resources: [configmaps] - verbs: [create] + verbs: [create, list, watch] - apiGroups: [""] resources: [configmaps] - verbs: [get, list, watch, update, patch, delete] + verbs: [get, update, patch, delete] resourceNames: [argocd-operator-manager-config] - apiGroups: [""] resources: [services] - verbs: [create] + verbs: [create, list, watch] - apiGroups: [""] resources: [services] - verbs: [get, list, watch, update, patch, delete] + verbs: [get, update, patch, delete] resourceNames: [argocd-operator-controller-manager-metrics-service] - apiGroups: [apps] resources: [deployments] - verbs: [create] + verbs: [create, list, watch] - apiGroups: [apps] resources: [deployments] - verbs: [get, list, watch, update, patch, delete] + verbs: [get, update, patch, delete] resourceNames: [argocd-operator-controller-manager] --- apiVersion: rbac.authorization.k8s.io/v1 diff --git a/go.mod b/go.mod index 21edbf10b..adfc16014 100644 --- a/go.mod +++ b/go.mod @@ -4,15 +4,19 @@ go 1.22.7 require ( carvel.dev/kapp v0.63.3 + github.com/BurntSushi/toml v1.4.0 github.com/Masterminds/semver/v3 v3.3.0 github.com/blang/semver/v4 v4.0.0 github.com/containerd/containerd v1.7.22 + github.com/containers/image/v5 v5.32.2 github.com/fsnotify/fsnotify v1.7.0 github.com/go-logr/logr v1.4.2 github.com/google/go-cmp v0.6.0 github.com/google/go-containerregistry v0.20.2 + github.com/olareg/olareg v0.1.1 github.com/onsi/ginkgo/v2 v2.20.2 github.com/onsi/gomega v1.34.2 + github.com/opencontainers/go-digest v1.0.0 github.com/operator-framework/api v0.27.0 github.com/operator-framework/catalogd v0.26.0 github.com/operator-framework/helm-operator-plugins v0.5.0 @@ -38,13 +42,14 @@ require ( carvel.dev/vendir v0.40.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/BurntSushi/toml v1.4.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/hcsshim v0.12.5 // 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 @@ -61,13 +66,13 @@ require ( github.com/containerd/ttrpc v1.2.5 // indirect github.com/containerd/typeurl/v2 v2.1.1 // indirect github.com/containers/common v0.60.2 // indirect - github.com/containers/image/v5 v5.32.2 // indirect github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect github.com/containers/ocicrypt v1.2.0 // indirect github.com/containers/storage v1.55.0 // indirect github.com/cppforlife/cobrautil v0.0.0-20221130162803-acdfead391ef // indirect github.com/cppforlife/color v1.9.1-0.20200716202919-6706ac40b835 // indirect github.com/cppforlife/go-cli-ui v0.0.0-20220425131040-94f26b16bc14 // indirect + github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f // indirect github.com/cyphar/filepath-securejoin v0.3.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect @@ -90,11 +95,19 @@ require ( github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-git/go-git/v5 v5.12.0 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect + github.com/go-jose/go-jose/v4 v4.0.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/analysis v0.23.0 // indirect + github.com/go-openapi/errors v0.22.0 // 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.0 // indirect + github.com/go-openapi/validate v0.24.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -128,17 +141,22 @@ require ( github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368 // indirect github.com/k14s/ytt v0.36.0 // indirect github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/pgzip v1.2.6 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/letsencrypt/boulder v0.0.0-20240418210053-89b07f4543e0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-sqlite3 v1.14.23 // indirect + github.com/miekg/pkcs11 v1.1.1 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/spdystream v0.4.0 // indirect @@ -152,7 +170,7 @@ require ( github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/oklog/ulid v1.3.1 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opencontainers/runtime-spec v1.2.0 // indirect github.com/openshift/crd-schema-checker v0.0.0-20240404194209-35a9033b1d11 // indirect @@ -161,6 +179,7 @@ require ( github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/proglottis/gpgme v0.1.3 // indirect github.com/prometheus/client_golang v1.20.3 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect @@ -168,14 +187,22 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/rubenv/sql-migrate v1.5.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.8.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect + github.com/sigstore/fulcio v1.4.5 // indirect + github.com/sigstore/rekor v1.3.6 // indirect + github.com/sigstore/sigstore v1.8.4 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/cast v1.6.0 // indirect github.com/spf13/cobra v1.8.1 // indirect + github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // 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.11.5 // indirect + github.com/vbauerster/mpb/v8 v8.7.5 // indirect github.com/vito/go-interact v1.0.1 // indirect github.com/vmware-tanzu/carvel-kapp-controller v0.51.0 // indirect github.com/x448/float16 v0.8.4 // indirect @@ -184,6 +211,8 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect go.etcd.io/bbolt v1.3.11 // indirect + go.mongodb.org/mongo-driver v1.14.0 // indirect + go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect go.opentelemetry.io/otel v1.28.0 // indirect @@ -205,7 +234,7 @@ require ( golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.24.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect google.golang.org/grpc v1.66.0 // indirect diff --git a/go.sum b/go.sum index ae892a516..17275b18c 100644 --- a/go.sum +++ b/go.sum @@ -14,7 +14,11 @@ cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7 cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 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-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= @@ -41,6 +45,10 @@ github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA github.com/Microsoft/hcsshim v0.12.5 h1:bpTInLlDy/nDRWFVcefDZZ1+U8tS+rz3MxjKgu9boo0= github.com/Microsoft/hcsshim v0.12.5/go.mod h1:tIUGego4G1EN5Hb6KC90aDYiUI2dqLSTTOCjVNpOgZ8= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +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/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= @@ -132,6 +140,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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-20231217050601-ba74d44ecf5f h1:eHnXnuK47UlSTOQexbzxAZfekVz6i+LKRdj1CU5DPaM= +github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= github.com/cyphar/filepath-securejoin v0.3.1 h1:1V7cHiaW+C+39wEfpH6XlLBQo3j/PciWFrgfCLS8XrE= github.com/cyphar/filepath-securejoin v0.3.1/go.mod h1:F7i41x/9cBF7lzCrVsYs9fuzwRZm4NQsGTBdpp6mETc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -183,8 +193,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -203,6 +213,8 @@ github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXY github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= +github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk= +github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -213,17 +225,34 @@ 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.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= +github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= 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.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +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-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= 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/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= +github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= @@ -313,8 +342,9 @@ github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16 github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -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/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 v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= @@ -344,7 +374,7 @@ github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= @@ -367,6 +397,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/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/joelanford/ignore v0.1.0 h1:VawbTDeg5EL+PN7W8gxVzGerfGpVo3gFdR5ZAqnkYRk= @@ -414,6 +446,8 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/letsencrypt/boulder v0.0.0-20240418210053-89b07f4543e0 h1:aiPrFdHDCCvigNBCkOWj2lv9Bx5xDp210OANZEoiP0I= +github.com/letsencrypt/boulder v0.0.0-20240418210053-89b07f4543e0/go.mod h1:srVwm2N3DC/tWqQ+igZXDrmKlNRN8X/dmJ1wEZrv760= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= @@ -445,8 +479,10 @@ github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWt github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.25 h1:dFwPR6SfLtrSwgDcIq2bcU/gVutB4sNApq2HBdqcakg= -github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= +github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= +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/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= @@ -461,6 +497,8 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +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/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -493,9 +531,12 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW 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/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olareg/olareg v0.1.1 h1:Ui7q93zjcoF+U9U71sgqgZWByDoZOpqHitUXEu2xV+g= +github.com/olareg/olareg v0.1.1/go.mod h1:w8NP4SWrHHtxsFaUiv1lnCnYPm4sN1seCd2h7FK/dc0= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= @@ -546,6 +587,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= +github.com/proglottis/gpgme v0.1.3 h1:Crxx0oz4LKB3QXc5Ea0J19K/3ICfy3ftr5exgUK1AU0= +github.com/proglottis/gpgme v0.1.3/go.mod h1:fPbW/EZ0LvwQtH8Hy7eixhp1eF3G39dtx7GUN+0Gmy0= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -574,8 +617,8 @@ github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fO github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= -github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY= -github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= +github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8= +github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= 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= @@ -591,12 +634,20 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/secure-systems-lab/go-securesystemslib v0.8.0 h1:mr5An6X45Kb2nddcFlbmfHkLguCE9laoZCUzEEpIZXA= +github.com/secure-systems-lab/go-securesystemslib v0.8.0/go.mod h1:UH2VZVuJfCYR8WgMlCU1uFsOUU+KeyrTWcSS73NBOzU= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sigstore/fulcio v1.4.5 h1:WWNnrOknD0DbruuZWCbN+86WRROpEl3Xts+WT2Ek1yc= +github.com/sigstore/fulcio v1.4.5/go.mod h1:oz3Qwlma8dWcSS/IENR/6SjbW4ipN0cxpRVfgdsjMU8= +github.com/sigstore/rekor v1.3.6 h1:QvpMMJVWAp69a3CHzdrLelqEqpTM3ByQRt5B5Kspbi8= +github.com/sigstore/rekor v1.3.6/go.mod h1:JDTSNNMdQ/PxdsS49DJkJ+pRJCO/83nbR5p3aZQteXc= +github.com/sigstore/sigstore v1.8.4 h1:g4ICNpiENFnWxjmBzBDWUn62rNFeny/P77HUC8da32w= +github.com/sigstore/sigstore v1.8.4/go.mod h1:1jIKtkTFEeISen7en+ZPWdDHazqhxco/+v9CNjc7oNg= 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= @@ -607,8 +658,8 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= @@ -617,6 +668,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +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= @@ -638,12 +691,16 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +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/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= +github.com/vbauerster/mpb/v8 v8.7.5 h1:hUF3zaNsuaBBwzEFoCvfuX3cpesQXZC0Phm/JcHZQ+c= +github.com/vbauerster/mpb/v8 v8.7.5/go.mod h1:bRCnR7K+mj5WXKsy0NWB6Or+wctYGvVwKn6huwvxKa0= github.com/vito/go-interact v0.0.0-20171111012221-fa338ed9e9ec/go.mod h1:wPlfmglZmRWMYv/qJy3P+fK/UnoQB5ISk4txfNd9tDo= github.com/vito/go-interact v1.0.1 h1:O8xi8c93bRUv2Tb/v6HdiuGc+WnWt+AQzF74MOOdlBs= github.com/vito/go-interact v1.0.1/go.mod h1:HrdHSJXD2yn1MhlTwSIMeFgQ5WftiIorszVGd3S/DAA= @@ -673,6 +730,10 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.14 h1:SaNH6Y+rVEdxfpA2Jr5wkEvN6Zykme5+YnbCkxv go.etcd.io/etcd/client/pkg/v3 v3.5.14/go.mod h1:8uMgAokyG1czCtIdsq+AGyYQMvpIKnSvPjFMunkgeZI= go.etcd.io/etcd/client/v3 v3.5.14 h1:CWfRs4FDaDoSz81giL7zPpZH2Z35tbOrAJkkjMqOupg= go.etcd.io/etcd/client/v3 v3.5.14/go.mod h1:k3XfdV/VIHy/97rqWjoUzrj9tk7GgJGH9J8L4dNXmAk= +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.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 h1:CCriYyAfq1Br1aIYettdHZTy8mBTIPo7We18TuO/bak= +go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= @@ -903,8 +964,8 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= +google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 h1:ImUcDPHjTrAqNhlOkSocDLfG9rrNHH7w7uoKWPaWZ8s= +google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7/go.mod h1:/3XmxOjePkvmKrHuBy4zNFw7IzxJXtAgdpXi8Ll990U= google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= diff --git a/hack/test/image-registry.sh b/hack/test/image-registry.sh index bbfa096a8..1187de77d 100755 --- a/hack/test/image-registry.sh +++ b/hack/test/image-registry.sh @@ -7,14 +7,16 @@ set -o pipefail help=" image-registry.sh is a script to stand up an image registry within a cluster. Usage: - image-registry.sh [NAMESPACE] [NAME] + image-registry.sh [NAMESPACE] [NAME] [CERT_REF] Argument Descriptions: - NAMESPACE is the namespace that should be created and is the namespace in which the image registry will be created - NAME is the name that should be used for the image registry Deployment and Service + - CERT_REF is the reference to the CA certificate that should be used to serve the image registry over HTTPS, in the + format of 'Issuer/' or 'ClusterIssuer/' " -if [[ "$#" -ne 2 ]]; then +if [[ "$#" -ne 3 ]]; then echo "Illegal number of arguments passed" echo "${help}" exit 1 @@ -22,12 +24,23 @@ fi namespace=$1 name=$2 +certRef=$3 + +echo "CERT_REF: ${certRef}" kubectl apply -f - << EOF apiVersion: v1 kind: Namespace metadata: name: ${namespace} +--- + apiVersion: cert-manager.io/v1 + kind: Issuer + metadata: + name: selfsigned-issuer + namespace: ${namespace} + spec: + selfSigned: {} --- apiVersion: cert-manager.io/v1 kind: Certificate @@ -44,8 +57,8 @@ spec: algorithm: ECDSA size: 256 issuerRef: - name: olmv1-ca - kind: ClusterIssuer + name: ${certRef#*/} + kind: ${certRef%/*} group: cert-manager.io --- apiVersion: apps/v1 diff --git a/internal/controllers/clusterextension_controller.go b/internal/controllers/clusterextension_controller.go index c2830dc29..dc3dd7247 100644 --- a/internal/controllers/clusterextension_controller.go +++ b/internal/controllers/clusterextension_controller.go @@ -266,7 +266,7 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp ensureAllConditionsWithReason(ext, ocv1alpha1.ReasonFailed, "unpack pending") return ctrl.Result{}, nil case rukpaksource.StateUnpacked: - setStatusUnpacked(ext, fmt.Sprintf("unpack successful: %v", unpackResult.Message)) + setStatusUnpacked(ext, unpackResult.Message) default: setStatusUnpackFailed(ext, "unexpected unpack status") // We previously exit with a failed status if error is not nil. diff --git a/internal/controllers/common_controller.go b/internal/controllers/common_controller.go index 6e94bcc10..8beeb3e31 100644 --- a/internal/controllers/common_controller.go +++ b/internal/controllers/common_controller.go @@ -79,6 +79,9 @@ func setStatusUnpackFailed(ext *ocv1alpha1.ClusterExtension, message string) { } func setStatusUnpacked(ext *ocv1alpha1.ClusterExtension, message string) { + if message == "" { + message = "unpack successful" + } apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ Type: ocv1alpha1.TypeUnpacked, Status: metav1.ConditionTrue, diff --git a/internal/rukpak/source/containers_image.go b/internal/rukpak/source/containers_image.go new file mode 100644 index 000000000..60841ea49 --- /dev/null +++ b/internal/rukpak/source/containers_image.go @@ -0,0 +1,276 @@ +package source + +import ( + "archive/tar" + "context" + "errors" + "fmt" + "io" + "os" + "path/filepath" + + "github.com/containerd/containerd/archive" + "github.com/containers/image/v5/copy" + "github.com/containers/image/v5/docker" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/oci/layout" + "github.com/containers/image/v5/pkg/blobinfocache/none" + "github.com/containers/image/v5/pkg/compression" + "github.com/containers/image/v5/signature" + "github.com/containers/image/v5/types" + "github.com/opencontainers/go-digest" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +type ContainersImageRegistry struct { + BaseCachePath string + SourceContext *types.SystemContext +} + +func (i *ContainersImageRegistry) Unpack(ctx context.Context, bundle *BundleSource) (*Result, error) { + l := log.FromContext(ctx) + + if bundle.Type != SourceTypeImage { + panic(fmt.Sprintf("programmer error: source type %q is unable to handle specified bundle source type %q", SourceTypeImage, bundle.Type)) + } + + if bundle.Image == nil { + return nil, NewUnrecoverable(fmt.Errorf("error parsing bundle, bundle %s has a nil image source", bundle.Name)) + } + + ////////////////////////////////////////////////////// + // + // Resolve a canonical reference for the image. + // + ////////////////////////////////////////////////////// + imgRef, err := reference.ParseNamed(bundle.Image.Ref) + if err != nil { + return nil, NewUnrecoverable(fmt.Errorf("error parsing image reference %q: %w", bundle.Image.Ref, err)) + } + + canonicalRef, err := resolveCanonicalRef(ctx, imgRef, i.SourceContext) + if err != nil { + return nil, fmt.Errorf("error resolving canonical reference: %w", err) + } + + ////////////////////////////////////////////////////// + // + // Check if the image is already unpacked. If it is, + // return the unpacked directory. + // + ////////////////////////////////////////////////////// + unpackPath := i.unpackPath(bundle.Name, canonicalRef.Digest()) + if unpackStat, err := os.Stat(unpackPath); err == nil { + if !unpackStat.IsDir() { + return nil, fmt.Errorf("unexpected file at unpack path %q: expected a directory", unpackPath) + } + l.Info("image already unpacked", "ref", imgRef.String(), "digest", canonicalRef.Digest().String()) + return successResult(bundle.Name, unpackPath, canonicalRef), nil + } + + ////////////////////////////////////////////////////// + // + // Create a docker reference for the source and an OCI + // layout reference for the destination, where we will + // temporarily store the image in order to unpack it. + // + // We use the OCI layout as a temporary storage because + // copy.Image can concurrently pull all the layers. + // + ////////////////////////////////////////////////////// + dockerRef, err := docker.NewReference(canonicalRef) + if err != nil { + return nil, fmt.Errorf("error creating source reference: %w", err) + } + + layoutDir, err := os.MkdirTemp("", fmt.Sprintf("oci-layout-%s", bundle.Name)) + if err != nil { + return nil, fmt.Errorf("error creating temporary directory: %w", err) + } + defer os.RemoveAll(layoutDir) + + layoutRef, err := layout.NewReference(layoutDir, canonicalRef.String()) + if err != nil { + return nil, fmt.Errorf("error creating reference: %w", err) + } + + ////////////////////////////////////////////////////// + // + // Load an image signature policy and build + // a policy context for the image pull. + // + ////////////////////////////////////////////////////// + policy, err := signature.DefaultPolicy(i.SourceContext) + if os.IsNotExist(err) { + l.Info("no default policy found, using insecure policy") + policy, err = signature.NewPolicyFromBytes([]byte(`{"default":[{"type":"insecureAcceptAnything"}]}`)) + } + if err != nil { + return nil, fmt.Errorf("error getting policy: %w", err) + } + policyContext, err := signature.NewPolicyContext(policy) + if err != nil { + return nil, fmt.Errorf("error getting policy context: %w", err) + } + defer func() { + if err := policyContext.Destroy(); err != nil { + l.Error(err, "error destroying policy context") + } + }() + + ////////////////////////////////////////////////////// + // + // Pull the image from the source to the destination + // + ////////////////////////////////////////////////////// + if _, err := copy.Image(ctx, policyContext, layoutRef, dockerRef, ©.Options{ + SourceCtx: i.SourceContext, + }); err != nil { + return nil, fmt.Errorf("error copying image: %w", err) + } + l.Info("pulled image", "ref", imgRef.String(), "digest", canonicalRef.Digest().String()) + + ////////////////////////////////////////////////////// + // + // Mount the image we just pulled + // + ////////////////////////////////////////////////////// + if err := i.unpackImage(ctx, unpackPath, layoutRef); err != nil { + return nil, fmt.Errorf("error unpacking image: %w", err) + } + + ////////////////////////////////////////////////////// + // + // Delete other images. They are no longer needed. + // + ////////////////////////////////////////////////////// + if err := i.deleteOtherImages(bundle.Name, canonicalRef.Digest()); err != nil { + return nil, fmt.Errorf("error deleting old images: %w", err) + } + + return successResult(bundle.Name, unpackPath, canonicalRef), nil +} + +func successResult(bundleName, unpackPath string, canonicalRef reference.Canonical) *Result { + return &Result{ + Bundle: os.DirFS(unpackPath), + ResolvedSource: &BundleSource{Type: SourceTypeImage, Name: bundleName, Image: &ImageSource{Ref: canonicalRef.String()}}, + State: StateUnpacked, + Message: fmt.Sprintf("unpacked %q successfully", canonicalRef), + } +} + +func (i *ContainersImageRegistry) Cleanup(_ context.Context, bundle *BundleSource) error { + return os.RemoveAll(i.bundlePath(bundle.Name)) +} + +func (i *ContainersImageRegistry) bundlePath(bundleName string) string { + return filepath.Join(i.BaseCachePath, bundleName) +} + +func (i *ContainersImageRegistry) unpackPath(bundleName string, digest digest.Digest) string { + return filepath.Join(i.bundlePath(bundleName), digest.String()) +} + +func resolveCanonicalRef(ctx context.Context, imgRef reference.Named, imageCtx *types.SystemContext) (reference.Canonical, error) { + if canonicalRef, ok := imgRef.(reference.Canonical); ok { + return canonicalRef, nil + } + + srcRef, err := docker.NewReference(imgRef) + if err != nil { + return nil, NewUnrecoverable(fmt.Errorf("error creating reference: %w", err)) + } + + imgSrc, err := srcRef.NewImageSource(ctx, imageCtx) + if err != nil { + return nil, fmt.Errorf("error creating image source: %w", err) + } + defer imgSrc.Close() + + imgManifestData, _, err := imgSrc.GetManifest(ctx, nil) + if err != nil { + return nil, fmt.Errorf("error getting manifest: %w", err) + } + imgDigest, err := manifest.Digest(imgManifestData) + if err != nil { + return nil, fmt.Errorf("error getting digest of manifest: %w", err) + } + return reference.WithDigest(reference.TrimNamed(imgRef), imgDigest) +} + +func (i *ContainersImageRegistry) unpackImage(ctx context.Context, unpackPath string, imageReference types.ImageReference) error { + img, err := imageReference.NewImage(ctx, i.SourceContext) + if err != nil { + return fmt.Errorf("error reading image: %w", err) + } + defer func() { + if err := img.Close(); err != nil { + panic(err) + } + }() + + layoutSrc, err := imageReference.NewImageSource(ctx, i.SourceContext) + if err != nil { + return fmt.Errorf("error creating image source: %w", err) + } + + if err := os.MkdirAll(unpackPath, 0755); err != nil { + return fmt.Errorf("error creating unpack directory: %w", err) + } + l := log.FromContext(ctx) + l.Info("unpacking image", "path", unpackPath) + for i, layerInfo := range img.LayerInfos() { + if err := func() error { + layerReader, _, err := layoutSrc.GetBlob(ctx, layerInfo, none.NoCache) + if err != nil { + return fmt.Errorf("error getting blob for layer[%d]: %w", i, err) + } + defer layerReader.Close() + + if err := applyLayer(ctx, unpackPath, layerReader); err != nil { + return fmt.Errorf("error applying layer[%d]: %w", i, err) + } + l.Info("applied layer", "layer", i) + return nil + }(); err != nil { + return errors.Join(err, os.RemoveAll(unpackPath)) + } + } + return nil +} + +func applyLayer(ctx context.Context, unpackPath string, layer io.ReadCloser) error { + decompressed, _, err := compression.AutoDecompress(layer) + if err != nil { + return fmt.Errorf("auto-decompress failed: %w", err) + } + defer decompressed.Close() + + _, err = archive.Apply(ctx, unpackPath, decompressed, archive.WithFilter(func(h *tar.Header) (bool, error) { + h.Uid = os.Getuid() + h.Gid = os.Getgid() + h.Mode |= 0770 + return true, nil + })) + return err +} + +func (i *ContainersImageRegistry) deleteOtherImages(bundleName string, digestToKeep digest.Digest) error { + bundlePath := i.bundlePath(bundleName) + imgDirs, err := os.ReadDir(bundlePath) + if err != nil { + return fmt.Errorf("error reading image directories: %w", err) + } + for _, imgDir := range imgDirs { + if imgDir.Name() == digestToKeep.String() { + continue + } + imgDirPath := filepath.Join(bundlePath, imgDir.Name()) + if err := os.RemoveAll(imgDirPath); err != nil { + return fmt.Errorf("error removing image directory: %w", err) + } + } + return nil +} diff --git a/internal/rukpak/source/containers_image_test.go b/internal/rukpak/source/containers_image_test.go new file mode 100644 index 000000000..1d1c42dcd --- /dev/null +++ b/internal/rukpak/source/containers_image_test.go @@ -0,0 +1,391 @@ +package source_test + +import ( + "context" + "fmt" + "io/fs" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "testing" + + "github.com/BurntSushi/toml" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/pkg/sysregistriesv2" + "github.com/containers/image/v5/types" + "github.com/google/go-containerregistry/pkg/crane" + "github.com/olareg/olareg" + "github.com/olareg/olareg/config" + "github.com/opencontainers/go-digest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/operator-framework/operator-controller/internal/rukpak/source" +) + +const ( + testFileName string = "test-file" + testFileContents string = "test-content" +) + +func TestUnpackValidInsecure(t *testing.T) { + imageTagRef, _, cleanup := setupRegistry(t) + defer cleanup() + + unpacker := &source.ContainersImageRegistry{ + BaseCachePath: t.TempDir(), + SourceContext: buildPullContext(t, imageTagRef), + } + bundleSource := &source.BundleSource{ + Name: "test-bundle", + Type: source.SourceTypeImage, + Image: &source.ImageSource{ + Ref: imageTagRef.String(), + }, + } + + oldBundlePath := filepath.Join(unpacker.BaseCachePath, bundleSource.Name, "old") + err := os.MkdirAll(oldBundlePath, 0755) + require.NoError(t, err) + + // Attempt to pull and unpack the image + result, err := unpacker.Unpack(context.Background(), bundleSource) + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, result.State, source.StateUnpacked) + + require.NoDirExists(t, oldBundlePath) + + unpackedFile, err := fs.ReadFile(result.Bundle, testFileName) + assert.NoError(t, err) + // Ensure the unpacked file matches the source content + assert.Equal(t, []byte(testFileContents), unpackedFile) +} + +func TestUnpackValidUsesCache(t *testing.T) { + _, imageDigestRef, cleanup := setupRegistry(t) + defer cleanup() + + unpacker := &source.ContainersImageRegistry{ + BaseCachePath: t.TempDir(), + SourceContext: buildPullContext(t, imageDigestRef), + } + + bundleSource := &source.BundleSource{ + Name: "test-bundle", + Type: source.SourceTypeImage, + Image: &source.ImageSource{ + Ref: imageDigestRef.String(), + }, + } + + // Populate the bundle cache with a folder that is not actually part of the image + testCacheFilePath := filepath.Join(unpacker.BaseCachePath, bundleSource.Name, imageDigestRef.Digest().String(), "test-folder") + require.NoError(t, os.MkdirAll(testCacheFilePath, 0700)) + + // Attempt to pull and unpack the image + result, err := unpacker.Unpack(context.Background(), bundleSource) + assert.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, result.State, source.StateUnpacked) + + // Make sure the original contents of the cache are still present. If the cached contents + // were not used, we would expect the original contents to be removed. + assert.DirExists(t, testCacheFilePath) +} + +func TestUnpackCacheCheckError(t *testing.T) { + imageTagRef, imageDigestRef, cleanup := setupRegistry(t) + defer cleanup() + + unpacker := &source.ContainersImageRegistry{ + BaseCachePath: t.TempDir(), + SourceContext: buildPullContext(t, imageTagRef), + } + bundleSource := &source.BundleSource{ + Name: "test-bundle", + Type: source.SourceTypeImage, + Image: &source.ImageSource{ + Ref: imageTagRef.String(), + }, + } + + // Create the unpack path and restrict its permissions + unpackPath := filepath.Join(unpacker.BaseCachePath, bundleSource.Name, imageDigestRef.Digest().String()) + require.NoError(t, os.MkdirAll(unpackPath, os.ModePerm)) + require.NoError(t, os.Chmod(unpacker.BaseCachePath, 0000)) + defer func() { + require.NoError(t, os.Chmod(unpacker.BaseCachePath, 0755)) + }() + + // Attempt to pull and unpack the image + _, err := unpacker.Unpack(context.Background(), bundleSource) + assert.ErrorContains(t, err, "permission denied") +} + +func TestUnpackNameOnlyImageReference(t *testing.T) { + imageTagRef, _, cleanup := setupRegistry(t) + defer cleanup() + + unpacker := &source.ContainersImageRegistry{ + BaseCachePath: t.TempDir(), + SourceContext: buildPullContext(t, imageTagRef), + } + bundleSource := &source.BundleSource{ + Name: "test-bundle", + Type: source.SourceTypeImage, + Image: &source.ImageSource{ + Ref: reference.TrimNamed(imageTagRef).String(), + }, + } + + // Attempt to pull and unpack the image + _, err := unpacker.Unpack(context.Background(), bundleSource) + assert.ErrorContains(t, err, "tag or digest is needed") + assert.ErrorAs(t, err, &source.Unrecoverable{}) +} + +func TestUnpackUnservedTaggedImageReference(t *testing.T) { + imageTagRef, _, cleanup := setupRegistry(t) + defer cleanup() + + unpacker := &source.ContainersImageRegistry{ + BaseCachePath: t.TempDir(), + SourceContext: buildPullContext(t, imageTagRef), + } + bundleSource := &source.BundleSource{ + Name: "test-bundle", + Type: source.SourceTypeImage, + Image: &source.ImageSource{ + // Use a valid reference that is not served + Ref: fmt.Sprintf("%s:unserved-tag", reference.TrimNamed(imageTagRef)), + }, + } + + // Attempt to pull and unpack the image + _, err := unpacker.Unpack(context.Background(), bundleSource) + assert.ErrorContains(t, err, "manifest unknown") +} + +func TestUnpackUnservedCanonicalImageReference(t *testing.T) { + imageTagRef, imageDigestRef, cleanup := setupRegistry(t) + defer cleanup() + + unpacker := &source.ContainersImageRegistry{ + BaseCachePath: t.TempDir(), + SourceContext: buildPullContext(t, imageTagRef), + } + + origRef := imageDigestRef.String() + nonExistentRef := origRef[:len(origRef)-1] + "1" + bundleSource := &source.BundleSource{ + Name: "test-bundle", + Type: source.SourceTypeImage, + Image: &source.ImageSource{ + // Use a valid reference that is not served + Ref: nonExistentRef, + }, + } + + // Attempt to pull and unpack the image + _, err := unpacker.Unpack(context.Background(), bundleSource) + assert.ErrorContains(t, err, "manifest unknown") +} + +func TestUnpackInvalidSourceType(t *testing.T) { + unpacker := &source.ContainersImageRegistry{} + // Create BundleSource with invalid source type + bundleSource := &source.BundleSource{ + Type: "invalid", + } + + shouldPanic := func() { + // Attempt to pull and unpack the image + _, err := unpacker.Unpack(context.Background(), bundleSource) + if err != nil { + t.Error("func should have panicked") + } + } + assert.Panics(t, shouldPanic) +} + +func TestUnpackInvalidNilImage(t *testing.T) { + unpacker := &source.ContainersImageRegistry{ + BaseCachePath: t.TempDir(), + } + // Create BundleSource with nil Image + bundleSource := &source.BundleSource{ + Name: "test-bundle", + Type: source.SourceTypeImage, + Image: nil, + } + + // Attempt to unpack + result, err := unpacker.Unpack(context.Background(), bundleSource) + assert.Nil(t, result) + assert.ErrorContains(t, err, "nil image source") + assert.ErrorAs(t, err, &source.Unrecoverable{}) + assert.NoDirExists(t, filepath.Join(unpacker.BaseCachePath, bundleSource.Name)) +} + +func TestUnpackInvalidImageRef(t *testing.T) { + unpacker := &source.ContainersImageRegistry{} + // Create BundleSource with malformed image reference + bundleSource := &source.BundleSource{ + Name: "test-bundle", + Type: source.SourceTypeImage, + Image: &source.ImageSource{ + Ref: "invalid image ref", + }, + } + + // Attempt to unpack + result, err := unpacker.Unpack(context.Background(), bundleSource) + assert.Nil(t, result) + assert.ErrorContains(t, err, "error parsing image reference") + assert.ErrorAs(t, err, &source.Unrecoverable{}) + assert.NoDirExists(t, filepath.Join(unpacker.BaseCachePath, bundleSource.Name)) +} + +func TestUnpackUnexpectedFile(t *testing.T) { + imageTagRef, imageDigestRef, cleanup := setupRegistry(t) + defer cleanup() + + unpacker := &source.ContainersImageRegistry{ + BaseCachePath: t.TempDir(), + SourceContext: buildPullContext(t, imageTagRef), + } + bundleSource := &source.BundleSource{ + Name: "test-bundle", + Type: source.SourceTypeImage, + Image: &source.ImageSource{ + Ref: imageTagRef.String(), + }, + } + + // Create an unpack path that is a file + unpackPath := filepath.Join(unpacker.BaseCachePath, bundleSource.Name, imageDigestRef.Digest().String()) + require.NoError(t, os.MkdirAll(filepath.Dir(unpackPath), 0700)) + require.NoError(t, os.WriteFile(unpackPath, []byte{}, 0600)) + + // Attempt to pull and unpack the image + _, err := unpacker.Unpack(context.Background(), bundleSource) + assert.ErrorContains(t, err, "expected a directory") +} + +func TestUnpackCopySucceedsMountFails(t *testing.T) { + imageTagRef, _, cleanup := setupRegistry(t) + defer cleanup() + + unpacker := &source.ContainersImageRegistry{ + BaseCachePath: t.TempDir(), + SourceContext: buildPullContext(t, imageTagRef), + } + bundleSource := &source.BundleSource{ + Name: "test-bundle", + Type: source.SourceTypeImage, + Image: &source.ImageSource{ + Ref: imageTagRef.String(), + }, + } + + // Create an unpack path that is a non-writable directory + bundleDir := filepath.Join(unpacker.BaseCachePath, bundleSource.Name) + require.NoError(t, os.MkdirAll(bundleDir, 0000)) + + // Attempt to pull and unpack the image + _, err := unpacker.Unpack(context.Background(), bundleSource) + assert.ErrorContains(t, err, "permission denied") +} + +func TestCleanup(t *testing.T) { + imageTagRef, _, cleanup := setupRegistry(t) + defer cleanup() + + unpacker := &source.ContainersImageRegistry{ + BaseCachePath: t.TempDir(), + SourceContext: buildPullContext(t, imageTagRef), + } + bundleSource := &source.BundleSource{ + Name: "test-bundle", + Type: source.SourceTypeImage, + Image: &source.ImageSource{ + Ref: imageTagRef.String(), + }, + } + + // Create an unpack path for the bundle + bundleDir := filepath.Join(unpacker.BaseCachePath, bundleSource.Name) + require.NoError(t, os.MkdirAll(bundleDir, 0755)) + + // Clean up the bundle + err := unpacker.Cleanup(context.Background(), bundleSource) + assert.NoError(t, err) + assert.NoDirExists(t, bundleDir) +} + +func setupRegistry(t *testing.T) (reference.NamedTagged, reference.Canonical, func()) { + regHandler := olareg.New(config.Config{ + Storage: config.ConfigStorage{ + StoreType: config.StoreMem, + }, + }) + server := httptest.NewServer(regHandler) + serverURL, err := url.Parse(server.URL) + require.NoError(t, err) + + // Generate an image with file contents + img, err := crane.Image(map[string][]byte{testFileName: []byte(testFileContents)}) + require.NoError(t, err) + + imageTagRef, err := newReference(serverURL.Host, "test-repo/test-image", "test-tag") + require.NoError(t, err) + + imgDigest, err := img.Digest() + require.NoError(t, err) + + imageDigestRef, err := reference.WithDigest(reference.TrimNamed(imageTagRef), digest.Digest(imgDigest.String())) + require.NoError(t, err) + + require.NoError(t, crane.Push(img, imageTagRef.String())) + + cleanup := func() { + server.Close() + require.NoError(t, regHandler.Close()) + } + return imageTagRef, imageDigestRef, cleanup +} + +func newReference(host, repo, tag string) (reference.NamedTagged, error) { + ref, err := reference.ParseNamed(fmt.Sprintf("%s/%s", host, repo)) + if err != nil { + return nil, err + } + return reference.WithTag(ref, tag) +} + +func buildPullContext(t *testing.T, ref reference.Named) *types.SystemContext { + // Build a containers/image context that allows pulling from the test registry insecurely + registriesConf := sysregistriesv2.V2RegistriesConf{Registries: []sysregistriesv2.Registry{ + { + Prefix: reference.Domain(ref), + Endpoint: sysregistriesv2.Endpoint{ + Location: reference.Domain(ref), + Insecure: true, + }, + }, + }} + configDir := t.TempDir() + registriesConfPath := filepath.Join(configDir, "registries.conf") + f, err := os.Create(registriesConfPath) + require.NoError(t, err) + + enc := toml.NewEncoder(f) + require.NoError(t, enc.Encode(registriesConf)) + require.NoError(t, f.Close()) + + return &types.SystemContext{ + SystemRegistriesConfPath: registriesConfPath, + } +} diff --git a/internal/rukpak/source/image_registry.go b/internal/rukpak/source/image_registry.go deleted file mode 100644 index 80233f7e6..000000000 --- a/internal/rukpak/source/image_registry.go +++ /dev/null @@ -1,197 +0,0 @@ -package source - -import ( - "archive/tar" - "context" - "crypto/tls" - "errors" - "fmt" - "io/fs" - "net/http" - "os" - "path/filepath" - "strings" - - "github.com/containerd/containerd/archive" - "github.com/google/go-containerregistry/pkg/name" - "github.com/google/go-containerregistry/pkg/v1/remote" - apimacherrors "k8s.io/apimachinery/pkg/util/errors" - "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/operator-framework/operator-controller/internal/httputil" -) - -// SourceTypeImage is the identifier for image-type bundle sources -const SourceTypeImage SourceType = "image" - -type ImageSource struct { - // Ref contains the reference to a container image containing Bundle contents. - Ref string - // InsecureSkipTLSVerify indicates that TLS certificate validation should be skipped. - // If this option is specified, the HTTPS protocol will still be used to - // fetch the specified image reference. - // This should not be used in a production environment. - InsecureSkipTLSVerify bool -} - -// Unrecoverable represents an error that can not be recovered -// from without user intervention. When this error is returned -// the request should not be requeued. -type Unrecoverable struct { - error -} - -func NewUnrecoverable(err error) *Unrecoverable { - return &Unrecoverable{err} -} - -// TODO: Make asynchronous - -type ImageRegistry struct { - BaseCachePath string - CertPoolWatcher *httputil.CertPoolWatcher -} - -func (i *ImageRegistry) Unpack(ctx context.Context, bundle *BundleSource) (*Result, error) { - l := log.FromContext(ctx) - if bundle.Type != SourceTypeImage { - panic(fmt.Sprintf("programmer error: source type %q is unable to handle specified bundle source type %q", SourceTypeImage, bundle.Type)) - } - - if bundle.Image == nil { - return nil, NewUnrecoverable(fmt.Errorf("error parsing bundle, bundle %s has a nil image source", bundle.Name)) - } - - imgRef, err := name.ParseReference(bundle.Image.Ref) - if err != nil { - return nil, NewUnrecoverable(fmt.Errorf("error parsing image reference: %w", err)) - } - - transport := remote.DefaultTransport.(*http.Transport).Clone() - if transport.TLSClientConfig == nil { - transport.TLSClientConfig = &tls.Config{ - InsecureSkipVerify: false, - MinVersion: tls.VersionTLS12, - } // nolint:gosec - } - if bundle.Image.InsecureSkipTLSVerify { - transport.TLSClientConfig.InsecureSkipVerify = true // nolint:gosec - } - if i.CertPoolWatcher != nil { - pool, _, err := i.CertPoolWatcher.Get() - if err != nil { - return nil, err - } - transport.TLSClientConfig.RootCAs = pool - } - - remoteOpts := []remote.Option{} - remoteOpts = append(remoteOpts, remote.WithTransport(transport)) - - digest, isDigest := imgRef.(name.Digest) - if isDigest { - hexVal := strings.TrimPrefix(digest.DigestStr(), "sha256:") - unpackPath := filepath.Join(i.BaseCachePath, bundle.Name, hexVal) - if stat, err := os.Stat(unpackPath); err == nil && stat.IsDir() { - l.V(1).Info("found image in filesystem cache", "digest", hexVal) - return unpackedResult(os.DirFS(unpackPath), bundle, digest.String()), nil - } - } - - // always fetch the hash - imgDesc, err := remote.Head(imgRef, remoteOpts...) - if err != nil { - return nil, fmt.Errorf("error fetching image descriptor: %w", err) - } - l.V(1).Info("resolved image descriptor", "digest", imgDesc.Digest.String()) - - unpackPath := filepath.Join(i.BaseCachePath, bundle.Name, imgDesc.Digest.Hex) - if _, err = os.Stat(unpackPath); errors.Is(err, os.ErrNotExist) { //nolint: nestif - // Ensure any previous unpacked bundle is cleaned up before unpacking the new catalog. - if err := i.Cleanup(ctx, bundle); err != nil { - return nil, fmt.Errorf("error cleaning up bundle cache: %w", err) - } - - if err = os.MkdirAll(unpackPath, 0700); err != nil { - return nil, fmt.Errorf("error creating unpack path: %w", err) - } - - if err = unpackImage(ctx, imgRef, unpackPath, remoteOpts...); err != nil { - cleanupErr := os.RemoveAll(unpackPath) - if cleanupErr != nil { - err = apimacherrors.NewAggregate( - []error{ - err, - fmt.Errorf("error cleaning up unpack path after unpack failed: %w", cleanupErr), - }, - ) - } - return nil, wrapUnrecoverable(fmt.Errorf("error unpacking image: %w", err), isDigest) - } - } else if err != nil { - return nil, fmt.Errorf("error checking if image is in filesystem cache: %w", err) - } - - resolvedRef := fmt.Sprintf("%s@sha256:%s", imgRef.Context().Name(), imgDesc.Digest.Hex) - return unpackedResult(os.DirFS(unpackPath), bundle, resolvedRef), nil -} - -func wrapUnrecoverable(err error, isUnrecoverable bool) error { - if isUnrecoverable { - return NewUnrecoverable(err) - } - return err -} - -func (i *ImageRegistry) Cleanup(_ context.Context, bundle *BundleSource) error { - return os.RemoveAll(filepath.Join(i.BaseCachePath, bundle.Name)) -} - -func unpackedResult(fsys fs.FS, bundle *BundleSource, ref string) *Result { - return &Result{ - Bundle: fsys, - ResolvedSource: &BundleSource{ - Type: SourceTypeImage, - Image: &ImageSource{ - Ref: ref, - InsecureSkipTLSVerify: bundle.Image.InsecureSkipTLSVerify, - }, - }, - State: StateUnpacked, - } -} - -// unpackImage unpacks a bundle image reference to the provided unpackPath, -// returning an error if any errors are encountered along the way. -func unpackImage(ctx context.Context, imgRef name.Reference, unpackPath string, remoteOpts ...remote.Option) error { - img, err := remote.Image(imgRef, remoteOpts...) - if err != nil { - return fmt.Errorf("error fetching remote image %q: %w", imgRef.Name(), err) - } - - layers, err := img.Layers() - if err != nil { - return fmt.Errorf("error getting image layers: %w", err) - } - - for _, layer := range layers { - layerRc, err := layer.Uncompressed() - if err != nil { - return fmt.Errorf("error getting uncompressed layer data: %w", err) - } - - // This filter ensures that the files created have the proper UID and GID - // for the filesystem they will be stored on to ensure no permission errors occur when attempting to create the - // files. - _, err = archive.Apply(ctx, unpackPath, layerRc, archive.WithFilter(func(th *tar.Header) (bool, error) { - th.Uid = os.Getuid() - th.Gid = os.Getgid() - return true, nil - })) - if err != nil { - return fmt.Errorf("error applying layer to archive: %w", err) - } - } - - return nil -} diff --git a/internal/rukpak/source/image_registry_test.go b/internal/rukpak/source/image_registry_test.go deleted file mode 100644 index 4cbd1d05b..000000000 --- a/internal/rukpak/source/image_registry_test.go +++ /dev/null @@ -1,360 +0,0 @@ -package source_test - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "io" - "log" - "net/http" - "net/http/httptest" - "net/url" - "os" - "path/filepath" - "testing" - - "github.com/google/go-containerregistry/pkg/crane" - "github.com/google/go-containerregistry/pkg/name" - v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-controller/internal/rukpak/source" -) - -const ( - testFilePathBase string = ".image-registry-test" - bogusDigestHex string = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - testImageTag string = "test-tag" - testImageName string = "test-image" - badImageName string = "bad-image" - testFileName string = "test-file" - testFileContents string = "test-content" -) - -func newReference(host, repo, ref string) (name.Reference, error) { - tag, err := name.NewTag(fmt.Sprintf("%s/%s:%s", host, repo, ref), name.WeakValidation) - if err == nil { - return tag, nil - } - return name.NewDigest(fmt.Sprintf("%s/%s@%s", host, repo, ref), name.WeakValidation) -} - -func imageToRawManifest(img v1.Image) ([]byte, error) { - manifest, err := img.Manifest() - if err != nil { - return nil, err - } - - layers, err := img.Layers() - if err != nil { - return nil, err - } - - rc, err := layers[0].Compressed() - if err != nil { - return nil, err - } - - lb, err := io.ReadAll(rc) - if err != nil { - return nil, err - } - - manifest.Layers[0].Data = lb - rawManifest, err := json.Marshal(manifest) - if err != nil { - return nil, err - } - - return rawManifest, nil -} - -// Adapted from: https://github.com/google/go-containerregistry/blob/main/pkg/v1/remote/image_test.go -// serveImageManifest starts a primitive image registry server hosting two images: "test-image" and "bad-image". -func serveImageManifest(rawManifest []byte) *httptest.Server { - return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case "/v2/": - w.WriteHeader(http.StatusOK) - case fmt.Sprintf("/v2/%s/manifests/%s", testImageName, testImageTag): - if r.Method == http.MethodHead { - w.Header().Set("Content-Length", fmt.Sprint(len(rawManifest))) - w.Header().Set("Docker-Content-Digest", fmt.Sprintf("sha256:%s", bogusDigestHex)) - w.WriteHeader(http.StatusOK) - } else if r.Method != http.MethodGet { - w.WriteHeader(http.StatusBadRequest) - } - _, err := w.Write(rawManifest) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - } - case fmt.Sprintf("/v2/%s/manifests/%s", badImageName, testImageTag): - case fmt.Sprintf("/v2/%s/manifests/sha256:%s", badImageName, bogusDigestHex): - if r.Method == http.MethodHead { - w.Header().Set("Content-Length", fmt.Sprint(len(rawManifest))) - w.Header().Set("Docker-Content-Digest", fmt.Sprintf("sha256:%s", bogusDigestHex)) - // We must set Content-Type since we're returning empty data below - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusOK) - return - } else if r.Method != http.MethodGet { - w.WriteHeader(http.StatusBadRequest) - } - _, err := w.Write(make([]byte, 0)) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - } - default: - w.WriteHeader(http.StatusBadRequest) - } - })) -} - -func testFileCleanup() { - if _, err := os.Stat(testFilePathBase); err != nil && errors.Is(err, os.ErrNotExist) { - // Nothing to clean up - return - } else if err != nil { - log.Fatalf("error occurred locating unpack folder in post-test cleanup: %v", err) - } - // Ensure permissions and remove the temporary directory - err := os.Chmod(testFilePathBase, os.ModePerm) - if err != nil { - log.Fatalf("error occurred ensuring unpack folder permissions in post-test cleanup: %v", err) - } - err = os.RemoveAll(testFilePathBase) - if err != nil { - log.Fatalf("error occurred deleting unpack folder in post-test cleanup: %v", err) - } -} - -var HostedImageReference name.Reference - -func TestMain(m *testing.M) { - // Generate an image with file contents - img, err := crane.Image(map[string][]byte{testFileName: []byte(testFileContents)}) - if err != nil { - log.Fatalf("failed to generate image for test") - } - // Create a raw bytes manifest from the image - rawManifest, err := imageToRawManifest(img) - if err != nil { - log.Fatalf("failed to generate manifest from image") - } - - // Start the image registry and serve the generated image manifest - server := serveImageManifest(rawManifest) - if err != nil { - log.Fatalf("image registry server failed to start") - } - u, err := url.Parse(server.URL) - if err != nil { - log.Fatalf("invalid server URL from image registry") - } - HostedImageReference, err = newReference(u.Host, testImageName, testImageTag) - if err != nil { - log.Fatalf("failed to generate image reference for served image") - } - code := m.Run() - server.Close() - os.Exit(code) -} - -func TestUnpackValidInsecure(t *testing.T) { - defer testFileCleanup() - unpacker := &source.ImageRegistry{ - BaseCachePath: testFilePathBase, - } - bundleSource := &source.BundleSource{ - Type: source.SourceTypeImage, - Image: &source.ImageSource{ - Ref: HostedImageReference.String(), - InsecureSkipTLSVerify: true, - }, - } - - unpackPath := filepath.Join(unpacker.BaseCachePath, bundleSource.Name, bogusDigestHex) - - // Create another folder to simulate an old unpacked bundle - oldBundlePath := filepath.Join(unpacker.BaseCachePath, bundleSource.Name, "foo") - require.NoError(t, os.MkdirAll(oldBundlePath, os.ModePerm)) - - // Attempt to pull and unpack the image - result, err := unpacker.Unpack(context.Background(), bundleSource) - // Check Result - require.NoError(t, err) - require.NotNil(t, result) - assert.Equal(t, result.State, source.StateUnpacked) - // Make sure the old bundle was cleaned up - require.NoDirExists(t, oldBundlePath) - - // Give permissions to read the file - require.NoError(t, os.Chmod(filepath.Join(unpackPath, testFileName), 0400)) - unpackedFile, err := os.ReadFile(filepath.Join(unpackPath, testFileName)) - require.NoError(t, err) - // Ensure the unpacked file matches the source content - require.Equal(t, []byte(testFileContents), unpackedFile) -} - -func TestUnpackValidUsesCache(t *testing.T) { - defer testFileCleanup() - unpacker := &source.ImageRegistry{ - BaseCachePath: testFilePathBase, - } - bundleSource := &source.BundleSource{ - Type: source.SourceTypeImage, - Image: &source.ImageSource{ - Ref: fmt.Sprintf("%s@sha256:%s", HostedImageReference.Context().Name(), bogusDigestHex), - InsecureSkipTLSVerify: true, - }, - } - - // Populate the bundle cache with a folder - testCacheFilePath := filepath.Join(unpacker.BaseCachePath, bundleSource.Name, bogusDigestHex, "test-folder") - require.NoError(t, os.MkdirAll(testCacheFilePath, os.ModePerm)) - - // Attempt to pull and unpack the image - result, err := unpacker.Unpack(context.Background(), bundleSource) - // Check Result - require.NoError(t, err) - require.NotNil(t, result) - assert.Equal(t, result.State, source.StateUnpacked) - // Make sure the old file was not cleaned up - require.DirExists(t, testCacheFilePath) -} - -func TestUnpackCacheCheckError(t *testing.T) { - defer testFileCleanup() - unpacker := &source.ImageRegistry{ - BaseCachePath: testFilePathBase, - } - bundleSource := &source.BundleSource{ - Type: source.SourceTypeImage, - Image: &source.ImageSource{ - Ref: HostedImageReference.String(), - InsecureSkipTLSVerify: true, - }, - } - - // Create the unpack path and restrict its permissions - unpackPath := filepath.Join(unpacker.BaseCachePath, bundleSource.Name, bogusDigestHex) - require.NoError(t, os.MkdirAll(unpackPath, os.ModePerm)) - require.NoError(t, os.Chmod(testFilePathBase, 0000)) - // Attempt to pull and unpack the image - _, err := unpacker.Unpack(context.Background(), bundleSource) - // Check Result - require.Error(t, err) -} - -func TestUnpackUnservedImageReference(t *testing.T) { - defer testFileCleanup() - unpacker := &source.ImageRegistry{ - BaseCachePath: testFilePathBase, - } - bundleSource := &source.BundleSource{ - Type: source.SourceTypeImage, - Image: &source.ImageSource{ - // Use a valid reference that is not served - Ref: fmt.Sprintf("%s/%s:unserved-tag", HostedImageReference.Context().Registry.RegistryStr(), badImageName), - InsecureSkipTLSVerify: true, - }, - } - - // Attempt to pull and unpack the image - _, err := unpacker.Unpack(context.Background(), bundleSource) - // Check Result - require.Error(t, err) -} - -func TestUnpackFailure(t *testing.T) { - defer testFileCleanup() - unpacker := &source.ImageRegistry{ - BaseCachePath: testFilePathBase, - } - bundleSource := &source.BundleSource{ - Type: source.SourceTypeImage, - Image: &source.ImageSource{ - // Use a valid reference that is served but will return bad image content - Ref: fmt.Sprintf("%s/%s:%s", HostedImageReference.Context().Registry.RegistryStr(), badImageName, testImageTag), - InsecureSkipTLSVerify: true, - }, - } - - // Attempt to pull and unpack the image - _, err := unpacker.Unpack(context.Background(), bundleSource) - // Check Result - require.Error(t, err) -} - -func TestUnpackFailureDigest(t *testing.T) { - defer testFileCleanup() - unpacker := &source.ImageRegistry{ - BaseCachePath: testFilePathBase, - } - bundleSource := &source.BundleSource{ - Type: source.SourceTypeImage, - Image: &source.ImageSource{ - // Use a valid reference that is served but will return bad image content - Ref: fmt.Sprintf("%s/%s@sha256:%s", HostedImageReference.Context().Registry.RegistryStr(), badImageName, bogusDigestHex), - InsecureSkipTLSVerify: true, - }, - } - - // Attempt to pull and unpack the image - _, err := unpacker.Unpack(context.Background(), bundleSource) - // Check Result - require.Error(t, err) - // Unpacker gives an error of type Unrecoverable - require.IsType(t, &source.Unrecoverable{}, err) -} - -func TestUnpackInvalidSourceType(t *testing.T) { - unpacker := &source.ImageRegistry{} - // Create BundleSource with invalid source type - bundleSource := &source.BundleSource{ - Type: "invalid", - } - - shouldPanic := func() { - // Attempt to pull and unpack the image - _, err := unpacker.Unpack(context.Background(), bundleSource) - if err != nil { - t.Error("func should have panicked") - } - } - require.Panics(t, shouldPanic) -} - -func TestUnpackInvalidNilImage(t *testing.T) { - unpacker := &source.ImageRegistry{} - // Create BundleSource with nil Image - bundleSource := &source.BundleSource{ - Type: source.SourceTypeImage, - Image: nil, - } - - // Attempt to unpack - result, err := unpacker.Unpack(context.Background(), bundleSource) - require.Error(t, err) - require.NoDirExists(t, testFilePathBase) - assert.Nil(t, result) -} - -func TestUnpackInvalidImageRef(t *testing.T) { - unpacker := &source.ImageRegistry{} - // Create BundleSource with malformed image reference - bundleSource := &source.BundleSource{ - Type: source.SourceTypeImage, - Image: &source.ImageSource{ - Ref: "invalid image ref", - }, - } - - // Attempt to unpack - result, err := unpacker.Unpack(context.Background(), bundleSource) - require.Error(t, err) - require.NoDirExists(t, testFilePathBase) - assert.Nil(t, result) -} diff --git a/internal/rukpak/source/unpacker.go b/internal/rukpak/source/unpacker.go index e98b92a68..5d2b5f633 100644 --- a/internal/rukpak/source/unpacker.go +++ b/internal/rukpak/source/unpacker.go @@ -2,10 +2,28 @@ package source import ( "context" - "fmt" "io/fs" ) +// SourceTypeImage is the identifier for image-type bundle sources +const SourceTypeImage SourceType = "image" + +type ImageSource struct { + // Ref contains the reference to a container image containing Bundle contents. + Ref string +} + +// Unrecoverable represents an error that can not be recovered +// from without user intervention. When this error is returned +// the request should not be requeued. +type Unrecoverable struct { + error +} + +func NewUnrecoverable(err error) Unrecoverable { + return Unrecoverable{err} +} + // Unpacker unpacks bundle content, either synchronously or asynchronously and // returns a Result, which conveys information about the progress of unpacking // the bundle content. @@ -74,29 +92,3 @@ type BundleSource struct { // Image is the bundle image that backs the content of this bundle. Image *ImageSource } - -type unpacker struct { - sources map[SourceType]Unpacker -} - -// NewUnpacker returns a new composite Source that unpacks bundles using the source -// mapping provided by the configured sources. -func NewUnpacker(sources map[SourceType]Unpacker) Unpacker { - return &unpacker{sources: sources} -} - -func (s *unpacker) Unpack(ctx context.Context, bundle *BundleSource) (*Result, error) { - source, ok := s.sources[bundle.Type] - if !ok { - return nil, fmt.Errorf("source type %q not supported", bundle.Type) - } - return source.Unpack(ctx, bundle) -} - -func (s *unpacker) Cleanup(ctx context.Context, bundle *BundleSource) error { - source, ok := s.sources[bundle.Type] - if !ok { - return fmt.Errorf("source type %q not supported", bundle.Type) - } - return source.Cleanup(ctx, bundle) -} diff --git a/test/e2e/cluster_extension_install_test.go b/test/e2e/cluster_extension_install_test.go index 64aeee219..4e908bda2 100644 --- a/test/e2e/cluster_extension_install_test.go +++ b/test/e2e/cluster_extension_install_test.go @@ -274,7 +274,7 @@ func TestClusterExtensionInstallRegistry(t *testing.T) { } assert.Equal(ct, metav1.ConditionTrue, cond.Status) assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Contains(ct, cond.Message, "unpack successful") + assert.Regexp(ct, "^unpacked .* successfully", cond.Message) }, pollDuration, pollInterval) t.Log("By eventually installing the package successfully") @@ -758,6 +758,7 @@ func TestClusterExtensionInstallReResolvesWhenManagedContentChanged(t *testing.T } assert.Equal(ct, metav1.ConditionTrue, cond.Status) assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) + assert.Contains(ct, cond.Message, "Installed bundle") assert.Equal(ct, &ocv1alpha1.ClusterExtensionResolutionStatus{Bundle: &ocv1alpha1.BundleMetadata{ Name: "prometheus-operator.1.2.0", @@ -849,7 +850,7 @@ func TestClusterExtensionRecoversFromInitialInstallFailedWhenFailureFixed(t *tes } assert.Equal(ct, metav1.ConditionTrue, cond.Status) assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Contains(ct, cond.Message, "unpack successful") + assert.Regexp(ct, "^unpacked .* successfully", cond.Message) }, pollDuration, pollInterval) t.Log("By eventually failing to install the package successfully due to insufficient ServiceAccount permissions")