meal-order-manager/terraform/data.tf
Adam Moussa d7ad49d00f
Some checks are pending
Deploy API / Resolve target (push) Waiting to run
Deploy API / Deploy API to (push) Blocked by required conditions
feat(api): add OpenAPI Redocly contract and VPC outputs (DEV-289) (#206)
* feat(infra): export attached VPC ids and lock prod to afterhours (DEV-289)

Prod must keep existing_vpc_id pointed at the afterhours VPC. Outputs
expose the resolved vpc_id and public subnet IDs.

Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>

* feat(api): add OpenAPI 3.1 and Redocly lint in CI (DEV-289)

Same extends: recommended ruleset and @redocly/cli 2.52.1 as
internal-portal. Documents current { error: string } JSON errors.

Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>

* fix(api): document 4xx and reject invalid form-status weeks (DEV-289)

Health, form-status, and roster document 400. form-status now maps
current and returns 400 for a week that is not current or YYYY-WNN.
Redocly treats 302 as a success response, matching the portal.

Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>

* style(test): format VPC contract assertions for ruff (DEV-289)

Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>

* fix(api): fail Redocly on missing 4xx and 2xx/3xx (DEV-289)

Promote operation-4xx-response and the 2xx-or-3xx success rule to error.
Replace unused health and roster 400s with 403, matching portal health.
Form-status keeps its real 400 for invalid week.

Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>

* fix(api): split week params and allow live menu nulls (DEV-289)

Menu and form-status take current or YYYY-WNN. Orders take YYYY-WNN or a
calendar date and reject current. Menu payloads may emit null menu_url,
calories, protein, and image_url.

Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>

* fix(infra): fail prod apply without the afterhours VPC (DEV-289)

Prod never creates the 10.60 fallback VPC. A terraform_data precondition
fails plan and apply when existing_vpc_id is empty, instead of a check
block that only warns.

Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-09-22 00:33:48 +00:00

100 lines
3.6 KiB
HCL

data "aws_caller_identity" "current" {}
# Resource names in locals.tf embed the account ID. If the workspace is ever
# pointed at another account, fail the plan here rather than creating a parallel
# set of oddly-named resources somewhere else.
check "correct_account" {
assert {
condition = data.aws_caller_identity.current.account_id == local.account_id
error_message = "This configuration targets account ${local.account_id} (${var.environment}), but the credentials resolve to ${data.aws_caller_identity.current.account_id}."
}
}
# Alarm sink owned by seahaven-org-baseline, not by this configuration.
# Looked up only in prod because CloudWatch alarms are skipped in dev.
data "aws_sns_topic" "site_alerts" {
count = local.is_prod ? 1 : 0
name = "site-alerts"
}
moved {
from = data.aws_sns_topic.site_alerts
to = data.aws_sns_topic.site_alerts[0]
}
# Shared CloudFront WAF WebACL (audit M-17), published to Parameter Store by the
# org baseline. aws_cloudfront_distribution.web_acl_id takes the WAFv2 ARN
# despite the attribute name.
data "aws_ssm_parameter" "app_web_acl_arn" {
name = "/seahaven/waf/app-web-acl-arn"
}
# Google OAuth client ID used by submit-order and the admin authorizer. The
# parameter is created and rotated out-of-band because it varies per
# environment; this lookup only asserts that it exists before an apply wires
# functions that read it at runtime. Its NAME, not its value, is what reaches
# the ECS task.
data "aws_ssm_parameter" "google_client_id" {
name = local.google_client_id_param
}
# Fail closed if the OOB Google client ID parameter is missing or empty. The
# functions receive the parameter NAME via env; this check forces the data
# source to be evaluated so apply cannot succeed without the parameter.
check "google_client_id_present" {
assert {
condition = length(data.aws_ssm_parameter.google_client_id.value) > 0
error_message = "SSM parameter ${local.google_client_id_param} is missing or empty; create it out of band before apply."
}
}
check "dev_has_no_paychex" {
assert {
condition = local.is_prod || var.checkcomponents_queue_url == ""
error_message = "checkcomponents_queue_url must be empty in non-prod so aggregate-orders cannot send to the prod Paychex queue."
}
}
check "checkcomponents_pair" {
assert {
condition = (var.checkcomponents_queue_url == "") == (var.checkcomponents_queue_arn == "")
error_message = "checkcomponents_queue_url and checkcomponents_queue_arn must both be set or both be empty."
}
}
check "dev_has_no_custom_domain" {
assert {
condition = local.is_prod || !var.attach_custom_domain
error_message = "attach_custom_domain must be false in non-prod; use the CloudFront distribution domain."
}
}
check "prod_reuses_afterhours_vpc" {
assert {
condition = !local.is_prod || var.existing_vpc_id != ""
error_message = "Prod must set existing_vpc_id to the afterhours VPC. Do not mint 10.60."
}
}
check "existing_vpc_pair" {
assert {
condition = (var.existing_vpc_id == "") == (length(var.existing_public_subnet_ids) == 0)
error_message = "existing_vpc_id and existing_public_subnet_ids must both be set or both be empty."
}
}
check "existing_vpc_two_az" {
assert {
condition = var.existing_vpc_id == "" || length(var.existing_public_subnet_ids) >= 2
error_message = "existing_public_subnet_ids must include at least two subnets."
}
}
check "existing_subnets_in_vpc" {
assert {
condition = alltrue([
for subnet in data.aws_subnet.existing_public : subnet.vpc_id == var.existing_vpc_id
])
error_message = "Every existing_public_subnet_ids value must belong to existing_vpc_id."
}
}