From f534ac2c8bee2f363bbd98484756b29dd4de9122 Mon Sep 17 00:00:00 2001 From: mbruzda Date: Tue, 29 Sep 2026 12:06:18 +0200 Subject: [PATCH 1/5] feat!: run workflow jobs on Python 3.13 BREAKING CHANGE: The reusable workflow no longer accepts python-version or test-python-version. Callers must remove these inputs and use Python 3.13-compatible dependencies and WFE images. --- .github/workflows/build-test-release.yaml | 2 +- .../workflows/reusable-build-test-release.yml | 258 ++---------------- .../reusable-publish-to-splunkbase.yml | 2 +- .../reusable-validate-deploy-docs.yml | 4 +- README.md | 5 +- 5 files changed, 31 insertions(+), 240 deletions(-) diff --git a/.github/workflows/build-test-release.yaml b/.github/workflows/build-test-release.yaml index 9729d1099..774a7eeb9 100644 --- a/.github/workflows/build-test-release.yaml +++ b/.github/workflows/build-test-release.yaml @@ -30,7 +30,7 @@ jobs: - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: - python-version: "3.7" + python-version: "3.13" - run: | bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/v1.6.26/scripts/download-actionlint.bash) - uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/reusable-build-test-release.yml b/.github/workflows/reusable-build-test-release.yml index 9109c7c40..4d427b6fa 100644 --- a/.github/workflows/reusable-build-test-release.yml +++ b/.github/workflows/reusable-build-test-release.yml @@ -57,16 +57,6 @@ on: description: "Forces WFE tests to run only on the latest Splunk when set to true. When set to false - will run on all supported Splunk versions required for the release. When not set - default behavior." type: string default: "false" - python-version: - required: false - description: "Python version to use for package build" - type: string - default: "3.9" - test-python-version: - required: false - description: "Python version to use for pre-commit, WFE test tooling, and test dependencies" - type: string - default: "3.13" spl2-generate: required: false description: "Run spl2-generate hook during pre-commit step" @@ -144,8 +134,7 @@ concurrency: group: ${{ github.head_ref || github.run_id }} cancel-in-progress: true env: - PYTHON_VERSION: ${{ inputs.python-version }} - TEST_PYTHON_VERSION: ${{ inputs.test-python-version }} + PYTHON_VERSION: "3.13" POETRY_VERSION: "2.2.1" POETRY_EXPORT_PLUGIN_VERSION: "1.9.0" GS_IMAGE_VERSION: ${{ inputs.gs-image-version }} @@ -715,7 +704,7 @@ jobs: - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: - python-version: ${{ env.TEST_PYTHON_VERSION }} + python-version: ${{ env.PYTHON_VERSION }} - uses: actions/setup-java@v5 with: distribution: 'temurin' @@ -760,85 +749,11 @@ jobs: block_mode: "policy" run-unit-tests: - name: run-unit-tests (${{ inputs.python-version }}) - runs-on: ubuntu-22.04 - needs: - - setup-workflow - if: ${{ needs.setup-workflow.outputs.execute-unit == 'true' }} - permissions: - actions: read - deployments: read - contents: read - packages: read - statuses: read - checks: write - env: - UNIT_TESTS_PYTHON_VERSION: ${{ inputs.python-version }} - steps: - - uses: actions/create-github-app-token@v3 - id: app-token - with: - client-id: ${{ secrets.GH_APP_CLIENT_ID }} - private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} - owner: ${{ github.repository_owner }} - - uses: actions/checkout@v7 - - uses: actions/setup-python@v6 - with: - python-version: ${{ env.UNIT_TESTS_PYTHON_VERSION }} - - name: Setup addon - run: | - if [ -f "poetry.lock" ] - then - python${{ env.UNIT_TESTS_PYTHON_VERSION }} -m pip install poetry==${{ env.POETRY_VERSION }} poetry-plugin-export==${{ env.POETRY_EXPORT_PLUGIN_VERSION }} - mkdir -p package/lib || true - poetry check --lock - poetry export --without-hashes --with dev -o package/lib/requirements.txt - else - echo "No poetry.lock found, make sure your dependencies are managed through poetry, exiting" - exit 1 - fi - git config --global --add url."https://x-access-token:${{ steps.app-token.outputs.token }}@github.com".insteadOf https://github.com - git config --global --add url."https://x-access-token:${{ steps.app-token.outputs.token }}@github.com".insteadOf ssh://git@github.com - python${{ env.UNIT_TESTS_PYTHON_VERSION }} -m venv ~/.dev_venv - ~/.dev_venv/bin/python${{ env.UNIT_TESTS_PYTHON_VERSION }} -m pip install -r package/lib/requirements.txt - - name: Create directories - run: | - mkdir -p /opt/splunk/var/log/splunk - chmod -R 777 /opt/splunk/var/log/splunk - - name: Copy pytest ini - run: cp tests/unit/pytest-ci.ini pytest.ini - - name: Run Pytest with coverage - run: ~/.dev_venv/bin/python${{ env.UNIT_TESTS_PYTHON_VERSION }} -m pytest --cov=./ --cov-report=xml --junitxml=test-results/junit.xml tests/unit - - name: Job summary - continue-on-error: true - run: | - sudo apt-get update - sudo apt-get install -y libxml2-utils - junit_xml_file=$(find "test-results" -name "*.xml" -type f 2>/dev/null | head -n 1) - - if [ -n "$junit_xml_file" ]; then - total_tests=$(xmllint --xpath "count(//testcase)" "$junit_xml_file") - failures=$(xmllint --xpath "count(//testcase[failure])" "$junit_xml_file") - errors=$(xmllint --xpath "count(//testcase[error and not(failure)])" "$junit_xml_file") - skipped=$(xmllint --xpath "count(//testcase[skipped])" "$junit_xml_file") - passed=$((total_tests - failures - errors - skipped)) - echo -e "| Total Tests | Passed Tests | Failed Tests | Errored Tests | Skipped Tests |\n| ----------- | ------------ | ------------ | ------------- | ------------- |\n| $total_tests | $passed | $failures | $errors | $skipped |" >> "$GITHUB_STEP_SUMMARY" - else - echo "no XML File found, exiting" - exit 1 - fi - - uses: actions/upload-artifact@v7 - if: success() || failure() - with: - name: test-results-unit-python_${{ env.UNIT_TESTS_PYTHON_VERSION }} - path: test-results/* - - run-unit-tests-py313: name: run-unit-tests (3.13) runs-on: ubuntu-22.04 needs: - setup-workflow - if: ${{ needs.setup-workflow.outputs.execute-unit == 'true' && inputs.python-version != '3.13' }} + if: ${{ needs.setup-workflow.outputs.execute-unit == 'true' }} permissions: actions: read deployments: read @@ -846,8 +761,6 @@ jobs: packages: read statuses: read checks: write - env: - UNIT_TESTS_PYTHON_VERSION: "3.13" steps: - uses: actions/create-github-app-token@v3 id: app-token @@ -858,12 +771,12 @@ jobs: - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: - python-version: ${{ env.UNIT_TESTS_PYTHON_VERSION }} + python-version: ${{ env.PYTHON_VERSION }} - name: Setup addon run: | if [ -f "poetry.lock" ] then - python${{ env.UNIT_TESTS_PYTHON_VERSION }} -m pip install poetry==${{ env.POETRY_VERSION }} poetry-plugin-export==${{ env.POETRY_EXPORT_PLUGIN_VERSION }} + python${{ env.PYTHON_VERSION }} -m pip install poetry==${{ env.POETRY_VERSION }} poetry-plugin-export==${{ env.POETRY_EXPORT_PLUGIN_VERSION }} mkdir -p package/lib || true poetry check --lock poetry export --without-hashes --with dev -o package/lib/requirements.txt @@ -873,8 +786,8 @@ jobs: fi git config --global --add url."https://x-access-token:${{ steps.app-token.outputs.token }}@github.com".insteadOf https://github.com git config --global --add url."https://x-access-token:${{ steps.app-token.outputs.token }}@github.com".insteadOf ssh://git@github.com - python${{ env.UNIT_TESTS_PYTHON_VERSION }} -m venv ~/.dev_venv - ~/.dev_venv/bin/python${{ env.UNIT_TESTS_PYTHON_VERSION }} -m pip install -r package/lib/requirements.txt + python${{ env.PYTHON_VERSION }} -m venv ~/.dev_venv + ~/.dev_venv/bin/python${{ env.PYTHON_VERSION }} -m pip install -r package/lib/requirements.txt - name: Create directories run: | mkdir -p /opt/splunk/var/log/splunk @@ -882,7 +795,7 @@ jobs: - name: Copy pytest ini run: cp tests/unit/pytest-ci.ini pytest.ini - name: Run Pytest with coverage - run: ~/.dev_venv/bin/python${{ env.UNIT_TESTS_PYTHON_VERSION }} -m pytest --cov=./ --cov-report=xml --junitxml=test-results/junit.xml tests/unit + run: ~/.dev_venv/bin/python${{ env.PYTHON_VERSION }} -m pytest --cov=./ --cov-report=xml --junitxml=test-results/junit.xml tests/unit - name: Job summary continue-on-error: true run: | @@ -904,7 +817,7 @@ jobs: - uses: actions/upload-artifact@v7 if: success() || failure() with: - name: test-results-unit-python_${{ env.UNIT_TESTS_PYTHON_VERSION }} + name: test-results-unit-python_${{ env.PYTHON_VERSION }} path: test-results/* build: @@ -919,9 +832,8 @@ jobs: - review_secrets - semgrep - run-unit-tests - - run-unit-tests-py313 - fossa-scan - if: ${{ !cancelled() && (needs.run-unit-tests.result == 'success' || needs.run-unit-tests.result == 'skipped') && (needs.run-unit-tests-py313.result == 'success' || needs.run-unit-tests-py313.result == 'skipped') && (needs.validate-custom-version.result == 'success' || needs.validate-custom-version.result == 'skipped') && (needs.check-docs-changes.outputs.docs-only == 'false') }} + if: ${{ !cancelled() && (needs.run-unit-tests.result == 'success' || needs.run-unit-tests.result == 'skipped') && (needs.validate-custom-version.result == 'success' || needs.validate-custom-version.result == 'skipped') && (needs.check-docs-changes.outputs.docs-only == 'false') }} outputs: buildname: ${{ steps.buildupload.outputs.name }} permissions: @@ -1127,124 +1039,6 @@ jobs: path: build/package/deployment** if: ${{ !cancelled() }} - build-py313: - name: build (3.13) - runs-on: ubuntu-22.04 - needs: - - check-docs-changes - - validate-custom-version - - setup-workflow - - meta - - compliance-copyrights - - pre-commit - - review_secrets - - semgrep - - run-unit-tests-py313 - - fossa-scan - if: ${{ !cancelled() && (needs.run-unit-tests-py313.result == 'success' || needs.run-unit-tests-py313.result == 'skipped') && (needs.validate-custom-version.result == 'success' || needs.validate-custom-version.result == 'skipped') && (needs.check-docs-changes.outputs.docs-only == 'false') }} - permissions: - contents: read - packages: read - env: - BUILD_PYTHON_VERSION: "3.13" - steps: - - uses: actions/create-github-app-token@v3 - id: app-token - with: - client-id: ${{ secrets.GH_APP_CLIENT_ID }} - private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} - owner: ${{ github.repository_owner }} - - uses: actions/checkout@v7 - with: - persist-credentials: false - - name: Setup python - uses: actions/setup-python@v6 - with: - python-version: ${{ env.BUILD_PYTHON_VERSION }} - - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - - name: create requirements file for pip - run: | - git config --global --add url."https://x-access-token:${{ steps.app-token.outputs.token }}@github.com".insteadOf https://github.com - git config --global --add url."https://x-access-token:${{ steps.app-token.outputs.token }}@github.com".insteadOf ssh://git@github.com - if [ -f "poetry.lock" ] - then - echo " poetry.lock found " - python${{ env.BUILD_PYTHON_VERSION }} -m pip install poetry==${{ env.POETRY_VERSION }} poetry-plugin-export==${{ env.POETRY_EXPORT_PLUGIN_VERSION }} - poetry export --without-hashes -o requirements.txt - if [ "$(grep -cve '^\s*$' requirements.txt)" -ne 0 ] - then - echo "Prod dependencies were found, creating package/lib folder" - mkdir -p package/lib || true - mv requirements.txt package/lib - else - echo "No prod dependencies were found" - rm requirements.txt - fi - fi - - name: Setup UCC - run: | - if [ -f "dev_deps/requirements_ucc.txt" ]; then - echo "Found requirements_ucc.txt. Installing UCC dependencies in an isolated environment" - python${{ env.BUILD_PYTHON_VERSION }} -m venv ~/.ucc_venv - ~/.ucc_venv/bin/python${{ env.BUILD_PYTHON_VERSION }} -m pip install -r dev_deps/requirements_ucc.txt - export UCC_GEN="$HOME/.ucc_venv/bin/ucc-gen" - - if [ ! -f "$UCC_GEN" ]; then - echo "ucc-gen not found after installing requirements from dev_deps/requirements_ucc.txt" - exit 1 - fi - - echo "UCC_GEN=$UCC_GEN" >> "$GITHUB_ENV" - else - echo "No UCC requirements file found, skipping UCC setup" - echo "Create a dev_deps/requirements_ucc.txt file and place UCC requirement there, to avoid dependency conflicts" - exit 1 - fi - - name: Download THIRDPARTY - if: github.event_name != 'pull_request' && github.event_name != 'schedule' - uses: actions/download-artifact@v8 - with: - name: THIRDPARTY - - name: Download THIRDPARTY (Optional for PR and schedule) - if: github.event_name == 'pull_request' || github.event_name == 'schedule' - continue-on-error: true - uses: actions/download-artifact@v8 - with: - name: THIRDPARTY - - name: Update Notices - run: | - cp -f THIRDPARTY package/THIRDPARTY || echo "THIRDPARTY file not found (allowed for PR and schedule)" - - name: Build Package - id: uccgen - run: | - "${UCC_GEN:-ucc-gen}" build --ta-version 0.0.${{ github.run_id }} - if [ -f package.json ] - then - yarn - yarn version --new-version 0.0.${{ github.run_id }} --no-git-tag-version - yarn run build - fi - echo "OUTPUT=output/$(ls output/)" >> "$GITHUB_OUTPUT" - - name: Slim Package - id: slim - run: | - pip install splunk-packaging-toolkit==1.2.4 - INPUT_SOURCE=${{ steps.uccgen.outputs.OUTPUT }} - SOURCE_REGEX='^.*/$' - if [[ $INPUT_SOURCE =~ $SOURCE_REGEX ]];then - echo Removing trailing / from INPUT_SOURCE slim is picky - INPUT_SOURCE=$(echo $INPUT_SOURCE | sed 's/\(.*\)\//\1/') - fi - slim generate-manifest "${INPUT_SOURCE}" --update >/tmp/app.manifest || true - cp /tmp/app.manifest "${INPUT_SOURCE}"/app.manifest - mkdir -p build/package/splunkbase - slim package -o build/package/splunkbase "${INPUT_SOURCE}" - slim validate build/package/splunkbase/* - if: always() - appinspect: name: quality-appinspect-${{ matrix.tags }} needs: build @@ -1460,12 +1254,12 @@ jobs: - name: Setup python uses: actions/setup-python@v6 with: - python-version: ${{ env.TEST_PYTHON_VERSION }} + python-version: ${{ env.PYTHON_VERSION }} - name: setup-poetry id: setup-poetry shell: bash run: | - python${{ env.TEST_PYTHON_VERSION }} -m pip install poetry==${{ env.POETRY_VERSION }} + python${{ env.PYTHON_VERSION }} -m pip install poetry==${{ env.POETRY_VERSION }} export POETRY_REPOSITORIES_SPLUNK_ADD_ON_UCC_MODINPUT_TEST_URL=https://github.com/splunk/addonfactory-ucc-test.git export POETRY_HTTP_BASIC_SPLUNK_ADD_ON_UCC_MODINPUT_TEST_USERNAME=${{ secrets.SA_GH_USER_NAME }} export POETRY_HTTP_BASIC_SPLUNK_ADD_ON_UCC_MODINPUT_TEST_PASSWORD=${{ steps.app-token.outputs.token }} @@ -1477,8 +1271,8 @@ jobs: env: PYTHON_KEYRING_BACKEND: keyring.backends.null.Keyring run: | - python${{ env.TEST_PYTHON_VERSION }} -m venv ~/.dev_venv - ~/.dev_venv/bin/python${{ env.TEST_PYTHON_VERSION }} -m pip install -r dev_deps/requirements_dev.txt + python${{ env.PYTHON_VERSION }} -m venv ~/.dev_venv + ~/.dev_venv/bin/python${{ env.PYTHON_VERSION }} -m pip install -r dev_deps/requirements_dev.txt if [ -f "tests/ucc_modinput_functional/tmp/openapi.json" ]; then ~/.dev_venv/bin/ucc-test-modinput gen -o tests/ucc_modinput_functional/tmp/openapi.json -t ${{ steps.download-openapi.outputs.download-path }}/tmp/ else @@ -1491,11 +1285,11 @@ jobs: AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} run: | - python${{ env.TEST_PYTHON_VERSION }} -m venv ~/.dev_venv + python${{ env.PYTHON_VERSION }} -m venv ~/.dev_venv echo "Found requirements_dev.txt. Installing dev dependencies in an isolated environment" - ~/.dev_venv/bin/python${{ env.TEST_PYTHON_VERSION }} -m pip install -r dev_deps/requirements_dev.txt + ~/.dev_venv/bin/python${{ env.PYTHON_VERSION }} -m pip install -r dev_deps/requirements_dev.txt libs_archive=libs_$(basename "$BUILD_NAME" .spl).tgz - cp -r ~/.dev_venv/lib/python${{ env.TEST_PYTHON_VERSION }}/site-packages/ libs/ + cp -r ~/.dev_venv/lib/python${{ env.PYTHON_VERSION }}/site-packages/ libs/ tar -czf "$libs_archive" libs aws s3 cp "$libs_archive" "s3://${{ needs.setup-workflow.outputs.s3_bucket_k8s }}/ta-apps/$libs_archive" --only-show-errors - name: upload-swagger-artifacts-to-s3 @@ -1588,7 +1382,7 @@ jobs: sc4s-version: ${{ matrix.sc4s.version }} sc4s-docker-registry: ${{ matrix.sc4s.docker_registry }} k8s-manifests-branch: ${{ needs.setup.outputs.k8s-manifests-branch }} - python-version: ${{ env.TEST_PYTHON_VERSION }} + python-version: ${{ env.PYTHON_VERSION }} server-conf-python-version: '' - name: Read secrets from AWS Secrets Manager again into environment variables in case credential rotation id: update-argo-token @@ -1838,7 +1632,7 @@ jobs: sc4s-version: ${{ matrix.sc4s.version }} sc4s-docker-registry: ${{ matrix.sc4s.docker_registry }} k8s-manifests-branch: ${{ needs.setup.outputs.k8s-manifests-branch }} - python-version: ${{ env.TEST_PYTHON_VERSION }} + python-version: ${{ env.PYTHON_VERSION }} server-conf-python-version: '' - name: Read secrets from AWS Secrets Manager again into environment variables in case credential rotation id: update-argo-token @@ -2125,7 +1919,7 @@ jobs: sc4s-version: "No" k8s-manifests-branch: ${{ needs.setup.outputs.k8s-manifests-branch }} test-browser: ${{ env.TEST_BROWSER }} - python-version: ${{ env.TEST_PYTHON_VERSION }} + python-version: ${{ env.PYTHON_VERSION }} server-conf-python-version: '' - name: Read secrets from AWS Secrets Manager again into environment variables in case credential rotation id: update-argo-token @@ -2403,7 +2197,7 @@ jobs: vendor-version: ${{ matrix.vendor-version.image }} sc4s-version: "No" k8s-manifests-branch: ${{ needs.setup.outputs.k8s-manifests-branch }} - python-version: ${{ env.TEST_PYTHON_VERSION }} + python-version: ${{ env.PYTHON_VERSION }} server-conf-python-version: ${{ matrix.splunk.serverConfPythonVersion || '' }} - name: Read secrets from AWS Secrets Manager again into environment variables in case credential rotation id: update-argo-token @@ -2680,7 +2474,7 @@ jobs: vendor-version: ${{ matrix.vendor-version.image }} sc4s-version: "No" k8s-manifests-branch: ${{ needs.setup.outputs.k8s-manifests-branch }} - python-version: ${{ env.TEST_PYTHON_VERSION }} + python-version: ${{ env.PYTHON_VERSION }} server-conf-python-version: ${{ matrix.splunk.serverConfPythonVersion || '' }} - name: Read secrets from AWS Secrets Manager again into environment variables in case credential rotation id: update-argo-token @@ -2946,7 +2740,7 @@ jobs: sc4s-version: "No" k8s-manifests-branch: ${{ needs.setup.outputs.k8s-manifests-branch }} ta-upgrade-version: ${{ matrix.ta-version-from-upgrade }} - python-version: ${{ env.TEST_PYTHON_VERSION }} + python-version: ${{ env.PYTHON_VERSION }} server-conf-python-version: '' - name: Read secrets from AWS Secrets Manager again into environment variables in case credential rotation id: update-argo-token @@ -3223,7 +3017,7 @@ jobs: os-name: ${{ steps.os-name-version.outputs.os-name }} os-version: ${{ steps.os-name-version.outputs.os-version }} k8s-manifests-branch: ${{ needs.setup.outputs.k8s-manifests-branch }} - python-version: ${{ env.TEST_PYTHON_VERSION }} + python-version: ${{ env.PYTHON_VERSION }} server-conf-python-version: '' - name: calculate timeout id: calculate-timeout @@ -3481,7 +3275,7 @@ jobs: vendor-version: ${{ matrix.vendor-version.image }} sc4s-version: "No" k8s-manifests-branch: ${{ needs.setup.outputs.k8s-manifests-branch }} - python-version: ${{ env.TEST_PYTHON_VERSION }} + python-version: ${{ env.PYTHON_VERSION }} server-conf-python-version: '' - name: Read secrets from AWS Secrets Manager again into environment variables in case credential rotation id: update-argo-token @@ -3664,9 +3458,7 @@ jobs: - review_secrets - semgrep - build - - build-py313 - run-unit-tests - - run-unit-tests-py313 - appinspect - setup - run-knowledge-tests diff --git a/.github/workflows/reusable-publish-to-splunkbase.yml b/.github/workflows/reusable-publish-to-splunkbase.yml index f8344ae05..37f99b49a 100644 --- a/.github/workflows/reusable-publish-to-splunkbase.yml +++ b/.github/workflows/reusable-publish-to-splunkbase.yml @@ -38,7 +38,7 @@ jobs: - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: - python-version: "3.12" + python-version: "3.13" - run: pip install splunk_add_on_ucc_framework-5.69.1-py3-none-any.whl - name: Fetch build env: diff --git a/.github/workflows/reusable-validate-deploy-docs.yml b/.github/workflows/reusable-validate-deploy-docs.yml index bddb2c2e0..1d06fd0bf 100644 --- a/.github/workflows/reusable-validate-deploy-docs.yml +++ b/.github/workflows/reusable-validate-deploy-docs.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: - python-version: 3.12 + python-version: "3.13" - name: Install mkdocs and plugins run: pip install mkdocs==1.6.0 mkdocs-material==9.5.32 mkdocs-print-site-plugin==2.6.0 - name: Validate docs change @@ -43,7 +43,7 @@ jobs: - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: - python-version: 3.12 + python-version: "3.13" - name: Install mkdocs and plugins run: pip install mkdocs==1.6.0 mkdocs-material==9.5.32 mkdocs-print-site-plugin==2.6.0 - name: Build and Deploy docs diff --git a/README.md b/README.md index 31d0f2c43..5297e6e04 100644 --- a/README.md +++ b/README.md @@ -188,8 +188,7 @@ gitGraph * `scripted-inputs-os-list` - list of OSes used for scripted inputs tests (default includes ubuntu 16.04–24.04 and redhat 8.4–9.5) * `upgrade-tests-ta-versions` - list of TA versions (format `X.X.X`) used as starting points for upgrade tests; e.g. `['7.6.0', '7.7.0']` * `wfe-run-on-splunk-latest` - when `true` forces WFE tests to run only on the latest Splunk version; when `false` runs on all supported Splunk versions required for release; default `false` -* `python-version` - Python version used to build the package and run the package-version unit-test job, default `3.9` -* `test-python-version` - Python version used for pre-commit, WFE test tooling, and test dependencies, default `3.13` +* All Python jobs, including package build, unit tests, pre-commit, WFE test tooling, and test dependencies, use Python `3.13`. The `python-version` and `test-python-version` workflow inputs have been removed. Callers must remove these inputs when upgrading to this breaking release. * `spl2-generate` - when `true` enables SPL2 generation, default `false` * `gs-image-version` - version of the GS Scorecard Docker image, default `1.2` * `gs-version` - version of the GS Scorecard tool, default `0.3` @@ -696,7 +695,7 @@ gs-scorecard-report (gs_scorecard.html) **Description:** -- Unit tests run against the configured `python-version`. A separate Python 3.13 job also runs unless `python-version` is already Python 3.13, avoiding duplicate coverage and artifact names. +- Unit tests run on Python 3.13. The package build and WFE test tooling use the same Python version. **Action used:** NA From 1c749bcbe7ea154e19a6c1e97edb060cee66d0d6 Mon Sep 17 00:00:00 2001 From: mbruzda Date: Wed, 30 Sep 2026 15:40:02 +0200 Subject: [PATCH 2/5] fix: collect WFE summaries from flat or nested artifacts --- .../workflows/reusable-build-test-release.yml | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/.github/workflows/reusable-build-test-release.yml b/.github/workflows/reusable-build-test-release.yml index 4d427b6fa..01ca299f0 100644 --- a/.github/workflows/reusable-build-test-release.yml +++ b/.github/workflows/reusable-build-test-release.yml @@ -1808,9 +1808,8 @@ jobs: run: | echo "| Job | Total Tests | Passed Tests | Failed Tests | Errored Tests | Skipped Tests | Report Link" >> "$GITHUB_STEP_SUMMARY" echo "| ---------- | ----------- | ------ | ------ | ------ | ------- | ------ |" >> "$GITHUB_STEP_SUMMARY" - for file in summary-ko*/job_summary.txt; do - cat "$file" >> "$GITHUB_STEP_SUMMARY" - done + test -n "$(find . -maxdepth 2 -type f -name job_summary.txt -print -quit)" + find . -maxdepth 2 -type f -name job_summary.txt -exec cat {} + >> "$GITHUB_STEP_SUMMARY" - uses: geekyeggo/delete-artifact@v6 with: name: | @@ -2088,9 +2087,8 @@ jobs: run: | echo "| Job | Total Tests | Passed Tests | Failed Tests | Errored Tests | Skipped Tests | Report Link" >> "$GITHUB_STEP_SUMMARY" echo "| ---------- | ----------- | ------ | ------ | ------ | ------- | ------ |" >> "$GITHUB_STEP_SUMMARY" - for file in summary-ui-*/job_summary.txt; do - cat "$file" >> "$GITHUB_STEP_SUMMARY" - done + test -n "$(find . -maxdepth 2 -type f -name job_summary.txt -print -quit)" + find . -maxdepth 2 -type f -name job_summary.txt -exec cat {} + >> "$GITHUB_STEP_SUMMARY" - uses: geekyeggo/delete-artifact@v6 with: name: | @@ -2366,9 +2364,8 @@ jobs: run: | echo "| Job | Total Tests | Passed Tests | Failed Tests | Errored Tests | Skipped Tests | Report Link" >> "$GITHUB_STEP_SUMMARY" echo "| ---------- | ----------- | ------ | ------ | ------ | ------- | ------ |" >> "$GITHUB_STEP_SUMMARY" - for file in summary-modinput*/job_summary.txt; do - cat "$file" >> "$GITHUB_STEP_SUMMARY" - done + test -n "$(find . -maxdepth 2 -type f -name job_summary.txt -print -quit)" + find . -maxdepth 2 -type f -name job_summary.txt -exec cat {} + >> "$GITHUB_STEP_SUMMARY" - uses: geekyeggo/delete-artifact@v6 with: name: | @@ -2643,9 +2640,8 @@ jobs: run: | echo "| Job | Total Tests | Passed Tests | Failed Tests | Errored Tests | Skipped Tests | Report Link" >> "$GITHUB_STEP_SUMMARY" echo "| ---------- | ----------- | ------ | ------ | ------ | ------- | ------ |" >> "$GITHUB_STEP_SUMMARY" - for file in summary-ucc_modinput*/job_summary.txt; do - cat "$file" >> "$GITHUB_STEP_SUMMARY" - done + test -n "$(find . -maxdepth 2 -type f -name job_summary.txt -print -quit)" + find . -maxdepth 2 -type f -name job_summary.txt -exec cat {} + >> "$GITHUB_STEP_SUMMARY" - uses: geekyeggo/delete-artifact@v6 with: name: | @@ -2909,9 +2905,8 @@ jobs: run: | echo "| Job | Total Tests | Passed Tests | Failed Tests | Errored Tests | Skipped Tests | Report Link" >> "$GITHUB_STEP_SUMMARY" echo "| ---------- | ----------- | ------ | ------ | ------ | ------- | ------ |" >> "$GITHUB_STEP_SUMMARY" - for file in summary-upgrade*/job_summary.txt; do - cat "$file" >> "$GITHUB_STEP_SUMMARY" - done + test -n "$(find . -maxdepth 2 -type f -name job_summary.txt -print -quit)" + find . -maxdepth 2 -type f -name job_summary.txt -exec cat {} + >> "$GITHUB_STEP_SUMMARY" - uses: geekyeggo/delete-artifact@v6 with: name: | @@ -3180,9 +3175,8 @@ jobs: run: | echo "| Job | Total Tests | Passed Tests | Failed Tests | Errored Tests | Skipped Tests | Report Link" >> "$GITHUB_STEP_SUMMARY" echo "| ---------- | ----------- | ------ | ------ | ------ | ------- | ------ |" >> "$GITHUB_STEP_SUMMARY" - for file in summary-scripted*/job_summary.txt; do - cat "$file" >> "$GITHUB_STEP_SUMMARY" - done + test -n "$(find . -maxdepth 2 -type f -name job_summary.txt -print -quit)" + find . -maxdepth 2 -type f -name job_summary.txt -exec cat {} + >> "$GITHUB_STEP_SUMMARY" - uses: geekyeggo/delete-artifact@v6 with: name: | @@ -3272,7 +3266,6 @@ jobs: workflow-template-ns: ${{ needs.setup.outputs.argo-namespace }} addon-url: ${{ needs.setup.outputs.addon-upload-path }} addon-name: ${{ needs.setup.outputs.addon-name }} - vendor-version: ${{ matrix.vendor-version.image }} sc4s-version: "No" k8s-manifests-branch: ${{ needs.setup.outputs.k8s-manifests-branch }} python-version: ${{ env.PYTHON_VERSION }} @@ -3433,9 +3426,8 @@ jobs: run: | echo "| Job | Total Tests | Passed Tests | Failed Tests | Errored Tests | Skipped Tests | Report Link" >> "$GITHUB_STEP_SUMMARY" echo "| ---------- | ----------- | ------ | ------ | ------ | ------- | ------ |" >> "$GITHUB_STEP_SUMMARY" - for file in summary-spl2_integration_tests*/job_summary.txt; do - cat "$file" >> "$GITHUB_STEP_SUMMARY" - done + test -n "$(find . -maxdepth 2 -type f -name job_summary.txt -print -quit)" + find . -maxdepth 2 -type f -name job_summary.txt -exec cat {} + >> "$GITHUB_STEP_SUMMARY" - uses: geekyeggo/delete-artifact@v6 with: name: | From 727ee2be0ed0b59b05318daa12834837002dba73 Mon Sep 17 00:00:00 2001 From: mbruzda Date: Fri, 2 Oct 2026 11:19:36 +0200 Subject: [PATCH 3/5] feat: allow Python 3.9 override with 3.13 default --- .github/workflows/reusable-build-test-release.yml | 14 ++++++++++++-- README.md | 3 ++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/reusable-build-test-release.yml b/.github/workflows/reusable-build-test-release.yml index 4db6e86d3..54477f64a 100644 --- a/.github/workflows/reusable-build-test-release.yml +++ b/.github/workflows/reusable-build-test-release.yml @@ -57,6 +57,16 @@ on: description: "Forces WFE tests to run only on the latest Splunk when set to true. When set to false - will run on all supported Splunk versions required for the release. When not set - default behavior." type: string default: "false" + python-version: + required: false + description: "Python version for build, tests, and WFE (3.9 or 3.13; defaults to 3.13)" + type: string + default: "" + test-python-version: + required: false + description: "Deprecated alias for python-version; used only when python-version is unset" + type: string + default: "" spl2-generate: required: false description: "Run spl2-generate hook during pre-commit step" @@ -134,7 +144,7 @@ concurrency: group: ${{ github.head_ref || github.run_id }} cancel-in-progress: true env: - PYTHON_VERSION: "3.13" + PYTHON_VERSION: ${{ inputs.python-version || inputs.test-python-version || '3.13' }} POETRY_VERSION: "2.2.1" POETRY_EXPORT_PLUGIN_VERSION: "1.9.0" FOSSA_CLI_VERSION: "v3.18.4" @@ -751,7 +761,7 @@ jobs: block_mode: "policy" run-unit-tests: - name: run-unit-tests (3.13) + name: run-unit-tests (${{ inputs.python-version || inputs.test-python-version || '3.13' }}) runs-on: ubuntu-22.04 needs: - setup-workflow diff --git a/README.md b/README.md index 1d01318ce..dcc3441f2 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,8 @@ organization's approved private security-reporting channel. * `scripted-inputs-os-list` - list of OSes used for scripted inputs tests (default includes ubuntu 16.04–24.04 and redhat 8.4–9.5) * `upgrade-tests-ta-versions` - list of TA versions (format `X.X.X`) used as starting points for upgrade tests; e.g. `['7.6.0', '7.7.0']` * `wfe-run-on-splunk-latest` - when `true` forces WFE tests to run only on the latest Splunk version; when `false` runs on all supported Splunk versions required for release; default `false` -* All Python jobs, including package build, unit tests, pre-commit, WFE test tooling, and test dependencies, use Python `3.13`. The `python-version` and `test-python-version` workflow inputs have been removed. Callers must remove these inputs when upgrading to this breaking release. +* `python-version` - Python version for package build, unit tests, pre-commit, WFE test tooling, and test dependencies. Supports `3.9` and `3.13`; defaults to `3.13`. +* `test-python-version` - deprecated alias for `python-version`. Used only when `python-version` is unset; both inputs select one version for all Python jobs. * `spl2-generate` - when `true` enables SPL2 generation, default `false` * `gs-image-version` - version of the GS Scorecard Docker image, default `1.2` * `gs-version` - version of the GS Scorecard tool, default `0.3` From 695cef87a263a0500b9d2c5ec27f914bd70d8416 Mon Sep 17 00:00:00 2001 From: mbruzda Date: Wed, 7 Oct 2026 15:37:16 +0200 Subject: [PATCH 4/5] fix: address Python migration review feedback (ADDON-89519) --- .../workflows/reusable-build-test-release.yml | 34 ++++-- README.md | 27 ++++- tests/test_python_migration_workflow.py | 113 ++++++++++++++++++ 3 files changed, 165 insertions(+), 9 deletions(-) create mode 100644 tests/test_python_migration_workflow.py diff --git a/.github/workflows/reusable-build-test-release.yml b/.github/workflows/reusable-build-test-release.yml index e4df785f4..493020d2c 100644 --- a/.github/workflows/reusable-build-test-release.yml +++ b/.github/workflows/reusable-build-test-release.yml @@ -144,6 +144,7 @@ concurrency: group: ${{ github.head_ref || github.run_id }} cancel-in-progress: true env: + # Keep this fallback in sync with jobs.run-unit-tests.name (env is unavailable there). PYTHON_VERSION: ${{ inputs.python-version || inputs.test-python-version || '3.13' }} POETRY_VERSION: "2.2.1" POETRY_EXPORT_PLUGIN_VERSION: "1.9.0" @@ -227,6 +228,16 @@ jobs: argo_server_domain_k8s: ${{ steps.k8s-environment.outputs.argo_server_domain }} argo_token_secret_id_k8s: ${{ steps.k8s-environment.outputs.argo_token_secret_id }} steps: + - name: Validate selected Python version + run: | + case "$PYTHON_VERSION" in + 3.9|3.13) ;; + *) + echo "::error::Supported Python versions are 3.9 and 3.13. Check python-version or its deprecated test-python-version alias." + exit 1 + ;; + esac + - name: set k8s environment id: k8s-environment run: | @@ -711,6 +722,7 @@ jobs: uses: fsfe/reuse-action@v6.0.0 pre-commit: + needs: setup-workflow runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v7.0.1 @@ -761,6 +773,7 @@ jobs: block_mode: "policy" run-unit-tests: + # Keep this fallback in sync with env.PYTHON_VERSION. name: run-unit-tests (${{ inputs.python-version || inputs.test-python-version || '3.13' }}) runs-on: ubuntu-22.04 needs: @@ -1858,7 +1871,8 @@ jobs: run: | echo "| Job | Total Tests | Passed Tests | Failed Tests | Errored Tests | Skipped Tests | Report Link" >> "$GITHUB_STEP_SUMMARY" echo "| ---------- | ----------- | ------ | ------ | ------ | ------- | ------ |" >> "$GITHUB_STEP_SUMMARY" - summary_files=$(find summaries -type f -name job_summary.txt -print | sort) + summary_files=$(find summaries -maxdepth 2 -type f -name job_summary.txt \ + \( -path 'summaries/job_summary.txt' -o -path 'summaries/summary-ko*/job_summary.txt' \) -print | sort) if [[ -z "$summary_files" ]]; then echo "::error::No knowledge-test summary artifacts were downloaded" exit 1 @@ -2144,7 +2158,8 @@ jobs: run: | echo "| Job | Total Tests | Passed Tests | Failed Tests | Errored Tests | Skipped Tests | Report Link" >> "$GITHUB_STEP_SUMMARY" echo "| ---------- | ----------- | ------ | ------ | ------ | ------- | ------ |" >> "$GITHUB_STEP_SUMMARY" - summary_files=$(find summaries -type f -name job_summary.txt -print | sort) + summary_files=$(find summaries -maxdepth 2 -type f -name job_summary.txt \ + \( -path 'summaries/job_summary.txt' -o -path 'summaries/summary-ui*/job_summary.txt' \) -print | sort) if [[ -z "$summary_files" ]]; then echo "::error::No UI-test summary artifacts were downloaded" exit 1 @@ -2428,7 +2443,8 @@ jobs: run: | echo "| Job | Total Tests | Passed Tests | Failed Tests | Errored Tests | Skipped Tests | Report Link" >> "$GITHUB_STEP_SUMMARY" echo "| ---------- | ----------- | ------ | ------ | ------ | ------- | ------ |" >> "$GITHUB_STEP_SUMMARY" - summary_files=$(find summaries -type f -name job_summary.txt -print | sort) + summary_files=$(find summaries -maxdepth 2 -type f -name job_summary.txt \ + \( -path 'summaries/job_summary.txt' -o -path 'summaries/summary-modinput*/job_summary.txt' \) -print | sort) if [[ -z "$summary_files" ]]; then echo "::error::No modinput-test summary artifacts were downloaded" exit 1 @@ -2711,7 +2727,8 @@ jobs: run: | echo "| Job | Total Tests | Passed Tests | Failed Tests | Errored Tests | Skipped Tests | Report Link" >> "$GITHUB_STEP_SUMMARY" echo "| ---------- | ----------- | ------ | ------ | ------ | ------- | ------ |" >> "$GITHUB_STEP_SUMMARY" - summary_files=$(find summaries -type f -name job_summary.txt -print | sort) + summary_files=$(find summaries -maxdepth 2 -type f -name job_summary.txt \ + \( -path 'summaries/job_summary.txt' -o -path 'summaries/summary-ucc_modinput*/job_summary.txt' \) -print | sort) if [[ -z "$summary_files" ]]; then echo "::error::No UCC modinput-test summary artifacts were downloaded" exit 1 @@ -2983,7 +3000,8 @@ jobs: run: | echo "| Job | Total Tests | Passed Tests | Failed Tests | Errored Tests | Skipped Tests | Report Link" >> "$GITHUB_STEP_SUMMARY" echo "| ---------- | ----------- | ------ | ------ | ------ | ------- | ------ |" >> "$GITHUB_STEP_SUMMARY" - summary_files=$(find summaries -type f -name job_summary.txt -print | sort) + summary_files=$(find summaries -maxdepth 2 -type f -name job_summary.txt \ + \( -path 'summaries/job_summary.txt' -o -path 'summaries/summary-upgrade*/job_summary.txt' \) -print | sort) if [[ -z "$summary_files" ]]; then echo "::error::No upgrade-test summary artifacts were downloaded" exit 1 @@ -3260,7 +3278,8 @@ jobs: run: | echo "| Job | Total Tests | Passed Tests | Failed Tests | Errored Tests | Skipped Tests | Report Link" >> "$GITHUB_STEP_SUMMARY" echo "| ---------- | ----------- | ------ | ------ | ------ | ------- | ------ |" >> "$GITHUB_STEP_SUMMARY" - summary_files=$(find summaries -type f -name job_summary.txt -print | sort) + summary_files=$(find summaries -maxdepth 2 -type f -name job_summary.txt \ + \( -path 'summaries/job_summary.txt' -o -path 'summaries/summary-scripted*/job_summary.txt' \) -print | sort) if [[ -z "$summary_files" ]]; then echo "::error::No scripted-input-test summary artifacts were downloaded" exit 1 @@ -3518,7 +3537,8 @@ jobs: run: | echo "| Job | Total Tests | Passed Tests | Failed Tests | Errored Tests | Skipped Tests | Report Link" >> "$GITHUB_STEP_SUMMARY" echo "| ---------- | ----------- | ------ | ------ | ------ | ------- | ------ |" >> "$GITHUB_STEP_SUMMARY" - summary_files=$(find summaries -type f -name job_summary.txt -print | sort) + summary_files=$(find summaries -maxdepth 2 -type f -name job_summary.txt \ + \( -path 'summaries/job_summary.txt' -o -path 'summaries/summary-spl2_integration_tests*/job_summary.txt' \) -print | sort) if [[ -z "$summary_files" ]]; then echo "::error::No SPL2 integration-test summary artifacts were downloaded" exit 1 diff --git a/README.md b/README.md index 4e7bdab9f..be5ae5fc6 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,29 @@ organization's approved private security-reporting channel. * `gs-image-version` - TA Validator Docker image tag in ECR, default `1.6.0` * `gs-version` - TA Validator tool version, default `0.3` +### Python migration contract + +The selected version is `python-version`, then the deprecated `test-python-version` +alias if the first input is unset, then `3.13`. Only `3.9` and `3.13` are accepted; +other selected values fail in `setup-workflow` before pre-commit, build, unit tests, +or WFE submission. + +This is a breaking change: `python-version` now also selects the runtime and test +dependencies for all WFE suites (btool, knowledge, UI, modinput, UCC modinput, +scripted inputs, upgrade, and SPL2), rather than just package build and unit tests. +An existing `python-version: "3.9"` caller therefore selects 3.9 for those WFE paths +as well. When both inputs are set, `python-version` wins; separate build and WFE +Python versions are no longer supported. Deploy the coordinated WFE templates +and images before adopting the new workflow release. SPL2 uses its separate +image compatibility mapping in the WFE templates. + +Unit tests run once on the selected version. The JUnit artifact is named +`test-results-unit-python_`: `test-results-unit-python_3.13` +for the default or `test-results-unit-python_3.9` for the override. The removed +parallel 3.13 job no longer guarantees a `_3.13` artifact for a 3.9 caller. +Artifact consumers must select the configured version or match +`test-results-unit-python_*`. + ## Workflow Secrets All secrets below are declared `required: true` under `on.workflow_call.secrets` and must be passed by @@ -779,7 +802,7 @@ gs-scorecard-report (gs_scorecard.html) **Description:** -- Unit tests run on Python 3.13. The package build and WFE test tooling use the same Python version. +- Unit tests run on the selected `python-version` (default `3.13`, or `3.9` when overridden). The package build, pre-commit, and WFE test tooling use the same selected version. **Action used:** NA @@ -793,7 +816,7 @@ gs-scorecard-report (gs_scorecard.html) **Artifacts:** -- Junit Test result xml file +- JUnit XML in `test-results-unit-python_` (see the Python migration contract above). ## [Job] run-btool-check diff --git a/tests/test_python_migration_workflow.py b/tests/test_python_migration_workflow.py new file mode 100644 index 000000000..f09c0d862 --- /dev/null +++ b/tests/test_python_migration_workflow.py @@ -0,0 +1,113 @@ +# +# Copyright 2026 Splunk Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import os +import subprocess +from pathlib import Path + +import pytest + +from scripts.check_workflow_hygiene import load_workflow + + +WORKFLOW = load_workflow(Path(".github/workflows/reusable-build-test-release.yml")) +JOBS = WORKFLOW["jobs"] +REPORTS = [ + (name, job) for name, job in JOBS.items() + if any(step.get("name") == "Combine summaries into a table" for step in job.get("steps", [])) +] + + +def run_shell(script, directory, **environment): + return subprocess.run( + ["bash", "-eo", "pipefail", "-c", script], cwd=directory, + env={**os.environ, **environment}, text=True, capture_output=True, + ) + + +@pytest.mark.parametrize("version", ["3.9", "3.13"]) +def test_python_guard_accepts_supported_versions(tmp_path, version): + script = JOBS["setup-workflow"]["steps"][0]["run"] + result = run_shell(script, tmp_path, PYTHON_VERSION=version) + assert result.returncode == 0, result.stdout + result.stderr + + +@pytest.mark.parametrize("version", ["", "3.10", "3.13.5", "3.9 ", "$(touch injected)", "3.13; touch injected"]) +def test_python_guard_rejects_unsupported_values_without_execution(tmp_path, version): + script = JOBS["setup-workflow"]["steps"][0]["run"] + result = run_shell(script, tmp_path, PYTHON_VERSION=version) + assert result.returncode != 0 + assert "::error::Supported Python versions" in result.stdout + assert not (tmp_path / "injected").exists() + + +def ancestors(job_name): + needs = JOBS[job_name].get("needs", []) + if isinstance(needs, str): + needs = [needs] + return set(needs).union(*(ancestors(name) for name in needs)) + + +def test_selected_runtime_consumers_wait_for_validation(): + # Any job that executes with the selected runtime must depend on the guard. + consumers = [name for name, job in JOBS.items() if "env.PYTHON_VERSION" in str(job)] + assert {"pre-commit", "build", "run-unit-tests", "run-knowledge-tests"} <= set(consumers) + for name in consumers: + assert "setup-workflow" in ancestors(name), name + + +@pytest.mark.parametrize("report_name,job", REPORTS, ids=[name for name, _ in REPORTS]) +def test_summary_aggregation_sorted_and_scoped(tmp_path, report_name, job): + download = next(step for step in job["steps"] if step.get("name") == "Download all summaries") + pattern = download["with"]["pattern"] + root = tmp_path / download["with"]["path"] + root.mkdir() + for name, text in [(pattern.rstrip("*") + "-z", "row-z"), (pattern.rstrip("*") + "-a", "row-a"), ("unrelated-artifact", "WRONG")]: + artifact = root / name + artifact.mkdir() + (artifact / "job_summary.txt").write_text(text + "\n") + # Flat extraction is valid too; deeper files are not downloaded summary artifacts. + (root / "job_summary.txt").write_text("row-flat\n") + deep = root / (pattern.rstrip("*") + "-deep") / "nested" + deep.mkdir(parents=True) + (deep / "job_summary.txt").write_text("WRONG-DEEP\n") + summary = tmp_path / "result.md" + script = next(step["run"] for step in job["steps"] if step.get("name") == "Combine summaries into a table") + result = run_shell(script, tmp_path, GITHUB_STEP_SUMMARY=str(summary)) + assert result.returncode == 0, result.stdout + result.stderr + assert summary.read_text().splitlines()[2:] == ["row-flat", "row-a", "row-z"], report_name + + +@pytest.mark.parametrize("report_name,job", REPORTS, ids=[name for name, _ in REPORTS]) +@pytest.mark.parametrize("layout", ["missing", "empty", "wrong-prefix", "one-flat", "one-nested"]) +def test_summary_aggregation_fails_when_no_matching_artifact(tmp_path, report_name, job, layout): + download = next(step for step in job["steps"] if step.get("name") == "Download all summaries") + root = tmp_path / download["with"]["path"] + if layout != "missing": + root.mkdir() + if layout in {"wrong-prefix", "one-flat", "one-nested"}: + directory = root + if layout != "one-flat": + directory = root / ("unrelated" if layout == "wrong-prefix" else download["with"]["pattern"].rstrip("*") + "-one") + directory.mkdir() + (directory / "job_summary.txt").write_text("one-row\n") + script = next(step["run"] for step in job["steps"] if step.get("name") == "Combine summaries into a table") + summary = tmp_path / "result.md" + result = run_shell(script, tmp_path, GITHUB_STEP_SUMMARY=str(summary)) + if layout in {"one-flat", "one-nested"}: + assert result.returncode == 0, result.stdout + result.stderr + assert summary.read_text().splitlines()[2:] == ["one-row"] + else: + assert result.returncode != 0, report_name From 2d9605a3152ddc065a8cfb147d09f6b80b7c9b50 Mon Sep 17 00:00:00 2001 From: mbruzda Date: Wed, 7 Oct 2026 15:39:12 +0200 Subject: [PATCH 5/5] fix: block build after Python version validation failure --- .github/workflows/reusable-build-test-release.yml | 2 +- tests/test_python_migration_workflow.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/reusable-build-test-release.yml b/.github/workflows/reusable-build-test-release.yml index 493020d2c..909588b68 100644 --- a/.github/workflows/reusable-build-test-release.yml +++ b/.github/workflows/reusable-build-test-release.yml @@ -858,7 +858,7 @@ jobs: - semgrep - run-unit-tests - fossa-scan - if: ${{ !cancelled() && (needs.run-unit-tests.result == 'success' || needs.run-unit-tests.result == 'skipped') && (needs.validate-custom-version.result == 'success' || needs.validate-custom-version.result == 'skipped') && (needs.check-docs-changes.outputs.docs-only == 'false') }} + if: ${{ !cancelled() && needs.setup-workflow.result == 'success' && (needs.run-unit-tests.result == 'success' || needs.run-unit-tests.result == 'skipped') && (needs.validate-custom-version.result == 'success' || needs.validate-custom-version.result == 'skipped') && (needs.check-docs-changes.outputs.docs-only == 'false') }} outputs: buildname: ${{ steps.buildupload.outputs.name }} permissions: diff --git a/tests/test_python_migration_workflow.py b/tests/test_python_migration_workflow.py index f09c0d862..00899e0c2 100644 --- a/tests/test_python_migration_workflow.py +++ b/tests/test_python_migration_workflow.py @@ -68,6 +68,11 @@ def test_selected_runtime_consumers_wait_for_validation(): assert "setup-workflow" in ancestors(name), name +def test_build_cannot_bypass_failed_python_guard(): + # !cancelled() permits failed dependencies: build must explicitly require setup success. + assert "needs.setup-workflow.result == 'success'" in JOBS["build"]["if"] + + @pytest.mark.parametrize("report_name,job", REPORTS, ids=[name for name, _ in REPORTS]) def test_summary_aggregation_sorted_and_scoped(tmp_path, report_name, job): download = next(step for step in job["steps"] if step.get("name") == "Download all summaries")