From f7d8de88f5edfb72eb0c1c4bf9001d5286fb27c2 Mon Sep 17 00:00:00 2001 From: seven Date: Tue, 29 Oct 2024 23:37:45 +0800 Subject: [PATCH] feat: evaluate service ctx reference Signed-off-by: seven --- src/common/iacHelper.ts | 25 ++++++++------- src/stack/iacStack.ts | 56 ++++++++++++++++----------------- tests/fixtures/deployFixture.ts | 2 +- tests/stack/deploy.test.ts | 2 +- 4 files changed, 44 insertions(+), 41 deletions(-) diff --git a/src/common/iacHelper.ts b/src/common/iacHelper.ts index 3b9e8af5..f0979102 100644 --- a/src/common/iacHelper.ts +++ b/src/common/iacHelper.ts @@ -1,7 +1,7 @@ import path from 'node:path'; import fs from 'node:fs'; import * as ros from '@alicloud/ros-cdk-core'; -import { ServerlessIac } from '../types'; +import { ActionContext } from '../types'; export const resolveCode = (location: string): string => { const filePath = path.resolve(process.cwd(), location); @@ -10,19 +10,28 @@ export const resolveCode = (location: string): string => { return fileContent.toString('base64'); }; -export const replaceReference = (value: T, stage: string): T => { +const evalCtx = (value: string, ctx: ActionContext): string => { + const containsStage = value.match(/\$\{ctx.\w+}/); + + return containsStage ? value.replace(/\$\{ctx.stage}/g, ctx.stage) : value; +}; + +export const replaceReference = (value: T, ctx: ActionContext): T => { if (typeof value === 'string') { const matchVar = value.match(/^\$\{vars\.(\w+)}$/); const containsVar = value.match(/\$\{vars\.(\w+)}/); const matchMap = value.match(/^\$\{stages\.(\w+)}$/); const containsMap = value.match(/\$\{stages\.(\w+)}/); const matchFn = value.match(/^\$\{functions\.(\w+(\.\w+)?)}$/); + if (value.match(/\$\{ctx.\w+}/)) { + return evalCtx(value, ctx) as T; + } if (matchVar?.length) { return ros.Fn.ref(matchVar[1]) as T; } if (matchMap?.length) { - return ros.Fn.findInMap('stages', stage, matchMap[1]) as T; + return ros.Fn.findInMap('stages', ctx.stage, matchMap[1]) as T; } if (matchFn?.length) { @@ -43,20 +52,14 @@ export const replaceReference = (value: T, stage: string): T => { } if (Array.isArray(value)) { - return value.map((item) => replaceReference(item, stage)) as T; + return value.map((item) => replaceReference(item, ctx)) as T; } if (typeof value === 'object' && value !== null) { return Object.fromEntries( - Object.entries(value).map(([key, val]) => [key, replaceReference(val, stage)]), + Object.entries(value).map(([key, val]) => [key, replaceReference(val, ctx)]), ) as T; } return value; }; - -export const evalRefValue = (value: string, iac: ServerlessIac, stage: string): string => { - const containsStage = value.match(/\$\{stage}/); - - return containsStage ? value.replace(/\$\{stage}/g, stage) : value; -}; diff --git a/src/stack/iacStack.ts b/src/stack/iacStack.ts index db10a0e3..e9754124 100644 --- a/src/stack/iacStack.ts +++ b/src/stack/iacStack.ts @@ -4,26 +4,26 @@ import { ActionContext, EventTypes, ServerlessIac } from '../types'; import * as fc from '@alicloud/ros-cdk-fc3'; import * as ram from '@alicloud/ros-cdk-ram'; import * as agw from '@alicloud/ros-cdk-apigateway'; -import { evalRefValue, replaceReference, resolveCode } from '../common'; +import { replaceReference, resolveCode } from '../common'; export class IacStack extends ros.Stack { - private service: string; + private readonly service: string; constructor(scope: ros.Construct, iac: ServerlessIac, context: ActionContext) { - super(scope, evalRefValue(iac.service, iac, context.stage), { + super(scope, replaceReference(iac.service, context), { stackName: context.stackName, tags: iac.tags?.reduce((acc: { [key: string]: string }, tag) => { - acc[tag.key] = replaceReference(tag.value, context.stage); + acc[tag.key] = replaceReference(tag.value, context); return acc; }, {}), }); - this.service = evalRefValue(iac.service, iac, context.stage); + this.service = replaceReference(iac.service, context); // Define Parameters if (iac.vars) { Object.entries(iac.vars).map( - ([key, value]) => - new ros.RosParameter(this, key, { + ([id, value]) => + new ros.RosParameter(this, id, { type: RosParameterType.STRING, defaultValue: value, }), @@ -32,13 +32,13 @@ export class IacStack extends ros.Stack { // Define Mappings if (iac.stages) { - new ros.RosMapping(this, 'stages', { mapping: replaceReference(iac.stages, context.stage) }); + new ros.RosMapping(this, 'stages', { mapping: replaceReference(iac.stages, context) }); } new ros.RosInfo( this, ros.RosInfo.description, - replaceReference(`${this.service} stack`, context.stage), + replaceReference(`${this.service} stack`, context), ); iac.functions.forEach((fnc) => { @@ -46,12 +46,12 @@ export class IacStack extends ros.Stack { this, fnc.key, { - functionName: replaceReference(fnc.name, context.stage), - handler: replaceReference(fnc.handler, context.stage), - runtime: replaceReference(fnc.runtime, context.stage), - memorySize: replaceReference(fnc.memory, context.stage), - timeout: replaceReference(fnc.timeout, context.stage), - environmentVariables: replaceReference(fnc.environment, context.stage), + functionName: replaceReference(fnc.name, context), + handler: replaceReference(fnc.handler, context), + runtime: replaceReference(fnc.runtime, context), + memorySize: replaceReference(fnc.memory, context), + timeout: replaceReference(fnc.timeout, context), + environmentVariables: replaceReference(fnc.environment, context), code: { zipFile: resolveCode(fnc.code), }, @@ -64,10 +64,10 @@ export class IacStack extends ros.Stack { if (apiGateway?.length) { const gatewayAccessRole = new ram.RosRole( this, - replaceReference(`${this.service}_role`, context.stage), + replaceReference(`${this.service}_role`, context), { - roleName: replaceReference(`${this.service}-gateway-access-role`, context.stage), - description: replaceReference(`${this.service} role`, context.stage), + roleName: replaceReference(`${this.service}-gateway-access-role`, context), + description: replaceReference(`${this.service} role`, context), assumeRolePolicyDocument: { version: '1', statement: [ @@ -82,7 +82,7 @@ export class IacStack extends ros.Stack { }, policies: [ { - policyName: replaceReference(`${this.service}-policy`, context.stage), + policyName: replaceReference(`${this.service}-policy`, context), policyDocument: { version: '1', statement: [ @@ -102,10 +102,10 @@ export class IacStack extends ros.Stack { const apiGatewayGroup = new agw.RosGroup( this, - replaceReference(`${this.service}_apigroup`, context.stage), + replaceReference(`${this.service}_apigroup`, context), { - groupName: replaceReference(`${this.service}_apigroup`, context.stage), - tags: replaceReference(iac.tags, context.stage), + groupName: replaceReference(`${this.service}_apigroup`, context), + tags: replaceReference(iac.tags, context), }, true, ); @@ -129,29 +129,29 @@ export class IacStack extends ros.Stack { const api = new agw.RosApi( this, - replaceReference(`${event.key}_api_${key}`, context.stage), + replaceReference(`${event.key}_api_${key}`, context), { - apiName: replaceReference(`${event.name}_api_${key}`, context.stage), + apiName: replaceReference(`${event.name}_api_${key}`, context), groupId: apiGatewayGroup.attrGroupId, visibility: 'PRIVATE', requestConfig: { requestProtocol: 'HTTP', - requestHttpMethod: replaceReference(trigger.method, context.stage), - requestPath: replaceReference(trigger.path, context.stage), + requestHttpMethod: replaceReference(trigger.method, context), + requestPath: replaceReference(trigger.path, context), requestMode: 'PASSTHROUGH', }, serviceConfig: { serviceProtocol: 'FunctionCompute', functionComputeConfig: { fcRegionId: context.region, - functionName: replaceReference(trigger.backend, context.stage), + functionName: replaceReference(trigger.backend, context), roleArn: gatewayAccessRole.attrArn, fcVersion: '3.0', }, }, resultSample: 'ServerlessInsight resultSample', resultType: 'JSON', - tags: replaceReference(iac.tags, context.stage), + tags: replaceReference(iac.tags, context), }, true, ); diff --git a/tests/fixtures/deployFixture.ts b/tests/fixtures/deployFixture.ts index c372cb1f..e96cee09 100644 --- a/tests/fixtures/deployFixture.ts +++ b/tests/fixtures/deployFixture.ts @@ -143,7 +143,7 @@ export const oneFcOneGatewayRos = { export const referredServiceIac = set( cloneDeep(oneFcOneGatewayIac), 'service', - 'my-demo-service-${stage}', + 'my-demo-service-${ctx.stage}', ); export const referredServiceRos = { diff --git a/tests/stack/deploy.test.ts b/tests/stack/deploy.test.ts index e0cf726a..fe61d8c9 100644 --- a/tests/stack/deploy.test.ts +++ b/tests/stack/deploy.test.ts @@ -89,7 +89,7 @@ describe('Unit tests for stack deployment', () => { options, ); }); - it('should evaluate service name as pure string when it reference ${stage}', async () => { + it('should evaluate service name as pure string when it reference ${ctx.stage}', async () => { const options = { stackName: 'my-demo-stack-fc-with-stage-1', stage: 'dev' }; mockedRosStackDeploy.mockResolvedValueOnce(options.stackName);