Skip to content

Commit 2a437b0

Browse files
refactor(RELEASE-1990): convert pulp-push-disk-images to python
This commit replaces the inline bash script for the pulp-push-disk-images internal task with a standalone python script in the utils image. Tekton tests use declarative mocks via mocks.yaml and tests/mocks/. Assisted-By: Cursor Signed-off-by: Happy Bhati <hbhati@redhat.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 820af7c commit 2a437b0

10 files changed

Lines changed: 65 additions & 240 deletions

File tree

tasks/internal/pulp-push-disk-images/pulp-push-disk-images.yaml

Lines changed: 13 additions & 234 deletions
Original file line numberDiff line numberDiff line change
@@ -107,237 +107,16 @@ spec:
107107
securityContext:
108108
runAsUser: 1001
109109
env:
110-
- name: "SNAPSHOT_JSON"
111-
value: "$(params.snapshot_json)"
112-
script: |
113-
#!/usr/bin/env bash
114-
set -e
115-
116-
# Check certificate expiration for all certificates used in this step
117-
echo "=== Checking certificate expiration ==="
118-
119-
# Check Exodus Gateway certificate
120-
echo "Checking Exodus Gateway certificate"
121-
if ! check_cert_expiration "/mnt/exodusGwSecret/cert" "$(params.certExpirationWarnDays)"; then
122-
echo "ERROR: Exodus Gateway certificate validation failed"
123-
exit 1
124-
fi
125-
126-
# Check Pulp certificate
127-
echo "Checking Pulp certificate"
128-
if ! check_cert_expiration "/mnt/pulpSecret/konflux-release-rhsm-pulp.crt" \
129-
"$(params.certExpirationWarnDays)"; then
130-
echo "ERROR: Pulp certificate validation failed"
131-
exit 1
132-
fi
133-
134-
# Check UDCache certificate
135-
echo "Checking UDCache certificate"
136-
if ! check_cert_expiration "/mnt/udcacheSecret/cert" "$(params.certExpirationWarnDays)"; then
137-
echo "ERROR: UDCache certificate validation failed"
138-
exit 1
139-
fi
140-
141-
echo "=== All certificates are valid ==="
142-
143-
EXODUS_CERT="$(cat /mnt/exodusGwSecret/cert)"
144-
EXODUS_KEY="$(cat /mnt/exodusGwSecret/key)"
145-
EXODUS_URL="$(cat /mnt/exodusGwSecret/url)"
146-
PULP_URL="$(cat /mnt/pulpSecret/pulp_url)"
147-
PULP_CERT="$(cat /mnt/pulpSecret/konflux-release-rhsm-pulp.crt)"
148-
PULP_KEY="$(cat /mnt/pulpSecret/konflux-release-rhsm-pulp.key)"
149-
UDC_URL="$(cat /mnt/udcacheSecret/url)"
150-
UDC_CERT="$(cat /mnt/udcacheSecret/cert)"
151-
UDC_KEY="$(cat /mnt/udcacheSecret/key)"
152-
DOCKER_CONFIG_JSON="$(cat /mnt/redhat-workloads-token/.dockerconfigjson)"
153-
154-
CGW_USERNAME="$(cat "/mnt/cgwSecret/username")"
155-
CGW_PASSWORD="$(cat "/mnt/cgwSecret/token")"
156-
157-
# export the variables used by the scripts in release-service-utils
158-
export CGW_USERNAME CGW_PASSWORD
159-
160-
set -x
161-
162-
STDERR_FILE=/tmp/stderr.txt
163-
164-
exitfunc() {
165-
local err=$1
166-
local line=$2
167-
local command="$3"
168-
if [ "$err" -eq 0 ] ; then
169-
echo -n "Success" > "$(results.result.path)"
170-
else
171-
echo "$0: ERROR '$command' failed at line $line - exited with status $err" \
172-
> "$(results.result.path)"
173-
if [ -f "$STDERR_FILE" ] ; then
174-
tail -n 20 "$STDERR_FILE" >> "$(results.result.path)"
175-
fi
176-
fi
177-
exit 0 # exit the script cleanly as there is no point in proceeding past an error or exit call
178-
}
179-
# due to set -e, this catches all EXIT and ERR calls and the task should never fail with nonzero exit code
180-
trap 'exitfunc $? $LINENO "$BASH_COMMAND"' EXIT
181-
182-
# Setup required variables
183-
export EXODUS_GW_CERT=/tmp/exodus.crt
184-
export EXODUS_GW_KEY=/tmp/exodus.key
185-
export PULP_CERT_FILE=/tmp/pulp.crt
186-
export PULP_KEY_FILE=/tmp/pulp.key
187-
export UDCACHE_CERT=/tmp/udc.crt
188-
export UDCACHE_KEY=/tmp/udc.key
189-
EXODUS_GW_ENV=$(params.exodusGwEnv)
190-
export EXODUS_GW_ENV
191-
export EXODUS_GW_URL="$EXODUS_URL"
192-
export EXODUS_PULP_HOOK_ENABLED=True
193-
export EXODUS_GW_TIMEOUT=7200
194-
mkdir -p ~/.docker
195-
196-
set +x
197-
echo "$EXODUS_CERT" > "$EXODUS_GW_CERT"
198-
echo "$EXODUS_KEY" > "$EXODUS_GW_KEY"
199-
echo "$PULP_CERT" > "$PULP_CERT_FILE"
200-
echo "$PULP_KEY" > "$PULP_KEY_FILE"
201-
echo "$UDC_CERT" > "$UDCACHE_CERT"
202-
echo "$UDC_KEY" > "$UDCACHE_KEY"
203-
# Quotes are added to the secret so it applies in k8s nicely. But now we have to remove them
204-
echo "$DOCKER_CONFIG_JSON" | sed -r 's/(^|\})[^{}]+(\{|$)/\1\2/g' > ~/.docker/config.json
205-
set -x
206-
207-
DISK_IMAGE_DIR="$(mktemp -d)"
208-
export DISK_IMAGE_DIR
209-
210-
process_component() { # Expected argument is [component json]
211-
COMPONENT=$1
212-
PULLSPEC=$(jq -er '.containerImage' <<< "${COMPONENT}")
213-
DESTINATION="${DISK_IMAGE_DIR}/$(jq -er '.staged.destination' <<< "${COMPONENT}")/FILES" \
214-
|| (echo "Missing staged.destination value for component. This should be an existing pulp repo. \
215-
Failing" && exit 1)
216-
mkdir -p "${DESTINATION}"
217-
DOWNLOAD_DIR=$(mktemp -d)
218-
cd "$DOWNLOAD_DIR"
219-
# oras has very limited support for selecting the right auth entry,
220-
# so create a custom auth file with just one entry
221-
AUTH_FILE=$(mktemp)
222-
select-oci-auth "${PULLSPEC}" > "$AUTH_FILE"
223-
oras pull --registry-config "$AUTH_FILE" "$PULLSPEC"
224-
NUM_MAPPED_FILES=$(jq '.staged.files | length' <<< "${COMPONENT}")
225-
for ((i = 0; i < NUM_MAPPED_FILES; i++)) ; do
226-
FILE=$(jq -c --arg i "$i" '.staged.files[$i|tonumber]' <<< "$COMPONENT")
227-
SOURCE=$(jq -er '.source' <<< "$FILE")
228-
FILENAME=$(jq -er '.filename' <<< "$FILE")
229-
# The .qcow2 images are not zipped
230-
if [ -f "${SOURCE}.gz" ] ; then
231-
gzip -d "${SOURCE}.gz"
232-
fi
233-
DESTINATION_FILE="${DESTINATION}/${FILENAME}"
234-
# Albeit a rare one, this is a race condition since this is run in parallel.
235-
# The race condition is if two files have the same $DESTINATION_FILE and both
236-
# if checks are run before either mv is run a few lines below.
237-
if [ -f "${DESTINATION_FILE}" ] ; then
238-
echo -n "Multiple files use the same destination value: $DESTINATION" >&2
239-
echo " and filename value: $FILENAME. Failing..." >&2
240-
exit 1
241-
fi
242-
mv "$SOURCE" "${DESTINATION_FILE}" || echo "didn't find mapped file: ${SOURCE}"
243-
done
244-
245-
}
246-
247-
process_component_for_developer_portal() { # Expected argument are [component json], [content_directory]
248-
COMPONENT=$1
249-
250-
productName="$(jq -er '.contentGateway.productName' <<< "${COMPONENT}")" \
251-
|| (echo "Missing contentGateway.productName value for component. This should be an existing product \
252-
in the Developer Portal. Failing" && exit 1)
253-
254-
productCode="$(jq -er '.contentGateway.productCode' <<< "${COMPONENT}")" \
255-
|| (echo "Missing contentGateway.productCode value for component. This should be an existing product \
256-
in the Developer Portal. Failing" && exit 1)
257-
258-
productVersionName="$(jq -er '.contentGateway.productVersionName' <<< "${COMPONENT}")" \
259-
|| (echo "Missing contentGateway.productVersionName value for component. This should be an existing \
260-
product in the Developer Portal. Failing" && exit 1)
261-
262-
filePrefix="$(jq -er '.contentGateway.filePrefix' <<< "${COMPONENT}")" \
263-
|| (echo "Missing contentGateway.filePrefix value for component. This should be the prefix for files to \
264-
upload to the Developer Portal. Failing" && exit 1)
265-
266-
# Set proxy for preprod CGW environment (requires squid proxy for access)
267-
if [[ "$(params.cgwHostname)" == *"developers.qa.redhat.com"* ]]; then
268-
export HTTP_PROXY="http://squid.corp.redhat.com:3128"
269-
export HTTPS_PROXY="http://squid.corp.redhat.com:3128"
270-
echo "Using squid proxy for preprod CGW access"
271-
fi
272-
273-
developer_portal_wrapper --debug --product-name "${productName}" \
274-
--product-code "${productCode}" \
275-
--product-version-name "${productVersionName}" \
276-
--cgw-hostname "$(params.cgwHostname)" \
277-
--content-directory "$2" \
278-
--file-prefix "${filePrefix}"
279-
280-
}
281-
282-
RUNNING_JOBS="\j" # Bash parameter for number of jobs currently running
283-
NUM_COMPONENTS=$(jq '.components | length' <<< "$SNAPSHOT_JSON")
284-
285-
# use the 1st component's version
286-
VERSION=$(jq -cr '.components[0].staged.version // ""' <<< "$SNAPSHOT_JSON")
287-
if [ "${VERSION}" == "" ] ; then
288-
echo "Error: version not specified in .components[0].staged.version. Needed to publish to customer portal"
289-
exit 1
290-
fi
291-
292-
# Process each component in parallel
293-
for ((i = 0; i < NUM_COMPONENTS; i++)) ; do
294-
COMPONENT=$(jq -c --arg i "$i" '.components[$i|tonumber]' <<< "$SNAPSHOT_JSON")
295-
# Limit batch size to concurrent limit
296-
while (( ${RUNNING_JOBS@P} >= $(params.concurrentLimit) )); do
297-
wait -n
298-
done
299-
process_component "$COMPONENT" 2> "$STDERR_FILE" &
300-
done
301-
302-
# Wait for remaining processes to finish
303-
while (( ${RUNNING_JOBS@P} > 0 )); do
304-
wait -n
305-
done
306-
307-
# Change to the subdir with the images
308-
cd "${DISK_IMAGE_DIR}"
309-
310-
STAGED_JSON='{"header":{"version": "0.2"},"payload":{"files":[]}}'
311-
312-
# Add the files to the payload
313-
# shell check wants us to find ./* but that adds `./` to the paths which breaks the script
314-
# shellcheck disable=SC2035
315-
while IFS= read -r -d '' file ; do
316-
STAGED_JSON=$(jq --arg filename "$(basename "$file")" --arg path "$file" \
317-
--arg version "$VERSION" \
318-
'.payload.files[.payload.files | length] =
319-
{"filename": $filename, "relative_path": $path, "version": $version}' <<< "$STAGED_JSON")
320-
done < <(find * -type f -print0)
321-
322-
echo "$STAGED_JSON" | yq -P -I 4 > staged.yaml
323-
324-
pulp_push_wrapper --debug --source "${DISK_IMAGE_DIR}" --pulp-url "$PULP_URL" \
325-
--pulp-cert $PULP_CERT_FILE --pulp-key $PULP_KEY_FILE --udcache-url "$UDC_URL" \
326-
2> >(tee -a "$STDERR_FILE" >&2)
327-
328-
relative_paths=$(echo "$STAGED_JSON" | jq -erc .payload.files[].relative_path)
329-
component_destinations=()
330-
for path in $relative_paths:
331-
do
332-
parent_dir=$(dirname "$path")
333-
component_destinations+=("${DISK_IMAGE_DIR}/$parent_dir")
334-
done
335-
336-
## Process Files for Developer Portal / CGW
337-
##
338-
NUM_COMPONENTS=$(jq '.components | length' <<< "$SNAPSHOT_JSON")
339-
for ((i = 0; i < NUM_COMPONENTS; i++)) ; do
340-
COMPONENT=$(jq -c --arg i "$i" '.components[$i|tonumber]' <<< "$SNAPSHOT_JSON")
341-
process_component_for_developer_portal "$COMPONENT" "${component_destinations[$i]}" \
342-
2> >(tee -a "$STDERR_FILE" >&2)
343-
done
110+
- name: SNAPSHOT_JSON
111+
value: $(params.snapshot_json)
112+
- name: CERT_EXPIRATION_WARN_DAYS
113+
value: $(params.certExpirationWarnDays)
114+
- name: CONCURRENT_LIMIT
115+
value: $(params.concurrentLimit)
116+
- name: EXODUS_GW_ENV
117+
value: $(params.exodusGwEnv)
118+
- name: CGW_HOSTNAME
119+
value: $(params.cgwHostname)
120+
- name: RESULT_RESULT
121+
value: $(results.result.path)
122+
command: ["/home/scripts/python/tasks/internal/pulp_push_disk_images.py"]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
# Declarative mocks for Tekton tests of this task (Python entrypoint). Rendered
3+
# by .github/scripts/render_python_task_mocks_from_yaml.py when
4+
# test_tekton_tasks.sh runs.
5+
version: 1
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env bash
2+
echo "Mock check_cert_expiration called with: $*"
3+
echo "Certificate $2 is valid (mocked)"
4+
exit 0
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env bash
2+
echo Mock developer_portal_wrapper called with: "$@"
3+
4+
/home/developer-portal-wrapper/developer_portal_wrapper "$@" --dry-run
5+
6+
if ! [[ "$?" -eq 0 ]]; then
7+
echo Unexpected call to developer_portal_wrapper
8+
exit 1
9+
fi
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env bash
2+
file="${@: -1}"
3+
if [[ "$file" == *"fail_gzip.raw.gz" ]]; then
4+
echo gzip failed
5+
exit 1
6+
fi
7+
mv "$file" "${file%.gz}"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env bash
2+
echo Mock oras called with: "$@"
3+
4+
if [[ "$*" != "pull --registry-config"* ]]; then
5+
echo Error: Unexpected call to oras
6+
exit 1
7+
fi
8+
9+
if [[ "$*" == *"nonexistent-disk-image"* ]]; then
10+
echo Simulating failing oras pull call
11+
exit 1
12+
fi
13+
14+
touch disk.qcow2
15+
touch disk.raw.gz
16+
touch fail_gzip.raw.gz
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env bash
2+
echo Mock pulp_push_wrapper called with: "$@"
3+
4+
if [[ "$*" != *"--pulp-url https://pulp.com"* ]]; then
5+
printf "Mocked failure of pulp_push_wrapper" > /nonexistent/location
6+
fi
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env bash
2+
echo Mock select-oci-auth called with: "$@"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/usr/bin/env bash
2+
# Mock yq: pass JSON stdin through as YAML-like output for tests.
3+
cat

tasks/internal/pulp-push-disk-images/tests/pre-apply-task-hook.sh

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
#!/usr/bin/env bash
22

3-
TASK_PATH="$1"
4-
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
5-
6-
# Add mocks to the beginning of task step script
7-
yq -i '.spec.steps[0].script = load_str("'$SCRIPT_DIR'/mocks.sh") + .spec.steps[0].script' "$TASK_PATH"
8-
93
# Create a dummy exodus secret (and delete it first if it exists)
104
kubectl delete secret pulp-task-exodus-secret --ignore-not-found
115
kubectl create secret generic pulp-task-exodus-secret --from-literal=cert=myexoduscert --from-literal=key=myexoduskey --from-literal=url=https://exodus.com

0 commit comments

Comments
 (0)