feat(P33): uptime-kuma primitive + deploy-uptime pipeline stage (REQ-88..91)
---ci--- project: acdl phase: 33 milestone: v1.8 status: execute ---/ci--- - New uptime L1 primitive (aws:ecs:uptime-service) deploying uptime-kuma on ECS Fargate with feature_flag_enabled, monitored_endpoints, static_checks, alert_channels (Teams/email/SMS/GitHub issues). - Adapter emits ECS Fargate task + service when feature_flag_enabled=true; emits nothing when false. Container image louislam/uptime-kuma:1. - New deploy-uptime pipeline stage in pipelines/deploy.yaml (after publish-outputs, before comment-outputs). Now 9 stages. - run_platform.sh --deploy-uptime flag + automatic uptime deployment after L2 module (separate state $WORK/uptime-tf). Endpoints from L2 outputs passed as monitored_endpoints. Feature flag from inputs.uptime_enabled (default true). - scripts/seed_uptime_monitors.py for post-deploy monitor seeding via uptime-kuma API. - Registered in registry.json (14 modules total). Tests: +6 (312 -> 318). All pass.
This commit is contained in:
+75
-5
@@ -31,14 +31,14 @@ class TestInstance:
|
||||
|
||||
|
||||
class TestRegistry:
|
||||
EXPECTED_L1_KEYS = {"s3", "vpc", "ecs-cluster", "ecs-service", "iam-role", "alb", "ecr", "cloudfront", "waf", "rds", "kms-key"}
|
||||
EXPECTED_L1_KEYS = {"s3", "vpc", "ecs-cluster", "ecs-service", "iam-role", "alb", "ecr", "cloudfront", "waf", "rds", "kms-key", "uptime"}
|
||||
EXPECTED_L2_KEYS = {"static-assets", "microservice"}
|
||||
|
||||
def test_registry_has_13_entries(self, registry):
|
||||
assert len(registry) == 13
|
||||
def test_registry_has_14_entries(self, registry):
|
||||
assert len(registry) == 14
|
||||
assert set(registry.keys()) == (self.EXPECTED_L1_KEYS | self.EXPECTED_L2_KEYS)
|
||||
|
||||
def test_registry_has_11_l1_entries(self, registry):
|
||||
def test_registry_has_12_l1_entries(self, registry):
|
||||
l1 = {k for k in registry if registry[k]["1.0.0"]["interface"].startswith("modules/l1/")}
|
||||
assert l1 == self.EXPECTED_L1_KEYS
|
||||
|
||||
@@ -585,4 +585,74 @@ class TestDeletionProtectionByDefault:
|
||||
stack = resolve(str(contract_path), str(ROOT))
|
||||
for res in stack["resources"]:
|
||||
assert res.get("nfrs", {}).get("deletion_protection") is False, \
|
||||
f"Resource {res['id']} should have deletion_protection=false"
|
||||
f"Resource {res['id']} should have deletion_protection=false"
|
||||
|
||||
|
||||
class TestUptimePrimitive:
|
||||
"""REQ-88/89/90/91: uptime-kuma primitive + feature flag + pipeline stage."""
|
||||
|
||||
def test_uptime_primitive_in_registry(self, registry):
|
||||
assert "uptime" in registry
|
||||
|
||||
def test_uptime_interface_has_feature_flag(self, repo_root):
|
||||
iface = json.load(open(os.path.join(str(repo_root), "modules", "l1", "uptime", "interface.json")))
|
||||
assert "feature_flag_enabled" in iface["inputs"]
|
||||
assert iface["inputs"]["feature_flag_enabled"]["default"] is True
|
||||
|
||||
def test_uptime_interface_has_alert_channels(self, repo_root):
|
||||
iface = json.load(open(os.path.join(str(repo_root), "modules", "l1", "uptime", "interface.json")))
|
||||
assert "alert_channels" in iface["inputs"]
|
||||
assert "monitored_endpoints" in iface["inputs"]
|
||||
|
||||
def test_uptime_adapter_emits_ecs_service_when_enabled(self, tmp_path):
|
||||
uptime_stack = {
|
||||
"version": "1.0.0",
|
||||
"stack": {"name": "uptime", "kind": "l1", "depth": 1},
|
||||
"resources": [{
|
||||
"id": "uptime",
|
||||
"type": "aws:ecs:uptime-service",
|
||||
"module": "uptime@1.0.0",
|
||||
"inputs": {
|
||||
"container_image": "louislam/uptime-kuma:1",
|
||||
"region": "us-east-1",
|
||||
"feature_flag_enabled": True,
|
||||
"monitored_endpoints": [{"name": "test", "url": "https://example.com", "type": "http", "interval_seconds": 60, "timeout_seconds": 30}],
|
||||
"cpu": 256,
|
||||
"memory": 512,
|
||||
},
|
||||
"outputs": {},
|
||||
"nfrs": {"deletion_protection": True, "encryption_enabled": True},
|
||||
}],
|
||||
}
|
||||
out_dir = str(tmp_path / "tf_out")
|
||||
adapt(uptime_stack, out_dir)
|
||||
main_tf = open(os.path.join(out_dir, "main.tf")).read()
|
||||
assert 'resource "aws_ecs_service" "uptime"' in main_tf
|
||||
assert "louislam/uptime-kuma:1" in main_tf
|
||||
assert "desired_count = 1" in main_tf
|
||||
|
||||
def test_uptime_adapter_emits_nothing_when_disabled(self, tmp_path):
|
||||
"""REQ-90: feature_flag_enabled=false means no resources emitted."""
|
||||
uptime_stack = {
|
||||
"version": "1.0.0",
|
||||
"stack": {"name": "uptime", "kind": "l1", "depth": 1},
|
||||
"resources": [{
|
||||
"id": "uptime",
|
||||
"type": "aws:ecs:uptime-service",
|
||||
"module": "uptime@1.0.0",
|
||||
"inputs": {"region": "us-east-1", "feature_flag_enabled": False},
|
||||
"outputs": {},
|
||||
"nfrs": {},
|
||||
}],
|
||||
}
|
||||
out_dir = str(tmp_path / "tf_out")
|
||||
adapt(uptime_stack, out_dir)
|
||||
main_tf = open(os.path.join(out_dir, "main.tf")).read()
|
||||
assert 'resource "aws_ecs_service" "uptime"' not in main_tf
|
||||
|
||||
def test_deploy_pipeline_has_deploy_uptime_stage(self):
|
||||
import yaml
|
||||
with open(ROOT / "pipelines/deploy.yaml") as fh:
|
||||
contract = yaml.safe_load(fh)
|
||||
stage_names = [s["name"] for s in contract["stages"]]
|
||||
assert "deploy-uptime" in stage_names
|
||||
@@ -265,7 +265,7 @@ class TestDeployPipelineContract:
|
||||
contract = _load_yaml("pipelines/deploy.yaml")
|
||||
jsonschema.validate(contract, schema)
|
||||
|
||||
def test_deploy_contract_has_eight_stages(self):
|
||||
def test_deploy_contract_has_nine_stages(self):
|
||||
contract = _load_yaml("pipelines/deploy.yaml")
|
||||
stage_names = [s["name"] for s in contract["stages"]]
|
||||
assert stage_names == [
|
||||
@@ -276,6 +276,7 @@ class TestDeployPipelineContract:
|
||||
"confidence",
|
||||
"apply",
|
||||
"publish-outputs",
|
||||
"deploy-uptime",
|
||||
"comment-outputs",
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user