11# --- Global Script Variables (Defaults) ---
22CLEANUP=" true"
33NO_CVE=" true" # Default to true
4+ # GitHub API curl retries (override in CI/local: export GITHUB_API_CURL_RETRY=5)
5+ GITHUB_API_CURL_RETRY=" ${GITHUB_API_CURL_RETRY:- 3} "
46
57# Variables that will be set by functions and used globally:
68# component_branch, component_base_branch, component_repo_name (from test.env or similar)
@@ -15,6 +17,140 @@ NO_CVE="true" # Default to true
1517# component_push_plr_name (set by wait_for_plr_to_appear)
1618# RELEASE_NAME, RELEASE_NAMESPACE (set and exported by wait_for_release)
1719
20+ # GET a GitHub API JSON resource; prints the response body on success, returns 1 on curl/HTTP error.
21+ # Args: token url detail_msg fallback_msg (used when curl fails, with/without .message from body)
22+ github_api_get_json () {
23+ local token=" $1 "
24+ local url=" $2 "
25+ local detail_msg=" $3 "
26+ local fallback_msg=" $4 "
27+ local response api_msg
28+ local xtrace_was_on=0
29+ case $- in
30+ * x* ) xtrace_was_on=1 ;;
31+ esac
32+ if [ " ${xtrace_was_on} " -eq 1 ]; then
33+ set +x
34+ fi
35+
36+ if ! response=$( curl --retry " ${GITHUB_API_CURL_RETRY} " -s --fail-with-body \
37+ -H " Authorization: token ${token} " \
38+ " ${url} " ) ; then
39+ if [ " ${xtrace_was_on} " -eq 1 ]; then
40+ set -x
41+ fi
42+ if api_msg=$( jq -r ' .message // empty' <<< " ${response}" 2> /dev/null) && [ -n " ${api_msg} " ]; then
43+ echo " ❌ error: ${detail_msg} : ${api_msg} " >&2
44+ else
45+ echo " ❌ error: ${fallback_msg} " >&2
46+ fi
47+ return 1
48+ fi
49+ if [ " ${xtrace_was_on} " -eq 1 ]; then
50+ set -x
51+ fi
52+ echo " ${response} "
53+ }
54+
55+ patch_component_source_before_merge () {
56+ # CI often runs scripts under xtrace (bash -x). Disable tracing only while handling tokens.
57+ local xtrace_was_on=0
58+ case $- in
59+ * x* ) xtrace_was_on=1 ;;
60+ esac
61+ if [ " ${xtrace_was_on} " -eq 1 ]; then
62+ set +x
63+ fi
64+
65+ trap ' unset secret_value 2>/dev/null; if [ "${xtrace_was_on}" -eq 1 ]; then set -x; fi' RETURN
66+
67+ echo " Patching component source BEFORE MERGE to ensure multi-arch build..."
68+
69+ # PaC GitHub token: do not export — scoped env only for the helper that requires GH_TOKEN.
70+ # Vault file uses ${component_name} placeholders; envsubst runs at kubectl apply, not on disk.
71+ local secret_value
72+ secret_value=$( yq ' . | select(.metadata.name | contains("pipelines-as-code-secret-")) | .stringData.password' \
73+ " ${SUITE_DIR} /resources/tenant/secrets/tenant-secrets.yaml" | head -n 1)
74+ while [[ " ${secret_value} " == * $' \n ' ]]; do
75+ secret_value=" ${secret_value% $' \n ' } "
76+ done
77+
78+ if [ -z " ${secret_value} " ] || [ " ${secret_value} " = " null" ]; then
79+ log_error " PaC token not found in tenant secrets (pipelines-as-code-secret-*)"
80+ fi
81+
82+ local file_names=(
83+ " .tekton/${component_name} -pull-request.yaml"
84+ " .tekton/${component_name} -push.yaml"
85+ )
86+ local head_sha pr_response
87+ pr_response=$( github_api_get_json " ${secret_value} " \
88+ " https://api.github.com/repos/${component_repo_name} /pulls/${pr_number} " \
89+ " GitHub API error fetching PR ${pr_number} " \
90+ " failed to fetch PR ${pr_number} from ${component_repo_name} (check PaC token and repo access)" ) || exit 1
91+ head_sha=$( jq -r -e ' .head.sha' <<< " ${pr_response}" ) || {
92+ log_error " missing or invalid .head.sha in PR ${pr_number} response"
93+ }
94+
95+ for file_name in " ${file_names[@]} " ; do
96+ local decoded_contents encoded_contents
97+ local contents_response encoded_content_field
98+ echo " Patching ${file_name} ..."
99+
100+ contents_response=$( github_api_get_json " ${secret_value} " \
101+ " https://api.github.com/repos/${component_repo_name} /contents/${file_name} ?ref=${head_sha} " \
102+ " GitHub API error fetching ${file_name} " \
103+ " failed to fetch ${file_name} at ref ${head_sha} from ${component_repo_name} " ) || exit 1
104+
105+ encoded_content_field=$( jq -r -e ' .content' <<< " ${contents_response}" ) || {
106+ log_error " missing or invalid .content for ${file_name} in PR ${pr_number} "
107+ }
108+ if ! decoded_contents=$( base64 -d <<< " ${encoded_content_field}" ) ; then
109+ log_error " failed to base64-decode ${file_name} from PR ${pr_number} "
110+ fi
111+ if [ -z " ${decoded_contents} " ]; then
112+ log_error " decoded contents for ${file_name} are empty"
113+ fi
114+
115+ encoded_contents=$(
116+ set -eo pipefail
117+ work_dir=$( mktemp -d)
118+ trap ' rm -rf "${work_dir}"' EXIT
119+ nopath_file_name=$( basename " ${file_name} " )
120+ echo " ${decoded_contents} " > " ${work_dir} /${nopath_file_name} "
121+
122+ # Ensure linux/amd64 + linux/arm64 are present.
123+ if yq -e ' (.spec.params[]? | select(.name == "build-platforms") | .value | type) == "!!seq"' \
124+ " ${work_dir} /${nopath_file_name} " > /dev/null 2>&1 ; then
125+ yq -i ' (.spec.params[] | select(.name == "build-platforms") | .value) |= ([.[] | select(. != "linux/arm64")] + ["linux/arm64"])' \
126+ " ${work_dir} /${nopath_file_name} "
127+ yq -i ' (.spec.params[] | select(.name == "build-platforms") | .value) |= ([.[] | select(. != "linux/amd64")] + ["linux/amd64"])' \
128+ " ${work_dir} /${nopath_file_name} "
129+ elif yq -e ' (.spec.params[]? | select(.name == "build-platforms"))' \
130+ " ${work_dir} /${nopath_file_name} " > /dev/null 2>&1 ; then
131+ yq -i ' (.spec.params[] | select(.name == "build-platforms") | .value) = ["linux/amd64", "linux/arm64"]' \
132+ " ${work_dir} /${nopath_file_name} "
133+ else
134+ yq -i ' .spec.params += [{"name": "build-platforms", "value": ["linux/amd64", "linux/arm64"]}]' \
135+ " ${work_dir} /${nopath_file_name} "
136+ fi
137+
138+ base64 -w 0 < " ${work_dir} /${nopath_file_name} "
139+ ) || {
140+ log_error " failed to patch ${file_name} for multi-arch build"
141+ }
142+
143+ GH_TOKEN=" ${secret_value} " " ${SCRIPT_DIR} /scripts/update-file-in-pull-request.sh" \
144+ " ${component_repo_name} " \
145+ " ${pr_number} " \
146+ " ${file_name} " \
147+ " Update PaC templates for multi-arch build" \
148+ " ${encoded_contents} " || log_error " failed to update ${file_name} in PR ${pr_number} "
149+ done
150+
151+ echo " ✅️ Successfully patched component PaC templates for multi-arch."
152+ }
153+
18154# Function to verify Release contents
19155# Relies on global variables: RELEASE_NAMES, RELEASE_NAMESPACE, SCRIPT_DIR, managed_namespace, managed_sa_name, NO_CVE
20156verify_release_contents () {
@@ -29,13 +165,34 @@ verify_release_contents() {
29165 log_error " Could not retrieve Release JSON for ${RELEASE_NAME} "
30166 fi
31167
32- echo " Release JSON: ${release_json} "
33-
34168 local failures=0
35- local image_url mergerequest_url
169+ local image_url mergerequest_url image_arches image_shasum released_status
170+
171+ image_url=$( jq -r ' .status.artifacts.images[0]?.urls[0]? // ""' <<< " ${release_json}" )
172+ mergerequest_url=$( jq -r ' .status.artifacts.merge_requests[0]?.url? // ""' <<< " ${release_json}" )
173+ # Release may list one arch per manifest/index entry (e.g. duplicate amd64); compare distinct sets.
174+ # Strip optional linux/ prefix (e.g. linux/amd64 -> amd64). Default null/missing .arches to [].
175+ image_arches=$( jq -r ' (.status.artifacts.images[0]?.arches? // [])
176+ | map((tostring | split("/") | .[-1]))
177+ | unique
178+ | join(" ")' <<< " ${release_json}" )
179+ image_shasum=$( jq -r ' .status.artifacts.images[0]?.shasum? // ""' <<< " ${release_json}" )
180+ released_status=$( jq -r ' ([.status.conditions[]? | select(.type=="Released") | .status] | first) // ""' <<< " ${release_json}" )
36181
37- image_url=$( jq -r ' .status.artifacts.images[0].urls[0] // ""' <<< " ${release_json}" )
38- mergerequest_url=$( jq -r ' .status.artifacts.merge_requests[0].url // ""' <<< " ${release_json}" )
182+ echo " Release fields under validation:"
183+ echo " Released: ${released_status} "
184+ echo " image_url: ${image_url} "
185+ echo " mergerequest_url: ${mergerequest_url} "
186+ echo " image_arches: ${image_arches} "
187+ echo " image_shasum: ${image_shasum} "
188+
189+ echo " Checking Released=True..."
190+ if [ " ${released_status} " = " True" ]; then
191+ echo " ✅️ Released=True"
192+ else
193+ echo " 🔴 Released was not True (found: '${released_status} ')"
194+ failures=$(( failures+ 1 ))
195+ fi
39196
40197 echo " Checking image_url..."
41198 if [ -n " ${image_url} " ]; then
@@ -52,6 +209,39 @@ verify_release_contents() {
52209 failures=$(( failures+ 1 ))
53210 fi
54211
212+ echo " Checking image arches include amd64 and arm64..."
213+ if [[ " ${image_arches} " == * " amd64 " * && " ${image_arches} " == * " arm64 " * ]]; then
214+ echo " ✅️ Found required arches: ${image_arches} "
215+ else
216+ echo " 🔴 Missing required arches (need: amd64 and arm64), found: '${image_arches} '"
217+ failures=$(( failures+ 1 ))
218+ fi
219+
220+ echo " Checking image shasum (manifest list digest) is present..."
221+ if [[ " ${image_shasum} " == sha256:* ]]; then
222+ echo " ✅️ image_shasum: ${image_shasum} "
223+ else
224+ echo " 🔴 image_shasum missing or invalid: '${image_shasum} '"
225+ failures=$(( failures+ 1 ))
226+ fi
227+
228+ echo " Checking skopeo inspect succeeds for both arches (digest pull + registry auth)..."
229+ if [ -n " ${image_url} " ] && [[ " ${image_shasum} " == sha256:* ]]; then
230+ set +e
231+ " ${SCRIPT_DIR} /scripts/skopeo-verify-image.sh" \
232+ " ${image_url} " " ${image_shasum} " \
233+ " ${SUITE_DIR} /resources/managed/secrets/managed-secrets.yaml" \
234+ " amd64 arm64"
235+ skopeo_rc=$?
236+ set -e
237+ if [ " ${skopeo_rc} " -ne 0 ]; then
238+ failures=$(( failures+ 1 ))
239+ fi
240+ elif [ -n " ${image_url} " ]; then
241+ echo " 🔴 Skipping skopeo multi-arch check: image_shasum missing or not sha256:*"
242+ failures=$(( failures+ 1 ))
243+ fi
244+
55245 if [ " ${failures} " -gt 0 ]; then
56246 echo " 🔴 Test has FAILED with ${failures} failure(s)!"
57247 failed_releases=" ${RELEASE_NAME} ${failed_releases} "
0 commit comments