aws / aws/aws-cdk

(aws-pipes-alpha): Incorrect usage of interfaces causes deadLetterTarget prop to be ignored

Open
#34,812 1 comment 1 reaction 0 assignees View on GitHub
@aws-cdk/aws-pipes bug effort/medium p2
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.