├── CODEOWNERS ├── testdata └── catalogs │ ├── test-catalog │ ├── .indexignore │ ├── expected_all.json │ └── catalog.yaml │ └── test-catalog.Dockerfile ├── pprof ├── manager_cpu_profile.pb ├── manager_heap_profile.pb ├── images │ ├── controller_metrics.png │ ├── customapiserver_metrics.png │ ├── kubeapiserver_metrics.png │ └── kubeapiserver_alone_metrics.png ├── kubeapiserver_cpu_profile.pb ├── kubeapiserver_heap_profile.pb ├── catalogd_apiserver_cpu_profile.pb ├── catalogd_apiserver_heap_profile.pb ├── kubeapiserver_alone_cpu_profile.pb ├── kubeapiserver_alone_heap_profile.pb ├── kind.yaml └── README.md ├── .bingo ├── kind.mod ├── go.mod ├── bingo.mod ├── crd-diff.mod ├── controller-gen.mod ├── .gitignore ├── ginkgo.mod ├── goreleaser.mod ├── kustomize.mod ├── golangci-lint.mod ├── setup-envtest.mod ├── README.md ├── variables.env ├── ginkgo.sum ├── Variables.mk ├── kind.sum ├── bingo.sum └── setup-envtest.sum ├── config ├── components │ ├── tls │ │ ├── patches │ │ │ ├── catalogd_webhook.yaml │ │ │ ├── catalogd_service_port.yaml │ │ │ └── manager_deployment_certs.yaml │ │ ├── resources │ │ │ └── certificate.yaml │ │ └── kustomization.yaml │ ├── registries-conf │ │ ├── kustomization.yaml │ │ ├── registries_conf_configmap.yaml │ │ └── manager_e2e_registries_conf_patch.yaml │ └── ca │ │ ├── kustomization.yaml │ │ ├── patches │ │ └── manager_deployment_cacerts.yaml │ │ └── resources │ │ └── issuers.yaml ├── base │ ├── rbac │ │ ├── service_account.yaml │ │ ├── auth_proxy_client_clusterrole.yaml │ │ ├── role_binding.yaml │ │ ├── auth_proxy_role_binding.yaml │ │ ├── leader_election_role_binding.yaml │ │ ├── auth_proxy_role.yaml │ │ ├── role.yaml │ │ ├── leader_election_role.yaml │ │ └── kustomization.yaml │ ├── nginx-ingress │ │ ├── kustomization.yaml │ │ └── resources │ │ │ └── nginx_ingress.yaml │ ├── default │ │ ├── clustercatalogs │ │ │ └── default-catalogs.yaml │ │ └── kustomization.yaml │ ├── crd │ │ └── kustomization.yaml │ └── manager │ │ ├── kustomization.yaml │ │ ├── catalogd_service.yaml │ │ ├── webhook │ │ ├── manifests.yaml │ │ └── patch.yaml │ │ └── manager.yaml ├── overlays │ ├── cert-manager │ │ └── kustomization.yaml │ └── e2e │ │ └── kustomization.yaml ├── samples │ └── core_v1_clustercatalog.yaml └── rbac │ └── role.yaml ├── .dockerignore ├── Dockerfile ├── .github ├── pull_request_template.md ├── workflows │ ├── pr-title.yaml │ ├── go-verdiff.yaml │ ├── demo.yaml │ ├── go-apidiff.yaml │ ├── add-to-project.yaml │ ├── unit.yaml │ ├── crd-diff.yaml │ ├── e2e.yaml │ ├── sanity.yaml │ ├── tilt.yaml │ └── release.yaml └── dependabot.yml ├── .gitignore ├── internal ├── features │ └── features.go ├── k8sutil │ ├── k8sutil.go │ └── k8sutil_test.go ├── storage │ ├── storage.go │ ├── suite_test.go │ └── localdir.go ├── version │ └── version.go ├── metrics │ └── metrics.go ├── webhook │ ├── cluster_catalog_webhook.go │ └── cluster_catalog_webhook_test.go ├── serverutil │ └── serverutil.go ├── source │ ├── unpacker.go │ └── containers_image_internal_test.go ├── garbagecollection │ ├── garbage_collector_test.go │ └── garbage_collector.go ├── controllers │ └── core │ │ ├── pull_secret_controller_test.go │ │ └── pull_secret_controller.go └── third_party │ └── server │ └── server.go ├── Tiltfile ├── hack ├── boilerplate.go.txt └── scripts │ ├── gzip-demo-script.sh │ ├── generate-asciidemo.sh │ ├── generate-gzip-asciidemo.sh │ ├── demo-script.sh │ └── check-go-version.sh ├── api ├── doc.go └── v1 │ ├── groupversion_info.go │ └── zz_generated.deepcopy.go ├── test ├── tools │ └── imageregistry │ │ ├── pre-upgrade-setup.sh │ │ ├── imagebuilder.yaml │ │ ├── imgreg.yaml │ │ └── registry.sh ├── e2e │ ├── e2e_suite_test.go │ ├── util.go │ ├── unpack_test.go │ └── metrics_endpoint_test.go └── upgrade │ ├── upgrade_suite_test.go │ └── unpack_test.go ├── DCO ├── RELEASING.md ├── scripts └── install.tpl.sh ├── .golangci.yaml ├── .goreleaser.yml ├── tilt.md ├── crd-diff-config.yaml ├── CONTRIBUTING.md ├── README.md ├── go.mod └── docs └── fetching-catalog-contents.md /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @operator-framework/catalogd-maintainers 2 | -------------------------------------------------------------------------------- /testdata/catalogs/test-catalog/.indexignore: -------------------------------------------------------------------------------- 1 | /expected_all.json 2 | ..* 3 | -------------------------------------------------------------------------------- /pprof/manager_cpu_profile.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/operator-framework/catalogd/HEAD/pprof/manager_cpu_profile.pb -------------------------------------------------------------------------------- /pprof/manager_heap_profile.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/operator-framework/catalogd/HEAD/pprof/manager_heap_profile.pb -------------------------------------------------------------------------------- /pprof/images/controller_metrics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/operator-framework/catalogd/HEAD/pprof/images/controller_metrics.png -------------------------------------------------------------------------------- /pprof/kubeapiserver_cpu_profile.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/operator-framework/catalogd/HEAD/pprof/kubeapiserver_cpu_profile.pb -------------------------------------------------------------------------------- /pprof/kubeapiserver_heap_profile.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/operator-framework/catalogd/HEAD/pprof/kubeapiserver_heap_profile.pb -------------------------------------------------------------------------------- /pprof/catalogd_apiserver_cpu_profile.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/operator-framework/catalogd/HEAD/pprof/catalogd_apiserver_cpu_profile.pb -------------------------------------------------------------------------------- /pprof/catalogd_apiserver_heap_profile.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/operator-framework/catalogd/HEAD/pprof/catalogd_apiserver_heap_profile.pb -------------------------------------------------------------------------------- /pprof/images/customapiserver_metrics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/operator-framework/catalogd/HEAD/pprof/images/customapiserver_metrics.png -------------------------------------------------------------------------------- /pprof/images/kubeapiserver_metrics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/operator-framework/catalogd/HEAD/pprof/images/kubeapiserver_metrics.png -------------------------------------------------------------------------------- /pprof/kubeapiserver_alone_cpu_profile.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/operator-framework/catalogd/HEAD/pprof/kubeapiserver_alone_cpu_profile.pb -------------------------------------------------------------------------------- /pprof/kubeapiserver_alone_heap_profile.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/operator-framework/catalogd/HEAD/pprof/kubeapiserver_alone_heap_profile.pb -------------------------------------------------------------------------------- /pprof/images/kubeapiserver_alone_metrics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/operator-framework/catalogd/HEAD/pprof/images/kubeapiserver_alone_metrics.png -------------------------------------------------------------------------------- /.bingo/kind.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.20 4 | 5 | require sigs.k8s.io/kind v0.24.0 6 | -------------------------------------------------------------------------------- /.bingo/go.mod: -------------------------------------------------------------------------------- 1 | module _ // Fake go.mod auto-created by 'bingo' for go -moddir compatibility with non-Go projects. Commit this file, together with other .mod files. -------------------------------------------------------------------------------- /.bingo/bingo.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.20 4 | 5 | require github.com/bwplotka/bingo v0.9.0 6 | -------------------------------------------------------------------------------- /config/components/tls/patches/catalogd_webhook.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /metadata/annotations/cert-manager.io~1inject-ca-from-secret 3 | value: cert-manager/olmv1-ca 4 | -------------------------------------------------------------------------------- /.bingo/crd-diff.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.22.5 4 | 5 | require github.com/everettraven/crd-diff v0.1.0 6 | -------------------------------------------------------------------------------- /config/components/tls/patches/catalogd_service_port.yaml: -------------------------------------------------------------------------------- 1 | - op: replace 2 | path: /spec/ports/0/port 3 | value: 443 4 | - op: replace 5 | path: /spec/ports/0/name 6 | value: https -------------------------------------------------------------------------------- /.bingo/controller-gen.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.22.0 4 | 5 | require sigs.k8s.io/controller-tools v0.16.1 // cmd/controller-gen 6 | -------------------------------------------------------------------------------- /.bingo/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Ignore everything 3 | * 4 | 5 | # But not these files: 6 | !.gitignore 7 | !*.mod 8 | !*.sum 9 | !README.md 10 | !Variables.mk 11 | !variables.env 12 | 13 | *tmp.mod 14 | -------------------------------------------------------------------------------- /.bingo/ginkgo.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.23.0 6 | 7 | require github.com/onsi/ginkgo/v2 v2.22.0 // ginkgo 8 | -------------------------------------------------------------------------------- /.bingo/goreleaser.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.22 4 | 5 | toolchain go1.23.0 6 | 7 | require github.com/goreleaser/goreleaser v1.26.2 8 | -------------------------------------------------------------------------------- /.bingo/kustomize.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.21 4 | 5 | toolchain go1.23.0 6 | 7 | require sigs.k8s.io/kustomize/kustomize/v5 v5.4.3 8 | -------------------------------------------------------------------------------- /.bingo/golangci-lint.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.22.1 4 | 5 | toolchain go1.23.0 6 | 7 | require github.com/golangci/golangci-lint v1.60.3 // cmd/golangci-lint 8 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore test and tool binaries. 3 | bin/controller-gen 4 | bin/goreleaser 5 | bin/kustomize 6 | bin/envtest 7 | testbin/ 8 | .tiltbuild/ 9 | -------------------------------------------------------------------------------- /config/base/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | labels: 5 | app.kubernetes.io/part-of: olm 6 | app.kubernetes.io/name: catalogd 7 | name: controller-manager 8 | namespace: system 9 | -------------------------------------------------------------------------------- /testdata/catalogs/test-catalog.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | COPY test-catalog /configs 3 | 4 | # Set DC-specific label for the location of the DC root directory 5 | # in the image 6 | LABEL operators.operatorframework.io.index.configs.v1=/configs -------------------------------------------------------------------------------- /.bingo/setup-envtest.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.23.0 6 | 7 | require sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20240820183333-e6c3d139d2b6 8 | -------------------------------------------------------------------------------- /config/components/registries-conf/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1alpha1 2 | kind: Component 3 | namespace: olmv1-system 4 | resources: 5 | - registries_conf_configmap.yaml 6 | patches: 7 | - path: manager_e2e_registries_conf_patch.yaml 8 | -------------------------------------------------------------------------------- /config/overlays/cert-manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../../base/crd 5 | - ../../base/rbac 6 | - ../../base/manager 7 | components: 8 | - ../../components/tls 9 | - ../../components/ca 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use distroless as minimal base image to package the manager binary 2 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 3 | FROM gcr.io/distroless/static:nonroot 4 | WORKDIR / 5 | COPY manager . 6 | USER 65532:65532 7 | 8 | ENTRYPOINT ["/manager"] -------------------------------------------------------------------------------- /config/base/nginx-ingress/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | resources: 5 | - ../default 6 | - resources/nginx_ingress.yaml 7 | - https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml 8 | -------------------------------------------------------------------------------- /config/samples/core_v1_clustercatalog.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: olm.operatorframework.io/v1 2 | kind: ClusterCatalog 3 | metadata: 4 | name: operatorhubio 5 | spec: 6 | priority: 0 7 | source: 8 | type: Image 9 | image: 10 | pollIntervalMinutes: 1440 11 | ref: quay.io/operatorhubio/catalog:latest 12 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /pprof/kind.yaml: -------------------------------------------------------------------------------- 1 | # A KinD configuration to enable profiling on the core apiserver 2 | kind: Cluster 3 | apiVersion: kind.x-k8s.io/v1alpha4 4 | nodes: 5 | - role: control-plane 6 | kubeadmConfigPatches: 7 | - | 8 | kind: ClusterConfiguration 9 | apiServer: 10 | extraArgs: 11 | profiling: "true" 12 | -------------------------------------------------------------------------------- /config/base/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/part-of: olm 6 | app.kubernetes.io/name: catalogd 7 | name: metrics-reader 8 | rules: 9 | - nonResourceURLs: 10 | - "/metrics" 11 | verbs: 12 | - get 13 | -------------------------------------------------------------------------------- /config/base/default/clustercatalogs/default-catalogs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: olm.operatorframework.io/v1 2 | kind: ClusterCatalog 3 | metadata: 4 | name: operatorhubio 5 | namespace: olmv1-system 6 | spec: 7 | source: 8 | type: Image 9 | image: 10 | ref: quay.io/operatorhubio/catalog:latest 11 | pollIntervalMinutes: 10 12 | -------------------------------------------------------------------------------- /config/base/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/olm.operatorframework.io_clustercatalogs.yaml 6 | #+kubebuilder:scaffold:crdkustomizeresource 7 | -------------------------------------------------------------------------------- /config/components/registries-conf/registries_conf_configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: e2e-registries-conf 5 | namespace: system 6 | data: 7 | registries.conf: | 8 | [[registry]] 9 | prefix = "docker-registry.catalogd-e2e.svc:5000" 10 | insecure = true 11 | location = "docker-registry.catalogd-e2e.svc:5000" 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | bin 4 | 5 | # Kubernetes Generated files - skip generated files, except for vendored files 6 | 7 | !vendor/**/zz_generated.* 8 | 9 | # Dependency directories 10 | vendor/ 11 | bin/ 12 | dist/ 13 | cover.out 14 | 15 | # Release output 16 | catalogd.yaml 17 | install.sh 18 | 19 | .tiltbuild/ 20 | .vscode 21 | .idea 22 | -------------------------------------------------------------------------------- /config/components/ca/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1alpha1 2 | kind: Component 3 | # No namespace is specified here, otherwise, it will overwrite _all_ the other namespaces! 4 | resources: 5 | - resources/issuers.yaml 6 | patches: 7 | - target: 8 | kind: Deployment 9 | name: controller-manager 10 | path: patches/manager_deployment_cacerts.yaml 11 | -------------------------------------------------------------------------------- /config/overlays/e2e/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # kustomization file for all the e2e's 2 | # DO NOT ADD A NAMESPACE HERE 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - ../../base/crd 7 | - ../../base/rbac 8 | - ../../base/manager 9 | components: 10 | - ../../components/tls 11 | - ../../components/registries-conf 12 | - ../../components/ca 13 | -------------------------------------------------------------------------------- /.github/workflows/pr-title.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request_target: 3 | types: [opened, edited, reopened, synchronize] 4 | 5 | jobs: 6 | pr-title: 7 | runs-on: ubuntu-latest 8 | name: Verify PR title 9 | steps: 10 | - name: Verify PR title 11 | uses: kubernetes-sigs/kubebuilder-release-tools@v0.4.3 12 | with: 13 | github_token: ${{ secrets.GITHUB_TOKEN }} 14 | -------------------------------------------------------------------------------- /.github/workflows/go-verdiff.yaml: -------------------------------------------------------------------------------- 1 | name: go-verdiff 2 | on: 3 | pull_request: 4 | paths: 5 | - '**.mod' 6 | branches: 7 | - main 8 | jobs: 9 | go-verdiff: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 15 | - name: Check golang version 16 | run: hack/scripts/check-go-version.sh "${{ github.event.pull_request.base.sha }}" 17 | -------------------------------------------------------------------------------- /config/base/nginx-ingress/resources/nginx_ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: catalogd-ingress 5 | namespace: olmv1-system 6 | spec: 7 | ingressClassName: nginx 8 | rules: 9 | - http: 10 | paths: 11 | - path: / 12 | pathType: Prefix 13 | backend: 14 | service: 15 | name: catalogd-service 16 | port: 17 | number: 80 18 | -------------------------------------------------------------------------------- /config/base/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/part-of: olm 6 | app.kubernetes.io/name: catalogd 7 | name: manager-rolebinding 8 | roleRef: 9 | apiGroup: rbac.authorization.k8s.io 10 | kind: ClusterRole 11 | name: manager-role 12 | subjects: 13 | - kind: ServiceAccount 14 | name: controller-manager 15 | namespace: system 16 | -------------------------------------------------------------------------------- /internal/features/features.go: -------------------------------------------------------------------------------- 1 | package features 2 | 3 | import ( 4 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 5 | "k8s.io/component-base/featuregate" 6 | ) 7 | 8 | var catalogdFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{} 9 | 10 | var CatalogdFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate() 11 | 12 | func init() { 13 | utilruntime.Must(CatalogdFeatureGate.Add(catalogdFeatureGates)) 14 | } 15 | -------------------------------------------------------------------------------- /config/base/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/part-of: olm 6 | app.kubernetes.io/name: catalogd 7 | name: proxy-rolebinding 8 | roleRef: 9 | apiGroup: rbac.authorization.k8s.io 10 | kind: ClusterRole 11 | name: proxy-role 12 | subjects: 13 | - kind: ServiceAccount 14 | name: controller-manager 15 | namespace: system 16 | -------------------------------------------------------------------------------- /config/base/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/part-of: olm 6 | app.kubernetes.io/name: catalogd 7 | name: leader-election-rolebinding 8 | roleRef: 9 | apiGroup: rbac.authorization.k8s.io 10 | kind: Role 11 | name: leader-election-role 12 | subjects: 13 | - kind: ServiceAccount 14 | name: controller-manager 15 | namespace: system 16 | -------------------------------------------------------------------------------- /testdata/catalogs/test-catalog/expected_all.json: -------------------------------------------------------------------------------- 1 | {"defaultChannel":"beta","name":"prometheus","schema":"olm.package"} 2 | {"entries":[{"name":"prometheus-operator.0.47.0"}],"name":"beta","package":"prometheus","schema":"olm.channel"} 3 | {"image":"localhost/testdata/bundles/registry-v1/prometheus-operator:v0.47.0","name":"prometheus-operator.0.47.0","package":"prometheus","properties":[{"type":"olm.package","value":{"packageName":"prometheus","version":"0.47.0"}}],"schema":"olm.bundle"} 4 | -------------------------------------------------------------------------------- /config/base/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/part-of: olm 6 | app.kubernetes.io/name: catalogd 7 | name: proxy-role 8 | rules: 9 | - apiGroups: 10 | - authentication.k8s.io 11 | resources: 12 | - tokenreviews 13 | verbs: 14 | - create 15 | - apiGroups: 16 | - authorization.k8s.io 17 | resources: 18 | - subjectaccessreviews 19 | verbs: 20 | - create 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | commit-message: 8 | prefix: ":seedling:" 9 | - package-ecosystem: "gomod" 10 | directory: "/" 11 | schedule: 12 | interval: "daily" 13 | commit-message: 14 | prefix: ":seedling:" 15 | groups: 16 | k8s-dependencies: 17 | patterns: 18 | - "k8s.io/*" 19 | - "sigs.k8s.io/*" 20 | -------------------------------------------------------------------------------- /config/components/registries-conf/manager_e2e_registries_conf_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | volumeMounts: 12 | - name: e2e-registries-conf 13 | mountPath: /etc/containers 14 | volumes: 15 | - name: e2e-registries-conf 16 | configMap: 17 | name: e2e-registries-conf 18 | -------------------------------------------------------------------------------- /.github/workflows/demo.yaml: -------------------------------------------------------------------------------- 1 | name: demo 2 | 3 | on: 4 | workflow_dispatch: 5 | merge_group: 6 | pull_request: 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | demo: 13 | runs-on: ubuntu-latest 14 | env: 15 | TERM: linux 16 | steps: 17 | - run: sudo apt update && sudo apt install -y asciinema curl 18 | - uses: actions/checkout@v4 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version-file: "go.mod" 22 | - run: make demo-update 23 | -------------------------------------------------------------------------------- /config/base/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | - catalogd_service.yaml 4 | - webhook/manifests.yaml 5 | apiVersion: kustomize.config.k8s.io/v1beta1 6 | kind: Kustomization 7 | images: 8 | - name: controller 9 | newName: quay.io/operator-framework/catalogd 10 | newTag: devel 11 | patches: 12 | - path: webhook/patch.yaml 13 | target: 14 | group: admissionregistration.k8s.io 15 | kind: MutatingWebhookConfiguration 16 | name: mutating-webhook-configuration 17 | version: v1 18 | -------------------------------------------------------------------------------- /.github/workflows/go-apidiff.yaml: -------------------------------------------------------------------------------- 1 | name: go-apidiff 2 | 3 | on: 4 | merge_group: 5 | pull_request: 6 | 7 | jobs: 8 | go-apidiff: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Check out code into the Go module directory 12 | uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 15 | - name: Set up Go 16 | uses: actions/setup-go@v5 17 | with: 18 | go-version-file: "go.mod" 19 | id: go 20 | - name: Run go-apidiff 21 | uses: joelanford/go-apidiff@main 22 | -------------------------------------------------------------------------------- /Tiltfile: -------------------------------------------------------------------------------- 1 | if not os.path.exists('../tilt-support'): 2 | fail('Please clone https://github.com/operator-framework/tilt-support to ../tilt-support') 3 | 4 | load('../tilt-support/Tiltfile', 'deploy_repo') 5 | 6 | repo = { 7 | 'image': 'quay.io/operator-framework/catalogd', 8 | 'yaml': 'config/overlays/cert-manager', 9 | 'binaries': { 10 | 'manager': 'catalogd-controller-manager', 11 | }, 12 | 'starting_debug_port': 20000, 13 | } 14 | 15 | deploy_repo('catalogd', repo, '-tags containers_image_openpgp') 16 | -------------------------------------------------------------------------------- /config/components/tls/resources/certificate.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: cert-manager.io/v1 3 | kind: Certificate 4 | metadata: 5 | name: service-cert 6 | namespace: system 7 | spec: 8 | secretName: catalogd-service-cert-git-version 9 | dnsNames: 10 | - localhost 11 | - catalogd-service.olmv1-system.svc 12 | - catalogd-service.olmv1-system.svc.cluster.local 13 | privateKey: 14 | algorithm: ECDSA 15 | size: 256 16 | issuerRef: 17 | kind: ClusterIssuer 18 | group: cert-manager.io 19 | name: olmv1-ca 20 | -------------------------------------------------------------------------------- /testdata/catalogs/test-catalog/catalog.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | schema: olm.package 3 | name: prometheus 4 | defaultChannel: beta 5 | --- 6 | schema: olm.channel 7 | name: beta 8 | package: prometheus 9 | entries: 10 | - name: prometheus-operator.0.47.0 11 | --- 12 | schema: olm.bundle 13 | name: prometheus-operator.0.47.0 14 | package: prometheus 15 | image: localhost/testdata/bundles/registry-v1/prometheus-operator:v0.47.0 16 | properties: 17 | - type: olm.package 18 | value: 19 | packageName: prometheus 20 | version: 0.47.0 21 | -------------------------------------------------------------------------------- /config/components/ca/patches/manager_deployment_cacerts.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /spec/template/spec/volumes/- 3 | value: {"name":"olmv1-certificate", "secret":{"secretName":"catalogd-service-cert-git-version", "optional": false, "items": [{"key": "ca.crt", "path": "olm-ca.crt"}]}} 4 | - op: add 5 | path: /spec/template/spec/containers/0/volumeMounts/- 6 | value: {"name":"olmv1-certificate", "readOnly": true, "mountPath":"/var/ca-certs/"} 7 | - op: add 8 | path: /spec/template/spec/containers/0/args/- 9 | value: "--ca-certs-dir=/var/ca-certs" 10 | -------------------------------------------------------------------------------- /.github/workflows/add-to-project.yaml: -------------------------------------------------------------------------------- 1 | name: Add epic issues to OLMv1 project 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | - labeled 8 | 9 | jobs: 10 | add-to-project: 11 | name: Add issue to project 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/add-to-project@v1.0.2 15 | with: 16 | project-url: https://github.com/orgs/operator-framework/projects/8 17 | github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} 18 | labeled: epic, v1.0, v1.x 19 | label-operator: OR 20 | -------------------------------------------------------------------------------- /config/components/tls/patches/manager_deployment_certs.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /spec/template/spec/volumes/- 3 | value: {"name":"catalogserver-certs", "secret":{"secretName":"catalogd-service-cert-git-version"}} 4 | - op: add 5 | path: /spec/template/spec/containers/0/volumeMounts/- 6 | value: {"name":"catalogserver-certs", "mountPath":"/var/certs"} 7 | - op: add 8 | path: /spec/template/spec/containers/0/args/- 9 | value: "--tls-cert=/var/certs/tls.crt" 10 | - op: add 11 | path: /spec/template/spec/containers/0/args/- 12 | value: "--tls-key=/var/certs/tls.key" 13 | -------------------------------------------------------------------------------- /config/base/manager/catalogd_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app.kubernetes.io/part-of: olm 6 | app.kubernetes.io/name: catalogd 7 | name: service 8 | namespace: system 9 | spec: 10 | selector: 11 | control-plane: catalogd-controller-manager 12 | ports: 13 | - name: http 14 | protocol: TCP 15 | port: 80 16 | targetPort: 8443 17 | - name: webhook 18 | protocol: TCP 19 | port: 9443 20 | targetPort: 9443 21 | - name: metrics 22 | protocol: TCP 23 | port: 7443 24 | targetPort: 7443 25 | -------------------------------------------------------------------------------- /internal/k8sutil/k8sutil.go: -------------------------------------------------------------------------------- 1 | package k8sutil 2 | 3 | import ( 4 | "regexp" 5 | 6 | "k8s.io/apimachinery/pkg/util/validation" 7 | ) 8 | 9 | var invalidNameChars = regexp.MustCompile(`[^\.\-a-zA-Z0-9]`) 10 | 11 | // MetadataName replaces all invalid DNS characters with a dash. If the result 12 | // is not a valid DNS subdomain, returns `result, false`. Otherwise, returns the 13 | // `result, true`. 14 | func MetadataName(name string) (string, bool) { 15 | result := invalidNameChars.ReplaceAllString(name, "-") 16 | return result, validation.IsDNS1123Subdomain(result) == nil 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/unit.yaml: -------------------------------------------------------------------------------- 1 | name: unit 2 | 3 | on: 4 | workflow_dispatch: 5 | merge_group: 6 | pull_request: 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | unit: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-go@v5 17 | with: 18 | go-version-file: "go.mod" 19 | - run: make test-unit 20 | 21 | - uses: codecov/codecov-action@v5 22 | with: 23 | disable_search: true 24 | files: cover.out 25 | token: ${{ secrets.CODECOV_TOKEN }} 26 | -------------------------------------------------------------------------------- /config/base/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: olmv1-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: catalogd- 10 | 11 | # the following config is for teaching kustomize how to do var substitution 12 | apiVersion: kustomize.config.k8s.io/v1beta1 13 | kind: Kustomization 14 | resources: 15 | - ../crd 16 | - ../rbac 17 | - ../manager 18 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /internal/storage/storage.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "context" 5 | "io/fs" 6 | "net/http" 7 | ) 8 | 9 | // Instance is a storage instance that stores FBC content of catalogs 10 | // added to a cluster. It can be used to Store or Delete FBC in the 11 | // host's filesystem. It also a manager runnable object, that starts 12 | // a server to serve the content stored. 13 | type Instance interface { 14 | Store(ctx context.Context, catalog string, fsys fs.FS) error 15 | Delete(catalog string) error 16 | BaseURL(catalog string) string 17 | StorageServerHandler() http.Handler 18 | ContentExists(catalog string) bool 19 | } 20 | -------------------------------------------------------------------------------- /config/base/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: manager-role 6 | rules: 7 | - apiGroups: 8 | - olm.operatorframework.io 9 | resources: 10 | - clustercatalogs 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - olm.operatorframework.io 21 | resources: 22 | - clustercatalogs/finalizers 23 | verbs: 24 | - update 25 | - apiGroups: 26 | - olm.operatorframework.io 27 | resources: 28 | - clustercatalogs/status 29 | verbs: 30 | - get 31 | - patch 32 | - update 33 | -------------------------------------------------------------------------------- /.github/workflows/crd-diff.yaml: -------------------------------------------------------------------------------- 1 | name: crd-diff 2 | on: 3 | pull_request: 4 | jobs: 5 | crd-diff: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | with: 10 | fetch-depth: 0 11 | 12 | - uses: actions/setup-go@v5 13 | with: 14 | go-version-file: go.mod 15 | 16 | - name: Run make verify-crd-compatibility 17 | run: make verify-crd-compatibility CRD_DIFF_ORIGINAL_REF=${{ github.event.pull_request.base.sha }} CRD_DIFF_UPDATED_SOURCE="git://${{ github.event.pull_request.head.sha }}?path=config/base/crd/bases/olm.operatorframework.io_clustercatalogs.yaml" 18 | 19 | -------------------------------------------------------------------------------- /config/components/tls/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1alpha1 2 | kind: Component 3 | namespace: olmv1-system 4 | namePrefix: catalogd- 5 | resources: 6 | - resources/certificate.yaml 7 | patches: 8 | - target: 9 | kind: Service 10 | name: service 11 | path: patches/catalogd_service_port.yaml 12 | - target: 13 | kind: Deployment 14 | name: controller-manager 15 | path: patches/manager_deployment_certs.yaml 16 | - target: 17 | group: admissionregistration.k8s.io 18 | kind: MutatingWebhookConfiguration 19 | name: mutating-webhook-configuration 20 | version: v1 21 | path: patches/catalogd_webhook.yaml 22 | -------------------------------------------------------------------------------- /.github/workflows/e2e.yaml: -------------------------------------------------------------------------------- 1 | name: e2e 2 | 3 | on: 4 | workflow_dispatch: 5 | merge_group: 6 | pull_request: 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | e2e: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-go@v5 17 | with: 18 | go-version-file: "go.mod" 19 | - run: make e2e 20 | upgrade-e2e: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: actions/setup-go@v5 25 | with: 26 | go-version-file: "go.mod" 27 | - name: Run the upgrade e2e test 28 | run: make test-upgrade-e2e 29 | -------------------------------------------------------------------------------- /config/base/manager/webhook/manifests.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: admissionregistration.k8s.io/v1 3 | kind: MutatingWebhookConfiguration 4 | metadata: 5 | name: mutating-webhook-configuration 6 | webhooks: 7 | - admissionReviewVersions: 8 | - v1 9 | clientConfig: 10 | service: 11 | name: webhook-service 12 | namespace: system 13 | path: /mutate-olm-operatorframework-io-v1-clustercatalog 14 | failurePolicy: Fail 15 | name: inject-metadata-name.olm.operatorframework.io 16 | rules: 17 | - apiGroups: 18 | - olm.operatorframework.io 19 | apiVersions: 20 | - v1 21 | operations: 22 | - CREATE 23 | - UPDATE 24 | resources: 25 | - clustercatalogs 26 | sideEffects: None 27 | timeoutSeconds: 10 28 | -------------------------------------------------------------------------------- /.github/workflows/sanity.yaml: -------------------------------------------------------------------------------- 1 | name: sanity 2 | 3 | on: 4 | workflow_dispatch: 5 | merge_group: 6 | pull_request: 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | verify: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-go@v5 17 | with: 18 | go-version-file: "go.mod" 19 | - name: Run verification checks 20 | run: make verify 21 | lint: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v4 25 | - uses: actions/setup-go@v5 26 | with: 27 | go-version-file: "go.mod" 28 | - name: Run lint checks 29 | run: make lint GOLANGCI_LINT_ARGS="--out-format github-actions" 30 | -------------------------------------------------------------------------------- /api/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //go:generate apiregister-gen --input-dirs ./... -h ../../boilerplate.go.txt 18 | 19 | // 20 | // +domain=operatorframework.io 21 | 22 | package api 23 | -------------------------------------------------------------------------------- /config/base/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | labels: 6 | app.kubernetes.io/part-of: olm 7 | app.kubernetes.io/name: catalogd 8 | name: leader-election-role 9 | rules: 10 | - apiGroups: 11 | - "" 12 | resources: 13 | - configmaps 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | - create 19 | - update 20 | - patch 21 | - delete 22 | - apiGroups: 23 | - coordination.k8s.io 24 | resources: 25 | - leases 26 | verbs: 27 | - get 28 | - list 29 | - watch 30 | - create 31 | - update 32 | - patch 33 | - delete 34 | - apiGroups: 35 | - "" 36 | resources: 37 | - events 38 | verbs: 39 | - create 40 | - patch 41 | -------------------------------------------------------------------------------- /config/components/ca/resources/issuers.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: Issuer 3 | metadata: 4 | name: self-sign-issuer 5 | namespace: cert-manager 6 | spec: 7 | selfSigned: {} 8 | --- 9 | apiVersion: cert-manager.io/v1 10 | kind: Certificate 11 | metadata: 12 | name: olmv1-ca 13 | namespace: cert-manager 14 | spec: 15 | isCA: true 16 | commonName: olmv1-ca 17 | secretName: olmv1-ca 18 | secretTemplate: 19 | annotations: 20 | cert-manager.io/allow-direct-injection: "true" 21 | privateKey: 22 | algorithm: ECDSA 23 | size: 256 24 | issuerRef: 25 | name: self-sign-issuer 26 | kind: Issuer 27 | group: cert-manager.io 28 | --- 29 | apiVersion: cert-manager.io/v1 30 | kind: ClusterIssuer 31 | metadata: 32 | name: olmv1-ca 33 | spec: 34 | ca: 35 | secretName: olmv1-ca 36 | -------------------------------------------------------------------------------- /.bingo/README.md: -------------------------------------------------------------------------------- 1 | # Project Development Dependencies. 2 | 3 | This is directory which stores Go modules with pinned buildable package that is used within this repository, managed by https://github.com/bwplotka/bingo. 4 | 5 | * Run `bingo get` to install all tools having each own module file in this directory. 6 | * Run `bingo get ` to install that have own module file in this directory. 7 | * For Makefile: Make sure to put `include .bingo/Variables.mk` in your Makefile, then use $() variable where is the .bingo/.mod. 8 | * For shell: Run `source .bingo/variables.env` to source all environment variable for each tool. 9 | * For go: Import `.bingo/variables.go` to for variable names. 10 | * See https://github.com/bwplotka/bingo or -h on how to add, remove or change binaries dependencies. 11 | 12 | ## Requirements 13 | 14 | * Go 1.14+ 15 | -------------------------------------------------------------------------------- /internal/storage/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package storage 18 | 19 | import ( 20 | "testing" 21 | 22 | . "github.com/onsi/ginkgo/v2" 23 | . "github.com/onsi/gomega" 24 | ) 25 | 26 | func TestAPIs(t *testing.T) { 27 | RegisterFailHandler(Fail) 28 | RunSpecs(t, "Storage Suite") 29 | } 30 | -------------------------------------------------------------------------------- /.bingo/variables.env: -------------------------------------------------------------------------------- 1 | # Auto generated binary variables helper managed by https://github.com/bwplotka/bingo v0.9. DO NOT EDIT. 2 | # All tools are designed to be build inside $GOBIN. 3 | # Those variables will work only until 'bingo get' was invoked, or if tools were installed via Makefile's Variables.mk. 4 | GOBIN=${GOBIN:=$(go env GOBIN)} 5 | 6 | if [ -z "$GOBIN" ]; then 7 | GOBIN="$(go env GOPATH)/bin" 8 | fi 9 | 10 | 11 | BINGO="${GOBIN}/bingo-v0.9.0" 12 | 13 | CONTROLLER_GEN="${GOBIN}/controller-gen-v0.16.1" 14 | 15 | CRD_DIFF="${GOBIN}/crd-diff-v0.1.0" 16 | 17 | GINKGO="${GOBIN}/ginkgo-v2.22.0" 18 | 19 | GOLANGCI_LINT="${GOBIN}/golangci-lint-v1.60.3" 20 | 21 | GORELEASER="${GOBIN}/goreleaser-v1.26.2" 22 | 23 | KIND="${GOBIN}/kind-v0.24.0" 24 | 25 | KUSTOMIZE="${GOBIN}/kustomize-v5.4.3" 26 | 27 | SETUP_ENVTEST="${GOBIN}/setup-envtest-v0.0.0-20240820183333-e6c3d139d2b6" 28 | 29 | -------------------------------------------------------------------------------- /test/tools/imageregistry/pre-upgrade-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | 6 | help="pre-upgrade-setup.sh is used to create some basic resources 7 | which will later be used in upgrade testing. 8 | 9 | Usage: 10 | pre-upgrade-setup.sh [TEST_CLUSTER_CATALOG_IMAGE] [TEST_CLUSTER_CATALOG_NAME] 11 | " 12 | 13 | if [[ "$#" -ne 2 ]]; then 14 | echo "Illegal number of arguments passed" 15 | echo "${help}" 16 | exit 1 17 | fi 18 | 19 | export TEST_CLUSTER_CATALOG_IMAGE=$1 20 | export TEST_CLUSTER_CATALOG_NAME=$2 21 | 22 | kubectl apply -f - << EOF 23 | apiVersion: olm.operatorframework.io/v1 24 | kind: ClusterCatalog 25 | metadata: 26 | name: ${TEST_CLUSTER_CATALOG_NAME} 27 | spec: 28 | source: 29 | type: Image 30 | image: 31 | ref: ${TEST_CLUSTER_CATALOG_IMAGE} 32 | EOF 33 | 34 | kubectl wait --for=condition=Serving --timeout=60s ClusterCatalog "$TEST_CLUSTER_CATALOG_NAME" 35 | -------------------------------------------------------------------------------- /config/base/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | # The following RBAC configurations are used to protect 13 | # the metrics endpoint with authn/authz. These configurations 14 | # ensure that only authorized users and service accounts 15 | # can access the metrics endpoint. Comment the following 16 | # permissions if you want to disable this protection. 17 | # More info: https://book.kubebuilder.io/reference/metrics.html 18 | - auth_proxy_role.yaml 19 | - auth_proxy_role_binding.yaml 20 | - auth_proxy_client_clusterrole.yaml 21 | -------------------------------------------------------------------------------- /config/base/manager/webhook/patch.yaml: -------------------------------------------------------------------------------- 1 | # None of these values can be set via the kubebuilder directive, hence this patch 2 | - op: replace 3 | path: /webhooks/0/clientConfig/service/namespace 4 | value: olmv1-system 5 | - op: replace 6 | path: /webhooks/0/clientConfig/service/name 7 | value: catalogd-service 8 | - op: add 9 | path: /webhooks/0/clientConfig/service/port 10 | value: 9443 11 | # Make sure there's a name defined, otherwise, we can't create a label. This could happen when generateName is set 12 | # Then, if any of the conditions are true, create the label: 13 | # 1. No labels exist 14 | # 2. The olm.operatorframework.io/metadata.name label doesn't exist 15 | # 3. The olm.operatorframework.io/metadata.name label doesn't match the name 16 | - op: add 17 | path: /webhooks/0/matchConditions 18 | value: 19 | - name: MissingOrIncorrectMetadataNameLabel 20 | expression: "'name' in object.metadata && (!has(object.metadata.labels) || !('olm.operatorframework.io/metadata.name' in object.metadata.labels) || object.metadata.labels['olm.operatorframework.io/metadata.name'] != object.metadata.name)" 21 | -------------------------------------------------------------------------------- /internal/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "strings" 7 | 8 | "github.com/blang/semver/v4" 9 | genericversion "k8s.io/apimachinery/pkg/version" 10 | ) 11 | 12 | var ( 13 | gitVersion = "unknown" 14 | gitCommit = "unknown" // sha1 from git, output of $(git rev-parse HEAD) 15 | gitTreeState = "unknown" // state of git tree, either "clean" or "dirty" 16 | commitDate = "unknown" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ') 17 | ) 18 | 19 | // Version returns a version struct for the build 20 | func Version() genericversion.Info { 21 | info := genericversion.Info{ 22 | GitVersion: gitVersion, 23 | GitCommit: gitCommit, 24 | GitTreeState: gitTreeState, 25 | BuildDate: commitDate, 26 | GoVersion: runtime.Version(), 27 | Compiler: runtime.Compiler, 28 | Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), 29 | } 30 | v, err := semver.Parse(strings.TrimPrefix(gitVersion, "v")) 31 | if err == nil { 32 | info.Major = fmt.Sprintf("%d", v.Major) 33 | info.Minor = fmt.Sprintf("%d", v.Minor) 34 | } 35 | return info 36 | } 37 | -------------------------------------------------------------------------------- /test/tools/imageregistry/imagebuilder.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: kaniko 5 | namespace: catalogd-e2e 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: kaniko 11 | image: gcr.io/kaniko-project/executor:latest 12 | args: ["--dockerfile=/workspace/test-catalog.Dockerfile", 13 | "--context=/workspace/", 14 | "--destination=docker-registry.catalogd-e2e.svc:5000/test-catalog:e2e", 15 | "--skip-tls-verify"] 16 | terminationMessagePolicy: FallbackToLogsOnError 17 | volumeMounts: 18 | - name: dockerfile 19 | mountPath: /workspace/ 20 | - name: build-contents 21 | mountPath: /workspace/test-catalog/ 22 | restartPolicy: Never 23 | volumes: 24 | - name: dockerfile 25 | configMap: 26 | name: catalogd-e2e.dockerfile 27 | items: 28 | - key: test-catalog.Dockerfile 29 | path: test-catalog.Dockerfile 30 | - name: build-contents 31 | configMap: 32 | name: catalogd-e2e.build-contents 33 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: manager-role 6 | rules: 7 | - apiGroups: 8 | - olm.operatorframework.io 9 | resources: 10 | - clustercatalogs 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - olm.operatorframework.io 21 | resources: 22 | - clustercatalogs/finalizers 23 | verbs: 24 | - update 25 | - apiGroups: 26 | - olm.operatorframework.io 27 | resources: 28 | - clustercatalogs/status 29 | verbs: 30 | - get 31 | - patch 32 | - update 33 | - apiGroups: 34 | - "" 35 | resources: 36 | - pods 37 | verbs: 38 | - create 39 | - delete 40 | - get 41 | - list 42 | - patch 43 | - update 44 | - watch 45 | - apiGroups: 46 | - "" 47 | resources: 48 | - pods/log 49 | verbs: 50 | - get 51 | - list 52 | - watch 53 | --- 54 | apiVersion: rbac.authorization.k8s.io/v1 55 | kind: Role 56 | metadata: 57 | name: manager-role 58 | namespace: system 59 | rules: 60 | - apiGroups: 61 | - "" 62 | resources: 63 | - secrets 64 | verbs: 65 | - get 66 | -------------------------------------------------------------------------------- /hack/scripts/gzip-demo-script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT 4 | # Welcome to the catalogd demo 5 | make run 6 | 7 | # create a clustercatalog 8 | kubectl apply -f $HOME/devel/tmp/operatorhubio-clustercatalog.yaml 9 | # shows catalog 10 | kubectl get clustercatalog -A 11 | # waiting for clustercatalog to report ready status 12 | time kubectl wait --for=condition=Unpacked clustercatalog/operatorhubio --timeout=1m 13 | 14 | # port forward the catalogd-service service to interact with the HTTP server serving catalog contents 15 | (kubectl -n olmv1-system port-forward svc/catalogd-service 8080:443)& 16 | sleep 5 17 | 18 | # retrieve catalog as plaintext JSONlines 19 | curl -k -vvv https://localhost:8080/catalogs/operatorhubio/api/v1/all --output /tmp/cat-content.json 20 | 21 | # advertise handling of compressed content 22 | curl -vvv -k https://localhost:8080/catalogs/operatorhubio/api/v1/all -H 'Accept-Encoding: gzip' --output /tmp/cat-content.gz 23 | 24 | # let curl handle the compress/decompress for us 25 | curl -vvv --compressed -k https://localhost:8080/catalogs/operatorhubio/api/v1/all --output /tmp/cat-content-decompressed.txt 26 | 27 | # show that there's no content change with changed format 28 | diff /tmp/cat-content.json /tmp/cat-content-decompressed.txt 29 | 30 | -------------------------------------------------------------------------------- /test/e2e/e2e_suite_test.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | "time" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | 12 | "k8s.io/client-go/kubernetes" 13 | "k8s.io/client-go/kubernetes/scheme" 14 | "k8s.io/client-go/rest" 15 | ctrl "sigs.k8s.io/controller-runtime" 16 | "sigs.k8s.io/controller-runtime/pkg/client" 17 | 18 | catalogdv1 "github.com/operator-framework/catalogd/api/v1" 19 | ) 20 | 21 | var ( 22 | cfg *rest.Config 23 | c client.Client 24 | err error 25 | kubeClient kubernetes.Interface 26 | ) 27 | 28 | func TestE2E(t *testing.T) { 29 | _, err := ctrl.GetConfig() 30 | if err != nil { 31 | fmt.Println("Error: Could not get current Kubernetes context. Verify the cluster configuration") 32 | os.Exit(0) 33 | } 34 | RegisterFailHandler(Fail) 35 | SetDefaultEventuallyTimeout(1 * time.Minute) 36 | SetDefaultEventuallyPollingInterval(1 * time.Second) 37 | RunSpecs(t, "E2E Suite") 38 | } 39 | 40 | var _ = BeforeSuite(func() { 41 | cfg = ctrl.GetConfigOrDie() 42 | 43 | sch := scheme.Scheme 44 | Expect(catalogdv1.AddToScheme(sch)).To(Succeed()) 45 | c, err = client.New(cfg, client.Options{Scheme: sch}) 46 | Expect(err).To(Not(HaveOccurred())) 47 | kubeClient, err = kubernetes.NewForConfig(cfg) 48 | Expect(err).ToNot(HaveOccurred()) 49 | }) 50 | -------------------------------------------------------------------------------- /api/v1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1 contains API Schema definitions for the core v1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=olm.operatorframework.io 20 | package v1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "olm.operatorframework.io", Version: "v1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /hack/scripts/generate-asciidemo.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | trap cleanup SIGINT SIGTERM EXIT 4 | 5 | SCRIPTPATH="$( cd -- "$(dirname "$0")" > /dev/null 2>&1 ; pwd -P )" 6 | 7 | function check_prereq() { 8 | prog=$1 9 | if ! command -v ${prog} &> /dev/null 10 | then 11 | echo "unable to find prerequisite: $1" 12 | exit 1 13 | fi 14 | } 15 | 16 | function cleanup() { 17 | if [ -d $WKDIR ] 18 | then 19 | rm -rf $WKDIR 20 | fi 21 | } 22 | 23 | function usage() { 24 | echo "$0 [options]" 25 | echo "where options is" 26 | echo " h help (this message)" 27 | exit 1 28 | } 29 | 30 | set +u 31 | while getopts 'h' flag; do 32 | case "${flag}" in 33 | h) usage ;; 34 | esac 35 | shift 36 | done 37 | set -u 38 | 39 | WKDIR=$(mktemp -td generate-asciidemo.XXXXX) 40 | if [ ! -d ${WKDIR} ] 41 | then 42 | echo "unable to create temporary workspace" 43 | exit 2 44 | fi 45 | 46 | for prereq in "asciinema curl" 47 | do 48 | check_prereq ${prereq} 49 | done 50 | 51 | 52 | curl https://raw.githubusercontent.com/zechris/asciinema-rec_script/main/bin/asciinema-rec_script -o ${WKDIR}/asciinema-rec_script 53 | chmod +x ${WKDIR}/asciinema-rec_script 54 | screencast=${WKDIR}/catalogd-demo.cast ${WKDIR}/asciinema-rec_script ${SCRIPTPATH}/demo-script.sh 55 | 56 | asciinema upload ${WKDIR}/catalogd-demo.cast 57 | 58 | -------------------------------------------------------------------------------- /hack/scripts/generate-gzip-asciidemo.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | trap cleanup SIGINT SIGTERM EXIT 4 | 5 | SCRIPTPATH="$( cd -- "$(dirname "$0")" > /dev/null 2>&1 ; pwd -P )" 6 | 7 | function check_prereq() { 8 | prog=$1 9 | if ! command -v ${prog} &> /dev/null 10 | then 11 | echo "unable to find prerequisite: $1" 12 | exit 1 13 | fi 14 | } 15 | 16 | function cleanup() { 17 | if [ -d $WKDIR ] 18 | then 19 | rm -rf $WKDIR 20 | fi 21 | } 22 | 23 | function usage() { 24 | echo "$0 [options]" 25 | echo "where options is" 26 | echo " h help (this message)" 27 | exit 1 28 | } 29 | 30 | set +u 31 | while getopts 'h' flag; do 32 | case "${flag}" in 33 | h) usage ;; 34 | esac 35 | shift 36 | done 37 | set -u 38 | 39 | WKDIR=$(mktemp -td generate-asciidemo.XXXXX) 40 | if [ ! -d ${WKDIR} ] 41 | then 42 | echo "unable to create temporary workspace" 43 | exit 2 44 | fi 45 | 46 | for prereq in "asciinema curl" 47 | do 48 | check_prereq ${prereq} 49 | done 50 | 51 | 52 | curl https://raw.githubusercontent.com/zechris/asciinema-rec_script/main/bin/asciinema-rec_script -o ${WKDIR}/asciinema-rec_script 53 | chmod +x ${WKDIR}/asciinema-rec_script 54 | screencast=${WKDIR}/catalogd-demo.cast ${WKDIR}/asciinema-rec_script ${SCRIPTPATH}/gzip-demo-script.sh 55 | 56 | asciinema upload ${WKDIR}/catalogd-demo.cast 57 | 58 | -------------------------------------------------------------------------------- /.github/workflows/tilt.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | paths: 4 | - '.bingo/**' 5 | - '.github/workflows/tilt.yaml' 6 | - 'api/**' 7 | - 'cmd/**' 8 | - 'config/**' 9 | - 'internal/**' 10 | - 'pkg/**' 11 | - 'Tiltfile' 12 | merge_group: 13 | 14 | jobs: 15 | tilt: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | repository: operator-framework/tilt-support 21 | path: tilt-support 22 | - uses: actions/checkout@v4 23 | with: 24 | path: catalogd 25 | - name: Install Go 26 | uses: actions/setup-go@v5 27 | with: 28 | go-version-file: "catalogd/go.mod" 29 | - name: Install Tilt 30 | run: | 31 | TILT_VERSION="0.33.3" 32 | curl -fsSL https://github.com/tilt-dev/tilt/releases/download/v$TILT_VERSION/tilt.$TILT_VERSION.linux.x86_64.tar.gz | \ 33 | tar -xzv -C /usr/local/bin tilt 34 | - name: Install ctlptl 35 | run: | 36 | CTLPTL_VERSION="0.8.20" 37 | curl -fsSL https://github.com/tilt-dev/ctlptl/releases/download/v$CTLPTL_VERSION/ctlptl.$CTLPTL_VERSION.linux.x86_64.tar.gz | \ 38 | tar -xzv -C /usr/local/bin ctlptl 39 | - name: Set up kind 40 | run: ctlptl create cluster kind --registry=ctlptl-registry 41 | - name: Test Tilt 42 | run: | 43 | cd catalogd 44 | tilt ci 45 | -------------------------------------------------------------------------------- /test/upgrade/upgrade_suite_test.go: -------------------------------------------------------------------------------- 1 | package upgradee2e 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | "time" 7 | 8 | . "github.com/onsi/ginkgo/v2" 9 | . "github.com/onsi/gomega" 10 | 11 | "k8s.io/client-go/kubernetes" 12 | "k8s.io/client-go/kubernetes/scheme" 13 | "k8s.io/client-go/rest" 14 | ctrl "sigs.k8s.io/controller-runtime" 15 | "sigs.k8s.io/controller-runtime/pkg/client" 16 | 17 | catalogdv1 "github.com/operator-framework/catalogd/api/v1" 18 | ) 19 | 20 | const ( 21 | testClusterCatalogNameEnv = "TEST_CLUSTER_CATALOG_NAME" 22 | ) 23 | 24 | var ( 25 | cfg *rest.Config 26 | c client.Client 27 | err error 28 | kubeClient kubernetes.Interface 29 | 30 | testClusterCatalogName string 31 | ) 32 | 33 | func TestUpgradeE2E(t *testing.T) { 34 | RegisterFailHandler(Fail) 35 | SetDefaultEventuallyTimeout(1 * time.Minute) 36 | SetDefaultEventuallyPollingInterval(1 * time.Second) 37 | RunSpecs(t, "Upgrade E2E Suite") 38 | } 39 | 40 | var _ = BeforeSuite(func() { 41 | cfg = ctrl.GetConfigOrDie() 42 | 43 | sch := scheme.Scheme 44 | Expect(catalogdv1.AddToScheme(sch)).To(Succeed()) 45 | c, err = client.New(cfg, client.Options{Scheme: sch}) 46 | Expect(err).To(Not(HaveOccurred())) 47 | kubeClient, err = kubernetes.NewForConfig(cfg) 48 | Expect(err).ToNot(HaveOccurred()) 49 | 50 | var ok bool 51 | testClusterCatalogName, ok = os.LookupEnv(testClusterCatalogNameEnv) 52 | Expect(ok).To(BeTrue()) 53 | }) 54 | -------------------------------------------------------------------------------- /DCO: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 1 Letterman Drive 6 | Suite D4700 7 | San Francisco, CA, 94129 8 | 9 | Everyone is permitted to copy and distribute verbatim copies of this 10 | license document, but changing it is not allowed. 11 | 12 | 13 | Developer's Certificate of Origin 1.1 14 | 15 | By making a contribution to this project, I certify that: 16 | 17 | (a) The contribution was created in whole or in part by me and I 18 | have the right to submit it under the open source license 19 | indicated in the file; or 20 | 21 | (b) The contribution is based upon previous work that, to the best 22 | of my knowledge, is covered under an appropriate open source 23 | license and I have the right under that license to submit that 24 | work with modifications, whether created in whole or in part 25 | by me, under the same open source license (unless I am 26 | permitted to submit under a different license), as indicated 27 | in the file; or 28 | 29 | (c) The contribution was provided directly to me by some other 30 | person who certified (a), (b) or (c) and I have not modified 31 | it. 32 | 33 | (d) I understand and agree that this project and the contribution 34 | are public and that a record of the contribution (including all 35 | personal information I submit with it, including my sign-off) is 36 | maintained indefinitely and may be redistributed consistent with 37 | this project or the open source license(s) involved. 38 | -------------------------------------------------------------------------------- /internal/k8sutil/k8sutil_test.go: -------------------------------------------------------------------------------- 1 | package k8sutil 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestMetadataName(t *testing.T) { 12 | type testCase struct { 13 | name string 14 | in string 15 | expectedResult string 16 | expectedValid bool 17 | } 18 | for _, tc := range []testCase{ 19 | { 20 | name: "empty", 21 | in: "", 22 | expectedResult: "", 23 | expectedValid: false, 24 | }, 25 | { 26 | name: "invalid", 27 | in: "foo-bar.123!", 28 | expectedResult: "foo-bar.123-", 29 | expectedValid: false, 30 | }, 31 | { 32 | name: "too long", 33 | in: fmt.Sprintf("foo-bar_%s", strings.Repeat("1234567890", 50)), 34 | expectedResult: fmt.Sprintf("foo-bar-%s", strings.Repeat("1234567890", 50)), 35 | expectedValid: false, 36 | }, 37 | { 38 | name: "valid", 39 | in: "foo-bar.123", 40 | expectedResult: "foo-bar.123", 41 | expectedValid: true, 42 | }, 43 | { 44 | name: "valid with underscore", 45 | in: "foo-bar_123", 46 | expectedResult: "foo-bar-123", 47 | expectedValid: true, 48 | }, 49 | { 50 | name: "valid with colon", 51 | in: "foo-bar:123", 52 | expectedResult: "foo-bar-123", 53 | expectedValid: true, 54 | }, 55 | } { 56 | t.Run(tc.name, func(t *testing.T) { 57 | actualResult, actualValid := MetadataName(tc.in) 58 | assert.Equal(t, tc.expectedResult, actualResult) 59 | assert.Equal(t, tc.expectedValid, actualValid) 60 | }) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Release Guide 2 | 3 | These steps describe how to cut a release of the catalogd repo. 4 | 5 | ## Table of Contents: 6 | 7 | - [Major and minor releases](#major-and-minor-releases) 8 | 9 | ## Major and Minor Releases 10 | 11 | Before starting, ensure the milestone is cleaned up. All issues that need to 12 | get into the release should be closed and any issue that won't make the release 13 | should be pushed to the next milestone. 14 | 15 | These instructions use `v0.Y.0` as the example release. Please ensure to replace 16 | the version with the correct release being cut. It is also assumed that the upstream 17 | operator-framework/catalogd repository is the `upstream` remote on your machine. 18 | 19 | ### Procedure 20 | 21 | 1. Create a release branch by running the following, assuming the upstream 22 | operator-framework/catalogd repository is the `upstream` remote on your machine: 23 | 24 | - ```sh 25 | git checkout main 26 | git fetch upstream 27 | git pull upstream main 28 | git checkout -b release-v0.Y 29 | git push upstream release-v0.Y 30 | ``` 31 | 32 | 2. Tag the release: 33 | 34 | - ```sh 35 | git tag -am "catalogd v0.Y.0" v0.Y.0 36 | git push upstream v0.Y.0 37 | ``` 38 | 39 | 3. Check the status of the [release GitHub Action](https://github.com/operator-framework/catalogd/actions/workflows/release.yaml). 40 | Once it is complete, the new release should appear on the [release page](https://github.com/operator-framework/catalogd/releases). 41 | 42 | 4. Clean up the GitHub milestone: 43 | - In the [GitHub milestone](https://github.com/operator-framework/catalogd/milestones), bump any open issues to the following release and then close out the milestone. 44 | -------------------------------------------------------------------------------- /test/e2e/util.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net/url" 8 | "strings" 9 | 10 | "k8s.io/client-go/kubernetes" 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | 13 | catalogdv1 "github.com/operator-framework/catalogd/api/v1" 14 | ) 15 | 16 | func ReadTestCatalogServerContents(ctx context.Context, catalog *catalogdv1.ClusterCatalog, c client.Client, kubeClient kubernetes.Interface) ([]byte, error) { 17 | if catalog == nil { 18 | return nil, fmt.Errorf("cannot read nil catalog") 19 | } 20 | if catalog.Status.URLs == nil { 21 | return nil, fmt.Errorf("catalog %q has no catalog urls", catalog.Name) 22 | } 23 | url, err := url.Parse(catalog.Status.URLs.Base) 24 | if err != nil { 25 | return nil, fmt.Errorf("error parsing clustercatalog url %q: %v", catalog.Status.URLs.Base, err) 26 | } 27 | // url is expected to be in the format of 28 | // http://{service_name}.{namespace}.svc/catalogs/{catalog_name}/ 29 | // so to get the namespace and name of the service we grab only 30 | // the hostname and split it on the '.' character 31 | ns := strings.Split(url.Hostname(), ".")[1] 32 | name := strings.Split(url.Hostname(), ".")[0] 33 | port := url.Port() 34 | // the ProxyGet() call below needs an explicit port value, so if 35 | // value from url.Port() is empty, we assume port 443. 36 | if port == "" { 37 | if url.Scheme == "https" { 38 | port = "443" 39 | } else { 40 | port = "80" 41 | } 42 | } 43 | resp := kubeClient.CoreV1().Services(ns).ProxyGet(url.Scheme, name, port, url.JoinPath("api", "v1", "all").Path, map[string]string{}) 44 | rc, err := resp.Stream(ctx) 45 | if err != nil { 46 | return nil, err 47 | } 48 | defer rc.Close() 49 | 50 | return io.ReadAll(rc) 51 | } 52 | -------------------------------------------------------------------------------- /scripts/install.tpl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | catalogd_manifest=$MANIFEST 6 | 7 | if [[ -z "$catalogd_manifest" ]]; then 8 | echo "Error: Missing required MANIFEST variable" 9 | exit 1 10 | fi 11 | 12 | cert_mgr_version=$CERT_MGR_VERSION 13 | default_catalogs=$DEFAULT_CATALOGS 14 | 15 | if [[ -z "$default_catalogs" || -z "$cert_mgr_version" ]]; then 16 | err="Error: Missing component value(s) for: " 17 | if [[ -z "$default_catalogs" ]]; then 18 | err+="default cluster catalogs " 19 | fi 20 | if [[ -z "$cert_mgr_version" ]]; then 21 | err+="cert-manager version " 22 | fi 23 | echo "$err" 24 | exit 1 25 | fi 26 | 27 | function kubectl_wait() { 28 | namespace=$1 29 | runtime=$2 30 | timeout=$3 31 | 32 | kubectl wait --for=condition=Available --namespace="${namespace}" "${runtime}" --timeout="${timeout}" 33 | } 34 | 35 | kubectl apply -f "https://github.com/cert-manager/cert-manager/releases/download/${cert_mgr_version}/cert-manager.yaml" 36 | kubectl_wait "cert-manager" "deployment/cert-manager-cainjector" "60s" 37 | kubectl_wait "cert-manager" "deployment/cert-manager-webhook" "60s" 38 | kubectl_wait "cert-manager" "deployment/cert-manager" "60s" 39 | kubectl wait mutatingwebhookconfigurations/cert-manager-webhook --for=jsonpath='{.webhooks[0].clientConfig.caBundle}' --timeout=60s 40 | kubectl wait validatingwebhookconfigurations/cert-manager-webhook --for=jsonpath='{.webhooks[0].clientConfig.caBundle}' --timeout=60s 41 | kubectl apply -f "${catalogd_manifest}" 42 | kubectl_wait "olmv1-system" "deployment/catalogd-controller-manager" "60s" 43 | 44 | kubectl apply -f "${default_catalogs}" 45 | kubectl wait --for=condition=Serving "clustercatalog/operatorhubio" --timeout="60s" -------------------------------------------------------------------------------- /test/tools/imageregistry/imgreg.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: catalogd-e2e 5 | --- 6 | apiVersion: cert-manager.io/v1 7 | kind: Issuer 8 | metadata: 9 | name: selfsigned-issuer 10 | namespace: catalogd-e2e 11 | spec: 12 | selfSigned: {} 13 | --- 14 | apiVersion: cert-manager.io/v1 15 | kind: Certificate 16 | metadata: 17 | name: catalogd-e2e-registry 18 | namespace: catalogd-e2e 19 | spec: 20 | secretName: catalogd-e2e-registry 21 | isCA: true 22 | dnsNames: 23 | - docker-registry.catalogd-e2e.svc 24 | privateKey: 25 | algorithm: ECDSA 26 | size: 256 27 | issuerRef: 28 | name: ${ISSUER_NAME} 29 | kind: ${ISSUER_KIND} 30 | group: cert-manager.io 31 | --- 32 | apiVersion: apps/v1 33 | kind: Deployment 34 | metadata: 35 | name: docker-registry 36 | namespace: catalogd-e2e 37 | labels: 38 | app: registry 39 | spec: 40 | replicas: 1 41 | selector: 42 | matchLabels: 43 | app: registry 44 | template: 45 | metadata: 46 | labels: 47 | app: registry 48 | spec: 49 | containers: 50 | - name: registry 51 | image: registry:2 52 | volumeMounts: 53 | - name: certs-vol 54 | mountPath: "/certs" 55 | env: 56 | - name: REGISTRY_HTTP_TLS_CERTIFICATE 57 | value: "/certs/tls.crt" 58 | - name: REGISTRY_HTTP_TLS_KEY 59 | value: "/certs/tls.key" 60 | volumes: 61 | - name: certs-vol 62 | secret: 63 | secretName: catalogd-e2e-registry 64 | --- 65 | apiVersion: v1 66 | kind: Service 67 | metadata: 68 | name: docker-registry 69 | namespace: catalogd-e2e 70 | spec: 71 | selector: 72 | app: registry 73 | ports: 74 | - port: 5000 75 | targetPort: 5000 76 | -------------------------------------------------------------------------------- /test/tools/imageregistry/registry.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | # registry.sh will create an in-cluster image registry useful for end-to-end testing 6 | # of catalogd's unpacking process. It does a few things: 7 | # 1. Installs cert-manager for creating a self-signed certificate for the image registry 8 | # 2. Creates all the resources necessary for deploying the image registry in the catalogd-e2e namespace 9 | # 3. Creates ConfigMaps containing the test catalog + Dockerfile to be mounted to the kaniko pod 10 | # 4. Waits for kaniko pod to have Condition Complete == true, indicating the test catalog image has been built + pushed 11 | # to the test image registry 12 | # Usage: 13 | # registry.sh 14 | 15 | if [[ "$#" -ne 2 ]]; then 16 | echo "Incorrect number of arguments passed" 17 | echo "Usage: registry.sh " 18 | exit 1 19 | fi 20 | 21 | export ISSUER_KIND=$1 22 | export ISSUER_NAME=$2 23 | 24 | # create the image registry with all the certs 25 | envsubst '${ISSUER_KIND},${ISSUER_NAME}' < test/tools/imageregistry/imgreg.yaml | kubectl apply -f - 26 | kubectl wait -n catalogd-e2e --for=condition=Available deployment/docker-registry --timeout=60s 27 | 28 | # Load the testdata onto the cluster as a configmap so it can be used with kaniko 29 | kubectl create configmap -n catalogd-e2e --from-file=testdata/catalogs/test-catalog.Dockerfile catalogd-e2e.dockerfile 30 | kubectl create configmap -n catalogd-e2e --from-file=testdata/catalogs/test-catalog catalogd-e2e.build-contents 31 | 32 | # Create the kaniko pod to build the test image and push it to the test registry. 33 | kubectl apply -f test/tools/imageregistry/imagebuilder.yaml 34 | kubectl wait --for=condition=Complete -n catalogd-e2e jobs/kaniko --timeout=60s 35 | -------------------------------------------------------------------------------- /hack/scripts/demo-script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # Welcome to the catalogd demo 5 | # 6 | trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT 7 | 8 | 9 | kind delete cluster 10 | kind create cluster 11 | kubectl cluster-info --context kind-kind 12 | sleep 10 13 | 14 | # use the install script from the latest github release 15 | curl -L -s https://github.com/operator-framework/catalogd/releases/latest/download/install.sh | bash 16 | 17 | # inspect crds (clustercatalog) 18 | kubectl get crds -A 19 | kubectl get clustercatalog -A 20 | 21 | echo "... checking catalogd controller is available" 22 | kubectl wait --for=condition=Available -n olmv1-system deploy/catalogd-controller-manager --timeout=1m 23 | echo "... checking clustercatalog is serving" 24 | kubectl wait --for=condition=Serving clustercatalog/operatorhubio --timeout=60s 25 | echo "... checking clustercatalog is finished unpacking" 26 | kubectl wait --for=condition=Progressing=False clustercatalog/operatorhubio --timeout=60s 27 | 28 | # port forward the catalogd-service service to interact with the HTTP server serving catalog contents 29 | (kubectl -n olmv1-system port-forward svc/catalogd-service 8081:443)& 30 | 31 | sleep 3 32 | 33 | # check what 'packages' are available in this catalog 34 | curl -k https://localhost:8081/catalogs/operatorhubio/api/v1/all | jq -s '.[] | select(.schema == "olm.package") | .name' 35 | # check what channels are included in the wavefront package 36 | curl -k https://localhost:8081/catalogs/operatorhubio/api/v1/all | jq -s '.[] | select(.schema == "olm.channel") | select(.package == "wavefront") | .name' 37 | # check what bundles are included in the wavefront package 38 | curl -k https://localhost:8081/catalogs/operatorhubio/api/v1/all | jq -s '.[] | select(.schema == "olm.bundle") | select(.package == "wavefront") | .name' 39 | 40 | -------------------------------------------------------------------------------- /internal/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/prometheus/client_golang/prometheus" 7 | "github.com/prometheus/client_golang/prometheus/promhttp" 8 | ) 9 | 10 | const ( 11 | RequestDurationMetricName = "catalogd_http_request_duration_seconds" 12 | ) 13 | 14 | // Sets up the necessary metrics for calculating the Apdex Score 15 | // If using Grafana for visualization connected to a Prometheus data 16 | // source that is scraping these metrics, you can create a panel that 17 | // uses the following queries + expressions for calculating the Apdex Score where T = 0.5: 18 | // Query A: sum(catalogd_http_request_duration_seconds_bucket{code!~"5..",le="0.5"}) 19 | // Query B: sum(catalogd_http_request_duration_seconds_bucket{code!~"5..",le="2"}) 20 | // Query C: sum(catalogd_http_request_duration_seconds_count) 21 | // Expression for Apdex Score: ($A + (($B - $A) / 2)) / $C 22 | var ( 23 | RequestDurationMetric = prometheus.NewHistogramVec( 24 | prometheus.HistogramOpts{ 25 | Name: RequestDurationMetricName, 26 | Help: "Histogram of request duration in seconds", 27 | // create a bucket for each 100 ms up to 1s and ensure it multiplied by 4 also exists. 28 | // Include a 10s bucket to capture very long running requests. This allows us to easily 29 | // calculate Apdex Scores up to a T of 1 second, but using various mathmatical formulas we 30 | // should be able to estimate Apdex Scores up to a T of 2.5. Having a larger range of buckets 31 | // will allow us to more easily calculate health indicators other than the Apdex Score. 32 | Buckets: []float64{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.2, 1.6, 2, 2.4, 2.8, 3.2, 3.6, 4, 10}, 33 | }, 34 | []string{"code"}, 35 | ) 36 | ) 37 | 38 | func AddMetricsToHandler(handler http.Handler) http.Handler { 39 | return promhttp.InstrumentHandlerDuration(RequestDurationMetric, handler) 40 | } 41 | -------------------------------------------------------------------------------- /internal/webhook/cluster_catalog_webhook.go: -------------------------------------------------------------------------------- 1 | package webhook 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "k8s.io/apimachinery/pkg/runtime" 8 | ctrl "sigs.k8s.io/controller-runtime" 9 | "sigs.k8s.io/controller-runtime/pkg/log" 10 | 11 | catalogdv1 "github.com/operator-framework/catalogd/api/v1" 12 | ) 13 | 14 | // +kubebuilder:webhook:admissionReviewVersions={v1},failurePolicy=Fail,groups=olm.operatorframework.io,mutating=true,name=inject-metadata-name.olm.operatorframework.io,path=/mutate-olm-operatorframework-io-v1-clustercatalog,resources=clustercatalogs,verbs=create;update,versions=v1,sideEffects=None,timeoutSeconds=10 15 | 16 | // +kubebuilder:rbac:groups=olm.operatorframework.io,resources=clustercatalogs,verbs=get;list;watch;patch;update 17 | 18 | // ClusterCatalog wraps the external v1.ClusterCatalog type and implements admission.Defaulter 19 | type ClusterCatalog struct{} 20 | 21 | // Default is the method that will be called by the webhook to apply defaults. 22 | func (r *ClusterCatalog) Default(ctx context.Context, obj runtime.Object) error { 23 | log := log.FromContext(ctx) 24 | log.Info("Invoking Default method for ClusterCatalog", "object", obj) 25 | catalog, ok := obj.(*catalogdv1.ClusterCatalog) 26 | if !ok { 27 | return fmt.Errorf("expected a ClusterCatalog but got a %T", obj) 28 | } 29 | 30 | // Defaulting logic: add the "olm.operatorframework.io/metadata.name" label 31 | if catalog.Labels == nil { 32 | catalog.Labels = map[string]string{} 33 | } 34 | catalog.Labels[catalogdv1.MetadataNameLabel] = catalog.GetName() 35 | log.Info("default", catalogdv1.MetadataNameLabel, catalog.Name, "labels", catalog.Labels) 36 | 37 | return nil 38 | } 39 | 40 | // SetupWebhookWithManager sets up the webhook with the manager 41 | func (r *ClusterCatalog) SetupWebhookWithManager(mgr ctrl.Manager) error { 42 | return ctrl.NewWebhookManagedBy(mgr). 43 | For(&catalogdv1.ClusterCatalog{}). 44 | WithDefaulter(r). 45 | Complete() 46 | } 47 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | ######## 2 | # NOTE 3 | # 4 | # This file is duplicated in the following repos: 5 | # - operator-framework/kubectl-operator 6 | # - operator-framework/catalogd 7 | # - operator-framework/operator-controller 8 | # - operator-framework/rukpak 9 | # 10 | # If you are making a change, please make it in ALL 11 | # of the above repositories! 12 | # 13 | # TODO: Find a way to have a shared golangci config. 14 | ######## 15 | 16 | run: 17 | # Default timeout is 1m, up to give more room 18 | timeout: 4m 19 | 20 | linters: 21 | enable: 22 | - asciicheck 23 | - bodyclose 24 | - errorlint 25 | - gci 26 | - gofmt 27 | - gosec 28 | - importas 29 | - misspell 30 | - nestif 31 | - nonamedreturns 32 | - prealloc 33 | - stylecheck 34 | - tparallel 35 | - unconvert 36 | - unparam 37 | - unused 38 | - whitespace 39 | 40 | linters-settings: 41 | gci: 42 | sections: 43 | - standard 44 | - dot 45 | - default 46 | - prefix(github.com/operator-framework) 47 | 48 | # TODO: change this to `localmodule` when golangci-lint 49 | # is updated to 1.58+ 50 | - prefix(github.com/operator-framework/catalogd) 51 | custom-order: true 52 | 53 | errorlint: 54 | errorf: false 55 | 56 | importas: 57 | alias: 58 | - pkg: k8s.io/apimachinery/pkg/apis/meta/v1 59 | alias: metav1 60 | - pkg: k8s.io/apimachinery/pkg/api/errors 61 | alias: apierrors 62 | - pkg: k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1 63 | alias: apiextensionsv1 64 | - pkg: k8s.io/apimachinery/pkg/util/runtime 65 | alias: utilruntime 66 | - pkg: "^k8s\\.io/api/([^/]+)/(v[^/]+)$" 67 | alias: $1$2 68 | - pkg: sigs.k8s.io/controller-runtime 69 | alias: ctrl 70 | - pkg: github.com/operator-framework/rukpak/api/v1alpha2 71 | alias: rukpakv1alpha2 72 | - pkg: github.com/blang/semver/v4 73 | alias: bsemver 74 | 75 | output: 76 | formats: 77 | - format: tab 78 | -------------------------------------------------------------------------------- /internal/serverutil/serverutil.go: -------------------------------------------------------------------------------- 1 | package serverutil 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | "time" 9 | 10 | ctrl "sigs.k8s.io/controller-runtime" 11 | "sigs.k8s.io/controller-runtime/pkg/certwatcher" 12 | 13 | catalogdmetrics "github.com/operator-framework/catalogd/internal/metrics" 14 | "github.com/operator-framework/catalogd/internal/storage" 15 | "github.com/operator-framework/catalogd/internal/third_party/server" 16 | ) 17 | 18 | type CatalogServerConfig struct { 19 | ExternalAddr string 20 | CatalogAddr string 21 | CertFile string 22 | KeyFile string 23 | LocalStorage storage.Instance 24 | } 25 | 26 | func AddCatalogServerToManager(mgr ctrl.Manager, cfg CatalogServerConfig, tlsFileWatcher *certwatcher.CertWatcher) error { 27 | listener, err := net.Listen("tcp", cfg.CatalogAddr) 28 | if err != nil { 29 | return fmt.Errorf("error creating catalog server listener: %w", err) 30 | } 31 | 32 | if cfg.CertFile != "" && cfg.KeyFile != "" { 33 | // Use the passed certificate watcher instead of creating a new one 34 | config := &tls.Config{ 35 | GetCertificate: tlsFileWatcher.GetCertificate, 36 | MinVersion: tls.VersionTLS12, 37 | } 38 | listener = tls.NewListener(listener, config) 39 | } 40 | 41 | shutdownTimeout := 30 * time.Second 42 | 43 | catalogServer := server.Server{ 44 | Kind: "catalogs", 45 | Server: &http.Server{ 46 | Addr: cfg.CatalogAddr, 47 | Handler: catalogdmetrics.AddMetricsToHandler(cfg.LocalStorage.StorageServerHandler()), 48 | ReadTimeout: 5 * time.Second, 49 | // TODO: Revert this to 10 seconds if/when the API 50 | // evolves to have significantly smaller responses 51 | WriteTimeout: 5 * time.Minute, 52 | }, 53 | ShutdownTimeout: &shutdownTimeout, 54 | Listener: listener, 55 | } 56 | 57 | err = mgr.Add(&catalogServer) 58 | if err != nil { 59 | return fmt.Errorf("error adding catalog server to manager: %w", err) 60 | } 61 | 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | workflow_dispatch: 5 | merge_group: 6 | push: 7 | branches: 8 | - 'main' 9 | tags: 10 | - 'v*' 11 | pull_request: 12 | branches: 13 | - main 14 | 15 | jobs: 16 | goreleaser: 17 | name: goreleaser 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Check out code into the Go module directory 21 | uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 24 | 25 | - name: Install Go 26 | uses: actions/setup-go@v5 27 | with: 28 | go-version-file: "go.mod" 29 | 30 | - name: Docker Login 31 | if: ${{ github.event_name != 'pull_request' }} 32 | uses: docker/login-action@v3 33 | with: 34 | registry: quay.io 35 | username: ${{ secrets.QUAY_USERNAME }} 36 | password: ${{ secrets.QUAY_PASSWORD }} 37 | 38 | - name: Set the release related variables 39 | run: | 40 | if [[ $GITHUB_REF == refs/tags/* ]]; then 41 | # Release tags. 42 | echo IMAGE_TAG="${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV 43 | echo GORELEASER_ARGS="--clean" >> $GITHUB_ENV 44 | echo ENABLE_RELEASE_PIPELINE=true >> $GITHUB_ENV 45 | elif [[ $GITHUB_REF == refs/heads/main ]]; then 46 | # 'main' branch build. 47 | echo IMAGE_TAG="$(echo "${GITHUB_REF#refs/heads/}" | sed -r 's|/+|-|g')" >> $GITHUB_ENV 48 | echo GORELEASER_ARGS="--clean --skip=validate" >> $GITHUB_ENV 49 | elif [[ $GITHUB_REF == refs/pull/* ]]; then 50 | # PR build. 51 | echo IMAGE_TAG="pr-$(echo "${GITHUB_REF}" | sed -E 's|refs/pull/([^/]+)/?.*|\1|')" >> $GITHUB_ENV 52 | else 53 | echo IMAGE_TAG="$(git describe --tags --always)" >> $GITHUB_ENV 54 | fi 55 | - name: Create release manifests 56 | run: | 57 | echo VERSION="${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV 58 | make quickstart 59 | 60 | - name: Run goreleaser 61 | run: make release 62 | env: 63 | GITHUB_TOKEN: ${{ github.token }} 64 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod tidy 4 | - go mod download 5 | builds: 6 | - id: manager 7 | main: ./cmd/manager/ 8 | binary: manager 9 | asmflags: "{{ .Env.GO_BUILD_ASMFLAGS }}" 10 | gcflags: "{{ .Env.GO_BUILD_GCFLAGS }}" 11 | ldflags: "{{ .Env.GO_BUILD_LDFLAGS }}" 12 | tags: 13 | - "{{ .Env.GO_BUILD_TAGS }}" 14 | mod_timestamp: "{{ .CommitTimestamp }}" 15 | goos: 16 | - linux 17 | goarch: 18 | - amd64 19 | - arm64 20 | - ppc64le 21 | - s390x 22 | dockers: 23 | - image_templates: 24 | - "{{ .Env.IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-amd64" 25 | dockerfile: Dockerfile 26 | goos: linux 27 | goarch: amd64 28 | use: buildx 29 | build_flag_templates: 30 | - "--platform=linux/amd64" 31 | - image_templates: 32 | - "{{ .Env.IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-arm64" 33 | dockerfile: Dockerfile 34 | goos: linux 35 | goarch: arm64 36 | use: buildx 37 | build_flag_templates: 38 | - "--platform=linux/arm64" 39 | - image_templates: 40 | - "{{ .Env.IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-ppc64le" 41 | dockerfile: Dockerfile 42 | goos: linux 43 | goarch: ppc64le 44 | use: buildx 45 | build_flag_templates: 46 | - "--platform=linux/ppc64le" 47 | - image_templates: 48 | - "{{ .Env.IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-s390x" 49 | dockerfile: Dockerfile 50 | goos: linux 51 | goarch: s390x 52 | use: buildx 53 | build_flag_templates: 54 | - "--platform=linux/s390x" 55 | docker_manifests: 56 | - name_template: "{{ .Env.IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}" 57 | image_templates: 58 | - "{{ .Env.IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-amd64" 59 | - "{{ .Env.IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-arm64" 60 | - "{{ .Env.IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-ppc64le" 61 | - "{{ .Env.IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-s390x" 62 | release: 63 | disable: '{{ ne .Env.ENABLE_RELEASE_PIPELINE "true" }}' 64 | extra_files: 65 | - glob: 'catalogd.yaml' 66 | - glob: './config/base/default/clustercatalogs/default-catalogs.yaml' 67 | - glob: 'install.sh' 68 | header: | 69 | ## Installation 70 | ```bash 71 | curl -L -s https://github.com/operator-framework/catalogd/releases/download/{{ .Tag }}/install.sh | bash -s 72 | ``` 73 | -------------------------------------------------------------------------------- /hack/scripts/check-go-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASE_REF=${1:-main} 4 | GO_VER=$(sed -En 's/^go (.*)$/\1/p' "go.mod") 5 | OLDIFS="${IFS}" 6 | IFS='.' MAX_VER=(${GO_VER}) 7 | IFS="${OLDIFS}" 8 | 9 | if [ ${#MAX_VER[*]} -ne 3 -a ${#MAX_VER[*]} -ne 2 ]; then 10 | echo "Invalid go version: ${GO_VER}" 11 | exit 1 12 | fi 13 | 14 | GO_MAJOR=${MAX_VER[0]} 15 | GO_MINOR=${MAX_VER[1]} 16 | GO_PATCH=${MAX_VER[2]} 17 | 18 | RETCODE=0 19 | 20 | check_version () { 21 | local whole=$1 22 | local file=$2 23 | OLDIFS="${IFS}" 24 | IFS='.' ver=(${whole}) 25 | IFS="${OLDIFS}" 26 | 27 | if [ ${ver[0]} -gt ${GO_MAJOR} ]; then 28 | echo "${file}: ${whole}: Bad golang version (expected ${GO_VER} or less)" 29 | return 1 30 | fi 31 | if [ ${ver[1]} -gt ${GO_MINOR} ]; then 32 | echo "${file}: ${whole}: Bad golang version (expected ${GO_VER} or less)" 33 | return 1 34 | fi 35 | 36 | if [ ${#ver[*]} -eq 2 ] ; then 37 | return 0 38 | fi 39 | if [ ${#ver[*]} -ne 3 ] ; then 40 | echo "${file}: ${whole}: Badly formatted golang version" 41 | return 1 42 | fi 43 | 44 | if [ ${ver[1]} -eq ${GO_MINOR} -a ${ver[2]} -gt ${GO_PATCH} ]; then 45 | echo "${file}: ${whole}: Bad golang version (expected ${GO_VER} or less)" 46 | return 1 47 | fi 48 | return 0 49 | } 50 | 51 | echo "Found golang version: ${GO_VER}" 52 | 53 | for f in $(find . -name "*.mod"); do 54 | v=$(sed -En 's/^go (.*)$/\1/p' ${f}) 55 | if [ -z ${v} ]; then 56 | echo "${f}: Skipping, no version found" 57 | continue 58 | fi 59 | if ! check_version ${v} ${f}; then 60 | RETCODE=1 61 | fi 62 | old=$(git grep -ohP '^go .*$' "${BASE_REF}" -- "${f}") 63 | old=${old#go } 64 | new=$(git grep -ohP '^go .*$' "${f}") 65 | new=${new#go } 66 | # If ${old} is empty, it means this is a new .mod file 67 | if [ -z "${old}" ]; then 68 | continue 69 | fi 70 | # Check if patch version remains 0: X.x.0 <-> X.x 71 | if [ "${new}.0" == "${old}" -o "${new}" == "${old}.0" ]; then 72 | continue 73 | fi 74 | if [ "${new}" != "${old}" ]; then 75 | echo "${f}: ${v}: Updated golang version from ${old}" 76 | RETCODE=1 77 | fi 78 | done 79 | 80 | exit ${RETCODE} 81 | -------------------------------------------------------------------------------- /config/base/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | app.kubernetes.io/part-of: olm 6 | pod-security.kubernetes.io/enforce: baseline 7 | pod-security.kubernetes.io/enforce-version: latest 8 | name: system 9 | --- 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | metadata: 13 | name: controller-manager 14 | namespace: system 15 | annotations: 16 | kubectl.kubernetes.io/default-logs-container: manager 17 | labels: 18 | control-plane: catalogd-controller-manager 19 | spec: 20 | selector: 21 | matchLabels: 22 | control-plane: catalogd-controller-manager 23 | replicas: 1 24 | minReadySeconds: 5 25 | template: 26 | metadata: 27 | annotations: 28 | kubectl.kubernetes.io/default-container: manager 29 | labels: 30 | control-plane: catalogd-controller-manager 31 | spec: 32 | affinity: 33 | nodeAffinity: 34 | requiredDuringSchedulingIgnoredDuringExecution: 35 | nodeSelectorTerms: 36 | - matchExpressions: 37 | - key: kubernetes.io/arch 38 | operator: In 39 | values: 40 | - amd64 41 | - arm64 42 | - ppc64le 43 | - s390x 44 | - key: kubernetes.io/os 45 | operator: In 46 | values: 47 | - linux 48 | securityContext: 49 | runAsNonRoot: true 50 | seccompProfile: 51 | type: RuntimeDefault 52 | containers: 53 | - command: 54 | - ./manager 55 | args: 56 | - --leader-elect 57 | - --metrics-bind-address=:7443 58 | - --external-address=catalogd-service.olmv1-system.svc 59 | image: controller:latest 60 | name: manager 61 | volumeMounts: 62 | - name: cache 63 | mountPath: /var/cache/ 64 | securityContext: 65 | allowPrivilegeEscalation: false 66 | capabilities: 67 | drop: 68 | - ALL 69 | livenessProbe: 70 | httpGet: 71 | path: /healthz 72 | port: 8081 73 | initialDelaySeconds: 15 74 | periodSeconds: 20 75 | readinessProbe: 76 | httpGet: 77 | path: /readyz 78 | port: 8081 79 | initialDelaySeconds: 5 80 | periodSeconds: 10 81 | resources: 82 | requests: 83 | cpu: 100m 84 | memory: 200Mi 85 | imagePullPolicy: IfNotPresent 86 | terminationMessagePolicy: FallbackToLogsOnError 87 | serviceAccountName: controller-manager 88 | terminationGracePeriodSeconds: 10 89 | volumes: 90 | - name: cache 91 | emptyDir: {} 92 | -------------------------------------------------------------------------------- /internal/source/unpacker.go: -------------------------------------------------------------------------------- 1 | package source 2 | 3 | import ( 4 | "context" 5 | "io/fs" 6 | "time" 7 | 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | 10 | catalogdv1 "github.com/operator-framework/catalogd/api/v1" 11 | ) 12 | 13 | // TODO: This package is almost entirely copy/pasted from rukpak. We should look 14 | // into whether it is possible to share this code. 15 | // 16 | // TODO: None of the rukpak CRD validations (both static and from the rukpak 17 | // webhooks) related to the source are present here. Which of them do we need? 18 | 19 | // Unpacker unpacks catalog content, either synchronously or asynchronously and 20 | // returns a Result, which conveys information about the progress of unpacking 21 | // the catalog content. 22 | // 23 | // If a Source unpacks content asynchronously, it should register one or more 24 | // watches with a controller to ensure that Bundles referencing this source 25 | // can be reconciled as progress updates are available. 26 | // 27 | // For asynchronous Sources, multiple calls to Unpack should be made until the 28 | // returned result includes state StateUnpacked. 29 | // 30 | // NOTE: A source is meant to be agnostic to specific catalog formats and 31 | // specifications. A source should treat a catalog root directory as an opaque 32 | // file tree and delegate catalog format concerns to catalog parsers. 33 | type Unpacker interface { 34 | Unpack(context.Context, *catalogdv1.ClusterCatalog) (*Result, error) 35 | Cleanup(context.Context, *catalogdv1.ClusterCatalog) error 36 | } 37 | 38 | // Result conveys progress information about unpacking catalog content. 39 | type Result struct { 40 | // Bundle contains the full filesystem of a catalog's root directory. 41 | FS fs.FS 42 | 43 | // ResolvedSource is a reproducible view of a Bundle's Source. 44 | // When possible, source implementations should return a ResolvedSource 45 | // that pins the Source such that future fetches of the catalog content can 46 | // be guaranteed to fetch the exact same catalog content as the original 47 | // unpack. 48 | // 49 | // For example, resolved image sources should reference a container image 50 | // digest rather than an image tag, and git sources should reference a 51 | // commit hash rather than a branch or tag. 52 | ResolvedSource *catalogdv1.ResolvedCatalogSource 53 | 54 | LastSuccessfulPollAttempt metav1.Time 55 | 56 | // State is the current state of unpacking the catalog content. 57 | State State 58 | 59 | // Message is contextual information about the progress of unpacking the 60 | // catalog content. 61 | Message string 62 | 63 | // UnpackTime is the timestamp when the transition to the current State happened 64 | UnpackTime time.Time 65 | } 66 | 67 | type State string 68 | 69 | // StateUnpacked conveys that the catalog has been successfully unpacked. 70 | const StateUnpacked State = "Unpacked" 71 | 72 | const UnpackCacheDir = "unpack" 73 | -------------------------------------------------------------------------------- /internal/garbagecollection/garbage_collector_test.go: -------------------------------------------------------------------------------- 1 | package garbagecollection 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/apimachinery/pkg/runtime" 13 | "k8s.io/client-go/metadata/fake" 14 | 15 | catalogdv1 "github.com/operator-framework/catalogd/api/v1" 16 | ) 17 | 18 | func TestRunGarbageCollection(t *testing.T) { 19 | for _, tt := range []struct { 20 | name string 21 | existCatalogs []*metav1.PartialObjectMetadata 22 | notExistCatalogs []*metav1.PartialObjectMetadata 23 | wantErr bool 24 | }{ 25 | { 26 | name: "successful garbage collection", 27 | existCatalogs: []*metav1.PartialObjectMetadata{ 28 | { 29 | TypeMeta: metav1.TypeMeta{ 30 | Kind: "ClusterCatalog", 31 | APIVersion: catalogdv1.GroupVersion.String(), 32 | }, 33 | ObjectMeta: metav1.ObjectMeta{ 34 | Name: "one", 35 | }, 36 | }, 37 | { 38 | TypeMeta: metav1.TypeMeta{ 39 | Kind: "ClusterCatalog", 40 | APIVersion: catalogdv1.GroupVersion.String(), 41 | }, 42 | ObjectMeta: metav1.ObjectMeta{ 43 | Name: "two", 44 | }, 45 | }, 46 | }, 47 | notExistCatalogs: []*metav1.PartialObjectMetadata{ 48 | { 49 | TypeMeta: metav1.TypeMeta{ 50 | Kind: "ClusterCatalog", 51 | APIVersion: catalogdv1.GroupVersion.String(), 52 | }, 53 | ObjectMeta: metav1.ObjectMeta{ 54 | Name: "three", 55 | }, 56 | }, 57 | }, 58 | }, 59 | } { 60 | t.Run(tt.name, func(t *testing.T) { 61 | ctx := context.Background() 62 | cachePath := t.TempDir() 63 | scheme := runtime.NewScheme() 64 | require.NoError(t, metav1.AddMetaToScheme(scheme)) 65 | 66 | allCatalogs := append(tt.existCatalogs, tt.notExistCatalogs...) 67 | for _, catalog := range allCatalogs { 68 | require.NoError(t, os.MkdirAll(filepath.Join(cachePath, catalog.Name, "fakedigest"), os.ModePerm)) 69 | } 70 | 71 | runtimeObjs := []runtime.Object{} 72 | for _, catalog := range tt.existCatalogs { 73 | runtimeObjs = append(runtimeObjs, catalog) 74 | } 75 | 76 | metaClient := fake.NewSimpleMetadataClient(scheme, runtimeObjs...) 77 | 78 | _, err := runGarbageCollection(ctx, cachePath, metaClient) 79 | if !tt.wantErr { 80 | assert.NoError(t, err) 81 | entries, err := os.ReadDir(cachePath) 82 | require.NoError(t, err) 83 | assert.Len(t, entries, len(tt.existCatalogs)) 84 | for _, catalog := range tt.existCatalogs { 85 | assert.DirExists(t, filepath.Join(cachePath, catalog.Name)) 86 | } 87 | 88 | for _, catalog := range tt.notExistCatalogs { 89 | assert.NoDirExists(t, filepath.Join(cachePath, catalog.Name)) 90 | } 91 | } else { 92 | assert.Error(t, err) 93 | } 94 | }) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /tilt.md: -------------------------------------------------------------------------------- 1 | # Rapid iterative development with Tilt 2 | 3 | [Tilt](https://tilt.dev) is a tool that enables rapid iterative development of containerized workloads. 4 | 5 | Here is an example workflow without Tilt for modifying some source code and testing those changes in a cluster: 6 | 7 | 1. Modify the source code. 8 | 2. Build the container image. 9 | 3. Either push the image to a registry or load it into your kind cluster. 10 | 4. Deploy all the appropriate Kubernetes manifests for your application. 11 | 1. Or, if this is an update, you'd instead scale the Deployment to 0 replicas, scale back to 1, and wait for the 12 | new pod to be running. 13 | 14 | This process can take minutes, depending on how long each step takes. 15 | 16 | Here is the same workflow with Tilt: 17 | 18 | 1. Run `tilt up` 19 | 2. Modify the source code 20 | 3. Wait for Tilt to update the container with your changes 21 | 22 | This ends up taking a fraction of the time, sometimes on the order of a few seconds! 23 | 24 | ## Installing Tilt 25 | 26 | Follow Tilt's [instructions](https://docs.tilt.dev/install.html) for installation. 27 | 28 | ## Install tilt-support Repo 29 | You must install the tilt-support repo at the directory level above this repo 30 | 31 | ```bash 32 | pushd .. 33 | git clone https://github.com/operator-framework/tilt-support 34 | popd 35 | ```` 36 | 37 | ## Starting Tilt 38 | 39 | This is typically as short as: 40 | 41 | ```shell 42 | tilt up 43 | ``` 44 | 45 | **NOTE:** if you are using Podman, at least as of v4.5.1, you need to do this: 46 | 47 | ```shell 48 | DOCKER_BUILDKIT=0 tilt up 49 | ``` 50 | 51 | Otherwise, you'll see an error when Tilt tries to build your image that looks similar to: 52 | 53 | ```text 54 | Build Failed: ImageBuild: stat /var/tmp/libpod_builder2384046170/build/Dockerfile: no such file or directory 55 | ``` 56 | 57 | When Tilt starts, you'll see something like this in your terminal: 58 | 59 | ```text 60 | Tilt started on http://localhost:10350/ 61 | v0.33.1, built 2023-06-28 62 | 63 | (space) to open the browser 64 | (s) to stream logs (--stream=true) 65 | (t) to open legacy terminal mode (--legacy=true) 66 | (ctrl-c) to exit 67 | ``` 68 | 69 | Typically, you'll want to press the space bar to have it open the UI in your web browser. 70 | 71 | Shortly after starting, Tilt processes the `Tiltfile`, resulting in: 72 | 73 | - Building the go binaries 74 | - Building the images 75 | - Loading the images into kind 76 | - Running kustomize and applying everything except the Deployments that reference the images above 77 | - Modifying the Deployments to use the just-built images 78 | - Creating the Deployments 79 | 80 | ## Making code changes 81 | 82 | Any time you change any of the files listed in the `deps` section in the `_binary` `local_resource`, 83 | Tilt automatically rebuilds the go binary. As soon as the binary is rebuilt, Tilt pushes it (and only it) into the 84 | appropriate running container, and then restarts the process. 85 | -------------------------------------------------------------------------------- /internal/controllers/core/pull_secret_controller_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | corev1 "k8s.io/api/core/v1" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/apimachinery/pkg/types" 13 | ctrl "sigs.k8s.io/controller-runtime" 14 | "sigs.k8s.io/controller-runtime/pkg/client/fake" 15 | ) 16 | 17 | func TestSecretSyncerReconciler(t *testing.T) { 18 | secretData := []byte(`{"auths":{"exampleRegistry": "exampledata"}}`) 19 | authFileName := "test-auth.json" 20 | for _, tt := range []struct { 21 | name string 22 | secret *corev1.Secret 23 | addSecret bool 24 | wantErr string 25 | fileShouldExistBefore bool 26 | fileShouldExistAfter bool 27 | }{ 28 | { 29 | name: "secret exists, content gets saved to authFile", 30 | secret: &corev1.Secret{ 31 | ObjectMeta: metav1.ObjectMeta{ 32 | Name: "test-secret", 33 | Namespace: "test-secret-namespace", 34 | }, 35 | Data: map[string][]byte{ 36 | ".dockerconfigjson": secretData, 37 | }, 38 | }, 39 | addSecret: true, 40 | fileShouldExistBefore: false, 41 | fileShouldExistAfter: true, 42 | }, 43 | { 44 | name: "secret does not exist, file exists previously, file should get deleted", 45 | secret: &corev1.Secret{ 46 | ObjectMeta: metav1.ObjectMeta{ 47 | Name: "test-secret", 48 | Namespace: "test-secret-namespace", 49 | }, 50 | Data: map[string][]byte{ 51 | ".dockerconfigjson": secretData, 52 | }, 53 | }, 54 | addSecret: false, 55 | fileShouldExistBefore: true, 56 | fileShouldExistAfter: false, 57 | }, 58 | } { 59 | t.Run(tt.name, func(t *testing.T) { 60 | ctx := context.Background() 61 | tempAuthFile := filepath.Join(t.TempDir(), authFileName) 62 | clientBuilder := fake.NewClientBuilder() 63 | if tt.addSecret { 64 | clientBuilder = clientBuilder.WithObjects(tt.secret) 65 | } 66 | cl := clientBuilder.Build() 67 | 68 | secretKey := types.NamespacedName{Namespace: tt.secret.Namespace, Name: tt.secret.Name} 69 | r := &PullSecretReconciler{ 70 | Client: cl, 71 | SecretKey: secretKey, 72 | AuthFilePath: tempAuthFile, 73 | } 74 | if tt.fileShouldExistBefore { 75 | err := os.WriteFile(tempAuthFile, secretData, 0600) 76 | require.NoError(t, err) 77 | } 78 | res, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: secretKey}) 79 | if tt.wantErr == "" { 80 | require.NoError(t, err) 81 | } else { 82 | require.ErrorContains(t, err, tt.wantErr) 83 | } 84 | require.Equal(t, ctrl.Result{}, res) 85 | 86 | if tt.fileShouldExistAfter { 87 | _, err := os.Stat(tempAuthFile) 88 | require.NoError(t, err) 89 | } else { 90 | _, err := os.Stat(tempAuthFile) 91 | require.True(t, os.IsNotExist(err)) 92 | } 93 | }) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /internal/webhook/cluster_catalog_webhook_test.go: -------------------------------------------------------------------------------- 1 | package webhook 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "k8s.io/apimachinery/pkg/runtime" 10 | 11 | catalogdv1 "github.com/operator-framework/catalogd/api/v1" 12 | ) 13 | 14 | // Define a dummy struct that implements runtime.Object but isn't a ClusterCatalog 15 | type NotClusterCatalog struct { 16 | metav1.TypeMeta 17 | metav1.ObjectMeta 18 | } 19 | 20 | func (n *NotClusterCatalog) DeepCopyObject() runtime.Object { 21 | return &NotClusterCatalog{} 22 | } 23 | 24 | func TestClusterCatalogDefaulting(t *testing.T) { 25 | tests := map[string]struct { 26 | clusterCatalog runtime.Object 27 | expectedLabels map[string]string 28 | expectError bool 29 | errorMessage string 30 | }{ 31 | "no labels provided, name label added": { 32 | clusterCatalog: &catalogdv1.ClusterCatalog{ 33 | ObjectMeta: metav1.ObjectMeta{ 34 | Name: "test-catalog", 35 | }, 36 | }, 37 | expectedLabels: map[string]string{ 38 | "olm.operatorframework.io/metadata.name": "test-catalog", 39 | }, 40 | expectError: false, 41 | }, 42 | "labels already present, name label added": { 43 | clusterCatalog: &catalogdv1.ClusterCatalog{ 44 | ObjectMeta: metav1.ObjectMeta{ 45 | Name: "test-catalog", 46 | Labels: map[string]string{ 47 | "existing": "label", 48 | }, 49 | }, 50 | }, 51 | expectedLabels: map[string]string{ 52 | "olm.operatorframework.io/metadata.name": "test-catalog", 53 | "existing": "label", 54 | }, 55 | expectError: false, 56 | }, 57 | "name label already present, no changes": { 58 | clusterCatalog: &catalogdv1.ClusterCatalog{ 59 | ObjectMeta: metav1.ObjectMeta{ 60 | Name: "test-catalog", 61 | Labels: map[string]string{ 62 | "olm.operatorframework.io/metadata.name": "existing-name", 63 | }, 64 | }, 65 | }, 66 | expectedLabels: map[string]string{ 67 | "olm.operatorframework.io/metadata.name": "test-catalog", // Defaulting should still override this to match the object name 68 | }, 69 | expectError: false, 70 | }, 71 | "invalid object type, expect error": { 72 | clusterCatalog: &NotClusterCatalog{ 73 | TypeMeta: metav1.TypeMeta{ 74 | Kind: "NotClusterCatalog", 75 | APIVersion: "v1", 76 | }, 77 | }, 78 | expectedLabels: nil, 79 | expectError: true, 80 | errorMessage: "expected a ClusterCatalog but got a *webhook.NotClusterCatalog", 81 | }, 82 | } 83 | 84 | for name, tc := range tests { 85 | t.Run(name, func(t *testing.T) { 86 | // Arrange 87 | clusterCatalogWrapper := &ClusterCatalog{} 88 | 89 | // Act 90 | err := clusterCatalogWrapper.Default(context.TODO(), tc.clusterCatalog) 91 | 92 | // Assert 93 | if tc.expectError { 94 | assert.Error(t, err) 95 | assert.Contains(t, err.Error(), tc.errorMessage) 96 | } else { 97 | assert.NoError(t, err) 98 | if tc.expectedLabels != nil { 99 | labels := tc.clusterCatalog.(*catalogdv1.ClusterCatalog).Labels 100 | assert.Equal(t, tc.expectedLabels, labels) 101 | } 102 | } 103 | }) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /internal/source/containers_image_internal_test.go: -------------------------------------------------------------------------------- 1 | package source 2 | 3 | import ( 4 | "archive/tar" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestContainersImage_applyLayerFilter(t *testing.T) { 11 | type testCase struct { 12 | name string 13 | srcPaths []string 14 | tarHeaders []tar.Header 15 | assertion func(*tar.Header, bool, error) 16 | } 17 | for _, tc := range []testCase{ 18 | { 19 | name: "everything found when srcPaths represent root", 20 | srcPaths: []string{"", "/"}, 21 | tarHeaders: []tar.Header{ 22 | { 23 | Name: "file", 24 | }, 25 | { 26 | Name: "/file", 27 | }, 28 | { 29 | Name: "/nested/file", 30 | }, 31 | { 32 | Name: "/deeply/nested/file", 33 | }, 34 | }, 35 | assertion: func(tarHeader *tar.Header, keep bool, err error) { 36 | assert.True(t, keep) 37 | assert.NoError(t, err) 38 | }, 39 | }, 40 | { 41 | name: "nothing found outside of srcPath", 42 | srcPaths: []string{"source"}, 43 | tarHeaders: []tar.Header{ 44 | { 45 | Name: "elsewhere", 46 | }, 47 | { 48 | Name: "/elsewhere", 49 | }, 50 | { 51 | Name: "/nested/elsewhere", 52 | }, 53 | { 54 | Name: "/deeply/nested/elsewhere", 55 | }, 56 | }, 57 | assertion: func(tarHeader *tar.Header, keep bool, err error) { 58 | assert.False(t, keep) 59 | assert.NoError(t, err) 60 | }, 61 | }, 62 | { 63 | name: "absolute paths are trimmed", 64 | srcPaths: []string{"source", "/source"}, 65 | tarHeaders: []tar.Header{ 66 | { 67 | Name: "source", 68 | }, 69 | { 70 | Name: "/source", 71 | }, 72 | { 73 | Name: "source/nested/elsewhere", 74 | }, 75 | { 76 | Name: "/source/nested/elsewhere", 77 | }, 78 | { 79 | Name: "source/deeply/nested/elsewhere", 80 | }, 81 | { 82 | Name: "/source/deeply/nested/elsewhere", 83 | }, 84 | }, 85 | assertion: func(tarHeader *tar.Header, keep bool, err error) { 86 | assert.True(t, keep) 87 | assert.NoError(t, err) 88 | }, 89 | }, 90 | { 91 | name: "up level source paths are not supported", 92 | srcPaths: []string{"../not-supported"}, 93 | tarHeaders: []tar.Header{ 94 | { 95 | Name: "anything", 96 | }, 97 | }, 98 | assertion: func(tarHeader *tar.Header, keep bool, err error) { 99 | assert.False(t, keep) 100 | assert.ErrorContains(t, err, "error getting relative path") 101 | }, 102 | }, 103 | { 104 | name: "up level tar headers are not supported", 105 | srcPaths: []string{"fine"}, 106 | tarHeaders: []tar.Header{ 107 | { 108 | Name: "../not-supported", 109 | }, 110 | { 111 | Name: "../fine", 112 | }, 113 | }, 114 | assertion: func(tarHeader *tar.Header, keep bool, err error) { 115 | assert.False(t, keep) 116 | assert.NoError(t, err) 117 | }, 118 | }, 119 | } { 120 | t.Run(tc.name, func(t *testing.T) { 121 | for _, srcPath := range tc.srcPaths { 122 | f := applyLayerFilter(srcPath) 123 | for _, tarHeader := range tc.tarHeaders { 124 | keep, err := f(&tarHeader) 125 | tc.assertion(&tarHeader, keep, err) 126 | } 127 | } 128 | }) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /internal/garbagecollection/garbage_collector.go: -------------------------------------------------------------------------------- 1 | package garbagecollection 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "time" 9 | 10 | "github.com/go-logr/logr" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/apimachinery/pkg/util/sets" 13 | "k8s.io/client-go/metadata" 14 | "sigs.k8s.io/controller-runtime/pkg/manager" 15 | 16 | catalogdv1 "github.com/operator-framework/catalogd/api/v1" 17 | ) 18 | 19 | var _ manager.Runnable = (*GarbageCollector)(nil) 20 | 21 | // GarbageCollector is an implementation of the manager.Runnable 22 | // interface for running garbage collection on the Catalog content 23 | // cache that is served by the catalogd HTTP server. It runs in a loop 24 | // and will ensure that no cache entries exist for Catalog resources 25 | // that no longer exist. This should only clean up cache entries that 26 | // were missed by the handling of a DELETE event on a Catalog resource. 27 | type GarbageCollector struct { 28 | CachePath string 29 | Logger logr.Logger 30 | MetadataClient metadata.Interface 31 | Interval time.Duration 32 | } 33 | 34 | // Start will start the garbage collector. It will always run once on startup 35 | // and loop until context is canceled after an initial garbage collection run. 36 | // Garbage collection will run again every X amount of time, where X is the 37 | // supplied garbage collection interval. 38 | func (gc *GarbageCollector) Start(ctx context.Context) error { 39 | // Run once on startup 40 | removed, err := runGarbageCollection(ctx, gc.CachePath, gc.MetadataClient) 41 | if err != nil { 42 | gc.Logger.Error(err, "running garbage collection") 43 | } 44 | if len(removed) > 0 { 45 | gc.Logger.Info("removed stale cache entries", "removed entries", removed) 46 | } 47 | 48 | // Loop until context is canceled, running garbage collection 49 | // at the configured interval 50 | for { 51 | select { 52 | case <-ctx.Done(): 53 | return ctx.Err() 54 | case <-time.After(gc.Interval): 55 | removed, err := runGarbageCollection(ctx, gc.CachePath, gc.MetadataClient) 56 | if err != nil { 57 | gc.Logger.Error(err, "running garbage collection") 58 | } 59 | if len(removed) > 0 { 60 | gc.Logger.Info("removed stale cache entries", "removed entries", removed) 61 | } 62 | } 63 | } 64 | } 65 | 66 | func runGarbageCollection(ctx context.Context, cachePath string, metaClient metadata.Interface) ([]string, error) { 67 | getter := metaClient.Resource(catalogdv1.GroupVersion.WithResource("clustercatalogs")) 68 | metaList, err := getter.List(ctx, metav1.ListOptions{}) 69 | if err != nil { 70 | return nil, fmt.Errorf("error listing clustercatalogs: %w", err) 71 | } 72 | 73 | expectedCatalogs := sets.New[string]() 74 | for _, meta := range metaList.Items { 75 | expectedCatalogs.Insert(meta.GetName()) 76 | } 77 | 78 | cacheDirEntries, err := os.ReadDir(cachePath) 79 | if err != nil { 80 | return nil, fmt.Errorf("error reading cache directory: %w", err) 81 | } 82 | removed := []string{} 83 | for _, cacheDirEntry := range cacheDirEntries { 84 | if cacheDirEntry.IsDir() && expectedCatalogs.Has(cacheDirEntry.Name()) { 85 | continue 86 | } 87 | if err := os.RemoveAll(filepath.Join(cachePath, cacheDirEntry.Name())); err != nil { 88 | return nil, fmt.Errorf("error removing cache directory entry %q: %w ", cacheDirEntry.Name(), err) 89 | } 90 | 91 | removed = append(removed, cacheDirEntry.Name()) 92 | } 93 | return removed, nil 94 | } 95 | -------------------------------------------------------------------------------- /crd-diff-config.yaml: -------------------------------------------------------------------------------- 1 | checks: 2 | crd: 3 | scope: 4 | enabled: true 5 | existingFieldRemoval: 6 | enabled: true 7 | storedVersionRemoval: 8 | enabled: true 9 | version: 10 | sameVersion: 11 | enabled: true 12 | unhandledFailureMode: "Closed" 13 | enum: 14 | enabled: true 15 | removalEnforcement: "Strict" 16 | additionEnforcement: "Strict" 17 | default: 18 | enabled: true 19 | changeEnforcement: "Strict" 20 | removalEnforcement: "Strict" 21 | additionEnforcement: "Strict" 22 | required: 23 | enabled: true 24 | newEnforcement: "Strict" 25 | type: 26 | enabled: true 27 | changeEnforcement: "Strict" 28 | maximum: 29 | enabled: true 30 | additionEnforcement: "Strict" 31 | decreaseEnforcement: "Strict" 32 | maxItems: 33 | enabled: true 34 | additionEnforcement: "Strict" 35 | decreaseEnforcement: "Strict" 36 | maxProperties: 37 | enabled: true 38 | additionEnforcement: "Strict" 39 | decreaseEnforcement: "Strict" 40 | maxLength: 41 | enabled: true 42 | additionEnforcement: "Strict" 43 | decreaseEnforcement: "Strict" 44 | minimum: 45 | enabled: true 46 | additionEnforcement: "Strict" 47 | increaseEnforcement: "Strict" 48 | minItems: 49 | enabled: true 50 | additionEnforcement: "Strict" 51 | increaseEnforcement: "Strict" 52 | minProperties: 53 | enabled: true 54 | additionEnforcement: "Strict" 55 | increaseEnforcement: "Strict" 56 | minLength: 57 | enabled: true 58 | additionEnforcement: "Strict" 59 | increaseEnforcement: "Strict" 60 | servedVersion: 61 | enabled: true 62 | unhandledFailureMode: "Closed" 63 | enum: 64 | enabled: true 65 | removalEnforcement: "Strict" 66 | additionEnforcement: "Strict" 67 | default: 68 | enabled: true 69 | changeEnforcement: "Strict" 70 | removalEnforcement: "Strict" 71 | additionEnforcement: "Strict" 72 | required: 73 | enabled: true 74 | newEnforcement: "Strict" 75 | type: 76 | enabled: true 77 | changeEnforcement: "Strict" 78 | maximum: 79 | enabled: true 80 | additionEnforcement: "Strict" 81 | decreaseEnforcement: "Strict" 82 | maxItems: 83 | enabled: true 84 | additionEnforcement: "Strict" 85 | decreaseEnforcement: "Strict" 86 | maxProperties: 87 | enabled: true 88 | additionEnforcement: "Strict" 89 | decreaseEnforcement: "Strict" 90 | maxLength: 91 | enabled: true 92 | additionEnforcement: "Strict" 93 | decreaseEnforcement: "Strict" 94 | minimum: 95 | enabled: true 96 | additionEnforcement: "Strict" 97 | increaseEnforcement: "Strict" 98 | minItems: 99 | enabled: true 100 | additionEnforcement: "Strict" 101 | increaseEnforcement: "Strict" 102 | minProperties: 103 | enabled: true 104 | additionEnforcement: "Strict" 105 | increaseEnforcement: "Strict" 106 | minLength: 107 | enabled: true 108 | additionEnforcement: "Strict" 109 | increaseEnforcement: "Strict" 110 | -------------------------------------------------------------------------------- /internal/storage/localdir.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/fs" 7 | "net/http" 8 | "net/url" 9 | "os" 10 | "path/filepath" 11 | 12 | "github.com/klauspost/compress/gzhttp" 13 | 14 | "github.com/operator-framework/operator-registry/alpha/declcfg" 15 | ) 16 | 17 | // LocalDirV1 is a storage Instance. When Storing a new FBC contained in 18 | // fs.FS, the content is first written to a temporary file, after which 19 | // it is copied to its final destination in RootDir/catalogName/. This is 20 | // done so that clients accessing the content stored in RootDir/catalogName have 21 | // atomic view of the content for a catalog. 22 | type LocalDirV1 struct { 23 | RootDir string 24 | RootURL *url.URL 25 | } 26 | 27 | const ( 28 | v1ApiPath = "api/v1" 29 | v1ApiData = "all" 30 | ) 31 | 32 | func (s LocalDirV1) Store(ctx context.Context, catalog string, fsys fs.FS) error { 33 | fbcDir := filepath.Join(s.RootDir, catalog, v1ApiPath) 34 | if err := os.MkdirAll(fbcDir, 0700); err != nil { 35 | return err 36 | } 37 | tempFile, err := os.CreateTemp(s.RootDir, fmt.Sprint(catalog)) 38 | if err != nil { 39 | return err 40 | } 41 | defer os.Remove(tempFile.Name()) 42 | if err := declcfg.WalkMetasFS(ctx, fsys, func(path string, meta *declcfg.Meta, err error) error { 43 | if err != nil { 44 | return err 45 | } 46 | _, err = tempFile.Write(meta.Blob) 47 | return err 48 | }); err != nil { 49 | return fmt.Errorf("error walking FBC root: %w", err) 50 | } 51 | fbcFile := filepath.Join(fbcDir, v1ApiData) 52 | return os.Rename(tempFile.Name(), fbcFile) 53 | } 54 | 55 | func (s LocalDirV1) Delete(catalog string) error { 56 | return os.RemoveAll(filepath.Join(s.RootDir, catalog)) 57 | } 58 | 59 | func (s LocalDirV1) BaseURL(catalog string) string { 60 | return s.RootURL.JoinPath(catalog).String() 61 | } 62 | 63 | func (s LocalDirV1) StorageServerHandler() http.Handler { 64 | mux := http.NewServeMux() 65 | fsHandler := http.FileServer(http.FS(&filesOnlyFilesystem{os.DirFS(s.RootDir)})) 66 | spHandler := http.StripPrefix(s.RootURL.Path, fsHandler) 67 | gzHandler := gzhttp.GzipHandler(spHandler) 68 | 69 | typeHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 70 | w.Header().Add("Content-Type", "application/jsonl") 71 | gzHandler.ServeHTTP(w, r) 72 | }) 73 | mux.Handle(s.RootURL.Path, typeHandler) 74 | return mux 75 | } 76 | 77 | func (s LocalDirV1) ContentExists(catalog string) bool { 78 | file, err := os.Stat(filepath.Join(s.RootDir, catalog, v1ApiPath, v1ApiData)) 79 | if err != nil { 80 | return false 81 | } 82 | if !file.Mode().IsRegular() { 83 | // path is not valid content 84 | return false 85 | } 86 | return true 87 | } 88 | 89 | // filesOnlyFilesystem is a file system that can open only regular 90 | // files from the underlying filesystem. All other file types result 91 | // in os.ErrNotExists 92 | type filesOnlyFilesystem struct { 93 | FS fs.FS 94 | } 95 | 96 | // Open opens a named file from the underlying filesystem. If the file 97 | // is not a regular file, it return os.ErrNotExists. Callers are resposible 98 | // for closing the file returned. 99 | func (f *filesOnlyFilesystem) Open(name string) (fs.File, error) { 100 | file, err := f.FS.Open(name) 101 | if err != nil { 102 | return nil, err 103 | } 104 | stat, err := file.Stat() 105 | if err != nil { 106 | _ = file.Close() 107 | return nil, err 108 | } 109 | if !stat.Mode().IsRegular() { 110 | _ = file.Close() 111 | return nil, os.ErrNotExist 112 | } 113 | return file, nil 114 | } 115 | -------------------------------------------------------------------------------- /test/e2e/unpack_test.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "context" 5 | "os" 6 | 7 | . "github.com/onsi/ginkgo/v2" 8 | . "github.com/onsi/gomega" 9 | 10 | "github.com/google/go-cmp/cmp" 11 | "k8s.io/apimachinery/pkg/api/errors" 12 | "k8s.io/apimachinery/pkg/api/meta" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | "k8s.io/apimachinery/pkg/types" 15 | 16 | catalogdv1 "github.com/operator-framework/catalogd/api/v1" 17 | ) 18 | 19 | const ( 20 | catalogRefEnvVar = "TEST_CATALOG_IMAGE" 21 | catalogName = "test-catalog" 22 | pkg = "prometheus" 23 | version = "0.47.0" 24 | channel = "beta" 25 | bundle = "prometheus-operator.0.47.0" 26 | bundleImage = "localhost/testdata/bundles/registry-v1/prometheus-operator:v0.47.0" 27 | ) 28 | 29 | // catalogImageRef returns the image reference for the test catalog image, defaulting to the value of the environment 30 | // variable TEST_CATALOG_IMAGE if set, falling back to docker-registry.catalogd-e2e.svc:5000/test-catalog:e2e otherwise. 31 | func catalogImageRef() string { 32 | if s := os.Getenv(catalogRefEnvVar); s != "" { 33 | return s 34 | } 35 | 36 | return "docker-registry.catalogd-e2e.svc:5000/test-catalog:e2e" 37 | } 38 | 39 | var _ = Describe("ClusterCatalog Unpacking", func() { 40 | var ( 41 | ctx context.Context 42 | catalog *catalogdv1.ClusterCatalog 43 | ) 44 | When("A ClusterCatalog is created", func() { 45 | BeforeEach(func() { 46 | ctx = context.Background() 47 | var err error 48 | 49 | catalog = &catalogdv1.ClusterCatalog{ 50 | ObjectMeta: metav1.ObjectMeta{ 51 | Name: catalogName, 52 | }, 53 | Spec: catalogdv1.ClusterCatalogSpec{ 54 | Source: catalogdv1.CatalogSource{ 55 | Type: catalogdv1.SourceTypeImage, 56 | Image: &catalogdv1.ImageSource{ 57 | Ref: catalogImageRef(), 58 | }, 59 | }, 60 | }, 61 | } 62 | 63 | err = c.Create(ctx, catalog) 64 | Expect(err).ToNot(HaveOccurred()) 65 | }) 66 | 67 | It("Successfully unpacks catalog contents", func() { 68 | By("Ensuring ClusterCatalog has Status.Condition of Progressing with a status == False and reason == Succeeded") 69 | Eventually(func(g Gomega) { 70 | err := c.Get(ctx, types.NamespacedName{Name: catalog.Name}, catalog) 71 | g.Expect(err).ToNot(HaveOccurred()) 72 | cond := meta.FindStatusCondition(catalog.Status.Conditions, catalogdv1.TypeProgressing) 73 | g.Expect(cond).ToNot(BeNil()) 74 | g.Expect(cond.Status).To(Equal(metav1.ConditionTrue)) 75 | g.Expect(cond.Reason).To(Equal(catalogdv1.ReasonSucceeded)) 76 | }).Should(Succeed()) 77 | 78 | By("Checking that it has an appropriate name label") 79 | Expect(catalog.ObjectMeta.Labels).To(Not(BeNil())) 80 | Expect(catalog.ObjectMeta.Labels).To(Not(BeEmpty())) 81 | Expect(catalog.ObjectMeta.Labels).To(HaveKeyWithValue("olm.operatorframework.io/metadata.name", catalogName)) 82 | 83 | By("Making sure the catalog content is available via the http server") 84 | actualFBC, err := ReadTestCatalogServerContents(ctx, catalog, c, kubeClient) 85 | Expect(err).To(Not(HaveOccurred())) 86 | 87 | expectedFBC, err := os.ReadFile("../../testdata/catalogs/test-catalog/expected_all.json") 88 | Expect(err).To(Not(HaveOccurred())) 89 | Expect(cmp.Diff(expectedFBC, actualFBC)).To(BeEmpty()) 90 | 91 | By("Ensuring ClusterCatalog has Status.Condition of Type = Serving with a status == True") 92 | Eventually(func(g Gomega) { 93 | err := c.Get(ctx, types.NamespacedName{Name: catalog.Name}, catalog) 94 | g.Expect(err).ToNot(HaveOccurred()) 95 | cond := meta.FindStatusCondition(catalog.Status.Conditions, catalogdv1.TypeServing) 96 | g.Expect(cond).ToNot(BeNil()) 97 | g.Expect(cond.Status).To(Equal(metav1.ConditionTrue)) 98 | g.Expect(cond.Reason).To(Equal(catalogdv1.ReasonAvailable)) 99 | }).Should(Succeed()) 100 | }) 101 | AfterEach(func() { 102 | Expect(c.Delete(ctx, catalog)).To(Succeed()) 103 | Eventually(func(g Gomega) { 104 | err = c.Get(ctx, types.NamespacedName{Name: catalog.Name}, &catalogdv1.ClusterCatalog{}) 105 | g.Expect(errors.IsNotFound(err)).To(BeTrue()) 106 | }).Should(Succeed()) 107 | }) 108 | }) 109 | }) 110 | -------------------------------------------------------------------------------- /.bingo/ginkgo.sum: -------------------------------------------------------------------------------- 1 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 2 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 3 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 7 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 8 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 9 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 10 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= 11 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 12 | github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= 13 | github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= 14 | github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA= 15 | github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 16 | github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= 17 | github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 18 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 19 | github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= 20 | github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= 21 | github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= 22 | github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= 23 | github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= 24 | github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= 25 | github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= 26 | github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= 27 | github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= 28 | github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= 29 | github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= 30 | github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= 31 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 32 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 33 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 34 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 35 | golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= 36 | golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= 37 | golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= 38 | golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= 39 | golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= 40 | golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 41 | golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= 42 | golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= 43 | golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= 44 | golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= 45 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 46 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 47 | -------------------------------------------------------------------------------- /internal/third_party/server/server.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // this is copied from https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/77b08a845e451b695cfa25b79ebe277d85064345/pkg/manager/server.go 18 | // we will remove this once we update to a version of controller-runitme that has this included 19 | // https://github.com/kubernetes-sigs/controller-runtime/pull/2473 20 | 21 | package server 22 | 23 | import ( 24 | "context" 25 | "errors" 26 | "net" 27 | "net/http" 28 | "time" 29 | 30 | "github.com/go-logr/logr" 31 | 32 | crlog "sigs.k8s.io/controller-runtime/pkg/log" 33 | "sigs.k8s.io/controller-runtime/pkg/manager" 34 | ) 35 | 36 | var ( 37 | _ manager.Runnable = (*Server)(nil) 38 | _ manager.LeaderElectionRunnable = (*Server)(nil) 39 | ) 40 | 41 | // Server is a general purpose HTTP(S) server Runnable for a manager. 42 | // It is used to serve some internal handlers for health probes and profiling, 43 | // but it can also be used to run custom servers. 44 | type Server struct { 45 | // Kind is an optional string that describes the purpose of the server. It is used in logs to distinguish 46 | // among multiple servers. 47 | Kind string 48 | 49 | // Log is the logger used by the server. If not set, a logger will be derived from the context passed to Start. 50 | Log logr.Logger 51 | 52 | // Server is the HTTP server to run. It is required. 53 | Server *http.Server 54 | 55 | // Listener is an optional listener to use. If not set, the server start a listener using the server.Addr. 56 | // Using a listener is useful when the port reservation needs to happen in advance of this runnable starting. 57 | Listener net.Listener 58 | 59 | // OnlyServeWhenLeader is an optional bool that indicates that the server should only be started when the manager is the leader. 60 | OnlyServeWhenLeader bool 61 | 62 | // ShutdownTimeout is an optional duration that indicates how long to wait for the server to shutdown gracefully. If not set, 63 | // the server will wait indefinitely for all connections to close. 64 | ShutdownTimeout *time.Duration 65 | } 66 | 67 | // Start starts the server. It will block until the server is stopped or an error occurs. 68 | func (s *Server) Start(ctx context.Context) error { 69 | log := s.Log 70 | if log.GetSink() == nil { 71 | log = crlog.FromContext(ctx) 72 | } 73 | if s.Kind != "" { 74 | log = log.WithValues("kind", s.Kind) 75 | } 76 | log = log.WithValues("addr", s.addr()) 77 | 78 | serverShutdown := make(chan struct{}) 79 | go func() { 80 | <-ctx.Done() 81 | log.Info("shutting down server") 82 | 83 | shutdownCtx := context.Background() 84 | if s.ShutdownTimeout != nil { 85 | var shutdownCancel context.CancelFunc 86 | shutdownCtx, shutdownCancel = context.WithTimeout(context.Background(), *s.ShutdownTimeout) 87 | defer shutdownCancel() 88 | } 89 | 90 | if err := s.Server.Shutdown(shutdownCtx); err != nil { 91 | log.Error(err, "error shutting down server") 92 | } 93 | close(serverShutdown) 94 | }() 95 | 96 | log.Info("starting server") 97 | if err := s.serve(); err != nil && !errors.Is(err, http.ErrServerClosed) { 98 | return err 99 | } 100 | 101 | <-serverShutdown 102 | return nil 103 | } 104 | 105 | // NeedLeaderElection returns true if the server should only be started when the manager is the leader. 106 | func (s *Server) NeedLeaderElection() bool { 107 | return s.OnlyServeWhenLeader 108 | } 109 | 110 | func (s *Server) addr() string { 111 | if s.Listener != nil { 112 | return s.Listener.Addr().String() 113 | } 114 | return s.Server.Addr 115 | } 116 | 117 | func (s *Server) serve() error { 118 | if s.Listener != nil { 119 | return s.Server.Serve(s.Listener) 120 | } 121 | 122 | return s.Server.ListenAndServe() 123 | } 124 | -------------------------------------------------------------------------------- /internal/controllers/core/pull_secret_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package core 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "os" 23 | 24 | "github.com/go-logr/logr" 25 | corev1 "k8s.io/api/core/v1" 26 | apierrors "k8s.io/apimachinery/pkg/api/errors" 27 | "k8s.io/apimachinery/pkg/types" 28 | ctrl "sigs.k8s.io/controller-runtime" 29 | "sigs.k8s.io/controller-runtime/pkg/client" 30 | "sigs.k8s.io/controller-runtime/pkg/log" 31 | "sigs.k8s.io/controller-runtime/pkg/predicate" 32 | ) 33 | 34 | // PullSecretReconciler reconciles a specific Secret object 35 | // that contains global pull secrets for pulling Catalog images 36 | type PullSecretReconciler struct { 37 | client.Client 38 | SecretKey types.NamespacedName 39 | AuthFilePath string 40 | } 41 | 42 | func (r *PullSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 43 | logger := log.FromContext(ctx) 44 | if req.Name != r.SecretKey.Name || req.Namespace != r.SecretKey.Namespace { 45 | logger.Error(fmt.Errorf("received unexpected request for Secret %v/%v", req.Namespace, req.Name), "reconciliation error") 46 | return ctrl.Result{}, nil 47 | } 48 | 49 | secret := &corev1.Secret{} 50 | err := r.Get(ctx, req.NamespacedName, secret) 51 | if err != nil { 52 | if apierrors.IsNotFound(err) { 53 | logger.Info("secret not found") 54 | return r.deleteSecretFile(logger) 55 | } 56 | logger.Error(err, "failed to get Secret") 57 | return ctrl.Result{}, err 58 | } 59 | 60 | return r.writeSecretToFile(logger, secret) 61 | } 62 | 63 | // SetupWithManager sets up the controller with the Manager. 64 | func (r *PullSecretReconciler) SetupWithManager(mgr ctrl.Manager) error { 65 | _, err := ctrl.NewControllerManagedBy(mgr). 66 | For(&corev1.Secret{}). 67 | WithEventFilter(newSecretPredicate(r.SecretKey)). 68 | Build(r) 69 | 70 | return err 71 | } 72 | 73 | func newSecretPredicate(key types.NamespacedName) predicate.Predicate { 74 | return predicate.NewPredicateFuncs(func(obj client.Object) bool { 75 | return obj.GetName() == key.Name && obj.GetNamespace() == key.Namespace 76 | }) 77 | } 78 | 79 | // writeSecretToFile writes the secret data to the specified file 80 | func (r *PullSecretReconciler) writeSecretToFile(logger logr.Logger, secret *corev1.Secret) (ctrl.Result, error) { 81 | // image registry secrets are always stored with the key .dockerconfigjson 82 | // ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#registry-secret-existing-credentials 83 | dockerConfigJSON, ok := secret.Data[".dockerconfigjson"] 84 | if !ok { 85 | logger.Error(fmt.Errorf("expected secret.Data key not found"), "expected secret Data to contain key .dockerconfigjson") 86 | return ctrl.Result{}, nil 87 | } 88 | // expected format for auth.json 89 | // https://github.com/containers/image/blob/main/docs/containers-auth.json.5.md 90 | err := os.WriteFile(r.AuthFilePath, dockerConfigJSON, 0600) 91 | if err != nil { 92 | return ctrl.Result{}, fmt.Errorf("failed to write secret data to file: %w", err) 93 | } 94 | logger.Info("saved global pull secret data locally") 95 | return ctrl.Result{}, nil 96 | } 97 | 98 | // deleteSecretFile deletes the auth file if the secret is deleted 99 | func (r *PullSecretReconciler) deleteSecretFile(logger logr.Logger) (ctrl.Result, error) { 100 | logger.Info("deleting local auth file", "file", r.AuthFilePath) 101 | if err := os.Remove(r.AuthFilePath); err != nil { 102 | if os.IsNotExist(err) { 103 | logger.Info("auth file does not exist, nothing to delete") 104 | return ctrl.Result{}, nil 105 | } 106 | return ctrl.Result{}, fmt.Errorf("failed to delete secret file: %w", err) 107 | } 108 | logger.Info("auth file deleted successfully") 109 | return ctrl.Result{}, nil 110 | } 111 | -------------------------------------------------------------------------------- /.bingo/Variables.mk: -------------------------------------------------------------------------------- 1 | # Auto generated binary variables helper managed by https://github.com/bwplotka/bingo v0.9. DO NOT EDIT. 2 | # All tools are designed to be build inside $GOBIN. 3 | BINGO_DIR := $(dir $(lastword $(MAKEFILE_LIST))) 4 | GOPATH ?= $(shell go env GOPATH) 5 | GOBIN ?= $(firstword $(subst :, ,${GOPATH}))/bin 6 | GO ?= $(shell which go) 7 | 8 | # Below generated variables ensure that every time a tool under each variable is invoked, the correct version 9 | # will be used; reinstalling only if needed. 10 | # For example for bingo variable: 11 | # 12 | # In your main Makefile (for non array binaries): 13 | # 14 | #include .bingo/Variables.mk # Assuming -dir was set to .bingo . 15 | # 16 | #command: $(BINGO) 17 | # @echo "Running bingo" 18 | # @$(BINGO) 19 | # 20 | BINGO := $(GOBIN)/bingo-v0.9.0 21 | $(BINGO): $(BINGO_DIR)/bingo.mod 22 | @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. 23 | @echo "(re)installing $(GOBIN)/bingo-v0.9.0" 24 | @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=bingo.mod -o=$(GOBIN)/bingo-v0.9.0 "github.com/bwplotka/bingo" 25 | 26 | CONTROLLER_GEN := $(GOBIN)/controller-gen-v0.16.1 27 | $(CONTROLLER_GEN): $(BINGO_DIR)/controller-gen.mod 28 | @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. 29 | @echo "(re)installing $(GOBIN)/controller-gen-v0.16.1" 30 | @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=controller-gen.mod -o=$(GOBIN)/controller-gen-v0.16.1 "sigs.k8s.io/controller-tools/cmd/controller-gen" 31 | 32 | CRD_DIFF := $(GOBIN)/crd-diff-v0.1.0 33 | $(CRD_DIFF): $(BINGO_DIR)/crd-diff.mod 34 | @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. 35 | @echo "(re)installing $(GOBIN)/crd-diff-v0.1.0" 36 | @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=crd-diff.mod -o=$(GOBIN)/crd-diff-v0.1.0 "github.com/everettraven/crd-diff" 37 | 38 | GINKGO := $(GOBIN)/ginkgo-v2.22.0 39 | $(GINKGO): $(BINGO_DIR)/ginkgo.mod 40 | @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. 41 | @echo "(re)installing $(GOBIN)/ginkgo-v2.22.0" 42 | @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=ginkgo.mod -o=$(GOBIN)/ginkgo-v2.22.0 "github.com/onsi/ginkgo/v2/ginkgo" 43 | 44 | GOLANGCI_LINT := $(GOBIN)/golangci-lint-v1.60.3 45 | $(GOLANGCI_LINT): $(BINGO_DIR)/golangci-lint.mod 46 | @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. 47 | @echo "(re)installing $(GOBIN)/golangci-lint-v1.60.3" 48 | @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=golangci-lint.mod -o=$(GOBIN)/golangci-lint-v1.60.3 "github.com/golangci/golangci-lint/cmd/golangci-lint" 49 | 50 | GORELEASER := $(GOBIN)/goreleaser-v1.26.2 51 | $(GORELEASER): $(BINGO_DIR)/goreleaser.mod 52 | @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. 53 | @echo "(re)installing $(GOBIN)/goreleaser-v1.26.2" 54 | @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=goreleaser.mod -o=$(GOBIN)/goreleaser-v1.26.2 "github.com/goreleaser/goreleaser" 55 | 56 | KIND := $(GOBIN)/kind-v0.24.0 57 | $(KIND): $(BINGO_DIR)/kind.mod 58 | @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. 59 | @echo "(re)installing $(GOBIN)/kind-v0.24.0" 60 | @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=kind.mod -o=$(GOBIN)/kind-v0.24.0 "sigs.k8s.io/kind" 61 | 62 | KUSTOMIZE := $(GOBIN)/kustomize-v5.4.3 63 | $(KUSTOMIZE): $(BINGO_DIR)/kustomize.mod 64 | @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. 65 | @echo "(re)installing $(GOBIN)/kustomize-v5.4.3" 66 | @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=kustomize.mod -o=$(GOBIN)/kustomize-v5.4.3 "sigs.k8s.io/kustomize/kustomize/v5" 67 | 68 | SETUP_ENVTEST := $(GOBIN)/setup-envtest-v0.0.0-20240820183333-e6c3d139d2b6 69 | $(SETUP_ENVTEST): $(BINGO_DIR)/setup-envtest.mod 70 | @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. 71 | @echo "(re)installing $(GOBIN)/setup-envtest-v0.0.0-20240820183333-e6c3d139d2b6" 72 | @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=setup-envtest.mod -o=$(GOBIN)/setup-envtest-v0.0.0-20240820183333-e6c3d139d2b6 "sigs.k8s.io/controller-runtime/tools/setup-envtest" 73 | 74 | -------------------------------------------------------------------------------- /test/e2e/metrics_endpoint_test.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os/exec" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | // nolint:gosec 13 | // TestCatalogdMetricsExportedEndpoint verifies that the metrics endpoint for the catalogd 14 | // is exported correctly and accessible by authorized users through RBAC and a ServiceAccount token. 15 | // The test performs the following steps: 16 | // 1. Creates a ClusterRoleBinding to grant necessary permissions for accessing metrics. 17 | // 2. Generates a ServiceAccount token for authentication. 18 | // 3. Deploys a curl pod to interact with the metrics endpoint. 19 | // 4. Waits for the curl pod to become ready. 20 | // 5. Executes a curl command from the pod to validate the metrics endpoint. 21 | // 6. Cleans up all resources created during the test, such as the ClusterRoleBinding and curl pod. 22 | func TestCatalogdMetricsExportedEndpoint(t *testing.T) { 23 | var ( 24 | token string 25 | curlPod = "curl-metrics" 26 | client = "" 27 | clients = []string{"kubectl", "oc"} 28 | ) 29 | 30 | t.Log("Looking for k8s client") 31 | for _, c := range clients { 32 | // Would prefer to use `command -v`, but even that may not be installed! 33 | err := exec.Command(c, "version", "--client").Run() 34 | if err == nil { 35 | client = c 36 | break 37 | } 38 | } 39 | if client == "" { 40 | t.Fatal("k8s client not found") 41 | } 42 | t.Logf("Using %q as k8s client", client) 43 | 44 | t.Log("Determining catalogd namespace") 45 | cmd := exec.Command(client, "get", "pods", "--all-namespaces", "--selector=control-plane=catalogd-controller-manager", "--output=jsonpath={.items[0].metadata.namespace}") 46 | output, err := cmd.CombinedOutput() 47 | require.NoError(t, err, "Error creating determining catalogd namespace: %s", string(output)) 48 | namespace := string(output) 49 | if namespace == "" { 50 | t.Fatal("No catalogd namespace found") 51 | } 52 | t.Logf("Using %q as catalogd namespace", namespace) 53 | 54 | t.Log("Creating ClusterRoleBinding for metrics access") 55 | cmd = exec.Command(client, "create", "clusterrolebinding", "catalogd-metrics-binding", 56 | "--clusterrole=catalogd-metrics-reader", 57 | "--serviceaccount="+namespace+":catalogd-controller-manager") 58 | output, err = cmd.CombinedOutput() 59 | require.NoError(t, err, "Error creating ClusterRoleBinding: %s", string(output)) 60 | 61 | defer func() { 62 | t.Log("Cleaning up ClusterRoleBinding") 63 | _ = exec.Command(client, "delete", "clusterrolebinding", "catalogd-metrics-binding", "--ignore-not-found=true").Run() 64 | }() 65 | 66 | t.Log("Creating service account token for authentication") 67 | tokenCmd := exec.Command(client, "create", "token", "catalogd-controller-manager", "-n", namespace) 68 | tokenOutput, tokenCombinedOutput, err := stdoutAndCombined(tokenCmd) 69 | require.NoError(t, err, "Error creating token: %s", string(tokenCombinedOutput)) 70 | token = string(bytes.TrimSpace(tokenOutput)) 71 | 72 | t.Log("Creating a pod to run curl commands") 73 | cmd = exec.Command(client, "run", curlPod, 74 | "--image=curlimages/curl:7.87.0", "-n", namespace, 75 | "--restart=Never", 76 | "--overrides", `{ 77 | "spec": { 78 | "containers": [{ 79 | "name": "curl", 80 | "image": "curlimages/curl:7.87.0", 81 | "command": ["sh", "-c", "sleep 3600"], 82 | "securityContext": { 83 | "allowPrivilegeEscalation": false, 84 | "capabilities": { 85 | "drop": ["ALL"] 86 | }, 87 | "runAsNonRoot": true, 88 | "runAsUser": 1000, 89 | "seccompProfile": { 90 | "type": "RuntimeDefault" 91 | } 92 | } 93 | }], 94 | "serviceAccountName": "catalogd-controller-manager" 95 | } 96 | }`) 97 | output, err = cmd.CombinedOutput() 98 | require.NoError(t, err, "Error creating curl pod: %s", string(output)) 99 | 100 | defer func() { 101 | t.Log("Cleaning up curl pod") 102 | _ = exec.Command(client, "delete", "pod", curlPod, "-n", namespace, "--ignore-not-found=true").Run() 103 | }() 104 | 105 | t.Log("Waiting for the curl pod to become ready") 106 | waitCmd := exec.Command(client, "wait", "--for=condition=Ready", "pod", curlPod, "-n", namespace, "--timeout=60s") 107 | waitOutput, waitErr := waitCmd.CombinedOutput() 108 | require.NoError(t, waitErr, "Error waiting for curl pod to be ready: %s", string(waitOutput)) 109 | 110 | t.Log("Validating the metrics endpoint") 111 | metricsURL := "https://catalogd-service.olmv1-system.svc.cluster.local:7443/metrics" 112 | curlCmd := exec.Command(client, "exec", curlPod, "-n", namespace, "--", 113 | "curl", "-v", "-k", "-H", "Authorization: Bearer "+token, metricsURL) 114 | output, err = curlCmd.CombinedOutput() 115 | require.NoError(t, err, "Error calling metrics endpoint: %s", string(output)) 116 | require.Contains(t, string(output), "200 OK", "Metrics endpoint did not return 200 OK") 117 | } 118 | 119 | func stdoutAndCombined(cmd *exec.Cmd) ([]byte, []byte, error) { 120 | var outOnly bytes.Buffer 121 | var outAndErr bytes.Buffer 122 | allWriter := io.MultiWriter(&outOnly, &outAndErr) 123 | cmd.Stderr = &outAndErr 124 | cmd.Stdout = allWriter 125 | err := cmd.Run() 126 | return outOnly.Bytes(), outAndErr.Bytes(), err 127 | } 128 | -------------------------------------------------------------------------------- /test/upgrade/unpack_test.go: -------------------------------------------------------------------------------- 1 | package upgradee2e 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "fmt" 7 | "os" 8 | "strings" 9 | "time" 10 | 11 | . "github.com/onsi/ginkgo/v2" 12 | . "github.com/onsi/gomega" 13 | 14 | "github.com/google/go-cmp/cmp" 15 | appsv1 "k8s.io/api/apps/v1" 16 | corev1 "k8s.io/api/core/v1" 17 | "k8s.io/apimachinery/pkg/api/meta" 18 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 19 | "k8s.io/apimachinery/pkg/labels" 20 | "k8s.io/apimachinery/pkg/types" 21 | "sigs.k8s.io/controller-runtime/pkg/client" 22 | 23 | catalogdv1 "github.com/operator-framework/catalogd/api/v1" 24 | "github.com/operator-framework/catalogd/test/e2e" 25 | ) 26 | 27 | var _ = Describe("ClusterCatalog Unpacking", func() { 28 | When("A ClusterCatalog is created", func() { 29 | It("Successfully unpacks catalog contents", func() { 30 | ctx := context.Background() 31 | 32 | var managerDeployment appsv1.Deployment 33 | managerLabelSelector := labels.Set{"control-plane": "catalogd-controller-manager"} 34 | By("Checking that the controller-manager deployment is updated") 35 | Eventually(func(g Gomega) { 36 | var managerDeployments appsv1.DeploymentList 37 | err := c.List(ctx, &managerDeployments, client.MatchingLabels(managerLabelSelector), client.InNamespace("olmv1-system")) 38 | g.Expect(err).ToNot(HaveOccurred()) 39 | g.Expect(managerDeployments.Items).To(HaveLen(1)) 40 | managerDeployment = managerDeployments.Items[0] 41 | g.Expect(managerDeployment.Status.UpdatedReplicas).To(Equal(*managerDeployment.Spec.Replicas)) 42 | g.Expect(managerDeployment.Status.Replicas).To(Equal(*managerDeployment.Spec.Replicas)) 43 | g.Expect(managerDeployment.Status.AvailableReplicas).To(Equal(*managerDeployment.Spec.Replicas)) 44 | g.Expect(managerDeployment.Status.ReadyReplicas).To(Equal(*managerDeployment.Spec.Replicas)) 45 | }).Should(Succeed()) 46 | 47 | var managerPod corev1.Pod 48 | By("Waiting for only one controller-manager pod to remain") 49 | Eventually(func(g Gomega) { 50 | var managerPods corev1.PodList 51 | err := c.List(ctx, &managerPods, client.MatchingLabels(managerLabelSelector)) 52 | g.Expect(err).ToNot(HaveOccurred()) 53 | g.Expect(managerPods.Items).To(HaveLen(1)) 54 | managerPod = managerPods.Items[0] 55 | }).Should(Succeed()) 56 | 57 | By("Reading logs to make sure that ClusterCatalog was reconciled by catalogdv1") 58 | logCtx, cancel := context.WithTimeout(ctx, time.Minute) 59 | defer cancel() 60 | substrings := []string{ 61 | "reconcile ending", 62 | fmt.Sprintf(`ClusterCatalog=%q`, testClusterCatalogName), 63 | } 64 | found, err := watchPodLogsForSubstring(logCtx, &managerPod, "manager", substrings...) 65 | Expect(err).ToNot(HaveOccurred()) 66 | Expect(found).To(BeTrue()) 67 | 68 | catalog := &catalogdv1.ClusterCatalog{} 69 | By("Ensuring ClusterCatalog has Status.Condition of Progressing with a status == True, reason == Succeeded") 70 | Eventually(func(g Gomega) { 71 | err := c.Get(ctx, types.NamespacedName{Name: testClusterCatalogName}, catalog) 72 | g.Expect(err).ToNot(HaveOccurred()) 73 | cond := meta.FindStatusCondition(catalog.Status.Conditions, catalogdv1.TypeProgressing) 74 | g.Expect(cond).ToNot(BeNil()) 75 | g.Expect(cond.Status).To(Equal(metav1.ConditionTrue)) 76 | g.Expect(cond.Reason).To(Equal(catalogdv1.ReasonSucceeded)) 77 | }).Should(Succeed()) 78 | 79 | expectedFBC, err := os.ReadFile("../../testdata/catalogs/test-catalog/expected_all.json") 80 | Expect(err).To(Not(HaveOccurred())) 81 | 82 | By("Making sure the catalog content is available via the http server") 83 | Eventually(func(g Gomega) { 84 | actualFBC, err := e2e.ReadTestCatalogServerContents(ctx, catalog, c, kubeClient) 85 | g.Expect(err).To(Not(HaveOccurred())) 86 | g.Expect(cmp.Diff(expectedFBC, actualFBC)).To(BeEmpty()) 87 | }).Should(Succeed()) 88 | 89 | By("Ensuring ClusterCatalog has Status.Condition of Serving with a status == True, reason == Available") 90 | Eventually(func(g Gomega) { 91 | err := c.Get(ctx, types.NamespacedName{Name: testClusterCatalogName}, catalog) 92 | g.Expect(err).ToNot(HaveOccurred()) 93 | cond := meta.FindStatusCondition(catalog.Status.Conditions, catalogdv1.TypeServing) 94 | g.Expect(cond).ToNot(BeNil()) 95 | g.Expect(cond.Status).To(Equal(metav1.ConditionTrue)) 96 | g.Expect(cond.Reason).To(Equal(catalogdv1.ReasonAvailable)) 97 | }).Should(Succeed()) 98 | }) 99 | }) 100 | }) 101 | 102 | func watchPodLogsForSubstring(ctx context.Context, pod *corev1.Pod, container string, substrings ...string) (bool, error) { 103 | podLogOpts := corev1.PodLogOptions{ 104 | Follow: true, 105 | Container: container, 106 | } 107 | 108 | req := kubeClient.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &podLogOpts) 109 | podLogs, err := req.Stream(ctx) 110 | if err != nil { 111 | return false, err 112 | } 113 | defer podLogs.Close() 114 | 115 | scanner := bufio.NewScanner(podLogs) 116 | for scanner.Scan() { 117 | line := scanner.Text() 118 | 119 | foundCount := 0 120 | for _, substring := range substrings { 121 | if strings.Contains(line, substring) { 122 | foundCount++ 123 | } 124 | } 125 | if foundCount == len(substrings) { 126 | return true, nil 127 | } 128 | } 129 | 130 | return false, scanner.Err() 131 | } 132 | -------------------------------------------------------------------------------- /.bingo/kind.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= 2 | github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 3 | github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= 4 | github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 5 | github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= 6 | github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= 7 | github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= 8 | github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= 9 | github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 10 | github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= 13 | github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= 14 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 15 | github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 h1:SJ+NtwL6QaZ21U+IrK7d0gGgpjGGvd2kz+FzTHVzdqI= 16 | github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2/go.mod h1:Tv1PlzqC9t8wNnpPdctvtSUOPUUg4SHeE6vR1Ir2hmg= 17 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 18 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 19 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 20 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 21 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 22 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 23 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 24 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 25 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 26 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 27 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 28 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 29 | github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= 30 | github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 31 | github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= 32 | github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 33 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 34 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 35 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 36 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 37 | github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= 38 | github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= 39 | github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= 40 | github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= 41 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 42 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 43 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= 44 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 45 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= 46 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 47 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 48 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 49 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 50 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 51 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 52 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 53 | sigs.k8s.io/kind v0.15.0 h1:Fskj234L4hjQlsScCgeYvCBIRt06cjLzc7+kbr1u8Tg= 54 | sigs.k8s.io/kind v0.15.0/go.mod h1:cKTqagdRyUQmihhBOd+7p43DpOPRn9rHsUC08K1Jbsk= 55 | sigs.k8s.io/kind v0.20.0 h1:f0sc3v9mQbGnjBUaqSFST1dwIuiikKVGgoTwpoP33a8= 56 | sigs.k8s.io/kind v0.20.0/go.mod h1:aBlbxg08cauDgZ612shr017/rZwqd7AS563FvpWKPVs= 57 | sigs.k8s.io/kind v0.23.0 h1:8fyDGWbWTeCcCTwA04v4Nfr45KKxbSPH1WO9K+jVrBg= 58 | sigs.k8s.io/kind v0.23.0/go.mod h1:ZQ1iZuJLh3T+O8fzhdi3VWcFTzsdXtNv2ppsHc8JQ7s= 59 | sigs.k8s.io/kind v0.24.0 h1:g4y4eu0qa+SCeKESLpESgMmVFBebL0BDa6f777OIWrg= 60 | sigs.k8s.io/kind v0.24.0/go.mod h1:t7ueEpzPYJvHA8aeLtI52rtFftNgUYUaCwvxjk7phfw= 61 | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= 62 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= 63 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 64 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 65 | -------------------------------------------------------------------------------- /.bingo/bingo.sum: -------------------------------------------------------------------------------- 1 | github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= 2 | github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= 3 | github.com/bwplotka/bingo v0.8.0 h1:Cx9eQb+ed9aU7sbrmZagomKx+wYor9y5z5HM91bvp1U= 4 | github.com/bwplotka/bingo v0.8.0/go.mod h1:eXPFwhZ92mmOUBk6F7aKcAJoq8HX88Ju3wLZKwtNKEw= 5 | github.com/bwplotka/bingo v0.9.0 h1:slnsdJYExR4iRalHR6/ZiYnr9vSazOuFGmc2LdX293g= 6 | github.com/bwplotka/bingo v0.9.0/go.mod h1:GxC/y/xbmOK5P29cn+B3HuOSw0s2gruddT3r+rDizDw= 7 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 8 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 9 | github.com/creack/pty v1.1.15/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 10 | github.com/efficientgo/core v1.0.0-rc.0 h1:jJoA0N+C4/knWYVZ6GrdHOtDyrg8Y/TR4vFpTaqTsqs= 11 | github.com/efficientgo/core v1.0.0-rc.0/go.mod h1:kQa0V74HNYMfuJH6jiPiwNdpWXl4xd/K4tzlrcvYDQI= 12 | github.com/frankban/quicktest v1.13.1/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= 13 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 14 | github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk= 15 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 16 | github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= 17 | github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 18 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 19 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 20 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 21 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 22 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 23 | github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= 24 | github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= 25 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 26 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 27 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 28 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 29 | github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= 30 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 31 | github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= 32 | github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= 33 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 34 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 35 | golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= 36 | golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= 37 | golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= 38 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 39 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 40 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 41 | golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= 42 | golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 43 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 44 | golang.org/x/sys v0.0.0-20210925032602-92d5a993a665/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 45 | golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f h1:rlezHXNlxYWvBCzNses9Dlc7nGFaNMJeqLolcmQSSZY= 46 | golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 47 | golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= 48 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 49 | golang.org/x/term v0.0.0-20210916214954-140adaaadfaf/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 50 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= 51 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 52 | golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= 53 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 54 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 55 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 56 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 57 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 58 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 59 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 60 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 61 | mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0= 62 | mvdan.cc/sh/v3 v3.4.3 h1:zbuKH7YH9cqU6PGajhFFXZY7dhPXcDr55iN/cUAqpuw= 63 | mvdan.cc/sh/v3 v3.4.3/go.mod h1:p/tqPPI4Epfk2rICAe2RoaNd8HBSJ8t9Y2DA9yQlbzY= 64 | mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= 65 | mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= 66 | -------------------------------------------------------------------------------- /api/v1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | 3 | /* 4 | Copyright 2022. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // Code generated by controller-gen. DO NOT EDIT. 20 | 21 | package v1 22 | 23 | import ( 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | ) 27 | 28 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 29 | func (in *CatalogSource) DeepCopyInto(out *CatalogSource) { 30 | *out = *in 31 | if in.Image != nil { 32 | in, out := &in.Image, &out.Image 33 | *out = new(ImageSource) 34 | (*in).DeepCopyInto(*out) 35 | } 36 | } 37 | 38 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogSource. 39 | func (in *CatalogSource) DeepCopy() *CatalogSource { 40 | if in == nil { 41 | return nil 42 | } 43 | out := new(CatalogSource) 44 | in.DeepCopyInto(out) 45 | return out 46 | } 47 | 48 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 49 | func (in *ClusterCatalog) DeepCopyInto(out *ClusterCatalog) { 50 | *out = *in 51 | out.TypeMeta = in.TypeMeta 52 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 53 | in.Spec.DeepCopyInto(&out.Spec) 54 | in.Status.DeepCopyInto(&out.Status) 55 | } 56 | 57 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterCatalog. 58 | func (in *ClusterCatalog) DeepCopy() *ClusterCatalog { 59 | if in == nil { 60 | return nil 61 | } 62 | out := new(ClusterCatalog) 63 | in.DeepCopyInto(out) 64 | return out 65 | } 66 | 67 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 68 | func (in *ClusterCatalog) DeepCopyObject() runtime.Object { 69 | if c := in.DeepCopy(); c != nil { 70 | return c 71 | } 72 | return nil 73 | } 74 | 75 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 76 | func (in *ClusterCatalogList) DeepCopyInto(out *ClusterCatalogList) { 77 | *out = *in 78 | out.TypeMeta = in.TypeMeta 79 | in.ListMeta.DeepCopyInto(&out.ListMeta) 80 | if in.Items != nil { 81 | in, out := &in.Items, &out.Items 82 | *out = make([]ClusterCatalog, len(*in)) 83 | for i := range *in { 84 | (*in)[i].DeepCopyInto(&(*out)[i]) 85 | } 86 | } 87 | } 88 | 89 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterCatalogList. 90 | func (in *ClusterCatalogList) DeepCopy() *ClusterCatalogList { 91 | if in == nil { 92 | return nil 93 | } 94 | out := new(ClusterCatalogList) 95 | in.DeepCopyInto(out) 96 | return out 97 | } 98 | 99 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 100 | func (in *ClusterCatalogList) DeepCopyObject() runtime.Object { 101 | if c := in.DeepCopy(); c != nil { 102 | return c 103 | } 104 | return nil 105 | } 106 | 107 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 108 | func (in *ClusterCatalogSpec) DeepCopyInto(out *ClusterCatalogSpec) { 109 | *out = *in 110 | in.Source.DeepCopyInto(&out.Source) 111 | } 112 | 113 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterCatalogSpec. 114 | func (in *ClusterCatalogSpec) DeepCopy() *ClusterCatalogSpec { 115 | if in == nil { 116 | return nil 117 | } 118 | out := new(ClusterCatalogSpec) 119 | in.DeepCopyInto(out) 120 | return out 121 | } 122 | 123 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 124 | func (in *ClusterCatalogStatus) DeepCopyInto(out *ClusterCatalogStatus) { 125 | *out = *in 126 | if in.Conditions != nil { 127 | in, out := &in.Conditions, &out.Conditions 128 | *out = make([]metav1.Condition, len(*in)) 129 | for i := range *in { 130 | (*in)[i].DeepCopyInto(&(*out)[i]) 131 | } 132 | } 133 | if in.ResolvedSource != nil { 134 | in, out := &in.ResolvedSource, &out.ResolvedSource 135 | *out = new(ResolvedCatalogSource) 136 | (*in).DeepCopyInto(*out) 137 | } 138 | if in.URLs != nil { 139 | in, out := &in.URLs, &out.URLs 140 | *out = new(ClusterCatalogURLs) 141 | **out = **in 142 | } 143 | if in.LastUnpacked != nil { 144 | in, out := &in.LastUnpacked, &out.LastUnpacked 145 | *out = (*in).DeepCopy() 146 | } 147 | } 148 | 149 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterCatalogStatus. 150 | func (in *ClusterCatalogStatus) DeepCopy() *ClusterCatalogStatus { 151 | if in == nil { 152 | return nil 153 | } 154 | out := new(ClusterCatalogStatus) 155 | in.DeepCopyInto(out) 156 | return out 157 | } 158 | 159 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 160 | func (in *ClusterCatalogURLs) DeepCopyInto(out *ClusterCatalogURLs) { 161 | *out = *in 162 | } 163 | 164 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterCatalogURLs. 165 | func (in *ClusterCatalogURLs) DeepCopy() *ClusterCatalogURLs { 166 | if in == nil { 167 | return nil 168 | } 169 | out := new(ClusterCatalogURLs) 170 | in.DeepCopyInto(out) 171 | return out 172 | } 173 | 174 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 175 | func (in *ImageSource) DeepCopyInto(out *ImageSource) { 176 | *out = *in 177 | if in.PollIntervalMinutes != nil { 178 | in, out := &in.PollIntervalMinutes, &out.PollIntervalMinutes 179 | *out = new(int) 180 | **out = **in 181 | } 182 | } 183 | 184 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageSource. 185 | func (in *ImageSource) DeepCopy() *ImageSource { 186 | if in == nil { 187 | return nil 188 | } 189 | out := new(ImageSource) 190 | in.DeepCopyInto(out) 191 | return out 192 | } 193 | 194 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 195 | func (in *ResolvedCatalogSource) DeepCopyInto(out *ResolvedCatalogSource) { 196 | *out = *in 197 | if in.Image != nil { 198 | in, out := &in.Image, &out.Image 199 | *out = new(ResolvedImageSource) 200 | **out = **in 201 | } 202 | } 203 | 204 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResolvedCatalogSource. 205 | func (in *ResolvedCatalogSource) DeepCopy() *ResolvedCatalogSource { 206 | if in == nil { 207 | return nil 208 | } 209 | out := new(ResolvedCatalogSource) 210 | in.DeepCopyInto(out) 211 | return out 212 | } 213 | 214 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 215 | func (in *ResolvedImageSource) DeepCopyInto(out *ResolvedImageSource) { 216 | *out = *in 217 | } 218 | 219 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResolvedImageSource. 220 | func (in *ResolvedImageSource) DeepCopy() *ResolvedImageSource { 221 | if in == nil { 222 | return nil 223 | } 224 | out := new(ResolvedImageSource) 225 | in.DeepCopyInto(out) 226 | return out 227 | } 228 | -------------------------------------------------------------------------------- /.bingo/setup-envtest.sum: -------------------------------------------------------------------------------- 1 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= 5 | github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 6 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 7 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 8 | github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk= 9 | github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= 10 | github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= 11 | github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= 12 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 13 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 14 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 15 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 16 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 17 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 18 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 19 | github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 20 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 21 | github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= 22 | github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= 23 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 24 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 25 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 26 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 27 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 28 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 29 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 30 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= 31 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 32 | go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= 33 | go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 34 | go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= 35 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 36 | go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= 37 | go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 38 | go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= 39 | go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= 40 | go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= 41 | go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= 42 | go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 43 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 44 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 45 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 46 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 47 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 48 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 49 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 50 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 51 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 52 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 53 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 54 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 55 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 56 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 57 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 58 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 59 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 60 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 61 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 62 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 63 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 64 | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= 65 | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 66 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 67 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 68 | golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 69 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 70 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 71 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 72 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 73 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 74 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 75 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 76 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 77 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 78 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 79 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 80 | sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20230524200249-30eae58f1b98 h1:NBx7t6AJzlY+2urLHbhQ/h1SM2XcTFA17uOdu39G1aY= 81 | sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20230524200249-30eae58f1b98/go.mod h1:B6HLcvOy2S1qq2eWOFm9xepiKPMIc8Z9OXSPsnUDaR4= 82 | sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20240820183333-e6c3d139d2b6 h1:Wzx3QswG7gfzqPDw7Ec6/xvJGyoxAKUEoaxWLrk1V/I= 83 | sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20240820183333-e6c3d139d2b6/go.mod h1:IaDsO8xSPRxRG1/rm9CP7+jPmj0nMNAuNi/yiHnLX8k= 84 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 85 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 86 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | Catalogd is [Apache 2.0 licensed](LICENSE.md) and accepts contributions via 4 | GitHub pull requests. This document puts together some guidelines on how to make 5 | contributions to Catalogd and provides contacts to get in touch with the 6 | Catalogd maintainers and developers. To learn more about Catalogd and how it 7 | fits into `OLM V1`, please refer to [Operator Lifecycle Manager (OLM) V1 Product 8 | Requirements Document 9 | (PRD)](https://docs.google.com/document/d/1-vsZ2dAODNfoHb7Nf0fbYeKDF7DUqEzS9HqgeMCvbDs/edit#heading=h.dbjdp199nxjk). 10 | 11 | ## Certificate of Origin 12 | 13 | By contributing to this project you agree to the Developer Certificate of Origin 14 | (DCO). This document was created by the Linux Kernel community and is a simple 15 | statement that you, as a contributor, have the legal right to make the 16 | contribution. See the 17 | [DCO](https://github.com/operator-framework/catalogd/blob/main/DCO) file for 18 | details. 19 | 20 | ## Communication Channels 21 | 22 | - Mailing List: 23 | [operator-framework-olm-dev](mailto:operator-framework-olm-dev@googlegroups.com) 24 | - Slack: [#olm-dev](https://kubernetes.slack.com/archives/C0181L6JYQ2) 25 | - Working Group Meeting: 26 | [olm-wg](https://groups.google.com/g/operator-framework-olm-dev) 27 | 28 | ## Getting started 29 | 30 | - Fork the repository on GitHub. 31 | - Clone the repository onto your local development machine via `git clone 32 | https://github.com/{$GH-USERNAME}/catalogd.git`. 33 | - Add `operator-framework/catalogd` upstream remote by running `git remote add 34 | upstream https://github.com/operator-framework/catalogd.git`. 35 | - Create a new branch from the default `main` branch and begin development in 36 | the area of your interest. 37 | 38 | ## How to Build and Deploy Locally 39 | 40 | After creating a fork and cloning the project locally, 41 | you can follow the steps below to test your changes: 42 | 43 | 1. Create the cluster: 44 | 45 | ```sh 46 | kind create cluster -n catalogd 47 | ``` 48 | 49 | 2. Build your changes: 50 | 51 | ```sh 52 | make build-container 53 | ``` 54 | 55 | 3. Load the image locally and Deploy to Kind 56 | 57 | ```sh 58 | make kind-load deploy 59 | ``` 60 | 61 | ## Reporting bugs and creating issues 62 | 63 | Any new contribution should be linked to a new or existing github issue in the 64 | Catalogd project. This issue can help track work, discuss the design and 65 | implementation, and help avoid duplicate efforts of multiple people working on 66 | the same issue. Trivial changes, like fixing a typo in the documentation, do not 67 | require the creation of a new issue but can be linked to an existing issue if it 68 | exists. 69 | 70 | Proposing larger changes to the Catalogd project may require an enhancement 71 | proposal, or some documentation, before being considered. The maintainers 72 | typically use Google Docs and an RFC process to write design drafts for any new 73 | features. If you're interested in proposing ideas for new features, you can use 74 | the [RFC 75 | Template](https://docs.google.com/document/d/1aYFGdq3W3UKzkRbNopISIdzABh-o5S0et7q0h2qPFGw/edit#heading=h.x3tfh25grvnv) 76 | to write an RFC and place it in the [Design Docs 77 | Folder](https://drive.google.com/drive/u/1/folders/1c5jSCrXuE9bziZcEiIX3X89OEC5tRgEg). 78 | 79 | Any changes to Catalogd's existing behavior or features, APIs, or changes and 80 | additions to tests do not require an enhancement proposal. 81 | 82 | ## Contribution flow 83 | 84 | Below is a rough outline of what a contributor's workflow looks like: 85 | 86 | - Identify an existing issue or create a new issue. If you're new to Catalogd 87 | and looking to make a quick contribution and ramp up on the project, a good 88 | way to do this is to identify `good-first-issues` from [Catalogd Github 89 | Issues](https://github.com/operator-framework/catalogd/issues) by using the 90 | filter by label and search for "good first issue". Also, please feel empowered 91 | to work on other issues that interest you which are not good first issues. 92 | - Create a new branch from the branch you would like to base your contribution 93 | from. Typically, this is the main branch. 94 | - Create commits that are of logical units. 95 | - Commit your changes and make sure to use commit messages that are in proper 96 | format following best practices and guidelines for git commit messages (see 97 | below). 98 | - Once you've committed your changes, push the new branch that contains your 99 | changes to your personal fork of the repository. 100 | - Submit a pull request to `operator-framework/catalogd` repository. 101 | - Respond to feedback from the Catalogd maintainers and community members and 102 | address any review suggestions. The PR must receive an approval from at least 103 | one Catalogd maintainer. 104 | 105 | ### Format of the commit message 106 | 107 | We follow a rough convention for commit messages that is designed to answer two 108 | questions: what changed and why. The subject line should feature the what and 109 | the body of the commit should describe the why. 110 | 111 | ```text 112 | (feature): create a minimal client library 113 | 114 | - Fixes #10 115 | - Add unit tests for the client library 116 | ``` 117 | 118 | The format can be described more formally as follows: 119 | 120 | ```text 121 | : 122 | 123 | 124 | 125 |