// SPDX-FileCopyrightText: © Matteo Settenvini // SPDX-License-Identifier: EUPL-1.2 mod minio; use { ::minio::s3::{ client::Client as MinIOClient, creds::StaticProvider, http::BaseUrl, types::S3Api, }, anyhow::{Result, anyhow}, object_store::{ObjectStore, aws::AmazonS3Builder}, reqwest::Url, std::{ptr::null_mut, str::FromStr}, testcontainers::{ContainerAsync, runners::AsyncRunner}, tokio::io::AsyncBufReadExt as _, }; pub struct Test { pub base_url: Url, pub bucket: Box, pub serves3: tokio::process::Child, pub _minio: ContainerAsync, } const MAXIMUM_SERVES3_INIT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5); const BUCKET_NAME: &'static str = "integration-test-bucket"; const REGION: &'static str = "test-region"; const ACCESS_KEY: &'static str = "minioadmin"; const SECRET_KEY: &'static str = "minioadmin"; impl Test { pub async fn new() -> Result { // NOTE: this testsuite was setup to work // against a recent version of podman, // which correctly distinguishes between // stdout and stderr of the running container. let image = minio::MinIO::default(); let container = image.start().await?; let endpoint = format!( "http://{host}:{port}", host = container.get_host().await?, port = container.get_host_port_ipv4(9000).await? ); // We need to create the bucket let minio_client = MinIOClient::new( BaseUrl::from_str(&endpoint).unwrap(), Some(Box::new(StaticProvider::new(ACCESS_KEY, SECRET_KEY, None))), None, None, )?; minio_client.create_bucket(BUCKET_NAME).send().await?; let bucket = AmazonS3Builder::new() .with_endpoint(&endpoint) .with_access_key_id(ACCESS_KEY) .with_secret_access_key(SECRET_KEY) .with_bucket_name(BUCKET_NAME) .with_allow_http(true) .build()?; let bin = std::env!("CARGO_BIN_EXE_serves3"); let mut child = tokio::process::Command::new(bin) .env("SERVES3_ADDRESS", "127.0.0.1") .env("SERVES3_PORT", "0") .env("SERVES3_LOG_LEVEL", "debug") .env( "SERVES3_S3_BUCKET", format!( r#"{{ name = "{name}", endpoint = "{endpoint}", region = "{region}", access_key_id = "{user}", secret_access_key = "{secret}", path_style = true }}"#, name = BUCKET_NAME, endpoint = endpoint, region = ®ION, user = ACCESS_KEY, secret = SECRET_KEY ), ) .stdout(std::process::Stdio::piped()) .spawn()?; let base_url = tokio::time::timeout(MAXIMUM_SERVES3_INIT_TIMEOUT, async { let stdout = child.stdout.as_mut().unwrap(); let mut lines = tokio::io::BufReader::new(stdout).lines(); let re = regex::Regex::new("^Rocket has launched from (http://.+)$").unwrap(); while let Some(line) = lines.next_line().await? { println!("{}", &line); if let Some(captures) = re.captures(&line) { let url = captures.get(1).unwrap().as_str(); return Ok(Url::from_str(url)?); } } Err(anyhow!("Rocket did not print that it has started")) }) .await??; Ok(Self { base_url, bucket: Box::new(bucket), serves3: child, _minio: container, }) } } impl Drop for Test { fn drop(&mut self) { unsafe { let pid = self.serves3.id().unwrap() as i32; libc::kill(pid, libc::SIGTERM); libc::waitpid(pid, null_mut(), 0); } } }