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
41 changes: 39 additions & 2 deletions .github/workflows/reusable-generic-web-stable-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,17 @@ name: Reusable Generic Web Stable Deploy
value: ${{ jobs.stable-deploy.outputs.error_message }}

permissions:
actions: read
contents: read
id-token: write

jobs:
stable-deploy:
if: >-
inputs.recovery_request_json == '' &&
(github.event_name != 'workflow_dispatch' || github.event.inputs.original_run_id == '')
(github.event_name != 'workflow_dispatch' || github.event.inputs.original_run_id == '') &&
(github.event_name != 'workflow_run' ||
github.event.workflow_run.name != 'Launchplane Recovery Request')
outputs:
deployment_record_id: ${{ steps.lp.outputs.deployment_record_id }}
deploy_status: ${{ steps.lp.outputs.deploy_status }}
Expand Down Expand Up @@ -165,7 +168,14 @@ jobs:
recovery-dry-run:
if: >-
inputs.recovery_request_json != '' ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.original_run_id != '')
(github.event_name == 'workflow_dispatch' && github.event.inputs.original_run_id != '') ||
(github.event_name == 'workflow_run' &&
github.event.workflow_run.name == 'Launchplane Recovery Request' &&
github.event.workflow_run.path == '.github/workflows/launchplane-recovery-request.yml' &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'workflow_dispatch' &&
github.event.workflow_run.head_branch == 'main' &&
github.event.workflow_run.head_repository.full_name == github.repository)
outputs:
recovery_digest: ${{ steps.recovery.outputs.recovery_digest }}
proposed_action: ${{ steps.recovery.outputs.proposed_action }}
Expand All @@ -176,6 +186,17 @@ jobs:
observed_at: ${{ steps.recovery.outputs.observed_at }}
runs-on: ubuntu-latest
steps:
- name: Download staged recovery request
if: >-
github.event_name == 'workflow_run' &&
github.event.workflow_run.name == 'Launchplane Recovery Request'
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: launchplane-recovery-request-${{ github.event.workflow_run.id }}
path: .launchplane-recovery
github-token: ${{ github.token }}
run-id: ${{ github.event.workflow_run.id }}

- name: Resolve Launchplane recovery request
id: request
env:
Expand All @@ -186,12 +207,28 @@ jobs:
ORIGINAL_RUN_ID: ${{ github.event.inputs.original_run_id }}
PRODUCT: ${{ inputs.product }}
REASON: ${{ github.event.inputs.reason }}
RECOVERY_ARTIFACT_RUN_ID: ${{ github.event.workflow_run.id }}
SOURCE_GIT_REF: ${{ inputs.source_git_ref }}
run: |
set -euo pipefail

if [ -n "$EXPLICIT_REQUEST" ]; then
request="$EXPLICIT_REQUEST"
elif [ -n "$RECOVERY_ARTIFACT_RUN_ID" ]; then
request_file=.launchplane-recovery/launchplane-recovery-request.json
if [ ! -f "$request_file" ] || [ -L "$request_file" ]; then
echo "Recovery request artifact is missing or invalid." >&2
exit 1
fi
if [ "$(find .launchplane-recovery -type f | wc -l | tr -d ' ')" != "1" ]; then
echo "Recovery request artifact must contain exactly one file." >&2
exit 1
fi
if [ "$(wc -c < "$request_file" | tr -d ' ')" -gt 32768 ]; then
echo "Recovery request artifact exceeds the size limit." >&2
exit 1
fi
request="$(jq -c . "$request_file")"
else
if [ -z "$PRODUCT" ]; then
PRODUCT="${GITHUB_REPOSITORY#*/}"
Expand Down
8 changes: 8 additions & 0 deletions docs/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,14 @@ non-empty `original_run_id`, `original_run_attempt`, and `reason` inputs. It
constructs the same private recovery envelope from the caller event plus the
existing product, instance, artifact, source, and Launchplane URL inputs, then
skips stable deploy exactly as the explicit envelope path does.
Product connectors whose authorization policy permits only `workflow_run` may
instead stage `launchplane-recovery-request.json` in a successful one-day
artifact from a `Launchplane Recovery Request` manual workflow on `main`. When
the caller event matches that exact workflow name, path, branch, repository, and
event type, the reusable workflow downloads only the artifact tied to the
triggering run, enforces a single-file and size bound, and passes the compact
request through the same bounded dry-run action. This mode also skips stable
deploy and exposes no apply path.

Stage 2 apply is explicit and digest-gated. Operators call
`POST /v1/admin/generic-web/deploy-recovery/apply` with the same request body as
Expand Down
10 changes: 10 additions & 0 deletions tests/test_generic_web_deploy_recovery_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,13 +201,23 @@ def test_stable_deploy_reusable_workflow_has_dry_run_only_recovery_mode(self) ->
workflow = REUSABLE_WORKFLOW.read_text(encoding="utf-8")

required_fragments = (
"actions: read",
"recovery_request_json:",
"github.event.inputs.original_run_id == ''",
"github.event.inputs.original_run_id != ''",
"github.event.workflow_run.name != 'Launchplane Recovery Request'",
"github.event.workflow_run.name == 'Launchplane Recovery Request'",
"github.event.workflow_run.path == '.github/workflows/launchplane-recovery-request.yml'",
"name: Download staged recovery request",
"actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c",
"launchplane-recovery-request-${{ github.event.workflow_run.id }}",
"name: Resolve Launchplane recovery request",
"ORIGINAL_RUN_ATTEMPT: ${{ github.event.inputs.original_run_attempt }}",
"ORIGINAL_RUN_ID: ${{ github.event.inputs.original_run_id }}",
"REASON: ${{ github.event.inputs.reason }}",
"RECOVERY_ARTIFACT_RUN_ID: ${{ github.event.workflow_run.id }}",
"Recovery request artifact must contain exactly one file.",
"Recovery request artifact exceeds the size limit.",
"name: Request Launchplane recovery dry run",
"uses: cbusillo/launchplane/.github/actions/"
"generic-web-deploy-recovery-dry-run@b2055d2944626234664390d6fcd96975ded38511",
Expand Down
Loading