testcontainers_modules/localstack/
pro.rs

1use std::borrow::Cow;
2
3use testcontainers::{core::WaitFor, Image};
4
5const NAME: &str = "localstack/localstack-pro";
6
7/// This module provides [LocalStack](https://www.localstack.cloud/) (Pro Edition).
8///
9/// Currently pinned to [version `3.0`](https://hub.docker.com/layers/localstack/localstack-pro/3.0/images/sha256-4d9167a049a705b0edafb9e0a6e362aaf5f9d890cebb894dd9113b17eed83153?context=explore)
10///
11/// # Configuration
12///
13/// For configuration, LocalStack uses environment variables. You can go [here](https://docs.localstack.cloud/references/configuration/)
14/// for the full list.
15///
16/// Testcontainers support setting environment variables with the method
17/// `RunnableImage::with_env_var((impl Into<String>, impl Into<String>))`. You will have to convert
18/// the Image into a RunnableImage first.
19///
20/// ```
21/// use testcontainers_modules::{localstack::LocalStackPro, testcontainers::ImageExt};
22///
23/// let container_request =
24///     LocalStackPro::new("YOUR AUTH TOKEN HERE").with_env_var("SERVICES", "s3");
25/// ```
26///
27/// No environment variables are required.
28#[derive(Clone)]
29pub struct LocalStackPro {
30    /// The [auth token](https://docs.localstack.cloud/getting-started/auth-token/)
31    /// to activate LocalStack Pro with
32    auth_token: Option<String>,
33}
34
35impl LocalStackPro {
36    /// Create new [`LocalStackPro`](LocalStackPro) container instance using the specified
37    /// LocalStack [auth token](https://docs.localstack.cloud/getting-started/auth-token/)
38    pub fn new(auth_token: impl Into<String>) -> Self {
39        Self::with_auth_token(Some(auth_token))
40    }
41
42    /// Create new [`LocalStackPro`](LocalStackPro) container instance using the
43    /// [auth token](https://docs.localstack.cloud/getting-started/auth-token/)
44    /// from the local `LOCALSTACK_AUTH_TOKEN` environment variable
45    pub fn from_env() -> Self {
46        Self::with_auth_token(std::env::var("LOCALSTACK_AUTH_TOKEN").ok())
47    }
48
49    /// Create new [`LocalStackPro`](LocalStackPro) container instance using the
50    /// specified (optional) [auth token](https://docs.localstack.cloud/getting-started/auth-token/)
51    pub fn with_auth_token(auth_token: Option<impl Into<String>>) -> Self {
52        Self {
53            auth_token: auth_token.map(Into::into),
54        }
55    }
56}
57
58impl Default for LocalStackPro {
59    fn default() -> Self {
60        Self::from_env()
61    }
62}
63
64impl std::fmt::Debug for LocalStackPro {
65    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        formatter
67            .debug_struct("LocalStackPro")
68            .field("auth_token", &{
69                if self.auth_token.is_none() {
70                    "[UNSET]"
71                } else {
72                    "[REDACTED]"
73                }
74            })
75            .finish()
76    }
77}
78
79impl Image for LocalStackPro {
80    fn name(&self) -> &str {
81        NAME
82    }
83
84    fn tag(&self) -> &str {
85        super::TAG
86    }
87
88    fn ready_conditions(&self) -> Vec<WaitFor> {
89        vec![WaitFor::message_on_stdout("Ready."), WaitFor::healthcheck()]
90    }
91    fn env_vars(
92        &self,
93    ) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
94        if let Some(token) = self.auth_token.as_deref() {
95            vec![("LOCALSTACK_AUTH_TOKEN", token), ("ACTIVATE_PRO", "1")]
96        } else {
97            vec![("ACTIVATE_PRO", "0")]
98        }
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use testcontainers::runners::AsyncRunner;
105
106    use super::LocalStackPro;
107
108    #[tokio::test]
109    #[should_panic]
110    #[allow(clippy::result_large_err)]
111    async fn fails_on_invalid_token() {
112        LocalStackPro::new("not-a-real-auth-token")
113            .start()
114            .await
115            .unwrap();
116    }
117
118    #[tokio::test]
119    #[allow(clippy::result_large_err)]
120    async fn skips_pro_activation_without_token() {
121        let container = LocalStackPro::with_auth_token(Option::<&str>::None)
122            .start()
123            .await;
124
125        assert!(container.is_ok());
126
127        let stdout = container
128            .unwrap()
129            .stdout_to_vec()
130            .await
131            .map(|value| String::from_utf8_lossy(&value).to_string())
132            .unwrap_or_default();
133
134        assert!(
135            stdout.trim().ends_with("Ready."),
136            "expected a string ending with \"Ready.\" but got: {stdout}",
137        );
138    }
139}