From 3cd581b57604f8a1783cfb70b3673eff30013050 Mon Sep 17 00:00:00 2001 From: Leandro Mendes Date: Tue, 24 Mar 2026 11:44:24 +0100 Subject: [PATCH 1/7] fix(RELEASE-12379): use completion_time instead of buildTimestamp Replace the buildTimestamp parameter/result with completion_time extracted directly from the IIB build info JSON response. This ensures timestamp consistency with the actual build completion time and adds support for conditional timestamped tags in FBC releases. Changes: - add-fbc-contribution: Extract completion_time from IIB build response (updated_by field), validate it's a 10-digit epoch timestamp, and create target_index_with_timestamp field that avoids duplicate timestamps for hotfix/pre-GA releases - collect-index-images: Use target_index_with_timestamp to conditionally include timestamped tags (2 tags for regular releases, 1 for hotfix/pre-GA) - publish-index-image: Publish both target_index and target_index_with_timestamp tags only when they differ - extract-index-image: Remove unused buildTimestamp parameter - prepare-fbc-snapshot: Update comment to clarify timestamp is arbitrary until build completion - fbc-release pipeline: Remove buildTimestamp parameter passing For regular releases, both v4.14 and v4.14- tags are published. For hotfix/pre-GA releases (which already include timestamps in target_index), only a single tag is published to avoid duplication. This change also fixes RELEASE-2666 by ensuring the timestamped image is properly created and available for signing. All integration tests updated to reflect the new behavior. Assisted-By: Claude Code Signed-off-by: Leandro Mendes --- .../managed/fbc-release/fbc-release.yaml | 6 - .../add-fbc-contribution.yaml | 41 ++- .../add-fbc-contribution/tests/mocks.sh | 2 +- .../test-add-fbc-contribution-hotfix.yaml | 248 ++++++++++++++++++ tasks/managed/collect-index-images/README.md | 1 - .../collect-index-images.yaml | 21 +- .../test-collect-index-images-hotfix.yaml | 47 +--- ...ollect-index-images-multiple-versions.yaml | 18 +- ...t-collect-index-images-single-version.yaml | 10 +- tasks/managed/extract-index-image/README.md | 7 +- .../extract-index-image.yaml | 9 +- ...act-index-image-fail-no-inputdatafile.yaml | 93 ------- ...tract-index-image-multiple-components.yaml | 2 - .../tests/test-extract-index-image.yaml | 2 - .../prepare-fbc-snapshot.yaml | 5 +- .../test-prepare-fbc-snapshot-hotfix.yaml | 11 +- tasks/managed/publish-index-image/README.md | 1 - .../publish-index-image.yaml | 15 +- ...st-publish-index-image-with-timestamp.yaml | 73 ++++-- .../tests/test-publish-index-image.yaml | 7 +- 20 files changed, 388 insertions(+), 231 deletions(-) create mode 100644 tasks/managed/add-fbc-contribution/tests/test-add-fbc-contribution-hotfix.yaml delete mode 100644 tasks/managed/extract-index-image/tests/test-extract-index-image-fail-no-inputdatafile.yaml diff --git a/pipelines/managed/fbc-release/fbc-release.yaml b/pipelines/managed/fbc-release/fbc-release.yaml index be0efaea7a..206092fcf2 100644 --- a/pipelines/managed/fbc-release/fbc-release.yaml +++ b/pipelines/managed/fbc-release/fbc-release.yaml @@ -494,8 +494,6 @@ spec: - name: pathInRepo value: tasks/managed/extract-index-image/extract-index-image.yaml params: - - name: inputDataFile - value: $(tasks.add-fbc-contribution-to-index-image.results.requestResultsFile) - name: resultsDirPath value: "$(tasks.collect-data.results.resultsDir)" - name: ociStorage @@ -529,8 +527,6 @@ spec: value: "$(tasks.collect-data.results.data)" - name: internalRequestResultsFile value: $(tasks.add-fbc-contribution-to-index-image.results.internalRequestResultsFile) - - name: buildTimestamp - value: $(tasks.add-fbc-contribution-to-index-image.results.buildTimestamp) - name: retries value: "3" - name: pipelineRunUid @@ -565,8 +561,6 @@ spec: - name: pathInRepo value: tasks/managed/collect-index-images/collect-index-images.yaml params: - - name: buildTimestamp - value: $(tasks.add-fbc-contribution-to-index-image.results.buildTimestamp) - name: internalRequestResultsFile value: $(tasks.add-fbc-contribution-to-index-image.results.internalRequestResultsFile) - name: ociStorage diff --git a/tasks/managed/add-fbc-contribution/add-fbc-contribution.yaml b/tasks/managed/add-fbc-contribution/add-fbc-contribution.yaml index 1e45742d84..ee2c58d3fa 100644 --- a/tasks/managed/add-fbc-contribution/add-fbc-contribution.yaml +++ b/tasks/managed/add-fbc-contribution/add-fbc-contribution.yaml @@ -100,8 +100,6 @@ spec: description: The name of the key in the ConfigMap that contains the CA bundle data default: ca-bundle.crt results: - - name: buildTimestamp - description: Build timestamp used in the tag - name: requestResultsFile description: Internal Request results file - name: internalRequestResultsFile @@ -211,9 +209,6 @@ spec: mustPublishIndexImage="$(params.mustPublishIndexImage)" mustOverwriteFromIndexImage="$(params.mustOverwriteFromIndexImage)" - timestamp_format=$(jq -r '.fbc.timestampFormat // "%s"' "${DATA_FILE}") - timestamp=$(date "+${timestamp_format}") - # Extract OCP versions from snapshot ocp_versions=$(jq -r '.components[].ocpVersion' "$SNAPSHOT_PATH" | sort -u) echo "INFO: Found OCP versions: $(echo "$ocp_versions" | tr '\n' ' ')" @@ -223,8 +218,6 @@ spec: echo " - mustOverwriteFromIndexImage: ${mustOverwriteFromIndexImage}" echo " - iibServiceAccountSecret: ${iib_service_account_secret}" - # Initialize results file for multi-OCP processing - echo -n "$timestamp" > "$(results.buildTimestamp.path)" jq -n '{"components": []}' | tee "$RESULTS_FILE" # Snapshot validation for multi-OCP processing @@ -472,10 +465,40 @@ spec: # Process results for each fragment in this batch local decompressed_json_build_info decompressed_json_build_info="$(jq -r '.jsonBuildInfo' <<< "${results}" | base64 -d | gunzip)" + + # Extract and validate completion_time from IIB build info local completion_time_raw completion_time_raw="$(jq -r '.updated' <<< "${decompressed_json_build_info}")" + + if [[ -z "${completion_time_raw}" || "${completion_time_raw}" == "null" ]]; then + echo "ERROR: completion_time not found in IIB build info" + return 1 + fi + + echo "INFO: Extracting completion_time from IIB build info" + echo " Raw value: ${completion_time_raw}" + local completion_time - completion_time=$(date +"${timestamp_format}" -d "${completion_time_raw}") + if ! completion_time=$(date +"%s" -d "${completion_time_raw}"); then + echo "ERROR: Failed to parse completion_time: ${completion_time_raw}" + echo "Date conversion error: ${completion_time}" + return 1 + fi + echo " Epoch timestamp: ${completion_time}" + + # Ensure completion_time contains ten digits + if [[ ! "${completion_time}" =~ ^[0-9]{10}$ ]]; then + echo "ERROR: Invalid completion_time format (expected 10 digits): ${completion_time}" + return 1 + fi + + # timestamped_target_index is the target_index with completion_time, unless + # for hotfix or pre-ga which already has it and can be the same. + if [[ "$group_target_index" =~ .*[0-9]{10}$ ]]; then + target_index_with_timestamp="$group_target_index" + else + target_index_with_timestamp="$group_target_index-${completion_time}" + fi # Process fragments in batch local fragment_index=0 @@ -488,12 +511,14 @@ spec: build_results=$(jq \ --arg fragment "$fragment" \ --arg target_index "$group_target_index" \ + --arg target_index_with_timestamp "$target_index_with_timestamp" \ --arg ocp_version "$group_ocp_version" \ --arg completion_time "$completion_time" \ --argjson decompressed_json "${decompressed_json_build_info}" \ '{ "fbc_fragment": $fragment, "target_index": $target_index, + "target_index_with_timestamp": $target_index_with_timestamp, "ocp_version": $ocp_version, "image_digests": (.indexImageDigests | split(" ") | del(.[] | select(. == ""))), "index_image": $decompressed_json.index_image, diff --git a/tasks/managed/add-fbc-contribution/tests/mocks.sh b/tasks/managed/add-fbc-contribution/tests/mocks.sh index 2a4670f5c0..da51a33310 100644 --- a/tasks/managed/add-fbc-contribution/tests/mocks.sh +++ b/tasks/managed/add-fbc-contribution/tests/mocks.sh @@ -133,7 +133,7 @@ function date() { "+%Y-%m-%dT%H:%M:%SZ") echo "2023-10-10T15:00:00Z" |tee $(params.dataDir)/mock_date_iso_format.txt ;; - "+%s") + "+%s"*) echo "1696946200" | tee $(params.dataDir)/mock_date_epoch.txt ;; "-u +%Hh%Mm%Ss -d @"*) diff --git a/tasks/managed/add-fbc-contribution/tests/test-add-fbc-contribution-hotfix.yaml b/tasks/managed/add-fbc-contribution/tests/test-add-fbc-contribution-hotfix.yaml new file mode 100644 index 0000000000..5a8ab740eb --- /dev/null +++ b/tasks/managed/add-fbc-contribution/tests/test-add-fbc-contribution-hotfix.yaml @@ -0,0 +1,248 @@ +--- +apiVersion: tekton.dev/v1 +kind: Pipeline +metadata: + name: test-add-fbc-contribution-hotfix +spec: + description: Test add-fbc-contribution task for hotfix releases + params: + - name: ociStorage + description: The OCI repository where the Trusted Artifacts are stored. + type: string + - name: ociArtifactExpiresAfter + description: Expiration date for the trusted artifacts created in the + OCI repository. An empty string means the artifacts do not expire. + type: string + default: "1d" + - name: orasOptions + description: oras options to pass to Trusted Artifacts calls + type: string + default: "--insecure" + - name: trustedArtifactsDebug + description: Flag to enable debug logging in trusted artifacts. Set to a non-empty string to enable. + type: string + default: "" + - name: dataDir + description: The location where data will be stored + type: string + - name: sourceDataArtifact + description: Location of trusted artifacts to be used to populate data directory + type: string + default: "" + tasks: + - name: setup + params: + - name: sourceDataArtifact + value: $(params.sourceDataArtifact) + - name: dataDir + value: $(params.dataDir) + taskSpec: + params: + - name: sourceDataArtifact + type: string + description: Location of trusted artifacts to be used to populate data directory + - name: dataDir + type: string + description: The location where data will be stored + results: + - name: sourceDataArtifact + type: string + volumes: + - name: workdir + emptyDir: {} + stepTemplate: + volumeMounts: + - mountPath: /var/workdir + name: workdir + env: + - name: IMAGE_EXPIRES_AFTER + value: $(params.ociArtifactExpiresAfter) + - name: "ORAS_OPTIONS" + value: "$(params.orasOptions)" + - name: "DEBUG" + value: "$(params.trustedArtifactsDebug)" + steps: + - name: setup-test-data + image: quay.io/konflux-ci/release-service-utils@sha256:5546fa78d3c88d7b6a2e8cff8902f7757f00541d0bbaf113b9f293133894afa3 + script: | + #!/usr/bin/env bash + set -eux + + # Ensure data directory exists + mkdir -p "$(params.dataDir)" + + # Create snapshot with prepared FBC components (already processed by prepare-fbc-snapshot) + # Note: In hotfix mode, prepare-fbc-snapshot would have already replaced {{timestamp}} + # with an actual timestamp. We use a fixed timestamp for test reproducibility. + cat > "$(params.dataDir)/snapshot.json" << 'EOF' + { + "components": [ + { + "name": "fbc-target-index-testing", + "containerImage": "quay.io/hacbs-release-tests/test-ocp-version/test-fbc-component@sha256:f6e744662e342c1321deddb92469b55197002717a15f8c0b1bb2d9440aac2297", + "repository": "quay.io/scoheb/fbc-target-index-testing", + "ocpVersion": "v4.12", + "updatedFromIndex": "quay.io/redhat/redhat-operator-index:v4.12", + "targetIndex": "quay.io/scoheb/fbc-target-index-testing:v4.12-KONFLUX-123-1234567890" + } + ] + } + EOF + + # Create FBC data configuration for staged release + cat > "$(params.dataDir)/data.json" << 'EOF' + { + "fbc": { + "fromIndex": "quay.io/redhat/redhat-operator-index:{{ OCP_VERSION }}", + "targetIndex": "quay.io/scoheb/fbc-target-index-testing:v4.12-KONFLUX-123-1234567890", + "hotfix": true, + "issueId": "KONFLUX-123" + } + } + EOF + + # Create mock results directory structure + mkdir -p "$(params.dataDir)/results" + + echo "Staged test data created for add-fbc-contribution" + - name: create-trusted-artifact + ref: + name: create-trusted-artifact + params: + - name: ociStorage + value: $(params.ociStorage) + - name: workDir + value: $(params.dataDir) + - name: sourceDataArtifact + value: $(results.sourceDataArtifact.path) + + - name: run-task + taskRef: + name: add-fbc-contribution + params: + - name: snapshotPath + value: snapshot.json + - name: dataPath + value: data.json + - name: pipelineRunUid + value: $(context.pipelineRun.uid) + - name: resultsDirPath + value: results + - name: ociStorage + value: $(params.ociStorage) + - name: sourceDataArtifact + value: "$(tasks.setup.results.sourceDataArtifact)=$(params.dataDir)" + - name: dataDir + value: $(params.dataDir) + - name: trustedArtifactsDebug + value: $(params.trustedArtifactsDebug) + - name: orasOptions + value: $(params.orasOptions) + - name: taskGitUrl + value: "https://github.com/konflux-ci/release-service-catalog.git" + - name: taskGitRevision + value: "development" + - name: maxBatchSize + value: "5" + - name: mustPublishIndexImage + value: "false" + - name: mustOverwriteFromIndexImage + value: "false" + - name: iibServiceAccountSecret + value: "iib-services-config" + runAfter: + - setup + + - name: verify-results + params: + - name: sourceDataArtifact + value: "$(tasks.run-task.results.sourceDataArtifact)=$(params.dataDir)" + - name: dataDir + value: $(params.dataDir) + taskSpec: + params: + - name: sourceDataArtifact + type: string + description: Location of trusted artifacts to be used to populate data directory + - name: dataDir + type: string + description: The location where data will be stored + results: + - name: sourceDataArtifact + type: string + volumes: + - name: workdir + emptyDir: {} + stepTemplate: + volumeMounts: + - mountPath: /var/workdir + name: workdir + env: + - name: IMAGE_EXPIRES_AFTER + value: $(params.ociArtifactExpiresAfter) + - name: "ORAS_OPTIONS" + value: "$(params.orasOptions)" + - name: "DEBUG" + value: "$(params.trustedArtifactsDebug)" + steps: + - name: use-trusted-artifact + ref: + name: use-trusted-artifact + params: + - name: workDir + value: $(params.dataDir) + - name: sourceDataArtifact + value: $(params.sourceDataArtifact) + - name: orasOptions + value: $(params.orasOptions) + - name: verify-hotfix-processing + image: quay.io/konflux-ci/release-service-utils@sha256:5546fa78d3c88d7b6a2e8cff8902f7757f00541d0bbaf113b9f293133894afa3 + script: | + #!/usr/bin/env bash + set -eux + + echo "=== HOTFIX RELEASE VERIFICATION ===" + + # Verify results file was created + RESULTS_FILE="$(params.dataDir)/results/internal-requests-results.json" + if [[ -f "$RESULTS_FILE" ]]; then + echo "✅ Results file exists" + echo "Results contents:" + jq . "$RESULTS_FILE" + else + echo "❌ Results file missing at $RESULTS_FILE" + exit 1 + fi + + # Verify components were processed despite empty targetIndex + COMPONENTS_COUNT=$(jq '.components | length' "$RESULTS_FILE") + if [[ "$COMPONENTS_COUNT" -gt 0 ]]; then + echo "✅ Components were processed successfully: $COMPONENTS_COUNT" + else + echo "❌ No components processed" + exit 1 + fi + + # Verify hotfix release specific behavior + FIRST_COMPONENT_TARGET=$(jq -r '.components[0].target_index' "$RESULTS_FILE") + FIRST_COMPONENT_TARGET_TS=$(jq -r '.components[0].target_index_with_timestamp' "$RESULTS_FILE") + if [[ "$FIRST_COMPONENT_TARGET" =~ v4\.12-KONFLUX-123-[0-9]{10}$ ]]; then + echo "✅ Target index matches hotfix pattern with 10-digit timestamp" + else + echo "❌ Unexpected target index format, got: $FIRST_COMPONENT_TARGET" + exit 1 + fi + + # Verify that for hotfix, target_index and target_index_with_timestamp are the same + if [[ "$FIRST_COMPONENT_TARGET" == "$FIRST_COMPONENT_TARGET_TS" ]]; then + echo "✅ Hotfix: target_index equals target_index_with_timestamp (no duplication)" + else + echo "❌ Hotfix should have same target_index and target_index_with_timestamp" + echo " target_index: $FIRST_COMPONENT_TARGET" + echo " target_index_with_timestamp: $FIRST_COMPONENT_TARGET_TS" + exit 1 + fi + + echo "=== HOTFIX RELEASE TEST PASSED ===" + runAfter: + - run-task diff --git a/tasks/managed/collect-index-images/README.md b/tasks/managed/collect-index-images/README.md index 61f04a68fb..7cc8765fde 100644 --- a/tasks/managed/collect-index-images/README.md +++ b/tasks/managed/collect-index-images/README.md @@ -6,7 +6,6 @@ Tekton task that generates a JSON file to be used to create pyxis image for inde | Name | Description | Optional | Default value | |----------------------------|----------------------------------------------------------------------------------------------------------------------------|----------|----------------------| -| buildTimestamp | Build timestamp for the index image | No | - | | internalRequestResultsFile | Path to the results file of the InternalRequest build result | No | - | | ociStorage | The OCI repository where the Trusted Artifacts are stored | Yes | empty | | ociArtifactExpiresAfter | Expiration date for the trusted artifacts created in the OCI repository. An empty string means the artifacts do not expire | Yes | 1d | diff --git a/tasks/managed/collect-index-images/collect-index-images.yaml b/tasks/managed/collect-index-images/collect-index-images.yaml index 5930bfee7b..360946bf3a 100644 --- a/tasks/managed/collect-index-images/collect-index-images.yaml +++ b/tasks/managed/collect-index-images/collect-index-images.yaml @@ -10,9 +10,6 @@ spec: description: >- Tekton task that generates a JSON file to be used to create pyxis image for index images. params: - - name: buildTimestamp - type: string - description: Build timestamp for the index image - name: internalRequestResultsFile type: string description: Path to the results file of the InternalRequest build result @@ -130,18 +127,24 @@ spec: LENGTH="$(jq -r '.components | length' "$RESULTS_FILE")" for((i=0; i" or "v4.13-product-1.0-"), so appending - # a second timestamp would create a redundant tag. - if [[ "${TAG}" =~ ^v[0-9]+\.[0-9]+$ ]]; then - TAGS+=("${TAG}-$(params.buildTimestamp)") + if [[ "${TARGETINDEX}" != "${TARGETINDEX_TS}" ]]; then + TAG=${TARGETINDEX_TS#*:} + TAGS+=("${TAG}") fi JSON_TAGS=$(jq -n -c '$ARGS.positional' --args -- "${TAGS[@]}") diff --git a/tasks/managed/collect-index-images/tests/test-collect-index-images-hotfix.yaml b/tasks/managed/collect-index-images/tests/test-collect-index-images-hotfix.yaml index 3439e7df7b..5b1853f05c 100644 --- a/tasks/managed/collect-index-images/tests/test-collect-index-images-hotfix.yaml +++ b/tasks/managed/collect-index-images/tests/test-collect-index-images-hotfix.yaml @@ -5,8 +5,7 @@ metadata: name: test-collect-index-images-hotfix spec: description: | - Run the collect-index-images task for hotfix and pre-GA index images and verify - that no redundant buildTimestamp tag is appended to tags that already have unique suffixes + Run the collect-index-images task for hotfix index image and verify the results params: - name: ociStorage description: The OCI repository where the Trusted Artifacts are stored. @@ -59,12 +58,10 @@ spec: { "components": [ { - "target_index": "quay.io/redhat/redhat----fbc-target-index:v4.12-12345-6789", + "target_index": "quay.io/redhat/redhat----fbc-target-index:v4.12-6789-1774353228", + "target_index_with_timestamp": "quay.io/redhat/redhat----fbc-target-index:v4.12-6789-1774353228", + "completion_time": "1774353228", "index_image_resolved": "redhat.com/rh-stage/iib@sha256:abcdefghijk" - }, - { - "target_index": "quay.io/redhat/redhat----preview-operator-index:v4.13-myproduct-1.0-20250220143022", - "index_image_resolved": "redhat.com/rh-stage/iib@sha256:lmnopqrstuv" } ] } @@ -85,8 +82,7 @@ spec: params: - name: internalRequestResultsFile value: "internal-requests-results.json" - - name: buildTimestamp - value: "9999" + - name: ociStorage value: $(params.ociStorage) - name: orasOptions @@ -159,7 +155,7 @@ spec: fi if [ "$(jq -c '.components[0].repositories[0].tags' < "${SNAPSHOT_FILE}")" != \ - "[\"v4.12-12345-6789\"]" ]; then + "[\"v4.12-6789-1774353228\"]" ]; then echo "tags do not match" exit 1 fi @@ -191,45 +187,16 @@ spec: exit 1 fi - # Verify pre-GA component (index 1) - tag should remain as-is - if [ "$(jq -r '.components[1].containerImage' < "${SNAPSHOT_FILE}")" != \ - "redhat.com/rh-stage/iib@sha256:lmnopqrstuv" ]; then - echo "pre-GA containerImage does not match" - exit 1 - fi - - if [ "$(jq -r '.components[1].repositories[0].url' < "${SNAPSHOT_FILE}")" != \ - "quay.io/redhat/redhat----preview-operator-index" ]; then - echo "pre-GA repository does not match" - exit 1 - fi - - if [ "$(jq -c '.components[1].repositories[0].tags' < "${SNAPSHOT_FILE}")" != \ - "[\"v4.13-myproduct-1.0-20250220143022\"]" ]; then - echo "pre-GA tags do not match - buildTimestamp may have been incorrectly appended" - exit 1 - fi - # Remove from here to next comment when repository key support is removed if [ "$(jq -r '.components[0].repository' < "${SNAPSHOT_FILE}")" != \ "quay.io/redhat/redhat----fbc-target-index" ]; then echo "repository does not match" exit 1 fi - if [ "$(jq -c '.components[0].tags' < "${SNAPSHOT_FILE}")" != "[\"v4.12-12345-6789\"]" ]; then + if [ "$(jq -c '.components[0].tags' < "${SNAPSHOT_FILE}")" != "[\"v4.12-6789-1774353228\"]" ]; then echo "tags do not match" exit 1 fi - if [ "$(jq -r '.components[1].repository' < "${SNAPSHOT_FILE}")" != \ - "quay.io/redhat/redhat----preview-operator-index" ]; then - echo "pre-GA repository does not match" - exit 1 - fi - if [ "$(jq -c '.components[1].tags' < "${SNAPSHOT_FILE}")" != \ - "[\"v4.13-myproduct-1.0-20250220143022\"]" ]; then - echo "pre-GA tags do not match - buildTimestamp may have been incorrectly appended" - exit 1 - fi # End of block to be removed runAfter: - run-task diff --git a/tasks/managed/collect-index-images/tests/test-collect-index-images-multiple-versions.yaml b/tasks/managed/collect-index-images/tests/test-collect-index-images-multiple-versions.yaml index d350a2544d..8ea5e54a4d 100644 --- a/tasks/managed/collect-index-images/tests/test-collect-index-images-multiple-versions.yaml +++ b/tasks/managed/collect-index-images/tests/test-collect-index-images-multiple-versions.yaml @@ -59,11 +59,15 @@ spec: "components": [ { "target_index": "quay.io/redhat/redhat----fbc-target-index:v4.12", - "index_image_resolved": "redhat.com/rh-stage/iib@sha256:abcdefghijk" + "target_index_with_timestamp": "quay.io/redhat/redhat----fbc-target-index:v4.12-1774353228", + "index_image_resolved": "redhat.com/rh-stage/iib@sha256:abcdefghijk", + "completion_time": "1774353228" }, { "target_index": "quay.io/redhat/redhat----fbc-target-index:v4.13", - "index_image_resolved": "redhat.com/rh-stage/iib@sha256:lmnopqrstuv" + "target_index_with_timestamp": "quay.io/redhat/redhat----fbc-target-index:v4.13-1774353229", + "index_image_resolved": "redhat.com/rh-stage/iib@sha256:lmnopqrstuv", + "completion_time": "1774353229" } ] } @@ -84,8 +88,6 @@ spec: params: - name: internalRequestResultsFile value: "internal-requests-results.json" - - name: buildTimestamp - value: "1357" - name: ociStorage value: $(params.ociStorage) - name: orasOptions @@ -158,7 +160,7 @@ spec: fi if [ "$(jq -c '.components[0].repositories[0].tags' < "${SNAPSHOT_FILE}")" != \ - "[\"v4.12\",\"v4.12-1357\"]" ]; then + "[\"v4.12\",\"v4.12-1774353228\"]" ]; then echo "tags do not match" exit 1 fi @@ -196,7 +198,7 @@ spec: echo "repository does not match" exit 1 fi - if [ "$(jq -c '.components[0].tags' < "${SNAPSHOT_FILE}")" != "[\"v4.12\",\"v4.12-1357\"]" ]; then + if [ "$(jq -c '.components[0].tags' < "${SNAPSHOT_FILE}")" != "[\"v4.12\",\"v4.12-1774353228\"]" ]; then echo "tags do not match" exit 1 fi @@ -215,7 +217,7 @@ spec: fi if [ "$(jq -c '.components[1].repositories[0].tags' < "${SNAPSHOT_FILE}")" != \ - "[\"v4.13\",\"v4.13-1357\"]" ]; then + "[\"v4.13\",\"v4.13-1774353229\"]" ]; then echo "tags do not match" exit 1 fi @@ -253,7 +255,7 @@ spec: echo "repository does not match" exit 1 fi - if [ "$(jq -c '.components[1].tags' < "${SNAPSHOT_FILE}")" != "[\"v4.13\",\"v4.13-1357\"]" ]; then + if [ "$(jq -c '.components[1].tags' < "${SNAPSHOT_FILE}")" != "[\"v4.13\",\"v4.13-1774353229\"]" ]; then echo "tags do not match" exit 1 fi diff --git a/tasks/managed/collect-index-images/tests/test-collect-index-images-single-version.yaml b/tasks/managed/collect-index-images/tests/test-collect-index-images-single-version.yaml index 01d02ee446..33675409f0 100644 --- a/tasks/managed/collect-index-images/tests/test-collect-index-images-single-version.yaml +++ b/tasks/managed/collect-index-images/tests/test-collect-index-images-single-version.yaml @@ -59,7 +59,9 @@ spec: "components": [ { "target_index": "quay.io/redhat/redhat----fbc-target-index:v4.12", - "index_image_resolved": "redhat.com/rh-stage/iib@sha256:abcdefghijk" + "target_index_with_timestamp": "quay.io/redhat/redhat----fbc-target-index:v4.12-1774353228", + "index_image_resolved": "redhat.com/rh-stage/iib@sha256:abcdefghijk", + "completion_time": "1774353228" } ] } @@ -80,8 +82,6 @@ spec: params: - name: internalRequestResultsFile value: "internal-requests-results.json" - - name: buildTimestamp - value: "2468" - name: ociStorage value: $(params.ociStorage) - name: orasOptions @@ -154,7 +154,7 @@ spec: fi if [ "$(jq -c '.components[0].repositories[0].tags' < "${SNAPSHOT_FILE}")" != \ - "[\"v4.12\",\"v4.12-2468\"]" ]; then + "[\"v4.12\",\"v4.12-1774353228\"]" ]; then echo "tags do not match" exit 1 fi @@ -192,7 +192,7 @@ spec: echo "repository does not match" exit 1 fi - if [ "$(jq -c '.components[0].tags' < "${SNAPSHOT_FILE}")" != "[\"v4.12\",\"v4.12-2468\"]" ]; then + if [ "$(jq -c '.components[0].tags' < "${SNAPSHOT_FILE}")" != "[\"v4.12\",\"v4.12-1774353228\"]" ]; then echo "tags do not match" exit 1 fi diff --git a/tasks/managed/extract-index-image/README.md b/tasks/managed/extract-index-image/README.md index 589ef9fec4..147c5411a7 100644 --- a/tasks/managed/extract-index-image/README.md +++ b/tasks/managed/extract-index-image/README.md @@ -1,15 +1,14 @@ # extract-index-image -Extract the index image fields from the inputDataFile +Extract the index image fields from the internalRequestResultsFile. -The inputDataFile is a result from another task which includes the workspace name in it. Thus, -the workspace name for this task *must* be input. +The internalRequestResultsFile is a result from another task that contains the result of a finished +internal-request. ## Parameters | Name | Description | Optional | Default value | |----------------------------|----------------------------------------------------------------------------------------------------------------------------|----------|----------------------| -| inputDataFile | Result from another task which includes the workspace name in it | No | - | | resultsDirPath | Path to the results directory in the data workspace | No | - | | internalRequestResultsFile | Path to the results file of the InternalRequest build result | No | - | | ociStorage | The OCI repository where the Trusted Artifacts are stored | Yes | empty | diff --git a/tasks/managed/extract-index-image/extract-index-image.yaml b/tasks/managed/extract-index-image/extract-index-image.yaml index ba9a3ec07e..f05e6b4859 100644 --- a/tasks/managed/extract-index-image/extract-index-image.yaml +++ b/tasks/managed/extract-index-image/extract-index-image.yaml @@ -8,14 +8,11 @@ metadata: tekton.dev/tags: release spec: description: |- - Extract the index image fields from the inputDataFile + Extract the index image fields from the internalRequestResultsFile. - The inputDataFile is a result from another task which includes the workspace name in it. Thus, - the workspace name for this task *must* be input. + The internalRequestResultsFile is a result from another task that contains the result of a finished + internal-request. params: - - name: inputDataFile - type: string - description: Result from another task which includes the workspace name in it - name: resultsDirPath type: string description: Path to the results directory in the data workspace diff --git a/tasks/managed/extract-index-image/tests/test-extract-index-image-fail-no-inputdatafile.yaml b/tasks/managed/extract-index-image/tests/test-extract-index-image-fail-no-inputdatafile.yaml deleted file mode 100644 index b31129db0b..0000000000 --- a/tasks/managed/extract-index-image/tests/test-extract-index-image-fail-no-inputdatafile.yaml +++ /dev/null @@ -1,93 +0,0 @@ ---- -apiVersion: tekton.dev/v1 -kind: Pipeline -metadata: - name: test-extract-index-image-fail-no-inputdatafile -spec: - description: | - Run the extract-index-image task with no inputDataFile present. The task - should fail. - params: - - name: ociStorage - description: The OCI repository where the Trusted Artifacts are stored. - type: string - - name: ociArtifactExpiresAfter - description: Expiration date for the trusted artifacts created in the - OCI repository. An empty string means the artifacts do not expire. - type: string - default: "1d" - - name: orasOptions - description: oras options to pass to Trusted Artifacts calls - type: string - default: "--insecure" - - name: trustedArtifactsDebug - description: Flag to enable debug logging in trusted artifacts. Set to a non-empty string to enable. - type: string - default: "" - - name: dataDir - description: The location where data will be stored - type: string - tasks: - - name: setup - taskSpec: - results: - - name: sourceDataArtifact - type: string - volumes: - - name: workdir - emptyDir: {} - stepTemplate: - volumeMounts: - - mountPath: /var/workdir - name: workdir - env: - - name: IMAGE_EXPIRES_AFTER - value: $(params.ociArtifactExpiresAfter) - - name: "ORAS_OPTIONS" - value: "$(params.orasOptions)" - - name: "DEBUG" - value: "$(params.trustedArtifactsDebug)" - steps: - - name: setup-values - image: quay.io/konflux-ci/release-service-utils@sha256:5546fa78d3c88d7b6a2e8cff8902f7757f00541d0bbaf113b9f293133894afa3 - script: | - #!/usr/bin/env sh - set -eux - - mkdir -p "$(params.dataDir)/$(context.pipelineRun.uid)/results" - - name: create-trusted-artifact - ref: - name: create-trusted-artifact - params: - - name: ociStorage - value: $(params.ociStorage) - - name: workDir - value: $(params.dataDir) - - name: sourceDataArtifact - value: $(results.sourceDataArtifact.path) - - name: run-task - taskRef: - name: extract-index-image - params: - - name: inputDataFile - value: $(params.dataDir)/$(context.pipelineRun.uid)/file.json - - name: resultsDirPath - value: $(context.pipelineRun.uid)/results - - name: internalRequestResultsFile - value: "internal-request-results.json" - - name: ociStorage - value: $(params.ociStorage) - - name: orasOptions - value: $(params.orasOptions) - - name: sourceDataArtifact - value: "$(tasks.setup.results.sourceDataArtifact)=$(params.dataDir)" - - name: dataDir - value: $(params.dataDir) - - name: trustedArtifactsDebug - value: $(params.trustedArtifactsDebug) - - name: taskGitUrl - value: "http://localhost" - - name: taskGitRevision - value: "main" - runAfter: - - setup diff --git a/tasks/managed/extract-index-image/tests/test-extract-index-image-multiple-components.yaml b/tasks/managed/extract-index-image/tests/test-extract-index-image-multiple-components.yaml index ae468c23c1..80ddadaca8 100644 --- a/tasks/managed/extract-index-image/tests/test-extract-index-image-multiple-components.yaml +++ b/tasks/managed/extract-index-image/tests/test-extract-index-image-multiple-components.yaml @@ -140,8 +140,6 @@ spec: taskRef: name: extract-index-image params: - - name: inputDataFile - value: $(params.dataDir)/$(context.pipelineRun.uid)/file.json - name: resultsDirPath value: $(context.pipelineRun.uid)/results - name: internalRequestResultsFile diff --git a/tasks/managed/extract-index-image/tests/test-extract-index-image.yaml b/tasks/managed/extract-index-image/tests/test-extract-index-image.yaml index 2fabca4461..b523040d7c 100644 --- a/tasks/managed/extract-index-image/tests/test-extract-index-image.yaml +++ b/tasks/managed/extract-index-image/tests/test-extract-index-image.yaml @@ -127,8 +127,6 @@ spec: taskRef: name: extract-index-image params: - - name: inputDataFile - value: $(params.dataDir)/$(context.pipelineRun.uid)/file.json - name: resultsDirPath value: $(context.pipelineRun.uid)/results - name: internalRequestResultsFile diff --git a/tasks/managed/prepare-fbc-snapshot/prepare-fbc-snapshot.yaml b/tasks/managed/prepare-fbc-snapshot/prepare-fbc-snapshot.yaml index 5fa8ef5db4..724c202059 100644 --- a/tasks/managed/prepare-fbc-snapshot/prepare-fbc-snapshot.yaml +++ b/tasks/managed/prepare-fbc-snapshot/prepare-fbc-snapshot.yaml @@ -285,8 +285,9 @@ spec: } # Generate common suffix for hotfix/pre-GA modes - timestamp_format=$(jq -r '.fbc.timestampFormat // "%s"' "${DATA_FILE}") - timestamp=$(date "+${timestamp_format}") + # this timestamp is arbitrary as "completion_time" is only available + # after building the index. + timestamp=$(date "+%s") common_suffix="" max_tag_length=128 timestamp_length=${#timestamp} diff --git a/tasks/managed/prepare-fbc-snapshot/tests/test-prepare-fbc-snapshot-hotfix.yaml b/tasks/managed/prepare-fbc-snapshot/tests/test-prepare-fbc-snapshot-hotfix.yaml index a6b73b5fcf..54e6e47baf 100644 --- a/tasks/managed/prepare-fbc-snapshot/tests/test-prepare-fbc-snapshot-hotfix.yaml +++ b/tasks/managed/prepare-fbc-snapshot/tests/test-prepare-fbc-snapshot-hotfix.yaml @@ -97,8 +97,7 @@ spec: "fromIndex": "quay.io/redhat/redhat-operator-index:{{ OCP_VERSION }}", "targetIndex": "quay.io/test/target-index:{{ OCP_VERSION }}", "hotfix": true, - "issueId": "RELEASE-1234", - "timestampFormat": "%Y%m%d-%H%M%S" + "issueId": "RELEASE-1234" } } EOF @@ -203,12 +202,12 @@ spec: TARGET_INDEX=$(jq -r '.components[0].targetIndex' "$SNAPSHOT_PATH") echo "Target index: $TARGET_INDEX" - # Verify hotfix suffix pattern: v4.12-RELEASE-1234-timestamp - if [[ "$TARGET_INDEX" =~ v[0-9]+\.[0-9]+-RELEASE-1234-[0-9]{8}-[0-9]{6} ]]; then - echo "✅ Hotfix suffix correctly applied" + # Verify hotfix suffix pattern: v4.12-RELEASE-1234-<10-digit-timestamp> + if [[ "$TARGET_INDEX" =~ v[0-9]+\.[0-9]+-RELEASE-1234-[0-9]{10}$ ]]; then + echo "✅ Hotfix suffix correctly applied with 10-digit timestamp" else echo "❌ Hotfix suffix not found or incorrect format" - echo "Expected pattern: v4.12-RELEASE-1234-YYYYMMDD-HHMMSS" + echo "Expected pattern: v4.12-RELEASE-1234-<10-digit-timestamp>" echo "Actual: $TARGET_INDEX" exit 1 fi diff --git a/tasks/managed/publish-index-image/README.md b/tasks/managed/publish-index-image/README.md index 40682ab5b8..a8ebed113f 100644 --- a/tasks/managed/publish-index-image/README.md +++ b/tasks/managed/publish-index-image/README.md @@ -10,7 +10,6 @@ Publish a built FBC index image using skopeo | internalRequestResultsFile | File containing the results of the build | No | - | | retries | Number of skopeo retries | Yes | 0 | | requestTimeout | Max seconds waiting for the status update | Yes | 360 | -| buildTimestamp | Build timestamp for the publishing image | No | - | | pipelineRunUid | The uid of the current pipelineRun. Used as a label value when creating internal requests | No | - | | ociStorage | The OCI repository where the Trusted Artifacts are stored | Yes | empty | | ociArtifactExpiresAfter | Expiration date for the trusted artifacts created in the OCI repository. An empty string means the artifacts do not expire | Yes | 1d | diff --git a/tasks/managed/publish-index-image/publish-index-image.yaml b/tasks/managed/publish-index-image/publish-index-image.yaml index 56b0d650a7..434dee73b5 100644 --- a/tasks/managed/publish-index-image/publish-index-image.yaml +++ b/tasks/managed/publish-index-image/publish-index-image.yaml @@ -24,9 +24,6 @@ spec: type: string default: "360" description: Max seconds waiting for the status update - - name: buildTimestamp - type: string - description: Build timestamp for the publishing image - name: pipelineRunUid type: string description: The uid of the current pipelineRun. Used as a label value when creating internal requests @@ -150,6 +147,8 @@ spec: for((i=0; i Date: Mon, 29 Jun 2026 16:32:43 +0200 Subject: [PATCH 2/7] fix: use index_image_resolved for ts index Signed-off-by: Leandro Mendes --- .../publish-index-image.yaml | 9 +++++++++ ...st-publish-index-image-with-timestamp.yaml | 20 ++++++++++++++++--- .../tests/test-publish-index-image.yaml | 20 ++++++++++++++++--- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/tasks/managed/publish-index-image/publish-index-image.yaml b/tasks/managed/publish-index-image/publish-index-image.yaml index 434dee73b5..e590df6075 100644 --- a/tasks/managed/publish-index-image/publish-index-image.yaml +++ b/tasks/managed/publish-index-image/publish-index-image.yaml @@ -152,6 +152,8 @@ spec: sourceIndex="$(jq -r --argjson i "$i" \ '.components[$i].index_image' "$(params.dataDir)/$(params.internalRequestResultsFile)")" + sourceResolvedIndex="$(jq -r --argjson i "$i" \ + '.components[$i].index_image_resolved' "$(params.dataDir)/$(params.internalRequestResultsFile)")" targetOcpVersion=$(jq -r --argjson i "$i" \ '.components[$i].ocp_version' "$(params.dataDir)/$(params.internalRequestResultsFile)") @@ -173,6 +175,13 @@ spec: for((x=0; x<${#publishingImages[@]}; x++ )); do echo "=== Creating internal request to publish image:" echo "" + + # if we are pushing the timestamped index (always the index #1), we should + # use index_image_resolved instead, because the target_index sha can poten_ + # tially be changed by a concurrent fbc process. + if [ "$x" -eq 1 ]; then + sourceIndex="$sourceResolvedIndex" + fi echo "- from: ${sourceIndex}" echo "- to: ${publishingImages[$x]}" diff --git a/tasks/managed/publish-index-image/tests/test-publish-index-image-with-timestamp.yaml b/tasks/managed/publish-index-image/tests/test-publish-index-image-with-timestamp.yaml index eff5a1f20b..2c7f315ced 100644 --- a/tasks/managed/publish-index-image/tests/test-publish-index-image-with-timestamp.yaml +++ b/tasks/managed/publish-index-image/tests/test-publish-index-image-with-timestamp.yaml @@ -193,9 +193,23 @@ spec: exit 1 fi - if [ "$(jq -r '.sourceIndex' <<< "${params}")" != "redhat.com/rh-stage/iib:01" ]; then - echo "sourceIndex image does not match for IR $i" - exit 1 + sourceIndex=$(jq -r '.sourceIndex' <<< "${params}") + # IR 0 (first created, x=0) should use index_image (tag) + # IR 1 (second created, x=1) should use index_image_resolved (digest) + if [ $i = 0 ]; then + if [ "$sourceIndex" != "redhat.com/rh-stage/iib:01" ]; then + echo "sourceIndex image does not match for IR 0" + echo "Expected: redhat.com/rh-stage/iib:01" + echo "Got: $sourceIndex" + exit 1 + fi + else + if [ "$sourceIndex" != "redhat.com/rh-stage/iib@sha256:abcdefghijk" ]; then + echo "sourceIndex image does not match for IR 1" + echo "Expected: redhat.com/rh-stage/iib@sha256:abcdefghijk" + echo "Got: $sourceIndex" + exit 1 + fi fi targetIndex=$(jq -r '.targetIndex' <<< "${params}") diff --git a/tasks/managed/publish-index-image/tests/test-publish-index-image.yaml b/tasks/managed/publish-index-image/tests/test-publish-index-image.yaml index 7e9d347e88..91fabb8ccc 100644 --- a/tasks/managed/publish-index-image/tests/test-publish-index-image.yaml +++ b/tasks/managed/publish-index-image/tests/test-publish-index-image.yaml @@ -188,9 +188,23 @@ spec: exit 1 fi - if [ "$(jq -r '.sourceIndex' <<< "${params}")" != "redhat.com/rh-stage/iib:01" ]; then - echo "sourceIndex image does not match" - exit 1 + sourceIndex=$(jq -r '.sourceIndex' <<< "${params}") + # IR 0 (first created, x=0) should use index_image (tag) + # IR 1 (second created, x=1) should use index_image_resolved (digest) + if [ $i = 0 ]; then + if [ "$sourceIndex" != "redhat.com/rh-stage/iib:01" ]; then + echo "sourceIndex image does not match for IR 0" + echo "Expected: redhat.com/rh-stage/iib:01" + echo "Got: $sourceIndex" + exit 1 + fi + else + if [ "$sourceIndex" != "redhat.com/rh-stage/iib@sha256:abcdefghijk" ]; then + echo "sourceIndex image does not match for IR 1" + echo "Expected: redhat.com/rh-stage/iib@sha256:abcdefghijk" + echo "Got: $sourceIndex" + exit 1 + fi fi targetIndex=$(jq -r '.targetIndex' <<< "${params}") From 1da115f7ca538771fc286f2843099dbc9ed03261 Mon Sep 17 00:00:00 2001 From: Leandro Mendes Date: Tue, 30 Jun 2026 15:46:53 +0200 Subject: [PATCH 3/7] fix: user index_image_resolved as source Signed-off-by: Leandro Mendes --- .../collect-index-images.yaml | 92 +++++++++---------- .../publish-index-image.yaml | 10 +- 2 files changed, 45 insertions(+), 57 deletions(-) diff --git a/tasks/managed/collect-index-images/collect-index-images.yaml b/tasks/managed/collect-index-images/collect-index-images.yaml index 360946bf3a..34dc2e44e3 100644 --- a/tasks/managed/collect-index-images/collect-index-images.yaml +++ b/tasks/managed/collect-index-images/collect-index-images.yaml @@ -130,61 +130,53 @@ spec: TARGETINDEX_TS=$(jq -r --argjson i "$i" '.components[$i].target_index_with_timestamp' "$RESULTS_FILE") SOURCEINDEX=$(jq -r --argjson i "$i" '.components[$i].index_image_resolved' "$RESULTS_FILE") IMAGE_DIGESTS=$(jq -c --argjson i "$i" '.components[$i].image_digests // []' "$RESULTS_FILE") - COMPLETION_TIME=$(jq -r --argjson i "$i" '.components[$i].completion_time // empty' "$RESULTS_FILE") - # Validate completion_time is present - if [[ -z "${COMPLETION_TIME}" || "${COMPLETION_TIME}" == "null" ]]; then - echo "ERROR: Component $i missing completion_time in results file" - exit 1 - fi + # create pyxis entry for the component for tags in separated + for TARGET in $TARGETINDEX $TARGETINDEX_TS; do + REPOSITORY=${TARGET%:*} - REPOSITORY=${TARGETINDEX%:*} + TAG=${TARGET#*:} + TAGS=("${TAG}") + JSON_TAGS=$(jq -n -c '$ARGS.positional' --args -- "${TAGS[@]}") - TAG=${TARGETINDEX#*:} - TAGS=("${TAG}") - if [[ "${TARGETINDEX}" != "${TARGETINDEX_TS}" ]]; then - TAG=${TARGETINDEX_TS#*:} - TAGS+=("${TAG}") - fi - JSON_TAGS=$(jq -n -c '$ARGS.positional' --args -- "${TAGS[@]}") + # Translate target_index to get rh-registry-repo and registry-access-repo + TRANSLATED=$(translate-delivery-repo "$TARGETINDEX") + RH_REGISTRY_REPO=$(jq -r '.[] | select(.repo=="redhat.io") | .url' <<< "$TRANSLATED" \ + | cut -d':' -f1) + REGISTRY_ACCESS_REPO=$(jq -r '.[] | select(.repo=="access.redhat.com") | .url' \ + <<< "$TRANSLATED" | cut -d':' -f1) - # Translate target_index to get rh-registry-repo and registry-access-repo - TRANSLATED=$(translate-delivery-repo "$TARGETINDEX") - RH_REGISTRY_REPO=$(jq -r '.[] | select(.repo=="redhat.io") | .url' <<< "$TRANSLATED" \ - | cut -d':' -f1) - REGISTRY_ACCESS_REPO=$(jq -r '.[] | select(.repo=="access.redhat.com") | .url' \ - <<< "$TRANSLATED" | cut -d':' -f1) + # Build repository object with translated fields + REPO_OBJECT=$(jq -n \ + --arg url "${REPOSITORY}" \ + --argjson tags "${JSON_TAGS}" \ + --arg rh_registry_repo "${RH_REGISTRY_REPO}" \ + --arg registry_access_repo "${REGISTRY_ACCESS_REPO}" \ + '{ + "url": $url, + "tags": $tags + } + (if $rh_registry_repo != "" then {"rh-registry-repo": $rh_registry_repo} else {} end) + + (if $registry_access_repo != "" then {"registry-access-repo": $registry_access_repo} else {} end)' + ) - # Build repository object with translated fields - REPO_OBJECT=$(jq -n \ - --arg url "${REPOSITORY}" \ - --argjson tags "${JSON_TAGS}" \ - --arg rh_registry_repo "${RH_REGISTRY_REPO}" \ - --arg registry_access_repo "${REGISTRY_ACCESS_REPO}" \ - '{ - "url": $url, - "tags": $tags - } + (if $rh_registry_repo != "" then {"rh-registry-repo": $rh_registry_repo} else {} end) - + (if $registry_access_repo != "" then {"registry-access-repo": $registry_access_repo} else {} end)' - ) - - COMPONENT=$(jq -n \ - --arg image "${SOURCEINDEX}" \ - --arg repository "${REPOSITORY}" \ - --argjson tags "${JSON_TAGS}" \ - --argjson image_digests "${IMAGE_DIGESTS}" \ - --argjson repo_object "${REPO_OBJECT}" \ - '{ - "containerImage": $image, - "repository": $repository, - "repositories": [$repo_object], - "tags": $tags, - "imageDigests": $image_digests - }' - ) - export COMPONENT - yq -i '.components += env(COMPONENT) ' "$SNAPSHOT_FILE" - done + COMPONENT=$(jq -n \ + --arg image "${SOURCEINDEX}" \ + --arg repository "${REPOSITORY}" \ + --argjson tags "${JSON_TAGS}" \ + --argjson image_digests "${IMAGE_DIGESTS}" \ + --argjson repo_object "${REPO_OBJECT}" \ + '{ + "containerImage": $image, + "repository": $repository, + "repositories": [$repo_object], + "tags": $tags, + "imageDigests": $image_digests + }' + ) + export COMPONENT + yq -i '.components += env(COMPONENT) ' "$SNAPSHOT_FILE" + done # end taget index loop + done # end components loop echo -n "index_image_snapshot.json" > "$(results.indexImageSnapshot.path)" - name: create-trusted-artifact computeResources: diff --git a/tasks/managed/publish-index-image/publish-index-image.yaml b/tasks/managed/publish-index-image/publish-index-image.yaml index e590df6075..9e50b1400e 100644 --- a/tasks/managed/publish-index-image/publish-index-image.yaml +++ b/tasks/managed/publish-index-image/publish-index-image.yaml @@ -152,7 +152,7 @@ spec: sourceIndex="$(jq -r --argjson i "$i" \ '.components[$i].index_image' "$(params.dataDir)/$(params.internalRequestResultsFile)")" - sourceResolvedIndex="$(jq -r --argjson i "$i" \ + sourceIndexResolved="$(jq -r --argjson i "$i" \ '.components[$i].index_image_resolved' "$(params.dataDir)/$(params.internalRequestResultsFile)")" targetOcpVersion=$(jq -r --argjson i "$i" \ @@ -175,14 +175,10 @@ spec: for((x=0; x<${#publishingImages[@]}; x++ )); do echo "=== Creating internal request to publish image:" echo "" - - # if we are pushing the timestamped index (always the index #1), we should - # use index_image_resolved instead, because the target_index sha can poten_ - # tially be changed by a concurrent fbc process. if [ "$x" -eq 1 ]; then - sourceIndex="$sourceResolvedIndex" + sourceIndex="$sourceIndexResolved" fi - echo "- from: ${sourceIndex}" + echo "- from: ${sourceIndexResolved}" echo "- to: ${publishingImages[$x]}" internal-request --pipeline "${request}" \ From 23e28c206f8c4fa1af16221f5e9a038e42c562b5 Mon Sep 17 00:00:00 2001 From: Leandro Mendes Date: Tue, 30 Jun 2026 17:03:20 +0200 Subject: [PATCH 4/7] fix(KONFLUX-12379): fix inspect-target-index and collect-index-images - inspect-target-index-task: add missing targetIndex parameter to skopeo - collect-index-images: fix IMAGE_DIGESTS handling - Save original digests from IIB build before loop - Reset to original digests at start of each iteration - Inspect base tag only (not timestamped tag) for regular releases - Extract InternalRequest result properly from kubectl - Base tag gets fresh digests, timestamped tag uses original Assisted-By: Claude Signed-off-by: Leandro Mendes --- .../inspect-target-index-pipeline/README.md | 12 ++++ .../inspect-target-index-pipeline.yaml | 44 ++++++++++++ .../managed/fbc-release/fbc-release.yaml | 4 ++ .../inspect-target-index-task/README.md | 12 ++++ .../inspect-target-index-task.yaml | 71 +++++++++++++++++++ tasks/managed/collect-index-images/README.md | 2 + .../collect-index-images.yaml | 43 ++++++++++- .../test-collect-index-images-hotfix.yaml | 14 +++- ...ollect-index-images-multiple-versions.yaml | 13 +++- ...t-collect-index-images-single-version.yaml | 13 +++- 10 files changed, 223 insertions(+), 5 deletions(-) create mode 100644 pipelines/internal/inspect-target-index-pipeline/README.md create mode 100644 pipelines/internal/inspect-target-index-pipeline/inspect-target-index-pipeline.yaml create mode 100644 tasks/internal/inspect-target-index-task/README.md create mode 100644 tasks/internal/inspect-target-index-task/inspect-target-index-task.yaml diff --git a/pipelines/internal/inspect-target-index-pipeline/README.md b/pipelines/internal/inspect-target-index-pipeline/README.md new file mode 100644 index 0000000000..a15f2c674c --- /dev/null +++ b/pipelines/internal/inspect-target-index-pipeline/README.md @@ -0,0 +1,12 @@ +# inspect-target-index pipeline + +Tekton pipeline to inspect a built FBC target index image using skopeo + +## Parameters + +| Name | Description | Optional | Default value | +|--------------------|---------------------------------------------------------------------------------------|----------|-----------------------------------------------------------| +| targetIndex | targetIndex signing image | No | - | +| inspectCredentials | The credentials used to access the registries | No | - | +| taskGitUrl | The url to the git repo where the release-service-catalog tasks to be used are stored | Yes | https://github.com/konflux-ci/release-service-catalog.git | +| taskGitRevision | The revision in the taskGitUrl repo to be used | No | - | diff --git a/pipelines/internal/inspect-target-index-pipeline/inspect-target-index-pipeline.yaml b/pipelines/internal/inspect-target-index-pipeline/inspect-target-index-pipeline.yaml new file mode 100644 index 0000000000..55ac9eef0d --- /dev/null +++ b/pipelines/internal/inspect-target-index-pipeline/inspect-target-index-pipeline.yaml @@ -0,0 +1,44 @@ +--- +apiVersion: tekton.dev/v1 +kind: Pipeline +metadata: + name: inspect-target-index-pipeline + annotations: + tekton.dev/pipelines.minVersion: "0.12.1" + tekton.dev/tags: release +spec: + description: |- + Tekton pipeline to inspect a built FBC target index image using skopeo + params: + - name: targetIndex + type: string + description: targetIndex signing image + - name: inspectCredentials + type: string + description: The credentials used to access the registries + - name: taskGitUrl + type: string + description: The url to the git repo where the release-service-catalog tasks to be used are stored + default: https://github.com/konflux-ci/release-service-catalog.git + - name: taskGitRevision + type: string + description: The revision in the taskGitUrl repo to be used + tasks: + - name: inspect-target-index-task + taskRef: + resolver: "git" + params: + - name: url + value: $(params.taskGitUrl) + - name: revision + value: $(params.taskGitRevision) + - name: pathInRepo + value: tasks/internal/inspect-target-index-task/inspect-target-index-task.yaml + params: + - name: targetIndex + value: $(params.targetIndex) + - name: inspectCredentials + value: $(params.inspectCredentials) + results: + - name: requestMessage + value: $(tasks.inspect-target-index-task.results.requestMessage) diff --git a/pipelines/managed/fbc-release/fbc-release.yaml b/pipelines/managed/fbc-release/fbc-release.yaml index 206092fcf2..fb26cf6e35 100644 --- a/pipelines/managed/fbc-release/fbc-release.yaml +++ b/pipelines/managed/fbc-release/fbc-release.yaml @@ -561,8 +561,12 @@ spec: - name: pathInRepo value: tasks/managed/collect-index-images/collect-index-images.yaml params: + - name: dataPath + value: "$(tasks.collect-data.results.data)" - name: internalRequestResultsFile value: $(tasks.add-fbc-contribution-to-index-image.results.internalRequestResultsFile) + - name: pipelineRunUid + value: $(context.pipelineRun.uid) - name: ociStorage value: $(params.ociStorage) - name: sourceDataArtifact diff --git a/tasks/internal/inspect-target-index-task/README.md b/tasks/internal/inspect-target-index-task/README.md new file mode 100644 index 0000000000..4305a0f98b --- /dev/null +++ b/tasks/internal/inspect-target-index-task/README.md @@ -0,0 +1,12 @@ +# inspect-target-index-task + +Tekton task to inspect a built FBC target index image using skopeo + +## Parameters + +| Name | Description | Optional | Default value | +|----------------------|-----------------------------------------------------------------------|----------|----------------------------| +| targetIndex | Target Image pullspec to be inspected | No | - | +| inspectCredentials | The credentials used to access the registries | Yes | fbc-publishing-credentials | +| caTrustConfigMapName | The name of the ConfigMap to read CA bundle data from | Yes | trusted-ca | +| caTrustConfigMapKey | The name of the key in the ConfigMap that contains the CA bundle data | Yes | ca-bundle.crt | diff --git a/tasks/internal/inspect-target-index-task/inspect-target-index-task.yaml b/tasks/internal/inspect-target-index-task/inspect-target-index-task.yaml new file mode 100644 index 0000000000..d62bf6abee --- /dev/null +++ b/tasks/internal/inspect-target-index-task/inspect-target-index-task.yaml @@ -0,0 +1,71 @@ +--- +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: inspect-target-index-task + annotations: + tekton.dev/pipelines.minVersion: "0.12.1" + tekton.dev/tags: release +spec: + description: |- + Tekton task to inspect a built FBC target index image using skopeo + params: + - name: targetIndex + type: string + description: Target Image pullspec to be inspected + - name: inspectCredentials + type: string + default: "fbc-publishing-credentials" + description: The credentials used to access the registries + - name: caTrustConfigMapName + type: string + description: The name of the ConfigMap to read CA bundle data from + default: trusted-ca + - name: caTrustConfigMapKey + type: string + description: The name of the key in the ConfigMap that contains the CA bundle data + default: ca-bundle.crt + results: + - name: requestMessage + volumes: + - name: inspect-credentials + secret: + secretName: $(params.inspectCredentials) + defaultMode: 0444 + - name: trusted-ca + configMap: + name: $(params.caTrustConfigMapName) + items: + - key: $(params.caTrustConfigMapKey) + path: ca-bundle.crt + optional: true + stepTemplate: + volumeMounts: + - name: trusted-ca + mountPath: /mnt/trusted-ca + readOnly: true + steps: + - name: inspect-image + volumeMounts: + - name: inspect-credentials + mountPath: /mnt/inspectCredentials + securityContext: + runAsUser: 1001 + image: >- + quay.io/konflux-ci/release-service-utils@sha256:5546fa78d3c88d7b6a2e8cff8902f7757f00541d0bbaf113b9f293133894afa3 + computeResources: + limits: + memory: 64Mi + requests: + memory: 64Mi + cpu: 400m + script: | + #!/usr/bin/env bash + set -euo pipefail + + PATH=/bin:/usr/bin:/usr/local/bin + export PATH + + TARGET_INDEX_CREDENTIALS="$(cat /mnt/inspectCredentials/targetIndexCredential)" + skopeo inspect --raw --creds "${TARGET_INDEX_CREDENTIALS}" "docker://$(params.targetIndex)" \ + | jq -c '.' | tee "$(results.requestMessage.path)" diff --git a/tasks/managed/collect-index-images/README.md b/tasks/managed/collect-index-images/README.md index 7cc8765fde..a8fe2d0f1c 100644 --- a/tasks/managed/collect-index-images/README.md +++ b/tasks/managed/collect-index-images/README.md @@ -13,6 +13,8 @@ Tekton task that generates a JSON file to be used to create pyxis image for inde | orasOptions | oras options to pass to Trusted Artifacts calls | Yes | "" | | sourceDataArtifact | Location of trusted artifacts to be used to populate data directory | Yes | "" | | dataDir | The location where data will be stored | Yes | /var/workdir/release | +| dataPath | Path to the JSON string of the merged data to use in the data workspace | No | - | +| pipelineRunUid | The uid of the current pipelineRun. Used as a label value when creating internal requests | No | - | | taskGitUrl | The url to the git repo where the release-service-catalog tasks and stepactions to be used are stored | No | - | | taskGitRevision | The revision in the taskGitUrl repo to be used | No | - | | caTrustConfigMapName | The name of the ConfigMap to read CA bundle data from | Yes | trusted-ca | diff --git a/tasks/managed/collect-index-images/collect-index-images.yaml b/tasks/managed/collect-index-images/collect-index-images.yaml index 34dc2e44e3..242c05fe96 100644 --- a/tasks/managed/collect-index-images/collect-index-images.yaml +++ b/tasks/managed/collect-index-images/collect-index-images.yaml @@ -38,6 +38,12 @@ spec: description: The location where data will be stored type: string default: /var/workdir/release + - name: dataPath + description: Path to the JSON string of the merged data to use in the data workspace + type: string + - name: pipelineRunUid + type: string + description: The uid of the current pipelineRun. Used as a label value when creating internal requests - name: taskGitUrl type: string description: The url to the git repo where the release-service-catalog tasks and stepactions to be used are stored @@ -120,16 +126,28 @@ spec: #!/usr/bin/env bash set -eux + DATA_FILE="$(params.dataDir)/$(params.dataPath)" + if [ ! -f "${DATA_FILE}" ] ; then + echo "No valid data file was provided." + exit 1 + fi + RESULTS_FILE=$(params.dataDir)/$(params.internalRequestResultsFile) SNAPSHOT_FILE=$(params.dataDir)/index_image_snapshot.json jq -n '{"components": []}' | tee "$SNAPSHOT_FILE" + request="inspect-target-index-pipeline" + credentials=$(jq -r '.fbc.publishingCredentials' "$DATA_FILE") + pipelinerun_label="internal-services.appstudio.openshift.io/pipelinerun-uid" + LENGTH="$(jq -r '.components | length' "$RESULTS_FILE")" for((i=0; i "$(params.dataDir)/$(context.pipelineRun.uid)/data.json" << EOF + { + "fbc": { + "publishingCredentials": "test-publishing-credentials" + } + } + EOF cat > "$(params.dataDir)/internal-requests-results.json" << EOF { "components": [ @@ -82,7 +89,10 @@ spec: params: - name: internalRequestResultsFile value: "internal-requests-results.json" - + - name: dataPath + value: "$(context.pipelineRun.uid)/data.json" + - name: pipelineRunUid + value: $(context.pipelineRun.uid) - name: ociStorage value: $(params.ociStorage) - name: orasOptions diff --git a/tasks/managed/collect-index-images/tests/test-collect-index-images-multiple-versions.yaml b/tasks/managed/collect-index-images/tests/test-collect-index-images-multiple-versions.yaml index 8ea5e54a4d..7916a1225c 100644 --- a/tasks/managed/collect-index-images/tests/test-collect-index-images-multiple-versions.yaml +++ b/tasks/managed/collect-index-images/tests/test-collect-index-images-multiple-versions.yaml @@ -53,7 +53,14 @@ spec: #!/usr/bin/env sh set -eux - mkdir -p "$(params.dataDir)" + mkdir -p "$(params.dataDir)/$(context.pipelineRun.uid)" + cat > "$(params.dataDir)/$(context.pipelineRun.uid)/data.json" << EOF + { + "fbc": { + "publishingCredentials": "test-publishing-credentials" + } + } + EOF cat > "$(params.dataDir)/internal-requests-results.json" << EOF { "components": [ @@ -88,6 +95,10 @@ spec: params: - name: internalRequestResultsFile value: "internal-requests-results.json" + - name: dataPath + value: "$(context.pipelineRun.uid)/data.json" + - name: pipelineRunUid + value: $(context.pipelineRun.uid) - name: ociStorage value: $(params.ociStorage) - name: orasOptions diff --git a/tasks/managed/collect-index-images/tests/test-collect-index-images-single-version.yaml b/tasks/managed/collect-index-images/tests/test-collect-index-images-single-version.yaml index 33675409f0..d4b63f27d5 100644 --- a/tasks/managed/collect-index-images/tests/test-collect-index-images-single-version.yaml +++ b/tasks/managed/collect-index-images/tests/test-collect-index-images-single-version.yaml @@ -53,7 +53,14 @@ spec: #!/usr/bin/env sh set -eux - mkdir -p "$(params.dataDir)" + mkdir -p "$(params.dataDir)/$(context.pipelineRun.uid)" + cat > "$(params.dataDir)/$(context.pipelineRun.uid)/data.json" << EOF + { + "fbc": { + "publishingCredentials": "test-publishing-credentials" + } + } + EOF cat > "$(params.dataDir)/internal-requests-results.json" << EOF { "components": [ @@ -82,6 +89,10 @@ spec: params: - name: internalRequestResultsFile value: "internal-requests-results.json" + - name: dataPath + value: "$(context.pipelineRun.uid)/data.json" + - name: pipelineRunUid + value: $(context.pipelineRun.uid) - name: ociStorage value: $(params.ociStorage) - name: orasOptions From 97e04a5fd65d9fac73d29824d3bcc7e98e84faba Mon Sep 17 00:00:00 2001 From: Leandro Mendes Date: Wed, 1 Jul 2026 12:18:44 +0200 Subject: [PATCH 5/7] fix: fixes --- .../collect-index-images.yaml | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/tasks/managed/collect-index-images/collect-index-images.yaml b/tasks/managed/collect-index-images/collect-index-images.yaml index 242c05fe96..0f743c21ff 100644 --- a/tasks/managed/collect-index-images/collect-index-images.yaml +++ b/tasks/managed/collect-index-images/collect-index-images.yaml @@ -114,14 +114,13 @@ spec: - name: sourceDataArtifact value: $(params.sourceDataArtifact) - name: collect-index-images - image: - quay.io/konflux-ci/release-service-utils@sha256:5546fa78d3c88d7b6a2e8cff8902f7757f00541d0bbaf113b9f293133894afa3 + image: quay.io/konflux-ci/release-service-utils@sha256:5546fa78d3c88d7b6a2e8cff8902f7757f00541d0bbaf113b9f293133894afa3 computeResources: limits: - memory: 128Mi + memory: 3Gi requests: - memory: 128Mi - cpu: 50m + memory: 3Gi + cpu: 1 script: | #!/usr/bin/env bash set -eux @@ -138,7 +137,11 @@ spec: request="inspect-target-index-pipeline" credentials=$(jq -r '.fbc.publishingCredentials' "$DATA_FILE") - pipelinerun_label="internal-services.appstudio.openshift.io/pipelinerun-uid" + + # Setup consistent labeling for FBC internal requests + TASK_LABEL="internal-services.appstudio.openshift.io/group-id" + TASK_ID=$(context.taskRun.uid) + PIPELINERUN_LABEL="internal-services.appstudio.openshift.io/pipelinerun-uid" LENGTH="$(jq -r '.components | length' "$RESULTS_FILE")" for((i=0; i Date: Thu, 2 Jul 2026 10:25:13 +0200 Subject: [PATCH 6/7] fix: return digest --- .../inspect-target-index-task/inspect-target-index-task.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tasks/internal/inspect-target-index-task/inspect-target-index-task.yaml b/tasks/internal/inspect-target-index-task/inspect-target-index-task.yaml index d62bf6abee..403de73d28 100644 --- a/tasks/internal/inspect-target-index-task/inspect-target-index-task.yaml +++ b/tasks/internal/inspect-target-index-task/inspect-target-index-task.yaml @@ -67,5 +67,5 @@ spec: export PATH TARGET_INDEX_CREDENTIALS="$(cat /mnt/inspectCredentials/targetIndexCredential)" - skopeo inspect --raw --creds "${TARGET_INDEX_CREDENTIALS}" "docker://$(params.targetIndex)" \ - | jq -c '.' | tee "$(results.requestMessage.path)" + skopeo inspect --creds "${TARGET_INDEX_CREDENTIALS}" "docker://$(params.targetIndex)" \ + | jq -c '{ "digest": .Digest, "labels": .Labels }' | tee "$(results.requestMessage.path)" From 397dd973ab142be5f75479a5d0299b68a0371b48 Mon Sep 17 00:00:00 2001 From: Leandro Mendes Date: Thu, 2 Jul 2026 11:10:15 +0200 Subject: [PATCH 7/7] fix(KONFLUX-12379): correct digest comparison logic in collect-index-images Fixed jq expressions for digest extraction and comparison: - Use jq -r '.[0]' to extract built digest from array - Use jq -r '.digest' to extract current digest from object - Match lowercase 'digest' field from inspect-target-index output Assisted-By: Claude Signed-off-by: Leandro Mendes --- .../collect-index-images.yaml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tasks/managed/collect-index-images/collect-index-images.yaml b/tasks/managed/collect-index-images/collect-index-images.yaml index 0f743c21ff..feb2f8b32b 100644 --- a/tasks/managed/collect-index-images/collect-index-images.yaml +++ b/tasks/managed/collect-index-images/collect-index-images.yaml @@ -147,7 +147,7 @@ spec: for((i=0; i