Skip to main content

fakecloud_core/
ecr_uri.rs

1//! AWS ECR URI recognition + translation to the local fakecloud OCI v2 endpoint.
2//!
3//! Shared between ECS and Lambda container runtimes: when a task definition
4//! or Lambda function references `<account>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>`,
5//! fakecloud can't pull from AWS — there is no AWS account. Instead we
6//! translate the URI to `127.0.0.1:<server-port>/<repo>:<tag>` and pull
7//! from fakecloud's own OCI v2 registry (which is just another route on
8//! the same HTTP server). Docker treats `127.0.0.1:<port>` as an insecure
9//! registry automatically on both Linux and Docker Desktop, so no daemon
10//! config is required.
11
12/// Detect whether `image` is an AWS private-ECR URI. Match shape:
13/// `<any>.dkr.ecr.<any>.amazonaws.com/<path>[:<tag>|@sha256:<digest>]`.
14/// The registry host is anchored so arbitrary-path substrings like
15/// `docker.io/user/.dkr.ecr.whatever.amazonaws.com/bar` don't get
16/// misclassified.
17pub fn is_aws_ecr_uri(image: &str) -> bool {
18    let Some((registry, _path)) = image.split_once('/') else {
19        return false;
20    };
21    registry.contains(".dkr.ecr.") && registry.ends_with(".amazonaws.com")
22}
23
24/// If `image` is an AWS private-ECR URI, return the local-registry URI
25/// that fakecloud's OCI v2 endpoint serves the same content at. Returns
26/// `None` for any other reference (public ECR, Docker Hub, etc.) — those
27/// go straight to the upstream daemon.
28///
29/// Docker's localhost-registry behaviour means the daemon on both Linux
30/// and macOS Docker Desktop accepts `127.0.0.1:<port>` over plain HTTP.
31pub fn translate_to_local(image: &str, server_port: u16) -> Option<String> {
32    translate_to_local_at(image, "127.0.0.1", server_port)
33}
34
35/// Like [`translate_to_local`] but lets the caller pick the host the
36/// rewritten URI should point at. Lambda's Kubernetes backend needs
37/// this — inside a cluster, the in-cluster fakecloud Service hostname
38/// (not `127.0.0.1`) is the only address Lambda Pods can reach.
39pub fn translate_to_local_at(image: &str, host: &str, server_port: u16) -> Option<String> {
40    if !is_aws_ecr_uri(image) {
41        return None;
42    }
43    let (_registry, path) = image.split_once('/')?;
44    if path.is_empty() {
45        return None;
46    }
47    Some(format!("{host}:{server_port}/{path}"))
48}
49
50/// Is `image` a digest-pinned reference (`repo@sha256:...`)? Docker's
51/// `tag` subcommand cannot target a digest ref, so the runtime must
52/// skip retagging and run the container under the local URI directly.
53pub fn is_digest_ref(image: &str) -> bool {
54    image.contains("@sha256:")
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    fn detects_private_ecr_uri() {
63        assert!(is_aws_ecr_uri(
64            "123456789012.dkr.ecr.us-east-1.amazonaws.com/repo:tag"
65        ));
66        assert!(is_aws_ecr_uri(
67            "123456789012.dkr.ecr.eu-west-2.amazonaws.com/team/svc:v1"
68        ));
69        assert!(!is_aws_ecr_uri("public.ecr.aws/lambda/python:3.12"));
70        assert!(!is_aws_ecr_uri("docker.io/library/alpine:3.20"));
71        assert!(!is_aws_ecr_uri("alpine:3.20"));
72        // Host anchoring — path containing the tokens must NOT match.
73        assert!(!is_aws_ecr_uri(
74            "docker.io/team/evil.dkr.ecr.us-east-1.amazonaws.com/repo:tag"
75        ));
76    }
77
78    #[test]
79    fn detects_digest_ref() {
80        assert!(is_digest_ref(
81            "123456789012.dkr.ecr.us-east-1.amazonaws.com/repo@sha256:abc"
82        ));
83        assert!(!is_digest_ref(
84            "123456789012.dkr.ecr.us-east-1.amazonaws.com/repo:tag"
85        ));
86    }
87
88    #[test]
89    fn translates_private_ecr_uri() {
90        assert_eq!(
91            translate_to_local(
92                "123456789012.dkr.ecr.us-east-1.amazonaws.com/repo:tag",
93                4566
94            ),
95            Some("127.0.0.1:4566/repo:tag".to_string())
96        );
97        assert_eq!(
98            translate_to_local(
99                "123456789012.dkr.ecr.us-east-1.amazonaws.com/team/svc:v1",
100                8080
101            ),
102            Some("127.0.0.1:8080/team/svc:v1".to_string())
103        );
104        assert_eq!(
105            translate_to_local(
106                "123456789012.dkr.ecr.us-east-1.amazonaws.com/repo@sha256:abc",
107                4566
108            ),
109            Some("127.0.0.1:4566/repo@sha256:abc".to_string())
110        );
111        assert_eq!(translate_to_local("alpine:3.20", 4566), None);
112        assert_eq!(
113            translate_to_local("public.ecr.aws/lambda/python:3.12", 4566),
114            None
115        );
116    }
117
118    #[test]
119    fn rejects_empty_path() {
120        assert_eq!(
121            translate_to_local("123456789012.dkr.ecr.us-east-1.amazonaws.com/", 4566),
122            None
123        );
124    }
125
126    #[test]
127    fn translate_to_local_at_overrides_host() {
128        assert_eq!(
129            translate_to_local_at(
130                "123456789012.dkr.ecr.us-east-1.amazonaws.com/repo:tag",
131                "fakecloud.fakecloud.svc.cluster.local",
132                4566
133            ),
134            Some("fakecloud.fakecloud.svc.cluster.local:4566/repo:tag".to_string())
135        );
136    }
137
138    #[test]
139    fn translate_to_local_at_returns_none_for_non_ecr() {
140        assert_eq!(translate_to_local_at("alpine:3.20", "anything", 4566), None);
141    }
142}