mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 17:43:12 +00:00
293 lines
8.9 KiB
Python
293 lines
8.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Deterministic tests for check-terraform-import-plan.py."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
from terraform_import_plan_resources import (
|
|
DEV_IMPORT_BASELINE,
|
|
DEV_IMPORT_IDS,
|
|
REQUIRED_RESOURCES,
|
|
)
|
|
|
|
SCRIPT = Path(__file__).with_name("check-terraform-import-plan.py")
|
|
|
|
|
|
def run_case(
|
|
environment: str,
|
|
*,
|
|
actions_by_address: dict[str, list[str]] | None = None,
|
|
omit_address: str | None = None,
|
|
extra_resource: tuple[str, str, list[str]] | None = None,
|
|
allowed_updates: tuple[str, ...] = (),
|
|
import_id_overrides: dict[str, str] | None = None,
|
|
state_overrides: dict[str, dict[str, object]] | None = None,
|
|
empty: bool = False,
|
|
) -> subprocess.CompletedProcess[str]:
|
|
changes = []
|
|
if not empty:
|
|
for address, resource_type in REQUIRED_RESOURCES[environment].items():
|
|
if address == omit_address:
|
|
continue
|
|
actions = (actions_by_address or {}).get(address, ["no-op"])
|
|
change: dict[str, object] = {"actions": actions}
|
|
if environment == "dev":
|
|
change["importing"] = {
|
|
"id": (import_id_overrides or {}).get(
|
|
address, DEV_IMPORT_IDS[address]
|
|
)
|
|
}
|
|
if (
|
|
address
|
|
== "module.environment.aws_elastic_beanstalk_environment.this"
|
|
):
|
|
state: dict[str, object] = {
|
|
"tags": DEV_IMPORT_BASELINE["environment_tags"],
|
|
"setting": [],
|
|
}
|
|
change["before"] = state
|
|
change["after"] = state
|
|
elif (
|
|
address
|
|
== "module.environment.aws_route53_record.api_alias[0]"
|
|
):
|
|
state = {"alias": [DEV_IMPORT_BASELINE["api_alias"]]}
|
|
change["before"] = state
|
|
change["after"] = state
|
|
if address in (state_overrides or {}):
|
|
change["before"] = (state_overrides or {})[address]
|
|
change["after"] = (state_overrides or {})[address]
|
|
changes.append(
|
|
{
|
|
"address": address,
|
|
"mode": "managed",
|
|
"type": resource_type,
|
|
"change": change,
|
|
}
|
|
)
|
|
if extra_resource:
|
|
address, resource_type, actions = extra_resource
|
|
changes.append(
|
|
{
|
|
"address": address,
|
|
"mode": "managed",
|
|
"type": resource_type,
|
|
"change": {"actions": actions},
|
|
}
|
|
)
|
|
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
plan_path = Path(directory) / "plan.json"
|
|
plan_path.write_text(
|
|
json.dumps({"resource_changes": changes}), encoding="utf-8"
|
|
)
|
|
command = [
|
|
sys.executable,
|
|
str(SCRIPT),
|
|
str(plan_path),
|
|
"--environment",
|
|
environment,
|
|
]
|
|
for allowed_address in allowed_updates:
|
|
command.extend(["--allow-update-address", allowed_address])
|
|
return subprocess.run(command, check=False, capture_output=True, text=True)
|
|
|
|
|
|
def main() -> int:
|
|
controlled_address = "module.environment.aws_iam_role.github_deploy"
|
|
cases = [
|
|
*[
|
|
(f"{environment} no-op", run_case(environment), 0)
|
|
for environment in REQUIRED_RESOURCES
|
|
],
|
|
("empty", run_case("dev", empty=True), 1),
|
|
(
|
|
"missing required",
|
|
run_case("dev", omit_address=controlled_address),
|
|
1,
|
|
),
|
|
(
|
|
"initial update",
|
|
run_case("dev", actions_by_address={controlled_address: ["update"]}),
|
|
1,
|
|
),
|
|
(
|
|
"unexpected initial action",
|
|
run_case("dev", actions_by_address={controlled_address: ["read"]}),
|
|
1,
|
|
),
|
|
(
|
|
"wrong import id",
|
|
run_case(
|
|
"dev",
|
|
import_id_overrides={controlled_address: "wrong-role"},
|
|
),
|
|
1,
|
|
),
|
|
(
|
|
"wrong environment tags",
|
|
run_case(
|
|
"dev",
|
|
state_overrides={
|
|
"module.environment.aws_elastic_beanstalk_environment.this": {
|
|
"tags": {
|
|
"Name": "shoc-backend-dev",
|
|
"env": "dev",
|
|
"project": "shoc",
|
|
},
|
|
"setting": [],
|
|
}
|
|
},
|
|
),
|
|
1,
|
|
),
|
|
(
|
|
"managed settings during import",
|
|
run_case(
|
|
"dev",
|
|
state_overrides={
|
|
"module.environment.aws_elastic_beanstalk_environment.this": {
|
|
"tags": DEV_IMPORT_BASELINE["environment_tags"],
|
|
"setting": [{"name": "ASPNETCORE_ENVIRONMENT"}],
|
|
}
|
|
},
|
|
),
|
|
1,
|
|
),
|
|
(
|
|
"wrong api alias",
|
|
run_case(
|
|
"dev",
|
|
state_overrides={
|
|
"module.environment.aws_route53_record.api_alias[0]": {
|
|
"alias": [
|
|
{
|
|
"name": "shoc-backend-dev.us-east-1.elasticbeanstalk.com",
|
|
"zone_id": "Z117KPS5GTRQ2G",
|
|
"evaluate_target_health": True,
|
|
}
|
|
]
|
|
}
|
|
},
|
|
),
|
|
1,
|
|
),
|
|
(
|
|
"controlled update",
|
|
run_case(
|
|
"dev",
|
|
actions_by_address={controlled_address: ["update"]},
|
|
allowed_updates=(controlled_address,),
|
|
),
|
|
0,
|
|
),
|
|
(
|
|
"tf-poc controlled update",
|
|
run_case(
|
|
"tf-poc",
|
|
actions_by_address={controlled_address: ["update"]},
|
|
allowed_updates=(controlled_address,),
|
|
),
|
|
0,
|
|
),
|
|
(
|
|
"wrong controlled address",
|
|
run_case(
|
|
"dev",
|
|
actions_by_address={controlled_address: ["update"]},
|
|
allowed_updates=("module.environment.aws_iam_role.runtime",),
|
|
),
|
|
1,
|
|
),
|
|
(
|
|
"create",
|
|
run_case("dev", actions_by_address={controlled_address: ["create"]}),
|
|
1,
|
|
),
|
|
(
|
|
"replacement",
|
|
run_case(
|
|
"dev",
|
|
actions_by_address={controlled_address: ["delete", "create"]},
|
|
),
|
|
1,
|
|
),
|
|
(
|
|
"destroy",
|
|
run_case("dev", actions_by_address={controlled_address: ["delete"]}),
|
|
1,
|
|
),
|
|
(
|
|
"outside address",
|
|
run_case(
|
|
"dev",
|
|
extra_resource=(
|
|
"module.environment.aws_iam_role.other",
|
|
"aws_iam_role",
|
|
["no-op"],
|
|
),
|
|
),
|
|
1,
|
|
),
|
|
(
|
|
"wrong type",
|
|
run_case(
|
|
"dev",
|
|
extra_resource=(controlled_address, "aws_iam_role_policy", ["no-op"]),
|
|
),
|
|
1,
|
|
),
|
|
(
|
|
"dev resource in staging",
|
|
run_case(
|
|
"staging",
|
|
extra_resource=(
|
|
"module.environment.aws_iam_role_policy.runtime_dynamo[0]",
|
|
"aws_iam_role_policy",
|
|
["no-op"],
|
|
),
|
|
),
|
|
1,
|
|
),
|
|
(
|
|
"live webhook policy in tf-poc",
|
|
run_case(
|
|
"tf-poc",
|
|
extra_resource=(
|
|
"module.environment.aws_iam_role_policy.runtime_webhook[0]",
|
|
"aws_iam_role_policy",
|
|
["no-op"],
|
|
),
|
|
),
|
|
1,
|
|
),
|
|
]
|
|
failures = [
|
|
(name, result, expected)
|
|
for name, result, expected in cases
|
|
if result.returncode != expected
|
|
]
|
|
if failures:
|
|
print(
|
|
"FAIL: plan-check cases failed: "
|
|
+ ", ".join(name for name, _, _ in failures),
|
|
file=sys.stderr,
|
|
)
|
|
for name, result, expected in failures:
|
|
print(
|
|
f"{name}: expected {expected}, got {result.returncode}\n"
|
|
f"{result.stdout}{result.stderr}",
|
|
file=sys.stderr,
|
|
)
|
|
return 1
|
|
print("PASS: Terraform import plan safety checks")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|