2
0

ci.yml 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  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. # The fingerprint NetBoxTestRunner stamps on kept test databases (the migrations of all installed apps).
  145. - name: Fingerprint the migrations
  146. id: schema
  147. if: env.RELEASE_PR != 'true'
  148. run: |
  149. hash=$(python netbox/manage.py shell --no-imports \
  150. -c "from utilities.testing.runner import get_schema_fingerprint; print(get_schema_fingerprint())")
  151. [[ "$hash" =~ ^[0-9a-f]{64}$ ]] || { echo "::error::Unexpected fingerprint output: $hash"; exit 1; }
  152. echo "hash=$hash" >> "$GITHUB_OUTPUT"
  153. - name: Restore the migrated test database
  154. id: test-db
  155. if: env.RELEASE_PR != 'true'
  156. uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
  157. with:
  158. path: ${{ runner.temp }}/test_netbox.dump
  159. # Bump the version when the build procedure changes; keep pg in sync with the postgres service image.
  160. key: test-db-v1-pg18-py${{ matrix.python-version }}-${{ steps.schema.outputs.hash }}
  161. # Push runs migrate from scratch, so every merge re-validates the migration chain.
  162. lookup-only: ${{ github.event_name == 'push' }}
  163. - name: Build the migrated test database
  164. if: >-
  165. env.RELEASE_PR != 'true'
  166. && (github.event_name == 'push' || steps.test-db.outputs.cache-hit != 'true')
  167. env:
  168. POSTGRES_CONTAINER: ${{ job.services.postgres.id }}
  169. run: |
  170. python netbox/manage.py migrate --no-input --run-syncdb
  171. docker exec "$POSTGRES_CONTAINER" pg_dump -U netbox -Fc netbox > "$RUNNER_TEMP/test_netbox.dump"
  172. - name: Load the test database
  173. if: env.RELEASE_PR != 'true'
  174. env:
  175. POSTGRES_CONTAINER: ${{ job.services.postgres.id }}
  176. run: |
  177. docker exec "$POSTGRES_CONTAINER" createdb -U netbox test_netbox
  178. docker exec -i "$POSTGRES_CONTAINER" pg_restore -U netbox -d test_netbox --exit-on-error \
  179. < "$RUNNER_TEMP/test_netbox.dump"
  180. # Mark the restored database as current; NetBoxTestRunner otherwise rebuilds it. The cache key
  181. # includes this same fingerprint, so a restored dump always matches the current migrations.
  182. python netbox/manage.py shell --no-imports \
  183. -c "from utilities.testing.runner import stamp_test_database; stamp_test_database()"
  184. - name: Cache the migrated test database
  185. if: env.RELEASE_PR != 'true' && steps.test-db.outputs.cache-hit != 'true'
  186. uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
  187. with:
  188. path: ${{ runner.temp }}/test_netbox.dump
  189. key: ${{ steps.test-db.outputs.cache-primary-key }}
  190. - name: Run tests
  191. if: ${{ ! matrix.coverage }}
  192. run: python netbox/manage.py test netbox/ --parallel --keepdb --timing
  193. - name: Run tests with coverage
  194. if: ${{ matrix.coverage }}
  195. run: coverage run netbox/manage.py test netbox/ --parallel --keepdb --timing
  196. - name: Combine coverage data
  197. if: ${{ matrix.coverage }}
  198. run: coverage combine
  199. - name: Show coverage report
  200. if: ${{ matrix.coverage }}
  201. run: coverage report
  202. frontend:
  203. name: Frontend
  204. needs: changes
  205. if: needs.changes.result == 'success' && needs.changes.outputs.frontend == 'true'
  206. runs-on: ubuntu-latest
  207. steps:
  208. - name: Check out repo
  209. uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
  210. - name: Use Node.js 20.x
  211. uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
  212. with:
  213. node-version: '20.x'
  214. - name: Install Yarn Package Manager
  215. run: npm install -g yarn
  216. - name: Setup Node.js with Yarn Caching
  217. uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
  218. with:
  219. node-version: '20.x'
  220. cache: yarn
  221. cache-dependency-path: netbox/project-static/yarn.lock
  222. - name: Install Frontend Dependencies
  223. run: yarn --cwd netbox/project-static
  224. - name: Validate TypeScript and run ESLint
  225. run: yarn --cwd netbox/project-static validate
  226. - name: Validate formatting
  227. run: yarn --cwd netbox/project-static validate:formatting
  228. - name: Validate Static Asset Integrity
  229. run: scripts/verify-bundles.sh
  230. docs:
  231. name: Documentation
  232. needs: changes
  233. if: needs.changes.result == 'success' && needs.changes.outputs.docs == 'true'
  234. runs-on: ubuntu-latest
  235. steps:
  236. - name: Check out repo
  237. uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
  238. - name: Set up Python 3.12
  239. uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
  240. with:
  241. python-version: '3.12'
  242. cache: pip
  243. cache-dependency-path: |
  244. requirements.txt
  245. .github/workflows/ci.yml
  246. - name: Install dependencies
  247. run: |
  248. # Shares the 3.12 test job's pip cache entry.
  249. sed -i 's/^psycopg\[c,pool\]==/psycopg[binary,pool]==/' requirements.txt
  250. python -m pip install --upgrade pip
  251. pip install -r requirements.txt
  252. - name: Build documentation
  253. run: zensical build