test.py4.4 KB
View on GitHub"""Exercise the complete local pipeline, including repeated and changed releases."""
import hashlib
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
ROOT = Path(__file__).resolve().parents[1]
BUILD = ROOT / "components/cloudformation/app/.build"
BUCKET = "atmos-cfn-advanced-artifacts-local"
ENV = {**os.environ, "ATMOS_DEMO_RELEASE": "v1", "AWS_PAGER": ""}
def atmos(*args, capture=False):
print("+ atmos", *args, flush=True)
return subprocess.run(
["atmos", *args], cwd=ROOT, env=ENV, check=True,
text=True, stdout=subprocess.PIPE if capture else None,
).stdout
def aws(*args, capture=False):
return atmos("auth", "exec", "--identity", "local-aws", "--", "aws", *args,
capture=capture)
def deploy(release, changed, unchanged):
ENV["ATMOS_DEMO_RELEASE"] = release
atmos("aws", "cfn", "deploy", "app", "-s", "advanced")
result = json.loads((BUILD / "publish.json").read_text())
assert result == {"changed": changed, "unchanged": unchanged}, result
# Verify the bytes in S3, in addition to the HTTP response checked by the hook.
downloaded = BUILD / "downloaded.zip"
aws("s3api", "get-object", "--bucket", BUCKET, "--key",
f"lambda/{release}/handler.zip", str(downloaded))
expected = (BUILD / "handler.zip").read_bytes()
assert downloaded.read_bytes() == expected, "Published archive differs from local build"
print(f"Verified {release}: {changed} uploaded, {unchanged} unchanged", flush=True)
return hashlib.sha256(expected).hexdigest()
def dump_container_logs():
"""Capture this example's emulator and Lambda logs before cleanup removes them."""
runtime = shutil.which("docker") or shutil.which("podman")
if runtime is None:
return
try:
containers = subprocess.run(
[runtime, "ps", "-a", "--filter", "name=atmos-advanced-emulator-aws",
"--filter", "name=floci-aws-atmos-cfn-advanced-app-local-Function-",
"--format", "{{.ID}}"],
check=True, text=True, stdout=subprocess.PIPE, timeout=20,
).stdout.splitlines()
for container in containers:
print(f"Container {container} logs before cleanup:", flush=True)
subprocess.run([runtime, "logs", "--tail", "200", container],
check=False, timeout=20)
except (OSError, subprocess.SubprocessError) as error:
# Diagnostics must never mask the original failure or prevent cleanup.
print(f"Could not collect container logs: {error}", file=sys.stderr)
def main():
for command in ("atmos", "aws", "python3"):
if shutil.which(command) is None:
raise SystemExit(f"Missing prerequisite: {command}")
atmos("validate", "stacks")
started = False
cleanup_errors = []
try:
# Set before startup so a partially started emulator is also cleaned up.
started = True
atmos("emulator", "up", "aws", "-s", "advanced")
atmos("aws", "cfn", "deploy", "artifacts", "-s", "advanced")
first = deploy("v1", changed=1, unchanged=0)
repeated = deploy("v1", changed=0, unchanged=1)
assert first == repeated, "Unchanged build produced a different archive"
updated = deploy("v2", changed=1, unchanged=0)
assert first != updated, "Updated release did not change the package"
print("PASS: build → test → archive → publish → deploy → HTTP validation", flush=True)
except (subprocess.SubprocessError, AssertionError):
dump_container_logs()
raise
finally:
if started:
# Attempt every cleanup even if deployment or an earlier cleanup failed.
for command in (
lambda: atmos("aws", "cfn", "delete", "app", "-s", "advanced", "--auto-approve"),
lambda: aws("s3", "rm", f"s3://{BUCKET}", "--recursive"),
lambda: atmos("aws", "cfn", "delete", "artifacts", "-s", "advanced", "--auto-approve"),
lambda: atmos("emulator", "down", "aws", "-s", "advanced"),
):
try:
command()
except subprocess.CalledProcessError as error:
cleanup_errors.append(str(error))
print(f"Cleanup failed: {error}", file=sys.stderr)
if cleanup_errors:
raise SystemExit("Cleanup failed:\n" + "\n".join(cleanup_errors))
if __name__ == "__main__":
main()