diff --git a/samples/aliyun-poc-fc.yml b/samples/aliyun-poc-api.yml similarity index 57% rename from samples/aliyun-poc-fc.yml rename to samples/aliyun-poc-api.yml index 42d18a7..f58bea2 100644 --- a/samples/aliyun-poc-fc.yml +++ b/samples/aliyun-poc-api.yml @@ -1,7 +1,7 @@ version: 0.0.1 provider: name: aliyun - region: cn-chengdu + region: cn-hongkong vars: region: cn-hangzhou @@ -18,17 +18,18 @@ stages: prod: region: cn-shanghai -service: insight-poc +service: insight-poc-api tags: owner: geek-fun functions: insight_poc_fn: - name: insight-poc-fn - runtime: nodejs18 - handler: ${vars.handler} - code: tests/fixtures/artifacts/artifact.zip + name: insight-poc-api-fn + code: + runtime: nodejs18 + handler: ${vars.handler} + path: tests/fixtures/artifacts/artifact.zip memory: 512 timeout: 10 environment: @@ -36,15 +37,6 @@ functions: TEST_VAR: ${vars.testv} TEST_VAR_EXTRA: abcds-${vars.testv}-andyou -databases: - insight_poc_db: - name: insight-poc-db - type: RDS_POSTGRESQL_SERVERLESS - version: '17.0' - security: - basic_auth: - password: 'U34I6InQ8elseTgqTWT2t2oFXpoqFg' - events: gateway_event: type: API_GATEWAY @@ -53,8 +45,5 @@ events: - method: GET path: /api/hello backend: ${functions.insight_poc_fn} -# custom_domain: -# domain_name: test.com -# certificate_name: test -# certificate_private_key: test -# certificate_body: test + domain: + domain_name: insight-api.serverlessinsight.com diff --git a/samples/huawei-poc-fc.yml b/samples/huawei-poc-fc.yml index fc4c596..3e85785 100644 --- a/samples/huawei-poc-fc.yml +++ b/samples/huawei-poc-fc.yml @@ -23,9 +23,10 @@ tags: functions: insight_poc_fn: name: insight-poc-fn - runtime: nodejs18 - handler: ${vars.handler} - code: tests/fixtures/artifacts/artifact.zip + code: + runtime: nodejs18 + handler: ${vars.handler} + path: tests/fixtures/artifacts/artifact.zip memory: 512 timeout: 10 environment: diff --git a/src/common/iacHelper.ts b/src/common/iacHelper.ts index fd3b243..ba3c6f7 100644 --- a/src/common/iacHelper.ts +++ b/src/common/iacHelper.ts @@ -29,7 +29,7 @@ export const getFileSource = ( const hash = crypto.createHash('md5').update(fs.readFileSync(filePath)).digest('hex'); const objectKey = `${fcName}/${hash}-${filePath.split('/').pop()}`; - const source = ossDeployment.Source.asset(filePath, {}, `${fcName}/${hash}-`); + const source = ossDeployment.Source.asset(filePath, { deployTime: true }, `${fcName}/${hash}-`); return { source, objectKey }; }; diff --git a/src/parser/eventParser.ts b/src/parser/eventParser.ts index 2144f7e..6215302 100644 --- a/src/parser/eventParser.ts +++ b/src/parser/eventParser.ts @@ -9,5 +9,6 @@ export const parseEvent = (events: { [key: string]: EventRaw }): Array event.type === EventTypes.API_GATEWAY); - if (apiGateway?.length) { + if (!apiGateway?.length) return; + + apiGateway.forEach((event) => { const gatewayAccessRole = new ram.RosRole( scope, - replaceReference(`${service}_role`, context), + replaceReference(`${event.key}_role`, context), { - roleName: replaceReference(`${service}-gateway-access-role`, context), + roleName: replaceReference(`${service}-${event.name}-agw-access-role`, context), description: replaceReference(`${service} role`, context), assumeRolePolicyDocument: { version: '1', @@ -37,7 +40,7 @@ export const resolveEvents = ( }, policies: [ { - policyName: replaceReference(`${service}-policy`, context), + policyName: replaceReference(`${service}-${event.name}-policy`, context), policyDocument: { version: '1', statement: [ @@ -66,64 +69,75 @@ export const resolveEvents = ( true, ); - // new agw.RosCustomDomain( - // this, - // 'customDomain', - // { - // domainName: 'example.com', - // certificateName: 'example.com', - // certificateBody: 'example.com', - // certificatePrivateKey: 'example.com', - // groupId: apiGatewayGroup.attrGroupId, - // }, - // true, - // ); + if (event.domain) { + const dnsRecordRosId = `${event.key}_custom_domain_record_${encodeBase64ForRosId(event.domain.domain_name)}`; + const { domainName, rr } = splitDomain(event.domain?.domain_name); - apiGateway.forEach((event) => { - event.triggers.forEach((trigger) => { - const key = encodeBase64ForRosId( - replaceReference(`${trigger.method}_${trigger.path}`, context), - ); + new dns.DomainRecord(scope, dnsRecordRosId, { + domainName, + rr, + type: 'CNAME', + value: apiGatewayGroup.attrSubDomain, + }); - const api = new agw.RosApi( - scope, - `${event.key}_api_${key}`, - { - apiName: replaceReference(`${event.name}_api_${key}`, context), - groupId: apiGatewayGroup.attrGroupId, - visibility: 'PRIVATE', - authType: 'ANONYMOUS', - requestConfig: { - requestProtocol: 'HTTP', - requestHttpMethod: replaceReference(trigger.method, context), - requestPath: replaceReference(trigger.path, context), - requestMode: 'PASSTHROUGH', - }, - serviceConfig: { - serviceProtocol: 'FunctionCompute', - functionComputeConfig: { - fcRegionId: context.region, - functionName: replaceReference(trigger.backend, context), - roleArn: gatewayAccessRole.attrArn, - fcVersion: '3.0', - method: replaceReference(trigger.method, context), - }, + const agwCustomDomain = new agw.RosCustomDomain( + scope, + `${event.key}_custom_domain_${encodeBase64ForRosId(event.domain.domain_name)}`, + { + groupId: apiGatewayGroup.attrGroupId, + domainName: event.domain.domain_name, + certificateName: event.domain.certificate_name, + certificateBody: event.domain.certificate_body, + certificatePrivateKey: event.domain.certificate_private_key, + }, + true, + ); + agwCustomDomain.addRosDependency(dnsRecordRosId); + } + + event.triggers.forEach((trigger) => { + const key = encodeBase64ForRosId( + replaceReference(`${trigger.method}_${trigger.path}`, context), + ); + + const api = new agw.RosApi( + scope, + `${event.key}_api_${key}`, + { + apiName: replaceReference(`${event.name}_api_${key}`, context), + groupId: apiGatewayGroup.attrGroupId, + visibility: 'PRIVATE', + authType: 'ANONYMOUS', + requestConfig: { + requestProtocol: 'HTTP', + requestHttpMethod: replaceReference(trigger.method, context), + requestPath: replaceReference(trigger.path, context), + requestMode: 'PASSTHROUGH', + }, + serviceConfig: { + serviceProtocol: 'FunctionCompute', + functionComputeConfig: { + fcRegionId: context.region, + functionName: replaceReference(trigger.backend, context), + roleArn: gatewayAccessRole.attrArn, + fcVersion: '3.0', + method: replaceReference(trigger.method, context), }, - resultSample: 'ServerlessInsight resultSample', - resultType: 'PASSTHROUGH', - tags: replaceReference(tags, context), }, - true, - ); - api.addDependsOn(apiGatewayGroup); + resultSample: 'ServerlessInsight resultSample', + resultType: 'PASSTHROUGH', + tags: replaceReference(tags, context), + }, + true, + ); + api.addDependsOn(apiGatewayGroup); - new agw.Deployment(scope, `${service}_deployment`, { - apiId: api.attrApiId, - groupId: apiGatewayGroup.attrGroupId, - stageName: 'RELEASE', - description: `${service} Api Gateway deployment`, - }); + new agw.Deployment(scope, `${service}_deployment`, { + apiId: api.attrApiId, + groupId: apiGatewayGroup.attrGroupId, + stageName: 'RELEASE', + description: `${service} Api Gateway deployment`, }); }); - } + }); }; diff --git a/src/types/domains/event.ts b/src/types/domains/event.ts index eff08e4..a006eb5 100644 --- a/src/types/domains/event.ts +++ b/src/types/domains/event.ts @@ -10,6 +10,12 @@ export type EventRaw = { path: string; backend: string; }>; + domain?: { + domain_name: string; + certificate_name?: string; + certificate_body?: string; + certificate_private_key?: string; + }; }; export type EventDomain = { diff --git a/src/validator/eventSchema.ts b/src/validator/eventSchema.ts index 98b4558..22875b3 100644 --- a/src/validator/eventSchema.ts +++ b/src/validator/eventSchema.ts @@ -16,20 +16,16 @@ export const eventSchema = { required: ['method', 'path', 'backend'], }, }, - custom_domain: { + domain: { type: 'object', + additionalProperties: false, + required: ['domain_name'], properties: { domain_name: { type: 'string' }, certificate_name: { type: 'string' }, certificate_body: { type: 'string' }, certificate_private_key: { type: 'string' }, }, - required: [ - 'domain_name', - 'certificate_name', - 'certificate_body', - 'certificate_private_key', - ], }, }, required: ['name', 'type', 'triggers'], diff --git a/tests/common/iacHelper.test.ts b/tests/common/iacHelper.test.ts index a776c90..b4ff650 100644 --- a/tests/common/iacHelper.test.ts +++ b/tests/common/iacHelper.test.ts @@ -18,7 +18,7 @@ describe('Unit test for iacHelper', () => { getFileSource(fcName, location); expect(ossDeployment.Source.asset).toHaveBeenCalledWith( `${process.cwd()}/${location}`, - {}, + { deployTime: true }, `${fcName}/50861cd99a3a678356030f5f189300af-`, ); }); diff --git a/tests/fixtures/deployFixture.ts b/tests/fixtures/deployFixture.ts index 6b67326..92cb257 100644 --- a/tests/fixtures/deployFixture.ts +++ b/tests/fixtures/deployFixture.ts @@ -41,6 +41,9 @@ export const oneFcOneGatewayIac = { type: 'API_GATEWAY', key: 'gateway_event', name: 'gateway_event', + domain: { + domain_name: 'api.my-demo-service.com', + }, triggers: [ { method: 'GET', @@ -108,7 +111,7 @@ export const oneFcOneGatewayRos = { }, Method: 'GET', RoleArn: { - 'Fn::GetAtt': ['my-demo-service_role', 'Arn'], + 'Fn::GetAtt': ['gateway_event_role', 'Arn'], }, }, ServiceProtocol: 'FunctionCompute', @@ -123,6 +126,62 @@ export const oneFcOneGatewayRos = { }, Type: 'ALIYUN::ApiGateway::Api', }, + gateway_event_custom_domain_YXBpLm15LWRlbW8tc2VydmljZS5jb20: { + DependsOn: ['gateway_event_custom_domain_record_YXBpLm15LWRlbW8tc2VydmljZS5jb20'], + Properties: { + DomainName: 'api.my-demo-service.com', + GroupId: { + 'Fn::GetAtt': ['my-demo-service_apigroup', 'GroupId'], + }, + }, + Type: 'ALIYUN::ApiGateway::CustomDomain', + }, + gateway_event_custom_domain_record_YXBpLm15LWRlbW8tc2VydmljZS5jb20: { + Properties: { + DomainName: 'my-demo-service.com', + RR: 'api', + TTL: 600, + Type: 'CNAME', + Value: { + 'Fn::GetAtt': ['my-demo-service_apigroup', 'SubDomain'], + }, + }, + Type: 'ALIYUN::DNS::DomainRecord', + }, + gateway_event_role: { + Properties: { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: ['apigateway.aliyuncs.com'], + }, + }, + ], + Version: '1', + }, + Description: 'my-demo-service role', + Policies: [ + { + PolicyDocument: { + Statement: [ + { + Action: ['fc:InvokeFunction'], + Effect: 'Allow', + Resource: ['*'], + }, + ], + Version: '1', + }, + PolicyName: 'my-demo-service-gateway_event-policy', + }, + ], + RoleName: 'my-demo-service-gateway_event-agw-access-role', + }, + Type: 'ALIYUN::RAM::Role', + }, hello_fn: { DependsOn: [ 'my-demo-service_sls', @@ -179,40 +238,6 @@ export const oneFcOneGatewayRos = { }, Type: 'ALIYUN::ApiGateway::Deployment', }, - 'my-demo-service_role': { - Properties: { - AssumeRolePolicyDocument: { - Statement: [ - { - Action: 'sts:AssumeRole', - Effect: 'Allow', - Principal: { - Service: ['apigateway.aliyuncs.com'], - }, - }, - ], - Version: '1', - }, - Description: 'my-demo-service role', - Policies: [ - { - PolicyDocument: { - Statement: [ - { - Action: ['fc:InvokeFunction'], - Effect: 'Allow', - Resource: ['*'], - }, - ], - Version: '1', - }, - PolicyName: 'my-demo-service-policy', - }, - ], - RoleName: 'my-demo-service-gateway-access-role', - }, - Type: 'ALIYUN::RAM::Role', - }, 'my-demo-service_sls': { Properties: { Name: 'my-demo-service-sls', @@ -303,7 +328,7 @@ export const referredServiceRos = { }, Method: 'GET', RoleArn: { - 'Fn::GetAtt': ['my-demo-service-dev_role', 'Arn'], + 'Fn::GetAtt': ['gateway_event_role', 'Arn'], }, }, ServiceProtocol: 'FunctionCompute', @@ -318,6 +343,62 @@ export const referredServiceRos = { }, Type: 'ALIYUN::ApiGateway::Api', }, + gateway_event_custom_domain_YXBpLm15LWRlbW8tc2VydmljZS5jb20: { + DependsOn: ['gateway_event_custom_domain_record_YXBpLm15LWRlbW8tc2VydmljZS5jb20'], + Properties: { + DomainName: 'api.my-demo-service.com', + GroupId: { + 'Fn::GetAtt': ['my-demo-service-dev_apigroup', 'GroupId'], + }, + }, + Type: 'ALIYUN::ApiGateway::CustomDomain', + }, + gateway_event_custom_domain_record_YXBpLm15LWRlbW8tc2VydmljZS5jb20: { + Properties: { + DomainName: 'my-demo-service.com', + RR: 'api', + TTL: 600, + Type: 'CNAME', + Value: { + 'Fn::GetAtt': ['my-demo-service-dev_apigroup', 'SubDomain'], + }, + }, + Type: 'ALIYUN::DNS::DomainRecord', + }, + gateway_event_role: { + Properties: { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: ['apigateway.aliyuncs.com'], + }, + }, + ], + Version: '1', + }, + Description: 'my-demo-service-dev role', + Policies: [ + { + PolicyDocument: { + Statement: [ + { + Action: ['fc:InvokeFunction'], + Effect: 'Allow', + Resource: ['*'], + }, + ], + Version: '1', + }, + PolicyName: 'my-demo-service-dev-gateway_event-policy', + }, + ], + RoleName: 'my-demo-service-dev-gateway_event-agw-access-role', + }, + Type: 'ALIYUN::RAM::Role', + }, hello_fn: { DependsOn: [ 'my-demo-service-dev_sls', @@ -374,40 +455,6 @@ export const referredServiceRos = { }, Type: 'ALIYUN::ApiGateway::Deployment', }, - 'my-demo-service-dev_role': { - Properties: { - AssumeRolePolicyDocument: { - Statement: [ - { - Action: 'sts:AssumeRole', - Effect: 'Allow', - Principal: { - Service: ['apigateway.aliyuncs.com'], - }, - }, - ], - Version: '1', - }, - Description: 'my-demo-service-dev role', - Policies: [ - { - PolicyDocument: { - Statement: [ - { - Action: ['fc:InvokeFunction'], - Effect: 'Allow', - Resource: ['*'], - }, - ], - Version: '1', - }, - PolicyName: 'my-demo-service-dev-policy', - }, - ], - RoleName: 'my-demo-service-dev-gateway-access-role', - }, - Type: 'ALIYUN::RAM::Role', - }, 'my-demo-service-dev_sls': { Properties: { Name: 'my-demo-service-dev-sls', @@ -1226,7 +1273,7 @@ export const largeCodeRos = { }, Method: 'GET', RoleArn: { - 'Fn::GetAtt': ['my-demo-service_role', 'Arn'], + 'Fn::GetAtt': ['gateway_event_role', 'Arn'], }, }, ServiceProtocol: 'FunctionCompute', @@ -1323,7 +1370,29 @@ export const largeCodeRos = { }, Type: 'ALIYUN::ApiGateway::Deployment', }, - 'my-demo-service_role': { + gateway_event_custom_domain_YXBpLm15LWRlbW8tc2VydmljZS5jb20: { + DependsOn: ['gateway_event_custom_domain_record_YXBpLm15LWRlbW8tc2VydmljZS5jb20'], + Properties: { + DomainName: 'api.my-demo-service.com', + GroupId: { + 'Fn::GetAtt': ['my-demo-service_apigroup', 'GroupId'], + }, + }, + Type: 'ALIYUN::ApiGateway::CustomDomain', + }, + gateway_event_custom_domain_record_YXBpLm15LWRlbW8tc2VydmljZS5jb20: { + Properties: { + DomainName: 'my-demo-service.com', + RR: 'api', + TTL: 600, + Type: 'CNAME', + Value: { + 'Fn::GetAtt': ['my-demo-service_apigroup', 'SubDomain'], + }, + }, + Type: 'ALIYUN::DNS::DomainRecord', + }, + gateway_event_role: { Properties: { AssumeRolePolicyDocument: { Statement: [ @@ -1350,10 +1419,10 @@ export const largeCodeRos = { ], Version: '1', }, - PolicyName: 'my-demo-service-policy', + PolicyName: 'my-demo-service-gateway_event-policy', }, ], - RoleName: 'my-demo-service-gateway-access-role', + RoleName: 'my-demo-service-gateway_event-agw-access-role', }, Type: 'ALIYUN::RAM::Role', }, @@ -1640,6 +1709,18 @@ export const bucketWithWebsiteRos = { }, Type: 'ALIYUN::OSS::Domain', }, + my_bucket_custom_domain_record_bXktYnVja2V0LmNvbQ: { + Properties: { + DomainName: 'my-bucket.com', + RR: '@', + TTL: 600, + Type: 'CNAME', + Value: { + 'Fn::GetAtt': ['my_bucket', 'InternalDomainName'], + }, + }, + Type: 'ALIYUN::DNS::DomainRecord', + }, si_auto_my_bucket_bucket_code_deployment: { Properties: { Parameters: { diff --git a/tests/fixtures/templateFixture.ts b/tests/fixtures/templateFixture.ts index b3f1542..611293d 100644 --- a/tests/fixtures/templateFixture.ts +++ b/tests/fixtures/templateFixture.ts @@ -123,7 +123,7 @@ export const jsonTemplate = { }, DependsOn: ['insight-poc_sls', 'insight-poc_sls_logstore', 'insight-poc_sls_index'], }, - 'insight-poc_role': { + gateway_event_role: { Type: 'ALIYUN::RAM::Role', Properties: { AssumeRolePolicyDocument: { @@ -138,11 +138,11 @@ export const jsonTemplate = { }, ], }, - RoleName: 'insight-poc-gateway-access-role', + RoleName: 'insight-poc-insight-poc-gateway-agw-access-role', Description: 'insight-poc role', Policies: [ { - PolicyName: 'insight-poc-policy', + PolicyName: 'insight-poc-insight-poc-gateway-policy', PolicyDocument: { Version: '1', Statement: [ @@ -193,7 +193,7 @@ export const jsonTemplate = { Method: 'GET', FcRegionId: 'cn-hangzhou', RoleArn: { - 'Fn::GetAtt': ['insight-poc_role', 'Arn'], + 'Fn::GetAtt': ['gateway_event_role', 'Arn'], }, FunctionName: { 'Fn::GetAtt': ['insight_poc_fn', 'FunctionName'], @@ -335,7 +335,7 @@ Resources: - insight-poc_sls - insight-poc_sls_logstore - insight-poc_sls_index - insight-poc_role: + gateway_event_role: Type: ALIYUN::RAM::Role Properties: AssumeRolePolicyDocument: @@ -346,10 +346,10 @@ Resources: Principal: Service: - apigateway.aliyuncs.com - RoleName: insight-poc-gateway-access-role + RoleName: insight-poc-insight-poc-gateway-agw-access-role Description: insight-poc role Policies: - - PolicyName: insight-poc-policy + - PolicyName: insight-poc-insight-poc-gateway-policy PolicyDocument: Version: "1" Statement: @@ -388,7 +388,7 @@ Resources: FcRegionId: cn-hangzhou RoleArn: Fn::GetAtt: - - insight-poc_role + - gateway_event_role - Arn FunctionName: Fn::GetAtt: diff --git a/tests/stack/deploy.test.ts b/tests/stack/deploy.test.ts index 4377885..4fbd42a 100644 --- a/tests/stack/deploy.test.ts +++ b/tests/stack/deploy.test.ts @@ -53,22 +53,6 @@ describe('Unit tests for stack deployment', () => { mockedGetIamInfo.mockRestore(); }); - it('should deploy generated stack when iac is valid', async () => { - const stackName = 'my-demo-stack'; - mockedRosStackDeploy.mockResolvedValueOnce(stackName); - - await deployStack(stackName, oneFcOneGatewayIac, { stackName } as ActionContext); - - expect(mockedRosStackDeploy).toHaveBeenCalledTimes(2); - expect(mockedRosStackDeploy.mock.calls[1]).toEqual([ - stackName, - oneFcOneGatewayRos, - { - stackName, - }, - ]); - }); - it('should deploy generated stack when minimum fields provided', async () => { const stackName = 'my-demo-minimum-stack'; mockedRosStackDeploy.mockResolvedValueOnce(stackName); @@ -170,6 +154,22 @@ describe('Unit tests for stack deployment', () => { ]); }); + describe('unit test for deploy of events', () => { + it('should deploy event with custom domain specified when domain is provided', async () => { + const stackName = 'my-event-stack-with-custom-domain'; + mockedRosStackDeploy.mockResolvedValue(stackName); + + await deployStack(stackName, oneFcOneGatewayIac, { stackName } as ActionContext); + + expect(mockedRosStackDeploy).toHaveBeenCalledTimes(2); + expect(mockedRosStackDeploy.mock.calls[1]).toEqual([ + stackName, + oneFcOneGatewayRos, + { stackName }, + ]); + }); + }); + describe('unit test for deploy of databases', () => { it('should deploy elasticsearch serverless when database minimum fields provided', async () => { const stackName = 'my-demo-es-serverless-stack';