diff --git a/pipelines/managed/push-artifacts-to-cdn/push-artifacts-to-cdn.yaml b/pipelines/managed/push-artifacts-to-cdn/push-artifacts-to-cdn.yaml index 5131d14451..338b9a8baa 100644 --- a/pipelines/managed/push-artifacts-to-cdn/push-artifacts-to-cdn.yaml +++ b/pipelines/managed/push-artifacts-to-cdn/push-artifacts-to-cdn.yaml @@ -296,6 +296,7 @@ spec: value: tasks/managed/populate-release-notes/populate-release-notes.yaml runAfter: - verify-conforma + - collect-task-params - name: check-data-keys params: - name: dataPath @@ -408,8 +409,8 @@ spec: - name: quayURL value: "$(params.quayURL)" runAfter: - - verify-conforma - embargo-check + - verify-conforma - name: create-advisory params: - name: releasePlanAdmissionPath diff --git a/tasks/internal/create-advisory-task/README.md b/tasks/internal/create-advisory-task/README.md index 4535fcae73..a3f1ea12ca 100644 --- a/tasks/internal/create-advisory-task/README.md +++ b/tasks/internal/create-advisory-task/README.md @@ -17,6 +17,6 @@ request. | advisory_secret_name | The name of the secret that contains the advisory creation metadata | No | - | | errata_secret_name | The name of the secret that contains the errata service account metadata | No | - | | internalRequestPipelineRunName | Name of the PipelineRun that called this task | No | - | -| contentType | The contentType of the release artifact. One of [image|binary|generic|rpm] | Yes | image | +| contentType | The contentType of the release artifact. One of [image|binary|generic|rpm|disk-image] | Yes | image | | 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/create-advisory-task/create-advisory-task.yaml b/tasks/internal/create-advisory-task/create-advisory-task.yaml index 15e035e593..7a42d8805b 100644 --- a/tasks/internal/create-advisory-task/create-advisory-task.yaml +++ b/tasks/internal/create-advisory-task/create-advisory-task.yaml @@ -40,7 +40,7 @@ spec: description: Name of the PipelineRun that called this task - name: contentType type: string - description: The contentType of the release artifact. One of [image|binary|generic|rpm] + description: The contentType of the release artifact. One of [image|binary|generic|rpm|disk-image] default: "image" - name: caTrustConfigMapName type: string diff --git a/tasks/internal/create-advisory-task/tests/test-create-advisory-disk-image-content.yaml b/tasks/internal/create-advisory-task/tests/test-create-advisory-disk-image-content.yaml new file mode 100644 index 0000000000..3c28b27d26 --- /dev/null +++ b/tasks/internal/create-advisory-task/tests/test-create-advisory-disk-image-content.yaml @@ -0,0 +1,89 @@ +--- +apiVersion: tekton.dev/v1 +kind: Pipeline +metadata: + name: test-create-advisory-disk-image-content +spec: + description: | + Run the create-advisory task with the contentType param set as disk-image + and check that an advisory url is emitted as a task result + tasks: + - name: setup + taskSpec: + results: + - name: advisory_json + type: string + steps: + - name: generate-advisory-json + image: registry.access.redhat.com/ubi9/ubi-minimal:latest + script: | + #!/usr/bin/env bash + set -euo pipefail + # advisory_json is the gzip+base64 encoding of: + # {"product_id":123,"product_name":"Red Hat Product","product_version":"1.2.3", + # "product_stream":"tp1","cpe":"cpe:/a:example:product:el8","type":"RHSA", + # "synopsis":"test synopsis","topic":"test topic","description":"test description", + # "solution":"test solution","references":["https://docs.example.com/notes"], + # "content":{"artifacts":[{"architecture":"x86_64","os":"linux","component":"test-disk-image", + # "purl":"pkg:generic/test-disk-image@1.0.0?filename=test-image.iso", + # "signingKey":"redhatrelease2"}]}} + advisory_b64="H4sIAAAAAAAAA12RT2sCMRDF736KJeeadbVIWShtb0IvxR6LSEjGdXA3CclsUcTv" + advisory_b64+="3vxD1p6SvN+bNzPkOqsqZp1Ro6Q9KtZWzXL1NBW1GCDIbAuq2giqvrLOHky/4Dwa" + advisory_b64+="HX0NX/LVI/XkQAwRkm0ykjaFhqOtRQtnMdge2lLQQv+SbXTJvu3m+yMr/qKN9ehTG" + advisory_b64+="niq7kIuMBblneVXAgq8dGipTJnwVMvhph8fHHchYQcHcKAlxO4/7EhkfVvXykjPywp" + advisory_b64+="cmqHWJhSzXV7UaAJNoeAankEQjvAgJKWMJFUFFSyPSCBpdGnz88t6v35O/YvDpN171" + advisory_b64+="ON5KofG1ujcKg0/V+hPcxxEB1OfHV0fLfbUtR1ocCjrf/b3hi/44u2APcTff004EY7e" + advisory_b64+="TMM8dhp19wmXGOlAHUX46x6EhyUrtls6d7N4u/0BSEfEJnACAAA=" + echo -n "${advisory_b64}" > "$(results.advisory_json.path)" + - name: run-task + runAfter: + - setup + taskRef: + name: create-advisory-task + params: + - name: advisory_json + value: $(tasks.setup.results.advisory_json) + - name: componentGroup + value: "test-app" + - name: origin + value: "not-existing-origin" + - name: config_map_name + value: "create-advisory-test-cm" + - name: advisory_secret_name + value: "create-advisory-secret" + - name: errata_secret_name + value: "create-advisory-errata-secret" + - name: internalRequestPipelineRunName + value: $(context.pipelineRun.name) + - name: contentType + value: "disk-image" + - name: check-result + runAfter: + - run-task + params: + - name: result + value: $(tasks.run-task.results.result) + - name: advisory_url + value: $(tasks.run-task.results.advisory_url) + taskSpec: + params: + - name: result + type: string + - name: advisory_url + type: string + steps: + - name: check-result + image: registry.access.redhat.com/ubi9/ubi-minimal:latest + script: | + #!/usr/bin/env bash + set -euxo pipefail + + echo Test that result is Success + test "$(params.result)" == Success + + echo Test that advisory_url was properly set + url="$(params.advisory_url)" + if [[ "${url}" != https://access.redhat.com/errata/RHSA-*:1234 ]]; then + echo "Unexpected advisory URL: ${url}" + exit 1 + fi diff --git a/tasks/internal/push-artifacts-to-cdn-task/push-artifacts-to-cdn-task.yaml b/tasks/internal/push-artifacts-to-cdn-task/push-artifacts-to-cdn-task.yaml index c72d4a94a1..bc856d0ce8 100644 --- a/tasks/internal/push-artifacts-to-cdn-task/push-artifacts-to-cdn-task.yaml +++ b/tasks/internal/push-artifacts-to-cdn-task/push-artifacts-to-cdn-task.yaml @@ -179,7 +179,9 @@ spec: steps: - name: push-artifacts-to-cdn - image: quay.io/konflux-ci/release-service-utils@sha256:67bb57a671d7e1b8d51ee07cefcca4ac46db02b8c3a9ee8fad60fb9365aab324 + # TODO(RELEASE-2460): temporary image from PR #833 branch; replace with the released + # quay.io/konflux-ci/release-service-utils digest once PR #833 is merged and published. + image: quay.io/redhat-user-workloads/rhtap-release-2-tenant/release-service-utils-standalone@sha256:2958c29f7ebd57fe7ca54724bbc5f883946c7b4f92ed1fca810d57072fde87f0 securityContext: runAsUser: 1001 computeResources: diff --git a/tasks/managed/create-advisory/create-advisory.yaml b/tasks/managed/create-advisory/create-advisory.yaml index baf9288a2d..1a9355ec04 100644 --- a/tasks/managed/create-advisory/create-advisory.yaml +++ b/tasks/managed/create-advisory/create-advisory.yaml @@ -283,6 +283,16 @@ spec: COMPONENTS=$(jq -c '.mapping.components[]' "$DATA_FILE") + # Build a name → staged.files lookup from the snapshot. apply-mapping substitutes + # {{ release_timestamp }} (and other template vars) in the snapshot's components but + # does NOT write those substitutions back to the data file. Reading staged.files from + # DATA_FILE would produce filenames like "foo-{{ release_timestamp }}-x86_64.iso" which + # Jinja2 would then render as empty, giving "foo--x86_64.iso" in the advisory. + SNAPSHOT_FILE="$(params.dataDir)/$(params.snapshotPath)" + SNAPSHOT_STAGED=$(jq -c \ + 'reduce .components[] as $c ({}; .[$c.name] = ($c.staged.files // []))' \ + "$SNAPSHOT_FILE") + UPDATED_ENTRIES=() while IFS= read -r component; do @@ -306,47 +316,96 @@ spec: continue fi - while IFS= read -r entry; do - ARCH=$(jq -r '.architecture' <<< "$entry") - OS=$(jq -r '.os' <<< "$entry") + if [ "$content_type" = "disk-image" ]; then + # For disk-images, generate one advisory entry per file in staged.files[]. + # Multiple files can share the same os+arch (e.g. ISO + QCOW2 both linux/amd64), + # so iterating over advisory entries (one per os+arch) would produce malformed PURLs. + # Instead, use the first advisory entry as a metadata template and expand it per file. + TEMPLATE_ENTRY=$(echo "$MATCHING_ENTRIES" | head -1) + # When a component has both contentGateway (Developer Portal) and staged (Customer + # Portal/CDN), CGW is used as the canonical download_url in the PURL. If only + # staged is present, the CDN URL is used instead. + HAS_CGW=$(jq -r 'if ((.contentGateway // {}) | length) > 0 then "true" else "false" end' \ + <<< "$component") + if [ "$HAS_CGW" = "true" ]; then + DOWNLOAD_URL="$CGW_BASE_URL" + else + DOWNLOAD_URL="$CDN_BASE_URL" + fi + + while IFS= read -r staged_file; do + FILENAME=$(jq -r '.filename' <<< "$staged_file") + FILENAME_BASENAME=$(basename "$FILENAME") - if [ "$content_type" = "disk-image" ]; then - # For disk-images, we need to match the filename from the populate-release-notes step - # with the staged.files[].filename that has the same architecture - FILENAME=$(jq -r --arg arch "$ARCH" \ - '.staged.files[] | select(.filename | contains($arch)) | .filename' <<< "$component") + # Derive arch from filename (same logic as populate-release-notes) + FILE_ARCH="unknown" + if [[ "$FILENAME_BASENAME" == *"aarch64"* ]]; then + FILE_ARCH="aarch64" + elif [[ "$FILENAME_BASENAME" == *"x86_64"* ]]; then + FILE_ARCH="x86_64" + fi + FILE_OS="linux" # Default for disk images - if [ -z "$FILENAME" ] || [ "$FILENAME" = "null" ]; then - echo "Warning: No filename found for arch $ARCH in component $COMPONENT_NAME" >&2 - continue - fi - else - # Binary/generic files have arch/os matching, falling back to staged.files - # for teams that use the CDN staged structure instead of a top-level files array - FILENAME=$(jq -r --arg arch "$ARCH" --arg os "$OS" \ - '((.files // []) as $f - | if ($f|length) > 0 then $f else (.staged.files // []) end)[] - | select(.arch == $arch and .os == $os) | .source' \ - <<< "$component") + CHECKSUM=$(jq -r --arg component "$COMPONENT_NAME" --arg filename "$FILENAME_BASENAME" \ + '.[] | select(.component == $component) | .files[$filename] // empty' \ + <<< "$CHECKSUM_MAP_JSON") + if [ -z "$CHECKSUM" ]; then + echo "Warning: No checksum found for $COMPONENT_NAME/$FILENAME_BASENAME in manifest" >&2 fi + PURL="pkg:generic/$COMPONENT_NAME@$VERSION_NAME" + QUERY_PARAMS=() + # Add filename first - this uniquely identifies the file + if [ -n "$FILENAME_BASENAME" ]; then + QUERY_PARAMS+=("filename=$FILENAME_BASENAME") + fi + if [ -n "$CHECKSUM" ]; then + QUERY_PARAMS+=("checksum=$CHECKSUM") + fi + if [ -n "$DOWNLOAD_URL" ]; then + QUERY_PARAMS+=("download_url=$DOWNLOAD_URL") + fi + if [ "${#QUERY_PARAMS[@]}" -gt 0 ]; then + PURL="${PURL}?$(IFS=\&; echo "${QUERY_PARAMS[*]}")" + fi + + UPDATED_ENTRY=$(jq -c \ + --arg purl "$PURL" --arg arch "$FILE_ARCH" --arg os "$FILE_OS" \ + '.purl = $purl | .architecture = $arch | .os = $os' <<< "$TEMPLATE_ENTRY") + UPDATED_ENTRIES+=("$UPDATED_ENTRY") + done <<< "$(jq -c --arg name "$COMPONENT_NAME" '.[$name][]?' <<< "$SNAPSHOT_STAGED")" + else + while IFS= read -r entry; do + ARCH=$(jq -r '.architecture' <<< "$entry") + OS=$(jq -r '.os' <<< "$entry") + + # Binary/generic files have arch/os matching, falling back to staged.files + # for teams that use the CDN staged structure instead of a top-level files array + FILENAME=$(jq -r --arg arch "$ARCH" --arg os "$OS" \ + '((.files // []) as $f + | if ($f|length) > 0 then $f else (.staged.files // []) end)[] + | select(.arch == $arch and .os == $os) | .source' \ + <<< "$component") + FILENAME_BASENAME=$(basename "$FILENAME") # Apply Windows archive conversion (.tar.gz -> .zip) when looking up checksum # This matches the conversion done in compress-artifacts step of push-artifacts-to-cdn-task if [ "$OS" = "windows" ] && [[ "$FILENAME_BASENAME" == *.tar.gz ]]; then FILENAME_BASENAME="${FILENAME_BASENAME%.tar.gz}.zip" fi - + CHECKSUM=$(jq -r --arg component "$COMPONENT_NAME" --arg filename "$FILENAME_BASENAME" \ - '.[] | select(.component == $component) | .files[$filename] // empty' <<< "$CHECKSUM_MAP_JSON") + '.[] | select(.component == $component) | .files[$filename] // empty' \ + <<< "$CHECKSUM_MAP_JSON") if [ -z "$CHECKSUM" ]; then echo "Warning: No checksum found for $COMPONENT_NAME/$FILENAME_BASENAME in manifest" >&2 fi - # Determine download URL based on portal (using environment-aware base URLs) - # - If .contentGateway: Developer Portal (CGW) - # - If only .staged (no .contentGateway): Customer Portal (CDN) - HAS_CGW=$(jq -r '.contentGateway | length > 0' <<< "$component") + # When a component has both contentGateway (Developer Portal) and staged (Customer + # Portal/CDN), CGW is used as the canonical download_url in the PURL. If only + # staged is present, the CDN URL is used instead. + HAS_CGW=$(jq -r 'if ((.contentGateway // {}) | length) > 0 then "true" else "false" end' \ + <<< "$component") if [ "$HAS_CGW" = "true" ]; then DOWNLOAD_URL="$CGW_BASE_URL" else @@ -371,30 +430,42 @@ spec: UPDATED_ENTRY=$(jq -c --arg purl "$PURL" '.purl = $purl' <<< "$entry") UPDATED_ENTRIES+=("$UPDATED_ENTRY") - done <<< "$MATCHING_ENTRIES" + done <<< "$MATCHING_ENTRIES" + fi done <<< "$COMPONENTS" - # Merge updated advisory entries back into releaseNotes.content.artifacts in the DATA_FILE - # Also deduplicate entries by component|architecture|os to handle cases where - # multiple files have the same arch/os (e.g., binary + ISO for same arch) if [ "${#UPDATED_ENTRIES[@]}" -eq 0 ]; then echo "No advisory entries were updated." exit 0 fi ENTRIES_JOINED=$(IFS=,; echo "${UPDATED_ENTRIES[*]}") - UPDATED_MAP=$(jq -nc --argjson updated "[$ENTRIES_JOINED]" ' - reduce $updated[] as $item ({}; . + {($item.component + "|" + $item.architecture + "|" + $item.os): $item}) - ') - # Update entries AND deduplicate: convert to map (dedup by key), then back to array - jq --argjson updated "$UPDATED_MAP" ' - .releaseNotes.content.artifacts |= ( - map(($updated[(.component + "|" + .architecture + "|" + .os)] // .)) - | reduce .[] as $item ({}; . + {($item.component + "|" + $item.architecture + "|" + $item.os): $item}) - | [.[]] - ) - ' "$DATA_FILE" > /tmp/data.tmp && mv /tmp/data.tmp "$DATA_FILE" + if [ "$content_type" = "disk-image" ]; then + # For disk-images, drop all existing advisory entries for the updated components and + # replace them with the new per-file entries. We cannot deduplicate by component|arch|os + # because multiple files (e.g. ISO + QCOW2) legitimately share the same arch/os. + jq --argjson updated "[$ENTRIES_JOINED]" ' + ($updated | map(.component) | unique) as $updated_components | + .releaseNotes.content.artifacts |= ( + map(select(.component | IN($updated_components[]) | not)) + $updated + ) + ' "$DATA_FILE" > /tmp/data.tmp && mv /tmp/data.tmp "$DATA_FILE" + else + # Merge updated advisory entries back into releaseNotes.content.artifacts in the DATA_FILE. + # Deduplicate by component|architecture|os to handle cases where multiple files share the + # same arch/os (e.g. binary + ISO for same arch). + UPDATED_MAP=$(jq -nc --argjson updated "[$ENTRIES_JOINED]" ' + reduce $updated[] as $item ({}; . + {($item.component + "|" + $item.architecture + "|" + $item.os): $item}) + ') + jq --argjson updated "$UPDATED_MAP" ' + .releaseNotes.content.artifacts |= ( + map(($updated[(.component + "|" + .architecture + "|" + .os)] // .)) + | reduce .[] as $item ({}; . + {($item.component + "|" + $item.architecture + "|" + $item.os): $item}) + | [.[]] + ) + ' "$DATA_FILE" > /tmp/data.tmp && mv /tmp/data.tmp "$DATA_FILE" + fi - name: run-script image: quay.io/konflux-ci/release-service-utils@sha256:5546fa78d3c88d7b6a2e8cff8902f7757f00541d0bbaf113b9f293133894afa3 computeResources: diff --git a/tasks/managed/create-advisory/tests/test-create-advisory-disk-image-secrets.yaml b/tasks/managed/create-advisory/tests/test-create-advisory-disk-image-secrets.yaml index 3b7231c4d1..33bab10efb 100644 --- a/tasks/managed/create-advisory/tests/test-create-advisory-disk-image-secrets.yaml +++ b/tasks/managed/create-advisory/tests/test-create-advisory-disk-image-secrets.yaml @@ -102,7 +102,15 @@ spec: "components": [ { "name": "intel-bootc-iso-disk-image-1-5", - "repository": "quay.io/redhat-prod/rhel-ai----disk-image" + "repository": "quay.io/redhat-prod/rhel-ai----disk-image", + "staged": { + "files": [ + { + "filename": "rhel-ai-intel-1.5.1-1749643937-x86_64.iso.gz", + "source": "install.iso.gz" + } + ] + } } ] } diff --git a/tasks/managed/create-advisory/tests/test-create-advisory-update-purl-disk-images-multi-file.yaml b/tasks/managed/create-advisory/tests/test-create-advisory-update-purl-disk-images-multi-file.yaml new file mode 100644 index 0000000000..11a1afc639 --- /dev/null +++ b/tasks/managed/create-advisory/tests/test-create-advisory-update-purl-disk-images-multi-file.yaml @@ -0,0 +1,332 @@ +--- +apiVersion: tekton.dev/v1 +kind: Pipeline +metadata: + name: test-create-advisory-update-purl-disk-images-multi-file +spec: + description: | + Assert that the update-purl step generates one advisory artifact entry per staged file + for a disk-image component that has multiple files (e.g. ISO + QCOW2) under the same + os/architecture. This covers the case where a single component ships multiple disk image + formats, all for linux/x86_64, and each must receive its own correct PURL entry. + 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 + - name: checksum_map + 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)" + securityContext: + runAsUser: 1001 + steps: + - name: create-crs + image: quay.io/konflux-ci/release-service-utils@sha256:5546fa78d3c88d7b6a2e8cff8902f7757f00541d0bbaf113b9f293133894afa3 + script: | + #!/usr/bin/env bash + set -exo pipefail + + mkdir -p "$(params.dataDir)/$(context.pipelineRun.uid)/results" + + cat > "$(params.dataDir)/$(context.pipelineRun.uid)/test_release_plan_admission.json" << 'EOF' + { + "apiVersion": "appstudio.redhat.com/v1alpha1", + "kind": "ReleasePlanAdmission", + "metadata": { + "name": "test", + "namespace": "default" + }, + "spec": { + "applications": ["test-app"], + "policy": "policy", + "pipeline": { + "pipelineRef": { + "resolver": "git", + "params": [ + {"name": "url", "value": "github.com"}, + {"name": "revision", "value": "main"}, + {"name": "pathInRepo", "value": "pipeline.yaml"} + ] + } + }, + "serviceAccountName": "sa", + "origin": "test-tenant" + } + } + EOF + + cat > "$(params.dataDir)/$(context.pipelineRun.uid)/test_snapshot_spec.json" << 'EOF' + { + "application": "test-app", + "components": [ + { + "name": "multi-file-disk-image", + "repository": "quay.io/test/multi-file-disk-image", + "staged": { + "files": [ + { + "filename": "test-product-1.0.0-1234567890-x86_64-boot.iso.gz", + "source": "/releases/install.iso.gz", + "os": "linux", + "arch": "amd64" + }, + { + "filename": "test-product-1.0.0-1234567890-x86_64-kvm.qcow2", + "source": "/releases/disk.qcow2", + "os": "linux", + "arch": "amd64" + } + ] + } + } + ] + } + EOF + + # Single component with two staged.files[] entries — both linux/amd64. + # populate-release-notes emits one artifact entry (linux/x86_64); update-purl + # must expand it into two entries (one per file) with correct PURLs. + cat > "$(params.dataDir)/$(context.pipelineRun.uid)/data.json" << 'EOF' + { + "mapping": { + "components": [ + { + "name": "multi-file-disk-image", + "staged": { + "destination": "test-product-1_DOT_0-for-rhel-9-x86_64-files", + "version": "1.0.0", + "files": [ + { + "filename": "test-product-1.0.0-1234567890-x86_64-boot.iso.gz", + "source": "/releases/install.iso.gz", + "os": "linux", + "arch": "amd64" + }, + { + "filename": "test-product-1.0.0-1234567890-x86_64-kvm.qcow2", + "source": "/releases/disk.qcow2", + "os": "linux", + "arch": "amd64" + } + ] + }, + "contentGateway": { + "contentType": "disk-image", + "productCode": "TestProduct", + "productName": "Test Product", + "productVersionName": "1.0.0" + } + } + ] + }, + "releaseNotes": { + "content": { + "artifacts": [ + { + "architecture": "x86_64", + "component": "multi-file-disk-image", + "os": "linux", + "purl": "placeholder" + } + ] + } + }, + "cdn": { + "env": "qa" + }, + "sign": { + "configMapName": "cm" + } + } + EOF + + jq -nc '[ + { + "component": "multi-file-disk-image", + "files": { + "test-product-1.0.0-1234567890-x86_64-boot.iso.gz": + "sha256:aaaa1111bbbb2222cccc3333dddd4444eeee5555ffff6666aaaa1111bbbb2222", + "test-product-1.0.0-1234567890-x86_64-kvm.qcow2": + "sha256:1111aaaa2222bbbb3333cccc4444dddd5555eeee6666ffff1111aaaa2222bbbb" + } + } + ]' > "$(params.dataDir)/mock_checksum_map.json" + echo -n "mock-registry/checksum-map@sha256:mock" | tee "$(results.checksum_map.path)" + - 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: create-advisory + params: + - name: releasePlanAdmissionPath + value: "$(context.pipelineRun.uid)/test_release_plan_admission.json" + - name: snapshotPath + value: "$(context.pipelineRun.uid)/test_snapshot_spec.json" + - name: dataPath + value: "$(context.pipelineRun.uid)/data.json" + - name: environment + value: stage + - name: resultsDirPath + value: "$(context.pipelineRun.uid)/results" + - name: synchronously + value: "false" + - name: pipelineRunUid + value: $(context.pipelineRun.uid) + - name: ociStorage + value: $(params.ociStorage) + - name: orasOptions + value: $(params.orasOptions) + - name: sourceDataArtifact + value: "$(tasks.setup.results.sourceDataArtifact)=$(params.dataDir)/$(context.pipelineRun.uid)" + - name: dataDir + value: $(params.dataDir) + - name: trustedArtifactsDebug + value: $(params.trustedArtifactsDebug) + - name: taskGitUrl + value: "http://localhost" + - name: taskGitRevision + value: "main" + - name: checksum_map + value: "$(tasks.setup.results.checksum_map)" + runAfter: + - setup + - name: check-result + params: + - name: sourceDataArtifact + value: "$(tasks.run-task.results.sourceDataArtifact)=$(params.dataDir)" + - name: dataDir + value: "$(params.dataDir)" + runAfter: + - run-task + taskSpec: + params: + - name: sourceDataArtifact + type: string + - name: dataDir + 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)" + securityContext: + runAsUser: 1001 + steps: + - name: use-trusted-artifact + ref: + name: use-trusted-artifact + params: + - name: workDir + value: $(params.dataDir) + - name: sourceDataArtifact + value: $(params.sourceDataArtifact) + - name: check-result + image: quay.io/konflux-ci/release-service-utils@sha256:5546fa78d3c88d7b6a2e8cff8902f7757f00541d0bbaf113b9f293133894afa3 + script: | + #!/usr/bin/env bash + set -exo pipefail + + DATA_FILE="$(params.dataDir)/$(context.pipelineRun.uid)/data.json" + artifacts=$(jq -c '.releaseNotes.content.artifacts' "${DATA_FILE}") + + # Expect two artifact entries: one per staged file, both for the same component. + artifact_count=$(echo "${artifacts}" | jq 'length') + if [ "${artifact_count}" != "2" ]; then + echo "Expected 2 artifact entries (one per file), got ${artifact_count}" + echo "artifacts: ${artifacts}" + exit 1 + fi + + iso_checksum="sha256:aaaa1111bbbb2222cccc3333dddd4444eeee5555ffff6666aaaa1111bbbb2222" + qcow2_checksum="sha256:1111aaaa2222bbbb3333cccc4444dddd5555eeee6666ffff1111aaaa2222bbbb" + cgw_base="https://developers.qa.redhat.com/products" + + iso_file="test-product-1.0.0-1234567890-x86_64-boot.iso.gz" + qcow2_file="test-product-1.0.0-1234567890-x86_64-kvm.qcow2" + base_purl="pkg:generic/multi-file-disk-image@1.0.0" + + expectedIsoPurl="${base_purl}?filename=${iso_file}&checksum=${iso_checksum}&download_url=${cgw_base}" + expectedQcow2Purl="${base_purl}?filename=${qcow2_file}&checksum=${qcow2_checksum}&download_url=${cgw_base}" + + isoFilter='.[] | select(.purl | contains("iso.gz")) | .purl' + isoPurl=$(echo "${artifacts}" | jq -r "${isoFilter}") + qcow2Filter='.[] | select(.purl | contains("qcow2")) | .purl' + qcow2Purl=$(echo "${artifacts}" | jq -r "${qcow2Filter}") + + if [ "${isoPurl}" != "${expectedIsoPurl}" ]; then + echo "Unexpected PURL for iso disk-image" + echo "got: ${isoPurl}" + echo "expected: ${expectedIsoPurl}" + exit 1 + fi + + if [ "${qcow2Purl}" != "${expectedQcow2Purl}" ]; then + echo "Unexpected PURL for qcow2 disk-image" + echo "got: ${qcow2Purl}" + echo "expected: ${expectedQcow2Purl}" + exit 1 + fi + + echo "All checks passed: two per-file advisory entries with correct PURLs" + finally: + - name: cleanup + taskSpec: + steps: + - name: delete-crs + image: quay.io/konflux-ci/release-service-utils@sha256:5546fa78d3c88d7b6a2e8cff8902f7757f00541d0bbaf113b9f293133894afa3 + script: | + #!/usr/bin/env bash + set -exo pipefail + + kubectl delete internalrequests --all diff --git a/tasks/managed/create-advisory/tests/test-create-advisory-update-purl-disk-images.yaml b/tasks/managed/create-advisory/tests/test-create-advisory-update-purl-disk-images.yaml index d30060cb37..fffb83bdb5 100644 --- a/tasks/managed/create-advisory/tests/test-create-advisory-update-purl-disk-images.yaml +++ b/tasks/managed/create-advisory/tests/test-create-advisory-update-purl-disk-images.yaml @@ -102,11 +102,27 @@ spec: "components": [ { "name": "intel-bootc-iso-disk-image-1-5", - "repository": "quay.io/redhat-prod/rhel-ai----disk-image" + "repository": "quay.io/redhat-prod/rhel-ai----disk-image", + "staged": { + "files": [ + { + "filename": "rhel-ai-intel-1.5.1-1749643937-x86_64.iso.gz", + "source": "install.iso.gz" + } + ] + } }, { "name": "intel-bootc-qcow2-disk-image-1-5", - "repository": "quay.io/redhat-prod/rhel-ai----disk-image" + "repository": "quay.io/redhat-prod/rhel-ai----disk-image", + "staged": { + "files": [ + { + "filename": "rhel-ai-intel-1.5.1-1749643939-x86_64-kvm.qcow2", + "source": "disk.qcow2" + } + ] + } } ] } diff --git a/tasks/managed/push-artifacts-to-cdn/push-artifacts-to-cdn.yaml b/tasks/managed/push-artifacts-to-cdn/push-artifacts-to-cdn.yaml index 55da28ec39..87c36857ac 100644 --- a/tasks/managed/push-artifacts-to-cdn/push-artifacts-to-cdn.yaml +++ b/tasks/managed/push-artifacts-to-cdn/push-artifacts-to-cdn.yaml @@ -132,6 +132,7 @@ spec: - name: sourceDataArtifact value: $(params.sourceDataArtifact) - name: run-script + # TODO(RELEASE-2460): update to released image once release-service-utils PR 833 lands image: quay.io/konflux-ci/release-service-utils@sha256:67bb57a671d7e1b8d51ee07cefcca4ac46db02b8c3a9ee8fad60fb9365aab324 computeResources: limits: