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/Dockerfile b/Dockerfile index c98e765..439382c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,8 +2,6 @@ ARG BASE_IMAGE=registry.access.redhat.com/ubi9-micro:latest FROM registry.access.redhat.com/ubi9/go-toolset:9.8-1786351949 AS builder -ARG APP_VERSION="0.0.0-dev" - USER root WORKDIR /workspace # Copy the Go Modules manifests @@ -23,12 +21,14 @@ RUN CGO_ENABLED=1 GOEXPERIMENT=boringcrypto \ go build -trimpath -ldflags="-s -w" -o manager ./cmd/main.go # Runtime stage -FROM ${BASE_IMAGE} as final +FROM ${BASE_IMAGE} AS final WORKDIR / COPY --from=builder /workspace/manager . USER 65532:65532 +ARG APP_VERSION="0.0.0-dev" + ENTRYPOINT ["/manager"] LABEL name="hyperfleet-operator" \ diff --git a/Makefile b/Makefile index 35af4c3..87fc497 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,49 +148,18 @@ 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 ?= $(IMG_REGISTRY)/$(IMG_NAME):$(IMG_TAG) +# Base image for production builds - matches Dockerfile default +# Override with DEV_BASE_IMAGE for dev builds (see image-dev target) +BASE_IMAGE ?= registry.access.redhat.com/ubi9-micro:latest 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") @@ -248,11 +176,6 @@ GOFLAGS ?= -trimpath # -X github.com/openshift-hyperfleet/hyperfleet-operator/pkg/version.Commit=$(GIT_SHA) \ # -X 'github.com/openshift-hyperfleet/hyperfleet-operator/pkg/version.BuildTime=$(BUILD_DATE)' -# Dev image configuration - set QUAY_USER to push to personal registry -QUAY_USER ?= -DEV_TAG ?= dev-$(GIT_SHA) -BASE_IMAGE ?= registry.access.redhat.com/ubi9/ubi-minimal:latest - .PHONY: check-container-tool check-container-tool: ifndef CONTAINER_TOOL @@ -260,8 +183,8 @@ ifndef CONTAINER_TOOL @exit 1 endif -.PHONY: image-build -image-build: check-container-tool manifests generate fmt vet ## Build container image with configurable registry/tag +.PHONY: image +image: check-container-tool manifests generate fmt vet ## Build container image with configurable registry/tag @echo "Building container image $(IMG)..." $(CONTAINER_TOOL) build \ --platform $(PLATFORM) \ @@ -278,17 +201,57 @@ image-push: check-container-tool ## Push container image to registry @echo "Image pushed: $(IMG)" .PHONY: image-build-push -image-build-push: image-build image-push ## Build and push container image to registry +image-build-push: image 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) +.PHONY: check-quay-user +check-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 + +# Usage: QUAY_USER=myuser make image-dev +# Dev image configuration - set QUAY_USER to push to personal registry +DEV_TAG ?= dev-$(GIT_SHA) +QUAY_USER ?= +DEV_BASE_IMAGE ?= registry.access.redhat.com/ubi9/ubi-minimal:latest + +.PHONY: image-dev +image-dev: QUAY_REPO = $(QUAY_USER) +image-dev: IMG_TAG = $(DEV_TAG) +image-dev: BASE_IMAGE = $(DEV_BASE_IMAGE) +image-dev: check-quay-user image-build-push ## Build and push dev image to dev Quay registry (requires QUAY_USER) + +# 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 @@ -305,13 +268,122 @@ uninstall: manifests ## Uninstall CRDs from the K8s cluster specified in ~/.kube @$(KUSTOMIZE) build config/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - .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 - +deploy: build-deployer-override-img ## Deploy controller to the K8s cluster specified in ~/.kube/config. + @$(KUBECTL) apply -f dist/install.yaml .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 - + @$(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f dist/install.yaml + + +##@ 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-deployer +build-deployer: manifests generate ## Generate a consolidated YAML with CRDs and deployment. + @mkdir -p dist + @$(KUSTOMIZE) build config/default > dist/install.yaml + +.PHONY: build-deployer-override-img +build-deployer-override-img: manifests generate ## Generate deployer 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 "Deployer 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 @@ -341,20 +413,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 +430,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/README.md b/README.md index 0488e88..da5a05e 100644 --- a/README.md +++ b/README.md @@ -62,63 +62,12 @@ kubectl delete -k config/samples/ make uninstall ``` -**UnDeploy the controller from the cluster:** +**Undeploy the controller from the cluster:** ```sh make undeploy ``` -## Project Distribution - -Following the options to release and provide this solution to the users. - -### By providing a bundle with all YAML files - -1. Build the installer for the image built and published in the registry: - -```sh -make build-installer IMG=/hyperfleet-operator:tag -``` - -**NOTE:** The makefile target mentioned above generates an 'install.yaml' -file in the dist directory. This file contains all the resources built -with Kustomize, which are necessary to install this project without its -dependencies. - -2. Using the installer - -Users can just run 'kubectl apply -f ' to install -the project, i.e.: - -```sh -kubectl apply -f https://raw.githubusercontent.com//hyperfleet-operator//dist/install.yaml -``` - -### By providing a Helm Chart - -1. Build the chart using the optional helm plugin - -```sh -operator-sdk edit --plugins=helm/v1-alpha -``` - -2. See that a chart was generated under 'dist/chart', and users -can obtain this solution from there. - -**NOTE:** If you change the project, you need to update the Helm Chart -using the same command above to sync the latest changes. Furthermore, -if you create webhooks, you need to use the above command with -the '--force' flag and manually ensure that any custom configuration -previously added to 'dist/chart/values.yaml' or 'dist/chart/manager/manager.yaml' -is manually re-applied afterwards. - -## Contributing -// TODO(user): Add detailed information on how you would like others to contribute to this project - -**NOTE:** Run `make help` for more information on all potential `make` targets - -More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html) - ## License Copyright 2026. diff --git a/bundle-hack/update_bundle.sh b/bundle-hack/update_bundle.sh new file mode 100755 index 0000000..5f0ceed --- /dev/null +++ b/bundle-hack/update_bundle.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +CSV_FILE="${CSV_FILE:-/manifests/hyperfleet-operator.clusterserviceversion.yaml}" + +# Update image references in the CSV file using yq +yq eval ' + # Update operator deployment image + (.spec.install.spec.deployments[].spec.template.spec.containers[] | select(.name == "manager") | .image) = strenv(HYPERFLEET_OPERATOR_IMAGE_PULLSPEC) | + + # Update RELATED_IMAGE_HYPERFLEET_API env var + (.spec.install.spec.deployments[].spec.template.spec.containers[] | select(.name == "manager") | .env[] | select(.name == "RELATED_IMAGE_HYPERFLEET_API") | .value) = strenv(HYPERFLEET_API_IMAGE_PULLSPEC) | + + # Update containerImage annotation + .metadata.annotations.containerImage = strenv(HYPERFLEET_OPERATOR_IMAGE_PULLSPEC) | + + # Update relatedImages + .spec.relatedImages = [ + {"name": "hyperfleet-operator", "image": strenv(HYPERFLEET_OPERATOR_IMAGE_PULLSPEC)}, + {"name": "hyperfleet-api", "image": strenv(HYPERFLEET_API_IMAGE_PULLSPEC)} + ] +' -i "${CSV_FILE}" + +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..a888235 --- /dev/null +++ b/bundle.konflux.Dockerfile @@ -0,0 +1,51 @@ +# 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 +# Konflux nudges update these variables with the latest digest-pinned pullspecs. +ARG HYPERFLEET_OPERATOR_IMAGE_PULLSPEC="quay.io/redhat-services-prod/hyperfleet-tenant/hyperfleet/hyperfleet-operator@sha256:placeholder" +ENV HYPERFLEET_OPERATOR_IMAGE_PULLSPEC=${HYPERFLEET_OPERATOR_IMAGE_PULLSPEC} + +ARG HYPERFLEET_API_IMAGE_PULLSPEC="quay.io/redhat-services-prod/hyperfleet-tenant/hyperfleet/hyperfleet-api@sha256:8533d0d875480f31f5112e454659a095a5d2e993c139a9045a06be6b67b829ca" +ENV HYPERFLEET_API_IMAGE_PULLSPEC=${HYPERFLEET_API_IMAGE_PULLSPEC} + +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/ + + +ARG APP_VERSION="0.0.0-dev" +LABEL name="hyperfleet-operator-bundle" \ + vendor="Red Hat, Inc." \ + version="${APP_VERSION}" \ + summary="OLM bundle for the HyperFleet Operator" \ + description="OLM bundle for the HyperFleet Operator, which installs and manages HyperFleet." 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..2654e73 --- /dev/null +++ b/bundle/manifests/hyperfleet-operator.clusterserviceversion.yaml @@ -0,0 +1,271 @@ +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: Basic Install + createdAt: "2026-09-01T18:11:57Z" + 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/redhat-services-prod/hyperfleet-tenant/hyperfleet/hyperfleet-api:latest + image: quay.io/redhat-services-prod/hyperfleet-tenant/hyperfleet/hyperfleet-operator:latest + 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 + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch + 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/redhat-services-prod/hyperfleet-tenant/hyperfleet/hyperfleet-api:latest + 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..18a37a1 --- /dev/null +++ b/bundle/manifests/hyperfleet.redhat.com_hyperfleetconfigs.yaml @@ -0,0 +1,304 @@ +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() != '' + jwkCertSecretRef: + description: |- + jwkCertSecretRef optionally references a Secret holding the JWKS document, + for air-gapped or private environments where the API cannot reach a JWKS + URL. The Secret must provide the key "jwks.json" containing a JSON Web Key + Set (the format the API parses; see HYPERFLEET-1408). When unset, the + operator derives the JWKS URL from the issuer via OIDC discovery + ({issuer}/.well-known/openid-configuration → jwks_uri). + 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 + 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..93a768e 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -1,2 +1,40 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + resources: - manager.yaml + +# Base image configuration for local development +# For production/Konflux builds, digest-pinned images are set via bundle.konflux.Dockerfile ARG overrides +# +# Local development override: +# make bundle-override-img IMG=quay.io//hyperfleet-operator:dev- +# make build-deployer-override-img IMG=quay.io//hyperfleet-operator:dev- +# +# NOTE: These commands modify this file in place. Restore before committing: +# git checkout config/manager/kustomization.yaml +images: +- name: controller + newName: quay.io/redhat-services-prod/hyperfleet-tenant/hyperfleet/hyperfleet-operator + newTag: latest + +# RELATED_IMAGE_HYPERFLEET_API environment variable +# Sets the image used for the API operand. +# +# IMPORTANT: Must stay in sync with api.DefaultImage in the operator code +# (v0.4.0+: config schema compatibility plus HYPERFLEET-1603 database *_FILE support) +# +# For local development with a custom API image, manually edit the value below +# or use yq to update programmatically, then restore before committing. +# +# Production/Konflux: Digest-pinned via bundle.konflux.Dockerfile ARG overrides +patches: +- patch: |- + - op: add + path: /spec/template/spec/containers/0/env/- + value: + name: RELATED_IMAGE_HYPERFLEET_API + value: quay.io/redhat-services-prod/hyperfleet-tenant/hyperfleet/hyperfleet-api:latest + target: + kind: Deployment + name: controller-manager diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 069e881..a8dd370 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -72,16 +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. Must stay in - # sync with api.DefaultImage (0.4.0+: config schema compatibility plus - # HYPERFLEET-1603 database *_FILE support, published to the - # redhat-services-prod registry via Konflux Release, not - # openshift-hyperfleet which stopped receiving pushes after v0.2.1) — - # see that constant's comment for why. - - name: RELATED_IMAGE_HYPERFLEET_API - value: quay.io/redhat-services-prod/hyperfleet-tenant/hyperfleet/hyperfleet-api:0.4.0 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..905049c --- /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: Basic Install + 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 diff --git a/docs/bundle.md b/docs/bundle.md new file mode 100644 index 0000000..99587ba --- /dev/null +++ b/docs/bundle.md @@ -0,0 +1,106 @@ +## Pre-merge checks +1. Updates to bundle.Dockerfile are also reflected in bundle.konflux.Dockerfile +2. bundle/ is correctly updated before merging +3. config/manager/kustomization.yaml is not wrongly updated + +## CI Installation + +Once Konflux is in place, the CI pipeline will automatically handle bundling building with operator image updates: + +1. Konflux builds the operator image and publishes it to quay.io/redhat-services-prod/hyperfleet-tenant/hyperfleet/hyperfleet-operator +2. The operator-bundle .tekton pipeline will be triggered by any update to the bundle.konflux.Dockerfile +2. `bundle.konflux.Dockerfile` runs `update_bundle.sh` with the new operator image reference +3. `update_bundle.sh` uses yq to update the CSV to ensure the operator deployment has proper values - image, relatedImages, annotations, etc. +4. Publishes the operator-bundle to quay.io/redhat-services-prod/hyperfleet-tenant/hyperfleet/hyperfleet-operator-bundle + +(HYPERFLEET-1411: TODO add more information once the konflux pipelines are in place) + +## Development Installation + +### Prerequisite steps +For local development and installation, set your Quay username to automatically configure image paths: + +```bash +# Set your Quay username (required for image-dev) +export QUAY_USER= +# Checkout dev branch +git checkout -b + +# Build and push dev image +make image-dev +# With default values - pushes to: quay.io/$QUAY_USER/hyperfleet-operator:dev- +export IMG=quay.io/$QUAY_USER/hyperfleet-operator:dev- +# export IMG so that it can be properly picked up for bundle generation +``` + +**Image path defaults:** +- IMG (hyperfleet-operator image): `quay.io/$QUAY_USER/hyperfleet-operator:dev-` (defaults `make image-dev`) +- BUNDLE_IMG (hyperfleet-operator-bundle): `quay.io/$QUAY_USER/hyperfleet-operator-bundle:v$(VERSION)` (default VERSION=0.0.1) + + +### OLM Installation +Testing hyperfleet-operator installation with OLM + +**Note:** Ensure `IMG` is properly exported before running these commands + +1. **Update bundle with operator image:** - WARNING restore changes once done testing! + ```bash + make bundle-override-img + # Updates bundle/ manifests with the operator image from step 2 + # Alternative: manually edit config/manager/kustomization.yaml + # Regenerates bundle.Dockerfile + bundle/ and override config/manager/kustomization.yaml + ``` + +2. **Build and push bundle image:** + ```bash + make bundle-build + make bundle-push + # Pushes to: quay.io/$QUAY_USER/hyperfleet-operator-bundle:v$(VERSION) + # To override: make bundle-build VERSION=0.0.2 BUNDLE_IMG= + ``` + +3. **Quick testing on a k8s cluster:** + ```bash + export BUNDLE_IMG=quay.io/$QUAY_USER/hyperfleet-operator-bundle:v$(VERSION) + # Install Operator Lifecycle Manager in your cluster + operator-sdk olm install + + # Install operator from bundle (note: bundle image uses v prefix) + operator-sdk run bundle $(BUNDLE_IMG) -n + + # Cleanup when done - IMPORTANT: Delete CRs before uninstalling operator + # 1. Export and delete the cluster-scoped HyperFleetConfig CR + kubectl get hyperfleetconfig -o yaml > hyperfleetconfig-backup.yaml + kubectl delete hyperfleetconfig --all + + # 2. Clean up operator (removes CRDs and controller) + operator-sdk cleanup hyperfleet-operator -n + + # 3. Uninstall Operator Lifecycle Manager from your cluster + operator-sdk olm uninstall + ``` + + +### Non-OLM Installation +Testing hyperfleet-operator installation without OLM (kubectl apply) + +**Note:** Ensure `IMG` is properly exported before running these commands +1. **Quick testing on a k8s cluster:** + ```bash + export IMG="quay.io/$QUAY_USER/hyperfleet-operator:dev-" + make deploy + # Generates: dist/install.yaml + # Again, make sure to restore config/manager/kustomization.yaml after testing + # Check status to see that everything installed properly + + # Cleanup - IMPORTANT: Delete CRs before uninstalling operator + # 1. Export and delete the cluster-scoped HyperFleetConfig CR + kubectl get hyperfleetconfig -o yaml > hyperfleetconfig-backup.yaml + kubectl delete hyperfleetconfig --all + + # 2. Undeploy operator (removes CRDs and controller) + make undeploy + # Or manually: kubectl delete -f dist/install.yaml + ``` + +**Note:** `bundle-override-img` and `build-deployer-override-img` modify config/manager/kustomization.yaml in place. So before committing any changes make sure to revert these changes. Additionally when running `bundle-override-img` the bundle/ and bundle.Dockerfile get regenerated in place, so make sure to check these changes before committing them.