1573 extensibility twenty cli handle custom layers for serverless functions of applications (#14779)
- allow specific layers for serverless functions - add a serverlessFunctionLayer table - sync application layer
This commit is contained in:
+7
-1
@@ -1,4 +1,4 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
import { Field, HideField, InputType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
IsNotEmpty,
|
||||
@@ -27,4 +27,10 @@ export class CreateServerlessFunctionInput {
|
||||
@Max(900)
|
||||
@IsOptional()
|
||||
timeoutSeconds?: number;
|
||||
|
||||
@HideField()
|
||||
applicationId?: string;
|
||||
|
||||
@HideField()
|
||||
serverlessFunctionLayerId?: string;
|
||||
}
|
||||
|
||||
+27
@@ -5,8 +5,11 @@ import {
|
||||
DeleteDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
Relation,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
@@ -17,6 +20,8 @@ import { DatabaseEventTrigger } from 'src/engine/metadata-modules/database-event
|
||||
import { Route } from 'src/engine/metadata-modules/route/route.entity';
|
||||
import { ServerlessFunctionEntityRelationProperties } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
|
||||
import { InputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/input-schema.type';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { ServerlessFunctionLayerEntity } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.entity';
|
||||
|
||||
const DEFAULT_SERVERLESS_TIMEOUT_SECONDS = 300; // 5 minutes
|
||||
|
||||
@@ -74,6 +79,28 @@ export class ServerlessFunctionEntity
|
||||
@Column({ nullable: true, type: 'text' })
|
||||
checksum: string | null;
|
||||
|
||||
@Column({ nullable: true, type: 'uuid' })
|
||||
serverlessFunctionLayerId: string | null;
|
||||
|
||||
@ManyToOne(
|
||||
() => ServerlessFunctionLayerEntity,
|
||||
(serverlessFunctionLayer) => serverlessFunctionLayer.serverlessFunctions,
|
||||
{ nullable: true },
|
||||
)
|
||||
@JoinColumn({ name: 'serverlessFunctionLayerId' })
|
||||
serverlessFunctionLayer: Relation<ServerlessFunctionLayerEntity> | null;
|
||||
|
||||
@ManyToOne(
|
||||
() => ApplicationEntity,
|
||||
(application) => application.serverlessFunctions,
|
||||
{
|
||||
onDelete: 'CASCADE',
|
||||
nullable: true,
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: 'applicationId' })
|
||||
application: Relation<ApplicationEntity> | null;
|
||||
|
||||
@OneToMany(
|
||||
() => CronTrigger,
|
||||
(cronTrigger) => cronTrigger.serverlessFunction,
|
||||
|
||||
+2
@@ -17,6 +17,7 @@ import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverles
|
||||
import { ServerlessFunctionV2Service } from 'src/engine/metadata-modules/serverless-function/services/serverless-function-v2.service';
|
||||
import { WorkspaceFlatServerlessFunctionMapCacheService } from 'src/engine/metadata-modules/serverless-function/services/workspace-flat-serverless-function-map-cache.service';
|
||||
import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module';
|
||||
import { ServerlessFunctionLayerModule } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -29,6 +30,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
|
||||
FeatureFlagModule,
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
WorkspaceMigrationV2Module,
|
||||
ServerlessFunctionLayerModule,
|
||||
],
|
||||
providers: [
|
||||
ServerlessFunctionService,
|
||||
|
||||
+20
-8
@@ -16,7 +16,6 @@ import { FileStorageService } from 'src/engine/core-modules/file-storage/file-st
|
||||
import { readFileContent } from 'src/engine/core-modules/file-storage/utils/read-file-content';
|
||||
import { ENV_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/env-file-name';
|
||||
import { INDEX_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/index-file-name';
|
||||
import { LAST_LAYER_VERSION } from 'src/engine/core-modules/serverless/drivers/layers/last-layer-version';
|
||||
import { getBaseTypescriptProjectFiles } from 'src/engine/core-modules/serverless/drivers/utils/get-base-typescript-project-files';
|
||||
import { getLayerDependencies } from 'src/engine/core-modules/serverless/drivers/utils/get-last-layer-dependencies';
|
||||
import { ServerlessService } from 'src/engine/core-modules/serverless/serverless.service';
|
||||
@@ -34,12 +33,14 @@ import {
|
||||
WorkflowVersionStepException,
|
||||
WorkflowVersionStepExceptionCode,
|
||||
} from 'src/modules/workflow/common/exceptions/workflow-version-step.exception';
|
||||
import { ServerlessFunctionLayerService } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.service';
|
||||
|
||||
@Injectable()
|
||||
export class ServerlessFunctionService {
|
||||
constructor(
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
private readonly serverlessService: ServerlessService,
|
||||
private readonly serverlessFunctionLayerService: ServerlessFunctionLayerService,
|
||||
@InjectRepository(ServerlessFunctionEntity)
|
||||
private readonly serverlessFunctionRepository: Repository<ServerlessFunctionEntity>,
|
||||
private readonly throttlerService: ThrottlerService,
|
||||
@@ -116,6 +117,7 @@ export class ServerlessFunctionService {
|
||||
id,
|
||||
workspaceId,
|
||||
},
|
||||
relations: ['serverlessFunctionLayer'],
|
||||
});
|
||||
|
||||
const resultServerlessFunction = await this.serverlessService.execute(
|
||||
@@ -304,14 +306,16 @@ export class ServerlessFunctionService {
|
||||
|
||||
async getAvailablePackages(serverlessFunctionId: string) {
|
||||
const serverlessFunction =
|
||||
await this.serverlessFunctionRepository.findOneBy({
|
||||
id: serverlessFunctionId,
|
||||
await this.serverlessFunctionRepository.findOneOrFail({
|
||||
where: { id: serverlessFunctionId },
|
||||
relations: ['serverlessFunctionLayer'],
|
||||
});
|
||||
const { packageJson, yarnLock } = await getLayerDependencies(
|
||||
serverlessFunction?.layerVersion || 'latest',
|
||||
);
|
||||
|
||||
const { packageJson, yarnLock } =
|
||||
await getLayerDependencies(serverlessFunction);
|
||||
|
||||
const packageVersionRegex = /^"([^@]+)@.*?":\n\s+version: (.+)$/gm;
|
||||
|
||||
const versions: Record<string, string> = {};
|
||||
|
||||
let match: RegExpExecArray | null;
|
||||
@@ -321,7 +325,7 @@ export class ServerlessFunctionService {
|
||||
const version = match[2];
|
||||
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
if (packageJson.dependencies[packageName]) {
|
||||
if (packageJson.dependencies?.[packageName]) {
|
||||
versions[packageName] = version;
|
||||
}
|
||||
}
|
||||
@@ -333,11 +337,16 @@ export class ServerlessFunctionService {
|
||||
serverlessFunctionInput: CreateServerlessFunctionInput,
|
||||
workspaceId: string,
|
||||
) {
|
||||
const commonServerlessFunctionLayer =
|
||||
await this.serverlessFunctionLayerService.createCommonLayerIfNotExist(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const serverlessFunctionToCreate = this.serverlessFunctionRepository.create(
|
||||
{
|
||||
...serverlessFunctionInput,
|
||||
workspaceId,
|
||||
layerVersion: LAST_LAYER_VERSION,
|
||||
serverlessFunctionLayerId: commonServerlessFunctionLayer.id,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -422,6 +431,9 @@ export class ServerlessFunctionService {
|
||||
name: serverlessFunctionToDuplicate.name,
|
||||
description: serverlessFunctionToDuplicate.description ?? undefined,
|
||||
timeoutSeconds: serverlessFunctionToDuplicate.timeoutSeconds,
|
||||
applicationId: serverlessFunctionToDuplicate.applicationId ?? undefined,
|
||||
serverlessFunctionLayerId:
|
||||
serverlessFunctionToDuplicate.serverlessFunctionLayerId ?? undefined,
|
||||
},
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
+8
-1
@@ -4,11 +4,18 @@ import { type DatabaseEventTrigger } from 'src/engine/metadata-modules/database-
|
||||
import { type Route } from 'src/engine/metadata-modules/route/route.entity';
|
||||
import { type ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
|
||||
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { type ServerlessFunctionLayerEntity } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.entity';
|
||||
|
||||
export type ServerlessFunctionEntityRelationProperties =
|
||||
ExtractRecordTypeOrmRelationProperties<
|
||||
ServerlessFunctionEntity,
|
||||
CronTrigger | DatabaseEventTrigger | Route | Workspace
|
||||
| CronTrigger
|
||||
| DatabaseEventTrigger
|
||||
| Route
|
||||
| Workspace
|
||||
| ApplicationEntity
|
||||
| ServerlessFunctionLayerEntity
|
||||
>;
|
||||
|
||||
export type FlatServerlessFunction = Omit<
|
||||
|
||||
+3
-1
@@ -26,11 +26,13 @@ export const fromCreateServerlessFunctionInputToFlatServerlessFunction = ({
|
||||
deletedAt: null,
|
||||
latestVersion: null,
|
||||
publishedVersions: [],
|
||||
applicationId: null,
|
||||
applicationId: createServerlessFunctionInput.applicationId ?? null,
|
||||
latestVersionInputSchema: null,
|
||||
runtime: ServerlessFunctionRuntime.NODE22,
|
||||
timeoutSeconds: createServerlessFunctionInput.timeoutSeconds ?? 300,
|
||||
layerVersion: LAST_LAYER_VERSION,
|
||||
serverlessFunctionLayerId:
|
||||
createServerlessFunctionInput.serverlessFunctionLayerId ?? null,
|
||||
workspaceId,
|
||||
checksum: null,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user