diff --git a/changelog/+minmaxsalt.changed.md b/changelog/+minmaxsalt.changed.md new file mode 100644 index 0000000..57bbb7a --- /dev/null +++ b/changelog/+minmaxsalt.changed.md @@ -0,0 +1 @@ +Ensured minimum and maximum supported Salt versions always at least follow their default values (supported versions at the time of the template release) when updating the template. diff --git a/changelog/+workflows.removed.md b/changelog/+workflows.removed.md new file mode 100644 index 0000000..7f2509a --- /dev/null +++ b/changelog/+workflows.removed.md @@ -0,0 +1 @@ +Dropped `workflows` question and `basic` and `org` workflow variants. All projects use the `enhanced` workflows from now on. diff --git a/copier.yml b/copier.yml index 0836423..22a21ae 100644 --- a/copier.yml +++ b/copier.yml @@ -209,6 +209,9 @@ max_salt_version: {%- endif -%} {%- endif -%} {%- endif -%} + {%- if (max_salt_version | float) < (salt_version | float) -%} + Maximum version needs to be at least {{ salt_version }} + {%- endif -%} no_saltext_namespace: type: bool @@ -226,18 +229,7 @@ test_containers: help: Add support for test containers in functional/integration tests default: false -workflows: - type: str - help: Choose which workflow style to create - default: '{{ "org" if "github.com/salt-extensions/" in source_url else "enhanced" }}' - choices: - basic (== official creation tool): basic - enhanced (~ organization, but local): enhanced - organization (rely on centralized artifacts from salt-extensions org): org - when: '{{ "github.com" in source_url }}' - os_support: - when: '{{ workflows == "enhanced" }}' type: str multiselect: true help: Supported OS. Influences where tests run. @@ -265,7 +257,7 @@ deploy_docs: never: never when tagging a release: release all events on `main` (rolling): rolling - when: '{{ "github.com" in source_url and workflows != "basic" }}' + when: '{{ "github.com" in source_url }}' docs_url: type: str diff --git a/docs/ref/layout.md b/docs/ref/layout.md index 270e618..99de6a3 100644 --- a/docs/ref/layout.md +++ b/docs/ref/layout.md @@ -36,26 +36,22 @@ Contains GitHub-related configurations and workflows. This directory is only pre :::{path} .github/workflows ::: ### `.github/workflows` -Houses GitHub Actions {question}`workflows`. +Houses GitHub Actions [workflows](workflows-target). :::{path} .github/workflows/ci.yml ::: #### `.github/workflows/ci.yml` A meta-workflow that triggers other workflows based on inputs. -:::{important} -Only present when {question}`workflows` == `enhanced`. -::: - :::{path} .github/workflows/pr.yml ::: #### `.github/workflows/pr.yml` -Handles workflows for Pull Requests and pushes to the `main` branch. Depending on {question}`workflows`, it either calls centralized workflows in [salt-extensions/central-artifacts](https://github.com/salt-extensions/central-artifacts/tree/main/.github/workflows) or local workflows in {path}`ci.yml <.github/workflows/ci.yml>`. +Handles workflows for Pull Requests and pushes to the `main` branch. Delegates to workflows in {path}`ci.yml <.github/workflows/ci.yml>`. :::{path} .github/workflows/tag.yml ::: #### `.github/workflows/tag.yml` -Triggered by [tag pushes](publishing-target) for tags beginning with `v`. Similar to {path}`pr.yml <.github/workflows/pr.yml>`, it either calls centralized workflows or local ones in {path}`ci.yml <.github/workflows/ci.yml>`. +Triggered by [tag pushes](publishing-target) for tags beginning with `v`. Similar to {path}`pr.yml <.github/workflows/pr.yml>`, it delegates to workflows in {path}`ci.yml <.github/workflows/ci.yml>`. :::{path} changelog ::: diff --git a/docs/ref/questions.md b/docs/ref/questions.md index fb045be..ef28f1c 100644 --- a/docs/ref/questions.md +++ b/docs/ref/questions.md @@ -110,11 +110,7 @@ The minimum Python version to support. Also affects pre-commit autoformatting ho :::{question} max_salt_version ::: ## `max_salt_version` -The maximum Salt version to support. - -:::{hint} -This is only relevant when non-centralized {question}`workflows` are used, as it affects the Salt versions tests are run against. -::: +The maximum Salt version to support. Influences the Salt versions tests are run against. :::{question} no_saltext_namespace ::: @@ -131,28 +127,6 @@ Include test fixtures for Salt-SSH tests (`salt_ssh_cli` etc.). Defaults to true ## `test_containers` Add support for running containers in the test suite (for functional and integration tests). -:::{question} workflows -::: -## `workflows` -Select a GitHub Actions workflow style: - -**org** -: Rely on reusable (centralized) workflows from the `salt-extensions` GitHub organization. - -**enhanced** -: Equivalent workflows to `org`, but stored in the extension repository. Ensures you can modify the workflows. - -**basic** -: Provided for compatibility with the deprecated create-salt-extension tool (not recommended for new projects). - -:::{important} -If you are not hosting within the `salt-extensions` GitHub organization, you need to [setup required secrets](required-secrets-target) yourself. -::: - -:::{note} -Not asked if {question}`source_url` is not on GitHub. -::: - :::{question} os_support ::: ## `os_support` @@ -160,10 +134,6 @@ Select supported operating systems. Usually, you should leave the default of `Li This question influences on which systems the tests are run. -:::{note} -Only asked if `enhanced` {question}`workflows` were selected. -::: - :::{question} deploy_docs ::: ## `deploy_docs` @@ -189,7 +159,7 @@ The current workflows do not support versioned documentation. ::: :::{note} -Not asked if {question}`source_url` is not on GitHub or the `basic` {question}`workflows` have been selected. +Not asked if {question}`source_url` is not on GitHub. ::: :::{question} docs_url diff --git a/docs/topics/documenting/publishing.md b/docs/topics/documenting/publishing.md index ac2b761..ba4f380 100644 --- a/docs/topics/documenting/publishing.md +++ b/docs/topics/documenting/publishing.md @@ -1,8 +1,7 @@ (docs-publish-target)= # Publishing documentation -If your {question}`source_url` is on GitHub and you selected either `org` or -`enhanced` {question}`workflows`, you can automatically deploy your documentation to your repository's GitHub Pages site. This deployment is controlled by the {question}`deploy_docs` setting. +If your {question}`source_url` is on GitHub, you can automatically deploy your documentation to your repository's GitHub Pages site. This deployment is controlled by the {question}`deploy_docs` setting. (docs-publish-setup-target)= ## Setup diff --git a/docs/topics/workflows.md b/docs/topics/workflows.md index 3611f8c..ef59daa 100644 --- a/docs/topics/workflows.md +++ b/docs/topics/workflows.md @@ -3,17 +3,12 @@ Your Salt extension repository includes several workflows out of the box if your {question}`source_url` is on GitHub. -:::{note} -The workflows used within the `salt-extensions` organization (`org`) are equivalent -to the `enhanced` ones. -::: - ## Provided functions The workflows currently: * Ensure `pre-commit` checks pass -* Run the test suite +* Run the test suite and upload code coverage reports * Build the documentation * Optionally deploy built documentation to GitHub Pages * Optionally build and release your project to PyPI @@ -51,5 +46,5 @@ The built HTML documentation, also available for preview when triggered by a Pul ## Workflows call stack 1. {path}`.github/workflows/pr.yml` or {path}`.github/workflows/tag.yml` is triggered -2. {path}`.github/workflows/ci.yml` (or its equivalent centralized workflow) is called as the main entry point to CI +2. {path}`.github/workflows/ci.yml` is called as the main entry point to CI 3. Depending on the event and inputs, select additional workflows perform the necessary tasks. diff --git a/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows == 'enhanced' %}ci.yml{% endif %}.j2 b/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/ci.yml.j2 similarity index 100% rename from project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows == 'enhanced' %}ci.yml{% endif %}.j2 rename to project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/ci.yml.j2 diff --git a/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows == 'enhanced' %}deploy-docs-action.yml{% endif %}.j2 b/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/deploy-docs-action.yml.j2 similarity index 100% rename from project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows == 'enhanced' %}deploy-docs-action.yml{% endif %}.j2 rename to project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/deploy-docs-action.yml.j2 diff --git a/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows == 'enhanced' %}deploy-package-action.yml{% endif %}.j2 b/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/deploy-package-action.yml.j2 similarity index 100% rename from project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows == 'enhanced' %}deploy-package-action.yml{% endif %}.j2 rename to project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/deploy-package-action.yml.j2 diff --git a/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows == 'enhanced' %}docs-action.yml{% endif %}.j2 b/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/docs-action.yml.j2 similarity index 100% rename from project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows == 'enhanced' %}docs-action.yml{% endif %}.j2 rename to project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/docs-action.yml.j2 diff --git a/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows == 'enhanced' %}get-changed-files.yml{% endif %}.j2 b/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/get-changed-files.yml.j2 similarity index 100% rename from project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows == 'enhanced' %}get-changed-files.yml{% endif %}.j2 rename to project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/get-changed-files.yml.j2 diff --git a/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows == 'enhanced' %}package-action.yml{% endif %}.j2 b/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/package-action.yml.j2 similarity index 100% rename from project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows == 'enhanced' %}package-action.yml{% endif %}.j2 rename to project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/package-action.yml.j2 diff --git a/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows in ['org', 'enhanced'] %}pr.yml{% endif %}.j2 b/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/pr.yml.j2 similarity index 64% rename from project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows in ['org', 'enhanced'] %}pr.yml{% endif %}.j2 rename to project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/pr.yml.j2 index 375d5cc..d91c417 100644 --- a/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows in ['org', 'enhanced'] %}pr.yml{% endif %}.j2 +++ b/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/pr.yml.j2 @@ -1,12 +1,6 @@ +{%- raw -%} --- name: Pull Request or Push -{%- set base = "." %} -{%- set suffix = "" %} -{%- if workflows == "org" %} -{%- set base = "salt-extensions/central-artifacts" %} -{%- set suffix = "@main" %} -{%- endif %} -{%- raw %} on: push: @@ -20,11 +14,13 @@ jobs: call_central_workflow: name: CI {%- endraw %} - uses: {{ base }}/.github/workflows/ci.yml{{ suffix }} + uses: ./.github/workflows/ci.yml with: deploy-docs: {{ (deploy_docs == "rolling") | lower }} +{%- raw %} permissions: contents: write id-token: write pages: write pull-requests: read +{%- endraw %} diff --git a/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows == 'enhanced' %}pre-commit-action.yml{% endif %}.j2 b/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/pre-commit-action.yml.j2 similarity index 100% rename from project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows == 'enhanced' %}pre-commit-action.yml{% endif %}.j2 rename to project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/pre-commit-action.yml.j2 diff --git a/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows in ['org', 'enhanced'] %}tag.yml{% endif %}.j2 b/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/tag.yml.j2 similarity index 79% rename from project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows in ['org', 'enhanced'] %}tag.yml{% endif %}.j2 rename to project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/tag.yml.j2 index 2429e3c..102798d 100644 --- a/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows in ['org', 'enhanced'] %}tag.yml{% endif %}.j2 +++ b/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/tag.yml.j2 @@ -1,12 +1,6 @@ +{%- raw -%} --- name: Tagged Releases -{%- set base = "." %} -{%- set suffix = "" %} -{%- if workflows == "org" %} -{%- set base = "salt-extensions/central-artifacts" %} -{%- set suffix = "@main" %} -{%- endif %} -{%- raw %} on: push: @@ -33,7 +27,7 @@ jobs: call_central_workflow: needs: get_tag_version {%- endraw %} - uses: {{ base }}/.github/workflows/ci.yml{{ suffix }} + uses: ./.github/workflows/ci.yml with: deploy-docs: {{ (deploy_docs in ["rolling", "release"]) | lower }} {%- raw %} diff --git a/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows == 'enhanced' %}test-action.yml{% endif %}.j2 b/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/test-action.yml.j2 similarity index 100% rename from project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows == 'enhanced' %}test-action.yml{% endif %}.j2 rename to project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/test-action.yml.j2 diff --git a/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows == 'basic' %}test.yml{% endif %}.j2 b/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows == 'basic' %}test.yml{% endif %}.j2 deleted file mode 100644 index 7b44922..0000000 --- a/project/{% if 'github.com' in source_url %}.github{% endif %}/workflows/{% if workflows == 'basic' %}test.yml{% endif %}.j2 +++ /dev/null @@ -1,462 +0,0 @@ ---- -name: Testing - -on: [push, pull_request] - -jobs: - Pre-Commit: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: {{ python_requires[:2] | join(".") }} -{%- raw %} - - name: Set Cache Key - run: echo "PY=$(python --version --version | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV - - name: Install System Deps - run: | - sudo apt-get update - sudo apt-get install -y libxml2 libxml2-dev libxslt-dev - - uses: actions/cache@v3 - with: - path: ~/.cache/pre-commit - key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }} - - uses: pre-commit/action@v3.0.0 - - Docs: - runs-on: ubuntu-latest - needs: Pre-Commit - - timeout-minutes: 10 - - steps: - - uses: actions/checkout@v4 -{% endraw %} - - name: Set up Python {{ python_requires[:2] | join(".") }} For Nox - uses: actions/setup-python@v5 - with: - python-version: {{ python_requires[:2] | join(".") }} -{% raw %} - - name: Install Nox - run: | - python -m pip install --upgrade pip - pip install nox - - - name: Install Doc Requirements - run: | - nox --force-color -e docs --install-only - - - name: Build Docs - env: - SKIP_REQUIREMENTS_INSTALL: true - run: | - nox --force-color -e docs - - - name: Upload built docs as artifact - uses: actions/upload-artifact@v4 - with: - name: html-docs - path: docs/_build/html - - Linux: - runs-on: ubuntu-latest - needs: Pre-Commit - - timeout-minutes: 15 - - strategy: - fail-fast: false - max-parallel: 4 - matrix: - include: -{%- endraw %} -{%- for sver in range(salt_version_major, max_salt_version_major + 1) %} -{%- for pyver in range( - (python_requires[1], salt_python_support[sver]["min"][1]) | max, - (max_python_minor, salt_python_support[sver]["max"][1]) | min + 1 - ) %} - - {salt-version: "{{ sver }}.{{ ((salt_latest_point[sver], max_salt_version_minor) | min) if sver == max_salt_version_major else salt_latest_point[sver] }}", python-version: "3.{{ pyver }}"} -{%- endfor %} -{%- endfor %} -{%- raw %} - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 2 # coverage: Issue detecting commit SHA - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install Nox - run: | - python -m pip install --upgrade pip - pip install nox - - - name: Install Test Requirements - env: - SALT_REQUIREMENT: salt==${{ matrix.salt-version }} - run: | - nox --force-color -e tests-3 --install-only - - - name: Test - env: - SALT_REQUIREMENT: salt==${{ matrix.salt-version }} - SKIP_REQUIREMENTS_INSTALL: true - run: | - nox --force-color -e tests-3 -- -vv tests/ - - - name: Create CodeCov Flags - if: always() - id: codecov-flags - run: | - echo flags=$(python -c "import sys; print('{},{},salt_{}'.format('${{ runner.os }}'.replace('-latest', ''), 'py{}{}'.format(*sys.version_info), '_'.join(str(v) for v in '${{ matrix.salt-version }}'.replace('==', '_').split('.'))))") >> $GITHUB_OUTPUT - - - name: Upload Project Code Coverage - if: always() - continue-on-error: true - shell: bash - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - REPORT_FLAGS: ${{ steps.codecov-flags.outputs.flags }},project - REPORT_NAME: ${{ runner.os }}-Py${{ matrix.python-version }}-Salt${{ matrix.salt-version }}-project - REPORT_PATH: artifacts/coverage-project.xml - run: | - if [ ! -f codecov.sh ]; then - n=0 - until [ "$n" -ge 5 ] - do - if curl --max-time 30 -L https://codecov.io/bash --output codecov.sh; then - break - fi - n=$((n+1)) - sleep 15 - done - fi - if [ -f codecov.sh ]; then - n=0 - until [ "$n" -ge 5 ] - do - if bash codecov.sh -R $(pwd) -n "${REPORT_NAME}" -f "${REPORT_PATH}" -F "${REPORT_FLAGS}"; then - break - fi - n=$((n+1)) - sleep 15 - done - fi - - - name: Upload Tests Code Coverage - if: always() - continue-on-error: true - shell: bash - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - REPORT_FLAGS: ${{ steps.codecov-flags.outputs.flags }},tests - REPORT_NAME: ${{ runner.os }}-Py${{ matrix.python-version }}-Salt${{ matrix.salt-version }}-tests - REPORT_PATH: artifacts/coverage-tests.xml - run: | - if [ ! -f codecov.sh ]; then - n=0 - until [ "$n" -ge 5 ] - do - if curl --max-time 30 -L https://codecov.io/bash --output codecov.sh; then - break - fi - n=$((n+1)) - sleep 15 - done - fi - if [ -f codecov.sh ]; then - n=0 - until [ "$n" -ge 5 ] - do - if bash codecov.sh -R $(pwd) -n "${REPORT_NAME}" -f "${REPORT_PATH}" -F "${REPORT_FLAGS}"; then - break - fi - n=$((n+1)) - sleep 15 - done - fi - - - name: Upload Logs - if: always() - uses: actions/upload-artifact@v4 - with: - name: runtests-${{ runner.os }}-py${{ matrix.python-version }}-Salt${{ matrix.salt-version }}.log - path: artifacts/runtests-*.log - - Windows: - runs-on: windows-latest - needs: Pre-Commit - - timeout-minutes: 40 - - strategy: - fail-fast: false - max-parallel: 3 - matrix: - include: -{%- endraw %} -{%- for sver in range(salt_version_major, max_salt_version_major + 1) %} -{%- if sver != max_salt_version_major %} - - {salt-version: "{{ sver }}.{{ salt_latest_point[sver] }}", python-version: "{{ (salt_python_support[sver]["min"], python_requires) | max | join(".") }}"} -{%- else %} -{%- for pyver in range( - (python_requires[1], salt_python_support[sver]["min"][1]) | max, - (max_python_minor, salt_python_support[sver]["max"][1]) | min + 1 - ) %} - - {salt-version: "{{ sver }}.{{ (salt_latest_point[sver], max_salt_version_minor) | min }}", python-version: "3.{{ pyver }}"} -{%- endfor %} -{%- endif %} -{%- endfor %} -{%- raw %} - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 2 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install Nox - run: | - python -m pip install --upgrade pip - pip install nox - - - name: Install Test Requirements - shell: bash - env: - SALT_REQUIREMENT: salt==${{ matrix.salt-version }} - EXTRA_REQUIREMENTS_INSTALL: Cython - run: | - export PATH="/C/Program Files (x86)/Windows Kits/10/bin/10.0.18362.0/x64;$PATH" - nox --force-color -e tests-3 --install-only - - - name: Test - shell: bash - env: - SALT_REQUIREMENT: salt==${{ matrix.salt-version }} - SKIP_REQUIREMENTS_INSTALL: true - run: | - export PATH="/C/Program Files (x86)/Windows Kits/10/bin/10.0.18362.0/x64;$PATH" - nox --force-color -e tests-3 -- -vv tests/ - - - name: Create CodeCov Flags - if: always() - id: codecov-flags - run: | - echo flags=$(python -c "import sys; print('{},{},salt_{}'.format('${{ runner.os }}'.replace('-latest', ''), 'py{}{}'.format(*sys.version_info), '_'.join(str(v) for v in '${{ matrix.salt-version }}'.replace('==', '_').split('.'))))") >> $GITHUB_OUTPUT - - - name: Upload Project Code Coverage - if: always() - continue-on-error: true - shell: bash - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - REPORT_FLAGS: ${{ steps.codecov-flags.outputs.flags }},project - REPORT_NAME: ${{ runner.os }}-Py${{ matrix.python-version }}-Salt${{ matrix.salt-version }}-project - REPORT_PATH: artifacts/coverage-project.xml - run: | - if [ ! -f codecov.sh ]; then - n=0 - until [ "$n" -ge 5 ] - do - if curl --max-time 30 -L https://codecov.io/bash --output codecov.sh; then - break - fi - n=$((n+1)) - sleep 15 - done - fi - if [ -f codecov.sh ]; then - n=0 - until [ "$n" -ge 5 ] - do - if bash codecov.sh -R $(pwd) -n "${REPORT_NAME}" -f "${REPORT_PATH}" -F "${REPORT_FLAGS}"; then - break - fi - n=$((n+1)) - sleep 15 - done - fi - - - name: Upload Tests Code Coverage - if: always() - continue-on-error: true - shell: bash - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - REPORT_FLAGS: ${{ steps.codecov-flags.outputs.flags }},tests - REPORT_NAME: ${{ runner.os }}-Py${{ matrix.python-version }}-Salt${{ matrix.salt-version }}-tests - REPORT_PATH: artifacts/coverage-tests.xml - run: | - if [ ! -f codecov.sh ]; then - n=0 - until [ "$n" -ge 5 ] - do - if curl --max-time 30 -L https://codecov.io/bash --output codecov.sh; then - break - fi - n=$((n+1)) - sleep 15 - done - fi - if [ -f codecov.sh ]; then - n=0 - until [ "$n" -ge 5 ] - do - if bash codecov.sh -R $(pwd) -n "${REPORT_NAME}" -f "${REPORT_PATH}" -F "${REPORT_FLAGS}"; then - break - fi - n=$((n+1)) - sleep 15 - done - fi - - - name: Upload Logs - if: always() - uses: actions/upload-artifact@v4 - with: - name: runtests-${{ runner.os }}-py${{ matrix.python-version }}-Salt${{ matrix.salt-version }}.log - path: artifacts/runtests-*.log - - macOS: - runs-on: macOS-latest - needs: Pre-Commit - - timeout-minutes: 40 - - strategy: - fail-fast: false - max-parallel: 3 - matrix: - include: -{%- endraw %} -{%- for sver in range(salt_version_major, max_salt_version_major + 1) %} -{%- if sver != max_salt_version_major %} - - {salt-version: "{{ sver }}.{{ salt_latest_point[sver] }}", python-version: "{{ salt_python_support[sver]["max"] | join(".") }}"} -{%- else %} -{%- for pyver in range( - (python_requires[1], salt_python_support[sver]["min"][1]) | max, - (max_python_minor, salt_python_support[sver]["max"][1]) | min + 1 - ) %} - - {salt-version: "{{ sver }}.{{ (salt_latest_point[sver], max_salt_version_minor) | min }}", python-version: "3.{{ pyver }}"} -{%- endfor %} -{%- endif %} -{%- endfor %} -{%- raw %} - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 2 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install Nox - run: | - python -m pip install --upgrade pip - pip install nox - - - name: Install Test Requirements - env: - SALT_REQUIREMENT: salt==${{ matrix.salt-version }} - run: | - nox --force-color -e tests-3 --install-only - - - name: Test - env: - SALT_REQUIREMENT: salt==${{ matrix.salt-version }} - SKIP_REQUIREMENTS_INSTALL: true - run: | - nox --force-color -e tests-3 -- -vv tests/ - - - name: Create CodeCov Flags - if: always() - id: codecov-flags - run: | - echo flags=$(python -c "import sys; print('{},{},salt_{}'.format('${{ runner.os }}'.replace('-latest', ''), 'py{}{}'.format(*sys.version_info), '_'.join(str(v) for v in '${{ matrix.salt-version }}'.replace('==', '_').split('.'))))") >> $GITHUB_OUTPUT - - - name: Upload Project Code Coverage - if: always() - continue-on-error: true - shell: bash - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - REPORT_FLAGS: ${{ steps.codecov-flags.outputs.flags }},project - REPORT_NAME: ${{ runner.os }}-Py${{ matrix.python-version }}-Salt${{ matrix.salt-version }}-project - REPORT_PATH: artifacts/coverage-project.xml - run: | - if [ ! -f codecov.sh ]; then - n=0 - until [ "$n" -ge 5 ] - do - if curl --max-time 30 -L https://codecov.io/bash --output codecov.sh; then - break - fi - n=$((n+1)) - sleep 15 - done - fi - if [ -f codecov.sh ]; then - n=0 - until [ "$n" -ge 5 ] - do - if bash codecov.sh -R $(pwd) -n "${REPORT_NAME}" -f "${REPORT_PATH}" -F "${REPORT_FLAGS}"; then - break - fi - n=$((n+1)) - sleep 15 - done - fi - - - name: Upload Tests Code Coverage - if: always() - continue-on-error: true - shell: bash - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - REPORT_FLAGS: ${{ steps.codecov-flags.outputs.flags }},tests - REPORT_NAME: ${{ runner.os }}-Py${{ matrix.python-version }}-Salt${{ matrix.salt-version }}-tests - REPORT_PATH: artifacts/coverage-tests.xml - run: | - if [ ! -f codecov.sh ]; then - n=0 - until [ "$n" -ge 5 ] - do - if curl --max-time 30 -L https://codecov.io/bash --output codecov.sh; then - break - fi - n=$((n+1)) - sleep 15 - done - fi - if [ -f codecov.sh ]; then - n=0 - until [ "$n" -ge 5 ] - do - if bash codecov.sh -R $(pwd) -n "${REPORT_NAME}" -f "${REPORT_PATH}" -F "${REPORT_FLAGS}"; then - break - fi - n=$((n+1)) - sleep 15 - done - fi - - - name: Upload Logs - if: always() - uses: actions/upload-artifact@v4 - with: - name: runtests-${{ runner.os }}-py${{ matrix.python-version }}-Salt${{ matrix.salt-version }}.log - path: artifacts/runtests-*.log -{%- endraw %} diff --git a/tasks/migrations.py b/tasks/migrations.py index 5ccbc89..9ff1780 100644 --- a/tasks/migrations.py +++ b/tasks/migrations.py @@ -1,7 +1,48 @@ +""" +Run migrations between template versions during updates. +""" + from packaging.version import Version +from task_helpers.copier import load_data_yaml +from task_helpers.migrate import COPIER_CONF +from task_helpers.migrate import migration from task_helpers.migrate import run_migrations +from task_helpers.migrate import status +from task_helpers.migrate import sync_minimum_version from task_helpers.migrate import var_migration +# 0.5.0 migrates all projects to the enhanced workflows, which +# require accurate Salt versions to generate sensible test matrices. +# The default values always represent the extremes, so sync them +# during all updates from now on. This avoids having to create +# separate migrations for future updates. +sync_minimum_version(None, "max_salt_version") +sync_salt_version = sync_minimum_version(None, "salt_version") + + +@migration(None, "before", desc=False, after=sync_salt_version) +def ensure_minimum_python_requires(answers): + """ + Ensure the minimum Python version is respected during each update. + We cannot use sync_minimum_version for this because the default + value is computed in Jinja. Let's replicate the Jinja here. + """ + if "python_requires" not in answers: + return + + salt_python_support = load_data_yaml("salt_python_support") + selected_salt_version = float( + int(answers.get("salt_version", COPIER_CONF["salt_version"]["default"])) + ) + default = ".".join(str(x) for x in salt_python_support[selected_salt_version]["min"]) + current = answers["python_requires"] + + if Version(str(current)) < Version(str(default)): + new = type(current)(default) + status(f"Answer migration: Updating python_requires from {current!r} to {new!r}") + answers["python_requires"] = new + return answers + @var_migration("0.4.5", "max_salt_version") def migrate_045_max_salt_version(val): @@ -21,14 +62,5 @@ def migrate_037_docs_url(val): return ... -@var_migration("0.3.0", "python_requires") -def migrate_030_python_requires(val): - """ - Raise minimum Python version to 3.8. - """ - if Version(val) < Version("3.8"): - return "3.8" - - if __name__ == "__main__": run_migrations() diff --git a/tasks/task_helpers/copier.py b/tasks/task_helpers/copier.py new file mode 100644 index 0000000..6ca3e32 --- /dev/null +++ b/tasks/task_helpers/copier.py @@ -0,0 +1,24 @@ +from pathlib import Path + +import copier.template +import yaml + +TEMPLATE_ROOT = (Path(__file__).parent.parent.parent).resolve() + + +def load_copier_conf(): + """ + Load the complete Copier configuration. + """ + return copier.template.load_template_config(TEMPLATE_ROOT / "copier.yml") + + +def load_data_yaml(name): + """ + Load a file in the template root `data` directory. + """ + file = (TEMPLATE_ROOT / "data" / name).with_suffix(".yaml") + if not file.exists(): + raise OSError(f"The file '{file}' does not exist") + with open(file, encoding="utf-8") as f: + return yaml.safe_load(f) diff --git a/tasks/task_helpers/migrate.py b/tasks/task_helpers/migrate.py index 425050b..be20adf 100644 --- a/tasks/task_helpers/migrate.py +++ b/tasks/task_helpers/migrate.py @@ -2,6 +2,7 @@ from packaging.version import Version +from .copier import load_copier_conf from .pythonpath import project_tools with project_tools(): @@ -17,6 +18,7 @@ MIGRATIONS = [] +COPIER_CONF = load_copier_conf() def run_migrations(): @@ -51,7 +53,7 @@ def _run_migrations_after(answers): func(answers.copy()) -def migration(trigger, stage="after", desc=None): +def migration(trigger, stage="after", desc=None, after=None): """ Decorator for declaring a general migration. @@ -108,7 +110,12 @@ def wrapper(func): # Other decorators delegate to this one, only create a general # migration if it's not already another subtype if not isinstance(func, Migration): - func = Migration(func, desc=desc or func.__name__.replace("_", " ")) + nonlocal desc + if desc is None: + desc = func.__name__.replace("_", " ") + elif not desc: + desc = None + func = Migration(func, desc=desc) global MIGRATIONS MIGRATIONS.append((trigger_version, func)) return func @@ -116,7 +123,7 @@ def wrapper(func): return wrapper -def var_migration(trigger, varname): +def var_migration(trigger, varname, after=None): """ Decorator for declaring an answer migration, e.g. when raising a minimum version or changing an answer's type. @@ -142,25 +149,68 @@ def migrate_foo_100(val): """ def wrapper(func): - return migration(trigger, "before")(VarMigration(func, varname)) + return migration(trigger, "before")(VarMigration(func, varname, after=after)) return wrapper +def raise_minimum_version(trigger, varname, minimum): + """ + Register a migration to increase the value of an answer representing + a version to a minimum value. + + Examples: + + raise_minimum_version("1.0.0", "python", 3.8) + raise_minimum_version("1.0.0", "python", "3.8") + """ + + def _raise_minimum(var): + if Version(str(var)) < Version(str(minimum)): + return type(var)(minimum) + + return var_migration(trigger, varname)(_raise_minimum) + + +def sync_minimum_version(trigger, varname): + """ + Register a migration to increase the value of an answer representing + a version to a minimum value, determined by its default answer. + """ + default = COPIER_CONF[varname]["default"] + return raise_minimum_version(trigger, varname, default) + + class Migration: - def __init__(self, func, desc=None): + def __init__(self, func, desc=None, after=None): self.func = func self.desc = desc + after = after or [] + if not isinstance(after, list): + after = [after] + self.after = after def __call__(self, answers): if self.desc is not None: status(f"Running migration: {self.desc}") return self.func(answers) + def __lt__(self, other): + """ + Ensure we can sort the list of migrations, even if there + are multiple migrations for a single version. + + This also allows explicit ordering of migrations of the + same version (or those without one). + """ + if not isinstance(other, Migration): + raise TypeError(f"Cannot compare Migration to {other!r}") + return any(migration is self for migration in other.after) + class VarMigration(Migration): - def __init__(self, func, varname): - super().__init__(func) + def __init__(self, func, varname, after=None): + super().__init__(func, after=after) self.varname = varname def __call__(self, answers): diff --git a/tests/conftest.py b/tests/conftest.py index b3f0b83..504373e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -70,6 +70,7 @@ def source_url(project_name, request): @pytest.fixture def workflows(source_url, request): + # Dropped in release 0.5.0, but still needed for upgrade tests default = "org" if "github.com/salt-extensions/" in source_url else "enhanced" return getattr(request, "param", default) diff --git a/tests/test_basics.py b/tests/test_basics.py index 3d315bb..d088e99 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -40,7 +40,6 @@ def test_copy_works(copie, answers): _assert_worked(res) -@pytest.mark.parametrize("workflows", ("basic", "enhanced"), indirect=True) @pytest.mark.parametrize("salt_version", ("3006.5",), indirect=True) @pytest.mark.parametrize("max_salt_version", ("3007.0",), indirect=True) def test_copy_works_with_salt_minor_version(copie, answers): @@ -139,8 +138,6 @@ def _commit_with_pre_commit(venv, max_retry=3, message="initial commit"): raise AssertionError(msg) -# We need to test both org and enhanced workflows (with actionlint/shellcheck) -@pytest.mark.parametrize("source_url", ("org", "non_org"), indirect=True) def test_first_commit_works(project): """ Ensure the generated project can be committed after generation