Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions components/utilities/approve-installplan/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`) |
54 changes: 47 additions & 7 deletions components/utilities/approve-installplan/job.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ spec:
value: "60"
- name: DEBUG
value: "true"
- name: SUBSCRIPTION_NAME
value: "openstack"
command:
- /bin/bash
- -c
Expand All @@ -63,18 +65,55 @@ 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() {
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) $@" >&2
}

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

Expand All @@ -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"
}

Expand Down Expand Up @@ -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

Expand Down
7 changes: 7 additions & 0 deletions components/utilities/approve-installplan/rbac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ rules:
- get
- list
- patch
- apiGroups:
- operators.coreos.com
resources:
- subscriptions
verbs:
- get
- list
- apiGroups:
- operator.openstack.org
resources:
Expand Down