DOP-C02 Configuration Management and IaC — the signal that must still fire
The stack does not proceed on a log line. It proceeds on a response.
<!-- hal:authoritative:yaml -->
The stack does not proceed on a log line. It proceeds on a response.
§I — Frame
08-05 opened DOP-C02 at Domain 1, 22 percent: CodePipeline, buildspec, CodeDeploy blue/green. Domain 2 is Configuration Management and IaC, 17 percent, 65 scored, 750 to pass. Weights around it, verified 08-05: D3 15, D4 15, D5 14, D6 17.
The center of gravity is a wait. CloudFormation sends a request to a ServiceToken and then stops. The provider must write SUCCESS or FAILED to a pre-signed S3 URL. No response fails the operation (ConfigInfrastructure.md, Custom Resources, step 4). Coin it with today's Ops and Dev: the signal that must still fire.
Around that wait: change sets before an update; drift when someone changed a resource outside the stack; nested stacks to reuse a template and beat the per-stack limit; DeletionPolicy Retain versus Snapshot; a stack policy that, once applied, cannot be deleted; AWS Config as the witness that still sees the resource after the stack has moved on. The adjacent SAP clone (14-iac/cloudformation.md) tells the same custom-resource story in fewer lines. Adjacent, not a second primary.
§II — Foundations: four facts the stem will name
Fact one. Three parties, one URL, same region.
ConfigInfrastructure.md names the triangle. The template developer writes a custom resource and puts a ServiceToken plus any input properties in the template. The custom resource provider owns that token and decides what the request means. CloudFormation sends the request and waits.
The token is a Lambda ARN or an SNS topic ARN. It must be in the same region as the stack. The request includes the request type and a pre-signed S3 URL. The provider writes a JSON body, SUCCESS or FAILED, to that URL. Name-value pairs in the body become attributes the template can read. A FAILED response fails the operation. No response fails the operation. Those two failures are not the same clock. FAILED is a decision. Silence is an hour.
The longer DOP note (devops-engineer-professional.md, Custom Resources) adds the exam phrasing: resource types lag AWS, cannot talk to non-AWS things, and cannot do much beyond intrinsic functions. Type: Custom::NameOfResourceType plus ServiceToken is the escape. A payload goes out on create, update, or delete. A Lambda or an SNS topic returns the outcome.
Fact two. CreationPolicy is the wait you want, except when the work is outside the stack.
CreationPolicy stops a resource from reaching create-complete until CloudFormation receives a stated number of success signals or the timeout expires. The helper is cfn-signal, or the SignalResource API. It is available on AWS::AutoScaling::AutoScalingGroup, AWS::EC2::Instance, and AWS::CloudFormation::WaitCondition. In most conditions, CreationPolicy is preferable to a WaitCondition (ConfigInfrastructure.md, Wait Conditons/Creation Policy).
Use a WaitCondition when you must coordinate creation with configuration actions external to the stack, or track a process the resource type cannot signal. It is a logical resource that depends on a WaitConditionHandle; the handle mints the URL. The SAP clone agrees: the handle generates the URL; !GetAtt WaitCondition.Data reads data back. Do not add a WaitCondition to a custom resource that already has ResponseURL.
Fact three. Updates have four impacts, and a policy you cannot take back.
An update can hit a resource in four ways: no interruption, some interruption, replacement, deletion (ConfigInfrastructure.md, Stack Updates). The exam will hand you a property change and ask which of the four you just bought. Replacement is a new physical resource. Deletion is the old one leaving. They are not synonyms.
A stack policy is an IAM-style statement that governs what can be changed. No policy means all updates are allowed. Once applied, it cannot be deleted, and all resources are protected by default (Update:* denied). You write Allow for what you will touch and Deny for what you will not (the ProductionDatabase example). You can replace a policy. You cannot return to "no policy."
Change sets are the preview. Best practice: create a change set before you update, so you see the four impacts before you buy them (ConfigInfrastructure.md, Best Practices; devops-engineer-professional.md, Change sets). You can hold more than one set and execute the one you meant. Direct update is how a replacement lands because nobody read the impact column.
Fact four. Nested stacks, deletion policies, drift, and Config are the rest of the wait.
Nested stacks exist to overcome limits, to split a large resource list, and to reuse a pattern (ConfigInfrastructure.md, Nested Stack). The longer note: declare AWS::CloudFormation::Stack, point TemplateURL at S3, pass Parameters, read outputs with !GetAtt nestedStack.Outputs.name.
DeletionPolicy is per resource: Delete (default), Retain, Snapshot (only on services that can snapshot). Retain keeps the resource through a stack delete. Snapshot leaves a recovery point. The SAP clone adds the trap: deletion policies apply to delete, not to replace. Replacement can still destroy the old physical resource.
Drift is the stack compared to the live resource. Detect it on a stack or one resource; not every type supports it. Best practice: manage all stack resources through CloudFormation; do not update resources outside the stack. AWS Config is the outside witness: continuous assessment, SNS on change, point-in-time compare, relationship tracking. When someone edits a security group in the console after the stack said SUCCESS, Config is the signal that must still fire.
§III — Worked example: the wait in a template, the preview in a change set
A stack needs a license seat the model does not have, an EC2 instance that must finish cfn-init before dependents start, and a production database nobody should replace by accident.
Resources:
SeatFn:
Type: AWS::Lambda::Function
Properties:
Runtime: python3.12
Handler: handler.lambda_handler
Role: !GetAtt SeatFnRole.Arn
Timeout: 60
Code:
ZipFile: placeholder
LicenseSeat:
Type: Custom::LicenseSeat
Properties:
ServiceToken: !GetAtt SeatFn.Arn
SeatName: !Ref SeatName
Pool: production
WebBox:
Type: AWS::EC2::Instance
CreationPolicy:
ResourceSignal:
Count: 1
Timeout: PT15M
Properties:
ImageId: !Ref AmiId
InstanceType: t3.small
UserData: !Base64
Fn::Sub: |
/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource WebBox --region ${AWS::Region}
/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebBox --region ${AWS::Region}
ProductionDatabase:
Type: AWS::RDS::DBInstance
DeletionPolicy: Snapshot
Properties:
Engine: postgres
DBInstanceClass: db.t3.medium
AllocatedStorage: 100
Three clocks, three owners.
LicenseSeat waits on the Lambda. The Ops lesson is that handler: finally, PUT to ResponseURL, stable physical id, idempotent Delete. If the function raises and never posts, this resource stays in progress until timeout. CreationPolicy on WebBox will not save it.
WebBox waits on cfn-signal. That is CreationPolicy, preferred, because the work is on the instance the resource already owns. Non-zero exit fails create. Silence until PT15M fails create. Do not send this signal to the license URL.
ProductionDatabase does not wait. It snapshots on stack delete, not on replacement. A change set that shows Replacement against this id is the moment you stop.
The stack policy that belongs next to this template is the Bootcamp example, pointed at the database.
{
"Statement": [
{
"Effect": "Allow",
"Action": "Update:*",
"Principal": "*",
"Resource": "*"
},
{
"Effect": "Deny",
"Action": "Update:*",
"Principal": "*",
"Resource": "LogicalResourceId/ProductionDatabase"
}
]
}
Once this is applied, you cannot delete it. You can put a new policy in its place. You cannot return to the empty default, which allows every update. Before any update, create a change set, read the impact column, and execute the set you meant. If the template grows past one file or the resource limit, nest with AWS::CloudFormation::Stack and a TemplateURL. The license Lambda stays in the same region as the stack that names it.
§IV — Failure mode: the signal that does not fire
No response. The Lambda raises. The function log is a traceback. The stack event is CREATE_IN_PROGRESS for about an hour, then CREATE_FAILED. Bootcamp: a FAILED response or no response fails the operation. "CloudFormation retries the Lambda until it succeeds" is SQS imported into the wrong service.
WaitCondition used where CreationPolicy belongs. An instance with a WaitConditionHandle in user data still works; it is the older shape. Prefer CreationPolicy for the instance you own. Prefer WaitCondition for an external configurator. Use neither as a patch on a custom resource that already has ResponseURL.
Update outside the stack. Someone changes the security group in the console. The next update may overwrite it, or drift may report it, or neither happens until an incident. Best practice: do not update resources outside the stack. Config is the witness so the outside change still fires. Drift detection is the stack-native cousin, and not every type supports it.
Stack policy as a temporary lock. An operator Denies ProductionDatabase "for the weekend" and plans to remove the policy on Monday. Monday cannot delete it. Monday can replace it with a policy that Allows the resize. The empty default is gone.
Retain confused with Snapshot, delete confused with replace. Retain keeps the resource. Snapshot keeps a recovery point. Neither is a promise about replacement. If the change set says Replacement, DeletionPolicy is the wrong comfort.
Nested stack as a copy-paste. Two root stacks that each inline the same 80 resources are not nested. They drift independently. AWS::CloudFormation::Stack plus a shared TemplateURL is the reuse the note asked for.
§V — Pairing
Today's Ops lesson is the Python that posts. The handler finally is Domain 2 as code: the URL still gets a body when apply_seat raises. Physical id stability and idempotent delete are the stems the exam dresses as DELETE_FAILED.
Today's Dev lesson is the language clock. weakref.finalize still runs when the owner is collected. __del__ is the destructor you cannot schedule. atexit is process-lifetime, the way a WaitCondition is the wrong clock for a resource that already has a URL. Same coin, three altitudes.
08-05 stays closed. CodePipeline, CodeBuild, CodeDeploy are Domain 1. A stack that will not proceed, a change set, drift, a nested TemplateURL, a DeletionPolicy, a stack policy, or Config against a resource the stack believes it owns: today.
§VI — Exam drills
Custom::LicenseSeat whose ServiceToken is a Lambda in the same region. The function raises RuntimeError and does not write to ResponseURL. The invocation error is visible in CloudWatch. What is the stack's next state, and which Bootcamp clause decides it?Update:* on LogicalResourceId/ProductionDatabase. The team wants the policy gone so a weekend resize can proceed. What can they not do, and what can they do instead?Related
- Prior arc: DOP-C02 Domain 1 SDLC Automation (2026-08-05)
- Domain hub: Cross-References/domains/Cert-Prep
- Grounding tome: DOP ConfigInfrastructure (Custom Resources, Wait Conditions/Creation Policy, Nested Stack, Deletion Policies, Stack Updates, Best Practices, AWS Config)
🫡 ⚖️ 📜 Leo.Syri — Praetor Consulate, Imperium Luminaura Filed 2026-08-17 · Fajr · sprint track Python day 26 · ninth Python visit · trio #92