lightshuttle_export/
resolve.rs1use lightshuttle_manifest::{ExportConfig, ImagePullPolicy};
9
10use crate::model::Target;
11
12const DEFAULT_REPLICAS: u32 = 1;
15
16const DEFAULT_CHART_VERSION: &str = "0.1.0";
19
20#[must_use]
23pub fn enabled_for(target: Target, resource: &str, export: Option<&ExportConfig>) -> bool {
24 let Some(export) = export else { return true };
25 let enabled = match target {
26 Target::Compose => export
27 .compose
28 .as_ref()
29 .and_then(|t| t.resources.get(resource))
30 .and_then(|r| r.enabled),
31 Target::Kubernetes => export
32 .kubernetes
33 .as_ref()
34 .and_then(|t| t.resources.get(resource))
35 .and_then(|r| r.enabled),
36 Target::Helm => export
37 .helm
38 .as_ref()
39 .and_then(|t| t.resources.get(resource))
40 .and_then(|r| r.enabled),
41 };
42 enabled.unwrap_or(true)
43}
44
45#[must_use]
49pub fn replicas_for(target: Target, resource: &str, export: Option<&ExportConfig>) -> u32 {
50 let Some(export) = export else {
51 return DEFAULT_REPLICAS;
52 };
53 match target {
54 Target::Compose => DEFAULT_REPLICAS,
55 Target::Kubernetes => export.kubernetes.as_ref().map_or(DEFAULT_REPLICAS, |t| {
56 t.resources
57 .get(resource)
58 .and_then(|r| r.replicas)
59 .or(t.replicas)
60 .unwrap_or(DEFAULT_REPLICAS)
61 }),
62 Target::Helm => export.helm.as_ref().map_or(DEFAULT_REPLICAS, |t| {
63 t.resources
64 .get(resource)
65 .and_then(|r| r.replicas)
66 .or(t.replicas)
67 .unwrap_or(DEFAULT_REPLICAS)
68 }),
69 }
70}
71
72#[must_use]
75pub fn namespace_for(project: &str, export: Option<&ExportConfig>) -> String {
76 export
77 .and_then(|e| e.kubernetes.as_ref())
78 .and_then(|k| k.namespace.clone())
79 .unwrap_or_else(|| project.to_owned())
80}
81
82#[must_use]
85pub fn image_pull_policy_for(resource: &str, export: Option<&ExportConfig>) -> ImagePullPolicy {
86 export
87 .and_then(|e| e.kubernetes.as_ref())
88 .map(|k| {
89 k.resources
90 .get(resource)
91 .and_then(|r| r.image_pull_policy)
92 .or(k.image_pull_policy)
93 .unwrap_or_default()
94 })
95 .unwrap_or_default()
96}
97
98#[must_use]
100pub fn chart_name_for(project: &str, export: Option<&ExportConfig>) -> String {
101 export
102 .and_then(|e| e.helm.as_ref())
103 .and_then(|h| h.chart_name.clone())
104 .unwrap_or_else(|| project.to_owned())
105}
106
107#[must_use]
110pub fn chart_version_for(project_version: Option<&str>, export: Option<&ExportConfig>) -> String {
111 export
112 .and_then(|e| e.helm.as_ref())
113 .and_then(|h| h.chart_version.clone())
114 .or_else(|| project_version.map(ToOwned::to_owned))
115 .unwrap_or_else(|| DEFAULT_CHART_VERSION.to_owned())
116}
117
118#[must_use]
125pub(crate) fn dns_name(name: &str) -> String {
126 let normalized: String = name
127 .to_lowercase()
128 .chars()
129 .map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
130 .collect();
131 let prefixed = if normalized
132 .chars()
133 .next()
134 .is_none_or(|c| c == '-' || c.is_ascii_digit())
135 {
136 format!("x{normalized}")
137 } else {
138 normalized
139 };
140 let truncated: String = prefixed.chars().take(63).collect();
141 truncated.trim_end_matches('-').to_owned()
142}
143
144#[cfg(test)]
145mod tests {
146 use super::dns_name;
147
148 #[test]
149 fn dns_name_already_valid() {
150 assert_eq!(dns_name("my-service"), "my-service");
151 }
152
153 #[test]
154 fn dns_name_lowercase() {
155 assert_eq!(dns_name("MyService"), "myservice");
156 }
157
158 #[test]
159 fn dns_name_underscores_become_hyphens() {
160 assert_eq!(dns_name("my_service"), "my-service");
161 }
162
163 #[test]
164 fn dns_name_leading_digit_gets_prefix() {
165 assert_eq!(dns_name("1redis"), "x1redis");
166 }
167
168 #[test]
169 fn dns_name_leading_hyphen_gets_prefix() {
170 assert_eq!(dns_name("-leading"), "x-leading");
171 }
172
173 #[test]
174 fn dns_name_trailing_hyphen_stripped() {
175 assert_eq!(dns_name("trailing-"), "trailing");
176 }
177
178 #[test]
179 fn dns_name_truncated_to_63() {
180 let long = "a".repeat(70);
181 assert_eq!(dns_name(&long).len(), 63);
182 }
183
184 #[test]
185 fn dns_name_truncation_strips_trailing_hyphen() {
186 let name = format!("{}-b", "a".repeat(62));
187 let result = dns_name(&name);
188 assert!(
189 !result.ends_with('-'),
190 "must not end with hyphen after truncation"
191 );
192 assert!(result.len() <= 63);
193 }
194}