fix: Match DNS add to the owning record #1117
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| pull_request: | |
| release: | |
| types: ['published'] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| env: | |
| CYPRESS_CACHE_FOLDER: ~/.cache/Cypress | |
| BUN_CACHE: ~/.bun/install/cache | |
| jobs: | |
| # ─── Quality ──────────────────────────────────────────────── | |
| install: | |
| name: Install Dependencies | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: { bun-version: 1.4.2 } # renovate: datasource=docker depName=oven/bun | |
| - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 | |
| id: cache-deps | |
| with: | |
| path: | | |
| ${{ env.BUN_CACHE }} | |
| **/node_modules | |
| key: ${{ runner.os }}-deps-${{ hashFiles('**/bun.lock', '**/bun.lockb', '**/package.json') }} | |
| - if: steps.cache-deps.outputs.cache-hit != 'true' | |
| run: bun install --frozen-lockfile | |
| lint: | |
| name: Lint & Format | |
| needs: install | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 | |
| with: | |
| path: | | |
| ${{ env.BUN_CACHE }} | |
| **/node_modules | |
| key: ${{ runner.os }}-deps-${{ hashFiles('**/bun.lock', '**/bun.lockb', '**/package.json') }} | |
| - run: bun run lint && bun run format | |
| typecheck: | |
| name: Type Check | |
| needs: install | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: { bun-version: 1.4.2 } # renovate: datasource=docker depName=oven/bun | |
| - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 | |
| with: | |
| path: | | |
| ${{ env.BUN_CACHE }} | |
| **/node_modules | |
| key: ${{ runner.os }}-deps-${{ hashFiles('**/bun.lock', '**/bun.lockb', '**/package.json') }} | |
| - run: bun typecheck | |
| build: | |
| name: Build | |
| needs: [lint, typecheck] | |
| runs-on: ubuntu-latest | |
| if: ${{ !failure() && !cancelled() }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: { bun-version: 1.4.2 } # renovate: datasource=docker depName=oven/bun | |
| - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 | |
| with: | |
| path: | | |
| ${{ env.BUN_CACHE }} | |
| **/node_modules | |
| key: ${{ runner.os }}-deps-${{ hashFiles('**/bun.lock', '**/bun.lockb', '**/package.json') }} | |
| - run: bun run build | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: build-${{ github.sha }} | |
| path: build | |
| retention-days: 1 | |
| # ─── Tests ────────────────────────────────────────────────── | |
| bun-tests: | |
| name: Bun Unit Tests | |
| needs: install | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: { bun-version: 1.4.2 } # renovate: datasource=docker depName=oven/bun | |
| - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 | |
| with: | |
| path: | | |
| ${{ env.BUN_CACHE }} | |
| **/node_modules | |
| key: ${{ runner.os }}-deps-${{ hashFiles('**/bun.lock', '**/bun.lockb', '**/package.json') }} | |
| # Coverage is reported (text in the log + lcov artifact) but not gated yet. | |
| # Bun only instruments files imported by tests, so the % reflects the | |
| # unit-tested surface, not the whole repo. | |
| - run: bun run test:coverage | |
| env: | |
| NODE_ENV: test | |
| AUTH_OIDC_ISSUER: ${{ secrets.AUTH_OIDC_ISSUER }} | |
| - if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: bun-coverage-${{ github.run_id }} | |
| path: coverage/lcov.info | |
| retention-days: 7 | |
| if-no-files-found: warn | |
| unit-tests: | |
| name: Unit Tests | |
| needs: build | |
| runs-on: ubuntu-latest | |
| if: ${{ !failure() && !cancelled() }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 | |
| with: | |
| node-version-file: '.nvmrc' | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: { bun-version: 1.4.2 } # renovate: datasource=docker depName=oven/bun | |
| - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 | |
| with: | |
| path: | | |
| ${{ env.BUN_CACHE }} | |
| **/node_modules | |
| key: ${{ runner.os }}-deps-${{ hashFiles('**/bun.lock', '**/bun.lockb', '**/package.json') }} | |
| - run: bunx cypress install && bunx cypress verify | |
| - run: bun run test:unit:prod | |
| env: | |
| NODE_ENV: test | |
| CYPRESS: 'true' | |
| e2e-smoke: | |
| name: E2E Smoke | |
| needs: build | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| if: ${{ !failure() && !cancelled() }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 | |
| with: | |
| node-version-file: '.nvmrc' | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: { bun-version: 1.4.2 } # renovate: datasource=docker depName=oven/bun | |
| - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 | |
| with: | |
| path: | | |
| ${{ env.BUN_CACHE }} | |
| **/node_modules | |
| key: ${{ runner.os }}-deps-${{ hashFiles('**/bun.lock', '**/bun.lockb', '**/package.json') }} | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| name: build-${{ github.sha }} | |
| path: build | |
| - name: Setup Environment | |
| env: | |
| SESSION_SECRET: ${{ secrets.SESSION_SECRET }} | |
| ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} | |
| SUB: ${{ secrets.SUB }} | |
| API_URL: ${{ secrets.API_URL }} | |
| GRAPHQL_URL: ${{ secrets.GRAPHQL_URL }} | |
| AUTH_OIDC_ISSUER: ${{ secrets.AUTH_OIDC_ISSUER }} | |
| AUTH_OIDC_CLIENT_ID: ${{ secrets.AUTH_OIDC_CLIENT_ID }} | |
| run: | | |
| cat > .env << EOF | |
| NODE_ENV=test | |
| CYPRESS=true | |
| APP_URL=http://localhost:3000 | |
| API_URL=$API_URL | |
| GRAPHQL_URL=$GRAPHQL_URL | |
| AUTH_OIDC_ISSUER=$AUTH_OIDC_ISSUER | |
| AUTH_OIDC_CLIENT_ID=$AUTH_OIDC_CLIENT_ID | |
| SESSION_SECRET=$SESSION_SECRET | |
| LOG_LEVEL=warn | |
| CYPRESS_BASE_URL=http://localhost:3000 | |
| EOF | |
| - run: bunx cypress install && bunx cypress verify | |
| - name: Run Smoke Tests | |
| env: | |
| NODE_ENV: test | |
| CYPRESS: 'true' | |
| CYPRESS_BASE_URL: http://localhost:3000 | |
| SESSION_SECRET: ${{ secrets.SESSION_SECRET }} | |
| ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} | |
| SUB: ${{ secrets.SUB }} | |
| run: >- | |
| bunx start-server-and-test | |
| 'bun run start' http://localhost:3000/_healthz | |
| 'bunx cypress run --spec "cypress/e2e/smoke/**" --config-file cypress.config.ts' | |
| - if: failure() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: e2e-smoke-artifacts-${{ github.run_id }} | |
| path: | | |
| cypress/videos | |
| cypress/screenshots | |
| retention-days: 7 | |
| if-no-files-found: warn | |
| e2e-regression: | |
| name: E2E Regression | |
| needs: e2e-smoke | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| # if: ${{ !failure() && !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/main' }} | |
| if: ${{ !failure() && !cancelled() }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [0, 1, 2, 3] | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 | |
| with: | |
| node-version-file: '.nvmrc' | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: { bun-version: 1.4.2 } # renovate: datasource=docker depName=oven/bun | |
| - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 | |
| with: | |
| path: | | |
| ${{ env.BUN_CACHE }} | |
| **/node_modules | |
| key: ${{ runner.os }}-deps-${{ hashFiles('**/bun.lock', '**/bun.lockb', '**/package.json') }} | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| name: build-${{ github.sha }} | |
| path: build | |
| - name: Setup Environment | |
| env: | |
| SESSION_SECRET: ${{ secrets.SESSION_SECRET }} | |
| ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} | |
| SUB: ${{ secrets.SUB }} | |
| API_URL: ${{ secrets.API_URL }} | |
| GRAPHQL_URL: ${{ secrets.GRAPHQL_URL }} | |
| AUTH_OIDC_ISSUER: ${{ secrets.AUTH_OIDC_ISSUER }} | |
| AUTH_OIDC_CLIENT_ID: ${{ secrets.AUTH_OIDC_CLIENT_ID }} | |
| run: | | |
| cat > .env << EOF | |
| NODE_ENV=test | |
| CYPRESS=true | |
| APP_URL=http://localhost:3000 | |
| API_URL=$API_URL | |
| GRAPHQL_URL=$GRAPHQL_URL | |
| AUTH_OIDC_ISSUER=$AUTH_OIDC_ISSUER | |
| AUTH_OIDC_CLIENT_ID=$AUTH_OIDC_CLIENT_ID | |
| SESSION_SECRET=$SESSION_SECRET | |
| LOG_LEVEL=warn | |
| CYPRESS_BASE_URL=http://localhost:3000 | |
| EOF | |
| - run: bunx cypress install && bunx cypress verify | |
| - name: Run Regression Tests | |
| env: | |
| NODE_ENV: test | |
| CYPRESS: 'true' | |
| CYPRESS_BASE_URL: http://localhost:3000 | |
| SESSION_SECRET: ${{ secrets.SESSION_SECRET }} | |
| ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} | |
| SUB: ${{ secrets.SUB }} | |
| SPLIT: ${{ strategy.job-total }} | |
| SPLIT_INDEX: ${{ strategy.job-index }} | |
| CYPRESS_specPattern: 'cypress/e2e/regression/**/*.{cy,spec}.{js,jsx,ts,tsx}' | |
| run: >- | |
| bunx start-server-and-test | |
| 'bun run start' http://localhost:3000/_healthz | |
| 'bunx cypress run --config-file cypress.config.ts' | |
| - if: failure() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: e2e-regression-artifacts-${{ github.run_id }}-shard-${{ matrix.shard }} | |
| path: | | |
| cypress/videos | |
| cypress/screenshots | |
| retention-days: 7 | |
| if-no-files-found: warn | |
| # ─── Status Gate ──────────────────────────────────────────── | |
| status-check: | |
| name: Final Status Check | |
| needs: [install, lint, typecheck, build, bun-tests, unit-tests, e2e-smoke, e2e-regression] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check Workflow Status | |
| run: | | |
| if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then | |
| echo "❌ One or more jobs failed" | |
| exit 1 | |
| elif [[ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then | |
| echo "⚠️ One or more jobs were cancelled" | |
| exit 1 | |
| else | |
| echo "✅ All jobs passed" | |
| fi | |
| # ─── Publish (only after all checks pass) ────────────────── | |
| publish-container-image: | |
| name: Publish Container Image | |
| needs: [status-check] | |
| if: ${{ !failure() && !cancelled() }} | |
| permissions: | |
| id-token: write | |
| contents: read | |
| packages: write | |
| attestations: write | |
| uses: datum-cloud/actions/.github/workflows/publish-docker.yaml@2dbca23d057e20ff1aab144b94a06d09ea75e5f6 # v1.21.0 | |
| with: | |
| image-name: cloud-portal | |
| # NOTE: `secrets: inherit` is required because the upstream workflow | |
| # references `secrets.SENTRY_AUTH_TOKEN` without declaring it under | |
| # `workflow_call.secrets`. Once upstream declares it, switch to an | |
| # explicit `secrets:` block passing only SENTRY_AUTH_TOKEN. | |
| secrets: inherit | |
| publish-kustomize-bundles: | |
| name: Publish Kustomize Bundles | |
| needs: [publish-container-image] | |
| if: ${{ !failure() && !cancelled() }} | |
| permissions: | |
| id-token: write | |
| contents: read | |
| packages: write | |
| uses: datum-cloud/actions/.github/workflows/publish-kustomize-bundle.yaml@2dbca23d057e20ff1aab144b94a06d09ea75e5f6 # v1.21.0 | |
| with: | |
| bundle-name: ghcr.io/datum-cloud/cloud-portal-kustomize | |
| bundle-path: config | |
| image-overlays: config/base | |
| image-name: ghcr.io/datum-cloud/cloud-portal | |
| # No explicit secrets block: upstream only uses GITHUB_TOKEN, which is | |
| # automatically available to reusable workflows. | |
| # ─── PR Comment ───────────────────────────────────────────── | |
| pr-comment: | |
| name: PR Test Summary | |
| # Must depend on EVERY test job it reports on — otherwise it fires as soon | |
| # as the fast jobs finish and snapshots the slow ones (the e2e-regression | |
| # shards) while they're still running, so they show up as "pending" and a | |
| # real failure would be masked as "pending" too. | |
| needs: [bun-tests, unit-tests, e2e-smoke, e2e-regression] | |
| runs-on: ubuntu-latest | |
| # `always()` so the summary still posts when a test job fails — that's when | |
| # the artifact links matter most. Without it, a failed dependency would | |
| # skip this job and leave the PR with no comment at all. | |
| if: always() && github.event_name == 'pull_request' | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| actions: read | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - name: Build test summary | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const runId = context.runId; | |
| const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; | |
| const { data: jobs } = await github.rest.actions.listJobsForWorkflowRun({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: runId, | |
| }); | |
| const testJobs = jobs.jobs.filter(j => | |
| j.name === 'Bun Unit Tests' || j.name === 'Unit Tests' || j.name.startsWith('E2E') | |
| ); | |
| // A job that hasn't finished has a null `conclusion` (distinct from | |
| // "skipped"). With this job now depending on every test job it | |
| // reports on, null should not occur — but label it honestly if it | |
| // ever does, rather than collapsing it into the "skipped" emoji. | |
| const statusEmoji = (conclusion) => { | |
| if (conclusion === 'success') return '✅'; | |
| if (conclusion === 'failure') return '❌'; | |
| if (conclusion === 'cancelled') return '🚫'; | |
| if (conclusion === 'skipped') return '⏭️'; | |
| if (!conclusion) return '⏳'; | |
| return '❓'; | |
| }; | |
| let table = '| Job | Status |\n|-----|--------|\n'; | |
| for (const job of testJobs.sort((a, b) => a.name.localeCompare(b.name))) { | |
| table += `| ${job.name} | ${statusEmoji(job.conclusion)} ${job.conclusion || 'in progress'} |\n`; | |
| } | |
| let body = `## 🧪 Test Summary\n\n${table}\n\n`; | |
| body += `[View workflow run](${runUrl})`; | |
| // Only surface debugging artifacts when a test actually failed. On a | |
| // green run the coverage/build artifacts still exist, so listing | |
| // them here would wrongly imply a failure. E2E failure artifacts are | |
| // named `e2e-*` (see the upload steps); coverage/build are not. | |
| const anyFailed = testJobs.some(j => j.conclusion === 'failure'); | |
| if (anyFailed) { | |
| const { data: artifacts } = await github.rest.actions.listWorkflowRunArtifacts({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: runId, | |
| }); | |
| // Dedupe by name: re-running a failed shard uploads its artifact | |
| // again under the same name (the name carries run_id but not the | |
| // run attempt), so the API returns two records for one shard. | |
| // Keep one row per shard rather than listing the re-run twice. | |
| const e2eArtifacts = [ | |
| ...new Map( | |
| artifacts.artifacts | |
| .filter(a => a.name.startsWith('e2e-')) | |
| .map(a => [a.name, a]) | |
| ).values(), | |
| ]; | |
| if (e2eArtifacts.length > 0) { | |
| body += `\n\n### 📎 Failure artifacts\n\nVideos and screenshots from failed E2E tests:\n\n`; | |
| for (const a of e2eArtifacts) { | |
| body += `- **${a.name}** – [Download](${runUrl})\n`; | |
| } | |
| } | |
| } | |
| fs.writeFileSync('pr-summary.md', body); | |
| - uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5 | |
| with: | |
| issue-number: ${{ github.event.pull_request.number }} | |
| body-path: pr-summary.md | |
| edit-mode: replace |