feat: add many things

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
2026-03-08 23:00:03 +01:00
parent 45353089c2
commit 5a5f9a3003
104 changed files with 23417 additions and 2027 deletions

View File

@@ -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,

View File

@@ -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)]