feat: add plan step

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
2026-03-15 22:38:18 +01:00
parent 7eb6ae7cbb
commit 04e452ecc3
71 changed files with 1059 additions and 319 deletions

View File

@@ -169,3 +169,83 @@ CREATE TABLE approval_decisions (
2. Run `buf generate` in forest to regenerate gRPC interface stubs
3. Run forest tests
4. E2E test: create approval policy, trigger release, verify UI shows approval buttons
---
## Plan Stage Support (Prepare-Before-Deploy)
### Overview
Added support for "plan" pipeline stages — destinations that run a prepare/dry-run (e.g. terraform plan) and require approval of the output before the actual deploy proceeds. Forest already had full infrastructure for this; this work surfaces it in the Forage UI.
### Changes
#### forage-core (`crates/forage-core/src/platform/mod.rs`)
- Added `PipelineStageConfig::Plan { environment, auto_approve }` variant
- Added `approval_status: Option<String>` and `auto_approve: Option<bool>` to `PipelineRunStageState`
- Added 3 new `ForestPlatform` trait methods: `approve_plan_stage`, `reject_plan_stage`, `get_plan_output`
- Added `PlanOutput` struct (`plan_output: String`, `status: String`)
#### forage-server gRPC client (`forest_client.rs`)
- `convert_pipeline_stage`: handles `Plan` config variant (was previously mapped to empty Deploy)
- `convert_pipeline_stage_state`: recognizes `Plan` stage type + `AwaitingApproval` status + new fields
- `convert_stages_to_grpc`: handles `PipelineStageConfig::Plan``PlanStageConfig`
- Implemented `approve_plan_stage`, `reject_plan_stage`, `get_plan_output` calling forest's RPCs
#### forage-server routes (`routes/platform.rs`)
- Added 3 API routes:
- `POST /api/orgs/{org}/projects/{project}/plan-stages/{stage_id}/approve`
- `POST /api/orgs/{org}/projects/{project}/plan-stages/{stage_id}/reject`
- `GET /api/orgs/{org}/projects/{project}/plan-stages/{stage_id}/output`
- `ApiPipelineStage` now includes `approval_status` and `auto_approve`
- `build_timeline_json`: plan stages with `AWAITING_APPROVAL` status are shown with that status; releases with plan stages awaiting approval are treated as `needs_action` (not hidden)
#### Pipeline builder (`static/js/pipeline-builder.js`)
- Added "plan" as third stage type in dropdown
- Plan stage config: environment + auto-approve checkbox
- Purple color scheme for plan nodes in DAG visualization
#### Svelte timeline (`frontend/src/ReleaseTimeline.svelte`)
- `approvePlanStage(release, stage, reject)` function for approve/reject via API
- `viewPlanOutput(release, stage)` function for on-demand plan output fetching (toggle)
- Plan stages render with purple shield icon when `AWAITING_APPROVAL`
- "Approve plan" / "Reject" buttons on plan stages awaiting approval
- "View plan" / "Hide plan" button to toggle plan output display
- Plan output shown in collapsible `<pre>` block (monospace, max-height 256px with scroll)
- Summary line shows plan stage badge + approve button when plan awaiting approval
#### Status helpers (`frontend/src/lib/status.js`)
- Added `planStageLabel(status)` function
- `pipelineSummary`: detects `AWAITING_APPROVAL` plan stages → "Awaiting plan approval" (purple)
#### Slack notifications (`forage-core/src/integrations/router.rs`)
- Plan stage rendering in Slack blocks: "Planning", "Awaiting plan approval", "Plan approved", "Plan failed"
- Shield emoji for AWAITING_APPROVAL status
#### Test support (`test_support.rs`)
- Added default mock implementations for the 3 new trait methods
### Forest Runner Infrastructure
#### Proto (`runner.proto`)
- Added `ReleaseMode` enum: `RELEASE_MODE_UNSPECIFIED`, `RELEASE_MODE_DEPLOY`, `RELEASE_MODE_PLAN`
- Added `mode` field (type `ReleaseMode`) to `WorkAssignment` — tells remote runners whether to deploy or plan
- Added `plan_output` field (optional string) to `CompleteReleaseRequest` — runners send plan output back
#### Scheduler (`scheduler.rs`)
- Reads `release_state.mode` and maps to `ReleaseMode::Plan` / `ReleaseMode::Deploy`
- Includes `mode` in `WorkAssignment` when dispatching to remote runners
#### Runner gRPC handler (`grpc/runner.rs`)
- `complete_release`: stores `plan_output` from `CompleteReleaseRequest` to `release_states.plan_output` in DB
#### Terraform destination (`destinations/terraformv1.rs`)
- `plan()`: now captures actual terraform plan stdout (not just a marker)
- Added `run_capture()` method — same as `run()` but captures stdout into a String
- Added `run_command_capture()` — like `run_command()` but returns captured stdout while still logging
#### Runner crate (`forest-runner`)
- `RunnerDestination` trait: added `plan()` method (default returns None)
- `Executor`: checks `ReleaseMode` from `WorkAssignment`, calls `plan()` instead of `release()` for plan mode
- `RunnerSession::complete_release`: accepts optional `plan_output` parameter
- `run_destination_plan()` function: prepare + plan, returns `Option<String>`