Files
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

442 lines
14 KiB
Python

#!/usr/bin/env python3
"""Deploy Flink ECS infrastructure using boto3 SDK.
Creates:
- ECS cluster for running Flink batch tasks
- ECS task definition for PyFlink container
- S3 buckets for landing and processed data
- Lambda function for /trigger endpoint (ingestion + ECS task launcher)
- API Gateway HTTP API
- Mock External Vendor API Lambda (shared)
Key difference from Prefect stack:
- No long-running service (tasks run on-demand and exit)
- Trigger Lambda starts ECS tasks via RunTask API
"""
import time
import uuid
from pathlib import Path
project_root = Path(__file__).resolve().parents[3]
from tests.shared.infrastructure_sdk import save_outputs
from tests.shared.infrastructure_sdk.resources import (
api_gateway,
ecr,
ecs,
iam,
lambda_,
logs,
s3,
secrets,
vpc,
)
STACK_NAME = "tracer-flink-ecs"
REGION = "us-east-1"
GRAFANA_SECRET_NAME = "tracer/grafana-cloud"
def deploy() -> dict:
"""Deploy all infrastructure and return outputs."""
start_time = time.time()
print(f"Deploying stack: {STACK_NAME}")
print("=" * 60)
# Generate unique suffix for S3 buckets
unique_suffix = uuid.uuid4().hex[:8]
# ========================================
# Phase 1: Parallel - VPC, S3, IAM, Logs, Security Group
# ========================================
print("\n[Phase 1] Creating VPC, S3, IAM, Logs, Security Group...")
# VPC lookup
print(" - Looking up default VPC...")
vpc_info = vpc.get_default_vpc(REGION)
vpc_id = vpc_info["vpc_id"]
subnet_ids = vpc.get_public_subnets(vpc_id, REGION)
print(f" VPC: {vpc_id}, Subnets: {subnet_ids}")
# S3 buckets
landing_bucket_name = f"tracer-flink-landing-{unique_suffix}"
processed_bucket_name = f"tracer-flink-processed-{unique_suffix}"
print(f" - Creating S3 bucket: {landing_bucket_name}")
s3.create_bucket(landing_bucket_name, STACK_NAME, REGION)
print(f" - Creating S3 bucket: {processed_bucket_name}")
s3.create_bucket(processed_bucket_name, STACK_NAME, REGION)
# CloudWatch log group
log_group_name = "/ecs/tracer-flink"
print(f" - Creating log group: {log_group_name}")
logs.create_log_group(log_group_name, retention_days=7, stack_name=STACK_NAME, region=REGION)
# IAM roles
print(" - Creating ECS task role...")
task_role = iam.create_ecs_task_role(f"{STACK_NAME}-task-role", STACK_NAME, REGION)
print(" - Creating ECS execution role...")
execution_role = iam.create_ecs_execution_role(
f"{STACK_NAME}-execution-role", STACK_NAME, REGION
)
print(" - Creating Lambda trigger role...")
trigger_lambda_role = iam.create_lambda_execution_role(
f"{STACK_NAME}-trigger-role", STACK_NAME, REGION
)
print(" - Creating Mock API Lambda role...")
mock_api_role = iam.create_lambda_execution_role(
f"{STACK_NAME}-mock-api-role", STACK_NAME, REGION
)
# Security group
print(" - Creating security group...")
security_group = vpc.create_security_group(
name=f"{STACK_NAME}-sg",
vpc_id=vpc_id,
description="Security group for Flink ECS tasks",
stack_name=STACK_NAME,
region=REGION,
)
# ========================================
# Phase 2: Add IAM permissions
# ========================================
print("\n[Phase 2] Configuring IAM permissions...")
# Task role: S3 access
print(" - Granting task role S3 read on landing bucket...")
s3.grant_read(landing_bucket_name, task_role["arn"], REGION)
print(" - Granting task role S3 read/write on processed bucket...")
s3.grant_read_write(processed_bucket_name, task_role["arn"], REGION)
# Execution role: Secrets Manager access for Grafana
print(" - Granting execution role Secrets Manager access...")
secret_arn = secrets.get_secret_arn(GRAFANA_SECRET_NAME, REGION)
iam.put_role_policy(
execution_role["name"],
"SecretsAccess",
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue"],
"Resource": [secret_arn],
}
],
},
REGION,
)
# Trigger Lambda role: S3 write + ECS RunTask (will update task definition ARN later)
print(" - Granting trigger role S3 write on landing bucket...")
s3.grant_write(landing_bucket_name, trigger_lambda_role["arn"], REGION)
# ========================================
# Phase 3: ECR and ECS Cluster
# ========================================
print("\n[Phase 3] Creating ECR repository and ECS cluster...")
# ECR repository
ecr_repo_name = f"{STACK_NAME}-flink"
print(f" - Creating ECR repository: {ecr_repo_name}")
ecr_repo = ecr.create_repository(ecr_repo_name, STACK_NAME, REGION)
# Build and push Docker image
# Build context is project root to include telemetry packages
context_dir = project_root
dockerfile_path = (
project_root
/ "tests"
/ "upstream_apache_flink_ecs"
/ "infrastructure_code"
/ "flink_image"
/ "Dockerfile"
)
print(" - Building and pushing Docker image (ARM64)...")
print(f" Dockerfile: {dockerfile_path}")
print(f" Context: {context_dir}")
image_uri = ecr.build_and_push(
dockerfile_path=dockerfile_path,
repository_uri=ecr_repo["uri"],
tag="latest",
platform="linux/arm64",
region=REGION,
context_dir=context_dir,
)
print(f" Image URI: {image_uri}")
# ECS cluster
cluster_name = "tracer-flink-cluster"
print(f" - Creating ECS cluster: {cluster_name}")
cluster = ecs.create_cluster(cluster_name, STACK_NAME, REGION)
# ========================================
# Phase 4: Task Definition
# ========================================
print("\n[Phase 4] Creating ECS task definition...")
# Build container definitions
flink_container = ecs.build_container_definition(
name="FlinkContainer",
image=image_uri,
cpu=256,
memory=1536,
environment={
"LANDING_BUCKET": landing_bucket_name,
"PROCESSED_BUCKET": processed_bucket_name,
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
"OTEL_SERVICE_NAME": "flink-etl-pipeline",
"OTEL_RESOURCE_ATTRIBUTES": "pipeline.name=upstream_downstream_pipeline_flink,pipeline.framework=flink,test_case=e2e_upstream_apache_flink_ecs",
},
secrets=[
{
"name": "GCLOUD_HOSTED_METRICS_URL",
"valueFrom": f"{secret_arn}:GCLOUD_HOSTED_METRICS_URL::",
},
{
"name": "GCLOUD_HOSTED_METRICS_ID",
"valueFrom": f"{secret_arn}:GCLOUD_HOSTED_METRICS_ID::",
},
{
"name": "GCLOUD_HOSTED_LOGS_URL",
"valueFrom": f"{secret_arn}:GCLOUD_HOSTED_LOGS_URL::",
},
{"name": "GCLOUD_HOSTED_LOGS_ID", "valueFrom": f"{secret_arn}:GCLOUD_HOSTED_LOGS_ID::"},
{"name": "GCLOUD_RW_API_KEY", "valueFrom": f"{secret_arn}:GCLOUD_RW_API_KEY::"},
{"name": "GCLOUD_OTLP_ENDPOINT", "valueFrom": f"{secret_arn}:GCLOUD_OTLP_ENDPOINT::"},
{
"name": "GCLOUD_OTLP_AUTH_HEADER",
"valueFrom": f"{secret_arn}:GCLOUD_OTLP_AUTH_HEADER::",
},
],
log_group=log_group_name,
region=REGION,
essential=True,
)
# Alloy sidecar for OTEL
alloy_container = ecs.build_container_definition(
name="alloy",
image="grafana/alloy:v1.4.2",
cpu=128,
memory=256,
command=[
"run",
"--server.http.listen-addr=0.0.0.0:12345",
"/etc/alloy/config.alloy",
],
secrets=[
{
"name": "GCLOUD_OTLP_ENDPOINT",
"valueFrom": f"{secret_arn}:GCLOUD_OTLP_ENDPOINT::",
},
{
"name": "GCLOUD_OTLP_AUTH_HEADER",
"valueFrom": f"{secret_arn}:GCLOUD_OTLP_AUTH_HEADER::",
},
],
log_group=log_group_name,
region=REGION,
essential=False,
)
# Add Alloy config file via environment or mount
# For simplicity, we use the embedded config approach
alloy_config = """
otelcol.receiver.otlp "default" {
grpc {
endpoint = "0.0.0.0:4317"
}
http {
endpoint = "0.0.0.0:4318"
}
output {
traces = [otelcol.exporter.otlphttp.grafana.input]
}
}
otelcol.exporter.otlphttp "grafana" {
client {
endpoint = env("GCLOUD_OTLP_ENDPOINT")
headers = {
"Authorization" = env("GCLOUD_OTLP_AUTH_HEADER"),
}
}
}
"""
# Update alloy container with config
alloy_container.setdefault("environment", []).append(
{"name": "ALLOY_CONFIG", "value": alloy_config.strip()}
)
# Add container dependency
flink_container["dependsOn"] = [
{"containerName": "alloy", "condition": "START"},
]
# Create task definition
task_def = ecs.create_task_definition(
family=f"{STACK_NAME}-task",
container_definitions=[flink_container, alloy_container],
task_role_arn=task_role["arn"],
execution_role_arn=execution_role["arn"],
cpu="512",
memory="2048",
runtime_platform={"cpuArchitecture": "ARM64", "operatingSystemFamily": "LINUX"},
stack_name=STACK_NAME,
region=REGION,
)
print(f" Task Definition: {task_def['arn']}")
# ========================================
# Phase 5: Update Trigger Lambda Role with Task Definition ARN
# ========================================
print("\n[Phase 5] Updating Lambda role with ECS RunTask permissions...")
iam.put_role_policy(
trigger_lambda_role["name"],
"EcsRunTask",
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["ecs:RunTask"],
"Resource": [f"{task_def['arn'].rsplit(':', 1)[0]}:*"],
},
{
"Effect": "Allow",
"Action": ["iam:PassRole"],
"Resource": [task_role["arn"], execution_role["arn"]],
},
],
},
REGION,
)
# ========================================
# Phase 6: Lambda Functions and API Gateways
# ========================================
print("\n[Phase 6] Creating Lambda functions and API Gateways...")
# Mock API Lambda
mock_api_code_dir = project_root / "tests/shared/external_vendor_api"
print(f" - Bundling Mock API Lambda from: {mock_api_code_dir}")
mock_api_code = lambda_.bundle_code(mock_api_code_dir)
mock_api_lambda_name = f"{STACK_NAME}-mock-api"
print(f" - Creating Mock API Lambda: {mock_api_lambda_name}")
mock_api_lambda = lambda_.create_function(
name=mock_api_lambda_name,
role_arn=mock_api_role["arn"],
handler="handler.lambda_handler",
code_zip=mock_api_code,
runtime="python3.11",
timeout=30,
memory=128,
stack_name=STACK_NAME,
region=REGION,
)
# Create Mock API Gateway
print(" - Creating Mock API Gateway...")
mock_api = api_gateway.create_simple_api_with_lambda(
api_name=f"{STACK_NAME}-mock-api",
lambda_arn=mock_api_lambda["arn"],
stack_name=STACK_NAME,
region=REGION,
)
print(f" Mock API URL: {mock_api['invoke_url']}")
# Trigger Lambda
trigger_code_dir = (
project_root / "tests/e2e/upstream_apache_flink_ecs/pipeline_code/trigger_lambda"
)
requirements_file = trigger_code_dir / "requirements.txt"
print(f" - Bundling Trigger Lambda from: {trigger_code_dir}")
trigger_code = lambda_.bundle_code(trigger_code_dir, requirements_file)
trigger_lambda_name = f"{STACK_NAME}-trigger"
print(f" - Creating Trigger Lambda: {trigger_lambda_name}")
trigger_lambda = lambda_.create_function(
name=trigger_lambda_name,
role_arn=trigger_lambda_role["arn"],
handler="handler.lambda_handler",
code_zip=trigger_code,
runtime="python3.11",
timeout=60,
memory=256,
environment={
"LANDING_BUCKET": landing_bucket_name,
"PROCESSED_BUCKET": processed_bucket_name,
"EXTERNAL_API_URL": mock_api["invoke_url"],
"ECS_CLUSTER": cluster["arn"],
"TASK_DEFINITION": task_def["arn"],
"SUBNET_IDS": ",".join(subnet_ids),
"SECURITY_GROUP_ID": security_group["group_id"],
},
stack_name=STACK_NAME,
region=REGION,
)
# Create Trigger API Gateway
print(" - Creating Trigger API Gateway...")
trigger_api = api_gateway.create_simple_api_with_lambda(
api_name=f"{STACK_NAME}-trigger",
lambda_arn=trigger_lambda["arn"],
stack_name=STACK_NAME,
region=REGION,
)
print(f" Trigger API URL: {trigger_api['invoke_url']}")
# ========================================
# Save Outputs
# ========================================
outputs = {
"LandingBucketName": landing_bucket_name,
"ProcessedBucketName": processed_bucket_name,
"TriggerApiUrl": trigger_api["invoke_url"],
"MockApiUrl": mock_api["invoke_url"],
"EcsClusterName": cluster_name,
"EcsClusterArn": cluster["arn"],
"LogGroupName": log_group_name,
"TaskDefinitionArn": task_def["arn"],
"SecurityGroupId": security_group["group_id"],
"SubnetIds": ",".join(subnet_ids),
"TriggerLambdaName": trigger_lambda_name,
"TriggerLambdaArn": trigger_lambda["arn"],
"MockApiLambdaName": mock_api_lambda_name,
"MockApiLambdaArn": mock_api_lambda["arn"],
"VpcId": vpc_id,
"EcrRepositoryUri": ecr_repo["uri"],
"ImageUri": image_uri,
}
save_outputs(STACK_NAME, outputs)
elapsed = time.time() - start_time
print("\n" + "=" * 60)
print(f"Deployment completed in {elapsed:.1f}s")
print("=" * 60)
print("\nOutputs:")
for key, value in outputs.items():
print(f" {key}: {value}")
return outputs
if __name__ == "__main__":
deploy()