use lightshuttle_manifest::{ExportConfig, ImagePullPolicy};
use crate::model::Target;
pub const SECRET_MARKERS: &[&str] = &[
"PASSWORD",
"PASSWD",
"PASS",
"SECRET",
"TOKEN",
"KEY",
"CREDENTIAL",
"AUTH",
"CERT",
"PWD",
];
const DEFAULT_REPLICAS: u32 = 1;
const DEFAULT_CHART_VERSION: &str = "0.1.0";
#[must_use]
pub fn enabled_for(target: Target, resource: &str, export: Option<&ExportConfig>) -> bool {
let Some(export) = export else { return true };
let enabled = match target {
Target::Compose => export
.compose
.as_ref()
.and_then(|t| t.resources.get(resource))
.and_then(|r| r.enabled),
Target::Kubernetes => export
.kubernetes
.as_ref()
.and_then(|t| t.resources.get(resource))
.and_then(|r| r.enabled),
Target::Helm => export
.helm
.as_ref()
.and_then(|t| t.resources.get(resource))
.and_then(|r| r.enabled),
};
enabled.unwrap_or(true)
}
#[must_use]
pub fn replicas_for(target: Target, resource: &str, export: Option<&ExportConfig>) -> u32 {
let Some(export) = export else {
return DEFAULT_REPLICAS;
};
match target {
Target::Compose => DEFAULT_REPLICAS,
Target::Kubernetes => export.kubernetes.as_ref().map_or(DEFAULT_REPLICAS, |t| {
t.resources
.get(resource)
.and_then(|r| r.replicas)
.or(t.replicas)
.unwrap_or(DEFAULT_REPLICAS)
}),
Target::Helm => export.helm.as_ref().map_or(DEFAULT_REPLICAS, |t| {
t.resources
.get(resource)
.and_then(|r| r.replicas)
.or(t.replicas)
.unwrap_or(DEFAULT_REPLICAS)
}),
}
}
#[must_use]
pub fn namespace_for(project: &str, export: Option<&ExportConfig>) -> String {
export
.and_then(|e| e.kubernetes.as_ref())
.and_then(|k| k.namespace.clone())
.unwrap_or_else(|| project.to_owned())
}
#[must_use]
pub fn image_pull_policy_for(resource: &str, export: Option<&ExportConfig>) -> ImagePullPolicy {
export
.and_then(|e| e.kubernetes.as_ref())
.map(|k| {
k.resources
.get(resource)
.and_then(|r| r.image_pull_policy)
.or(k.image_pull_policy)
.unwrap_or_default()
})
.unwrap_or_default()
}
#[must_use]
pub fn chart_name_for(project: &str, export: Option<&ExportConfig>) -> String {
export
.and_then(|e| e.helm.as_ref())
.and_then(|h| h.chart_name.clone())
.unwrap_or_else(|| project.to_owned())
}
#[must_use]
pub fn chart_version_for(project_version: Option<&str>, export: Option<&ExportConfig>) -> String {
export
.and_then(|e| e.helm.as_ref())
.and_then(|h| h.chart_version.clone())
.or_else(|| project_version.map(ToOwned::to_owned))
.unwrap_or_else(|| DEFAULT_CHART_VERSION.to_owned())
}
#[must_use]
pub(crate) fn dns_name(name: &str) -> String {
let normalized: String = name
.to_lowercase()
.chars()
.map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
.collect();
let prefixed = if normalized
.chars()
.next()
.is_none_or(|c| c == '-' || c.is_ascii_digit())
{
format!("x{normalized}")
} else {
normalized
};
let truncated: String = prefixed.chars().take(63).collect();
truncated.trim_end_matches('-').to_owned()
}
#[cfg(test)]
mod tests {
use super::dns_name;
#[test]
fn dns_name_already_valid() {
assert_eq!(dns_name("my-service"), "my-service");
}
#[test]
fn dns_name_lowercase() {
assert_eq!(dns_name("MyService"), "myservice");
}
#[test]
fn dns_name_underscores_become_hyphens() {
assert_eq!(dns_name("my_service"), "my-service");
}
#[test]
fn dns_name_leading_digit_gets_prefix() {
assert_eq!(dns_name("1redis"), "x1redis");
}
#[test]
fn dns_name_leading_hyphen_gets_prefix() {
assert_eq!(dns_name("-leading"), "x-leading");
}
#[test]
fn dns_name_trailing_hyphen_stripped() {
assert_eq!(dns_name("trailing-"), "trailing");
}
#[test]
fn dns_name_truncated_to_63() {
let long = "a".repeat(70);
assert_eq!(dns_name(&long).len(), 63);
}
#[test]
fn dns_name_truncation_strips_trailing_hyphen() {
let name = format!("{}-b", "a".repeat(62));
let result = dns_name(&name);
assert!(
!result.ends_with('-'),
"must not end with hyphen after truncation"
);
assert!(result.len() <= 63);
}
}