The infrastructure side¶
Infrastructure keeps the service and stops describing a task definition. It reads the revision the service runs now and sets that exact ARN on the service resource. The template then holds no task definition. The ARN is a snapshot from synthesis, so a saved plan can still revert a newer application deployment. Keep synthesis/plan, revision checks, deploy and completion inside the same service lock used by every application writer. A check immediately before deployment alone still has a race.
AWS CDK, Python¶
PyPI currently has 0.1.0, which does not include the construct, and neither do the existing tags. Install from git, pinned to a full commit SHA:
pip install "ecs-contract[cdk] @ git+https://github.com/tanya-ok/ecs-contract@<full-commit-sha>"
from ecs_contract.cdk import ServiceFromLiveRevision
ServiceFromLiveRevision(
self,
"Orderbook",
cluster="exchange",
service_name="orderbook",
launch_type="FARGATE",
network_configuration=network,
load_balancers=[target],
)
| Behaviour | Why |
|---|---|
DescribeServices runs at every synthesis, never cached |
a cached revision is an old one, and deploying it reverts the application |
| any lookup failure stops synthesis: no credentials, access denied, throttling | a placeholder would be deployed |
| the ARN must be complete and revisioned | family or family:latest would let ECS pick |
task_definition, desired_count, cluster, service_name are refused as extra props |
the construct owns them; a count overrides autoscaling |
cluster and service_name must be literal names |
the lookup runs before tokens resolve |
initial_task_definition is used only while the service does not exist |
the first creation needs a revision; afterwards it is ignored |
| an AWS error is reported as operation and code only | the AWS message can carry ARNs and account ids |
The synthesizing role needs ecs:DescribeServices on the service. Other props pass through to
AWS::ECS::Service unchanged. The construct records the live family:revision as node metadata.
A lookup can be injected with lookup=, a function from (cluster, service) to an ARN or None.
Tests use it to synthesize without AWS. A deploy must use the default.
Terraform¶
The provider reads the live revision through the aws_ecs_service data source. The data source
fails when the service does not exist, so the first creation takes the revision from a variable.
variable "initial_task_definition" {
type = string
default = null
}
data "aws_ecs_service" "live" {
count = var.initial_task_definition == null ? 1 : 0
cluster_arn = aws_ecs_cluster.exchange.arn
service_name = "orderbook"
}
resource "aws_ecs_service" "orderbook" {
name = "orderbook"
cluster = aws_ecs_cluster.exchange.id
launch_type = "FARGATE"
task_definition = coalesce(var.initial_task_definition, one(data.aws_ecs_service.live[*].task_definition))
lifecycle {
ignore_changes = [desired_count]
}
}
There is no aws_ecs_task_definition resource. If one exists today, follow
migration.md: mark it lifecycle { prevent_destroy = true } and remove it from
state with a removed block and destroy = false, never by deleting it from the configuration
alone.
Planning a migration¶
ecsc migrate --plan reads the live service and prints the runbook in
migration.md filled in by name: the revision to record, the pointer each secret
needs, and the keys each contract file will hold. It changes nothing and prints no value or ARN.
ecsc migrate --plan -e production --cluster exchange --service orderbook --container app