Skip to content

fix: Match DNS add to the owning record #1117

fix: Match DNS add to the owning record

fix: Match DNS add to the owning record #1117

Workflow file for this run

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