twentyhq / twenty

Building a modern alternative to Salesforce, powered by the community.
https://twenty.com
Other
20.15k stars 2.24k forks source link

[Workflows] Create a custom code executor #6181

Closed martmull closed 3 months ago

martmull commented 4 months ago

Scope & Context

We are working on enabling users to run custom code in their workspace. This ticket is about creating a way to execute safely custom code on our server

What

greptile-apps[bot] commented 4 months ago

Implementation Steps

  1. Create Functions Section in Settings

    • Add a new section in the settings UI for Functions.
  2. Define Function Storage

    • Store function folders in the storage defined by STORAGE_TYPE env variable (s3 or local).
  3. Create Function Table

    • Define a new table <workspace_schema>.function to store function metadata.
  4. Implement Code Executor

    • Create a code-executor service to handle function execution.
    • For lambda context:
      • Compile TypeScript handler to JavaScript.
      • Create AWS Lambda function and store Lambda ID in <workspace_schema>.function.
      • Update sync_status to ready.
      • Execute Lambda with provided event.
    • For local context:
      • Copy TypeScript handler and execute using eval or child_process.exec.
  5. Update WorkspacePreQueryHookService

    • Reference workspace-pre-query-hook.service.ts for executing hooks.
  6. Handle Jobs

    • Reference call-webhook-jobs.job.ts for job handling logic.
// Example: code-executor.service.ts
import { Injectable } from '@nestjs/common';
import { exec } from 'child_process';
import * as AWS from 'aws-sdk';

@Injectable()
export class CodeExecutorService {
  async executeFunction(event: any, context: any, functionId: string) {
    const functionData = await this.getFunctionData(functionId);
    if (process.env.FUNCTION_EXECUTION_CONTEXT === 'lambda') {
      return this.executeLambdaFunction(functionData, event);
    } else {
      return this.executeLocalFunction(functionData, event);
    }
  }

  private async executeLambdaFunction(functionData: any, event: any) {
    const lambda = new AWS.Lambda();
    const params = {
      FunctionName: functionData.lambdaId,
      Payload: JSON.stringify(event)
    };
    return lambda.invoke(params).promise();
  }

  private async executeLocalFunction(functionData: any, event: any) {
    return new Promise((resolve, reject) => {
      exec(`node ${functionData.filePath}`, (error, stdout, stderr) => {
        if (error) {
          reject(error);
        } else {
          resolve(stdout);
        }
      });
    });
  }

  private async getFunctionData(functionId: string) {
    // Fetch function data from the database
  }
}

References

/packages/twenty-server/src/workspace/workspace-query-runner/workspace-pre-query-hook/interfaces/workspace-pre-query-hook.interface.ts /packages/twenty-server/src/workspace/workspace-query-runner/workspace-pre-query-hook/workspace-pre-query-hook.service.ts /packages/twenty-server/src/workspace/workspace-query-runner/workspace-pre-query-hook/workspace-pre-query-hook.module.ts /packages/twenty-server/src/workspace/workspace-query-runner/jobs/call-webhook-jobs.job.ts /packages/twenty-server/src/workspace/workspace-query-runner/workspace-pre-query-hook/workspace-pre-query-hook.config.ts /packages/twenty-server/src/workspace/workspace-query-runner/workspace-pre-query-hook/types/workspace-query-hook.type.ts /packages/twenty-server/src/workspace/workspace-query-runner /packages/twenty-server/src/workspace/workspace-resolver-builder /packages/twenty-server/src/integrations/exception-handler/hooks

#### About Greptile This response provides a starting point for your research, not a precise solution. Help us improve! Please leave a 👍 if this is helpful and 👎 if it is irrelevant. [Ask Greptile](https://app.greptile.com/chat/github/twentyhq/twenty/main) · [Edit Issue Bot Settings](https://app.greptile.com/apps/github)