Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/cloudflare-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ permissions:

jobs:
quality:
uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@992944a4d890e517aa314204e5dceb49a42555c3 # v1.6.0
uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@3332fc8e0049b837e851778cb1fb2be1f24acb1a # v1.7.0
with:
quality-policy-path: ${{ inputs.quality-policy-path }}
deployment-policy-path: ${{ inputs.deployment-policy-path }}
Expand All @@ -46,7 +46,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: burnt-labs/github-workflows
ref: ed309eaa57dfea000dfe6f4fbcb3c5c16773cd6e # v1.6.0
ref: d7e4d93a212430e14a60f811f6bb07e7e93d1c98 # v1.7.0
path: .burnt-workflows
- name: Read stable release tags
env:
Expand Down Expand Up @@ -76,7 +76,7 @@ jobs:
permissions:
contents: read
deployments: write
uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@992944a4d890e517aa314204e5dceb49a42555c3 # v1.6.0
uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@3332fc8e0049b837e851778cb1fb2be1f24acb1a # v1.7.0
with:
# Under single topology the candidate and the release are the same
# Worker, so deploying here would serve the merge immediately and there
Expand All @@ -98,7 +98,7 @@ jobs:
permissions:
contents: read
deployments: write
uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@992944a4d890e517aa314204e5dceb49a42555c3 # v1.6.0
uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@3332fc8e0049b837e851778cb1fb2be1f24acb1a # v1.7.0
with:
operation: preview
target: release
Expand Down Expand Up @@ -184,7 +184,7 @@ jobs:
permissions:
contents: read
deployments: write
uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@992944a4d890e517aa314204e5dceb49a42555c3 # v1.6.0
uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@3332fc8e0049b837e851778cb1fb2be1f24acb1a # v1.7.0
with:
operation: deploy
target: release
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/cloudflare-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
github.event_name != 'pull_request' ||
(github.event.pull_request.draft == false &&
github.event.pull_request.head.repo.full_name == github.repository)
uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@992944a4d890e517aa314204e5dceb49a42555c3 # v1.6.0
uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@3332fc8e0049b837e851778cb1fb2be1f24acb1a # v1.7.0
with:
quality-policy-path: ${{ inputs.quality-policy-path }}
deployment-policy-path: ${{ inputs.deployment-policy-path }}
Expand Down Expand Up @@ -62,7 +62,7 @@ jobs:
permissions:
contents: read
deployments: write
uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@992944a4d890e517aa314204e5dceb49a42555c3 # v1.6.0
uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@3332fc8e0049b837e851778cb1fb2be1f24acb1a # v1.7.0
with:
operation: preview
target: candidate
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/cloudflare-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:

quality:
needs: validate
uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@992944a4d890e517aa314204e5dceb49a42555c3 # v1.6.0
uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@3332fc8e0049b837e851778cb1fb2be1f24acb1a # v1.7.0
with:
quality-policy-path: ${{ inputs.quality-policy-path }}
deployment-policy-path: ${{ inputs.deployment-policy-path }}
Expand Down Expand Up @@ -89,7 +89,7 @@ jobs:
permissions:
contents: read
deployments: write
uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@992944a4d890e517aa314204e5dceb49a42555c3 # v1.6.0
uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@3332fc8e0049b837e851778cb1fb2be1f24acb1a # v1.7.0
with:
operation: preview
target: release
Expand All @@ -108,7 +108,7 @@ jobs:
permissions:
contents: read
deployments: write
uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@992944a4d890e517aa314204e5dceb49a42555c3 # v1.6.0
uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@3332fc8e0049b837e851778cb1fb2be1f24acb1a # v1.7.0
with:
operation: deploy
target: release
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/npm-changesets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ permissions:

jobs:
quality:
uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@992944a4d890e517aa314204e5dceb49a42555c3 # v1.6.0
uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@3332fc8e0049b837e851778cb1fb2be1f24acb1a # v1.7.0
with:
quality-policy-path: ${{ inputs.quality-policy-path }}

Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/npm-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ permissions:

jobs:
quality:
uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@992944a4d890e517aa314204e5dceb49a42555c3 # v1.6.0
uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@3332fc8e0049b837e851778cb1fb2be1f24acb1a # v1.7.0

metadata:
needs: quality
Expand All @@ -31,7 +31,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: burnt-labs/github-workflows
ref: ed309eaa57dfea000dfe6f4fbcb3c5c16773cd6e # v1.6.0
ref: d7e4d93a212430e14a60f811f6bb07e7e93d1c98 # v1.7.0
path: .burnt-workflows
- name: Read stable release tags
env:
Expand Down Expand Up @@ -107,7 +107,7 @@ jobs:
permissions:
contents: read
id-token: write
uses: burnt-labs/github-workflows/.github/workflows/npm-publish.yml@992944a4d890e517aa314204e5dceb49a42555c3 # v1.6.0
uses: burnt-labs/github-workflows/.github/workflows/npm-publish.yml@3332fc8e0049b837e851778cb1fb2be1f24acb1a # v1.7.0
with:
quality-policy: ${{ needs.quality.outputs.quality-policy }}
npm-policy: ${{ needs.quality.outputs.npm-policy }}
Expand Down Expand Up @@ -171,7 +171,7 @@ jobs:
permissions:
contents: read
id-token: write
uses: burnt-labs/github-workflows/.github/workflows/npm-publish.yml@992944a4d890e517aa314204e5dceb49a42555c3 # v1.6.0
uses: burnt-labs/github-workflows/.github/workflows/npm-publish.yml@3332fc8e0049b837e851778cb1fb2be1f24acb1a # v1.7.0
with:
quality-policy: ${{ needs.quality.outputs.quality-policy }}
npm-policy: ${{ needs.quality.outputs.npm-policy }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/npm-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ permissions:

jobs:
quality:
uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@992944a4d890e517aa314204e5dceb49a42555c3 # v1.6.0
uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@3332fc8e0049b837e851778cb1fb2be1f24acb1a # v1.7.0

pack:
name: Package dry run
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/npm-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ permissions:

jobs:
quality:
uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@992944a4d890e517aa314204e5dceb49a42555c3 # v1.6.0
uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@3332fc8e0049b837e851778cb1fb2be1f24acb1a # v1.7.0

metadata:
needs: quality
Expand Down Expand Up @@ -52,7 +52,7 @@ jobs:
permissions:
contents: read
id-token: write
uses: burnt-labs/github-workflows/.github/workflows/npm-publish.yml@992944a4d890e517aa314204e5dceb49a42555c3 # v1.6.0
uses: burnt-labs/github-workflows/.github/workflows/npm-publish.yml@3332fc8e0049b837e851778cb1fb2be1f24acb1a # v1.7.0
with:
quality-policy: ${{ needs.quality.outputs.quality-policy }}
npm-policy: ${{ needs.quality.outputs.npm-policy }}
Expand Down
27 changes: 23 additions & 4 deletions .github/workflows/phala-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
type: string
outputs:
deployment-url:
description: Public HTTPS endpoint reported by Phala
description: Public HTTPS endpoint that passed the health check (the target's publicUrl when set, otherwise the one Phala reports)
value: ${{ jobs.deploy.outputs.deployment-url }}

permissions:
Expand Down Expand Up @@ -39,15 +39,15 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: burnt-labs/github-workflows
ref: ed309eaa57dfea000dfe6f4fbcb3c5c16773cd6e # v1.6.0
ref: d7e4d93a212430e14a60f811f6bb07e7e93d1c98 # v1.7.0
path: .burnt-workflows
- id: policy
name: Require and validate Phala policy
run: node .burnt-workflows/scripts/policy.bundle.mjs --phala

quality:
needs: policy
uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@992944a4d890e517aa314204e5dceb49a42555c3 # v1.6.0
uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@3332fc8e0049b837e851778cb1fb2be1f24acb1a # v1.7.0

deploy:
name: Phala ${{ inputs.target }}
Expand Down Expand Up @@ -201,8 +201,15 @@ jobs:
fs.appendFileSync(process.env.GITHUB_OUTPUT, `file=${target}\n`);
process.stdout.write(`Deploying ${reference}\n`);
NODE
# Only a candidate CVM is created on a miss. A release CVM carries
# identity that lives outside this flow: DNS that names its app id, and
# relying-party allowlists that name its measurements. Creating a new one
# because the named CVM was deleted or renamed would deploy an instance
# none of that points at, and report success. A release target is
# provisioned by hand once, then only ever updated by id here.
- name: Deploy CVM
env:
TARGET_ROLE: ${{ inputs.target }}
CVM_NAME: ${{ fromJSON(needs.policy.outputs.phala).targets[inputs.target].cvmName }}
COMPOSE_FILE: ${{ steps.compose.outputs.file }}
WORKING_DIRECTORY: ${{ fromJSON(needs.policy.outputs.phala).workingDirectory }}
Expand All @@ -225,8 +232,11 @@ jobs:
exit 1
fi
target=(--cvm-id "$existing_id")
else
elif [ "$TARGET_ROLE" = "candidate" ]; then
target=(-n "$CVM_NAME")
else
echo "::error::No Phala CVM is named $CVM_NAME. The $TARGET_ROLE target is never created by this workflow; provision it by hand, or restore the name, then rerun"
exit 1
fi
npx --yes phala@1.1.20 deploy \
-c "$COMPOSE_FILE" \
Expand All @@ -238,7 +248,16 @@ jobs:
id: phala-url
env:
CVM_NAME: ${{ fromJSON(needs.policy.outputs.phala).targets[inputs.target].cvmName }}
PUBLIC_URL: ${{ fromJSON(needs.policy.outputs.phala).targets[inputs.target].publicUrl }}
Comment thread
2xburnt marked this conversation as resolved.
run: |
# A target that terminates TLS inside the CVM (dstack-ingress, TLS
# passthrough) is not reachable at the URL Phala reports: that one
# expects the gateway to terminate TLS. Such a target names the URL it
# actually serves in policy, and the health check goes there instead.
if [ -n "$PUBLIC_URL" ]; then
echo "deployment-url=${PUBLIC_URL%/}" >> "$GITHUB_OUTPUT"
Comment thread
2xburnt marked this conversation as resolved.
exit 0
fi
PHALA_API_KEY="$(<"$RUNNER_TEMP/phala-api-key")"
npx --yes phala@1.1.20 cvms get "$CVM_NAME" --json --api-key "$PHALA_API_KEY" > "$RUNNER_TEMP/phala-cvm.json"
deployment_url="$(jq -r '
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/required-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: burnt-labs/github-workflows
ref: ed309eaa57dfea000dfe6f4fbcb3c5c16773cd6e # v1.6.0
ref: d7e4d93a212430e14a60f811f6bb07e7e93d1c98 # v1.7.0
path: .burnt-workflows
- id: policy
name: Validate policies
Expand Down
15 changes: 15 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,21 @@ Missing declared values fail before build or deploy. Credential names, dstack
registry variables, and `image.composeVariable` are reserved and cannot also be
runtime configuration.

A target may add `publicUrl`, a bare `https://` origin. The health check and
the returned `deployment-url` use it instead of the URL Phala reports. It exists
for services that terminate TLS inside the CVM (dstack-ingress with TLS
passthrough): Phala reports a gateway-terminated URL those services do not
answer on.

The deploy looks the target's CVM up by exact name and updates it by id. Only
the candidate is created when no CVM has that name. **The release CVM is never
created by this workflow**: a miss fails the deploy. A release CVM's identity
lives outside this flow (DNS naming its app id, relying-party allowlists naming
its measurements), so a CVM deleted or renamed between a caller's checks and
the deploy must not come back as a new instance that nothing points at while
the run reports success. Provision the release CVM by hand once, before its
first release deploy; this is unconditional rather than a policy knob.

The workflow returns `deployment-url` and does not mutate GitHub variables or
dispatch another workflow. Application-specific URL propagation belongs in a
caller job that consumes this output.
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,10 @@ pnpm 10 already default-denies build scripts, so pnpm consumers see no change.
`phala-deploy.yml` is an application-neutral deployment primitive. It requires
the repository's quality gates, builds a commit-addressed private GHCR image,
deploys or updates the selected Phala CVM, resolves and health-checks its public
HTTPS endpoint, and returns that URL to the caller. It does not know about a
HTTPS endpoint, and returns that URL to the caller. Only the candidate CVM is
created when missing; the release CVM must already exist (provision it by hand
once), and a release deploy that finds no CVM of that name fails rather than
creating one. It does not know about a
consumer's service names, secret bundle format, GitHub variables, or dependent
workflows.

Expand Down
18 changes: 18 additions & 0 deletions scripts/policy.bundle.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1411,6 +1411,24 @@ function validatePhalaPolicy(policy) {
`Phala targets.${role}.githubEnvironment`,
);
requireString(target.cvmName, `Phala targets.${role}.cvmName`);
if (target.publicUrl !== void 0) {
requireString(target.publicUrl, `Phala targets.${role}.publicUrl`);
let url;
try {
url = new URL(target.publicUrl);
} catch {
throw new Error(`Phala targets.${role}.publicUrl must be a URL`);
}
if (
url.protocol !== "https:" ||
(target.publicUrl !== url.origin &&
target.publicUrl !== `${url.origin}/`)
) {
throw new Error(
`Phala targets.${role}.publicUrl must be a bare https:// origin`,
);
}
}
if (!/^[a-z](?!.*--)[a-z0-9-]{3,61}[a-z0-9]$/.test(target.cvmName)) {
throw new Error(
`Phala targets.${role}.cvmName must be 5-63 characters, start with a lowercase letter, end with a letter or digit, and contain no consecutive hyphens`,
Expand Down
22 changes: 22 additions & 0 deletions scripts/policy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,28 @@ export function validatePhalaPolicy(policy) {
`Phala targets.${role}.githubEnvironment`,
);
requireString(target.cvmName, `Phala targets.${role}.cvmName`);
if (target.publicUrl !== undefined) {
requireString(target.publicUrl, `Phala targets.${role}.publicUrl`);
let url;
try {
url = new URL(target.publicUrl);
} catch {
throw new Error(`Phala targets.${role}.publicUrl must be a URL`);
}
// Compare the raw value, not the parsed one: the workflow uses the raw
// string, and URL parsing drops what it normalizes away (an empty `?` or
// `#`, surrounding whitespace, embedded tabs and newlines), so a value
// that parses to a bare origin can still request something else.
if (
url.protocol !== "https:" ||
(target.publicUrl !== url.origin &&
target.publicUrl !== `${url.origin}/`)
) {
throw new Error(
`Phala targets.${role}.publicUrl must be a bare https:// origin`,
);
}
}
if (!/^[a-z](?!.*--)[a-z0-9-]{3,61}[a-z0-9]$/.test(target.cvmName)) {
throw new Error(
`Phala targets.${role}.cvmName must be 5-63 characters, start with a lowercase letter, end with a letter or digit, and contain no consecutive hyphens`,
Expand Down
26 changes: 26 additions & 0 deletions tests/policy.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,32 @@ test("Phala requires concrete deployment and image fields", () => {
}
});

test("Phala targets may name the public URL they serve", () => {
const policy = phalaPolicy();
policy.targets.release.publicUrl = "https://service.example.com";
assert.deepEqual(validatePhalaPolicy(structuredClone(policy)), policy);
policy.targets.candidate.publicUrl = "https://service.example.com/";
assert.deepEqual(validatePhalaPolicy(structuredClone(policy)), policy);
for (const publicUrl of [
"",
"service.example.com",
"http://service.example.com",
"https://service.example.com/health",
"https://service.example.com?x=1",
"https://service.example.com?",
"https://service.example.com#",
"https://service.example.com/?",
" https://service.example.com",
"https://service.example.com\n",
"https://service.exam\nple.com",
"https://user@service.example.com",
]) {
const invalid = phalaPolicy();
invalid.targets.candidate.publicUrl = publicUrl;
assert.throws(() => validatePhalaPolicy(invalid), /publicUrl/);
}
});

test("Phala environment forwarding is explicit and cannot include credentials", () => {
for (const name of [
"PHALA_CLOUD_API_KEY",
Expand Down
Loading
Loading