Skip to content

Commit 1326278

Browse files
authored
fix(vault): add support for HashiCorp Vault container (testcontainers#366)
Add support for a Vault container.
1 parent 9a89748 commit 1326278

File tree

6 files changed

+141
-1
lines changed

6 files changed

+141
-1
lines changed

index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ testcontainers-python facilitates the use of Docker containers for functional an
4242
modules/redis/README
4343
modules/registry/README
4444
modules/selenium/README
45+
modules/vault/README
4546
modules/weaviate/README
4647

4748
Getting Started

modules/vault/README.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.. autoclass:: testcontainers.vault.VaultContainer
2+
.. title:: testcontainers.vault.VaultContainer
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#
2+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
3+
# not use this file except in compliance with the License. You may obtain
4+
# a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11+
# License for the specific language governing permissions and limitations
12+
# under the License.
13+
14+
from http.client import HTTPException
15+
from urllib.request import urlopen
16+
17+
from testcontainers.core.container import DockerContainer
18+
from testcontainers.core.waiting_utils import wait_container_is_ready
19+
20+
21+
class VaultContainer(DockerContainer):
22+
"""
23+
Vault container.
24+
25+
Example:
26+
27+
.. doctest::
28+
29+
>>> from testcontainers.vault import VaultContainer
30+
>>> import hvac
31+
32+
>>> with VaultContainer("hashicorp/vault:1.16.1") as vault_container:
33+
... connection_url = vault_container.get_connection_url()
34+
... client = hvac.Client(url=connection_url, token=vault_container.root_token)
35+
... assert client.is_authenticated()
36+
... # use root client to perform desired actions, e.g.
37+
... policies = client.sys.list_acl_policies()
38+
"""
39+
40+
def __init__(
41+
self,
42+
image: str = "hashicorp/vault:latest",
43+
port: int = 8200,
44+
root_token: str = "toor",
45+
**kwargs,
46+
) -> None:
47+
super().__init__(image, **kwargs)
48+
self.port = port
49+
self.root_token = root_token
50+
self.with_exposed_ports(self.port)
51+
self.with_env("VAULT_DEV_ROOT_TOKEN_ID", self.root_token)
52+
53+
def get_connection_url(self) -> str:
54+
"""
55+
Get the connection URL used to connect to the Vault container.
56+
57+
Returns:
58+
str: The address to connect to.
59+
"""
60+
host_ip = self.get_container_host_ip()
61+
exposed_port = self.get_exposed_port(self.port)
62+
return f"http://{host_ip}:{exposed_port}"
63+
64+
@wait_container_is_ready(HTTPException)
65+
def _healthcheck(self) -> None:
66+
url = f"{self.get_connection_url()}/v1/sys/health"
67+
with urlopen(url) as res:
68+
if res.status > 299:
69+
raise HTTPException()
70+
71+
def start(self) -> "VaultContainer":
72+
super().start()
73+
self._healthcheck()
74+
return self

modules/vault/tests/test_vault.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import hvac
2+
from testcontainers.vault import VaultContainer
3+
4+
5+
def test_docker_run_vault():
6+
config = VaultContainer("hashicorp/vault:1.16.1")
7+
with config as vault:
8+
url = vault.get_connection_url()
9+
client = hvac.Client(url=url)
10+
status = client.sys.read_health_status()
11+
assert status.status_code == 200
12+
13+
14+
def test_docker_run_vault_act_as_root():
15+
config = VaultContainer("hashicorp/vault:1.16.1")
16+
with config as vault:
17+
url = vault.get_connection_url()
18+
client = hvac.Client(url=url, token=vault.root_token)
19+
assert client.is_authenticated()
20+
assert client.sys.is_initialized()
21+
assert not client.sys.is_sealed()
22+
23+
client.sys.enable_secrets_engine(
24+
backend_type="kv",
25+
path="secrets",
26+
config={
27+
"version": "2",
28+
},
29+
)
30+
client.secrets.kv.v2.create_or_update_secret(
31+
path="my-secret",
32+
mount_point="secrets",
33+
secret={
34+
"pssst": "this is secret",
35+
},
36+
)
37+
resp = client.secrets.kv.v2.read_secret(
38+
path="my-secret",
39+
mount_point="secrets",
40+
)
41+
assert resp["data"]["data"]["pssst"] == "this is secret"

poetry.lock

Lines changed: 19 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ packages = [
5555
{ include = "testcontainers", from = "modules/redis" },
5656
{ include = "testcontainers", from = "modules/registry" },
5757
{ include = "testcontainers", from = "modules/selenium" },
58+
{ include = "testcontainers", from = "modules/vault" },
5859
{ include = "testcontainers", from = "modules/weaviate" }
5960
]
6061

@@ -125,6 +126,7 @@ rabbitmq = ["pika"]
125126
redis = ["redis"]
126127
registry = ["bcrypt"]
127128
selenium = ["selenium"]
129+
vault = []
128130
weaviate = ["weaviate-client"]
129131
chroma = ["chromadb-client"]
130132

@@ -144,6 +146,7 @@ psycopg = "*"
144146
cassandra-driver = "*"
145147
pytest-asyncio = "0.23.5"
146148
kafka-python-ng = "^2.2.0"
149+
hvac = "*"
147150

148151
[[tool.poetry.source]]
149152
name = "PyPI"
@@ -262,6 +265,7 @@ mypy_path = [
262265
# "modules/rabbitmq",
263266
# "modules/redis",
264267
# "modules/selenium"
268+
# "modules/vault"
265269
# "modules/weaviate"
266270
]
267271
enable_error_code = [

0 commit comments

Comments
 (0)