Skip to content
cdkd

cdkd export: what blocks an export

cdkd export refuses a stack when CloudFormation could not adopt one of its resources. It checks before it locks the stack or submits anything, and one message names every offending resource. A refused export has changed nothing.

To see the blocks for a stack without exporting it, run:

cdkd export MyStack --dry-run

The common blocks are on the cdkd export page. This page covers the rest, grouped by cause.

Resource types CloudFormation cannot import

CloudFormation cannot adopt every resource type. It rejects an IMPORT changeset that contains one of the types below with ResourceTypes [<T>] are not supported for Import. cdkd refuses the stack first, so that you see all such resources at once.

Resource type
AWS::Glue::Table
AWS::Route53::RecordSet
AWS::Route53::RecordSetGroup
AWS::AppSync::GraphQLSchema
AWS::AppSync::GraphQLApi
AWS::EC2::NetworkAclEntry
AWS::SQS::QueuePolicy
AWS::SNS::TopicPolicy

For each such resource, do one of these before you export:

  • Remove the resource from the stack. It stays in AWS, and you can declare it in CloudFormation afterwards.
  • Destroy it, and let CloudFormation create it fresh after the export.

Custom Resources and AWS::IAM::Policy cannot be imported either, but cdkd migrates them in a second phase. See cdkd export: Custom Resources and IAM policies.

The list is not what cdkd checks

cdkd does not check against the table above. It asks the CloudFormation registry about each type, and refuses a type when the registry schema declares no read handler and reports ProvisioningType: NON_PROVISIONABLE. The check therefore also covers types that are not listed here, and it stops applying to a type once AWS makes that type importable.

AWS::AppSync::GraphQLApi is the one exception. Its registry schema looks importable, but CloudFormation refuses it, so cdkd blocks it by name.

--skip-import-support-preflight

Neither check is AWS's published list of importable types. If AWS has made a type importable and cdkd still refuses it, pass --skip-import-support-preflight. cdkd then skips both checks and lets CloudFormation answer.

Resources cdkd cannot plan

These blocks mean cdkd lacks something it needs to tell CloudFormation which AWS resource to adopt.

A template resource with no state entry

The template declares a resource that cdkd's state record does not hold, so cdkd does not know its physical id. Import the resource first with cdkd import, or remove it from the stack.

A nested stack whose state record is missing

Without the nested stack's record, its resources cannot be imported. Repair the nested stack's state, or import it again.

A multi-field identifier cdkd has no mapping for

Some types have an identifier with more than one field, and cdkd needs a mapping for each such type to build it. The export refuses a type it has no mapping for. Remove the resource before exporting. Composite identifiers lists the types cdkd can map.

Two resources with the same import identifier

An import adopts one AWS resource into one logical id. Two template resources that resolve to the same AWS resource cannot both be imported. This happens with two AWS::EC2::SecurityGroupIngress resources that resolve to one rule: they share one sgr-… id.

Keep one of them in the app and export. Then add the others back with a CloudFormation stack update.

An identifier property declared as a list

cdkd sometimes has to overwrite an identifier property in the template with the recorded value. It cannot do that when the template declares the property as a list that contains an intrinsic function or a nested list, or as an empty list. The message names the scalar value to declare instead.

Identifiers read from recorded attributes

For five types, CloudFormation's identifier is a single value that is not cdkd's physical id. cdkd reads that value from the attributes it recorded for the resource at deploy time.

Resource type CloudFormation identifier cdkd physical id
AWS::S3Tables::Table TableARN <tableBucketARN>|<namespace>|<name>
AWS::AppSync::DataSource DataSourceArn <apiId>|<name>
AWS::AppSync::Resolver ResolverArn <apiId>|<typeName>|<fieldName>
AWS::AppSync::GraphQLApi Arn <apiId>
AWS::EC2::SecurityGroupIngress Id (the sgr-… rule id) <groupId>|<ipProtocol>|<fromPort>|<toPort>

AWS::AppSync::GraphQLApi reaches this table only when you pass --skip-import-support-preflight, because it is blocked by default. State Management describes the |-joined physical ids.

When the record lacks the attribute

The export blocks the resource and the message says that the attribute is missing.

For the four types identified by an ARN, deploy the stack once and export again. An older record did not store the ARN, and a deploy adds it.

A deploy does not add a missing rule id for AWS::EC2::SecurityGroupIngress. The next section covers that type.

AWS::EC2::SecurityGroupIngress without a recorded rule id

When the record has no rule id, the export looks the rule up in AWS. It calls DescribeSecurityGroupRules, which needs the ec2:DescribeSecurityGroupRules permission, and searches the security group for ingress rules with the same protocol and port range.

Rules that match Outcome
Exactly one Adopted.
None Refused, naming the resource and what cdkd searched for.
More than one Refused, naming the resource and every candidate sgr-… id.

More than one match has two causes:

  • One ingress resource declares both CidrIp and CidrIpv6. AWS creates one rule per source. Split the resource into one resource per source.
  • Two ingress resources differ only by source. Repair the resource's recorded Id attribute, or remove the resource before exporting.

A record that already has the rule id triggers no lookup. Every record a current cdkd writes has it.

When a recorded value is ***

cdkd stores *** in state where the real value is a secret. This mask keeps the secret out of the state bucket.

Most masks do not block an export. A mask blocks only where the export has to read the real value, because cdkd cannot recover it from ***. Find the place where your mask sits below.

A property filled by a NoEcho template parameter

Nothing to do. The exported template still reads the parameter, and CloudFormation receives the parameter's value.

An identifier or an IAM policy's principals filled by a NoEcho parameter

This covers a resource's import identifier, and an AWS::IAM::Policy's principals or name. It also covers a longer identifier value that has *** embedded in it.

Export the stack without that resource. Then adopt the resource into CloudFormation by hand, and pass the value yourself.

A nested stack's Parameter

The export refuses with EXPORT_MASKED_CHILD_PARAMETER when a Parameter that a parent passes to a nested stack resolves to *** or embeds it.

Export without that nested stack, or adopt it by hand and pass the value yourself.

A recorded attribute that cdkd uses as the identifier

This applies to the types under Identifiers read from recorded attributes. Import the resource again so that its record holds the real value:

cdkd import MyStack --resource '<logicalId>=<physicalId>' --force

The import needs the cloudformation:DescribeType permission to record the attribute unmasked. Alternatively, export without the resource.

Masked attributes are otherwise harmless. A record that was imported through the AWS Cloud Control API routinely carries *** in its attributes, and it exports normally. The export reads attributes only for the five types in that table.

Any other recorded property

The record does not say what wrote the mask, so identify the source yourself. There are three:

  • A Custom Resource response that sets NoEcho. Stop setting NoEcho on the response, deploy again, then export. Forcing the Custom Resource to update does not help, because cdkd masks the value again when it writes state.
  • A secret under Fn::Base64, such as a {{resolve:...}} reference in EC2 UserData. Change the resource to read the secret at run time, deploy again, then export. Do not put the plaintext in the template: cdkd would record it in state.
  • A mask copied from another record. cdkd orphan --force in older releases could copy one, and so can a cdkd import that resolves a Ref or Fn::GetAtt. Repair the record that holds the original mask with a selective cdkd import --force. Then run again the command that wrote this property.

In every case you can also export the stack without that resource and adopt it into CloudFormation by hand.

A damaged state record

The export reads two maps in the state record: the record's resources, and each resource's properties. It refuses when either one is not a JSON object, which a hand edit or a truncated write can cause. The export deletes the record at the end, so it does not guess at a record it cannot read.

Repair the record by hand, or import the named resources again with a selective cdkd import --force.

Removing the damaged record does not unblock the export. Without the root stack's record there is nothing to migrate. Without a nested stack's record, the export refuses the tree because that record is missing.

Last updated: