Skip to content

Commit

Permalink
Update sfn workflow
Browse files Browse the repository at this point in the history
Add mock invocation function to the workflow. Update the
InvokeCspApi task to retry when a custom 500 erorr is thrown and
catch for all other errors. This simplifies and allows for the task
to handle retries based on the error thrown in the lambda. It also
allows for additional tasks to be chained after the choice state.

If the choice state references a state/task that already has a next
state (e.g., InvokeCspApi task), there is an error and
'.afterwards()' cannot be used to chain additional tasks. I believe
this is a limitation seen when forming sfn with CDK, rather than
the sfn itself.

Ticket: AT-7055
  • Loading branch information
juliusfitzhugh-ccpo committed Mar 15, 2022
1 parent cf5bf91 commit 831c422
Show file tree
Hide file tree
Showing 3 changed files with 16 additions and 39 deletions.
51 changes: 14 additions & 37 deletions lib/constructs/provisioning-sfn-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,16 @@ import { mapTasks, TasksMap } from "./sfn-lambda-invoke-task";
/**
* Successful condition check
*/
export const successResponse = sfn.Condition.numberEquals("$.cspResponse.Payload.statusCodeFn", 200);
export const successResponse = sfn.Condition.numberEquals("$.cspResponse.Payload.code", 200);

/**
* Client error condition check
*/
export const clientErrorResponse = sfn.Condition.or(
sfn.Condition.numberEquals("$.cspResponse.statusCode", 400),
sfn.Condition.numberEquals("$.cspResponse.statusCode", 402),
sfn.Condition.numberEquals("$.cspResponse.statusCode", 404)
sfn.Condition.numberEquals("$.cspResponse.Payload.code", 400),
sfn.Condition.numberEquals("$.cspResponse.Payload.code", 402),
sfn.Condition.numberEquals("$.cspResponse.Payload.code", 404)
);

/**
* Internal error condition check
*/
export const internalErrorResponse = sfn.Condition.numberGreaterThanEquals("$.cspResponse.statusCode", 500);

/**
* Retry condition check
*/
export const maxRetries = sfn.Condition.and(
sfn.Condition.numberGreaterThan("$.cspResponsePass.retryCount", 6),
sfn.Condition.numberGreaterThanEquals("$.cspResponse.statusCode", 500)
);

export interface ProvisioningWorkflowProps {
environmentName: string;
}
Expand All @@ -53,17 +39,19 @@ export class ProvisioningWorkflow implements IProvisioningWorkflow {
constructor(scope: Construct, props: ProvisioningWorkflowProps) {
const { environmentName } = props;
// Provisioning State machine functions
const mockInvocationFn = new lambdaNodeJs.NodejsFunction(scope, "MockInvocationFunction", {
entry: "api/provision/mock-invocation-lambda.ts",
});
const sampleFn = new lambdaNodeJs.NodejsFunction(scope, "SampleFunction", {
entry: "api/provision/sample-fn.ts",
// vpc: props.vpc,
});

// Tasks for the Provisioning State Machine
const tasks = [
{
id: "InvokeCspApi",
props: {
lambdaFunction: sampleFn, // replace w/ mock CSP ATAT API lambda
lambdaFunction: mockInvocationFn,
inputPath: "$",
resultPath: "$.cspResponse",
outputPath: "$",
Expand All @@ -78,32 +66,21 @@ export class ProvisioningWorkflow implements IProvisioningWorkflow {
outputPath: "$",
},
},
{
id: "Demo",
props: {
lambdaFunction: sampleFn,
inputPath: "$",
resultSelector: {
statusCode: 200,
retryCount: 7,
},
resultPath: "$.cspResponseDemo",
outputPath: "$",
},
},
];
this.mappedTasks = mapTasks(scope, tasks);
const { InvokeCspApi, Results, Demo } = this.mappedTasks;
const { InvokeCspApi, Results } = this.mappedTasks;
// update retry for the tasks
InvokeCspApi.sfnTask.addRetry({ maxAttempts: 2, errors: ["MockCspApiError"] });
InvokeCspApi.sfnTask.addCatch(Results.sfnTask, { errors: ["States.ALL"], resultPath: "$.catchErrorResult" });

// State machine choices based on the tasks output
const httpResponseChoices = new sfn.Choice(scope, "HttpResponse")
.when(successResponse, Results.sfnTask)
.when(clientErrorResponse, Results.sfnTask)
.when(maxRetries, Results.sfnTask)
.when(internalErrorResponse, InvokeCspApi.sfnTask);
.afterwards(); // converge choices and allows for additional tasks to be chained on

// Composing state machine
this.workflow = InvokeCspApi.sfnTask.next(Demo.sfnTask).next(httpResponseChoices);
this.workflow = InvokeCspApi.sfnTask.next(httpResponseChoices);

this.logGroup = new logs.LogGroup(scope, "StepFunctionsLogs", {
retention: logs.RetentionDays.ONE_WEEK,
Expand Down
2 changes: 1 addition & 1 deletion lib/constructs/sfn-lambda-invoke-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class SfnLambdaInvokeTask extends Construct {
constructor(scope: Construct, id: string, props: SfnTasksProps) {
super(scope, id);
const sfnTask = new sfnTasks.LambdaInvoke(this, id, {
timeout: Duration.seconds(60),
timeout: Duration.seconds(20),
...props.sfnTask,
});

Expand Down
2 changes: 1 addition & 1 deletion utils/middleware/error-handling-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const errorHandlingMiddleware = (): middy.MiddlewareObj<ILambdaEvent, API
const onError: middy.MiddlewareFn<ILambdaEvent, APIGatewayProxyResult> = async (
request
): Promise<ValidationErrorResponse | void> => {
const error = serializeError(request.error)!;
const error = serializeError(request.error!);
const errorMessage = error.message;

if (error.name === "MockCspApiError") {
Expand Down

0 comments on commit 831c422

Please sign in to comment.