diff --git a/package-lock.json b/package-lock.json index 76f1fab..4393291 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,10 @@ "license": "Apache-2.0", "dependencies": { "@alicloud/openapi-client": "^0.4.11", + "@alicloud/ros-cdk-apigateway": "^1.2.0", "@alicloud/ros-cdk-core": "^1.2.0", "@alicloud/ros-cdk-fc": "^1.2.0", + "@alicloud/ros-cdk-ram": "^1.2.0", "@alicloud/ros20190910": "^3.4.3", "ajv": "^8.17.1", "chalk": "^4.1.2", @@ -102,6 +104,19 @@ "sm3": "^1.0.3" } }, + "node_modules/@alicloud/ros-cdk-apigateway": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@alicloud/ros-cdk-apigateway/-/ros-cdk-apigateway-1.2.0.tgz", + "integrity": "sha512-52n/Liy0jRRilpqWHEBqG02d4TCe3O5h04G+k+6G+jctdNP6ThirRDZ+TRtussor540+PslbkLi1VzMSwXnwoQ==", + "dependencies": { + "@alicloud/ros-cdk-core": "^1.0.27", + "constructs": "^3.0.4" + }, + "peerDependencies": { + "@alicloud/ros-cdk-core": "^1.0.27", + "constructs": "^3.0.4" + } + }, "node_modules/@alicloud/ros-cdk-assembly-schema": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@alicloud/ros-cdk-assembly-schema/-/ros-cdk-assembly-schema-1.2.0.tgz", @@ -1129,6 +1144,19 @@ "node": ">=0.4" } }, + "node_modules/@alicloud/ros-cdk-ram": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@alicloud/ros-cdk-ram/-/ros-cdk-ram-1.2.0.tgz", + "integrity": "sha512-b/Rp55oto80ukYXLDcB22uzOev90o0V3RXSxaYjfSy+4BmmDfKfMBXHSEggxmJCGCxROAcgA3qqgTuXe9hh9OQ==", + "dependencies": { + "@alicloud/ros-cdk-core": "^1.0.27", + "constructs": "^3.0.4" + }, + "peerDependencies": { + "@alicloud/ros-cdk-core": "^1.0.27", + "constructs": "^3.0.4" + } + }, "node_modules/@alicloud/ros20190910": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/@alicloud/ros20190910/-/ros20190910-3.4.3.tgz", @@ -6756,6 +6784,15 @@ "sm3": "^1.0.3" } }, + "@alicloud/ros-cdk-apigateway": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@alicloud/ros-cdk-apigateway/-/ros-cdk-apigateway-1.2.0.tgz", + "integrity": "sha512-52n/Liy0jRRilpqWHEBqG02d4TCe3O5h04G+k+6G+jctdNP6ThirRDZ+TRtussor540+PslbkLi1VzMSwXnwoQ==", + "requires": { + "@alicloud/ros-cdk-core": "^1.0.27", + "constructs": "^3.0.4" + } + }, "@alicloud/ros-cdk-assembly-schema": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@alicloud/ros-cdk-assembly-schema/-/ros-cdk-assembly-schema-1.2.0.tgz", @@ -7431,6 +7468,15 @@ } } }, + "@alicloud/ros-cdk-ram": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@alicloud/ros-cdk-ram/-/ros-cdk-ram-1.2.0.tgz", + "integrity": "sha512-b/Rp55oto80ukYXLDcB22uzOev90o0V3RXSxaYjfSy+4BmmDfKfMBXHSEggxmJCGCxROAcgA3qqgTuXe9hh9OQ==", + "requires": { + "@alicloud/ros-cdk-core": "^1.0.27", + "constructs": "^3.0.4" + } + }, "@alicloud/ros20190910": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/@alicloud/ros20190910/-/ros20190910-3.4.3.tgz", diff --git a/package.json b/package.json index c009407..ee05289 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,10 @@ ], "dependencies": { "@alicloud/openapi-client": "^0.4.11", + "@alicloud/ros-cdk-apigateway": "^1.2.0", "@alicloud/ros-cdk-core": "^1.2.0", "@alicloud/ros-cdk-fc": "^1.2.0", + "@alicloud/ros-cdk-ram": "^1.2.0", "@alicloud/ros20190910": "^3.4.3", "ajv": "^8.17.1", "chalk": "^4.1.2", diff --git a/src/common/rosClient.ts b/src/common/rosClient.ts index dc04c28..c5c3b6a 100644 --- a/src/common/rosClient.ts +++ b/src/common/rosClient.ts @@ -4,6 +4,7 @@ import ROS20190910, { CreateStackRequestParameters, CreateStackRequestTags, ListStacksRequest, + UpdateStackRequest, } from '@alicloud/ros20190910'; import { Config } from '@alicloud/openapi-client'; import { ActionContext } from '../types'; @@ -42,7 +43,7 @@ const createStack = async (stackName: string, templateBody: unknown, context: Ac return response.body?.stackId; }; -const updateStack = async (stackName: string, templateBody: unknown, context: ActionContext) => { +const updateStack = async (stackId: string, templateBody: unknown, context: ActionContext) => { const parameters = context.parameters?.map( (parameter) => new CreateStackRequestParameters({ @@ -51,10 +52,12 @@ const updateStack = async (stackName: string, templateBody: unknown, context: Ac }), ); - const createStackRequest = new CreateStackRequest({ - stackName, - templateBody, + const createStackRequest = new UpdateStackRequest({ + regionId: context.region, + stackId, + templateBody: JSON.stringify(templateBody), parameters, + tags: context.tags?.map((tag) => new CreateStackRequestTags(tag)), }); const response = await client.updateStack(createStackRequest); @@ -93,7 +96,7 @@ export const rosStackDeploy = async ( } printer.info(`Update stack: ${stackName} deploying... `); - return await updateStack(stackName, templateBody, context); + return await updateStack(stackInfo.stackId as string, templateBody, context); } else { // create stack printer.info(`Create stack: ${stackName} deploying... `); diff --git a/src/iac/iacSchema.ts b/src/iac/iacSchema.ts index 7b6ac3a..a2a6267 100644 --- a/src/iac/iacSchema.ts +++ b/src/iac/iacSchema.ts @@ -79,6 +79,7 @@ const schema = { '.*': { type: 'object', properties: { + name: { type: 'string' }, type: { type: 'string', enum: ['API_GATEWAY'] }, triggers: { type: 'array', @@ -90,7 +91,7 @@ const schema = { }, }, }, - required: ['type', 'triggers'], + required: ['name', 'type', 'triggers'], }, }, }, diff --git a/src/stack/deploy.ts b/src/stack/deploy.ts index a615fe7..76c94e2 100644 --- a/src/stack/deploy.ts +++ b/src/stack/deploy.ts @@ -1,6 +1,9 @@ import * as ros from '@alicloud/ros-cdk-core'; import * as fc from '@alicloud/ros-cdk-fc'; -import { ActionContext, ServerlessIac } from '../types'; +import * as agw from '@alicloud/ros-cdk-apigateway'; +import * as ram from '@alicloud/ros-cdk-ram'; + +import { ActionContext, EventTypes, ServerlessIac } from '../types'; import { printer, rosStackDeploy } from '../common'; import path from 'node:path'; import * as fs from 'node:fs'; @@ -13,9 +16,10 @@ const resolveCode = (location: string): string => { }; export class IacStack extends ros.Stack { - constructor(scope: ros.Construct, iac: ServerlessIac, props?: ros.StackProps) { - super(scope, iac.service, props); - new ros.RosInfo(this, ros.RosInfo.description, 'This is the simple ros cdk app example.'); + constructor(scope: ros.Construct, iac: ServerlessIac, context: ActionContext) { + super(scope, iac.service); + new ros.RosInfo(this, ros.RosInfo.description, `${iac.service} stack`); + const service = new fc.RosService( this, `${iac.service}-service`, @@ -45,12 +49,98 @@ export class IacStack extends ros.Stack { ); func.addDependsOn(service); }); + + const apiGateway = iac.events.find((event) => event.type === EventTypes.API_GATEWAY); + if (apiGateway) { + const gatewayAccessRole = new ram.RosRole( + this, + `${iac.service}_role`, + { + roleName: `${iac.service}-gateway-access-role`, + description: `${iac.service} role`, + assumeRolePolicyDocument: { + version: '1', + statement: [ + { + action: 'sts:AssumeRole', + effect: 'Allow', + principal: { + service: ['apigateway.aliyuncs.com'], + }, + }, + ], + }, + policies: [ + { + policyName: `${iac.service}-policy`, + policyDocument: { + version: '1', + statement: [ + { + action: ['fc:InvokeFunction'], + effect: 'Allow', + // @TODO implement at least permission granting + resource: ['*'], + }, + ], + }, + }, + ], + }, + true, + ); + + const apiGatewayGroup = new agw.RosGroup( + this, + `${iac.service}_apigroup`, + { + groupName: `${iac.service}_apigroup`, + }, + true, + ); + + iac.events + .filter((event) => event.type === EventTypes.API_GATEWAY) + .forEach((event) => { + event.triggers.forEach((trigger) => { + const key = `${trigger.method}_${trigger.path}`.toLowerCase().replace(/\//g, '_'); + const api = new agw.RosApi( + this, + `${event.key}_api_${key}`, + { + apiName: `${event.name}_api_${key}`, + groupId: apiGatewayGroup.attrGroupId, + visibility: 'PRIVATE', + requestConfig: { + requestProtocol: 'HTTP', + requestHttpMethod: trigger.method, + requestPath: trigger.path, + requestMode: 'PASSTHROUGH', + }, + serviceConfig: { + serviceProtocol: 'FunctionCompute', + functionComputeConfig: { + fcRegionId: context.region, + serviceName: service.serviceName, + functionName: trigger.backend, + roleArn: gatewayAccessRole.attrArn, + }, + }, + resultSample: 'ServerlessInsight resultSample', + resultType: 'JSON', + }, + true, + ); + api.addDependsOn(apiGatewayGroup); + }); + }); + } } } -const generateStackTemplate = (stackName: string, iac: ServerlessIac) => { +const generateStackTemplate = (stackName: string, iac: ServerlessIac, context: ActionContext) => { const app = new ros.App(); - new IacStack(app, iac); + new IacStack(app, iac, context); const assembly = app.synth(); const stackArtifact = assembly.getStackByName(stackName); @@ -69,7 +159,7 @@ export const deployStack = async ( ) => { printer.info(`Deploying stack... ${JSON.stringify(iac)}`); - const { template, parameters } = generateStackTemplate(stackName, iac); + const { template, parameters } = generateStackTemplate(stackName, iac, context); console.log('Generated ROS YAML:', JSON.stringify({ template, parameters })); await rosStackDeploy(stackName, template, { ...context, parameters }); printer.info(`Stack deployed! 🎉`); diff --git a/src/types.ts b/src/types.ts index 05e4eb3..c35ec13 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,6 +10,10 @@ type Stages = { [key: string]: Stage; }; +export enum EventTypes { + API_GATEWAY = 'API_GATEWAY', +} + export type IacFunction = { name: string; key: string; @@ -24,12 +28,14 @@ export type IacFunction = { }; export type Event = { - type: string; - source: string; - function: string; - batch_size?: number; - enabled?: boolean; - target: string; + key: string; + name: string; + type: EventTypes; + triggers: Array<{ + method: string; + path: string; + backend: string; + }>; }; type Events = { diff --git a/tests/fixtures/serverless-insignt.yml b/tests/fixtures/serverless-insignt.yml index 7f80074..3e2a73d 100644 --- a/tests/fixtures/serverless-insignt.yml +++ b/tests/fixtures/serverless-insignt.yml @@ -18,7 +18,7 @@ tags: functions: insight_poc_fn: name: insight-poc-fn - runtime: nodejs14 + runtime: nodejs18 handler: index.handler code: artifacts/artifact.zip memory: 512 @@ -30,6 +30,7 @@ functions: events: gateway_event: type: API_GATEWAY + name: insight-poc-gateway triggers: - method: GET path: /api/hello