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

@@ -6,7 +6,7 @@ use forage_core::platform::{
ApprovalDecisionEntry, ApprovalState, Artifact, ArtifactContext, ArtifactDestination,
ArtifactRef, ArtifactSource, CreatePolicyInput, CreateReleasePipelineInput, CreateTriggerInput,
Destination, DestinationType, Environment, ForestPlatform, NotificationPreference, Organisation,
OrgMember, PipelineStage, PipelineStageConfig, PlatformError, Policy, PolicyConfig,
OrgMember, PipelineStage, PipelineStageConfig, PlanOutput, PlatformError, Policy, PolicyConfig,
PolicyEvaluation, ReleasePipeline, Trigger, UpdatePolicyInput, UpdateReleasePipelineInput,
UpdateTriggerInput,
};
@@ -583,8 +583,8 @@ fn convert_pipeline_stage(s: forage_grpc::PipelineStage) -> PipelineStage {
Some(forage_grpc::pipeline_stage::Config::Wait(w)) => {
PipelineStageConfig::Wait { duration_seconds: w.duration_seconds }
}
Some(forage_grpc::pipeline_stage::Config::Plan(_)) => {
PipelineStageConfig::Deploy { environment: String::new() }
Some(forage_grpc::pipeline_stage::Config::Plan(p)) => {
PipelineStageConfig::Plan { environment: p.environment, auto_approve: p.auto_approve }
}
None => PipelineStageConfig::Deploy { environment: String::new() },
};
@@ -603,6 +603,7 @@ fn convert_pipeline_stage_state(
let stage_type = match forage_grpc::PipelineRunStageType::try_from(s.stage_type) {
Ok(forage_grpc::PipelineRunStageType::Deploy) => "deploy",
Ok(forage_grpc::PipelineRunStageType::Wait) => "wait",
Ok(forage_grpc::PipelineRunStageType::Plan) => "plan",
_ => "unknown",
};
let status = match forage_grpc::PipelineRunStageStatus::try_from(s.status) {
@@ -611,6 +612,7 @@ fn convert_pipeline_stage_state(
Ok(forage_grpc::PipelineRunStageStatus::Succeeded) => "SUCCEEDED",
Ok(forage_grpc::PipelineRunStageStatus::Failed) => "FAILED",
Ok(forage_grpc::PipelineRunStageStatus::Cancelled) => "CANCELLED",
Ok(forage_grpc::PipelineRunStageStatus::AwaitingApproval) => "AWAITING_APPROVAL",
_ => "PENDING",
};
forage_core::platform::PipelineRunStageState {
@@ -626,6 +628,8 @@ fn convert_pipeline_stage_state(
error_message: s.error_message,
wait_until: s.wait_until,
release_ids: s.release_ids,
approval_status: s.approval_status,
auto_approve: s.auto_approve,
}
}
@@ -663,6 +667,12 @@ fn convert_stages_to_grpc(stages: &[PipelineStage]) -> Vec<forage_grpc::Pipeline
duration_seconds: *duration_seconds,
})
}
PipelineStageConfig::Plan { environment, auto_approve } => {
forage_grpc::pipeline_stage::Config::Plan(forage_grpc::PlanStageConfig {
environment: environment.clone(),
auto_approve: *auto_approve,
})
}
}),
})
.collect()
@@ -1872,6 +1882,81 @@ impl ForestPlatform for GrpcForestClient {
.map_err(map_platform_status)?;
Ok(convert_approval_state(resp.into_inner().state))
}
async fn approve_plan_stage(
&self,
access_token: &str,
release_intent_id: &str,
stage_id: &str,
) -> Result<(), PlatformError> {
let req = platform_authed_request(
access_token,
forage_grpc::ApprovePlanStageRequest {
release_intent_id: release_intent_id.into(),
stage_id: stage_id.into(),
},
)?;
self.release_client()
.approve_plan_stage(req)
.await
.map_err(map_platform_status)?;
Ok(())
}
async fn reject_plan_stage(
&self,
access_token: &str,
release_intent_id: &str,
stage_id: &str,
reason: Option<&str>,
) -> Result<(), PlatformError> {
let req = platform_authed_request(
access_token,
forage_grpc::RejectPlanStageRequest {
release_intent_id: release_intent_id.into(),
stage_id: stage_id.into(),
reason: reason.map(|s| s.into()),
},
)?;
self.release_client()
.reject_plan_stage(req)
.await
.map_err(map_platform_status)?;
Ok(())
}
async fn get_plan_output(
&self,
access_token: &str,
release_intent_id: &str,
stage_id: &str,
) -> Result<PlanOutput, PlatformError> {
let req = platform_authed_request(
access_token,
forage_grpc::GetPlanOutputRequest {
release_intent_id: release_intent_id.into(),
stage_id: stage_id.into(),
},
)?;
let resp = self
.release_client()
.get_plan_output(req)
.await
.map_err(map_platform_status)?;
let inner = resp.into_inner();
Ok(PlanOutput {
plan_output: inner.plan_output,
status: inner.status,
outputs: inner.outputs.into_iter().map(|o| {
forage_core::platform::PlanDestinationOutput {
destination_id: o.destination_id,
destination_name: o.destination_name,
plan_output: o.plan_output,
status: o.status,
}
}).collect(),
})
}
}
fn convert_policy_evaluation(e: forage_grpc::PolicyEvaluation) -> PolicyEvaluation {
@@ -1881,7 +1966,7 @@ fn convert_policy_evaluation(e: forage_grpc::PolicyEvaluation) -> PolicyEvaluati
3 => "approval",
_ => "unknown",
};
let approval_state = e.approval_state.map(|s| convert_approval_state(Some(s)));
let approval_state = e.external_approval_state.map(|s| convert_approval_state(Some(s)));
PolicyEvaluation {
policy_name: e.policy_name,
policy_type: policy_type.into(),