mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 16:33:12 +00:00
fix(terraform): guard staging import baseline
This commit is contained in:
parent
3b87bf64eb
commit
2c7c9b12e0
6 changed files with 189 additions and 16 deletions
|
|
@ -10,8 +10,10 @@ from pathlib import Path
|
|||
|
||||
from terraform_import_plan_resources import (
|
||||
DEV_IMPORT_BASELINE,
|
||||
DEV_IMPORT_IDS,
|
||||
IMPORT_BASELINES,
|
||||
IMPORT_IDS,
|
||||
REQUIRED_RESOURCES,
|
||||
STAGING_IMPORT_BASELINE,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -92,6 +94,51 @@ def validate_dev_import_baseline(
|
|||
)
|
||||
|
||||
|
||||
def validate_staging_import_baseline(
|
||||
resources_by_address: dict[str, dict], violations: list[str]
|
||||
) -> None:
|
||||
environment_address = (
|
||||
"module.environment.aws_elastic_beanstalk_environment.this"
|
||||
)
|
||||
route_address = "module.environment.aws_route53_record.api_cname[0]"
|
||||
expected_tags = STAGING_IMPORT_BASELINE["environment_tags"]
|
||||
expected_cname = STAGING_IMPORT_BASELINE["api_cname"]
|
||||
|
||||
for side in ("before", "after"):
|
||||
environment = (
|
||||
resources_by_address.get(environment_address, {})
|
||||
.get("change", {})
|
||||
.get(side)
|
||||
or {}
|
||||
)
|
||||
if environment.get("tags") != expected_tags:
|
||||
violations.append(
|
||||
f"{environment_address}: {side} environment tags do not match "
|
||||
f"the exact staging import baseline"
|
||||
)
|
||||
if environment.get("setting") != []:
|
||||
violations.append(
|
||||
f"{environment_address}: {side} contains managed EB settings "
|
||||
"during the import-only phase"
|
||||
)
|
||||
|
||||
route = (
|
||||
resources_by_address.get(route_address, {})
|
||||
.get("change", {})
|
||||
.get(side)
|
||||
or {}
|
||||
)
|
||||
actual_cname = {
|
||||
"records": route.get("records"),
|
||||
"ttl": route.get("ttl"),
|
||||
}
|
||||
if actual_cname != expected_cname:
|
||||
violations.append(
|
||||
f"{route_address}: {side} CNAME does not match the exact "
|
||||
"live ALB target and TTL"
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
plan = json.loads(args.plan_json.read_text(encoding="utf-8"))
|
||||
|
|
@ -143,13 +190,14 @@ def main() -> int:
|
|||
f"{address}: update is not explicitly allowlisted"
|
||||
)
|
||||
|
||||
if initial_import and args.environment == "dev":
|
||||
if initial_import and args.environment in IMPORT_IDS:
|
||||
if actions != {"no-op"}:
|
||||
violations.append(
|
||||
f"{address}: initial dev import actions must be ['no-op'], "
|
||||
f"{address}: initial {args.environment} import actions "
|
||||
"must be ['no-op'], "
|
||||
f"got {sorted(actions)}"
|
||||
)
|
||||
expected_import_id = DEV_IMPORT_IDS.get(address)
|
||||
expected_import_id = IMPORT_IDS[args.environment].get(address)
|
||||
actual_import_id = (
|
||||
resource.get("change", {}).get("importing") or {}
|
||||
).get("id")
|
||||
|
|
@ -167,6 +215,8 @@ def main() -> int:
|
|||
|
||||
if initial_import and args.environment == "dev":
|
||||
validate_dev_import_baseline(resources_by_address, violations)
|
||||
elif initial_import and args.environment == "staging":
|
||||
validate_staging_import_baseline(resources_by_address, violations)
|
||||
|
||||
if violations:
|
||||
print("FAIL: live Terraform plan is not import-safe", file=sys.stderr)
|
||||
|
|
@ -191,8 +241,8 @@ def main() -> int:
|
|||
for address, resource in sorted(resources_by_address.items())
|
||||
},
|
||||
}
|
||||
if args.environment == "dev" and initial_import:
|
||||
evidence["asserted_live_baseline"] = DEV_IMPORT_BASELINE
|
||||
if args.environment in IMPORT_BASELINES and initial_import:
|
||||
evidence["asserted_live_baseline"] = IMPORT_BASELINES[args.environment]
|
||||
args.evidence_out.write_text(
|
||||
json.dumps(evidence, indent=2, sort_keys=True) + "\n",
|
||||
encoding="utf-8",
|
||||
|
|
|
|||
|
|
@ -71,3 +71,54 @@ DEV_IMPORT_BASELINE = {
|
|||
"evaluate_target_health": True,
|
||||
},
|
||||
}
|
||||
|
||||
STAGING_IMPORT_IDS = {
|
||||
"module.environment.aws_elastic_beanstalk_environment.this": "e-6c9m4vb62z",
|
||||
"module.environment.aws_iam_instance_profile.runtime": "shoc-backend-staging",
|
||||
"module.environment.aws_iam_role.github_deploy": (
|
||||
"githubdeploy-shoc-backend-staging"
|
||||
),
|
||||
"module.environment.aws_iam_role.runtime": "shoc-backend-staging",
|
||||
"module.environment.aws_iam_role_policy.github_deploy": (
|
||||
"githubdeploy-shoc-backend-staging:GithubDeployRoleDefaultPolicyE8F540D1"
|
||||
),
|
||||
"module.environment.aws_iam_role_policy.runtime_app_config": (
|
||||
"shoc-backend-staging:shoc-staging-secrets-read"
|
||||
),
|
||||
"module.environment.aws_iam_role_policy.runtime_webhook[0]": (
|
||||
"shoc-backend-staging:shoc-backend-staging-webhook-secret-access"
|
||||
),
|
||||
"module.environment.aws_iam_role_policy_attachment.web_tier": (
|
||||
"shoc-backend-staging/arn:aws:iam::aws:policy/AWSElasticBeanstalkWebTier"
|
||||
),
|
||||
"module.environment.aws_secretsmanager_secret.app_config": (
|
||||
"arn:aws:secretsmanager:us-east-1:396287094661:secret:"
|
||||
"shoc/staging/app-config-CVV99L"
|
||||
),
|
||||
"module.environment.aws_route53_record.api_cname[0]": (
|
||||
"Z02602739VQWBWCAGXP4_api.staging.seahaven.com_CNAME"
|
||||
),
|
||||
}
|
||||
|
||||
STAGING_IMPORT_BASELINE = {
|
||||
"environment_tags": {
|
||||
"env": "staging",
|
||||
"project": "shoc",
|
||||
},
|
||||
"api_cname": {
|
||||
"records": [
|
||||
"awseb--AWSEB-pPXqiRgNnZe8-16996010.us-east-1.elb.amazonaws.com"
|
||||
],
|
||||
"ttl": 60,
|
||||
},
|
||||
}
|
||||
|
||||
IMPORT_IDS = {
|
||||
"dev": DEV_IMPORT_IDS,
|
||||
"staging": STAGING_IMPORT_IDS,
|
||||
}
|
||||
|
||||
IMPORT_BASELINES = {
|
||||
"dev": DEV_IMPORT_BASELINE,
|
||||
"staging": STAGING_IMPORT_BASELINE,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@ from pathlib import Path
|
|||
|
||||
from terraform_import_plan_resources import (
|
||||
DEV_IMPORT_BASELINE,
|
||||
DEV_IMPORT_IDS,
|
||||
IMPORT_IDS,
|
||||
REQUIRED_RESOURCES,
|
||||
STAGING_IMPORT_BASELINE,
|
||||
)
|
||||
|
||||
SCRIPT = Path(__file__).with_name("check-terraform-import-plan.py")
|
||||
|
|
@ -36,29 +37,40 @@ def run_case(
|
|||
continue
|
||||
actions = (actions_by_address or {}).get(address, ["no-op"])
|
||||
change: dict[str, object] = {"actions": actions}
|
||||
if environment == "dev":
|
||||
if environment in IMPORT_IDS:
|
||||
change["importing"] = {
|
||||
"id": (import_id_overrides or {}).get(
|
||||
address, DEV_IMPORT_IDS[address]
|
||||
address, IMPORT_IDS[environment][address]
|
||||
)
|
||||
}
|
||||
if (
|
||||
address
|
||||
== "module.environment.aws_elastic_beanstalk_environment.this"
|
||||
if address == (
|
||||
"module.environment.aws_elastic_beanstalk_environment.this"
|
||||
):
|
||||
baseline = (
|
||||
DEV_IMPORT_BASELINE
|
||||
if environment == "dev"
|
||||
else STAGING_IMPORT_BASELINE
|
||||
)
|
||||
state: dict[str, object] = {
|
||||
"tags": DEV_IMPORT_BASELINE["environment_tags"],
|
||||
"tags": baseline["environment_tags"],
|
||||
"setting": [],
|
||||
}
|
||||
change["before"] = state
|
||||
change["after"] = state
|
||||
elif (
|
||||
elif environment == "dev" and (
|
||||
address
|
||||
== "module.environment.aws_route53_record.api_alias[0]"
|
||||
):
|
||||
state = {"alias": [DEV_IMPORT_BASELINE["api_alias"]]}
|
||||
change["before"] = state
|
||||
change["after"] = state
|
||||
elif environment == "staging" and (
|
||||
address
|
||||
== "module.environment.aws_route53_record.api_cname[0]"
|
||||
):
|
||||
state = STAGING_IMPORT_BASELINE["api_cname"]
|
||||
change["before"] = state
|
||||
change["after"] = state
|
||||
if address in (state_overrides or {}):
|
||||
change["before"] = (state_overrides or {})[address]
|
||||
change["after"] = (state_overrides or {})[address]
|
||||
|
|
@ -177,6 +189,59 @@ def main() -> int:
|
|||
),
|
||||
1,
|
||||
),
|
||||
(
|
||||
"wrong staging import id",
|
||||
run_case(
|
||||
"staging",
|
||||
import_id_overrides={controlled_address: "wrong-role"},
|
||||
),
|
||||
1,
|
||||
),
|
||||
(
|
||||
"wrong staging environment tags",
|
||||
run_case(
|
||||
"staging",
|
||||
state_overrides={
|
||||
"module.environment.aws_elastic_beanstalk_environment.this": {
|
||||
"tags": {
|
||||
"Name": "shoc-backend-staging",
|
||||
"env": "staging",
|
||||
"project": "shoc",
|
||||
},
|
||||
"setting": [],
|
||||
}
|
||||
},
|
||||
),
|
||||
1,
|
||||
),
|
||||
(
|
||||
"staging managed settings during import",
|
||||
run_case(
|
||||
"staging",
|
||||
state_overrides={
|
||||
"module.environment.aws_elastic_beanstalk_environment.this": {
|
||||
"tags": STAGING_IMPORT_BASELINE["environment_tags"],
|
||||
"setting": [{"name": "ASPNETCORE_ENVIRONMENT"}],
|
||||
}
|
||||
},
|
||||
),
|
||||
1,
|
||||
),
|
||||
(
|
||||
"wrong staging cname",
|
||||
run_case(
|
||||
"staging",
|
||||
state_overrides={
|
||||
"module.environment.aws_route53_record.api_cname[0]": {
|
||||
"records": [
|
||||
"shoc-backend-staging.us-east-1.elasticbeanstalk.com"
|
||||
],
|
||||
"ttl": 60,
|
||||
}
|
||||
},
|
||||
),
|
||||
1,
|
||||
),
|
||||
(
|
||||
"controlled update",
|
||||
run_case(
|
||||
|
|
|
|||
|
|
@ -509,7 +509,7 @@ resource "aws_route53_record" "api_cname" {
|
|||
name = var.api_domain
|
||||
type = "CNAME"
|
||||
ttl = 60
|
||||
records = [aws_elastic_beanstalk_environment.this.endpoint_url]
|
||||
records = [var.api_cname_target == null ? aws_elastic_beanstalk_environment.this.endpoint_url : var.api_cname_target]
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
|
|
|
|||
|
|
@ -221,6 +221,12 @@ variable "api_alias_target" {
|
|||
default = null
|
||||
}
|
||||
|
||||
variable "api_cname_target" {
|
||||
type = string
|
||||
description = "Exact existing Route 53 CNAME target preserved during import. Null resolves the target from Elastic Beanstalk."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "metadata_before_adoption" {
|
||||
description = "Exact current metadata preserved while adoption_complete is false."
|
||||
type = object({
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ module "environment" {
|
|||
aws_region = local.aws_region
|
||||
environment = "staging"
|
||||
adoption_complete = false
|
||||
manage_eb_settings = false
|
||||
eb_application_name = local.eb_application_name
|
||||
eb_environment_name = local.eb_environment_name
|
||||
eb_environment_id = local.eb_environment_id
|
||||
|
|
@ -59,6 +60,7 @@ module "environment" {
|
|||
hosted_zone_id = "Z02602739VQWBWCAGXP4"
|
||||
api_domain = local.api_domain
|
||||
api_record_type = "CNAME"
|
||||
api_cname_target = "awseb--AWSEB-pPXqiRgNnZe8-16996010.us-east-1.elb.amazonaws.com"
|
||||
metadata_before_adoption = {
|
||||
runtime_role_description = "SHOC backend staging compute role (EB instance profile)"
|
||||
runtime_role_tags = {
|
||||
|
|
@ -80,7 +82,6 @@ module "environment" {
|
|||
Project = "shoc-backend"
|
||||
}
|
||||
environment_tags = {
|
||||
Name = "shoc-backend-staging"
|
||||
env = "staging"
|
||||
project = "shoc"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue