Skip to main content

rightsize_modules/
floci.rs

1//! A [floci.io](https://floci.io) cloud emulator — one native Quarkus image per cloud
2//! provider, each speaking that provider's REST APIs against an in-memory backing
3//! store. One module type covers all three variants; pick one with the
4//! [`FlociContainer::aws`]/[`FlociContainer::azure`]/[`FlociContainer::gcp`] factory
5//! functions rather than a bare constructor — each factory pins the provider's own
6//! image and guest port.
7//!
8//! ### Readiness — `/health` works uniformly, unlike the AWS-flavored `/_localstack/health`
9//!
10//! The AWS variant ships a LocalStack-compatible `/_localstack/health` endpoint (spec
11//! hypothesis confirmed), but the Azure and GCP variants do not carry that path (Azure:
12//! `501`; GCP: `404`). All three, however, answer a plain **`GET /health`** with `200`
13//! and a small JSON status body the moment the embedded Quarkus HTTP listener is up —
14//! verified directly against real boots of `floci/floci:1.5.30`,
15//! `floci/floci-az:0.8.0`, and `floci/floci-gcp:0.4.0`. `/health` is pinned as the one
16//! wait path that works across all three variants; no log-wait fallback was needed.
17//!
18//! ### No signing needed — verified against the AWS variant's S3 surface
19//!
20//! The AWS variant's S3-shaped REST endpoints accept unsigned requests with no
21//! `Authorization` header at all: `PUT /<bucket>`, `PUT /<bucket>/<key>`, and `GET
22//! /<bucket>/<key>` all round-trip successfully with a bare HTTP client call — no
23//! SigV4, no AWS SDK dependency required. This module's IT exercises that plain-REST
24//! path rather than pulling in an AWS SDK.
25//!
26//! ### Memory — tiny, no ladder needed
27//!
28//! All three images are native (GraalVM) Quarkus binaries; a real boot settles at
29//! roughly 11-27 MiB RSS (`docker stats`), and each variant boots and answers
30//! `/health` under msb's default microVM RAM with no `with_memory_limit` override.
31//!
32//! No control characters were found in any of the three images' baked env (checked
33//! via `docker image inspect`).
34
35use rightsize::{Container, ContainerGuard, Result, Wait};
36
37const AWS_PORT: u16 = 4566;
38const AZURE_PORT: u16 = 4577;
39const GCP_PORT: u16 = 4588;
40
41/// A floci.io cloud emulator container — one AWS/Azure/GCP variant, picked via
42/// [`FlociContainer::aws`]/[`FlociContainer::azure`]/[`FlociContainer::gcp`].
43pub struct FlociContainer {
44    container: Container,
45    port: u16,
46}
47
48impl FlociContainer {
49    fn new(image: &str, port: u16) -> Self {
50        Self {
51            container: Container::new(image)
52                .with_exposed_ports(&[port])
53                .waiting_for(Wait::for_http("/health").for_port(port)),
54            port,
55        }
56    }
57
58    /// The AWS emulator (`floci/floci:1.5.30`), guest port 4566 — S3, DynamoDB, SQS,
59    /// etc.
60    pub fn aws() -> Self {
61        Self::aws_with_image("floci/floci:1.5.30")
62    }
63
64    /// The AWS emulator, from a caller-chosen image.
65    pub fn aws_with_image(image: &str) -> Self {
66        Self::new(image, AWS_PORT)
67    }
68
69    /// The Azure emulator (`floci/floci-az:0.8.0`), guest port 4577.
70    pub fn azure() -> Self {
71        Self::azure_with_image("floci/floci-az:0.8.0")
72    }
73
74    /// The Azure emulator, from a caller-chosen image.
75    pub fn azure_with_image(image: &str) -> Self {
76        Self::new(image, AZURE_PORT)
77    }
78
79    /// The GCP emulator (`floci/floci-gcp:0.4.0`), guest port 4588.
80    pub fn gcp() -> Self {
81        Self::gcp_with_image("floci/floci-gcp:0.4.0")
82    }
83
84    /// The GCP emulator, from a caller-chosen image.
85    pub fn gcp_with_image(image: &str) -> Self {
86        Self::new(image, GCP_PORT)
87    }
88
89    /// Boots the container.
90    pub async fn start(self) -> Result<FlociGuard> {
91        crate::register_default_backends();
92        Ok(FlociGuard {
93            guard: self.container.start().await?,
94            port: self.port,
95        })
96    }
97}
98
99/// The running guard for a [`FlociContainer`].
100pub struct FlociGuard {
101    guard: ContainerGuard,
102    port: u16,
103}
104
105impl FlociGuard {
106    /// This variant's REST endpoint (`http://<host>:<mapped port>`), the base URI for
107    /// every emulated API call.
108    pub fn endpoint_url(&self) -> String {
109        format!(
110            "http://{}:{}",
111            self.guard.host(),
112            self.guard.get_mapped_port(self.port).unwrap()
113        )
114    }
115
116    /// Stops and removes the container, releasing its host port.
117    pub async fn stop(self) -> Result<()> {
118        self.guard.stop().await
119    }
120}
121
122impl std::ops::Deref for FlociGuard {
123    type Target = ContainerGuard;
124    fn deref(&self) -> &ContainerGuard {
125        &self.guard
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn aws_picks_port_4566() {
135        let c = FlociContainer::aws();
136        assert_eq!(c.port, AWS_PORT);
137    }
138
139    #[test]
140    fn azure_picks_port_4577() {
141        let c = FlociContainer::azure();
142        assert_eq!(c.port, AZURE_PORT);
143    }
144
145    #[test]
146    fn gcp_picks_port_4588() {
147        let c = FlociContainer::gcp();
148        assert_eq!(c.port, GCP_PORT);
149    }
150}