release.yml 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. name: Build and publish Python package
  2. # Least-privilege default for every job; the publish job grants itself id-token below.
  3. permissions:
  4. contents: read
  5. on:
  6. pull_request:
  7. paths:
  8. - '.github/workflows/release.yml'
  9. - 'pyproject.toml'
  10. - 'base_requirements.txt'
  11. - 'requirements.txt'
  12. - 'upgrade.sh'
  13. - 'contrib/**'
  14. - 'docs/**'
  15. - 'mkdocs.yml'
  16. - 'netbox/**'
  17. - 'scripts/packaging/**'
  18. - 'scripts/verify_*.py'
  19. - 'scripts/smoketest_configuration.py'
  20. push:
  21. tags:
  22. - 'v*'
  23. workflow_dispatch:
  24. jobs:
  25. build:
  26. name: Build package artifacts
  27. runs-on: ubuntu-latest
  28. steps:
  29. - name: Check out repository
  30. uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
  31. with:
  32. persist-credentials: false
  33. - name: Set up Python
  34. uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
  35. with:
  36. python-version: '3.12'
  37. cache: pip
  38. - name: Install build tooling
  39. run: python -m pip install --upgrade build twine
  40. - name: Build sdist and wheel
  41. run: python -m build
  42. - name: Check package metadata
  43. run: twine check dist/*
  44. - name: Upload package artifacts
  45. uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
  46. with:
  47. name: python-package-distributions
  48. path: dist/
  49. if-no-files-found: error
  50. verify-dependencies:
  51. name: Verify dependency pins are in sync
  52. runs-on: ubuntu-latest
  53. needs: build
  54. steps:
  55. - name: Check out repository
  56. uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
  57. with:
  58. persist-credentials: false
  59. - name: Set up Python
  60. uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
  61. with:
  62. python-version: '3.12'
  63. cache: pip
  64. - name: Install packaging
  65. run: python -m pip install packaging
  66. - name: Verify requirements.txt is consistent with base_requirements.txt
  67. run: python scripts/verify_dependencies.py
  68. - name: Download package artifacts
  69. uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
  70. with:
  71. name: python-package-distributions
  72. path: dist/
  73. - name: Verify wheel Requires-Dist matches requirements.txt
  74. run: python scripts/verify_wheel_metadata.py dist/*.whl
  75. - name: Verify wheel excludes live configuration files
  76. run: python scripts/verify_wheel_contents.py dist/*.whl
  77. verify-sdist:
  78. name: Verify the sdist builds a wheel
  79. runs-on: ubuntu-latest
  80. needs: build
  81. steps:
  82. - name: Check out repository
  83. uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
  84. with:
  85. persist-credentials: false
  86. - name: Set up Python
  87. uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
  88. with:
  89. python-version: '3.12'
  90. cache: pip
  91. - name: Install tooling
  92. run: python -m pip install --upgrade pip packaging
  93. - name: Download package artifacts
  94. uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
  95. with:
  96. name: python-package-distributions
  97. path: dist/
  98. - name: Verify the sdist contents
  99. run: |
  100. python scripts/verify_sdist_contents.py dist/*.tar.gz
  101. - name: Build a wheel from the sdist
  102. run: |
  103. python -m pip wheel --no-deps dist/*.tar.gz -w sdist-wheel/
  104. - name: Verify the sdist-built wheel
  105. run: |
  106. python scripts/verify_wheel_metadata.py sdist-wheel/*.whl
  107. python scripts/verify_wheel_contents.py sdist-wheel/*.whl
  108. cli-smoke-test:
  109. name: Smoke test wheel CLI (no dependencies)
  110. runs-on: ubuntu-latest
  111. needs: build
  112. # The pre-configuration CLI paths are stdlib-only, so a --no-deps install suffices.
  113. # Unlike smoke-test, this job also runs on pull requests.
  114. steps:
  115. - name: Set up Python
  116. uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
  117. with:
  118. python-version: '3.12'
  119. - name: Download package artifacts
  120. uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
  121. with:
  122. name: python-package-distributions
  123. path: dist/
  124. - name: Install wheel without dependencies
  125. run: |
  126. python -m venv /tmp/netbox-cli-venv
  127. /tmp/netbox-cli-venv/bin/python -m pip install --no-deps dist/*.whl
  128. - name: Exercise the pre-configuration CLI
  129. run: |
  130. /tmp/netbox-cli-venv/bin/netbox --version
  131. /tmp/netbox-cli-venv/bin/netbox version
  132. /tmp/netbox-cli-venv/bin/netbox secret-key | grep -Eq '^.{50}$' || { echo "secret-key not 50 chars"; exit 1; }
  133. - name: Smoke-test netbox setup from the wheel
  134. run: |
  135. /tmp/netbox-cli-venv/bin/netbox setup --target /tmp/nbroot
  136. for f in /tmp/nbroot/conf/__init__.py /tmp/nbroot/conf/configuration.py /tmp/nbroot/local_requirements.txt; do
  137. test -f "$f" || { echo "missing $f"; exit 1; }
  138. done
  139. for f in apache.conf gunicorn.py netbox-rq.service netbox.env netbox.service nginx.conf uwsgi.ini; do
  140. test -s "/tmp/nbroot/contrib/$f" || { echo "missing or empty contrib/$f"; exit 1; }
  141. done
  142. smoke-test:
  143. name: Smoke test wheel install
  144. runs-on: ubuntu-latest
  145. needs: build
  146. # The wheel install + database migration is expensive; only run it for tag
  147. # pushes and manual dispatch, not on every packaging-related pull request.
  148. # cli-smoke-test provides lightweight, dependency-free CLI coverage on every PR instead.
  149. if: github.event_name != 'pull_request'
  150. services:
  151. postgres:
  152. image: postgres:17
  153. env:
  154. POSTGRES_DB: netbox
  155. POSTGRES_USER: netbox
  156. POSTGRES_PASSWORD: netbox
  157. ports:
  158. - 5432:5432
  159. options: >-
  160. --health-cmd "pg_isready -U netbox -d netbox"
  161. --health-interval 10s
  162. --health-timeout 5s
  163. --health-retries 5
  164. redis:
  165. image: redis:7
  166. ports:
  167. - 6379:6379
  168. options: >-
  169. --health-cmd "redis-cli ping"
  170. --health-interval 10s
  171. --health-timeout 5s
  172. --health-retries 5
  173. env:
  174. NETBOX_CONFIGURATION: smoketest_configuration
  175. NETBOX_SMOKETEST_BASE: /tmp/netbox-smoketest
  176. POSTGRES_DB: netbox
  177. POSTGRES_USER: netbox
  178. POSTGRES_PASSWORD: netbox
  179. POSTGRES_HOST: 127.0.0.1
  180. POSTGRES_PORT: 5432
  181. REDIS_HOST: 127.0.0.1
  182. REDIS_PORT: 6379
  183. steps:
  184. - name: Check out repository
  185. uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
  186. with:
  187. persist-credentials: false
  188. - name: Set up Python
  189. uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
  190. with:
  191. python-version: '3.12'
  192. cache: pip
  193. - name: Download package artifacts
  194. uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
  195. with:
  196. name: python-package-distributions
  197. path: dist/
  198. - name: Install system build dependencies for psycopg
  199. run: sudo apt-get update && sudo apt-get install -y libpq-dev
  200. - name: Install wheel into a clean virtual environment
  201. run: |
  202. python -m venv /tmp/netbox-wheel-venv
  203. /tmp/netbox-wheel-venv/bin/python -m pip install --upgrade pip
  204. /tmp/netbox-wheel-venv/bin/python -m pip install dist/*.whl
  205. - name: Run NetBox smoke checks
  206. env:
  207. PYTHONPATH: ${{ github.workspace }}/scripts
  208. run: |
  209. # The docs build runs `zensical` as a subprocess, which resolves via PATH;
  210. # prepending the venv bin is equivalent to activating it (as upgrade.sh does).
  211. export PATH="/tmp/netbox-wheel-venv/bin:$PATH"
  212. /tmp/netbox-wheel-venv/bin/netbox check
  213. upgrade_output=$(/tmp/netbox-wheel-venv/bin/netbox upgrade --no-input --build-docs)
  214. echo "$upgrade_output"
  215. ! grep -q 'Skipping documentation build' <<< "$upgrade_output" || { echo "documentation build was skipped"; exit 1; }
  216. test -f /tmp/netbox-smoketest/static/docs/index.html || { echo "documentation was not collected to STATIC_ROOT"; exit 1; }
  217. - name: Smoke-test netbox setup from the wheel
  218. run: |
  219. /tmp/netbox-wheel-venv/bin/netbox setup --target /tmp/nbroot
  220. diff -q /tmp/nbroot/conf/configuration.py netbox/netbox/configuration_example.py
  221. for f in apache.conf gunicorn.py netbox-rq.service netbox.env netbox.service nginx.conf uwsgi.ini; do
  222. diff -q "/tmp/nbroot/contrib/$f" "contrib/$f"
  223. done
  224. publish-testpypi:
  225. name: Publish package to Test PyPI
  226. runs-on: ubuntu-latest
  227. needs: [smoke-test, cli-smoke-test, verify-dependencies, verify-sdist]
  228. # Publishing always requires a v* tag ref: a tag push publishes to Test PyPI
  229. # automatically, and a manual dispatch does the same when the chosen ref is a v* tag.
  230. # Branch dispatches still run the build, verify, and smoke-test jobs (a useful dry run)
  231. # but the publish job is skipped. Production PyPI publishing is intentionally absent
  232. # during the v4.6.x preview; it arrives with the v4.7.0 feature branch.
  233. # startsWith() only routes to this job (workflow `if:` expressions cannot regex-match);
  234. # the exact tag format (v<release.yaml version>) is enforced below by the "Enforce
  235. # release tag format" step and scripts/verify_release_tag.py before any upload.
  236. if: startsWith(github.ref, 'refs/tags/v') && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
  237. environment:
  238. name: testpypi
  239. url: https://test.pypi.org/p/netbox
  240. permissions:
  241. contents: read
  242. id-token: write
  243. steps:
  244. - name: Enforce release tag format
  245. env:
  246. TAG: ${{ github.ref_name }}
  247. run: |
  248. [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]] || {
  249. echo "Ref '$TAG' is not a release tag of the form vX.Y.Z[-designation]"
  250. exit 1
  251. }
  252. - name: Check out repository
  253. uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
  254. with:
  255. persist-credentials: false
  256. - name: Set up Python
  257. uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
  258. with:
  259. python-version: '3.12'
  260. - name: Install tooling
  261. run: python -m pip install --upgrade pip packaging
  262. - name: Download package artifacts
  263. uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
  264. with:
  265. name: python-package-distributions
  266. path: dist/
  267. - name: Verify the git tag matches the built version
  268. run: python scripts/verify_release_tag.py "${{ github.ref_name }}" dist/*.whl
  269. - name: Publish package distributions to Test PyPI
  270. uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
  271. with:
  272. repository-url: https://test.pypi.org/legacy/