ci.yml 11 KB

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