diff --git a/components/utilities/README.md b/components/utilities/README.md new file mode 100644 index 0000000..14385be --- /dev/null +++ b/components/utilities/README.md @@ -0,0 +1,8 @@ +# Utilities + +Shared Kustomize `Component` helpers for GitOps overlays. Add new utilities as subdirectories +here (each subdirectory should contain a `kustomization.yaml` with `kind: Component`). + +| Component | Role | +| --------- | ---- | +| [approve-installplan](./approve-installplan/) | RBAC, `Job`, and Subscription sync-wave patch for Manual OLM InstallPlan approval (OpenStack operator subscription from `architecture` `olm-openstack-subscriptions`). | diff --git a/components/utilities/approve-installplan/README.md b/components/utilities/approve-installplan/README.md new file mode 100644 index 0000000..6064f17 --- /dev/null +++ b/components/utilities/approve-installplan/README.md @@ -0,0 +1,27 @@ +# approve-installplan + +Kustomize `Component` that adds: + +- `ClusterRole` / `ClusterRoleBinding` so the OpenShift GitOps Argo CD application controller can patch `InstallPlan` objects and read related OpenStack APIs used by the Job script. +- `Job` `approve-openstack-installplan` (sync-wave `1`) that approves a Manual InstallPlan and waits for operator install. +- JSON6902 patch on `Subscription` named `openstack` (sync-wave `0`), matching + [`architecture` `olm-openstack-subscriptions`](https://github.com/openstack-k8s-operators/architecture/tree/main/lib/olm-openstack-subscriptions). + +## Usage + +List this directory under `components` **after** the overlay that emits the `Subscription` (for +example the remote `lib/olm-openstack-subscriptions/overlays/default` component). If this component +runs first, the Subscription sync-wave patch will not apply. + +```yaml +components: + - pin-version + - https://github.com/openstack-k8s-operators/architecture/lib/olm-openstack-subscriptions/overlays/default?ref=COMMIT_SHA + - https://github.com/openstack-k8s-operators/gitops/components/utilities/approve-installplan?ref=v0.1.0 +``` + +Images: `registry.redhat.io/openshift4/ose-tools-rhel9:latest` (Job). + +In-tree overlays may reference this component with a local path until a release tag that contains +`components/utilities/approve-installplan` is published; use the HTTPS URL above for reproducible +builds outside the repository. diff --git a/components/utilities/approve-installplan/job.yaml b/components/utilities/approve-installplan/job.yaml new file mode 100644 index 0000000..d4957c0 --- /dev/null +++ b/components/utilities/approve-installplan/job.yaml @@ -0,0 +1,160 @@ +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: approve-openstack-installplan + namespace: openshift-gitops + annotations: + argocd.argoproj.io/sync-wave: "1" +spec: + backoffLimit: 1 + template: + spec: + containers: + - name: installplan-approver + image: registry.redhat.io/openshift4/ose-tools-rhel9:latest + env: + - name: OS_OPERATORS_NAMESPACE + value: "openstack-operators" + - name: OS_NAMESPACE + value: "openstack" + - name: RETRIES + value: "30" + - name: DELAY + value: "10" + - name: INSTALL_PLAN_RETRIES + value: "60" + - name: DEBUG + value: "true" + command: + - /bin/bash + - -c + - | + set -eo pipefail + if [ "${DEBUG}" = "true" ]; then + set -x + fi + # --- Configuration with defaults --- + # Namespace where the OpenStack operators are installed. + OS_OPERATORS_NAMESPACE=${OS_OPERATORS_NAMESPACE:-"openstack-operators"} + # Namespace where the OpenStack control plane is deployed and the OpenStackVersion CR exists. + OS_NAMESPACE=${OS_NAMESPACE:-"openstack"} + # Default number of retries for most checks. + RETRIES=${RETRIES:-60} + # Number of retries specifically for waiting for the InstallPlan to complete. + # This is longer because pulling operator images and starting pods can take a significant amount of time. + INSTALL_PLAN_RETRIES=${INSTALL_PLAN_RETRIES:-60} + # Delay in seconds between retries. + DELAY=${DELAY:-10} + # --- + + log() { + echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) $@" >&2 + } + + find_and_approve_installplan() { + log "Waiting for unapproved InstallPlan..." + for i in $(seq 1 $RETRIES); do + install_plan_name=$(oc get installplan -n $OS_OPERATORS_NAMESPACE -o json | jq -r '.items[] | select(.spec.approval=="Manual" and .spec.approved==false) | .metadata.name' | head -n1) || true + [ -n "$install_plan_name" ] && break + log "Attempt $i/$RETRIES: No InstallPlan found, retrying..." + sleep $DELAY + done + + if [ -z "$install_plan_name" ]; then + log "Error: No manual InstallPlan found to approve within the time limit." + exit 1 + fi + + log "Found InstallPlan: $install_plan_name, approving..." + oc patch installplan $install_plan_name -n $OS_OPERATORS_NAMESPACE --type merge -p '{"spec":{"approved":true}}' >&2 + echo "$install_plan_name" + } + + wait_for_installplan_completion() { + local install_plan_name=$1 + for i in $(seq 1 $INSTALL_PLAN_RETRIES); do + phase=$(oc get installplan $install_plan_name -n $OS_OPERATORS_NAMESPACE -o jsonpath='{.status.phase}') + if [ "$phase" == "Complete" ]; then + log "InstallPlan completed successfully." + return 0 + fi + if [ $i -eq $INSTALL_PLAN_RETRIES ]; then + log "Error: InstallPlan did not complete in time." + exit 1 + fi + log "Attempt $i/$INSTALL_PLAN_RETRIES: Status is $phase, retrying..." + sleep $DELAY + done + } + + wait_for_crd() { + log "Waiting for OpenStack CRD to become available..." + for i in $(seq 1 $RETRIES); do + if oc get crd openstacks.operator.openstack.org -o jsonpath='{.status.conditions[?(@.type=="Established")].status}' | grep -q True; then + log "OpenStack CRD is Established." + return 0 + fi + if [ $i -eq $RETRIES ]; then + log "Error: OpenStack CRD did not become Established in time." + exit 1 + fi + log "Attempt $i/$RETRIES: CRD not Established, retrying..." + sleep $DELAY + done + } + + wait_for_new_version() { + log "Waiting for a new OpenStack version to become available..." + + local deployed_version="" + for i in $(seq 1 $RETRIES); do + # There should be only one openstackversion CR + deployed_version=$(oc get openstackversion -n $OS_NAMESPACE -o jsonpath='{.items[0].status.deployedVersion}' 2>/dev/null) || true + if [ -n "$deployed_version" ]; then + log "Current deployed version is: $deployed_version" + break + fi + if [ $i -eq $RETRIES ]; then + log "Error: Could not get current deployed version in time." + exit 1 + fi + log "Attempt $i/$RETRIES: Waiting for OpenStackVersion resource to get deployedVersion. Retrying..." + sleep $DELAY + done + + for i in $(seq 1 $RETRIES); do + available_version=$(oc get openstackversion -n $OS_NAMESPACE -o jsonpath='{.items[0].status.availableVersion}' 2>/dev/null) || true + if [ -n "$available_version" ] && [ "$available_version" != "$deployed_version" ]; then + log "New available version found: $available_version" + return 0 + fi + if [ $i -eq $RETRIES ]; then + log "Error: New version did not become available in time." + exit 1 + fi + log "Attempt $i/$RETRIES: No new version available yet. Current available is '$available_version'. Retrying..." + sleep $DELAY + done + } + + # --- Main execution --- + log "Starting install_plan_approval" + install_plan_name=$(find_and_approve_installplan) + + log "Waiting for InstallPlan '$install_plan_name' to complete..." + + wait_for_installplan_completion "$install_plan_name" + + log "Checking if this is an initial installation or an update..." + if oc get openstack openstack -n $OS_OPERATORS_NAMESPACE; then + log "OpenStack CR 'openstack' exists. This is an update." + wait_for_new_version + else + log "OpenStack CR 'openstack' does not exist. This is an initial installation." + wait_for_crd + fi + + log "Job completed successfully." + restartPolicy: Never + serviceAccountName: openshift-gitops-argocd-application-controller diff --git a/components/utilities/approve-installplan/kustomization.yaml b/components/utilities/approve-installplan/kustomization.yaml new file mode 100644 index 0000000..8b0c57b --- /dev/null +++ b/components/utilities/approve-installplan/kustomization.yaml @@ -0,0 +1,16 @@ +--- +# Apply this component after the Subscription exists in the build (e.g. after +# openstack-k8s-operators/architecture lib/olm-openstack-subscriptions). +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +resources: + - job.yaml + - rbac.yaml +patches: + - target: + kind: Subscription + name: openstack + patch: |- + - op: add + path: /metadata/annotations/argocd.argoproj.io~1sync-wave + value: "0" diff --git a/components/utilities/approve-installplan/rbac.yaml b/components/utilities/approve-installplan/rbac.yaml new file mode 100644 index 0000000..0e256b8 --- /dev/null +++ b/components/utilities/approve-installplan/rbac.yaml @@ -0,0 +1,51 @@ +--- +# ClusterRole and binding so the OpenShift GitOps Argo CD application controller +# can approve OLM InstallPlans and read resources used by the approval Job. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: installplan-approver-role +rules: + - apiGroups: + - operators.coreos.com + resources: + - installplans + verbs: + - get + - list + - patch + - apiGroups: + - operator.openstack.org + resources: + - openstacks + verbs: + - get + - list + - apiGroups: + - core.openstack.org + resources: + - openstackversions + verbs: + - get + - list + - patch + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - get + - list +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: installplan-approver-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: installplan-approver-role +subjects: + - kind: ServiceAccount + name: openshift-gitops-argocd-application-controller + namespace: openshift-gitops diff --git a/example/README.md b/example/README.md index 8065e6b..ad8ea57 100644 --- a/example/README.md +++ b/example/README.md @@ -11,7 +11,7 @@ where the overlay includes `components/argocd/annotations`). | Directory | Role | | --------- | ---- | -| [openstack-operator](./openstack-operator/) | Foundational OpenStack operators, namespaces, subscription, install-plan approval (placeholder until a component exists). | +| [openstack-operator](./openstack-operator/) | Foundational OpenStack operators: CDN catalog + pinned CSV, OLM subscription from `architecture` `olm-openstack-subscriptions`, InstallPlan approval Job, RBAC. | | [openstack-operator-cr](./openstack-operator-cr/) | Main OpenStack custom resource (placeholder until a component exists). | | [openstack-networks](./openstack-networks/) | Underlying networks: NNCP, NAD, NetConfig, MetalLB pools / advertisements, etc. | | [openstack-controlplane](./openstack-controlplane/) | `OpenStackControlPlane` and optional watcher service (networking is a separate overlay). | diff --git a/example/openstack-operator/README.md b/example/openstack-operator/README.md index d8208e0..08f8542 100644 --- a/example/openstack-operator/README.md +++ b/example/openstack-operator/README.md @@ -1,10 +1,53 @@ # openstack-operator -Placeholder overlay for the **openstack-operator** application described in the root -`README.md` (*Application responsibilities and content*). +Sample Kustomize overlay for the **openstack-operator** application described in the repository root +`README.md` under *Application responsibilities and content*. -There is no matching component under `components/rhoso/` yet. Replace this directory's -`kustomization.yaml` with real `components` / `resources` when a slice is added. +It installs the OpenStack operator from the **Red Hat Operator Hub** (`redhat-operators` catalog): +namespaces, `OperatorGroup`, `CatalogSource`, `Subscription` with a pinned `startingCSV`, Manual +InstallPlan approval, a post-sync `Job` that approves the InstallPlan, and RBAC for the OpenShift +GitOps application controller. The OLM manifests are composed from the +[`openstack-k8s-operators/architecture`](https://github.com/openstack-k8s-operators/architecture) +`lib/olm-openstack-subscriptions` component; local pieces supply CDN catalog settings and the CSV +pin. -Upstream context: Red Hat documentation *Installing and preparing the OpenStack -operator* (namespaces, subscription, install-plan approval job). +See Red Hat documentation: +[Installing and preparing the OpenStack operator](https://docs.redhat.com/en/documentation/red_hat_openstack_services_on_openshift/18.0/html/deploying_red_hat_openstack_services_on_openshift/assembly_installing-and-preparing-the-openstack-operator). + +## Layout + +| Path | Role | +| ---- | ---- | +| `catalog/values.yaml` | `ConfigMap` `olm-values` (channel, `redhat-operators`, index image, Manual approval) | +| `pin-version/` | Adds `data.openstack-operator-version` for `spec.startingCSV` | +| Remote component | `architecture` `olm-openstack-subscriptions/overlays/default` (pinned git ref in `kustomization.yaml`) | +| [approve-installplan](https://github.com/openstack-k8s-operators/gitops/tree/v0.1.0/components/utilities/approve-installplan) (`?ref=v0.1.0`) | Shared utility: RBAC, InstallPlan approval `Job`, Subscription sync-wave | + +Component order in `kustomization.yaml` matters: the version pin runs before the remote OLM +component; the approval `Component` runs after it so the Subscription exists when the sync-wave patch +is applied. + +## Render + +From the repository root (network access is required the first time to fetch the remote component): + +```bash +kustomize build example/openstack-operator +``` + +## Tunables + +- **`registry.redhat.io/redhat/redhat-operator-index` tag** in `catalog/values.yaml` (`openstack-operator-image`): + use an index tag that matches your OpenShift cluster version (for example `v4.18` on OpenShift 4.18). +- **`openstack-operator.v1.0.16`** in `pin-version/patch.yaml`: adjust the CSV string when you target + a different RHOSO release available in the catalog. +- **Remote `architecture` git ref** in `kustomization.yaml`: pinned to a commit SHA for reproducible + builds; bump when you intentionally adopt newer `lib/olm-openstack-subscriptions` behavior. + +Clusters must be able to pull images from `registry.redhat.io` (pull secret / global pull secret). + +## Related + +- [`components/utilities/`](../../components/utilities/) for shared helper components (including InstallPlan approval). +- `openstack-operator-cr` example: main `OpenStack` CR (separate overlay). +- `example/dependencies` for other OLM-related dependencies (MetalLB, NMState, cert-manager, etc.). diff --git a/example/openstack-operator/catalog/kustomization.yaml b/example/openstack-operator/catalog/kustomization.yaml new file mode 100644 index 0000000..58bd586 --- /dev/null +++ b/example/openstack-operator/catalog/kustomization.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - values.yaml diff --git a/example/openstack-operator/catalog/values.yaml b/example/openstack-operator/catalog/values.yaml new file mode 100644 index 0000000..33b65bf --- /dev/null +++ b/example/openstack-operator/catalog/values.yaml @@ -0,0 +1,18 @@ +# local-config: referenced, but not emitted by kustomize +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: olm-values + annotations: + config.kubernetes.io/local-config: 'true' +data: + openstack-operator-subscription-namespace: "openstack-operators" + openstack-operator-channel: "stable-v1.0" + openstack-operator-installplanapproval: "Manual" + # --- CatalogSource Settings --- + openstack-operator-catalog-source: "redhat-operators" + openstack-catalog-namespace: "openshift-marketplace" + openstack-operator-image: "registry.redhat.io/redhat/redhat-operator-index:v4.18" + openstack-operator-display-name: "Red Hat Operators" + openstack-operator-publisher: "Red Hat" diff --git a/example/openstack-operator/kustomization.yaml b/example/openstack-operator/kustomization.yaml index bc75526..73be201 100644 --- a/example/openstack-operator/kustomization.yaml +++ b/example/openstack-operator/kustomization.yaml @@ -2,12 +2,15 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: [] +resources: + - catalog -# No gitops component yet for this slice. See README.md. -# Future: add components (remote refs and local paths), for example: -# components: -# - https://github.com/openstack-k8s-operators/gitops/components/argocd/annotations?ref=TAG -# - https://github.com/openstack-k8s-operators/gitops/components/rhoso/...?ref=TAG -# Local paths (for "kustomize build" testing): -# - ../../components/argocd/annotations +# pin-version must run before the remote OLM component (olm-values feeds replacements). +# approve-installplan must run after the remote component so the Subscription exists for the sync-wave patch. +components: + - pin-version + - https://github.com/openstack-k8s-operators/architecture/lib/olm-openstack-subscriptions/overlays/default?ref=7da5f2e1dc2bfce99e269b0017783679ca405d8c + # TODO: replace the local path with a git HTTPS URI, e.g. + # https://github.com/openstack-k8s-operators/gitops/components/utilities/approve-installplan?ref=v0.1.0 + # once that tag includes this component (same pattern as other example overlays). + - ../../components/utilities/approve-installplan diff --git a/example/openstack-operator/pin-version/kustomization.yaml b/example/openstack-operator/pin-version/kustomization.yaml new file mode 100644 index 0000000..1011495 --- /dev/null +++ b/example/openstack-operator/pin-version/kustomization.yaml @@ -0,0 +1,9 @@ +--- +# Patches olm-values with a pinned ClusterServiceVersion for spec.startingCSV on the Subscription. +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +patches: + - path: patch.yaml + target: + kind: ConfigMap + name: olm-values diff --git a/example/openstack-operator/pin-version/patch.yaml b/example/openstack-operator/pin-version/patch.yaml new file mode 100644 index 0000000..16e1b9c --- /dev/null +++ b/example/openstack-operator/pin-version/patch.yaml @@ -0,0 +1,4 @@ +--- +- op: add + path: /data/openstack-operator-version + value: "openstack-operator.v1.0.16"