use super::controller::DeploymentUrls;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
pub enum DeploymentStatus {
#[default]
Pending,
Building,
Pushing,
Pushed, Deploying,
Healthy,
Unhealthy,
Cancelling,
Cancelled,
Terminating,
Stopped,
Superseded,
Failed,
Expired,
}
impl std::fmt::Display for DeploymentStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DeploymentStatus::Pending => write!(f, "Pending"),
DeploymentStatus::Building => write!(f, "Building"),
DeploymentStatus::Pushing => write!(f, "Pushing"),
DeploymentStatus::Pushed => write!(f, "Pushed"),
DeploymentStatus::Deploying => write!(f, "Deploying"),
DeploymentStatus::Healthy => write!(f, "Healthy"),
DeploymentStatus::Unhealthy => write!(f, "Unhealthy"),
DeploymentStatus::Cancelling => write!(f, "Cancelling"),
DeploymentStatus::Cancelled => write!(f, "Cancelled"),
DeploymentStatus::Terminating => write!(f, "Terminating"),
DeploymentStatus::Stopped => write!(f, "Stopped"),
DeploymentStatus::Superseded => write!(f, "Superseded"),
DeploymentStatus::Failed => write!(f, "Failed"),
DeploymentStatus::Expired => write!(f, "Expired"),
}
}
}
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct Deployment {
#[serde(default)]
pub id: String,
pub deployment_id: String,
pub project: String, pub created_by: String, pub created_by_email: String, #[serde(default)]
pub status: DeploymentStatus,
#[serde(default = "default_group")]
pub deployment_group: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<String>, #[serde(skip_serializing_if = "Option::is_none")]
pub error_message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub completed_at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub build_logs: Option<String>,
#[serde(default)]
pub controller_metadata: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub primary_url: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub custom_domain_urls: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub image: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub image_digest: Option<String>,
#[serde(default)]
pub http_port: u16,
#[serde(default)]
pub is_active: bool,
#[serde(default)]
pub created: String,
#[serde(default)]
pub updated: String,
}
fn default_group() -> String {
DEFAULT_DEPLOYMENT_GROUP.to_string()
}
pub const DEFAULT_DEPLOYMENT_GROUP: &str = "default";
pub fn normalize_deployment_group(deployment_group: &str) -> String {
let mut result = String::new();
let mut last_was_invalid = false;
for ch in deployment_group.chars() {
if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' || ch == '.' {
result.push(ch);
last_was_invalid = false;
} else if !last_was_invalid {
result.push_str("--");
last_was_invalid = true;
}
}
result
.trim_matches(|c: char| !c.is_ascii_alphanumeric())
.to_string()
}
pub fn rise_system_env_vars(
public_url: &str,
deployment_group: &str,
deployment_urls: &DeploymentUrls,
) -> Vec<(String, String)> {
let mut all_urls = vec![deployment_urls.default_url.clone()];
all_urls.extend(deployment_urls.custom_domain_urls.clone());
let app_urls_json = serde_json::to_string(&all_urls).unwrap_or_else(|_| "[]".to_string());
vec![
("RISE_ISSUER".to_string(), public_url.to_string()),
(
"RISE_APP_URL".to_string(),
deployment_urls.primary_url.clone(),
),
("RISE_APP_URLS".to_string(), app_urls_json),
(
"RISE_DEPLOYMENT_GROUP".to_string(),
deployment_group.to_string(),
),
(
"RISE_DEPLOYMENT_GROUP_NORMALIZED".to_string(),
normalize_deployment_group(deployment_group),
),
]
}
#[derive(Debug, Deserialize)]
pub struct CreateDeploymentRequest {
pub project: String, #[serde(skip_serializing_if = "Option::is_none")]
pub image: Option<String>, #[serde(default = "default_group")]
pub group: String, #[serde(skip_serializing_if = "Option::is_none")]
pub expires_in: Option<String>, #[serde(skip_serializing_if = "Option::is_none")]
pub http_port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub from_deployment: Option<String>, #[serde(default)]
pub use_source_env_vars: bool, #[serde(default)]
pub push_image: bool, }
#[derive(Debug, Serialize)]
pub struct CreateDeploymentResponse {
pub deployment_id: String,
pub image_tag: String, pub credentials: crate::server::registry::models::RegistryCredentials,
}
#[derive(Debug, Deserialize)]
pub struct UpdateDeploymentStatusRequest {
pub status: DeploymentStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub error_message: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normalize_deployment_group() {
assert_eq!(normalize_deployment_group("default"), "default");
assert_eq!(normalize_deployment_group("mr/123"), "mr--123");
assert_eq!(normalize_deployment_group("mr-123"), "mr-123");
assert_eq!(normalize_deployment_group("/leading"), "leading");
assert_eq!(normalize_deployment_group("trailing/"), "trailing");
assert_eq!(normalize_deployment_group("/both/"), "both");
assert_eq!(normalize_deployment_group(".dotted."), "dotted");
assert_eq!(normalize_deployment_group("_underscored_"), "underscored");
assert_eq!(normalize_deployment_group("_.-mixed-._"), "mixed");
assert_eq!(normalize_deployment_group("mr//123"), "mr--123");
assert_eq!(normalize_deployment_group("a///b"), "a--b");
assert_eq!(normalize_deployment_group(""), "");
assert_eq!(normalize_deployment_group("/"), "");
assert_eq!(normalize_deployment_group("///"), "");
assert_eq!(normalize_deployment_group("a.b_c"), "a.b_c");
}
#[test]
fn test_rise_system_env_vars_default_group() {
let urls = DeploymentUrls {
default_url: "https://myapp.rise.dev".to_string(),
primary_url: "https://myapp.rise.dev".to_string(),
custom_domain_urls: vec![],
};
let vars = rise_system_env_vars("https://rise.dev", "default", &urls);
let map: std::collections::HashMap<_, _> = vars.into_iter().collect();
assert_eq!(map["RISE_ISSUER"], "https://rise.dev");
assert_eq!(map["RISE_APP_URL"], "https://myapp.rise.dev");
assert_eq!(map["RISE_APP_URLS"], r#"["https://myapp.rise.dev"]"#);
assert_eq!(map["RISE_DEPLOYMENT_GROUP"], "default");
assert_eq!(map["RISE_DEPLOYMENT_GROUP_NORMALIZED"], "default");
}
#[test]
fn test_rise_system_env_vars_custom_group_with_domains() {
let urls = DeploymentUrls {
default_url: "https://myapp-mr--42.rise.dev".to_string(),
primary_url: "https://custom.example.com".to_string(),
custom_domain_urls: vec!["https://custom.example.com".to_string()],
};
let vars = rise_system_env_vars("https://rise.dev", "mr/42", &urls);
let map: std::collections::HashMap<_, _> = vars.into_iter().collect();
assert_eq!(map["RISE_APP_URL"], "https://custom.example.com");
assert_eq!(
map["RISE_APP_URLS"],
r#"["https://myapp-mr--42.rise.dev","https://custom.example.com"]"#
);
assert_eq!(map["RISE_DEPLOYMENT_GROUP"], "mr/42");
assert_eq!(map["RISE_DEPLOYMENT_GROUP_NORMALIZED"], "mr--42");
}
}