Skip to main content

alien_core/
image_rewrite.rs

1//! Image URI utilities for the registry proxy.
2//!
3//! The manager IS the container registry — its `/v2/` endpoint serves images.
4//! Releases store proxy URIs (e.g., `manager.alien.dev/my-app:v1`).
5//! Only Lambda and Cloud Run need native registry URIs (ECR/GAR).
6
7/// Strip the registry hostname from an image URI, returning just the repo path.
8///
9/// Input: `us-central1-docker.pkg.dev/project/repo:tag`
10/// Output: `project/repo:tag`
11///
12/// Input: `manager.alien.dev/my-app:v1`
13/// Output: `my-app:v1`
14pub fn strip_registry_host(image_uri: &str) -> Option<String> {
15    // Skip local paths
16    if image_uri.starts_with('/') || image_uri.starts_with("./") {
17        return None;
18    }
19
20    // OCI image reference format: [host[:port]/]path[:tag|@digest]
21    // The host always contains a dot or colon (to distinguish from a path component).
22    let parts: Vec<&str> = image_uri.splitn(2, '/').collect();
23    if parts.len() == 2 && (parts[0].contains('.') || parts[0].contains(':')) {
24        Some(parts[1].to_string())
25    } else {
26        // No registry host prefix — return as-is
27        Some(image_uri.to_string())
28    }
29}
30
31/// Strip URL scheme and trailing slash from a URL, returning just host[:port].
32pub fn strip_url_scheme(url: &str) -> &str {
33    let trimmed = url.trim_end_matches('/');
34    trimmed
35        .strip_prefix("https://")
36        .or_else(|| trimmed.strip_prefix("http://"))
37        .unwrap_or(trimmed)
38}
39
40/// Resolve a proxy image URI to a native registry URI.
41///
42/// Used by Lambda (ECR) and Cloud Run (GAR) which require native registries.
43/// Strips the proxy host from the URI and prepends the native registry host,
44/// preserving the full repo path and tag/digest.
45///
46/// Input:  `manager.alien.dev/alien-e2e:fn-abc123`, `123456.dkr.ecr.us-east-1.amazonaws.com`
47/// Output: `123456.dkr.ecr.us-east-1.amazonaws.com/alien-e2e:fn-abc123`
48///
49/// Input:  `manager.alien.dev/project/repo/default:fn-abc123`, `us-central1-docker.pkg.dev`
50/// Output: `us-central1-docker.pkg.dev/project/repo/default:fn-abc123`
51pub fn resolve_native_image_uri(
52    proxy_image_uri: &str,
53    native_registry_host: &str,
54) -> Option<String> {
55    // Strip the proxy host to get /repo-path:tag or /repo-path@sha256:...
56    let repo_and_ref = strip_registry_host(proxy_image_uri)?;
57    if repo_and_ref.is_empty() {
58        return None;
59    }
60    Some(format!(
61        "{}/{}",
62        native_registry_host.trim_end_matches('/'),
63        repo_and_ref
64    ))
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn test_strip_registry_host_gar() {
73        assert_eq!(
74            strip_registry_host("us-central1-docker.pkg.dev/project/repo:tag"),
75            Some("project/repo:tag".to_string())
76        );
77    }
78
79    #[test]
80    fn test_strip_registry_host_ecr() {
81        assert_eq!(
82            strip_registry_host("123456.dkr.ecr.us-east-1.amazonaws.com/repo:tag"),
83            Some("repo:tag".to_string())
84        );
85    }
86
87    #[test]
88    fn test_strip_registry_host_proxy() {
89        assert_eq!(
90            strip_registry_host("manager.alien.dev/my-app:v1"),
91            Some("my-app:v1".to_string())
92        );
93    }
94
95    #[test]
96    fn test_strip_registry_host_localhost() {
97        assert_eq!(
98            strip_registry_host("localhost:5000/my-app:v1"),
99            Some("my-app:v1".to_string())
100        );
101    }
102
103    #[test]
104    fn test_strip_registry_host_no_host() {
105        assert_eq!(
106            strip_registry_host("simple-image:latest"),
107            Some("simple-image:latest".to_string())
108        );
109    }
110
111    #[test]
112    fn test_strip_registry_host_local_path() {
113        assert_eq!(strip_registry_host("/local/path/to/image.tar"), None);
114    }
115
116    #[test]
117    fn test_strip_url_scheme() {
118        assert_eq!(strip_url_scheme("https://example.com"), "example.com");
119        assert_eq!(strip_url_scheme("http://localhost:8080"), "localhost:8080");
120        assert_eq!(strip_url_scheme("https://example.com/"), "example.com");
121        assert_eq!(strip_url_scheme("manager:8080"), "manager:8080");
122    }
123
124    #[test]
125    fn test_resolve_native_image_uri_ecr() {
126        // ECR: proxy host stripped, repo path preserved, native host prepended
127        assert_eq!(
128            resolve_native_image_uri(
129                "manager.alien.dev/alien-e2e:fn-abc123",
130                "123456.dkr.ecr.us-east-1.amazonaws.com"
131            ),
132            Some("123456.dkr.ecr.us-east-1.amazonaws.com/alien-e2e:fn-abc123".to_string())
133        );
134    }
135
136    #[test]
137    fn test_resolve_native_image_uri_gar() {
138        // GAR: 3-segment repo path preserved from proxy URI
139        assert_eq!(
140            resolve_native_image_uri(
141                "manager.alien.dev/project/repo/default:my-fn-abc123",
142                "us-central1-docker.pkg.dev"
143            ),
144            Some("us-central1-docker.pkg.dev/project/repo/default:my-fn-abc123".to_string())
145        );
146    }
147
148    #[test]
149    fn test_resolve_native_image_uri_digest() {
150        assert_eq!(
151            resolve_native_image_uri(
152                "manager.alien.dev/alien-e2e@sha256:abcdef123456",
153                "123456.dkr.ecr.us-east-1.amazonaws.com"
154            ),
155            Some(
156                "123456.dkr.ecr.us-east-1.amazonaws.com/alien-e2e@sha256:abcdef123456".to_string()
157            )
158        );
159    }
160}