Skip to content

Commit 8e88225

Browse files
authored
Merge pull request #727 from factorial-io/claude/extract-auto-domain-helper-d02vc
Refactor domain resolution logic into ServicePortMapping method
2 parents 06acf53 + 34190ba commit 8e88225

File tree

5 files changed

+103
-56
lines changed

5 files changed

+103
-56
lines changed

config/casbin/policy.yaml

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
scopes:
2-
client-a:
3-
description: Client A
4-
created_at: 2024-01-01T00:00:00Z
52
client-b:
63
description: Client B
74
created_at: 2024-01-01T00:00:00Z
8-
qa:
9-
description: QA
5+
client-a:
6+
description: Client A
107
created_at: 2024-01-01T00:00:00Z
118
default:
129
description: Default scope for unassigned apps
1310
created_at: 2024-01-01T00:00:00Z
11+
qa:
12+
description: QA
13+
created_at: 2024-01-01T00:00:00Z
1414
roles:
15+
viewer:
16+
permissions:
17+
- view
18+
description: Read-only access
19+
admin:
20+
permissions:
21+
- '*'
22+
description: Full system access
1523
operator:
1624
permissions:
1725
- view
@@ -26,54 +34,46 @@ roles:
2634
- logs
2735
- create
2836
description: Developer access - all except destroy
29-
viewer:
30-
permissions:
31-
- view
32-
description: Read-only access
33-
admin:
34-
permissions:
35-
- '*'
36-
description: Full system access
3737
assignments:
38-
identifier:hello-world:
38+
dev:system:internal:
39+
- role: admin
40+
scopes:
41+
- '*'
42+
identifier:client-a:
3943
- role: developer
4044
scopes:
4145
- client-a
46+
identifier:test-service:
47+
- role: admin
48+
scopes:
49+
- client-a
4250
- client-b
4351
- qa
52+
- default
4453
identifier:admin:
4554
- role: admin
4655
scopes:
4756
- client-a
4857
- client-b
4958
- qa
5059
- default
51-
'*':
52-
- role: viewer
53-
scopes:
54-
- default
55-
identifier:client-a:
60+
'@factorial.io':
5661
- role: developer
5762
scopes:
5863
- client-a
59-
dev:system:internal:
60-
- role: admin
61-
scopes:
62-
- '*'
63-
identifier:test-service:
64-
- role: admin
64+
- client-b
65+
- qa
66+
identifier:hello-world:
67+
- role: developer
6568
scopes:
6669
- client-a
6770
- client-b
6871
- qa
69-
- default
7072
xxxstephan@factorial.io:
7173
- role: admin
7274
scopes:
7375
- '*'
74-
'@factorial.io':
75-
- role: developer
76+
'*':
77+
- role: viewer
7678
scopes:
77-
- client-a
78-
- client-b
79-
- qa
79+
- default

scotty-core/src/apps/app_data/service.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,22 @@ pub struct ServicePortMapping {
88
pub domains: Vec<String>,
99
}
1010

11+
impl ServicePortMapping {
12+
/// Returns the effective domains for this service.
13+
///
14+
/// If custom domains are configured, returns those.
15+
/// Otherwise, returns the auto-generated domain: `{service}.{app_domain}`.
16+
pub fn get_domains(&self, app_domain: &str) -> Vec<String> {
17+
if !self.domains.is_empty() {
18+
self.domains.clone()
19+
} else if !app_domain.is_empty() {
20+
vec![format!("{}.{}", self.service, app_domain)]
21+
} else {
22+
vec![]
23+
}
24+
}
25+
}
26+
1127
#[derive(Deserialize)]
1228
#[serde(untagged)]
1329
enum DomainField {
@@ -90,4 +106,41 @@ mod tests {
90106
assert_eq!(mapping.port, 3000);
91107
assert_eq!(mapping.domains, vec!["api1.com", "api2.com"]);
92108
}
109+
110+
#[test]
111+
fn test_get_domains_with_custom_domains() {
112+
let mapping = ServicePortMapping {
113+
service: "web".to_string(),
114+
port: 8080,
115+
domains: vec!["custom.example.com".to_string()],
116+
};
117+
assert_eq!(
118+
mapping.get_domains("myapp.apps.example.com"),
119+
vec!["custom.example.com"]
120+
);
121+
}
122+
123+
#[test]
124+
fn test_get_domains_auto_generated() {
125+
let mapping = ServicePortMapping {
126+
service: "web".to_string(),
127+
port: 8080,
128+
domains: vec![],
129+
};
130+
assert_eq!(
131+
mapping.get_domains("myapp.apps.example.com"),
132+
vec!["web.myapp.apps.example.com"]
133+
);
134+
}
135+
136+
#[test]
137+
fn test_get_domains_empty_app_domain() {
138+
let mapping = ServicePortMapping {
139+
service: "web".to_string(),
140+
port: 8080,
141+
domains: vec![],
142+
};
143+
let result: Vec<String> = vec![];
144+
assert_eq!(mapping.get_domains(""), result);
145+
}
93146
}

scotty-core/src/apps/shared_app_list.rs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -110,26 +110,16 @@ impl SharedAppList {
110110
pub async fn find_app_by_domain(&self, domain: &str) -> Option<AppData> {
111111
let apps = self.apps.read().await;
112112
for app in apps.values() {
113-
// Check settings-based domains
113+
// Check settings-based domains (custom and auto-generated)
114114
if let Some(settings) = &app.settings {
115115
for service in &settings.public_services {
116-
// Check explicit custom domains
117116
if service
118-
.domains
117+
.get_domains(&settings.domain)
119118
.iter()
120119
.any(|d| d.eq_ignore_ascii_case(domain))
121120
{
122121
return Some(app.clone());
123122
}
124-
// Check auto-generated domain: {service_name}.{settings.domain}
125-
// Must match the Traefik label format in
126-
// scotty/src/docker/loadbalancer/traefik.rs
127-
if !settings.domain.is_empty() {
128-
let auto_domain = format!("{}.{}", service.service, settings.domain);
129-
if auto_domain.eq_ignore_ascii_case(domain) {
130-
return Some(app.clone());
131-
}
132-
}
133123
}
134124
}
135125

@@ -268,7 +258,7 @@ mod tests {
268258
#[tokio::test]
269259
async fn test_find_app_by_domain_case_insensitive() {
270260
let list = SharedAppList::new();
271-
let app = make_app_with_settings(
261+
let app_custom = make_app_with_settings(
272262
"myapp",
273263
"myapp.example.com",
274264
vec![ServicePortMapping {
@@ -277,16 +267,26 @@ mod tests {
277267
domains: vec!["Custom.Example.COM".to_string()],
278268
}],
279269
);
280-
list.add_app(app).await.unwrap();
270+
let app_auto = make_app_with_settings(
271+
"otherapp",
272+
"otherapp.example.com",
273+
vec![ServicePortMapping {
274+
service: "api".to_string(),
275+
port: 3000,
276+
domains: vec![],
277+
}],
278+
);
279+
list.add_app(app_custom).await.unwrap();
280+
list.add_app(app_auto).await.unwrap();
281281

282-
// Lowercase lookup should match uppercase stored domain
282+
// Lowercase lookup should match uppercase stored custom domain
283283
let found = list.find_app_by_domain("custom.example.com").await;
284284
assert!(found.is_some());
285285
assert_eq!(found.unwrap().name, "myapp");
286286

287287
// Uppercase lookup should match auto-generated domain
288-
let found = list.find_app_by_domain("WEB.MYAPP.EXAMPLE.COM").await;
288+
let found = list.find_app_by_domain("API.OTHERAPP.EXAMPLE.COM").await;
289289
assert!(found.is_some());
290-
assert_eq!(found.unwrap().name, "myapp");
290+
assert_eq!(found.unwrap().name, "otherapp");
291291
}
292292
}

scotty/src/docker/loadbalancer/haproxy.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,7 @@ impl LoadBalancerImpl for HaproxyLoadBalancer {
103103
let environment = service_config.environment.as_mut().unwrap();
104104
environment.insert(
105105
"VHOST".into(),
106-
match &service.domains.is_empty() {
107-
false => service.domains.join(" "),
108-
true => format!("{}.{}", &service.service, &settings.domain),
109-
},
106+
service.get_domains(&settings.domain).join(" "),
110107
);
111108
environment.insert("VPORT".into(), format!("{}", &service.port));
112109

scotty/src/docker/loadbalancer/traefik.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,7 @@ impl LoadBalancerImpl for TraefikLoadBalancer {
113113
// Add Traefik labels
114114
labels.insert("traefik.enable".to_string(), "true".to_string());
115115

116-
let domains = match &service.domains.is_empty() {
117-
false => &service.domains,
118-
true => &vec![format!("{}.{}", &service.service, &settings.domain)],
119-
};
116+
let domains = service.get_domains(&settings.domain);
120117
for (idx, domain) in domains.iter().enumerate() {
121118
labels.insert(
122119
format!("traefik.http.routers.{}-{}.rule", &service_name, idx),

0 commit comments

Comments
 (0)