docs(P15): plan-as-execute + verify (v1.2.5, PARTIAL — terraform apply blocked by IAM)
---ci---
project: acdl
phase: 15
milestone: v1.2
status: verify
verdict: PARTIAL
requirements:
covered: [REQ-34]
partial: [REQ-33]
blocker:
- id: P0-IAM
description: terraform apply fails with AccessDenied on ECS/ECR/IAM/EC2 — live spike_runner_policy.json not pushed (root key deactivated per D-034)
unblock: operator runs create_iam_user.py with root/admin creds to push the expanded policy, then terraform apply succeeds (plan valid, 13 to add)
---/ci---
Phase 15 plan-as-execute + verify. PARTIAL: terraform apply blocked by IAM.
- Consumer microservice content authored (app.py + Dockerfile + README.md).
- Docker image acdl-microservice:latest built.
- Adapter fixed: ref emission (bare), JSON-string jsonencode, ECS service
network_configuration/load_balancer/desired_count/launch_type/task_definition,
listener default_action/load_balancer_arn, target group target_type/vpc_id/protocol,
VPC tags (not name), IGW + route table association, managed_policy_arns list.
- L1 fixes: l1-ecs-service (removed port from service sub-resource),
l1-vpc (added intra_refs, removed igw_id output).
- Resolver: intra_refs resolution (refs between sub-resources of same L1).
- terraform validate + plan succeed (13 to add).
- terraform apply BLOCKED (AccessDenied — live IAM policy not updated).
- Evidence event TERRAFORM_APPLY_BLOCKED written to DynamoDB outbox.
- v1.1 S3 regression: byte-identical.
Ready to ship v1.2.5 (partial).
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python3
|
||||
"""ACDL Phase 15 — push the consumer microservice Docker image to ECR.
|
||||
|
||||
Steps performed by this script:
|
||||
1. Load AWS creds from /root/acdl/.env.secrets
|
||||
(ACDL_AWS_ACCESS_KEY_ID, ACDL_AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION).
|
||||
2. Create the ECR repo `acdl-microservice` if it doesn't exist
|
||||
(ecr:DescribeRepositories / ecr:CreateRepository). Region: us-east-1.
|
||||
3. Get the ECR login password (ecr:GetAuthorizationToken) and run
|
||||
`docker login` with it.
|
||||
|
||||
After this script runs, it prints the docker `tag` and `push` commands
|
||||
for the caller to run in the shell (steps 4-5 of T-15.1).
|
||||
|
||||
Usage:
|
||||
python3 scripts/push_consumer_image.py
|
||||
|
||||
Constraints (T-15.1): the `aws` CLI is NOT installed — boto3 is used for
|
||||
every AWS API call. `docker` is invoked via subprocess for the login
|
||||
(since docker is the only thing that can use the auth token meaningfully).
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import pathlib
|
||||
|
||||
import boto3
|
||||
|
||||
|
||||
REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
|
||||
ENV_FILE = REPO_ROOT / ".env.secrets"
|
||||
AWS_ACCOUNT_ID = "581513795199"
|
||||
AWS_REGION = "us-east-1"
|
||||
ECR_REPO_NAME = "acdl-microservice"
|
||||
IMAGE_TAG = "latest"
|
||||
|
||||
|
||||
def _load_env(path):
|
||||
"""Load ACDL_AWS_* + AWS_DEFAULT_REGION from a flat KEY=VALUE file."""
|
||||
creds = {}
|
||||
with open(path, "r") as fh:
|
||||
for line in fh:
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
k, v = line.split("=", 1)
|
||||
creds[k.strip()] = v.strip()
|
||||
return creds
|
||||
|
||||
|
||||
def main():
|
||||
if not ENV_FILE.exists():
|
||||
print(f"FAIL: {ENV_FILE} not found", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
creds = _load_env(ENV_FILE)
|
||||
access_key = creds.get("ACDL_AWS_ACCESS_KEY_ID")
|
||||
secret_key = creds.get("ACDL_AWS_SECRET_ACCESS_KEY")
|
||||
region = creds.get("AWS_DEFAULT_REGION", AWS_REGION)
|
||||
if not access_key or not secret_key:
|
||||
print("FAIL: ACDL_AWS_ACCESS_KEY_ID / ACDL_AWS_SECRET_ACCESS_KEY missing",
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
|
||||
# Export the creds for the docker subprocess (it doesn't need them, but
|
||||
# keeps parity with the terraform step that runs after this).
|
||||
os.environ["AWS_ACCESS_KEY_ID"] = access_key
|
||||
os.environ["AWS_SECRET_ACCESS_KEY"] = secret_key
|
||||
os.environ["AWS_DEFAULT_REGION"] = region
|
||||
|
||||
session = boto3.Session(
|
||||
aws_access_key_id=access_key,
|
||||
aws_secret_access_key=secret_key,
|
||||
region_name=region,
|
||||
)
|
||||
ecr = session.client("ecr")
|
||||
|
||||
# Step 2: create the ECR repo if it doesn't exist.
|
||||
repo_uri = None
|
||||
try:
|
||||
resp = ecr.describe_repositories(repositoryNames=[ECR_REPO_NAME])
|
||||
repo = resp["repositories"][0]
|
||||
repo_uri = repo["repositoryUri"]
|
||||
print(f"ecr: repository {ECR_REPO_NAME!r} already exists -> {repo_uri}")
|
||||
except ecr.exceptions.RepositoryNotFoundException:
|
||||
print(f"ecr: repository {ECR_REPO_NAME!r} not found, creating...")
|
||||
resp = ecr.create_repository(repositoryName=ECR_REPO_NAME)
|
||||
repo = resp["repository"]
|
||||
repo_uri = repo["repositoryUri"]
|
||||
print(f"ecr: created repository {ECR_REPO_NAME!r} -> {repo_uri}")
|
||||
except Exception as exc:
|
||||
print(f"FAIL: ecr describe/create failed: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# Step 3: get login password + run `docker login`.
|
||||
auth = ecr.get_authorization_token()
|
||||
token = auth["authorizationData"][0]["authorizationToken"]
|
||||
# The token is base64(USERNAME:PASSWORD); docker login wants them split.
|
||||
import base64
|
||||
user_pw = base64.b64decode(token).decode("utf-8")
|
||||
username, password = user_pw.split(":", 1)
|
||||
registry = f"{AWS_ACCOUNT_ID}.dkr.ecr.{region}.amazonaws.com"
|
||||
|
||||
print(f"docker: logging in to {registry} ...")
|
||||
login_cmd = [
|
||||
"docker", "login",
|
||||
"--username", username,
|
||||
"--password-stdin",
|
||||
registry,
|
||||
]
|
||||
proc = subprocess.run(login_cmd, input=password.encode("utf-8"),
|
||||
capture_output=True)
|
||||
if proc.returncode != 0:
|
||||
print("FAIL: docker login failed:", file=sys.stderr)
|
||||
sys.stderr.write(proc.stderr.decode("utf-8", "replace"))
|
||||
return 1
|
||||
print("docker: login OK")
|
||||
|
||||
# Steps 4-5: print the tag + push commands for the caller to run.
|
||||
full_tag = f"{repo_uri}:{IMAGE_TAG}"
|
||||
print("")
|
||||
print("=== NEXT: run these commands in the shell to tag + push ===")
|
||||
print(f"docker tag acdl-microservice:latest {full_tag}")
|
||||
print(f"docker push {full_tag}")
|
||||
print("")
|
||||
print(f"ECR_IMAGE={full_tag}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user