Skip to content

Commit 1557793

Browse files
authored
feat(pipes-enrichments): support API destination enrichment (#31312)
### Issue # (if applicable) Closes #29383 . ### Reason for this change To support API destination enrichment for EventBridge pipes. ### Description of changes Add `ApiDestinationEnrichment` class. ### Description of how you validated changes Add unit tests and an integ test. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 7815144 commit 1557793

16 files changed

+34265
-0
lines changed

packages/@aws-cdk/aws-pipes-enrichments-alpha/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,22 @@ const pipe = new pipes.Pipe(this, 'Pipe', {
6969
target: new SomeTarget(targetQueue),
7070
});
7171
```
72+
73+
### API destination
74+
75+
API destination can be used to enrich events of a pipe.
76+
77+
```ts
78+
declare const sourceQueue: sqs.Queue;
79+
declare const targetQueue: sqs.Queue;
80+
81+
declare const apiDestination: events.ApiDestination;
82+
83+
const enrichment = new enrichments.ApiDestinationEnrichment(apiDestination);
84+
85+
const pipe = new pipes.Pipe(this, 'Pipe', {
86+
source: new SomeSource(sourceQueue),
87+
enrichment,
88+
target: new SomeTarget(targetQueue),
89+
});
90+
```
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { EnrichmentParametersConfig, IEnrichment, IPipe, InputTransformation } from '@aws-cdk/aws-pipes-alpha';
2+
import { IApiDestination } from 'aws-cdk-lib/aws-events';
3+
import { IRole, PolicyStatement } from 'aws-cdk-lib/aws-iam';
4+
import { CfnPipe } from 'aws-cdk-lib/aws-pipes';
5+
6+
/**
7+
* Properties for a ApiDestinationEnrichment
8+
*/
9+
export interface ApiDestinationEnrichmentProps {
10+
/**
11+
* The input transformation for the enrichment
12+
* @see https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-pipes-input-transformation.html
13+
* @default - None
14+
*/
15+
readonly inputTransformation?: InputTransformation;
16+
17+
/**
18+
* The headers that need to be sent as part of request invoking the EventBridge ApiDestination.
19+
*
20+
* @default - none
21+
*/
22+
readonly headerParameters?: Record<string, string>;
23+
24+
/**
25+
* The path parameter values used to populate the EventBridge API destination path wildcards ("*").
26+
*
27+
* @default - none
28+
*/
29+
readonly pathParameterValues?: string[];
30+
31+
/**
32+
* The query string keys/values that need to be sent as part of request invoking the EventBridge API destination.
33+
*
34+
* @default - none
35+
*/
36+
readonly queryStringParameters?: Record<string, string>;
37+
}
38+
39+
/**
40+
* An API Destination enrichment for a pipe
41+
*/
42+
export class ApiDestinationEnrichment implements IEnrichment {
43+
public readonly enrichmentArn: string;
44+
45+
private readonly inputTransformation?: InputTransformation;
46+
private readonly headerParameters?: Record<string, string>;
47+
private readonly pathParameterValues?: string[];
48+
private readonly queryStringParameters?: Record<string, string>;
49+
50+
constructor(private readonly destination: IApiDestination, props?: ApiDestinationEnrichmentProps) {
51+
this.enrichmentArn = destination.apiDestinationArn;
52+
this.inputTransformation = props?.inputTransformation;
53+
this.headerParameters = props?.headerParameters;
54+
this.queryStringParameters = props?.queryStringParameters;
55+
this.pathParameterValues = props?.pathParameterValues;
56+
}
57+
58+
bind(pipe: IPipe): EnrichmentParametersConfig {
59+
60+
const httpParameters: CfnPipe.PipeEnrichmentHttpParametersProperty | undefined =
61+
this.headerParameters ??
62+
this.pathParameterValues ??
63+
this.queryStringParameters
64+
? {
65+
headerParameters: this.headerParameters,
66+
pathParameterValues: this.pathParameterValues,
67+
queryStringParameters: this.queryStringParameters,
68+
}
69+
: undefined;
70+
71+
return {
72+
enrichmentParameters: {
73+
inputTemplate: this.inputTransformation?.bind(pipe).inputTemplate,
74+
httpParameters,
75+
},
76+
};
77+
}
78+
79+
grantInvoke(pipeRole: IRole): void {
80+
pipeRole.addToPrincipalPolicy(new PolicyStatement({
81+
resources: [this.destination.apiDestinationArn],
82+
actions: ['events:InvokeApiDestination'],
83+
}));
84+
}
85+
}
86+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
export * from './api-destination';
12
export * from './lambda';
23
export * from './stepfunctions';

packages/@aws-cdk/aws-pipes-enrichments-alpha/rosetta/default.ts-fixture

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Fixture with packages imported, but nothing else
22
import * as cdk from 'aws-cdk-lib';
3+
import * as events from 'aws-cdk-lib/aws-events';
34
import * as sqs from 'aws-cdk-lib/aws-sqs';
45
import * as lambda from 'aws-cdk-lib/aws-lambda';
56
import * as stepfunctions from 'aws-cdk-lib/aws-stepfunctions';
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`api-destination should grant pipe role invoke access 1`] = `
4+
{
5+
"MyPipeRoleCBC8E9AB": {
6+
"Properties": {
7+
"AssumeRolePolicyDocument": {
8+
"Statement": [
9+
{
10+
"Action": "sts:AssumeRole",
11+
"Effect": "Allow",
12+
"Principal": {
13+
"Service": "pipes.amazonaws.com",
14+
},
15+
},
16+
],
17+
"Version": "2012-10-17",
18+
},
19+
},
20+
"Type": "AWS::IAM::Role",
21+
},
22+
}
23+
`;
24+
25+
exports[`api-destination should grant pipe role invoke access 2`] = `
26+
{
27+
"MyPipeRoleDefaultPolicy31387C20": {
28+
"Properties": {
29+
"PolicyDocument": {
30+
"Statement": [
31+
{
32+
"Action": "events:InvokeApiDestination",
33+
"Effect": "Allow",
34+
"Resource": {
35+
"Fn::GetAtt": [
36+
"ApiDestination3AB57A39",
37+
"Arn",
38+
],
39+
},
40+
},
41+
],
42+
"Version": "2012-10-17",
43+
},
44+
"PolicyName": "MyPipeRoleDefaultPolicy31387C20",
45+
"Roles": [
46+
{
47+
"Ref": "MyPipeRoleCBC8E9AB",
48+
},
49+
],
50+
},
51+
"Type": "AWS::IAM::Policy",
52+
},
53+
}
54+
`;
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { DynamicInput, InputTransformation, Pipe } from '@aws-cdk/aws-pipes-alpha';
2+
import { App, Stack, SecretValue } from 'aws-cdk-lib';
3+
import { Template } from 'aws-cdk-lib/assertions';
4+
import * as events from 'aws-cdk-lib/aws-events';
5+
import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
6+
import { TestSource, TestTarget } from './test-classes';
7+
import { ApiDestinationEnrichment } from '../lib';
8+
9+
describe('api-destination', () => {
10+
let app: App;
11+
let stack: Stack;
12+
let secret: Secret;
13+
let connection: events.Connection;
14+
let apiDestination: events.ApiDestination;
15+
16+
beforeEach(() => {
17+
app = new App();
18+
stack = new Stack(app, 'TestStack');
19+
secret = new Secret(stack, 'MySecret', {
20+
secretStringValue: SecretValue.unsafePlainText('abc123'),
21+
});
22+
connection = new events.Connection(stack, 'MyConnection', {
23+
authorization: events.Authorization.apiKey('x-api-key', secret.secretValue),
24+
description: 'Connection with API Key x-api-key',
25+
connectionName: 'MyConnection',
26+
});
27+
28+
apiDestination = new events.ApiDestination(stack, 'ApiDestination', {
29+
apiDestinationName: 'ApiDestination',
30+
connection,
31+
description: 'ApiDestination',
32+
httpMethod: events.HttpMethod.GET,
33+
endpoint: 'someendpoint',
34+
rateLimitPerSecond: 60,
35+
});
36+
});
37+
38+
it('should have only enrichment arn', () => {
39+
// ARRANGE
40+
const enrichment = new ApiDestinationEnrichment(apiDestination);
41+
42+
new Pipe(stack, 'MyPipe', {
43+
source: new TestSource(),
44+
enrichment,
45+
target: new TestTarget(),
46+
});
47+
48+
// ACT
49+
const template = Template.fromStack(stack);
50+
51+
// ASSERT
52+
template.hasResourceProperties('AWS::Pipes::Pipe', {
53+
Enrichment: {
54+
'Fn::GetAtt': [
55+
'ApiDestination3AB57A39',
56+
'Arn',
57+
],
58+
},
59+
EnrichmentParameters: {},
60+
});
61+
});
62+
63+
it('should have enrichment parameters', () => {
64+
// ARRANGE
65+
const enrichment = new ApiDestinationEnrichment(apiDestination, {
66+
inputTransformation: InputTransformation.fromObject({
67+
body: DynamicInput.fromEventPath('$.body'),
68+
}),
69+
headerParameters: {
70+
headerParam: 'headerParam',
71+
},
72+
pathParameterValues: ['pathParam'],
73+
queryStringParameters: {
74+
param: 'queryParam',
75+
},
76+
});
77+
78+
new Pipe(stack, 'MyPipe', {
79+
source: new TestSource(),
80+
enrichment,
81+
target: new TestTarget(),
82+
});
83+
84+
// ACT
85+
const template = Template.fromStack(stack);
86+
87+
// ASSERT
88+
template.hasResourceProperties('AWS::Pipes::Pipe', {
89+
EnrichmentParameters: {
90+
InputTemplate: '{"body":<$.body>}',
91+
HttpParameters: {
92+
HeaderParameters: {
93+
headerParam: 'headerParam',
94+
},
95+
PathParameterValues: [
96+
'pathParam',
97+
],
98+
QueryStringParameters: {
99+
param: 'queryParam',
100+
},
101+
},
102+
},
103+
});
104+
});
105+
106+
it('should grant pipe role invoke access', () => {
107+
// ARRANGE
108+
const enrichment = new ApiDestinationEnrichment(apiDestination);
109+
110+
new Pipe(stack, 'MyPipe', {
111+
source: new TestSource(),
112+
enrichment,
113+
target: new TestTarget(),
114+
});
115+
116+
// ACT
117+
const template = Template.fromStack(stack);
118+
119+
// ASSERT
120+
expect(template.findResources('AWS::IAM::Role')).toMatchSnapshot();
121+
expect(template.findResources('AWS::IAM::Policy')).toMatchSnapshot();
122+
});
123+
});
124+

0 commit comments

Comments
 (0)