diff --git a/.github/workflows/pr-env-comment.yml b/.github/workflows/pr-env-comment.yml
index 89eedc9..e1385cb 100644
--- a/.github/workflows/pr-env-comment.yml
+++ b/.github/workflows/pr-env-comment.yml
@@ -57,13 +57,53 @@ on:
description: >-
Heading text, with the env-name appended. Defaults to the per-PR
environment wording; a caller posting something else (installer
- artifacts, say) supplies its own.
+ artifacts, say) supplies its own. Ignored when `status: absent`.
type: string
default: "PR environment is up"
+ status:
+ description: >-
+ `up` when the environment exists, `absent` when it does not.
+
+ Absent is a real state worth saying out loud rather than staying quiet.
+ The namespace convention is documented, so someone WILL construct the URL
+ for a PR that never got the label and land on a dead host; and because
+ this comment rolls rather than reposting, one left behind after a teardown
+ points at something that used to work. Saying "there is no environment,
+ here is how to get one" answers both.
+ type: string
+ default: up
+ deploy-label:
+ description: "Label that creates the environment, named in the absent message."
+ type: string
+ default: deploy
+ standard-links:
+ description: >-
+ Render the per-PR environment's full service table, derived from
+ `env-name`.
+
+ A PR environment is ONE namespace holding the whole stack — hub, auth,
+ Keycloak, inference, cowork-server — whichever repo anchored it. So the
+ table is a property of the environment, not of the repo, and every
+ caller composing its own produced five near-identical tables that each
+ listed only that repo's own service. Set false for a comment that is not
+ about an environment (build artifacts, say).
+ type: boolean
+ default: true
+ env-host-suffix:
+ description: "Domain the per-PR hosts sit under."
+ type: string
+ default: dev.mindshub.ai
+ default-tag:
+ description: >-
+ Image tag the services this PR does not build are running. Named in the
+ comment so nobody assumes the whole namespace is their branch.
+ type: string
+ default: staging
links:
description: >-
- JSON array of {"label","url"} objects, rendered as a table in order.
- e.g. '[{"label":"Console","url":"https://..."}]'
+ EXTRA links, appended after the standard table. JSON array of
+ {"label","url"} objects. For genuinely repo-specific things — a Swagger
+ path, a health endpoint — not for the service hosts, which are derived.
type: string
default: "[]"
access-doc-url:
@@ -107,10 +147,15 @@ jobs:
PR: ${{ github.event.pull_request.number }}
ENV_NAME: ${{ inputs.env-name }}
LINKS: ${{ inputs.links }}
+ STANDARD: ${{ inputs.standard-links }}
+ HOST_SUFFIX: ${{ inputs.env-host-suffix }}
+ DEFAULT_TAG: ${{ inputs.default-tag }}
DOC_URL: ${{ inputs.access-doc-url }}
NOTES: ${{ inputs.notes }}
MARKER_KEY: ${{ inputs.marker }}
HEADING: ${{ inputs.heading }}
+ STATUS: ${{ inputs.status }}
+ DEPLOY_LABEL: ${{ inputs.deploy-label }}
IS_PRIVATE: ${{ github.event.repository.private }}
run: |
set -euo pipefail
@@ -120,25 +165,63 @@ jobs:
exit 1
fi
- # Fail closed: `private` is present on a pull_request payload, and if it
- # ever is not, the safe reading is "assume public".
+ # On a public repo, drop what describes internals by nature: the runbook
+ # link and the free-text notes. NOT the links — an environment's own URL
+ # is already published on this PR by the deployment entry in the sidebar,
+ # so withholding it here hides it from the reviewer and from nobody else,
+ # and it is the one thing the comment exists to say. Whether a link is
+ # safe to publish is the caller's call, since only the caller knows what
+ # it points at.
+ #
+ # Fail closed on an absent value: `private` is present on a pull_request
+ # payload, and if it ever is not, the safe reading is "assume public".
if [ "${IS_PRIVATE:-false}" != "true" ]; then
- LINKS="[]"
DOC_URL=""
NOTES=""
- echo "Public repository: posting the environment name only."
+ echo "Public repository: omitting the runbook link and the notes."
fi
MARKER=""
{
echo "${MARKER}"
+
+ if [ "$STATUS" = "absent" ]; then
+ # No `exit` here: this block is a redirect group, not a subshell, so
+ # exiting would end the whole step and never post what it just wrote.
+ echo "## No PR environment for this pull request"
+ echo
+ echo "Add the \`${DEPLOY_LABEL}\` label and push to create one. It is torn down when the label is removed or the PR closes, so any URL you saw here earlier is gone."
+ echo
+ echo "Updated on every push to this PR."
+ else
+
echo "## ${HEADING} · \`${ENV_NAME}\`"
echo
- if [ "$(jq 'length' <<< "${LINKS:-[]}")" != "0" ]; then
+ # The whole stack lives in this one namespace, so list the whole
+ # stack. Derived from the env name rather than passed in, because it
+ # is identical for every anchor repo.
+ ALL="${LINKS:-[]}"
+ if [ "$STANDARD" = "true" ]; then
+ ALL="$(jq -cn --arg e "$ENV_NAME" --arg d "$HOST_SUFFIX" --argjson extra "${LINKS:-[]}" '
+ [ {label: "Hub (console)", url: "https://\($e).\($d)"},
+ {label: "Auth API", url: "https://auth-\($e).\($d)"},
+ {label: "Keycloak admin", url: "https://auth-\($e).\($d)/auth/admin/"},
+ {label: "Inference API", url: "https://api-\($e).\($d)/v1"},
+ {label: "cowork-server", url: "https://cowork-\($e).\($d)/api"} ]
+ + $extra
+ + [ {label: "Namespace", url: "`\($e)`"} ]')"
+ fi
+
+ if [ "$(jq 'length' <<< "$ALL")" != "0" ]; then
echo "| | |"
echo "| :--- | :--- |"
- jq -r '.[] | "| \(.label) | \(.url) |"' <<< "$LINKS"
+ jq -r '.[] | "| \(.label) | \(.url) |"' <<< "$ALL"
+ echo
+ fi
+
+ if [ "$STANDARD" = "true" ]; then
+ echo "Every service is in this namespace. The ones this PR does not build run the \`${DEFAULT_TAG}\` image unless you link them with \`Deploys:\` in the PR body."
echo
fi
@@ -157,6 +240,7 @@ jobs:
fi
echo "Updated on every push to this PR."
+ fi
} > body.md
# One rolling comment per PR rather than a new one per push.