Skip to content
cdkd

cdkd deploy: checks on the cloud assembly

The cloud assembly is the directory that synthesis writes, usually cdk.out. It holds the templates, the asset manifests and the files to upload. cdkd deploy checks its nested templates and file paths right after synthesis, before it expands a macro, publishes an asset, takes a lock or creates a resource.

An assembly that the CDK generated passes every check on this page. The checks catch an assembly that was edited by hand or produced by another tool. One malformed stack stops the whole run with exit code 1, and that includes --dry-run.

cdkd deploy MyStack                 # synthesizes, checks, then deploys
cdkd deploy -a cdk.out MyStack      # checks an assembly that already exists
cdkd deploy -a cdk.out 'MyStage/*'  # a Stage's stacks, from the app's cdk.out

This page is part of cdkd deploy: safety & compatibility flags.

Cyclic nested templates are refused

cdkd refuses an assembly in which a nested stack's template includes itself, directly or through other nested stacks:

SynthesisError: The nested template tree under stack Parent contains a
cycle: Child (/path/to/cdk.out/child.json) then Loop
(/path/to/cdk.out/child.json). Nested stack Loop (declared in stack
Parent~Child) resolves to a template that is already on that nesting chain,
so its Metadata['aws:asset:path'] closes a cycle. CDK emits an acyclic nested
template tree with relative asset paths, so this indicates the synth output was
hand-modified or generated by a non-CDK toolchain. Refusing to start the
deploy; nothing has been published or provisioned.

A parent template points at each nested stack's template through the nested stack resource's Metadata['aws:asset:path']. cdkd follows those paths from every stack it is about to deploy, dependencies included. It refuses when a path leads to a template that is already on the same chain from parent to child.

Edge cases

  • Two nested stacks that name the same template are a shared child, and that is allowed. Only a repeat along one chain from the root to a child is refused.
  • A Condition on the nested stack is not evaluated. A template that includes itself behind a condition is refused too, as it is by cdkd diff --recursive.
  • An absolute aws:asset:path is refused, and the message says which is absolute. The CDK always writes nested templates into the output directory.
  • A very large tree is refused: more than 512 levels deep, or more than 10,000 nested stacks.
  • A stack you are not deploying is not checked.

Paths that leave the assembly directory

cdkd refuses a path in the assembly that points outside the assembly. For a nested template, that is an aws:asset:path that resolves outside the directory its template sits in:

SynthesisError: The nested template tree under stack Parent has nested stack
Child (reached through Child (/path/to/cdk.out/child.json)) with
Metadata['aws:asset:path']=../../etc/passwd which resolves to
/etc/passwd, outside /path/to/cdk.out. CDK emits assembly paths that stay
inside the assembly directory; one that leaves it indicates the synth output
was hand-modified or generated by a non-CDK toolchain. Refusing to start the
deploy; nothing has been published or provisioned.

A path that leaves the directory only through a symbolic link is refused the same way, and the message names the link's target.

cdkd holds every other path the assembly supplies to the same rule, because it would otherwise act on the file:

Path in the assembly What cdkd does with the file
A nested assembly's directoryName Reads that directory's manifest.json
A stack's templateFile Deploys whatever parses as its template
An asset manifest's file Reads the list of assets to publish
A file asset's source.path Zips it and uploads it to the bucket the manifest names
A Docker asset's source.directory Sends it to the image build as the context
A stack's metadata side file Reads its annotations
A stack name, under cdkd synth --verbose Writes <name>.template.json there

Asset source paths

The two asset rows, source.path and source.directory, are measured against the app's output directory (cdk.out). That is because a Stage's asset manifest legitimately points up to ../asset.<hash>. cdkd treats a relative path and an absolute path differently:

The asset source path is What cdkd does
Absolute, inside the output directory Accepts it; every staged asset is there
Absolute, outside the output directory Accepts it with a warning
The output directory itself, by any spelling Accepts it with a warning
Relative, escaping the output directory Refuses

The warning for an outside path names the directory and the destination. When the path names the output directory itself, the whole assembly becomes the asset. No cdk synth emits that.

Warning

An absolute asset path outside the output directory is uploaded. cdkd accepts it because cdk synth --no-staging produces such paths, and the warning is the only protection. An assembly you did not synthesize can name any directory your user can read and upload it to a bucket the same manifest names, with your credentials.

Pointing -a at a Stage's directory

Pointing -a at a Stage's sub-assembly (cdkd deploy -a cdk.out/assembly-MyStage) makes cdkd refuse that Stage's own ../asset.<hash>, because the asset is outside the directory you named. Point -a at the app's cdk.out and select the stack by its display path instead:

cdkd deploy -a cdk.out 'MyStage/*'

The same rule in cdkd local

The cdkd local commands that mount Lambda code (invoke, start-api, start-alb, start-cloudfront) apply the same rule to aws:asset:path. They refuse a relative path that escapes and accept an absolute one with the warning. Local Execution lists which other paths are covered.

A pre-synthesized assembly is trusted input

Outside the path rules above, cdkd uses an asset manifest as it is written, as the CDK CLI does. So an assembly can make cdkd run a command, read a file or write a file on your machine.

Important

Deploying a pre-synthesized assembly can run code from it. A Docker asset may declare source.executable instead of a Dockerfile. Pointing -a at an assembly you did not produce is the same decision as running someone else's build output. Synthesize it yourself, or read it first.

cdkd warns about four kinds of manifest value that reach outside the assembly.

A command cdkd runs: source.executable

cdkd runs the value on your machine as a command line. It warns on every command that builds a Docker asset and names the command. Every cdkd local command that builds such an asset runs the executable too, and prints the warning first. cdkd announces each distinct command once per run, and repeats go to --verbose.

Host paths the image build reads

These are dockerFile, dockerBuildContexts, dockerBuildSecrets, dockerBuildSsh, cacheFrom and cacheTo. cdkd warns when the path is outside the output directory. A path inside the build context usually gets no warning.

A build secret, an SSH key or a cache directory is a host path the template never shows. Reading the template therefore does not tell you what a deploy touches.

Host paths the image build writes

A dest= in dockerOutputs or in a cache option makes the build write to that host path. cdkd warns when the path is outside the output directory, wherever the build context is.

Whether a cache option reads or writes follows its key. A cacheFrom that carries a dest= is a write, and a cacheTo that carries a src= is a read.

Where assets are uploaded

cdkd uploads to dest.bucketName and to the ECR repository the manifest names, with your credentials. It warns once per name when the name is neither shaped like a CDK bootstrap resource nor managed by cdkd.

The check looks at the name only. A bucket named like a CDK bootstrap bucket for your account can still belong to someone else.

A Stage that failed to load stops every command

When cdkd cannot read a Stage's own manifest.json, it stops, as the AWS CDK CLI does. That happens when the Stage was never synthesized or its directory is gone.

Stage MyStage failed to load: ENOENT reading assembly-MyStage/manifest.json.
Every stack under it is missing from the cloud assembly, so cdkd will not act
on the app. Re-synthesize the app so the Stage is written, or point --app at a
complete cloud assembly.

cdkd stops whatever you selected, including a stack outside the Stage, and cdkd destroy stops too. To reach a deployed stack by name without the app, use cdkd state destroy '<stack>'. cdkd scrub exits 2 (SCRUB_STAGE_LOAD_FAILED) for a Stage that failed to load.

Any other refusal under a Stage aborts the run with the innermost Stage named ahead of it (Stage MyStage: Stack MyStage-Api ...).

Last updated: