@@ -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>`
|
||||
|
||||
Reference in New Issue
Block a user