0fea29cdbb
---ci--- project: acdl phase: 12 milestone: v1.2 status: verify verdict: VERIFIED requirements: covered: [REQ-30] ---/ci--- Phase 12 plan-as-execute + verify. scripts/verify_phase12.sh green (22 assertions). All Wave 1 + Wave 2 tasks complete: - T-12.1: run_spike_*.sh -> run_platform.sh (D-048, --plan-only flag) - T-12.2: spike_runner_policy.json expanded (ECS + ECR + ELB + IAM + EC2) - T-12.3: idempotency documented in bootstrap scripts - T-12.4: P1-1 redacted (no live AWS key IDs in .ciagent/) - T-12.5: P1-B fixed (PERSONAS.md platform/registry -> modules-ir/registry.json) Subagent confirmed run_platform.sh --plan-only runs against real AWS, exit 0. Ready to ship v1.2.2.
97 lines
3.5 KiB
Python
97 lines
3.5 KiB
Python
"""Create the ACDL v1.1 spike AWS state backend (idempotent).
|
|
|
|
- S3 bucket acdl-tfstate-<account_id>-us-east-1 (versioning enabled).
|
|
- DynamoDB table acdl-outbox (PAY_PER_REQUEST; PK contractId, SK
|
|
eventType#eventTs) — used for BOTH Terraform state locking AND the
|
|
evidence outbox (D-P08-1).
|
|
|
|
Run with the bootstrap root key in env:
|
|
ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID / ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY
|
|
AWS_DEFAULT_REGION (defaults to us-east-1)
|
|
|
|
Writes terraform/bootstrap/.bootstrap_state.json (gitignored bookkeeping).
|
|
|
|
Idempotent: re-running this script against an already-bootstrapped account
|
|
exits 0 without duplicating resources. The S3 state bucket is guarded by a
|
|
head_bucket probe (skips creation if it exists), bucket versioning is
|
|
re-PUT on every run (PutBucketVersioning is itself idempotent), and the
|
|
DynamoDB outbox table is guarded by a describe_table probe (skips creation
|
|
on ResourceNotFoundException). The bootstrap-state marker file is always
|
|
overwritten with the current run's timestamp (it is bookkeeping, not a
|
|
resource).
|
|
"""
|
|
|
|
import datetime
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
import boto3
|
|
|
|
|
|
REGION = os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
|
|
STATE_BUCKET = "acdl-tfstate-581513795199-us-east-1"
|
|
OUTBOX_TABLE = "acdl-outbox"
|
|
ACCOUNT_ID = "581513795199"
|
|
|
|
|
|
def main():
|
|
session = boto3.Session(
|
|
aws_access_key_id=os.environ["ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID"],
|
|
aws_secret_access_key=os.environ["ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY"],
|
|
region_name=REGION,
|
|
)
|
|
s3 = session.client("s3", region_name=REGION)
|
|
dyn = session.client("dynamodb", region_name=REGION)
|
|
|
|
# --- S3 state bucket (idempotent) ---
|
|
try:
|
|
s3.head_bucket(Bucket=STATE_BUCKET)
|
|
print(f"s3: bucket {STATE_BUCKET} already exists")
|
|
except Exception:
|
|
kwargs = {"Bucket": STATE_BUCKET}
|
|
if REGION != "us-east-1":
|
|
kwargs["CreateBucketConfiguration"] = {"LocationConstraint": REGION}
|
|
s3.create_bucket(**kwargs)
|
|
print(f"s3: created bucket {STATE_BUCKET}")
|
|
# Enable versioning (idempotent)
|
|
s3.put_bucket_versioning(
|
|
Bucket=STATE_BUCKET,
|
|
VersioningConfiguration={"Status": "Enabled"},
|
|
)
|
|
print(f"s3: versioning enabled on {STATE_BUCKET}")
|
|
|
|
# --- DynamoDB outbox table (idempotent) ---
|
|
try:
|
|
dyn.describe_table(TableName=OUTBOX_TABLE)
|
|
print(f"dynamodb: table {OUTBOX_TABLE} already exists")
|
|
except dyn.exceptions.ResourceNotFoundException:
|
|
dyn.create_table(
|
|
TableName=OUTBOX_TABLE,
|
|
BillingMode="PAY_PER_REQUEST",
|
|
AttributeDefinitions=[
|
|
{"AttributeName": "contractId", "AttributeType": "S"},
|
|
{"AttributeName": "eventType#eventTs", "AttributeType": "S"},
|
|
],
|
|
KeySchema=[
|
|
{"AttributeName": "contractId", "KeyType": "HASH"},
|
|
{"AttributeName": "eventType#eventTs", "KeyType": "RANGE"},
|
|
],
|
|
)
|
|
print(f"dynamodb: created table {OUTBOX_TABLE}")
|
|
dyn.get_waiter("table_exists").wait(TableName=OUTBOX_TABLE)
|
|
|
|
marker = {
|
|
"account_id": ACCOUNT_ID,
|
|
"bucket_name": STATE_BUCKET,
|
|
"table_name": OUTBOX_TABLE,
|
|
"region": REGION,
|
|
"created_at": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
}
|
|
with open(os.path.join(os.path.dirname(__file__), ".bootstrap_state.json"), "w") as fh:
|
|
json.dump(marker, fh, indent=2)
|
|
print("bootstrap state marker written:", marker)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |