diff --git a/.gitignore b/.gitignore index 3513d3f..2b7fe6a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ *.so *.dylib bin/* +dist/ _output/ Dockerfile.cross diff --git a/Makefile b/Makefile index 35af4c3..8be14e5 100644 --- a/Makefile +++ b/Makefile @@ -5,47 +5,6 @@ # - use environment variables to overwrite this value (e.g export VERSION=0.0.2) VERSION ?= 0.0.1 -# CHANNELS define the bundle channels used in the bundle. -# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable") -# To re-generate a bundle for other specific channels without changing the standard setup, you can: -# - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=candidate,fast,stable) -# - use environment variables to overwrite this value (e.g export CHANNELS="candidate,fast,stable") -ifneq ($(origin CHANNELS), undefined) -BUNDLE_CHANNELS := --channels=$(CHANNELS) -endif - -# DEFAULT_CHANNEL defines the default channel used in the bundle. -# Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable") -# To re-generate a bundle for any other default channel without changing the default setup, you can: -# - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable) -# - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable") -ifneq ($(origin DEFAULT_CHANNEL), undefined) -BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL) -endif -BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) - -# IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images. -# This variable is used to construct full image tags for bundle and catalog images. -# -# For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both -# redhat.com/hyperfleet-operator-bundle:$VERSION and redhat.com/hyperfleet-operator-catalog:$VERSION. -IMAGE_TAG_BASE ?= redhat.com/hyperfleet-operator - -# BUNDLE_IMG defines the image:tag used for the bundle. -# You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) -BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION) - -# BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command -BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) - -# USE_IMAGE_DIGESTS defines if images are resolved via tags or digests -# You can enable this value if you would like to use SHA Based Digests -# To enable set flag to true -USE_IMAGE_DIGESTS ?= false -ifeq ($(USE_IMAGE_DIGESTS), true) - BUNDLE_GEN_FLAGS += --use-image-digests -endif - # Set the Operator SDK version to use. By default, what is installed on the system is used. # This is useful for CI or a project to utilize a specific version of the operator-sdk toolkit. OPERATOR_SDK_VERSION ?= v1.42.3 @@ -189,50 +148,17 @@ run: manifests generate fmt vet ## Run a controller from your host. OPERATOR_NAMESPACE=$(OPERATOR_NAMESPACE) go run ./cmd/main.go -# If you wish to build the manager image targeting other platforms you can use the --platform flag. -# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it. -# More info: https://docs.docker.com/develop/develop-images/build_enhancements/ -.PHONY: docker-build -docker-build: ## Build docker image with the manager. - $(CONTAINER_TOOL) build -t ${IMG} . - -.PHONY: docker-push -docker-push: ## Push docker image with the manager. - $(CONTAINER_TOOL) push ${IMG} - -# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple -# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: -# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/ -# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/ -# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=> then the export will fail) -# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option. -PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le -.PHONY: docker-buildx -docker-buildx: ## Build and push docker image for the manager for cross-platform support - # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile - sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross - - $(CONTAINER_TOOL) buildx create --name hyperfleet-operator-builder - $(CONTAINER_TOOL) buildx use hyperfleet-operator-builder - - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . - - $(CONTAINER_TOOL) buildx rm hyperfleet-operator-builder - rm Dockerfile.cross - -.PHONY: build-installer -build-installer: manifests generate ## Generate a consolidated YAML with CRDs and deployment. - @mkdir -p dist - @cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} - @$(KUSTOMIZE) build config/default > dist/install.yaml - - ##@ Container Images # Image configuration PLATFORM ?= linux/amd64 -IMG_REGISTRY ?= quay.io/openshift-hyperfleet +QUAY_REPO ?= openshift-hyperfleet +IMG_REGISTRY ?= quay.io/$(QUAY_REPO) IMG_NAME ?= hyperfleet-operator -IMG_TAG ?= $(APP_VERSION) +IMG_TAG ?= v0.0.1 IMG ?= $(IMG_REGISTRY)/$(IMG_NAME):$(IMG_TAG) + APP_VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "0.0.0-dev") GIT_SHA ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") GIT_DIRTY ?= $(shell [ -z "$$(git status --porcelain 2>/dev/null)" ] || echo "-modified") @@ -281,14 +207,43 @@ image-push: check-container-tool ## Push container image to registry image-build-push: image-build image-push ## Build and push container image to registry .PHONY: image-dev -image-build-push-dev: ## Build and push dev image to dev Quay registry (requires QUAY_USER) +image-dev: ## Build and push dev image to dev Quay registry (requires QUAY_USER) ifeq ($(strip $(QUAY_USER)),) @echo "Error: QUAY_USER is not set" @echo "" @echo "Usage: QUAY_USER=myuser make image-dev" @exit 1 endif - IMG_REGISTRY=quay.io/$(QUAY_USER) IMG_TAG=$(DEV_TAG) $(MAKE) image-build-push + QUAY_REPO=$(QUAY_USER) IMG_TAG=$(DEV_TAG) $(MAKE) image-build-push + +# If you wish to build the manager image targeting other platforms you can use the --platform flag. +# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it. +# More info: https://docs.docker.com/develop/develop-images/build_enhancements/ +.PHONY: docker-build +docker-build: ## Build docker image with the manager. + $(CONTAINER_TOOL) build -t ${IMG} . + +.PHONY: docker-push +docker-push: ## Push docker image with the manager. + $(CONTAINER_TOOL) push ${IMG} + +# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple +# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: +# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/ +# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/ +# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=> then the export will fail) +# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option. +PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le +.PHONY: docker-buildx +docker-buildx: ## Build and push docker image for the manager for cross-platform support + # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile + sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross + - $(CONTAINER_TOOL) buildx create --name hyperfleet-operator-builder + $(CONTAINER_TOOL) buildx use hyperfleet-operator-builder + - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . + - $(CONTAINER_TOOL) buildx rm hyperfleet-operator-builder + rm Dockerfile.cross + ##@ Deployment @@ -306,13 +261,123 @@ uninstall: manifests ## Uninstall CRDs from the K8s cluster specified in ~/.kube .PHONY: deploy deploy: manifests ## Deploy controller to the K8s cluster specified in ~/.kube/config. - cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} @$(KUSTOMIZE) build config/default | $(KUBECTL) apply -f - .PHONY: undeploy undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. @$(KUSTOMIZE) build config/default | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - + +##@ Bundles/Catalog + + +# Non-olm installs +# Generates dist/install.yaml +# Install resources +# kubectl apply -f dist/install.yaml +# Uninstall resources +# kubectl delete -f dist/install.yaml +# For image overrides edit config/manager/kustomization.yaml +.PHONY: build-installer +build-installer: manifests generate ## Generate a consolidated YAML with CRDs and deployment. + @mkdir -p dist + @$(KUSTOMIZE) build config/default > dist/install.yaml + +.PHONY: build-installer-override-img +build-installer-override-img: manifests generate ## Generate installer with IMG override, then restore kustomization.yaml + @mkdir -p dist + cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) + @$(KUSTOMIZE) build config/default > dist/install.yaml + @echo "Installer generated with IMG=$(IMG)" + @echo "Note: config/manager/kustomization.yaml has been modified. Commit or reset as needed." + +# For now `stable` channel is the default and only channel +# CHANNELS define the bundle channels used in the bundle. +# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable") +# To re-generate a bundle for other specific channels without changing the standard setup, you can: +# - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=candidate,fast,stable) +# - use environment variables to overwrite this value (e.g export CHANNELS="candidate,fast,stable") +CHANNELS ?= stable +BUNDLE_CHANNELS := --channels=$(CHANNELS) + +# DEFAULT_CHANNEL defines the default channel used in the bundle. +# Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable") +# To re-generate a bundle for any other default channel without changing the default setup, you can: +# - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable) +# - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable") +DEFAULT_CHANNEL ?= stable +BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL) +BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) + + +# USE_IMAGE_DIGESTS defines if images are resolved via tags or digests +# You can enable this value if you would like to use SHA Based Digests +# To enable set flag to true +USE_IMAGE_DIGESTS ?= false +ifeq ($(USE_IMAGE_DIGESTS), true) + BUNDLE_GEN_FLAGS += --use-image-digests +endif + +# Defines the base of the registry we use for `make bundle-build catalog-build catalog-push bundle-push` +# Defaults to quay.io/openshift-hyperfleet/hyperfleet-operator +# For dev: If QUAY_REPO is set quay.io//hyperfleet-operator +REG_REPO_BASE ?= $(IMG_REGISTRY)/$(IMG_NAME) + +# Image tag for the bundle +BUNDLE_IMG ?= $(REG_REPO_BASE)-bundle:v$(VERSION) + +# BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command +BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + +# A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0). +# These images MUST exist in a registry and be pull-able. +BUNDLE_IMGS ?= $(BUNDLE_IMG) + +# The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0). +CATALOG_IMG ?= $(REG_REPO_BASE)-catalog:v$(VERSION) + +# Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image. +ifneq ($(origin CATALOG_BASE_IMG), undefined) +FROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG) +endif + + +.PHONY: bundle +bundle: manifests operator-sdk ## Generate bundle manifests and metadata, then validate generated files. + $(OPERATOR_SDK) generate kustomize manifests -q + $(KUSTOMIZE) build config/manifests | $(OPERATOR_SDK) generate bundle $(BUNDLE_GEN_FLAGS) + $(OPERATOR_SDK) bundle validate ./bundle + +.PHONY: bundle-override-img +bundle-override-img: manifests operator-sdk ## Generate bundle with IMG override, then restore kustomization.yaml + $(OPERATOR_SDK) generate kustomize manifests -q + cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) + $(KUSTOMIZE) build config/manifests | $(OPERATOR_SDK) generate bundle $(BUNDLE_GEN_FLAGS) + $(OPERATOR_SDK) bundle validate ./bundle + @echo "Bundle generated with IMG=$(IMG)" + @echo "Note: config/manager/kustomization.yaml has been modified. Commit or reset as needed." + +.PHONY: bundle-build +bundle-build: ## Build the bundle image. + $(CONTAINER_TOOL) build -f bundle.Dockerfile -t $(BUNDLE_IMG) . + +.PHONY: bundle-push +bundle-push: ## Push the bundle image. + $(MAKE) docker-push IMG=$(BUNDLE_IMG) + +# Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'. +# This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see: +# https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator +.PHONY: catalog-build +catalog-build: opm ## Build a catalog image. + $(OPM) index add --container-tool $(CONTAINER_TOOL) --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT) + +# Push the catalog image. +.PHONY: catalog-push +catalog-push: ## Push a catalog image. + $(MAKE) docker-push IMG=$(CATALOG_IMG) + + ##@ Dependencies ## Location to install dependencies to @@ -341,20 +406,6 @@ OPERATOR_SDK = $(shell which operator-sdk) endif endif -.PHONY: bundle -bundle: manifests operator-sdk ## Generate bundle manifests and metadata, then validate generated files. - $(OPERATOR_SDK) generate kustomize manifests -q - cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) - $(KUSTOMIZE) build config/manifests | $(OPERATOR_SDK) generate bundle $(BUNDLE_GEN_FLAGS) - $(OPERATOR_SDK) bundle validate ./bundle - -.PHONY: bundle-build -bundle-build: ## Build the bundle image. - $(CONTAINER_TOOL) build -f bundle.Dockerfile -t $(BUNDLE_IMG) . - -.PHONY: bundle-push -bundle-push: ## Push the bundle image. - $(MAKE) docker-push IMG=$(BUNDLE_IMG) .PHONY: opm OPM = $(LOCALBIN)/opm @@ -372,27 +423,3 @@ else OPM = $(shell which opm) endif endif - -# A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0). -# These images MUST exist in a registry and be pull-able. -BUNDLE_IMGS ?= $(BUNDLE_IMG) - -# The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0). -CATALOG_IMG ?= $(IMAGE_TAG_BASE)-catalog:v$(VERSION) - -# Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image. -ifneq ($(origin CATALOG_BASE_IMG), undefined) -FROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG) -endif - -# Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'. -# This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see: -# https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator -.PHONY: catalog-build -catalog-build: opm ## Build a catalog image. - $(OPM) index add --container-tool $(CONTAINER_TOOL) --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT) - -# Push the catalog image. -.PHONY: catalog-push -catalog-push: ## Push a catalog image. - $(MAKE) docker-push IMG=$(CATALOG_IMG) diff --git a/bundle-hack/update_bundle.sh b/bundle-hack/update_bundle.sh new file mode 100755 index 0000000..3bb79a6 --- /dev/null +++ b/bundle-hack/update_bundle.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Konflux nudges update these variables with the latest digest-pinned pullspecs. + +HYPERFLEET_OPERATOR_IMAGE_PULLSPEC="${HYPERFLEET_OPERATOR_IMAGE_PULLSPEC:-quay.io/openshift-hyperfleet/hyperfleet-operator:v0.0.1}" + +CSV_FILE="${CSV_FILE:-/manifests/hyperfleet-operator.clusterserviceversion.yaml}" + +# Update operator deployment image +yq eval ".spec.install.spec.deployments[].spec.template.spec.containers[] |= ( + select(.name == \"manager\") | + .image = \"${HYPERFLEET_OPERATOR_IMAGE_PULLSPEC}\" +)" -i "${CSV_FILE}" + + +# Update containerImage annotation +yq eval ".metadata.annotations.containerImage = \"${HYPERFLEET_OPERATOR_IMAGE_PULLSPEC}\"" -i "${CSV_FILE}" + +# Update relatedImages + +cat "${CSV_FILE}" diff --git a/bundle.Dockerfile b/bundle.Dockerfile new file mode 100644 index 0000000..ed59506 --- /dev/null +++ b/bundle.Dockerfile @@ -0,0 +1,21 @@ +FROM scratch + +# Core bundle labels. +LABEL operators.operatorframework.io.bundle.mediatype.v1=registry+v1 +LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ +LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ +LABEL operators.operatorframework.io.bundle.package.v1=hyperfleet-operator +LABEL operators.operatorframework.io.bundle.channels.v1=stable +LABEL operators.operatorframework.io.bundle.channel.default.v1=stable +LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.42.3 +LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 +LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v4 + +# Labels for testing. +LABEL operators.operatorframework.io.test.mediatype.v1=scorecard+v1 +LABEL operators.operatorframework.io.test.config.v1=tests/scorecard/ + +# Copy files to locations specified by labels. +COPY bundle/manifests /manifests/ +COPY bundle/metadata /metadata/ +COPY bundle/tests/scorecard /tests/scorecard/ diff --git a/bundle.konflux.Dockerfile b/bundle.konflux.Dockerfile new file mode 100644 index 0000000..fc6470a --- /dev/null +++ b/bundle.konflux.Dockerfile @@ -0,0 +1,36 @@ +# Konflux bundle image build. Unlike the auto-generated bundle.Dockerfile (used +# for local dev with operator-sdk), this runs bundle-hack/update_bundle.sh to +# patch digest-pinned image references into the CSV at build time. +FROM registry.access.redhat.com/ubi9/ubi-minimal:latest AS builder-runner +RUN microdnf install -y tar gzip && \ + curl -sL https://github.com/mikefarah/yq/releases/download/v4.44.1/yq_linux_amd64.tar.gz | tar xz && \ + mv yq_linux_amd64 /usr/bin/yq + +FROM builder-runner AS builder +# Hack to set the operator container image in the deployment +COPY bundle-hack . +COPY bundle/manifests /manifests/ + +RUN ./update_bundle.sh + +FROM scratch + +# Core bundle labels. +LABEL operators.operatorframework.io.bundle.mediatype.v1=registry+v1 +LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ +LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ +LABEL operators.operatorframework.io.bundle.package.v1=hyperfleet-operator +LABEL operators.operatorframework.io.bundle.channels.v1=stable +LABEL operators.operatorframework.io.bundle.channel.default.v1=stable +LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.42.3 +LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 +LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v4 + +# Labels for testing. +LABEL operators.operatorframework.io.test.mediatype.v1=scorecard+v1 +LABEL operators.operatorframework.io.test.config.v1=tests/scorecard/ + +# Copy patched manifests from builder, metadata and tests from source. +COPY --from=builder /manifests /manifests/ +COPY bundle/metadata /metadata/ +COPY bundle/tests/scorecard /tests/scorecard/ diff --git a/bundle/manifests/hyperfleet-operator-controller-manager-metrics-service_v1_service.yaml b/bundle/manifests/hyperfleet-operator-controller-manager-metrics-service_v1_service.yaml new file mode 100644 index 0000000..b042de1 --- /dev/null +++ b/bundle/manifests/hyperfleet-operator-controller-manager-metrics-service_v1_service.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: hyperfleet-operator + control-plane: controller-manager + name: hyperfleet-operator-controller-manager-metrics-service +spec: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: 8443 + selector: + app.kubernetes.io/name: hyperfleet-operator + control-plane: controller-manager +status: + loadBalancer: {} diff --git a/bundle/manifests/hyperfleet-operator-hyperfleetconfig-admin-role_rbac.authorization.k8s.io_v1_clusterrole.yaml b/bundle/manifests/hyperfleet-operator-hyperfleetconfig-admin-role_rbac.authorization.k8s.io_v1_clusterrole.yaml new file mode 100644 index 0000000..99652ea --- /dev/null +++ b/bundle/manifests/hyperfleet-operator-hyperfleetconfig-admin-role_rbac.authorization.k8s.io_v1_clusterrole.yaml @@ -0,0 +1,21 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: hyperfleet-operator + name: hyperfleet-operator-hyperfleetconfig-admin-role +rules: +- apiGroups: + - hyperfleet.redhat.com + resources: + - hyperfleetconfigs + verbs: + - '*' +- apiGroups: + - hyperfleet.redhat.com + resources: + - hyperfleetconfigs/status + verbs: + - get diff --git a/bundle/manifests/hyperfleet-operator-hyperfleetconfig-editor-role_rbac.authorization.k8s.io_v1_clusterrole.yaml b/bundle/manifests/hyperfleet-operator-hyperfleetconfig-editor-role_rbac.authorization.k8s.io_v1_clusterrole.yaml new file mode 100644 index 0000000..9ffd846 --- /dev/null +++ b/bundle/manifests/hyperfleet-operator-hyperfleetconfig-editor-role_rbac.authorization.k8s.io_v1_clusterrole.yaml @@ -0,0 +1,27 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: hyperfleet-operator + name: hyperfleet-operator-hyperfleetconfig-editor-role +rules: +- apiGroups: + - hyperfleet.redhat.com + resources: + - hyperfleetconfigs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - hyperfleet.redhat.com + resources: + - hyperfleetconfigs/status + verbs: + - get diff --git a/bundle/manifests/hyperfleet-operator-hyperfleetconfig-viewer-role_rbac.authorization.k8s.io_v1_clusterrole.yaml b/bundle/manifests/hyperfleet-operator-hyperfleetconfig-viewer-role_rbac.authorization.k8s.io_v1_clusterrole.yaml new file mode 100644 index 0000000..cf45968 --- /dev/null +++ b/bundle/manifests/hyperfleet-operator-hyperfleetconfig-viewer-role_rbac.authorization.k8s.io_v1_clusterrole.yaml @@ -0,0 +1,23 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: hyperfleet-operator + name: hyperfleet-operator-hyperfleetconfig-viewer-role +rules: +- apiGroups: + - hyperfleet.redhat.com + resources: + - hyperfleetconfigs + verbs: + - get + - list + - watch +- apiGroups: + - hyperfleet.redhat.com + resources: + - hyperfleetconfigs/status + verbs: + - get diff --git a/bundle/manifests/hyperfleet-operator-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml b/bundle/manifests/hyperfleet-operator-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml new file mode 100644 index 0000000..4450500 --- /dev/null +++ b/bundle/manifests/hyperfleet-operator-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: hyperfleet-operator + name: hyperfleet-operator-metrics-reader +rules: +- nonResourceURLs: + - /metrics + verbs: + - get diff --git a/bundle/manifests/hyperfleet-operator.clusterserviceversion.yaml b/bundle/manifests/hyperfleet-operator.clusterserviceversion.yaml new file mode 100644 index 0000000..ad158e3 --- /dev/null +++ b/bundle/manifests/hyperfleet-operator.clusterserviceversion.yaml @@ -0,0 +1,263 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + annotations: + alm-examples: |- + [ + { + "apiVersion": "hyperfleet.redhat.com/v1alpha1", + "kind": "HyperFleetConfig", + "metadata": { + "labels": { + "app.kubernetes.io/managed-by": "kustomize", + "app.kubernetes.io/name": "hyperfleet-operator" + }, + "name": "cluster" + }, + "spec": { + "api": { + "auth": { + "audience": "hyperfleet-api", + "enabled": true, + "issuer": "https://issuer.example.com" + }, + "database": { + "secretRef": { + "name": "hyperfleet-db" + } + }, + "profile": "small" + }, + "bundle": "cloud-capi" + } + } + ] + capabilities: Full Lifecycle + createdAt: "2026-08-27T22:48:48Z" + operators.operatorframework.io/builder: operator-sdk-v1.42.3 + operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 + name: hyperfleet-operator.v0.0.1 + namespace: placeholder +spec: + apiservicedefinitions: {} + customresourcedefinitions: + owned: + - description: |- + HyperFleetConfig is the Schema for the hyperfleetconfigs API. It is a + cluster-scoped singleton: exactly one instance, named "cluster", is permitted. + displayName: Hyper Fleet Config + kind: HyperFleetConfig + name: hyperfleetconfigs.hyperfleet.redhat.com + version: v1alpha1 + description: |- + Delivers HyperFleet as a standard Kubernetes operator, installed and + managed through OLM. It exposes a single cluster-scoped CR: HyperFleetConfig. + displayName: HyperFleet Operator + icon: + - base64data: "" + mediatype: "" + install: + spec: + clusterPermissions: + - rules: + - apiGroups: + - "" + resources: + - configmaps + - serviceaccounts + - services + verbs: + - create + - get + - list + - patch + - update + - watch + - apiGroups: + - apps + resources: + - deployments + verbs: + - create + - get + - list + - patch + - update + - watch + - apiGroups: + - hyperfleet.redhat.com + resources: + - hyperfleetconfigs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - hyperfleet.redhat.com + resources: + - hyperfleetconfigs/finalizers + verbs: + - update + - apiGroups: + - hyperfleet.redhat.com + resources: + - hyperfleetconfigs/status + verbs: + - get + - patch + - update + - apiGroups: + - rbac.authorization.k8s.io + resources: + - rolebindings + - roles + verbs: + - create + - get + - list + - patch + - update + - watch + - apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create + - apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create + serviceAccountName: hyperfleet-operator-controller-manager + deployments: + - label: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: hyperfleet-operator + control-plane: controller-manager + name: hyperfleet-operator-controller-manager + spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: hyperfleet-operator + control-plane: controller-manager + strategy: {} + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + labels: + app.kubernetes.io/name: hyperfleet-operator + control-plane: controller-manager + spec: + containers: + - args: + - --metrics-bind-address=:8443 + - --leader-elect + - --health-probe-bind-address=:8081 + command: + - /manager + env: + - name: OPERATOR_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: RELATED_IMAGE_HYPERFLEET_API + value: quay.io/openshift-hyperfleet/hyperfleet-api@sha256:c2957324b525fbd7d5ec776f4a395783599e6a878fb3f5993edc06371f1579bd + image: quay.io/openshift-hyperfleet/hyperfleet-operator:placeholder + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + name: manager + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + serviceAccountName: hyperfleet-operator-controller-manager + terminationGracePeriodSeconds: 10 + permissions: + - rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + serviceAccountName: hyperfleet-operator-controller-manager + strategy: deployment + installModes: + - supported: false + type: OwnNamespace + - supported: false + type: SingleNamespace + - supported: false + type: MultiNamespace + - supported: true + type: AllNamespaces + keywords: + - hyperfleet + - cluster-management + - multicluster + links: + - name: HyperFleet Operator + url: https://github.com/openshift-hyperfleet/hyperfleet-operator + minKubeVersion: 1.27.0 + provider: + name: Red Hat + url: https://github.com/openshift-hyperfleet + relatedImages: + - image: quay.io/openshift-hyperfleet/hyperfleet-api@sha256:c2957324b525fbd7d5ec776f4a395783599e6a878fb3f5993edc06371f1579bd + name: hyperfleet-api + version: 0.0.1 diff --git a/bundle/manifests/hyperfleet.redhat.com_hyperfleetconfigs.yaml b/bundle/manifests/hyperfleet.redhat.com_hyperfleetconfigs.yaml new file mode 100644 index 0000000..89778b9 --- /dev/null +++ b/bundle/manifests/hyperfleet.redhat.com_hyperfleetconfigs.yaml @@ -0,0 +1,281 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.21.0 + creationTimestamp: null + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: hyperfleet-operator + name: hyperfleetconfigs.hyperfleet.redhat.com +spec: + group: hyperfleet.redhat.com + names: + kind: HyperFleetConfig + listKind: HyperFleetConfigList + plural: hyperfleetconfigs + shortNames: + - hfc + singular: hyperfleetconfig + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.bundle + name: Bundle + type: string + - jsonPath: .spec.api.profile + name: Profile + type: string + - jsonPath: .status.conditions[?(@.type=="Available")].status + name: Available + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + HyperFleetConfig is the Schema for the hyperfleetconfigs API. It is a + cluster-scoped singleton: exactly one instance, named "cluster", is permitted. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + HyperFleetConfigSpec defines the desired state of HyperFleetConfig. It captures + partner intent only; internal machinery (broker, adapters, sentinel) is never + expressed here. + properties: + api: + description: api is the partner-facing configuration for the HyperFleet + API component. + properties: + auth: + description: auth configures partner-facing JWT authentication + intent. + properties: + audience: + description: |- + audience is the token audience the API requires. Required and non-empty + when enabled is true. + maxLength: 253 + minLength: 1 + type: string + enabled: + default: true + description: |- + enabled turns JWT authentication on for the API endpoint. It defaults to + true, so a config that omits it gets authentication ON. It is a pointer to + distinguish "unset" (apply the default, true) from an explicit false + (disable auth), which a non-pointer bool cannot express: with omitempty a + plain false is dropped and re-defaulted to true, so auth could never be + turned off via the typed client; without omitempty an unset field serializes + as false and suppresses the default. Only *bool avoids both traps. + type: boolean + issuer: + description: |- + issuer is the OIDC issuer URL that mints accepted tokens. Required when + enabled is true. Whenever it is set (regardless of enabled) it must be a + valid https URL with a host, so a malformed issuer is rejected at admission + rather than surfacing later at token-validation time. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: issuer must be a valid https URL + rule: isURL(self) && url(self).getScheme() == 'https' && + url(self).getHostname() != '' + type: object + x-kubernetes-validations: + - message: issuer and audience are required when auth is enabled + rule: '!self.enabled || (has(self.issuer) && has(self.audience))' + database: + description: database configures the external PostgreSQL connection. + properties: + secretRef: + description: |- + secretRef references a Secret holding the database connection credentials. + The Secret must provide the keys db.host, db.port, db.name, db.user and + db.password. + properties: + name: + description: |- + name is the name of the Secret in the operator's namespace. It must be a + valid DNS-1123 subdomain, matching what k8s.io/apimachinery/pkg/util/validation + enforces for Secret names (IsDNS1123Subdomain, max length 253), so an + unresolvable reference is rejected at admission rather than failing opaquely + when the reference is later resolved. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - secretRef + type: object + profile: + default: small + description: profile selects a sizing profile for the API. Defaults + to "small". + enum: + - small + - medium + - large + type: string + tls: + description: |- + tls optionally configures TLS for the API endpoint. When omitted, the + operator applies its default serving configuration. + properties: + secretRef: + description: |- + secretRef references a kubernetes.io/tls Secret (providing tls.crt and + tls.key) used to serve the API endpoint. + properties: + name: + description: |- + name is the name of the Secret in the operator's namespace. It must be a + valid DNS-1123 subdomain, matching what k8s.io/apimachinery/pkg/util/validation + enforces for Secret names (IsDNS1123Subdomain, max length 253), so an + unresolvable reference is rejected at admission rather than failing opaquely + when the reference is later resolved. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - secretRef + type: object + required: + - auth + - database + type: object + bundle: + description: |- + bundle selects one of the operator-internal bundle definitions. It is + immutable after creation: switching deployments requires recreating the + resource. + enum: + - cloud-capi + - onprem-agent + type: string + x-kubernetes-validations: + - message: bundle is immutable + rule: self == oldSelf + required: + - api + - bundle + type: object + status: + description: |- + HyperFleetConfigStatus defines the observed state of HyperFleetConfig. It is + populated by the bundle controller in later stories; this story defines the + schema only. + properties: + conditions: + description: |- + conditions represent the current installation health of the operand. + Recognized types are Available, Progressing and Degraded. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + observedGeneration: + description: observedGeneration is the .metadata.generation the operator + last acted on. + format: int64 + minimum: 0 + type: integer + type: object + required: + - spec + type: object + x-kubernetes-validations: + - message: the only permitted name is 'cluster'; HyperFleetConfig is a cluster-scoped + singleton + rule: self.metadata.name == 'cluster' + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/metadata/annotations.yaml b/bundle/metadata/annotations.yaml new file mode 100644 index 0000000..d1973b6 --- /dev/null +++ b/bundle/metadata/annotations.yaml @@ -0,0 +1,15 @@ +annotations: + # Core bundle annotations. + operators.operatorframework.io.bundle.mediatype.v1: registry+v1 + operators.operatorframework.io.bundle.manifests.v1: manifests/ + operators.operatorframework.io.bundle.metadata.v1: metadata/ + operators.operatorframework.io.bundle.package.v1: hyperfleet-operator + operators.operatorframework.io.bundle.channels.v1: stable + operators.operatorframework.io.bundle.channel.default.v1: stable + operators.operatorframework.io.metrics.builder: operator-sdk-v1.42.3 + operators.operatorframework.io.metrics.mediatype.v1: metrics+v1 + operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v4 + + # Annotations for testing. + operators.operatorframework.io.test.mediatype.v1: scorecard+v1 + operators.operatorframework.io.test.config.v1: tests/scorecard/ diff --git a/bundle/tests/scorecard/config.yaml b/bundle/tests/scorecard/config.yaml new file mode 100644 index 0000000..b2761e6 --- /dev/null +++ b/bundle/tests/scorecard/config.yaml @@ -0,0 +1,70 @@ +apiVersion: scorecard.operatorframework.io/v1alpha3 +kind: Configuration +metadata: + name: config +stages: +- parallel: true + tests: + - entrypoint: + - scorecard-test + - basic-check-spec + image: quay.io/operator-framework/scorecard-test:v1.42.3 + labels: + suite: basic + test: basic-check-spec-test + storage: + spec: + mountPath: {} + - entrypoint: + - scorecard-test + - olm-bundle-validation + image: quay.io/operator-framework/scorecard-test:v1.42.3 + labels: + suite: olm + test: olm-bundle-validation-test + storage: + spec: + mountPath: {} + - entrypoint: + - scorecard-test + - olm-crds-have-validation + image: quay.io/operator-framework/scorecard-test:v1.42.3 + labels: + suite: olm + test: olm-crds-have-validation-test + storage: + spec: + mountPath: {} + - entrypoint: + - scorecard-test + - olm-crds-have-resources + image: quay.io/operator-framework/scorecard-test:v1.42.3 + labels: + suite: olm + test: olm-crds-have-resources-test + storage: + spec: + mountPath: {} + - entrypoint: + - scorecard-test + - olm-spec-descriptors + image: quay.io/operator-framework/scorecard-test:v1.42.3 + labels: + suite: olm + test: olm-spec-descriptors-test + storage: + spec: + mountPath: {} + - entrypoint: + - scorecard-test + - olm-status-descriptors + image: quay.io/operator-framework/scorecard-test:v1.42.3 + labels: + suite: olm + test: olm-status-descriptors-test + storage: + spec: + mountPath: {} +storage: + spec: + mountPath: {} diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 5c5f0b8..3bbe8f5 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -1,2 +1,23 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + resources: - manager.yaml + +images: +- name: controller + newName: quay.io/openshift-hyperfleet/hyperfleet-operator + newTag: placeholder + +# Environment variables injected into the manager container +# These are used for related image references in the operator +patches: +- patch: |- + - op: add + path: /spec/template/spec/containers/0/env/- + value: + name: RELATED_IMAGE_HYPERFLEET_API + value: quay.io/openshift-hyperfleet/hyperfleet-api@sha256:c2957324b525fbd7d5ec776f4a395783599e6a878fb3f5993edc06371f1579bd + target: + kind: Deployment + name: controller-manager diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 3e10a56..7d4e511 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -63,7 +63,7 @@ spec: args: - --leader-elect - --health-probe-bind-address=:8081 - image: controller:latest + image: controller name: manager env: # OPERATOR_NAMESPACE tells the operator which namespace to create operands @@ -72,11 +72,6 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - # RELATED_IMAGE_HYPERFLEET_API is the image used for the API operand. - # Follows the OLM relatedImages convention so it can be digest-pinned at - # bundle build time; override per environment as needed. - - name: RELATED_IMAGE_HYPERFLEET_API - value: quay.io/openshift-hyperfleet/hyperfleet-api:v0.2.1 ports: [] securityContext: allowPrivilegeEscalation: false diff --git a/config/manifests/bases/hyperfleet-operator.clusterserviceversion.yaml b/config/manifests/bases/hyperfleet-operator.clusterserviceversion.yaml new file mode 100644 index 0000000..136eb6c --- /dev/null +++ b/config/manifests/bases/hyperfleet-operator.clusterserviceversion.yaml @@ -0,0 +1,80 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + annotations: + alm-examples: |- + [ + { + "apiVersion": "hyperfleet.redhat.com/v1alpha1", + "kind": "HyperFleetConfig", + "metadata": { + "labels": { + "app.kubernetes.io/managed-by": "kustomize", + "app.kubernetes.io/name": "hyperfleet-operator" + }, + "name": "cluster" + }, + "spec": { + "api": { + "auth": { + "audience": "hyperfleet-api", + "enabled": true, + "issuer": "https://issuer.example.com" + }, + "database": { + "secretRef": { + "name": "hyperfleet-db" + } + }, + "profile": "small" + }, + "bundle": "cloud-capi" + } + } + ] + capabilities: Full Lifecycle + name: hyperfleet-operator.v0.0.0 + namespace: placeholder +spec: + apiservicedefinitions: {} + customresourcedefinitions: + owned: + - description: |- + HyperFleetConfig is the Schema for the hyperfleetconfigs API. It is a + cluster-scoped singleton: exactly one instance, named "cluster", is permitted. + displayName: Hyper Fleet Config + kind: HyperFleetConfig + name: hyperfleetconfigs.hyperfleet.redhat.com + version: v1alpha1 + description: |- + Delivers HyperFleet as a standard Kubernetes operator, installed and + managed through OLM. It exposes a single cluster-scoped CR: HyperFleetConfig. + displayName: HyperFleet Operator + icon: + - base64data: "" + mediatype: "" + install: + spec: + deployments: null + strategy: "" + installModes: + - supported: false + type: OwnNamespace + - supported: false + type: SingleNamespace + - supported: false + type: MultiNamespace + - supported: true + type: AllNamespaces + keywords: + - hyperfleet + - cluster-management + - multicluster + links: + - name: HyperFleet Operator + url: https://github.com/openshift-hyperfleet/hyperfleet-operator + minKubeVersion: 1.27.0 + provider: + name: Red Hat + url: https://github.com/openshift-hyperfleet + version: 0.0.0