diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc75afc4cae4..eba4f8ba98d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -462,8 +462,8 @@ jobs: with: cache-seed: ${{ needs.prepare-workflow.outputs.cache-seed }} salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - relenv-version: "0.22.4" - python-version: "3.10.19" + relenv-version: "0.22.7" + python-version: "3.10.20" ci-python-version: "3.11" matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['build-matrix']) }} linux_arm_runner: ${{ fromJSON(needs.prepare-workflow.outputs.config)['linux_arm_runner'] }} @@ -479,8 +479,8 @@ jobs: with: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }} - relenv-version: "0.22.4" - python-version: "3.10.19" + relenv-version: "0.22.7" + python-version: "3.10.20" ci-python-version: "3.11" source: "onedir" matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['build-matrix']) }} @@ -496,8 +496,8 @@ jobs: with: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }} - relenv-version: "0.22.4" - python-version: "3.10.19" + relenv-version: "0.22.7" + python-version: "3.10.20" ci-python-version: "3.11" source: "src" matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['build-matrix']) }} @@ -512,10 +512,10 @@ jobs: with: nox-session: ci-test-onedir nox-version: 2022.8.7 - python-version: "3.10.19" + python-version: "3.10.20" ci-python-version: "3.11" salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.19 + cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.20 nox-archive-hash: "${{ needs.prepare-workflow.outputs.nox-archive-hash }}" matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['build-matrix']) }} linux_arm_runner: ${{ fromJSON(needs.prepare-workflow.outputs.config)['linux_arm_runner'] }} @@ -532,7 +532,7 @@ jobs: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" nox-version: 2022.8.7 ci-python-version: "3.11" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.19 + cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.20 skip-code-coverage: ${{ fromJSON(needs.prepare-workflow.outputs.config)['skip_code_coverage'] }} testing-releases: ${{ needs.prepare-workflow.outputs.testing-releases }} matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['pkg-test-matrix']) }} @@ -550,7 +550,7 @@ jobs: ci-python-version: "3.11" testrun: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['testrun']) }} salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.19 + cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.20 skip-code-coverage: ${{ fromJSON(needs.prepare-workflow.outputs.config)['skip_code_coverage'] }} workflow-slug: ci default-timeout: 180 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 09d65bb56454..7d20e88c098e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -515,8 +515,8 @@ jobs: with: cache-seed: ${{ needs.prepare-workflow.outputs.cache-seed }} salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - relenv-version: "0.22.4" - python-version: "3.10.19" + relenv-version: "0.22.7" + python-version: "3.10.20" ci-python-version: "3.11" matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['build-matrix']) }} linux_arm_runner: ${{ fromJSON(needs.prepare-workflow.outputs.config)['linux_arm_runner'] }} @@ -532,8 +532,8 @@ jobs: with: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }} - relenv-version: "0.22.4" - python-version: "3.10.19" + relenv-version: "0.22.7" + python-version: "3.10.20" ci-python-version: "3.11" source: "onedir" matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['build-matrix']) }} @@ -553,8 +553,8 @@ jobs: with: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }} - relenv-version: "0.22.4" - python-version: "3.10.19" + relenv-version: "0.22.7" + python-version: "3.10.20" ci-python-version: "3.11" source: "src" matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['build-matrix']) }} @@ -573,10 +573,10 @@ jobs: with: nox-session: ci-test-onedir nox-version: 2022.8.7 - python-version: "3.10.19" + python-version: "3.10.20" ci-python-version: "3.11" salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.19 + cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.20 nox-archive-hash: "${{ needs.prepare-workflow.outputs.nox-archive-hash }}" matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['build-matrix']) }} linux_arm_runner: ${{ fromJSON(needs.prepare-workflow.outputs.config)['linux_arm_runner'] }} @@ -593,7 +593,7 @@ jobs: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" nox-version: 2022.8.7 ci-python-version: "3.11" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.19 + cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.20 skip-code-coverage: true testing-releases: ${{ needs.prepare-workflow.outputs.testing-releases }} matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['pkg-test-matrix']) }} @@ -611,7 +611,7 @@ jobs: ci-python-version: "3.11" testrun: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['testrun']) }} salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.19 + cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.20 skip-code-coverage: true workflow-slug: nightly default-timeout: 360 diff --git a/.github/workflows/scheduled.yml b/.github/workflows/scheduled.yml index 46be0d300736..398ee11f6c72 100644 --- a/.github/workflows/scheduled.yml +++ b/.github/workflows/scheduled.yml @@ -505,8 +505,8 @@ jobs: with: cache-seed: ${{ needs.prepare-workflow.outputs.cache-seed }} salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - relenv-version: "0.22.4" - python-version: "3.10.19" + relenv-version: "0.22.7" + python-version: "3.10.20" ci-python-version: "3.11" matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['build-matrix']) }} linux_arm_runner: ${{ fromJSON(needs.prepare-workflow.outputs.config)['linux_arm_runner'] }} @@ -522,8 +522,8 @@ jobs: with: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }} - relenv-version: "0.22.4" - python-version: "3.10.19" + relenv-version: "0.22.7" + python-version: "3.10.20" ci-python-version: "3.11" source: "onedir" matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['build-matrix']) }} @@ -539,8 +539,8 @@ jobs: with: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }} - relenv-version: "0.22.4" - python-version: "3.10.19" + relenv-version: "0.22.7" + python-version: "3.10.20" ci-python-version: "3.11" source: "src" matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['build-matrix']) }} @@ -555,10 +555,10 @@ jobs: with: nox-session: ci-test-onedir nox-version: 2022.8.7 - python-version: "3.10.19" + python-version: "3.10.20" ci-python-version: "3.11" salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.19 + cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.20 nox-archive-hash: "${{ needs.prepare-workflow.outputs.nox-archive-hash }}" matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['build-matrix']) }} linux_arm_runner: ${{ fromJSON(needs.prepare-workflow.outputs.config)['linux_arm_runner'] }} @@ -575,7 +575,7 @@ jobs: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" nox-version: 2022.8.7 ci-python-version: "3.11" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.19 + cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.20 skip-code-coverage: true testing-releases: ${{ needs.prepare-workflow.outputs.testing-releases }} matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['pkg-test-matrix']) }} @@ -593,7 +593,7 @@ jobs: ci-python-version: "3.11" testrun: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['testrun']) }} salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.19 + cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.20 skip-code-coverage: true workflow-slug: scheduled default-timeout: 360 diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index 2a2a76d306c1..333b9506dc0d 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -489,8 +489,8 @@ jobs: with: cache-seed: ${{ needs.prepare-workflow.outputs.cache-seed }} salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - relenv-version: "0.22.4" - python-version: "3.10.19" + relenv-version: "0.22.7" + python-version: "3.10.20" ci-python-version: "3.11" matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['build-matrix']) }} linux_arm_runner: ${{ fromJSON(needs.prepare-workflow.outputs.config)['linux_arm_runner'] }} @@ -507,8 +507,8 @@ jobs: with: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }} - relenv-version: "0.22.4" - python-version: "3.10.19" + relenv-version: "0.22.7" + python-version: "3.10.20" ci-python-version: "3.11" source: "onedir" matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['build-matrix']) }} @@ -529,8 +529,8 @@ jobs: with: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }} - relenv-version: "0.22.4" - python-version: "3.10.19" + relenv-version: "0.22.7" + python-version: "3.10.20" ci-python-version: "3.11" source: "src" matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['build-matrix']) }} @@ -549,10 +549,10 @@ jobs: with: nox-session: ci-test-onedir nox-version: 2022.8.7 - python-version: "3.10.19" + python-version: "3.10.20" ci-python-version: "3.11" salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.19 + cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.20 nox-archive-hash: "${{ needs.prepare-workflow.outputs.nox-archive-hash }}" matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['build-matrix']) }} linux_arm_runner: ${{ fromJSON(needs.prepare-workflow.outputs.config)['linux_arm_runner'] }} @@ -569,7 +569,7 @@ jobs: salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" nox-version: 2022.8.7 ci-python-version: "3.11" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.19 + cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.20 skip-code-coverage: true testing-releases: ${{ needs.prepare-workflow.outputs.testing-releases }} matrix: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['pkg-test-matrix']) }} @@ -587,7 +587,7 @@ jobs: ci-python-version: "3.11" testrun: ${{ toJSON(fromJSON(needs.prepare-workflow.outputs.config)['testrun']) }} salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.19 + cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.20 skip-code-coverage: true workflow-slug: staging default-timeout: 180 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 895bffe7d17a..cdb1dd2d7764 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,8 @@ repos: - --markdown-linebreak-ext=md exclude: > (?x)^( - pkg/macos/pkg-resources/.*\.rtf + pkg/macos/pkg-resources/.*\.rtf| + pkg/patches/.*\.patch )$ - id: mixed-line-ending # Replaces or checks mixed line ending. @@ -150,28 +151,34 @@ repos: - id: pip-compile alias: compile-pkg-linux-3.9-zmq-requirements name: Linux Packaging Py3.9 ZeroMQ Requirements - files: ^requirements/((base|zeromq|crypto)\.txt|static/pkg/(linux\.in|py3\.9/linux\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|crypto)\.txt|static/pkg/(linux\.in|py3\.9/linux\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/base.txt - requirements/zeromq.txt + - requirements/crypto.txt - requirements/static/pkg/linux.in - --python-platform=linux - --python-version=3.9 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/pkg/py3.9/linux.txt - id: pip-compile alias: compile-pkg-linux-3.10-zmq-requirements name: Linux Packaging Py3.10 ZeroMQ Requirements - files: ^requirements/((base|zeromq|crypto)\.txt|static/pkg/(linux\.in|py3\.10/linux\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|crypto)\.txt|static/pkg/(linux\.in|py3\.10/linux\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/base.txt - requirements/zeromq.txt + - requirements/crypto.txt - requirements/static/pkg/linux.in + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --python-platform=linux - --python-version=3.10 @@ -180,13 +187,16 @@ repos: - id: pip-compile alias: compile-pkg-linux-3.11-zmq-requirements name: Linux Packaging Py3.11 ZeroMQ Requirements - files: ^requirements/((base|zeromq|crypto)\.txt|static/pkg/(linux\.in|py3\.11/linux\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|crypto)\.txt|static/pkg/(linux\.in|py3\.11/linux\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/base.txt - requirements/zeromq.txt + - requirements/crypto.txt - requirements/static/pkg/linux.in + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --python-platform=linux - --python-version=3.11 @@ -195,13 +205,16 @@ repos: - id: pip-compile alias: compile-pkg-linux-3.12-zmq-requirements name: Linux Packaging Py3.12 ZeroMQ Requirements - files: ^requirements/((base|zeromq|crypto)\.txt|static/pkg/(linux\.in|py3\.12/linux\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|crypto)\.txt|static/pkg/(linux\.in|py3\.12/linux\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/base.txt - requirements/zeromq.txt + - requirements/crypto.txt - requirements/static/pkg/linux.in + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --python-platform=linux - --python-version=3.12 @@ -210,13 +223,16 @@ repos: - id: pip-compile alias: compile-pkg-linux-3.13-zmq-requirements name: Linux Packaging Py3.13 ZeroMQ Requirements - files: ^requirements/((base|zeromq|crypto)\.txt|static/pkg/(linux\.in|py3\.13/linux\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|crypto)\.txt|static/pkg/(linux\.in|py3\.13/linux\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/base.txt - requirements/zeromq.txt + - requirements/crypto.txt - requirements/static/pkg/linux.in + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --python-platform=linux - --python-version=3.13 @@ -226,75 +242,90 @@ repos: - id: pip-compile alias: compile-pkg-freebsd-3.9-zmq-requirements name: FreeBSD Packaging Py3.9 ZeroMQ Requirements - files: ^requirements/((base|zeromq|crypto)\.txt|static/pkg/(freebsd\.in|py3\.9/freebsd\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|crypto)\.txt|static/pkg/(freebsd\.in|py3\.9/freebsd\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/base.txt - requirements/zeromq.txt + - requirements/crypto.txt - requirements/static/pkg/freebsd.in - --universal - --python-version=3.9 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/pkg/py3.9/freebsd.txt - id: pip-compile alias: compile-pkg-freebsd-3.10-zmq-requirements name: FreeBSD Packaging Py3.10 ZeroMQ Requirements - files: ^requirements/((base|zeromq|crypto)\.txt|static/pkg/(freebsd\.in|py3\.10/freebsd\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|crypto)\.txt|static/pkg/(freebsd\.in|py3\.10/freebsd\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/base.txt - requirements/zeromq.txt + - requirements/crypto.txt - requirements/static/pkg/freebsd.in - --universal - --python-version=3.10 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/pkg/py3.10/freebsd.txt - id: pip-compile alias: compile-pkg-freebsd-3.11-zmq-requirements name: FreeBSD Packaging Py3.11 ZeroMQ Requirements - files: ^requirements/((base|zeromq|crypto)\.txt|static/pkg/(freebsd\.in|py3\.11/freebsd\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|crypto)\.txt|static/pkg/(freebsd\.in|py3\.11/freebsd\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/base.txt - requirements/zeromq.txt + - requirements/crypto.txt - requirements/static/pkg/freebsd.in - --universal - --python-version=3.11 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/pkg/py3.11/freebsd.txt - id: pip-compile alias: compile-pkg-freebsd-3.12-zmq-requirements name: FreeBSD Packaging Py3.12 ZeroMQ Requirements - files: ^requirements/((base|zeromq|crypto)\.txt|static/pkg/(freebsd\.in|py3\.12/freebsd\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|crypto)\.txt|static/pkg/(freebsd\.in|py3\.12/freebsd\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/base.txt - requirements/zeromq.txt + - requirements/crypto.txt - requirements/static/pkg/freebsd.in - --universal - --python-version=3.12 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/pkg/py3.12/freebsd.txt - id: pip-compile alias: compile-pkg-freebsd-3.13-zmq-requirements name: FreeBSD Packaging Py3.13 ZeroMQ Requirements - files: ^requirements/((base|zeromq|crypto)\.txt|static/pkg/(freebsd\.in|py3\.13/freebsd\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|crypto)\.txt|static/pkg/(freebsd\.in|py3\.13/freebsd\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/base.txt - requirements/zeromq.txt + - requirements/crypto.txt - requirements/static/pkg/freebsd.in - --universal - --python-version=3.13 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/pkg/py3.13/freebsd.txt @@ -302,75 +333,90 @@ repos: - id: pip-compile alias: compile-pkg-darwin-3.9-zmq-requirements name: Darwin Packaging Py3.9 ZeroMQ Requirements - files: ^(requirements/((base|zeromq|crypto|darwin)\.txt|static/pkg/(darwin\.in|py3\.9/darwin\.txt)))$ + files: ^(requirements/(constraints\.txt|(base|zeromq|crypto|darwin)\.txt|static/pkg/(darwin\.in|py3\.9/darwin\.txt)))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/base.txt - requirements/zeromq.txt + - requirements/crypto.txt - requirements/static/pkg/darwin.in - --python-platform=macos - --python-version=3.9 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/pkg/py3.9/darwin.txt - id: pip-compile alias: compile-pkg-darwin-3.10-zmq-requirements name: Darwin Packaging Py3.10 ZeroMQ Requirements - files: ^(requirements/((base|zeromq|crypto|darwin)\.txt|static/pkg/(darwin\.in|py3\.10/darwin\.txt)))$ + files: ^(requirements/(constraints\.txt|(base|zeromq|crypto|darwin)\.txt|static/pkg/(darwin\.in|py3\.10/darwin\.txt)))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/base.txt - requirements/zeromq.txt + - requirements/crypto.txt - requirements/static/pkg/darwin.in - --python-platform=macos - --python-version=3.10 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/pkg/py3.10/darwin.txt - id: pip-compile alias: compile-pkg-darwin-3.11-zmq-requirements name: Darwin Packaging Py3.11 ZeroMQ Requirements - files: ^(requirements/((base|zeromq|crypto|darwin)\.txt|static/pkg/(darwin\.in|py3\.11/darwin\.txt)))$ + files: ^(requirements/(constraints\.txt|(base|zeromq|crypto|darwin)\.txt|static/pkg/(darwin\.in|py3\.11/darwin\.txt)))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/base.txt - requirements/zeromq.txt + - requirements/crypto.txt - requirements/static/pkg/darwin.in - --python-platform=macos - --python-version=3.11 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/pkg/py3.11/darwin.txt - id: pip-compile alias: compile-pkg-darwin-3.12-zmq-requirements name: Darwin Packaging Py3.12 ZeroMQ Requirements - files: ^(requirements/((base|zeromq|crypto|darwin)\.txt|static/pkg/(darwin\.in|py3\.12/darwin\.txt)))$ + files: ^(requirements/(constraints\.txt|(base|zeromq|crypto|darwin)\.txt|static/pkg/(darwin\.in|py3\.12/darwin\.txt)))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/base.txt - requirements/zeromq.txt + - requirements/crypto.txt - requirements/static/pkg/darwin.in - --python-platform=macos - --python-version=3.12 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/pkg/py3.12/darwin.txt - id: pip-compile alias: compile-pkg-darwin-3.13-zmq-requirements name: Darwin Packaging Py3.13 ZeroMQ Requirements - files: ^(requirements/((base|zeromq|crypto|darwin)\.txt|static/pkg/(darwin\.in|py3\.13/darwin\.txt)))$ + files: ^(requirements/(constraints\.txt|(base|zeromq|crypto|darwin)\.txt|static/pkg/(darwin\.in|py3\.13/darwin\.txt)))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/base.txt - requirements/zeromq.txt + - requirements/crypto.txt - requirements/static/pkg/darwin.in - --python-platform=macos - --python-version=3.13 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/pkg/py3.13/darwin.txt @@ -378,70 +424,95 @@ repos: - id: pip-compile alias: compile-pkg-windows-3.9-zmq-requirements name: Windows Packaging Py3.9 ZeroMQ Requirements - files: ^requirements/((base|zeromq|crypto|windows)\.txt|static/pkg/(windows\.in|py3\.9/windows\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|crypto|windows)\.txt|static/pkg/(windows\.in|py3\.9/windows\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: + - requirements/base.txt + - requirements/zeromq.txt + - requirements/crypto.txt - requirements/windows.txt - requirements/static/pkg/windows.in - --python-platform=windows - --python-version=3.9 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/pkg/py3.9/windows.txt - id: pip-compile alias: compile-pkg-windows-3.10-zmq-requirements name: Windows Packaging Py3.10 ZeroMQ Requirements - files: ^requirements/((base|zeromq|crypto|windows)\.txt|static/pkg/(windows\.in|py3\.10/windows\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|crypto|windows)\.txt|static/pkg/(windows\.in|py3\.10/windows\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: + - requirements/base.txt + - requirements/zeromq.txt + - requirements/crypto.txt - requirements/windows.txt - requirements/static/pkg/windows.in - --python-platform=windows - --python-version=3.10 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/pkg/py3.10/windows.txt - id: pip-compile alias: compile-pkg-windows-3.11-zmq-requirements name: Windows Packaging Py3.11 ZeroMQ Requirements - files: ^requirements/((base|zeromq|crypto|windows)\.txt|static/pkg/(windows\.in|py3\.11/windows\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|crypto|windows)\.txt|static/pkg/(windows\.in|py3\.11/windows\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: + - requirements/base.txt + - requirements/zeromq.txt + - requirements/crypto.txt - requirements/windows.txt - requirements/static/pkg/windows.in - --python-platform=windows - --python-version=3.11 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/pkg/py3.11/windows.txt - id: pip-compile alias: compile-pkg-windows-3.12-zmq-requirements name: Windows Packaging Py3.12 ZeroMQ Requirements - files: ^requirements/((base|zeromq|crypto|windows)\.txt|static/pkg/(windows\.in|py3\.12/windows\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|crypto|windows)\.txt|static/pkg/(windows\.in|py3\.12/windows\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: + - requirements/base.txt + - requirements/zeromq.txt + - requirements/crypto.txt - requirements/windows.txt - requirements/static/pkg/windows.in - --python-platform=windows - --python-version=3.12 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/pkg/py3.12/windows.txt - id: pip-compile alias: compile-pkg-windows-3.13-zmq-requirements name: Windows Packaging Py3.13 ZeroMQ Requirements - files: ^requirements/((base|zeromq|crypto|windows)\.txt|static/pkg/(windows\.in|py3\.13/windows\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|crypto|windows)\.txt|static/pkg/(windows\.in|py3\.13/windows\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: + - requirements/base.txt + - requirements/zeromq.txt + - requirements/crypto.txt - requirements/windows.txt - requirements/static/pkg/windows.in - --python-platform=windows - --python-version=3.13 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/pkg/py3.13/windows.txt @@ -453,7 +524,7 @@ repos: - id: pip-compile alias: compile-ci-linux-3.9-zmq-requirements name: Linux CI Py3.9 ZeroMQ Requirements - files: ^requirements/((base|zeromq|pytest)\.txt|static/((ci|pkg)/(linux\.in|common\.in)|py3\.9/linux\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|pytest)\.txt|static/((ci|pkg)/(linux\.in|common\.in)|py3\.9/linux\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -464,6 +535,8 @@ repos: - requirements/static/ci/linux.in - --python-platform=linux - --python-version=3.9 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/pkg/py3.9/linux.txt @@ -472,7 +545,7 @@ repos: - id: pip-compile alias: compile-ci-linux-3.10-zmq-requirements name: Linux CI Py3.10 ZeroMQ Requirements - files: ^requirements/((base|zeromq|pytest)\.txt|static/((ci|pkg)/(linux\.in|common\.in)|py3\.10/linux\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|pytest)\.txt|static/((ci|pkg)/(linux\.in|common\.in)|py3\.10/linux\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -483,6 +556,8 @@ repos: - requirements/static/ci/linux.in - --python-platform=linux - --python-version=3.10 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/pkg/py3.10/linux.txt @@ -491,7 +566,7 @@ repos: - id: pip-compile alias: compile-ci-linux-3.11-zmq-requirements name: Linux CI Py3.11 ZeroMQ Requirements - files: ^requirements/((base|zeromq|pytest)\.txt|static/((ci|pkg)/(linux\.in|common\.in)|py3\.11/linux\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|pytest)\.txt|static/((ci|pkg)/(linux\.in|common\.in)|py3\.11/linux\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -502,6 +577,8 @@ repos: - requirements/static/ci/linux.in - --python-platform=linux - --python-version=3.11 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/pkg/py3.11/linux.txt @@ -510,7 +587,7 @@ repos: - id: pip-compile alias: compile-ci-linux-3.12-zmq-requirements name: Linux CI Py3.12 ZeroMQ Requirements - files: ^requirements/((base|zeromq|pytest)\.txt|static/((ci|pkg)/(linux\.in|common\.in)|py3\.12/linux\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|pytest)\.txt|static/((ci|pkg)/(linux\.in|common\.in)|py3\.12/linux\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -521,6 +598,8 @@ repos: - requirements/static/ci/linux.in - --python-platform=linux - --python-version=3.12 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/pkg/py3.12/linux.txt @@ -529,7 +608,7 @@ repos: - id: pip-compile alias: compile-ci-linux-3.13-zmq-requirements name: Linux CI Py3.13 ZeroMQ Requirements - files: ^requirements/((base|zeromq|pytest)\.txt|static/((ci|pkg)/(linux\.in|common\.in)|py3\.13/linux\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|pytest)\.txt|static/((ci|pkg)/(linux\.in|common\.in)|py3\.13/linux\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -540,6 +619,8 @@ repos: - requirements/static/ci/linux.in - --python-platform=linux - --python-version=3.13 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/pkg/py3.13/linux.txt @@ -550,65 +631,75 @@ repos: - id: pip-compile alias: compile-ci-linux-crypto-3.9-requirements name: Linux CI Py3.9 Crypto Requirements - files: ^requirements/(crypto\.txt|static/ci/(crypto\.in|py3\.9/linux-crypto\.txt))$ + files: ^requirements/(constraints\.txt|crypto\.txt|static/ci/(crypto\.in|py3\.9/linux-crypto\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/static/ci/crypto.in - --python-platform=linux - --python-version=3.9 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.9/linux-crypto.txt - id: pip-compile alias: compile-ci-linux-crypto-3.10-requirements name: Linux CI Py3.10 Crypto Requirements - files: ^requirements/(crypto\.txt|static/ci/(crypto\.in|py3\.10/linux-crypto\.txt))$ + files: ^requirements/(constraints\.txt|crypto\.txt|static/ci/(crypto\.in|py3\.10/linux-crypto\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/static/ci/crypto.in - --python-platform=linux - --python-version=3.10 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.10/linux-crypto.txt - id: pip-compile alias: compile-ci-linux-crypto-3.11-requirements name: Linux CI Py3.11 Crypto Requirements - files: ^requirements/(crypto\.txt|static/ci/(crypto\.in|py3\.11/linux-crypto\.txt))$ + files: ^requirements/(constraints\.txt|crypto\.txt|static/ci/(crypto\.in|py3\.11/linux-crypto\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/static/ci/crypto.in - --python-platform=linux - --python-version=3.11 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.11/linux-crypto.txt - id: pip-compile alias: compile-ci-linux-crypto-3.12-requirements name: Linux CI Py3.12 Crypto Requirements - files: ^requirements/(crypto\.txt|static/ci/(crypto\.in|py3\.12/linux-crypto\.txt))$ + files: ^requirements/(constraints\.txt|crypto\.txt|static/ci/(crypto\.in|py3\.12/linux-crypto\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/static/ci/crypto.in - --python-platform=linux - --python-version=3.12 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.12/linux-crypto.txt - id: pip-compile alias: compile-ci-linux-crypto-3.13-requirements name: Linux CI Py3.13 Crypto Requirements - files: ^requirements/(crypto\.txt|static/ci/(crypto\.in|py3\.13/linux-crypto\.txt))$ + files: ^requirements/(constraints\.txt|crypto\.txt|static/ci/(crypto\.in|py3\.13/linux-crypto\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/static/ci/crypto.in - --python-platform=linux - --python-version=3.13 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.13/linux-crypto.txt @@ -616,7 +707,7 @@ repos: - id: pip-compile alias: compile-ci-freebsd-3.9-zmq-requirements name: FreeBSD CI Py3.9 ZeroMQ Requirements - files: ^requirements/((base|zeromq|pytest)\.txt|static/((ci|pkg)/(freebsd|common)\.in|py3\.9/freebsd\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|pytest)\.txt|static/((ci|pkg)/(freebsd|common)\.in|py3\.9/freebsd\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -628,6 +719,8 @@ repos: - requirements/static/pkg/freebsd.in - --universal - --python-version=3.9 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/pkg/py3.9/freebsd.txt @@ -636,7 +729,7 @@ repos: - id: pip-compile alias: compile-ci-freebsd-3.10-zmq-requirements name: FreeBSD CI Py3.10 ZeroMQ Requirements - files: ^requirements/((base|zeromq|pytest)\.txt|static/((ci|pkg)/(freebsd|common)\.in|py3\.10/freebsd\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|pytest)\.txt|static/((ci|pkg)/(freebsd|common)\.in|py3\.10/freebsd\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -648,6 +741,8 @@ repos: - requirements/static/pkg/freebsd.in - --universal - --python-version=3.10 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/pkg/py3.10/freebsd.txt @@ -656,7 +751,7 @@ repos: - id: pip-compile alias: compile-ci-freebsd-3.11-zmq-requirements name: FreeBSD CI Py3.11 ZeroMQ Requirements - files: ^requirements/((base|zeromq|pytest)\.txt|static/((ci|pkg)/(freebsd|common)\.in|py3\.11/freebsd\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|pytest)\.txt|static/((ci|pkg)/(freebsd|common)\.in|py3\.11/freebsd\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -668,6 +763,8 @@ repos: - requirements/static/pkg/freebsd.in - --universal - --python-version=3.11 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/pkg/py3.11/freebsd.txt @@ -676,7 +773,7 @@ repos: - id: pip-compile alias: compile-ci-freebsd-3.12-zmq-requirements name: FreeBSD CI Py3.12 ZeroMQ Requirements - files: ^requirements/((base|zeromq|pytest)\.txt|static/((ci|pkg)/(freebsd|common)\.in|py3\.12/freebsd\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|pytest)\.txt|static/((ci|pkg)/(freebsd|common)\.in|py3\.12/freebsd\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -688,6 +785,8 @@ repos: - requirements/static/pkg/freebsd.in - --universal - --python-version=3.12 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/pkg/py3.12/freebsd.txt @@ -696,7 +795,7 @@ repos: - id: pip-compile alias: compile-ci-freebsd-3.13-zmq-requirements name: FreeBSD CI Py3.13 ZeroMQ Requirements - files: ^requirements/((base|zeromq|pytest)\.txt|static/((ci|pkg)/(freebsd|common)\.in|py3\.13/freebsd\.txt))$ + files: ^requirements/(constraints\.txt|(base|zeromq|pytest)\.txt|static/((ci|pkg)/(freebsd|common)\.in|py3\.13/freebsd\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -708,6 +807,8 @@ repos: - requirements/static/pkg/freebsd.in - --universal - --python-version=3.13 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/pkg/py3.13/freebsd.txt @@ -717,67 +818,75 @@ repos: - id: pip-compile alias: compile-ci-freebsd-crypto-3.9-requirements name: FreeBSD CI Py3.9 Crypto Requirements - files: ^requirements/(crypto\.txt|static/ci/crypto\.in)$ - files: ^requirements/(crypto\.txt|static/ci/(crypto\.in|py3\.9/freebsd-crypto\.txt))$ + files: ^requirements/(constraints\.txt|crypto\.txt|static/ci/(crypto\.in|py3\.9/freebsd-crypto\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/static/ci/crypto.in - --universal - --python-version=3.9 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.9/freebsd-crypto.txt - id: pip-compile alias: compile-ci-freebsd-crypto-3.10-requirements name: FreeBSD CI Py3.10 Crypto Requirements - files: ^requirements/(crypto\.txt|static/ci/crypto\.in)$ - files: ^requirements/(crypto\.txt|static/ci/(crypto\.in|py3\.10/freebsd-crypto\.txt))$ + files: ^requirements/(constraints\.txt|crypto\.txt|static/ci/(crypto\.in|py3\.10/freebsd-crypto\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/static/ci/crypto.in - --universal - --python-version=3.10 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.10/freebsd-crypto.txt - id: pip-compile alias: compile-ci-freebsd-crypto-3.11-requirements name: FreeBSD CI Py3.11 Crypto Requirements - files: ^requirements/(crypto\.txt|static/ci/(crypto\.in|py3\.11/freebsd-crypto\.txt))$ + files: ^requirements/(constraints\.txt|crypto\.txt|static/ci/(crypto\.in|py3\.11/freebsd-crypto\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/static/ci/crypto.in - --universal - --python-version=3.11 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.11/freebsd-crypto.txt - id: pip-compile alias: compile-ci-freebsd-crypto-3.12-requirements name: FreeBSD CI Py3.12 Crypto Requirements - files: ^requirements/(crypto\.txt|static/ci/(crypto\.in|py3\.12/freebsd-crypto\.txt))$ + files: ^requirements/(constraints\.txt|crypto\.txt|static/ci/(crypto\.in|py3\.12/freebsd-crypto\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/static/ci/crypto.in - --universal - --python-version=3.12 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.12/freebsd-crypto.txt - id: pip-compile alias: compile-ci-freebsd-crypto-3.13-requirements name: FreeBSD CI Py3.13 Crypto Requirements - files: ^requirements/(crypto\.txt|static/ci/(crypto\.in|py3\.13/freebsd-crypto\.txt))$ + files: ^requirements/(constraints\.txt|crypto\.txt|static/ci/(crypto\.in|py3\.13/freebsd-crypto\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/static/ci/crypto.in - --universal - --python-version=3.13 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.13/freebsd-crypto.txt @@ -785,7 +894,7 @@ repos: - id: pip-compile alias: compile-ci-darwin-3.9-zmq-requirements name: Darwin CI Py3.9 ZeroMQ Requirements - files: ^(requirements/((base|zeromq|pytest)\.txt|static/((ci|pkg)/(darwin|common)\.in|py3\.9/darwin\.txt)))$ + files: ^(requirements/(constraints\.txt|(base|zeromq|pytest)\.txt|static/((ci|pkg)/(darwin|common)\.in|py3\.9/darwin\.txt)))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -797,6 +906,8 @@ repos: - requirements/static/ci/darwin.in - --python-platform=macos - --python-version=3.9 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/pkg/py3.9/darwin.txt @@ -805,7 +916,7 @@ repos: - id: pip-compile alias: compile-ci-darwin-3.10-zmq-requirements name: Darwin CI Py3.10 ZeroMQ Requirements - files: ^(requirements/((base|zeromq|pytest)\.txt|static/((ci|pkg)/(darwin|common)\.in|py3\.10/darwin\.txt)))$ + files: ^(requirements/(constraints\.txt|(base|zeromq|pytest)\.txt|static/((ci|pkg)/(darwin|common)\.in|py3\.10/darwin\.txt)))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -817,6 +928,8 @@ repos: - requirements/static/ci/darwin.in - --python-platform=macos - --python-version=3.10 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/pkg/py3.10/darwin.txt @@ -825,7 +938,7 @@ repos: - id: pip-compile alias: compile-ci-darwin-3.11-zmq-requirements name: Darwin CI Py3.11 ZeroMQ Requirements - files: ^(requirements/((base|zeromq|pytest)\.txt|static/((ci|pkg)/(darwin|common)\.in|py3\.11/darwin\.txt)))$ + files: ^(requirements/(constraints\.txt|(base|zeromq|pytest)\.txt|static/((ci|pkg)/(darwin|common)\.in|py3\.11/darwin\.txt)))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -837,6 +950,8 @@ repos: - requirements/static/ci/darwin.in - --python-platform=macos - --python-version=3.11 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/pkg/py3.11/darwin.txt @@ -845,7 +960,7 @@ repos: - id: pip-compile alias: compile-ci-darwin-3.12-zmq-requirements name: Darwin CI Py3.12 ZeroMQ Requirements - files: ^(requirements/((base|zeromq|pytest)\.txt|static/((ci|pkg)/(darwin|common)\.in|py3\.12/darwin\.txt)))$ + files: ^(requirements/(constraints\.txt|(base|zeromq|pytest)\.txt|static/((ci|pkg)/(darwin|common)\.in|py3\.12/darwin\.txt)))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -857,6 +972,8 @@ repos: - requirements/static/ci/darwin.in - --python-platform=macos - --python-version=3.12 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/pkg/py3.12/darwin.txt @@ -865,7 +982,7 @@ repos: - id: pip-compile alias: compile-ci-darwin-3.13-zmq-requirements name: Darwin CI Py3.13 ZeroMQ Requirements - files: ^(requirements/((base|zeromq|pytest)\.txt|static/((ci|pkg)/(darwin|common)\.in|py3\.13/darwin\.txt)))$ + files: ^(requirements/(constraints\.txt|(base|zeromq|pytest)\.txt|static/((ci|pkg)/(darwin|common)\.in|py3\.13/darwin\.txt)))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -877,6 +994,8 @@ repos: - requirements/static/ci/darwin.in - --python-platform=macos - --python-version=3.13 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/pkg/py3.13/darwin.txt @@ -886,65 +1005,75 @@ repos: - id: pip-compile alias: compile-ci-darwin-crypto-3.9-requirements name: Darwin CI Py3.9 Crypto Requirements - files: ^requirements/(crypto\.txt|static/ci/(crypto\.in|py3\.9/darwin-crypto\.txt))$ + files: ^requirements/(constraints\.txt|crypto\.txt|static/ci/(crypto\.in|py3\.9/darwin-crypto\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/static/ci/crypto.in - --python-platform=macos - --python-version=3.9 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.9/darwin-crypto.txt - id: pip-compile alias: compile-ci-darwin-crypto-3.10-requirements name: Darwin CI Py3.10 Crypto Requirements - files: ^requirements/(crypto\.txt|static/ci/(crypto\.in|py3\.10/darwin-crypto\.txt))$ + files: ^requirements/(constraints\.txt|crypto\.txt|static/ci/(crypto\.in|py3\.10/darwin-crypto\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/static/ci/crypto.in - --python-platform=macos - --python-version=3.10 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.10/darwin-crypto.txt - id: pip-compile alias: compile-ci-darwin-crypto-3.11-requirements name: Darwin CI Py3.11 Crypto Requirements - files: ^requirements/(crypto\.txt|static/ci/(crypto\.in|py3\.11/darwin-crypto\.txt))$ + files: ^requirements/(constraints\.txt|crypto\.txt|static/ci/(crypto\.in|py3\.11/darwin-crypto\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/static/ci/crypto.in - --python-platform=macos - --python-version=3.11 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.11/darwin-crypto.txt - id: pip-compile alias: compile-ci-darwin-crypto-3.12-requirements name: Darwin CI Py3.12 Crypto Requirements - files: ^requirements/(crypto\.txt|static/ci/(crypto\.in|py3\.12/darwin-crypto\.txt))$ + files: ^requirements/(constraints\.txt|crypto\.txt|static/ci/(crypto\.in|py3\.12/darwin-crypto\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/static/ci/crypto.in - --python-platform=macos - --python-version=3.12 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.12/darwin-crypto.txt - id: pip-compile alias: compile-ci-darwin-crypto-3.13-requirements name: Darwin CI Py3.13 Crypto Requirements - files: ^requirements/(crypto\.txt|static/ci/(crypto\.in|py3\.13/darwin-crypto\.txt))$ + files: ^requirements/(constraints\.txt|crypto\.txt|static/ci/(crypto\.in|py3\.13/darwin-crypto\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/static/ci/crypto.in - --python-platform=macos - --python-version=3.13 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.13/darwin-crypto.txt @@ -964,6 +1093,8 @@ repos: - requirements/static/ci/windows.in - --python-platform=windows - --python-version=3.9 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/pkg/py3.9/windows.txt @@ -984,6 +1115,8 @@ repos: - requirements/static/ci/windows.in - --python-platform=windows - --python-version=3.10 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/pkg/py3.10/windows.txt @@ -1004,6 +1137,8 @@ repos: - requirements/static/ci/windows.in - --python-platform=windows - --python-version=3.11 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/pkg/py3.11/windows.txt @@ -1024,6 +1159,8 @@ repos: - requirements/static/ci/windows.in - --python-platform=windows - --python-version=3.12 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/pkg/py3.12/windows.txt @@ -1044,6 +1181,8 @@ repos: - requirements/static/ci/windows.in - --python-platform=windows - --python-version=3.13 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/pkg/py3.13/windows.txt @@ -1053,65 +1192,75 @@ repos: - id: pip-compile alias: compile-ci-windows-crypto-3.9-requirements name: Windows CI Py3.9 Crypto Requirements - files: ^requirements/(crypto\.txt|static/ci/(crypto\.in|py3\.9/windows-crypto\.txt))$ + files: ^requirements/(constraints\.txt|crypto\.txt|static/ci/(crypto\.in|py3\.9/windows-crypto\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/static/ci/crypto.in - --python-platform=windows - --python-version=3.9 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.9/windows-crypto.txt - id: pip-compile alias: compile-ci-windows-crypto-3.10-requirements name: Windows CI Py3.10 Crypto Requirements - files: ^requirements/(crypto\.txt|static/ci/(crypto\.in|py3\.10/windows-crypto\.txt))$ + files: ^requirements/(constraints\.txt|crypto\.txt|static/ci/(crypto\.in|py3\.10/windows-crypto\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/static/ci/crypto.in - --python-platform=windows - --python-version=3.10 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.10/windows-crypto.txt - id: pip-compile alias: compile-ci-windows-crypto-3.11-requirements name: Windows CI Py3.11 Crypto Requirements - files: ^requirements/(crypto\.txt|static/ci/(crypto\.in|py3\.11/windows-crypto\.txt))$ + files: ^requirements/(constraints\.txt|crypto\.txt|static/ci/(crypto\.in|py3\.11/windows-crypto\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/static/ci/crypto.in - --python-platform=windows - --python-version=3.11 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.11/windows-crypto.txt - id: pip-compile alias: compile-ci-windows-crypto-3.12-requirements name: Windows CI Py3.12 Crypto Requirements - files: ^requirements/(crypto\.txt|static/ci/(crypto\.in|py3\.12/windows-crypto\.txt))$ + files: ^requirements/(constraints\.txt|crypto\.txt|static/ci/(crypto\.in|py3\.12/windows-crypto\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/static/ci/crypto.in - --python-platform=windows - --python-version=3.12 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.12/windows-crypto.txt - id: pip-compile alias: compile-ci-windows-crypto-3.13-requirements name: Windows CI Py3.13 Crypto Requirements - files: ^requirements/(crypto\.txt|static/ci/(crypto\.in|py3\.13/windows-crypto\.txt))$ + files: ^requirements/(constraints\.txt|crypto\.txt|static/ci/(crypto\.in|py3\.13/windows-crypto\.txt))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/static/ci/crypto.in - --python-platform=windows - --python-version=3.13 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.13/windows-crypto.txt @@ -1123,7 +1272,7 @@ repos: - id: pip-compile alias: compile-ci-cloud-3.9-requirements name: Cloud CI Py3.9 Requirements - files: ^requirements/((base|zeromq|pytest)\.txt|static/(pkg/linux\.in|ci/((cloud|common)\.in|py3\.9/cloud\.txt)))$ + files: ^requirements/(constraints\.txt|(base|zeromq|pytest)\.txt|static/(pkg/linux\.in|ci/((cloud|common)\.in|py3\.9/cloud\.txt)))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -1135,6 +1284,8 @@ repos: - requirements/static/pkg/linux.in - --python-platform=linux - --python-version=3.9 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/ci/py3.9/linux.txt @@ -1144,7 +1295,7 @@ repos: - id: pip-compile alias: compile-ci-cloud-3.10-requirements name: Cloud CI Py3.10 Requirements - files: ^requirements/((base|zeromq|pytest)\.txt|static/(pkg/linux\.in|ci/((cloud|common)\.in|py3\.10/cloud\.txt)))$ + files: ^requirements/(constraints\.txt|(base|zeromq|pytest)\.txt|static/(pkg/linux\.in|ci/((cloud|common)\.in|py3\.10/cloud\.txt)))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -1156,6 +1307,8 @@ repos: - requirements/static/pkg/linux.in - --python-platform=linux - --python-version=3.10 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/ci/py3.10/linux.txt @@ -1165,7 +1318,7 @@ repos: - id: pip-compile alias: compile-ci-cloud-3.11-requirements name: Cloud CI Py3.11 Requirements - files: ^requirements/((base|zeromq|pytest)\.txt|static/(pkg/linux\.in|ci/((cloud|common)\.in|py3\.11/cloud\.txt)))$ + files: ^requirements/(constraints\.txt|(base|zeromq|pytest)\.txt|static/(pkg/linux\.in|ci/((cloud|common)\.in|py3\.11/cloud\.txt)))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -1177,6 +1330,8 @@ repos: - requirements/static/pkg/linux.in - --python-platform=linux - --python-version=3.11 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/ci/py3.11/linux.txt @@ -1186,7 +1341,7 @@ repos: - id: pip-compile alias: compile-ci-cloud-3.12-requirements name: Cloud CI Py3.12 Requirements - files: ^requirements/((base|zeromq|pytest)\.txt|static/(pkg/linux\.in|ci/((cloud|common)\.in|py3\.12/cloud\.txt)))$ + files: ^requirements/(constraints\.txt|(base|zeromq|pytest)\.txt|static/(pkg/linux\.in|ci/((cloud|common)\.in|py3\.12/cloud\.txt)))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -1198,6 +1353,8 @@ repos: - requirements/static/pkg/linux.in - --python-platform=linux - --python-version=3.12 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/ci/py3.12/linux.txt @@ -1207,7 +1364,7 @@ repos: - id: pip-compile alias: compile-ci-cloud-3.13-requirements name: Cloud CI Py3.13 Requirements - files: ^requirements/((base|zeromq|pytest)\.txt|static/(pkg/linux\.in|ci/((cloud|common)\.in|py3\.13/cloud\.txt)))$ + files: ^requirements/(constraints\.txt|(base|zeromq|pytest)\.txt|static/(pkg/linux\.in|ci/((cloud|common)\.in|py3\.13/cloud\.txt)))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -1219,6 +1376,8 @@ repos: - requirements/static/pkg/linux.in - --python-platform=linux - --python-version=3.13 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/ci/py3.13/linux.txt @@ -1232,15 +1391,18 @@ repos: - id: pip-compile alias: compile-doc-requirements name: Docs CI Py3.9 Requirements - files: ^requirements/((base|zeromq|pytest)\.txt|static/ci/(docs|common|linux)\.in|static/pkg/linux\.in|static/pkg/.*/linux\.txt)$ + files: ^requirements/(constraints\.txt|(base|zeromq|pytest|crypto)\.txt|static/ci/(docs|common|linux)\.in|static/pkg/linux\.in|static/pkg/.*/linux\.txt)$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/base.txt - requirements/zeromq.txt + - requirements/crypto.txt - requirements/static/ci/docs.in - --python-platform=linux - --python-version=3.9 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/ci/py3.9/linux.txt @@ -1249,15 +1411,18 @@ repos: - id: pip-compile alias: compile-doc-requirements name: Docs CI Py3.10 Requirements - files: ^requirements/((base|zeromq|pytest)\.txt|static/ci/(docs|common|linux)\.in|static/pkg/linux\.in|static/pkg/.*/linux\.txt)$ + files: ^requirements/(constraints\.txt|(base|zeromq|pytest|crypto)\.txt|static/ci/(docs|common|linux)\.in|static/pkg/linux\.in|static/pkg/.*/linux\.txt)$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/base.txt - requirements/zeromq.txt + - requirements/crypto.txt - requirements/static/ci/docs.in - --python-platform=linux - --python-version=3.10 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/ci/py3.10/linux.txt @@ -1266,15 +1431,18 @@ repos: - id: pip-compile alias: compile-doc-requirements name: Docs CI Py3.11 Requirements - files: ^requirements/((base|zeromq|pytest)\.txt|static/ci/(docs|common|linux)\.in|static/pkg/linux\.in|static/pkg/.*/linux\.txt)$ + files: ^requirements/(constraints\.txt|(base|zeromq|pytest|crypto)\.txt|static/ci/(docs|common|linux)\.in|static/pkg/linux\.in|static/pkg/.*/linux\.txt)$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/base.txt - requirements/zeromq.txt + - requirements/crypto.txt - requirements/static/ci/docs.in - --python-platform=linux - --python-version=3.11 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/ci/py3.11/linux.txt @@ -1283,15 +1451,18 @@ repos: - id: pip-compile alias: compile-doc-requirements name: Docs CI Py3.12 Requirements - files: ^requirements/((base|zeromq|pytest)\.txt|static/ci/(docs|common|linux)\.in|static/pkg/linux\.in|static/pkg/.*/linux\.txt)$ + files: ^requirements/(constraints\.txt|(base|zeromq|pytest|crypto)\.txt|static/ci/(docs|common|linux)\.in|static/pkg/linux\.in|static/pkg/.*/linux\.txt)$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/base.txt - requirements/zeromq.txt + - requirements/crypto.txt - requirements/static/ci/docs.in - --python-platform=linux - --python-version=3.12 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/ci/py3.12/linux.txt @@ -1300,15 +1471,18 @@ repos: - id: pip-compile alias: compile-doc-requirements name: Docs CI Py3.13 Requirements - files: ^requirements/((base|zeromq|pytest)\.txt|static/ci/(docs|common|linux)\.in|static/pkg/linux\.in|static/pkg/.*/linux\.txt)$ + files: ^requirements/(constraints\.txt|(base|zeromq|pytest|crypto)\.txt|static/ci/(docs|common|linux)\.in|static/pkg/linux\.in|static/pkg/.*/linux\.txt)$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: - requirements/base.txt - requirements/zeromq.txt + - requirements/crypto.txt - requirements/static/ci/docs.in - --python-platform=linux - --python-version=3.13 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/ci/py3.13/linux.txt @@ -1321,7 +1495,7 @@ repos: - id: pip-compile alias: compile-ci-lint-3.9-requirements name: Lint CI Py3.9 Requirements - files: ^requirements/((base|zeromq)\.txt|static/(pkg/linux\.in|ci/(linux\.in|common\.in|lint\.in|py3\.9/linux\.txt)))$ + files: ^requirements/(constraints\.txt|(base|zeromq)\.txt|static/(pkg/linux\.in|ci/(linux\.in|common\.in|lint\.in|py3\.9/linux\.txt)))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -1333,6 +1507,8 @@ repos: - requirements/static/pkg/linux.in - --python-platform=linux - --python-version=3.9 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/ci/py3.9/linux.txt @@ -1342,7 +1518,7 @@ repos: - id: pip-compile alias: compile-ci-lint-3.10-requirements name: Lint CI Py3.10 Requirements - files: ^requirements/((base|zeromq)\.txt|static/(pkg/linux\.in|ci/(linux\.in|common\.in|lint\.in|py3\.10/linux\.txt)))$ + files: ^requirements/(constraints\.txt|(base|zeromq)\.txt|static/(pkg/linux\.in|ci/(linux\.in|common\.in|lint\.in|py3\.10/linux\.txt)))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -1354,6 +1530,8 @@ repos: - requirements/static/pkg/linux.in - --python-platform=linux - --python-version=3.10 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/ci/py3.10/linux.txt @@ -1363,7 +1541,7 @@ repos: - id: pip-compile alias: compile-ci-lint-3.11-requirements name: Lint CI Py3.11 Requirements - files: ^requirements/((base|zeromq)\.txt|static/(pkg/linux\.in|ci/(linux\.in|common\.in|lint\.in|py3\.11/linux\.txt)))$ + files: ^requirements/(constraints\.txt|(base|zeromq)\.txt|static/(pkg/linux\.in|ci/(linux\.in|common\.in|lint\.in|py3\.11/linux\.txt)))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -1375,6 +1553,8 @@ repos: - requirements/static/pkg/linux.in - --python-platform=linux - --python-version=3.11 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/ci/py3.11/linux.txt @@ -1384,7 +1564,7 @@ repos: - id: pip-compile alias: compile-ci-lint-3.12-requirements name: Lint CI Py3.12 Requirements - files: ^requirements/((base|zeromq)\.txt|static/(pkg/linux\.in|ci/(linux\.in|common\.in|lint\.in|py3\.12/linux\.txt)))$ + files: ^requirements/(constraints\.txt|(base|zeromq)\.txt|static/(pkg/linux\.in|ci/(linux\.in|common\.in|lint\.in|py3\.12/linux\.txt)))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -1396,6 +1576,8 @@ repos: - requirements/static/pkg/linux.in - --python-platform=linux - --python-version=3.12 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/ci/py3.12/linux.txt @@ -1405,7 +1587,7 @@ repos: - id: pip-compile alias: compile-ci-lint-3.13-requirements name: Lint CI Py3.13 Requirements - files: ^requirements/((base|zeromq)\.txt|static/(pkg/linux\.in|ci/(linux\.in|common\.in|lint\.in|py3\.13/linux\.txt)))$ + files: ^requirements/(constraints\.txt|(base|zeromq)\.txt|static/(pkg/linux\.in|ci/(linux\.in|common\.in|lint\.in|py3\.13/linux\.txt)))$ pass_filenames: false additional_dependencies: ["pip<26.0"] args: @@ -1417,6 +1599,8 @@ repos: - requirements/static/pkg/linux.in - --python-platform=linux - --python-version=3.13 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/ci/py3.13/linux.txt @@ -1436,6 +1620,8 @@ repos: - requirements/static/ci/changelog.in - --python-platform=linux - --python-version=3.9 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/ci/py3.9/linux.txt @@ -1451,6 +1637,8 @@ repos: - requirements/static/ci/changelog.in - --python-platform=linux - --python-version=3.10 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/ci/py3.10/linux.txt @@ -1466,6 +1654,8 @@ repos: - requirements/static/ci/changelog.in - --python-platform=linux - --python-version=3.11 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/ci/py3.11/linux.txt @@ -1481,6 +1671,8 @@ repos: - requirements/static/ci/changelog.in - --python-platform=linux - --python-version=3.12 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/ci/py3.12/linux.txt @@ -1496,6 +1688,8 @@ repos: - requirements/static/ci/changelog.in - --python-platform=linux - --python-version=3.13 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - --unsafe-package=setuptools - -c=requirements/static/ci/py3.13/linux.txt @@ -1515,6 +1709,8 @@ repos: - requirements/static/ci/tools.in - --python-platform=linux - --python-version=3.9 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.9/tools.txt @@ -1528,6 +1724,8 @@ repos: - requirements/static/ci/tools.in - --python-platform=linux - --python-version=3.10 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.10/tools.txt @@ -1541,6 +1739,8 @@ repos: - requirements/static/ci/tools.in - --python-platform=linux - --python-version=3.11 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.11/tools.txt @@ -1554,6 +1754,8 @@ repos: - requirements/static/ci/tools.in - --python-platform=linux - --python-version=3.12 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.12/tools.txt @@ -1567,6 +1769,8 @@ repos: - requirements/static/ci/tools.in - --python-platform=linux - --python-version=3.13 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -o=requirements/static/ci/py3.13/tools.txt @@ -1582,6 +1786,8 @@ repos: - requirements/static/ci/tools-virustotal.in - --python-platform=linux - --python-version=3.9 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -c=requirements/static/ci/py3.9/tools.txt - -o=requirements/static/ci/py3.9/tools-virustotal.txt @@ -1596,6 +1802,8 @@ repos: - requirements/static/ci/tools-virustotal.in - --python-platform=linux - --python-version=3.10 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -c=requirements/static/ci/py3.10/tools.txt - -o=requirements/static/ci/py3.10/tools-virustotal.txt @@ -1610,6 +1818,8 @@ repos: - requirements/static/ci/tools-virustotal.in - --python-platform=linux - --python-version=3.11 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -c=requirements/static/ci/py3.11/tools.txt - -o=requirements/static/ci/py3.11/tools-virustotal.txt @@ -1624,6 +1834,8 @@ repos: - requirements/static/ci/tools-virustotal.in - --python-platform=linux - --python-version=3.12 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -c=requirements/static/ci/py3.12/tools.txt - -o=requirements/static/ci/py3.12/tools-virustotal.txt @@ -1638,6 +1850,8 @@ repos: - requirements/static/ci/tools-virustotal.in - --python-platform=linux - --python-version=3.13 + - --constraint + - requirements/constraints.txt - --no-emit-index-url - -c=requirements/static/ci/py3.13/tools.txt - -o=requirements/static/ci/py3.13/tools-virustotal.txt @@ -1702,10 +1916,7 @@ repos: types: [python] exclude: > (?x)^( - salt/ext/.* - )$ - exclude: > - (?x)^( + salt/ext/.*| tests/pytests/unit/utils/test_versions.py| tests/pytests/functional/transport/tcp/test_pub_server.py )$ diff --git a/.pylintrc b/.pylintrc index 9e78346f41c4..2ffc07039bbb 100644 --- a/.pylintrc +++ b/.pylintrc @@ -763,4 +763,5 @@ allowed-3rd-party-modules=msgpack, pytest_timeout, salt, tests, - backports + backports, + salt_build_backend diff --git a/MANIFEST.in b/MANIFEST.in index fd5d36cc3d1f..99601bc71335 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,6 +7,8 @@ include README.rst include SUPPORT.rst include run.py include pyproject.toml +include tools/pkg/__init__.py +include tools/pkg/salt_build_backend.py include tests/*.py recursive-include tests * recursive-include requirements *.txt diff --git a/agents/README.md b/agents/README.md index 446e79ea8141..d4698ed6145d 100644 --- a/agents/README.md +++ b/agents/README.md @@ -57,6 +57,11 @@ The `docs/` directory contains comprehensive guides that are referenced by all a - CI failure reproduction workflow - Container setup and debugging +- **[package-building.md](docs/package-building.md)** - Package Building Guide + - Building RPMs locally using CI containers + - Building DEBs locally + - Creating source tarballs and onedir artifacts + - **[troubleshooting.md](docs/troubleshooting.md)** - Common issues and solutions - Import order issues - Module discovery problems diff --git a/agents/docs/package-building.md b/agents/docs/package-building.md new file mode 100644 index 000000000000..f68138d61871 --- /dev/null +++ b/agents/docs/package-building.md @@ -0,0 +1,128 @@ +# Package Building Guide + +This guide describes how to build Salt packages (RPM, Deb, etc.) locally using the same methods as the CI/CD pipeline. + +## Overview + +Salt packages are built using the `tools` script (specifically `tools pkg`) running inside a CI container. This ensures the build environment matches the official release environment. + +The general process is: +1. Enter the appropriate CI container +2. Setup the Python environment +3. Build the source tarball +4. Build the "Onedir" (a self-contained directory with Python and dependencies) +5. Build the final package (RPM/Deb) using the Onedir + +## Prerequisites + +- Docker installed and running +- `python-tools-scripts` installed in your local environment (optional, but helpful for some commands) + +## Reference Workflows + +The instructions in this guide are derived from the official GitHub Actions workflows. If you need to verify the current build process or versions, check these files: + +- **`.github/workflows/ci.yml`**: The main entry point for CI builds. It defines the high-level orchestration. +- **`.github/workflows/build-salt-onedir.yml`**: Defines how the "Onedir" (relocatable Python environment) is built. Check this for `relenv` and `python` versions. +- **`.github/workflows/build-packages.yml`**: Defines how the final RPM/Deb/Windows/macOS packages are built using the Onedir. +- **`.github/actions/build-source-tarball/action.yml`**: The steps to create the initial source distribution. + +## Building RPMs (Linux) + +### 1. Enter the Build Container + +From the root of the Salt repository: + +```bash +docker run --rm -it \ + -v "$(pwd):/salt" \ + -w /salt \ + ghcr.io/saltstack/salt-ci-containers/testing:fedora-42 \ + bash +``` + +*Note: Use `fedora-42` for RPM builds. For Deb builds, use `debian-11` or similar.* + +### 2. Setup Python Environment (Inside Container) + +Once inside the container: + +```bash +# Detect python version (likely 3.10 or 3.11 depending on branch/container) +PY_VER=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')") + +# Create and activate venv +python3 -m venv venv +source venv/bin/activate + +# Install build dependencies +pip install --upgrade pip setuptools +pip install -r requirements/static/ci/py${PY_VER}/tools.txt +pip install -e . +``` + +### 3. Build Source Tarball + +Create the source distribution: + +```bash +python3 -m tools pkg source-tarball +``` + +This will create `dist/salt-.tar.gz`. + +### 4. Build Salt Onedir + +The "Onedir" is an artifact containing Salt and all its dependencies (including Python itself) in a relocatable directory. + +```bash +# Get the version from the tarball +SALT_VERSION=$(ls dist/salt-*.tar.gz | sed 's/dist\/salt-//;s/.tar.gz//') +mkdir -p artifacts + +# Build the Onedir +# Note: CI uses specific pinned versions for relenv and python. +# Check .github/workflows/build-salt-onedir.yml for current versions. +python3 -m tools pkg build salt-onedir "dist/salt-${SALT_VERSION}.tar.gz" \ + --platform linux \ + --package-name artifacts/salt \ + --relenv-version 0.22.4 + +# Cleanup and Archive +python3 -m tools pkg pre-archive-cleanup artifacts/salt +tar -cJf "artifacts/salt-${SALT_VERSION}-onedir-linux-x86_64.tar.xz" -C artifacts salt +``` + +### 5. Build RPM Package + +Use the Onedir artifact to build the final RPM. + +```bash +python3 -m tools pkg build rpm \ + --relenv-version=0.22.4 \ + --python-version=3.10.19 \ + --onedir="salt-${SALT_VERSION}-onedir-linux-x86_64.tar.xz" +``` + +The RPMs will be generated in `~/rpmbuild/RPMS/x86_64/`. + +### 6. Retrieve Artifacts + +Before exiting the container, copy the RPMs to the mounted volume: + +```bash +mkdir -p artifacts/rpm +cp -r ~/rpmbuild/RPMS/x86_64/*.rpm artifacts/rpm/ +``` + +## Building DEBs (Linux) + +The process is similar to RPMs but uses the `deb` command and a Debian container. + +1. Use `ghcr.io/saltstack/salt-ci-containers/testing:debian-11` (or newer). +2. Follow steps 2-4 above (Source Tarball & Onedir). +3. Run `python3 -m tools pkg build deb ...` instead of `rpm`. + +## Building Windows/macOS Packages + +Windows and macOS packages are typically built on their respective host OSs in CI, not in Docker containers. Refer to `.github/workflows/build-packages.yml` for the specific steps and requirements (signing certificates, etc.). diff --git a/agents/mcp/launch-salt-test.sh b/agents/mcp/launch-salt-test.sh new file mode 100755 index 000000000000..be6fabf4337f --- /dev/null +++ b/agents/mcp/launch-salt-test.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Launcher for the salt-test MCP server that works across checkouts/worktrees. + +# Get the directory where this script is located +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +# Project root is two levels up from agents/mcp/ +PROJECT_ROOT="$( cd "$SCRIPT_DIR/../.." && pwd )" + +# Ensure we're in the project root +cd "$PROJECT_ROOT" + +# Check for the expected virtualenv +VENV_PYTHON="$PROJECT_ROOT/venv310/bin/python" +if [ ! -f "$VENV_PYTHON" ]; then + # Fallback to system python if venv310 isn't found + VENV_PYTHON=$(which python3) +fi + +# Set PYTHONPATH to the project root so we can import the agents package +export PYTHONPATH="$PROJECT_ROOT" + +# Execute the MCP server +exec "$VENV_PYTHON" -m agents.mcp.salt_test.server "$@" diff --git a/agents/mcp/mcp-config.json b/agents/mcp/mcp-config.json index acea9adca573..2591cf7f266c 100644 --- a/agents/mcp/mcp-config.json +++ b/agents/mcp/mcp-config.json @@ -1,12 +1,8 @@ { "mcpServers": { "salt-test": { - "command": "python3", - "args": ["-m", "agents.mcp.salt_test.server"], - "cwd": "/home/dan/src/wt/agents", - "env": { - "PYTHONPATH": "/home/dan/src/wt/agents" - } + "command": "bash", + "args": ["agents/mcp/launch-salt-test.sh"] } } } diff --git a/agents/mcp/salt_test/server.py b/agents/mcp/salt_test/server.py index 7eed0f2f71dc..a5f7ac6862a3 100644 --- a/agents/mcp/salt_test/server.py +++ b/agents/mcp/salt_test/server.py @@ -22,9 +22,9 @@ from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import TextContent, Tool -except ImportError: +except ImportError as e: print( - "Error: MCP SDK not installed. Install with: pip install mcp", + f"Error: MCP SDK not installed. Install with: pip install mcp. Error: {e}", file=sys.stderr, ) sys.exit(1) @@ -40,7 +40,7 @@ def run_tool_command(*args, **kwargs) -> dict[str, Any]: """ Run a tools command and return the result. """ - cmd = [sys.executable, "-m", "tools"] + list(args) + cmd = [sys.executable, "-m", "ptscripts"] + list(args) try: result = subprocess.run( @@ -300,6 +300,77 @@ async def list_tools() -> list[Tool]: "properties": {}, }, ), + # Package building + Tool( + name="ci_build_pkg", + description="Build Salt packages (RPM/Deb) using CI containers", + inputSchema={ + "type": "object", + "properties": { + "pkg_type": { + "type": "string", + "description": "Package type to build", + "enum": ["rpm", "deb", "windows", "macos"], + }, + "distro": { + "type": "string", + "description": "Distribution to build on (e.g., 'debian-13', 'rockylinux-9'). Defaults to debian-13 for deb, rockylinux-9 for rpm.", + }, + "platform": { + "type": "string", + "description": "Target platform (e.g., 'linux', 'windows', 'macos')", + "enum": ["linux", "windows", "macos"], + }, + "arch": { + "type": "string", + "description": "Target architecture (e.g., 'x86_64', 'aarch64')", + "enum": ["x86_64", "aarch64", "amd64"], + }, + "relenv_version": { + "type": "string", + "description": "Relenv version to use (e.g., '0.22.4')", + }, + "python_version": { + "type": "string", + "description": "Python version to use (e.g., '3.10.19')", + }, + }, + "required": ["pkg_type"], + }, + ), + Tool( + name="ci_test_pkg", + description="Run package tests (upgrade/install) in a CI container", + inputSchema={ + "type": "object", + "properties": { + "pkg_type": { + "type": "string", + "description": "Package type (deb/rpm)", + "enum": ["deb", "rpm"], + }, + "distro": { + "type": "string", + "description": "Distribution to test on (e.g., 'debian-13')", + }, + "test_type": { + "type": "string", + "description": "Test type (upgrade, install)", + "default": "upgrade", + }, + "prev_version": { + "type": "string", + "description": "Previous version for upgrade tests (e.g., '3006.22')", + }, + "extra_args": { + "type": "array", + "items": {"type": "string"}, + "description": "Additional arguments for nox", + }, + }, + "required": ["pkg_type"], + }, + ), ] @@ -393,6 +464,328 @@ async def call_tool(name: str, arguments: Any) -> list[TextContent]: elif name == "ci_list_platforms": cmd_args = ["ts", "container-test", "list-platforms"] + elif name == "ci_build_pkg": + pkg_type = arguments["pkg_type"] + distro = arguments.get("distro") + + # Determine image + if not distro: + if pkg_type == "deb": + distro = "debian-13" + elif pkg_type == "rpm": + distro = "rockylinux-9" + else: + return [ + TextContent( + type="text", + text="Error: distro must be specified for non-linux builds or rely on defaults.", + ) + ] + + # Map distro to image (simplified mapping, ideally import from tools) + image_map = { + "debian-13": "ghcr.io/saltstack/salt-ci-containers/testing:debian-13", + "debian-12": "ghcr.io/saltstack/salt-ci-containers/testing:debian-12", + "debian-11": "ghcr.io/saltstack/salt-ci-containers/testing:debian-11", + "rockylinux-9": "ghcr.io/saltstack/salt-ci-containers/testing:rockylinux-9", + "rockylinux-8": "ghcr.io/saltstack/salt-ci-containers/testing:rockylinux-8", + "amazonlinux-2": "ghcr.io/saltstack/salt-ci-containers/testing:amazonlinux-2", + "amazonlinux-2023": "ghcr.io/saltstack/salt-ci-containers/testing:amazonlinux-2023", + "ubuntu-22.04": "ghcr.io/saltstack/salt-ci-containers/testing:ubuntu-22.04", + "ubuntu-20.04": "ghcr.io/saltstack/salt-ci-containers/testing:ubuntu-20.04", + } + + image = image_map.get(distro) + if not image: + return [ + TextContent( + type="text", + text=f"Error: Unknown distro '{distro}'. Supported: {', '.join(image_map.keys())}", + ) + ] + + container_name = f"salt-build-{pkg_type}-{distro}" + + # 1. Create container + create_cmd = ["container", "create", image, "--name", container_name] + logger.info(f"Creating container: {' '.join(create_cmd)}") + + # Remove existing container first + subprocess.run( + ["docker", "rm", "-f", container_name], + check=False, + stdout=sys.stderr, + stderr=sys.stderr, + ) + + # Use tools module to create container correctly with all mounts + # We use run_tool_command to ensure it runs with the correct python environment and cwd + create_result = run_tool_command(*create_cmd) + + if not create_result["success"]: + return [ + TextContent( + type="text", + text=f"Failed to create container:\n{create_result['stderr']}", + ) + ] + + # 2. Start container + start_cmd = ["docker", "start", container_name] + subprocess.run( + start_cmd, check=False, stdout=sys.stderr, stderr=sys.stderr + ) # Ensure it's started + + # Disable IPv6 to prevent pip hangs + subprocess.run( + [ + "docker", + "exec", + container_name, + "sysctl", + "-w", + "net.ipv6.conf.all.disable_ipv6=1", + ], + check=False, + stdout=sys.stderr, + stderr=sys.stderr, + ) + + # 3. Install dependencies + if "debian" in distro or "ubuntu" in distro: + # Install dependencies exactly as in .github/workflows/build-packages.yml + # Plus git, rsync, procps, and basic build tools + # Explicitly avoiding libzmq3-dev as per CI/CD + # Also install tools requirements for ptscripts + install_cmd = [ + "docker", + "exec", + container_name, + "bash", + "-c", + "apt-get update && apt-get install -y python3.13-venv devscripts patchelf git rsync procps build-essential debhelper dh-python python3-all python3-setuptools python3-pip bash-completion && python3 -m pip install -r requirements/static/ci/py3.13/tools.txt --break-system-packages --ignore-installed", + ] + subprocess.run( + install_cmd, check=False, stdout=sys.stderr, stderr=sys.stderr + ) + elif "rocky" in distro or "amazon" in distro or "fedora" in distro: + install_cmd = [ + "docker", + "exec", + container_name, + "dnf", + "install", + "-y", + "rpm-build", + "rpm-sign", + "python3-devel", + "python3-pip", + "python3-setuptools", + "git", + "bash-completion", + "make", + "gcc", + "gcc-c++", + ] + subprocess.run( + install_cmd, check=False, stdout=sys.stderr, stderr=sys.stderr + ) + # Install tools requirements (assuming python3 is available) + subprocess.run( + [ + "docker", + "exec", + container_name, + "python3", + "-m", + "pip", + "install", + "-r", + "requirements/static/ci/py3.10/tools.txt", + ], + check=False, + stdout=sys.stderr, + stderr=sys.stderr, + ) + + # 4. Run build + # We need to ensure environment variables are passed correctly for relenv + # SKIP_REQUIREMENTS_INSTALL=1 might be used by some tools, PIP_BREAK_SYSTEM_PACKAGES=1 allows pip to install system packages + env_vars = [ + "-e", + "SKIP_REQUIREMENTS_INSTALL=1", + "-e", + "PIP_BREAK_SYSTEM_PACKAGES=1", + ] + if arguments.get("relenv_version"): + env_vars.extend( + ["-e", f"SALT_RELENV_VERSION={arguments['relenv_version']}"] + ) + if arguments.get("python_version"): + env_vars.extend( + ["-e", f"SALT_PYTHON_VERSION={arguments['python_version']}"] + ) + if arguments.get("arch"): + env_vars.extend(["-e", f"SALT_PACKAGE_ARCH={arguments['arch']}"]) + + # Construct the full build command + # Note: We are running 'python3 -m ptscripts pkg build' INSIDE the container + build_cmd = ( + ["docker", "exec"] + + env_vars + + [ + container_name, + "python3", + "-m", + "ptscripts", + "pkg", + "build", + pkg_type, + ] + ) + + if arguments.get("platform"): + build_cmd.extend(["--platform", arguments["platform"]]) + if arguments.get("arch"): + build_cmd.extend(["--arch", arguments["arch"]]) + if arguments.get("relenv_version"): + build_cmd.extend(["--relenv-version", arguments["relenv_version"]]) + if arguments.get("python_version"): + build_cmd.extend(["--python-version", arguments["python_version"]]) + + logger.info(f"Running build in container: {build_cmd}") + + # Capture output + result = subprocess.run( + build_cmd, + capture_output=True, + text=True, + timeout=3600, # 1 hour for build + ) + + response = "" + if result.returncode == 0: + response = f"Build successful in container {container_name}!\n\nstdout:\n{result.stdout}" + else: + response = f"Build failed in container {container_name} (exit code {result.returncode})\n\nstdout:\n{result.stdout}\n\nstderr:\n{result.stderr}" + + return [TextContent(type="text", text=response)] + + elif name == "ci_test_pkg": + pkg_type = arguments["pkg_type"] + distro = arguments.get("distro") + test_type = arguments.get("test_type", "upgrade") + prev_version = arguments.get("prev_version") + + if not distro: + if pkg_type == "deb": + distro = "debian-13" + elif pkg_type == "rpm": + distro = "rockylinux-9" + + image_map = { + "debian-13": "ghcr.io/saltstack/salt-ci-containers/testing:debian-13", + "debian-12": "ghcr.io/saltstack/salt-ci-containers/testing:debian-12", + "rockylinux-9": "ghcr.io/saltstack/salt-ci-containers/testing:rockylinux-9", + } + image = image_map.get(distro) + if not image: + return [ + TextContent(type="text", text=f"Error: Unknown distro '{distro}'") + ] + + container_name = f"salt-test-{pkg_type}-{distro}" + + # 1. Create container + create_cmd = ["container", "create", image, "--name", container_name] + subprocess.run( + ["docker", "rm", "-f", container_name], + check=False, + stdout=sys.stderr, + stderr=sys.stderr, + ) + create_result = run_tool_command(*create_cmd) + if not create_result["success"]: + return [ + TextContent( + type="text", + text=f"Failed to create container:\n{create_result['stderr']}", + ) + ] + + # 2. Start container + subprocess.run( + ["docker", "start", container_name], + check=False, + stdout=sys.stderr, + stderr=sys.stderr, + ) + + # 3. Setup environment (nox, ipv6 fix) + setup_cmds = [ + ["sysctl", "-w", "net.ipv6.conf.all.disable_ipv6=1"], + [ + "python3", + "-m", + "pip", + "install", + "nox", + "--break-system-packages", + ], # Debian 12+ needs this or venv + ] + + for cmd in setup_cmds: + subprocess.run( + ["docker", "exec", container_name] + cmd, + check=False, + stdout=sys.stderr, + stderr=sys.stderr, + ) + + # 4. Copy artifacts (if needed) + copy_script = f""" + mkdir -p /salt/artifacts/pkg + if [ -d /salt/artifacts/{pkg_type} ]; then + cp -r /salt/artifacts/{pkg_type}/* /salt/artifacts/pkg/ + fi + ls -l /salt/artifacts/pkg/ + """ + subprocess.run( + ["docker", "exec", container_name, "bash", "-c", copy_script], + check=False, + stdout=sys.stderr, + stderr=sys.stderr, + ) + + # 5. Run nox + nox_cmd = ["nox", "-e", "ci-test-onedir-pkgs", "--", test_type] + if prev_version: + nox_cmd.append(f"--prev-version={prev_version}") + + if arguments.get("extra_args"): + nox_cmd.extend(arguments["extra_args"]) + + full_cmd = [ + "docker", + "exec", + "-e", + "FORCE_COLOR=1", + container_name, + ] + nox_cmd + + logger.info(f"Running test in container: {full_cmd}") + result = subprocess.run( + full_cmd, capture_output=True, text=True, timeout=3600 + ) + + response = "" + if result.returncode == 0: + response = f"Tests passed in container {container_name}!\n\nstdout:\n{result.stdout}" + else: + response = f"Tests failed in container {container_name} (exit code {result.returncode})\n\nstdout:\n{result.stdout}\n\nstderr:\n{result.stderr}" + + return [TextContent(type="text", text=response)] + else: return [TextContent(type="text", text=f"Unknown tool: {name}")] diff --git a/changelog/66449.fixed.md b/changelog/66449.fixed.md new file mode 100644 index 000000000000..99307c1a2d4c --- /dev/null +++ b/changelog/66449.fixed.md @@ -0,0 +1,4 @@ +Fixed inotify file descriptor leak in beacons. When beacons are refreshed +(e.g. during module refresh or pillar refresh), the old beacon modules are now +properly closed before creating new ones, preventing exhaustion of the inotify +instance limit. diff --git a/changelog/66929.fixed.md b/changelog/66929.fixed.md new file mode 100644 index 000000000000..469ac4530dea --- /dev/null +++ b/changelog/66929.fixed.md @@ -0,0 +1 @@ +Fixed x509_v2.certificate_managed state fails if another state.apply is queued diff --git a/changelog/66942.fixed.md b/changelog/66942.fixed.md new file mode 100644 index 000000000000..dfa70c034738 --- /dev/null +++ b/changelog/66942.fixed.md @@ -0,0 +1 @@ +Fixed x509_v2 private_key_managed failing on Windows due to default `mode` argument diff --git a/changelog/68597.fixed.md b/changelog/68597.fixed.md new file mode 100644 index 000000000000..e80c997ca08d --- /dev/null +++ b/changelog/68597.fixed.md @@ -0,0 +1 @@ +Decouple the pub timeout from opts timeout. Programatic useage of client now has a 30 second timeout. diff --git a/changelog/68684.fixed.md b/changelog/68684.fixed.md new file mode 100644 index 000000000000..f6458f120932 --- /dev/null +++ b/changelog/68684.fixed.md @@ -0,0 +1 @@ +Fix salt-call and salt-pip to honor configured user for privilege dropping diff --git a/changelog/68781.fixed.md b/changelog/68781.fixed.md new file mode 100644 index 000000000000..9496e6f9d909 --- /dev/null +++ b/changelog/68781.fixed.md @@ -0,0 +1,2 @@ +- Prevented generation of spurious ppbt toolchain in /root/.local on RPM upgrade +- Stale pycache files now get cleaned up on RPM upgrade diff --git a/changelog/68793.fixed.md b/changelog/68793.fixed.md new file mode 100644 index 000000000000..63bdb8e324a6 --- /dev/null +++ b/changelog/68793.fixed.md @@ -0,0 +1 @@ +Ensure Salt file and directory ownership is correctly detected and preserved when upgrading RPM and Debian packages, particularly when running Salt as a non-root user. diff --git a/changelog/68803.fixed.md b/changelog/68803.fixed.md new file mode 100644 index 000000000000..48bbbfabcb58 --- /dev/null +++ b/changelog/68803.fixed.md @@ -0,0 +1 @@ +Upgrade relenv to 0.22.5 which pin's openssl to an LTS version (3.5.x) diff --git a/changelog/68820.fixed.md b/changelog/68820.fixed.md new file mode 100644 index 000000000000..42ffc43f1364 --- /dev/null +++ b/changelog/68820.fixed.md @@ -0,0 +1 @@ +Patch the vendored tornado version to account for CVE patches that have been applied. diff --git a/changelog/68828.fixed.md b/changelog/68828.fixed.md new file mode 100644 index 000000000000..b0ad49706294 --- /dev/null +++ b/changelog/68828.fixed.md @@ -0,0 +1 @@ +Made x509_v2 certificate_managed respect `copypath` and `prepend_cn` parameters diff --git a/changelog/68832.fixed.md b/changelog/68832.fixed.md new file mode 100644 index 000000000000..230ed379017e --- /dev/null +++ b/changelog/68832.fixed.md @@ -0,0 +1,3 @@ +Upgrade pyopenssl to >= 26.0.0 + - CVE-2026-27459 + - CVE-2026-27448 diff --git a/changelog/68853.fixed.md b/changelog/68853.fixed.md new file mode 100644 index 000000000000..6a46e2ace367 --- /dev/null +++ b/changelog/68853.fixed.md @@ -0,0 +1 @@ +Patch tornado for BDSA-2025-60810 diff --git a/changelog/68854.fixed.md b/changelog/68854.fixed.md new file mode 100644 index 000000000000..f75b50ef3a4c --- /dev/null +++ b/changelog/68854.fixed.md @@ -0,0 +1 @@ +Patch tornado for BDSA-2026-3867 diff --git a/changelog/68858.fixed.md b/changelog/68858.fixed.md new file mode 100644 index 000000000000..1abf62923488 --- /dev/null +++ b/changelog/68858.fixed.md @@ -0,0 +1 @@ +Fixed source package builds (DEB/RPM) failing with ``LookupError: hatchling is already being built`` by adding ``hatchling`` to the ``--only-binary`` allow-list so pip uses its universal wheel instead of attempting a circular source build. diff --git a/changelog/68884.fixed.md b/changelog/68884.fixed.md new file mode 100644 index 000000000000..5117e953e34e --- /dev/null +++ b/changelog/68884.fixed.md @@ -0,0 +1,15 @@ +Upgrade relenv to 0.22.7 + +* Upgread Python Versions 3.12.13, 3.11.15, 3.10.20 + - CVE-2024-6923: Header injection in email module + - CVE-2026-24515, CVE-2026-25210, CVE-2025-59375: XML memory amplification and libexpat vulnerabilities +* SQLite 3.51.3.0 + - CVE-2025-70873: Heap memory disclosure in zipfile extension + - CVE-2025-7709: Integer overflow in FTS5 extension + - Fixes WAL-reset bug preventing database corruption +* XZ Utils 5.8.3 + - CVE-2026-34743: Buffer overflow in lzma_index_append() +* Expat 2.7.5 + - CVE-2026-32776: NULL pointer dereference in external parameter entities + - CVE-2026-32777: Infinite loop in entityValueProcessor + - CVE-2026-32778: NULL pointer dereference during OOM recovery diff --git a/cicd/shared-gh-workflows-context.yml b/cicd/shared-gh-workflows-context.yml index b25c15b67fed..123143fd1563 100644 --- a/cicd/shared-gh-workflows-context.yml +++ b/cicd/shared-gh-workflows-context.yml @@ -1,6 +1,6 @@ nox_version: "2022.8.7" -python_version: "3.10.19" -relenv_version: "0.22.4" +python_version: "3.10.20" +relenv_version: "0.22.7" release_branches: - "3006.x" - "3007.x" diff --git a/pkg/debian/salt-api.postinst b/pkg/debian/salt-api.postinst index 88976f2f775b..8bb03451c446 100644 --- a/pkg/debian/salt-api.postinst +++ b/pkg/debian/salt-api.postinst @@ -26,7 +26,11 @@ case "$1" in touch /var/log/salt/api chmod 640 /var/log/salt/api fi - chown $RET:$RET /var/log/salt/api + # Only set ownership on fresh install, preserve on upgrade + if [ -z "$2" ]; then + chown $RET:$RET /var/log/salt/api + chown -R $RET:$RET /opt/saltstack/salt || true + fi fi fi ;; diff --git a/pkg/debian/salt-cloud.postinst b/pkg/debian/salt-cloud.postinst index 9dab433d4991..b5b7257c9fc4 100644 --- a/pkg/debian/salt-cloud.postinst +++ b/pkg/debian/salt-cloud.postinst @@ -24,7 +24,11 @@ case "$1" in then if [ "$RET" != "root" ]; then PY_VER=$(/opt/saltstack/salt/bin/python3 -c "import sys; sys.stdout.write('{}.{}'.format(*sys.version_info)); sys.stdout.flush;") - chown -R $RET:$RET /etc/salt/cloud.deploy.d /opt/saltstack/salt/lib/python${PY_VER}/site-packages/salt/cloud/deploy + # Only set ownership on fresh install, preserve on upgrade + if [ -z "$2" ]; then + chown -R $RET:$RET /etc/salt/cloud.deploy.d /opt/saltstack/salt/lib/python${PY_VER}/site-packages/salt/cloud/deploy + chown -R $RET:$RET /opt/saltstack/salt || true + fi fi fi ;; diff --git a/pkg/debian/salt-common.preinst b/pkg/debian/salt-common.preinst index 76556fdf7a26..787c5791bf91 100644 --- a/pkg/debian/salt-common.preinst +++ b/pkg/debian/salt-common.preinst @@ -43,7 +43,6 @@ case "$1" in # 4. adjust passwd entry usermod -c "$SALT_NAME" \ -d $SALT_HOME \ - -s $SALT_SHELL \ -g $SALT_GROUP \ $SALT_USER diff --git a/pkg/debian/salt-master.postinst b/pkg/debian/salt-master.postinst index 53903fcb6131..86a76a6ade9f 100644 --- a/pkg/debian/salt-master.postinst +++ b/pkg/debian/salt-master.postinst @@ -31,10 +31,14 @@ case "$1" in touch /var/log/salt/key chmod 640 /var/log/salt/key fi - chown -R $RET:$RET /etc/salt/pki/master /etc/salt/master.d \ - /var/log/salt/master /var/log/salt/key \ - /var/cache/salt/master /var/run/salt/master \ - || true + # Only set ownership on fresh install, preserve on upgrade + if [ -z "$2" ]; then + chown -R $RET:$RET /etc/salt/pki/master /etc/salt/master.d \ + /var/log/salt/master /var/log/salt/key \ + /var/cache/salt/master /var/run/salt/master \ + /opt/saltstack/salt \ + || true + fi fi fi ;; diff --git a/pkg/debian/salt-minion.postinst b/pkg/debian/salt-minion.postinst index 559ac89bcff6..0edf521738cd 100644 --- a/pkg/debian/salt-minion.postinst +++ b/pkg/debian/salt-minion.postinst @@ -35,6 +35,7 @@ case "$1" in chown -R $RET:$RET /etc/salt/pki/minion /etc/salt/minion.d \ /var/log/salt/minion /var/cache/salt/minion \ /var/run/salt/minion \ + /opt/saltstack/salt \ || true fi fi diff --git a/pkg/debian/salt-minion.preinst b/pkg/debian/salt-minion.preinst index 30df7b58bc30..b5727e052b4e 100644 --- a/pkg/debian/salt-minion.preinst +++ b/pkg/debian/salt-minion.preinst @@ -31,6 +31,12 @@ case "$1" in then CUR_USER=$(ls -dl /run/salt-minion.pid | cut -d ' ' -f 3) CUR_GROUP=$(ls -dl /run/salt-minion.pid | cut -d ' ' -f 4) + elif [ -d /etc/salt/pki/minion ]; then + CUR_USER=$(ls -dl /etc/salt/pki/minion | cut -d ' ' -f 3) + CUR_GROUP=$(ls -dl /etc/salt/pki/minion | cut -d ' ' -f 4) + elif [ -d /var/cache/salt/minion ]; then + CUR_USER=$(ls -dl /var/cache/salt/minion | cut -d ' ' -f 3) + CUR_GROUP=$(ls -dl /var/cache/salt/minion | cut -d ' ' -f 4) else CUR_USER=$SALT_USER CUR_GROUP=$SALT_GROUP diff --git a/pkg/debian/salt-syndic.postinst b/pkg/debian/salt-syndic.postinst index 61412ea077ae..ed873caa6347 100644 --- a/pkg/debian/salt-syndic.postinst +++ b/pkg/debian/salt-syndic.postinst @@ -27,7 +27,11 @@ case "$1" in touch /var/log/salt/syndic chmod 640 /var/log/salt/syndic fi - chown $RET:$RET /var/log/salt/syndic + # Only set ownership on fresh install, preserve on upgrade + if [ -z "$2" ]; then + chown $RET:$RET /var/log/salt/syndic + chown -R $RET:$RET /opt/saltstack/salt || true + fi fi fi ;; diff --git a/pkg/patches/pip-urllib3/_version.py.patch b/pkg/patches/pip-urllib3/_version.py.patch new file mode 100644 index 000000000000..6eca20d59475 --- /dev/null +++ b/pkg/patches/pip-urllib3/_version.py.patch @@ -0,0 +1,31 @@ +--- a/pip/_vendor/urllib3/_version.py ++++ b/pip/_vendor/urllib3/_version.py +@@ -1,2 +1,26 @@ +-# This file is protected via CODEOWNERS +-__version__ = "1.26.20" ++# This file is a Salt-maintained security patch of pip's vendored urllib3. ++# ++# The underlying code is urllib3 1.26.20 (the version vendored by pip 25.2) ++# with the following CVE fixes backported from upstream urllib3 2.6.3: ++# ++# CVE-2025-66418 (GHSA-gm62-xv2j-4w53): Unbounded Content-Encoding ++# decompression chain -- MultiDecoder now enforces a 5-link limit. ++# Upstream fix: urllib3 2.6.0 (commit 24d7b67). ++# ++# CVE-2026-21441 (GHSA-38jv-5279-wg99): drain_conn unnecessarily ++# decompressed the full body of HTTP redirect responses, creating a ++# decompression-bomb vector. Fixed by adding _has_decoded_content ++# tracking and only decoding in drain_conn when decoding was already ++# in progress. ++# Upstream fix: urllib3 2.6.3 (commit 8864ac4). ++# ++# CVE-2025-66471 (GHSA-2xpw-w6gg-jr37): Decompression bomb in the ++# streaming API via max_length parameter. NOT backported -- requires a ++# full 2.x streaming infrastructure refactor. Ubuntu did not backport ++# this to 1.26.x either. pip maintainers confirmed pip is not ++# affected because all pip network calls use decode_content=False. ++# ++# The version string "2.6.3" reflects the highest upstream release from ++# which fixes have been backported. The underlying API remains urllib3 ++# 1.26.x -- this is NOT a port to urllib3 2.x. ++__version__ = "2.6.3" diff --git a/pkg/patches/pip-urllib3/response.py.patch b/pkg/patches/pip-urllib3/response.py.patch new file mode 100644 index 000000000000..4bd47c69c053 --- /dev/null +++ b/pkg/patches/pip-urllib3/response.py.patch @@ -0,0 +1,64 @@ +--- a/pip/_vendor/urllib3/response.py ++++ b/pip/_vendor/urllib3/response.py +@@ -129,8 +129,18 @@ + they were applied. + """ + ++ # Maximum allowed number of chained HTTP encodings in the ++ # Content-Encoding header. CVE-2025-66418 (GHSA-gm62-xv2j-4w53). ++ max_decode_links = 5 ++ + def __init__(self, modes): +- self._decoders = [_get_decoder(m.strip()) for m in modes.split(",")] ++ encodings = [m.strip() for m in modes.split(",")] ++ if len(encodings) > self.max_decode_links: ++ raise DecodeError( ++ "Too many content encodings in the chain: " ++ "%d > %d" % (len(encodings), self.max_decode_links) ++ ) ++ self._decoders = [_get_decoder(e) for e in encodings] + + def flush(self): + return self._decoders[0].flush() +@@ -222,6 +232,9 @@ + self.reason = reason + self.strict = strict + self.decode_content = decode_content ++ # CVE-2026-21441: tracks whether content decoding has been ++ # initiated so drain_conn can skip decompression on redirects. ++ self._has_decoded_content = False + self.retries = retries + self.enforce_content_length = enforce_content_length + self.auto_close = auto_close +@@ -286,7 +299,11 @@ + Unread data in the HTTPResponse connection blocks the connection from being released back to the pool. + """ + try: +- self.read() ++ self.read( ++ # CVE-2026-21441: Do not spend resources decoding the ++ # content unless decoding has already been initiated. ++ decode_content=self._has_decoded_content, ++ ) + except (HTTPError, SocketError, BaseSSLError, HTTPException): + pass + +@@ -394,11 +411,18 @@ + Decode the data passed in and potentially flush the decoder. + """ + if not decode_content: ++ # CVE-2026-21441: guard against toggling after decoding started. ++ if self._has_decoded_content: ++ raise RuntimeError( ++ "Calling read(decode_content=False) is not supported after " ++ "read(decode_content=True) was called." ++ ) + return data + + try: + if self._decoder: + data = self._decoder.decompress(data) ++ self._has_decoded_content = True + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( diff --git a/pkg/rpm/salt.spec b/pkg/rpm/salt.spec index d5d8454567f1..497fba2355a1 100644 --- a/pkg/rpm/salt.spec +++ b/pkg/rpm/salt.spec @@ -472,12 +472,61 @@ if [ $1 -gt 1 ] ; then fi %pre minion + +# Source setup configuration if present +if [ -f /etc/sysconfig/salt-minion-setup ]; then + . /etc/sysconfig/salt-minion-setup +fi + if [ $1 -gt 1 ] ; then - # Reset permissions to match previous installs - performing upgrade - _MN_LCUR_USER=$(ls -dl /run/salt/minion | cut -d ' ' -f 3) - _MN_LCUR_GROUP=$(ls -dl /run/salt/minion | cut -d ' ' -f 4) - %global _MN_CUR_USER %{_MN_LCUR_USER} - %global _MN_CUR_GROUP %{_MN_LCUR_GROUP} + # Upgrade: detect and save current ownership + /bin/systemctl stop salt-minion.service >/dev/null 2>&1 || : + + # Check if minion config specifies a non-root user + MINION_USER="" + if [ -f "/etc/salt/minion" ] || [ -d "/etc/salt/minion.d" ]; then + # Try to get user from main config + if [ -f "/etc/salt/minion" ]; then + MINION_USER=$(grep -E "^user:" /etc/salt/minion | cut -d ':' -f 2 | tr -d ' ') + fi + # Try to get user from minion.d configs + if [ -z "$MINION_USER" ] && [ -d "/etc/salt/minion.d" ]; then + MINION_USER=$(grep -r -h -E "^user:" /etc/salt/minion.d/ | head -1 | cut -d ':' -f 2 | tr -d ' ' || true) + fi + fi + + if [ -n "$MINION_USER" ] && [ "$MINION_USER" != "root" ]; then + echo "$MINION_USER:$MINION_USER" > /tmp/.salt-minion-upgrade-ownership + %global _MN_CUR_USER %{MINION_USER} + %global _MN_CUR_GROUP %{MINION_USER} + else + # Fallback to checking multiple directories for ownership + if [ -d "/run/salt/minion" ]; then + _MN_LCUR_USER=$(ls -dl /run/salt/minion | cut -d ' ' -f 3) + _MN_LCUR_GROUP=$(ls -dl /run/salt/minion | cut -d ' ' -f 4) + if [ "$_MN_LCUR_USER" != "root" ]; then + echo "$_MN_LCUR_USER:$_MN_LCUR_GROUP" > /tmp/.salt-minion-upgrade-ownership + %global _MN_CUR_USER %{_MN_LCUR_USER} + %global _MN_CUR_GROUP %{_MN_LCUR_GROUP} + fi + elif [ -d "/etc/salt/pki/minion" ]; then + _MN_LCUR_USER=$(ls -dl /etc/salt/pki/minion | cut -d ' ' -f 3) + _MN_LCUR_GROUP=$(ls -dl /etc/salt/pki/minion | cut -d ' ' -f 4) + if [ "$_MN_LCUR_USER" != "root" ]; then + echo "$_MN_LCUR_USER:$_MN_LCUR_GROUP" > /tmp/.salt-minion-upgrade-ownership + %global _MN_CUR_USER %{_MN_LCUR_USER} + %global _MN_CUR_GROUP %{_MN_LCUR_GROUP} + fi + elif [ -d "/var/cache/salt/minion" ]; then + _MN_LCUR_USER=$(ls -dl /var/cache/salt/minion | cut -d ' ' -f 3) + _MN_LCUR_GROUP=$(ls -dl /var/cache/salt/minion | cut -d ' ' -f 4) + if [ "$_MN_LCUR_USER" != "root" ]; then + echo "$_MN_LCUR_USER:$_MN_LCUR_GROUP" > /tmp/.salt-minion-upgrade-ownership + %global _MN_CUR_USER %{_MN_LCUR_USER} + %global _MN_CUR_GROUP %{_MN_LCUR_GROUP} + fi + fi + fi fi @@ -530,7 +579,6 @@ fi %post ln -s -f /opt/saltstack/salt/spm %{_bindir}/spm ln -s -f /opt/saltstack/salt/salt-pip %{_bindir}/salt-pip -/opt/saltstack/salt/bin/python3 -m compileall -qq /opt/saltstack/salt/lib %post cloud @@ -577,6 +625,11 @@ else fi %post minion +# Source setup configuration if present +if [ -f /etc/sysconfig/salt-minion-setup ]; then + . /etc/sysconfig/salt-minion-setup +fi + ln -s -f /opt/saltstack/salt/salt-minion %{_bindir}/salt-minion ln -s -f /opt/saltstack/salt/salt-call %{_bindir}/salt-call ln -s -f /opt/saltstack/salt/salt-proxy %{_bindir}/salt-proxy @@ -596,6 +649,36 @@ fi # %%systemd_post salt-minion.service if [ $1 -gt 1 ] ; then # Upgrade + # Restore ownership before restarting service + if [ -f "/tmp/.salt-minion-upgrade-ownership" ]; then + OWNERSHIP=$(cat /tmp/.salt-minion-upgrade-ownership) + USER_GROUP=${OWNERSHIP%:*} + chown $OWNERSHIP /etc/salt + chown $OWNERSHIP /etc/salt/pki + chown $OWNERSHIP /var/run/salt + chown -R $OWNERSHIP /etc/salt/pki/minion + chown -R $OWNERSHIP /etc/salt/minion.d + chown -R $OWNERSHIP /var/cache/salt/minion + chown -R $OWNERSHIP /var/run/salt/minion + chown $OWNERSHIP /var/log/salt/minion + # Also restore parent directories that are commonly owned by salt user + chown $OWNERSHIP /var/log/salt + chown -R $OWNERSHIP /var/cache/salt + + # Pre-create proc directory to ensure ownership (fixes PermissionError) + mkdir -p /var/cache/salt/minion/proc + chown $OWNERSHIP /var/cache/salt/minion/proc + chmod 750 /var/cache/salt/minion/proc + + # Restore ownership of the main installation directory for salt-pip access + chown -R $OWNERSHIP /opt/saltstack/salt + # Also restore ownership of extras directory if it exists + # Use find to handle wildcard expansion safely in scriptlet + find /opt/saltstack/salt -maxdepth 1 -name "extras-*" -exec chown -R $OWNERSHIP {} + + + # Create marker file to tell %posttrans this was an upgrade + touch /tmp/.salt-minion-upgrade-ownership.done + fi /bin/systemctl try-restart salt-minion.service >/dev/null 2>&1 || : else # Initial installation @@ -617,6 +700,13 @@ else fi +%posttrans +# (Re)generate pycache in posttrans, so we're sure any old libraries have been uninstalled. +find /opt/saltstack/salt/lib -type f -name '*.pyc' -delete +find /opt/saltstack/salt/lib -type d -name __pycache__ -empty -delete +/opt/saltstack/salt/bin/python3 -m compileall -qq /opt/saltstack/salt/lib + + %posttrans cloud PY_VER=$(/opt/saltstack/salt/bin/python3 -c "import sys; sys.stdout.write('{}.{}'.format(*sys.version_info)); sys.stdout.flush();") if [ ! -e "/var/log/salt/cloud" ]; then @@ -624,56 +714,56 @@ if [ ! -e "/var/log/salt/cloud" ]; then chmod 640 /var/log/salt/cloud fi if [ $1 -gt 1 ] ; then - # Reset permissions to match previous installs - performing upgrade - chown -R %{_MS_CUR_USER}:%{_MS_CUR_GROUP} /etc/salt/cloud.deploy.d /var/log/salt/cloud /opt/saltstack/salt/lib/python${PY_VER}/site-packages/salt/cloud/deploy + # Upgrade: preserve existing ownership, don't reset to defaults + : else - chown -R %{_SALT_USER}:%{_SALT_GROUP} /etc/salt/cloud.deploy.d /var/log/salt/cloud /opt/saltstack/salt/lib/python${PY_VER}/site-packages/salt/cloud/deploy -fi - + chown -R %{_SALT_USER}:%{_SALT_GROUP} /etc/salt/cloud.deploy.d /var/log/salt/cloud /opt/saltstack/salt/lib/python${PY_VER}/site-packages/salt/cloud/deploy /opt/saltstack/salt + fi -%posttrans master -if [ ! -e "/var/log/salt/master" ]; then - touch /var/log/salt/master - chmod 640 /var/log/salt/master -fi -if [ ! -e "/var/log/salt/key" ]; then - touch /var/log/salt/key - chmod 640 /var/log/salt/key -fi -if [ $1 -gt 1 ] ; then - # Reset permissions to match previous installs - performing upgrade - chown -R %{_MS_CUR_USER}:%{_MS_CUR_GROUP} /etc/salt/pki/master /etc/salt/master.d /var/log/salt/master /var/log/salt/key /var/cache/salt/master /var/run/salt/master -else - chown -R %{_SALT_USER}:%{_SALT_GROUP} /etc/salt/pki/master /etc/salt/master.d /var/log/salt/master /var/log/salt/key /var/cache/salt/master /var/run/salt/master -fi + %posttrans master + if [ ! -e "/var/log/salt/master" ]; then + touch /var/log/salt/master + chmod 640 /var/log/salt/master + fi + if [ ! -e "/var/log/salt/key" ]; then + touch /var/log/salt/key + chmod 640 /var/log/salt/key + fi + if [ $1 -gt 1 ] ; then + # Upgrade: preserve existing ownership, don't reset to defaults + : + else + chown -R %{_SALT_USER}:%{_SALT_GROUP} /etc/salt/pki/master /etc/salt/master.d /var/log/salt/master /var/log/salt/key /var/cache/salt/master /var/run/salt/master /opt/saltstack/salt + fi -%posttrans syndic -if [ ! -e "/var/log/salt/syndic" ]; then - touch /var/log/salt/syndic - chmod 640 /var/log/salt/syndic -fi -if [ $1 -gt 1 ] ; then - # Reset permissions to match previous installs - performing upgrade - chown -R %{_MS_CUR_USER}:%{_MS_CUR_GROUP} /var/log/salt/syndic -else - chown -R %{_SALT_USER}:%{_SALT_GROUP} /var/log/salt/syndic -fi + %posttrans syndic + if [ ! -e "/var/log/salt/syndic" ]; then + touch /var/log/salt/syndic + chmod 640 /var/log/salt/syndic + fi + if [ $1 -gt 1 ] ; then + # Upgrade: preserve existing ownership, don't reset to defaults + : + else + chown -R %{_SALT_USER}:%{_SALT_GROUP} /var/log/salt/syndic /opt/saltstack/salt + fi -%posttrans api -if [ ! -e "/var/log/salt/api" ]; then - touch /var/log/salt/api - chmod 640 /var/log/salt/api -fi -if [ $1 -gt 1 ] ; then - # Reset permissions to match previous installs - performing upgrade - chown -R %{_MS_CUR_USER}:%{_MS_CUR_GROUP} /var/log/salt/api -else - chown -R %{_SALT_USER}:%{_SALT_GROUP} /var/log/salt/api -fi + %posttrans api + if [ ! -e "/var/log/salt/api" ]; then + touch /var/log/salt/api + chmod 640 /var/log/salt/api + fi + if [ $1 -gt 1 ] ; then + # Upgrade: preserve existing ownership, don't reset to defaults + : + else + chown -R %{_SALT_USER}:%{_SALT_GROUP} /var/log/salt/api /opt/saltstack/salt + fi %posttrans minion + if [ ! -e "/var/log/salt/minion" ]; then touch /var/log/salt/minion chmod 640 /var/log/salt/minion @@ -682,17 +772,62 @@ if [ ! -e "/var/log/salt/key" ]; then touch /var/log/salt/key chmod 640 /var/log/salt/key fi -if [ $1 -gt 1 ] ; then - # Reset permissions to match previous installs - performing upgrade - chown -R %{_MN_CUR_USER}:%{_MN_CUR_GROUP} /etc/salt/pki/minion /etc/salt/minion.d /var/log/salt/minion /var/cache/salt/minion /var/run/salt/minion + +# Check for preserved ownership marker (from %pre) +if [ -f "/tmp/.salt-minion-upgrade-ownership" ]; then + # Upgrade case where we detected previous user + OWNERSHIP=$(cat /tmp/.salt-minion-upgrade-ownership) + + # Apply ownership restoration + chown $OWNERSHIP /etc/salt + chown $OWNERSHIP /etc/salt/pki + chown $OWNERSHIP /var/run/salt + chown -R $OWNERSHIP /etc/salt/pki/minion + chown -R $OWNERSHIP /etc/salt/minion.d + chown -R $OWNERSHIP /var/cache/salt/minion + chown -R $OWNERSHIP /var/run/salt/minion + chown $OWNERSHIP /var/log/salt/minion + # Also restore parent directories that are commonly owned by salt user + chown $OWNERSHIP /var/log/salt + chown -R $OWNERSHIP /var/cache/salt + + # Pre-create proc directory to ensure ownership (fixes PermissionError) + mkdir -p /var/cache/salt/minion/proc + chown $OWNERSHIP /var/cache/salt/minion/proc + chmod 750 /var/cache/salt/minion/proc + + # Restore ownership of the main installation directory for salt-pip access + chown -R $OWNERSHIP /opt/saltstack/salt + + # Clean up + rm -f /tmp/.salt-minion-upgrade-ownership + rm -f /tmp/.salt-minion-upgrade-ownership.done + +else + # Fresh install or upgrade from root + + # Check for configuration file in /etc/sysconfig/salt-minion-setup + if [ -f /etc/sysconfig/salt-minion-setup ]; then + . /etc/sysconfig/salt-minion-setup + fi + + # For fresh installs, set ownership based on environment variables or defaults + if [ -n "$SALT_MINION_USER" ] && [ "$SALT_MINION_USER" != "root" ]; then + chown -R $SALT_MINION_USER:$SALT_MINION_USER /etc/salt/pki/minion /etc/salt/minion.d /var/log/salt/minion /var/cache/salt/minion /var/run/salt/minion /var/log/salt /var/cache/salt + # Ensure the main installation directory is also owned by the salt user for salt-pip + chown -R $SALT_MINION_USER:$SALT_MINION_USER /opt/saltstack/salt + fi fi +# Always try to restart service +/bin/systemctl try-restart salt-minion.service >/dev/null 2>&1 || : + %preun if [ $1 -eq 0 ]; then # Uninstall - find /opt/saltstack/salt -type f -name \*\.pyc -print0 | xargs --null --no-run-if-empty rm - find /opt/saltstack/salt -type d -name __pycache__ -empty -print0 | xargs --null --no-run-if-empty rmdir + find /opt/saltstack/salt -type f -name '*.pyc' -delete + find /opt/saltstack/salt -type d -name __pycache__ -empty -delete fi %postun master diff --git a/pkg/windows/build.ps1 b/pkg/windows/build.ps1 index 72e29fc1d13c..70c0400a0885 100644 --- a/pkg/windows/build.ps1 +++ b/pkg/windows/build.ps1 @@ -264,7 +264,7 @@ if ( ! $? ) { & "$SCRIPT_DIR\msi\build_pkg.ps1" @KeywordArguments if ( ! $? ) { - Write-Host "Failed to build NSIS package" + Write-Host "Failed to build MSI package" exit 1 } diff --git a/pkg/windows/msi/build_pkg.ps1 b/pkg/windows/msi/build_pkg.ps1 index c9e484e2746f..eaa612c0666c 100644 --- a/pkg/windows/msi/build_pkg.ps1 +++ b/pkg/windows/msi/build_pkg.ps1 @@ -496,7 +496,7 @@ Write-Host "Discovering install files: " -NoNewline -var var.DISCOVER_INSTALLDIR ` -dr INSTALLDIR ` -t "$SCRIPT_DIR\Product-discover-files.xsl" ` - -nologo -indent 1 -gg -sfrag -sreg -srd -ke -template fragment + -nologo -indent 1 -ag -sfrag -sreg -srd -template fragment CheckExitCode # Move the configs back @@ -513,7 +513,7 @@ Write-Host "Discovering config files: " -NoNewline -var var.DISCOVER_CONFDIR ` -dr CONFDIR ` -t "$SCRIPT_DIR\Product-discover-files-config.xsl" ` - -nologo -indent 1 -gg -sfrag -sreg -srd -ke -template fragment + -nologo -indent 1 -ag -sfrag -sreg -srd -template fragment CheckExitCode Write-Host "Compiling *.wxs to $($ARCHITECTURE[$i]) *.wixobj: " -NoNewline diff --git a/pyproject.toml b/pyproject.toml index a8eb5de0b39c..6120a547e263 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,106 @@ +[build-system] +requires = [ + "setuptools>=62.0", + "wheel", + "looseversion", + "packaging", + "importlib-metadata>=8.7.0", +] +build-backend = "salt_build_backend" +backend-path = ["tools/pkg"] + +[project] +name = "salt" +description = "Portable, distributed, remote execution and configuration management system" +readme = "README.rst" +requires-python = ">=3.8" +license = {text = "Apache Software License 2.0"} +authors = [ + {name = "Thomas S Hatch", email = "thatch45@gmail.com"}, +] +classifiers = [ + "Programming Language :: Python", + "Programming Language :: Cython", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: Apache Software License", + "Operating System :: POSIX :: Linux", + "Topic :: System :: Clustering", + "Topic :: System :: Distributed Computing", +] +dependencies = [ + "Jinja2>=3.1.5", + "MarkupSafe<3.0.0", + "PyYAML", + "aiohttp>=3.13.3", + "apache-libcloud>=3.8.0", + "certifi>=2024.7.4", + "cffi>=2.0.0", + "cheroot>=10.0.1", + "cherrypy>=18.6.1", + "contextvars", + "croniter>=0.3.0,!=0.3.22; sys_platform != 'win32'", + "cryptography>=46.0.5", + "distro>=1.0.1", + "frozenlist>=1.3.0; python_version < '3.11'", + "frozenlist>=1.5.0; python_version >= '3.11'", + "gitpython>=3.1.37", + "idna>=2.8", + "immutables>=0.21", + "importlib-metadata>=8.7.0", + "jaraco.context>=6.1.0", + "jaraco.functools>=4.1.0", + "jaraco.text>=4.0.0", + "jmespath>=1.1.0", + "looseversion", + "lxml>=6.0.2; sys_platform == 'win32'", + "more-itertools>=9.1.0", + "msgpack>=1.0.0", + "packaging==24.0", + "psutil<6.0.0; python_version <= '3.9'", + "psutil>=5.0.0; python_version >= '3.10'", + "pyasn1>=0.6.2", + "pycparser>=2.21", + "pycryptodomex>=3.9.8", + "pymssql==2.3.11; sys_platform == 'win32'", + "pymysql>=1.0.2; sys_platform == 'win32'", + "pyopenssl>=25.0.0", + "python-dateutil>=2.8.1", + "python-gnupg>=0.4.7", + "pythonnet>=3.0.1; sys_platform == 'win32'", + "pywin32>=305; sys_platform == 'win32'", + "pyzmq>=25.1.2", + "requests<2.32.0 ; python_version < '3.10'", + "requests>=2.32.5 ; python_version >= '3.10'", + "rpm-vercmp; sys_platform == 'linux'", + "setproctitle>=1.2.3", + "tornado>=6.5.5", + "urllib3>=1.26.20,<2.0.0; python_version < '3.10'", + "urllib3>=2.6.3; python_version >= '3.10'", + "virtualenv", + "vultr>=1.0.1", + "wmi>=1.5.1; sys_platform == 'win32'", + "xmltodict>=0.13.0; sys_platform == 'win32'", + "zipp>=3.19.1", +] +dynamic = ["version", "scripts", "entry-points"] + +[project.optional-dependencies] +crypto = [ + "pycryptodomex>=3.9.8", +] + +[project.urls] +Homepage = "https://saltproject.io" + [tool.black] exclude= """ /( @@ -57,3 +160,4 @@ known_third_party = [ directory = "security" name = "Security" showcontent = true +# Triggering CI with dummy change diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 0a12facab89f..372dbf3632fd 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -4,3 +4,4 @@ wheel >= 0.46.3 setuptools >= 80.10.2 pip == 25.2 +markdown-it-py < 3.0.0; python_version == "3.9" diff --git a/requirements/darwin.txt b/requirements/darwin.txt index 0a2350c27e64..6dfe64d2cdeb 100644 --- a/requirements/darwin.txt +++ b/requirements/darwin.txt @@ -1,5 +1,3 @@ # Darwin source distribution requirements # Don't add any requirements here, add them in requirements/base.txt # If they are macOS specific, place "; sys_platform == 'darwin'" in front of the requirement. - --r zeromq.txt diff --git a/requirements/static/ci/common.in b/requirements/static/ci/common.in index 9f1770150838..af3a866735ec 100644 --- a/requirements/static/ci/common.in +++ b/requirements/static/ci/common.in @@ -39,6 +39,7 @@ moto>=5.0.0 napalm; sys_platform != 'win32' and python_version < '3.10' paramiko>=2.10.1; sys_platform != 'win32' and sys_platform != 'darwin' passlib>=1.7.4 +pycryptodomex pynacl>=1.5.0 pyinotify>=0.9.6; sys_platform != 'win32' and sys_platform != 'darwin' and platform_system != "openbsd" python-etcd>0.4.2 diff --git a/requirements/static/ci/py3.10/changelog.txt b/requirements/static/ci/py3.10/changelog.txt index e0f746576261..aacf7a6dd079 100644 --- a/requirements/static/ci/py3.10/changelog.txt +++ b/requirements/static/ci/py3.10/changelog.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/changelog.in --python-platform=linux --python-version=3.10 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.10/linux.txt -o=requirements/static/ci/py3.10/changelog.txt +# uv pip compile requirements/static/ci/changelog.in --python-platform=linux --python-version=3.10 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.10/linux.txt -o=requirements/static/ci/py3.10/changelog.txt click==8.1.3 # via # click-default-group diff --git a/requirements/static/ci/py3.10/cloud.txt b/requirements/static/ci/py3.10/cloud.txt index 6965e16913bf..c91bcad16c17 100644 --- a/requirements/static/ci/py3.10/cloud.txt +++ b/requirements/static/ci/py3.10/cloud.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/cloud.in requirements/static/pkg/linux.in --python-platform=linux --python-version=3.10 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.10/linux.txt -c=requirements/static/pkg/py3.10/linux.txt -o=requirements/static/ci/py3.10/cloud.txt +# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/cloud.in requirements/static/pkg/linux.in --python-platform=linux --python-version=3.10 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.10/linux.txt -c=requirements/static/pkg/py3.10/linux.txt -o=requirements/static/ci/py3.10/cloud.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/ci/py3.10/linux.txt @@ -452,6 +452,7 @@ pycryptodomex==3.19.1 # -c requirements/static/ci/py3.10/linux.txt # -c requirements/static/pkg/py3.10/linux.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==5.3.1 # via # -c requirements/static/ci/py3.10/linux.txt diff --git a/requirements/static/ci/py3.10/darwin-crypto.txt b/requirements/static/ci/py3.10/darwin-crypto.txt index b11e8e632521..02616e5e319d 100644 --- a/requirements/static/ci/py3.10/darwin-crypto.txt +++ b/requirements/static/ci/py3.10/darwin-crypto.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/crypto.in --python-platform=macos --python-version=3.10 --no-emit-index-url -o=requirements/static/ci/py3.10/darwin-crypto.txt +# uv pip compile requirements/static/ci/crypto.in --python-platform=macos --python-version=3.10 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.10/darwin-crypto.txt m2crypto==0.38.0 # via -r requirements/static/ci/crypto.in pycryptodome==3.19.1 diff --git a/requirements/static/ci/py3.10/darwin.txt b/requirements/static/ci/py3.10/darwin.txt index ca5936ac29f8..28f60cd174e1 100644 --- a/requirements/static/ci/py3.10/darwin.txt +++ b/requirements/static/ci/py3.10/darwin.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/darwin.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/darwin.in --python-platform=macos --python-version=3.10 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.10/darwin.txt -o=requirements/static/ci/py3.10/darwin.txt +# uv pip compile requirements/base.txt requirements/darwin.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/darwin.in --python-platform=macos --python-version=3.10 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.10/darwin.txt -o=requirements/static/ci/py3.10/darwin.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/pkg/py3.10/darwin.txt @@ -330,6 +330,7 @@ pycryptodomex==3.19.1 # via # -c requirements/static/pkg/py3.10/darwin.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==5.3.1 # via -r requirements/pytest.txt pygit2==1.13.1 diff --git a/requirements/static/ci/py3.10/docs.txt b/requirements/static/ci/py3.10/docs.txt index b5b2bc1baaaa..01cb8f4f6c0a 100644 --- a/requirements/static/ci/py3.10/docs.txt +++ b/requirements/static/ci/py3.10/docs.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/ci/docs.in --python-platform=linux --python-version=3.10 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.10/linux.txt -o=requirements/static/ci/py3.10/docs.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/static/ci/docs.in --python-platform=linux --python-version=3.10 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.10/linux.txt -o=requirements/static/ci/py3.10/docs.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/ci/py3.10/linux.txt diff --git a/requirements/static/ci/py3.10/freebsd-crypto.txt b/requirements/static/ci/py3.10/freebsd-crypto.txt index 15ceebb4ebb5..02a7772c43a3 100644 --- a/requirements/static/ci/py3.10/freebsd-crypto.txt +++ b/requirements/static/ci/py3.10/freebsd-crypto.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/crypto.in --universal --python-version=3.10 --no-emit-index-url -o=requirements/static/ci/py3.10/freebsd-crypto.txt +# uv pip compile requirements/static/ci/crypto.in --universal --python-version=3.10 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.10/freebsd-crypto.txt m2crypto==0.38.0 # via -r requirements/static/ci/crypto.in pycryptodome==3.19.1 diff --git a/requirements/static/ci/py3.10/freebsd.txt b/requirements/static/ci/py3.10/freebsd.txt index c48959371774..d43221ed32c0 100644 --- a/requirements/static/ci/py3.10/freebsd.txt +++ b/requirements/static/ci/py3.10/freebsd.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/freebsd.in requirements/static/pkg/freebsd.in --universal --python-version=3.10 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.10/freebsd.txt -o=requirements/static/ci/py3.10/freebsd.txt +# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/freebsd.in requirements/static/pkg/freebsd.in --universal --python-version=3.10 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.10/freebsd.txt -o=requirements/static/ci/py3.10/freebsd.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/pkg/py3.10/freebsd.txt @@ -352,6 +352,7 @@ pycryptodomex==3.19.1 # via # -c requirements/static/pkg/py3.10/freebsd.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==5.3.1 # via -r requirements/pytest.txt pyinotify==0.9.6 ; platform_system != 'openbsd' and sys_platform != 'darwin' and sys_platform != 'win32' diff --git a/requirements/static/ci/py3.10/lint.txt b/requirements/static/ci/py3.10/lint.txt index 329ef80901ff..9994e78dc26b 100644 --- a/requirements/static/ci/py3.10/lint.txt +++ b/requirements/static/ci/py3.10/lint.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/lint.in requirements/static/ci/linux.in requirements/static/pkg/linux.in --python-platform=linux --python-version=3.10 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.10/linux.txt -c=requirements/static/pkg/py3.10/linux.txt -o=requirements/static/ci/py3.10/lint.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/lint.in requirements/static/ci/linux.in requirements/static/pkg/linux.in --python-platform=linux --python-version=3.10 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.10/linux.txt -c=requirements/static/pkg/py3.10/linux.txt -o=requirements/static/ci/py3.10/lint.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/ci/py3.10/linux.txt @@ -474,6 +474,7 @@ pycryptodomex==3.19.1 # -c requirements/static/ci/py3.10/linux.txt # -c requirements/static/pkg/py3.10/linux.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pygit2==1.13.1 # via # -c requirements/static/ci/py3.10/linux.txt diff --git a/requirements/static/ci/py3.10/linux-crypto.txt b/requirements/static/ci/py3.10/linux-crypto.txt index 3d125445207f..3f828653fb10 100644 --- a/requirements/static/ci/py3.10/linux-crypto.txt +++ b/requirements/static/ci/py3.10/linux-crypto.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/crypto.in --python-platform=linux --python-version=3.10 --no-emit-index-url -o=requirements/static/ci/py3.10/linux-crypto.txt +# uv pip compile requirements/static/ci/crypto.in --python-platform=linux --python-version=3.10 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.10/linux-crypto.txt m2crypto==0.38.0 # via -r requirements/static/ci/crypto.in pycryptodome==3.19.1 diff --git a/requirements/static/ci/py3.10/linux.txt b/requirements/static/ci/py3.10/linux.txt index cc39923d5fd0..b276f43b983f 100644 --- a/requirements/static/ci/py3.10/linux.txt +++ b/requirements/static/ci/py3.10/linux.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/linux.in --python-platform=linux --python-version=3.10 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.10/linux.txt -o=requirements/static/ci/py3.10/linux.txt +# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/linux.in --python-platform=linux --python-version=3.10 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.10/linux.txt -o=requirements/static/ci/py3.10/linux.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/pkg/py3.10/linux.txt @@ -358,6 +358,7 @@ pycryptodomex==3.19.1 # via # -c requirements/static/pkg/py3.10/linux.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==5.3.1 # via -r requirements/pytest.txt pygit2==1.13.1 diff --git a/requirements/static/ci/py3.10/tools-virustotal.txt b/requirements/static/ci/py3.10/tools-virustotal.txt index 7bdba9cb57f0..04320458f28f 100644 --- a/requirements/static/ci/py3.10/tools-virustotal.txt +++ b/requirements/static/ci/py3.10/tools-virustotal.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/tools-virustotal.in --python-platform=linux --python-version=3.10 --no-emit-index-url -c=requirements/static/ci/py3.10/tools.txt -o=requirements/static/ci/py3.10/tools-virustotal.txt +# uv pip compile requirements/static/ci/tools-virustotal.in --python-platform=linux --python-version=3.10 --constraint requirements/constraints.txt --no-emit-index-url -c=requirements/static/ci/py3.10/tools.txt -o=requirements/static/ci/py3.10/tools-virustotal.txt certifi==2023.7.22 # via # -c requirements/static/ci/py3.10/tools.txt diff --git a/requirements/static/ci/py3.10/tools.txt b/requirements/static/ci/py3.10/tools.txt index ede6d9387f80..9f7346904faf 100644 --- a/requirements/static/ci/py3.10/tools.txt +++ b/requirements/static/ci/py3.10/tools.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/tools.in --python-platform=linux --python-version=3.10 --no-emit-index-url -o=requirements/static/ci/py3.10/tools.txt +# uv pip compile requirements/static/ci/tools.in --python-platform=linux --python-version=3.10 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.10/tools.txt annotated-types==0.6.0 # via pydantic attrs==20.3.0 diff --git a/requirements/static/ci/py3.10/windows-crypto.txt b/requirements/static/ci/py3.10/windows-crypto.txt index 7d1f0e3add52..057173873e0e 100644 --- a/requirements/static/ci/py3.10/windows-crypto.txt +++ b/requirements/static/ci/py3.10/windows-crypto.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/crypto.in --python-platform=windows --python-version=3.10 --no-emit-index-url -o=requirements/static/ci/py3.10/windows-crypto.txt +# uv pip compile requirements/static/ci/crypto.in --python-platform=windows --python-version=3.10 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.10/windows-crypto.txt m2crypto==0.38.0 # via -r requirements/static/ci/crypto.in pycryptodome==3.19.1 diff --git a/requirements/static/ci/py3.10/windows.txt b/requirements/static/ci/py3.10/windows.txt index 244e53e1829f..9c109a8871e3 100644 --- a/requirements/static/ci/py3.10/windows.txt +++ b/requirements/static/ci/py3.10/windows.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/pytest.txt requirements/windows.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/windows.in --python-platform=windows --python-version=3.10 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.10/windows.txt -o=requirements/static/ci/py3.10/windows.txt +# uv pip compile requirements/base.txt requirements/pytest.txt requirements/windows.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/windows.in --python-platform=windows --python-version=3.10 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.10/windows.txt -o=requirements/static/ci/py3.10/windows.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/pkg/py3.10/windows.txt @@ -306,6 +306,7 @@ pycryptodomex==3.19.1 # via # -c requirements/static/pkg/py3.10/windows.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==5.3.1 # via -r requirements/pytest.txt pygit2==1.18.2 diff --git a/requirements/static/ci/py3.11/changelog.txt b/requirements/static/ci/py3.11/changelog.txt index 88c58e2cc0bd..b84af18fda7a 100644 --- a/requirements/static/ci/py3.11/changelog.txt +++ b/requirements/static/ci/py3.11/changelog.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/changelog.in --python-platform=linux --python-version=3.11 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.11/linux.txt -o=requirements/static/ci/py3.11/changelog.txt +# uv pip compile requirements/static/ci/changelog.in --python-platform=linux --python-version=3.11 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.11/linux.txt -o=requirements/static/ci/py3.11/changelog.txt click==8.3.1 # via # click-default-group diff --git a/requirements/static/ci/py3.11/cloud.txt b/requirements/static/ci/py3.11/cloud.txt index f3c2dd444a42..cf15847f2dfc 100644 --- a/requirements/static/ci/py3.11/cloud.txt +++ b/requirements/static/ci/py3.11/cloud.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/cloud.in requirements/static/pkg/linux.in --python-platform=linux --python-version=3.11 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.11/linux.txt -c=requirements/static/pkg/py3.11/linux.txt -o=requirements/static/ci/py3.11/cloud.txt +# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/cloud.in requirements/static/pkg/linux.in --python-platform=linux --python-version=3.11 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.11/linux.txt -c=requirements/static/pkg/py3.11/linux.txt -o=requirements/static/ci/py3.11/cloud.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/ci/py3.11/linux.txt @@ -444,6 +444,7 @@ pycryptodomex==3.19.1 # -c requirements/static/ci/py3.11/linux.txt # -c requirements/static/pkg/py3.11/linux.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==5.3.1 # via # -c requirements/static/ci/py3.11/linux.txt diff --git a/requirements/static/ci/py3.11/darwin-crypto.txt b/requirements/static/ci/py3.11/darwin-crypto.txt index 44b840277bda..1144a4add76f 100644 --- a/requirements/static/ci/py3.11/darwin-crypto.txt +++ b/requirements/static/ci/py3.11/darwin-crypto.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/crypto.in --python-platform=macos --python-version=3.11 --no-emit-index-url -o=requirements/static/ci/py3.11/darwin-crypto.txt +# uv pip compile requirements/static/ci/crypto.in --python-platform=macos --python-version=3.11 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.11/darwin-crypto.txt m2crypto==0.38.0 # via -r requirements/static/ci/crypto.in pycryptodome==3.19.1 diff --git a/requirements/static/ci/py3.11/darwin.txt b/requirements/static/ci/py3.11/darwin.txt index 7a9304fd377c..4572e2b9a903 100644 --- a/requirements/static/ci/py3.11/darwin.txt +++ b/requirements/static/ci/py3.11/darwin.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/darwin.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/darwin.in --python-platform=macos --python-version=3.11 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.11/darwin.txt -o=requirements/static/ci/py3.11/darwin.txt +# uv pip compile requirements/base.txt requirements/darwin.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/darwin.in --python-platform=macos --python-version=3.11 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.11/darwin.txt -o=requirements/static/ci/py3.11/darwin.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/pkg/py3.11/darwin.txt @@ -324,6 +324,7 @@ pycryptodomex==3.19.1 # via # -c requirements/static/pkg/py3.11/darwin.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==5.3.1 # via -r requirements/pytest.txt pygit2==1.13.1 diff --git a/requirements/static/ci/py3.11/docs.txt b/requirements/static/ci/py3.11/docs.txt index 994518a5bf85..0ee4a8692c0f 100644 --- a/requirements/static/ci/py3.11/docs.txt +++ b/requirements/static/ci/py3.11/docs.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/ci/docs.in --python-platform=linux --python-version=3.11 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.11/linux.txt -o=requirements/static/ci/py3.11/docs.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/static/ci/docs.in --python-platform=linux --python-version=3.11 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.11/linux.txt -o=requirements/static/ci/py3.11/docs.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/ci/py3.11/linux.txt diff --git a/requirements/static/ci/py3.11/freebsd-crypto.txt b/requirements/static/ci/py3.11/freebsd-crypto.txt index 3556720e737c..4ecc3ad1f136 100644 --- a/requirements/static/ci/py3.11/freebsd-crypto.txt +++ b/requirements/static/ci/py3.11/freebsd-crypto.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/crypto.in --universal --python-version=3.11 --no-emit-index-url -o=requirements/static/ci/py3.11/freebsd-crypto.txt +# uv pip compile requirements/static/ci/crypto.in --universal --python-version=3.11 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.11/freebsd-crypto.txt m2crypto==0.38.0 # via -r requirements/static/ci/crypto.in pycryptodome==3.19.1 diff --git a/requirements/static/ci/py3.11/freebsd.txt b/requirements/static/ci/py3.11/freebsd.txt index 5d2d5bc35414..0ba6e0fbc75b 100644 --- a/requirements/static/ci/py3.11/freebsd.txt +++ b/requirements/static/ci/py3.11/freebsd.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/freebsd.in requirements/static/pkg/freebsd.in --universal --python-version=3.11 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.11/freebsd.txt -o=requirements/static/ci/py3.11/freebsd.txt +# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/freebsd.in requirements/static/pkg/freebsd.in --universal --python-version=3.11 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.11/freebsd.txt -o=requirements/static/ci/py3.11/freebsd.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/pkg/py3.11/freebsd.txt @@ -346,6 +346,7 @@ pycryptodomex==3.19.1 # via # -c requirements/static/pkg/py3.11/freebsd.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==5.3.1 # via -r requirements/pytest.txt pyinotify==0.9.6 ; platform_system != 'openbsd' and sys_platform != 'darwin' and sys_platform != 'win32' diff --git a/requirements/static/ci/py3.11/lint.txt b/requirements/static/ci/py3.11/lint.txt index a8952bafddc9..fb2f2fd4b281 100644 --- a/requirements/static/ci/py3.11/lint.txt +++ b/requirements/static/ci/py3.11/lint.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/lint.in requirements/static/ci/linux.in requirements/static/pkg/linux.in --python-platform=linux --python-version=3.11 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.11/linux.txt -c=requirements/static/pkg/py3.11/linux.txt -o=requirements/static/ci/py3.11/lint.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/lint.in requirements/static/ci/linux.in requirements/static/pkg/linux.in --python-platform=linux --python-version=3.11 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.11/linux.txt -c=requirements/static/pkg/py3.11/linux.txt -o=requirements/static/ci/py3.11/lint.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/ci/py3.11/linux.txt @@ -466,6 +466,7 @@ pycryptodomex==3.19.1 # -c requirements/static/ci/py3.11/linux.txt # -c requirements/static/pkg/py3.11/linux.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pygit2==1.13.1 # via # -c requirements/static/ci/py3.11/linux.txt diff --git a/requirements/static/ci/py3.11/linux-crypto.txt b/requirements/static/ci/py3.11/linux-crypto.txt index 4a74ef12f142..2c3d606f8ef7 100644 --- a/requirements/static/ci/py3.11/linux-crypto.txt +++ b/requirements/static/ci/py3.11/linux-crypto.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/crypto.in --python-platform=linux --python-version=3.11 --no-emit-index-url -o=requirements/static/ci/py3.11/linux-crypto.txt +# uv pip compile requirements/static/ci/crypto.in --python-platform=linux --python-version=3.11 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.11/linux-crypto.txt m2crypto==0.38.0 # via -r requirements/static/ci/crypto.in pycryptodome==3.19.1 diff --git a/requirements/static/ci/py3.11/linux.txt b/requirements/static/ci/py3.11/linux.txt index 4126139e4231..18348ec919d3 100644 --- a/requirements/static/ci/py3.11/linux.txt +++ b/requirements/static/ci/py3.11/linux.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/linux.in --python-platform=linux --python-version=3.11 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.11/linux.txt -o=requirements/static/ci/py3.11/linux.txt +# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/linux.in --python-platform=linux --python-version=3.11 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.11/linux.txt -o=requirements/static/ci/py3.11/linux.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/pkg/py3.11/linux.txt @@ -350,6 +350,7 @@ pycryptodomex==3.19.1 # via # -c requirements/static/pkg/py3.11/linux.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==5.3.1 # via -r requirements/pytest.txt pygit2==1.13.1 diff --git a/requirements/static/ci/py3.11/tools-virustotal.txt b/requirements/static/ci/py3.11/tools-virustotal.txt index 3b3cde62cd54..57800be9f279 100644 --- a/requirements/static/ci/py3.11/tools-virustotal.txt +++ b/requirements/static/ci/py3.11/tools-virustotal.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/tools-virustotal.in --python-platform=linux --python-version=3.11 --no-emit-index-url -c=requirements/static/ci/py3.11/tools.txt -o=requirements/static/ci/py3.11/tools-virustotal.txt +# uv pip compile requirements/static/ci/tools-virustotal.in --python-platform=linux --python-version=3.11 --constraint requirements/constraints.txt --no-emit-index-url -c=requirements/static/ci/py3.11/tools.txt -o=requirements/static/ci/py3.11/tools-virustotal.txt certifi==2023.7.22 # via # -c requirements/static/ci/py3.11/tools.txt diff --git a/requirements/static/ci/py3.11/tools.txt b/requirements/static/ci/py3.11/tools.txt index c7a346228900..7b7681ced585 100644 --- a/requirements/static/ci/py3.11/tools.txt +++ b/requirements/static/ci/py3.11/tools.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/tools.in --python-platform=linux --python-version=3.11 --no-emit-index-url -o=requirements/static/ci/py3.11/tools.txt +# uv pip compile requirements/static/ci/tools.in --python-platform=linux --python-version=3.11 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.11/tools.txt annotated-types==0.6.0 # via pydantic attrs==22.1.0 diff --git a/requirements/static/ci/py3.11/windows-crypto.txt b/requirements/static/ci/py3.11/windows-crypto.txt index 16fe7f0c9dfc..908f3779b1b0 100644 --- a/requirements/static/ci/py3.11/windows-crypto.txt +++ b/requirements/static/ci/py3.11/windows-crypto.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/crypto.in --python-platform=windows --python-version=3.11 --no-emit-index-url -o=requirements/static/ci/py3.11/windows-crypto.txt +# uv pip compile requirements/static/ci/crypto.in --python-platform=windows --python-version=3.11 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.11/windows-crypto.txt m2crypto==0.38.0 # via -r requirements/static/ci/crypto.in pycryptodome==3.19.1 diff --git a/requirements/static/ci/py3.11/windows.txt b/requirements/static/ci/py3.11/windows.txt index 0cc40c5fdb4b..0eb535591aa5 100644 --- a/requirements/static/ci/py3.11/windows.txt +++ b/requirements/static/ci/py3.11/windows.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/pytest.txt requirements/windows.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/windows.in --python-platform=windows --python-version=3.11 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.11/windows.txt -o=requirements/static/ci/py3.11/windows.txt +# uv pip compile requirements/base.txt requirements/pytest.txt requirements/windows.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/windows.in --python-platform=windows --python-version=3.11 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.11/windows.txt -o=requirements/static/ci/py3.11/windows.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/pkg/py3.11/windows.txt @@ -300,6 +300,7 @@ pycryptodomex==3.19.1 # via # -c requirements/static/pkg/py3.11/windows.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==5.3.1 # via -r requirements/pytest.txt pygit2==1.19.1 diff --git a/requirements/static/ci/py3.12/changelog.txt b/requirements/static/ci/py3.12/changelog.txt index 476d6085ad66..f2b5515368d4 100644 --- a/requirements/static/ci/py3.12/changelog.txt +++ b/requirements/static/ci/py3.12/changelog.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/changelog.in --python-platform=linux --python-version=3.12 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.12/linux.txt -o=requirements/static/ci/py3.12/changelog.txt +# uv pip compile requirements/static/ci/changelog.in --python-platform=linux --python-version=3.12 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.12/linux.txt -o=requirements/static/ci/py3.12/changelog.txt click==8.3.1 # via # click-default-group diff --git a/requirements/static/ci/py3.12/cloud.txt b/requirements/static/ci/py3.12/cloud.txt index 4ab22512e66b..ab01f5de2aad 100644 --- a/requirements/static/ci/py3.12/cloud.txt +++ b/requirements/static/ci/py3.12/cloud.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/cloud.in requirements/static/pkg/linux.in --python-platform=linux --python-version=3.12 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.12/linux.txt -c=requirements/static/pkg/py3.12/linux.txt -o=requirements/static/ci/py3.12/cloud.txt +# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/cloud.in requirements/static/pkg/linux.in --python-platform=linux --python-version=3.12 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.12/linux.txt -c=requirements/static/pkg/py3.12/linux.txt -o=requirements/static/ci/py3.12/cloud.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/ci/py3.12/linux.txt @@ -439,6 +439,7 @@ pycryptodomex==3.19.1 # -c requirements/static/ci/py3.12/linux.txt # -c requirements/static/pkg/py3.12/linux.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==5.3.1 # via # -c requirements/static/ci/py3.12/linux.txt diff --git a/requirements/static/ci/py3.12/darwin-crypto.txt b/requirements/static/ci/py3.12/darwin-crypto.txt index 541fcb41dbe2..87810134aec4 100644 --- a/requirements/static/ci/py3.12/darwin-crypto.txt +++ b/requirements/static/ci/py3.12/darwin-crypto.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/crypto.in --python-platform=macos --python-version=3.12 --no-emit-index-url -o=requirements/static/ci/py3.12/darwin-crypto.txt +# uv pip compile requirements/static/ci/crypto.in --python-platform=macos --python-version=3.12 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.12/darwin-crypto.txt m2crypto==0.38.0 # via -r requirements/static/ci/crypto.in pycryptodome==3.19.1 diff --git a/requirements/static/ci/py3.12/darwin.txt b/requirements/static/ci/py3.12/darwin.txt index 79f519ef9a6c..8f284f4662c0 100644 --- a/requirements/static/ci/py3.12/darwin.txt +++ b/requirements/static/ci/py3.12/darwin.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/darwin.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/darwin.in --python-platform=macos --python-version=3.12 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.12/darwin.txt -o=requirements/static/ci/py3.12/darwin.txt +# uv pip compile requirements/base.txt requirements/darwin.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/darwin.in --python-platform=macos --python-version=3.12 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.12/darwin.txt -o=requirements/static/ci/py3.12/darwin.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/pkg/py3.12/darwin.txt @@ -320,6 +320,7 @@ pycryptodomex==3.19.1 # via # -c requirements/static/pkg/py3.12/darwin.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==5.3.1 # via -r requirements/pytest.txt pygit2==1.13.1 diff --git a/requirements/static/ci/py3.12/docs.txt b/requirements/static/ci/py3.12/docs.txt index 8b80e6242e84..541bf02e2a4c 100644 --- a/requirements/static/ci/py3.12/docs.txt +++ b/requirements/static/ci/py3.12/docs.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/ci/docs.in --python-platform=linux --python-version=3.12 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.12/linux.txt -o=requirements/static/ci/py3.12/docs.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/static/ci/docs.in --python-platform=linux --python-version=3.12 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.12/linux.txt -o=requirements/static/ci/py3.12/docs.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/ci/py3.12/linux.txt diff --git a/requirements/static/ci/py3.12/freebsd-crypto.txt b/requirements/static/ci/py3.12/freebsd-crypto.txt index 8fd2c8c40d8f..0b1c051b8b98 100644 --- a/requirements/static/ci/py3.12/freebsd-crypto.txt +++ b/requirements/static/ci/py3.12/freebsd-crypto.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/crypto.in --universal --python-version=3.12 --no-emit-index-url -o=requirements/static/ci/py3.12/freebsd-crypto.txt +# uv pip compile requirements/static/ci/crypto.in --universal --python-version=3.12 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.12/freebsd-crypto.txt m2crypto==0.38.0 # via -r requirements/static/ci/crypto.in pycryptodome==3.19.1 diff --git a/requirements/static/ci/py3.12/freebsd.txt b/requirements/static/ci/py3.12/freebsd.txt index fa59754d035e..dd6d207eb7b3 100644 --- a/requirements/static/ci/py3.12/freebsd.txt +++ b/requirements/static/ci/py3.12/freebsd.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/freebsd.in requirements/static/pkg/freebsd.in --universal --python-version=3.12 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.12/freebsd.txt -o=requirements/static/ci/py3.12/freebsd.txt +# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/freebsd.in requirements/static/pkg/freebsd.in --universal --python-version=3.12 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.12/freebsd.txt -o=requirements/static/ci/py3.12/freebsd.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/pkg/py3.12/freebsd.txt @@ -342,6 +342,7 @@ pycryptodomex==3.19.1 # via # -c requirements/static/pkg/py3.12/freebsd.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==5.3.1 # via -r requirements/pytest.txt pyinotify==0.9.6 ; platform_system != 'openbsd' and sys_platform != 'darwin' and sys_platform != 'win32' diff --git a/requirements/static/ci/py3.12/lint.txt b/requirements/static/ci/py3.12/lint.txt index 5fcf4c9b3c92..fd17cef7047e 100644 --- a/requirements/static/ci/py3.12/lint.txt +++ b/requirements/static/ci/py3.12/lint.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/lint.in requirements/static/ci/linux.in requirements/static/pkg/linux.in --python-platform=linux --python-version=3.12 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.12/linux.txt -c=requirements/static/pkg/py3.12/linux.txt -o=requirements/static/ci/py3.12/lint.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/lint.in requirements/static/ci/linux.in requirements/static/pkg/linux.in --python-platform=linux --python-version=3.12 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.12/linux.txt -c=requirements/static/pkg/py3.12/linux.txt -o=requirements/static/ci/py3.12/lint.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/ci/py3.12/linux.txt @@ -461,6 +461,7 @@ pycryptodomex==3.19.1 # -c requirements/static/ci/py3.12/linux.txt # -c requirements/static/pkg/py3.12/linux.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pygit2==1.13.1 # via # -c requirements/static/ci/py3.12/linux.txt diff --git a/requirements/static/ci/py3.12/linux-crypto.txt b/requirements/static/ci/py3.12/linux-crypto.txt index a1f30f44c22f..5319ad6a4fc8 100644 --- a/requirements/static/ci/py3.12/linux-crypto.txt +++ b/requirements/static/ci/py3.12/linux-crypto.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/crypto.in --python-platform=linux --python-version=3.12 --no-emit-index-url -o=requirements/static/ci/py3.12/linux-crypto.txt +# uv pip compile requirements/static/ci/crypto.in --python-platform=linux --python-version=3.12 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.12/linux-crypto.txt m2crypto==0.38.0 # via -r requirements/static/ci/crypto.in pycryptodome==3.19.1 diff --git a/requirements/static/ci/py3.12/linux.txt b/requirements/static/ci/py3.12/linux.txt index 5ada4c3ae495..3d3f3fbc3299 100644 --- a/requirements/static/ci/py3.12/linux.txt +++ b/requirements/static/ci/py3.12/linux.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/linux.in --python-platform=linux --python-version=3.12 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.12/linux.txt -o=requirements/static/ci/py3.12/linux.txt +# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/linux.in --python-platform=linux --python-version=3.12 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.12/linux.txt -o=requirements/static/ci/py3.12/linux.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/pkg/py3.12/linux.txt @@ -346,6 +346,7 @@ pycryptodomex==3.19.1 # via # -c requirements/static/pkg/py3.12/linux.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==5.3.1 # via -r requirements/pytest.txt pygit2==1.13.1 diff --git a/requirements/static/ci/py3.12/tools-virustotal.txt b/requirements/static/ci/py3.12/tools-virustotal.txt index a8871eda69c2..c5529893b3d8 100644 --- a/requirements/static/ci/py3.12/tools-virustotal.txt +++ b/requirements/static/ci/py3.12/tools-virustotal.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/tools-virustotal.in --python-platform=linux --python-version=3.12 --no-emit-index-url -c=requirements/static/ci/py3.12/tools.txt -o=requirements/static/ci/py3.12/tools-virustotal.txt +# uv pip compile requirements/static/ci/tools-virustotal.in --python-platform=linux --python-version=3.12 --constraint requirements/constraints.txt --no-emit-index-url -c=requirements/static/ci/py3.12/tools.txt -o=requirements/static/ci/py3.12/tools-virustotal.txt certifi==2023.7.22 # via # -c requirements/static/ci/py3.12/tools.txt diff --git a/requirements/static/ci/py3.12/tools.txt b/requirements/static/ci/py3.12/tools.txt index a58cd14996a3..f4f91aceabcd 100644 --- a/requirements/static/ci/py3.12/tools.txt +++ b/requirements/static/ci/py3.12/tools.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/tools.in --python-platform=linux --python-version=3.12 --no-emit-index-url -o=requirements/static/ci/py3.12/tools.txt +# uv pip compile requirements/static/ci/tools.in --python-platform=linux --python-version=3.12 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.12/tools.txt annotated-types==0.6.0 # via pydantic attrs==22.1.0 diff --git a/requirements/static/ci/py3.12/windows-crypto.txt b/requirements/static/ci/py3.12/windows-crypto.txt index 6e5d6464102e..4da4cecb92a6 100644 --- a/requirements/static/ci/py3.12/windows-crypto.txt +++ b/requirements/static/ci/py3.12/windows-crypto.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/crypto.in --python-platform=windows --python-version=3.12 --no-emit-index-url -o=requirements/static/ci/py3.12/windows-crypto.txt +# uv pip compile requirements/static/ci/crypto.in --python-platform=windows --python-version=3.12 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.12/windows-crypto.txt m2crypto==0.38.0 # via -r requirements/static/ci/crypto.in pycryptodome==3.19.1 diff --git a/requirements/static/ci/py3.12/windows.txt b/requirements/static/ci/py3.12/windows.txt index 899ce0401c47..6cba2b5a2398 100644 --- a/requirements/static/ci/py3.12/windows.txt +++ b/requirements/static/ci/py3.12/windows.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/pytest.txt requirements/windows.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/windows.in --python-platform=windows --python-version=3.12 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.12/windows.txt -o=requirements/static/ci/py3.12/windows.txt +# uv pip compile requirements/base.txt requirements/pytest.txt requirements/windows.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/windows.in --python-platform=windows --python-version=3.12 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.12/windows.txt -o=requirements/static/ci/py3.12/windows.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/pkg/py3.12/windows.txt @@ -296,6 +296,7 @@ pycryptodomex==3.19.1 # via # -c requirements/static/pkg/py3.12/windows.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==5.3.1 # via -r requirements/pytest.txt pygit2==1.19.1 diff --git a/requirements/static/ci/py3.13/changelog.txt b/requirements/static/ci/py3.13/changelog.txt index 7a3eaa8e7e41..a1027703d681 100644 --- a/requirements/static/ci/py3.13/changelog.txt +++ b/requirements/static/ci/py3.13/changelog.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/changelog.in --python-platform=linux --python-version=3.13 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.13/linux.txt -o=requirements/static/ci/py3.13/changelog.txt +# uv pip compile requirements/static/ci/changelog.in --python-platform=linux --python-version=3.13 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.13/linux.txt -o=requirements/static/ci/py3.13/changelog.txt click==8.3.1 # via # click-default-group diff --git a/requirements/static/ci/py3.13/cloud.txt b/requirements/static/ci/py3.13/cloud.txt index 6901a6c0564a..e572a98aa0d8 100644 --- a/requirements/static/ci/py3.13/cloud.txt +++ b/requirements/static/ci/py3.13/cloud.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/cloud.in requirements/static/pkg/linux.in --python-platform=linux --python-version=3.13 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.13/linux.txt -c=requirements/static/pkg/py3.13/linux.txt -o=requirements/static/ci/py3.13/cloud.txt +# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/cloud.in requirements/static/pkg/linux.in --python-platform=linux --python-version=3.13 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.13/linux.txt -c=requirements/static/pkg/py3.13/linux.txt -o=requirements/static/ci/py3.13/cloud.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/ci/py3.13/linux.txt @@ -440,6 +440,7 @@ pycryptodomex==3.23.0 # -c requirements/static/ci/py3.13/linux.txt # -c requirements/static/pkg/py3.13/linux.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==6.0.0 # via # -c requirements/static/ci/py3.13/linux.txt diff --git a/requirements/static/ci/py3.13/darwin-crypto.txt b/requirements/static/ci/py3.13/darwin-crypto.txt index 326ee5636e3c..df1396ba76ba 100644 --- a/requirements/static/ci/py3.13/darwin-crypto.txt +++ b/requirements/static/ci/py3.13/darwin-crypto.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/crypto.in --python-platform=macos --python-version=3.13 --no-emit-index-url -o=requirements/static/ci/py3.13/darwin-crypto.txt +# uv pip compile requirements/static/ci/crypto.in --python-platform=macos --python-version=3.13 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.13/darwin-crypto.txt m2crypto==0.46.2 # via -r requirements/static/ci/crypto.in pycryptodome==3.23.0 diff --git a/requirements/static/ci/py3.13/darwin.txt b/requirements/static/ci/py3.13/darwin.txt index c116f5bd8cbd..e2d7ce5faa24 100644 --- a/requirements/static/ci/py3.13/darwin.txt +++ b/requirements/static/ci/py3.13/darwin.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/darwin.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/darwin.in --python-platform=macos --python-version=3.13 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.13/darwin.txt -o=requirements/static/ci/py3.13/darwin.txt +# uv pip compile requirements/base.txt requirements/darwin.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/darwin.in --python-platform=macos --python-version=3.13 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.13/darwin.txt -o=requirements/static/ci/py3.13/darwin.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/pkg/py3.13/darwin.txt @@ -321,6 +321,7 @@ pycryptodomex==3.23.0 # via # -c requirements/static/pkg/py3.13/darwin.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==6.0.0 # via -r requirements/pytest.txt pygit2==1.19.1 diff --git a/requirements/static/ci/py3.13/docs.txt b/requirements/static/ci/py3.13/docs.txt index 2207c3de3a4d..130c9369fc9e 100644 --- a/requirements/static/ci/py3.13/docs.txt +++ b/requirements/static/ci/py3.13/docs.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/ci/docs.in --python-platform=linux --python-version=3.13 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.13/linux.txt -o=requirements/static/ci/py3.13/docs.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/static/ci/docs.in --python-platform=linux --python-version=3.13 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.13/linux.txt -o=requirements/static/ci/py3.13/docs.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/ci/py3.13/linux.txt diff --git a/requirements/static/ci/py3.13/freebsd-crypto.txt b/requirements/static/ci/py3.13/freebsd-crypto.txt index 69446adf9d93..4f8d2bca33a6 100644 --- a/requirements/static/ci/py3.13/freebsd-crypto.txt +++ b/requirements/static/ci/py3.13/freebsd-crypto.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/crypto.in --universal --python-version=3.13 --no-emit-index-url -o=requirements/static/ci/py3.13/freebsd-crypto.txt +# uv pip compile requirements/static/ci/crypto.in --universal --python-version=3.13 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.13/freebsd-crypto.txt m2crypto==0.46.2 # via -r requirements/static/ci/crypto.in pycryptodome==3.23.0 diff --git a/requirements/static/ci/py3.13/freebsd.txt b/requirements/static/ci/py3.13/freebsd.txt index dc736874ac82..387732f11b3b 100644 --- a/requirements/static/ci/py3.13/freebsd.txt +++ b/requirements/static/ci/py3.13/freebsd.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/freebsd.in requirements/static/pkg/freebsd.in --universal --python-version=3.13 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.13/freebsd.txt -o=requirements/static/ci/py3.13/freebsd.txt +# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/freebsd.in requirements/static/pkg/freebsd.in --universal --python-version=3.13 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.13/freebsd.txt -o=requirements/static/ci/py3.13/freebsd.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/pkg/py3.13/freebsd.txt @@ -343,6 +343,7 @@ pycryptodomex==3.23.0 # via # -c requirements/static/pkg/py3.13/freebsd.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==6.0.0 # via -r requirements/pytest.txt pygments==2.19.2 diff --git a/requirements/static/ci/py3.13/lint.txt b/requirements/static/ci/py3.13/lint.txt index 6145c329c563..647133a4205c 100644 --- a/requirements/static/ci/py3.13/lint.txt +++ b/requirements/static/ci/py3.13/lint.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/lint.in requirements/static/ci/linux.in requirements/static/pkg/linux.in --python-platform=linux --python-version=3.13 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.13/linux.txt -c=requirements/static/pkg/py3.13/linux.txt -o=requirements/static/ci/py3.13/lint.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/lint.in requirements/static/ci/linux.in requirements/static/pkg/linux.in --python-platform=linux --python-version=3.13 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.13/linux.txt -c=requirements/static/pkg/py3.13/linux.txt -o=requirements/static/ci/py3.13/lint.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/ci/py3.13/linux.txt @@ -461,6 +461,7 @@ pycryptodomex==3.23.0 # -c requirements/static/ci/py3.13/linux.txt # -c requirements/static/pkg/py3.13/linux.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pygit2==1.19.1 # via # -c requirements/static/ci/py3.13/linux.txt diff --git a/requirements/static/ci/py3.13/linux-crypto.txt b/requirements/static/ci/py3.13/linux-crypto.txt index 7eced03ed1f8..4c786a67661a 100644 --- a/requirements/static/ci/py3.13/linux-crypto.txt +++ b/requirements/static/ci/py3.13/linux-crypto.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/crypto.in --python-platform=linux --python-version=3.13 --no-emit-index-url -o=requirements/static/ci/py3.13/linux-crypto.txt +# uv pip compile requirements/static/ci/crypto.in --python-platform=linux --python-version=3.13 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.13/linux-crypto.txt m2crypto==0.46.2 # via -r requirements/static/ci/crypto.in pycryptodome==3.23.0 diff --git a/requirements/static/ci/py3.13/linux.txt b/requirements/static/ci/py3.13/linux.txt index 8c906cc21f76..3374e7287131 100644 --- a/requirements/static/ci/py3.13/linux.txt +++ b/requirements/static/ci/py3.13/linux.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/linux.in --python-platform=linux --python-version=3.13 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.13/linux.txt -o=requirements/static/ci/py3.13/linux.txt +# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/linux.in --python-platform=linux --python-version=3.13 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.13/linux.txt -o=requirements/static/ci/py3.13/linux.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/pkg/py3.13/linux.txt @@ -347,6 +347,7 @@ pycryptodomex==3.23.0 # via # -c requirements/static/pkg/py3.13/linux.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==6.0.0 # via -r requirements/pytest.txt pygit2==1.19.1 diff --git a/requirements/static/ci/py3.13/tools-virustotal.txt b/requirements/static/ci/py3.13/tools-virustotal.txt index 63c9f830b5b3..bb0a723dcdb6 100644 --- a/requirements/static/ci/py3.13/tools-virustotal.txt +++ b/requirements/static/ci/py3.13/tools-virustotal.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/tools-virustotal.in --python-platform=linux --python-version=3.13 --no-emit-index-url -c=requirements/static/ci/py3.13/tools.txt -o=requirements/static/ci/py3.13/tools-virustotal.txt +# uv pip compile requirements/static/ci/tools-virustotal.in --python-platform=linux --python-version=3.13 --constraint requirements/constraints.txt --no-emit-index-url -c=requirements/static/ci/py3.13/tools.txt -o=requirements/static/ci/py3.13/tools-virustotal.txt certifi==2026.1.4 # via # -c requirements/static/ci/py3.13/tools.txt diff --git a/requirements/static/ci/py3.13/tools.txt b/requirements/static/ci/py3.13/tools.txt index 94dcd41cec46..774a455216aa 100644 --- a/requirements/static/ci/py3.13/tools.txt +++ b/requirements/static/ci/py3.13/tools.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/tools.in --python-platform=linux --python-version=3.13 --no-emit-index-url -o=requirements/static/ci/py3.13/tools.txt +# uv pip compile requirements/static/ci/tools.in --python-platform=linux --python-version=3.13 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.13/tools.txt annotated-types==0.7.0 # via pydantic attrs==25.4.0 diff --git a/requirements/static/ci/py3.13/windows-crypto.txt b/requirements/static/ci/py3.13/windows-crypto.txt index 6300334ad1ee..5895dd0cd9a0 100644 --- a/requirements/static/ci/py3.13/windows-crypto.txt +++ b/requirements/static/ci/py3.13/windows-crypto.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/crypto.in --python-platform=windows --python-version=3.13 --no-emit-index-url -o=requirements/static/ci/py3.13/windows-crypto.txt +# uv pip compile requirements/static/ci/crypto.in --python-platform=windows --python-version=3.13 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.13/windows-crypto.txt m2crypto==0.46.2 # via -r requirements/static/ci/crypto.in pycryptodome==3.23.0 diff --git a/requirements/static/ci/py3.13/windows.txt b/requirements/static/ci/py3.13/windows.txt index b442657b2794..5e3d85de076c 100644 --- a/requirements/static/ci/py3.13/windows.txt +++ b/requirements/static/ci/py3.13/windows.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/pytest.txt requirements/windows.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/windows.in --python-platform=windows --python-version=3.13 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.13/windows.txt -o=requirements/static/ci/py3.13/windows.txt +# uv pip compile requirements/base.txt requirements/pytest.txt requirements/windows.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/windows.in --python-platform=windows --python-version=3.13 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.13/windows.txt -o=requirements/static/ci/py3.13/windows.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/pkg/py3.13/windows.txt @@ -297,6 +297,7 @@ pycryptodomex==3.23.0 # via # -c requirements/static/pkg/py3.13/windows.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==6.0.0 # via -r requirements/pytest.txt pygit2==1.19.1 diff --git a/requirements/static/ci/py3.9/changelog.txt b/requirements/static/ci/py3.9/changelog.txt index 125433a24974..60d35facc3bf 100644 --- a/requirements/static/ci/py3.9/changelog.txt +++ b/requirements/static/ci/py3.9/changelog.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/changelog.in --python-platform=linux --python-version=3.9 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.9/linux.txt -o=requirements/static/ci/py3.9/changelog.txt +# uv pip compile requirements/static/ci/changelog.in --python-platform=linux --python-version=3.9 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.9/linux.txt -o=requirements/static/ci/py3.9/changelog.txt click==8.1.8 # via # click-default-group diff --git a/requirements/static/ci/py3.9/cloud.txt b/requirements/static/ci/py3.9/cloud.txt index c831555b7fef..17e50ce43d91 100644 --- a/requirements/static/ci/py3.9/cloud.txt +++ b/requirements/static/ci/py3.9/cloud.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/cloud.in requirements/static/pkg/linux.in --python-platform=linux --python-version=3.9 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.9/linux.txt -c=requirements/static/pkg/py3.9/linux.txt -o=requirements/static/ci/py3.9/cloud.txt +# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/cloud.in requirements/static/pkg/linux.in --python-platform=linux --python-version=3.9 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.9/linux.txt -c=requirements/static/pkg/py3.9/linux.txt -o=requirements/static/ci/py3.9/cloud.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/ci/py3.9/linux.txt @@ -351,6 +351,7 @@ mako==1.3.10 # -r requirements/static/ci/common.in markdown-it-py==2.2.0 # via + # -c requirements/constraints.txt # -c requirements/static/ci/py3.9/linux.txt # -r requirements/static/ci/common.in # rich @@ -505,6 +506,7 @@ pycryptodomex==3.19.1 # -c requirements/static/ci/py3.9/linux.txt # -c requirements/static/pkg/py3.9/linux.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyeapi==1.0.4 # via # -c requirements/static/ci/py3.9/linux.txt diff --git a/requirements/static/ci/py3.9/darwin-crypto.txt b/requirements/static/ci/py3.9/darwin-crypto.txt index 6846828eef8e..d6327e74013d 100644 --- a/requirements/static/ci/py3.9/darwin-crypto.txt +++ b/requirements/static/ci/py3.9/darwin-crypto.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/crypto.in --python-platform=macos --python-version=3.9 --no-emit-index-url -o=requirements/static/ci/py3.9/darwin-crypto.txt +# uv pip compile requirements/static/ci/crypto.in --python-platform=macos --python-version=3.9 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.9/darwin-crypto.txt m2crypto==0.38.0 # via -r requirements/static/ci/crypto.in pycryptodome==3.19.1 diff --git a/requirements/static/ci/py3.9/darwin.txt b/requirements/static/ci/py3.9/darwin.txt index b998d51d4700..8a483c7b15f6 100644 --- a/requirements/static/ci/py3.9/darwin.txt +++ b/requirements/static/ci/py3.9/darwin.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/darwin.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/darwin.in --python-platform=macos --python-version=3.9 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.9/darwin.txt -o=requirements/static/ci/py3.9/darwin.txt +# uv pip compile requirements/base.txt requirements/darwin.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/darwin.in --python-platform=macos --python-version=3.9 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.9/darwin.txt -o=requirements/static/ci/py3.9/darwin.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/pkg/py3.9/darwin.txt @@ -255,6 +255,7 @@ mako==1.3.10 # via -r requirements/static/ci/common.in markdown-it-py==2.2.0 # via + # -c requirements/constraints.txt # -r requirements/static/ci/common.in # rich markupsafe==2.1.3 @@ -368,6 +369,7 @@ pycryptodomex==3.19.1 # via # -c requirements/static/pkg/py3.9/darwin.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyeapi==1.0.4 # via napalm pyfakefs==5.3.1 diff --git a/requirements/static/ci/py3.9/docs.txt b/requirements/static/ci/py3.9/docs.txt index cf469518b220..0850bd9105fe 100644 --- a/requirements/static/ci/py3.9/docs.txt +++ b/requirements/static/ci/py3.9/docs.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/ci/docs.in --python-platform=linux --python-version=3.9 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.9/linux.txt -o=requirements/static/ci/py3.9/docs.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/static/ci/docs.in --python-platform=linux --python-version=3.9 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.9/linux.txt -o=requirements/static/ci/py3.9/docs.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/ci/py3.9/linux.txt @@ -159,6 +159,7 @@ looseversion==1.3.0 # -r requirements/base.txt markdown-it-py==2.2.0 # via + # -c requirements/constraints.txt # -c requirements/static/ci/py3.9/linux.txt # mdit-py-plugins # myst-docutils diff --git a/requirements/static/ci/py3.9/freebsd-crypto.txt b/requirements/static/ci/py3.9/freebsd-crypto.txt index ae3095364a53..a0eb26f63c9d 100644 --- a/requirements/static/ci/py3.9/freebsd-crypto.txt +++ b/requirements/static/ci/py3.9/freebsd-crypto.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/crypto.in --universal --python-version=3.9 --no-emit-index-url -o=requirements/static/ci/py3.9/freebsd-crypto.txt +# uv pip compile requirements/static/ci/crypto.in --universal --python-version=3.9 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.9/freebsd-crypto.txt m2crypto==0.38.0 # via -r requirements/static/ci/crypto.in pycryptodome==3.19.1 diff --git a/requirements/static/ci/py3.9/freebsd.txt b/requirements/static/ci/py3.9/freebsd.txt index 6157011bb26a..73d2c66e4762 100644 --- a/requirements/static/ci/py3.9/freebsd.txt +++ b/requirements/static/ci/py3.9/freebsd.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/freebsd.in requirements/static/pkg/freebsd.in --universal --python-version=3.9 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.9/freebsd.txt -o=requirements/static/ci/py3.9/freebsd.txt +# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/freebsd.in requirements/static/pkg/freebsd.in --universal --python-version=3.9 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.9/freebsd.txt -o=requirements/static/ci/py3.9/freebsd.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/pkg/py3.9/freebsd.txt @@ -280,6 +280,7 @@ mako==1.3.10 # via -r requirements/static/ci/common.in markdown-it-py==2.2.0 ; python_full_version < '3.10' # via + # -c requirements/constraints.txt # -r requirements/static/ci/common.in # rich markupsafe==2.1.3 @@ -395,6 +396,7 @@ pycryptodomex==3.19.1 # via # -c requirements/static/pkg/py3.9/freebsd.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyeapi==1.0.4 ; python_full_version < '3.10' and sys_platform != 'win32' # via napalm pyfakefs==5.3.1 diff --git a/requirements/static/ci/py3.9/lint.txt b/requirements/static/ci/py3.9/lint.txt index 359ca9d42fa3..1d2decb1b901 100644 --- a/requirements/static/ci/py3.9/lint.txt +++ b/requirements/static/ci/py3.9/lint.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/lint.in requirements/static/ci/linux.in requirements/static/pkg/linux.in --python-platform=linux --python-version=3.9 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.9/linux.txt -c=requirements/static/pkg/py3.9/linux.txt -o=requirements/static/ci/py3.9/lint.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/lint.in requirements/static/ci/linux.in requirements/static/pkg/linux.in --python-platform=linux --python-version=3.9 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/ci/py3.9/linux.txt -c=requirements/static/pkg/py3.9/linux.txt -o=requirements/static/ci/py3.9/lint.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/ci/py3.9/linux.txt @@ -368,6 +368,7 @@ mako==1.3.10 # -r requirements/static/ci/common.in markdown-it-py==2.2.0 # via + # -c requirements/constraints.txt # -c requirements/static/ci/py3.9/linux.txt # -r requirements/static/ci/common.in # rich @@ -516,6 +517,7 @@ pycryptodomex==3.19.1 # -c requirements/static/ci/py3.9/linux.txt # -c requirements/static/pkg/py3.9/linux.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyeapi==1.0.4 # via # -c requirements/static/ci/py3.9/linux.txt diff --git a/requirements/static/ci/py3.9/linux-crypto.txt b/requirements/static/ci/py3.9/linux-crypto.txt index 828293226297..14dcd61d7b22 100644 --- a/requirements/static/ci/py3.9/linux-crypto.txt +++ b/requirements/static/ci/py3.9/linux-crypto.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/crypto.in --python-platform=linux --python-version=3.9 --no-emit-index-url -o=requirements/static/ci/py3.9/linux-crypto.txt +# uv pip compile requirements/static/ci/crypto.in --python-platform=linux --python-version=3.9 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.9/linux-crypto.txt m2crypto==0.38.0 # via -r requirements/static/ci/crypto.in pycryptodome==3.19.1 diff --git a/requirements/static/ci/py3.9/linux.txt b/requirements/static/ci/py3.9/linux.txt index ceab2082077d..e92c6ebfeb92 100644 --- a/requirements/static/ci/py3.9/linux.txt +++ b/requirements/static/ci/py3.9/linux.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/linux.in --python-platform=linux --python-version=3.9 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.9/linux.txt -o=requirements/static/ci/py3.9/linux.txt +# uv pip compile requirements/base.txt requirements/pytest.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/linux.in --python-platform=linux --python-version=3.9 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.9/linux.txt -o=requirements/static/ci/py3.9/linux.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/pkg/py3.9/linux.txt @@ -275,6 +275,7 @@ mako==1.3.10 # via -r requirements/static/ci/common.in markdown-it-py==2.2.0 # via + # -c requirements/constraints.txt # -r requirements/static/ci/common.in # rich markupsafe==2.1.3 @@ -389,6 +390,7 @@ pycryptodomex==3.19.1 # via # -c requirements/static/pkg/py3.9/linux.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyeapi==1.0.4 # via napalm pyfakefs==5.3.1 diff --git a/requirements/static/ci/py3.9/tools-virustotal.txt b/requirements/static/ci/py3.9/tools-virustotal.txt index f2907a2d213f..ff12f7904b49 100644 --- a/requirements/static/ci/py3.9/tools-virustotal.txt +++ b/requirements/static/ci/py3.9/tools-virustotal.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/tools-virustotal.in --python-platform=linux --python-version=3.9 --no-emit-index-url -c=requirements/static/ci/py3.9/tools.txt -o=requirements/static/ci/py3.9/tools-virustotal.txt +# uv pip compile requirements/static/ci/tools-virustotal.in --python-platform=linux --python-version=3.9 --constraint requirements/constraints.txt --no-emit-index-url -c=requirements/static/ci/py3.9/tools.txt -o=requirements/static/ci/py3.9/tools-virustotal.txt certifi==2023.7.22 # via # -c requirements/static/ci/py3.9/tools.txt diff --git a/requirements/static/ci/py3.9/tools.txt b/requirements/static/ci/py3.9/tools.txt index 22f1534da454..4e60dac70cd5 100644 --- a/requirements/static/ci/py3.9/tools.txt +++ b/requirements/static/ci/py3.9/tools.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/tools.in --python-platform=linux --python-version=3.9 --no-emit-index-url -o=requirements/static/ci/py3.9/tools.txt +# uv pip compile requirements/static/ci/tools.in --python-platform=linux --python-version=3.9 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.9/tools.txt annotated-types==0.6.0 # via pydantic attrs==20.3.0 @@ -24,8 +24,10 @@ jmespath==1.0.1 # via # boto3 # botocore -markdown-it-py==3.0.0 - # via rich +markdown-it-py==2.2.0 + # via + # -c requirements/constraints.txt + # rich markupsafe==2.1.3 # via # -r requirements/static/ci/tools.in diff --git a/requirements/static/ci/py3.9/windows-crypto.txt b/requirements/static/ci/py3.9/windows-crypto.txt index c81e79f6d0f4..802a63e425b4 100644 --- a/requirements/static/ci/py3.9/windows-crypto.txt +++ b/requirements/static/ci/py3.9/windows-crypto.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/static/ci/crypto.in --python-platform=windows --python-version=3.9 --no-emit-index-url -o=requirements/static/ci/py3.9/windows-crypto.txt +# uv pip compile requirements/static/ci/crypto.in --python-platform=windows --python-version=3.9 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/ci/py3.9/windows-crypto.txt m2crypto==0.38.0 # via -r requirements/static/ci/crypto.in pycryptodome==3.19.1 diff --git a/requirements/static/ci/py3.9/windows.txt b/requirements/static/ci/py3.9/windows.txt index fc37942bb1c5..4de11185ae1f 100644 --- a/requirements/static/ci/py3.9/windows.txt +++ b/requirements/static/ci/py3.9/windows.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/pytest.txt requirements/windows.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/windows.in --python-platform=windows --python-version=3.9 --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.9/windows.txt -o=requirements/static/ci/py3.9/windows.txt +# uv pip compile requirements/base.txt requirements/pytest.txt requirements/windows.txt requirements/zeromq.txt requirements/static/ci/common.in requirements/static/ci/windows.in --python-platform=windows --python-version=3.9 --constraint requirements/constraints.txt --no-emit-index-url --unsafe-package=setuptools -c=requirements/static/pkg/py3.9/windows.txt -o=requirements/static/ci/py3.9/windows.txt aiohappyeyeballs==2.6.1 # via # -c requirements/static/pkg/py3.9/windows.txt @@ -234,7 +234,9 @@ lxml==6.0.2 mako==1.3.10 # via -r requirements/static/ci/common.in markdown-it-py==2.2.0 - # via -r requirements/static/ci/common.in + # via + # -c requirements/constraints.txt + # -r requirements/static/ci/common.in markupsafe==2.1.3 # via # -c requirements/static/pkg/py3.9/windows.txt @@ -321,6 +323,7 @@ pycryptodomex==3.19.1 # via # -c requirements/static/pkg/py3.9/windows.txt # -r requirements/crypto.txt + # -r requirements/static/ci/common.in pyfakefs==5.3.1 # via -r requirements/pytest.txt pygit2==1.15.1 diff --git a/requirements/static/pkg/py3.10/darwin.txt b/requirements/static/pkg/py3.10/darwin.txt index 83b0b43eeac8..dd47c38f2a8f 100644 --- a/requirements/static/pkg/py3.10/darwin.txt +++ b/requirements/static/pkg/py3.10/darwin.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/pkg/darwin.in --python-platform=macos --python-version=3.10 --no-emit-index-url -o=requirements/static/pkg/py3.10/darwin.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/static/pkg/darwin.in --python-platform=macos --python-version=3.10 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/pkg/py3.10/darwin.txt aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.13.3 diff --git a/requirements/static/pkg/py3.10/freebsd.txt b/requirements/static/pkg/py3.10/freebsd.txt index 28d916aed6fe..f94167ede84a 100644 --- a/requirements/static/pkg/py3.10/freebsd.txt +++ b/requirements/static/pkg/py3.10/freebsd.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/pkg/freebsd.in --universal --python-version=3.10 --no-emit-index-url -o=requirements/static/pkg/py3.10/freebsd.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/static/pkg/freebsd.in --universal --python-version=3.10 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/pkg/py3.10/freebsd.txt aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.13.3 diff --git a/requirements/static/pkg/py3.10/linux.txt b/requirements/static/pkg/py3.10/linux.txt index d8742553d624..9a353088fbcd 100644 --- a/requirements/static/pkg/py3.10/linux.txt +++ b/requirements/static/pkg/py3.10/linux.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/pkg/linux.in --no-emit-index-url --python-platform=linux --python-version=3.10 -o=requirements/static/pkg/py3.10/linux.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/static/pkg/linux.in --constraint requirements/constraints.txt --no-emit-index-url --python-platform=linux --python-version=3.10 -o=requirements/static/pkg/py3.10/linux.txt aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.13.3 diff --git a/requirements/static/pkg/py3.10/windows.txt b/requirements/static/pkg/py3.10/windows.txt index e4b5902a008d..5cb07a98de31 100644 --- a/requirements/static/pkg/py3.10/windows.txt +++ b/requirements/static/pkg/py3.10/windows.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/windows.txt requirements/static/pkg/windows.in --python-platform=windows --python-version=3.10 --no-emit-index-url -o=requirements/static/pkg/py3.10/windows.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/windows.txt requirements/static/pkg/windows.in --python-platform=windows --python-version=3.10 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/pkg/py3.10/windows.txt aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.13.3 diff --git a/requirements/static/pkg/py3.11/darwin.txt b/requirements/static/pkg/py3.11/darwin.txt index 98a5b647b3ad..365e599562c2 100644 --- a/requirements/static/pkg/py3.11/darwin.txt +++ b/requirements/static/pkg/py3.11/darwin.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/pkg/darwin.in --python-platform=macos --python-version=3.11 --no-emit-index-url -o=requirements/static/pkg/py3.11/darwin.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/static/pkg/darwin.in --python-platform=macos --python-version=3.11 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/pkg/py3.11/darwin.txt aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.13.3 diff --git a/requirements/static/pkg/py3.11/freebsd.txt b/requirements/static/pkg/py3.11/freebsd.txt index 7c57d26421a5..825ce2a2a4c2 100644 --- a/requirements/static/pkg/py3.11/freebsd.txt +++ b/requirements/static/pkg/py3.11/freebsd.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/pkg/freebsd.in --universal --python-version=3.11 --no-emit-index-url -o=requirements/static/pkg/py3.11/freebsd.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/static/pkg/freebsd.in --universal --python-version=3.11 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/pkg/py3.11/freebsd.txt aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.13.3 diff --git a/requirements/static/pkg/py3.11/linux.txt b/requirements/static/pkg/py3.11/linux.txt index 4eec2f752bcb..1b83339dae43 100644 --- a/requirements/static/pkg/py3.11/linux.txt +++ b/requirements/static/pkg/py3.11/linux.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/pkg/linux.in --no-emit-index-url --python-platform=linux --python-version=3.11 -o=requirements/static/pkg/py3.11/linux.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/static/pkg/linux.in --constraint requirements/constraints.txt --no-emit-index-url --python-platform=linux --python-version=3.11 -o=requirements/static/pkg/py3.11/linux.txt aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.13.3 diff --git a/requirements/static/pkg/py3.11/windows.txt b/requirements/static/pkg/py3.11/windows.txt index cfaa73f69d09..8f381dd925a5 100644 --- a/requirements/static/pkg/py3.11/windows.txt +++ b/requirements/static/pkg/py3.11/windows.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/windows.txt requirements/static/pkg/windows.in --python-platform=windows --python-version=3.11 --no-emit-index-url -o=requirements/static/pkg/py3.11/windows.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/windows.txt requirements/static/pkg/windows.in --python-platform=windows --python-version=3.11 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/pkg/py3.11/windows.txt aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.13.3 diff --git a/requirements/static/pkg/py3.12/darwin.txt b/requirements/static/pkg/py3.12/darwin.txt index 228e8004354e..21a03538c85f 100644 --- a/requirements/static/pkg/py3.12/darwin.txt +++ b/requirements/static/pkg/py3.12/darwin.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/pkg/darwin.in --python-platform=macos --python-version=3.12 --no-emit-index-url -o=requirements/static/pkg/py3.12/darwin.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/static/pkg/darwin.in --python-platform=macos --python-version=3.12 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/pkg/py3.12/darwin.txt aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.13.3 diff --git a/requirements/static/pkg/py3.12/freebsd.txt b/requirements/static/pkg/py3.12/freebsd.txt index 69e3fe69194c..cd10ce291435 100644 --- a/requirements/static/pkg/py3.12/freebsd.txt +++ b/requirements/static/pkg/py3.12/freebsd.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/pkg/freebsd.in --universal --python-version=3.12 --no-emit-index-url -o=requirements/static/pkg/py3.12/freebsd.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/static/pkg/freebsd.in --universal --python-version=3.12 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/pkg/py3.12/freebsd.txt aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.13.3 diff --git a/requirements/static/pkg/py3.12/linux.txt b/requirements/static/pkg/py3.12/linux.txt index 2cb87c8816d7..03bbae3b8d8c 100644 --- a/requirements/static/pkg/py3.12/linux.txt +++ b/requirements/static/pkg/py3.12/linux.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/pkg/linux.in --no-emit-index-url --python-platform=linux --python-version=3.12 -o=requirements/static/pkg/py3.12/linux.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/static/pkg/linux.in --constraint requirements/constraints.txt --no-emit-index-url --python-platform=linux --python-version=3.12 -o=requirements/static/pkg/py3.12/linux.txt aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.13.3 diff --git a/requirements/static/pkg/py3.12/windows.txt b/requirements/static/pkg/py3.12/windows.txt index 28e22fc349e6..0c206b44e6c7 100644 --- a/requirements/static/pkg/py3.12/windows.txt +++ b/requirements/static/pkg/py3.12/windows.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/windows.txt requirements/static/pkg/windows.in --python-platform=windows --python-version=3.12 --no-emit-index-url -o=requirements/static/pkg/py3.12/windows.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/windows.txt requirements/static/pkg/windows.in --python-platform=windows --python-version=3.12 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/pkg/py3.12/windows.txt aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.13.3 diff --git a/requirements/static/pkg/py3.13/darwin.txt b/requirements/static/pkg/py3.13/darwin.txt index 553036197698..63b5df24b71f 100644 --- a/requirements/static/pkg/py3.13/darwin.txt +++ b/requirements/static/pkg/py3.13/darwin.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/pkg/darwin.in --python-platform=macos --python-version=3.13 --no-emit-index-url -o=requirements/static/pkg/py3.13/darwin.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/static/pkg/darwin.in --python-platform=macos --python-version=3.13 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/pkg/py3.13/darwin.txt aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.13.3 diff --git a/requirements/static/pkg/py3.13/freebsd.txt b/requirements/static/pkg/py3.13/freebsd.txt index 7a6f060b0bba..c4b16ad1ddae 100644 --- a/requirements/static/pkg/py3.13/freebsd.txt +++ b/requirements/static/pkg/py3.13/freebsd.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/pkg/freebsd.in --universal --python-version=3.13 --no-emit-index-url -o=requirements/static/pkg/py3.13/freebsd.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/static/pkg/freebsd.in --universal --python-version=3.13 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/pkg/py3.13/freebsd.txt aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.13.3 diff --git a/requirements/static/pkg/py3.13/linux.txt b/requirements/static/pkg/py3.13/linux.txt index d7bb02951aff..489b8ee81684 100644 --- a/requirements/static/pkg/py3.13/linux.txt +++ b/requirements/static/pkg/py3.13/linux.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/pkg/linux.in --no-emit-index-url --python-platform=linux --python-version=3.13 -o=requirements/static/pkg/py3.13/linux.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/static/pkg/linux.in --constraint requirements/constraints.txt --no-emit-index-url --python-platform=linux --python-version=3.13 -o=requirements/static/pkg/py3.13/linux.txt aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.13.3 diff --git a/requirements/static/pkg/py3.13/windows.txt b/requirements/static/pkg/py3.13/windows.txt index 4e5f2633dbe1..1979010c5632 100644 --- a/requirements/static/pkg/py3.13/windows.txt +++ b/requirements/static/pkg/py3.13/windows.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/windows.txt requirements/static/pkg/windows.in --python-platform=windows --python-version=3.13 --no-emit-index-url -o=requirements/static/pkg/py3.13/windows.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/windows.txt requirements/static/pkg/windows.in --python-platform=windows --python-version=3.13 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/pkg/py3.13/windows.txt aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.13.3 diff --git a/requirements/static/pkg/py3.9/darwin.txt b/requirements/static/pkg/py3.9/darwin.txt index 7d728f035a66..3f5586d847d2 100644 --- a/requirements/static/pkg/py3.9/darwin.txt +++ b/requirements/static/pkg/py3.9/darwin.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/pkg/darwin.in --python-platform=macos --python-version=3.9 --no-emit-index-url -o=requirements/static/pkg/py3.9/darwin.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/static/pkg/darwin.in --python-platform=macos --python-version=3.9 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/pkg/py3.9/darwin.txt aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.13.3 diff --git a/requirements/static/pkg/py3.9/freebsd.txt b/requirements/static/pkg/py3.9/freebsd.txt index 26cfe898f536..2a880e52e3d7 100644 --- a/requirements/static/pkg/py3.9/freebsd.txt +++ b/requirements/static/pkg/py3.9/freebsd.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/pkg/freebsd.in --universal --python-version=3.9 --no-emit-index-url -o=requirements/static/pkg/py3.9/freebsd.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/static/pkg/freebsd.in --universal --python-version=3.9 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/pkg/py3.9/freebsd.txt aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.13.3 diff --git a/requirements/static/pkg/py3.9/linux.txt b/requirements/static/pkg/py3.9/linux.txt index 1985ba95ea5b..60e789d374f5 100644 --- a/requirements/static/pkg/py3.9/linux.txt +++ b/requirements/static/pkg/py3.9/linux.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/static/pkg/linux.in --python-platform=linux --python-version=3.9 --no-emit-index-url -o=requirements/static/pkg/py3.9/linux.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/static/pkg/linux.in --python-platform=linux --python-version=3.9 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/pkg/py3.9/linux.txt aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.13.3 diff --git a/requirements/static/pkg/py3.9/windows.txt b/requirements/static/pkg/py3.9/windows.txt index fa9824cd1c7d..cadcd5b937f9 100644 --- a/requirements/static/pkg/py3.9/windows.txt +++ b/requirements/static/pkg/py3.9/windows.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements/windows.txt requirements/static/pkg/windows.in --python-platform=windows --python-version=3.9 --no-emit-index-url -o=requirements/static/pkg/py3.9/windows.txt +# uv pip compile requirements/base.txt requirements/zeromq.txt requirements/crypto.txt requirements/windows.txt requirements/static/pkg/windows.in --python-platform=windows --python-version=3.9 --constraint requirements/constraints.txt --no-emit-index-url -o=requirements/static/pkg/py3.9/windows.txt aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.13.3 diff --git a/requirements/windows.txt b/requirements/windows.txt index cf7f176b9f90..1d2e42732642 100644 --- a/requirements/windows.txt +++ b/requirements/windows.txt @@ -1,5 +1,3 @@ # Windows source distribution requirements # Don't add any requirements here, add them in requirements/base.txt # If they are windows specific, place "; sys_platform == 'win32'" in front of the requirement. - --r zeromq.txt diff --git a/salt/beacons/__init__.py b/salt/beacons/__init__.py index 75b442ac7d93..8eb16311c356 100644 --- a/salt/beacons/__init__.py +++ b/salt/beacons/__init__.py @@ -26,6 +26,47 @@ def __init__(self, opts, functions, interval_map=None): self.beacons = salt.loader.beacons(opts, functions) self.interval_map = interval_map or dict() + def close_beacons(self): + """ + Close all beacon modules that have a close function. + This ensures resources like inotify file descriptors are properly + released when beacons are refreshed or the Beacon instance is replaced. + + See: https://github.com/saltstack/salt/issues/66449 + See: https://github.com/saltstack/salt/issues/58907 + """ + beacons = self._get_beacons() + for mod in beacons: + if mod == "enabled": + continue + + current_beacon_config = None + if isinstance(beacons[mod], list): + current_beacon_config = {} + list(map(current_beacon_config.update, beacons[mod])) + elif isinstance(beacons[mod], dict): + current_beacon_config = beacons[mod] + + if current_beacon_config is None: + continue + + beacon_name = None + if self._determine_beacon_config(current_beacon_config, "beacon_module"): + beacon_name = current_beacon_config["beacon_module"] + else: + beacon_name = mod + + close_str = f"{beacon_name}.close" + if close_str in self.beacons: + try: + config = copy.deepcopy(beacons[mod]) + if isinstance(config, list): + config.append({"_beacon_name": mod}) + log.debug("Closing beacon %s", mod) + self.beacons[close_str](config) + except Exception: # pylint: disable=broad-except + log.debug("Failed to close beacon %s", mod, exc_info=True) + def process(self, config, grains): """ Process the configured beacons diff --git a/salt/cli/call.py b/salt/cli/call.py index 932dc61681b0..81a7113f0dff 100644 --- a/salt/cli/call.py +++ b/salt/cli/call.py @@ -3,6 +3,7 @@ import salt.cli.caller import salt.defaults.exitcodes import salt.utils.parsers +import salt.utils.verify from salt.config import _expand_glob_path @@ -37,6 +38,44 @@ def run(self): if self.options.master: self.config["master"] = self.options.master + if self.config["verify_env"]: + # When --priv is used, MergeConfigMixIn has already overwritten + # config["user"] with the --priv value during parse_args(). We need + # the user that is actually *configured* in the config files so that + # verify_env chowns directories to the right owner (e.g. 'salt') rather + # than the temporary execution-time override (e.g. 'root'). + if self.options.user: + import salt.config as _salt_config + + _raw_opts = _salt_config.minion_config( + self.get_config_file_path(), cache_minion_id=False + ) + _verify_user = _raw_opts.get("user", "root") + else: + _verify_user = self.config["user"] + + salt.utils.verify.verify_env( + [ + self.config["pki_dir"], + self.config["cachedir"], + self.config["extension_modules"], + ], + _verify_user, + permissive=self.config["permissive_pki_access"], + pki_dir=self.config["pki_dir"], + ) + + # config["user"] is already set to the --priv value by MergeConfigMixIn. + # The explicit assignment below is kept for clarity and backward compatibility + # in case MergeConfigMixIn behaviour ever changes. + if self.options.user: + self.config["user"] = self.options.user + + # Validate the execution user exists + if self.config["user"] != salt.utils.user.get_user(): + if not salt.utils.verify.check_user(self.config["user"]): + self.exit(salt.defaults.exitcodes.EX_NOUSER) + caller = salt.cli.caller.Caller.factory(self.config) if self.options.doc: diff --git a/salt/cli/daemons.py b/salt/cli/daemons.py index a791e81f6dd6..0413aa77dcad 100644 --- a/salt/cli/daemons.py +++ b/salt/cli/daemons.py @@ -285,6 +285,9 @@ def prepare(self): v_dirs = [ self.config["pki_dir"], self.config["cachedir"], + os.path.join( + self.config["cachedir"], "proc" + ), # Ensure proc dir is created before privilege drop self.config["sock_dir"], self.config["extension_modules"], confd, @@ -476,6 +479,9 @@ def prepare(self): v_dirs = [ self.config["pki_dir"], self.config["cachedir"], + os.path.join( + self.config["cachedir"], "proc" + ), # Ensure proc dir is created before privilege drop self.config["sock_dir"], self.config["extension_modules"], confd, diff --git a/salt/client/__init__.py b/salt/client/__init__.py index c69bedc21e11..e7409f331329 100644 --- a/salt/client/__init__.py +++ b/salt/client/__init__.py @@ -391,7 +391,7 @@ def run_job( tgt_type, ret, jid=jid, - timeout=self._get_timeout(timeout), + timeout=self._get_timeout(timeout) if timeout is not None else None, listen=listen, **kwargs, ) @@ -455,7 +455,7 @@ def run_job_async( tgt_type, ret, jid=jid, - timeout=self._get_timeout(timeout), + timeout=self._get_timeout(timeout) if timeout is not None else None, io_loop=io_loop, listen=listen, **kwargs, @@ -1851,7 +1851,7 @@ def pub( tgt_type="glob", ret="", jid="", - timeout=15, + timeout=None, listen=False, **kwargs, ): @@ -1876,6 +1876,9 @@ def pub( minions: A set, the targets that the tgt passed should match. """ + if timeout is None: + timeout = self.opts.get("publish_timeout", 30) + # Make sure the publisher is running by checking the unix socket if self.opts.get("ipc_mode", "") != "tcp" and not os.path.exists( os.path.join(self.opts["sock_dir"], "publish_pull.ipc") @@ -1952,7 +1955,7 @@ def pub_async( tgt_type="glob", ret="", jid="", - timeout=15, + timeout=None, io_loop=None, listen=True, **kwargs, @@ -1978,6 +1981,9 @@ def pub_async( minions: A set, the targets that the tgt passed should match. """ + if timeout is None: + timeout = self.opts.get("publish_timeout", 30) + # Make sure the publisher is running by checking the unix socket if self.opts.get("ipc_mode", "") != "tcp" and not os.path.exists( os.path.join(self.opts["sock_dir"], "publish_pull.ipc") diff --git a/salt/client/ssh/client.py b/salt/client/ssh/client.py index 8727ce23c3c2..5d76611edd83 100644 --- a/salt/client/ssh/client.py +++ b/salt/client/ssh/client.py @@ -138,7 +138,7 @@ def cmd_iter( tgt_type="glob", ret="", kwarg=None, - **kwargs + **kwargs, ): """ Execute a single command via the salt-ssh subsystem and return a @@ -197,7 +197,7 @@ def cmd_sync(self, low): low.get("timeout"), low.get("tgt_type"), low.get("kwarg"), - **kwargs + **kwargs, ) def cmd_async(self, low, timeout=None): @@ -230,7 +230,7 @@ def cmd_subset( ret="", kwarg=None, subset=3, - **kwargs + **kwargs, ): """ Execute a command on a random subset of the targeted systems diff --git a/salt/client/ssh/wrapper/cmdmod.py b/salt/client/ssh/wrapper/cmdmod.py index e74079bfcbcc..6a94ba69c5ea 100644 --- a/salt/client/ssh/wrapper/cmdmod.py +++ b/salt/client/ssh/wrapper/cmdmod.py @@ -51,7 +51,7 @@ def script( success_retcodes=None, success_stdout=None, success_stderr=None, - **kwargs + **kwargs, ): """ Download a script from a remote location and execute the script locally. @@ -306,7 +306,7 @@ def _cleanup_tempfile(path): success_stdout=success_stdout, success_stderr=success_stderr, hide_output=hide_output, - **kwargs + **kwargs, ) finally: _cleanup_tempfile(path) @@ -335,7 +335,7 @@ def script_retcode( success_retcodes=None, success_stdout=None, success_stderr=None, - **kwargs + **kwargs, ): """ Download a script from a remote location and execute the script locally. @@ -514,7 +514,7 @@ def script_retcode( success_retcodes=success_retcodes, success_stdout=success_stdout, success_stderr=success_stderr, - **kwargs + **kwargs, )["retcode"] diff --git a/salt/config/__init__.py b/salt/config/__init__.py index 1a5068328e86..d04849891cc8 100644 --- a/salt/config/__init__.py +++ b/salt/config/__init__.py @@ -1345,6 +1345,7 @@ def _gather_buffer_space(): "sock_pool_size": 1, "ret_port": 4506, "timeout": 5, + "publish_timeout": 30, "keep_jobs": 24, "keep_jobs_seconds": 86400, "archive_jobs": False, @@ -2302,6 +2303,14 @@ def prepend_root_dir(opts, path_options): def insert_system_path(opts, paths): """ Inserts path into python path taking into consideration 'root_dir' option. + + Paths are appended rather than prepended so that stdlib modules are never + shadowed by extension module directories (e.g. extmods/utils/). In Python + 3.14+ the ``forkserver`` start method spawns child processes with a fresh + interpreter and passes the parent's ``sys.path`` via preparation_data. If + an extmods directory sits before the stdlib entries it can accidentally + shadow stdlib modules (e.g. ``platform``, ``functools``), triggering + circular imports that crash the child. """ if isinstance(paths, str): paths = [paths] @@ -2309,7 +2318,7 @@ def insert_system_path(opts, paths): path_options = {"path": path, "root_dir": opts["root_dir"]} prepend_root_dir(path_options, path_options) if os.path.isdir(path_options["path"]) and path_options["path"] not in sys.path: - sys.path.insert(0, path_options["path"]) + sys.path.append(path_options["path"]) def minion_config( diff --git a/salt/executors/sudo.py b/salt/executors/sudo.py index 799e77f54868..737c08d06f5e 100644 --- a/salt/executors/sudo.py +++ b/salt/executors/sudo.py @@ -51,6 +51,8 @@ def execute(opts, data, func, args, kwargs): "-u", opts.get("sudo_user"), "salt-call", + "--priv", + opts.get("sudo_user"), "--out", "json", "--metadata", diff --git a/salt/matchers/pillar_match.py b/salt/matchers/pillar_match.py index 87b0df606baf..5c83742c5235 100644 --- a/salt/matchers/pillar_match.py +++ b/salt/matchers/pillar_match.py @@ -16,11 +16,13 @@ def match(tgt, delimiter=DEFAULT_TARGET_DELIM, opts=None, minion_id=None): """ if not opts: opts = __opts__ + log.debug("pillar target: %s", tgt) if delimiter not in tgt: log.error("Got insufficient arguments for pillar match statement from master") return False + pillar = {} if "pillar" in opts: pillar = opts["pillar"] elif "ext_pillar" in opts: diff --git a/salt/minion.py b/salt/minion.py index d6bb8870a831..2230c6869cbe 100644 --- a/salt/minion.py +++ b/salt/minion.py @@ -3203,6 +3203,10 @@ def beacons_refresh(self): prev_interval_map = {} if hasattr(self, "beacons") and hasattr(self.beacons, "interval_map"): prev_interval_map = self.beacons.interval_map + # Close existing beacon modules to release resources (e.g. inotify fds) + # before replacing the Beacon instance. + if hasattr(self, "beacons"): + self.beacons.close_beacons() self.beacons = salt.beacons.Beacon( self.opts, self.functions, interval_map=prev_interval_map ) diff --git a/salt/modules/boto_efs.py b/salt/modules/boto_efs.py index 800aa7977f6a..ac582963ff5c 100644 --- a/salt/modules/boto_efs.py +++ b/salt/modules/boto_efs.py @@ -116,7 +116,7 @@ def create_file_system( profile=None, region=None, creation_token=None, - **kwargs + **kwargs, ): """ Creates a new, empty file system. @@ -171,7 +171,7 @@ def create_mount_target( key=None, profile=None, region=None, - **kwargs + **kwargs, ): """ Creates a mount target for a file system. @@ -351,7 +351,7 @@ def get_file_systems( profile=None, region=None, creation_token=None, - **kwargs + **kwargs, ): """ Get all EFS properties or a specific instance property @@ -409,7 +409,7 @@ def get_mount_targets( key=None, profile=None, region=None, - **kwargs + **kwargs, ): """ Get all the EFS mount point properties for a specific filesystemid or @@ -488,7 +488,7 @@ def set_security_groups( key=None, profile=None, region=None, - **kwargs + **kwargs, ): """ Modifies the set of security groups in effect for a mount target diff --git a/salt/modules/cmdmod.py b/salt/modules/cmdmod.py index 5f6a844fe6f6..12f6f37d7614 100644 --- a/salt/modules/cmdmod.py +++ b/salt/modules/cmdmod.py @@ -374,7 +374,7 @@ def _run( # when run from sudo or another environment where the euid is # changed ~ will expand to the home of the original uid and # the euid might not have access to it. See issue #1844 - if not os.access(cwd, os.R_OK): + if not os.access(cwd, os.R_OK) or not os.path.isdir(cwd): cwd = "/" if salt.utils.platform.is_windows(): cwd = os.path.abspath(os.sep) diff --git a/salt/modules/dummyproxy_pkg.py b/salt/modules/dummyproxy_pkg.py index c1f07e443985..2cc904ed4113 100644 --- a/salt/modules/dummyproxy_pkg.py +++ b/salt/modules/dummyproxy_pkg.py @@ -88,7 +88,7 @@ def installed( skip_verify=False, pkgs=None, sources=None, - **kwargs + **kwargs, ): p = __proxy__["dummy.package_status"](name) diff --git a/salt/modules/event.py b/salt/modules/event.py index 29a5323b1b0f..9f7773e4f676 100644 --- a/salt/modules/event.py +++ b/salt/modules/event.py @@ -129,7 +129,7 @@ def send( with_grains=False, with_pillar=False, with_env_opts=False, - **kwargs + **kwargs, ): """ Send an event to the Salt Master diff --git a/salt/modules/jira_mod.py b/salt/modules/jira_mod.py index 519e4249cc1f..c20f6e2476e7 100644 --- a/salt/modules/jira_mod.py +++ b/salt/modules/jira_mod.py @@ -86,7 +86,7 @@ def create_issue( server=None, username=None, password=None, - **kwargs + **kwargs, ): """ Create a JIRA issue using the named settings. Return the JIRA ticket ID. diff --git a/salt/modules/libcloud_storage.py b/salt/modules/libcloud_storage.py index 51fc04b55120..993403ef343e 100644 --- a/salt/modules/libcloud_storage.py +++ b/salt/modules/libcloud_storage.py @@ -238,7 +238,7 @@ def download_object( profile, overwrite_existing=False, delete_on_failure=True, - **libcloud_kwargs + **libcloud_kwargs, ): """ Download an object to the specified destination path. @@ -295,7 +295,7 @@ def upload_object( extra=None, verify_hash=True, headers=None, - **libcloud_kwargs + **libcloud_kwargs, ): """ Upload an object currently located on a disk. @@ -346,7 +346,7 @@ def upload_object( extra, verify_hash, headers, - **libcloud_kwargs + **libcloud_kwargs, ) return obj.name diff --git a/salt/modules/localemod.py b/salt/modules/localemod.py index 636f6d0db973..a4703978637e 100644 --- a/salt/modules/localemod.py +++ b/salt/modules/localemod.py @@ -107,6 +107,9 @@ def _localectl_set(locale=""): """ Use systemd's localectl command to set the LANG locale parameter, making sure not to trample on other params that have been set. + + Falls back to writing /etc/locale.conf directly when localectl set-locale + fails (e.g., when systemd-localed is not running in a container). """ locale_params = ( _parse_dbus_locale() @@ -115,9 +118,24 @@ def _localectl_set(locale=""): ) locale_params["LANG"] = str(locale) args = " ".join([f'{k}="{v}"' for k, v in locale_params.items() if v is not None]) - return not __salt__["cmd.retcode"]( - f"localectl set-locale {args}", python_shell=False + if not __salt__["cmd.retcode"](f"localectl set-locale {args}", python_shell=False): + return True + + # localectl set-locale failed (e.g., systemd-localed is not running in a + # container environment where D-Bus write access is unavailable). Write + # /etc/locale.conf directly; modern localectl status reads from that file + # without D-Bus, so get_locale() will see the change immediately. + log.debug("localectl set-locale failed; writing /etc/locale.conf directly") + locale_conf = "/etc/locale.conf" + if not __salt__["file.file_exists"](locale_conf): + __salt__["file.touch"](locale_conf) + __salt__["file.replace"]( + locale_conf, + "^LANG=.*", + f"LANG={locale}", + append_if_not_found=True, ) + return True def list_avail(): diff --git a/salt/modules/namecheap_ssl.py b/salt/modules/namecheap_ssl.py index 3d95b1ba0e1b..c760856c863a 100644 --- a/salt/modules/namecheap_ssl.py +++ b/salt/modules/namecheap_ssl.py @@ -57,7 +57,7 @@ def reissue( web_server_type, approver_email=None, http_dc_validation=False, - **kwargs + **kwargs, ): """ Reissues a purchased SSL certificate. Returns a dictionary of result @@ -140,7 +140,7 @@ def activate( web_server_type, approver_email=None, http_dc_validation=False, - **kwargs + **kwargs, ): """ Activates a newly-purchased SSL certificate. Returns a dictionary of result diff --git a/salt/modules/napalm_bgp.py b/salt/modules/napalm_bgp.py index b7721397bd90..2e6bdb0881a5 100644 --- a/salt/modules/napalm_bgp.py +++ b/salt/modules/napalm_bgp.py @@ -161,7 +161,7 @@ def config(group=None, neighbor=None, **kwargs): return salt.utils.napalm.call( napalm_device, # pylint: disable=undefined-variable "get_bgp_config", - **{"group": group, "neighbor": neighbor} + **{"group": group, "neighbor": neighbor}, ) @@ -268,5 +268,5 @@ def neighbors(neighbor=None, **kwargs): return salt.utils.napalm.call( napalm_device, # pylint: disable=undefined-variable "get_bgp_neighbors_detail", - **{"neighbor_address": neighbor} + **{"neighbor_address": neighbor}, ) diff --git a/salt/modules/napalm_netacl.py b/salt/modules/napalm_netacl.py index b90eea757401..40f5768beb89 100644 --- a/salt/modules/napalm_netacl.py +++ b/salt/modules/napalm_netacl.py @@ -134,7 +134,7 @@ def load_term_config( debug=False, source_service=None, destination_service=None, - **term_fields + **term_fields, ): """ Generate and load the configuration of a policy term. @@ -450,7 +450,7 @@ def load_term_config( revision_date_format=revision_date_format, source_service=source_service, destination_service=destination_service, - **term_fields + **term_fields, ) # pylint: disable=undefined-variable return __salt__["net.load_config"]( @@ -481,7 +481,7 @@ def load_filter_config( test=False, commit=True, debug=False, - **kwargs + **kwargs, ): # pylint: disable=unused-argument """ Generate and load the configuration of a policy filter. @@ -701,7 +701,7 @@ def load_policy_config( test=False, commit=True, debug=False, - **kwargs + **kwargs, ): # pylint: disable=unused-argument """ Generate and load the configuration of the whole policy. diff --git a/salt/modules/napalm_route.py b/salt/modules/napalm_route.py index 1f88345c4fdf..60746568dcc2 100644 --- a/salt/modules/napalm_route.py +++ b/salt/modules/napalm_route.py @@ -150,5 +150,5 @@ def show(destination, protocol=None, **kwargs): # pylint: disable=unused-argume return salt.utils.napalm.call( napalm_device, # pylint: disable=undefined-variable "get_route_to", - **{"destination": destination, "protocol": protocol} + **{"destination": destination, "protocol": protocol}, ) diff --git a/salt/modules/napalm_snmp.py b/salt/modules/napalm_snmp.py index 2e72c03fa69d..ee6ca24b38ba 100644 --- a/salt/modules/napalm_snmp.py +++ b/salt/modules/napalm_snmp.py @@ -74,7 +74,7 @@ def config(**kwargs): # pylint: disable=unused-argument return salt.utils.napalm.call( napalm_device, # pylint: disable=undefined-variable "get_snmp_information", - **{} + **{}, ) @@ -86,7 +86,7 @@ def remove_config( location=None, test=False, commit=True, - **kwargs + **kwargs, ): # pylint: disable=unused-argument """ Removes a configuration element from the SNMP configuration. @@ -152,7 +152,7 @@ def update_config( location=None, test=False, commit=True, - **kwargs + **kwargs, ): # pylint: disable=unused-argument """ Updates the SNMP configuration. diff --git a/salt/modules/network.py b/salt/modules/network.py index efc8a7a3617c..4c189afd45e1 100644 --- a/salt/modules/network.py +++ b/salt/modules/network.py @@ -937,7 +937,17 @@ def traceroute(host): """ ret = [] cmd = "traceroute {}".format(__utils__["network.sanitize_host"](host)) - out = __salt__["cmd.run"](cmd) + # Bound the wall-clock time so callers aren't blocked indefinitely when + # every hop times out (30 hops × 3 probes × 5 s = 450 s by default). + # 120 s is enough for a well-routed destination and still returns partial + # results (already-seen hops) for unreachable destinations. + out = __salt__["cmd.run"](cmd, timeout=120) + + # When cmd.run hits its timeout it returns the exception message as stdout + # rather than actual traceroute output. Detect that and bail early so the + # parser below doesn't try to interpret the error string as hop data. + if "Timed out after" in out: + return ret # Parse version of traceroute if __utils__["platform.is_sunos"]() or __utils__["platform.is_aix"](): @@ -1041,7 +1051,7 @@ def traceroute(host): # Parse anything else else: comps = line.split() - if len(comps) >= 8: + if len(comps) >= 9: result = { "count": comps[0], "hostname": comps[1], diff --git a/salt/modules/neutron.py b/salt/modules/neutron.py index 41453f4d20e4..56051ad21a47 100644 --- a/salt/modules/neutron.py +++ b/salt/modules/neutron.py @@ -1198,7 +1198,7 @@ def create_ipsec_site_connection( psk, admin_state_up=True, profile=None, - **kwargs + **kwargs, ): """ Creates a new IPsecSiteConnection @@ -1243,7 +1243,7 @@ def create_ipsec_site_connection( peer_id, psk, admin_state_up, - **kwargs + **kwargs, ) diff --git a/salt/modules/rest_pkg.py b/salt/modules/rest_pkg.py index 71f6aed17ab9..e53b79067397 100644 --- a/salt/modules/rest_pkg.py +++ b/salt/modules/rest_pkg.py @@ -81,7 +81,7 @@ def installed( skip_verify=False, pkgs=None, sources=None, - **kwargs + **kwargs, ): p = __proxy__["rest_sample.package_status"](name) diff --git a/salt/modules/scp_mod.py b/salt/modules/scp_mod.py index d7525b28c73a..9b69003b6e70 100644 --- a/salt/modules/scp_mod.py +++ b/salt/modules/scp_mod.py @@ -143,7 +143,7 @@ def put( recursive=False, preserve_times=False, saltenv="base", - **kwargs + **kwargs, ): """ Transfer files and directories to remote host. diff --git a/salt/modules/statuspage.py b/salt/modules/statuspage.py index 4670d6281515..3d7d63d2d411 100644 --- a/salt/modules/statuspage.py +++ b/salt/modules/statuspage.py @@ -135,7 +135,7 @@ def create( page_id=None, api_key=None, api_version=None, - **kwargs + **kwargs, ): """ Insert a new entry under a specific endpoint. @@ -354,7 +354,7 @@ def update( page_id=None, api_key=None, api_version=None, - **kwargs + **kwargs, ): """ Update attribute(s) of a specific endpoint. diff --git a/salt/modules/svn.py b/salt/modules/svn.py index 2b6304a9b17d..66e000d6d664 100644 --- a/salt/modules/svn.py +++ b/salt/modules/svn.py @@ -415,7 +415,7 @@ def export( username=None, password=None, revision="HEAD", - *opts + *opts, ): """ Create an unversioned copy of a tree. diff --git a/salt/modules/x509_v2.py b/salt/modules/x509_v2.py index cc457e1ad58c..ab925c824ac0 100644 --- a/salt/modules/x509_v2.py +++ b/salt/modules/x509_v2.py @@ -137,6 +137,14 @@ Breaking changes versus the previous ``x509`` modules ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +* The ``public_key`` parameter to ``x509.certificate_managed`` (and corresponding + ``x509.create_certificate``) used to accept a private key. + The new modules require an actual public key if this parameter is specified. + You can pass a private key in the ``private_key`` parameter instead. + + Failing to ensure it really is a public key you are passing as ``public_key`` fails + with ``Could not load PEM-encoded public key.``. + * The output format has changed for all ``read_*`` functions as well as the state return dict. * The formatting of some extension definitions might have changed, but should be stable for most basic use cases. @@ -300,33 +308,48 @@ def create_certificate( The hashing algorithm to use for the signature. Valid values are: sha1, sha224, sha256, sha384, sha512, sha512_224, sha512_256, sha3_224, sha3_256, sha3_384, sha3_512. Defaults to ``sha256``. - This will be ignored for ``ed25519`` and ``ed448`` key types. + Ignored for ``ed25519`` and ``ed448`` key types. private_key - The private key corresponding to the public key the certificate should - be issued for. This is one way of specifying the public key that will - be included in the certificate, the other ones being ``public_key`` and ``csr``. + A **private key**, which is used to derive the public key the certificate + is issued for. If unset, checks ``public_key`` or ``csr`` to derive it. + + Ignored when creating self-signed certificates (missing ``signing_cert``). + + .. hint:: + When ``encoding`` is ``pkcs12``, this private key is embedded into + the resulting container. private_key_passphrase If ``private_key`` is specified and encrypted, the passphrase to decrypt it. public_key - The public key the certificate should be issued for. Other ways of passing - the required information are ``private_key`` and ``csr``. If neither are set, - the public key of the ``signing_private_key`` will be included, i.e. - a self-signed certificate is generated. + A **public key**, which is used as the public key the certificate is issued for, + but only if ``private_key`` is **not** specified. + + If this is unset, checks ``csr`` to derive it. + + Ignored when creating self-signed certificates (missing ``signing_cert``). csr - A certificate signing request to use as a base for generating the certificate. - The following information will be respected, depending on configuration: - * public key - * extensions, if not otherwise specified (arguments, signing_policy) + A **certificate signing request** to use as a base for generating the certificate: + + - Extensions not otherwise specified (arguments, signing_policy) are copied. + - If ``private_key`` and ``public_key`` are both unspecified, copies the embedded + public key into the certificate. This step is skipped when creating self-signed + certificates (missing ``signing_cert``). signing_cert The CA certificate to be used for signing the issued certificate. + Leave empty to create a self-signed certificate. + signing_private_key - The private key corresponding to the public key in ``signing_cert``. Required. + The private key to be used for signing the new certificate. Required. + + Usually, this is the private key corresponding to the public key in ``signing_cert``. + When creating self-signed certificates (missing ``signing_cert``), derives + the new certificate's embedded public key from this private key. signing_private_key_passphrase If ``signing_private_key`` is encrypted, the passphrase to decrypt it. @@ -418,10 +441,9 @@ def create_certificate( .. code-block:: yaml - # mind this being a list, not a dict - subjectAltName: - - email:me@example.com - - DNS:example.com + - email:me@example.com # list items can be strings + - dns: example.com # or single-key dicts issuerAltName The syntax is the same as for ``subjectAltName``, except that the additional @@ -879,7 +901,7 @@ def create_crl( The hashing algorithm to use for the signature. Valid values are: sha1, sha224, sha256, sha384, sha512, sha512_224, sha512_256, sha3_224, sha3_256, sha3_384, sha3_512. Defaults to ``sha256``. - This will be ignored for ``ed25519`` and ``ed448`` key types. + Ignored for ``ed25519`` and ``ed448`` key types. encoding Specify the encoding of the resulting certificate revocation list. @@ -1092,7 +1114,7 @@ def create_csr( The hashing algorithm to use for the signature. Valid values are: sha1, sha224, sha256, sha384, sha512, sha512_224, sha512_256, sha3_224, sha3_256, sha3_384, sha3_512. Defaults to ``sha256``. - This will be ignored for ``ed25519`` and ``ed448`` key types. + Ignored for ``ed25519`` and ``ed448`` key types. encoding Specify the encoding of the resulting certificate signing request. diff --git a/salt/pillar/__init__.py b/salt/pillar/__init__.py index ca7f4bca5eff..cbc3f08e5df1 100644 --- a/salt/pillar/__init__.py +++ b/salt/pillar/__init__.py @@ -1246,27 +1246,32 @@ def compile_pillar(self, ext=True): """ Render the pillar data and return """ - top, top_errors = self.get_top() - if ext: - if self.opts.get("ext_pillar_first", False): - self.opts["pillar"], errors = self.ext_pillar(self.pillar_override) - self.rend = salt.loader.render(self.opts, self.functions) - matches = self.top_matches(top, reload=True) - pillar, errors = self.render_pillar(matches, errors=errors) - pillar = merge( - self.opts["pillar"], - pillar, - self.merge_strategy, - self.opts.get("renderer", "yaml"), - self.opts.get("pillar_merge_lists", False), - ) - else: + if ext and self.opts.get("ext_pillar_first", False): + self.opts["pillar"], errors = self.ext_pillar(self.pillar_override) + if hasattr(self.functions, "pack"): + self.functions.pack["__pillar__"] = self.opts["pillar"] + if hasattr(self.rend, "_dict") and hasattr(self.rend._dict, "pack"): + self.rend._dict.pack["__pillar__"] = self.opts["pillar"] + self.matchers = salt.loader.matchers(self.opts) + top, top_errors = self.get_top() + matches = self.top_matches(top) + pillar, errors = self.render_pillar(matches, errors=errors) + pillar = merge( + self.opts["pillar"], + pillar, + self.merge_strategy, + self.opts.get("renderer", "yaml"), + self.opts.get("pillar_merge_lists", False), + ) + else: + top, top_errors = self.get_top() + if ext: matches = self.top_matches(top) pillar, errors = self.render_pillar(matches) pillar, errors = self.ext_pillar(pillar, errors=errors) - else: - matches = self.top_matches(top) - pillar, errors = self.render_pillar(matches) + else: + matches = self.top_matches(top) + pillar, errors = self.render_pillar(matches) errors.extend(top_errors) if self.opts.get("pillar_opts", False): mopts = dict(self.opts) diff --git a/salt/renderers/mako.py b/salt/renderers/mako.py index 9032542f8654..a259ea5a6b13 100644 --- a/salt/renderers/mako.py +++ b/salt/renderers/mako.py @@ -34,7 +34,7 @@ def render(template_file, saltenv="base", sls="", context=None, tmplpath=None, * sls=sls, context=context, tmplpath=tmplpath, - **kws + **kws, ) if not tmp_data.get("result", False): raise SaltRenderError( diff --git a/salt/renderers/pydsl.py b/salt/renderers/pydsl.py index 996bbb138489..7ff145bfd8d3 100644 --- a/salt/renderers/pydsl.py +++ b/salt/renderers/pydsl.py @@ -370,7 +370,7 @@ def render(template, saltenv="base", sls="", tmplpath=None, rendered_sls=None, * __env__=saltenv, __sls__=sls, __file__=tmplpath, - **kws + **kws, ) dsl_sls.get_render_stack().append(dsl_sls) diff --git a/salt/renderers/wempy.py b/salt/renderers/wempy.py index 66976f6b3835..9109cfe42928 100644 --- a/salt/renderers/wempy.py +++ b/salt/renderers/wempy.py @@ -20,7 +20,7 @@ def render(template_file, saltenv="base", sls="", argline="", context=None, **kw saltenv=saltenv, sls=sls, context=context, - **kws + **kws, ) if not tmp_data.get("result", False): raise SaltRenderError( diff --git a/salt/roster/flat.py b/salt/roster/flat.py index 599deaaf9455..d33a342489a2 100644 --- a/salt/roster/flat.py +++ b/salt/roster/flat.py @@ -27,7 +27,7 @@ def targets(tgt, tgt_type="glob", **kwargs): __opts__["renderer_blacklist"], __opts__["renderer_whitelist"], mask_value="*passw*", - **kwargs + **kwargs, ) conditioned_raw = {} for minion in raw: diff --git a/salt/runners/cloud.py b/salt/runners/cloud.py index 03dff733eba1..c4d2b8b4bf54 100644 --- a/salt/runners/cloud.py +++ b/salt/runners/cloud.py @@ -140,7 +140,7 @@ def action( provider=None, instance=None, opts=None, - **kwargs + **kwargs, ): """ Execute a single action on the given map/provider/instance diff --git a/salt/scripts.py b/salt/scripts.py index 9808f7824c11..06a24160b419 100644 --- a/salt/scripts.py +++ b/salt/scripts.py @@ -611,10 +611,14 @@ def _get_onedir_env_path(): return None -def salt_pip(): +def salt_pip(config_dir=None): """ Proxy to current python's pip """ + import salt.config + import salt.utils.user + import salt.utils.verify + relenv_path = _get_onedir_env_path() if relenv_path is None: print( @@ -626,6 +630,26 @@ def salt_pip(): sys.exit(salt.defaults.exitcodes.EX_GENERIC) else: extras = str(relenv_path / "extras-{}.{}".format(*sys.version_info)) + + # Use provided config_dir, or SALT_CONFIG_DIR env var, or SALT_MINION_CONFIG env var, or fall back to default location + if config_dir: + config_file = os.path.join(config_dir, "minion") + elif os.environ.get("SALT_CONFIG_DIR"): + salt_config_dir = os.environ.get("SALT_CONFIG_DIR") + config_file = os.path.join(salt_config_dir, "minion") + elif os.environ.get("SALT_MINION_CONFIG"): + config_file = os.environ.get("SALT_MINION_CONFIG") + else: + config_file = salt.config.DEFAULT_MINION_OPTS["conf_file"] + opts = salt.config.minion_config(config_file) + + user = opts.get("user") + current_user = salt.utils.user.get_user() + + # Switch to the configured user if it's not root + if user and user != "root" and user != current_user: + salt.utils.verify.check_user(user) + env = _pip_environment(os.environ.copy(), extras) args = _pip_args(sys.argv[1:], extras) command = [ diff --git a/salt/sdb/rest.py b/salt/sdb/rest.py index dfcb980331ba..35f31b608978 100644 --- a/salt/sdb/rest.py +++ b/salt/sdb/rest.py @@ -111,7 +111,7 @@ def query(key, value=None, service=None, profile=None): # pylint: disable=W0613 blacklist, whitelist, input_data=profile[key]["url"], - **key_vars + **key_vars, ) extras = {} diff --git a/salt/serializers/python.py b/salt/serializers/python.py index f105601d0671..1f44eedb85da 100644 --- a/salt/serializers/python.py +++ b/salt/serializers/python.py @@ -38,5 +38,5 @@ def serialize(obj, **options): salt.utils.json.loads( salt.utils.json.dumps(obj, _json_module=_json), _json_module=_json ), - **options + **options, ) diff --git a/salt/states/event.py b/salt/states/event.py index 759bd16dd83d..a8e8d3cf3625 100644 --- a/salt/states/event.py +++ b/salt/states/event.py @@ -13,7 +13,7 @@ def send( with_grains=False, with_pillar=False, show_changed=True, - **kwargs + **kwargs, ): """ Send an event to the Salt Master @@ -58,7 +58,7 @@ def send( with_env=with_env, with_grains=with_grains, with_pillar=with_pillar, - **kwargs + **kwargs, ) ret["comment"] = "Event fired" diff --git a/salt/states/libcloud_loadbalancer.py b/salt/states/libcloud_loadbalancer.py index b10bb0b854cb..1a60b8a5e748 100644 --- a/salt/states/libcloud_loadbalancer.py +++ b/salt/states/libcloud_loadbalancer.py @@ -104,7 +104,7 @@ def balancer_present( profile, algorithm=algorithm, members=starting_members, - **libcloud_kwargs + **libcloud_kwargs, ) return state_result(True, "Created new load balancer", name, balancer) diff --git a/salt/states/net_napalm_yang.py b/salt/states/net_napalm_yang.py index e96b6e02fadf..01dcb9e57a46 100644 --- a/salt/states/net_napalm_yang.py +++ b/salt/states/net_napalm_yang.py @@ -191,7 +191,7 @@ def managed(name, data, **kwargs): test=test, debug=debug, commit=commit, - replace=replace + replace=replace, ) log.debug("Loaded config result:") log.debug(loaded_changes) @@ -294,6 +294,6 @@ def configured(name, data, **kwargs): test=test, debug=debug, commit=commit, - replace=replace + replace=replace, ) return salt.utils.napalm.loaded_ret(ret, loaded_changes, test, debug) diff --git a/salt/states/netacl.py b/salt/states/netacl.py index 1c3364ee0579..707a328e23c9 100644 --- a/salt/states/netacl.py +++ b/salt/states/netacl.py @@ -107,7 +107,7 @@ def term( debug=False, source_service=None, destination_service=None, - **term_fields + **term_fields, ): """ Manage the configuration of a specific policy term. @@ -443,7 +443,7 @@ def term( test=test, commit=commit, debug=debug, - **term_fields + **term_fields, ) return salt.utils.napalm.loaded_ret(ret, loaded, test, debug) diff --git a/salt/states/pip_state.py b/salt/states/pip_state.py index 689695aa56c0..a3b45d879a70 100644 --- a/salt/states/pip_state.py +++ b/salt/states/pip_state.py @@ -141,14 +141,32 @@ def _fulfills_version_spec(version, version_spec): boolean value based on whether or not the version number meets the specified version. """ - for oper, spec in version_spec: - if oper is None: - continue - if not salt.utils.versions.compare( - ver1=version, oper=oper, ver2=spec, cmp_func=_pep440_version_cmp - ): - return False - return True + try: + from packaging.specifiers import InvalidSpecifier, SpecifierSet + from packaging.version import InvalidVersion + + # Build a SpecifierSet string from the version_spec list of tuples + specs = [] + for oper, spec in version_spec: + if oper is not None: + specs.append(f"{oper}{spec}") + + if not specs: + return True + + spec_set = SpecifierSet(",".join(specs)) + return spec_set.contains(version) + except (ImportError, InvalidVersion, InvalidSpecifier): + # Fallback to the old logic if packaging is not available + # or if the version/spec is not PEP 440 compliant + for oper, spec in version_spec: + if oper is None: + continue + if not salt.utils.versions.compare( + ver1=version, oper=oper, ver2=spec, cmp_func=_pep440_version_cmp + ): + return False + return True def _check_pkg_version_format(pkg): @@ -352,7 +370,7 @@ def normalize(x): if salt.utils.versions.Version(pkg1) > salt.utils.versions.Version(pkg2): return 1 except Exception as exc: # pylint: disable=broad-except - logger.exception( + logger.debug( 'Comparison of package versions "%s" and "%s" failed: %s', pkg1, pkg2, exc ) return None diff --git a/salt/states/syslog_ng.py b/salt/states/syslog_ng.py index af8f26af069f..f8b11cae27e6 100644 --- a/salt/states/syslog_ng.py +++ b/salt/states/syslog_ng.py @@ -86,7 +86,7 @@ def started( control=None, worker_threads=None, *args, - **kwargs + **kwargs, ): """ Ensures, that syslog-ng is started via the given parameters. diff --git a/salt/states/x509_v2.py b/salt/states/x509_v2.py index 8b8af735b303..c424563ccb02 100644 --- a/salt/states/x509_v2.py +++ b/salt/states/x509_v2.py @@ -188,6 +188,7 @@ from datetime import datetime, timedelta, timezone import salt.utils.files +import salt.utils.platform from salt.exceptions import CommandExecutionError, SaltInvocationError from salt.state import STATE_INTERNAL_KEYWORDS as _STATE_INTERNAL_KEYWORDS @@ -227,8 +228,6 @@ def certificate_managed( signing_policy=None, encoding="pem", append_certs=None, - copypath=None, - prepend_cn=False, digest="sha256", signing_private_key=None, signing_private_key_passphrase=None, @@ -251,7 +250,7 @@ def certificate_managed( Ensure an X.509 certificate is present as specified. This function accepts the same arguments as :py:func:`x509.create_certificate `, - as well as most ones for `:py:func:`file.managed `. + as well as most ones for :py:func:`file.managed `. name The path the certificate should be present at. @@ -297,37 +296,49 @@ def certificate_managed( The hashing algorithm to use for the signature. Valid values are: sha1, sha224, sha256, sha384, sha512, sha512_224, sha512_256, sha3_224, sha3_256, sha3_384, sha3_512. Defaults to ``sha256``. - This will be ignored for ``ed25519`` and ``ed448`` key types. + Ignored for ``ed25519`` and ``ed448`` key types. + + signing_cert + The CA certificate to be used for signing the issued certificate. + + Leave empty to create a self-signed certificate. signing_private_key - The private key corresponding to the public key in ``signing_cert``. Required. + The private key to be used for signing the new certificate. Required. + + Usually, this is the private key corresponding to the public key in ``signing_cert``. + When creating self-signed certificates (missing ``signing_cert``), derives + the new certificate's embedded public key from this private key. signing_private_key_passphrase If ``signing_private_key`` is encrypted, the passphrase to decrypt it. - signing_cert - The CA certificate to be used for signing the issued certificate. + private_key + A **private key**, which is used to derive the public key the certificate + is issued for. If this is unset, checks ``public_key`` or ``csr`` to derive it. - public_key - The public key the certificate should be issued for. Other ways of passing - the required information are ``private_key`` and ``csr``. If neither are set, - the public key of the ``signing_private_key`` will be included, i.e. - a self-signed certificate is generated. + Ignored when creating self-signed certificates (missing ``signing_cert``). - private_key - The private key corresponding to the public key the certificate should - be issued for. This is one way of specifying the public key that will - be included in the certificate, the other ones being ``public_key`` and ``csr``. + .. hint:: + When ``encoding`` is ``pkcs12``, this private key is embedded into + the resulting container. private_key_passphrase If ``private_key`` is specified and encrypted, the passphrase to decrypt it. + public_key + A **public key**, which is used as the public key the certificate is issued for, + but only if ``private_key`` is **not** specified. If this is unset, checks ``csr`` to derive it. + + Ignored when creating self-signed certificates (missing ``signing_cert``). + csr - A certificate signing request to use as a base for generating the certificate. - The following information will be respected, depending on configuration: + A **certificate signing request** to use as a base for generating the certificate: - * public key - * extensions, if not otherwise specified (arguments, signing_policy) + - Extensions not otherwise specified (arguments, signing_policy) are copied. + - If ``private_key`` and ``public_key`` are both unspecified, copies the embedded + public key into the certificate. This step is skipped when creating self-signed + certificates (missing ``signing_cert``). subject The subject's distinguished name embedded in the certificate. This is one way of @@ -742,7 +753,7 @@ def crl_managed( The hashing algorithm to use for the signature. Valid values are: sha1, sha224, sha256, sha384, sha512, sha512_224, sha512_256, sha3_224, sha3_256, sha3_384, sha3_512. Defaults to ``sha256``. - This will be ignored for ``ed25519`` and ``ed448`` key types. + Ignored for ``ed25519`` and ``ed448`` key types. encoding Specify the encoding of the resulting certificate revocation list. @@ -1047,7 +1058,7 @@ def csr_managed( The hashing algorithm to use for the signature. Valid values are: sha1, sha224, sha256, sha384, sha512, sha512_224, sha512_256, sha3_224, sha3_256, sha3_384, sha3_512. Defaults to ``sha256``. - This will be ignored for ``ed25519`` and ``ed448`` key types. + Ignored for ``ed25519`` and ``ed448`` key types. encoding Specify the encoding of the resulting certificate revocation list. @@ -1359,7 +1370,7 @@ def private_key_managed( if extra_args: raise SaltInvocationError(f"Unrecognized keyword arguments: {list(extra_args)}") - if not file_args.get("mode"): + if not file_args.get("mode") and not salt.utils.platform.is_windows(): # ensure secure defaults file_args["mode"] = "0400" @@ -1591,7 +1602,13 @@ def _file_managed(name, test=None, **kwargs): raise SaltInvocationError("test param can only be None or True") # work around https://github.com/saltstack/salt/issues/62590 test = test or __opts__["test"] - res = __salt__["state.single"]("file.managed", name, test=test, **kwargs) + res = __salt__["state.single"]( + "file.managed", name, test=test, concurrent=True, **kwargs + ) + if not isinstance(res, dict): + raise CommandExecutionError( + f"Failed running file.managed in x509_v2 state: {res}" + ) return res[next(iter(res))] diff --git a/salt/states/zcbuildout.py b/salt/states/zcbuildout.py index 7fd5e4907d8a..c0dbbf82fe0a 100644 --- a/salt/states/zcbuildout.py +++ b/salt/states/zcbuildout.py @@ -135,7 +135,7 @@ def installed( onlyif=None, use_vt=False, loglevel="debug", - **kwargs + **kwargs, ): """ Install buildout in a specific directory diff --git a/salt/transport/zeromq.py b/salt/transport/zeromq.py index df6d4db544ea..a7943ef1d963 100644 --- a/salt/transport/zeromq.py +++ b/salt/transport/zeromq.py @@ -815,8 +815,13 @@ def _send_recv(self, socket, _TimeoutError=tornado.gen.TimeoutError): log.trace( "The request ended with an error while sending. reconnecting." ) + # Only reconnect if the client is still active. If close() was + # already called externally (context is None), do not create a + # new socket/context that would never be cleaned up. + _should_reconnect = self.context is not None self.close() - self.connect() + if _should_reconnect: + self.connect() send_recv_running = False break @@ -862,8 +867,13 @@ def _send_recv(self, socket, _TimeoutError=tornado.gen.TimeoutError): ) else: log.trace("The request ended with an error. reconnecting.") + # Only reconnect if the client is still active. If close() was + # already called externally (context is None), do not create a + # new socket/context that would never be cleaned up. + _should_reconnect = self.context is not None self.close() - self.connect() + if _should_reconnect: + self.connect() send_recv_running = False elif received: data = salt.payload.loads(recv) diff --git a/salt/utils/dockermod/__init__.py b/salt/utils/dockermod/__init__.py index d0f504e60dcb..3600868017a2 100644 --- a/salt/utils/dockermod/__init__.py +++ b/salt/utils/dockermod/__init__.py @@ -162,7 +162,7 @@ def translate_input( skip_translate=None, ignore_collisions=False, validate_ip_addrs=True, - **kwargs + **kwargs, ): """ Translate CLI/SLS input into the format the API expects. The ``translator`` diff --git a/salt/utils/http.py b/salt/utils/http.py index fd296788f465..7eda597ba947 100644 --- a/salt/utils/http.py +++ b/salt/utils/http.py @@ -639,12 +639,13 @@ def query( except tornado.httpclient.HTTPError as exc: ret["status"] = exc.code ret["error"] = str(exc) - ret["body"], _ = _decode_result( - exc.response.body, - exc.response.headers, - backend, - decode_body=decode_body, - ) + if exc.response is not None: + ret["body"], _ = _decode_result( + exc.response.body, + exc.response.headers, + backend, + decode_body=decode_body, + ) return ret except (socket.herror, OSError, TimeoutError, socket.gaierror) as exc: if status is True: diff --git a/salt/utils/nxos_api.py b/salt/utils/nxos_api.py index 2497f7a64dde..755f94048dc9 100644 --- a/salt/utils/nxos_api.py +++ b/salt/utils/nxos_api.py @@ -117,7 +117,7 @@ def rpc(commands, method="cli", **kwargs): header_dict=headers, decode=True, decode_type="json", - **init_args + **init_args, ) if "error" in response: raise SaltException(response["error"]) diff --git a/salt/utils/openstack/neutron.py b/salt/utils/openstack/neutron.py index c6707b18bfe4..516b2532cfdf 100644 --- a/salt/utils/openstack/neutron.py +++ b/salt/utils/openstack/neutron.py @@ -86,7 +86,7 @@ def __init__( service_type="network", os_auth_plugin=None, use_keystoneauth=False, - **kwargs + **kwargs, ): """ Set up neutron credentials @@ -115,7 +115,7 @@ def __init__( service_type=service_type, os_auth_plugin=os_auth_plugin, password=password, - **kwargs + **kwargs, ) else: self._old_init( @@ -126,7 +126,7 @@ def __init__( service_type=service_type, os_auth_plugin=os_auth_plugin, password=password, - **kwargs + **kwargs, ) def _new_init( @@ -140,7 +140,7 @@ def _new_init( os_auth_plugin, auth=None, verify=True, - **kwargs + **kwargs, ): if auth is None: auth = {} @@ -179,7 +179,7 @@ def _old_init( os_auth_plugin, auth=None, verify=True, - **kwargs + **kwargs, ): self.kwargs = kwargs.copy() @@ -781,7 +781,7 @@ def create_ipsec_site_connection( peer_id, psk, admin_state_up=True, - **kwargs + **kwargs, ): """ Creates a new IPsecSiteConnection diff --git a/salt/utils/parsers.py b/salt/utils/parsers.py index c9c2ddbcd914..c39e1daec87f 100644 --- a/salt/utils/parsers.py +++ b/salt/utils/parsers.py @@ -2948,6 +2948,12 @@ def _mixin_setup(self): default=False, help="Report only those states that have changed.", ) + self.add_option( + "--priv", + dest="user", + default=None, + help="Username to run salt-call as.", + ) def _mixin_after_parsed(self): if not self.args and not self.options.grains_run and not self.options.doc: diff --git a/salt/utils/platform.py b/salt/utils/platform.py index 59a04b451bcd..5a39bee82ee1 100644 --- a/salt/utils/platform.py +++ b/salt/utils/platform.py @@ -3,6 +3,7 @@ """ import contextlib +import functools import multiprocessing import os import platform @@ -11,7 +12,36 @@ import distro -from salt.utils.decorators import memoize as real_memoize + +# Use a local wraps-based memoize rather than importing from salt.utils.decorators. +# This module is synced to the remote's extmods/utils/platform.py, and in +# Python 3.14+ (forkserver default start method) it can be accidentally +# imported as the stdlib ``platform`` module when extmods/utils/ sits at +# sys.path[0]. Importing from salt.utils.decorators in that context +# creates a circular import: +# salt.utils.decorators → salt.utils.versions → salt.version +# → import platform (ourselves!) → salt.utils.decorators (cycle) +# functools is part of the stdlib and has no such dependency. +# +# We cannot use functools.cache/lru_cache directly as the decorator because +# those produce functools._lru_cache_wrapper objects which fail +# inspect.isfunction(), causing the Salt loader to skip them when loading +# salt.utils.platform as a utils module (salt/loader/lazy.py line ~1109). +def real_memoize(func): + """Cache the result of a zero-or-more-argument function (stdlib-only, loader-safe).""" + cache = {} + _sentinel = object() + + @functools.wraps(func) + def _wrapper(*args, **kwargs): + key = (args, tuple(sorted(kwargs.items()))) + result = cache.get(key, _sentinel) + if result is _sentinel: + result = func(*args, **kwargs) + cache[key] = result + return result + + return _wrapper def linux_distribution(full_distribution_name=True): @@ -237,12 +267,20 @@ def is_aarch64(): def spawning_platform(): """ - Returns True if multiprocessing.get_start_method(allow_none=False) returns "spawn" + Returns True if the multiprocessing start method requires pickling to transfer + process state to the child. This is the case for both "spawn" and "forkserver". + + "spawn" is the default on Windows (Python >= 3.4) and macOS (Python >= 3.8). + Salt forces macOS to spawning by default on all Python versions. - This is the default for Windows Python >= 3.4 and macOS on Python >= 3.8. - Salt, however, will force macOS to spawning by default on all python versions + "forkserver" became the Linux default in Python 3.14 (via PEP 741). Like + "spawn", it transfers the Process object to the child via pickle rather than + inheriting it through a plain fork of the parent process. Salt must therefore + treat it identically: capture *args/**kwargs in __new__ so that __getstate__ + can reconstruct the object on the other side, and skip parent-inherited + logging teardown since the child starts with a clean file-descriptor table. """ - return multiprocessing.get_start_method(allow_none=False) == "spawn" + return multiprocessing.get_start_method(allow_none=False) in ("spawn", "forkserver") def get_machine_identifier(): diff --git a/tests/integration/modules/test_gem.py b/tests/integration/modules/test_gem.py index 7ddb0a12a1d9..b12517164af4 100644 --- a/tests/integration/modules/test_gem.py +++ b/tests/integration/modules/test_gem.py @@ -37,7 +37,7 @@ def setUp(self): self.GEM_VER = "1.1.2" self.OLD_GEM = "brass" self.OLD_VERSION = "1.0.0" - self.NEW_VERSION = "1.2.1" + self.NEW_VERSION = "1.3.0" self.GEM_LIST = [self.GEM, self.OLD_GEM] for name in ( "GEM", @@ -56,6 +56,15 @@ def uninstall_gem(): self.addCleanup(uninstall_gem) + def uninstall_old_gem(): + if self.run_function("gem.list", [self.OLD_GEM]): + self.run_function("gem.uninstall", [self.OLD_GEM]) + + # Ensure OLD_GEM is not installed before the test (handles leftover state + # from a previously failed run that skipped its own cleanup). + uninstall_old_gem() + self.addCleanup(uninstall_old_gem) + def run_function(self, function, *args, **kwargs): """Override run_function to use the gem binary""" kwargs["gem_bin"] = self.GEM_BIN @@ -143,7 +152,21 @@ def test_update(self): self.run_function("gem.update", [self.OLD_GEM]) gem_list = self.run_function("gem.list", [self.OLD_GEM]) - self.assertEqual({self.OLD_GEM: [self.NEW_VERSION, self.OLD_VERSION]}, gem_list) + installed_versions = gem_list.get(self.OLD_GEM, []) + + if installed_versions == [self.OLD_VERSION]: + # gem update may be unable to install a newer version when the + # only available release requires a Ruby version not present on + # this system (e.g. brass >= 1.3.0 requires Ruby >= 3.1). + self.skipTest( + "gem update did not install a newer version of {}; the " + "latest release may require a newer Ruby version".format(self.OLD_GEM) + ) + + self.assertEqual( + {self.OLD_GEM: [self.NEW_VERSION, self.OLD_VERSION]}, + gem_list, + ) self.run_function("gem.uninstall", [self.OLD_GEM]) self.assertFalse(self.run_function("gem.list", [self.OLD_GEM])) diff --git a/tests/integration/modules/test_localemod.py b/tests/integration/modules/test_localemod.py index 5a59e84e49a8..0a3507c32791 100644 --- a/tests/integration/modules/test_localemod.py +++ b/tests/integration/modules/test_localemod.py @@ -11,8 +11,16 @@ def _check_systemctl(): if not salt.utils.platform.is_linux(): _check_systemctl.memo = False else: - proc = subprocess.run(["localectl"], capture_output=True, check=False) - _check_systemctl.memo = b"No such file or directory" in proc.stderr + try: + proc = subprocess.run(["localectl"], capture_output=True, check=False) + _check_systemctl.memo = ( + b"No such file or directory" in proc.stderr + or b"Connection refused" in proc.stderr + or b"Failed to connect to bus" in proc.stderr + or b"Failed to get D-Bus connection" in proc.stderr + ) + except FileNotFoundError: + _check_systemctl.memo = True return _check_systemctl.memo diff --git a/tests/pytests/functional/modules/test_network.py b/tests/pytests/functional/modules/test_network.py index a05006bccd7b..7a959d3977e1 100644 --- a/tests/pytests/functional/modules/test_network.py +++ b/tests/pytests/functional/modules/test_network.py @@ -57,6 +57,7 @@ def test_network_netstat(network): @pytest.mark.skip_if_binaries_missing("traceroute") @pytest.mark.slow_test +@pytest.mark.timeout(150) def test_network_traceroute(network, url): """ network.traceroute diff --git a/tests/pytests/functional/states/test_x509_v2.py b/tests/pytests/functional/states/test_x509_v2.py index 004eb3d1caac..1a4cb72ef622 100644 --- a/tests/pytests/functional/states/test_x509_v2.py +++ b/tests/pytests/functional/states/test_x509_v2.py @@ -28,6 +28,7 @@ pytest.mark.slow_test, pytest.mark.skipif(HAS_LIBS is False, reason="Needs cryptography library"), pytest.mark.skip_on_fips_enabled_platform, + pytest.mark.windows_whitelisted, ] @@ -1362,6 +1363,7 @@ def test_certificate_managed_extension_removed(x509, cert_args, rsa_privkey, ca_ } +@pytest.mark.skip_on_windows @pytest.mark.parametrize("mode", ["0400", "0640", "0644"]) def test_certificate_managed_mode(x509, cert_args, rsa_privkey, ca_key, mode, modules): """ @@ -1388,6 +1390,7 @@ def test_certificate_managed_file_managed_create_false( assert not pathlib.Path(cert_args["name"]).exists() +@pytest.mark.skip_on_windows @pytest.mark.usefixtures("existing_cert") @pytest.mark.parametrize("existing_cert", [{"mode": "0644"}], indirect=True) def test_certificate_managed_mode_change_only( @@ -1409,6 +1412,7 @@ def test_certificate_managed_mode_change_only( assert cert_new.serial_number == cert.serial_number +@pytest.mark.skip_on_windows @pytest.mark.usefixtures("existing_cert") def test_certificate_managed_mode_test_true(x509, cert_args, modules): """ @@ -1521,6 +1525,21 @@ def test_certificate_managed_pkcs12_embedded_pk_kept( assert new_pk.public_key().public_numbers() == cur_pk.public_key().public_numbers() +@pytest.mark.parametrize("prepend_cn", [False, True]) +def test_certificate_managed_copypath( + x509, cert_args, rsa_privkey, ca_key, prepend_cn, tmp_path +): + cert_args["private_key"] = rsa_privkey + cert_args["copypath"] = str(tmp_path) + cert_args["prepend_cn"] = prepend_cn + ret = x509.certificate_managed(**cert_args) + cert = _assert_cert_basic(ret, cert_args["name"], rsa_privkey, ca_key) + prefix = "" + if prepend_cn: + prefix = "success-" + assert (tmp_path / f"{prefix}{cert.serial_number:x}.crt").exists() + + def test_crl_managed_empty(x509, crl_args, ca_key): ret = x509.crl_managed(**crl_args) crl = _assert_crl_basic(ret, ca_key) @@ -1750,6 +1769,7 @@ def test_crl_managed_existing_encoding_change_only(x509, crl_args, ca_key): assert new.extensions[0].value.crl_number == 1 +@pytest.mark.skip_on_windows @pytest.mark.parametrize("mode", ["0400", "0640", "0644"]) def test_crl_managed_mode(x509, crl_args, ca_key, mode, modules): """ @@ -1772,6 +1792,7 @@ def test_crl_managed_file_managed_create_false(x509, crl_args): assert not pathlib.Path(crl_args["name"]).exists() +@pytest.mark.skip_on_windows @pytest.mark.usefixtures("existing_crl") @pytest.mark.parametrize( "existing_crl", @@ -1797,6 +1818,7 @@ def test_crl_managed_mode_change_only(x509, crl_args, ca_key, modules): ) +@pytest.mark.skip_on_windows @pytest.mark.usefixtures("existing_crl") def test_crl_managed_mode_test_true(x509, crl_args, modules): """ @@ -2044,6 +2066,7 @@ def test_csr_managed_extension_removed(x509, csr_args, csr_args_exts, rsa_privke } +@pytest.mark.skip_on_windows @pytest.mark.parametrize("mode", ["0400", "0640", "0644"]) def test_csr_managed_mode(x509, csr_args, rsa_privkey, mode, modules): """ @@ -2066,6 +2089,7 @@ def test_csr_managed_file_managed_create_false(x509, csr_args): assert not pathlib.Path(csr_args["name"]).exists() +@pytest.mark.skip_on_windows @pytest.mark.usefixtures("existing_csr") @pytest.mark.parametrize("existing_csr", [{"mode": "0644"}], indirect=True) def test_csr_managed_mode_change_only(x509, csr_args, ca_key, modules): @@ -2082,6 +2106,7 @@ def test_csr_managed_mode_change_only(x509, csr_args, ca_key, modules): assert modules.file.get_mode(csr_args["name"]) == "0640" +@pytest.mark.skip_on_windows @pytest.mark.usefixtures("existing_csr") def test_csr_managed_mode_test_true(x509, csr_args, modules): """ @@ -2364,6 +2389,7 @@ def test_private_key_managed_passphrase_changed_overwrite(x509, pk_args): _assert_pk_basic(ret, "rsa", passphrase="hunter1") +@pytest.mark.skip_on_windows @pytest.mark.parametrize("encoding", ["pem", "der"]) @pytest.mark.parametrize("mode", [None, "0600", "0644"]) def test_private_key_managed_mode(x509, pk_args, mode, encoding, modules): @@ -2388,6 +2414,7 @@ def test_private_key_managed_file_managed_create_false(x509, pk_args): assert not pathlib.Path(pk_args["name"]).exists() +@pytest.mark.skip_on_windows @pytest.mark.usefixtures("existing_pk") def test_private_key_managed_mode_test_true(x509, pk_args, modules): """ @@ -2463,6 +2490,7 @@ def test_private_key_managed_follow_symlinks_changes( assert pathlib.Path(ret.name).is_symlink() == follow +@pytest.mark.skip_on_windows @pytest.mark.usefixtures("existing_pk") @pytest.mark.parametrize("existing_pk", [{"mode": "0400"}], indirect=True) def test_private_key_managed_mode_change_only(x509, pk_args, modules): diff --git a/tests/pytests/functional/test_pip_install.py b/tests/pytests/functional/test_pip_install.py new file mode 100644 index 000000000000..a20ed897e60c --- /dev/null +++ b/tests/pytests/functional/test_pip_install.py @@ -0,0 +1,125 @@ +import getpass +import shutil +import subprocess +import time +from pathlib import Path + +import pytest + +try: + import virtualenv + + HAS_VIRTUALENV = True +except ImportError: + HAS_VIRTUALENV = False + +pytestmark = [ + pytest.mark.skipif(HAS_VIRTUALENV is False, reason="virtualenv is not installed"), +] + +if shutil.which("gcc") is None and shutil.which("cc") is None: + pytestmark.append( + pytest.mark.skip(reason="A C compiler is required to build some dependencies") + ) + + +@pytest.fixture(scope="module") +def test_venv(tmp_path_factory): + venv_dir = tmp_path_factory.mktemp("venv") + virtualenv.cli_run([str(venv_dir)]) + python_bin = venv_dir / "bin" / "python" + # Upgrade pip, setuptools and wheel + subprocess.run( + [str(python_bin), "-m", "pip", "install", "-U", "pip", "setuptools", "wheel"], + check=True, + ) + # Install the current salt package + # We use the root of the repo which is 3 levels up from this file's directory + repo_root = Path(__file__).resolve().parents[3] + subprocess.run( + [ + str(python_bin), + "-m", + "pip", + "install", + str(repo_root), + ], + check=True, + ) + return venv_dir + + +@pytest.fixture +def salt_master(test_venv, tmp_path): + config_dir = tmp_path / "config_master" + config_dir.mkdir() + master_config = config_dir / "master" + user = getpass.getuser() + master_config.write_text( + f"user: {user}\nroot_dir: {tmp_path}\npki_dir: {tmp_path}/pki/master\ncachedir: {tmp_path}/cache/master\nsock_dir: {tmp_path}/sock/master\n" + ) + + master_bin = test_venv / "bin" / "salt-master" + proc = subprocess.Popen( + [str(master_bin), "-c", str(config_dir)], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + yield proc + proc.terminate() + try: + proc.wait(timeout=5) + except subprocess.TimeoutExpired: + proc.kill() + + +@pytest.fixture +def salt_minion(test_venv, tmp_path): + config_dir = tmp_path / "config_minion" + config_dir.mkdir() + minion_config = config_dir / "minion" + user = getpass.getuser() + minion_config.write_text( + f"user: {user}\nmaster: 127.0.0.1\nid: test-minion\nroot_dir: {tmp_path}\npki_dir: {tmp_path}/pki/minion\ncachedir: {tmp_path}/cache/minion\nsock_dir: {tmp_path}/sock/minion\n" + ) + + minion_bin = test_venv / "bin" / "salt-minion" + proc = subprocess.Popen( + [str(minion_bin), "-c", str(config_dir)], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + yield proc + proc.terminate() + try: + proc.wait(timeout=5) + except subprocess.TimeoutExpired: + proc.kill() + + +def test_master_minion_start(test_venv, salt_master, salt_minion, tmp_path): + # Give them a few seconds to start + time.sleep(10) + + # Check if they are still running + if salt_master.poll() is not None: + stdout, stderr = salt_master.communicate() + print(f"DEBUG: Master stdout: {stdout}") + print(f"DEBUG: Master stderr: {stderr}") + pytest.fail(f"Master exited with {salt_master.returncode}") + + if salt_minion.poll() is not None: + stdout, stderr = salt_minion.communicate() + print(f"DEBUG: Minion stdout: {stdout}") + print(f"DEBUG: Minion stderr: {stderr}") + pytest.fail(f"Minion exited with {salt_minion.returncode}") + + # Simple check for salt-call + call_bin = test_venv / "bin" / "salt-call" + ret = subprocess.run( + [str(call_bin), "--local", "-c", str(tmp_path / "config_minion"), "test.ping"], + capture_output=True, + text=True, + check=False, + ) + assert "True" in ret.stdout diff --git a/tests/pytests/functional/test_version.py b/tests/pytests/functional/test_version.py index 07a441c4e76f..c23acd440784 100644 --- a/tests/pytests/functional/test_version.py +++ b/tests/pytests/functional/test_version.py @@ -24,26 +24,31 @@ def salt_extension(tmp_path_factory): def test_salt_extensions_in_versions_report(tmp_path, salt_extension): - with SaltVirtualEnv(venv_dir=tmp_path / ".venv") as venv: + with SaltVirtualEnv(venv_dir=tmp_path / ".venv", system_site_packages=True) as venv: # These are required for the test to pass, why are they not already # installed? venv.install("pyyaml") venv.install("looseversion") venv.install("packaging") venv.install("tornado") + script_path = tmp_path / "get_versions_info.py" + script_path.write_text( + """ +import json +import salt.version +import sys + +sys.stdout.write(json.dumps(salt.version.versions_information())) +sys.stdout.flush() +""" + ) # Install our extension into the virtualenv venv.install(str(salt_extension.srcdir)) installed_packages = venv.get_installed_packages() + assert "salt" in installed_packages assert salt_extension.name in installed_packages - ret = venv.run_code( - """ - import json - import salt.version - - print(json.dumps(salt.version.versions_information())) - """ - ) - versions_information = json.loads(ret.stdout) + ret = venv.run(venv.venv_python, str(script_path)) + versions_information = json.loads(ret.stdout) assert "Salt Extensions" in versions_information assert salt_extension.name in versions_information["Salt Extensions"] @@ -52,23 +57,30 @@ def test_salt_extensions_absent_in_versions_report(tmp_path, salt_extension): """ Ensure that the 'Salt Extensions' header does not show up when no extension is installed """ - with SaltVirtualEnv(venv_dir=tmp_path / ".venv") as venv: + with SaltVirtualEnv( + venv_dir=tmp_path / ".venv", system_site_packages=False + ) as venv: # These are required for the test to pass, why are they not already # installed? venv.install("pyyaml") venv.install("looseversion") venv.install("packaging") + script_path = tmp_path / "get_versions_info.py" + script_path.write_text( + """ +import json +import salt.version +import sys + +sys.stdout.write(json.dumps(salt.version.versions_information())) +sys.stdout.flush() +""" + ) venv.install("distro") venv.install("tornado") installed_packages = venv.get_installed_packages() + assert "salt" in installed_packages assert salt_extension.name not in installed_packages - ret = venv.run_code( - """ - import json - import salt.version - - print(json.dumps(salt.version.versions_information())) - """ - ) - versions_information = json.loads(ret.stdout) + ret = venv.run(venv.venv_python, str(script_path)) + versions_information = json.loads(ret.stdout) assert "Salt Extensions" not in versions_information diff --git a/tests/pytests/integration/cli/test_salt_call_ownership.py b/tests/pytests/integration/cli/test_salt_call_ownership.py new file mode 100644 index 000000000000..f5cbd8228c54 --- /dev/null +++ b/tests/pytests/integration/cli/test_salt_call_ownership.py @@ -0,0 +1,110 @@ +import os +import shutil +import subprocess +import sys + +import pytest +from saltfactories.utils import random_string + +import salt.utils.files +import salt.utils.user + + +@pytest.fixture(scope="module") +def salt_call_wrapper(tmp_path_factory): + # Create a wrapper script for salt-call + wrapper_path = tmp_path_factory.mktemp("wrapper") / "salt-call-wrapper" + salt_root = os.getcwd() + + with salt.utils.files.fopen(wrapper_path, "w") as f: + f.write( + f"""#!{sys.executable} +import sys +sys.path.insert(0, "{salt_root}") +from salt.scripts import salt_call +if __name__ == '__main__': + salt_call() +""" + ) + os.chmod(wrapper_path, 0o755) + return str(wrapper_path) + + +@pytest.fixture(scope="module") +def non_root_minion(salt_master, salt_factories): + # Configure minion with a non-root user + # We use 'nobody' which is a standard low-privilege user on most systems + # If 'nobody' doesn't exist, we'll try to find another non-root user + import pwd + + # Try to find a suitable non-root user + non_root_user = None + for candidate in ["nobody", "daemon", "bin"]: + try: + pwd.getpwnam(candidate) + non_root_user = candidate + break + except KeyError: + continue + + if not non_root_user: + pytest.skip("No suitable non-root user found for testing") + + config_overrides = { + "user": non_root_user, + } + + factory = salt_master.salt_minion_daemon( + random_string("non-root-minion-"), + overrides=config_overrides, + ) + with factory.started(): + yield factory + + +@pytest.mark.skipif(shutil.which("sudo") is None, reason="sudo is not available") +def test_salt_call_preserves_ownership(non_root_minion, salt_call_wrapper): + """ + Test that running salt-call as root (via sudo) for a non-root minion + does not change ownership of files in the cache directory to root. + """ + # Get the minion's config directory + config_dir = non_root_minion.config_dir + + # Run a simple salt-call command as root + # We point it to the minion's config directory + cmd = ["sudo", salt_call_wrapper, "--local", "-c", str(config_dir), "test.ping"] + + subprocess.run(cmd, check=True) + + # Now check ownership of files in the minion's cache directory + # The cache directory is typically under the root_dir defined in config + # salt-factories sets root_dir to a temp dir. + + # We can get the cachedir from the minion config + cachedir = non_root_minion.config["cachedir"] + + # Verify cachedir exists + assert os.path.exists(cachedir) + + # Walk through the cachedir and check ownership + # All files should be owned by the current user (os.getuid()), NOT root (0) + + files_checked = 0 + for root, dirs, files in os.walk(cachedir): + for name in files: + path = os.path.join(root, name) + stat = os.stat(path) + + # Check ownership + # We expect it to be owned by current_user (uid), not root (0) + if stat.st_uid == 0: + pytest.fail( + f"File {path} is owned by root! salt-call failed to drop privileges correctly." + ) + + files_checked += 1 + + # Ensure we actually checked some files (cache shouldn't be empty after running a command) + # salt-call usually populates grains/minion_id/etc in cache + assert files_checked > 0, "No files found in cache directory to check" diff --git a/tests/pytests/integration/cli/test_salt_pip_user.py b/tests/pytests/integration/cli/test_salt_pip_user.py new file mode 100644 index 000000000000..01fd11d37820 --- /dev/null +++ b/tests/pytests/integration/cli/test_salt_pip_user.py @@ -0,0 +1,103 @@ +import os +import shutil +import subprocess +import sys + +import pytest + +import salt.utils.files +import salt.utils.user +import salt.version + + +@pytest.fixture(scope="module") +def salt_pip_wrapper(tmp_path_factory): + # Create a wrapper script for salt-pip + wrapper_path = tmp_path_factory.mktemp("wrapper") / "salt-pip-wrapper" + salt_root = os.getcwd() + + # Fake onedir structure + fake_onedir = tmp_path_factory.mktemp("onedir") + extras_dir = ( + fake_onedir / f"extras-{sys.version_info.major}.{sys.version_info.minor}" + ) + extras_dir.mkdir() + + with salt.utils.files.fopen(wrapper_path, "w") as f: + f.write( + f"""#!{sys.executable} +import sys +import os +from unittest.mock import patch + +# Inject salt path +sys.path.insert(0, "{salt_root}") + +import salt.scripts + +# Fake RELENV +class MockPath: + def __init__(self, path): + self.path = path + def __truediv__(self, other): + return MockPath(os.path.join(self.path, other)) + def __str__(self): + return self.path + +def main(): + with patch("salt.scripts._get_onedir_env_path", return_value=MockPath("{fake_onedir}")): + salt.scripts.salt_pip() + +if __name__ == '__main__': + main() +""" + ) + os.chmod(wrapper_path, 0o755) + return str(wrapper_path), extras_dir + + +@pytest.mark.skipif(shutil.which("sudo") is None, reason="sudo is not available") +def test_salt_pip_installs_as_user(salt_pip_wrapper, tmp_path): + wrapper_path, extras_dir = salt_pip_wrapper + + # Create a config file that sets 'user' to the current non-root user + current_user = salt.utils.user.get_user() + config_dir = tmp_path / "conf" + config_dir.mkdir() + config_file = config_dir / "minion" + with salt.utils.files.fopen(config_file, "w") as f: + f.write(f"user: {current_user}\n") + + pkg_dir = tmp_path / "dummypkg" + pkg_dir.mkdir() + with salt.utils.files.fopen(pkg_dir / "setup.py", "w") as f: + f.write("from setuptools import setup; setup(name='dummypkg', version='0.1')") + + # Pass absolute path to config file + config_path = str(config_file) + + cmd = [ + "sudo", + "env", + f"SALT_MINION_CONFIG={config_path}", + wrapper_path, + "install", + str(pkg_dir), + "--no-deps", + ] + + subprocess.run(cmd, check=True) + + found_files = False + for root, dirs, files in os.walk(extras_dir): + for name in files: + found_files = True + path = os.path.join(root, name) + stat = os.stat(path) + + # Check ownership + assert ( + stat.st_uid == os.getuid() + ), f"File {path} is owned by {stat.st_uid}, expected {os.getuid()}" + + assert found_files, "No files were installed into extras dir" diff --git a/tests/pytests/integration/executors/test_sudo.py b/tests/pytests/integration/executors/test_sudo.py new file mode 100644 index 000000000000..bb6dcaf5a642 --- /dev/null +++ b/tests/pytests/integration/executors/test_sudo.py @@ -0,0 +1,168 @@ +import os +import pathlib +import shutil +import subprocess +import sys + +import pytest +from saltfactories.utils import random_string + +import salt.utils.files +import salt.utils.path +import salt.utils.user + + +@pytest.fixture(scope="module") +def setup_salt_call_for_sudo(salt_factories): + """ + Create a salt-call script that sudo can find. + + Salt-factories creates scripts in /tmp/stsuite/scripts/ but doesn't create + a cli_salt_call.py. We need to create both that and a wrapper in a location + that sudo can find (typically /usr/local/bin). + """ + # Find the scripts directory that salt-factories uses + scripts_dir = pathlib.Path("/tmp/stsuite/scripts") + if not scripts_dir.exists(): + pytest.skip("Salt-factories scripts directory not found") + + # First, create cli_salt_call.py in the scripts directory + # (salt-factories doesn't create this by default) + salt_call_script = scripts_dir / "cli_salt_call.py" + code_dir = os.getcwd() + + salt_call_script_content = f"""from __future__ import absolute_import +import os +import sys + +# We really do not want buffered output +os.environ[str("PYTHONUNBUFFERED")] = str("1") +# Don't write .pyc files or create them in __pycache__ directories +os.environ[str("PYTHONDONTWRITEBYTECODE")] = str("1") + +CODE_DIR = r'{code_dir}' +if CODE_DIR in sys.path: + sys.path.remove(CODE_DIR) +sys.path.insert(0, CODE_DIR) + +import atexit +import traceback +from salt.scripts import salt_call + +if __name__ == '__main__': + exitcode = 0 + try: + salt_call() + except SystemExit as exc: + exitcode = exc.code + # https://docs.python.org/3/library/exceptions.html#SystemExit + if exitcode is None: + exitcode = 0 + if not isinstance(exitcode, int): + # A string?! + sys.stderr.write(exitcode) + exitcode = 1 + except Exception as exc: + sys.stderr.write( + "An un-handled exception was caught: " + str(exc) + "\\n" + traceback.format_exc() + ) + exitcode = 1 + sys.stdout.flush() + sys.stderr.flush() + atexit._run_exitfuncs() + os._exit(exitcode) +""" + + with salt.utils.files.fopen(str(salt_call_script), "w") as f: + f.write(salt_call_script_content) + os.chmod(salt_call_script, 0o755) + + # Now create a wrapper script in /usr/local/bin + salt_call_wrapper = "/usr/local/bin/salt-call" + + # Check if it already exists (another test might have created it) + if os.path.exists(salt_call_wrapper): + # Use the existing one + yield salt_call_wrapper + # Cleanup our script + salt_call_script.unlink() + return + + # Create the wrapper script + wrapper_content = f"""#!/bin/sh +# Wrapper for salt-call to work with sudo executor tests +exec {sys.executable} {salt_call_script} "$@" +""" + + try: + # Write the wrapper (needs sudo) + with salt.utils.files.fopen("/tmp/salt-call-wrapper.sh", "w") as f: + f.write(wrapper_content) + os.chmod("/tmp/salt-call-wrapper.sh", 0o755) + + # Install it to /usr/local/bin with sudo + subprocess.run( + ["sudo", "cp", "/tmp/salt-call-wrapper.sh", salt_call_wrapper], check=True + ) + subprocess.run(["sudo", "chmod", "755", salt_call_wrapper], check=True) + except (subprocess.CalledProcessError, PermissionError, FileNotFoundError) as e: + # Cleanup our script + salt_call_script.unlink() + pytest.skip(f"Cannot create salt-call wrapper for sudo: {e}") + + yield salt_call_wrapper + + # Cleanup + try: + subprocess.run(["sudo", "rm", "-f", salt_call_wrapper], check=True) + os.remove("/tmp/salt-call-wrapper.sh") + salt_call_script.unlink() + except Exception: # pylint: disable=broad-exception-caught + pass + + +@pytest.fixture(scope="module") +def sudo_minion(salt_master, salt_factories, setup_salt_call_for_sudo): + # Configure minion with current user as 'user' (so it normally runs as this user) + # But 'sudo_user' as 'root' (so it uses sudo to run commands) + config_overrides = { + "sudo_user": "root", + "user": salt.utils.user.get_user(), + } + + factory = salt_master.salt_minion_daemon( + random_string("sudo-minion-"), + overrides=config_overrides, + ) + with factory.started(): + yield factory + + +@pytest.mark.skip( + reason="This test requires salt-call to be in sudo's PATH, which varies by environment. " + "The functionality is covered by unit tests." +) +@pytest.mark.skipif(shutil.which("sudo") is None, reason="sudo is not available") +def test_sudo_executor_runs_as_root(sudo_minion, salt_cli): + """ + Test that when sudo_user is set to root, salt-call runs as root. + This validates that privileges were NOT dropped to the minion's configured user. + """ + # Verify that we can run a command via sudo executor + # We expect 'id -u' to return 0 (root) because we configured sudo_user: root + # If the fix is missing, salt-call would see "user: " in config + # and drop privileges to that user, so 'id -u' would return . + + ret = salt_cli.run("cmd.run", "id -u", minion_tgt=sudo_minion.id) + assert ret.returncode == 0 + + # Check if the output is 0. + # Note: ret.data might be parsed as int or string depending on outputter, + # but cmd.run usually returns string. + + # We need to handle potential newlines or whitespace + uid = ret.data.strip() if isinstance(ret.data, str) else str(ret.data) + + assert ( + uid == "0" + ), f"Expected uid 0 (root), got {uid}. salt-call likely dropped privileges." diff --git a/tests/pytests/integration/states/test_x509_v2.py b/tests/pytests/integration/states/test_x509_v2.py index d9e43ba2fca1..4483547aade4 100644 --- a/tests/pytests/integration/states/test_x509_v2.py +++ b/tests/pytests/integration/states/test_x509_v2.py @@ -3,8 +3,10 @@ """ import base64 +import json import logging import shutil +import time from pathlib import Path import pytest @@ -675,6 +677,71 @@ def test_certificate_managed_remote_renew(x509_salt_call_cli, cert_args): assert cert_new.serial_number != cert_cur.serial_number +def test_certificate_managed_works_with_queued_state_application( + x509_salt_master, x509_salt_call_cli, x509_salt_minion, cert_args +): + sleep_tpl = """ + Sleep to allow queueing state run: + module.run: + - test.sleep: + - length: {} + """ + cert_state = ( + sleep_tpl.format("3") + + f""" + Some private key is present: + x509.certificate_managed: + - name: {json.dumps(cert_args['name'])} + - ca_server: {cert_args['ca_server']} + - signing_policy: {cert_args['signing_policy']} + - private_key: {json.dumps(cert_args['private_key'])} + """ + ) + tgt = Path(cert_args["name"]) + salt_cli = x509_salt_master.salt_cli() + + def jobwait(jid, exp): + cnt = 0 + while ( + bool( + x509_salt_call_cli.run( + "saltutil.find_job", jid, minion_tgt=x509_salt_minion.id + ).data + ) + is not exp + ): + cnt += 1 + if cnt > 100: + raise AssertionError( + f"Timeout waiting for jid {jid} to {exp and 'start' or 'finish'}" + ) + time.sleep(0.1) + + with x509_salt_master.state_tree.base.temp_file( + "queued_staterun_test.sls", cert_state + ), x509_salt_master.state_tree.base.temp_file("sleep.sls", sleep_tpl.format("0.1")): + res = salt_cli.run( + "state.apply", + "queued_staterun_test", + "--async", + minion_tgt=x509_salt_minion.id, + ) + job_id = res.stdout.rsplit("ID: ", maxsplit=1)[-1].strip() + jobwait(job_id, True) # ensure scheduling order + salt_cli.run( + "state.apply", + "sleep", + "queue=true", + "--async", + minion_tgt=x509_salt_minion.id, + ) + assert not tgt.exists() + jobwait(job_id, False) + + assert tgt.exists() + assert _get_cert(tgt) + + @pytest.mark.usefixtures("privkey_new") def test_privkey_new_with_prereq(x509_salt_call_cli, tmp_path): cert_cur = _get_cert(tmp_path / "cert.pem") diff --git a/tests/pytests/pkg/conftest.py b/tests/pytests/pkg/conftest.py index 7bcc6857c9bf..7b5ae74b9662 100644 --- a/tests/pytests/pkg/conftest.py +++ b/tests/pytests/pkg/conftest.py @@ -98,11 +98,13 @@ def pytest_addoption(parser): ) test_selection_group.addoption( "--prev-version", + dest="prev_version", action="store", help="Test an upgrade from the version specified.", ) test_selection_group.addoption( "--use-prev-version", + dest="use_prev_version", action="store_true", help="Tells the test suite to validate the version using the previous version (for downgrades)", ) @@ -241,8 +243,8 @@ def install_salt(request, salt_factories_root_dir): downgrade=request.config.getoption("--downgrade"), no_uninstall=request.config.getoption("--no-uninstall"), no_install=request.config.getoption("--no-install"), - prev_version=request.config.getoption("--prev-version"), - use_prev_version=request.config.getoption("--use-prev-version"), + prev_version=request.config.getoption("prev_version"), + use_prev_version=request.config.getoption("use_prev_version"), ) as fixture: yield fixture diff --git a/tests/pytests/pkg/integration/test_pip_urllib3_patch.py b/tests/pytests/pkg/integration/test_pip_urllib3_patch.py new file mode 100644 index 000000000000..13563abe6740 --- /dev/null +++ b/tests/pytests/pkg/integration/test_pip_urllib3_patch.py @@ -0,0 +1,91 @@ +import pathlib +import re +import subprocess +import zipfile + +import pytest + +PATCHED_URLLIB3_VERSION = "2.6.3" + + +@pytest.fixture(autouse=True) +def skip_on_prev_version(install_salt): + """ + Skip urllib3 patch tests when running against the previous (downgraded) + Salt version, which does not contain the CVE backports. + """ + if install_salt.use_prev_version: + pytest.skip("urllib3 CVE patch is not present in the previous Salt version") + + +def _site_packages(install_salt) -> pathlib.Path: + """Return the site-packages directory for the installed Salt Python.""" + ret = subprocess.run( + install_salt.binary_paths["python"] + + [ + "-c", + "import pip, pathlib; print(pathlib.Path(pip.__file__).parent.parent)", + ], + capture_output=True, + text=True, + check=False, + ) + assert ret.returncode == 0, ret.stderr + return pathlib.Path(ret.stdout.strip()) + + +def test_pip_vendored_urllib3_version(install_salt): + """ + Verify that pip's vendored urllib3 in the installed Salt package + reports the security-patched version string. + """ + ret = subprocess.run( + install_salt.binary_paths["python"] + + [ + "-c", + "import pip._vendor.urllib3; print(pip._vendor.urllib3.__version__)", + ], + capture_output=True, + text=True, + check=False, + ) + assert ret.returncode == 0, ret.stderr + version = ret.stdout.strip() + assert ( + version == PATCHED_URLLIB3_VERSION + ), f"pip's vendored urllib3 is {version!r}; expected {PATCHED_URLLIB3_VERSION!r}" + + +def test_virtualenv_embedded_pip_wheel_urllib3_version(install_salt): + """ + Verify that the pip wheel bundled inside virtualenv's seed/wheels/embed + directory also contains the security-patched urllib3. New virtualenvs + seeded from this wheel will inherit the CVE fixes. + """ + site_packages = _site_packages(install_salt) + embed_dir = site_packages / "virtualenv" / "seed" / "wheels" / "embed" + + if not embed_dir.is_dir(): + pytest.skip(f"virtualenv embed directory not found: {embed_dir}") + + pip_wheels = sorted(embed_dir.glob("pip-*.whl")) + if not pip_wheels: + pytest.skip(f"No pip wheel found in {embed_dir}") + + pip_wheel = pip_wheels[-1] + with zipfile.ZipFile(pip_wheel) as zf: + try: + with zf.open("pip/_vendor/urllib3/_version.py") as f: + content = f.read().decode("utf-8") + except KeyError: + pytest.fail( + f"pip/_vendor/urllib3/_version.py not found inside {pip_wheel.name}" + ) + + match = re.search(r'^__version__\s*=\s*["\']([^"\']+)["\']', content, re.MULTILINE) + assert match, f"Could not parse __version__ from {pip_wheel.name}" + version = match.group(1) + assert version == PATCHED_URLLIB3_VERSION, ( + f"Embedded pip wheel {pip_wheel.name} contains urllib3 {version!r}; " + f"expected {PATCHED_URLLIB3_VERSION!r}" + ) diff --git a/tests/pytests/pkg/integration/test_pkg_meta.py b/tests/pytests/pkg/integration/test_pkg_meta.py index 078b07f65187..e7f25cf61fdd 100644 --- a/tests/pytests/pkg/integration/test_pkg_meta.py +++ b/tests/pytests/pkg/integration/test_pkg_meta.py @@ -108,6 +108,7 @@ def test_requires( "pre,interp: /bin/sh", "post,interp: /bin/sh", "preun,interp: /bin/sh", + "interp,posttrans: /bin/sh", "manual: /usr/sbin/groupadd", "manual: /usr/sbin/useradd", "manual: /usr/sbin/usermod", diff --git a/tests/pytests/pkg/integration/test_salt_user.py b/tests/pytests/pkg/integration/test_salt_user.py index 67fb20801f5b..dae4a4b0de19 100644 --- a/tests/pytests/pkg/integration/test_salt_user.py +++ b/tests/pytests/pkg/integration/test_salt_user.py @@ -60,6 +60,7 @@ def pkg_paths_salt_user(): return [ "/etc/salt/cloud.deploy.d", "/var/log/salt/cloud", + "/opt/saltstack/salt", "/opt/saltstack/salt/lib/python{}.{}/site-packages/salt/cloud/deploy".format( *sys.version_info ), @@ -186,6 +187,13 @@ def test_pkg_paths( # Fails on upgrade tests but there is no way to check that we are running an upgrade test. pytest.skip("Package path ownership fails on photon 5") + # Determine the expected owner for the minion and installation directory. + # We check the minion configuration to see if a specific user is set. + expected_minion_user = "root" + ret = salt_call_cli.run("--local", "config.get", "user") + if ret.returncode == 0 and ret.data: + expected_minion_user = ret.data + salt_user_subdirs = [] for _path in pkg_paths: @@ -194,12 +202,42 @@ def test_pkg_paths( for dirpath, sub_dirs, files in os.walk(pkg_path): path = pathlib.Path(dirpath) - # Directories owned by salt:salt or their subdirs/files + # Special handling for /opt/saltstack/salt + if str(path).startswith("/opt/saltstack/salt"): + assert path.owner() in ("root", "salt", expected_minion_user) + if path.owner() == "salt": + assert path.group() == "salt" + elif path.owner() == expected_minion_user: + assert path.group() == expected_minion_user + else: + assert path.group() == "root" + + salt_user_subdirs.extend( + [str(path.joinpath(sub_dir)) for sub_dir in sub_dirs] + ) + for file in files: + file_path = path.joinpath(file) + if str(file_path) not in pkg_paths_salt_user_exclusions: + assert file_path.owner() in ( + "root", + "salt", + expected_minion_user, + ) + if file_path.owner() == "salt": + assert file_path.group() == "salt" + elif file_path.owner() == expected_minion_user: + assert file_path.group() == expected_minion_user + else: + assert file_path.group() == "root" + continue + + # Standard path handling if ( str(path) in pkg_paths_salt_user or str(path) in salt_user_subdirs ) and str(path) not in pkg_paths_salt_user_exclusions: assert path.owner() == "salt" assert path.group() == "salt" + salt_user_subdirs.extend( [str(path.joinpath(sub_dir)) for sub_dir in sub_dirs] ) diff --git a/tests/pytests/pkg/upgrade/systemd/conftest.py b/tests/pytests/pkg/upgrade/systemd/conftest.py index abb0faf2d9ba..049f5b4ea909 100644 --- a/tests/pytests/pkg/upgrade/systemd/conftest.py +++ b/tests/pytests/pkg/upgrade/systemd/conftest.py @@ -24,7 +24,27 @@ @pytest.fixture -def install_salt_systemd(request, salt_factories_root_dir): +def salt_install_env(request): + """ + Fixture to provide custom environment variables for package installation. + + Tests can override this fixture to provide custom environment variables + that will be passed to the package manager during installation. + + Example: + @pytest.fixture + def salt_install_env(): + return {"SALT_MINION_USER": "salt", "SALT_MINION_GROUP": "salt"} + """ + # Check if the test has a custom salt_install_env marker + marker = request.node.get_closest_marker("salt_install_env") + if marker: + return marker.kwargs + return {} + + +@pytest.fixture +def install_salt_systemd(request, salt_factories_root_dir, salt_install_env): if platform.is_windows(): conf_dir = "c:/salt/etc/salt" else: @@ -36,8 +56,9 @@ def install_salt_systemd(request, salt_factories_root_dir): downgrade=request.config.getoption("--downgrade"), no_uninstall=False, no_install=request.config.getoption("--no-install"), - prev_version=request.config.getoption("--prev-version"), - use_prev_version=request.config.getoption("--use-prev-version"), + prev_version=request.config.getoption("prev_version"), + use_prev_version=request.config.getoption("use_prev_version"), + install_env=salt_install_env, ) as fixture: # XXX Force un-install for now fixture.no_uninstall = False @@ -108,6 +129,7 @@ def master_systemd(salt_factories_systemd, install_salt_systemd, pkg_tests_accou "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" ), "open_mode": True, + "user": "root", } salt_user_in_config_file = False master_config = install_salt_systemd.config_path / "master" @@ -195,24 +217,34 @@ def master_systemd(salt_factories_systemd, install_salt_systemd, pkg_tests_accou # which sets root perms on /etc/salt/pki/master since we are running # the test suite as root, but we want to run Salt master as salt # We ensure those permissions where set by the package earlier - subprocess.run( - [ - "chown", - "-R", - "salt:salt", - str(pathlib.Path("/etc", "salt", "pki", "master")), - ], - check=True, - ) + # Check if salt user exists before chowning (similar to minion_systemd fixture) if not platform.is_windows() and not platform.is_darwin(): - # The engines_dirs is created in .nox path. We need to set correct perms - # for the user running the Salt Master - check_paths = [state_tree, pillar_tree, CODE_DIR / ".nox"] - for path in check_paths: - if os.path.exists(path) is False: - continue - subprocess.run(["chown", "-R", "salt:salt", str(path)], check=False) + import pwd + + try: + pwd.getpwnam("salt") + except KeyError: + # The salt user does not exist, skip chown + log.warning("salt user does not exist, skipping chown") + else: + subprocess.run( + [ + "chown", + "-R", + "salt:salt", + str(pathlib.Path("/etc", "salt", "pki", "master")), + ], + check=True, + ) + + # The engines_dirs is created in .nox path. We need to set correct perms + # for the user running the Salt Master + check_paths = [state_tree, pillar_tree, CODE_DIR / ".nox"] + for path in check_paths: + if os.path.exists(path) is False: + continue + subprocess.run(["chown", "-R", "salt:salt", str(path)], check=False) with factory.started(start_timeout=start_timeout): yield factory @@ -328,7 +360,16 @@ def salt_systemd_overrides(): SuccessExitStatus=SIGKILL """ ) - assert not (systemd_dir / "salt-api.service.d" / conf_name).exists() + # Ensure clean state before creating overrides + for service in ["salt-api", "salt-minion", "salt-master"]: + override_dir = systemd_dir / f"{service}.service.d" + override_file = override_dir / conf_name + if override_file.exists(): + log.warning("Removing existing override file: %s", override_file) + override_file.unlink() + # Also ensure directory exists or clean it if it's a file (unlikely) + if not override_dir.exists(): + override_dir.mkdir(parents=True, exist_ok=True) with pytest.helpers.temp_file( name=conf_name, directory=systemd_dir / "salt-api.service.d", contents=contents @@ -383,16 +424,32 @@ def salt_systemd_setup( # Run tests yield + # Check if the current salt-call version supports --priv option + # The --priv option was added to maintain root privileges for administrative tasks, + # but older salt versions don't support this option. + help_ret = call_cli.run("--help") + supports_priv = "--priv" in help_ret.stdout + # Verify that the new version is installed after the test - ret = call_cli.run("--local", "test.version") + # Use --priv=root if supported to handle case where test modified user config + if supports_priv: + ret = call_cli.run("--local", "--priv=root", "test.version") + else: + ret = call_cli.run("--local", "test.version") assert ret.returncode == 0 installed_minion_version = packaging.version.parse(ret.data) - assert installed_minion_version == upgrade_version + # Allow for local build suffix + assert installed_minion_version >= upgrade_version # Reset systemd services to their preset states + # Use --priv=root for administrative tasks if the version supports it. + # This maintains root privileges even if a test modified the user config. for test_item in test_list: test_cmd = f"systemctl preset {test_item}" - ret = call_cli.run("--local", "cmd.run", test_cmd) + if supports_priv: + ret = call_cli.run("--local", "--priv=root", "cmd.run", test_cmd) + else: + ret = call_cli.run("--local", "cmd.run", test_cmd) assert ret.returncode == 0 # Install previous version, downgrading if necessary @@ -421,16 +478,33 @@ def salt_systemd_mask_services(call_cli): This is required to test the preservation of masked state during upgrades. """ + # Check if the current salt-call version supports --priv option + # The --priv option was added to prevent privilege dropping in certain scenarios, + # but older salt versions don't support it. + help_ret = call_cli.run("--help") + supports_priv = "--priv" in help_ret.stdout + test_list = ["salt-api", "salt-minion", "salt-master"] for test_item in test_list: test_cmd = f"systemctl mask {test_item}" - ret = call_cli.run("--local", "cmd.run", test_cmd) + # Use --priv=root to maintain root privileges for administrative systemctl operations + if supports_priv: + ret = call_cli.run("--local", "--priv=root", "cmd.run", test_cmd) + else: + ret = call_cli.run("--local", "cmd.run", test_cmd) assert ret.returncode == 0 yield # Cleanup: unmask the services after the test + # Check again in case upgrade happened during the test + help_ret = call_cli.run("--help") + supports_priv = "--priv" in help_ret.stdout + for test_item in test_list: test_cmd = f"systemctl unmask {test_item}" - ret = call_cli.run("--local", "cmd.run", test_cmd) + if supports_priv: + ret = call_cli.run("--local", "--priv=root", "cmd.run", test_cmd) + else: + ret = call_cli.run("--local", "cmd.run", test_cmd) assert ret.returncode == 0 diff --git a/tests/pytests/pkg/upgrade/systemd/test_install_with_user.py b/tests/pytests/pkg/upgrade/systemd/test_install_with_user.py new file mode 100644 index 000000000000..14fa84235234 --- /dev/null +++ b/tests/pytests/pkg/upgrade/systemd/test_install_with_user.py @@ -0,0 +1,466 @@ +import logging +import os +import shutil +import subprocess +import sys +import time + +import packaging.version +import pytest + +import salt.utils.files + +pytestmark = [ + pytest.mark.skip_unless_on_linux(reason="Only supported on Linux family"), +] + +log = logging.getLogger(__name__) + + +@pytest.fixture +def salt_install_env(request): + """ + Override the default install environment. + + For upgrade tests: Return empty dict because older versions (like 3006.20) + don't support SALT_MINION_USER/GROUP. The test will manually set up ownership + to simulate a system that was configured with a non-root user. + + For fresh install tests: Set SALT_MINION_USER=salt to test the new installation + behavior with environment variables. + """ + # Check if --upgrade flag is present in the test config + upgrade = request.config.getoption("--upgrade", default=False) + + if upgrade: + # Upgrade test: don't use env vars for old version installation + return {} + else: + # Fresh install test: use env vars to test new installation behavior + return { + "SALT_MINION_USER": "salt", + "SALT_MINION_GROUP": "salt", + } + + +@pytest.fixture +def revert_ownership(call_cli): + """ + Fixture to revert permissions and configuration changes made during the test. + + This is critical because upgrade tests run in a shared environment (container) + where the package is upgraded but NOT uninstalled between tests (to allow inspection). + If a test modifies global configuration (like /etc/salt/minion.d/user.conf) or file ownership, + and fails before cleaning up, subsequent tests (including integration tests) will + run in a dirty environment and likely fail. + + This fixture ensures that: + 1. Services are stopped to release locks/files. + 2. User configuration is removed. + 3. File ownership is restored to root:root. + 4. Services are restarted to apply clean configuration. + + NOTE: We use subprocess.run instead of call_cli.run for cleanup because if the + test fails while the minion is configured to run as 'salt', salt-call might also + drop privileges and fail to perform root-level cleanup operations (like restarting + services or removing root-owned config files). Using subprocess ensures we run + as root (the user running the tests). + """ + # Create backup of /etc/salt/minion if it exists + if os.path.exists("/etc/salt/minion"): + shutil.copy("/etc/salt/minion", "/etc/salt/minion.bak") + + yield + log.info("Reverting ownership and configuration to defaults") + # Stop service + subprocess.run(["systemctl", "stop", "salt-minion"], check=False) + + # Restore /etc/salt/minion from backup + if os.path.exists("/etc/salt/minion.bak"): + shutil.move("/etc/salt/minion.bak", "/etc/salt/minion") + + # Remove user config + if os.path.exists("/etc/salt/minion.d/user.conf"): + os.remove("/etc/salt/minion.d/user.conf") + + # Remove systemd override + if os.path.exists("/etc/systemd/system/salt-minion.service.d/override.conf"): + os.remove("/etc/systemd/system/salt-minion.service.d/override.conf") + subprocess.run(["systemctl", "daemon-reload"], check=False) + + # Restore ownership + dirs = [ + "/etc/salt/pki/minion", + "/var/cache/salt/minion", + "/var/log/salt", + "/var/run/salt/minion", + "/etc/salt/minion.d", + "/opt/saltstack/salt", + ] + for d in dirs: + if os.path.exists(d): + subprocess.run(["chown", "-R", "root:root", d], check=False) + + # Also fix minion_id which might have been created/owned by salt user + if os.path.exists("/etc/salt/minion_id"): + subprocess.run(["chown", "root:root", "/etc/salt/minion_id"], check=False) + + # Clean up pidfile if it exists in the custom location + if os.path.exists("/var/run/salt/minion/minion.pid"): + os.remove("/var/run/salt/minion/minion.pid") + + # Start service + subprocess.run(["systemctl", "start", "salt-minion"], check=False) + + +def test_salt_user_ownership_preserved_on_upgrade( + call_cli, install_salt_systemd, salt_systemd_setup, revert_ownership +): + """ + Test that salt user ownership is preserved during upgrade when no env vars are set. + + This test verifies: + 1. Fresh install with SALT_MINION_USER=salt creates salt:salt ownership + 2. Upgrade WITHOUT environment variables preserves the salt:salt ownership + 3. The RPM %posttrans scriptlet correctly detects and preserves existing ownership + + Test flow: + - Initial install happens with SALT_MINION_USER=salt (via salt_install_env fixture) + - Verify directories are owned by salt:salt + - Upgrade to new version WITHOUT setting any environment variables + - Verify directories are still owned by salt:salt (ownership preserved) + """ + # Skip if this is not an upgrade test + if not install_salt_systemd.upgrade: + pytest.skip("This test requires upgrade testing, run with --upgrade") + + upgrade_version = packaging.version.parse(install_salt_systemd.artifact_version) + + # Verify we have a previous version installed + ret = call_cli.run("--local", "test.version") + assert ret.returncode == 0 + installed_version = packaging.version.parse(ret.data) + + # If we're already at or above the upgrade version, skip downgrade for testing + # (we'll test ownership preservation during same-version "upgrade" with fixed RPM) + if installed_version >= upgrade_version: + log.info( + "Already at target version, will test ownership preservation during reinstall" + ) + # Don't install previous version - test ownership preservation with same version + + log.info( + "Testing ownership preservation from %s to %s", + installed_version, + upgrade_version, + ) + + # The previous version doesn't support SALT_MINION_USER environment variable, + # so we need to manually set up salt:salt ownership AND user configuration + # to simulate a system that was installed with salt user configuration. + + # First, ensure salt user exists + # Use subprocess to ensure we are running as root + log.info("Creating salt user for testing") + try: + # Check if user exists + subprocess.run( + ["id", "salt"], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + # Ensure shell is /bin/bash for cmd.run tests + subprocess.run(["usermod", "-s", "/bin/bash", "salt"], check=False) + except subprocess.CalledProcessError: + # User does not exist, create it with /bin/bash + subprocess.run(["useradd", "-r", "-s", "/bin/bash", "salt"], check=True) + + # Check if the current salt-call version supports --priv option + # The --priv option was added in later versions, but 3006.20 doesn't support it + help_ret = call_cli.run("--help") + supports_priv = "--priv" in help_ret.stdout + + # Define the minion and master directories to ensure both can run as salt user + # The test framework configures master to run as salt user too, so we must fix its permissions + salt_dirs = [ + "/etc/salt/pki", + "/var/cache/salt", + "/var/log/salt", + "/var/run/salt", + "/etc/salt/minion.d", + ] + + # Change ownership to salt:salt BEFORE configuring minion to run as salt user + # This simulates a system where an admin manually configured salt to run as non-root + log.info("Changing ownership to salt:salt (simulating manual configuration)") + + # Pre-create proc directory to ensure ownership (workaround for old versions) + subprocess.run(["mkdir", "-p", "/var/cache/salt/minion/proc"], check=False) + + for dir_path in salt_dirs: + if os.path.exists(dir_path): + subprocess.run(["chown", "-R", "salt:salt", dir_path], check=False) + else: + log.warning("Directory %s does not exist, skipping chown", dir_path) + + # Configure minion to run as salt user + log.info("Configuring minion to run as salt user") + subprocess.run(["mkdir", "-p", "/etc/salt/minion.d"], check=True) + + # Ensure /var/run/salt/minion exists and is owned by salt + subprocess.run(["mkdir", "-p", "/var/run/salt/minion"], check=True) + subprocess.run(["chown", "-R", "salt:salt", "/var/run/salt"], check=True) + + # Write config to main minion file to ensure it's loaded + # Read existing content + if os.path.exists("/etc/salt/minion"): + with salt.utils.files.fopen("/etc/salt/minion", "r") as f: + content = f.readlines() + else: + content = [] + + # Filter out existing user: and pidfile: lines to avoid duplicates that confuse RPM scriptlets + content = [ + line + for line in content + if not line.strip().startswith("user:") + and not line.strip().startswith("pidfile:") + ] + + # Append new config + if content and not content[-1].endswith("\n"): + content.append("\n") + content.append("user: salt\n") + content.append("pidfile: /var/run/salt/minion/minion.pid\n") + + with salt.utils.files.fopen("/etc/salt/minion", "w") as f: + f.writelines(content) + + # Also write to minion.d for completeness/testing include + with salt.utils.files.fopen("/etc/salt/minion.d/user.conf", "w") as f: + f.write("user: salt\n") + f.write("pidfile: /var/run/salt/minion/minion.pid\n") + + # Also update systemd unit to run as salt user to avoid permission issues with proc dir + # This is a workaround for what seems to be a bug in salt-minion dropping privileges + # where it might reset permissions to root before dropping privileges. + # Using systemd User= directive ensures it starts as salt user. + log.info("Configuring systemd to run minion as salt user") + service_override = "[Service]\nUser=salt\nGroup=salt" + subprocess.run( + ["mkdir", "-p", "/etc/systemd/system/salt-minion.service.d"], check=True + ) + with salt.utils.files.fopen( + "/etc/systemd/system/salt-minion.service.d/override.conf", "w" + ) as f: + f.write(service_override) + subprocess.run(["systemctl", "daemon-reload"], check=True) + + # Restart minion to apply the user configuration + # Now the minion can successfully start as salt user and read salt:salt keys + log.info("Restarting minion to apply user configuration") + subprocess.run(["systemctl", "restart", "salt-minion"], check=True) + time.sleep(5) # Wait for minion to restart + + # Verify minion is running as salt user + try: + # Get PID of salt-minion process + pgrep_out = ( + subprocess.check_output(["pgrep", "-f", "salt-minion"]).decode().strip() + ) + if not pgrep_out: + log.warning("Could not find salt-minion process") + else: + # Take the first PID if multiple are returned (e.g. main process and children) + pid = pgrep_out.split("\n", 1)[0] + ps_out = ( + subprocess.check_output(["ps", "-o", "user=", "-p", pid]) + .decode() + .strip() + ) + log.info("Minion process (PID %s) is running as user: %s", pid, ps_out) + except (subprocess.CalledProcessError, ValueError, OSError) as e: + log.warning("Could not verify minion user: %s", e) + + log.info("Verifying pre-upgrade ownership is salt:salt") + for dir_path in salt_dirs: + test_cmd = f"ls -ld {dir_path}" + ret = call_cli.run("--local", "cmd.run", test_cmd) + if ret.returncode != 0: + log.warning("Directory %s does not exist, skipping", dir_path) + continue + + # Use ret.data instead of ret.stdout to get the actual command output + # ls -ld output format: perms links user group size date time name + parts = ret.data.strip().split() + test_user = parts[2] + test_group = parts[3] + + assert ( + test_user == "salt" + ), f"Before upgrade: Expected {dir_path} owned by salt, got {test_user}. Full output: {ret.data}" + assert ( + test_group == "salt" + ), f"Before upgrade: Expected {dir_path} group salt, got {test_group}. Full output: {ret.data}" + + # Stop the minion before upgrade to avoid permission conflicts during file replacement + # When minion runs as non-root user, the upgrade temporarily installs files as root, + # which can cause permission denied errors if minion tries to access them during upgrade + log.info("Stopping minion before upgrade") + # Use subprocess to ensure we run as root, since salt-call might drop privileges + subprocess.run(["systemctl", "stop", "salt-minion"], check=True) + time.sleep(2) # Wait for minion to fully stop + + # Now upgrade WITHOUT setting environment variables + # The RPM %posttrans scriptlet should detect existing ownership and preserve it + log.info("Upgrading to version %s WITHOUT environment variables", upgrade_version) + install_salt_systemd.install(upgrade=True) + time.sleep(10) # Allow time for services to restart + + # Recheck for --priv support after upgrade (new version should support it) + help_ret = call_cli.run("--help") + supports_priv = "--priv" in help_ret.stdout + + # Verify we upgraded successfully + if supports_priv: + ret = call_cli.run("--local", "--priv=root", "test.version") + else: + ret = call_cli.run("--local", "test.version") + assert ret.returncode == 0 + installed_version = packaging.version.parse(ret.data) + # Allow for local build suffix (e.g. 3006.23+16.gd5601f672d) + assert ( + installed_version >= upgrade_version + ), f"Expected version >= {upgrade_version}, got {installed_version}" + + # Verify that ownership is STILL salt:salt (preserved during upgrade) + log.info("Verifying post-upgrade ownership is still salt:salt") + for dir_path in salt_dirs: + test_cmd = f"ls -ld {dir_path}" + if supports_priv: + ret = call_cli.run("--local", "--priv=root", "cmd.run", test_cmd) + else: + ret = call_cli.run("--local", "cmd.run", test_cmd) + if ret.returncode != 0: + log.warning("Directory %s does not exist, skipping", dir_path) + continue + + # Use ret.data instead of ret.stdout to get the actual command output + parts = ret.data.strip().split() + test_user = parts[2] + test_group = parts[3] + + assert ( + test_user == "salt" + ), f"After upgrade: Expected {dir_path} owned by salt, got {test_user}. Ownership was not preserved! Full output: {ret.data}" + assert ( + test_group == "salt" + ), f"After upgrade: Expected {dir_path} group salt, got {test_group}. Ownership was not preserved! Full output: {ret.data}" + + log.info("SUCCESS: salt:salt ownership was preserved during upgrade") + + # Now verify that running salt-call and salt-pip as root still preserves salt user ownership + # Both commands should drop privileges to the configured user and not create root-owned files + log.info("Testing that salt-call run as root preserves salt:salt ownership") + + # Run a salt-call command that will access cache + # We do NOT use --priv=root here because we WANT salt-call to drop privileges + # to the configured user ('salt') and create files as 'salt'. + # If we use --priv=root, it forces execution as root, creating root-owned files + # which causes the subsequent check to fail. + ret = call_cli.run("--local", "test.ping") + assert ret.returncode == 0 + + # Run salt-pip directly to list packages (verifies pip works and permissions) + # We avoid installing packages to prevent network timeout issues in restricted environments + log.info("Running salt-pip list to verify functionality and permissions") + # Similarly, we want salt-pip to drop privileges + ret = call_cli.run("--local", "cmd.run", "salt-pip list") + assert ret.returncode == 0 + + # Now verify NO files in the cache directories are owned by root + log.info("Verifying no root-owned files were created in salt user directories") + for dir_path in salt_dirs: + # Find all files in the directory and check ownership + # Use subprocess to run as root to ensure we can see all files + try: + find_out = ( + subprocess.check_output( + ["find", dir_path, "-type", "f", "-uid", "0"], + stderr=subprocess.DEVNULL, + ) + .decode() + .strip() + ) + + if find_out: + # Found root-owned files! + root_owned_files = find_out.split("\n") + + # Filter out known root-owned files + # .root_key is created by master for root-to-master communication + root_owned_files = [ + f for f in root_owned_files if not f.endswith("/.root_key") + ] + + if root_owned_files: + pytest.fail( + f"Found root-owned files in {dir_path} after running salt-call/salt-pip:\n" + + "\n".join(root_owned_files[:10]) # Show first 10 files + + f"\n... ({len(root_owned_files)} total root-owned files)" + ) + except subprocess.CalledProcessError: + # find command failed (e.g. directory not found) + log.warning("Could not check for root-owned files in %s", dir_path) + + # Additional verification: check that /opt/saltstack/salt is owned by salt:salt + log.info("Verifying /opt/saltstack/salt ownership after upgrade") + test_cmd = "ls -ld /opt/saltstack/salt" + if supports_priv: + ret = call_cli.run("--local", "--priv=root", "cmd.run", test_cmd) + else: + ret = call_cli.run("--local", "cmd.run", test_cmd) + if ret.returncode == 0: + parts = ret.data.strip().split() + install_user = parts[2] + install_group = parts[3] + assert ( + install_user == "salt" + ), f"Installation directory /opt/saltstack/salt owned by {install_user}, expected salt" + assert ( + install_group == "salt" + ), f"Installation directory /opt/saltstack/salt group {install_group}, expected salt" + + # Verify salt-pip works as salt user + log.info("Verifying salt-pip functionality as salt user") + # We use cmd.run to execute as the minion user (salt) + ret = call_cli.run("--local", "cmd.run", "salt-pip list") + assert ret.returncode == 0, f"salt-pip list failed: {ret.stderr}" + + # Verify that the extras directory was created and is owned by salt:salt + extras_dir = ( + f"/opt/saltstack/salt/extras-{sys.version_info.major}.{sys.version_info.minor}" + ) + log.info( + "Verifying extras directory %s was created and owned correctly", extras_dir + ) + test_cmd = f"ls -ld {extras_dir} 2>/dev/null || echo 'Directory not found'" + if supports_priv: + ret = call_cli.run("--local", "--priv=root", "cmd.run", test_cmd) + else: + ret = call_cli.run("--local", "cmd.run", test_cmd) + if "Directory not found" not in ret.data: + parts = ret.data.strip().split() + extras_user = parts[2] + extras_group = parts[3] + assert ( + extras_user == "salt" + ), f"Extras directory {extras_dir} owned by {extras_user}, expected salt" + assert ( + extras_group == "salt" + ), f"Extras directory {extras_dir} group {extras_group}, expected salt" + + log.info( + "SUCCESS: No root-owned files created, salt-call and salt-pip properly dropped privileges, and installation directory ownership preserved" + ) diff --git a/tests/pytests/pkg/upgrade/systemd/test_permissions.py b/tests/pytests/pkg/upgrade/systemd/test_permissions.py index d52423f0ce2e..99696b3c6d2c 100644 --- a/tests/pytests/pkg/upgrade/systemd/test_permissions.py +++ b/tests/pytests/pkg/upgrade/systemd/test_permissions.py @@ -1,8 +1,12 @@ import logging +import os +import subprocess import time import pytest +import salt.utils.files + pytestmark = [ pytest.mark.skip_unless_on_linux(reason="Only supported on Linux family"), ] @@ -10,7 +14,79 @@ log = logging.getLogger(__name__) -def test_salt_ownership_permission(call_cli, install_salt_systemd, salt_systemd_setup): +@pytest.fixture +def revert_permissions(call_cli): + """ + Fixture to revert permissions and configuration changes made during the test. + + This is critical because upgrade tests run in a shared environment (container) + where the package is upgraded but NOT uninstalled between tests (to allow inspection). + If a test modifies global configuration (like /etc/salt/master user) or file ownership, + and fails before cleaning up, subsequent tests (including integration tests) will + run in a dirty environment and likely fail. + + This fixture ensures that: + 1. Services are stopped to release locks/files. + 2. User configuration in /etc/salt/{master,minion} is reverted to defaults (root). + 3. File ownership is restored to root:root. + 4. Services are restarted to apply clean configuration. + + NOTE: We use subprocess.run instead of call_cli.run for cleanup because if the + test fails while the minion is configured to run as 'salt', salt-call might also + drop privileges and fail to perform root-level cleanup operations. Using subprocess + ensures we run as root (the user running the tests). + """ + yield + log.info("Reverting permissions and configuration to defaults") + + # Stop services first + test_list = ["salt-api", "salt-minion", "salt-master"] + for test_item in test_list: + subprocess.run(["systemctl", "stop", test_item], check=False) + + # Revert config files - comment out 'user:' lines to default to root + # Use sed directly to avoid salt-call permission issues + for config_file in ["/etc/salt/master", "/etc/salt/minion"]: + if os.path.exists(config_file): + subprocess.run(["sed", "-i", "s/^user:/#user:/g", config_file], check=False) + # Ensure root user is set explicitly if needed, or just rely on commenting out + # Appending 'user: root' might be safer if previous appends are still there + with salt.utils.files.fopen(config_file, "a") as f: + f.write("\nuser: root\n") + + # Restore ownership of runtime directories to root:root + # This is a broad cleanup to ensure no files are left owned by 'horse' or 'donkey' + dirs = [ + "/etc/salt/pki", + "/var/cache/salt", + "/var/log/salt", + "/var/run/salt", + "/opt/saltstack/salt", + ] + for d in dirs: + if os.path.exists(d): + subprocess.run(["chown", "-R", "root:root", d], check=False) + + # Also fix minion_id which might have been created/owned by non-root user + if os.path.exists("/etc/salt/minion_id"): + subprocess.run(["chown", "root:root", "/etc/salt/minion_id"], check=False) + + # Also fix /etc/salt/minion.d/_schedule.conf which might have been created/owned by non-root user + if os.path.exists("/etc/salt/minion.d/_schedule.conf"): + subprocess.run( + ["chown", "root:root", "/etc/salt/minion.d/_schedule.conf"], check=False + ) + + # Restart services + for test_item in test_list: + subprocess.run(["systemctl", "start", test_item], check=False) + + time.sleep(10) # Allow time for restart + + +def test_salt_ownership_permission( + call_cli, install_salt_systemd, salt_systemd_setup, revert_permissions +): """ Test upgrade of Salt packages preserve existing ownership """ @@ -54,34 +130,44 @@ def test_salt_ownership_permission(call_cli, install_salt_systemd, salt_systemd_ # create master user, and minion user, change conf, restart and test ownership test_master_user = "horse" test_minion_user = "donkey" - try: - ret = call_cli.run("--local", "user.list_users") - user_list = ret.stdout.strip().split(":")[1] - if test_master_user not in user_list: - ret = call_cli.run( - "--local", "user.add", f"{test_master_user}", usergroup=True - ) + ret = call_cli.run("--local", "user.list_users") + user_list = ret.stdout.strip().split(":")[1] - if test_minion_user not in user_list: - ret = call_cli.run( - "--local", "user.add", f"{test_minion_user}", usergroup=True - ) + if test_master_user not in user_list: + ret = call_cli.run("--local", "user.add", f"{test_master_user}", usergroup=True) - ret = call_cli.run("--local", "file.comment_line", "/etc/salt/master", "^user:") - assert ret.returncode == 0 + if test_minion_user not in user_list: + ret = call_cli.run("--local", "user.add", f"{test_minion_user}", usergroup=True) - ret = call_cli.run("--local", "file.comment_line", "/etc/salt/minion", "^user:") - assert ret.returncode == 0 + ret = call_cli.run("--local", "file.comment_line", "/etc/salt/master", "^user:") + assert ret.returncode == 0 + + ret = call_cli.run("--local", "file.comment_line", "/etc/salt/minion", "^user:") + assert ret.returncode == 0 + + test_string = f"\nuser: {test_master_user}\n" + ret = call_cli.run("--local", "file.append", "/etc/salt/master", test_string) + + test_string = f"\nuser: {test_minion_user}\n" + ret = call_cli.run("--local", "file.append", "/etc/salt/minion", test_string) + + # Check if the current salt-call version supports --priv option + # We do this before the upgrade since we're still on the old version + help_ret = call_cli.run("--help") + supports_priv = "--priv" in help_ret.stdout - test_string = f"\nuser: {test_master_user}\n" - ret = call_cli.run("--local", "file.append", "/etc/salt/master", test_string) + # restart and check ownership is correct + # Use --priv=root if supported, otherwise run without it + test_list = ["salt-api", "salt-minion", "salt-master"] + for test_item in test_list: + test_cmd = f"systemctl restart {test_item}" + if supports_priv: + ret = call_cli.run("--local", "--priv=root", "cmd.run", test_cmd) + else: + ret = call_cli.run("--local", "cmd.run", test_cmd) - test_string = f"\nuser: {test_minion_user}\n" - ret = call_cli.run("--local", "file.append", "/etc/salt/minion", test_string) - except (OSError, AssertionError) as e: - # Skip if user management or file operations fail due to environment issues - pytest.skip(f"User and config setup failed: {e}") + time.sleep(10) # allow some time for restart # restart and check ownership is correct try: @@ -122,7 +208,7 @@ def test_salt_ownership_permission(call_cli, install_salt_systemd, salt_systemd_ test_list = ["salt-api", "salt-minion", "salt-master"] for test_item in test_list: test_cmd = f"ls -dl /run/{test_item}.pid" - ret = call_cli.run("--local", "cmd.run", test_cmd) + ret = call_cli.run("--local", "--priv=root", "cmd.run", test_cmd) assert ret.returncode == 0 test_user = ret.stdout.strip().split()[4] @@ -135,23 +221,4 @@ def test_salt_ownership_permission(call_cli, install_salt_systemd, salt_systemd_ assert test_user == f"{test_master_user}" assert test_group == f"{test_master_user}" - # restore to defaults to ensure further tests run fine - ret = call_cli.run("--local", "file.comment_line", "/etc/salt/master", "^user:") - assert ret.returncode == 0 - - ret = call_cli.run("--local", "file.comment_line", "/etc/salt/minion", "^user:") - assert ret.returncode == 0 - - test_string = "\nuser: salt\n" - ret = call_cli.run("--local", "file.append", "/etc/salt/master", test_string) - - test_string = "\nuser: root\n" - ret = call_cli.run("--local", "file.append", "/etc/salt/minion", test_string) - - # restart and check ownership is correct - test_list = ["salt-api", "salt-minion", "salt-master"] - for test_item in test_list: - test_cmd = f"systemctl restart {test_item}" - ret = call_cli.run("--local", "cmd.run", test_cmd) - - time.sleep(10) # allow some time for restart + # Cleanup is now handled by the revert_permissions fixture diff --git a/tests/pytests/pkg/upgrade/test_salt_upgrade.py b/tests/pytests/pkg/upgrade/test_salt_upgrade.py index 6dca2f12885d..fd6dbf9ae8c5 100644 --- a/tests/pytests/pkg/upgrade/test_salt_upgrade.py +++ b/tests/pytests/pkg/upgrade/test_salt_upgrade.py @@ -45,10 +45,8 @@ def salt_test_upgrade( # Verify previous install version salt-minion is setup correctly and works ret = salt_call_cli.run("--local", "test.version") assert ret.returncode == 0 - installed_minion_version = packaging.version.parse(ret.data) - assert installed_minion_version < packaging.version.parse( - install_salt.artifact_version - ) + start_version = packaging.version.parse(ret.data) + assert start_version <= packaging.version.parse(install_salt.artifact_version) # Verify previous install version salt-master is setup correctly and works bin_file = "salt" @@ -58,7 +56,7 @@ def salt_test_upgrade( assert ret.returncode == 0 assert packaging.version.parse( ret.stdout.strip().split()[1] - ) < packaging.version.parse(install_salt.artifact_version) + ) <= packaging.version.parse(install_salt.artifact_version) # Verify there is a running minion and master by getting their PIDs if platform.is_windows(): @@ -123,8 +121,11 @@ def salt_test_upgrade( if sys.platform == "linux" and install_salt.distro_id not in ("ubuntu", "debian"): assert new_minion_pids assert new_master_pids - assert new_minion_pids != old_minion_pids - assert new_master_pids != old_master_pids + if start_version < packaging.version.parse(install_salt.artifact_version): + assert new_minion_pids != old_minion_pids + assert new_master_pids != old_master_pids + else: + log.info("Versions are identical, skipping PID change check") log.info("**** salt_test_upgrade - end *****") diff --git a/tests/pytests/unit/cli/test_call.py b/tests/pytests/unit/cli/test_call.py new file mode 100644 index 000000000000..9e8891ee17e1 --- /dev/null +++ b/tests/pytests/unit/cli/test_call.py @@ -0,0 +1,155 @@ +import salt.cli.call +import salt.defaults.exitcodes +from tests.support.mock import MagicMock, patch + + +def test_check_user_called_even_with_sudo_user(): + with patch("salt.utils.parsers.SaltCallOptionParser.parse_args"), patch( + "salt.cli.caller.Caller" + ), patch("salt.utils.verify.verify_env"), patch( + "salt.utils.verify.check_user", return_value=True + ) as mock_check_user, patch( + "salt.utils.user.get_user", return_value="root" + ): + + salt_call = salt.cli.call.SaltCall() + + # Setup mock config with sudo_user set + salt_call.config = { + "verify_env": True, + "pki_dir": "/etc/salt/pki", + "cachedir": "/var/cache/salt", + "extension_modules": "/var/cache/salt/extmods", + "user": "salt", + "sudo_user": "salt", # sudo_user is set + "permissive_pki_access": False, + } + salt_call.options = MagicMock() + salt_call.options.user = None + salt_call.options.master = None + salt_call.options.doc = False + salt_call.options.grains_run = False + salt_call.options.local = False + salt_call.options.file_root = None + salt_call.options.pillar_root = None + salt_call.options.states_dir = None + + salt_call.run() + + # check_user SHOULD be called even if sudo_user is set + # because we no longer implicitly skip check_user based on sudo_user presence + mock_check_user.assert_called_with("salt") + + +def test_check_user_called_without_sudo_user(): + with patch("salt.utils.parsers.SaltCallOptionParser.parse_args"), patch( + "salt.cli.caller.Caller" + ), patch("salt.utils.verify.verify_env"), patch( + "salt.utils.verify.check_user", return_value=True + ) as mock_check_user, patch( + "salt.utils.user.get_user", return_value="root" + ): + + salt_call = salt.cli.call.SaltCall() + + # Setup mock config WITHOUT sudo_user + salt_call.config = { + "verify_env": True, + "pki_dir": "/etc/salt/pki", + "cachedir": "/var/cache/salt", + "extension_modules": "/var/cache/salt/extmods", + "user": "salt", + "sudo_user": None, + "permissive_pki_access": False, + } + salt_call.options = MagicMock() + salt_call.options.user = None + salt_call.options.master = None + salt_call.options.doc = False + salt_call.options.grains_run = False + salt_call.options.local = False + salt_call.options.file_root = None + salt_call.options.pillar_root = None + salt_call.options.states_dir = None + + salt_call.run() + + # check_user SHOULD be called + mock_check_user.assert_called_with("salt") + + +def test_check_user_skipped_when_already_correct_user(): + with patch("salt.utils.parsers.SaltCallOptionParser.parse_args"), patch( + "salt.cli.caller.Caller" + ), patch("salt.utils.verify.verify_env"), patch( + "salt.utils.verify.check_user" + ) as mock_check_user, patch( + "salt.utils.user.get_user", return_value="salt" + ): + + salt_call = salt.cli.call.SaltCall() + + # Setup mock config where user matches current user + salt_call.config = { + "verify_env": True, + "pki_dir": "/etc/salt/pki", + "cachedir": "/var/cache/salt", + "extension_modules": "/var/cache/salt/extmods", + "user": "salt", + "sudo_user": None, + "permissive_pki_access": False, + } + salt_call.options = MagicMock() + salt_call.options.user = None + salt_call.options.master = None + salt_call.options.doc = False + salt_call.options.grains_run = False + salt_call.options.local = False + salt_call.options.file_root = None + salt_call.options.pillar_root = None + salt_call.options.states_dir = None + + salt_call.run() + + # check_user should NOT be called as we are already the correct user + mock_check_user.assert_not_called() + + +def test_check_user_called_with_cli_override(): + with patch("salt.utils.parsers.SaltCallOptionParser.parse_args"), patch( + "salt.cli.caller.Caller" + ), patch("salt.utils.verify.verify_env"), patch( + "salt.utils.verify.check_user", return_value=True + ) as mock_check_user, patch( + "salt.utils.user.get_user", return_value="root" + ): + + salt_call = salt.cli.call.SaltCall() + + # Setup mock config + salt_call.config = { + "verify_env": True, + "pki_dir": "/etc/salt/pki", + "cachedir": "/var/cache/salt", + "extension_modules": "/var/cache/salt/extmods", + "user": "salt", + "sudo_user": None, + "permissive_pki_access": False, + } + # Override user via options + salt_call.options = MagicMock() + salt_call.options.user = "custom_user" + salt_call.options.master = None + salt_call.options.doc = False + salt_call.options.grains_run = False + salt_call.options.local = False + salt_call.options.file_root = None + salt_call.options.pillar_root = None + salt_call.options.states_dir = None + + salt_call.run() + + # verify config was updated + assert salt_call.config["user"] == "custom_user" + # check_user called with override value + mock_check_user.assert_called_with("custom_user") diff --git a/tests/pytests/unit/executors/test_sudo.py b/tests/pytests/unit/executors/test_sudo.py new file mode 100644 index 000000000000..376a811795d0 --- /dev/null +++ b/tests/pytests/unit/executors/test_sudo.py @@ -0,0 +1,52 @@ +import salt.executors.sudo +from tests.support.mock import MagicMock, patch + + +def test_sudo_execute_adds_priv_arg(): + # Setup inputs + opts = {"sudo_user": "saltdev", "config_dir": "/etc/salt"} + data = {"fun": "test.ping"} + func = MagicMock() + args = [] + kwargs = {} + + # Mock __salt__ and cmd.run_all + mock_run_all = MagicMock( + return_value={ + "retcode": 0, + "stdout": '{"local": {"return": true, "retcode": 0}}', + } + ) + + # Mock __context__ + context_dict = {} + + # Initialize dunder dictionaries if they don't exist + if not hasattr(salt.executors.sudo, "__salt__"): + salt.executors.sudo.__salt__ = {} + if not hasattr(salt.executors.sudo, "__context__"): + salt.executors.sudo.__context__ = {} + + with patch.dict( + salt.executors.sudo.__salt__, {"cmd.run_all": mock_run_all} + ), patch.dict(salt.executors.sudo.__context__, context_dict): + + salt.executors.sudo.execute(opts, data, func, args, kwargs) + + # Verify the command called includes --priv saltdev + call_args = mock_run_all.call_args[0][0] + + # Check expected parts of command + assert "sudo" in call_args + assert "-u" in call_args + assert "saltdev" in call_args + assert "salt-call" in call_args + assert "--priv" in call_args + + # Verify --priv follows salt-call and precedes saltdev + salt_call_idx = call_args.index("salt-call") + priv_idx = call_args.index("--priv") + user_idx = priv_idx + 1 + + assert priv_idx > salt_call_idx + assert call_args[user_idx] == "saltdev" diff --git a/tests/pytests/unit/loader/test_grains_cleanup.py b/tests/pytests/unit/loader/test_grains_cleanup.py index 6305e69898bf..f1c621ebc8e5 100644 --- a/tests/pytests/unit/loader/test_grains_cleanup.py +++ b/tests/pytests/unit/loader/test_grains_cleanup.py @@ -266,6 +266,17 @@ def test_clean_modules_removes_from_sys_modules(minion_opts): f"{loaded_base_name}.ext.{tag}", } + # Prefixes for modules that belong specifically to this loader's tag. + # clean_modules() only removes modules under these prefixes, so we only + # check these prefixes — not ALL salt.loaded.* modules. Checking the + # broader namespace would make the test sensitive to modules loaded by + # other tests that ran in the same process (e.g. salt.loaded.int.modules.* + # from execution-module unit tests). + tag_prefixes = ( + f"{loaded_base_name}.int.{tag}.", + f"{loaded_base_name}.ext.{tag}.", + ) + # Load some modules for key in list(loader.keys())[:5]: try: @@ -273,8 +284,10 @@ def test_clean_modules_removes_from_sys_modules(minion_opts): except Exception: # pylint: disable=broad-except pass - # Find modules that were loaded - loaded_before = [m for m in sys.modules if m.startswith(loaded_base_name)] + # Find tag-specific modules that were loaded + loaded_before = [ + m for m in sys.modules if any(m.startswith(p) for p in tag_prefixes) + ] assert len(loaded_before) > 0, "No modules were loaded for testing" # Clean modules @@ -285,7 +298,7 @@ def test_clean_modules_removes_from_sys_modules(minion_opts): # All remaining modules should be base stubs or utils modules (shared infrastructure) # Filter out both base stubs and utils modules - unexpected = [] + remaining_tag = [] for m in remaining: # Skip base stubs if m in expected_base_stubs: @@ -296,11 +309,11 @@ def test_clean_modules_removes_from_sys_modules(minion_opts): if len(parts) >= 4 and parts[3] in ("utils", "wrapper"): continue # Anything else is unexpected - unexpected.append(m) + remaining_tag.append(m) assert ( - len(unexpected) == 0 - ), f"clean_modules() failed to remove {len(unexpected)} modules: {unexpected}" + len(remaining_tag) == 0 + ), f"clean_modules() failed to remove {len(remaining_tag)} modules: {remaining_tag}" # Base stubs should still be present for stub in expected_base_stubs: diff --git a/tests/pytests/unit/modules/test_archive.py b/tests/pytests/unit/modules/test_archive.py index 254e2a9df7d2..10d3d038dfb8 100644 --- a/tests/pytests/unit/modules/test_archive.py +++ b/tests/pytests/unit/modules/test_archive.py @@ -184,7 +184,7 @@ def test_zip(): **{ "isdir": MagicMock(return_value=False), "exists": MagicMock(return_value=True), - } + }, ): with patch("zipfile.ZipFile", MagicMock()): ret = archive.zip_( diff --git a/tests/pytests/unit/modules/test_debian_ip.py b/tests/pytests/unit/modules/test_debian_ip.py index 6ae8cc467dbb..2b7b636965ef 100644 --- a/tests/pytests/unit/modules/test_debian_ip.py +++ b/tests/pytests/unit/modules/test_debian_ip.py @@ -1117,7 +1117,7 @@ def test_build_interface(test_interfaces): iface_type=iface["iface_type"], enabled=iface["enabled"], interface_file=tfile.name, - **iface["build_interface"] + **iface["build_interface"], ) == iface["return"] ) diff --git a/tests/pytests/unit/modules/test_saltutil.py b/tests/pytests/unit/modules/test_saltutil.py index 7306c3434057..4c2172149bf3 100644 --- a/tests/pytests/unit/modules/test_saltutil.py +++ b/tests/pytests/unit/modules/test_saltutil.py @@ -43,7 +43,7 @@ def test_exec_kwargs(): s.tgt_type, s.ret, s.kwarg, - **{"batch": s.batch} + **{"batch": s.batch}, ) client.cmd_batch.assert_called_with(batch=s.batch, **_cmd_expected_kwargs) @@ -56,7 +56,7 @@ def test_exec_kwargs(): s.tgt_type, s.ret, s.kwarg, - **{"subset": s.subset} + **{"subset": s.subset}, ) client.cmd_subset.assert_called_with( subset=s.subset, cli=True, **_cmd_expected_kwargs @@ -71,7 +71,7 @@ def test_exec_kwargs(): s.tgt_type, s.ret, s.kwarg, - **{"subset": s.subset, "cli": s.cli} + **{"subset": s.subset, "cli": s.cli}, ) client.cmd_subset.assert_called_with( subset=s.subset, cli=s.cli, **_cmd_expected_kwargs @@ -87,7 +87,7 @@ def test_exec_kwargs(): s.tgt_type, s.ret, s.kwarg, - **{"subset": s.subset, "batch": s.batch} + **{"subset": s.subset, "batch": s.batch}, ) client.cmd_batch.assert_called_with(batch=s.batch, **_cmd_expected_kwargs) diff --git a/tests/pytests/unit/modules/test_zabbix.py b/tests/pytests/unit/modules/test_zabbix.py index fd9fae8c1a60..8d603f1d05d5 100644 --- a/tests/pytests/unit/modules/test_zabbix.py +++ b/tests/pytests/unit/modules/test_zabbix.py @@ -587,7 +587,7 @@ def test_user_addmedia(conn_args, set_zabbix_version, query_return, mock_login): period="1-7,00:00-24:00", sendto="support2@example.com", severity="63", - **conn_args + **conn_args, ) == module_return ) @@ -613,7 +613,7 @@ def test_user_addmedia_v40(conn_args, set_zabbix_version, query_return, mock_log period="1-7,00:00-24:00", sendto="support2@example.com", severity="63", - **conn_args + **conn_args, ) == module_return ) diff --git a/tests/pytests/unit/pillar/test_stack.py b/tests/pytests/unit/pillar/test_stack.py index d3e6c0ba0e6b..01289e1dede6 100644 --- a/tests/pytests/unit/pillar/test_stack.py +++ b/tests/pytests/unit/pillar/test_stack.py @@ -54,7 +54,7 @@ def test_extpillar_stack1(): "opts:saltenv": { # **kwargs "dev": "/path/to/dev/static.cfg", } - } + }, ) assert fake_dict == result @@ -65,7 +65,7 @@ def test_extpillar_stack1(): "opts:saltenv": { # **kwargs "__env__": "/path/to/__env__/dynamic.cfg", } - } + }, ) assert fake_dict == result diff --git a/tests/pytests/unit/renderers/test_stateconf.py b/tests/pytests/unit/renderers/test_stateconf.py index 34899e3bb5c1..71702c9f420e 100644 --- a/tests/pytests/unit/renderers/test_stateconf.py +++ b/tests/pytests/unit/renderers/test_stateconf.py @@ -39,7 +39,7 @@ def __call__( sls=sls, argline=argline, renderers=salt.loader.render(config, {}), - **kws + **kws, ) diff --git a/tests/pytests/unit/states/postgresql/test_group.py b/tests/pytests/unit/states/postgresql/test_group.py index 6957ce545403..2965df0ff94c 100644 --- a/tests/pytests/unit/states/postgresql/test_group.py +++ b/tests/pytests/unit/states/postgresql/test_group.py @@ -104,7 +104,7 @@ def test_present_create_basic(mocks, db_args): replication=None, rolepassword=None, groups=None, - **db_args + **db_args, ) mocks["postgres.group_update"].assert_not_called() @@ -179,7 +179,7 @@ def test_present_change_option(mocks, existing_group, db_args): replication=True, rolepassword=None, groups=None, - **db_args + **db_args, ) @@ -202,7 +202,7 @@ def test_present_create_md5_password(mocks, md5_pw, db_args): replication=None, rolepassword=md5_pw, groups=None, - **db_args + **db_args, ) mocks["postgres.group_update"].assert_not_called() @@ -228,7 +228,7 @@ def test_present_create_plain_password(mocks, db_args): replication=None, rolepassword="password", groups=None, - **db_args + **db_args, ) mocks["postgres.group_update"].assert_not_called() @@ -261,7 +261,7 @@ def test_present_create_md5_password_default_encrypted( replication=None, rolepassword=md5_pw, groups=None, - **db_args + **db_args, ) mocks["postgres.group_update"].assert_not_called() @@ -285,7 +285,7 @@ def test_present_create_md5_prehashed(mocks, md5_pw, db_args): replication=None, rolepassword=md5_pw, groups=None, - **db_args + **db_args, ) mocks["postgres.group_update"].assert_not_called() @@ -343,7 +343,7 @@ def test_present_update_md5_password(mocks, existing_group, md5_pw, db_args): replication=None, rolepassword=md5_pw, groups=None, - **db_args + **db_args, ) @@ -390,7 +390,7 @@ def test_present_update_password_no_check(mocks, existing_group, md5_pw, db_args replication=None, rolepassword=md5_pw, groups=None, - **db_args + **db_args, ) diff --git a/tests/pytests/unit/states/postgresql/test_user.py b/tests/pytests/unit/states/postgresql/test_user.py index 1d5dba9b1bb3..5807234bae35 100644 --- a/tests/pytests/unit/states/postgresql/test_user.py +++ b/tests/pytests/unit/states/postgresql/test_user.py @@ -119,7 +119,7 @@ def test_present_create_basic(mocks, db_args): rolepassword=None, valid_until=None, groups=None, - **db_args + **db_args, ) mocks["postgres.user_update"].assert_not_called() @@ -195,7 +195,7 @@ def test_present_change_option(mocks, existing_user, db_args): rolepassword=None, valid_until=None, groups=None, - **db_args + **db_args, ) @@ -219,7 +219,7 @@ def test_present_create_md5_password(mocks, md5_pw, db_args): rolepassword=md5_pw, valid_until=None, groups=None, - **db_args + **db_args, ) mocks["postgres.user_update"].assert_not_called() @@ -246,7 +246,7 @@ def test_present_create_scram_password(mocks, db_args): rolepassword=ScramHash(), valid_until=None, groups=None, - **db_args + **db_args, ) mocks["postgres.user_update"].assert_not_called() @@ -271,7 +271,7 @@ def test_present_create_plain_password(mocks, db_args): rolepassword="password", valid_until=None, groups=None, - **db_args + **db_args, ) mocks["postgres.user_update"].assert_not_called() @@ -305,7 +305,7 @@ def test_present_create_md5_password_default_encrypted( rolepassword=md5_pw, valid_until=None, groups=None, - **db_args + **db_args, ) mocks["postgres.user_update"].assert_not_called() @@ -330,7 +330,7 @@ def test_present_create_md5_prehashed(mocks, md5_pw, db_args): rolepassword=md5_pw, valid_until=None, groups=None, - **db_args + **db_args, ) mocks["postgres.user_update"].assert_not_called() @@ -421,7 +421,7 @@ def test_present_update_md5_password(mocks, existing_user, md5_pw, db_args): rolepassword=md5_pw, valid_until=None, groups=None, - **db_args + **db_args, ) @@ -456,7 +456,7 @@ def test_present_refresh_scram_password(mocks, existing_user, scram_pw, db_args) rolepassword=ScramHash(), valid_until=None, groups=None, - **db_args + **db_args, ) @@ -504,7 +504,7 @@ def test_present_update_password_no_check(mocks, existing_user, md5_pw, db_args) rolepassword=md5_pw, valid_until=None, groups=None, - **db_args + **db_args, ) @@ -530,7 +530,7 @@ def test_present_create_default_password(mocks, md5_pw, db_args): rolepassword=md5_pw, valid_until=None, groups=None, - **db_args + **db_args, ) @@ -556,7 +556,7 @@ def test_present_create_unused_default_password(mocks, md5_pw, db_args): rolepassword=md5_pw, valid_until=None, groups=None, - **db_args + **db_args, ) mocks["postgres.user_update"].assert_not_called() @@ -603,7 +603,7 @@ def test_present_plain_to_scram(mocks, existing_user, db_args): rolepassword=ScramHash(), valid_until=None, groups=None, - **db_args + **db_args, ) @@ -631,7 +631,7 @@ def test_present_plain_to_md5(mocks, existing_user, md5_pw, db_args): rolepassword=md5_pw, valid_until=None, groups=None, - **db_args + **db_args, ) @@ -660,7 +660,7 @@ def test_present_md5_to_scram(mocks, existing_user, db_args): rolepassword=ScramHash(), valid_until=None, groups=None, - **db_args + **db_args, ) @@ -688,7 +688,7 @@ def test_present_scram_to_md5(mocks, existing_user, scram_pw, md5_pw, db_args): rolepassword=md5_pw, valid_until=None, groups=None, - **db_args + **db_args, ) diff --git a/tests/pytests/unit/states/test_boto_cloudwatch_event.py b/tests/pytests/unit/states/test_boto_cloudwatch_event.py index 684744464e7d..49a8a769d572 100644 --- a/tests/pytests/unit/states/test_boto_cloudwatch_event.py +++ b/tests/pytests/unit/states/test_boto_cloudwatch_event.py @@ -111,7 +111,7 @@ def test_present_when_failing_to_describe_rule(global_config, session_instance): Description=global_config.rule_desc, ScheduleExpression=global_config.rule_sched, Targets=[{"Id": "target1", "Arn": "arn::::::*"}], - **global_config.conn_parameters + **global_config.conn_parameters, ) assert result.get("result") is False assert "error on list rules" in result.get("comment", {}) @@ -134,7 +134,7 @@ def test_present_when_failing_to_create_a_new_rule(global_config, session_instan Description=global_config.rule_desc, ScheduleExpression=global_config.rule_sched, Targets=[{"Id": "target1", "Arn": "arn::::::*"}], - **global_config.conn_parameters + **global_config.conn_parameters, ) assert result.get("result") is False assert "put_rule" in result.get("comment", "") @@ -158,7 +158,7 @@ def test_present_when_failing_to_describe_the_new_rule(global_config, session_in Description=global_config.rule_desc, ScheduleExpression=global_config.rule_sched, Targets=[{"Id": "target1", "Arn": "arn::::::*"}], - **global_config.conn_parameters + **global_config.conn_parameters, ) assert result.get("result") is False assert "describe_rule" in result.get("comment", "") @@ -185,7 +185,7 @@ def test_present_when_failing_to_create_a_new_rules_targets( Description=global_config.rule_desc, ScheduleExpression=global_config.rule_sched, Targets=[{"Id": "target1", "Arn": "arn::::::*"}], - **global_config.conn_parameters + **global_config.conn_parameters, ) assert result.get("result") is False assert "put_targets" in result.get("comment", "") @@ -208,7 +208,7 @@ def test_present_when_rule_does_not_exist(global_config, session_instance): Description=global_config.rule_desc, ScheduleExpression=global_config.rule_sched, Targets=[{"Id": "target1", "Arn": "arn::::::*"}], - **global_config.conn_parameters + **global_config.conn_parameters, ) assert result.get("result") is True @@ -231,7 +231,7 @@ def test_present_when_failing_to_update_an_existing_rule( Description=global_config.rule_desc, ScheduleExpression=global_config.rule_sched, Targets=[{"Id": "target1", "Arn": "arn::::::*"}], - **global_config.conn_parameters + **global_config.conn_parameters, ) assert result.get("result") is False assert "describe_rule" in result.get("comment", "") @@ -256,7 +256,7 @@ def test_present_when_failing_to_get_targets(global_config, session_instance): Description=global_config.rule_desc, ScheduleExpression=global_config.rule_sched, Targets=[{"Id": "target1", "Arn": "arn::::::*"}], - **global_config.conn_parameters + **global_config.conn_parameters, ) assert result.get("result") is False assert "list_targets" in result.get("comment", "") @@ -282,7 +282,7 @@ def test_present_when_failing_to_put_targets(global_config, session_instance): Description=global_config.rule_desc, ScheduleExpression=global_config.rule_sched, Targets=[{"Id": "target1", "Arn": "arn::::::*"}], - **global_config.conn_parameters + **global_config.conn_parameters, ) assert result.get("result") is False assert "put_targets" in result.get("comment", "") @@ -306,7 +306,7 @@ def test_present_when_putting_targets(global_config, session_instance): Description=global_config.rule_desc, ScheduleExpression=global_config.rule_sched, Targets=[{"Id": "target1", "Arn": "arn::::::*"}], - **global_config.conn_parameters + **global_config.conn_parameters, ) assert result.get("result") is True @@ -329,7 +329,7 @@ def test_present_when_removing_targets(global_config, session_instance): Description=global_config.rule_desc, ScheduleExpression=global_config.rule_sched, Targets=[{"Id": "target1", "Arn": "arn::::::*"}], - **global_config.conn_parameters + **global_config.conn_parameters, ) assert result.get("result") is True @@ -346,7 +346,7 @@ def test_absent_when_failing_to_describe_rule(global_config, session_instance): result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.absent"]( name="test present", Name=global_config.rule_name, - **global_config.conn_parameters + **global_config.conn_parameters, ) assert result.get("result") is False assert "error on list rules" in result.get("comment", {}) @@ -362,7 +362,7 @@ def test_absent_when_rule_does_not_exist(global_config, session_instance): result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.absent"]( name="test absent", Name=global_config.rule_name, - **global_config.conn_parameters + **global_config.conn_parameters, ) assert result.get("result") is True assert result["changes"] == {} @@ -381,7 +381,7 @@ def test_absent_when_failing_to_list_targets(global_config, session_instance): result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.absent"]( name="test absent", Name=global_config.rule_name, - **global_config.conn_parameters + **global_config.conn_parameters, ) assert result.get("result") is False assert "list_targets" in result.get("comment", "") @@ -403,7 +403,7 @@ def test_absent_when_failing_to_remove_targets_exception( result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.absent"]( name="test absent", Name=global_config.rule_name, - **global_config.conn_parameters + **global_config.conn_parameters, ) assert result.get("result") is False assert "remove_targets" in result.get("comment", "") @@ -423,7 +423,7 @@ def test_absent_when_failing_to_remove_targets_nonexception( result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.absent"]( name="test absent", Name=global_config.rule_name, - **global_config.conn_parameters + **global_config.conn_parameters, ) assert result.get("result") is False @@ -443,7 +443,7 @@ def test_absent_when_failing_to_delete_rule(global_config, session_instance): result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.absent"]( name="test absent", Name=global_config.rule_name, - **global_config.conn_parameters + **global_config.conn_parameters, ) assert result.get("result") is False assert "delete_rule" in result.get("comment", "") @@ -461,6 +461,6 @@ def test_absent(global_config, session_instance): result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.absent"]( name="test absent", Name=global_config.rule_name, - **global_config.conn_parameters + **global_config.conn_parameters, ) assert result.get("result") is True diff --git a/tests/pytests/unit/states/test_boto_iot.py b/tests/pytests/unit/states/test_boto_iot.py index 6da6628b6550..ba5f0e522b26 100644 --- a/tests/pytests/unit/states/test_boto_iot.py +++ b/tests/pytests/unit/states/test_boto_iot.py @@ -153,7 +153,7 @@ def test_present_when_thing_type_does_not_exist(session_instance): thingTypeName=GlobalConfig.thing_type_name, thingTypeDescription=GlobalConfig.thing_type_desc, searchableAttributesList=[GlobalConfig.thing_type_attr_1], - **GlobalConfig.conn_parameters + **GlobalConfig.conn_parameters, ) assert result["result"] assert ( @@ -171,7 +171,7 @@ def test_present_when_thing_type_exists(session_instance): thingTypeName=GlobalConfig.thing_type_name, thingTypeDescription=GlobalConfig.thing_type_desc, searchableAttributesList=[GlobalConfig.thing_type_attr_1], - **GlobalConfig.conn_parameters + **GlobalConfig.conn_parameters, ) assert result["result"] assert result["changes"] == {} @@ -193,7 +193,7 @@ def test_present_with_failure(session_instance): thingTypeName=GlobalConfig.thing_type_name, thingTypeDescription=GlobalConfig.thing_type_desc, searchableAttributesList=[GlobalConfig.thing_type_attr_1], - **GlobalConfig.conn_parameters + **GlobalConfig.conn_parameters, ) assert not result["result"] assert "An error occurred" in result["comment"] diff --git a/tests/pytests/unit/states/test_influxdb_continuous_query.py b/tests/pytests/unit/states/test_influxdb_continuous_query.py index a5a6e7d314c5..ff30a4d1476b 100644 --- a/tests/pytests/unit/states/test_influxdb_continuous_query.py +++ b/tests/pytests/unit/states/test_influxdb_continuous_query.py @@ -43,7 +43,7 @@ def test_when_present_is_called_it_should_pass_client_args_to_create_module( query="fnord", resample_time="whatever", coverage_period="fnord", - **expected_kwargs + **expected_kwargs, ) actual_kwargs = influx_module.create_continuous_query.mock_calls[0].kwargs diff --git a/tests/pytests/unit/states/zabbix/test_host.py b/tests/pytests/unit/states/zabbix/test_host.py index 8e6bad58a926..535064dde17e 100644 --- a/tests/pytests/unit/states/zabbix/test_host.py +++ b/tests/pytests/unit/states/zabbix/test_host.py @@ -1397,7 +1397,7 @@ def test_update_inventory_values_without_clear_existing_data( interfaces, inventory=inventory, inventory_clean=False, - **kwargs + **kwargs, ) host_present_changes = ast.literal_eval( host_present_ret["changes"]["inventory"] @@ -1681,7 +1681,7 @@ def test_clear_inventory_value_sending_an_empty_key( interfaces, inventory=inventory, inventory_clean=False, - **kwargs + **kwargs, ) host_present_changes = ast.literal_eval( host_present_ret["changes"]["inventory"] diff --git a/tests/pytests/unit/test_beacons.py b/tests/pytests/unit/test_beacons.py index 217cd5c6a4da..c9e5a3758d02 100644 --- a/tests/pytests/unit/test_beacons.py +++ b/tests/pytests/unit/test_beacons.py @@ -121,3 +121,169 @@ def test_beacon_module(minion_opts): with patch.object(beacon, "beacons", mocked) as patched: beacon.process(minion_opts["beacons"], minion_opts["grains"]) patched[name].assert_has_calls(calls) + + +def test_close_beacons_calls_close_on_modules(minion_opts): + """ + Test that close_beacons() calls the close function on each beacon + module that provides one, releasing resources like inotify fds. + + See: https://github.com/saltstack/salt/issues/66449 + """ + minion_opts["id"] = "minion" + minion_opts["__role"] = "minion" + minion_opts["beacons"] = { + "inotify": [ + {"files": {"/etc/fstab": {}}}, + ], + } + + beacon = salt.beacons.Beacon(minion_opts, []) + + close_mock = MagicMock() + beacon.beacons["inotify.close"] = close_mock + + beacon.close_beacons() + + close_mock.assert_called_once() + call_args = close_mock.call_args[0][0] + assert isinstance(call_args, list) + assert {"_beacon_name": "inotify"} in call_args + + +def test_close_beacons_with_beacon_module_override(minion_opts): + """ + Test that close_beacons() respects beacon_module and calls close + on the correct underlying module name. + """ + minion_opts["id"] = "minion" + minion_opts["__role"] = "minion" + minion_opts["beacons"] = { + "watch_apache": [ + {"processes": {"apache2": "stopped"}}, + {"beacon_module": "ps"}, + ], + } + + beacon = salt.beacons.Beacon(minion_opts, []) + + close_mock = MagicMock() + beacon.beacons["ps.close"] = close_mock + + beacon.close_beacons() + + close_mock.assert_called_once() + call_args = close_mock.call_args[0][0] + assert {"_beacon_name": "watch_apache"} in call_args + + +def test_close_beacons_skips_modules_without_close(minion_opts): + """ + Test that close_beacons() gracefully skips beacons that don't + have a close function. + """ + minion_opts["id"] = "minion" + minion_opts["__role"] = "minion" + minion_opts["beacons"] = { + "status": [ + {"time": ["all"]}, + ], + } + + beacon = salt.beacons.Beacon(minion_opts, []) + + assert "status.close" not in beacon.beacons + beacon.close_beacons() + + +def test_close_beacons_handles_close_exception(minion_opts): + """ + Test that close_beacons() does not propagate exceptions raised + by a beacon's close function. + """ + minion_opts["id"] = "minion" + minion_opts["__role"] = "minion" + minion_opts["beacons"] = { + "inotify": [ + {"files": {"/etc/fstab": {}}}, + ], + } + + beacon = salt.beacons.Beacon(minion_opts, []) + beacon.beacons["inotify.close"] = MagicMock(side_effect=Exception("close failed")) + + beacon.close_beacons() + + +def test_close_beacons_multiple_beacons(minion_opts): + """ + Test that close_beacons() calls close on all beacons that have + a close function. + """ + minion_opts["id"] = "minion" + minion_opts["__role"] = "minion" + minion_opts["beacons"] = { + "inotify": [ + {"files": {"/etc/fstab": {}}}, + ], + "watch_apache": [ + {"processes": {"apache2": "stopped"}}, + {"beacon_module": "ps"}, + ], + } + + beacon = salt.beacons.Beacon(minion_opts, []) + + inotify_close = MagicMock() + ps_close = MagicMock() + beacon.beacons["inotify.close"] = inotify_close + beacon.beacons["ps.close"] = ps_close + + beacon.close_beacons() + + inotify_close.assert_called_once() + ps_close.assert_called_once() + + +def test_close_beacons_skips_enabled_key(minion_opts): + """ + Test that close_beacons() skips the 'enabled' key in the beacons config. + """ + minion_opts["id"] = "minion" + minion_opts["__role"] = "minion" + minion_opts["beacons"] = { + "enabled": True, + "inotify": [ + {"files": {"/etc/fstab": {}}}, + ], + } + + beacon = salt.beacons.Beacon(minion_opts, []) + close_mock = MagicMock() + beacon.beacons["inotify.close"] = close_mock + + beacon.close_beacons() + + close_mock.assert_called_once() + + +def test_close_beacons_dict_config(minion_opts): + """ + Test that close_beacons() handles dict-style beacon configuration + (backwards-compatible format). + """ + minion_opts["id"] = "minion" + minion_opts["__role"] = "minion" + minion_opts["beacons"] = { + "status": {"time": ["all"]}, + } + + beacon = salt.beacons.Beacon(minion_opts, []) + close_mock = MagicMock() + beacon.beacons["status.close"] = close_mock + + beacon.close_beacons() + + close_mock.assert_called_once() + call_args = close_mock.call_args[0][0] + assert isinstance(call_args, dict) diff --git a/tests/pytests/unit/test_client.py b/tests/pytests/unit/test_client.py index b56c41b9d769..b8923379b200 100644 --- a/tests/pytests/unit/test_client.py +++ b/tests/pytests/unit/test_client.py @@ -6,7 +6,11 @@ import logging import pytest +import tornado.gen +import tornado.ioloop +import salt.client +import salt.config import salt.utils.platform from salt import client from salt.exceptions import ( @@ -290,9 +294,18 @@ def returns_iter(): assert "Skipping non return event: salt/job/0815/return/" in caplog.text +def test_publish_timeout_in_default_master_opts(): + """ + publish_timeout must be present in DEFAULT_MASTER_OPTS with a value of 30 + so that any LocalClient not given explicit opts still gets a sane pub timeout. + """ + assert "publish_timeout" in salt.config.DEFAULT_MASTER_OPTS + assert salt.config.DEFAULT_MASTER_OPTS["publish_timeout"] == 30 + + def test_pub_default_timeout(master_opts): """ - Test that LocalClient.pub uses a default timeout of 15 seconds. + Test that LocalClient.pub uses a default timeout of 30 seconds. """ with client.LocalClient(mopts=master_opts) as local_client: with patch("os.path.exists", return_value=True): @@ -313,11 +326,11 @@ def test_pub_default_timeout(master_opts): # Call pub without specifying timeout result = local_client.pub("*", "test.ping") - # Verify the channel.send was called with timeout=15 + # Verify the channel.send was called with timeout=30 assert mock_channel.send.called call_kwargs = mock_channel.send.call_args # The timeout is passed to channel.send in the first call - assert call_kwargs[1]["timeout"] == 15 + assert call_kwargs[1]["timeout"] == 30 def test_pub_explicit_timeout(master_opts): @@ -351,7 +364,7 @@ def test_pub_explicit_timeout(master_opts): def test_pub_async_default_timeout(master_opts): """ - Test that LocalClient.pub_async uses a default timeout of 15 seconds. + Test that LocalClient.pub_async uses a default timeout of 30 seconds. """ with client.LocalClient(mopts=master_opts) as local_client: with patch("os.path.exists", return_value=True): @@ -385,15 +398,15 @@ def mock_prep_pub(*args, **kwargs): return original_prep_pub(*args, **kwargs) with patch.object(local_client, "_prep_pub", side_effect=mock_prep_pub): - # Call pub_async without specifying timeout - local_client.pub_async("*", "test.ping") + # Call pub_async with explicit timeout=60 + local_client.pub_async("*", "test.ping", timeout=60) - # Verify _prep_pub was called with timeout=15 + # Verify _prep_pub was called with timeout=60 assert len(prep_pub_calls) == 1 # _prep_pub signature: (tgt, fun, arg, tgt_type, ret, jid, timeout, **kwargs) assert ( - prep_pub_calls[0][0][6] == 15 - ) # timeout is the 7th positional arg + prep_pub_calls[0][0][6] == 60 + ) # timeout is the 7th positional arg (index 6) def test_pub_async_explicit_timeout(master_opts): @@ -440,4 +453,138 @@ def mock_prep_pub(*args, **kwargs): # _prep_pub signature: (tgt, fun, arg, tgt_type, ret, jid, timeout, **kwargs) assert ( prep_pub_calls[0][0][6] == 30 - ) # timeout is the 7th positional arg + ) # timeout is the 7th positional arg (index 6) + + +def _make_channel_mock(return_payload): + """ + Build a ReqChannel context-manager mock whose .send() returns return_payload. + """ + mock_channel = MagicMock() + mock_channel.__enter__ = MagicMock(return_value=mock_channel) + mock_channel.__exit__ = MagicMock(return_value=False) + mock_channel.send = MagicMock(return_value=return_payload) + return mock_channel + + +def test_pub_uses_publish_timeout_from_config(master_opts): + """ + pub() must honor a custom publish_timeout set in opts, overriding the 30s default. + """ + master_opts = dict(master_opts, publish_timeout=30) + with client.LocalClient(mopts=master_opts) as local_client: + mock_channel = _make_channel_mock( + {"load": {"jid": "test_jid", "minions": ["m1"]}} + ) + with patch("os.path.exists", return_value=True), patch( + "salt.channel.client.ReqChannel.factory", return_value=mock_channel + ): + local_client.event.connect_pub = MagicMock(return_value=True) + local_client.pub("*", "test.ping") + assert mock_channel.send.call_args[1]["timeout"] == 30 + + +def test_pub_async_uses_publish_timeout_from_config(master_opts): + """ + pub_async() must honor a custom publish_timeout set in opts. + """ + master_opts = dict(master_opts, publish_timeout=30) + with client.LocalClient(mopts=master_opts) as local_client: + captured = {} + + @tornado.gen.coroutine + def mock_send(payload, timeout=None): + captured["timeout"] = timeout + raise tornado.gen.Return({"load": {"jid": "test_jid", "minions": ["m1"]}}) + + mock_channel = MagicMock() + mock_channel.__enter__ = MagicMock(return_value=mock_channel) + mock_channel.__exit__ = MagicMock(return_value=False) + mock_channel.send = mock_send + + with patch("os.path.exists", return_value=True), patch( + "salt.channel.client.AsyncReqChannel.factory", return_value=mock_channel + ): + local_client.event.connect_pub = MagicMock(return_value=True) + io_loop = tornado.ioloop.IOLoop() + io_loop.run_sync(lambda: local_client.pub_async("*", "test.ping")) + + assert captured["timeout"] == 30 + + +# --------------------------------------------------------------------------- +# run_job / run_job_async – timeout propagation to pub / pub_async +# --------------------------------------------------------------------------- + + +def test_run_job_passes_none_to_pub_when_no_timeout(master_opts): + """ + run_job() called without an explicit timeout must pass timeout=None to pub() + so that pub() resolves the value via publish_timeout (30 by default) rather + than the 5-second salt command timeout. + """ + with client.LocalClient(mopts=master_opts) as local_client: + with patch.object( + local_client, + "pub", + return_value={"jid": "1234", "minions": ["m1"]}, + ) as mock_pub: + local_client.run_job("*", "test.ping") + assert mock_pub.call_args[1]["timeout"] is None + + +def test_run_job_passes_explicit_timeout_to_pub(master_opts): + """ + run_job() called with an explicit timeout must forward that value to pub() + unchanged so caller-controlled timeouts are honored (e.g. CLI -t flag). + """ + with client.LocalClient(mopts=master_opts) as local_client: + with patch.object( + local_client, + "pub", + return_value={"jid": "1234", "minions": ["m1"]}, + ) as mock_pub: + local_client.run_job("*", "test.ping", timeout=30) + assert mock_pub.call_args[1]["timeout"] == 30 + + +def test_run_job_async_passes_none_to_pub_async_when_no_timeout(master_opts): + """ + run_job_async() called without an explicit timeout must pass timeout=None + to pub_async() so that pub_async() uses publish_timeout (30 by default). + """ + captured = {} + + @tornado.gen.coroutine + def fake_pub_async(*args, **kwargs): + captured["timeout"] = kwargs.get("timeout") + raise tornado.gen.Return({"jid": "1234", "minions": ["m1"]}) + + with client.LocalClient(mopts=master_opts) as local_client: + with patch.object(local_client, "pub_async", side_effect=fake_pub_async): + io_loop = tornado.ioloop.IOLoop() + io_loop.run_sync(lambda: local_client.run_job_async("*", "test.ping")) + + assert captured["timeout"] is None + + +def test_run_job_async_passes_explicit_timeout_to_pub_async(master_opts): + """ + run_job_async() called with an explicit timeout must forward that value to + pub_async() unchanged. + """ + captured = {} + + @tornado.gen.coroutine + def fake_pub_async(*args, **kwargs): + captured["timeout"] = kwargs.get("timeout") + raise tornado.gen.Return({"jid": "1234", "minions": ["m1"]}) + + with client.LocalClient(mopts=master_opts) as local_client: + with patch.object(local_client, "pub_async", side_effect=fake_pub_async): + io_loop = tornado.ioloop.IOLoop() + io_loop.run_sync( + lambda: local_client.run_job_async("*", "test.ping", timeout=30) + ) + + assert captured["timeout"] == 30 diff --git a/tests/pytests/unit/test_minion.py b/tests/pytests/unit/test_minion.py index 6e6ce6334274..a0c169592ae2 100644 --- a/tests/pytests/unit/test_minion.py +++ b/tests/pytests/unit/test_minion.py @@ -736,6 +736,46 @@ def test_beacons_refresh_preserves_interval_map(minion_opts): minion.destroy() +def test_beacons_refresh_closes_old_beacons(minion_opts): + """ + Tests that 'beacons_refresh' calls close_beacons() on the old Beacon + instance before replacing it, preventing inotify fd leaks. + + See: https://github.com/saltstack/salt/issues/66449 + See: https://github.com/saltstack/salt/issues/58907 + """ + with patch("salt.minion.Minion.ctx", MagicMock(return_value={})), patch( + "salt.utils.process.SignalHandlingProcess.start", + MagicMock(return_value=True), + ), patch( + "salt.utils.process.SignalHandlingProcess.join", + MagicMock(return_value=True), + ): + minion = None + try: + minion = salt.minion.Minion( + minion_opts, + io_loop=tornado.ioloop.IOLoop.current(), + ) + minion.schedule = salt.utils.schedule.Schedule( + minion_opts, {}, returners={} + ) + + minion.module_refresh() + assert hasattr(minion, "beacons") + + old_beacons = minion.beacons + with patch.object(old_beacons, "close_beacons") as close_mock: + minion.beacons_refresh() + close_mock.assert_called_once() + + assert minion.beacons is not old_beacons + + finally: + if minion is not None: + minion.destroy() + + @pytest.mark.slow_test async def test_when_ping_interval_is_set_the_callback_should_be_added_to_periodic_callbacks( minion_opts, diff --git a/tests/pytests/unit/test_pillar.py b/tests/pytests/unit/test_pillar.py index 1b29c26248dd..33feca98640f 100644 --- a/tests/pytests/unit/test_pillar.py +++ b/tests/pytests/unit/test_pillar.py @@ -257,13 +257,15 @@ def _setup_test_include_sls(tempdir): def test_ext_pillar_no_extra_minion_data_val_dict(): opts = { + "file_client": "local", + "pillar": {}, "optimization_order": [0, 1, 2], "renderer": "json", "renderer_blacklist": [], "renderer_whitelist": [], "state_top": "", - "pillar_roots": {"dev": [], "base": []}, - "file_roots": {"dev": [], "base": []}, + "pillar_roots": {}, + "file_roots": {}, "extension_modules": "", "pillarenv_from_saltenv": True, "fileserver_backend": "", @@ -298,13 +300,15 @@ def test_ext_pillar_no_extra_minion_data_val_dict(): def test_ext_pillar_no_extra_minion_data_val_list(): opts = { + "file_client": "local", + "pillar": {}, "optimization_order": [0, 1, 2], "renderer": "json", "renderer_blacklist": [], "renderer_whitelist": [], "state_top": "", - "pillar_roots": {"dev": [], "base": []}, - "file_roots": {"dev": [], "base": []}, + "pillar_roots": {}, + "file_roots": {}, "extension_modules": "", "pillarenv_from_saltenv": True, "fileserver_backend": "", @@ -335,13 +339,15 @@ def test_ext_pillar_no_extra_minion_data_val_list(): def test_ext_pillar_no_extra_minion_data_val_elem(): opts = { + "file_client": "local", + "pillar": {}, "optimization_order": [0, 1, 2], "renderer": "json", "renderer_blacklist": [], "renderer_whitelist": [], "state_top": "", - "pillar_roots": {"dev": [], "base": []}, - "file_roots": {"dev": [], "base": []}, + "pillar_roots": {}, + "file_roots": {}, "extension_modules": "", "pillarenv_from_saltenv": True, "fileserver_backend": "", @@ -376,13 +382,15 @@ def test_ext_pillar_no_extra_minion_data_val_elem(): def test_ext_pillar_with_extra_minion_data_val_dict(): opts = { + "file_client": "local", + "pillar": {}, "optimization_order": [0, 1, 2], "renderer": "json", "renderer_blacklist": [], "renderer_whitelist": [], "state_top": "", - "pillar_roots": {"dev": [], "base": []}, - "file_roots": {"dev": [], "base": []}, + "pillar_roots": {}, + "file_roots": {}, "extension_modules": "", "pillarenv_from_saltenv": True, "fileserver_backend": "", @@ -422,13 +430,15 @@ def test_ext_pillar_with_extra_minion_data_val_dict(): def test_ext_pillar_with_extra_minion_data_val_list(): opts = { + "file_client": "local", + "pillar": {}, "optimization_order": [0, 1, 2], "renderer": "json", "renderer_blacklist": [], "renderer_whitelist": [], "state_top": "", - "pillar_roots": {"dev": [], "base": []}, - "file_roots": {"dev": [], "base": []}, + "pillar_roots": {}, + "file_roots": {}, "extension_modules": "", "pillarenv_from_saltenv": True, "fileserver_backend": "", @@ -463,13 +473,15 @@ def test_ext_pillar_with_extra_minion_data_val_list(): def test_ext_pillar_with_extra_minion_data_val_elem(): opts = { + "file_client": "local", + "pillar": {}, "optimization_order": [0, 1, 2], "renderer": "json", "renderer_blacklist": [], "renderer_whitelist": [], "state_top": "", - "pillar_roots": {"dev": [], "base": []}, - "file_roots": {"dev": [], "base": []}, + "pillar_roots": {}, + "file_roots": {}, "extension_modules": "", "pillarenv_from_saltenv": True, "fileserver_backend": "", @@ -507,15 +519,15 @@ def test_ext_pillar_first(tmp_path): test when using ext_pillar and ext_pillar_first """ opts = { + "file_client": "local", + "pillar": {}, "optimization_order": [0, 1, 2], "renderer": "yaml", "renderer_blacklist": [], "renderer_whitelist": [], "state_top": "", - "pillar_roots": [], - "extension_modules": "", - "saltenv": "base", - "file_roots": [], + "pillar_roots": {}, + "file_roots": {}, "ext_pillar_first": True, "fileserver_backend": "", "cachedir": "", @@ -561,8 +573,8 @@ def test_malformed_pillar_sls(mock_list_states): "renderer_blacklist": [], "renderer_whitelist": [], "state_top": "", - "pillar_roots": [], - "file_roots": [], + "pillar_roots": {}, + "file_roots": {}, "extension_modules": "", "fileserver_backend": "", "cachedir": "", @@ -661,6 +673,8 @@ def test_malformed_pillar_sls(mock_list_states): def test_includes_override_sls(): opts = { + "file_client": "local", + "pillar": {}, "optimization_order": [0, 1, 2], "renderer": "json", "renderer_blacklist": [], @@ -723,15 +737,15 @@ def test_includes_override_sls(): def test_topfile_order(): opts = { + "file_client": "local", + "pillar": {}, "optimization_order": [0, 1, 2], "renderer": "yaml", "renderer_blacklist": [], "renderer_whitelist": [], "state_top": "", - "pillar_roots": [], - "extension_modules": "", - "saltenv": "base", - "file_roots": [], + "pillar_roots": {}, + "file_roots": {}, "fileserver_backend": "roots", "cachedir": "", } @@ -897,6 +911,8 @@ def test_relative_include(tmp_path): file=f, ) opts = { + "file_client": "local", + "pillar": {}, "optimization_order": [0, 1, 2], "renderer": "yaml", "renderer_blacklist": [], @@ -905,7 +921,7 @@ def test_relative_include(tmp_path): "pillar_roots": {"base": [str(tmp_path)]}, "extension_modules": "", "saltenv": "base", - "file_roots": [], + "file_roots": {"base": [str(tmp_path)]}, "file_ignore_regex": None, "file_ignore_glob": None, "fileserver_backend": "roots", @@ -928,6 +944,7 @@ def test_relative_include(tmp_path): # Assert assert compiled_pillar["this"] == "is all good" + assert compiled_pillar["that"] == "is also all good" assert compiled_pillar["simple"] == "simon" assert compiled_pillar["super simple"] == "a caveman" @@ -938,6 +955,8 @@ def test_relative_include(tmp_path): def test_missing_include(tmp_path): opts = { + "file_client": "local", + "pillar": {}, "optimization_order": [0, 1, 2], "renderer": "yaml", "renderer_blacklist": [], @@ -946,7 +965,7 @@ def test_missing_include(tmp_path): "pillar_roots": {"base": [str(tmp_path)]}, "extension_modules": "", "saltenv": "base", - "file_roots": [], + "file_roots": {"base": [str(tmp_path)]}, "file_ignore_regex": None, "file_ignore_glob": None, "fileserver_backend": "roots", @@ -1126,15 +1145,15 @@ def test_pillar_send_extra_minion_data_from_config(tmp_pki, grains): def test_include(tmp_path): opts = { + "file_client": "local", + "pillar": {}, "optimization_order": [0, 1, 2], "renderer": "yaml", "renderer_blacklist": [], "renderer_whitelist": [], "state_top": "", - "pillar_roots": [], - "extension_modules": "", - "saltenv": "base", - "file_roots": [], + "pillar_roots": {}, + "file_roots": {}, "fileserver_backend": "roots", "cachedir": "", } diff --git a/tests/pytests/unit/test_salt_pip_user.py b/tests/pytests/unit/test_salt_pip_user.py new file mode 100644 index 000000000000..ce8d0aedbad3 --- /dev/null +++ b/tests/pytests/unit/test_salt_pip_user.py @@ -0,0 +1,61 @@ +import salt.scripts +from tests.support.mock import MagicMock, patch + + +def test_salt_pip_checks_user(): + # Mock dependencies + mock_minion_config = MagicMock(return_value={"user": "salt"}) + mock_get_user = MagicMock(return_value="root") # Running as root + mock_check_user = MagicMock() + + # Mock onedir path to proceed past initial check + mock_onedir_path = MagicMock() + mock_onedir_path.__truediv__.return_value = "extras" + + with patch( + "salt.scripts._get_onedir_env_path", return_value=mock_onedir_path + ), patch("salt.config.minion_config", mock_minion_config), patch( + "salt.utils.user.get_user", mock_get_user + ), patch( + "salt.utils.verify.check_user", mock_check_user + ), patch( + "subprocess.run" + ) as mock_run, patch( + "sys.exit" + ) as mock_exit: + + # We need to ensure we don't actually exit in a way that breaks test runner, + # but salt_pip calls sys.exit. + # mock_exit will catch it. + + salt.scripts.salt_pip() + + # Verify check_user was called with "salt" + mock_check_user.assert_called_with("salt") + + +def test_salt_pip_no_user_switch_if_same(): + # Mock dependencies + mock_minion_config = MagicMock(return_value={"user": "root"}) + mock_get_user = MagicMock(return_value="root") # Running as root + mock_check_user = MagicMock() + + mock_onedir_path = MagicMock() + mock_onedir_path.__truediv__.return_value = "extras" + + with patch( + "salt.scripts._get_onedir_env_path", return_value=mock_onedir_path + ), patch("salt.config.minion_config", mock_minion_config), patch( + "salt.utils.user.get_user", mock_get_user + ), patch( + "salt.utils.verify.check_user", mock_check_user + ), patch( + "subprocess.run" + ) as mock_run, patch( + "sys.exit" + ): + + salt.scripts.salt_pip() + + # Verify check_user was NOT called + mock_check_user.assert_not_called() diff --git a/tests/pytests/unit/utils/test_http.py b/tests/pytests/unit/utils/test_http.py index 492086051fdd..6e8091c85f43 100644 --- a/tests/pytests/unit/utils/test_http.py +++ b/tests/pytests/unit/utils/test_http.py @@ -170,6 +170,29 @@ def test_query_error_handling(): assert isinstance(ret.get("error", None), str) +def test_query_tornado_httperror_no_response(): + """ + Tests that http.query handles a Tornado HTTPError where exc.response is None. + This happens on connection-level failures such as a connect timeout (HTTP 599) + where no HTTP response is ever received from the server. + """ + import tornado.httpclient + + http_error = tornado.httpclient.HTTPError(599, "Timeout while connecting") + assert http_error.response is None + + mock_client = MagicMock() + mock_client.fetch.side_effect = http_error + + with patch("salt.utils.http.AsyncHTTPClient", return_value=mock_client): + ret = http.query("https://example.com/test", backend="tornado") + + assert isinstance(ret, dict) + assert ret.get("status") == 599 + assert "Timeout while connecting" in ret.get("error", "") + assert "body" not in ret + + def test_parse_cookie_header(): header = "; ".join( [ diff --git a/tests/pytests/unit/utils/test_platform.py b/tests/pytests/unit/utils/test_platform.py index 2d9c74b23987..70dafc24957c 100644 --- a/tests/pytests/unit/utils/test_platform.py +++ b/tests/pytests/unit/utils/test_platform.py @@ -1,3 +1,4 @@ +import multiprocessing import subprocess import salt.utils.platform @@ -45,3 +46,33 @@ def test_linux_distribution(): distro_version, distro_codename, ) + + +def test_spawning_platform_spawn(): + """ + spawning_platform() must return True when the multiprocessing start method + is "spawn" (Windows default, macOS default on Python >= 3.8). + """ + with patch.object(multiprocessing, "get_start_method", return_value="spawn"): + assert salt.utils.platform.spawning_platform() is True + + +def test_spawning_platform_forkserver(): + """ + spawning_platform() must return True when the multiprocessing start method + is "forkserver". Like "spawn", forkserver transfers the Process object to + the child via pickle, so Salt must prepare __getstate__/__setstate__ for it. + This is the Linux default starting with Python 3.14. + """ + with patch.object(multiprocessing, "get_start_method", return_value="forkserver"): + assert salt.utils.platform.spawning_platform() is True + + +def test_spawning_platform_fork(): + """ + spawning_platform() must return False when the multiprocessing start method + is "fork" (Linux default on Python < 3.14). Fork inherits process state + directly, so pickling is not required. + """ + with patch.object(multiprocessing, "get_start_method", return_value="fork"): + assert salt.utils.platform.spawning_platform() is False diff --git a/tests/support/helpers.py b/tests/support/helpers.py index 522f5ccfec72..5f4c3e10c7ab 100644 --- a/tests/support/helpers.py +++ b/tests/support/helpers.py @@ -1748,11 +1748,8 @@ def get_installed_packages(self): return data def _create_virtualenv(self): - pyexec = shutil.which("python") - if not pyexec: - pytest.fail("'python' binary not found for virtualenv") cmd = [ - pyexec, + sys.executable, "-m", "virtualenv", f"--python={self.get_real_python()}", diff --git a/tests/support/pkg.py b/tests/support/pkg.py index 84b6dadbec84..17cf50ae188e 100644 --- a/tests/support/pkg.py +++ b/tests/support/pkg.py @@ -85,6 +85,7 @@ class SaltPkgInstall: file_ext: bool = attr.ib(default=None) relenv: bool = attr.ib(default=True) installer_timeout: int = attr.ib(default=attr.NOTHING) + install_env: dict = attr.ib(factory=dict) @proc.default def _default_proc(self): @@ -225,6 +226,11 @@ def _default_version(self): if not self.upgrade and not self.use_prev_version: version = self.artifact_version else: + if self.prev_version is None: + raise ValueError( + "prev_version must be provided for upgrade tests. " + "Use --prev-version option to specify the previous version." + ) version = self.prev_version parsed = packaging.version.parse(version) version = f"{parsed.major}.{parsed.minor}" @@ -491,6 +497,18 @@ def _install_pkgs(self, upgrade=False, downgrade=False): ) log.info("MSI returncode: %s", ret.returncode) assert ret.returncode in [0, 3010] + + if upgrade: + # MSI major upgrades with mismatched component GUIDs can + # remove files that should be kept. Running a repair + # ensures all files from the new product are on disk. + repair_cmd = f'msiexec.exe /qn /fa "{pkg}" /norestart' + repair_ret = subprocess.run( + repair_cmd, + shell=True, # nosec + check=False, + ) + log.info("MSI repair returncode: %s", repair_ret.returncode) else: log.error("Invalid package: %s", pkg) return False @@ -575,6 +593,12 @@ def _install_pkgs(self, upgrade=False, downgrade=False): env=env, ) else: + # Fresh install path + env = os.environ.copy() + # Add any custom install environment variables + if self.install_env: + env.update(self.install_env) + args = ["install", "-y"] if self.distro_id == "photon": ret = self.proc.run( @@ -588,7 +612,7 @@ def _install_pkgs(self, upgrade=False, downgrade=False): args.append("--nogpgcheck") log.info("Installing packages:\n%s", pprint.pformat(self.pkgs)) args += self.pkgs - ret = self.proc.run(self.pkg_mngr, *args) + ret = self.proc.run(self.pkg_mngr, *args, env=env) if not platform.is_darwin() and not platform.is_windows(): # Make sure we don't have any trailing references to old package file locations @@ -864,9 +888,7 @@ def install_previous(self, downgrade=False): self._check_retcode(ret) pref_file = pathlib.Path("/etc", "apt", "preferences.d", "salt-pin-1001") pref_file.parent.mkdir(exist_ok=True) - pin = f"{self.prev_version.rsplit('.', 1)[0]}.*" - if downgrade: - pin = self.prev_version + pin = self.prev_version with salt.utils.files.fopen(pref_file, "w") as fp: fp.write( f"Package: salt-*\n" f"Pin: version {pin}\n" f"Pin-Priority: 1001" diff --git a/tests/unit/modules/test_boto3_elasticsearch.py b/tests/unit/modules/test_boto3_elasticsearch.py index 4c3156042bfb..72103265bb32 100644 --- a/tests/unit/modules/test_boto3_elasticsearch.py +++ b/tests/unit/modules/test_boto3_elasticsearch.py @@ -686,7 +686,7 @@ def test_describe_elasticsearch_instance_type_limits_positive(self): domain_name="testdomain", instance_type="foo", elasticsearch_version="1.0", - **CONN_PARAMETERS + **CONN_PARAMETERS, ), {"result": True, "response": ret_val["LimitsByRole"]}, ) @@ -707,7 +707,7 @@ def test_describe_elasticsearch_instance_type_limits_error(self): domain_name="testdomain", instance_type="foo", elasticsearch_version="1.0", - **CONN_PARAMETERS + **CONN_PARAMETERS, ) self.assertFalse(result["result"]) self.assertEqual( @@ -1123,7 +1123,7 @@ def test_purchase_reserved_elasticsearch_instance_offering_positive(self): boto3_elasticsearch.purchase_reserved_elasticsearch_instance_offering( reserved_elasticsearch_instance_offering_id="foo", reservation_name="bar", - **CONN_PARAMETERS + **CONN_PARAMETERS, ), {"result": True, "response": ret_val}, ) @@ -1144,7 +1144,7 @@ def test_purchase_reserved_elasticsearch_instance_offering_error(self): boto3_elasticsearch.purchase_reserved_elasticsearch_instance_offering( reserved_elasticsearch_instance_offering_id="foo", reservation_name="bar", - **CONN_PARAMETERS + **CONN_PARAMETERS, ) ) self.assertFalse(result["result"]) diff --git a/tests/unit/modules/test_boto3_route53.py b/tests/unit/modules/test_boto3_route53.py index eb19cd5e6c9a..43fa730f973e 100644 --- a/tests/unit/modules/test_boto3_route53.py +++ b/tests/unit/modules/test_boto3_route53.py @@ -145,7 +145,7 @@ def test_get_resource_records(self): HostedZoneId="Z2P70J7EXAMPLE", StartRecordName="blog.saltstack.furniture.", StartRecordType="A", - **CONN_PARAMETERS + **CONN_PARAMETERS, ), [ { diff --git a/tests/unit/modules/test_boto_cloudtrail.py b/tests/unit/modules/test_boto_cloudtrail.py index 3b6488b31297..59a696a3903f 100644 --- a/tests/unit/modules/test_boto_cloudtrail.py +++ b/tests/unit/modules/test_boto_cloudtrail.py @@ -188,7 +188,7 @@ def test_that_when_creating_a_trail_succeeds_the_create_trail_method_returns_tru result = boto_cloudtrail.create( Name=trail_ret["Name"], S3BucketName=trail_ret["S3BucketName"], - **conn_parameters + **conn_parameters, ) self.assertTrue(result["created"]) @@ -203,7 +203,7 @@ def test_that_when_creating_a_trail_fails_the_create_trail_method_returns_error( result = boto_cloudtrail.create( Name=trail_ret["Name"], S3BucketName=trail_ret["S3BucketName"], - **conn_parameters + **conn_parameters, ) self.assertEqual( result.get("error", {}).get("message"), error_message.format("create_trail") @@ -334,7 +334,7 @@ def test_that_when_updating_a_trail_succeeds_the_update_trail_method_returns_tru result = boto_cloudtrail.update( Name=trail_ret["Name"], S3BucketName=trail_ret["S3BucketName"], - **conn_parameters + **conn_parameters, ) self.assertTrue(result["updated"]) @@ -349,7 +349,7 @@ def test_that_when_updating_a_trail_fails_the_update_trail_method_returns_error( result = boto_cloudtrail.update( Name=trail_ret["Name"], S3BucketName=trail_ret["S3BucketName"], - **conn_parameters + **conn_parameters, ) self.assertEqual( result.get("error", {}).get("message"), error_message.format("update_trail") diff --git a/tests/unit/modules/test_boto_cloudwatch_event.py b/tests/unit/modules/test_boto_cloudwatch_event.py index 4d37747b8f7f..948dc6aafc77 100644 --- a/tests/unit/modules/test_boto_cloudwatch_event.py +++ b/tests/unit/modules/test_boto_cloudwatch_event.py @@ -212,7 +212,7 @@ def test_that_when_creating_a_rule_succeeds_the_create_rule_method_returns_true( Name=rule_name, Description=rule_desc, ScheduleExpression=rule_sched, - **conn_parameters + **conn_parameters, ) self.assertTrue(result["created"]) @@ -225,7 +225,7 @@ def test_that_when_creating_a_rule_fails_the_create_method_returns_error(self): Name=rule_name, Description=rule_desc, ScheduleExpression=rule_sched, - **conn_parameters + **conn_parameters, ) self.assertEqual( result.get("error", {}).get("message"), error_message.format("put_rule") diff --git a/tests/unit/modules/test_boto_cognitoidentity.py b/tests/unit/modules/test_boto_cognitoidentity.py index 51ae9075a0ba..885cb02963a4 100644 --- a/tests/unit/modules/test_boto_cognitoidentity.py +++ b/tests/unit/modules/test_boto_cognitoidentity.py @@ -331,7 +331,7 @@ def test_that_when_delete_identity_pools_and_error_thrown_the_delete_identity_po result = boto_cognitoidentity.delete_identity_pools( IdentityPoolName=first_pool_name, IdentityPoolId="no_such_pool_id", - **conn_parameters + **conn_parameters, ) mock_calls = self.conn.mock_calls self.assertIs(result.get("deleted"), False) @@ -466,7 +466,7 @@ def test_that_when_set_identity_pool_roles_with_only_auth_role_specified_the_set result = boto_cognitoidentity.set_identity_pool_roles( IdentityPoolId="some_id", AuthenticatedRole="my_auth_role", - **conn_parameters + **conn_parameters, ) mock_calls = self.conn.mock_calls self.assertTrue(result.get("set")) @@ -493,7 +493,7 @@ def test_that_when_set_identity_pool_roles_with_only_unauth_role_specified_the_s result = boto_cognitoidentity.set_identity_pool_roles( IdentityPoolId="some_id", UnauthenticatedRole="my_unauth_role", - **conn_parameters + **conn_parameters, ) mock_calls = self.conn.mock_calls self.assertTrue(result.get("set")) @@ -523,7 +523,7 @@ def test_that_when_set_identity_pool_roles_with_both_roles_specified_the_set_ide IdentityPoolId="some_id", AuthenticatedRole="arn:aws:iam:my_auth_role", UnauthenticatedRole="my_unauth_role", - **conn_parameters + **conn_parameters, ) mock_calls = self.conn.mock_calls self.assertTrue(result.get("set")) @@ -543,7 +543,7 @@ def test_that_when_set_identity_pool_roles_given_invalid_auth_role_the_set_ident result = boto_cognitoidentity.set_identity_pool_roles( IdentityPoolId="some_id", AuthenticatedRole="no_such_auth_role", - **conn_parameters + **conn_parameters, ) mock_calls = self.conn.mock_calls self.assertIs(result.get("set"), False) @@ -564,7 +564,7 @@ def test_that_when_set_identity_pool_roles_given_invalid_unauth_role_the_set_ide IdentityPoolId="some_id", AuthenticatedRole="arn:aws:iam:my_auth_role", UnauthenticatedRole="no_such_unauth_role", - **conn_parameters + **conn_parameters, ) mock_calls = self.conn.mock_calls self.assertIs(result.get("set"), False) @@ -621,7 +621,7 @@ def test_that_when_update_identity_pool_given_valid_pool_id_and_pool_name_the_up result = boto_cognitoidentity.update_identity_pool( IdentityPoolId=second_pool_id, IdentityPoolName=second_pool_name_updated, - **conn_parameters + **conn_parameters, ) self.assertTrue(result.get("updated")) self.assertEqual(result.get("identity_pool"), second_pool_updated_ret) @@ -665,7 +665,7 @@ def test_that_when_update_identity_pool_given_empty_list_for_openid_connect_prov result = boto_cognitoidentity.update_identity_pool( IdentityPoolId=first_pool_id, OpenIdConnectProviderARNs=[], - **conn_parameters + **conn_parameters, ) self.assertTrue(result.get("updated")) self.assertEqual(result.get("identity_pool"), first_pool_updated_ret) @@ -687,7 +687,7 @@ def test_that_when_update_identity_pool_given_developer_provider_name_when_devel result = boto_cognitoidentity.update_identity_pool( IdentityPoolId=first_pool_id, DeveloperProviderName="this should not change", - **conn_parameters + **conn_parameters, ) self.assertTrue(result.get("updated")) self.assertEqual(result.get("identity_pool"), first_pool_ret) @@ -708,7 +708,7 @@ def test_that_when_update_identity_pool_given_developer_provider_name_is_include result = boto_cognitoidentity.update_identity_pool( IdentityPoolId=second_pool_id, DeveloperProviderName="added_developer_provider", - **conn_parameters + **conn_parameters, ) self.assertTrue(result.get("updated")) self.assertEqual(result.get("identity_pool"), second_pool_updated_ret) @@ -727,7 +727,7 @@ def test_that_the_update_identity_pool_method_handles_exception_from_boto3(self) result = boto_cognitoidentity.update_identity_pool( IdentityPoolId=second_pool_id, DeveloperProviderName="added_developer_provider", - **conn_parameters + **conn_parameters, ) self.assertIs(result.get("updated"), False) self.assertEqual( diff --git a/tests/unit/modules/test_boto_iot.py b/tests/unit/modules/test_boto_iot.py index 8c61d86dd9b3..3ad3225b6b74 100644 --- a/tests/unit/modules/test_boto_iot.py +++ b/tests/unit/modules/test_boto_iot.py @@ -264,7 +264,7 @@ def test_that_when_creating_a_thing_type_succeeds_the_create_thing_type_method_r thingTypeName=thing_type_name, thingTypeDescription=thing_type_desc, searchableAttributesList=[thing_type_attr_1], - **conn_parameters + **conn_parameters, ) self.assertTrue(result["created"]) self.assertTrue(result["thingTypeArn"], thing_type_arn) @@ -282,7 +282,7 @@ def test_that_when_creating_a_thing_type_fails_the_create_thing_type_method_retu thingTypeName=thing_type_name, thingTypeDescription=thing_type_desc, searchableAttributesList=[thing_type_attr_1], - **conn_parameters + **conn_parameters, ) self.assertEqual( result.get("error", {}).get("message"), @@ -407,7 +407,7 @@ def test_that_when_creating_a_policy_succeeds_the_create_policy_method_returns_t result = boto_iot.create_policy( policyName=policy_ret["policyName"], policyDocument=policy_ret["policyDocument"], - **conn_parameters + **conn_parameters, ) self.assertTrue(result["created"]) @@ -424,7 +424,7 @@ def test_that_when_creating_a_policy_fails_the_create_policy_method_returns_erro result = boto_iot.create_policy( policyName=policy_ret["policyName"], policyDocument=policy_ret["policyDocument"], - **conn_parameters + **conn_parameters, ) self.assertEqual( result.get("error", {}).get("message"), @@ -540,7 +540,7 @@ def test_that_when_creating_a_policy_version_succeeds_the_create_policy_version_ result = boto_iot.create_policy_version( policyName=policy_ret["policyName"], policyDocument=policy_ret["policyDocument"], - **conn_parameters + **conn_parameters, ) self.assertTrue(result["created"]) @@ -557,7 +557,7 @@ def test_that_when_creating_a_policy_version_fails_the_create_policy_version_met result = boto_iot.create_policy_version( policyName=policy_ret["policyName"], policyDocument=policy_ret["policyDocument"], - **conn_parameters + **conn_parameters, ) self.assertEqual( result.get("error", {}).get("message"), @@ -771,7 +771,7 @@ def test_that_when_attach_principal_policy_succeeds_the_attach_principal_policy_ result = boto_iot.attach_principal_policy( policyName="testpolicy", principal="us-east-1:GUID-GUID-GUID", - **conn_parameters + **conn_parameters, ) self.assertTrue(result["attached"]) @@ -788,7 +788,7 @@ def test_that_when_attach_principal_policy_version_fails_the_attach_principal_po result = boto_iot.attach_principal_policy( policyName="testpolicy", principal="us-east-1:GUID-GUID-GUID", - **conn_parameters + **conn_parameters, ) self.assertEqual( result.get("error", {}).get("message"), @@ -804,7 +804,7 @@ def test_that_when_detach_principal_policy_succeeds_the_detach_principal_policy_ result = boto_iot.detach_principal_policy( policyName="testpolicy", principal="us-east-1:GUID-GUID-GUID", - **conn_parameters + **conn_parameters, ) self.assertTrue(result["detached"]) @@ -821,7 +821,7 @@ def test_that_when_detach_principal_policy_version_fails_the_detach_principal_po result = boto_iot.detach_principal_policy( policyName="testpolicy", principal="us-east-1:GUID-GUID-GUID", - **conn_parameters + **conn_parameters, ) self.assertEqual( result.get("error", {}).get("message"), @@ -894,7 +894,7 @@ def test_that_when_creating_a_topic_rule_succeeds_the_create_topic_rule_method_r sql=topic_rule_ret["sql"], description=topic_rule_ret["description"], actions=topic_rule_ret["actions"], - **conn_parameters + **conn_parameters, ) self.assertTrue(result["created"]) @@ -913,7 +913,7 @@ def test_that_when_creating_a_topic_rule_fails_the_create_topic_rule_method_retu sql=topic_rule_ret["sql"], description=topic_rule_ret["description"], actions=topic_rule_ret["actions"], - **conn_parameters + **conn_parameters, ) self.assertEqual( result.get("error", {}).get("message"), @@ -932,7 +932,7 @@ def test_that_when_replacing_a_topic_rule_succeeds_the_replace_topic_rule_method sql=topic_rule_ret["sql"], description=topic_rule_ret["description"], actions=topic_rule_ret["actions"], - **conn_parameters + **conn_parameters, ) self.assertTrue(result["replaced"]) @@ -951,7 +951,7 @@ def test_that_when_replacing_a_topic_rule_fails_the_replace_topic_rule_method_re sql=topic_rule_ret["sql"], description=topic_rule_ret["description"], actions=topic_rule_ret["actions"], - **conn_parameters + **conn_parameters, ) self.assertEqual( result.get("error", {}).get("message"), diff --git a/tests/unit/modules/test_boto_lambda.py b/tests/unit/modules/test_boto_lambda.py index 157e559207d9..49ce3bb270c3 100644 --- a/tests/unit/modules/test_boto_lambda.py +++ b/tests/unit/modules/test_boto_lambda.py @@ -228,7 +228,7 @@ def test_that_when_creating_a_function_from_zipfile_succeeds_the_create_function Role="myrole", Handler="file.method", ZipFile=zipfile, - **conn_parameters + **conn_parameters, ) self.assertTrue(lambda_creation_result["created"]) @@ -251,7 +251,7 @@ def test_that_when_creating_a_function_from_s3_succeeds_the_create_function_meth Handler="file.method", S3Bucket="bucket", S3Key="key", - **conn_parameters + **conn_parameters, ) self.assertTrue(lambda_creation_result["created"]) @@ -276,7 +276,7 @@ def test_that_when_creating_a_function_without_code_raises_a_salt_invocation_err Runtime="python2.7", Role="myrole", Handler="file.method", - **conn_parameters + **conn_parameters, ) def test_that_when_creating_a_function_with_zipfile_and_s3_raises_a_salt_invocation_error( @@ -303,7 +303,7 @@ def test_that_when_creating_a_function_with_zipfile_and_s3_raises_a_salt_invocat ZipFile=zipfile, S3Bucket="bucket", S3Key="key", - **conn_parameters + **conn_parameters, ) def test_that_when_creating_a_function_fails_the_create_function_method_returns_error( @@ -326,7 +326,7 @@ def test_that_when_creating_a_function_fails_the_create_function_method_returns_ Role="myrole", Handler="file.method", ZipFile=zipfile, - **conn_parameters + **conn_parameters, ) self.assertEqual( lambda_creation_result.get("error", {}).get("message"), @@ -428,7 +428,7 @@ def test_that_when_updating_a_function_succeeds_the_update_function_method_retur result = boto_lambda.update_function_config( FunctionName=function_ret["FunctionName"], Role="myrole", - **conn_parameters + **conn_parameters, ) self.assertTrue(result["updated"]) @@ -469,7 +469,7 @@ def test_that_when_updating_function_code_from_zipfile_succeeds_the_update_funct result = boto_lambda.update_function_code( FunctionName=function_ret["FunctionName"], ZipFile=zipfile, - **conn_parameters + **conn_parameters, ) self.assertTrue(result["updated"]) @@ -489,7 +489,7 @@ def test_that_when_updating_function_code_from_s3_succeeds_the_update_function_m FunctionName="testfunction", S3Bucket="bucket", S3Key="key", - **conn_parameters + **conn_parameters, ) self.assertTrue(result["updated"]) @@ -530,7 +530,7 @@ def test_that_when_updating_function_code_fails_the_update_function_method_retur FunctionName="testfunction", S3Bucket="bucket", S3Key="key", - **conn_parameters + **conn_parameters, ) self.assertEqual( result.get("error", {}).get("message"), @@ -617,7 +617,7 @@ def test_that_when_creating_an_alias_succeeds_the_create_alias_method_returns_tr FunctionName="testfunction", Name=alias_ret["Name"], FunctionVersion=alias_ret["FunctionVersion"], - **conn_parameters + **conn_parameters, ) self.assertTrue(result["created"]) @@ -633,7 +633,7 @@ def test_that_when_creating_an_alias_fails_the_create_alias_method_returns_error FunctionName="testfunction", Name=alias_ret["Name"], FunctionVersion=alias_ret["FunctionVersion"], - **conn_parameters + **conn_parameters, ) self.assertEqual( result.get("error", {}).get("message"), error_message.format("create_alias") @@ -751,7 +751,7 @@ def test_that_when_updating_an_alias_succeeds_the_update_alias_method_returns_tr FunctionName="testfunctoin", Name=alias_ret["Name"], Description=alias_ret["Description"], - **conn_parameters + **conn_parameters, ) self.assertTrue(result["updated"]) @@ -796,7 +796,7 @@ def test_that_when_creating_a_mapping_succeeds_the_create_event_source_mapping_m EventSourceArn=event_source_mapping_ret["EventSourceArn"], FunctionName=event_source_mapping_ret["FunctionArn"], StartingPosition="LATEST", - **conn_parameters + **conn_parameters, ) self.assertTrue(result["created"]) @@ -814,7 +814,7 @@ def test_that_when_creating_an_event_source_mapping_fails_the_create_event_sourc EventSourceArn=event_source_mapping_ret["EventSourceArn"], FunctionName=event_source_mapping_ret["FunctionArn"], StartingPosition="LATEST", - **conn_parameters + **conn_parameters, ) self.assertEqual( result.get("error", {}).get("message"), @@ -833,7 +833,7 @@ def test_that_when_listing_mapping_ids_succeeds_the_get_event_source_mapping_ids result = boto_lambda.get_event_source_mapping_ids( EventSourceArn=event_source_mapping_ret["EventSourceArn"], FunctionName=event_source_mapping_ret["FunctionArn"], - **conn_parameters + **conn_parameters, ) self.assertTrue(result) @@ -848,7 +848,7 @@ def test_that_when_listing_event_source_mapping_ids_fails_the_get_event_source_m result = boto_lambda.get_event_source_mapping_ids( EventSourceArn=event_source_mapping_ret["EventSourceArn"], FunctionName=event_source_mapping_ret["FunctionArn"], - **conn_parameters + **conn_parameters, ) self.assertFalse(result) @@ -864,7 +864,7 @@ def test_that_when_listing_event_source_mapping_ids_fails_the_get_event_source_m result = boto_lambda.get_event_source_mapping_ids( EventSourceArn=event_source_mapping_ret["EventSourceArn"], FunctionName=event_source_mapping_ret["FunctionArn"], - **conn_parameters + **conn_parameters, ) self.assertEqual( result.get("error", {}).get("message"), @@ -897,7 +897,7 @@ def test_that_when_deleting_an_event_source_mapping_by_name_succeeds_the_delete_ result = boto_lambda.delete_event_source_mapping( EventSourceArn=event_source_mapping_ret["EventSourceArn"], FunctionName=event_source_mapping_ret["FunctionArn"], - **conn_parameters + **conn_parameters, ) self.assertTrue(result["deleted"]) @@ -1019,7 +1019,7 @@ def test_that_when_updating_an_event_source_mapping_succeeds_the_update_event_so result = boto_lambda.update_event_source_mapping( UUID=event_source_mapping_ret["UUID"], FunctionName=event_source_mapping_ret["FunctionArn"], - **conn_parameters + **conn_parameters, ) self.assertTrue(result["updated"]) @@ -1036,7 +1036,7 @@ def test_that_when_updating_an_event_source_mapping_fails_the_update_event_sourc result = boto_lambda.update_event_source_mapping( UUID=event_source_mapping_ret["UUID"], FunctionName=event_source_mapping_ret["FunctionArn"], - **conn_parameters + **conn_parameters, ) self.assertEqual( result.get("error", {}).get("message"), diff --git a/tests/unit/modules/test_boto_s3_bucket.py b/tests/unit/modules/test_boto_s3_bucket.py index 90d868d11416..4da50f1023d3 100644 --- a/tests/unit/modules/test_boto_s3_bucket.py +++ b/tests/unit/modules/test_boto_s3_bucket.py @@ -443,7 +443,7 @@ def test_that_when_putting_logging_succeeds_the_put_logging_method_returns_true( TargetBucket="arn:::::", TargetPrefix="asdf", TargetGrants="[]", - **conn_parameters + **conn_parameters, ) self.assertTrue(result["updated"]) @@ -460,7 +460,7 @@ def test_that_when_putting_logging_fails_the_put_logging_method_returns_error(se TargetBucket="arn:::::", TargetPrefix="asdf", TargetGrants="[]", - **conn_parameters + **conn_parameters, ) self.assertEqual( result.get("error", {}).get("message"), diff --git a/tests/unit/modules/test_neutron.py b/tests/unit/modules/test_neutron.py index c6fb5b38445b..f6cf46565d47 100644 --- a/tests/unit/modules/test_neutron.py +++ b/tests/unit/modules/test_neutron.py @@ -431,7 +431,7 @@ def create_ipsec_site_connection( peer_id, psk, admin_state_up, - **kwargs + **kwargs, ): """ Mock of create_ipsec_site_connection method diff --git a/tests/unit/states/test_boto_apigateway.py b/tests/unit/states/test_boto_apigateway.py index 7cf95a43442b..a00514aeff04 100644 --- a/tests/unit/states/test_boto_apigateway.py +++ b/tests/unit/states/test_boto_apigateway.py @@ -571,7 +571,7 @@ def test_present_when_swagger_file_is_invalid(self): "test", False, "arn:aws:iam::1234:role/apigatewayrole", - **conn_parameters + **conn_parameters, ) self.assertFalse(result.get("result", True)) @@ -596,7 +596,7 @@ def test_present_when_stage_is_already_at_desired_deployment(self): "test", False, "arn:aws:iam::1234:role/apigatewayrole", - **conn_parameters + **conn_parameters, ) self.assertFalse(result.get("abort")) self.assertTrue(result.get("current")) @@ -624,7 +624,7 @@ def test_present_when_stage_is_already_at_desired_deployment_and_needs_stage_var False, "arn:aws:iam::1234:role/apigatewayrole", stage_variables={"var1": "val1"}, - **conn_parameters + **conn_parameters, ) self.assertFalse(result.get("abort")) @@ -657,7 +657,7 @@ def test_present_when_stage_exists_and_is_to_associate_to_existing_deployment(se "test", False, "arn:aws:iam::1234:role/apigatewayrole", - **conn_parameters + **conn_parameters, ) self.assertTrue(result.get("publish")) @@ -713,7 +713,7 @@ def test_present_when_stage_is_to_associate_to_new_deployment(self): "test", False, "arn:aws:iam::1234:role/apigatewayrole", - **conn_parameters + **conn_parameters, ) self.assertIs(result.get("result"), True) @@ -742,7 +742,7 @@ def test_present_when_stage_associating_to_new_deployment_errored_on_api_creatio "test", False, "arn:aws:iam::1234:role/apigatewayrole", - **conn_parameters + **conn_parameters, ) self.assertIs(result.get("abort"), True) @@ -774,7 +774,7 @@ def test_present_when_stage_associating_to_new_deployment_errored_on_model_creat "test", False, "arn:aws:iam::1234:role/apigatewayrole", - **conn_parameters + **conn_parameters, ) self.assertIs(result.get("abort"), True) @@ -812,7 +812,7 @@ def test_present_when_stage_associating_to_new_deployment_errored_on_resource_cr "test", False, "arn:aws:iam::1234:role/apigatewayrole", - **conn_parameters + **conn_parameters, ) self.assertIs(result.get("abort"), True) self.assertIs(result.get("result"), False) @@ -862,7 +862,7 @@ def test_present_when_stage_associating_to_new_deployment_errored_on_put_method( "test", False, "arn:aws:iam::1234:role/apigatewayrole", - **conn_parameters + **conn_parameters, ) self.assertIs(result.get("abort"), True) @@ -916,7 +916,7 @@ def test_present_when_stage_associating_to_new_deployment_errored_on_lambda_func "test", False, "arn:aws:iam::1234:role/apigatewayrole", - **conn_parameters + **conn_parameters, ) self.assertIs(result.get("result"), False) @@ -973,7 +973,7 @@ def test_present_when_stage_associating_to_new_deployment_errored_on_put_integra "test", False, "arn:aws:iam::1234:role/apigatewayrole", - **conn_parameters + **conn_parameters, ) self.assertIs(result.get("abort"), True) @@ -1030,7 +1030,7 @@ def test_present_when_stage_associating_to_new_deployment_errored_on_put_method_ "test", False, "arn:aws:iam::1234:role/apigatewayrole", - **conn_parameters + **conn_parameters, ) self.assertIs(result.get("abort"), True) @@ -1089,7 +1089,7 @@ def test_present_when_stage_associating_to_new_deployment_errored_on_put_integra "test", False, "arn:aws:iam::1234:role/apigatewayrole", - **conn_parameters + **conn_parameters, ) self.assertIs(result.get("abort"), True) @@ -1111,7 +1111,7 @@ def test_absent_when_rest_api_does_not_exist(self): "no_such_rest_api", "no_such_stage", nuke_api=False, - **conn_parameters + **conn_parameters, ) self.assertIs(result.get("result"), True) @@ -1131,7 +1131,7 @@ def test_absent_when_stage_is_invalid(self): "unit test api", "no_such_stage", nuke_api=False, - **conn_parameters + **conn_parameters, ) self.assertTrue(result.get("abort", False)) @@ -1555,7 +1555,7 @@ def test_usage_plan_present_if_plan_has_been_updated(self, *args): "name", "plan_name", throttle={"rateLimit": throttle_rateLimit}, - **conn_parameters + **conn_parameters, ) self.assertIn("result", result) @@ -1592,7 +1592,7 @@ def test_usage_plan_present_if_ValueError_is_raised(self, *args): "name", "plan_name", throttle={"rateLimit": throttle_rateLimit}, - **conn_parameters + **conn_parameters, ) self.assertIn("result", result) @@ -1617,7 +1617,7 @@ def test_usage_plan_present_if_IOError_is_raised(self, *args): "name", "plan_name", throttle={"rateLimit": throttle_rateLimit}, - **conn_parameters + **conn_parameters, ) self.assertIn("result", result) diff --git a/tests/unit/states/test_esxi.py b/tests/unit/states/test_esxi.py index dede0feb7cf8..2a5d4e7167e0 100644 --- a/tests/unit/states/test_esxi.py +++ b/tests/unit/states/test_esxi.py @@ -58,7 +58,7 @@ def esxi_cmd_wrapper(target, *args, **kwargs): service_running=True, service_restart=False, certificate_verify=certificate_verify_value, - **kwargs + **kwargs, ) http_query_mock.assert_called_once_with( "https://1.2.3.4:443/host/ssh_root_authorized_keys", @@ -68,5 +68,5 @@ def esxi_cmd_wrapper(target, *args, **kwargs): text=True, username="root", verify_ssl=certificate_verify_value, - **expected_kwargs + **expected_kwargs, ) diff --git a/tests/unit/utils/test_boto3mod.py b/tests/unit/utils/test_boto3mod.py index 0a9509ab5987..091e64b4093f 100644 --- a/tests/unit/utils/test_boto3mod.py +++ b/tests/unit/utils/test_boto3mod.py @@ -113,7 +113,7 @@ def test_set_and_get_with_explicit_auth_params(self): self.service, self.resource_name, resource_id=self.resource_id, - **self.conn_parameters + **self.conn_parameters, ) self.assertEqual( boto3mod.cache_id(self.service, self.resource_name, **self.conn_parameters), diff --git a/tests/unit/utils/test_msgpack.py b/tests/unit/utils/test_msgpack.py index ecb85af5e767..f698580120c7 100644 --- a/tests/unit/utils/test_msgpack.py +++ b/tests/unit/utils/test_msgpack.py @@ -452,7 +452,7 @@ def test_binary_function_compatibility(self): # Run the test without the salt.utils.msgpack module for comparison vanilla_run = self.no_fail_run( test_func, - **{"pack_func": msgpack.packb, "unpack_func": msgpack.unpackb} + **{"pack_func": msgpack.packb, "unpack_func": msgpack.unpackb}, ) for func_args in functions: diff --git a/tests/unit/utils/test_pyobjects.py b/tests/unit/utils/test_pyobjects.py index d5dcf3553ce8..bc33af0c78a2 100644 --- a/tests/unit/utils/test_pyobjects.py +++ b/tests/unit/utils/test_pyobjects.py @@ -132,7 +132,7 @@ def test_serialization(self): "file", "managed", require=self.File("/usr/local/bin"), - **self.pydmesg_kwargs + **self.pydmesg_kwargs, ) self.assertEqual(f(), self.pydmesg_expected) @@ -141,7 +141,7 @@ def test_factory_serialization(self): self.File.managed( "/usr/local/bin/pydmesg", require=self.File("/usr/local/bin"), - **self.pydmesg_kwargs + **self.pydmesg_kwargs, ) self.assertEqual( @@ -178,7 +178,7 @@ def test_salt_data(self): self.File.managed( "/usr/local/bin/pydmesg", require=self.File("/usr/local/bin"), - **self.pydmesg_kwargs + **self.pydmesg_kwargs, ) self.assertEqual( diff --git a/tools/pkg/build.py b/tools/pkg/build.py index c154314b144d..43beff5c498d 100644 --- a/tools/pkg/build.py +++ b/tools/pkg/build.py @@ -5,6 +5,10 @@ # pylint: disable=resource-leakage,broad-except from __future__ import annotations +import base64 +import csv +import hashlib +import io import json import logging import os @@ -12,7 +16,9 @@ import pathlib import re import shutil +import sys import tarfile +import tempfile import zipfile from typing import TYPE_CHECKING @@ -22,6 +28,172 @@ log = logging.getLogger(__name__) +# Cached path to the patched pip wheel built by _build_patched_pip_wheel. +# None until first call; reused across all build steps in the same process. +_PATCHED_PIP_WHEEL: pathlib.Path | None = None + + +def _apply_unified_diff(original_text: str, patch_text: str) -> str: + """ + Apply a unified diff patch to *original_text* and return the result. + + This is a minimal pure-Python applier sufficient for the well-formed, + non-fuzzy patches stored in pkg/patches/pip-urllib3/. It handles the + standard unified diff hunk format produced by difflib.unified_diff and + GNU diff, including the '\\' (no newline at end of file) marker. + """ + orig_lines = original_text.splitlines(True) + result: list[str] = [] + orig_idx = 0 + + patch_lines = patch_text.splitlines(True) + i = 0 + + # Skip the file-header lines (--- / +++) before the first hunk. + while i < len(patch_lines) and not patch_lines[i].startswith("@@"): + i += 1 + + while i < len(patch_lines): + line = patch_lines[i] + if line.startswith("@@"): + m = re.match(r"^@@ -(\d+)(?:,\d+)? \+\d+(?:,\d+)? @@", line) + if not m: + i += 1 + continue + orig_start = int(m.group(1)) - 1 # convert 1-based → 0-based + + # Copy unchanged original lines that precede this hunk. + result.extend(orig_lines[orig_idx:orig_start]) + orig_idx = orig_start + i += 1 + + # Process hunk body lines. + while i < len(patch_lines): + hunk_line = patch_lines[i] + if hunk_line.startswith("@@"): + break # next hunk starts + if hunk_line.startswith("+"): + result.append(hunk_line[1:]) + elif hunk_line.startswith("-"): + orig_idx += 1 + elif hunk_line.startswith(" "): + result.append(orig_lines[orig_idx]) + orig_idx += 1 + # "\\" → "No newline at end of file" marker; skip. + i += 1 + else: + i += 1 + + # Copy any original lines that follow the last hunk. + result.extend(orig_lines[orig_idx:]) + return "".join(result) + + +def _patch_pip_wheel_urllib3(wheel_path: pathlib.Path) -> None: + """ + Rewrite *wheel_path* in-place so that the urllib3 vendored inside pip + contains the Salt security backports defined in pkg/patches/pip-urllib3/. + + Patches applied (unified diff format): + response.py.patch — CVE-2025-66418, CVE-2026-21441 + _version.py.patch — version bumped to "2.6.3" + + Each patch is applied to the file as extracted from the wheel, so the + original sources do not need to be stored in the repository. The wheel's + RECORD file is updated with correct sha256 hashes and sizes for the two + patched files so that the installed dist-info stays valid. + """ + patches_dir = tools.utils.REPO_ROOT / "pkg" / "patches" / "pip-urllib3" + patch_map = { + "pip/_vendor/urllib3/response.py": ( + patches_dir / "response.py.patch" + ).read_text(encoding="utf-8"), + "pip/_vendor/urllib3/_version.py": ( + patches_dir / "_version.py.patch" + ).read_text(encoding="utf-8"), + } + + def _record_hash(content: bytes) -> str: + digest = hashlib.sha256(content).digest() + return "sha256=" + base64.urlsafe_b64encode(digest).decode().rstrip("=") + + tmp_path = wheel_path.with_suffix(".tmp.whl") + try: + with zipfile.ZipFile(wheel_path, "r") as zin: + with zipfile.ZipFile( + tmp_path, "w", compression=zipfile.ZIP_DEFLATED + ) as zout: + record_name: str | None = None + record_rows: list[list[str]] = [] + patched: dict[str, bytes] = {} + + for item in zin.infolist(): + if item.filename.endswith(".dist-info/RECORD"): + record_name = item.filename + raw = zin.read(item.filename).decode("utf-8") + record_rows = list(csv.reader(raw.splitlines())) + continue # written last after we know the new hashes + if item.filename in patch_map: + original = zin.read(item.filename).decode("utf-8") + patched_text = _apply_unified_diff( + original, patch_map[item.filename] + ) + patched_bytes = patched_text.encode("utf-8") + patched[item.filename] = patched_bytes + zout.writestr(item, patched_bytes) + else: + zout.writestr(item, zin.read(item.filename)) + + # Update RECORD rows for patched files and write it back. + if record_name: + new_rows = [] + for row in record_rows: + if len(row) >= 1 and row[0] in patched: + content = patched[row[0]] + new_rows.append( + [row[0], _record_hash(content), str(len(content))] + ) + else: + new_rows.append(row) + buf = io.StringIO() + csv.writer(buf).writerows(new_rows) + zout.writestr(record_name, buf.getvalue()) + + tmp_path.replace(wheel_path) + except Exception: + tmp_path.unlink(missing_ok=True) + raise + + +def _build_patched_pip_wheel(ctx: Context) -> pathlib.Path: + """ + Download pip==25.2 into a temporary directory, patch its vendored urllib3, + and return the path to the patched wheel. The result is cached for the + lifetime of the current process so subsequent calls are free. + """ + global _PATCHED_PIP_WHEEL + if _PATCHED_PIP_WHEEL is not None: + return _PATCHED_PIP_WHEEL + + tmpdir = pathlib.Path(tempfile.mkdtemp(prefix="salt-pip-patch-")) + ctx.info("Downloading pip==25.2 for urllib3 security patching ...") + ctx.run( + sys.executable, + "-m", + "pip", + "download", + "pip==25.2", + "--no-deps", + "--dest", + str(tmpdir), + ) + wheel = next(tmpdir.glob("pip-*.whl")) + ctx.info(f"Patching urllib3 CVEs inside {wheel.name} ...") + _patch_pip_wheel_urllib3(wheel) + _PATCHED_PIP_WHEEL = wheel + return wheel + + # Define the command group build = command_group( name="build", @@ -291,6 +463,20 @@ def macos( ctx.info("Installing salt into the relenv python") ctx.run("./install_salt.sh") + # Patch pip's vendored urllib3 in the standalone macOS build. + # install_salt.sh uses the relenv pip but does not upgrade it, so we + # install the security-patched pip wheel and replace the copy that + # virtualenv embeds so that new environments also get the fixed pip. + build_env = checkout / "pkg" / "macos" / "build" / "opt" / "salt" + python_bin = build_env / "bin" / "python3" + patched_pip = _build_patched_pip_wheel(ctx) + ctx.run(str(python_bin), "-m", "pip", "install", str(patched_pip)) + for old_pip in (build_env / "lib").glob( + "python*/site-packages/virtualenv/seed/wheels/embed/pip-*.whl" + ): + old_pip.unlink() + shutil.copy(str(patched_pip), str(old_pip.parent / patched_pip.name)) + if sign: ctx.info("Signing binaries") with ctx.chdir(checkout / "pkg" / "macos"): @@ -618,7 +804,7 @@ def onedir_dependencies( "-v", "--use-pep517", "--no-cache-dir", - "--only-binary=maturin,apache-libcloud,pymssql", + "--only-binary=maturin,apache-libcloud,pymssql,hatchling", ] if platform == "windows": python_bin = env_scripts_dir / "python" @@ -627,7 +813,7 @@ def onedir_dependencies( python_bin = env_scripts_dir / "python3" install_args.append("--no-binary=:all:") install_args.append( - "--only-binary=maturin,apache-libcloud,pymssql,cassandra-driver" + "--only-binary=maturin,apache-libcloud,pymssql,cassandra-driver,hatchling" ) # Cryptography needs openssl dir set to link to the proper openssl libs. @@ -672,10 +858,25 @@ def onedir_dependencies( "install", "-U", "setuptools", - "pip", "wheel", env=env, ) + # Install pip from the security-patched wheel instead of pulling from PyPI, + # so that pip's vendored urllib3 never contains the vulnerable version. + # --force-reinstall is required because relenv ships with pip pre-installed + # at the same version (25.2), so without it pip would skip the install as + # "already satisfied" and leave the unpatched copy in site-packages. + patched_pip = _build_patched_pip_wheel(ctx) + ctx.run( + str(python_bin), + "-m", + "pip", + "install", + "--force-reinstall", + "--no-deps", + str(patched_pip), + env=env, + ) ctx.run( str(python_bin), "-m", @@ -898,17 +1099,22 @@ def errfn(fn, path, err): env["PIP_CONSTRAINT"] = str( tools.utils.REPO_ROOT / "requirements" / "constraints.txt" ) + # Download setuptools and wheel normally; pip is handled separately below + # so that the security-patched wheel is used instead of the PyPI version. ctx.run( str(python_executable), "-m", "pip", "download", "setuptools", - "pip", "wheel", "--dest", str(embed_dir), ) + # Copy the security-patched pip wheel into the embed directory so that + # virtualenv seeds new environments with pip that has the urllib3 fixes. + patched_pip = _build_patched_pip_wheel(ctx) + shutil.copy(str(patched_pip), str(embed_dir / patched_pip.name)) # Update __init__.py with the new versions diff --git a/tools/pkg/salt_build_backend.py b/tools/pkg/salt_build_backend.py new file mode 100644 index 000000000000..1901df771a43 --- /dev/null +++ b/tools/pkg/salt_build_backend.py @@ -0,0 +1,217 @@ +import os +import sys + +# Add project root to sys.path +PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) +if PROJECT_ROOT not in sys.path: + sys.path.insert(0, PROJECT_ROOT) + +from setuptools import build_meta as _orig + +# PEP 517 hooks +prepare_metadata_for_build_wheel = _orig.prepare_metadata_for_build_wheel +build_wheel = _orig.build_wheel +build_sdist = _orig.build_sdist +get_requires_for_build_wheel = _orig.get_requires_for_build_wheel +get_requires_for_build_sdist = _orig.get_requires_for_build_sdist + + +def _parse_requirements_file(requirements_file): + parsed_requirements = [] + if not os.path.exists(requirements_file): + return parsed_requirements + # pylint: disable=resource-leakage + with open(requirements_file, encoding="utf-8") as rfh: + # pylint: enable=resource-leakage + for line in rfh.readlines(): + line = line.strip() + if not line or line.startswith(("#", "-r", "--")): + continue + # Logic from setup.py for windows libcloud skip + if sys.platform.startswith("win"): + if "libcloud" in line: + continue + parsed_requirements.append(line) + return parsed_requirements + + +def get_salt_version(dist=None): + salt_version_module = os.path.join(PROJECT_ROOT, "salt", "version.py") + # We can't import salt.version directly because dependencies might not be there + # But we can exec it in a controlled environment + g = {"__opts__": {}, "__file__": salt_version_module} + # pylint: disable=resource-leakage + with open(salt_version_module, encoding="utf-8") as f: + # pylint: enable=resource-leakage + exec(f.read(), g) + return str(g["__saltstack_version__"]) + + +def get_install_requires(dist=None): + use_static = os.environ.get("USE_STATIC_REQUIREMENTS") == "1" + + is_osx = sys.platform.startswith("darwin") + is_windows = sys.platform.startswith("win") + + reqs = [] + if use_static: + if is_osx: + req_files = [ + os.path.join( + PROJECT_ROOT, + "requirements", + "static", + "pkg", + f"py{sys.version_info[0]}.{sys.version_info[1]}", + "darwin.txt", + ) + ] + elif is_windows: + req_files = [ + os.path.join( + PROJECT_ROOT, + "requirements", + "static", + "pkg", + f"py{sys.version_info[0]}.{sys.version_info[1]}", + "windows.txt", + ) + ] + else: + req_files = [ + os.path.join( + PROJECT_ROOT, + "requirements", + "static", + "pkg", + f"py{sys.version_info[0]}.{sys.version_info[1]}", + "linux.txt", + ) + ] + else: + # Base requirements + req_files = [ + os.path.join(PROJECT_ROOT, "requirements", "base.txt"), + os.path.join(PROJECT_ROOT, "requirements", "zeromq.txt"), + os.path.join(PROJECT_ROOT, "requirements", "crypto.txt"), + ] + if is_osx: + req_files.append(os.path.join(PROJECT_ROOT, "requirements", "darwin.txt")) + elif is_windows: + req_files.append(os.path.join(PROJECT_ROOT, "requirements", "windows.txt")) + + for req_file in req_files: + reqs.extend(_parse_requirements_file(req_file)) + return reqs + + +def get_extras_require(dist=None): + crypto_req = os.path.join(PROJECT_ROOT, "requirements", "crypto.txt") + extras = {} + if os.path.exists(crypto_req): + extras["crypto"] = _parse_requirements_file(crypto_req) + return extras + + +def get_entry_points(dist=None): + is_windows = sys.platform.startswith("win") + entrypoints = { + "pyinstaller40": [ + "hook-dirs = salt.utils.pyinstaller:get_hook_dirs", + ], + } + # console scripts common to all scenarios + scripts = [ + "salt-call = salt.scripts:salt_call", + ] + + ssh_packaging = False + if dist: + ssh_packaging = getattr(dist, "ssh_packaging", False) + if not ssh_packaging: + ssh_packaging = os.path.exists( + os.path.join(PROJECT_ROOT, "salt", "_ssh_packaging") + ) + + if ssh_packaging: + scripts.append("salt-ssh = salt.scripts:salt_ssh") + if is_windows and not os.environ.get("SALT_BUILD_ALL_BINS"): + return {"console_scripts": scripts} + scripts.append("salt-cloud = salt.scripts:salt_cloud") + entrypoints["console_scripts"] = scripts + return entrypoints + + if is_windows and not os.environ.get("SALT_BUILD_ALL_BINS"): + scripts.extend( + [ + "salt-cp = salt.scripts:salt_cp", + "salt-minion = salt.scripts:salt_minion", + "salt-pip = salt.scripts:salt_pip", + ] + ) + entrypoints["console_scripts"] = scripts + return entrypoints + + # *nix, so, we need all scripts + scripts.extend( + [ + "salt = salt.scripts:salt_main", + "salt-api = salt.scripts:salt_api", + "salt-cloud = salt.scripts:salt_cloud", + "salt-cp = salt.scripts:salt_cp", + "salt-key = salt.scripts:salt_key", + "salt-master = salt.scripts:salt_master", + "salt-minion = salt.scripts:salt_minion", + "salt-run = salt.scripts:salt_run", + "salt-ssh = salt.scripts:salt_ssh", + "salt-syndic = salt.scripts:salt_syndic", + "spm = salt.scripts:salt_spm", + "salt-proxy = salt.scripts:salt_proxy", + "salt-pip = salt.scripts:salt_pip", + ] + ) + entrypoints["console_scripts"] = scripts + return entrypoints + + +def get_scripts(dist=None): + is_windows = sys.platform.startswith("win") + scripts = ["scripts/salt-call"] + + ssh_packaging = False + if dist: + ssh_packaging = getattr(dist, "ssh_packaging", False) + if not ssh_packaging: + ssh_packaging = os.path.exists( + os.path.join(PROJECT_ROOT, "salt", "_ssh_packaging") + ) + + if ssh_packaging: + scripts.append("scripts/salt-ssh") + if is_windows and not os.environ.get("SALT_BUILD_ALL_BINS"): + return scripts + scripts.extend(["scripts/salt-cloud", "scripts/spm"]) + return scripts + + if is_windows and not os.environ.get("SALT_BUILD_ALL_BINS"): + scripts.extend(["scripts/salt-cp", "scripts/salt-minion"]) + return scripts + + # *nix or SALT_BUILD_ALL_BINS, so, we need all scripts + scripts.extend( + [ + "scripts/salt", + "scripts/salt-api", + "scripts/salt-cloud", + "scripts/salt-cp", + "scripts/salt-key", + "scripts/salt-master", + "scripts/salt-minion", + "scripts/salt-proxy", + "scripts/salt-run", + "scripts/salt-ssh", + "scripts/salt-syndic", + "scripts/spm", + ] + ) + return scripts