Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,369 @@ if [[ -n "${AAP_SOURCE_SHA}" ]]; then
fi

# --- Phase 5: refresh ---
# Patched copies of prepare-aap.sh and refresh-after-snapshot.sh.
# Each is IDENTICAL to osac-installer main, with changes wrapped in
# BEGIN CHANGE / END CHANGE comments.

cat > /tmp/prepare-aap-patched.sh << 'PREPARE_AAP_EOF'
#!/usr/bin/env bash

set -o nounset
set -o errexit
set -o pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/lib.sh"

INSTALLER_KUSTOMIZE_OVERLAY=${INSTALLER_KUSTOMIZE_OVERLAY:-"development"}
INSTALLER_NAMESPACE=${INSTALLER_NAMESPACE:-$(grep "^namespace:" "overlays/${INSTALLER_KUSTOMIZE_OVERLAY}/kustomization.yaml" | awk '{print $2}')}
[[ -z "${INSTALLER_NAMESPACE}" ]] && echo "ERROR: Could not determine namespace from overlays/${INSTALLER_KUSTOMIZE_OVERLAY}/kustomization.yaml" && exit 1

# Get the AAP gateway route URL
AAP_ROUTE_HOST=$(oc get routes -n "${INSTALLER_NAMESPACE}" --no-headers osac-aap -o jsonpath='{.spec.host}')
AAP_URL="https://${AAP_ROUTE_HOST}"

# Get the AAP admin password
AAP_ADMIN_PASSWORD=$(oc get secret osac-aap-admin-password -n ${INSTALLER_NAMESPACE} -o jsonpath='{.data.password}' | base64 -d)

########## BEGIN CHANGE ##########
# Capture curl response so we see what AAP returns on failure instead of a jq parse error
AAP_RESPONSE=$(curl -sk -X POST \
-u "admin:${AAP_ADMIN_PASSWORD}" \
-H "Content-Type: application/json" \
-d '{"description": "osac-operator", "scope": "write"}' \
"${AAP_URL}/api/gateway/v1/tokens/")
AAP_TOKEN=$(echo "${AAP_RESPONSE}" | jq -r '.token') || {
echo "ERROR: AAP gateway returned non-JSON response: ${AAP_RESPONSE:0:500}"
exit 1
}

if [[ -z "${AAP_TOKEN}" || "${AAP_TOKEN}" == "null" ]]; then
echo "Failed to create AAP API token. Response: ${AAP_RESPONSE:0:500}"
exit 1
fi
########## END CHANGE ##########

# Store the token in a Kubernetes secret
oc create secret generic osac-aap-api-token \
--from-literal=token="${AAP_TOKEN}" \
-n ${INSTALLER_NAMESPACE} \
--dry-run=client -o yaml | oc apply -f -

# Set the correct AAP URL on the operator deployment (triggers rollout)
oc set env deployment/osac-operator-controller-manager \
-n ${INSTALLER_NAMESPACE} \
OSAC_AAP_URL="${AAP_URL}/api/controller"

echo "AAP API token created and stored in secret osac-aap-api-token"
PREPARE_AAP_EOF
chmod +x /tmp/prepare-aap-patched.sh

cat > /tmp/refresh-patched.sh << 'REFRESH_EOF'
#!/usr/bin/env bash

set -o nounset
set -o errexit
set -o pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/lib.sh"

INSTALLER_KUSTOMIZE_OVERLAY=${INSTALLER_KUSTOMIZE_OVERLAY:-"development"}
INSTALLER_NAMESPACE=${INSTALLER_NAMESPACE:-$(grep "^namespace:" "overlays/${INSTALLER_KUSTOMIZE_OVERLAY}/kustomization.yaml" | awk '{print $2}')}
[[ -z "${INSTALLER_NAMESPACE}" ]] && echo "ERROR: Could not determine namespace from overlays/${INSTALLER_KUSTOMIZE_OVERLAY}/kustomization.yaml" && exit 1
INSTALLER_VM_TEMPLATE=${INSTALLER_VM_TEMPLATE:-}

CLUSTER_DOMAIN=$(oc get ingresses.config/cluster -o jsonpath='{.spec.domain}')
KEYCLOAK_NS="keycloak"
REALM_JSON="prerequisites/keycloak/service/files/realm.json"
REFRESH_START=${SECONDS}

ts() { echo "[$(( SECONDS - REFRESH_START ))s]"; }

echo "=== Refreshing OSAC after snapshot boot ==="
echo "Namespace: ${INSTALLER_NAMESPACE}"
echo "Overlay: ${INSTALLER_KUSTOMIZE_OVERLAY}"
echo "Cluster domain: ${CLUSTER_DOMAIN}"
echo ""

# ── Phase 0: cluster services (parallel, unchanged from original) ────────────

echo "$(ts) Phase 0: Waiting for cluster services to stabilize..."

patch_stale_routes() {
echo " Patching stale routes with new domain..."
for ns in "${INSTALLER_NAMESPACE}" "${KEYCLOAK_NS}"; do
for route in $(oc get routes -n "${ns}" -o jsonpath='{.items[*].metadata.name}' 2>/dev/null); do
OLD_HOST=$(oc get route "${route}" -n "${ns}" -o jsonpath='{.spec.host}')
ROUTE_DOMAIN=$(echo "${OLD_HOST}" | sed "s/^[^.]*\.//")
if [[ "${ROUTE_DOMAIN}" != "${CLUSTER_DOMAIN}" ]]; then
ROUTE_NAME=$(echo "${OLD_HOST}" | sed "s/\.${ROUTE_DOMAIN}$//")
NEW_HOST="${ROUTE_NAME}.${CLUSTER_DOMAIN}"
echo " ${ns}/${route}: ${OLD_HOST} -> ${NEW_HOST}"
retry_command 300 10 oc patch route "${route}" -n "${ns}" --type=merge -p "{\"spec\":{\"host\":\"${NEW_HOST}\"}}"
fi
done
done
}

oc rollout status deploy/trust-manager -n cert-manager --timeout=300s &
pid_tm=$!
oc wait --for=condition=Ready certificate/keycloak-tls -n "${KEYCLOAK_NS}" --timeout=300s &
pid_kc=$!
patch_stale_routes &
pid_rt=$!

failed=0
wait ${pid_tm} || failed=1
wait ${pid_kc} || failed=1
wait ${pid_rt} || failed=1
if (( failed )); then
echo "ERROR: Cluster services did not stabilize within timeout"
exit 1
fi
echo "$(ts) Phase 0 complete: cluster services ready"
echo ""

########## BEGIN CHANGE: Phase 1 — Keycloak and credentials in parallel ##########

echo "$(ts) Phase 1: Keycloak sync + fulfillment credentials (parallel)..."

keycloak_sync() {
echo "$(ts) [1/8] Syncing Keycloak realm..."
NEW_HASH=$(md5sum "${REALM_JSON}" | awk '{print $1}')
OLD_HASH=$(oc get configmap keycloak-realm -n "${KEYCLOAK_NS}" -o jsonpath='{.data.realm\.json}' 2>/dev/null | md5sum | awk '{print $1}')
if [[ "${NEW_HASH}" != "${OLD_HASH}" ]]; then
echo " ConfigMap changed (${OLD_HASH:0:8} -> ${NEW_HASH:0:8}), restarting Keycloak..."
oc create configmap keycloak-realm \
--from-file=realm.json="${REALM_JSON}" \
-n "${KEYCLOAK_NS}" --dry-run=client -o yaml | oc apply -f -
oc rollout restart deploy/keycloak-service -n "${KEYCLOAK_NS}"
oc rollout status deploy/keycloak-service -n "${KEYCLOAK_NS}" --timeout=300s
else
echo " ConfigMap unchanged, skipping Keycloak restart"
fi

KC_URL="https://$(oc get route keycloak -n "${KEYCLOAK_NS}" -o jsonpath='{.spec.host}')"
retry_until 60 5 '[[ "$(curl -sk -o /dev/null -w %{http_code} '"${KC_URL}"'/realms/osac)" == "200" ]]' || {
echo "Timed out waiting for Keycloak"
exit 1
}
KC_ADMIN_TOKEN=$(curl -sk "${KC_URL}/realms/master/protocol/openid-connect/token" \
-d "client_id=admin-cli" -d "username=admin" -d "password=admin" -d "grant_type=password" | jq -r '.access_token')
[[ -n "${KC_ADMIN_TOKEN}" && "${KC_ADMIN_TOKEN}" != "null" ]] || { echo "ERROR: Could not get Keycloak admin token" >&2; exit 1; }

echo " Syncing clients and users via admin API..."
jq -c '.clients[] | select(.protocol == "openid-connect" and .publicClient != true and .bearerOnly != true)' "${REALM_JSON}" | while IFS= read -r CLIENT_JSON; do
CID=$(echo "${CLIENT_JSON}" | jq -r '.clientId')
CLIENT_UUID=$(echo "${CLIENT_JSON}" | jq -r '.id')
HTTP_CODE=$(curl -sk -o /dev/null -w "%{http_code}" -H "Authorization: Bearer ${KC_ADMIN_TOKEN}" "${KC_URL}/admin/realms/osac/clients/${CLIENT_UUID}")
if [[ "${HTTP_CODE}" == "200" ]]; then
curl -sk -X PUT -H "Authorization: Bearer ${KC_ADMIN_TOKEN}" -H "Content-Type: application/json" \
"${KC_URL}/admin/realms/osac/clients/${CLIENT_UUID}" -d "${CLIENT_JSON}" >/dev/null
echo " Updated client: ${CID}"
else
curl -sk -X POST -H "Authorization: Bearer ${KC_ADMIN_TOKEN}" -H "Content-Type: application/json" \
"${KC_URL}/admin/realms/osac/clients" -d "${CLIENT_JSON}" >/dev/null
echo " Created client: ${CID}"
fi
done

jq -c '.users[]?' "${REALM_JSON}" | while IFS= read -r USER_JSON; do
USERNAME=$(echo "${USER_JSON}" | jq -r '.username')
USER_UUID=$(echo "${USER_JSON}" | jq -r '.id')
HTTP_CODE=$(curl -sk -o /dev/null -w "%{http_code}" -H "Authorization: Bearer ${KC_ADMIN_TOKEN}" "${KC_URL}/admin/realms/osac/users/${USER_UUID}")
if [[ "${HTTP_CODE}" == "200" ]]; then
curl -sk -X PUT -H "Authorization: Bearer ${KC_ADMIN_TOKEN}" -H "Content-Type: application/json" \
"${KC_URL}/admin/realms/osac/users/${USER_UUID}" -d "${USER_JSON}" >/dev/null
echo " Updated user: ${USERNAME}"
else
curl -sk -X POST -H "Authorization: Bearer ${KC_ADMIN_TOKEN}" -H "Content-Type: application/json" \
"${KC_URL}/admin/realms/osac/users" -d "${USER_JSON}" >/dev/null
echo " Created user: ${USERNAME}"
fi
done

if [[ -f prerequisites/keycloak/service/password-setup-job.yaml ]]; then
oc delete job keycloak-set-passwords -n "${KEYCLOAK_NS}" --ignore-not-found
oc apply -f prerequisites/keycloak/service/password-setup-job.yaml -n "${KEYCLOAK_NS}"
oc wait --for=condition=Complete job/keycloak-set-passwords -n "${KEYCLOAK_NS}" --timeout=120s
fi
echo "$(ts) [1/8] Keycloak sync complete"
}

create_fulfillment_credentials() {
echo "$(ts) [2/8] Recreating fulfillment controller credentials..."
FC_CLIENT_ID=$(jq -er '.clients[] | select(.serviceAccountsEnabled == true) | .clientId' "${REALM_JSON}")
FC_CLIENT_SECRET=$(jq -er ".clients[] | select(.clientId == \"${FC_CLIENT_ID}\") | .secret // empty" "${REALM_JSON}")
[[ -n "${FC_CLIENT_SECRET}" ]] || { echo "ERROR: Could not resolve secret for ${FC_CLIENT_ID} in realm.json" >&2; exit 1; }
oc delete secret fulfillment-controller-credentials -n "${INSTALLER_NAMESPACE}" --ignore-not-found
oc create secret generic fulfillment-controller-credentials \
--from-literal=client-id="${FC_CLIENT_ID}" \
--from-literal=client-secret="${FC_CLIENT_SECRET}" \
-n "${INSTALLER_NAMESPACE}"
echo "$(ts) [2/8] Credentials created for client: ${FC_CLIENT_ID}"
}

keycloak_sync &
pid_kc_sync=$!
create_fulfillment_credentials &
pid_creds=$!

failed=0
wait ${pid_creds} || failed=1
if (( failed )); then echo "ERROR: Failed to create fulfillment credentials"; exit 1; fi

########## END CHANGE ##########

# ── Phase 2: kustomize apply (needs [2/8] credentials) ──────────────────────

echo "$(ts) [3/8] Applying kustomize overlay..."
oc delete job -n "${INSTALLER_NAMESPACE}" --all --ignore-not-found

########## BEGIN CHANGE: exclude AAP CR and bootstrap job from kustomize apply ##########
# The AnsibleAutomationPlatform CR (aap.yaml) already exists on the cluster from the
# snapshot with the correct spec. Re-applying it via oc apply -k touches its
# resourceVersion, which triggers the AAP operator to start a fresh multi-pass
# reconciliation — causing cascading controller-task rollouts that kill in-flight
# AAP jobs. The CR spec hasn't changed, so skip it.
# Also skip the bootstrap job (job.yaml) — it's unnecessary on snapshot boot and
# races the operator reconciliation.
sed -i '/aap\.yaml/d; /job\.yaml/d' base/osac-aap/config/base/kustomization.yaml
echo "$(ts) [3/8] Excluded aap.yaml and job.yaml from kustomize apply"
########## END CHANGE ##########

oc apply -k "overlays/${INSTALLER_KUSTOMIZE_OVERLAY}"
KUSTOMIZE_APPLY_TIME=${SECONDS}
echo "$(ts) [3/8] Kustomize applied (without AAP CR — no operator reconciliation triggered)"

########## BEGIN CHANGE: Phase 3 — all waits in parallel (including gateway) ##########

echo "$(ts) Phase 3: Waiting for all rollouts + AAP config (parallel)..."

wait_fulfillment_rollouts() {
echo "$(ts) [4/8] Waiting for fulfillment rollouts..."
local pids=()
for deploy in fulfillment-controller fulfillment-grpc-server fulfillment-rest-gateway fulfillment-ingress-proxy; do
oc rollout status "deploy/${deploy}" -n "${INSTALLER_NAMESPACE}" --timeout=300s &
pids+=($!)
done
local failed=0
for pid in "${pids[@]}"; do wait "${pid}" || failed=1; done
if (( failed )); then echo "ERROR: Fulfillment rollout failed"; exit 1; fi
echo "$(ts) [4/8] Fulfillment rollouts complete"
}

apply_aap_configuration() {
echo "$(ts) [5/8] Applying AAP configuration..."
INSTALLER_NAMESPACE="${INSTALLER_NAMESPACE}" \
INSTALLER_KUSTOMIZE_OVERLAY="${INSTALLER_KUSTOMIZE_OVERLAY}" \
./scripts/aap-configuration.sh
echo "$(ts) [5/8] AAP configuration applied"
}

wait_aap_controller() {
echo "$(ts) [6/8] Waiting for AAP controller..."
retry_until 300 10 '[[ "$(oc get automationcontroller osac-aap-controller -n '"${INSTALLER_NAMESPACE}"' -o jsonpath='"'"'{.status.conditions[?(@.type=="Running")].status}'"'"' 2>/dev/null)" == "True" ]]' || {
echo "Timed out waiting for AAP controller to be Running"
exit 1
}
AAP_ROUTE_HOST=$(oc get route osac-aap -n "${INSTALLER_NAMESPACE}" -o jsonpath='{.spec.host}')
retry_until 120 5 '[[ "$(curl -sk -o /dev/null -w %{http_code} https://'"${AAP_ROUTE_HOST}"'/api/gateway/v1/)" == "200" ]]' || {
echo "Timed out waiting for AAP gateway API to respond"
exit 1
}
echo "$(ts) [6/8] AAP controller Running, gateway responding"
}

wait_aap_gateway_rollout() {
echo "$(ts) [NEW] Waiting for AAP gateway deployment rollout..."
oc rollout status deployment/osac-aap-gateway -n "${INSTALLER_NAMESPACE}" --timeout=300s
echo "$(ts) [NEW] AAP gateway rollout complete"
}

wait_fulfillment_rollouts &
pid_fulfill=$!
apply_aap_configuration &
pid_aapconf=$!
wait_aap_controller &
pid_aapwait=$!
wait_aap_gateway_rollout &
pid_gw=$!

failed=0
wait ${pid_fulfill} || failed=1
if (( failed )); then echo "ERROR: Phase 3 fulfillment rollout failed"; exit 1; fi
wait ${pid_aapconf} || { echo "ERROR: Phase 3 AAP configuration failed"; exit 1; }
wait ${pid_aapwait} || { echo "ERROR: Phase 3 AAP controller wait failed"; exit 1; }
wait ${pid_gw} || { echo "ERROR: Phase 3 AAP gateway rollout failed"; exit 1; }

# Also wait for [1/8] Keycloak sync if it's still running
wait ${pid_kc_sync} || { echo "ERROR: Keycloak sync failed"; exit 1; }

oc config set-context --current --namespace="${INSTALLER_NAMESPACE}"

echo "$(ts) Phase 3 complete — all rollouts done, AAP gateway stable"
echo "$(ts) Deployment status after Phase 3:"
oc get deployments -n "${INSTALLER_NAMESPACE}" -o custom-columns=NAME:.metadata.name,READY:.status.readyReplicas,UPDATED:.status.updatedReplicas,AVAILABLE:.status.availableReplicas,GEN:.metadata.generation 2>/dev/null || true

########## END CHANGE ##########

# ── Phase 4: prepare-aap + prepare-fulfillment (sequential — fulfillment needs AAP token) ──

echo "$(ts) [7/8] Configuring AAP access and fulfillment service..."
./scripts/prepare-aap.sh
./scripts/prepare-fulfillment-service.sh
echo "$(ts) [7/8] AAP and fulfillment configured"

# ── Phase 5: restart fulfillment + tenant ────────────────────────────────────

echo "$(ts) [8/8] Restarting fulfillment pods and configuring tenant..."
for deploy in fulfillment-controller fulfillment-grpc-server fulfillment-rest-gateway fulfillment-ingress-proxy; do
oc rollout restart "deploy/${deploy}" -n "${INSTALLER_NAMESPACE}"
done
pids=()
for deploy in fulfillment-controller fulfillment-grpc-server fulfillment-rest-gateway fulfillment-ingress-proxy; do
oc rollout status "deploy/${deploy}" -n "${INSTALLER_NAMESPACE}" --timeout=300s &
pids+=($!)
done
failed=0
for pid in "${pids[@]}"; do wait "${pid}" || failed=1; done
if (( failed )); then echo "ERROR: Fulfillment rollout failed after restart"; exit 1; fi
./scripts/prepare-tenant.sh

########## BEGIN CHANGE: Phase 6 — verify controller-task is stable ##########
# Since we excluded aap.yaml from kustomize apply, the AAP operator should NOT
# have triggered any new reconciliation. Verify that the controller-task deployment
# hasn't changed since Phase 3.
echo "$(ts) [post] Verifying AAP controller-task stability..."
oc rollout status deployment/osac-aap-controller-task -n "${INSTALLER_NAMESPACE}" --timeout=300s

CONTROLLER_TASK_POD=$(oc get pods -n "${INSTALLER_NAMESPACE}" -l app.kubernetes.io/name=osac-aap-controller-task --field-selector=status.phase=Running --no-headers -o custom-columns=NAME:.metadata.name 2>/dev/null | head -1)
POST_GEN=$(oc get deployment/osac-aap-controller-task -n "${INSTALLER_NAMESPACE}" -o jsonpath='{.metadata.generation}' 2>/dev/null)
echo "$(ts) [post] Controller-task pod: ${CONTROLLER_TASK_POD} (generation: ${POST_GEN})"
echo "$(ts) [post] Controller-task checksum annotations:"
oc get pod "${CONTROLLER_TASK_POD}" -n "${INSTALLER_NAMESPACE}" -o jsonpath='{.metadata.annotations}' 2>/dev/null | jq -r 'to_entries[] | select(.key | startswith("checksum-")) | " \(.key): \(.value)"' 2>/dev/null || true
Comment on lines +527 to +528
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="ci-operator/step-registry/osac-project/cluster-tool/boot/osac-project-cluster-tool-boot-commands.sh"

# Show the exact context around the reported lines
nl -ba "$FILE" | sed -n '480,540p'

Repository: openshift/release

Length of output: 104


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="ci-operator/step-registry/osac-project/cluster-tool/boot/osac-project-cluster-tool-boot-commands.sh"

# Extract only the checksum annotation dump line(s)
rg -n "checksum-\"|checksum annotations|Controller-task checksum" "$FILE"

Repository: openshift/release

Length of output: 350


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="ci-operator/step-registry/osac-project/cluster-tool/boot/osac-project-cluster-tool-boot-commands.sh"

# Print the exact lines (no nl dependency)
sed -n '505,525p' "$FILE"

# Show if there are other checksum annotation dumps that use jq (context/pattern)
rg -n "checksum-.*annotations|Controller-task checksum|to_entries\\[]|startswith\\(\"checksum-\"\\)" "$FILE"

Repository: openshift/release

Length of output: 1933


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n --fixed-string "-o jsonpath='{.metadata.annotations}'" -S .

Repository: openshift/release

Length of output: 509


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for other occurrences of the same jsonpath annotations pattern
rg -n -- "-o jsonpath='\\{\\.metadata\\.annotations\\}'" .

Repository: openshift/release

Length of output: 593


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the other file that uses -o jsonpath='{.metadata.annotations}'
FILE2="ci-operator/step-registry/openshift-qe/move-pods-infra/openshift-qe-move-pods-infra-commands.sh"
sed -n '1,120p' "$FILE2"

# Search for any jq pipelines using jsonpath annotations
rg -n "jsonpath='\\{\\.metadata\\.annotations\\}'[^\\n]*\\|[^\\n]*jq" -S .

# Search for jq to_entries on metadata.annotations in repo
rg -n "to_entries\\[\\]|to_entries\\[]|to_entries\\[\\]|to_entries" -S ci-operator/step-registry

Repository: openshift/release

Length of output: 25271


🌐 Web query:

kubectl -o jsonpath '{.metadata.annotations}' output format

💡 Result:

The correct kubectl JSONPath usage for annotations is: kubectl get -o jsonpath='{.metadata.annotations}' Notes: 1) kubectl supports the -o jsonpath= output format; the JSONPath template is enclosed in curly braces and you can directly reference object fields like.metadata.annotations. [1][2] 2) If an annotation value contains special characters (notably dots in annotation keys), you may need bracket-style JSONPath field access or escaping. Kubernetes’ kubectl JSONPath docs show bracket/dot alternatives (e.g., {['kind']} and escaping termination characters), and there are known cases where bracket access/escaping is required for keys with dots. [2][3] If you want a specific annotation key value (rather than the whole annotations map), use bracket notation: kubectl get -o jsonpath='{.metadata.annotations["key"]}' And for keys that contain dots, prefer bracket notation with escaping, e.g.: kubectl get -o jsonpath='{.metadata.annotations["external-dns.alpha.kubernetes.io/hostname"]}' [2][3] Example (all pods in a namespace, print their annotations map): kubectl get pods -n -o jsonpath='{range.items[*]}{.metadata.name}{"\t"}{.metadata.annotations}{"\n"}{end}' [2]

Citations:


🌐 Web query:

kubectl -o jsonpath '{.metadata.annotations}' output format jq to_entries

💡 Result:

Use jq, but you can’t pipe “kubectl -o jsonpath …” directly into jq to get an object unless the jsonpath output is valid JSON. 1) If you want the annotations as a JSON object (recommended): - kubectl -o json then jq kubectl get -o json | jq -r '.metadata.annotations | to_entries' 2) If you insist on jsonpath, you must emit valid JSON. - kubectl can output just the raw annotation map values, but JSONPath output is not JSON by default; jq expects JSON. - Kubernetes documents that kubectl JSONPath can format output, but jq is typically used when you need operations not supported by kubectl JSONPath (e.g., regex); the docs also emphasize that regex matching isn’t supported in kubectl JSONPath and suggests using jq on -o json instead [1][2]. 3) Typical working jq for entries: - From a pod/object JSON response: kubectl get -o json | jq -r '.metadata.annotations | to_entries[] | "(.key)=(.value)"' Why your exact idea usually fails: - kubectl JSONPath template returns a string (or concatenation) rather than a proper JSON object suitable for jq 'to_entries' unless you carefully craft it to produce JSON text. For annotations, easiest is to request JSON from kubectl and let jq do to_entries. Citations: - Kubernetes kubectl JSONPath reference (mentions kubectl JSONPath output behavior and recommends jq for cases JSONPath can’t do, e.g., regex; and gives the canonical approach of using -o json | jq when needed) [1][2].

Citations:


🌐 Web query:

kubectl get pod -o jsonpath='{.metadata.annotations}' example output

💡 Result:

To retrieve the annotations of a pod using kubectl and JSONPath, you can use the following command: kubectl get pod -o jsonpath='{.metadata.annotations}' Example Output: If a pod has annotations such as "kubernetes.io/config.seen" and "example.com/version", the output will be a map (dictionary) of key-value pairs: map[example.com/version:1.0.0 kubernetes.io/config.seen:2026-05-23T10:00:00Z] Important Considerations: 1. Accessing Specific Annotations: If you need to access a specific annotation key that contains dots (e.g., "kubernetes.io/created-by"), you must escape the dots with a backslash to prevent the JSONPath parser from interpreting them as child operators [1][2]. Example: kubectl get pod -o jsonpath='{.metadata.annotations.kubernetes.io/created-by}' 2. Handling Special Characters: If escaping does not work for complex keys, you can use bracket notation with single quotes [1][3]: kubectl get pod -o jsonpath="{.metadata.annotations['kubernetes.io/created-by']}" 3. Formatting: The output of {.metadata.annotations} is the raw map representation provided by the Go template engine used in kubectl. If you require structured JSON output for further processing, it is often more reliable to pipe the output to a tool like jq [4][3]: kubectl get pod -o json | jq '.metadata.annotations'

Citations:


Fix jq input for checksum annotation dump

oc get pod ... -o jsonpath='{.metadata.annotations}' prints a non-JSON “map[...]” style string, so jq doesn’t receive a JSON object; since jq stderr is discarded and the command has || true, the checksum annotation dump can end up printing nothing.

🩹 Suggested fix
-oc get pod "${CONTROLLER_TASK_POD}" -n "${INSTALLER_NAMESPACE}" -o jsonpath='{.metadata.annotations}' 2>/dev/null | jq -r 'to_entries[] | select(.key | startswith("checksum-")) | "  \(.key): \(.value)"' 2>/dev/null || true
+oc get pod "${CONTROLLER_TASK_POD}" -n "${INSTALLER_NAMESPACE}" -o json 2>/dev/null \
+    | jq -r '.metadata.annotations // {} | to_entries[] | select(.key | startswith("checksum-")) | "  \(.key): \(.value)"' 2>/dev/null || true
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@ci-operator/step-registry/osac-project/cluster-tool/boot/osac-project-cluster-tool-boot-commands.sh`
around lines 513 - 514, The oc command is outputting a non-JSON “map[…]" when
using -o jsonpath, so jq receives invalid input; change the invocation to
request full JSON and let jq extract annotations: call oc get pod
"${CONTROLLER_TASK_POD}" -n "${INSTALLER_NAMESPACE}" -o json (instead of -o
jsonpath) and pipe that to jq -r '.metadata.annotations | to_entries[] |
select(.key | startswith("checksum-")) | "  \(.key): \(.value)"' so jq gets a
real object; keep the final || true if you want to tolerate missing pods but
avoid redirecting jq stderr into /dev/null so errors are visible.

echo "$(ts) [post] Time since kustomize apply: $(( SECONDS - KUSTOMIZE_APPLY_TIME ))s"
echo "$(ts) [post] Deployment status:"
oc get deployments -n "${INSTALLER_NAMESPACE}" -o custom-columns=NAME:.metadata.name,READY:.status.readyReplicas,UPDATED:.status.updatedReplicas,GEN:.metadata.generation 2>/dev/null || true
echo "$(ts) [post] AAP controller-task stable"
########## END CHANGE ##########

echo ""
echo "$(ts) === Refresh complete (total: $(( SECONDS - REFRESH_START ))s) ==="
echo "Cluster domain: ${CLUSTER_DOMAIN}"
echo "Namespace: ${INSTALLER_NAMESPACE}"
REFRESH_EOF
chmod +x /tmp/refresh-patched.sh

echo "=== Running refresh ==="
podman run --authfile /root/pull-secret --rm --network=host \
-v "${KUBECONFIG_PATH}":/root/.kube/config:z \
-v /root/pull-secret:/installer/overlays/${KUSTOMIZE_OVERLAY}/files/quay-pull-secret.json:z \
-v /tmp/license.zip:/installer/overlays/${KUSTOMIZE_OVERLAY}/files/license.zip:z \
-v /tmp/prepare-aap-patched.sh:/installer/scripts/prepare-aap.sh:z \
-v /tmp/refresh-patched.sh:/installer/scripts/refresh-after-snapshot.sh:z \
-e KUBECONFIG=/root/.kube/config \
-e INSTALLER_KUSTOMIZE_OVERLAY="${KUSTOMIZE_OVERLAY}" \
-e INSTALLER_VM_TEMPLATE="${VM_TEMPLATE}" \
Expand Down
Loading