testcontainers_modules/hashicorp_vault/
mod.rs

1use std::{borrow::Cow, collections::BTreeMap};
2
3use testcontainers::{
4    core::{wait::HttpWaitStrategy, ContainerPort, WaitFor},
5    Image,
6};
7
8const DEFAULT_IMAGE_NAME: &str = "hashicorp/vault";
9const DEFAULT_IMAGE_TAG: &str = "1.17";
10
11/// Module to work with [`Hashicorp Vault`] inside of tests.
12///
13/// This module is based on the official [`Hashicorp Vault docker image`].
14///
15/// # Example
16/// ```
17/// use testcontainers_modules::{hashicorp_vault, testcontainers::runners::SyncRunner};
18///
19/// let vault = hashicorp_vault::HashicorpVault::default().start().unwrap();
20/// let http_port = vault.get_host_port_ipv4(8200).unwrap();
21///
22/// // do something with the running vault instance..
23/// ```
24///
25/// [`Hashicorp Vault`]: https://github.com/hashicorp/vault
26/// [`Hashicorp Vault docker image`]: https://hub.docker.com/r/hashicorp/vault
27/// [`Hashicorp Vault commands`]: https://developer.hashicorp.com/vault/docs/commands
28#[derive(Debug, Clone)]
29pub struct HashicorpVault {
30    name: String,
31    tag: String,
32    env_vars: BTreeMap<String, String>,
33}
34
35impl Default for HashicorpVault {
36    /**
37     * Starts an in-memory instance in dev mode, with horrible token values.
38     * Obviously not to be emulated in production.
39     */
40    fn default() -> Self {
41        let mut env_vars = BTreeMap::new();
42        env_vars.insert("VAULT_DEV_ROOT_TOKEN_ID".to_string(), "myroot".to_string());
43        HashicorpVault::new(
44            DEFAULT_IMAGE_NAME.to_string(),
45            DEFAULT_IMAGE_TAG.to_string(),
46            env_vars,
47        )
48    }
49}
50
51impl HashicorpVault {
52    fn new(name: String, tag: String, env_vars: BTreeMap<String, String>) -> Self {
53        HashicorpVault {
54            name,
55            tag,
56            env_vars,
57        }
58    }
59}
60
61impl Image for HashicorpVault {
62    fn name(&self) -> &str {
63        &self.name
64    }
65
66    fn tag(&self) -> &str {
67        &self.tag
68    }
69
70    fn ready_conditions(&self) -> Vec<WaitFor> {
71        let http_strategy = HttpWaitStrategy::new("/sys/health")
72            .with_port(ContainerPort::Tcp(8200))
73            .with_response_matcher(|resp| resp.status().as_u16() == 200);
74        vec![WaitFor::http(http_strategy)]
75    }
76
77    fn env_vars(
78        &self,
79    ) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
80        &self.env_vars
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use serde::{Deserialize, Serialize};
87    use vaultrs::{
88        client::{VaultClient, VaultClientSettingsBuilder},
89        kv2,
90    };
91
92    use super::*;
93    use crate::testcontainers::runners::AsyncRunner;
94
95    // Create and read secrets
96    #[derive(Debug, Deserialize, Serialize)]
97    struct MySecret {
98        key: String,
99        password: String,
100    }
101
102    #[tokio::test]
103    async fn hashicorp_vault_secret_set_and_read(
104    ) -> Result<(), Box<dyn std::error::Error + 'static>> {
105        let vault = HashicorpVault::default().start().await.unwrap();
106        let endpoint = format!("http://0.0.0.0:{}", vault.get_host_port_ipv4(8200).await?);
107
108        // Create a client
109        let client = VaultClient::new(
110            VaultClientSettingsBuilder::default()
111                .address(endpoint)
112                .token("myroot")
113                .build()
114                .unwrap(),
115        )
116        .unwrap();
117
118        let secret = MySecret {
119            key: "super".to_string(),
120            password: "secret".to_string(),
121        };
122        kv2::set(&client, "secret", "mysecret", &secret).await?;
123
124        let secret: MySecret = kv2::read(&client, "secret", "mysecret").await.unwrap();
125        assert_eq!(secret.key, "super");
126        assert_eq!(secret.password, "secret");
127        Ok(())
128    }
129}