Skip to main content

testcontainers_modules/rustfs/
mod.rs

1use std::{borrow::Cow, collections::HashMap};
2
3use testcontainers::{core::WaitFor, Image};
4
5const NAME: &str = "rustfs/rustfs";
6const TAG: &str = "latest";
7
8const API_PORT: u16 = 9000;
9
10/// Module to work with [`RustFS`] inside of tests.
11///
12/// Starts an instance of RustFS based on the official [`RustFS docker image`].
13///
14/// RustFS is a high-performance object storage server compatible with Amazon S3 APIs.
15/// The container exposes port `9000` for API access and port `9001` for the web console by default.
16///
17/// # Example
18/// ```
19/// use testcontainers_modules::{rustfs::RustFS, testcontainers::runners::SyncRunner};
20///
21/// let rustfs_instance = RustFS::default().start().unwrap();
22/// let host = rustfs_instance.get_host().unwrap();
23/// let api_port = rustfs_instance.get_host_port_ipv4(9000).unwrap();
24///
25/// // Use the S3-compatible API at http://{host}:{api_port}
26/// ```
27///
28/// [`RustFS`]: https://rustfs.com/
29/// [`RustFS docker image`]: https://hub.docker.com/r/rustfs/rustfs
30#[derive(Debug, Clone)]
31pub struct RustFS {
32    env_vars: HashMap<String, String>,
33}
34
35impl Default for RustFS {
36    fn default() -> Self {
37        let mut env_vars = HashMap::new();
38        env_vars.insert("RUSTFS_ADDRESS".to_owned(), format!(":{}", API_PORT));
39        env_vars.insert("RUSTFS_CONSOLE_ENABLE".to_owned(), "true".to_owned());
40
41        Self { env_vars }
42    }
43}
44
45impl Image for RustFS {
46    fn name(&self) -> &str {
47        NAME
48    }
49
50    fn tag(&self) -> &str {
51        TAG
52    }
53
54    fn ready_conditions(&self) -> Vec<WaitFor> {
55        vec![WaitFor::message_on_stdout("RustFS Http API:")]
56    }
57
58    fn env_vars(
59        &self,
60    ) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
61        &self.env_vars
62    }
63
64    fn cmd(&self) -> impl IntoIterator<Item = impl Into<Cow<'_, str>>> {
65        ["/data"]
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use aws_config::{meta::region::RegionProviderChain, BehaviorVersion};
72    use aws_sdk_s3::{config::Credentials, Client};
73    use testcontainers::runners::AsyncRunner;
74
75    use crate::rustfs;
76
77    #[tokio::test]
78    async fn rustfs_buckets() -> Result<(), Box<dyn std::error::Error + 'static>> {
79        let rustfs = rustfs::RustFS::default();
80        let node = rustfs.start().await?;
81
82        let host_port = node.get_host_port_ipv4(9000).await?;
83        let client = build_s3_client(host_port).await;
84
85        let bucket_name = "test-bucket";
86
87        client
88            .create_bucket()
89            .bucket(bucket_name)
90            .send()
91            .await
92            .expect("Failed to create test bucket");
93
94        let buckets = client
95            .list_buckets()
96            .send()
97            .await
98            .expect("Failed to get list of buckets")
99            .buckets
100            .unwrap();
101
102        let bucket_exists = buckets
103            .iter()
104            .any(|b| b.name.as_deref() == Some(bucket_name));
105        assert!(bucket_exists, "Bucket {} not found", bucket_name);
106        Ok(())
107    }
108
109    async fn build_s3_client(host_port: u16) -> Client {
110        let endpoint_uri = format!("http://127.0.0.1:{host_port}");
111        let region_provider = RegionProviderChain::default_provider().or_else("us-east-1");
112        let creds = Credentials::new("rustfsadmin", "rustfsadmin", None, None, "test");
113
114        let shared_config = aws_config::defaults(BehaviorVersion::latest())
115            .region(region_provider)
116            .endpoint_url(endpoint_uri)
117            .credentials_provider(creds)
118            .load()
119            .await;
120
121        Client::new(&shared_config)
122    }
123}