(aws-pipes-alpha): Incorrect usage of interfaces causes deadLetterTarget prop to be ignored
- Dominant language
- TypeScript
- Stars
- 12.9k
- Forks
- 4.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 83
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](https://github.com/aws/aws-cdk/blob/main/packages/%40aws-cdk/aws-pipes-alpha/lib/source.ts#L148C3-L155C4)
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_
Contributor guide
Research direction
Start in packages/@aws-cdk/aws-pipes-alpha/lib/source.ts, especially the lines around 148-155 that resolve the dead-letter target. Reproduce the issue with the provided CustomQueue and DynamoDBSource example, then inspect the synthesized template; done means a custom IQueue produces a dead letter target in DynamoDBStreamParameters.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, typescript
- Domain
- cloud, infrastructure
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100