| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- name: Build and publish Python package
- # Least-privilege default for every job; the publish job grants itself id-token below.
- permissions:
- contents: read
- on:
- pull_request:
- paths:
- - '.github/workflows/release.yml'
- - 'pyproject.toml'
- - 'base_requirements.txt'
- - 'requirements.txt'
- - 'upgrade.sh'
- - 'contrib/**'
- - 'docs/**'
- - 'mkdocs.yml'
- - 'netbox/**'
- - 'scripts/packaging/**'
- - 'scripts/verify_*.py'
- - 'scripts/smoketest_configuration.py'
- push:
- tags:
- - 'v*'
- workflow_dispatch:
- jobs:
- build:
- name: Build package artifacts
- runs-on: ubuntu-latest
- steps:
- - name: Check out repository
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
- - name: Set up Python
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- with:
- python-version: '3.12'
- cache: pip
- - name: Install build tooling
- run: python -m pip install --upgrade build twine
- - name: Build sdist and wheel
- run: python -m build
- - name: Check package metadata
- run: twine check dist/*
- - name: Upload package artifacts
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
- with:
- name: python-package-distributions
- path: dist/
- if-no-files-found: error
- verify-dependencies:
- name: Verify dependency pins are in sync
- runs-on: ubuntu-latest
- needs: build
- steps:
- - name: Check out repository
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
- - name: Set up Python
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- with:
- python-version: '3.12'
- cache: pip
- - name: Install packaging
- run: python -m pip install packaging
- - name: Verify requirements.txt is consistent with base_requirements.txt
- run: python scripts/verify_dependencies.py
- - name: Download package artifacts
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with:
- name: python-package-distributions
- path: dist/
- - name: Verify wheel Requires-Dist matches requirements.txt
- run: python scripts/verify_wheel_metadata.py dist/*.whl
- - name: Verify wheel excludes live configuration files
- run: python scripts/verify_wheel_contents.py dist/*.whl
- verify-sdist:
- name: Verify the sdist builds a wheel
- runs-on: ubuntu-latest
- needs: build
- steps:
- - name: Check out repository
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
- - name: Set up Python
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- with:
- python-version: '3.12'
- cache: pip
- - name: Install tooling
- run: python -m pip install --upgrade pip packaging
- - name: Download package artifacts
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with:
- name: python-package-distributions
- path: dist/
- - name: Verify the sdist contents
- run: |
- python scripts/verify_sdist_contents.py dist/*.tar.gz
- - name: Build a wheel from the sdist
- run: |
- python -m pip wheel --no-deps dist/*.tar.gz -w sdist-wheel/
- - name: Verify the sdist-built wheel
- run: |
- python scripts/verify_wheel_metadata.py sdist-wheel/*.whl
- python scripts/verify_wheel_contents.py sdist-wheel/*.whl
- cli-smoke-test:
- name: Smoke test wheel CLI (no dependencies)
- runs-on: ubuntu-latest
- needs: build
- # The pre-configuration CLI paths are stdlib-only, so a --no-deps install suffices.
- # Unlike smoke-test, this job also runs on pull requests.
- steps:
- - name: Set up Python
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- with:
- python-version: '3.12'
- - name: Download package artifacts
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with:
- name: python-package-distributions
- path: dist/
- - name: Install wheel without dependencies
- run: |
- python -m venv /tmp/netbox-cli-venv
- /tmp/netbox-cli-venv/bin/python -m pip install --no-deps dist/*.whl
- - name: Exercise the pre-configuration CLI
- run: |
- /tmp/netbox-cli-venv/bin/netbox --version
- /tmp/netbox-cli-venv/bin/netbox version
- /tmp/netbox-cli-venv/bin/netbox secret-key | grep -Eq '^.{50}$' || { echo "secret-key not 50 chars"; exit 1; }
- - name: Smoke-test netbox setup from the wheel
- run: |
- /tmp/netbox-cli-venv/bin/netbox setup --target /tmp/nbroot
- for f in /tmp/nbroot/conf/__init__.py /tmp/nbroot/conf/configuration.py /tmp/nbroot/local_requirements.txt; do
- test -f "$f" || { echo "missing $f"; exit 1; }
- done
- for f in apache.conf gunicorn.py netbox-rq.service netbox.env netbox.service nginx.conf uwsgi.ini; do
- test -s "/tmp/nbroot/contrib/$f" || { echo "missing or empty contrib/$f"; exit 1; }
- done
- smoke-test:
- name: Smoke test wheel install
- runs-on: ubuntu-latest
- needs: build
- # The wheel install + database migration is expensive; only run it for tag
- # pushes and manual dispatch, not on every packaging-related pull request.
- # cli-smoke-test provides lightweight, dependency-free CLI coverage on every PR instead.
- if: github.event_name != 'pull_request'
- services:
- postgres:
- image: postgres:17
- env:
- POSTGRES_DB: netbox
- POSTGRES_USER: netbox
- POSTGRES_PASSWORD: netbox
- ports:
- - 5432:5432
- options: >-
- --health-cmd "pg_isready -U netbox -d netbox"
- --health-interval 10s
- --health-timeout 5s
- --health-retries 5
- redis:
- image: redis:7
- ports:
- - 6379:6379
- options: >-
- --health-cmd "redis-cli ping"
- --health-interval 10s
- --health-timeout 5s
- --health-retries 5
- env:
- NETBOX_CONFIGURATION: smoketest_configuration
- NETBOX_SMOKETEST_BASE: /tmp/netbox-smoketest
- POSTGRES_DB: netbox
- POSTGRES_USER: netbox
- POSTGRES_PASSWORD: netbox
- POSTGRES_HOST: 127.0.0.1
- POSTGRES_PORT: 5432
- REDIS_HOST: 127.0.0.1
- REDIS_PORT: 6379
- steps:
- - name: Check out repository
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
- - name: Set up Python
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- with:
- python-version: '3.12'
- cache: pip
- - name: Download package artifacts
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with:
- name: python-package-distributions
- path: dist/
- - name: Install system build dependencies for psycopg
- run: sudo apt-get update && sudo apt-get install -y libpq-dev
- - name: Install wheel into a clean virtual environment
- run: |
- python -m venv /tmp/netbox-wheel-venv
- /tmp/netbox-wheel-venv/bin/python -m pip install --upgrade pip
- /tmp/netbox-wheel-venv/bin/python -m pip install dist/*.whl
- - name: Run NetBox smoke checks
- env:
- PYTHONPATH: ${{ github.workspace }}/scripts
- run: |
- # The docs build runs `zensical` as a subprocess, which resolves via PATH;
- # prepending the venv bin is equivalent to activating it (as upgrade.sh does).
- export PATH="/tmp/netbox-wheel-venv/bin:$PATH"
- /tmp/netbox-wheel-venv/bin/netbox check
- upgrade_output=$(/tmp/netbox-wheel-venv/bin/netbox upgrade --no-input --build-docs)
- echo "$upgrade_output"
- ! grep -q 'Skipping documentation build' <<< "$upgrade_output" || { echo "documentation build was skipped"; exit 1; }
- test -f /tmp/netbox-smoketest/static/docs/index.html || { echo "documentation was not collected to STATIC_ROOT"; exit 1; }
- - name: Smoke-test netbox setup from the wheel
- run: |
- /tmp/netbox-wheel-venv/bin/netbox setup --target /tmp/nbroot
- diff -q /tmp/nbroot/conf/configuration.py netbox/netbox/configuration_example.py
- for f in apache.conf gunicorn.py netbox-rq.service netbox.env netbox.service nginx.conf uwsgi.ini; do
- diff -q "/tmp/nbroot/contrib/$f" "contrib/$f"
- done
- publish-testpypi:
- name: Publish package to Test PyPI
- runs-on: ubuntu-latest
- needs: [smoke-test, cli-smoke-test, verify-dependencies, verify-sdist]
- # Publishing always requires a v* tag ref: a tag push publishes to Test PyPI
- # automatically, and a manual dispatch does the same when the chosen ref is a v* tag.
- # Branch dispatches still run the build, verify, and smoke-test jobs (a useful dry run)
- # but the publish job is skipped. Production PyPI publishing is intentionally absent
- # during the v4.6.x preview; it arrives with the v4.7.0 feature branch.
- # startsWith() only routes to this job (workflow `if:` expressions cannot regex-match);
- # the exact tag format (v<release.yaml version>) is enforced below by the "Enforce
- # release tag format" step and scripts/verify_release_tag.py before any upload.
- if: startsWith(github.ref, 'refs/tags/v') && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
- environment:
- name: testpypi
- url: https://test.pypi.org/p/netbox
- permissions:
- contents: read
- id-token: write
- steps:
- - name: Enforce release tag format
- env:
- TAG: ${{ github.ref_name }}
- run: |
- [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]] || {
- echo "Ref '$TAG' is not a release tag of the form vX.Y.Z[-designation]"
- exit 1
- }
- - name: Check out repository
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
- - name: Set up Python
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- with:
- python-version: '3.12'
- - name: Install tooling
- run: python -m pip install --upgrade pip packaging
- - name: Download package artifacts
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with:
- name: python-package-distributions
- path: dist/
- - name: Verify the git tag matches the built version
- run: python scripts/verify_release_tag.py "${{ github.ref_name }}" dist/*.whl
- - name: Publish package distributions to Test PyPI
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
- with:
- repository-url: https://test.pypi.org/legacy/
|