AWS Plugin Release Notes

0.1.11

Requires formae 0.86.0+. This release marks 60 provider-immutable fields across 13 services (AppRunner, EC2, ECR, ECS, EKS, Elastic Beanstalk, ELBv2, KMS, Lambda, RDS, Route 53, S3, SageMaker) as createOnly, to line up with the new planning behaviour in formae 0.86.0. Under 0.86.0, fields that aren't explicitly immutable are treated as mutable and updated in place rather than triggering a replace. Any field AWS actually rejects on update needs to be marked immutable, or the apply fails at the provider. With this release every such field is annotated correctly, so the 0.86.0 in-place-update behaviour lands without surprise provider rejections. minFormaeVersion is bumped to 0.86.0 accordingly.

New: Seven new resource types bring fully-managed CloudFront stacks to formae — distributions, their policies, edge functions, and the ACM certificates they depend on, all wired together through the resource graph. The set is AWS::CertificateManager::Certificate, AWS::CloudFront::Function, AWS::CloudFront::KeyValueStore, AWS::CloudFront::CachePolicy, AWS::CloudFront::OriginRequestPolicy, AWS::CloudFront::ResponseHeadersPolicy, and AWS::CloudFront::OriginAccessControl. ACM certs ship with a full custom provisioner that talks to the ACM API directly (the resource type is non-provisionable through CloudControl); cert.res.validationRecords exposes the DNS validation CNAMEs ACM publishes, so a Route53::RecordSet (or any other DNS-publisher resource) can wire them through the resource graph instead of being filled in by hand. CloudFront Distributions can now reference all of the above through Resolvable links — cache policy ID, origin-request policy ID, response-headers policy ID, origin access control ID, function ARN, lambda ARN, and ACM cert ARN — and formae orders creates and destroys correctly based on those references.

New: AWS::ECS::Service now exposes an endpoints resolvable (service.res.endpoints.at("containerName:containerPort")) so downstream resources can wire their config URL through the service itself instead of through the listener. Because the endpoint resolves only once the service is operationally stable (the deployment-stability gating added in 0.1.10), anything consuming it waits until tasks are actually serving traffic. This closes the fresh-apply race where a listener URL resolved seconds before the tasks behind it were healthy, leaving consumers pointed at an endpoint returning 503s. Alongside this, AWS::ElasticLoadBalancingV2::ListenerRule now exposes its target group ARN so consumers of rule-routed services get correct ordering instead of racing the rule, and a load balancer name longer than the AWS 32-character limit is now rejected at pkl eval time rather than deep in apply. Listener-rule path/host routing, weighted target groups, and NLB endpoints are deferred follow-ups.

Fix: Security group rules are now destroyed after the workloads that sit behind the security group, instead of in the first destroy wave. Previously AWS::EC2::SecurityGroupIngress and AWS::EC2::SecurityGroupEgress had no incoming edges in the destroy graph, so they were torn down first, severing ALB-to-task health checks, task-to-EFS NFS, and task-to-internet connectivity, which then cascade-failed the rest of the teardown. The destroy order is now workloads, then the security group rules, then the security group itself.

Fix: AWS::Lambda::EventInvokeConfig no longer fails intermittently on create. CloudControl injects an empty DestinationConfig (with empty OnFailure/OnSuccess sub-objects) into every read of this resource, even when you never set one. formae's required-field validation then walked into the injected empty object and reported a missing Destination, surfacing as a flaky apply failure. The empty sub-objects are now stripped on read; genuine user-set destinations are non-empty and pass through untouched.

Fix: An EFS file system's mount targets are now torn down ahead of the file system itself, so destroying a stack that mounts EFS into ECS tasks no longer strands resources or fails on dependency-still-attached errors. This is driven by typed edge annotations (runtimeDependency) that pull the mount targets into the destroy ordering ahead of their file system.

New: When a CloudControl operation fails with an error code formae doesn't already handle, the plugin now logs the full progress event: operation, error code, status message, resource type, request token, and identifier. Previously these failures flowed back with no record of the underlying AWS error code, making resource-type-specific timeout and stabilization failures (such as the occasional ECS Service delete that fails once and succeeds on retry) hard to diagnose. The line is emitted once per genuine failure, so it doesn't add noise to the many in-progress polls during long-running operations.


0.1.10

New: AWS::ECS::Service now reports success only once the deployment is operationally stable: the rollout has completed, the running task count matches the desired count, and at least one healthy target exists behind each attached target group. Previously the service reported success as soon as CloudControl acknowledged the request, so any downstream resource reachable through the load balancer (for example a Grafana target driving its config through the listener URL) frequently hit 503s before the tasks were serving traffic. Non-standard service shapes (CODE_DEPLOY and EXTERNAL deployment controllers, the DAEMON scheduling strategy, classic-ELB attachments without a target group ARN, and desiredCount = 0) fall through to safe defaults rather than waiting on target health.


0.1.9

Fix: ELBv2 target groups no longer produce phantom drift that blocks formae apply. The target group's Targets field is populated at runtime by ECS Services (and anything else calling the register-targets API), and LoadBalancerArns is populated when a listener attaches the target group to a load balancer; neither is meaningfully user-settable. Tracking them in formae state meant the periodic synchronizer rewrote the resource on every ECS task placement and every listener attach, after which reconcile rejected the next apply with the stacks-have-been-modified error even though the forma hadn't changed, forcing operators into force mode or extract-and-absorb on every reconcile. Both fields are now dropped from the schema and stripped from the AWS read response before they reach formae state.


0.1.8

Fix: ECS Service creation no longer fails with target group <arn> does not have an associated load balancer when the Service and its Listener are scheduled together in the same apply. The plugin now treats that specific CloudControl error (InvalidRequest + matching message text, on Create operations only) as a transient in-progress state; the PluginOperator's existing status-poll loop absorbs the race until the Listener finishes wiring the target group to the load balancer. No PKL change needed — direct tg.res.targetGroupArn references keep working and don't have to be rewritten through listener.res.targetGroupArn to avoid the race.

Fix: Inverts the destroy edge between an ECS Service and its target groups via attachesTo field hints on Service.LoadBalancer.targetGroupArn and Service.VpcLatticeConfiguration.targetGroupArn. AWS rejects target-group deletion while a Service is still attached; without the annotation, the Service tore down in parallel with the listener chain, and any plugin-target driving CRUD through the Service's listener URL (Grafana, Loki, Tempo, and similar) wedged mid-tear-down with no URL backing it. With the annotation, the Service is destroyed before its target group. Requires formae 0.85.0+ to take effect: 0.84.0 agents silently ignore the annotation (the plugin still installs and every other fix applies, but the destroy-edge inversion no-ops). The plugin's minFormaeVersion stays at 0.84.0 deliberately — sibling plugins built against the same SDK family work fine on 0.84.0 and forcing an agent upgrade for one annotation isn't worth the disruption.

Fix: Transient CloudControl errors are no longer silently terminal. Synchronous errors from CCAPI on Create, Update, and Delete were previously surfaced to the agent as bare Go errors, which the agent classified as UnforeseenError, a non-recoverable code that bypasses the retry pipeline entirely. Even errors that AWS itself flags as recoverable (Throttling, NotStabilized, ResourceConflict, …) ended up as terminal failures. The plugin now translates these into the typed OperationErrorCode formae's PluginOperator understands, so the agent retries recoverable conditions instead of failing the whole apply.

Fix: AWS::RDS::DBSubnetGroup no longer fails non-deterministically when its subnets are created in the same forma. AWS RDS rejected freshly-created EC2 subnets with InvalidRequestException: Some input subnets ... are invalid until RDS's internal subnet cache caught up, a classic cross-service eventual-consistency window between EC2 and RDS. The plugin recognises this specific class of InvalidRequest as a recoverable race, so the agent retries through the propagation gap and the subnet group lands on the first apply.

Fix: AWS::SES::ConfigurationSetEventDestination updates now succeed. Previously, any update to an event destination (toggling enabled, changing matchingEventTypes, switching destination targets) failed within milliseconds with no AWS API round-trip. CCAPI rejects this resource's composite <csName>|<edName> identifier on Update with ValidationException: not valid for identifier [/properties/Id], the same limitation that affected Read in 0.1.7. Updates now route through sesv2.UpdateConfigurationSetEventDestination directly.

Fix: AWS::SES::ConfigurationSetEventDestination deletes now succeed. Same CCAPI composite-identifier limitation as Update. The most user-visible symptom was that formae destroy failed on any stack containing an event destination, and replace flows (when the parent ConfigurationSet.name changes) failed at the destroy step. Deletes now go through sesv2.DeleteConfigurationSetEventDestination, with a missing-destination error treated as a successful no-op so retried destroys are idempotent.

Fix: Discovery of AWS::SES::ConfigurationSetEventDestination now correctly surfaces existing event destinations as unmanaged resources. CCAPI's ListResources returns bare EventDestinationNames ("bounces") instead of the composite <csName>|<edName> the resource's Read path requires, so every discovered destination failed its per-resource Read and never made it into the inventory. The plugin's List now walks ListConfigurationSetsGetConfigurationSetEventDestinations and emits properly-formed composite identifiers.


0.1.7

New: Amazon SES support for outbound transactional email. AWS::SES::EmailIdentity covers verified sending domains or addresses (with bundled MAIL FROM, feedback, and Easy DKIM attributes). AWS::SES::ConfigurationSet and AWS::SES::ConfigurationSetEventDestination route bounce/complaint/delivery events to SNS, Kinesis Firehose, EventBridge, or CloudWatch — exactly one of the four destination types is enforced at PKL evaluation time, so bad shapes fail at pkl eval rather than at apply time. AWS::SES::EmailIdentityVerification is a polling gate downstream consumers depend on for send-readiness; it sits between the identity (and the DNS records that verify it) and any resource that needs to send mail, breaking the apply-time deadlock where verification needs DNS, DNS depends on the identity, and the identity can't wait on either.

New: EmailIdentity.res.requiredDnsRecords is a typed listing resolvable that exposes the DNS records SES expects — 3 DKIM CNAMEs, plus an MX and SPF TXT for MAIL FROM when configured. A forma drives Route53 (or any DNS plugin) directly off id.res.requiredDnsRecords.at(N).name and .values, with no manual token extraction. See examples/ses-basic/main.pkl in the plugin repo for the full pattern. Terraform, Pulumi, and Crossplane all force users to extract verification_token/dkim_tokens[] strings and hand-author the records; this is the first IaC tool to wire them automatically.

Fix: AWS::EC2::PlacementGroup's spreadLevel is now marked hasProviderDefault. AWS auto-populates SpreadLevel after a partition-strategy create even when not specified, which previously caused replace flows (where the user switches strategy from spread to partition) to fail with Property SpreadLevel is not expected and not a provider default. The field stays createOnly.

Fix: Container-level changes on AWS::ECS::TaskDefinition are no longer silently dropped from the diff. 19 user-canonical sub-fields on ContainerDefinition — including environment, portMappings, mountPoints, secrets, command, entryPoint, dependsOn, extraHosts, and dockerLabels — were previously annotated hasProviderDefault, which symmetrically stripped them from both the desired and actual sides before comparison. Any real change to those fields produced an empty plan on formae apply, forcing operators into out-of-band aws ecs register-task-definition workarounds (which then showed up as drift on the next reconcile). The annotation is now scoped to the genuinely cloud-defaulted scalars (cpu, essential, versionConsistency).

Fix: AWS::Lambda::LayerVersion's compatibleArchitectures, compatibleRuntimes, and description are now marked hasProviderDefault. AWS Read returns empty values for these when the user omits them in PKL, and because every LayerVersion field is createOnly, the resulting phantom drift would schedule a destroy + create on every reapply.


0.1.6

Fix: Updating an AWS::Lambda::EventInvokeConfig no longer fails with Model validation failed: required key [Destination] not found. CloudControl's update handler for this resource type re-validates the full server-side state on every patch, even fields the patch doesn't touch — and AWS materialises empty DestinationConfig.OnFailure / OnSuccess sub-objects into the response on Read whether or not the caller ever set them, which then fail the schema's "if present, Destination is required" rule. Updates that only changed MaximumRetryAttempts or MaximumEventAgeInSeconds would still get rejected on the server side. Updates now route through the Lambda UpdateFunctionEventInvokeConfig API directly, which has no such cross-field re-validation.

Fix: ELBv2 Listener.defaultActions[*].forwardConfig and ECS Cluster.clusterSettings are now marked hasProviderDefault. ELBv2 derives ForwardConfig (target groups + stickiness defaults) on Read from the action's TargetGroupArn when the user specifies a simple forward target, and ECS populates clusterSettings with default entries like containerInsights: disabled when none are configured. Without the annotations, every reapply emitted a no-op patch on these fields that surfaced as a spurious update.


0.1.5

Per-field config mutability: The profile field is now mutable — changing it updates the target in place without recreating resources. The region field remains immutable; changing it triggers a full target replace as before. See Per-field config mutability for details.

New: Complete EKS resource coverage. Addon, AccessEntry, FargateProfile, PodIdentityAssociation, and IdentityProviderConfig are now first-class resources alongside Cluster and Nodegroup, enabling end-to-end EKS cluster management (including discovery of cluster-child resources) from a single forma.

New: ECS ExpressGatewayService support. Express Gateway services can now be managed through formae. Uses the native ECS SDK because the CloudControl handler for this type is broken server-side.

Fix: ECS Service and TaskSet discovery no longer spam InvalidRequestException: Missing Or Invalid ResourceModel property errors on every cycle. Service is now discovered as a child of Cluster (its list handler requires a Cluster filter), and TaskSet is enumerated via the ECS SDK's DescribeServices because its CloudControl list handler demands an Id and is effectively a Read.

New: ALB Listener URL resolvable — Listeners now expose a computed url property that combines the parent ALB's DNS name with the Listener's protocol and port. Use listener.res.url to wire load balancer endpoints into target configs without manual URL construction.

Fix: TaskDefinition was missing its resolvable wiring — taskDef.res.taskDefinitionArn now works as expected, enabling ECS Services to reference task definitions via resolvables.

Fix: EFSVolumeConfiguration.filesystemId now accepts resolvable references, so ECS tasks can reference EFS filesystems created in the same forma.

Fix: VPCGatewayAttachment now exposes resolvable references (igwAttach.res.internetGatewayId). Routes should reference the gateway ID through the attachment — not the gateway directly — to ensure correct destroy ordering. Without this, destroying a stack with Routes and an IGW attachment could hang for hours because formae tried to detach the IGW before deleting the routes that use it.

Fix: Listener now exposes listener.res.targetGroupArn, resolving to the target-group ARN attached via the listener's first default action. ECS Services (and any other consumer that needs the target group already wired to the load balancer) should reference this instead of tg.res.targetGroupArn — without it, AWS rejects ECS service creation with "target group does not have an associated load balancer" when formae schedules the service before the listener attaches the TG. Same pattern as igwAttach.res.internetGatewayId for routes/IGW.

Fix: ~225 reference-bearing properties (IDs, ARNs, names) across 77 schema files now accept formae.Resolvable, and all resources with Resolvable classes now have hidden res wired up.

Fix: Patch-mode updates to resources with optional nested objects (for example, Lambda EventInvokeConfig.DestinationConfig) could fail with CloudControl errors like required key [Destination] not found when the nested object was empty. The plugin now strips empty sub-objects from replace operations the same way it already did for add operations, so these updates succeed.

Fix: Reapplying an unchanged forma containing an ECS Service no longer spuriously replaces the service. AWS fills in default port-mapping values on awsvpc tasks that the user didn't set, which previously made the planner think the task definition had changed. Those fields are now recognised as provider-populated and ignored during comparison. Requires formae 0.84.0.

Fix: EFSVolumeConfiguration.rootDirectory no longer causes phantom replacements. CloudControl returns RootDirectory as "/" even when it was never set, which made the planner see a change on every reapply.

Fix: ECS Service and TaskSet no longer produce spurious replacements when the Cluster field is referenced via ARN. CloudControl normalises the Cluster to a short name on Read, which flipped the createOnly field and triggered a full replace. ARN-vs-short-name differences are now normalised before comparison.

Fix: TaskSet updates no longer hang indefinitely. CloudControl's update handler for TaskSet Scale never returns; a custom ECS SDK update provisioner is used instead.


0.1.4

Fix: Route53 RecordSets with the same name but different types (e.g., SOA and NS for the same domain) were discovered with identical labels, causing duplicate entries that churned on every sync cycle. Labels now include the record type, producing unique entries like example.com.-SOA and example.com.-NS.

Fix: Resources with composite CloudControl identifiers (e.g., ECS Services, Lambda EventInvokeConfigs) could show up as duplicates in inventory — one from the initial create and another from discovery. This happened because AWS CloudControl returns full ARNs during create but short names during list. Identifiers are now normalized so the resource created by apply and the resource found by discovery are correctly recognized as the same thing.

Fix: Discovery of subnet route table associations could cause apply commands to get permanently stuck. AWS returns VPC-level (main) route table associations in discovery results that cannot be read, which triggered a cascade of internal failures. These associations are now filtered out during discovery.

Fix: Updates to resources with provider-default nested objects (e.g. Lambda EventInvokeConfig destination config) could fail with CloudControl validation errors. Empty nested objects left behind after stripping unused fields were not being removed, causing required-field violations.


0.1.3

Conformance test coverage for 88 resource types across EC2, ECS, EKS, ELBv2, Elastic Beanstalk, Lambda, API Gateway, RDS, Route53, S3, SQS, IAM, KMS, EFS, ECR, CloudWatch Logs, Secrets Manager, and DynamoDB — validating the full create, read, update, delete, sync, and discovery lifecycle.

Resource fixes: Several resource types had broken operations through AWS CloudControl that are now fixed:

  • S3 BucketPolicy — reads were broken, now works correctly
  • S3 StorageLensGroup — updates were missing resource properties in the response
  • SQS QueuePolicy — was not provisionable through CloudControl, now works via direct SQS API
  • IAM Policy — was not provisionable through CloudControl, now works via direct IAM API
  • IAM AccessKey — was not provisionable through CloudControl, now works via direct IAM API
  • IAM InstanceProfile — suffered from a 60-second propagation delay through CloudControl, now works via direct IAM API
  • EC2 NetworkAclEntry — was not supported through CloudControl, now works via direct EC2 API
  • Elastic Beanstalk ConfigurationTemplate — updates through CloudControl injected CloudFormation references, now works via direct EB API

Cross-resource references: TransitGateway, FlowLog, ResourcePolicy, ConfigurationTemplate, NetworkAclEntry, and TargetGroupTuple fields now support resolvable references, enabling correct dependency ordering during apply.

Fix: Spurious diffs during updates and synchronization for resources where AWS populates default values (e.g. LoadBalancer attributes, ECS container defaults, Lambda runtime settings). Over 130 fields across 54 resource schemas now correctly distinguish user-specified values from provider defaults.

Fix: Newly created resources could appear with missing identifiers or properties in the inventory until the next sync.

Fix: Empty optional fields could cause apply failures with CloudControl validation errors (e.g. Lambda Architectures, ECS container definitions). Optional fields that are not set are now correctly omitted.

Fix: Elastic Beanstalk ConfigurationTemplates deleted outside of formae were not correctly detected during synchronization.

Fix: CloudControl status polling now correctly handles ELBv2 update semantics and prevents extract crashes on resources with complex nested properties.


0.1.2

Conformance tests: Phase 1 conformance tests covering 32 standalone resources, validating the full CRUD and discovery lifecycle.

Schema: Expanded Route53 HealthCheck schema with fully typed HealthCheckConfig and AlarmIdentifier sub-resources, enabling richer health check definitions in Pkl.

Schema: Added ResourceLifecycleConfig fields to ElasticBeanstalk environments and configuration templates.

Rename: Renamed apprunner/service.pkl to apprunner/apprunnerservice.pkl for consistency with the naming convention used across other resource schemas.

Example: Added an AppRunner example demonstrating a simple web service deployment.

Fix: Extract now correctly filters ListResults, preventing unrelated resources from appearing in extracted Pkl output.


0.1.1

Feature: Added support for AppRunner resources (AWS::AppRunner::Service), enabling management of AppRunner web services through formae.

Fix: Added missing af-south-1 availability zone pattern. The Region typealias already included af-south-1, but the AvailabilityZone constraint was missing it, causing Pkl evaluation failures for resources in that region.


0.1.0

Initial release of the AWS plugin as a standalone package built on the formae Plugin SDK.