use super::*;
pub fn previous_version_label(events: &[EbEvent], current: &str) -> Option<String> {
events
.iter()
.filter_map(|e| e.version_label.as_deref())
.filter(|v| !v.is_empty())
.find(|v| *v != current)
.map(|v| v.to_string())
}
pub fn is_config_event(message: &str) -> bool {
let m = message.to_ascii_lowercase();
m.contains("version label")
|| m.contains("deploying")
|| m.contains("configuration")
|| m.contains("config setting")
}
pub fn compute_unavailability_count(
policy: &str,
batch_size: i32,
batch_size_type: &str,
asg_max: i32,
) -> i32 {
let asg_max = asg_max.max(1);
match policy {
p if p.eq_ignore_ascii_case("AllAtOnce") => asg_max,
p if p.eq_ignore_ascii_case("Rolling") => {
compute_batch_count(batch_size, batch_size_type, asg_max)
}
p if p.eq_ignore_ascii_case("RollingWithAdditionalBatch") => 0,
p if p.eq_ignore_ascii_case("Immutable") => 0,
p if p.eq_ignore_ascii_case("TrafficSplitting") => 0,
_ => asg_max,
}
}
pub fn compute_batch_count(batch_size: i32, batch_size_type: &str, asg_max: i32) -> i32 {
if batch_size_type.eq_ignore_ascii_case("Percentage") {
let pct = batch_size.clamp(1, 100);
let count = (asg_max * pct + 99) / 100;
count.max(1).min(asg_max)
} else {
batch_size.max(1).min(asg_max)
}
}
pub fn format_unavailability_line(policy: &str, unavailable: i32, asg_max: i32) -> (String, bool) {
let asg_max = asg_max.max(1);
let caution = unavailable > 0;
let plural = if unavailable == 1 {
"instance"
} else {
"instances"
};
let body = if unavailable == 0 {
format!("deploy plan: {policy} → no in-service unavailability")
} else {
format!("deploy plan: {policy} → max {unavailable}/{asg_max} {plural} unavailable")
};
(body, caution)
}
pub fn extract_unavailability_inputs(
opts: &[(String, String, String)],
) -> (String, i32, String, i32) {
let get = |ns: &str, name: &str| -> Option<&String> {
opts.iter()
.find(|(n, k, _)| n == ns && k == name)
.map(|(_, _, v)| v)
};
let policy = get("aws:elasticbeanstalk:command", "DeploymentPolicy")
.cloned()
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "AllAtOnce".to_string());
let batch_size = get("aws:elasticbeanstalk:command", "BatchSize")
.and_then(|s| s.parse::<i32>().ok())
.unwrap_or(1);
let batch_size_type = get("aws:elasticbeanstalk:command", "BatchSizeType")
.cloned()
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "Fixed".to_string());
let asg_max = get("aws:autoscaling:asg", "MaxSize")
.and_then(|s| s.parse::<i32>().ok())
.unwrap_or(1);
(policy, batch_size, batch_size_type, asg_max)
}
pub fn deploy_settled_green(status: &str, health: &str) -> bool {
status.eq_ignore_ascii_case("Ready")
&& (health.eq_ignore_ascii_case("Green") || health.eq_ignore_ascii_case("Ok"))
}
pub(crate) fn merge_app_latest_versions(prev: &[Application], next: &mut [Application]) {
let by_name: std::collections::HashMap<
&str,
(&Option<String>, &Option<chrono::DateTime<chrono::Utc>>),
> = prev
.iter()
.map(|a| {
(
a.name.as_str(),
(&a.latest_version_label, &a.latest_version_created),
)
})
.collect();
for app in next.iter_mut() {
let Some((label, created)) = by_name.get(app.name.as_str()) else {
continue;
};
if app.latest_version_label.is_none() {
app.latest_version_label = (*label).clone();
}
if app.latest_version_created.is_none() {
app.latest_version_created = **created;
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UpdateKind {
Deploy { version_label: Option<String> },
Config,
Scale,
Platform,
Generic,
}
pub fn classify_update_kind(events: &[crate::aws::Event]) -> UpdateKind {
for e in events {
let lower = e.message.to_lowercase();
if lower.contains("version label") {
return UpdateKind::Deploy {
version_label: extract_quoted_after(&e.message, "version label"),
};
}
if lower.contains("deploying") && lower.contains("version") {
return UpdateKind::Deploy {
version_label: extract_quoted_after(&e.message, "version"),
};
}
if lower.contains("platform") && (lower.contains("updat") || lower.contains("upgrad")) {
return UpdateKind::Platform;
}
if lower.contains("configuration") && lower.contains("updat") {
return UpdateKind::Config;
}
if (lower.contains("adding") || lower.contains("removing")) && lower.contains("instance") {
return UpdateKind::Scale;
}
}
UpdateKind::Generic
}