e15eea067b
P5 final-review-ship complete: dual-read fallback removed (REQ-164) — core/env.py NOVA-only, .env.secrets load paths NOVA-only (G-106 retired), nova_tagging.py hard-fails any acdl:* tag, legacy ACDL_* Gitea secrets deleted, ACDL_LIFECYCLE_MODE/ACDL_LOCAL_TIER/ACDL_HITL_* exports removed from scripts, SNS subject → Nova SoD halt (P1-2), bootstrap scripts NOVA-only. Review: 2 P0 auto-fixed (duplicate delenv), P1-1/P1-2 resolved, doc-drift fixed. Audit: tags v1.15.0-4 exist; traceability REQ-155..164 all complete; ARCHITECTURE naming table matches codebase. 615 pytest PASS; run_ci.sh 3-stage PASS. NOVA_MIGRATION.md marked COMPLETE. ---ci--- project: acdl phase: 5 milestone: v1.15 status: complete phase_role: final requirements: covered: [REQ-155, REQ-156, REQ-157, REQ-158, REQ-159, REQ-160, REQ-161, REQ-162, REQ-163, REQ-164] partial: [] ---/ci---
142 lines
5.0 KiB
Python
142 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Nova Phase 15 — push the consumer microservice Docker image to ECR.
|
|
|
|
Steps performed by this script:
|
|
1. Load AWS creds from /root/acdl/.env.secrets
|
|
(NOVA_AWS_ACCESS_KEY_ID, NOVA_AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION;
|
|
dual-read ACDL_* fallback until P5).
|
|
2. Create the ECR repo `nova-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 on sys.path so `from core import env` resolves to THIS package
|
|
# (avoids editable-installed third-party `core` shadow).
|
|
_REPO_ROOT = str(pathlib.Path(__file__).resolve().parent.parent)
|
|
if _REPO_ROOT not in sys.path:
|
|
sys.path.insert(0, _REPO_ROOT)
|
|
|
|
from core import env
|
|
|
|
|
|
REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
ENV_FILE = REPO_ROOT / ".env.secrets"
|
|
AWS_ACCOUNT_ID = env.get_env("AWS_ACCOUNT_ID", "581513795199")
|
|
AWS_REGION = "us-east-1"
|
|
ECR_REPO_NAME = "nova-microservice"
|
|
IMAGE_TAG = "latest"
|
|
|
|
|
|
def _load_env(path):
|
|
"""Load NOVA_AWS_* (preferred) / ACDL_AWS_* (fallback) + AWS_DEFAULT_REGION
|
|
from a flat KEY=VALUE file (dual-read per G-106, until P5)."""
|
|
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)
|
|
# Dual-read: NOVA_* preferred, ACDL_* fallback (G-106, removed in P5).
|
|
access_key = creds.get("NOVA_AWS_ACCESS_KEY_ID")
|
|
secret_key = creds.get("NOVA_AWS_SECRET_ACCESS_KEY")
|
|
region = creds.get("AWS_DEFAULT_REGION", AWS_REGION)
|
|
if not access_key or not secret_key:
|
|
print("FAIL: NOVA_AWS_ACCESS_KEY_ID / NOVA_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 nova-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()) |