@@ -20,6 +20,14 @@ pub struct User {
|
||||
pub emails: Vec<UserEmail>,
|
||||
}
|
||||
|
||||
/// Public user profile (no emails).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UserProfile {
|
||||
pub user_id: String,
|
||||
pub username: String,
|
||||
pub created_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UserEmail {
|
||||
pub email: String,
|
||||
@@ -91,6 +99,12 @@ pub trait ForestAuth: Send + Sync {
|
||||
|
||||
async fn get_user(&self, access_token: &str) -> Result<User, AuthError>;
|
||||
|
||||
async fn get_user_by_username(
|
||||
&self,
|
||||
access_token: &str,
|
||||
username: &str,
|
||||
) -> Result<UserProfile, AuthError>;
|
||||
|
||||
async fn list_tokens(
|
||||
&self,
|
||||
access_token: &str,
|
||||
|
||||
@@ -69,6 +69,8 @@ pub struct ArtifactDestination {
|
||||
pub type_name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub type_version: Option<u64>,
|
||||
#[serde(default)]
|
||||
pub status: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -79,6 +81,16 @@ pub struct OrgMember {
|
||||
pub joined_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Environment {
|
||||
pub id: String,
|
||||
pub organisation: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub sort_order: i32,
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Destination {
|
||||
pub name: String,
|
||||
@@ -97,6 +109,201 @@ pub struct DestinationType {
|
||||
pub version: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DestinationState {
|
||||
pub destination_id: String,
|
||||
pub destination_name: String,
|
||||
pub environment: String,
|
||||
pub release_id: Option<String>,
|
||||
pub artifact_id: Option<String>,
|
||||
pub status: Option<String>,
|
||||
pub error_message: Option<String>,
|
||||
pub queued_at: Option<String>,
|
||||
pub completed_at: Option<String>,
|
||||
pub queue_position: Option<i32>,
|
||||
#[serde(default)]
|
||||
pub started_at: Option<String>,
|
||||
}
|
||||
|
||||
/// Runtime status of a single pipeline stage.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
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 environment: Option<String>,
|
||||
pub duration_seconds: Option<i64>,
|
||||
pub queued_at: Option<String>,
|
||||
pub started_at: Option<String>,
|
||||
pub completed_at: Option<String>,
|
||||
pub error_message: Option<String>,
|
||||
pub wait_until: Option<String>,
|
||||
#[serde(default)]
|
||||
pub release_ids: Vec<String>,
|
||||
}
|
||||
|
||||
/// Combined response from get_destination_states: destinations only.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct DeploymentStates {
|
||||
pub destinations: Vec<DestinationState>,
|
||||
}
|
||||
|
||||
/// Full state of a release intent: pipeline stages + individual release steps.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ReleaseIntentState {
|
||||
pub release_intent_id: String,
|
||||
pub artifact_id: String,
|
||||
pub project: String,
|
||||
pub created_at: String,
|
||||
pub stages: Vec<PipelineRunStageState>,
|
||||
pub steps: Vec<ReleaseStepState>,
|
||||
}
|
||||
|
||||
/// Status of an individual release step (deploy work item).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ReleaseStepState {
|
||||
pub release_id: String,
|
||||
pub stage_id: Option<String>,
|
||||
pub destination_name: String,
|
||||
pub environment: String,
|
||||
pub status: String,
|
||||
pub queued_at: Option<String>,
|
||||
pub assigned_at: Option<String>,
|
||||
pub started_at: Option<String>,
|
||||
pub completed_at: Option<String>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
// ── Triggers (auto-release triggers) ────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Trigger {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub enabled: bool,
|
||||
pub branch_pattern: Option<String>,
|
||||
pub title_pattern: Option<String>,
|
||||
pub author_pattern: Option<String>,
|
||||
pub commit_message_pattern: Option<String>,
|
||||
pub source_type_pattern: Option<String>,
|
||||
pub target_environments: Vec<String>,
|
||||
pub target_destinations: Vec<String>,
|
||||
pub force_release: bool,
|
||||
pub use_pipeline: bool,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CreateTriggerInput {
|
||||
pub name: String,
|
||||
pub branch_pattern: Option<String>,
|
||||
pub title_pattern: Option<String>,
|
||||
pub author_pattern: Option<String>,
|
||||
pub commit_message_pattern: Option<String>,
|
||||
pub source_type_pattern: Option<String>,
|
||||
pub target_environments: Vec<String>,
|
||||
pub target_destinations: Vec<String>,
|
||||
pub force_release: bool,
|
||||
pub use_pipeline: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UpdateTriggerInput {
|
||||
pub enabled: Option<bool>,
|
||||
pub branch_pattern: Option<String>,
|
||||
pub title_pattern: Option<String>,
|
||||
pub author_pattern: Option<String>,
|
||||
pub commit_message_pattern: Option<String>,
|
||||
pub source_type_pattern: Option<String>,
|
||||
pub target_environments: Vec<String>,
|
||||
pub target_destinations: Vec<String>,
|
||||
pub force_release: Option<bool>,
|
||||
pub use_pipeline: Option<bool>,
|
||||
}
|
||||
|
||||
// ── Policies (deployment gating) ────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Policy {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub enabled: bool,
|
||||
pub policy_type: String,
|
||||
pub config: PolicyConfig,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum PolicyConfig {
|
||||
SoakTime {
|
||||
source_environment: String,
|
||||
target_environment: String,
|
||||
duration_seconds: i64,
|
||||
},
|
||||
BranchRestriction {
|
||||
target_environment: String,
|
||||
branch_pattern: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CreatePolicyInput {
|
||||
pub name: String,
|
||||
pub config: PolicyConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UpdatePolicyInput {
|
||||
pub enabled: Option<bool>,
|
||||
pub config: Option<PolicyConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PolicyEvaluation {
|
||||
pub policy_name: String,
|
||||
pub policy_type: String,
|
||||
pub passed: bool,
|
||||
pub reason: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PipelineStage {
|
||||
pub id: String,
|
||||
pub depends_on: Vec<String>,
|
||||
pub config: PipelineStageConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum PipelineStageConfig {
|
||||
Deploy { environment: String },
|
||||
Wait { duration_seconds: i64 },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ReleasePipeline {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub enabled: bool,
|
||||
pub stages: Vec<PipelineStage>,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CreateReleasePipelineInput {
|
||||
pub name: String,
|
||||
pub stages: Vec<PipelineStage>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UpdateReleasePipelineInput {
|
||||
pub enabled: Option<bool>,
|
||||
pub stages: Option<Vec<PipelineStage>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum PlatformError {
|
||||
#[error("not authenticated")]
|
||||
@@ -175,11 +382,170 @@ pub trait ForestPlatform: Send + Sync {
|
||||
slug: &str,
|
||||
) -> Result<Artifact, PlatformError>;
|
||||
|
||||
async fn list_environments(
|
||||
&self,
|
||||
access_token: &str,
|
||||
organisation: &str,
|
||||
) -> Result<Vec<Environment>, PlatformError>;
|
||||
|
||||
async fn list_destinations(
|
||||
&self,
|
||||
access_token: &str,
|
||||
organisation: &str,
|
||||
) -> Result<Vec<Destination>, PlatformError>;
|
||||
|
||||
async fn create_environment(
|
||||
&self,
|
||||
access_token: &str,
|
||||
organisation: &str,
|
||||
name: &str,
|
||||
description: Option<&str>,
|
||||
sort_order: i32,
|
||||
) -> Result<Environment, PlatformError>;
|
||||
|
||||
async fn create_destination(
|
||||
&self,
|
||||
access_token: &str,
|
||||
organisation: &str,
|
||||
name: &str,
|
||||
environment: &str,
|
||||
metadata: &std::collections::HashMap<String, String>,
|
||||
dest_type: Option<&DestinationType>,
|
||||
) -> Result<(), PlatformError>;
|
||||
|
||||
async fn update_destination(
|
||||
&self,
|
||||
access_token: &str,
|
||||
name: &str,
|
||||
metadata: &std::collections::HashMap<String, String>,
|
||||
) -> Result<(), PlatformError>;
|
||||
|
||||
async fn get_destination_states(
|
||||
&self,
|
||||
access_token: &str,
|
||||
organisation: &str,
|
||||
project: Option<&str>,
|
||||
) -> Result<DeploymentStates, PlatformError>;
|
||||
|
||||
async fn get_release_intent_states(
|
||||
&self,
|
||||
access_token: &str,
|
||||
organisation: &str,
|
||||
project: Option<&str>,
|
||||
include_completed: bool,
|
||||
) -> Result<Vec<ReleaseIntentState>, PlatformError>;
|
||||
|
||||
async fn release_artifact(
|
||||
&self,
|
||||
access_token: &str,
|
||||
artifact_id: &str,
|
||||
destinations: &[String],
|
||||
environments: &[String],
|
||||
use_pipeline: bool,
|
||||
) -> Result<(), PlatformError>;
|
||||
|
||||
async fn list_triggers(
|
||||
&self,
|
||||
access_token: &str,
|
||||
organisation: &str,
|
||||
project: &str,
|
||||
) -> Result<Vec<Trigger>, PlatformError>;
|
||||
|
||||
async fn create_trigger(
|
||||
&self,
|
||||
access_token: &str,
|
||||
organisation: &str,
|
||||
project: &str,
|
||||
input: &CreateTriggerInput,
|
||||
) -> Result<Trigger, PlatformError>;
|
||||
|
||||
async fn update_trigger(
|
||||
&self,
|
||||
access_token: &str,
|
||||
organisation: &str,
|
||||
project: &str,
|
||||
name: &str,
|
||||
input: &UpdateTriggerInput,
|
||||
) -> Result<Trigger, PlatformError>;
|
||||
|
||||
async fn delete_trigger(
|
||||
&self,
|
||||
access_token: &str,
|
||||
organisation: &str,
|
||||
project: &str,
|
||||
name: &str,
|
||||
) -> Result<(), PlatformError>;
|
||||
|
||||
async fn list_policies(
|
||||
&self,
|
||||
access_token: &str,
|
||||
organisation: &str,
|
||||
project: &str,
|
||||
) -> Result<Vec<Policy>, PlatformError>;
|
||||
|
||||
async fn create_policy(
|
||||
&self,
|
||||
access_token: &str,
|
||||
organisation: &str,
|
||||
project: &str,
|
||||
input: &CreatePolicyInput,
|
||||
) -> Result<Policy, PlatformError>;
|
||||
|
||||
async fn update_policy(
|
||||
&self,
|
||||
access_token: &str,
|
||||
organisation: &str,
|
||||
project: &str,
|
||||
name: &str,
|
||||
input: &UpdatePolicyInput,
|
||||
) -> Result<Policy, PlatformError>;
|
||||
|
||||
async fn delete_policy(
|
||||
&self,
|
||||
access_token: &str,
|
||||
organisation: &str,
|
||||
project: &str,
|
||||
name: &str,
|
||||
) -> Result<(), PlatformError>;
|
||||
|
||||
async fn list_release_pipelines(
|
||||
&self,
|
||||
access_token: &str,
|
||||
organisation: &str,
|
||||
project: &str,
|
||||
) -> Result<Vec<ReleasePipeline>, PlatformError>;
|
||||
|
||||
async fn create_release_pipeline(
|
||||
&self,
|
||||
access_token: &str,
|
||||
organisation: &str,
|
||||
project: &str,
|
||||
input: &CreateReleasePipelineInput,
|
||||
) -> Result<ReleasePipeline, PlatformError>;
|
||||
|
||||
async fn update_release_pipeline(
|
||||
&self,
|
||||
access_token: &str,
|
||||
organisation: &str,
|
||||
project: &str,
|
||||
name: &str,
|
||||
input: &UpdateReleasePipelineInput,
|
||||
) -> Result<ReleasePipeline, PlatformError>;
|
||||
|
||||
async fn delete_release_pipeline(
|
||||
&self,
|
||||
access_token: &str,
|
||||
organisation: &str,
|
||||
project: &str,
|
||||
name: &str,
|
||||
) -> Result<(), PlatformError>;
|
||||
|
||||
/// Get the spec (forest.cue) content for an artifact. Returns empty string if no spec was uploaded.
|
||||
async fn get_artifact_spec(
|
||||
&self,
|
||||
access_token: &str,
|
||||
artifact_id: &str,
|
||||
) -> Result<String, PlatformError>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user