Add workflow runner (#6458)
- add workflow runner module
- add an endpoint to trigger a workflow via api
- improve error handling for serverless functions
## Testing
- create 2 serverless functions
- create a workflow
- create this workflow Version
```
{
"type": "MANUAL",
"input": {"b": "bb"},
"nextAction": {
"name": "step_1",
"displayName": "Code",
"type": "CODE",
"valid": true,
"settings": {
"serverlessFunctionId": "Serverless function 1 Id",
"errorHandlingOptions": {
"retryOnFailure": {
"value": false
},
"continueOnFailure": {
"value": false
}
}
},
"nextAction": {
"name": "step_1",
"displayName": "Code",
"type": "CODE",
"valid": true,
"settings": {
"serverlessFunctionId": "Serverless function 1 Id",
"errorHandlingOptions": {
"retryOnFailure": {
"value": false
},
"continueOnFailure": {
"value": false
}
}
},
"nextAction": {
"name": "step_1",
"displayName": "Code",
"type": "CODE",
"valid": true,
"settings": {
"serverlessFunctionId": "Serverless function 2 Id",
"errorHandlingOptions": {
"retryOnFailure": {
"value": false
},
"continueOnFailure": {
"value": false
}
}
}
}
}
}
}
`
``
- call
```
mutation Trigger {
triggerWorkflow(workflowVersionId: "WORKFLOW_VERSION_ID") {
result
}
}
```
- try when errors are injected in serverless function
This commit is contained in:
@ -16,4 +16,5 @@ export enum MessageQueue {
|
||||
recordPositionBackfillQueue = 'record-position-backfill-queue',
|
||||
entityEventsToDbQueue = 'entity-events-to-db-queue',
|
||||
testQueue = 'test-queue',
|
||||
workflowQueue = 'workflow-queue',
|
||||
}
|
||||
|
||||
@ -1,4 +1,18 @@
|
||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import { ServerlessFunctionExecutionStatus } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function-execution-result.dto';
|
||||
|
||||
export type ServerlessExecuteError = {
|
||||
errorType: string;
|
||||
errorMessage: string;
|
||||
stackTrace: string;
|
||||
};
|
||||
|
||||
export type ServerlessExecuteResult = {
|
||||
data: object | null;
|
||||
duration: number;
|
||||
status: ServerlessFunctionExecutionStatus;
|
||||
error?: ServerlessExecuteError;
|
||||
};
|
||||
|
||||
export interface ServerlessDriver {
|
||||
delete(serverlessFunction: ServerlessFunctionEntity): Promise<void>;
|
||||
@ -6,5 +20,5 @@ export interface ServerlessDriver {
|
||||
execute(
|
||||
serverlessFunction: ServerlessFunctionEntity,
|
||||
payload: object | undefined,
|
||||
): Promise<object>;
|
||||
): Promise<ServerlessExecuteResult>;
|
||||
}
|
||||
|
||||
@ -13,13 +13,17 @@ import {
|
||||
import { CreateFunctionCommandInput } from '@aws-sdk/client-lambda/dist-types/commands/CreateFunctionCommand';
|
||||
import { UpdateFunctionCodeCommandInput } from '@aws-sdk/client-lambda/dist-types/commands/UpdateFunctionCodeCommand';
|
||||
|
||||
import { ServerlessDriver } from 'src/engine/integrations/serverless/drivers/interfaces/serverless-driver.interface';
|
||||
import {
|
||||
ServerlessDriver,
|
||||
ServerlessExecuteResult,
|
||||
} from 'src/engine/integrations/serverless/drivers/interfaces/serverless-driver.interface';
|
||||
|
||||
import { createZipFile } from 'src/engine/integrations/serverless/drivers/utils/create-zip-file';
|
||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import { FileStorageService } from 'src/engine/integrations/file-storage/file-storage.service';
|
||||
import { BaseServerlessDriver } from 'src/engine/integrations/serverless/drivers/base-serverless.driver';
|
||||
import { BuildDirectoryManagerService } from 'src/engine/integrations/serverless/drivers/services/build-directory-manager.service';
|
||||
import { ServerlessFunctionExecutionStatus } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function-execution-result.dto';
|
||||
|
||||
export interface LambdaDriverOptions extends LambdaClientConfig {
|
||||
fileStorageService: FileStorageService;
|
||||
@ -134,7 +138,8 @@ export class LambdaDriver
|
||||
async execute(
|
||||
functionToExecute: ServerlessFunctionEntity,
|
||||
payload: object | undefined = undefined,
|
||||
): Promise<object> {
|
||||
): Promise<ServerlessExecuteResult> {
|
||||
const startTime = Date.now();
|
||||
const params = {
|
||||
FunctionName: functionToExecute.id,
|
||||
Payload: JSON.stringify(payload),
|
||||
@ -144,10 +149,25 @@ export class LambdaDriver
|
||||
|
||||
const result = await this.lambdaClient.send(command);
|
||||
|
||||
if (!result.Payload) {
|
||||
return {};
|
||||
const parsedResult = result.Payload
|
||||
? JSON.parse(result.Payload.transformToString())
|
||||
: {};
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
if (result.FunctionError) {
|
||||
return {
|
||||
data: null,
|
||||
duration,
|
||||
status: ServerlessFunctionExecutionStatus.ERROR,
|
||||
error: parsedResult,
|
||||
};
|
||||
}
|
||||
|
||||
return JSON.parse(result.Payload.transformToString());
|
||||
return {
|
||||
data: parsedResult,
|
||||
duration,
|
||||
status: ServerlessFunctionExecutionStatus.SUCCESS,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,13 +5,18 @@ import { fork } from 'child_process';
|
||||
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { ServerlessDriver } from 'src/engine/integrations/serverless/drivers/interfaces/serverless-driver.interface';
|
||||
import {
|
||||
ServerlessDriver,
|
||||
ServerlessExecuteError,
|
||||
ServerlessExecuteResult,
|
||||
} from 'src/engine/integrations/serverless/drivers/interfaces/serverless-driver.interface';
|
||||
|
||||
import { FileStorageService } from 'src/engine/integrations/file-storage/file-storage.service';
|
||||
import { readFileContent } from 'src/engine/integrations/file-storage/utils/read-file-content';
|
||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import { BUILD_FILE_NAME } from 'src/engine/integrations/serverless/drivers/constants/build-file-name';
|
||||
import { BaseServerlessDriver } from 'src/engine/integrations/serverless/drivers/base-serverless.driver';
|
||||
import { ServerlessFunctionExecutionStatus } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function-execution-result.dto';
|
||||
|
||||
export interface LocalDriverOptions {
|
||||
fileStorageService: FileStorageService;
|
||||
@ -51,7 +56,8 @@ export class LocalDriver
|
||||
async execute(
|
||||
serverlessFunction: ServerlessFunctionEntity,
|
||||
payload: object | undefined = undefined,
|
||||
): Promise<object> {
|
||||
): Promise<ServerlessExecuteResult> {
|
||||
const startTime = Date.now();
|
||||
const fileStream = await this.fileStorageService.read({
|
||||
folderPath: this.getFolderPath(serverlessFunction),
|
||||
filename: BUILD_FILE_NAME,
|
||||
@ -83,8 +89,23 @@ export class LocalDriver
|
||||
return await new Promise((resolve, reject) => {
|
||||
const child = fork(tmpFilePath, { silent: true });
|
||||
|
||||
child.on('message', (message: object) => {
|
||||
resolve(message);
|
||||
child.on('message', (message: object | ServerlessExecuteError) => {
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
if ('errorType' in message) {
|
||||
resolve({
|
||||
data: null,
|
||||
duration,
|
||||
error: message,
|
||||
status: ServerlessFunctionExecutionStatus.ERROR,
|
||||
});
|
||||
} else {
|
||||
resolve({
|
||||
data: message,
|
||||
duration,
|
||||
status: ServerlessFunctionExecutionStatus.SUCCESS,
|
||||
});
|
||||
}
|
||||
child.kill();
|
||||
fs.unlink(tmpFilePath);
|
||||
});
|
||||
@ -93,8 +114,8 @@ export class LocalDriver
|
||||
const stackTrace = data
|
||||
.toString()
|
||||
.split('\n')
|
||||
.filter((line) => line.trim() !== '');
|
||||
const errorTrace = stackTrace.filter((line) =>
|
||||
.filter((line: string) => line.trim() !== '');
|
||||
const errorTrace = stackTrace.filter((line: string) =>
|
||||
line.includes('Error: '),
|
||||
)?.[0];
|
||||
|
||||
@ -105,11 +126,17 @@ export class LocalDriver
|
||||
errorType = errorTrace.split(':')[0];
|
||||
errorMessage = errorTrace.split(': ')[1];
|
||||
}
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
resolve({
|
||||
errorType,
|
||||
errorMessage,
|
||||
stackTrace: stackTrace,
|
||||
data: null,
|
||||
duration,
|
||||
status: ServerlessFunctionExecutionStatus.ERROR,
|
||||
error: {
|
||||
errorType,
|
||||
errorMessage,
|
||||
stackTrace: stackTrace,
|
||||
},
|
||||
});
|
||||
child.kill();
|
||||
fs.unlink(tmpFilePath);
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
|
||||
import { ServerlessDriver } from 'src/engine/integrations/serverless/drivers/interfaces/serverless-driver.interface';
|
||||
import {
|
||||
ServerlessDriver,
|
||||
ServerlessExecuteResult,
|
||||
} from 'src/engine/integrations/serverless/drivers/interfaces/serverless-driver.interface';
|
||||
|
||||
import { SERVERLESS_DRIVER } from 'src/engine/integrations/serverless/serverless.constants';
|
||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
@ -20,7 +23,7 @@ export class ServerlessService implements ServerlessDriver {
|
||||
async execute(
|
||||
serverlessFunction: ServerlessFunctionEntity,
|
||||
payload: object | undefined = undefined,
|
||||
) {
|
||||
): Promise<ServerlessExecuteResult> {
|
||||
return this.driver.execute(serverlessFunction, payload);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user