ci.yml 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. ---
  2. name: CI
  3. on:
  4. push:
  5. branches:
  6. - main
  7. - feature
  8. paths-ignore:
  9. - '.github/ISSUE_TEMPLATE/**'
  10. - '.github/PULL_REQUEST_TEMPLATE.md'
  11. - 'contrib/**'
  12. - 'netbox/translations/**'
  13. pull_request:
  14. paths-ignore:
  15. - '.github/ISSUE_TEMPLATE/**'
  16. - '.github/PULL_REQUEST_TEMPLATE.md'
  17. - 'contrib/**'
  18. - 'netbox/translations/**'
  19. permissions:
  20. contents: read
  21. pull-requests: read
  22. # Add concurrency group to control job running
  23. concurrency:
  24. group: ${{ github.event_name }}-${{ github.ref }}-${{ github.actor }}
  25. cancel-in-progress: true
  26. jobs:
  27. # Detect which areas of the codebase changed so downstream jobs can be skipped
  28. # when their inputs haven't changed. Jobs that don't match any filter are shown
  29. # as "skipped" in GitHub's check list, which satisfies required-status-checks.
  30. changes:
  31. name: Detect changed files
  32. runs-on: ubuntu-latest
  33. outputs:
  34. python: ${{ steps.filter.outputs.python }}
  35. frontend: ${{ steps.filter.outputs.frontend }}
  36. docs: ${{ steps.filter.outputs.docs }}
  37. # Release PRs (feature into main, or a release-vX.Y.Z branch) always run the test suite
  38. # against what ships: the [c] psycopg build and a test database Django builds from scratch.
  39. release_pr: ${{ (github.head_ref == 'feature' && github.base_ref == 'main') || startsWith(github.head_ref, 'release-v') }}
  40. steps:
  41. - name: Check out repo
  42. uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
  43. - name: Detect changed files
  44. uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
  45. id: filter
  46. with:
  47. filters: |
  48. python:
  49. - 'netbox/**/*.py'
  50. - 'netbox/**/migrations/**'
  51. - 'requirements*.txt'
  52. - 'pyproject.toml'
  53. - '.github/workflows/ci.yml'
  54. frontend:
  55. - 'netbox/project-static/**'
  56. - '.github/workflows/ci.yml'
  57. docs:
  58. - 'docs/**'
  59. - 'mkdocs.yml'
  60. - '.github/workflows/ci.yml'
  61. lint:
  62. name: Lint (Python)
  63. needs: changes
  64. if: needs.changes.result == 'success' && needs.changes.outputs.python == 'true'
  65. runs-on: ubuntu-latest
  66. steps:
  67. - name: Check out repo
  68. uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
  69. - name: Check Python linting & PEP8 compliance
  70. uses: astral-sh/ruff-action@278981a28ce3188b1e39527901f38254bf3aac89 # v4.1.0
  71. with:
  72. version: "0.15.20"
  73. args: "check --output-format=github"
  74. src: "netbox/"
  75. test:
  76. name: >-
  77. Tests (Python ${{ matrix.python-version }}${{ matrix.coverage && ', coverage' || '' }})
  78. needs: changes
  79. if: >-
  80. needs.changes.result == 'success'
  81. && (needs.changes.outputs.python == 'true' || needs.changes.outputs.release_pr == 'true')
  82. runs-on: ubuntu-latest
  83. env:
  84. NETBOX_CONFIGURATION: netbox.configuration_testing
  85. RELEASE_PR: ${{ needs.changes.outputs.release_pr }}
  86. strategy:
  87. matrix:
  88. python-version: ['3.12', '3.13', '3.14']
  89. include:
  90. - coverage: false
  91. # Run coverage only once, using the Python 3.14 job.
  92. - python-version: '3.14'
  93. coverage: true
  94. services:
  95. redis:
  96. image: redis:8
  97. ports:
  98. - 6379:6379
  99. postgres:
  100. image: postgres:18
  101. env:
  102. POSTGRES_USER: netbox
  103. POSTGRES_PASSWORD: netbox
  104. options: >-
  105. --health-cmd pg_isready
  106. --health-interval 10s
  107. --health-timeout 5s
  108. --health-retries 5
  109. ports:
  110. - 5432:5432
  111. steps:
  112. - name: Check out repo
  113. uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
  114. - name: Set up Python ${{ matrix.python-version }}
  115. uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
  116. with:
  117. python-version: ${{ matrix.python-version }}
  118. cache: pip
  119. cache-dependency-path: |
  120. requirements.txt
  121. .github/workflows/ci.yml
  122. - name: Select the psycopg implementation
  123. run: |
  124. if [ "$RELEASE_PR" = true ]; then
  125. echo "PSYCOPG_IMPL=c" >> "$GITHUB_ENV"
  126. else
  127. # Prebuilt wheels skip compiling against libpq.
  128. sed -i 's/^psycopg\[c,pool\]==/psycopg[binary,pool]==/' requirements.txt
  129. grep -q '^psycopg\[binary,pool\]==' requirements.txt \
  130. || { echo "::error::requirements.txt no longer pins psycopg[c,pool]=="; exit 1; }
  131. echo "PSYCOPG_IMPL=binary" >> "$GITHUB_ENV"
  132. fi
  133. - name: Install dependencies
  134. run: |
  135. python -m pip install --upgrade pip
  136. pip install -r requirements.txt
  137. pip install -r requirements_testing.txt
  138. - name: Check for missing migrations
  139. run: python netbox/manage.py makemigrations --check
  140. # Copy frontend-generated files into STATIC_ROOT before SVG rendering
  141. # tests read their CSS directly.
  142. - name: Collect static files
  143. run: python netbox/manage.py collectstatic --no-input
  144. - name: Fingerprint the schema inputs
  145. id: schema
  146. if: env.RELEASE_PR != 'true'
  147. run: |
  148. # Path and blob of every file that shapes the migrated schema, so renames count too.
  149. inputs=$(git ls-files --stage -- \
  150. .github/workflows/ci.yml \
  151. requirements.txt \
  152. netbox/netbox/settings.py \
  153. netbox/netbox/configuration_testing.py \
  154. netbox/core/signals.py \
  155. ':(glob)netbox/**/migrations/**' \
  156. ':(glob)netbox/*/fields.py' \
  157. netbox/netbox/models/ltree.py \
  158. netbox/ipam/lookups.py \
  159. netbox/utilities/query_functions.py \
  160. netbox/utilities/ltree.py \
  161. netbox/utilities/migration.py \
  162. netbox/utilities/mptt_to_ltree.py)
  163. echo "hash=$(sha256sum <<< "$inputs" | cut -c1-32)" >> "$GITHUB_OUTPUT"
  164. - name: Restore the migrated test database
  165. id: test-db
  166. if: env.RELEASE_PR != 'true'
  167. uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
  168. with:
  169. path: ${{ runner.temp }}/test_netbox.dump
  170. key: test-db-py${{ matrix.python-version }}-${{ steps.schema.outputs.hash }}
  171. # Push runs migrate from scratch, so every merge re-validates the migration chain.
  172. lookup-only: ${{ github.event_name == 'push' }}
  173. - name: Build the migrated test database
  174. if: >-
  175. env.RELEASE_PR != 'true'
  176. && (github.event_name == 'push' || steps.test-db.outputs.cache-hit != 'true')
  177. env:
  178. POSTGRES_CONTAINER: ${{ job.services.postgres.id }}
  179. run: |
  180. python netbox/manage.py migrate --no-input --run-syncdb
  181. docker exec "$POSTGRES_CONTAINER" pg_dump -U netbox -Fc netbox > "$RUNNER_TEMP/test_netbox.dump"
  182. - name: Load the test database
  183. if: env.RELEASE_PR != 'true'
  184. env:
  185. POSTGRES_CONTAINER: ${{ job.services.postgres.id }}
  186. run: |
  187. docker exec "$POSTGRES_CONTAINER" createdb -U netbox test_netbox
  188. docker exec -i "$POSTGRES_CONTAINER" pg_restore -U netbox -d test_netbox --exit-on-error \
  189. < "$RUNNER_TEMP/test_netbox.dump"
  190. - name: Cache the migrated test database
  191. if: env.RELEASE_PR != 'true' && steps.test-db.outputs.cache-hit != 'true'
  192. uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
  193. with:
  194. path: ${{ runner.temp }}/test_netbox.dump
  195. key: ${{ steps.test-db.outputs.cache-primary-key }}
  196. - name: Run tests
  197. if: ${{ ! matrix.coverage }}
  198. run: python netbox/manage.py test netbox/ --parallel --keepdb --timing
  199. - name: Run tests with coverage
  200. if: ${{ matrix.coverage }}
  201. run: coverage run netbox/manage.py test netbox/ --parallel --keepdb --timing
  202. - name: Combine coverage data
  203. if: ${{ matrix.coverage }}
  204. run: coverage combine
  205. - name: Show coverage report
  206. if: ${{ matrix.coverage }}
  207. run: coverage report
  208. frontend:
  209. name: Frontend
  210. needs: changes
  211. if: needs.changes.result == 'success' && needs.changes.outputs.frontend == 'true'
  212. runs-on: ubuntu-latest
  213. steps:
  214. - name: Check out repo
  215. uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
  216. - name: Use Node.js 20.x
  217. uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
  218. with:
  219. node-version: '20.x'
  220. - name: Install Yarn Package Manager
  221. run: npm install -g yarn
  222. - name: Setup Node.js with Yarn Caching
  223. uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
  224. with:
  225. node-version: '20.x'
  226. cache: yarn
  227. cache-dependency-path: netbox/project-static/yarn.lock
  228. - name: Install Frontend Dependencies
  229. run: yarn --cwd netbox/project-static
  230. - name: Validate TypeScript and run ESLint
  231. run: yarn --cwd netbox/project-static validate
  232. - name: Validate formatting
  233. run: yarn --cwd netbox/project-static validate:formatting
  234. - name: Validate Static Asset Integrity
  235. run: scripts/verify-bundles.sh
  236. docs:
  237. name: Documentation
  238. needs: changes
  239. if: needs.changes.result == 'success' && needs.changes.outputs.docs == 'true'
  240. runs-on: ubuntu-latest
  241. steps:
  242. - name: Check out repo
  243. uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
  244. - name: Set up Python 3.12
  245. uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
  246. with:
  247. python-version: '3.12'
  248. cache: pip
  249. cache-dependency-path: |
  250. requirements.txt
  251. .github/workflows/ci.yml
  252. - name: Install dependencies
  253. run: |
  254. # Shares the 3.12 test job's pip cache entry.
  255. sed -i 's/^psycopg\[c,pool\]==/psycopg[binary,pool]==/' requirements.txt
  256. python -m pip install --upgrade pip
  257. pip install -r requirements.txt
  258. - name: Build documentation
  259. run: zensical build