@@ -520,6 +520,7 @@ pub fn format_pipeline_blocks(
|
||||
"RUNNING" => ":arrows_counterclockwise:",
|
||||
"FAILED" => ":x:",
|
||||
"CANCELLED" => ":no_entry_sign:",
|
||||
"AWAITING_APPROVAL" => ":shield:",
|
||||
_ => ":radio_button:", // PENDING
|
||||
};
|
||||
|
||||
@@ -546,6 +547,16 @@ pub fn format_pipeline_blocks(
|
||||
_ => format!("Wait {dur_str}"),
|
||||
}
|
||||
}
|
||||
"plan" => {
|
||||
let env = stage.environment.as_deref().unwrap_or("unknown");
|
||||
match stage.status.as_str() {
|
||||
"SUCCEEDED" => format!("Plan approved for `{env}`"),
|
||||
"RUNNING" => format!("Planning `{env}`"),
|
||||
"AWAITING_APPROVAL" => format!("Awaiting plan approval for `{env}`"),
|
||||
"FAILED" => format!("Plan failed for `{env}`"),
|
||||
_ => format!("Plan `{env}`"),
|
||||
}
|
||||
}
|
||||
_ => format!("Stage {}", stage.stage_id),
|
||||
};
|
||||
|
||||
@@ -928,6 +939,8 @@ mod tests {
|
||||
error_message: None,
|
||||
wait_until: None,
|
||||
release_ids: vec![],
|
||||
approval_status: None,
|
||||
auto_approve: None,
|
||||
},
|
||||
PipelineRunStageState {
|
||||
stage_id: "s2".into(),
|
||||
@@ -942,6 +955,8 @@ mod tests {
|
||||
error_message: None,
|
||||
wait_until: None,
|
||||
release_ids: vec![],
|
||||
approval_status: None,
|
||||
auto_approve: None,
|
||||
},
|
||||
PipelineRunStageState {
|
||||
stage_id: "s3".into(),
|
||||
@@ -956,6 +971,8 @@ mod tests {
|
||||
error_message: None,
|
||||
wait_until: None,
|
||||
release_ids: vec![],
|
||||
approval_status: None,
|
||||
auto_approve: None,
|
||||
},
|
||||
PipelineRunStageState {
|
||||
stage_id: "s4".into(),
|
||||
@@ -970,6 +987,8 @@ mod tests {
|
||||
error_message: None,
|
||||
wait_until: None,
|
||||
release_ids: vec![],
|
||||
approval_status: None,
|
||||
auto_approve: None,
|
||||
},
|
||||
PipelineRunStageState {
|
||||
stage_id: "s5".into(),
|
||||
@@ -984,6 +1003,8 @@ mod tests {
|
||||
error_message: None,
|
||||
wait_until: None,
|
||||
release_ids: vec![],
|
||||
approval_status: None,
|
||||
auto_approve: None,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1024,6 +1045,8 @@ mod tests {
|
||||
error_message: Some("OOM killed".into()),
|
||||
wait_until: None,
|
||||
release_ids: vec![],
|
||||
approval_status: None,
|
||||
auto_approve: None,
|
||||
}];
|
||||
|
||||
let blocks = format_pipeline_blocks(&stages);
|
||||
|
||||
@@ -130,8 +130,8 @@ pub struct DestinationState {
|
||||
pub struct PipelineRunStageState {
|
||||
pub stage_id: String,
|
||||
pub depends_on: Vec<String>,
|
||||
pub stage_type: String, // "deploy" or "wait"
|
||||
pub status: String, // "PENDING", "RUNNING", "SUCCEEDED", "FAILED", "CANCELLED"
|
||||
pub stage_type: String, // "deploy", "wait", or "plan"
|
||||
pub status: String, // "PENDING", "RUNNING", "SUCCEEDED", "FAILED", "CANCELLED", "AWAITING_APPROVAL"
|
||||
pub environment: Option<String>,
|
||||
pub duration_seconds: Option<i64>,
|
||||
pub queued_at: Option<String>,
|
||||
@@ -141,6 +141,10 @@ pub struct PipelineRunStageState {
|
||||
pub wait_until: Option<String>,
|
||||
#[serde(default)]
|
||||
pub release_ids: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub approval_status: Option<String>,
|
||||
#[serde(default)]
|
||||
pub auto_approve: Option<bool>,
|
||||
}
|
||||
|
||||
/// Combined response from get_destination_states: destinations only.
|
||||
@@ -302,6 +306,7 @@ pub struct PipelineStage {
|
||||
pub enum PipelineStageConfig {
|
||||
Deploy { environment: String },
|
||||
Wait { duration_seconds: i64 },
|
||||
Plan { environment: String, auto_approve: bool },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -628,6 +633,43 @@ pub trait ForestPlatform: Send + Sync {
|
||||
release_intent_id: &str,
|
||||
target_environment: &str,
|
||||
) -> Result<ApprovalState, PlatformError>;
|
||||
|
||||
async fn approve_plan_stage(
|
||||
&self,
|
||||
access_token: &str,
|
||||
release_intent_id: &str,
|
||||
stage_id: &str,
|
||||
) -> Result<(), PlatformError>;
|
||||
|
||||
async fn reject_plan_stage(
|
||||
&self,
|
||||
access_token: &str,
|
||||
release_intent_id: &str,
|
||||
stage_id: &str,
|
||||
reason: Option<&str>,
|
||||
) -> Result<(), PlatformError>;
|
||||
|
||||
async fn get_plan_output(
|
||||
&self,
|
||||
access_token: &str,
|
||||
release_intent_id: &str,
|
||||
stage_id: &str,
|
||||
) -> Result<PlanOutput, PlatformError>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PlanOutput {
|
||||
pub plan_output: String,
|
||||
pub status: String,
|
||||
pub outputs: Vec<PlanDestinationOutput>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PlanDestinationOutput {
|
||||
pub destination_id: String,
|
||||
pub destination_name: String,
|
||||
pub plan_output: String,
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user