diff --git a/components/utilities/approve-installplan/README.md b/components/utilities/approve-installplan/README.md index 2e7c660..6dfb17b 100644 --- a/components/utilities/approve-installplan/README.md +++ b/components/utilities/approve-installplan/README.md @@ -25,3 +25,25 @@ 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. + +## Version-gated approval + +When the `openstack` Subscription has `spec.startingCSV` set (as done by the `pin-version` +component), the Job only approves InstallPlans whose `spec.clusterServiceVersionNames` contains +the pinned CSV. If OLM resolves to a different (typically newer) version, the Job fails immediately +with a clear error showing expected vs. actual CSV names. + +When `spec.startingCSV` is not set, the Job falls back to approving the first unapproved +InstallPlan it finds (original behavior). + +## Environment variables + +| Variable | Default | Description | +| -------- | ------- | ----------- | +| `OS_OPERATORS_NAMESPACE` | `openstack-operators` | Namespace where operators are installed | +| `OS_NAMESPACE` | `openstack` | Namespace where the OpenStack control plane lives | +| `SUBSCRIPTION_NAME` | `openstack` | OLM Subscription name to read `startingCSV` from | +| `RETRIES` | `30` | Number of polling attempts for most checks | +| `DELAY` | `10` | Seconds between retries | +| `INSTALL_PLAN_RETRIES` | `60` | Retries for InstallPlan completion (longer due to image pulls) | +| `DEBUG` | `true` | Enable bash debug tracing (`set -x`) | diff --git a/components/utilities/approve-installplan/job.yaml b/components/utilities/approve-installplan/job.yaml index e5348c4..22e10e2 100644 --- a/components/utilities/approve-installplan/job.yaml +++ b/components/utilities/approve-installplan/job.yaml @@ -43,6 +43,8 @@ spec: value: "60" - name: DEBUG value: "true" + - name: SUBSCRIPTION_NAME + value: "openstack" command: - /bin/bash - -c @@ -63,6 +65,8 @@ spec: INSTALL_PLAN_RETRIES=${INSTALL_PLAN_RETRIES:-60} # Delay in seconds between retries. DELAY=${DELAY:-10} + # Name of the OLM Subscription to read startingCSV from. + SUBSCRIPTION_NAME=${SUBSCRIPTION_NAME:-"openstack"} # --- log() { @@ -70,11 +74,46 @@ spec: } find_and_approve_installplan() { + local pinned_csv="" + pinned_csv=$(oc get subscription "${SUBSCRIPTION_NAME}" \ + -n "${OS_OPERATORS_NAMESPACE}" \ + -o jsonpath='{.spec.startingCSV}' 2>/dev/null) || true + + if [ -n "$pinned_csv" ]; then + log "Subscription '${SUBSCRIPTION_NAME}' pins startingCSV: ${pinned_csv}" + else + log "Subscription '${SUBSCRIPTION_NAME}' has no startingCSV; will approve any unapproved InstallPlan." + fi + log "Waiting for unapproved InstallPlan..." + local install_plan_name="" 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 + if [ -n "$pinned_csv" ]; then + install_plan_name=$(oc get installplan -n "${OS_OPERATORS_NAMESPACE}" -o json | \ + jq -r --arg csv "$pinned_csv" \ + '.items[] | select(.spec.approval=="Manual" and .spec.approved==false and (.spec.clusterServiceVersionNames | index($csv))) | .metadata.name' | head -n1) || true + + if [ -z "$install_plan_name" ]; then + local wrong_plan="" + wrong_plan=$(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 + + if [ -n "$wrong_plan" ]; then + local actual_csvs="" + actual_csvs=$(oc get installplan "${wrong_plan}" -n "${OS_OPERATORS_NAMESPACE}" -o json | \ + jq -r '.spec.clusterServiceVersionNames | join(", ")') || true + log "ERROR: InstallPlan '${wrong_plan}' targets CSV [${actual_csvs}], not pinned '${pinned_csv}'." + log "ERROR: The catalog resolved a newer version than what is pinned. Refusing to approve." + exit 1 + fi + fi + else + 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 + fi + [ -n "$install_plan_name" ] && break - log "Attempt $i/$RETRIES: No InstallPlan found, retrying..." + log "Attempt $i/$RETRIES: No matching InstallPlan found, retrying..." sleep $DELAY done @@ -83,8 +122,8 @@ spec: 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 + 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" } @@ -164,11 +203,12 @@ spec: 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." + deployed_version=$(oc get openstackversion -n $OS_NAMESPACE -o jsonpath='{.items[0].status.deployedVersion}' 2>/dev/null) || true + if [ -n "$deployed_version" ]; then + log "Deployed version $deployed_version found. This is an update." wait_for_new_version else - log "OpenStack CR 'openstack' does not exist. This is an initial installation." + log "No deployed version found. This is an initial installation." wait_for_crd fi diff --git a/components/utilities/approve-installplan/rbac.yaml b/components/utilities/approve-installplan/rbac.yaml index 0e256b8..723fd75 100644 --- a/components/utilities/approve-installplan/rbac.yaml +++ b/components/utilities/approve-installplan/rbac.yaml @@ -14,6 +14,13 @@ rules: - get - list - patch + - apiGroups: + - operators.coreos.com + resources: + - subscriptions + verbs: + - get + - list - apiGroups: - operator.openstack.org resources: