From d639cdd5d3be80d32d2767452201e4115d79af67 Mon Sep 17 00:00:00 2001 From: Johnny Bieren Date: Wed, 1 Jul 2026 08:15:57 -0400 Subject: [PATCH 1/2] refactor(RELEASE-2475): convert close-advisory-issues to python This commit replaces the inline task script for the close-advisory-issues managed task with a standalone python script contained in the utils image. The tekton unit tests are updated accordingly. We only keep one happy path test as the other scenarios are covered by pytest in the utils repo. Assisted-By: Cursor Signed-off-by: Johnny Bieren --- .../close-advisory-issues.yaml | 96 ++--------- .../close-advisory-issues/tests/mocks.sh | 37 ---- .../close-advisory-issues/tests/mocks.yaml | 21 +++ .../tests/pre-apply-task-hook.sh | 5 - ...se-advisory-issues-cannot-close-issue.yaml | 110 ------------ ...dvisory-issues-cannot-get-transitions.yaml | 111 ------------ ...st-close-advisory-issues-closed-issue.yaml | 159 ------------------ ...close-advisory-issues-jira-conversion.yaml | 154 ----------------- .../test-close-advisory-issues-no-issues.yaml | 94 ----------- .../tests/test-close-advisory-issues.yaml | 60 +------ 10 files changed, 38 insertions(+), 809 deletions(-) delete mode 100644 tasks/managed/close-advisory-issues/tests/mocks.sh create mode 100644 tasks/managed/close-advisory-issues/tests/mocks.yaml delete mode 100644 tasks/managed/close-advisory-issues/tests/test-close-advisory-issues-cannot-close-issue.yaml delete mode 100644 tasks/managed/close-advisory-issues/tests/test-close-advisory-issues-cannot-get-transitions.yaml delete mode 100644 tasks/managed/close-advisory-issues/tests/test-close-advisory-issues-closed-issue.yaml delete mode 100644 tasks/managed/close-advisory-issues/tests/test-close-advisory-issues-jira-conversion.yaml delete mode 100644 tasks/managed/close-advisory-issues/tests/test-close-advisory-issues-no-issues.yaml diff --git a/tasks/managed/close-advisory-issues/close-advisory-issues.yaml b/tasks/managed/close-advisory-issues/close-advisory-issues.yaml index facb9d3fbc..a4cf6de8b5 100644 --- a/tasks/managed/close-advisory-issues/close-advisory-issues.yaml +++ b/tasks/managed/close-advisory-issues/close-advisory-issues.yaml @@ -121,96 +121,26 @@ spec: - name: sourceDataArtifact value: $(params.sourceDataArtifact) - name: close-issues - image: quay.io/konflux-ci/release-service-utils@sha256:5546fa78d3c88d7b6a2e8cff8902f7757f00541d0bbaf113b9f293133894afa3 + image: quay.io/jbieren/release-service-utils:closeissues_2 computeResources: limits: - memory: 32Mi + memory: 128Mi requests: - memory: 32Mi + memory: 128Mi cpu: 100m volumeMounts: - name: access-token-secret-vol mountPath: "/etc/secrets" - script: | - #!/usr/bin/env bash - # We do not set -x here because it would leak the ACCESS_TOKEN - set -eu - - ACCESS_TOKEN="$(cat /etc/secrets/token)" - EMAIL="$(cat /etc/secrets/email)" - - ISSUE_TRACKERS='{ - "Jira": { - "api": "rest/api/2/issue", - "servers": [ - "issues.redhat.com", - "jira.atlassian.com", - "redhat.atlassian.net" - ] - }, - "bugzilla": { - "api": "rest/bug", - "servers": [ - "bugzilla.redhat.com" - ] - } - }' - - DATA_FILE="$(params.dataDir)/$(params.dataPath)" - if [ ! -f "${DATA_FILE}" ] ; then - echo "No data JSON was provided." - exit 1 - fi - - NUM_ISSUES=$(jq -cr '.releaseNotes.issues.fixed | length' "${DATA_FILE}") - for ((i = 0; i < NUM_ISSUES; i++)); do - issue=$(jq -c --argjson i "$i" '.releaseNotes.issues.fixed[$i]' "${DATA_FILE}") - server=$(jq -r '.source' <<< "$issue") - if [ "$server" = "issues.redhat.com" ] ; then - server=redhat.atlassian.net - fi - if [ "$server" != "redhat.atlassian.net" ] ; then - echo "This task currently only supports closing issues on issues.redhat.com" - echo "and redhat.atlassian.net. Skipping issue $issue as it is on $server" - continue - fi - - CURL_ARGS=(-u "${EMAIL}:${ACCESS_TOKEN}") - - API=$(jq -r '.[] | select(.servers[] | contains("'"$server"'")) | .api' <<< "$ISSUE_TRACKERS") - API_URL="https://${server}/${API}/$(jq -r '.id' <<< "$issue")" - - if [ "$(curl-with-retry "${CURL_ARGS[@]}" "${API_URL}" | jq -r '.fields.status.name')" == "Closed" ] ; then - echo "Issue $issue is already in Closed state. Skipping it." - continue - fi - - echo "Closing issue $issue" - CLOSE_COMMENT="Fixed in Konflux Advisory $(params.advisoryUrl)" - CLOSING_RC=0 - # Get the Closed transition id. This varies per Jira project - if ! CLOSED_ID="$(curl-with-retry "${CURL_ARGS[@]}" "${API_URL}/transitions" \ - | jq -er '.transitions[] | select(.name=="Closed") | .id')"; then - echo "Warning: failed to fetch the closed state id for issue $issue. We most likely do not have" \ - "permission to close it. Will try to add a comment instead." - CLOSING_RC=1 - # Close the issue - elif ! curl-with-retry "${CURL_ARGS[@]}" -XPOST --data \ - '{"transition":{"id":"'"$CLOSED_ID"'"},"update":{"comment":[{"add":{"body":"'"$CLOSE_COMMENT"'"}}]}}' \ - -H "Content-Type: application/json" "${API_URL}/transitions"; then - echo "Warning: failed to close issue $issue. Will try to add a comment instead." - CLOSING_RC=1 - fi - - if [ "$CLOSING_RC" -ne 0 ] ; then - # Try to add the comment even if closing failed - if ! curl-with-retry "${CURL_ARGS[@]}" -XPOST --data \ - '{"body":"'"$CLOSE_COMMENT"'"}' \ - -H "Content-Type: application/json" "${API_URL}/comment"; then - echo "Warning: failed to add comment to issue $issue." - fi - fi - done + env: + - name: PARAM_DATA_DIR + value: $(params.dataDir) + - name: PARAM_DATA_PATH + value: $(params.dataPath) + - name: PARAM_ADVISORY_URL + value: $(params.advisoryUrl) + - name: JIRA_SECRET_PATH + value: /etc/secrets + command: ["/home/scripts/python/tasks/managed/close_advisory_issues.py"] - name: create-trusted-artifact computeResources: limits: diff --git a/tasks/managed/close-advisory-issues/tests/mocks.sh b/tasks/managed/close-advisory-issues/tests/mocks.sh deleted file mode 100644 index 6367bcd83c..0000000000 --- a/tasks/managed/close-advisory-issues/tests/mocks.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash -set -eux - -# mocks to be injected into task step scripts -function curl-with-retry() { - echo Mock curl called with: $* >&2 - echo $* >> "$(params.dataDir)/mock_curl.txt" - - if [[ "$*" == *"-u team@domain.com:abcdefg"*"Content-Type"*"https://redhat.atlassian.net/rest/api/2/issue/ISSUE-123/transitions" ]] - then - : - elif [[ "$*" == *"-u team@domain.com:abcdefg"*"Content-Type"*"https://redhat.atlassian.net/rest/api/2/issue/FAIL-999/transitions" ]] - then - return 1 - elif [[ "$*" == *"https://redhat.atlassian.net/rest/api/2/issue/FAIL-999/transitions" ]] - then - echo '{"transitions":[{"id":"91","name":"Closed","description":""},{"id":"11","name":"New"}]}' - elif [[ "$*" == *"https://redhat.atlassian.net/rest/api/2/issue/ISSUE-123/transitions" ]] - then - echo '{"transitions":[{"id":"91","name":"Closed","description":""},{"id":"11","name":"New"}]}' - elif [[ "$*" == *"https://redhat.atlassian.net/rest/api/2/issue/NOCLOSE-555/transitions" ]] - then - echo '{"expand":"transitions","transitions":[]}' - elif [[ "$*" == *"https://redhat.atlassian.net/rest/api/2/issue/CLOSED-987" ]] - then - echo '{"fields":{"status":{"name":"Closed","id":"99"}}}' - elif [[ "$*" == *"https://redhat.atlassian.net/rest/api/2/issue/FAIL-999/comment" ]] - then - echo '{}' - elif [[ "$*" == *"https://redhat.atlassian.net/rest/api/2/issue/NOCLOSE-555/comment" ]] - then - echo '{}' - else - echo Error: Unexpected call - exit 1 - fi -} diff --git a/tasks/managed/close-advisory-issues/tests/mocks.yaml b/tasks/managed/close-advisory-issues/tests/mocks.yaml new file mode 100644 index 0000000000..f2d478d136 --- /dev/null +++ b/tasks/managed/close-advisory-issues/tests/mocks.yaml @@ -0,0 +1,21 @@ +--- +# Declarative mocks for Tekton tests of this task (Python entrypoint). Rendered +# by .github/scripts/render_python_task_mocks_from_yaml.py when +# test_tekton_tasks.sh runs. +version: 1 +services: + - type: http_json + port: 8080 + bind: 127.0.0.1 + mock_server_for_env_var: JIRA_API_BASE_URL + mock_server_api_path: "" + routes: + - path_contains: /issue/ISSUE-123/transitions + method: POST + body: '{}' + - path_contains: /issue/ISSUE-123/transitions + method: GET + body: '{"transitions":[{"id":"91","name":"Closed","description":""},{"id":"11","name":"New"}]}' + - path_contains: /issue/ISSUE-123 + method: GET + body: '{"fields":{"status":{"name":"Open"}}}' diff --git a/tasks/managed/close-advisory-issues/tests/pre-apply-task-hook.sh b/tasks/managed/close-advisory-issues/tests/pre-apply-task-hook.sh index 887c1d73e8..ad346164c7 100755 --- a/tasks/managed/close-advisory-issues/tests/pre-apply-task-hook.sh +++ b/tasks/managed/close-advisory-issues/tests/pre-apply-task-hook.sh @@ -1,10 +1,5 @@ #!/usr/bin/env bash -# Add mocks to the beginning of task step script -TASK_PATH="$1" -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -yq -i '.spec.steps[1].script = load_str("'$SCRIPT_DIR'/mocks.sh") + .spec.steps[1].script' "$TASK_PATH" - # Create a dummy access token secret (and delete it first if it exists) kubectl delete secret konflux-advisory-jira-secret --ignore-not-found kubectl create secret generic konflux-advisory-jira-secret --from-literal=token=abcdefg --from-literal=email=team@domain.com diff --git a/tasks/managed/close-advisory-issues/tests/test-close-advisory-issues-cannot-close-issue.yaml b/tasks/managed/close-advisory-issues/tests/test-close-advisory-issues-cannot-close-issue.yaml deleted file mode 100644 index 26e0a844f3..0000000000 --- a/tasks/managed/close-advisory-issues/tests/test-close-advisory-issues-cannot-close-issue.yaml +++ /dev/null @@ -1,110 +0,0 @@ ---- -apiVersion: tekton.dev/v1 -kind: Pipeline -metadata: - name: test-close-advisory-issues-cannot-close-issue -spec: - description: | - Test for close-advisory-issues where the curl command to close the issue fails. This is - done with the mocks because of the issue id. The task will add a comment to the Jira and - succeed. - 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)" - cat > "$(params.dataDir)/$(context.pipelineRun.uid)/data.json" << EOF - { - "releaseNotes": { - "issues": { - "fixed": [ - { - "id": "FAIL-999", - "source": "redhat.atlassian.net" - }, - { - "id": "12345", - "source": "bugzilla.redhat.com" - } - ] - } - } - } - EOF - - 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: close-advisory-issues - params: - - name: dataPath - value: $(context.pipelineRun.uid)/data.json - - name: advisoryUrl - value: https://access.redhat.com/errata/RHBA-2025:1111 - - 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/close-advisory-issues/tests/test-close-advisory-issues-cannot-get-transitions.yaml b/tasks/managed/close-advisory-issues/tests/test-close-advisory-issues-cannot-get-transitions.yaml deleted file mode 100644 index d17876950e..0000000000 --- a/tasks/managed/close-advisory-issues/tests/test-close-advisory-issues-cannot-get-transitions.yaml +++ /dev/null @@ -1,111 +0,0 @@ ---- -apiVersion: tekton.dev/v1 -kind: Pipeline -metadata: - name: test-close-advisory-issues-cannot-get-transitions -spec: - description: | - Test for close-advisory-issues where the curl command to get the id of the Closed - transition state returns an empty transitions array. This is done with the mocks because of the issue id. - This can happen if we don't have the permissions to close the issue. - The task will add a comment to the Jira and succeed. - 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)" - cat > "$(params.dataDir)/$(context.pipelineRun.uid)/data.json" << EOF - { - "releaseNotes": { - "issues": { - "fixed": [ - { - "id": "NOCLOSE-555", - "source": "redhat.atlassian.net" - }, - { - "id": "12345", - "source": "bugzilla.redhat.com" - } - ] - } - } - } - EOF - - 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: close-advisory-issues - params: - - name: dataPath - value: $(context.pipelineRun.uid)/data.json - - name: advisoryUrl - value: https://access.redhat.com/errata/RHBA-2025:1111 - - 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/close-advisory-issues/tests/test-close-advisory-issues-closed-issue.yaml b/tasks/managed/close-advisory-issues/tests/test-close-advisory-issues-closed-issue.yaml deleted file mode 100644 index 07593816ff..0000000000 --- a/tasks/managed/close-advisory-issues/tests/test-close-advisory-issues-closed-issue.yaml +++ /dev/null @@ -1,159 +0,0 @@ ---- -apiVersion: tekton.dev/v1 -kind: Pipeline -metadata: - name: test-close-advisory-issues-closed-issue -spec: - description: | - Test for close-advisory-issues where the issue is already closed. Curl should only be - called once for the issue - 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)" - cat > "$(params.dataDir)/$(context.pipelineRun.uid)/data.json" << EOF - { - "releaseNotes": { - "issues": { - "fixed": [ - { - "id": "CLOSED-987", - "source": "redhat.atlassian.net" - }, - { - "id": "12345", - "source": "bugzilla.redhat.com" - } - ] - } - } - } - EOF - - 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: close-advisory-issues - params: - - name: dataPath - value: $(context.pipelineRun.uid)/data.json - - name: advisoryUrl - value: https://access.redhat.com/errata/RHBA-2025:1111 - - 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 - - name: check-result - params: - - name: sourceDataArtifact - value: "$(tasks.run-task.results.sourceDataArtifact)=$(params.dataDir)" - - name: dataDir - value: $(params.dataDir) - 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)" - 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 -eux - - if [ "$(wc -l < "$(params.dataDir)/mock_curl.txt")" != 1 ]; then - echo Error: curl was expected to be called 1 time. Actual calls: - cat "$(params.dataDir)/mock_curl.txt" - exit 1 - fi - - [[ $(head -n 1 "$(params.dataDir)/mock_curl.txt") == *"CLOSED-987" ]] - runAfter: - - run-task diff --git a/tasks/managed/close-advisory-issues/tests/test-close-advisory-issues-jira-conversion.yaml b/tasks/managed/close-advisory-issues/tests/test-close-advisory-issues-jira-conversion.yaml deleted file mode 100644 index 1aecc35a3f..0000000000 --- a/tasks/managed/close-advisory-issues/tests/test-close-advisory-issues-jira-conversion.yaml +++ /dev/null @@ -1,154 +0,0 @@ ---- -apiVersion: tekton.dev/v1 -kind: Pipeline -metadata: - name: test-close-advisory-issues-jira-conversion -spec: - description: | - Test for close-advisory-issues where an issues.redhat.com issue is converted to - redhat.atlassian.net as this is the new server. - 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)" - cat > "$(params.dataDir)/$(context.pipelineRun.uid)/data.json" << EOF - { - "releaseNotes": { - "issues": { - "fixed": [ - { - "id": "ISSUE-123", - "source": "issues.redhat.com" - } - ] - } - } - } - EOF - - 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: close-advisory-issues - params: - - name: dataPath - value: $(context.pipelineRun.uid)/data.json - - name: advisoryUrl - value: https://access.redhat.com/errata/RHBA-2025:1111 - - 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 - - name: check-result - params: - - name: sourceDataArtifact - value: "$(tasks.run-task.results.sourceDataArtifact)=$(params.dataDir)" - - name: dataDir - value: $(params.dataDir) - 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)" - 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 -eux - - - if grep -q "issues.redhat.com" "$(params.dataDir)/mock_curl.txt" ; then - echo Error: issues.redhat.com should not be queried. Calls: - cat "$(params.dataDir)/mock_curl.txt" - exit 1 - fi - runAfter: - - run-task diff --git a/tasks/managed/close-advisory-issues/tests/test-close-advisory-issues-no-issues.yaml b/tasks/managed/close-advisory-issues/tests/test-close-advisory-issues-no-issues.yaml deleted file mode 100644 index 7bc2a90eec..0000000000 --- a/tasks/managed/close-advisory-issues/tests/test-close-advisory-issues-no-issues.yaml +++ /dev/null @@ -1,94 +0,0 @@ ---- -apiVersion: tekton.dev/v1 -kind: Pipeline -metadata: - name: test-close-advisory-issues-no-issues -spec: - description: Test for close-advisory-issues where there are no issues listed - 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)" - cat > "$(params.dataDir)/$(context.pipelineRun.uid)/data.json" << EOF - { - "foo": "bar" - } - EOF - - 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: close-advisory-issues - params: - - name: dataPath - value: $(context.pipelineRun.uid)/data.json - - name: advisoryUrl - value: https://access.redhat.com/errata/RHBA-2025:1111 - - 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/close-advisory-issues/tests/test-close-advisory-issues.yaml b/tasks/managed/close-advisory-issues/tests/test-close-advisory-issues.yaml index b12659f3c3..5260f267aa 100644 --- a/tasks/managed/close-advisory-issues/tests/test-close-advisory-issues.yaml +++ b/tasks/managed/close-advisory-issues/tests/test-close-advisory-issues.yaml @@ -4,7 +4,9 @@ kind: Pipeline metadata: name: test-close-advisory-issues spec: - description: Test for close-advisory-issues where the curl commands all succeed + description: | + Run close-advisory-issues against an in-process mock Jira server (via mocks.yaml) + and check it completes successfully. params: - name: ociStorage description: The OCI repository where the Trusted Artifacts are stored. @@ -47,7 +49,7 @@ spec: value: "$(params.trustedArtifactsDebug)" steps: - name: setup-values - image: quay.io/konflux-ci/release-service-utils@sha256:5546fa78d3c88d7b6a2e8cff8902f7757f00541d0bbaf113b9f293133894afa3 + image: quay.io/jbieren/release-service-utils:closeissues_2 script: | #!/usr/bin/env sh set -eux @@ -105,57 +107,3 @@ spec: value: "main" runAfter: - setup - - name: check-result - params: - - name: sourceDataArtifact - value: "$(tasks.run-task.results.sourceDataArtifact)=$(params.dataDir)" - - name: dataDir - value: $(params.dataDir) - 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)" - 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 -eux - - if [ "$(wc -l < "$(params.dataDir)/mock_curl.txt")" != 3 ]; then - echo Error: curl was expected to be called 3 times. Actual calls: - cat "$(params.dataDir)/mock_curl.txt" - exit 1 - fi - - [[ $(head -n 1 "$(params.dataDir)/mock_curl.txt") == *"ISSUE-123" ]] - [[ $(head -n 2 "$(params.dataDir)/mock_curl.txt" | tail -n 1) \ - == *"ISSUE-123/transitions" ]] - [[ $(tail -n 1 "$(params.dataDir)/mock_curl.txt") \ - == *"Content-Type"*"ISSUE-123"* ]] - runAfter: - - run-task From 4d152202566648af5872a553eb108c1281e2f268 Mon Sep 17 00:00:00 2001 From: Johnny Bieren Date: Wed, 1 Jul 2026 14:49:32 -0400 Subject: [PATCH 2/2] test: let tests mock any server, not just IP addr Signed-off-by: Johnny Bieren --- .github/scripts/mock_http_json.py | 7 +- .../render_python_task_mocks_from_yaml.py | 50 +++++++++--- .github/scripts/test_tekton_tasks.sh | 78 ++++++++++++++++++- .../close-advisory-issues/tests/mocks.yaml | 11 ++- .../tests/pre-apply-task-hook.sh | 12 ++- 5 files changed, 138 insertions(+), 20 deletions(-) diff --git a/.github/scripts/mock_http_json.py b/.github/scripts/mock_http_json.py index 6b779c1f92..dfd816ee03 100755 --- a/.github/scripts/mock_http_json.py +++ b/.github/scripts/mock_http_json.py @@ -155,11 +155,16 @@ def main() -> None: bind = os.environ.get("TEKTON_HTTP_BIND", "127.0.0.1") routes = _routes_from_env() _Handler.routes = routes - server = _ReuseHTTPServer((bind, port), _Handler) + cert_path: str | None = None + key_path: str | None = None if os.environ.get("TEKTON_MOCK_TLS") == "1": hostname = os.environ.get("TEKTON_MOCK_HOSTNAME") cert_path, key_path, _ca = _generate_self_signed_cert(bind, hostname) + + server = _ReuseHTTPServer((bind, port), _Handler) + + if cert_path is not None and key_path is not None: ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) ctx.load_cert_chain(cert_path, key_path) server.socket = ctx.wrap_socket(server.socket, server_side=True) diff --git a/.github/scripts/render_python_task_mocks_from_yaml.py b/.github/scripts/render_python_task_mocks_from_yaml.py index 50c001aceb..dedc9762b5 100755 --- a/.github/scripts/render_python_task_mocks_from_yaml.py +++ b/.github/scripts/render_python_task_mocks_from_yaml.py @@ -173,14 +173,6 @@ def _render_http_json(svc: dict[str, Any]) -> str: "", ] - if use_tls: - lines += [ - 'export SSL_CERT_FILE="/tmp/mock-ca.crt"', - 'export REQUESTS_CA_BUNDLE="/tmp/mock-ca.crt"', - 'export CURL_CA_BUNDLE="/tmp/mock-ca.crt"', - "", - ] - mock_server_for_env_var = svc.get("mock_server_for_env_var") if mock_server_for_env_var is not None: if not isinstance(mock_server_for_env_var, str) or not _ENV_SAFE.match( @@ -241,6 +233,36 @@ def _render_http_json(svc: dict[str, Any]) -> str: return "\n".join(lines) +def _render_tls_task_entrypoint_exec() -> str: + """Run TASK_ENTRYPOINT under Python after configuring TLS trust for mock HTTPS.""" + return r"""exec python3 - "${TASK_ENTRYPOINT[@]}" <<'PY' +import os +import runpy +import sys +import tempfile + +ca = os.environ.get("TEKTON_MOCK_CA_CERT_PATH", "") +if ca and os.path.isfile(ca): + bundle = ca + system_ca = "/etc/pki/tls/certs/ca-bundle.crt" + if os.path.isfile(system_ca): + fd, bundle = tempfile.mkstemp(suffix=".crt") + os.close(fd) + with open(bundle, "wb") as out: + with open(system_ca, "rb") as handle: + out.write(handle.read()) + with open(ca, "rb") as handle: + out.write(handle.read()) + os.environ["SSL_CERT_FILE"] = bundle + os.environ["REQUESTS_CA_BUNDLE"] = bundle + os.environ["CURL_CA_BUNDLE"] = bundle + +script = sys.argv[1] +sys.argv = [script, *sys.argv[2:]] +raise SystemExit(runpy.run_path(script, run_name="__main__") or 0) +PY""" + + def _render_mock_binaries_from_dir(tests_dir: Path) -> str: """Turn ``tests/mocks/`` files into a temp dir prepended to ``PATH``.""" mock_dir = tests_dir / "mocks" @@ -322,11 +344,19 @@ def render(tests_dir: Path) -> str: _die("at most one http_json service (v1)") parts: list[str] = [] + use_tls_entrypoint = False if http_svcs: - parts.append(_render_http_json(http_svcs[0])) + svc = http_svcs[0] + parts.append(_render_http_json(svc)) + use_tls_entrypoint = bool(svc.get("tls", False)) and svc.get( + "rewrite_ca_cert_env" + ) is None parts.append(_render_mock_binaries_from_dir(tests_dir)) parts.append(_render_python_module_mocks_from_dir(tests_dir)) - parts.append('exec "${TASK_ENTRYPOINT[@]}"') + if use_tls_entrypoint: + parts.append(_render_tls_task_entrypoint_exec()) + else: + parts.append('exec "${TASK_ENTRYPOINT[@]}"') return "\n".join(p for p in parts if p) diff --git a/.github/scripts/test_tekton_tasks.sh b/.github/scripts/test_tekton_tasks.sh index 54f6d21778..767e1cd570 100755 --- a/.github/scripts/test_tekton_tasks.sh +++ b/.github/scripts/test_tekton_tasks.sh @@ -86,6 +86,62 @@ apply_python_command_mocks_merge() { done } +# When mocks.yaml sets hostname on an http_json service, map that hostname to the +# mock bind address via PipelineRun podTemplate.hostAliases (Task CRDs cannot set this). +apply_mock_host_aliases_to_pipelinerun_spec() { + local tests_dir="$1" + local test_path="$2" + local task_name="$3" + local pipelinerun_json="$4" + local mocks_yaml="${tests_dir}/mocks.yaml" + local mock_host mock_bind pipeline_task + + [[ -f "$mocks_yaml" ]] || { echo "$pipelinerun_json"; return 0; } + + mock_host=$(yq -r '.services[0].hostname // ""' "$mocks_yaml") + mock_bind=$(yq -r '.services[0].bind // "127.0.0.1"' "$mocks_yaml") + if [[ -z "$mock_host" || "$mock_host" == "null" || "$mock_host" == "$mock_bind" ]]; then + echo "$pipelinerun_json" + return 0 + fi + + pipeline_task=$( + yq -r ".spec.tasks[] | select(.taskRef.name == \"${task_name}\") | .name" \ + "$test_path" + ) + if [[ -z "$pipeline_task" || "$pipeline_task" == "null" ]]; then + echo " Warning: mocks.yaml hostname set but no taskRef.name=${task_name} in ${test_path}" >&2 + echo "$pipelinerun_json" + return 0 + fi + + echo " Applying hostAliases ${mock_host} -> ${mock_bind} for pipeline task ${pipeline_task}" >&2 + jq \ + --arg task "$pipeline_task" \ + --arg ip "$mock_bind" \ + --arg host "$mock_host" \ + '.spec.taskRunSpecs = [{ + pipelineTaskName: $task, + podTemplate: { + hostAliases: [{ + ip: $ip, + hostnames: [$host] + }] + } + }]' <<<"$pipelinerun_json" +} + +needs_mock_host_aliases() { + local tests_dir="$1" + local mocks_yaml="${tests_dir}/mocks.yaml" + local mock_host mock_bind + + [[ -f "$mocks_yaml" ]] || return 1 + mock_host=$(yq -r '.services[0].hostname // ""' "$mocks_yaml") + mock_bind=$(yq -r '.services[0].bind // "127.0.0.1"' "$mocks_yaml") + [[ -n "$mock_host" && "$mock_host" != "null" && "$mock_host" != "$mock_bind" ]] +} + show_help() { echo "Usage: $0 [--remove-compute-resources] [--no-cleanup] [item1] [item2] [...]" echo @@ -338,8 +394,26 @@ do sleep 5 done - PIPELINERUNJSON=$(tkn p start --use-param-defaults $TEST_NAME ${ociStorageParam} ${dataDirParam} -w "name=tests-workspace,${workSpaceParams}" -o json) - PIPELINERUN=$(jq -r '.metadata.name' <<< "${PIPELINERUNJSON}") + if needs_mock_host_aliases "$TESTS_DIR"; then + PIPELINERUNJSON=$( + tkn p start --dry-run --use-param-defaults "$TEST_NAME" \ + ${ociStorageParam} ${dataDirParam} \ + -w "name=tests-workspace,${workSpaceParams}" -o json + ) + PIPELINERUNJSON=$( + apply_mock_host_aliases_to_pipelinerun_spec \ + "$TESTS_DIR" "$TEST_PATH" "$TASK_NAME" "$PIPELINERUNJSON" + ) + CREATED_PRLINE=$(echo "$PIPELINERUNJSON" | kubectl create -f - -o json) + PIPELINERUN=$(jq -r '.metadata.name' <<< "${CREATED_PRLINE}") + else + PIPELINERUNJSON=$( + tkn p start --use-param-defaults "$TEST_NAME" \ + ${ociStorageParam} ${dataDirParam} \ + -w "name=tests-workspace,${workSpaceParams}" -o json + ) + PIPELINERUN=$(jq -r '.metadata.name' <<< "${PIPELINERUNJSON}") + fi echo " Started pipelinerun $PIPELINERUN" sleep 1 # allow a second for the pr object to appear (including a status condition) diff --git a/tasks/managed/close-advisory-issues/tests/mocks.yaml b/tasks/managed/close-advisory-issues/tests/mocks.yaml index f2d478d136..879c6e67e8 100644 --- a/tasks/managed/close-advisory-issues/tests/mocks.yaml +++ b/tasks/managed/close-advisory-issues/tests/mocks.yaml @@ -1,14 +1,13 @@ --- -# Declarative mocks for Tekton tests of this task (Python entrypoint). Rendered -# by .github/scripts/render_python_task_mocks_from_yaml.py when -# test_tekton_tasks.sh runs. +# Mock Jira on localhost; test_tekton_tasks.sh applies hostAliases on the PipelineRun +# when hostname is set below. pre-apply-task-hook.sh adds NET_BIND_SERVICE for port 443. version: 1 services: - type: http_json - port: 8080 bind: 127.0.0.1 - mock_server_for_env_var: JIRA_API_BASE_URL - mock_server_api_path: "" + port: 443 + tls: true + hostname: redhat.atlassian.net routes: - path_contains: /issue/ISSUE-123/transitions method: POST diff --git a/tasks/managed/close-advisory-issues/tests/pre-apply-task-hook.sh b/tasks/managed/close-advisory-issues/tests/pre-apply-task-hook.sh index ad346164c7..37816f5f45 100755 --- a/tasks/managed/close-advisory-issues/tests/pre-apply-task-hook.sh +++ b/tasks/managed/close-advisory-issues/tests/pre-apply-task-hook.sh @@ -1,5 +1,15 @@ #!/usr/bin/env bash +set -euo pipefail + +TASK_PATH="$1" + +# Bind port 443 for the in-pod mock without running as root (test task copy only). +yq -i \ + '.spec.steps[1].securityContext.capabilities = {"add": ["NET_BIND_SERVICE"]}' \ + "$TASK_PATH" # Create a dummy access token secret (and delete it first if it exists) kubectl delete secret konflux-advisory-jira-secret --ignore-not-found -kubectl create secret generic konflux-advisory-jira-secret --from-literal=token=abcdefg --from-literal=email=team@domain.com +kubectl create secret generic konflux-advisory-jira-secret \ + --from-literal=token=abcdefg \ + --from-literal=email=team@domain.com