Description
Describe the bug
Hi, we use a library with its own set of constructs that copy regular constructs.
Lets say we have our own CustomSQS that implements IQueue interface.
Then I create a DynamoDBSource
from @aws-cdk/aws-pipes-sources-alpha
new DynamoDBSource(myStream, {
startingPosition: 'TRIM-HORIZON',
deadLetterTarget: myCustomQueue,
})
This builds since myCustomQueue implements IQueue.
But in the end the deadLetterTarget is ignored and does not show in template.
Reason are these line of code here
If your class receives a prop of type "IQueue", then it is incorrect to test if (object instanceof Queue)
What you can do is something like:
protected getDeadLetterTargetArn(deadLetterTarget?: IQueue | ITopic): string | undefined {
if (isIQueue(deadLetterTarget)) {
return deadLetterTarget.queueArn;
} else if (isITopic(deadLetterTarget)) {
return deadLetterTarget.topicArn;
}
return undefined;
}
function isIQueue(x: any): x is IQueue {
return x && typeof x.queueArn === 'string';
}
function isITopic(x: any): x is ITopic {
return x && typeof x.topicArn === 'string';
}
Regression Issue
- Select this option if this issue appears to be a regression.
Last Known Working CDK Library Version
No response
Expected Behavior
If I pass an object with a class that implements IQueue, I should get a deadLetterTarget in template
Current Behavior
If I pass an object with a class that implements IQueue but is not a Queue, deadLetterTarget is null
Reproduction Steps
- Create a custom queue class
import { Construct } from 'constructs';
import { IQueue, QueueEncryption } from 'aws-cdk-lib/aws-sqs';
import { IResource, Resource } from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as kms from 'aws-cdk-lib/aws-kms';
import { Grant, IGrantable } from 'aws-cdk-lib/aws-iam';
interface CustomQueueProps {
readonly queueArn: string;
readonly queueUrl: string;
readonly queueName: string;
readonly fifo?: boolean;
readonly encryptionType?: QueueEncryption;
readonly encryptionMasterKey?: kms.IKey;
}
export class CustomQueue extends Resource implements IQueue {
public readonly queueArn: string;
public readonly queueUrl: string;
public readonly queueName: string;
public readonly fifo: boolean;
public readonly encryptionType?: QueueEncryption;
public readonly encryptionMasterKey?: kms.IKey;
constructor(scope: Construct, id: string, props: CustomQueueProps) {
super(scope, id);
this.queueArn = props.queueArn;
this.queueUrl = props.queueUrl;
this.queueName = props.queueName;
this.fifo = props.fifo ?? false;
this.encryptionType = props.encryptionType;
this.encryptionMasterKey = props.encryptionMasterKey;
}
addToResourcePolicy(statement: iam.PolicyStatement): iam.AddToResourcePolicyResult {
// For mock/custom queues, you can either return a dummy result or implement actual logic
return {
statementAdded: false,
policyDependable: this,
};
}
grantConsumeMessages(grantee: IGrantable): Grant {
return this.grant(grantee,
'sqs:ChangeMessageVisibility',
'sqs:DeleteMessage',
'sqs:ReceiveMessage',
'sqs:GetQueueAttributes',
'sqs:GetQueueUrl',
);
}
grantSendMessages(grantee: IGrantable): Grant {
return this.grant(grantee,
'sqs:SendMessage',
'sqs:GetQueueAttributes',
'sqs:GetQueueUrl',
);
}
grantPurge(grantee: IGrantable): Grant {
return this.grant(grantee,
'sqs:PurgeQueue',
'sqs:GetQueueAttributes',
'sqs:GetQueueUrl',
);
}
grant(grantee: IGrantable, ...queueActions: string[]): Grant {
return Grant.addToPrincipal({
grantee,
actions: queueActions,
resourceArns: [this.queueArn],
});
}
}
- Then do something like:
const ddbTable = new TableV2(this, "DDBTable", {
tableName: "ddb-table",
partitionKey: {
name: "Location",
type: AttributeType.STRING,
},
dynamoStream: StreamViewType.NEW_AND_OLD_IMAGES,
removalPolicy: RemovalPolicy.DESTROY,
});
const queue = new CustomQueue(this, 'MyCustomQueue', {
queueArn: 'arn:aws:sqs:us-east-1:123456789012:MyQueue',
queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue',
queueName: 'MyQueue',
fifo: false,
});
const pipeSource = new DynamoDBSource(ddbTable, {
startingPosition: DynamoDBStartingPosition.LATEST,
batchSize: 1,
maximumRetryAttempts: 0,
deadLetterTarget: queue,
});
new Pipe(this, "Pipe", {
source: pipeSource,
target: anotherSqs,
});
Check produced template after build:
DynamoDBStreamParameters won't have a dead letter target.
Possible Solution
protected getDeadLetterTargetArn(deadLetterTarget?: IQueue | ITopic): string | undefined {
if (isIQueue(deadLetterTarget)) {
return deadLetterTarget.queueArn;
} else if (isITopic(deadLetterTarget)) {
return deadLetterTarget.topicArn;
}
return undefined;
}
function isIQueue(x: any): x is IQueue {
return x && typeof x.queueArn === 'string';
}
function isITopic(x: any): x is ITopic {
return x && typeof x.topicArn === 'string';
}
Additional Information/Context
No response
AWS CDK Library version (aws-cdk-lib)
2.191.0-alpha.0
AWS CDK CLI version
2.174.0
Node.js Version
v16.20.2
OS
macOS Sequoia 15.4
Language
TypeScript
Language Version
No response
Other information
No response