Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions scripts/install-compat-tools.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env bash
set -euo pipefail

AWS_CLI_VERSION="2.37.6"
TERRAFORM_VERSION="1.16.4"
AWS_CLI_FINGERPRINT="FB5DB77FD5C118B80511ADA8A6310ACC4672475C"
HASHICORP_FINGERPRINT="798AEC654E5C15428C8E42EEAA16FCBCA621E701"

if [[ $# -ne 1 ]]; then
echo "usage: $0 <tool-root>" >&2
exit 2
fi
if [[ "$(uname -s)" != "Linux" || "$(uname -m)" != "x86_64" ]]; then
echo "install-compat-tools.sh supports Linux x86_64 only" >&2
exit 2
fi
for command in curl gpg sha256sum unzip; do
command -v "$command" >/dev/null || { echo "missing required command: $command" >&2; exit 2; }
done

TOOL_ROOT=$(mkdir -p "$1" && cd "$1" && pwd)
TEMP_DIR=$(mktemp -d)
trap 'rm -rf "$TEMP_DIR"' EXIT
GNUPGHOME="$TEMP_DIR/gnupg"
export GNUPGHOME
mkdir -m 700 "$GNUPGHOME"

verify_key() {
local expected=$1
local actual
actual=$(gpg --batch --with-colons --fingerprint "$expected" | awk -F: '$1 == "fpr" { print $10; exit }')
[[ "$actual" == "$expected" ]] || { echo "unexpected signing key fingerprint: $actual" >&2; exit 1; }
}

gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$AWS_CLI_FINGERPRINT"
verify_key "$AWS_CLI_FINGERPRINT"
curl --fail --silent --show-error --location \
"https://awscli.amazonaws.com/awscli-exe-linux-x86_64-${AWS_CLI_VERSION}.zip" \
--output "$TEMP_DIR/awscliv2.zip"
curl --fail --silent --show-error --location \
"https://awscli.amazonaws.com/awscli-exe-linux-x86_64-${AWS_CLI_VERSION}.zip.sig" \
--output "$TEMP_DIR/awscliv2.zip.sig"
gpg --batch --verify "$TEMP_DIR/awscliv2.zip.sig" "$TEMP_DIR/awscliv2.zip"
unzip -q "$TEMP_DIR/awscliv2.zip" -d "$TEMP_DIR/aws"
"$TEMP_DIR/aws/aws/install" --install-dir "$TOOL_ROOT/aws-cli" --bin-dir "$TOOL_ROOT/aws"

gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$HASHICORP_FINGERPRINT"
verify_key "$HASHICORP_FINGERPRINT"
TERRAFORM_BASE="https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}"
curl --fail --silent --show-error --location \
"$TERRAFORM_BASE/terraform_${TERRAFORM_VERSION}_linux_amd64.zip" \
--output "$TEMP_DIR/terraform.zip"
curl --fail --silent --show-error --location \
"$TERRAFORM_BASE/terraform_${TERRAFORM_VERSION}_SHA256SUMS" \
--output "$TEMP_DIR/terraform_SHA256SUMS"
curl --fail --silent --show-error --location \
"$TERRAFORM_BASE/terraform_${TERRAFORM_VERSION}_SHA256SUMS.sig" \
--output "$TEMP_DIR/terraform_SHA256SUMS.sig"
gpg --batch --verify "$TEMP_DIR/terraform_SHA256SUMS.sig" "$TEMP_DIR/terraform_SHA256SUMS"
(cd "$TEMP_DIR" && grep ' terraform_.*_linux_amd64.zip$' terraform_SHA256SUMS | sha256sum --check --status -)
mkdir -p "$TOOL_ROOT/terraform"
unzip -qo "$TEMP_DIR/terraform.zip" -d "$TOOL_ROOT/terraform"

"$TOOL_ROOT/aws/aws" --version
"$TOOL_ROOT/terraform/terraform" --version
54 changes: 54 additions & 0 deletions test/compatibility/client_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Pinned external-client helpers for DevCloud compatibility tests."""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from collections.abc import Mapping, Sequence
import subprocess


AWS_CLI_VERSION = "2.37.6"
TERRAFORM_VERSION = "1.16.4"


@dataclass(frozen=True)
class ToolPaths:
aws: Path
terraform: Path


def run_command(
argv: Sequence[str],
*,
cwd: Path | None = None,
env: Mapping[str, str] | None = None,
) -> subprocess.CompletedProcess[str]:
return subprocess.run(
argv,
cwd=cwd,
env=dict(env) if env is not None else None,
capture_output=True,
text=True,
check=False,
)


def _require_version(path: Path, expected: str, label: str) -> None:
if not path.is_file() or not path.stat().st_mode & 0o111:
version = expected.removeprefix("aws-cli/").removeprefix("Terraform v")
raise RuntimeError(f"missing {label} {version} binary at {path}")
result = run_command([str(path), "--version"])
if result.returncode != 0 or expected not in result.stdout:
actual = result.stdout.strip() or result.stderr.strip() or "no version output"
raise RuntimeError(f"{label} expected {expected}, got {actual}")


def resolve_tools(tool_root: Path) -> ToolPaths:
tools = ToolPaths(
aws=tool_root / "aws" / "aws",
terraform=tool_root / "terraform" / "terraform",
)
_require_version(tools.aws, f"aws-cli/{AWS_CLI_VERSION}", "aws CLI")
_require_version(tools.terraform, f"Terraform v{TERRAFORM_VERSION}", "Terraform")
return tools
78 changes: 78 additions & 0 deletions test/compatibility/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
import boto3
import os
import signal
import json
from pathlib import Path
import shlex

from client_tools import resolve_tools, run_command


def _find_free_port():
Expand Down Expand Up @@ -160,6 +165,79 @@ def service_client(devcloud_server):
return _make_client


def _client_env():
env = os.environ.copy()
env.update(
{
"AWS_ACCESS_KEY_ID": "test",
"AWS_SECRET_ACCESS_KEY": "test",
"AWS_EC2_METADATA_DISABLED": "true",
"AWS_PAGER": "",
}
)
return env


@pytest.fixture(scope="session")
def compat_tools():
root = os.environ.get("DEVCLOUD_COMPAT_TOOLS")
if not root:
raise RuntimeError("DEVCLOUD_COMPAT_TOOLS must point to pinned client binaries")
return resolve_tools(Path(root))


@pytest.fixture
def aws_cli(devcloud_server, compat_tools):
def invoke(command):
return run_command(
[
str(compat_tools.aws),
"--endpoint-url",
DEVCLOUD_URL,
"--region",
"us-east-1",
"--output",
"json",
*shlex.split(command),
],
env=_client_env(),
)

return invoke


@pytest.fixture
def terraform_workspace(devcloud_server, tmp_path):
template = Path(__file__).with_name("terraform")

def create(module):
if module not in {"s3", "sqs", "dynamodb", "iam_lambda"}:
raise ValueError(f"unknown Terraform contract module: {module}")
workspace = tmp_path / "terraform"
shutil.copytree(template, workspace)
(workspace / "main.tf").write_text(
f'module "contract" {{ source = "./modules/{module}" }}\n'
)
(workspace / "devcloud.auto.tfvars.json").write_text(
json.dumps({"devcloud_endpoint": DEVCLOUD_URL})
)
return workspace

return create


@pytest.fixture
def terraform_cmd(compat_tools):
def invoke(workspace, *args):
return run_command(
[str(compat_tools.terraform), "-no-color", *args],
cwd=workspace,
env=_client_env(),
)

return invoke


# --- Existing service fixtures ---


Expand Down
7 changes: 4 additions & 3 deletions test/compatibility/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
boto3>=1.34.0
pytest>=8.0.0
cryptography>=42.0.0
# Version upgrades are deliberate compatibility-contract changes.
boto3==1.43.105
pytest==9.1.1
cryptography==50.0.1
Empty file.
Empty file.
20 changes: 20 additions & 0 deletions test/compatibility/terraform/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
variable "devcloud_endpoint" {
type = string
}

provider "aws" {
region = "us-east-1"
access_key = "test"
secret_key = "test"
skip_credentials_validation = true
skip_metadata_api_check = true

endpoints {
s3 = var.devcloud_endpoint
sqs = var.devcloud_endpoint
dynamodb = var.devcloud_endpoint
lambda = var.devcloud_endpoint
iam = var.devcloud_endpoint
sts = var.devcloud_endpoint
}
}
10 changes: 10 additions & 0 deletions test/compatibility/terraform/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
terraform {
required_version = "= 1.16.4"

required_providers {
aws = {
source = "hashicorp/aws"
version = "6.66.0"
}
}
}
15 changes: 15 additions & 0 deletions test/compatibility/test_client_harness.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import json


def test_aws_cli_uses_devcloud_endpoint(aws_cli):
result = aws_cli("sts get-caller-identity")

assert result.returncode == 0, result.stderr
assert json.loads(result.stdout)["Account"]


def test_terraform_workspace_is_isolated(terraform_workspace):
workspace = terraform_workspace("s3")

assert (workspace / "devcloud.auto.tfvars.json").is_file()
assert 'source = "./modules/s3"' in (workspace / "main.tf").read_text()
45 changes: 45 additions & 0 deletions test/compatibility/test_client_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from pathlib import Path

import pytest

from client_tools import resolve_tools


def _executable(path: Path, output: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(f"#!/bin/sh\nprintf '%s\\n' '{output}'\n")
path.chmod(0o755)


def test_resolve_tools_rejects_missing_aws_cli(tmp_path):
with pytest.raises(RuntimeError, match="aws CLI 2.37.6"):
resolve_tools(tmp_path)


def test_resolve_tools_rejects_an_unpinned_version(tmp_path):
_executable(tmp_path / "aws" / "aws", "aws-cli/2.36.43 Python/3.14")
_executable(tmp_path / "terraform" / "terraform", "Terraform v1.16.4")

with pytest.raises(RuntimeError, match="expected.*2.37.6"):
resolve_tools(tmp_path)


def test_resolve_tools_returns_exact_pinned_binaries(tmp_path):
_executable(tmp_path / "aws" / "aws", "aws-cli/2.37.6 Python/3.14")
_executable(tmp_path / "terraform" / "terraform", "Terraform v1.16.4")

tools = resolve_tools(tmp_path)

assert tools.aws == tmp_path / "aws" / "aws"
assert tools.terraform == tmp_path / "terraform" / "terraform"


def test_installer_pins_the_contract_versions():
installer = Path(__file__).parents[2] / "scripts" / "install-compat-tools.sh"

content = installer.read_text()

assert 'AWS_CLI_VERSION="2.37.6"' in content
assert 'TERRAFORM_VERSION="1.16.4"' in content
assert "awscli-exe-linux-x86_64-${AWS_CLI_VERSION}.zip.sig" in content
assert "terraform_${TERRAFORM_VERSION}_SHA256SUMS" in content
16 changes: 16 additions & 0 deletions test/compatibility/test_terraform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import json
from pathlib import Path


def test_terraform_configuration_pins_aws_provider():
content = (Path(__file__).parent / "terraform" / "versions.tf").read_text()
assert 'source = "hashicorp/aws"' in content
assert 'version = "6.66.0"' in content


def test_terraform_workspace_writes_local_endpoint(terraform_workspace):
workspace = terraform_workspace("s3")

config = json.loads((workspace / "devcloud.auto.tfvars.json").read_text())
assert config["devcloud_endpoint"].startswith("http://localhost:")
assert (workspace / "providers.tf").is_file()
Loading