-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[#4] switch from docker compose to testcontainers #439
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,18 +64,23 @@ def secrets_host_url(self): | |
Returns the secrets host url. | ||
This should only be used for integration tests, and not otherwise. | ||
""" | ||
return self.properties["secrets.host.url"] | ||
if "SECRETS_HOST_URL" in os.environ: | ||
# host and port of the aissemble-vault container | ||
return os.environ["SECRETS_HOST_URL"] | ||
else: | ||
return self.properties["secrets.host.url"] | ||
|
||
@staticmethod | ||
def validate_container_start(): | ||
def validate_container_start(port): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I/A: I'm a little skeptical about including this method here in the Config class instead of just in the test classes, but I didn't feel strongly enough to move it. |
||
started = False | ||
max_wait = 20 | ||
wait = 0 | ||
while wait < max_wait: | ||
logger.info("Waiting for Vault to start") | ||
url = f"http://localhost:{port}/v1/sys/health" | ||
logger.info(f"Waiting for Vault to start at {url}") | ||
|
||
try: | ||
requests.get("http://localhost:8200/v1/sys/health") | ||
requests.get(url) | ||
logger.info("Vault started successfully!") | ||
started = True | ||
break | ||
|
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,12 +8,15 @@ | |
# #L% | ||
### | ||
import os | ||
import platform | ||
import json | ||
import time | ||
import packaging.version | ||
from importlib import metadata | ||
from pyspark.sql import SparkSession | ||
from krausening.logging import LogManager | ||
from testcontainers.compose import DockerCompose | ||
from testcontainers.core.container import DockerContainer | ||
from aissemble_test_data_delivery_pyspark_model.generated import environment_base | ||
from aissemble_encrypt.vault_config import VaultConfig | ||
|
||
""" | ||
Behave test environment setup to configure Spark for unit tests. | ||
|
@@ -33,15 +36,6 @@ def before_all(context): | |
|
||
print("Created spark session for tests...") | ||
|
||
os.environ["S3Test_FS_PROVIDER"] = "s3" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I: Moved this to where we're setting the PORT so it's all together. |
||
os.environ["S3Test_FS_ACCESS_KEY_ID"] = "000000000000" | ||
os.environ["S3Test_FS_SECRET_ACCESS_KEY"] = ( | ||
"E3FF2839C048B25C084DEBE9B26995E310250568" | ||
) | ||
os.environ["S3Test_FS_SECURE"] = "False" | ||
os.environ["S3Test_FS_HOST"] = "localhost" | ||
os.environ["S3Test_FS_PORT"] = "4566" | ||
|
||
|
||
def after_all(context): | ||
environment_base.cleanup() | ||
|
@@ -63,47 +57,104 @@ def after_scenario(context, scenario): | |
|
||
def before_feature(context, feature): | ||
if "integration" in feature.tags: | ||
test_staging_path = "target/generated-sources/docker-compose-files" | ||
logger.info( | ||
f"Starting services defined in Docker Compose file at {test_staging_path}" | ||
) | ||
|
||
compose = DockerCompose(test_staging_path, "docker-compose.yml") | ||
context.docker_compose_containers = compose | ||
|
||
compose.start() | ||
wait_for_docker_url = "http://localhost:4566" | ||
logger.info( | ||
f"Waiting for Docker Compose services to start - waiting for a response from {wait_for_docker_url}" | ||
) | ||
compose.wait_for(wait_for_docker_url) | ||
logger.info("Starting Test container services") | ||
context.test_containers = [] | ||
setup_vault(context) | ||
setup_s3_local(context) | ||
|
||
|
||
def setup_vault(context): | ||
docker_image = "ghcr.io/boozallen/aissemble-vault:" | ||
# append current version to docker image | ||
# pyproject.toml has a "version" property, e.g. version = "0.12.0.dev" | ||
# using major, minor, patch and -SNAPSHOT if dev | ||
version = metadata.version("aissemble-extensions-encryption-vault-python") | ||
docker_image += version_to_tag(version) | ||
vault = DockerContainer(docker_image) | ||
port = start_container(vault, 8200, VaultConfig.validate_container_start) | ||
context.test_containers.append(vault) | ||
os.environ["SECRETS_HOST_URL"] = f"http://127.0.0.1:{port}" | ||
|
||
root_key_tuple = vault.exec("cat /root_key.txt") | ||
secrets_root_key = root_key_tuple.output.decode() | ||
os.environ["SECRETS_ROOT_KEY"] = secrets_root_key | ||
|
||
unseal_keys_tuple = vault.exec("cat /unseal_keys.txt") | ||
unseal_keys_txt = unseal_keys_tuple.output.decode() | ||
unseal_keys_json = json.loads(unseal_keys_txt) | ||
secrets_unseal_keys = ",".join(unseal_keys_json) | ||
os.environ["SECRETS_UNSEAL_KEYS"] = secrets_unseal_keys | ||
|
||
transit_client_token_tuple = vault.exec("cat /transit_client_token.txt") | ||
transit_client_token_txt = transit_client_token_tuple.output.decode() | ||
transit_client_token_json = json.loads(transit_client_token_txt) | ||
encrypt_client_token = transit_client_token_json["auth"]["client_token"] | ||
os.environ["ENCRYPT_CLIENT_TOKEN"] = encrypt_client_token | ||
|
||
|
||
def setup_s3_local(context): | ||
localstack = DockerContainer("localstack/localstack:latest") | ||
localstack.with_env("SERVICES", "s3") | ||
port = start_container(localstack, 4566, lambda _: test_aws(localstack)) | ||
context.test_containers.append(localstack) | ||
os.environ["S3Test_FS_PROVIDER"] = "s3" | ||
os.environ["S3Test_FS_ACCESS_KEY_ID"] = "000000000000" | ||
os.environ["S3Test_FS_SECRET_ACCESS_KEY"] = ( | ||
"E3FF2839C048B25C084DEBE9B26995E310250568" | ||
) | ||
os.environ["S3Test_FS_SECURE"] = "False" | ||
os.environ["S3Test_FS_HOST"] = "localhost" | ||
os.environ["S3Test_FS_PORT"] = f"{port}" | ||
|
||
root_key_tuple = context.docker_compose_containers.exec_in_container( | ||
["cat", "/root_key.txt"], "vault" | ||
) | ||
secrets_root_key = root_key_tuple[0] | ||
os.environ["SECRETS_ROOT_KEY"] = secrets_root_key | ||
|
||
unseal_keys_tuple = context.docker_compose_containers.exec_in_container( | ||
["cat", "/unseal_keys.txt"], "vault" | ||
) | ||
unseal_keys_txt = unseal_keys_tuple[0] | ||
unseal_keys_json = json.loads(unseal_keys_txt) | ||
secrets_unseal_keys = ",".join(unseal_keys_json) | ||
os.environ["SECRETS_UNSEAL_KEYS"] = secrets_unseal_keys | ||
|
||
transit_client_token_tuple = ( | ||
context.docker_compose_containers.exec_in_container( | ||
["cat", "/transit_client_token.txt"], "vault" | ||
) | ||
def after_feature(context, feature): | ||
if hasattr(context, "test_containers"): | ||
logger.info("Stopping Test container services") | ||
for container in context.test_containers: | ||
logger.info(f"...stopping {container.image}") | ||
container.stop() | ||
|
||
|
||
def start_container(container, port, healthcheck=lambda x: True) -> int: | ||
logger.info(f"Starting container: {container.image}") | ||
container.with_exposed_ports(port) | ||
container.start() | ||
extport = container.get_exposed_port(port) | ||
if not healthcheck(extport): | ||
raise Exception(f"Failed to start {container.image}") | ||
return extport | ||
|
||
|
||
def version_to_tag(version_str: str) -> str: | ||
"""Convert a python version into a docker tag for the same version. | ||
|
||
Args: | ||
version_str (str): The version string to convert. | ||
Returns: | ||
str: The docker tag for the version. | ||
""" | ||
version = packaging.version.parse(version_str) | ||
tag = version.base_version | ||
if version.pre: | ||
tag += "-" + "".join([str(x) for x in version.pre]) | ||
if version.is_devrelease: | ||
tag += "-SNAPSHOT" | ||
return tag | ||
|
||
|
||
def test_aws(container): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I: I think this is less necessary now that we are confining the services started to just s3, but when we first tried this there was a connection refused in CI because it tried to authenticate before localstack was fully started. |
||
started = False | ||
tries = 0 | ||
exitcode = -1 | ||
while exitcode != 0 and tries < 20: | ||
logger.info("Waiting for s3 to start...") | ||
tries += 1 | ||
exitcode, _ = container.exec( | ||
"bash -c 'AWS_ACCESS_KEY_ID=fake AWS_SECRET_ACCESS_KEY=fake aws --endpoint-url=http://localhost:4566 s3 ls'" | ||
) | ||
transit_client_token_txt = transit_client_token_tuple[0] | ||
transit_client_token_json = json.loads(transit_client_token_txt) | ||
encrypt_client_token = transit_client_token_json["auth"]["client_token"] | ||
os.environ["ENCRYPT_CLIENT_TOKEN"] = encrypt_client_token | ||
if exitcode == 0: | ||
started = True | ||
else: | ||
time.sleep(1) | ||
|
||
|
||
def after_feature(context, feature): | ||
if "integration" in feature.tags: | ||
logger.info("Stopping Docker Compose services") | ||
context.docker_compose_containers.stop() | ||
return started |
This file was deleted.
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I: Allows us to use a dynamic port and assign it at test time.