Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(stepfunctions-tasks): add httpinvoke step functions task #28673

Merged
merged 26 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
bc37725
Add http:invoke step functions task
MathieuGilbert Jan 11, 2024
425c28f
Change connectionArn prop to IConnection, so policies can be set.
MathieuGilbert Jan 12, 2024
a0d5900
Add an aws-stepfunctions-tasks README entry for HttpInvoke .
MathieuGilbert Jan 13, 2024
8821eaf
Merge branch 'main' into add-awsstepfunctions-httpinvoke
MathieuGilbert Jan 31, 2024
75d049f
Add integration test.
MathieuGilbert Jan 31, 2024
15f1b5a
Fix tests
MathieuGilbert Feb 1, 2024
83a8e1c
drop trailing whitespace
MathieuGilbert Feb 1, 2024
1c5a3c2
Add functional integration test using default params, and its snapsho…
MathieuGilbert Feb 2, 2024
40902c6
Import order in test, tidy readme entry.
MathieuGilbert Feb 3, 2024
5f3c9cf
Add events from aws-events import for aws-stepfunctions-tasks Rosetta…
MathieuGilbert Feb 10, 2024
215420b
aws-events is already imported in individual examples, so adding it t…
MathieuGilbert Feb 10, 2024
8d8873c
Merge remote-tracking branch 'upstream/main' into add-awsstepfunction…
MathieuGilbert Feb 10, 2024
fb6e25b
Update task props, make use of TaskInput.
MathieuGilbert Feb 22, 2024
f87d5f2
Merge remote-tracking branch 'upstream/main' into add-awsstepfunction…
MathieuGilbert Feb 22, 2024
e872adc
Fix (?) prop examples for TaskInputs.
MathieuGilbert Feb 22, 2024
ffdb124
Merge remote-tracking branch 'upstream/main' into add-awsstepfunction…
MathieuGilbert Feb 22, 2024
fe5e725
Merge branch 'main' into add-awsstepfunctions-httpinvoke
MathieuGilbert Feb 23, 2024
35e906f
Merge branch 'main' into add-awsstepfunctions-httpinvoke
MathieuGilbert Feb 23, 2024
d09027a
Merge branch 'main' into add-awsstepfunctions-httpinvoke
MathieuGilbert Feb 23, 2024
a02719f
Merge branch 'main' into add-awsstepfunctions-httpinvoke
MathieuGilbert Feb 29, 2024
05962fa
Fix casing in README section for Invoke HTTP API.
MathieuGilbert Apr 2, 2024
3155043
Apply consistent comment formatting.
MathieuGilbert Apr 2, 2024
bf056d9
Refactor urlEncodeBody and arrayEncodingFormat into urlEncodingFormat…
MathieuGilbert Apr 2, 2024
45441fa
Merge remote-tracking branch 'upstream/main' into add-awsstepfunction…
MathieuGilbert Apr 2, 2024
84079df
Merge branch 'main' into add-awsstepfunctions-httpinvoke
MathieuGilbert Apr 4, 2024
f784b79
Merge branch 'main' into add-awsstepfunctions-httpinvoke
TheRealAmazonKendra Apr 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions packages/aws-cdk-lib/aws-stepfunctions-tasks/lib/http/invoke.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { Construct } from 'constructs';
import * as iam from '../../../aws-iam';
import * as sfn from '../../../aws-stepfunctions';
import { integrationResourceArn } from '../private/task-utils';

export enum URLEncodingArrayFormat {
/**
* Encode arrays using brackets. For example, {"array": ["a","b","c"]} encodes to "array[]=a&array[]=b&array[]=c"
*/
BRACKETS = 'BRACKETS',
/**
* Encode arrays using commas. For example, {"array": ["a","b","c"]} encodes to "array=a,b,c,d"
*/
COMMAS = 'COMMAS',
/**
* Encode arrays using the index value. For example, {"array": ["a","b","c"]} encodes to "array[0]=a&array[1]=b&array[2]=c"
*/
INDICES = 'INDICES',
/**
* Repeat key for each item in the array. For example, {"array": ["a","b","c"]} encodes to "array[]=a&array[]=b&array[]=c"
*/
REPEAT = 'REPEAT',
}

/**
* Properties for calling an external HTTP endpoint with HttpInvoke.
*/
export interface HttpInvokeProps extends sfn.TaskStateBaseProps {
/**
* The API apiEndpoint to call.
*/
readonly apiEndpoint: string;

/**
* The HTTP method to use.
*
*/
readonly method: string;

/**
* The EventBridge Connection ARN to use for authentication.
*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: extra newline

*/
readonly connectionArn: string;

/**
* The body to send to the HTTP endpoint.
*
* @default - No body.
*/
readonly body?: string;

/**
* The headers to send to the HTTP endpoint.
*
* @default - No headers.
*/
readonly headers?: { [key: string]: string };

/**
* The query string parameters to send to the HTTP endpoint.
*
* @default - No query string parameters.
*/
readonly queryStringParameters?: { [key: string]: string };

/**
* Whether to URL-encode the request body.
* If set to true, also sets 'content-type' header to 'application/x-www-form-urlencoded'
*
* @default - No encoding.
*/
readonly urlEncodeBody?: boolean;

/**
* The format of the array encoding if urlEncodeBody is set to true.
*
* @default - ArrayEncodingFormat.INDICES
*/
readonly arrayEncodingFormat?: URLEncodingArrayFormat;
}

export class HttpInvoke extends sfn.TaskStateBase {
protected readonly taskMetrics?: sfn.TaskMetricsConfig;
protected readonly taskPolicies?: iam.PolicyStatement[];

constructor(
scope: Construct,
id: string,
private readonly props: HttpInvokeProps,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can these be on the same line?

) {
super(scope, id, props);

this.taskPolicies = [];
}

/**
* Provides the HTTP Invoke service integration task configuration.
*/
/**
* @internal
*/
protected _renderTask(): any {
return {
Resource: integrationResourceArn('http', 'invoke'),
Parameters: sfn.FieldUtils.renderObject({
Method: this.props.method,
ApiEndpoint: this.props.apiEndpoint,
Authentication: {
ConnectionArn: this.props.connectionArn,
},
RequestBody: this.props.body,
Headers: this.props.headers,
QueryParameters: this.props.queryStringParameters,
Transform:
this.props.urlEncodeBody != null
? {
RequestBodyEncoding: 'URL_ENCODED',
RequestEncodingOptions: {
ArrayFormat:
this.props.arrayEncodingFormat ??
URLEncodingArrayFormat.INDICES,
},
}
: undefined,
}),
};
}
}
1 change: 1 addition & 0 deletions packages/aws-cdk-lib/aws-stepfunctions-tasks/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ export * from './apigateway';
export * from './eventbridge/put-events';
export * from './aws-sdk/call-aws-service';
export * from './bedrock/invoke-model';
export * from './http/invoke';
157 changes: 157 additions & 0 deletions packages/aws-cdk-lib/aws-stepfunctions-tasks/test/http/invoke.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { Stack } from '../../../core';
import * as lib from '../../lib';

let stack: Stack;
const connectionArn =
'arn:aws:events:us-test-1:123456789012:connection/connectionName';

const expectTaskWithParameters = (task: lib.HttpInvoke, parameters: any) => {
expect(stack.resolve(task.toStateJson())).toEqual({
Type: 'Task',
Resource: {
'Fn::Join': [
'',
[
'arn:',
{
Ref: 'AWS::Partition',
},
':states:::http:invoke',
],
],
},
End: true,
Parameters: parameters,
});
};

describe('AWS::StepFunctions::Tasks::HttpInvoke', () => {
beforeEach(() => {
stack = new Stack();
});

test('invoke with default props', () => {
const task = new lib.HttpInvoke(stack, 'Task', {
apiEndpoint: 'https://api.example.com',
connectionArn,
method: 'POST',
});

expectTaskWithParameters(task, {
ApiEndpoint: 'https://api.example.com',
Authentication: {
ConnectionArn: connectionArn,
},
Method: 'POST',
});
});

test('invoke with request body', () => {
const task = new lib.HttpInvoke(stack, 'Task', {
apiEndpoint: 'https://api.example.com',
body: JSON.stringify({ foo: 'bar' }),
connectionArn,
method: 'POST',
});

expectTaskWithParameters(task, {
ApiEndpoint: 'https://api.example.com',
Authentication: {
ConnectionArn: connectionArn,
},
Method: 'POST',
RequestBody: JSON.stringify({ foo: 'bar' }),
});
});

test('invoke with headers', () => {
const task = new lib.HttpInvoke(stack, 'Task', {
apiEndpoint: 'https://api.example.com',
connectionArn,
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
});

expectTaskWithParameters(task, {
ApiEndpoint: 'https://api.example.com',
Authentication: {
ConnectionArn: connectionArn,
},
Method: 'POST',
Headers: {
'Content-Type': 'application/json',
},
});
});

test('invoke with query string parameters', () => {
const task = new lib.HttpInvoke(stack, 'Task', {
apiEndpoint: 'https://api.example.com',
connectionArn,
method: 'POST',
queryStringParameters: {
foo: 'bar',
},
});

expectTaskWithParameters(task, {
ApiEndpoint: 'https://api.example.com',
Authentication: {
ConnectionArn: connectionArn,
},
Method: 'POST',
QueryParameters: {
foo: 'bar',
},
});
});

test('invoke with request body encoding and default arrayEncodingFormat', () => {
const task = new lib.HttpInvoke(stack, 'Task', {
apiEndpoint: 'https://api.example.com',
method: 'POST',
connectionArn,
urlEncodeBody: true,
});

expectTaskWithParameters(task, {
ApiEndpoint: 'https://api.example.com',
Authentication: {
ConnectionArn: connectionArn,
},
Method: 'POST',
Transform: {
RequestBodyEncoding: 'URL_ENCODED',
RequestEncodingOptions: {
ArrayFormat: lib.URLEncodingArrayFormat.INDICES,
},
},
});
});

test('invoke with request body encoding and arrayEncodingFormat', () => {
const task = new lib.HttpInvoke(stack, 'Task', {
apiEndpoint: 'https://api.example.com',
arrayEncodingFormat: lib.URLEncodingArrayFormat.BRACKETS,
connectionArn,
method: 'POST',
urlEncodeBody: true,
});

expectTaskWithParameters(task, {
ApiEndpoint: 'https://api.example.com',
Authentication: {
ConnectionArn: connectionArn,
},
Method: 'POST',
Transform: {
RequestBodyEncoding: 'URL_ENCODED',
RequestEncodingOptions: {
ArrayFormat: lib.URLEncodingArrayFormat.BRACKETS,
},
},
});
});
});
Loading