diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index c465f8de0fb7..690b27ae6728 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,7 @@ blank_issues_enabled: true contact_links: - - name: Salt Community Slack - url: https://saltstackcommunity.slack.com/ + - name: Salt Community Discord + url: https://discord.com/invite/J7b7EscrAs about: Please ask and answer questions here. - name: Salt-Users Forum url: https://groups.google.com/forum/#!forum/salt-users diff --git a/.github/ISSUE_TEMPLATE/tech-debt.md b/.github/ISSUE_TEMPLATE/tech-debt.md index a13303e3ee11..6761145a4429 100644 --- a/.github/ISSUE_TEMPLATE/tech-debt.md +++ b/.github/ISSUE_TEMPLATE/tech-debt.md @@ -8,7 +8,7 @@ assignees: '' --- ### Description of the tech debt to be addressed, include links and screenshots - + ### Versions Report (Provided by running `salt --versions-report`. Please also mention any differences in master/minion versions.) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index f06d37e9e140..d707b6c88481 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -11,7 +11,9 @@ Remove this section if not relevant ### Merge requirements satisfied? **[NOTICE] Bug fixes or features added to Salt require tests.** - + - [ ] Docs - [ ] Changelog - https://docs.saltproject.io/en/master/topics/development/changelog.html - [ ] Tests written/updated @@ -19,7 +21,13 @@ Remove this section if not relevant ### Commits signed with GPG? Yes/No -Please review [Salt's Contributing Guide](https://docs.saltproject.io/en/master/topics/development/contributing.html) for best practices, including the -[PR Guidelines](https://docs.saltproject.io/en/master/topics/development/pull_requests.html). + -See GitHub's [page on GPG signing](https://help.github.com/articles/signing-commits-using-gpg/) for more information about signing commits with GPG. + + + diff --git a/.github/config.yml b/.github/config.yml index 6bf3cadf069f..b89e3c822bd1 100644 --- a/.github/config.yml +++ b/.github/config.yml @@ -13,7 +13,7 @@ newIssueWelcomeComment: > - [Community Wiki](https://github.com/saltstack/community/wiki) - [Salt’s Contributor Guide](https://docs.saltproject.io/en/master/topics/development/contributing.html) - - [Join our Community Slack](https://via.vmw.com/salt-slack) + - [Join our Community Discord](https://discord.com/invite/J7b7EscrAs) - [IRC on LiberaChat](https://web.libera.chat/#salt) - [Salt Project YouTube channel](https://www.youtube.com/channel/UCpveTIucFx9ljGelW63-BWg) - [Salt Project Twitch channel](https://www.twitch.tv/saltprojectoss) @@ -39,7 +39,7 @@ newPRWelcomeComment: > - [Community Wiki](https://github.com/saltstack/community/wiki) - [Salt’s Contributor Guide](https://docs.saltproject.io/en/master/topics/development/contributing.html) - - [Join our Community Slack](https://via.vmw.com/salt-slack) + - [Join our Community Discord](https://discord.com/invite/J7b7EscrAs) - [IRC on LiberaChat](https://web.libera.chat/#salt) - [Salt Project YouTube channel](https://www.youtube.com/channel/UCpveTIucFx9ljGelW63-BWg) - [Salt Project Twitch channel](https://www.twitch.tv/saltprojectoss) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a64c29957c8b..becb750a300e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -128,6 +128,9 @@ jobs: - pkg/** - *pkg_requirements - *salt_added_modified + nsis_tests: + - added|modified: &nsis_tests + - pkg/windows/nsis/** testrun: - added|modified: - *pkg_requirements @@ -260,6 +263,14 @@ jobs: - prepare-workflow with: changed-files: ${{ needs.prepare-workflow.outputs.changed-files }} + nsis-tests: + name: NSIS Tests + if: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['github-hosted'] }} + uses: ./.github/workflows/nsis-tests.yml + needs: + - prepare-workflow + with: + changed-files: ${{ needs.prepare-workflow.outputs.changed-files }} prepare-release: name: "Prepare Release: ${{ needs.prepare-workflow.outputs.salt-version }}" @@ -1537,27 +1548,6 @@ jobs: workflow-slug: ci timeout-minutes: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['type'] == 'full' && 180 || 360 }} - archlinux-lts: - name: Arch Linux LTS Test - if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} - needs: - - prepare-workflow - - build-ci-deps - uses: ./.github/workflows/test-action-linux.yml - with: - distro-slug: archlinux-lts - nox-session: ci-test-onedir - platform: linux - arch: x86_64 - nox-version: 2022.8.7 - gh-actions-python-version: "3.10" - testrun: ${{ needs.prepare-workflow.outputs.testrun }} - salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.14 - skip-code-coverage: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['skip_code_coverage'] }} - workflow-slug: ci - timeout-minutes: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['type'] == 'full' && 180 || 360 }} - debian-11: name: Debian 11 Test if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] && contains(fromJSON(needs.prepare-workflow.outputs.os-labels), 'debian-11') }} @@ -2005,7 +1995,6 @@ jobs: - amazonlinux-2-arm64 - amazonlinux-2023 - amazonlinux-2023-arm64 - - archlinux-lts - debian-11 - debian-11-arm64 - debian-12 @@ -2059,8 +2048,9 @@ jobs: id: get-coverage-reports uses: actions/download-artifact@v4 with: - name: all-testrun-coverage-artifacts path: artifacts/coverage/ + pattern: all-testrun-coverage-artifacts* + merge-multiple: true - name: Display structure of downloaded files run: tree -a artifacts/ @@ -2152,6 +2142,7 @@ jobs: - prepare-workflow - pre-commit - lint + - nsis-tests - build-docs - build-deps-onedir - build-salt-onedir @@ -2172,7 +2163,6 @@ jobs: - amazonlinux-2-arm64 - amazonlinux-2023 - amazonlinux-2023-arm64 - - archlinux-lts - debian-11 - debian-11-arm64 - debian-12 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index ed9f9c70740a..d6e3fe4ce792 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -185,6 +185,9 @@ jobs: - pkg/** - *pkg_requirements - *salt_added_modified + nsis_tests: + - added|modified: &nsis_tests + - pkg/windows/nsis/** testrun: - added|modified: - *pkg_requirements @@ -317,6 +320,14 @@ jobs: - prepare-workflow with: changed-files: ${{ needs.prepare-workflow.outputs.changed-files }} + nsis-tests: + name: NSIS Tests + if: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['github-hosted'] }} + uses: ./.github/workflows/nsis-tests.yml + needs: + - prepare-workflow + with: + changed-files: ${{ needs.prepare-workflow.outputs.changed-files }} prepare-release: name: "Prepare Release: ${{ needs.prepare-workflow.outputs.salt-version }}" @@ -1602,27 +1613,6 @@ jobs: workflow-slug: nightly timeout-minutes: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['type'] == 'full' && 180 || 360 }} - archlinux-lts: - name: Arch Linux LTS Test - if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} - needs: - - prepare-workflow - - build-ci-deps - uses: ./.github/workflows/test-action-linux.yml - with: - distro-slug: archlinux-lts - nox-session: ci-test-onedir - platform: linux - arch: x86_64 - nox-version: 2022.8.7 - gh-actions-python-version: "3.10" - testrun: ${{ needs.prepare-workflow.outputs.testrun }} - salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.14 - skip-code-coverage: false - workflow-slug: nightly - timeout-minutes: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['type'] == 'full' && 180 || 360 }} - debian-11: name: Debian 11 Test if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} @@ -2070,7 +2060,6 @@ jobs: - amazonlinux-2-arm64 - amazonlinux-2023 - amazonlinux-2023-arm64 - - archlinux-lts - debian-11 - debian-11-arm64 - debian-12 @@ -2124,8 +2113,9 @@ jobs: id: get-coverage-reports uses: actions/download-artifact@v4 with: - name: all-testrun-coverage-artifacts path: artifacts/coverage/ + pattern: all-testrun-coverage-artifacts* + merge-multiple: true - name: Display structure of downloaded files run: tree -a artifacts/ @@ -2977,7 +2967,6 @@ jobs: - amazonlinux-2-arm64 - amazonlinux-2023 - amazonlinux-2023-arm64 - - archlinux-lts - debian-11 - debian-11-arm64 - debian-12 @@ -3050,6 +3039,7 @@ jobs: - prepare-workflow - pre-commit - lint + - nsis-tests - build-docs - build-deps-onedir - build-salt-onedir diff --git a/.github/workflows/nsis-tests.yml b/.github/workflows/nsis-tests.yml new file mode 100644 index 000000000000..e80ed43f4099 --- /dev/null +++ b/.github/workflows/nsis-tests.yml @@ -0,0 +1,67 @@ +--- +name: Test NSIS Installer + +on: + workflow_call: + inputs: + changed-files: + required: true + type: string + description: JSON string containing information about changed files + +jobs: + Test-NSIS-Logic: + name: Logic Tests + runs-on: + - windows-latest + if: ${{ contains(fromJSON('["push", "schedule", "workflow_dispatch"]'), github.event_name) || fromJSON(inputs.changed-files)['nsis_tests'] }} + + steps: + + - name: Checkout Salt + uses: actions/checkout@v4 + + - name: Set Up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install NSIS + run: .\pkg\windows\install_nsis.cmd -CICD + shell: cmd + + - name: Build Test Installer + run: .\pkg\windows\nsis\tests\setup.cmd -CICD + shell: cmd + + - name: Run Config Tests + run: .\pkg\windows\nsis\tests\test.cmd -CICD .\config_tests + shell: cmd + + Test-NSIS-Stress: + name: Stress Tests + runs-on: + - windows-latest + if: ${{ contains(fromJSON('["push", "schedule", "workflow_dispatch"]'), github.event_name) || fromJSON(inputs.changed-files)['nsis_tests'] }} + + steps: + + - name: Checkout Salt + uses: actions/checkout@v4 + + - name: Set Up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install NSIS + run: .\pkg\windows\install_nsis.cmd -CICD + shell: cmd + + - name: Build Test Installer + run: .\pkg\windows\nsis\tests\setup.cmd -CICD + shell: cmd + + - name: Run Stress Test + run: .\pkg\windows\nsis\tests\test.cmd -CICD .\stress_tests + shell: cmd diff --git a/.github/workflows/scheduled.yml b/.github/workflows/scheduled.yml index 37e50f5d98d3..af79ddbd638f 100644 --- a/.github/workflows/scheduled.yml +++ b/.github/workflows/scheduled.yml @@ -175,6 +175,9 @@ jobs: - pkg/** - *pkg_requirements - *salt_added_modified + nsis_tests: + - added|modified: &nsis_tests + - pkg/windows/nsis/** testrun: - added|modified: - *pkg_requirements @@ -307,6 +310,14 @@ jobs: - prepare-workflow with: changed-files: ${{ needs.prepare-workflow.outputs.changed-files }} + nsis-tests: + name: NSIS Tests + if: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['github-hosted'] }} + uses: ./.github/workflows/nsis-tests.yml + needs: + - prepare-workflow + with: + changed-files: ${{ needs.prepare-workflow.outputs.changed-files }} prepare-release: name: "Prepare Release: ${{ needs.prepare-workflow.outputs.salt-version }}" @@ -1584,27 +1595,6 @@ jobs: workflow-slug: scheduled timeout-minutes: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['type'] == 'full' && 180 || 360 }} - archlinux-lts: - name: Arch Linux LTS Test - if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} - needs: - - prepare-workflow - - build-ci-deps - uses: ./.github/workflows/test-action-linux.yml - with: - distro-slug: archlinux-lts - nox-session: ci-test-onedir - platform: linux - arch: x86_64 - nox-version: 2022.8.7 - gh-actions-python-version: "3.10" - testrun: ${{ needs.prepare-workflow.outputs.testrun }} - salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.14 - skip-code-coverage: false - workflow-slug: scheduled - timeout-minutes: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['type'] == 'full' && 180 || 360 }} - debian-11: name: Debian 11 Test if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} @@ -2052,7 +2042,6 @@ jobs: - amazonlinux-2-arm64 - amazonlinux-2023 - amazonlinux-2023-arm64 - - archlinux-lts - debian-11 - debian-11-arm64 - debian-12 @@ -2106,8 +2095,9 @@ jobs: id: get-coverage-reports uses: actions/download-artifact@v4 with: - name: all-testrun-coverage-artifacts path: artifacts/coverage/ + pattern: all-testrun-coverage-artifacts* + merge-multiple: true - name: Display structure of downloaded files run: tree -a artifacts/ @@ -2201,6 +2191,7 @@ jobs: - prepare-workflow - pre-commit - lint + - nsis-tests - build-docs - build-deps-onedir - build-salt-onedir @@ -2221,7 +2212,6 @@ jobs: - amazonlinux-2-arm64 - amazonlinux-2023 - amazonlinux-2023-arm64 - - archlinux-lts - debian-11 - debian-11-arm64 - debian-12 diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index 3eac8682d009..80aacdfe6ee6 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -158,6 +158,9 @@ jobs: - pkg/** - *pkg_requirements - *salt_added_modified + nsis_tests: + - added|modified: &nsis_tests + - pkg/windows/nsis/** testrun: - added|modified: - *pkg_requirements @@ -299,6 +302,14 @@ jobs: - prepare-workflow with: changed-files: ${{ needs.prepare-workflow.outputs.changed-files }} + nsis-tests: + name: NSIS Tests + if: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['github-hosted'] }} + uses: ./.github/workflows/nsis-tests.yml + needs: + - prepare-workflow + with: + changed-files: ${{ needs.prepare-workflow.outputs.changed-files }} prepare-release: name: "Prepare Release: ${{ needs.prepare-workflow.outputs.salt-version }}" @@ -1584,27 +1595,6 @@ jobs: workflow-slug: staging timeout-minutes: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['type'] == 'full' && 180 || 360 }} - archlinux-lts: - name: Arch Linux LTS Test - if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} - needs: - - prepare-workflow - - build-ci-deps - uses: ./.github/workflows/test-action-linux.yml - with: - distro-slug: archlinux-lts - nox-session: ci-test-onedir - platform: linux - arch: x86_64 - nox-version: 2022.8.7 - gh-actions-python-version: "3.10" - testrun: ${{ needs.prepare-workflow.outputs.testrun }} - salt-version: "${{ needs.prepare-workflow.outputs.salt-version }}" - cache-prefix: ${{ needs.prepare-workflow.outputs.cache-seed }}|3.10.14 - skip-code-coverage: true - workflow-slug: staging - timeout-minutes: ${{ fromJSON(needs.prepare-workflow.outputs.testrun)['type'] == 'full' && 180 || 360 }} - debian-11: name: Debian 11 Test if: ${{ fromJSON(needs.prepare-workflow.outputs.jobs)['test'] && fromJSON(needs.prepare-workflow.outputs.runners)['self-hosted'] }} @@ -2927,7 +2917,6 @@ jobs: - amazonlinux-2-arm64 - amazonlinux-2023 - amazonlinux-2023-arm64 - - archlinux-lts - debian-11 - debian-11-arm64 - debian-12 @@ -3044,6 +3033,7 @@ jobs: - prepare-workflow - pre-commit - lint + - nsis-tests - build-docs - build-deps-onedir - build-salt-onedir diff --git a/.github/workflows/templates/ci.yml.jinja b/.github/workflows/templates/ci.yml.jinja index a259e6dd43d8..1d230c8b7482 100644 --- a/.github/workflows/templates/ci.yml.jinja +++ b/.github/workflows/templates/ci.yml.jinja @@ -39,6 +39,19 @@ <%- endif %> + <%- set job_name = "nsis-tests" %> + <%- if includes.get(job_name, True) %> + <{ job_name }>: + <%- do conclusion_needs.append(job_name) %> + name: NSIS Tests + if: ${{ fromJSON(needs.prepare-workflow.outputs.runners)['github-hosted'] }} + uses: ./.github/workflows/nsis-tests.yml + needs: + - prepare-workflow + with: + changed-files: ${{ needs.prepare-workflow.outputs.changed-files }} + + <%- endif %> <%- set job_name = "prepare-release" %> <%- if includes.get(job_name, True) %> @@ -362,8 +375,9 @@ id: get-coverage-reports uses: actions/download-artifact@v4 with: - name: all-testrun-coverage-artifacts path: artifacts/coverage/ + pattern: all-testrun-coverage-artifacts* + merge-multiple: true - name: Display structure of downloaded files run: tree -a artifacts/ diff --git a/.github/workflows/templates/layout.yml.jinja b/.github/workflows/templates/layout.yml.jinja index 2efbee7bee61..82663013e8e5 100644 --- a/.github/workflows/templates/layout.yml.jinja +++ b/.github/workflows/templates/layout.yml.jinja @@ -176,6 +176,9 @@ jobs: - pkg/** - *pkg_requirements - *salt_added_modified + nsis_tests: + - added|modified: &nsis_tests + - pkg/windows/nsis/** testrun: - added|modified: - *pkg_requirements diff --git a/.github/workflows/test-action-linux.yml b/.github/workflows/test-action-linux.yml index 3a25fe5f57fc..f398693949cf 100644 --- a/.github/workflows/test-action-linux.yml +++ b/.github/workflows/test-action-linux.yml @@ -286,6 +286,7 @@ jobs: name: testrun-coverage-artifacts-${{ inputs.distro-slug }}${{ inputs.fips && '-fips' || '' }}-${{ inputs.nox-session }}-${{ matrix.transport }}-${{ matrix.tests-chunk }}-grp${{ matrix.test-group || '1' }}-${{ env.TIMESTAMP }} path: | artifacts/coverage/ + include-hidden-files: true - name: Upload JUnit XML Test Run Artifacts if: always() && steps.download-artifacts-from-vm.outcome == 'success' @@ -357,8 +358,9 @@ jobs: if: ${{ inputs.skip-code-coverage == false }} id: download-coverage-artifacts with: - name: testrun-coverage-artifacts-${{ inputs.distro-slug }}${{ inputs.fips && '-fips' || '' }}-${{ inputs.nox-session }} path: artifacts/coverage/ + pattern: testrun-coverage-artifacts-${{ inputs.distro-slug }}${{ inputs.fips && '-fips' || '' }}-${{ inputs.nox-session }}* + merge-multiple: true - name: Show Downloaded Test Run Artifacts if: ${{ inputs.skip-code-coverage == false }} @@ -400,3 +402,4 @@ jobs: with: name: all-testrun-coverage-artifacts-${{ inputs.distro-slug }}${{ inputs.fips && '-fips' || '' }}-${{ inputs.nox-session }} path: artifacts/coverage + include-hidden-files: true diff --git a/.github/workflows/test-action-macos.yml b/.github/workflows/test-action-macos.yml index 38bf4204a9b3..72bc6af27f8d 100644 --- a/.github/workflows/test-action-macos.yml +++ b/.github/workflows/test-action-macos.yml @@ -316,6 +316,7 @@ jobs: name: testrun-coverage-artifacts-${{ inputs.distro-slug }}-${{ inputs.nox-session }}-${{ matrix.transport }}-${{ matrix.tests-chunk }}-${{ env.TIMESTAMP }} path: | artifacts/coverage/ + include-hidden-files: true - name: Upload JUnit XML Test Run Artifacts if: always() && steps.download-artifacts-from-vm.outcome == 'success' @@ -387,8 +388,9 @@ jobs: if: ${{ inputs.skip-code-coverage == false }} id: download-coverage-artifacts with: - name: testrun-coverage-artifacts-${{ inputs.distro-slug }}-${{ inputs.nox-session }} path: artifacts/coverage/ + pattern: testrun-coverage-artifacts-${{ inputs.distro-slug }}-${{ inputs.nox-session }}* + merge-multiple: true - name: Show Downloaded Test Run Artifacts if: ${{ inputs.skip-code-coverage == false }} @@ -435,3 +437,4 @@ jobs: with: name: all-testrun-coverage-artifacts-${{ inputs.distro-slug }}.${{ inputs.nox-session }} path: artifacts/coverage + include-hidden-files: true diff --git a/.github/workflows/test-action-windows.yml b/.github/workflows/test-action-windows.yml index dc54f501e693..3d3d82427864 100644 --- a/.github/workflows/test-action-windows.yml +++ b/.github/workflows/test-action-windows.yml @@ -286,6 +286,7 @@ jobs: name: testrun-coverage-artifacts-${{ inputs.distro-slug }}-${{ inputs.nox-session }}-${{ matrix.transport }}-${{ matrix.tests-chunk }}-grp${{ matrix.test-group || '1' }}-${{ env.TIMESTAMP }} path: | artifacts/coverage/ + include-hidden-files: true - name: Upload JUnit XML Test Run Artifacts if: always() && steps.download-artifacts-from-vm.outcome == 'success' @@ -358,8 +359,9 @@ jobs: if: ${{ inputs.skip-code-coverage == false }} id: download-coverage-artifacts with: - name: testrun-coverage-artifacts-${{ inputs.distro-slug }}-${{ inputs.nox-session }} path: artifacts/coverage/ + pattern: testrun-coverage-artifacts-${{ inputs.distro-slug }}-${{ inputs.nox-session }}* + merge-multiple: true - name: Show Downloaded Test Run Artifacts if: ${{ inputs.skip-code-coverage == false }} @@ -401,3 +403,4 @@ jobs: with: name: all-testrun-coverage-artifacts-${{ inputs.distro-slug }}.${{ inputs.nox-session }} path: artifacts/coverage + include-hidden-files: true diff --git a/.github/workflows/test-installer-action-windows.yml b/.github/workflows/test-installer-action-windows.yml deleted file mode 100644 index cf0b48556bb9..000000000000 --- a/.github/workflows/test-installer-action-windows.yml +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: Test Windows Installer - -on: pull_request - -permissions: - contents: read - -jobs: - Test-Windows-Installer: - runs-on: - - windows-latest - - steps: - - - name: Checkout Salt - uses: actions/checkout@v4 - - - name: Set Up Python 3.10 - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - - name: Install NSIS - run: .\pkg\windows\install_nsis.cmd -CICD - shell: cmd - - - name: Build Test Installer - run: .\pkg\windows\nsis\tests\setup.cmd -CICD - shell: cmd - - - name: Run Stress Test - run: .\pkg\windows\nsis\tests\test.cmd -CICD .\stress_tests - shell: cmd - - - name: Run Config Tests - run: .\pkg\windows\nsis\tests\test.cmd -CICD .\config_tests - shell: cmd diff --git a/.gitignore b/.gitignore index 5ec47806846a..6c9257dafd5d 100644 --- a/.gitignore +++ b/.gitignore @@ -91,6 +91,7 @@ tests/unit/templates/roots # Pycharm .idea venv/ +.venv/ # VS Code .vscode diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 6a41d924ae30..c9c459ef5584 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,30 +1,56 @@ -============ -Contributing -============ +============================================== +Contributing to Salt: A Guide for Contributors +============================================== + +So, you want to contribute to the Salt project? That's fantastic! There are many +ways you can help improve Salt: + +- Use Salt and report bugs with clear, detailed descriptions. +- Join a `working group `__ to + collaborate with other contributors. +- Answer questions on platforms like `IRC `__, + the `community Discord `__, + the `salt-users mailing list `__, + `Server Fault `__, + or `r/saltstack on Reddit `__. +- Fix bugs or contribute to the `documentation `__. +- Submit workarounds, patches, or code (even without tests). +- Share your experiences and solutions to problems you've solved using Salt. + +Choosing the Right Branch for Your Pull Request +=============================================== + +We appreciate your contributions to the project! To ensure a smooth and +efficient workflow, please follow these guidelines when submitting a Pull +Request. Each type of contribution—whether it's fixing a bug, adding a feature, +updating documentation, or fixing tests—should be targeted at the appropriate +branch. This helps us manage changes effectively and maintain stability across +versions. + +- **Bug Fixes:** + + Create your Pull Request against the oldest supported branch where the bug + exists. This ensures that the fix can be applied to all relevant versions. + +- **New Features**: + + For new features or enhancements, create your Pull Request against the master + branch. + +- **Documentation Updates:** + + Documentation changes should be made against the master branch, unless they + are related to a bug fix, in which case they should follow the same branch as + the bug fix. + +- **Test Fixes:** + + Pull Requests that fix broken or failing tests should be created against the + oldest supported branch where the issue occurs. + +Setting Up Your Salt Development Environment +============================================ -So you want to contribute to the Salt project? Excellent! You can help -in a number of ways: - -- Use Salt and open well-written bug reports. -- Join a `working group `__. -- Answer questions on `irc `__, - the `community Slack `__, - the `salt-users mailing - list `__, - `Server Fault `__, - or `r/saltstack on Reddit `__. -- Fix bugs. -- `Improve the documentation `__. -- Provide workarounds, patches, or other code without tests. -- Tell other people about problems you solved using Salt. - -If you'd like to update docs or fix an issue, you're going to need the -Salt repo. The best way to contribute is using -`Git `__. - - -Environment setup -================= To hack on Salt or the docs you're going to need to set up your development environment. If you already have a workflow that you're comfortable with, you can use that, but otherwise this is an opinionated @@ -109,7 +135,7 @@ Then activate it: Sweet! Now you're ready to clone Salt so you can start hacking away! If you get stuck at any point, check out the resources at the beginning of -this guide. IRC and Slack are particularly helpful places to go. +this guide. IRC and Discord are particularly helpful places to go. Get the source! @@ -605,7 +631,7 @@ your PR is submitted during the week you should be able to expect some kind of communication within that business day. If your tests are passing and we're not in a code freeze, ideally your code will be merged that week or month. If you haven't heard from your assigned reviewer, ping them -on GitHub, `irc `__, or Community Slack. +on GitHub, `irc `__, or Community Discord. It's likely that your reviewer will leave some comments that need addressing - it may be a style change, or you forgot a changelog entry, diff --git a/README.rst b/README.rst index dd32e11cc5fb..f3129299b114 100644 --- a/README.rst +++ b/README.rst @@ -10,9 +10,9 @@ :alt: PyPi Package Downloads :target: https://lgtm.com/projects/g/saltstack/salt/context:python -.. image:: https://img.shields.io/badge/slack-SaltProject-blue.svg?logo=slack - :alt: Salt Project Slack Community - :target: https://via.vmw.com/salt-slack +.. image:: https://img.shields.io/badge/discord-SaltProject-blue.svg?logo=discord + :alt: Salt Project Discord Community + :target: https://discord.com/invite/J7b7EscrAs .. image:: https://img.shields.io/twitch/status/saltprojectoss :alt: Salt Project Twitch Channel @@ -103,7 +103,8 @@ Report bugs or problems using Salt by opening an issue: ``_ -and the `Salt Project Community Slack`_. +and the `Salt Project Community Discord`_. Responsibly reporting security vulnerabilities @@ -153,7 +154,7 @@ Please be sure to review our Also, check out some of our community resources including: * `Salt Project Community Wiki `_ -* `Salt Project Community Slack`_ +* `Salt Project Community Discord`_ * `Salt Project: IRC on LiberaChat `_ * `Salt Project YouTube channel `_ * `Salt Project Twitch channel `_ @@ -165,8 +166,7 @@ to the **Salt Project Community Events Calendar** on the main ``_ website. If you have additional questions, email us at saltproject@vmware.com or reach out -directly to the Community Manager, Jimmy Chunga via Slack. We'd be glad to -have you join our community! +directly to the Community Discord. We'd be glad to have you join our community! License @@ -180,7 +180,7 @@ used by external modules. A complete list of attributions and dependencies can be found here: `salt/DEPENDENCIES.md `_ -.. _Salt Project Community Slack: https://via.vmw.com/salt-slack +.. _Salt Project Community Discord: https://discord.com/invite/J7b7EscrAs .. _VMware Aria Automation Config: https://www.vmware.com/products/vrealize-automation/saltstack-config.html .. _Latest Salt Documentation: https://docs.saltproject.io/en/latest/ .. _Open an issue: https://github.com/saltstack/salt/issues/new/choose diff --git a/SUPPORT.rst b/SUPPORT.rst index f98c4d3f2eee..537039bf1275 100644 --- a/SUPPORT.rst +++ b/SUPPORT.rst @@ -8,10 +8,10 @@ it may take a few moments for someone to reply. ``_ -**SaltStack Slack** - Alongside IRC is our SaltStack Community Slack for the +**SaltStack Slack** - Alongside IRC is our SaltStack Community Discord for the SaltStack Working groups. Use the following link to request an invitation. -``_ +``_ **Mailing List** - The SaltStack community users mailing list is hosted by Google groups. Anyone can post to ask questions about SaltStack products and diff --git a/changelog/61001.fixed.md b/changelog/61001.fixed.md new file mode 100644 index 000000000000..f9e6acf934d2 --- /dev/null +++ b/changelog/61001.fixed.md @@ -0,0 +1,2 @@ +Fixed an issue uninstalling packages on Windows using pkg.removed where there +are multiple versions of the same software installed diff --git a/changelog/62501.fixed.md b/changelog/62501.fixed.md new file mode 100644 index 000000000000..5b9b04603220 --- /dev/null +++ b/changelog/62501.fixed.md @@ -0,0 +1 @@ +Convert stdin string to bytes regardless of stdin_raw_newlines diff --git a/changelog/64630.fixed.md b/changelog/64630.fixed.md new file mode 100644 index 000000000000..f49c58d4c2e0 --- /dev/null +++ b/changelog/64630.fixed.md @@ -0,0 +1,3 @@ +Fixed an intermittent issue with file.recurse where the state would +report failure even on success. Makes sure symlinks are created +after the target file is created diff --git a/changelog/66560.fixed.md b/changelog/66560.fixed.md new file mode 100644 index 000000000000..e71230f25b44 --- /dev/null +++ b/changelog/66560.fixed.md @@ -0,0 +1 @@ +Correct bash-completion for Debian / Ubuntu diff --git a/changelog/66596.fixed.md b/changelog/66596.fixed.md new file mode 100644 index 000000000000..a4a27151f2c1 --- /dev/null +++ b/changelog/66596.fixed.md @@ -0,0 +1,2 @@ +Fixed an issue with cmd.run with requirements when the shell is not the +default diff --git a/changelog/66772.fixed.md b/changelog/66772.fixed.md new file mode 100644 index 000000000000..2f9f40ee523d --- /dev/null +++ b/changelog/66772.fixed.md @@ -0,0 +1 @@ +Fixed nacl.keygen for not yet existing sk_file or pk_file diff --git a/changelog/66783.fixed.md b/changelog/66783.fixed.md new file mode 100644 index 000000000000..2bd08e3411d3 --- /dev/null +++ b/changelog/66783.fixed.md @@ -0,0 +1 @@ +fix yaml output diff --git a/changelog/66784.fixed.md b/changelog/66784.fixed.md new file mode 100644 index 000000000000..afe9df887a54 --- /dev/null +++ b/changelog/66784.fixed.md @@ -0,0 +1,2 @@ +Fixed an issue where enabling `grain_opts` in the minion config would cause +some core grains to be overwritten. diff --git a/changelog/66835.fixed.md b/changelog/66835.fixed.md new file mode 100644 index 000000000000..33d932b7fdf1 --- /dev/null +++ b/changelog/66835.fixed.md @@ -0,0 +1,2 @@ +Removed ``salt.utils.data.decode`` usage from the fileserver. This function was +necessary to support Python 2. This speeds up loading the list cache by 80-90x. diff --git a/changelog/66837.fixed.md b/changelog/66837.fixed.md new file mode 100644 index 000000000000..ccbe4a1155fd --- /dev/null +++ b/changelog/66837.fixed.md @@ -0,0 +1,3 @@ +Issue 66837: Fixes an issue with the `network.local_port_tcp` function +where it was not parsing the IPv4 mapped IPv6 address correctly. The +``::ffff:`` is now removed and only the IP address is returned. diff --git a/changelog/66886.deprecated.md b/changelog/66886.deprecated.md new file mode 100644 index 000000000000..597c0aee10a4 --- /dev/null +++ b/changelog/66886.deprecated.md @@ -0,0 +1 @@ +Drop Arch Linux support diff --git a/cicd/golden-images.json b/cicd/golden-images.json index ca7818fdd6ba..b0504ad777a7 100644 --- a/cicd/golden-images.json +++ b/cicd/golden-images.json @@ -1,8 +1,8 @@ { "amazonlinux-2-arm64": { - "ami": "ami-0c98c023fba59d522", + "ami": "ami-0aab00f54b6cddde6", "ami_description": "CI Image of AmazonLinux 2 arm64", - "ami_name": "salt-project/ci/amazonlinux/2/arm64/20240509.1530", + "ami_name": "salt-project/ci/amazonlinux/2/arm64/20240912.2135", "arch": "arm64", "cloudwatch-agent-available": "true", "instance_type": "m6g.large", @@ -10,9 +10,9 @@ "ssh_username": "ec2-user" }, "amazonlinux-2": { - "ami": "ami-02cba95cfd7074794", + "ami": "ami-0fd6cec7bbcf52d36", "ami_description": "CI Image of AmazonLinux 2 x86_64", - "ami_name": "salt-project/ci/amazonlinux/2/x86_64/20240509.1530", + "ami_name": "salt-project/ci/amazonlinux/2/x86_64/20240912.2135", "arch": "x86_64", "cloudwatch-agent-available": "true", "instance_type": "t3a.large", @@ -20,9 +20,9 @@ "ssh_username": "ec2-user" }, "amazonlinux-2023-arm64": { - "ami": "ami-0609f0e98f5a6b73d", + "ami": "ami-095e9e4757b5fca1a", "ami_description": "CI Image of AmazonLinux 2023 arm64", - "ami_name": "salt-project/ci/amazonlinux/2023/arm64/20240509.1529", + "ami_name": "salt-project/ci/amazonlinux/2023/arm64/20240912.2136", "arch": "arm64", "cloudwatch-agent-available": "true", "instance_type": "m6g.large", @@ -30,29 +30,19 @@ "ssh_username": "ec2-user" }, "amazonlinux-2023": { - "ami": "ami-0554a801eb6dcc42c", + "ami": "ami-002d043f1a36bf06e", "ami_description": "CI Image of AmazonLinux 2023 x86_64", - "ami_name": "salt-project/ci/amazonlinux/2023/x86_64/20240509.1529", + "ami_name": "salt-project/ci/amazonlinux/2023/x86_64/20240912.2136", "arch": "x86_64", "cloudwatch-agent-available": "true", "instance_type": "t3a.large", "is_windows": "false", "ssh_username": "ec2-user" }, - "archlinux-lts": { - "ami": "ami-01ad78f19930b9747", - "ami_description": "CI Image of ArchLinux lts x86_64", - "ami_name": "salt-project/ci/archlinux/lts/x86_64/20240509.1530", - "arch": "x86_64", - "cloudwatch-agent-available": "false", - "instance_type": "t3a.large", - "is_windows": "false", - "ssh_username": "arch" - }, "debian-11-arm64": { - "ami": "ami-0eff227d9a94d8692", + "ami": "ami-0ff63235fce7bea1d", "ami_description": "CI Image of Debian 11 arm64", - "ami_name": "salt-project/ci/debian/11/arm64/20240509.1529", + "ami_name": "salt-project/ci/debian/11/arm64/20240912.2135", "arch": "arm64", "cloudwatch-agent-available": "false", "instance_type": "m6g.large", @@ -60,9 +50,9 @@ "ssh_username": "admin" }, "debian-11": { - "ami": "ami-099b2a5a1fb995166", + "ami": "ami-08685bfca48beeb67", "ami_description": "CI Image of Debian 11 x86_64", - "ami_name": "salt-project/ci/debian/11/x86_64/20240509.1529", + "ami_name": "salt-project/ci/debian/11/x86_64/20240912.2135", "arch": "x86_64", "cloudwatch-agent-available": "true", "instance_type": "t3a.large", @@ -70,9 +60,9 @@ "ssh_username": "admin" }, "debian-12-arm64": { - "ami": "ami-0ab6b0cc8488f8880", + "ami": "ami-07d383138f04b32ba", "ami_description": "CI Image of Debian 12 arm64", - "ami_name": "salt-project/ci/debian/12/arm64/20240509.1529", + "ami_name": "salt-project/ci/debian/12/arm64/20240912.2135", "arch": "arm64", "cloudwatch-agent-available": "false", "instance_type": "m6g.large", @@ -80,9 +70,9 @@ "ssh_username": "admin" }, "debian-12": { - "ami": "ami-0e1f5b55325249c4e", + "ami": "ami-0867ec74072fd97a0", "ami_description": "CI Image of Debian 12 x86_64", - "ami_name": "salt-project/ci/debian/12/x86_64/20240509.1530", + "ami_name": "salt-project/ci/debian/12/x86_64/20240912.2135", "arch": "x86_64", "cloudwatch-agent-available": "true", "instance_type": "t3a.large", @@ -90,9 +80,9 @@ "ssh_username": "admin" }, "fedora-40-arm64": { - "ami": "ami-064df327a55f83953", + "ami": "ami-03be8e03c17f1abeb", "ami_description": "CI Image of Fedora 40 arm64", - "ami_name": "salt-project/ci/fedora/40/arm64/20240509.1530", + "ami_name": "salt-project/ci/fedora/40/arm64/20240912.2136", "arch": "arm64", "cloudwatch-agent-available": "true", "instance_type": "m6g.large", @@ -100,9 +90,9 @@ "ssh_username": "fedora" }, "fedora-40": { - "ami": "ami-08d8dbd4f063788de", + "ami": "ami-060a59b30809758b2", "ami_description": "CI Image of Fedora 40 x86_64", - "ami_name": "salt-project/ci/fedora/40/x86_64/20240509.1530", + "ami_name": "salt-project/ci/fedora/40/x86_64/20240912.2136", "arch": "x86_64", "cloudwatch-agent-available": "true", "instance_type": "t3a.large", @@ -110,9 +100,9 @@ "ssh_username": "fedora" }, "opensuse-15": { - "ami": "ami-0f82d5ab3015af6ad", + "ami": "ami-0aaf63315ada5365b", "ami_description": "CI Image of Opensuse 15 x86_64", - "ami_name": "salt-project/ci/opensuse/15/x86_64/20240509.1529", + "ami_name": "salt-project/ci/opensuse/15/x86_64/20240912.2135", "arch": "x86_64", "cloudwatch-agent-available": "true", "instance_type": "t3a.large", @@ -120,9 +110,9 @@ "ssh_username": "ec2-user" }, "photonos-4-arm64": { - "ami": "ami-0ea152c346cb8e13b", + "ami": "ami-0d425acec9d0d78a5", "ami_description": "CI Image of PhotonOS 4 arm64", - "ami_name": "salt-project/ci/photonos/4/arm64/20240509.1530", + "ami_name": "salt-project/ci/photonos/4/arm64/20240912.2136", "arch": "arm64", "cloudwatch-agent-available": "true", "instance_type": "m6g.large", @@ -130,9 +120,9 @@ "ssh_username": "root" }, "photonos-4": { - "ami": "ami-09b55d0bf3a1aa7e5", + "ami": "ami-056d988807f8b586d", "ami_description": "CI Image of PhotonOS 4 x86_64", - "ami_name": "salt-project/ci/photonos/4/x86_64/20240509.1530", + "ami_name": "salt-project/ci/photonos/4/x86_64/20240912.2136", "arch": "x86_64", "cloudwatch-agent-available": "true", "instance_type": "t3a.large", @@ -140,9 +130,9 @@ "ssh_username": "root" }, "photonos-5-arm64": { - "ami": "ami-09de4952bc9fc068a", + "ami": "ami-059f47b459d04544a", "ami_description": "CI Image of PhotonOS 5 arm64", - "ami_name": "salt-project/ci/photonos/5/arm64/20240509.1530", + "ami_name": "salt-project/ci/photonos/5/arm64/20240912.2136", "arch": "arm64", "cloudwatch-agent-available": "true", "instance_type": "m6g.large", @@ -150,9 +140,9 @@ "ssh_username": "root" }, "photonos-5": { - "ami": "ami-0c3375a583643fc77", + "ami": "ami-06424daf7c85ffff0", "ami_description": "CI Image of PhotonOS 5 x86_64", - "ami_name": "salt-project/ci/photonos/5/x86_64/20240509.1530", + "ami_name": "salt-project/ci/photonos/5/x86_64/20240912.2136", "arch": "x86_64", "cloudwatch-agent-available": "true", "instance_type": "t3a.large", @@ -160,9 +150,9 @@ "ssh_username": "root" }, "rockylinux-8-arm64": { - "ami": "ami-0662cc201cada14b8", + "ami": "ami-0a21b175629f1a793", "ami_description": "CI Image of RockyLinux 8 arm64", - "ami_name": "salt-project/ci/rockylinux/8/arm64/20240509.1530", + "ami_name": "salt-project/ci/rockylinux/8/arm64/20240912.2136", "arch": "arm64", "cloudwatch-agent-available": "true", "instance_type": "m6g.large", @@ -170,9 +160,9 @@ "ssh_username": "rocky" }, "rockylinux-8": { - "ami": "ami-071ca70a907d79e05", + "ami": "ami-01032695e18f0fe85", "ami_description": "CI Image of RockyLinux 8 x86_64", - "ami_name": "salt-project/ci/rockylinux/8/x86_64/20240509.1530", + "ami_name": "salt-project/ci/rockylinux/8/x86_64/20240912.2136", "arch": "x86_64", "cloudwatch-agent-available": "true", "instance_type": "t3a.large", @@ -180,9 +170,9 @@ "ssh_username": "rocky" }, "rockylinux-9-arm64": { - "ami": "ami-065842dfdf03a1a03", + "ami": "ami-0c9147ca5f07effc6", "ami_description": "CI Image of RockyLinux 9 arm64", - "ami_name": "salt-project/ci/rockylinux/9/arm64/20240509.1530", + "ami_name": "salt-project/ci/rockylinux/9/arm64/20240912.2136", "arch": "arm64", "cloudwatch-agent-available": "true", "instance_type": "m6g.large", @@ -190,9 +180,9 @@ "ssh_username": "rocky" }, "rockylinux-9": { - "ami": "ami-09f5d6df00e99ba16", + "ami": "ami-01a72f34d198efc4a", "ami_description": "CI Image of RockyLinux 9 x86_64", - "ami_name": "salt-project/ci/rockylinux/9/x86_64/20240509.1530", + "ami_name": "salt-project/ci/rockylinux/9/x86_64/20240912.2136", "arch": "x86_64", "cloudwatch-agent-available": "true", "instance_type": "t3a.large", @@ -200,9 +190,9 @@ "ssh_username": "rocky" }, "ubuntu-20.04-arm64": { - "ami": "ami-00171fa604b826054", + "ami": "ami-0bf8ea4c07a88d6c5", "ami_description": "CI Image of Ubuntu 20.04 arm64", - "ami_name": "salt-project/ci/ubuntu/20.04/arm64/20240509.1530", + "ami_name": "salt-project/ci/ubuntu/20.04/arm64/20240912.2136", "arch": "arm64", "cloudwatch-agent-available": "true", "instance_type": "m6g.large", @@ -210,9 +200,9 @@ "ssh_username": "ubuntu" }, "ubuntu-20.04": { - "ami": "ami-07ddfbdc489064022", + "ami": "ami-08a84f7455622c3d5", "ami_description": "CI Image of Ubuntu 20.04 x86_64", - "ami_name": "salt-project/ci/ubuntu/20.04/x86_64/20240509.1530", + "ami_name": "salt-project/ci/ubuntu/20.04/x86_64/20240912.2136", "arch": "x86_64", "cloudwatch-agent-available": "true", "instance_type": "t3a.large", @@ -220,9 +210,9 @@ "ssh_username": "ubuntu" }, "ubuntu-22.04-arm64": { - "ami": "ami-0e6b6fc1dd298e055", + "ami": "ami-0415a2d2279277d61", "ami_description": "CI Image of Ubuntu 22.04 arm64", - "ami_name": "salt-project/ci/ubuntu/22.04/arm64/20240509.1530", + "ami_name": "salt-project/ci/ubuntu/22.04/arm64/20240912.2136", "arch": "arm64", "cloudwatch-agent-available": "true", "instance_type": "m6g.large", @@ -230,9 +220,9 @@ "ssh_username": "ubuntu" }, "ubuntu-22.04": { - "ami": "ami-0736289579c0d01ba", + "ami": "ami-055513129ce06397c", "ami_description": "CI Image of Ubuntu 22.04 x86_64", - "ami_name": "salt-project/ci/ubuntu/22.04/x86_64/20240509.1530", + "ami_name": "salt-project/ci/ubuntu/22.04/x86_64/20240912.2136", "arch": "x86_64", "cloudwatch-agent-available": "true", "instance_type": "t3a.large", @@ -240,9 +230,9 @@ "ssh_username": "ubuntu" }, "ubuntu-24.04-arm64": { - "ami": "ami-015058823f69446b3", + "ami": "ami-035ef6d54ec25b0fa", "ami_description": "CI Image of Ubuntu 24.04 arm64", - "ami_name": "salt-project/ci/ubuntu/24.04/arm64/20240509.1530", + "ami_name": "salt-project/ci/ubuntu/24.04/arm64/20240912.2136", "arch": "arm64", "cloudwatch-agent-available": "true", "instance_type": "m6g.large", @@ -250,9 +240,9 @@ "ssh_username": "ubuntu" }, "ubuntu-24.04": { - "ami": "ami-0eb04152e7cafaaf9", + "ami": "ami-0a287b781a487ec65", "ami_description": "CI Image of Ubuntu 24.04 x86_64", - "ami_name": "salt-project/ci/ubuntu/24.04/x86_64/20240509.1530", + "ami_name": "salt-project/ci/ubuntu/24.04/x86_64/20240912.2136", "arch": "x86_64", "cloudwatch-agent-available": "true", "instance_type": "t3a.large", @@ -260,9 +250,9 @@ "ssh_username": "ubuntu" }, "windows-2016": { - "ami": "ami-06026cb4d83072df5", + "ami": "ami-030cdb60764141f56", "ami_description": "CI Image of Windows 2016 x86_64", - "ami_name": "salt-project/ci/windows/2016/x86_64/20240509.1530", + "ami_name": "salt-project/ci/windows/2016/x86_64/20240913.1756", "arch": "x86_64", "cloudwatch-agent-available": "true", "instance_type": "t3a.xlarge", @@ -270,9 +260,9 @@ "ssh_username": "Administrator" }, "windows-2019": { - "ami": "ami-095a9256ec0e8261c", + "ami": "ami-08f10b0d4914572de", "ami_description": "CI Image of Windows 2019 x86_64", - "ami_name": "salt-project/ci/windows/2019/x86_64/20240509.1530", + "ami_name": "salt-project/ci/windows/2019/x86_64/20240913.1756", "arch": "x86_64", "cloudwatch-agent-available": "true", "instance_type": "t3a.xlarge", @@ -280,9 +270,9 @@ "ssh_username": "Administrator" }, "windows-2022": { - "ami": "ami-0d295c0711e513c05", + "ami": "ami-07eda52ffbd76a4c6", "ami_description": "CI Image of Windows 2022 x86_64", - "ami_name": "salt-project/ci/windows/2022/x86_64/20240509.1530", + "ami_name": "salt-project/ci/windows/2022/x86_64/20240913.1756", "arch": "x86_64", "cloudwatch-agent-available": "true", "instance_type": "t3a.xlarge", diff --git a/cicd/shared-gh-workflows-context.yml b/cicd/shared-gh-workflows-context.yml index 68a5dd25541c..77e8151f7f2e 100644 --- a/cicd/shared-gh-workflows-context.yml +++ b/cicd/shared-gh-workflows-context.yml @@ -7,7 +7,6 @@ release_branches: mandatory_os_slugs: - rockylinux-9 - amazonlinux-2023-arm64 - - archlinux-lts - photonos-5-arm64 - macos-12 - ubuntu-24.04-arm64 diff --git a/doc/_incl/requisite_incl.rst b/doc/_incl/requisite_incl.rst index b478527d6b1b..f5723a952eaf 100644 --- a/doc/_incl/requisite_incl.rst +++ b/doc/_incl/requisite_incl.rst @@ -7,4 +7,4 @@ following the instructions in the The Salt Project community can help offer advice and help troubleshoot technical issues as you're learning about Salt. One of the best places to talk to the community is on the - `Salt Project Slack workspace `_. + `Salt Project Discord Community `_. diff --git a/doc/conf.py b/doc/conf.py index 16deb36b7003..30edb8b2511e 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -181,7 +181,7 @@ .. _`salt-users`: https://groups.google.com/forum/#!forum/salt-users .. _`salt-announce`: https://groups.google.com/forum/#!forum/salt-announce .. _`salt-packagers`: https://groups.google.com/forum/#!forum/salt-packagers -.. _`salt-slack`: https://via.vmw.com/salt-slack +.. _`salt-discord`: https://discord.com/invite/J7b7EscrAs .. |windownload| raw:: html

Python3 x86: `__ landing page * `SaltStack Security RSS Feed `__ -* `SaltStack Community Slack Workspace `__ +* `Salt Project Discord Community `__ diff --git a/doc/topics/development/conventions/formulas.rst b/doc/topics/development/conventions/formulas.rst index ad9033b2d100..7e069d41ec96 100644 --- a/doc/topics/development/conventions/formulas.rst +++ b/doc/topics/development/conventions/formulas.rst @@ -222,7 +222,7 @@ repository in your own account on GitHub and notify a SaltStack employee when it is ready. We will add you to the Contributors team on the `saltstack-formulas`_ organization and help you transfer the repository over. Ping a SaltStack employee on IRC (`#salt`_ on LiberaChat), join the -``#formulas`` channel on the `salt-slack`_ (bridged to ``#saltstack-formulas`` +``#formulas`` channel on the `salt-discord`_ (bridged to ``#saltstack-formulas`` on LiberaChat) or send an email to the `salt-users`_ mailing list. Note that IRC logs are available at http://ngxbot.nginx.org/logs/%23salt/ and archives for FreeNode (up to mid-June 2021) https://logbot-archive.s3.amazonaws.com/freenode/salt.gz diff --git a/doc/topics/development/conventions/release.rst b/doc/topics/development/conventions/release.rst index c08447e20078..1cf05c8ff1f0 100644 --- a/doc/topics/development/conventions/release.rst +++ b/doc/topics/development/conventions/release.rst @@ -46,7 +46,7 @@ example): #. Publish the docs. #. Create release at `github`_ #. Update win-repo-ng with new salt versions. -#. Announce release is live to irc, salt-users, salt-announce and release slack +#. Announce release is live to irc, salt-users, salt-announce and release discord community channel. @@ -79,7 +79,7 @@ for a bugfix release. #. Publish the docs. #. Create release at `github`_ #. Update win-repo-ng with new salt versions. -#. Announce release is live to irc, salt-users, salt-announce and release slack channel. +#. Announce release is live to irc, salt-users, salt-announce and release discord channel. .. _`github`: https://github.com/saltstack/salt/releases .. _`repo.saltproject.io`: https://repo.saltproject.io diff --git a/doc/topics/development/pull_requests.rst b/doc/topics/development/pull_requests.rst index 4b6ffee9135e..c1cb30a035c0 100644 --- a/doc/topics/development/pull_requests.rst +++ b/doc/topics/development/pull_requests.rst @@ -193,12 +193,21 @@ By default, PRs run a limited subset of the test suite against the following operating systems: * Linux: + - Latest ``Amazon Linux Arm64`` + - Latest ``Amazon Linux x86_64`` + - Latest ``Debian Linux Arm64`` + - Latest ``Debian Linux x86_64`` + - Latest ``Photon OS Arm64`` + - Latest ``Photon OS x86_64`` + - Latest ``Rocky Linux Arm64`` - Latest ``Rocky Linux x86_64`` - - Latest ``Amazon Linux aarch64`` - - Latest ``Ubuntu LTS arm64`` - - Latest ``Arch Linux x86_64`` -* Latest ``Windows Server x86_64`` -* Latest ``MacOS arm64`` + - Latest ``Ubuntu LTS Arm64`` + - Latest ``Ubuntu LTS x86_64`` +* Windows Server: + - Latest ``Windows Server x86_64`` +* macOS: + - Latest ``MacOS Arm64`` + - Latest ``MacOS x86_64`` Optional OS additions --------------------- diff --git a/doc/topics/tutorials/states_pt4.rst b/doc/topics/tutorials/states_pt4.rst index 96701b20af37..943f3079f114 100644 --- a/doc/topics/tutorials/states_pt4.rst +++ b/doc/topics/tutorials/states_pt4.rst @@ -211,7 +211,7 @@ can be found on GitHub in the `saltstack-formulas`_ collection of repositories. If you have any questions, suggestions, or just want to chat with other people who are using Salt, we have a very active community and we'd love to hear from you. One of the best places to talk to the community is on the -`Salt Project Slack workspace `_. +`Salt Project Discord Community `_. In addition, by continuing to the :ref:`Orchestrate Runner ` docs, you can learn about the powerful orchestration of which Salt is capable. diff --git a/pkg/debian/rules b/pkg/debian/rules index 40446c7b25f9..9ebc51b9422f 100755 --- a/pkg/debian/rules +++ b/pkg/debian/rules @@ -4,7 +4,7 @@ DH_VERBOSE = 1 .PHONY: override_dh_strip %: - dh $@ + dh $@ --with bash-completion,systemd # dh_auto_clean tries to invoke distutils causing failures. override_dh_auto_clean: diff --git a/pkg/debian/salt-api.preinst b/pkg/debian/salt-api.preinst index ddc7c9e0ec7b..c063108ea55e 100644 --- a/pkg/debian/salt-api.preinst +++ b/pkg/debian/salt-api.preinst @@ -22,7 +22,6 @@ case "$1" in else db_set salt-api/enabled enabled db_set salt-api/active active - fi ;; esac diff --git a/pkg/common/salt.bash b/pkg/debian/salt-common.bash-completion similarity index 63% rename from pkg/common/salt.bash rename to pkg/debian/salt-common.bash-completion index 35fe0695dbe8..aba866bc7958 100644 --- a/pkg/common/salt.bash +++ b/pkg/debian/salt-common.bash-completion @@ -5,86 +5,47 @@ # TODO: solve somehow completion for salt -G pythonversion:[tab] # (not sure what to do with lists) # TODO: --range[tab] -- how? +# TODO: -E --exsel[tab] -- how? # TODO: --compound[tab] -- how? # TODO: use history to extract some words, esp. if ${cur} is empty -# TODO: TEST EVERYTHING a lot +# TODO: TEST EVERYTING a lot +# TODO: cache results of some functions? where? how long? # TODO: is it ok to use '--timeout 2' ? _salt_get_grains(){ if [ "$1" = 'local' ] ; then - salt-call --log-level=error --out=txt -- grains.ls | sed 's/^.*\[//' | tr -d ",']" |sed 's:\([a-z0-9]\) :\1\: :g' + salt-call --out=txt -- grains.ls | sed 's/^.*\[//' | tr -d ",']" |sed 's:\([a-z0-9]\) :\1\: :g' else - salt '*' --timeout 2 --hide-timeout --log-level=error --out=txt -- grains.ls | sed 's/^.*\[//' | tr -d ",']" |sed 's:\([a-z0-9]\) :\1\: :g' + salt '*' --timeout 2 --out=txt -- grains.ls | sed 's/^.*\[//' | tr -d ",']" |sed 's:\([a-z0-9]\) :\1\: :g' fi } _salt_get_grain_values(){ if [ "$1" = 'local' ] ; then - salt-call --log-level=error --out=txt -- grains.item $1 |sed 's/^\S*:\s//' |grep -v '^\s*$' + salt-call --out=txt -- grains.item $1 |sed 's/^\S*:\s//' |grep -v '^\s*$' else - salt '*' --timeout 2 --hide-timeout --log-level=error --out=txt -- grains.item $1 |sed 's/^\S*:\s//' |grep -v '^\s*$' + salt '*' --timeout 2 --out=txt -- grains.item $1 |sed 's/^\S*:\s//' |grep -v '^\s*$' fi } -_salt_get_keys(){ - for type in $*; do - # remove header from data: - salt-key --no-color -l $type | tail -n+2 - done -} - -_salt_list_functions(){ - # salt-call: get all functions on this minion - # salt: get all functions on all minions - # sed: remove all array overhead and convert to newline separated list - # sort: chop out doubled entries, so overhead is minimal later during actual completion - if [ "$1" = 'local' ] ; then - salt-call --log-level=quiet --out=txt -- sys.list_functions \ - | sed "s/^.*\[//;s/[],']//g;s/ /\n/g" \ - | sort -u - else - salt '*' --timeout 2 --hide-timeout --log-level=quiet --out=txt -- sys.list_functions \ - | sed "s/^.*\[//;s/[],']//g;s/ /\n/g" \ - | sort -u - fi -} - -_salt_get_coms() { - CACHE_DIR="$HOME/.cache/salt-${1}-comp-cache_functions" - local _salt_cache_functions=${SALT_COMP_CACHE_FUNCTIONS:=$CACHE_DIR} - local _salt_cache_timeout=${SALT_COMP_CACHE_TIMEOUT:='last hour'} - - if [ ! -d "$(dirname ${_salt_cache_functions})" ]; then - mkdir -p "$(dirname ${_salt_cache_functions})" - fi - - # Regenerate cache if timed out - if [[ "$(stat --format=%Z ${_salt_cache_functions} 2>/dev/null)" -lt "$(date --date="${_salt_cache_timeout}" +%s)" ]]; then - _salt_list_functions $1 > "${_salt_cache_functions}" - fi - - # filter results, to only print the part to next dot (or end of function) - sed 's/^\('${cur}'\(\.\|[^.]*\)\)\?.*/\1/' "${_salt_cache_functions}" | sort -u -} _salt(){ - local cur prev opts _salt_grains _salt_coms pprev ppprev COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [ ${COMP_CWORD} -gt 2 ]; then - pprev="${COMP_WORDS[COMP_CWORD-2]}" + pprev="${COMP_WORDS[COMP_CWORD-2]}" fi if [ ${COMP_CWORD} -gt 3 ]; then - ppprev="${COMP_WORDS[COMP_CWORD-3]}" + ppprev="${COMP_WORDS[COMP_CWORD-3]}" fi opts="-h --help -d --doc --documentation --version --versions-report -c \ --config-dir= -v --verbose -t --timeout= -s --static -b --batch= \ --batch-size= -E --pcre -L --list -G --grain --grain-pcre -N \ - --nodegroup -R --range -C --compound -I --pillar \ + --nodegroup -R --range -C --compound -X --exsel -I --pillar \ --return= -a --auth= --eauth= --extended-auth= -T --make-token -S \ --ipcidr --out=pprint --out=yaml --out=overstatestage --out=json \ --out=raw --out=highstate --out=key --out=txt --no-color --out-indent= " @@ -98,7 +59,7 @@ _salt(){ case "${pprev}" in -G|--grain|--grain-pcre) if [ "${cur}" = ":" ]; then - COMPREPLY=($(compgen -W "`_salt_get_grain_values ${prev}`")) + COMPREPLY=($(compgen -W "`_salt_get_grain_values ${prev}`" )) return 0 fi ;; @@ -126,17 +87,17 @@ _salt(){ return 0 ;; salt) - COMPREPLY=($(compgen -W "\'*\' ${opts} $(_salt_get_keys acc)" -- ${cur})) + COMPREPLY=($(compgen -W "\'*\' ${opts} `salt-key --no-color -l acc`" -- ${cur})) return 0 ;; -E|--pcre) - COMPREPLY=($(compgen -W "$(_salt_get_keys acc)" -- ${cur})) + COMPREPLY=($(compgen -W "`salt-key --no-color -l acc`" -- ${cur})) return 0 ;; -G|--grain|--grain-pcre) COMPREPLY=($(compgen -W "$(_salt_get_grains)" -- ${cur})) return 0 - ;; + ;; -C|--compound) COMPREPLY=() # TODO: finish this one? how? return 0 @@ -149,18 +110,17 @@ _salt(){ COMPREPLY=($(compgen -W "1 2 3 4 5 6 7 8 9 10 15 20 30 40 50 60 70 80 90 100 120 150 200")) return 0 ;; + -X|--exsel) # TODO: finish this one? how? + return 0 + ;; -N|--nodegroup) - MASTER_CONFIG='/etc/salt/master' + MASTER_CONFIG='/etc/salt/master' COMPREPLY=($(compgen -W "`awk -F ':' 'BEGIN {print_line = 0}; /^nodegroups/ {print_line = 1;getline } print_line && /^ */ {print $1} /^[^ ]/ {print_line = 0}' <${MASTER_CONFIG}`" -- ${cur})) return 0 ;; esac - _salt_coms=$(_salt_get_coms remote) - - # If there are still dots in the suggestion, do not append space - grep "^${cur}.*\." "${_salt_coms}" &>/dev/null && compopt -o nospace - + _salt_coms="$(salt '*' --timeout 2 --out=txt -- sys.list_functions | sed 's/^.*\[//' | tr -d ",']" )" all="${opts} ${_salt_coms}" COMPREPLY=( $(compgen -W "${all}" -- ${cur}) ) @@ -202,15 +162,15 @@ _saltkey(){ case "${prev}" in -a|--accept) - COMPREPLY=($(compgen -W "$(_salt_get_keys un rej)" -- ${cur})) + COMPREPLY=($(compgen -W "$(salt-key -l un --no-color; salt-key -l rej --no-color)" -- ${cur})) return 0 ;; -r|--reject) - COMPREPLY=($(compgen -W "$(_salt_get_keys acc)" -- ${cur})) + COMPREPLY=($(compgen -W "$(salt-key -l acc --no-color)" -- ${cur})) return 0 ;; -d|--delete) - COMPREPLY=($(compgen -W "$(_salt_get_keys acc un rej)" -- ${cur})) + COMPREPLY=($(compgen -W "$(salt-key -l acc --no-color; salt-key -l un --no-color; salt-key -l rej --no-color)" -- ${cur})) return 0 ;; -c|--config) @@ -229,7 +189,7 @@ _saltkey(){ return 0 ;; -p|--print) - COMPREPLY=($(compgen -W "$(_salt_get_keys acc un rej)" -- ${cur})) + COMPREPLY=($(compgen -W "$(salt-key -l acc --no-color; salt-key -l un --no-color; salt-key -l rej --no-color)" -- ${cur})) return 0 ;; -l|--list) @@ -237,7 +197,7 @@ _saltkey(){ return 0 ;; --accept-all) - return 0 + return 0 ;; esac COMPREPLY=($(compgen -W "${opts} " -- ${cur})) @@ -276,26 +236,22 @@ _saltcall(){ case ${prev} in -m|--module-dirs) COMPREPLY=( $(compgen -d ${cur} )) + return 0 + ;; + -l|--log-level) + COMPREPLY=( $(compgen -W "info none garbage trace warning error debug" -- ${cur})) + return 0 + ;; + -g|grains) return 0 - ;; - -l|--log-level) - COMPREPLY=( $(compgen -W "info none garbage trace warning error debug" -- ${cur})) - return 0 - ;; - -g|grains) - return 0 - ;; - salt-call) + ;; + salt-call) COMPREPLY=($(compgen -W "${opts}" -- ${cur})) - return 0 - ;; + return 0 + ;; esac - _salt_coms=$(_salt_get_coms local) - - # If there are still dots in the suggestion, do not append space - grep "^${cur}.*\." "${_salt_coms}" &>/dev/null && compopt -o nospace - + _salt_coms="$(salt-call --out=txt -- sys.list_functions|sed 's/^.*\[//' | tr -d ",']" )" COMPREPLY=( $(compgen -W "${opts} ${_salt_coms}" -- ${cur} )) return 0 } @@ -311,7 +267,7 @@ _saltcp(){ opts="-t --timeout= -s --static -b --batch= --batch-size= \ -h --help --version --versions-report -c --config-dir= \ -E --pcre -L --list -G --grain --grain-pcre -N --nodegroup \ - -R --range -C --compound -I --pillar \ + -R --range -C --compound -X --exsel -I --pillar \ --out=pprint --out=yaml --out=overstatestage --out=json --out=raw \ --out=highstate --out=key --out=txt --no-color --out-indent= " if [[ "${cur}" == -* ]] ; then @@ -327,45 +283,46 @@ _saltcp(){ fi case ${prev} in - salt-cp) - COMPREPLY=($(compgen -W "${opts} $(_salt_get_keys acc)" -- ${cur})) - return 0 - ;; + salt-cp) + COMPREPLY=($(compgen -W "${opts} `salt-key -l acc --no-color`" -- ${cur})) + return 0 + ;; -t|--timeout) - # those numbers are just a hint + # those numbers are just a hint COMPREPLY=($(compgen -W "2 3 4 8 10 15 20 25 30 40 60 90 120 180 240 300" -- ${cur} )) + return 0 + ;; + -E|--pcre) + COMPREPLY=($(compgen -W "`salt-key -l acc --no-color`" -- ${cur})) return 0 - ;; - -E|--pcre) - COMPREPLY=($(compgen -W "$(_salt_get_keys acc)" -- ${cur})) - return 0 - ;; - -L|--list) - # IMPROVEMENTS ARE WELCOME - prefpart="${cur%,*}," - postpart=${cur##*,} - filt="^\($(echo ${cur}| sed 's:,:\\|:g')\)$" - helper=($(_salt_get_keys acc | grep -v "${filt}" | sed "s/^/${prefpart}/")) - COMPREPLY=($(compgen -W "${helper[*]}" -- ${cur})) - return 0 - ;; - -G|--grain|--grain-pcre) + ;; + -L|--list) + # IMPROVEMENTS ARE WELCOME + prefpart="${cur%,*}," + postpart=${cur##*,} + filt="^\($(echo ${cur}| sed 's:,:\\|:g')\)$" + helper=($(salt-key -l acc --no-color | grep -v "${filt}" | sed "s/^/${prefpart}/")) + COMPREPLY=($(compgen -W "${helper[*]}" -- ${cur})) + + return 0 + ;; + -G|--grain|--grain-pcre) COMPREPLY=($(compgen -W "$(_salt_get_grains)" -- ${cur})) return 0 - ;; - # FIXME - -R|--range) - # FIXME ?? - return 0 - ;; - -C|--compound) - # FIXME ?? - return 0 - ;; - -c|--config) - COMPREPLY=($(compgen -f -- ${cur})) - return 0 - ;; + ;; + # FIXME + -R|--range) + # FIXME ?? + return 0 + ;; + -C|--compound) + # FIXME ?? + return 0 + ;; + -c|--config) + COMPREPLY=($(compgen -f -- ${cur})) + return 0 + ;; esac # default is using opts: diff --git a/pkg/debian/salt-common.install b/pkg/debian/salt-common.install index 4f8dac552ece..63f1d5a1287a 100644 --- a/pkg/debian/salt-common.install +++ b/pkg/debian/salt-common.install @@ -1,9 +1,10 @@ +#! /usr/bin/dh-exec + pkg/common/logrotate/salt-common /etc/logrotate.d pkg/common/fish-completions/salt-cp.fish /usr/share/fish/vendor_completions.d pkg/common/fish-completions/salt-call.fish /usr/share/fish/vendor_completions.d pkg/common/fish-completions/salt-syndic.fish /usr/share/fish/vendor_completions.d pkg/common/fish-completions/salt_common.fish /usr/share/fish/vendor_completions.d -pkg/common/salt.bash /usr/share/bash-completions/completions/salt-common.bash pkg/common/fish-completions/salt-minion.fish /usr/share/fish/vendor_completions.d pkg/common/fish-completions/salt-key.fish /usr/share/fish/vendor_completions.d pkg/common/fish-completions/salt-master.fish /usr/share/fish/vendor_completions.d diff --git a/pkg/debian/salt-common.links b/pkg/debian/salt-common.links index ef1cd42e5dd1..cddd400ceeb0 100644 --- a/pkg/debian/salt-common.links +++ b/pkg/debian/salt-common.links @@ -1,2 +1,9 @@ +# permissions on /var/log/salt to permit adm group ownership +salt-common: non-standard-dir-perm + +# minor formatting error in table in man page +salt-common: manpage-has-errors-from-man + opt/saltstack/salt/salt-pip /usr/bin/salt-pip opt/saltstack/salt/salt-call /usr/bin/salt-call +usr/share/bash-completion/completions/salt-common usr/share/bash-completion/completions/salt-call diff --git a/pkg/debian/salt-master.links b/pkg/debian/salt-master.links index e6c0ef2446ad..77c8bdc67b29 100644 --- a/pkg/debian/salt-master.links +++ b/pkg/debian/salt-master.links @@ -4,3 +4,6 @@ opt/saltstack/salt/salt-cp /usr/bin/salt-cp opt/saltstack/salt/salt-key /usr/bin/salt-key opt/saltstack/salt/salt-run /usr/bin/salt-run opt/saltstack/salt/spm /usr/bin/spm +usr/share/bash-completion/completions/salt-common usr/share/bash-completion/completions/salt +usr/share/bash-completion/completions/salt-common usr/share/bash-completion/completions/salt-cp +usr/share/bash-completion/completions/salt-common usr/share/bash-completion/completions/salt-key diff --git a/pkg/debian/salt-master.preinst b/pkg/debian/salt-master.preinst index af978b8e508e..a96f9dd67678 100644 --- a/pkg/debian/salt-master.preinst +++ b/pkg/debian/salt-master.preinst @@ -39,7 +39,6 @@ case "$1" in else db_set salt-master/enabled enabled db_set salt-master/active active - fi ;; esac diff --git a/pkg/debian/salt-minion.preinst b/pkg/debian/salt-minion.preinst index 4a4cd949c642..51be48e0677f 100644 --- a/pkg/debian/salt-minion.preinst +++ b/pkg/debian/salt-minion.preinst @@ -24,7 +24,6 @@ case "$1" in else db_set salt-minion/enabled enabled db_set salt-minion/active active - fi ;; esac diff --git a/pkg/debian/salt-syndic.postinst b/pkg/debian/salt-syndic.postinst new file mode 100644 index 000000000000..071ba38e1859 --- /dev/null +++ b/pkg/debian/salt-syndic.postinst @@ -0,0 +1,37 @@ +#!/bin/sh + +. /usr/share/debconf/confmodule + +case "$1" in + configure) + db_get salt-syndic/user + if [ "$RET" != "root" ]; then + if [ ! -e "/var/log/salt/syndic" ]; then + touch /var/log/salt/syndic + chmod 640 /var/log/salt/syndic + fi + chown $RET:$RET /var/log/salt/syndic + fi + if command -v systemctl; then + db_get salt-syndic/active + RESLT=$(echo "$RET" | cut -d ' ' -f 1) + if [ "$RESLT" != 10 ]; then + systemctl daemon-reload + if [ "$RESLT" = "active" ]; then + systemctl restart salt-syndic + fi + db_get salt-syndic/enabled + RESLT=$(echo "$RET" | cut -d ' ' -f 1) + if [ "$RESLT" = "disabled" ]; then + systemctl disable salt-syndic + else + systemctl enable salt-syndic + fi + else + systemctl daemon-reload + systemctl restart salt-syndic + systemctl enable salt-syndic + fi + fi + ;; +esac diff --git a/pkg/debian/salt-syndic.preinst b/pkg/debian/salt-syndic.preinst new file mode 100644 index 000000000000..da43d779163c --- /dev/null +++ b/pkg/debian/salt-syndic.preinst @@ -0,0 +1,27 @@ +#!/bin/sh + +. /usr/share/debconf/confmodule + +case "$1" in + upgrade) + [ -z "$SALT_HOME" ] && SALT_HOME=/opt/saltstack/salt + [ -z "$SALT_USER" ] && SALT_USER=salt + [ -z "$SALT_NAME" ] && SALT_NAME="Salt" + [ -z "$SALT_GROUP" ] && SALT_GROUP=salt + + # Reset permissions to fix previous installs + CUR_USER=$(ls -dl /run/salt-syndic.pid | cut -d ' ' -f 3) + CUR_GROUP=$(ls -dl /run/salt-syndic.pid | cut -d ' ' -f 4) + db_set salt-syndic/user $CUR_USER + chown -R $CUR_USER:$CUR_GROUP /var/log/salt/syndic + if command -v systemctl; then + SM_ENABLED=$(systemctl show -p UnitFileState salt-syndic | cut -d '=' -f 2) + db_set salt-syndic/enabled $SM_ENABLED + SM_ACTIVE=$(systemctl is-active salt-syndic) + db_set salt-syndic/active $SM_ACTIVE + else + db_set salt-syndic/enabled enabled + db_set salt-syndic/active active + fi + ;; +esac diff --git a/pkg/debian/salt-syndic.templates b/pkg/debian/salt-syndic.templates new file mode 100644 index 000000000000..c27859e0a24f --- /dev/null +++ b/pkg/debian/salt-syndic.templates @@ -0,0 +1,17 @@ +Template: salt-syndic/user +Type: string +Default: salt +Description: User for salt-syndic + User to run the salt-syndic process as + +Template: salt-syndic/enabled +Type: string +Default: enabled +Description: Systemd enable state for salt-syndic + default enable state for salt-syndic systemd state + +Template: salt-syndic/active +Type: string +Default: active +Description: Systemd active state for salt-syndic + default active state for salt-syndic systemd state diff --git a/pkg/rpm/salt.bash b/pkg/rpm/salt.bash deleted file mode 120000 index 98ee56c40cd6..000000000000 --- a/pkg/rpm/salt.bash +++ /dev/null @@ -1 +0,0 @@ -../common/salt.bash \ No newline at end of file diff --git a/pkg/rpm/salt.bash b/pkg/rpm/salt.bash new file mode 100644 index 000000000000..35fe0695dbe8 --- /dev/null +++ b/pkg/rpm/salt.bash @@ -0,0 +1,375 @@ +# written by David Pravec +# - feel free to /msg alekibango on IRC if you want to talk about this file + +# TODO: check if --config|-c was used and use configured config file for queries +# TODO: solve somehow completion for salt -G pythonversion:[tab] +# (not sure what to do with lists) +# TODO: --range[tab] -- how? +# TODO: --compound[tab] -- how? +# TODO: use history to extract some words, esp. if ${cur} is empty +# TODO: TEST EVERYTHING a lot +# TODO: is it ok to use '--timeout 2' ? + + +_salt_get_grains(){ + if [ "$1" = 'local' ] ; then + salt-call --log-level=error --out=txt -- grains.ls | sed 's/^.*\[//' | tr -d ",']" |sed 's:\([a-z0-9]\) :\1\: :g' + else + salt '*' --timeout 2 --hide-timeout --log-level=error --out=txt -- grains.ls | sed 's/^.*\[//' | tr -d ",']" |sed 's:\([a-z0-9]\) :\1\: :g' + fi +} + +_salt_get_grain_values(){ + if [ "$1" = 'local' ] ; then + salt-call --log-level=error --out=txt -- grains.item $1 |sed 's/^\S*:\s//' |grep -v '^\s*$' + else + salt '*' --timeout 2 --hide-timeout --log-level=error --out=txt -- grains.item $1 |sed 's/^\S*:\s//' |grep -v '^\s*$' + fi +} + +_salt_get_keys(){ + for type in $*; do + # remove header from data: + salt-key --no-color -l $type | tail -n+2 + done +} + +_salt_list_functions(){ + # salt-call: get all functions on this minion + # salt: get all functions on all minions + # sed: remove all array overhead and convert to newline separated list + # sort: chop out doubled entries, so overhead is minimal later during actual completion + if [ "$1" = 'local' ] ; then + salt-call --log-level=quiet --out=txt -- sys.list_functions \ + | sed "s/^.*\[//;s/[],']//g;s/ /\n/g" \ + | sort -u + else + salt '*' --timeout 2 --hide-timeout --log-level=quiet --out=txt -- sys.list_functions \ + | sed "s/^.*\[//;s/[],']//g;s/ /\n/g" \ + | sort -u + fi +} + +_salt_get_coms() { + CACHE_DIR="$HOME/.cache/salt-${1}-comp-cache_functions" + local _salt_cache_functions=${SALT_COMP_CACHE_FUNCTIONS:=$CACHE_DIR} + local _salt_cache_timeout=${SALT_COMP_CACHE_TIMEOUT:='last hour'} + + if [ ! -d "$(dirname ${_salt_cache_functions})" ]; then + mkdir -p "$(dirname ${_salt_cache_functions})" + fi + + # Regenerate cache if timed out + if [[ "$(stat --format=%Z ${_salt_cache_functions} 2>/dev/null)" -lt "$(date --date="${_salt_cache_timeout}" +%s)" ]]; then + _salt_list_functions $1 > "${_salt_cache_functions}" + fi + + # filter results, to only print the part to next dot (or end of function) + sed 's/^\('${cur}'\(\.\|[^.]*\)\)\?.*/\1/' "${_salt_cache_functions}" | sort -u +} + +_salt(){ + + local cur prev opts _salt_grains _salt_coms pprev ppprev + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + if [ ${COMP_CWORD} -gt 2 ]; then + pprev="${COMP_WORDS[COMP_CWORD-2]}" + fi + if [ ${COMP_CWORD} -gt 3 ]; then + ppprev="${COMP_WORDS[COMP_CWORD-3]}" + fi + + opts="-h --help -d --doc --documentation --version --versions-report -c \ + --config-dir= -v --verbose -t --timeout= -s --static -b --batch= \ + --batch-size= -E --pcre -L --list -G --grain --grain-pcre -N \ + --nodegroup -R --range -C --compound -I --pillar \ + --return= -a --auth= --eauth= --extended-auth= -T --make-token -S \ + --ipcidr --out=pprint --out=yaml --out=overstatestage --out=json \ + --out=raw --out=highstate --out=key --out=txt --no-color --out-indent= " + + if [[ "${cur}" == -* ]] ; then + COMPREPLY=($(compgen -W "${opts}" -- ${cur})) + return 0 + fi + + # 2 special cases for filling up grain values + case "${pprev}" in + -G|--grain|--grain-pcre) + if [ "${cur}" = ":" ]; then + COMPREPLY=($(compgen -W "`_salt_get_grain_values ${prev}`")) + return 0 + fi + ;; + esac + case "${ppprev}" in + -G|--grain|--grain-pcre) + if [ "${prev}" = ":" ]; then + COMPREPLY=( $(compgen -W "`_salt_get_grain_values ${pprev}`" -- ${cur}) ) + return 0 + fi + ;; + esac + + if [ "${cur}" = "=" ] && [[ "${prev}" == --* ]]; then + cur="" + fi + if [ "${prev}" = "=" ] && [[ "${pprev}" == --* ]]; then + prev="${pprev}" + fi + + case "${prev}" in + + -c|--config) + COMPREPLY=($(compgen -f -- ${cur})) + return 0 + ;; + salt) + COMPREPLY=($(compgen -W "\'*\' ${opts} $(_salt_get_keys acc)" -- ${cur})) + return 0 + ;; + -E|--pcre) + COMPREPLY=($(compgen -W "$(_salt_get_keys acc)" -- ${cur})) + return 0 + ;; + -G|--grain|--grain-pcre) + COMPREPLY=($(compgen -W "$(_salt_get_grains)" -- ${cur})) + return 0 + ;; + -C|--compound) + COMPREPLY=() # TODO: finish this one? how? + return 0 + ;; + -t|--timeout) + COMPREPLY=($( compgen -W "1 2 3 4 5 6 7 8 9 10 15 20 30 40 60 90 120 180" -- ${cur})) + return 0 + ;; + -b|--batch|--batch-size) + COMPREPLY=($(compgen -W "1 2 3 4 5 6 7 8 9 10 15 20 30 40 50 60 70 80 90 100 120 150 200")) + return 0 + ;; + -N|--nodegroup) + MASTER_CONFIG='/etc/salt/master' + COMPREPLY=($(compgen -W "`awk -F ':' 'BEGIN {print_line = 0}; /^nodegroups/ {print_line = 1;getline } print_line && /^ */ {print $1} /^[^ ]/ {print_line = 0}' <${MASTER_CONFIG}`" -- ${cur})) + return 0 + ;; + esac + + _salt_coms=$(_salt_get_coms remote) + + # If there are still dots in the suggestion, do not append space + grep "^${cur}.*\." "${_salt_coms}" &>/dev/null && compopt -o nospace + + all="${opts} ${_salt_coms}" + COMPREPLY=( $(compgen -W "${all}" -- ${cur}) ) + + return 0 +} + +complete -F _salt salt + + +_saltkey(){ + local cur prev opts prev pprev + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + opts="-c --config-dir= -h --help --version --versions-report -q --quiet \ + -y --yes --gen-keys= --gen-keys-dir= --keysize= --key-logfile= \ + -l --list= -L --list-all -a --accept= -A --accept-all \ + -r --reject= -R --reject-all -p --print= -P --print-all \ + -d --delete= -D --delete-all -f --finger= -F --finger-all \ + --out=pprint --out=yaml --out=overstatestage --out=json --out=raw \ + --out=highstate --out=key --out=txt --no-color --out-indent= " + if [ ${COMP_CWORD} -gt 2 ]; then + pprev="${COMP_WORDS[COMP_CWORD-2]}" + fi + if [ ${COMP_CWORD} -gt 3 ]; then + ppprev="${COMP_WORDS[COMP_CWORD-3]}" + fi + if [[ "${cur}" == -* ]] ; then + COMPREPLY=($(compgen -W "${opts}" -- ${cur})) + return 0 + fi + + if [ "${cur}" = "=" ] && [[ "${prev}" == --* ]]; then + cur="" + fi + if [ "${prev}" = "=" ] && [[ "${pprev}" == --* ]]; then + prev="${pprev}" + fi + + case "${prev}" in + -a|--accept) + COMPREPLY=($(compgen -W "$(_salt_get_keys un rej)" -- ${cur})) + return 0 + ;; + -r|--reject) + COMPREPLY=($(compgen -W "$(_salt_get_keys acc)" -- ${cur})) + return 0 + ;; + -d|--delete) + COMPREPLY=($(compgen -W "$(_salt_get_keys acc un rej)" -- ${cur})) + return 0 + ;; + -c|--config) + COMPREPLY=($(compgen -f -- ${cur})) + return 0 + ;; + --keysize) + COMPREPLY=($(compgen -W "2048 3072 4096 5120 6144" -- ${cur})) + return 0 + ;; + --gen-keys) + return 0 + ;; + --gen-keys-dir) + COMPREPLY=($(compgen -d -- ${cur})) + return 0 + ;; + -p|--print) + COMPREPLY=($(compgen -W "$(_salt_get_keys acc un rej)" -- ${cur})) + return 0 + ;; + -l|--list) + COMPREPLY=($(compgen -W "pre un acc accepted unaccepted rej rejected all" -- ${cur})) + return 0 + ;; + --accept-all) + return 0 + ;; + esac + COMPREPLY=($(compgen -W "${opts} " -- ${cur})) + return 0 +} + +complete -F _saltkey salt-key + +_saltcall(){ + local cur prev opts _salt_coms pprev ppprev + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + opts="-h --help -d --doc --documentation --version --versions-report \ + -m --module-dirs= -g --grains --return= --local -c --config-dir= -l --log-level= \ + --out=pprint --out=yaml --out=overstatestage --out=json --out=raw \ + --out=highstate --out=key --out=txt --no-color --out-indent= " + if [ ${COMP_CWORD} -gt 2 ]; then + pprev="${COMP_WORDS[COMP_CWORD-2]}" + fi + if [ ${COMP_CWORD} -gt 3 ]; then + ppprev="${COMP_WORDS[COMP_CWORD-3]}" + fi + if [[ "${cur}" == -* ]] ; then + COMPREPLY=($(compgen -W "${opts}" -- ${cur})) + return 0 + fi + + if [ "${cur}" = "=" ] && [[ ${prev} == --* ]]; then + cur="" + fi + if [ "${prev}" = "=" ] && [[ ${pprev} == --* ]]; then + prev="${pprev}" + fi + + case ${prev} in + -m|--module-dirs) + COMPREPLY=( $(compgen -d ${cur} )) + return 0 + ;; + -l|--log-level) + COMPREPLY=( $(compgen -W "info none garbage trace warning error debug" -- ${cur})) + return 0 + ;; + -g|grains) + return 0 + ;; + salt-call) + COMPREPLY=($(compgen -W "${opts}" -- ${cur})) + return 0 + ;; + esac + + _salt_coms=$(_salt_get_coms local) + + # If there are still dots in the suggestion, do not append space + grep "^${cur}.*\." "${_salt_coms}" &>/dev/null && compopt -o nospace + + COMPREPLY=( $(compgen -W "${opts} ${_salt_coms}" -- ${cur} )) + return 0 +} + +complete -F _saltcall salt-call + + +_saltcp(){ + local cur prev opts target prefpart postpart helper filt pprev ppprev + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + opts="-t --timeout= -s --static -b --batch= --batch-size= \ + -h --help --version --versions-report -c --config-dir= \ + -E --pcre -L --list -G --grain --grain-pcre -N --nodegroup \ + -R --range -C --compound -I --pillar \ + --out=pprint --out=yaml --out=overstatestage --out=json --out=raw \ + --out=highstate --out=key --out=txt --no-color --out-indent= " + if [[ "${cur}" == -* ]] ; then + COMPREPLY=($(compgen -W "${opts}" -- ${cur})) + return 0 + fi + + if [ "${cur}" = "=" ] && [[ "${prev}" == --* ]]; then + cur="" + fi + if [ "${prev}" = "=" ] && [[ "${pprev}" == --* ]]; then + prev=${pprev} + fi + + case ${prev} in + salt-cp) + COMPREPLY=($(compgen -W "${opts} $(_salt_get_keys acc)" -- ${cur})) + return 0 + ;; + -t|--timeout) + # those numbers are just a hint + COMPREPLY=($(compgen -W "2 3 4 8 10 15 20 25 30 40 60 90 120 180 240 300" -- ${cur} )) + return 0 + ;; + -E|--pcre) + COMPREPLY=($(compgen -W "$(_salt_get_keys acc)" -- ${cur})) + return 0 + ;; + -L|--list) + # IMPROVEMENTS ARE WELCOME + prefpart="${cur%,*}," + postpart=${cur##*,} + filt="^\($(echo ${cur}| sed 's:,:\\|:g')\)$" + helper=($(_salt_get_keys acc | grep -v "${filt}" | sed "s/^/${prefpart}/")) + COMPREPLY=($(compgen -W "${helper[*]}" -- ${cur})) + return 0 + ;; + -G|--grain|--grain-pcre) + COMPREPLY=($(compgen -W "$(_salt_get_grains)" -- ${cur})) + return 0 + ;; + # FIXME + -R|--range) + # FIXME ?? + return 0 + ;; + -C|--compound) + # FIXME ?? + return 0 + ;; + -c|--config) + COMPREPLY=($(compgen -f -- ${cur})) + return 0 + ;; + esac + + # default is using opts: + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) +} + +complete -F _saltcp salt-cp diff --git a/pkg/rpm/salt.spec b/pkg/rpm/salt.spec index 7c1aef068181..fbac2262a50e 100644 --- a/pkg/rpm/salt.spec +++ b/pkg/rpm/salt.spec @@ -283,7 +283,7 @@ install -p -m 0644 %{_salt_src}/pkg/common/logrotate/salt-common %{buildroot}%{_ # Bash completion mkdir -p %{buildroot}%{_sysconfdir}/bash_completion.d/ -install -p -m 0644 %{_salt_src}/pkg/common/salt.bash %{buildroot}%{_sysconfdir}/bash_completion.d/salt.bash +install -p -m 0644 %{_salt_src}/pkg/rpm/salt.bash %{buildroot}%{_sysconfdir}/bash_completion.d/salt.bash # Fish completion (TBD remove -v) mkdir -p %{buildroot}%{fish_dir} @@ -441,6 +441,15 @@ if [ $1 -gt 1 ] ; then %global _MS_CUR_GROUP %{_MS_LCUR_GROUP} fi +%pre syndic +if [ $1 -gt 1 ] ; then + # Reset permissions to match previous installs - performing upgrade + _MS_LCUR_USER=$(ls -dl /run/salt/master | cut -d ' ' -f 3) + _MS_LCUR_GROUP=$(ls -dl /run/salt/master | cut -d ' ' -f 4) + %global _MS_CUR_USER %{_MS_LCUR_USER} + %global _MS_CUR_GROUP %{_MS_LCUR_GROUP} +fi + %pre minion if [ $1 -gt 1 ] ; then # Reset permissions to match previous installs - performing upgrade @@ -463,6 +472,14 @@ if [ $1 -eq 0 ] ; then /bin/systemctl stop salt-syndic.service > /dev/null 2>&1 || : fi +%preun syndic +# %%systemd_preun salt-syndic.service +if [ $1 -eq 0 ] ; then + # Package removal, not upgrade + /bin/systemctl --no-reload disable salt-syndic.service > /dev/null 2>&1 || : + /bin/systemctl stop salt-syndic.service > /dev/null 2>&1 || : +fi + %preun minion # %%systemd_preun salt-minion.service if [ $1 -eq 0 ] ; then @@ -471,7 +488,6 @@ if [ $1 -eq 0 ] ; then /bin/systemctl stop salt-minion.service > /dev/null 2>&1 || : fi - %preun api # %%systemd_preun salt-api.service if [ $1 -eq 0 ] ; then @@ -602,6 +618,19 @@ else 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 api if [ ! -e "/var/log/salt/api" ]; then touch /var/log/salt/api diff --git a/salt/_logging/impl.py b/salt/_logging/impl.py index 1d5927319d80..321ccf794b5b 100644 --- a/salt/_logging/impl.py +++ b/salt/_logging/impl.py @@ -158,6 +158,9 @@ def set_log_record_factory(factory): class SaltLoggingClass(LOGGING_LOGGER_CLASS, metaclass=LoggingMixinMeta): + + ONCECACHE = set() + def __new__(cls, *args): """ We override `__new__` in our logging logger class in order to provide @@ -234,7 +237,13 @@ def _log( stack_info=False, stacklevel=1, exc_info_on_loglevel=None, + once=False, ): + if once: + if str(args) in self.ONCECACHE: + return + self.ONCECACHE.add(str(args)) + if extra is None: extra = {} @@ -270,6 +279,7 @@ def _log( exc_info_on_loglevel ) ) + # XXX: extra is never None if extra is None: extra = {"exc_info_on_loglevel": exc_info_on_loglevel} else: diff --git a/salt/config/__init__.py b/salt/config/__init__.py index 7cf0261a0177..1e44215f6bcb 100644 --- a/salt/config/__init__.py +++ b/salt/config/__init__.py @@ -2513,7 +2513,7 @@ def syndic_config( ), ) ), - "user": opts.get("syndic_user", opts["user"]), + "user": opts.get("syndic_user", master_opts["user"]), "sock_dir": os.path.join( opts["cachedir"], opts.get("syndic_sock_dir", opts["sock_dir"]) ), @@ -2521,6 +2521,7 @@ def syndic_config( "cachedir": master_opts["cachedir"], } opts.update(syndic_opts) + # Prepend root_dir to other paths prepend_root_dirs = [ "pki_dir", diff --git a/salt/fileserver/__init__.py b/salt/fileserver/__init__.py index fe4b3b8e4968..b71af97b12b9 100644 --- a/salt/fileserver/__init__.py +++ b/salt/fileserver/__init__.py @@ -11,7 +11,6 @@ from collections.abc import Sequence import salt.loader -import salt.utils.data import salt.utils.files import salt.utils.path import salt.utils.url @@ -147,13 +146,7 @@ def check_file_list_cache(opts, form, list_cache, w_lock): opts.get("fileserver_list_cache_time", 20), list_cache, ) - return ( - salt.utils.data.decode( - salt.payload.load(fp_).get(form, []) - ), - False, - False, - ) + return salt.payload.load(fp_).get(form, []), False, False elif _lock_cache(w_lock): # Set the w_lock and go refresh_cache = True @@ -189,7 +182,7 @@ def check_env_cache(opts, env_cache): try: with salt.utils.files.fopen(env_cache, "rb") as fp_: log.trace("Returning env cache data from %s", env_cache) - return salt.utils.data.decode(salt.payload.load(fp_)) + return salt.payload.load(fp_) except OSError: pass return None diff --git a/salt/fileserver/roots.py b/salt/fileserver/roots.py index e81f37dcf029..cb27396b9790 100644 --- a/salt/fileserver/roots.py +++ b/salt/fileserver/roots.py @@ -325,7 +325,7 @@ def file_hash(load, fnd): def _file_lists(load, form): """ - Return a dict containing the file lists for files, dirs, emtydirs and symlinks + Return a dict containing the file lists for files, dirs, empty dirs and symlinks """ if "env" in load: # "env" is not supported; Use "saltenv". diff --git a/salt/grains/core.py b/salt/grains/core.py index af159702fe6a..2b37182379e6 100644 --- a/salt/grains/core.py +++ b/salt/grains/core.py @@ -1276,6 +1276,7 @@ def _virtual(osdata): "cannot execute it. Grains output might not be " "accurate.", command, + once=True, ) return grains diff --git a/salt/grains/opts.py b/salt/grains/opts.py index c014f484bcbf..3d63d41b7e9f 100644 --- a/salt/grains/opts.py +++ b/salt/grains/opts.py @@ -11,5 +11,5 @@ def opts(): if __opts__.get("grain_opts", False) or ( isinstance(__pillar__, dict) and __pillar__.get("grain_opts", False) ): - return __opts__ + return {"opts": __opts__} return {} diff --git a/salt/loader/context.py b/salt/loader/context.py index 88a6472a8f3c..38d0093a8baf 100644 --- a/salt/loader/context.py +++ b/salt/loader/context.py @@ -43,6 +43,9 @@ def __init__(self, name, loader_context, default=None): self.loader_context = loader_context self.default = default + def with_default(self, default): + return NamedLoaderContext(self.name, self.loader_context, default=default) + def loader(self): """ The LazyLoader in the current context. This will return None if there @@ -68,10 +71,12 @@ def value(self): loader = self.loader() if loader is None: return self.default - if self.name == "__context__": - return loader.pack[self.name] if self.name == loader.pack_self: return loader + elif self.name == "__context__": + return loader.pack[self.name] + elif self.name == "__opts__": + return loader.pack[self.name] try: return loader.pack[self.name] except KeyError: diff --git a/salt/loader/dunder.py b/salt/loader/dunder.py index d3027098b5ae..3b198b1497f8 100644 --- a/salt/loader/dunder.py +++ b/salt/loader/dunder.py @@ -8,3 +8,7 @@ __file_client__ = loader_context.named_context("__file_client__", default=None) +__opts__ = loader_context.named_context("__opts__") +__context__ = loader_context.named_context("__context__") +__pillar__ = loader_context.named_context("__pillar__") +__grains__ = loader_context.named_context("__grains__") diff --git a/salt/master.py b/salt/master.py index b7fe667ac2d6..f39e2c0a8027 100644 --- a/salt/master.py +++ b/salt/master.py @@ -1108,8 +1108,8 @@ def __init__(self, opts, mkey, key, req_channels, **kwargs): Create a salt master worker process :param dict opts: The salt options - :param dict mkey: The user running the salt master and the AES key - :param dict key: The user running the salt master and the RSA key + :param dict mkey: The user running the salt master and the RSA key + :param dict key: The user running the salt master and the AES key :rtype: MWorker :return: Master worker diff --git a/salt/minion.py b/salt/minion.py index 9a6484d91225..6cfca07d29ca 100644 --- a/salt/minion.py +++ b/salt/minion.py @@ -280,6 +280,7 @@ def get_proc_dir(cachedir, **kwargs): made. Same applies if the directory is already owned by this gid. Must be int. Works only on unix/unix like systems. """ + # pylint: disable=logging-fstring-interpolation fn_ = os.path.join(cachedir, "proc") mode = kwargs.pop("mode", None) @@ -305,11 +306,13 @@ def get_proc_dir(cachedir, **kwargs): uid = kwargs.pop("uid", -1) gid = kwargs.pop("gid", -1) + # pylint: disable=logging-fstring-interpolation # if uid and gid are both -1 then go ahead with # no changes at all if (d_stat.st_uid != uid or d_stat.st_gid != gid) and [ i for i in (uid, gid) if i != -1 ]: + # pylint: disable=logging-fstring-interpolation os.chown(fn_, uid, gid) return fn_ diff --git a/salt/modules/cp.py b/salt/modules/cp.py index b698398d42ef..b6b43775ff6f 100644 --- a/salt/modules/cp.py +++ b/salt/modules/cp.py @@ -21,7 +21,13 @@ import salt.utils.templates import salt.utils.url from salt.exceptions import CommandExecutionError -from salt.loader.dunder import __file_client__ +from salt.loader.dunder import ( + __context__, + __file_client__, + __grains__, + __opts__, + __pillar__, +) log = logging.getLogger(__name__) @@ -168,7 +174,7 @@ def _client(): """ if __file_client__: return __file_client__.value() - return salt.fileclient.get_file_client(__opts__) + return salt.fileclient.get_file_client(__opts__.value()) def _render_filenames(path, dest, saltenv, template, **kw): diff --git a/salt/modules/jinja.py b/salt/modules/jinja.py index 86187c29f2bb..db8082e676fc 100644 --- a/salt/modules/jinja.py +++ b/salt/modules/jinja.py @@ -100,7 +100,7 @@ def import_json(path): .. code-block:: bash - salt myminion jinja.import_JSON myformula/foo.json + salt myminion jinja.import_json myformula/foo.json """ tmplstr = textwrap.dedent( """\ diff --git a/salt/modules/system.py b/salt/modules/system.py index c9e3db3f7b55..0765c9506f78 100644 --- a/salt/modules/system.py +++ b/salt/modules/system.py @@ -636,7 +636,7 @@ def get_computer_name(): .. code-block:: bash - salt '*' network.get_hostname + salt '*' system.get_computer_name """ return __salt__["network.get_hostname"]() diff --git a/salt/state.py b/salt/state.py index 66cc8ec88c6d..531c7801bf01 100644 --- a/salt/state.py +++ b/salt/state.py @@ -52,7 +52,7 @@ from salt.serializers.msgpack import deserialize as msgpack_deserialize from salt.serializers.msgpack import serialize as msgpack_serialize from salt.template import compile_template, compile_template_str -from salt.utils.odict import DefaultOrderedDict, OrderedDict +from salt.utils.odict import DefaultOrderedDict, HashableOrderedDict log = logging.getLogger(__name__) @@ -129,11 +129,6 @@ ).union(STATE_RUNTIME_KEYWORDS) -class HashableOrderedDict(OrderedDict): - def __hash__(self): - return id(self) - - def split_low_tag(tag): """ Take a low tag and split it back into the low dict that it came from diff --git a/salt/states/file.py b/salt/states/file.py index 5b2b1531bca3..ae5828a57212 100644 --- a/salt/states/file.py +++ b/salt/states/file.py @@ -282,6 +282,7 @@ def run(): import itertools import logging import os +import pathlib import posixpath import re import shutil @@ -557,7 +558,26 @@ def process_symlinks(filenames, symlinks): managed_directories.add(mdest) keep.add(mdest) - return managed_files, managed_directories, managed_symlinks, keep + # Sets are randomly ordered. We need to use a list so we can make sure + # symlinks are always at the end. This is necessary because the file must + # exist before we can create a symlink to it. See issue: + # https://github.com/saltstack/salt/issues/64630 + new_managed_files = list(managed_files) + # Now let's move all the symlinks to the end + for link_src_relpath, _ in managed_symlinks: + for file_dest, file_src in managed_files: + # We need to convert relpath to fullpath. We're using pathlib to + # be platform-agnostic + symlink_full_path = pathlib.Path(f"{name}{os.sep}{link_src_relpath}") + file_dest_full_path = pathlib.Path(file_dest) + if symlink_full_path == file_dest_full_path: + new_managed_files.append( + new_managed_files.pop( + new_managed_files.index((file_dest, file_src)) + ) + ) + + return new_managed_files, managed_directories, managed_symlinks, keep def _gen_keep_files(name, require, walk_d=None): @@ -4554,18 +4574,26 @@ def recurse( or immediate subdirectories keep_symlinks - Keep symlinks when copying from the source. This option will cause - the copy operation to terminate at the symlink. If desire behavior - similar to rsync, then set this to True. This option is not taken - in account if ``fileserver_followsymlinks`` is set to False. + + Determines how symbolic links (symlinks) are handled during the copying + process. When set to ``True``, the copy operation will copy the symlink + itself, rather than the file or directory it points to. When set to + ``False``, the operation will follow the symlink and copy the target + file or directory. If you want behavior similar to rsync, set this + option to ``True``. + + However, if the ``fileserver_followsymlinks`` option is set to ``False``, + the ``keep_symlinks`` setting will be ignored, and symlinks will not be + copied at all. force_symlinks - Force symlink creation. This option will force the symlink creation. - If a file or directory is obstructing symlink creation it will be - recursively removed so that symlink creation can proceed. This - option is usually not needed except in special circumstances. This - option is not taken in account if ``fileserver_followsymlinks`` is - set to False. + + Controls the creation of symlinks when using ``keep_symlinks``. When set + to ``True``, it forces the creation of symlinks by removing any existing + files or directories that might be obstructing their creation. This + removal is done recursively if a directory is blocking the symlink. This + option is only used when ``keep_symlinks`` is passed and is ignored if + ``fileserver_followsymlinks`` is set to ``False``. win_owner The owner of the symlink and directories if ``makedirs`` is True. If diff --git a/salt/states/pkg.py b/salt/states/pkg.py index da31436376b9..c579927961d7 100644 --- a/salt/states/pkg.py +++ b/salt/states/pkg.py @@ -2964,7 +2964,7 @@ def _uninstall( try: pkg_params = __salt__["pkg_resource.parse_targets"]( - name, pkgs, normalize=normalize + name, pkgs, normalize=normalize, version=version, **kwargs )[0] except MinionError as exc: return { @@ -3031,7 +3031,7 @@ def _uninstall( new = __salt__["pkg.list_pkgs"](versions_as_list=True, **kwargs) failed = [] for param in pkg_params: - if __grains__["os_family"] in ["Suse", "RedHat"]: + if __grains__["os_family"] in ["Suse", "RedHat", "Windows"]: # Check if the package version set to be removed is actually removed: if param in new and not pkg_params[param]: failed.append(param) diff --git a/salt/utils/nacl.py b/salt/utils/nacl.py index cac3455d1a68..63d97e6f9abb 100644 --- a/salt/utils/nacl.py +++ b/salt/utils/nacl.py @@ -182,12 +182,12 @@ def keygen(sk_file=None, pk_file=None, **kwargs): with salt.utils.files.fopen(sk_file, "rb") as keyf: sk = salt.utils.stringutils.to_unicode(keyf.read()).rstrip("\n") sk = base64.b64decode(sk) - kp = nacl.public.PublicKey(sk) + kp = nacl.public.PrivateKey(sk) with salt.utils.files.fopen(pk_file, "wb") as keyf: - keyf.write(base64.b64encode(kp.encode())) + keyf.write(base64.b64encode(kp.public_key.encode())) return f"saved pk_file: {pk_file}" - kp = nacl.public.PublicKey.generate() + kp = nacl.public.PrivateKey.generate() with salt.utils.files.fopen(sk_file, "wb") as keyf: keyf.write(base64.b64encode(kp.encode())) if salt.utils.platform.is_windows(): @@ -200,7 +200,7 @@ def keygen(sk_file=None, pk_file=None, **kwargs): # chmod 0600 file os.chmod(sk_file, 1536) with salt.utils.files.fopen(pk_file, "wb") as keyf: - keyf.write(base64.b64encode(kp.encode())) + keyf.write(base64.b64encode(kp.public_key.encode())) return f"saved sk_file:{sk_file} pk_file: {pk_file}" diff --git a/salt/utils/network.py b/salt/utils/network.py index b415f572750b..69384819d3bf 100644 --- a/salt/utils/network.py +++ b/salt/utils/network.py @@ -1740,7 +1740,13 @@ def _netlink_tool_remote_on(port, which_end): continue if which_end == "local_port" and int(local_port) != int(port): continue - remotes.add(remote_host.strip("[]")) + + # Interpret IPv4-mapped IPv6 addresses as IPv4 (strip prefix) + remote_host = remote_host.strip("[]").lower() + if remote_host.startswith("::ffff:"): + remote_host = remote_host[7:] + + remotes.add(remote_host) if valid is False: remotes = None diff --git a/salt/utils/odict.py b/salt/utils/odict.py index 2834f1d92469..11a3f3a30977 100644 --- a/salt/utils/odict.py +++ b/salt/utils/odict.py @@ -62,3 +62,8 @@ def __repr__(self, _repr_running={}): # pylint: disable=W0102 return "DefaultOrderedDict({}, {})".format( self.default_factory, super().__repr__() ) + + +class HashableOrderedDict(OrderedDict): + def __hash__(self): + return id(self) diff --git a/salt/utils/timed_subprocess.py b/salt/utils/timed_subprocess.py index 627d3f712ed1..c41d1a7377bc 100644 --- a/salt/utils/timed_subprocess.py +++ b/salt/utils/timed_subprocess.py @@ -33,9 +33,8 @@ def __init__(self, args, **kwargs): if not self.stdin_raw_newlines: # Translate a newline submitted as '\n' on the CLI to an actual # newline character. - self.stdin = salt.utils.stringutils.to_bytes( - self.stdin.replace("\\n", "\n") - ) + self.stdin = self.stdin.replace("\\n", "\n") + self.stdin = salt.utils.stringutils.to_bytes(self.stdin) kwargs["stdin"] = subprocess.PIPE if not self.with_communicate: diff --git a/salt/utils/yamldumper.py b/salt/utils/yamldumper.py index 8c6e40394a35..8e694ab4a763 100644 --- a/salt/utils/yamldumper.py +++ b/salt/utils/yamldumper.py @@ -13,7 +13,7 @@ import yaml # pylint: disable=blacklisted-import import salt.utils.context -from salt.utils.odict import OrderedDict +from salt.utils.odict import HashableOrderedDict, OrderedDict try: from yaml import CDumper as Dumper @@ -71,7 +71,9 @@ def represent_undefined(dumper, data): OrderedDumper.add_representer(OrderedDict, represent_ordereddict) +OrderedDumper.add_representer(HashableOrderedDict, represent_ordereddict) SafeOrderedDumper.add_representer(OrderedDict, represent_ordereddict) +SafeOrderedDumper.add_representer(HashableOrderedDict, represent_ordereddict) SafeOrderedDumper.add_representer(None, represent_undefined) OrderedDumper.add_representer( diff --git a/tests/integration/modules/test_cmdmod.py b/tests/integration/modules/test_cmdmod.py index 4f2a72c45608..7d76aafcb743 100644 --- a/tests/integration/modules/test_cmdmod.py +++ b/tests/integration/modules/test_cmdmod.py @@ -78,6 +78,18 @@ def test_run(self): ), "four\nfive", ) + self.assertEqual( + self.run_function( + "cmd.run", ["cat"], stdin="one\\ntwo", stdin_raw_newlines=False + ), + "one\ntwo", + ) + self.assertEqual( + self.run_function( + "cmd.run", ["cat"], stdin="one\\ntwo", stdin_raw_newlines=True + ), + "one\\ntwo", + ) self.assertEqual( self.run_function( "cmd.run", ['echo "a=b" | sed -e s/=/:/g'], python_shell=True diff --git a/tests/pytests/functional/conftest.py b/tests/pytests/functional/conftest.py index 2fb2246b6338..0a8219b8f717 100644 --- a/tests/pytests/functional/conftest.py +++ b/tests/pytests/functional/conftest.py @@ -1,5 +1,6 @@ import logging import shutil +import sys import pytest from saltfactories.utils.functional import Loaders @@ -70,6 +71,17 @@ def minion_opts( }, } ) + + if sys.platform.startswith("win"): + # We need to set up winrepo on Windows + minion_config_overrides.update( + { + "winrepo_source_dir": "salt://winrepo_ng", + "winrepo_dir_ng": str(state_tree / "winrepo_ng"), + "winrepo_dir": str(state_tree / "winrepo"), + } + ) + factory = salt_factories.salt_minion_daemon( minion_id, defaults=minion_config_defaults or None, diff --git a/tests/pytests/functional/loader/test_dunder.py b/tests/pytests/functional/loader/test_dunder.py new file mode 100644 index 000000000000..581d669a12f3 --- /dev/null +++ b/tests/pytests/functional/loader/test_dunder.py @@ -0,0 +1,50 @@ +import salt.loader.context +import salt.loader.lazy +import salt.utils.files +import tests.support.helpers + + +def test_opts_dunder_opts_without_import(tmp_path): + """ + Test __opts__ without being imported. + + When a loaded module uses __opts__ but does not import it from + salt.loader.dunder the __opts__ object will be a dictionary. + """ + opts = {"optimization_order": [0, 1, 2]} + with salt.utils.files.fopen(tmp_path / "mymod.py", "w") as fp: + fp.write( + tests.support.helpers.dedent( + """ + def mymethod(): + return __opts__ + """ + ) + ) + loader = salt.loader.lazy.LazyLoader([tmp_path], opts) + assert type(loader["mymod.mymethod"]()) == dict + + +def test_opts_dunder_opts_with_import(tmp_path): + """ + Test __opts__ when imported. + + When a loaded module uses __opts__ by importing it from + salt.loader.dunder the __opts__ object will be a NamedLoaderContext. + """ + opts = {"optimization_order": [0, 1, 2]} + with salt.utils.files.fopen(tmp_path / "mymod.py", "w") as fp: + fp.write( + tests.support.helpers.dedent( + """ + from salt.loader.dunder import __opts__ + def optstype(): + return type(__opts__) + def opts(): + return __opts__ + """ + ) + ) + loader = salt.loader.lazy.LazyLoader([tmp_path], opts) + assert loader["mymod.optstype"]() == salt.loader.context.NamedLoaderContext + assert loader["mymod.opts"]() == opts diff --git a/tests/pytests/functional/modules/test_aptpkg.py b/tests/pytests/functional/modules/test_aptpkg.py index 161e3c7827ef..6e8f08566c4a 100644 --- a/tests/pytests/functional/modules/test_aptpkg.py +++ b/tests/pytests/functional/modules/test_aptpkg.py @@ -14,6 +14,7 @@ import salt.modules.pkg_resource as pkg_resource import salt.utils.files import salt.utils.stringutils +from salt.loader.dunder import __opts__ from tests.support.mock import Mock, patch pytestmark = [ @@ -86,7 +87,7 @@ def configure_loader_modules(minion_opts, grains): }, gpg: {}, cp: { - "__opts__": minion_opts, + "__opts__": __opts__.with_default(minion_opts), }, config: { "__opts__": minion_opts, diff --git a/tests/pytests/functional/modules/test_system.py b/tests/pytests/functional/modules/test_system.py index 07f34e8a5164..5944507c1ee0 100644 --- a/tests/pytests/functional/modules/test_system.py +++ b/tests/pytests/functional/modules/test_system.py @@ -5,10 +5,12 @@ import signal import subprocess import textwrap +import time import pytest import salt.utils.files +from salt.exceptions import CommandExecutionError pytestmark = [ pytest.mark.skip_unless_on_linux, @@ -76,7 +78,13 @@ def setup_teardown_vars(file, service, system): file.remove("/etc/machine-info") if _systemd_timesyncd_available_: - res = service.start("systemd-timesyncd") + try: + res = service.start("systemd-timesyncd") + except CommandExecutionError: + # We possibly did too many restarts in too short time + # Wait 10s (default systemd timeout) and try again + time.sleep(10) + res = service.start("systemd-timesyncd") assert res diff --git a/tests/pytests/functional/modules/test_win_pkg.py b/tests/pytests/functional/modules/test_win_pkg.py index b68895ef6253..6bcfaa9bd849 100644 --- a/tests/pytests/functional/modules/test_win_pkg.py +++ b/tests/pytests/functional/modules/test_win_pkg.py @@ -29,7 +29,7 @@ def pkg(modules): def test_refresh_db(pkg, pkg_def_contents, state_tree, minion_opts): assert len(pkg.get_package_info("my-software")) == 0 - repo_dir = state_tree / "win" / "repo-ng" + repo_dir = state_tree / "winrepo_ng" with pytest.helpers.temp_file("my-software.sls", pkg_def_contents, repo_dir): pkg.refresh_db() assert len(pkg.get_package_info("my-software")) == 1 diff --git a/tests/pytests/functional/states/file/test_recurse.py b/tests/pytests/functional/states/file/test_recurse.py index c735d5128dac..9b69bbf5fffd 100644 --- a/tests/pytests/functional/states/file/test_recurse.py +++ b/tests/pytests/functional/states/file/test_recurse.py @@ -7,6 +7,60 @@ ] +@pytest.fixture(scope="module") +def symlink_scenario_1(state_tree): + # Create directory structure + dir_name = "symlink_scenario_1" + source_dir = state_tree / dir_name + if not source_dir.is_dir(): + source_dir.mkdir() + source_file = source_dir / "source_file.txt" + source_file.write_text("This is the source file...") + symlink_file = source_dir / "symlink" + symlink_file.symlink_to(source_file) + yield dir_name + + +@pytest.fixture(scope="module") +def symlink_scenario_2(state_tree): + # Create directory structure + dir_name = "symlink_scenario_2" + source_dir = state_tree / dir_name / "test" + if not source_dir.is_dir(): + source_dir.mkdir(parents=True) + test1 = source_dir / "test1" + test2 = source_dir / "test2" + test3 = source_dir / "test3" + test_link = source_dir / "test" + test1.touch() + test2.touch() + test3.touch() + test_link.symlink_to(test3) + yield dir_name + + +@pytest.fixture(scope="module") +def symlink_scenario_3(state_tree): + # Create directory structure + dir_name = "symlink_scenario_3" + source_dir = state_tree / dir_name + if not source_dir.is_dir(): + source_dir.mkdir(parents=True) + # Create a file with the same name but is not a symlink + source_file = source_dir / "not_a_symlink" / "symlink" + source_file.parent.mkdir(parents=True) + source_file.write_text("This is the source file...") + # Create other fluff files + just_a_file = source_dir / "just_a_file.txt" + just_a_file.touch() + dummy_file = source_dir / "notasymlink" + dummy_file.touch() + # Create symlink to source with the same name + symlink_file = source_dir / "symlink" + symlink_file.symlink_to(source_file) + yield dir_name + + @pytest.mark.parametrize("test", (False, True)) def test_recurse(file, tmp_path, grail, test): """ @@ -249,3 +303,148 @@ def test_issue_2726_mode_kwarg(modules, tmp_path, state_tree): ret = modules.state.template_str("\n".join(good_template)) for state_run in ret: assert state_run.result is True + + +def test_issue_64630_keep_symlinks_true(file, symlink_scenario_1, tmp_path): + """ + Make sure that symlinks are created and that there isn't an error when there + are no conflicting target files + """ + target_dir = tmp_path / symlink_scenario_1 # Target for the file.recurse state + target_file = target_dir / "source_file.txt" + target_symlink = target_dir / "symlink" + + ret = file.recurse( + name=str(target_dir), source=f"salt://{target_dir.name}", keep_symlinks=True + ) + assert ret.result is True + + assert target_dir.exists() + assert target_file.is_file() + assert target_symlink.is_symlink() + + +def test_issue_64630_keep_symlinks_false(file, symlink_scenario_1, tmp_path): + """ + Make sure that symlinks are created as files and that there isn't an error + """ + target_dir = tmp_path / symlink_scenario_1 # Target for the file.recurse state + target_file = target_dir / "source_file.txt" + target_symlink = target_dir / "symlink" + + ret = file.recurse( + name=str(target_dir), source=f"salt://{target_dir.name}", keep_symlinks=False + ) + assert ret.result is True + + assert target_dir.exists() + assert target_file.is_file() + assert target_symlink.is_file() + assert target_file.read_text() == target_symlink.read_text() + + +def test_issue_64630_keep_symlinks_conflicting_force_symlinks_false( + file, symlink_scenario_1, tmp_path +): + """ + Make sure that symlinks are not created when there is a conflict. The state + should return False + """ + target_dir = tmp_path / symlink_scenario_1 # Target for the file.recurse state + target_file = target_dir / "source_file.txt" + target_symlink = target_dir / "symlink" + + # Create the conflicting file + target_symlink.parent.mkdir(parents=True) + target_symlink.touch() + assert target_symlink.is_file() + + ret = file.recurse( + name=str(target_dir), + source=f"salt://{target_dir.name}", + keep_symlinks=True, + force_symlinks=False, + ) + # We expect it to fail + assert ret.result is False + + # And files not to be created properly + assert target_dir.exists() + assert target_file.is_file() + assert target_symlink.is_file() + + +def test_issue_64630_keep_symlinks_conflicting_force_symlinks_true( + file, symlink_scenario_1, tmp_path +): + """ + Make sure that symlinks are created when there is a conflict with an + existing file. + """ + target_dir = tmp_path / symlink_scenario_1 # Target for the file.recurse state + target_file = target_dir / "source_file.txt" + target_symlink = target_dir / "symlink" + + # Create the conflicting file + target_symlink.parent.mkdir(parents=True) + target_symlink.touch() + assert target_symlink.is_file() + + ret = file.recurse( + name=str(target_dir), + source=f"salt://{target_dir.name}", + force_symlinks=True, + keep_symlinks=True, + ) + assert ret.result is True + + assert target_dir.exists() + assert target_file.is_file() + assert target_symlink.is_symlink() + + +def test_issue_64630_keep_symlinks_similar_names(file, symlink_scenario_3, tmp_path): + """ + Make sure that symlinks are created when there is a file that shares part + of the name of the actual symlink file. I'm not sure what I'm testing here + as I couldn't really get this to fail either way + """ + target_dir = tmp_path / symlink_scenario_3 # Target for the file.recurse state + # symlink target, but has the same name as the symlink itself + target_source = target_dir / "not_a_symlink" / "symlink" + target_symlink = target_dir / "symlink" + decoy_file = target_dir / "notasymlink" + just_a_file = target_dir / "just_a_file.txt" + + ret = file.recurse( + name=str(target_dir), source=f"salt://{target_dir.name}", keep_symlinks=True + ) + assert ret.result is True + + assert target_dir.exists() + assert target_source.is_file() + assert decoy_file.is_file() + assert just_a_file.is_file() + assert target_symlink.is_symlink() + + +def test_issue_62117(file, symlink_scenario_2, tmp_path): + target_dir = tmp_path / symlink_scenario_2 / "test" + target_file_1 = target_dir / "test1" + target_file_2 = target_dir / "test2" + target_file_3 = target_dir / "test3" + target_symlink = target_dir / "test" + + ret = file.recurse( + name=str(target_dir), + source=f"salt://{target_dir.parent.name}/test", + clean=True, + keep_symlinks=True, + ) + assert ret.result is True + + assert target_dir.exists() + assert target_file_1.is_file() + assert target_file_2.is_file() + assert target_file_3.is_file() + assert target_symlink.is_symlink() diff --git a/tests/pytests/functional/states/test_pkg.py b/tests/pytests/functional/states/test_pkg.py index 158fd065b44c..09b14704f2e0 100644 --- a/tests/pytests/functional/states/test_pkg.py +++ b/tests/pytests/functional/states/test_pkg.py @@ -20,12 +20,17 @@ pytest.mark.slow_test, pytest.mark.skip_if_not_root, pytest.mark.destructive_test, + pytest.mark.windows_whitelisted, pytest.mark.timeout_unless_on_windows(240), ] @pytest.fixture(scope="module", autouse=True) def refresh_db(grains, modules): + + if salt.utils.platform.is_windows(): + modules.winrepo.update_git_repos() + modules.pkg.refresh_db() # If this is Arch Linux, check if pacman is in use by another process @@ -43,7 +48,7 @@ def refresh_db(grains, modules): def refresh_keys(grains, modules): if grains["os_family"] == "Arch": # We should be running this periodically when building new test runner - # images, otherwise this could take several minuets to complete. + # images, otherwise this could take several minutes to complete. proc = subprocess.run(["pacman-key", "--refresh-keys"], check=False) if proc.returncode != 0: pytest.fail("pacman-key --refresh-keys command failed.") @@ -53,7 +58,7 @@ def refresh_keys(grains, modules): def PKG_TARGETS(grains): _PKG_TARGETS = ["figlet", "sl"] if grains["os"] == "Windows": - _PKG_TARGETS = ["vlc", "putty"] + _PKG_TARGETS = ["npp_x64", "winrar"] elif grains["os"] == "Amazon": if grains["osfinger"] == "Amazon Linux-2023": _PKG_TARGETS = ["lynx", "gnuplot-minimal"] @@ -108,7 +113,12 @@ def PKG_32_TARGETS(grains): _PKG_32_TARGETS = [] if grains["os_family"] == "RedHat" and grains["oscodename"] != "Photon": if grains["os"] == "CentOS": - _PKG_32_TARGETS.append("xz-devel.i686") + if grains["osmajorrelease"] == 5: + _PKG_32_TARGETS = ["xz-devel.i386"] + else: + _PKG_32_TARGETS.append("xz-devel.i686") + elif grains["os"] == "Windows": + _PKG_32_TARGETS = ["npp", "putty"] if not _PKG_32_TARGETS: pytest.skip("No 32 bit packages have been specified for testing") return _PKG_32_TARGETS @@ -198,6 +208,23 @@ def run_command(*names): return run_command +@pytest.fixture(scope="function") +def install_7zip(modules): + try: + modules.pkg.install(name="7zip", version="22.01.00.0") + modules.pkg.install(name="7zip", version="19.00.00.0") + versions = modules.pkg.version("7zip") + assert "19.00.00.0" in versions + assert "22.01.00.0" in versions + yield + finally: + modules.pkg.remove(name="7zip", version="19.00.00.0") + modules.pkg.remove(name="7zip", version="22.01.00.0") + versions = modules.pkg.version("7zip") + assert "19.00.00.0" not in versions + assert "22.01.00.0" not in versions + + @pytest.mark.requires_salt_modules("pkg.version") @pytest.mark.requires_salt_states("pkg.installed", "pkg.removed") @pytest.mark.slow_test @@ -261,7 +288,8 @@ def test_pkg_003_installed_multipkg(caplog, PKG_TARGETS, modules, states, grains try: ret = states.pkg.installed(name=None, pkgs=PKG_TARGETS, refresh=False) assert ret.result is True - assert "WARNING" not in caplog.text + if not salt.utils.platform.is_windows(): + assert "WARNING" not in caplog.text finally: ret = states.pkg.removed(name=None, pkgs=PKG_TARGETS) assert ret.result is True @@ -1084,3 +1112,17 @@ def test_pkg_purged_with_removed_pkg(grains, PKG_TARGETS, states, modules): "installed": {}, "removed": {target: {"new": "", "old": version}}, } + + +@pytest.mark.skip_unless_on_windows() +def test_pkg_removed_with_version_multiple(install_7zip, modules, states): + """ + This tests removing a specific version of a package when multiple versions + are installed. This is specific to Windows. The only version I could find + that allowed multiple installs of differing versions was 7zip, so we'll use + that. + """ + ret = states.pkg.removed(name="7zip", version="19.00.00.0") + assert ret.result is True + current = modules.pkg.version("7zip") + assert "22.01.00.0" in current diff --git a/tests/pytests/functional/utils/yamllint/test_yamllint.py b/tests/pytests/functional/utils/yamllint/test_yamllint.py index 403c6fc610ea..3c730523c4db 100644 --- a/tests/pytests/functional/utils/yamllint/test_yamllint.py +++ b/tests/pytests/functional/utils/yamllint/test_yamllint.py @@ -7,7 +7,7 @@ try: import salt.utils.yamllint as yamllint - YAMLLINT_AVAILABLE = True + YAMLLINT_AVAILABLE = yamllint.has_yamllint() except ImportError: YAMLLINT_AVAILABLE = False diff --git a/tests/pytests/pkg/integration/test_salt_api.py b/tests/pytests/pkg/integration/test_salt_api.py index e962fbe32213..b13775bd7942 100644 --- a/tests/pytests/pkg/integration/test_salt_api.py +++ b/tests/pytests/pkg/integration/test_salt_api.py @@ -5,7 +5,7 @@ ] -def test_salt_api(api_request, salt_master, install_salt): +def test_salt_api(api_request, install_salt, salt_master): """ Test running a command against the salt api """ diff --git a/tests/pytests/pkg/integration/test_salt_ufw.py b/tests/pytests/pkg/integration/test_salt_ufw.py index 0e0471aebf24..6c86e0a3339f 100644 --- a/tests/pytests/pkg/integration/test_salt_ufw.py +++ b/tests/pytests/pkg/integration/test_salt_ufw.py @@ -9,8 +9,8 @@ @pytest.fixture def salt_systemd_setup( - salt_call_cli, install_salt, + salt_call_cli, ): """ Fixture to set systemd for salt packages to enabled and active @@ -31,7 +31,7 @@ def salt_systemd_setup( @pytest.mark.skip_if_binaries_missing("ufw") -def test_salt_ufw(salt_systemd_setup, salt_call_cli, install_salt): +def test_salt_ufw(salt_systemd_setup, install_salt, salt_call_cli): """ Test salt.ufw for Debian/Ubuntu salt-master """ diff --git a/tests/pytests/pkg/integration/test_salt_user.py b/tests/pytests/pkg/integration/test_salt_user.py index fb42ae3c9f6b..3978bfe9ca75 100644 --- a/tests/pytests/pkg/integration/test_salt_user.py +++ b/tests/pytests/pkg/integration/test_salt_user.py @@ -2,6 +2,7 @@ import pathlib import subprocess import sys +import time import packaging.version import psutil @@ -15,8 +16,8 @@ @pytest.fixture def salt_systemd_setup( - salt_call_cli, install_salt, + salt_call_cli, ): """ Fixture to set systemd for salt packages to enabled and active @@ -67,8 +68,12 @@ def pkg_paths_salt_user(): "/var/log/salt/master", "/var/log/salt/api", "/var/log/salt/key", + "/var/log/salt/syndic", "/var/cache/salt/master", "/var/run/salt/master", + "/run/salt-master.pid", + "/run/salt-syndic.pid", + "/run/salt-api.pid", ] @@ -83,10 +88,16 @@ def pkg_paths_salt_user_exclusions(): return paths -def test_salt_user_master(salt_master, install_salt): +def test_salt_user_master(install_salt, salt_master): """ Test the correct user is running the Salt Master """ + for count in range(0, 30): + if salt_master.is_running(): + break + else: + time.sleep(2) + assert salt_master.is_running() match = False @@ -158,6 +169,7 @@ def test_pkg_paths( pkg_paths, pkg_paths_salt_user, pkg_paths_salt_user_exclusions, + salt_call_cli, ): """ Test package paths ownership @@ -174,6 +186,7 @@ def test_pkg_paths( assert pkg_path.exists() for dirpath, sub_dirs, files in os.walk(pkg_path): path = pathlib.Path(dirpath) + # Directories owned by salt:salt or their subdirs/files if ( str(path) in pkg_paths_salt_user or str(path) in salt_user_subdirs @@ -206,10 +219,10 @@ def test_pkg_paths( @pytest.mark.skip_if_binaries_missing("logrotate") def test_paths_log_rotation( + install_salt, salt_master, salt_minion, salt_call_cli, - install_salt, pkg_tests_account, ): """ @@ -401,3 +414,7 @@ def test_paths_log_rotation( bkup_count += 1 assert ret.returncode == 0 + + # ensure leave salt_master running + salt_master.start() + assert salt_master.is_running() is True diff --git a/tests/pytests/pkg/integration/test_version.py b/tests/pytests/pkg/integration/test_version.py index 6ef06f310e24..8bbef78793a5 100644 --- a/tests/pytests/pkg/integration/test_version.py +++ b/tests/pytests/pkg/integration/test_version.py @@ -1,6 +1,7 @@ import os.path import pathlib import subprocess +import time import pytest from pytestskipmarkers.utils import platform @@ -35,6 +36,7 @@ def test_salt_version(version, install_salt): @pytest.mark.skip_on_windows +@pytest.mark.skip_on_darwin def test_salt_versions_report_master(install_salt): """ Test running --versions-report on master @@ -53,17 +55,33 @@ def test_salt_versions_report_master(install_salt): @pytest.mark.skip_on_windows -def test_salt_versions_report_minion(salt_cli, salt_call_cli, salt_minion): +def test_salt_versions_report_minion(salt_cli, salt_call_cli, salt_master, salt_minion): """ Test running test.versions_report on minion """ # Make sure the minion is running + for count in range(0, 30): + if salt_minion.is_running(): + break + else: + time.sleep(2) + assert salt_minion.is_running() + # Make sure the master is running + for count in range(0, 30): + if salt_master.is_running(): + break + else: + time.sleep(2) + + assert salt_master.is_running() + # Make sure we can ping the minion ... ret = salt_cli.run( - "--timeout=300", "test.ping", minion_tgt=salt_minion.id, _timeout=300 + "--timeout=600", "test.ping", minion_tgt=salt_minion.id, _timeout=600 ) + assert ret.returncode == 0 assert ret.data is True ret = salt_cli.run( @@ -77,6 +95,8 @@ def test_salt_versions_report_minion(salt_cli, salt_call_cli, salt_minion): ret.stdout.matcher.fnmatch_lines(["*Salt Version:*"]) +@pytest.mark.skip_on_windows +@pytest.mark.skip_on_darwin @pytest.mark.parametrize( "binary", ["master", "cloud", "syndic", "minion", "call", "api"] ) @@ -132,8 +152,7 @@ def test_symlinks_created(version, symlink, install_salt): ret.stdout.matcher.fnmatch_lines([f"*{version}*"]) -@pytest.mark.skip_on_windows -@pytest.mark.skip_on_darwin +@pytest.mark.skip_unless_on_linux @pytest.mark.skip_if_binaries_missing("rpmdev-vercmp") def test_compare_pkg_versions_redhat_rc(version, install_salt): """ diff --git a/tests/pytests/unit/grains/test_opts.py b/tests/pytests/unit/grains/test_opts.py new file mode 100644 index 000000000000..35fc8a06b207 --- /dev/null +++ b/tests/pytests/unit/grains/test_opts.py @@ -0,0 +1,20 @@ +""" +tests.pytests.unit.grains.test_opts +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +""" + +import salt.grains.opts as opts +from tests.support.mock import patch + + +def test_grain_opts_does_not_overwrite_core_grains(tmp_path): + """ + Tests that enabling grain_opts doesn't overwrite the core grains + + See: https://github.com/saltstack/salt/issues/66784 + """ + dunder_opts = {"grain_opts": True} + + with patch.object(opts, "__opts__", dunder_opts, create=True): + with patch.object(opts, "__pillar__", {}, create=True): + assert opts.opts() == {"opts": dunder_opts} diff --git a/tests/pytests/unit/modules/test_pip.py b/tests/pytests/unit/modules/test_pip.py index b2b5a8988d0a..1622a8482383 100644 --- a/tests/pytests/unit/modules/test_pip.py +++ b/tests/pytests/unit/modules/test_pip.py @@ -10,6 +10,10 @@ from salt.exceptions import CommandExecutionError from tests.support.mock import MagicMock, patch +TARGET = [] +if os.environ.get("VENV_PIP_TARGET"): + TARGET = ["--target", os.environ.get("VENV_PIP_TARGET")] + class FakeFopen: def __init__(self, filename): @@ -97,6 +101,7 @@ def test_install_frozen_app(python_binary): expected = [ *python_binary, "install", + *TARGET, pkg, ] mock.assert_called_with( @@ -118,6 +123,7 @@ def test_install_source_app(python_binary): expected = [ *python_binary, "install", + *TARGET, pkg, ] mock.assert_called_with( @@ -138,6 +144,7 @@ def test_fix4361(python_binary): "install", "--requirement", "requirements.txt", + *TARGET, ] mock.assert_called_with( expected_cmd, @@ -164,7 +171,7 @@ def test_install_multiple_editable(python_binary): "git+https://github.com/saltstack/salt-testing.git#egg=SaltTesting", ] - expected = [*python_binary, "install"] + expected = [*python_binary, "install", *TARGET] for item in editables: expected.extend(["--editable", item]) @@ -200,7 +207,7 @@ def test_install_multiple_pkgs_and_editables(python_binary): "git+https://github.com/saltstack/salt-testing.git#egg=SaltTesting", ] - expected = [*python_binary, "install"] + expected = [*python_binary, "install", *TARGET] expected.extend(pkgs) for item in editables: expected.extend(["--editable", item]) @@ -236,6 +243,7 @@ def test_install_multiple_pkgs_and_editables(python_binary): expected = [ *python_binary, "install", + *TARGET, pkgs[0], "--editable", editables[0], @@ -263,7 +271,7 @@ def test_issue5940_install_multiple_pip_mirrors(python_binary): expected = [*python_binary, "install", "--use-mirrors"] for item in mirrors: expected.extend(["--mirrors", item]) - expected.append("pep8") + expected = [*expected, *TARGET, "pep8"] # Passing mirrors as a list mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) @@ -295,6 +303,7 @@ def test_issue5940_install_multiple_pip_mirrors(python_binary): "--use-mirrors", "--mirrors", mirrors[0], + *TARGET, "pep8", ] @@ -322,7 +331,7 @@ def test_install_with_multiple_find_links(python_binary): expected = [*python_binary, "install"] for item in find_links: expected.extend(["--find-links", item]) - expected.append(pkg) + expected = [*expected, *TARGET, pkg] # Passing mirrors as a list mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) @@ -365,6 +374,7 @@ def test_install_with_multiple_find_links(python_binary): "install", "--find-links", find_links[0], + *TARGET, pkg, ] @@ -430,6 +440,7 @@ def test_install_cached_requirements_used(python_binary): "install", "--requirement", "my_cached_reqs", + *TARGET, ] mock.assert_called_with( expected, @@ -486,6 +497,7 @@ def test_install_log_argument_in_resulting_command(python_binary, tmp_path): "install", "--log", log_path, + *TARGET, pkg, ] mock.assert_called_with( @@ -516,7 +528,7 @@ def test_install_timeout_argument_in_resulting_command(python_binary): with patch.dict(pip.__salt__, {"cmd.run_all": mock}): pip.install(pkg, timeout=10) mock.assert_called_with( - expected + [10, pkg], + expected + [10, *TARGET, pkg], saltenv="base", runas=None, use_vt=False, @@ -528,7 +540,7 @@ def test_install_timeout_argument_in_resulting_command(python_binary): with patch.dict(pip.__salt__, {"cmd.run_all": mock}): pip.install(pkg, timeout="10") mock.assert_called_with( - expected + ["10", pkg], + expected + ["10", *TARGET, pkg], saltenv="base", runas=None, use_vt=False, @@ -552,6 +564,7 @@ def test_install_index_url_argument_in_resulting_command(python_binary): "install", "--index-url", index_url, + *TARGET, pkg, ] mock.assert_called_with( @@ -574,6 +587,7 @@ def test_install_extra_index_url_argument_in_resulting_command(python_binary): "install", "--extra-index-url", extra_index_url, + *TARGET, pkg, ] mock.assert_called_with( @@ -590,7 +604,7 @@ def test_install_no_index_argument_in_resulting_command(python_binary): mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) with patch.dict(pip.__salt__, {"cmd.run_all": mock}): pip.install(pkg, no_index=True) - expected = [*python_binary, "install", "--no-index", pkg] + expected = [*python_binary, "install", "--no-index", *TARGET, pkg] mock.assert_called_with( expected, saltenv="base", @@ -606,7 +620,7 @@ def test_install_build_argument_in_resulting_command(python_binary): mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) with patch.dict(pip.__salt__, {"cmd.run_all": mock}): pip.install(pkg, build=build) - expected = [*python_binary, "install", "--build", build, pkg] + expected = [*python_binary, "install", "--build", build, *TARGET, pkg] mock.assert_called_with( expected, saltenv="base", @@ -641,6 +655,7 @@ def test_install_download_argument_in_resulting_command(python_binary): expected = [ *python_binary, "install", + *TARGET, "--download", download, pkg, @@ -659,7 +674,7 @@ def test_install_no_download_argument_in_resulting_command(python_binary): mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) with patch.dict(pip.__salt__, {"cmd.run_all": mock}): pip.install(pkg, no_download=True) - expected = [*python_binary, "install", "--no-download", pkg] + expected = [*python_binary, "install", *TARGET, "--no-download", pkg] mock.assert_called_with( expected, saltenv="base", @@ -686,6 +701,7 @@ def test_install_download_cache_dir_arguments_in_resulting_command(python_binary expected = [ *python_binary, "install", + *TARGET, cmd_arg, download_cache, pkg, @@ -715,7 +731,7 @@ def test_install_source_argument_in_resulting_command(python_binary): mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) with patch.dict(pip.__salt__, {"cmd.run_all": mock}): pip.install(pkg, source=source) - expected = [*python_binary, "install", "--source", source, pkg] + expected = [*python_binary, "install", *TARGET, "--source", source, pkg] mock.assert_called_with( expected, saltenv="base", @@ -734,6 +750,7 @@ def test_install_exists_action_argument_in_resulting_command(python_binary): expected = [ *python_binary, "install", + *TARGET, "--exists-action", action, pkg, @@ -756,7 +773,7 @@ def test_install_install_options_argument_in_resulting_command(python_binary): install_options = ["--exec-prefix=/foo/bar", "--install-scripts=/foo/bar/bin"] pkg = "pep8" - expected = [*python_binary, "install"] + expected = [*python_binary, "install", *TARGET] for item in install_options: expected.extend(["--install-option", item]) expected.append(pkg) @@ -792,6 +809,7 @@ def test_install_install_options_argument_in_resulting_command(python_binary): expected = [ *python_binary, "install", + *TARGET, "--install-option", install_options[0], pkg, @@ -809,7 +827,7 @@ def test_install_global_options_argument_in_resulting_command(python_binary): global_options = ["--quiet", "--no-user-cfg"] pkg = "pep8" - expected = [*python_binary, "install"] + expected = [*python_binary, "install", *TARGET] for item in global_options: expected.extend(["--global-option", item]) expected.append(pkg) @@ -845,6 +863,7 @@ def test_install_global_options_argument_in_resulting_command(python_binary): expected = [ *python_binary, "install", + *TARGET, "--global-option", global_options[0], pkg, @@ -863,7 +882,7 @@ def test_install_upgrade_argument_in_resulting_command(python_binary): mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) with patch.dict(pip.__salt__, {"cmd.run_all": mock}): pip.install(pkg, upgrade=True) - expected = [*python_binary, "install", "--upgrade", pkg] + expected = [*python_binary, "install", *TARGET, "--upgrade", pkg] mock.assert_called_with( expected, saltenv="base", @@ -881,6 +900,7 @@ def test_install_force_reinstall_argument_in_resulting_command(python_binary): expected = [ *python_binary, "install", + *TARGET, "--force-reinstall", pkg, ] @@ -901,6 +921,7 @@ def test_install_ignore_installed_argument_in_resulting_command(python_binary): expected = [ *python_binary, "install", + *TARGET, "--ignore-installed", pkg, ] @@ -918,7 +939,7 @@ def test_install_no_deps_argument_in_resulting_command(python_binary): mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) with patch.dict(pip.__salt__, {"cmd.run_all": mock}): pip.install(pkg, no_deps=True) - expected = [*python_binary, "install", "--no-deps", pkg] + expected = [*python_binary, "install", *TARGET, "--no-deps", pkg] mock.assert_called_with( expected, saltenv="base", @@ -933,7 +954,7 @@ def test_install_no_install_argument_in_resulting_command(python_binary): mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) with patch.dict(pip.__salt__, {"cmd.run_all": mock}): pip.install(pkg, no_install=True) - expected = [*python_binary, "install", "--no-install", pkg] + expected = [*python_binary, "install", *TARGET, "--no-install", pkg] mock.assert_called_with( expected, saltenv="base", @@ -949,7 +970,7 @@ def test_install_proxy_argument_in_resulting_command(python_binary): mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) with patch.dict(pip.__salt__, {"cmd.run_all": mock}): pip.install(pkg, proxy=proxy) - expected = [*python_binary, "install", "--proxy", proxy, pkg] + expected = [*python_binary, "install", "--proxy", proxy, *TARGET, pkg] mock.assert_called_with( expected, saltenv="base", @@ -976,7 +997,7 @@ def test_install_proxy_false_argument_in_resulting_command(python_binary): with patch.dict(pip.__salt__, {"cmd.run_all": mock}): with patch.dict(pip.__opts__, config_mock): pip.install(pkg, proxy=proxy) - expected = [*python_binary, "install", pkg] + expected = [*python_binary, "install", *TARGET, pkg] mock.assert_called_with( expected, saltenv="base", @@ -1007,6 +1028,7 @@ def test_install_global_proxy_in_resulting_command(python_binary): "install", "--proxy", proxy, + *TARGET, pkg, ] mock.assert_called_with( @@ -1027,6 +1049,7 @@ def test_install_multiple_requirements_arguments_in_resulting_command(python_bin expected = [*python_binary, "install"] for item in cached_reqs: expected.extend(["--requirement", item]) + expected.extend(TARGET) # Passing option as a list mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) @@ -1063,6 +1086,7 @@ def test_install_multiple_requirements_arguments_in_resulting_command(python_bin "install", "--requirement", cached_reqs[0], + *TARGET, ] mock.assert_called_with( expected, @@ -1083,6 +1107,7 @@ def test_install_extra_args_arguments_in_resulting_command(python_binary): expected = [ *python_binary, "install", + *TARGET, pkg, "--latest-pip-kwarg", "param", @@ -1598,7 +1623,7 @@ def test_install_pre_argument_in_resulting_command(python_binary): with patch.dict(pip.__salt__, {"cmd.run_all": mock}): with patch("salt.modules.pip.version", MagicMock(return_value="1.3")): pip.install(pkg, pre_releases=True) - expected = [*python_binary, "install", pkg] + expected = [*python_binary, "install", *TARGET, pkg] mock.assert_called_with( expected, saltenv="base", @@ -1614,7 +1639,7 @@ def test_install_pre_argument_in_resulting_command(python_binary): ): with patch("salt.modules.pip._get_pip_bin", MagicMock(return_value=["pip"])): pip.install(pkg, pre_releases=True) - expected = ["pip", "install", "--pre", pkg] + expected = ["pip", "install", *TARGET, "--pre", pkg] mock_run_all.assert_called_with( expected, saltenv="base", diff --git a/tests/pytests/unit/modules/test_win_pkg.py b/tests/pytests/unit/modules/test_win_pkg.py index 3ae8f24f8dd6..a976b6d6083b 100644 --- a/tests/pytests/unit/modules/test_win_pkg.py +++ b/tests/pytests/unit/modules/test_win_pkg.py @@ -6,6 +6,7 @@ import pytest +import salt.loader.dunder import salt.modules.config as config import salt.modules.cp as cp import salt.modules.pkg_resource as pkg_resource @@ -57,7 +58,7 @@ def configure_loader_modules(minion_opts): opts = minion_opts opts["master_uri"] = "localhost" return { - cp: {"__opts__": opts}, + cp: {"__opts__": salt.loader.dunder.__opts__.with_default(opts)}, win_pkg: { "_get_latest_package_version": MagicMock(return_value="3.03"), "_get_package_info": MagicMock(return_value=pkg_info), diff --git a/tests/pytests/unit/modules/test_yaml.py b/tests/pytests/unit/modules/test_yaml.py index 1f00af710c84..75bad8b5cf1a 100644 --- a/tests/pytests/unit/modules/test_yaml.py +++ b/tests/pytests/unit/modules/test_yaml.py @@ -13,7 +13,7 @@ import salt.modules.yaml import salt.utils.yamllint - YAMLLINT_AVAILABLE = True + YAMLLINT_AVAILABLE = salt.utils.yamllint.has_yamllint() except ImportError: YAMLLINT_AVAILABLE = False diff --git a/tests/pytests/unit/states/file/test_recurse.py b/tests/pytests/unit/states/file/test_recurse.py new file mode 100644 index 000000000000..53e6e0fd22f1 --- /dev/null +++ b/tests/pytests/unit/states/file/test_recurse.py @@ -0,0 +1,48 @@ +import logging +import os +import pathlib + +import pytest + +import salt.states.file as filestate +from tests.support.mock import MagicMock, patch + +log = logging.getLogger(__name__) + + +@pytest.fixture +def configure_loader_modules(): + return {filestate: {"__salt__": {}, "__opts__": {}, "__env__": "base"}} + + +def test__gen_recurse_managed_files(): + """ + Test _gen_recurse_managed_files to make sure it puts symlinks at the end of the list of files. + """ + target_dir = pathlib.Path(f"{os.sep}some{os.sep}path{os.sep}target") + cp_list_master = MagicMock( + return_value=[ + "target/symlink", + "target/just_a_file.txt", + "target/not_a_symlink/symlink", + "target/notasymlink", + ], + ) + cp_list_master_symlinks = MagicMock( + return_value={ + "target/symlink": f"{target_dir}{os.sep}not_a_symlink{os.sep}symlink" + } + ) + patch_salt = { + "cp.list_master": cp_list_master, + "cp.list_master_symlinks": cp_list_master_symlinks, + } + with patch.dict(filestate.__salt__, patch_salt): + files, dirs, links, keep = filestate._gen_recurse_managed_files( + name=str(target_dir), source=f"salt://{target_dir.name}", keep_symlinks=True + ) + expected = ( + f"{os.sep}some{os.sep}path{os.sep}target{os.sep}symlink", + "salt://target/symlink?saltenv=base", + ) + assert files[-1] == expected diff --git a/tests/pytests/unit/states/test_pkg.py b/tests/pytests/unit/states/test_pkg.py index a7b6b048f888..21f389d8fee0 100644 --- a/tests/pytests/unit/states/test_pkg.py +++ b/tests/pytests/unit/states/test_pkg.py @@ -11,6 +11,7 @@ import salt.states.beacon as beaconstate import salt.states.pkg as pkg import salt.utils.state as state_utils +from salt.loader.dunder import __opts__ from salt.utils.event import SaltEvent from tests.support.mock import MagicMock, patch @@ -21,7 +22,7 @@ def configure_loader_modules(minion_opts): return { cp: { - "__opts__": minion_opts, + "__opts__": __opts__.with_default(minion_opts), }, pkg: { "__env__": "base", diff --git a/tests/pytests/unit/test_fileserver.py b/tests/pytests/unit/test_fileserver.py index 8dd3ea0a27d6..49be3967dc40 100644 --- a/tests/pytests/unit/test_fileserver.py +++ b/tests/pytests/unit/test_fileserver.py @@ -75,9 +75,7 @@ def test_file_server_url_escape(tmp_path): opts = { "fileserver_backend": ["roots"], "extension_modules": "", - "optimization_order": [ - 0, - ], + "optimization_order": [0, 1], "file_roots": { "base": [fileroot], }, @@ -102,9 +100,7 @@ def test_file_server_serve_url_escape(tmp_path): opts = { "fileserver_backend": ["roots"], "extension_modules": "", - "optimization_order": [ - 0, - ], + "optimization_order": [0, 1], "file_roots": { "base": [fileroot], }, diff --git a/tests/pytests/unit/utils/test_msgpack.py b/tests/pytests/unit/utils/test_msgpack.py index e15da262b00f..feebcf1f88d4 100644 --- a/tests/pytests/unit/utils/test_msgpack.py +++ b/tests/pytests/unit/utils/test_msgpack.py @@ -4,6 +4,10 @@ from tests.support.mock import MagicMock, patch +@pytest.mark.skipif( + salt.utils.msgpack.version < (1, 0, 0), + reason="Test requires msgpack version >= 1.0.0", +) def test_load_encoding(tmp_path): """ test when using msgpack version >= 1.0.0 we diff --git a/tests/pytests/unit/utils/test_nacl.py b/tests/pytests/unit/utils/test_nacl.py index 5c60d880b2fd..91be6855487b 100644 --- a/tests/pytests/unit/utils/test_nacl.py +++ b/tests/pytests/unit/utils/test_nacl.py @@ -73,6 +73,20 @@ def test_keygen_keyfile(test_keygen): ret = nacl.keygen(keyfile=fpath) assert f"saved pk_file: {fpath}.pub" == ret + with salt.utils.files.fopen(str(fpath) + ".pub", "rb") as rfh: + assert test_keygen["pk"] == rfh.read() + salt.utils.files.remove(str(fpath) + ".pub") + + +def test_keygen_nonexistent_sk_file(): + """ + test nacl.keygen function + with nonexistent/new sk_file + """ + with pytest.helpers.temp_file("test_keygen_sk_file") as fpath: + salt.utils.files.remove(str(fpath)) + ret = nacl.keygen(sk_file=str(fpath)) + assert f"saved sk_file:{fpath} pk_file: {fpath}.pub" == ret salt.utils.files.remove(str(fpath) + ".pub") diff --git a/tests/pytests/unit/utils/test_network.py b/tests/pytests/unit/utils/test_network.py index 12d545b01545..557f315a3372 100644 --- a/tests/pytests/unit/utils/test_network.py +++ b/tests/pytests/unit/utils/test_network.py @@ -7,11 +7,12 @@ import salt.exceptions import salt.utils.network import salt.utils.network as network +import salt.utils.platform from salt._compat import ipaddress from tests.support.mock import MagicMock, create_autospec, mock_open, patch pytestmark = [ - pytest.mark.skip_on_windows, + pytest.mark.windows_whitelisted, ] @@ -722,13 +723,13 @@ def test_netlink_tool_remote_on_a(): with patch("salt.utils.platform.is_linux", return_value=True): with patch("subprocess.check_output", return_value=LINUX_NETLINK_SS_OUTPUT): remotes = network._netlink_tool_remote_on("4506", "local_port") - assert remotes == {"192.168.122.177", "::ffff:127.0.0.1"} + assert remotes == {"192.168.122.177", "127.0.0.1"} def test_netlink_tool_remote_on_b(): with patch("subprocess.check_output", return_value=LINUX_NETLINK_SS_OUTPUT): remotes = network._netlink_tool_remote_on("4505", "remote_port") - assert remotes == {"127.0.0.1", "::ffff:1.2.3.4"} + assert remotes == {"127.0.0.1", "1.2.3.4"} def test_openbsd_remotes_on(): @@ -1431,7 +1432,11 @@ def test_isportopen_false(): def test_isportopen(): - ret = network.isportopen("127.0.0.1", "22") + if salt.utils.platform.is_windows(): + port = "135" + else: + port = "22" + ret = network.isportopen("127.0.0.1", port) assert ret == 0 @@ -1445,13 +1450,19 @@ def test_get_socket(): assert ret.type == socket.SOCK_STREAM +# @pytest.mark.skip_on_windows(reason="Do not run on Windows") def test_ip_to_host(grains): + if salt.utils.platform.is_windows(): + hostname = socket.gethostname() + else: + hostname = "localhost" + ret = network.ip_to_host("127.0.0.1") - if grains["oscodename"] == "Photon": + if grains.get("oscodename") == "Photon": # Photon returns this for IPv4 assert ret == "ipv6-localhost" else: - assert ret == "localhost" + assert ret == hostname ret = network.ip_to_host("2001:a71::1") assert ret is None @@ -1461,22 +1472,22 @@ def test_ip_to_host(grains): assert ret == "localhost6" elif grains["os_family"] == "Debian": if grains["osmajorrelease"] == 12: - assert ret == "localhost" + assert ret == hostname else: assert ret == "ip6-localhost" elif grains["os_family"] == "RedHat": if grains["oscodename"] == "Photon": assert ret == "ipv6-localhost" else: - assert ret == "localhost" + assert ret == hostname elif grains["os_family"] == "Arch": if grains.get("osmajorrelease", None) is None: # running doesn't have osmajorrelease grains - assert ret == "localhost" + assert ret == hostname else: assert ret == "ip6-localhost" else: - assert ret == "localhost" + assert ret == hostname @pytest.mark.parametrize( @@ -1509,7 +1520,7 @@ def test_rpad_ipv4_network(addr, expected): def test_hw_addr(linux_interfaces_dict, freebsd_interfaces_dict): with patch( - "salt.utils.network.linux_interfaces", + "salt.utils.network.interfaces", MagicMock(return_value=linux_interfaces_dict), ): hw_addrs = network.hw_addr("eth0") @@ -1534,7 +1545,7 @@ def test_hw_addr(linux_interfaces_dict, freebsd_interfaces_dict): def test_interface_and_ip(linux_interfaces_dict): with patch( - "salt.utils.network.linux_interfaces", + "salt.utils.network.interfaces", MagicMock(return_value=linux_interfaces_dict), ): expected = [ @@ -1560,7 +1571,7 @@ def test_interface_and_ip(linux_interfaces_dict): def test_subnets(linux_interfaces_dict): with patch( - "salt.utils.network.linux_interfaces", + "salt.utils.network.interfaces", MagicMock(return_value=linux_interfaces_dict), ): ret = network.subnets() @@ -1583,14 +1594,14 @@ def test_in_subnet(caplog): def test_ip_addrs(linux_interfaces_dict): with patch( - "salt.utils.network.linux_interfaces", + "salt.utils.network.interfaces", MagicMock(return_value=linux_interfaces_dict), ): ret = network.ip_addrs("eth0") assert ret == ["10.10.10.56"] with patch( - "salt.utils.network.linux_interfaces", + "salt.utils.network.interfaces", MagicMock(return_value=linux_interfaces_dict), ): ret = network.ip_addrs6("eth0") diff --git a/tests/pytests/unit/utils/test_pycrypto.py b/tests/pytests/unit/utils/test_pycrypto.py index 1dfcf9621c41..9dff08f883e1 100644 --- a/tests/pytests/unit/utils/test_pycrypto.py +++ b/tests/pytests/unit/utils/test_pycrypto.py @@ -57,21 +57,20 @@ def test_gen_hash_crypt(algorithm, expected): """ Test gen_hash with crypt library """ - with patch("salt.utils.pycrypto.methods", {}): - ret = salt.utils.pycrypto.gen_hash( - crypt_salt=expected["salt"], password=passwd, algorithm=algorithm - ) - assert ret == expected["hashed"] + ret = salt.utils.pycrypto.gen_hash( + crypt_salt=expected["salt"], password=passwd, algorithm=algorithm + ) + assert ret == expected["hashed"] - ret = salt.utils.pycrypto.gen_hash( - crypt_salt=expected["badsalt"], password=passwd, algorithm=algorithm - ) - assert ret != expected["hashed"] + ret = salt.utils.pycrypto.gen_hash( + crypt_salt=expected["badsalt"], password=passwd, algorithm=algorithm + ) + assert ret != expected["hashed"] - ret = salt.utils.pycrypto.gen_hash( - crypt_salt=None, password=passwd, algorithm=algorithm - ) - assert ret != expected["hashed"] + ret = salt.utils.pycrypto.gen_hash( + crypt_salt=None, password=passwd, algorithm=algorithm + ) + assert ret != expected["hashed"] @pytest.mark.skipif(not salt.utils.pycrypto.HAS_CRYPT, reason="crypt not available") diff --git a/tests/unit/utils/test_yamldumper.py b/tests/unit/utils/test_yamldumper.py index 9a1a6ab103ba..65c900ebb25e 100644 --- a/tests/unit/utils/test_yamldumper.py +++ b/tests/unit/utils/test_yamldumper.py @@ -2,7 +2,11 @@ Unit tests for salt.utils.yamldumper """ +from collections import OrderedDict, defaultdict + import salt.utils.yamldumper +from salt.utils.context import NamespacedDictWrapper +from salt.utils.odict import HashableOrderedDict from tests.support.unit import TestCase @@ -35,3 +39,80 @@ def test_yaml_safe_dump(self): salt.utils.yamldumper.safe_dump(data, default_flow_style=False) == "foo: bar\n" ) + + def test_yaml_ordered_dump(self): + """ + Test yaml.dump with OrderedDict + """ + data = OrderedDict([("foo", "bar"), ("baz", "qux")]) + exp_yaml = "{foo: bar, baz: qux}\n" + assert ( + salt.utils.yamldumper.dump(data, Dumper=salt.utils.yamldumper.OrderedDumper) + == exp_yaml + ) + + def test_yaml_safe_ordered_dump(self): + """ + Test yaml.safe_dump with OrderedDict + """ + data = OrderedDict([("foo", "bar"), ("baz", "qux")]) + exp_yaml = "{foo: bar, baz: qux}\n" + assert salt.utils.yamldumper.safe_dump(data) == exp_yaml + + def test_yaml_indent_safe_ordered_dump(self): + """ + Test yaml.dump with IndentedSafeOrderedDumper + """ + data = OrderedDict([("foo", ["bar", "baz"]), ("qux", "quux")]) + exp_yaml = "foo:\n- bar\n- baz\nqux: quux\n" + assert ( + salt.utils.yamldumper.dump( + data, + Dumper=salt.utils.yamldumper.IndentedSafeOrderedDumper, + default_flow_style=False, + ) + == exp_yaml + ) + + def test_yaml_defaultdict_dump(self): + """ + Test yaml.dump with defaultdict + """ + data = defaultdict(list) + data["foo"].append("bar") + exp_yaml = "foo: [bar]\n" + assert salt.utils.yamldumper.safe_dump(data) == exp_yaml + + def test_yaml_namespaced_dict_wrapper_dump(self): + """ + Test yaml.dump with NamespacedDictWrapper + """ + data = NamespacedDictWrapper({"test": {"foo": "bar"}}, "test") + exp_yaml = ( + "!!python/object/new:salt.utils.context.NamespacedDictWrapper\n" + "dictitems: {foo: bar}\n" + "state:\n" + " _NamespacedDictWrapper__dict:\n" + " test: {foo: bar}\n" + " pre_keys: !!python/tuple [test]\n" + ) + assert salt.utils.yamldumper.dump(data) == exp_yaml + + def test_yaml_undefined_dump(self): + """ + Test yaml.safe_dump with None + """ + data = {"foo": None} + exp_yaml = "{foo: null}\n" + assert salt.utils.yamldumper.safe_dump(data) == exp_yaml + + def test_yaml_hashable_ordered_dict_dump(self): + """ + Test yaml.dump with HashableOrderedDict + """ + data = HashableOrderedDict([("foo", "bar"), ("baz", "qux")]) + exp_yaml = "{foo: bar, baz: qux}\n" + assert ( + salt.utils.yamldumper.dump(data, Dumper=salt.utils.yamldumper.OrderedDumper) + == exp_yaml + ) diff --git a/tools/ci.py b/tools/ci.py index ae3ad518239b..9cf3f19d957b 100644 --- a/tools/ci.py +++ b/tools/ci.py @@ -1011,7 +1011,6 @@ def get_pkg_downloads_matrix(ctx: Context): "photon", ) linux_skip_pkg_download_tests = ( - "archlinux-lts", "opensuse-15", "windows", ) diff --git a/tools/precommit/workflows.py b/tools/precommit/workflows.py index bd1fdca76df8..81234530f2ec 100644 --- a/tools/precommit/workflows.py +++ b/tools/precommit/workflows.py @@ -51,7 +51,6 @@ display_name="Amazon Linux 2023 Arm64", arch="arm64", ), - Linux(slug="archlinux-lts", display_name="Arch Linux LTS", arch="x86_64"), Linux(slug="debian-11", display_name="Debian 11", arch="x86_64"), Linux(slug="debian-11-arm64", display_name="Debian 11 Arm64", arch="arm64"), Linux(slug="debian-12", display_name="Debian 12", arch="x86_64"),