Skip to content

Commit 782dc4e

Browse files
committed
feat: evaluate service reference value
Signed-off-by: seven <zilisheng1996@gmail.com>
1 parent 6974dd8 commit 782dc4e

File tree

7 files changed

+197
-24
lines changed

7 files changed

+197
-24
lines changed

src/common/actionContext.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ export const constructActionContext = (config?: {
2424
const projectRoot = path.resolve(process.cwd());
2525
return config?.location
2626
? path.resolve(projectRoot, config?.location)
27-
: path.resolve(projectRoot, 'serverless-insight.yml');
27+
: path.resolve(projectRoot, 'serverlessinsight.yml') ||
28+
path.resolve(projectRoot, 'serverlessInsight.yml') ||
29+
path.resolve(projectRoot, 'ServerlessInsight.yml') ||
30+
path.resolve(projectRoot, 'serverless-insight.yml');
2831
})(),
2932
parameters: Object.entries(config?.parameters ?? {}).map(([key, value]) => ({ key, value })),
3033
};

src/common/iacHelper.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import path from 'node:path';
22
import fs from 'node:fs';
33
import * as ros from '@alicloud/ros-cdk-core';
4+
import { ServerlessIac } from '../types';
45

56
export const resolveCode = (location: string): string => {
67
const filePath = path.resolve(process.cwd(), location);
@@ -53,3 +54,9 @@ export const replaceReference = <T>(value: T, stage: string): T => {
5354

5455
return value;
5556
};
57+
58+
export const evalRefValue = (value: string, iac: ServerlessIac, stage: string): string => {
59+
const containsStage = value.match(/\$\{stage}/);
60+
61+
return containsStage ? value.replace(/\$\{stage}/g, stage) : value;
62+
};

src/common/rosClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ const updateStack = async (stackId: string, templateBody: unknown, context: Acti
6464
});
6565
try {
6666
const response = await client.updateStack(updateStackRequest);
67-
logger.info(`更新中,资源栈ID:${response.body?.stackId}`);
67+
logger.info(`更新中,资源栈ID: ${response.body?.stackId}`);
6868
// wait for stack update complete
6969
return await getStackActionResult(response.body?.stackId || '', context.region);
7070
} catch (err) {

src/stack/iacStack.ts

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,41 @@ import { ActionContext, EventTypes, ServerlessIac } from '../types';
44
import * as fc from '@alicloud/ros-cdk-fc3';
55
import * as ram from '@alicloud/ros-cdk-ram';
66
import * as agw from '@alicloud/ros-cdk-apigateway';
7-
import { replaceReference, resolveCode } from '../common';
7+
import { evalRefValue, replaceReference, resolveCode } from '../common';
88

99
export class IacStack extends ros.Stack {
10+
private service: string;
11+
1012
constructor(scope: ros.Construct, iac: ServerlessIac, context: ActionContext) {
11-
super(scope, iac.service, {
13+
super(scope, evalRefValue(iac.service, iac, context.stage), {
1214
stackName: context.stackName,
13-
tags: iac.tags.reduce((acc: { [key: string]: string }, tag) => {
15+
tags: iac.tags?.reduce((acc: { [key: string]: string }, tag) => {
1416
acc[tag.key] = replaceReference(tag.value, context.stage);
1517
return acc;
1618
}, {}),
1719
});
20+
this.service = evalRefValue(iac.service, iac, context.stage);
1821

1922
// Define Parameters
20-
Object.entries(iac.vars).map(
21-
([key, value]) =>
22-
new ros.RosParameter(this, key, {
23-
type: RosParameterType.STRING,
24-
defaultValue: value,
25-
}),
26-
);
23+
if (iac.vars) {
24+
Object.entries(iac.vars).map(
25+
([key, value]) =>
26+
new ros.RosParameter(this, key, {
27+
type: RosParameterType.STRING,
28+
defaultValue: value,
29+
}),
30+
);
31+
}
2732

2833
// Define Mappings
29-
new ros.RosMapping(this, 'stages', { mapping: replaceReference(iac.stages, context.stage) });
34+
if (iac.stages) {
35+
new ros.RosMapping(this, 'stages', { mapping: replaceReference(iac.stages, context.stage) });
36+
}
3037

3138
new ros.RosInfo(
3239
this,
3340
ros.RosInfo.description,
34-
replaceReference(`${iac.service} stack`, context.stage),
41+
replaceReference(`${this.service} stack`, context.stage),
3542
);
3643

3744
iac.functions.forEach((fnc) => {
@@ -57,10 +64,10 @@ export class IacStack extends ros.Stack {
5764
if (apiGateway?.length) {
5865
const gatewayAccessRole = new ram.RosRole(
5966
this,
60-
replaceReference(`${iac.service}_role`, context.stage),
67+
replaceReference(`${this.service}_role`, context.stage),
6168
{
62-
roleName: replaceReference(`${iac.service}-gateway-access-role`, context.stage),
63-
description: replaceReference(`${iac.service} role`, context.stage),
69+
roleName: replaceReference(`${this.service}-gateway-access-role`, context.stage),
70+
description: replaceReference(`${this.service} role`, context.stage),
6471
assumeRolePolicyDocument: {
6572
version: '1',
6673
statement: [
@@ -75,7 +82,7 @@ export class IacStack extends ros.Stack {
7582
},
7683
policies: [
7784
{
78-
policyName: replaceReference(`${iac.service}-policy`, context.stage),
85+
policyName: replaceReference(`${this.service}-policy`, context.stage),
7986
policyDocument: {
8087
version: '1',
8188
statement: [
@@ -95,9 +102,9 @@ export class IacStack extends ros.Stack {
95102

96103
const apiGatewayGroup = new agw.RosGroup(
97104
this,
98-
replaceReference(`${iac.service}_apigroup`, context.stage),
105+
replaceReference(`${this.service}_apigroup`, context.stage),
99106
{
100-
groupName: replaceReference(`${iac.service}_apigroup`, context.stage),
107+
groupName: replaceReference(`${this.service}_apigroup`, context.stage),
101108
tags: replaceReference(iac.tags, context.stage),
102109
},
103110
true,
@@ -137,7 +144,7 @@ export class IacStack extends ros.Stack {
137144
serviceProtocol: 'FunctionCompute',
138145
functionComputeConfig: {
139146
fcRegionId: context.region,
140-
functionName: replaceReference(trigger.backend, trigger.backend),
147+
functionName: replaceReference(trigger.backend, context.stage),
141148
roleArn: gatewayAccessRole.attrArn,
142149
fcVersion: '3.0',
143150
},

src/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@ export type RawServerlessIac = {
6060
export type ServerlessIac = {
6161
version: string;
6262
provider: string;
63-
vars: Vars;
64-
stages: Stages;
6563
service: string;
66-
tags: Array<{ key: string; value: string }>;
64+
vars?: Vars;
65+
stages?: Stages;
66+
tags?: Array<{ key: string; value: string }>;
6767
functions: Array<IacFunction>;
6868
events?: Array<Event>;
6969
};

tests/fixtures/deployFixture.ts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ServerlessIac } from '../../src/types';
2+
import { cloneDeep, set } from 'lodash';
23

34
export const oneFcOneGatewayIac = {
45
service: 'my-demo-service',
@@ -139,6 +140,134 @@ export const oneFcOneGatewayRos = {
139140
},
140141
},
141142
};
143+
export const referredServiceIac = set(
144+
cloneDeep(oneFcOneGatewayIac),
145+
'service',
146+
'my-demo-service-${stage}',
147+
);
148+
149+
export const referredServiceRos = {
150+
Description: 'my-demo-service-dev stack',
151+
Mappings: {
152+
stages: {
153+
dev: {
154+
account_id: { Ref: 'account_id' },
155+
region: { Ref: 'region' },
156+
},
157+
},
158+
},
159+
Metadata: { 'ALIYUN::ROS::Interface': { TemplateTags: ['Create by ROS CDK'] } },
160+
Parameters: {
161+
account_id: { Default: 1234567890, Type: 'String' },
162+
region: { Default: 'cn-hangzhou', Type: 'String' },
163+
},
164+
ROSTemplateFormatVersion: '2015-09-01',
165+
Resources: {
166+
gateway_event_api_get__api_hello: {
167+
Properties: {
168+
ApiName: 'gateway_event_api_get__api_hello',
169+
GroupId: { 'Fn::GetAtt': ['my-demo-service-dev_apigroup', 'GroupId'] },
170+
RequestConfig: {
171+
RequestHttpMethod: 'GET',
172+
RequestMode: 'PASSTHROUGH',
173+
RequestPath: '/api/hello',
174+
RequestProtocol: 'HTTP',
175+
},
176+
ResultSample: 'ServerlessInsight resultSample',
177+
ResultType: 'JSON',
178+
ServiceConfig: {
179+
FunctionComputeConfig: {
180+
FunctionName: { 'Fn::GetAtt': ['hello_fn', 'FunctionName'] },
181+
RoleArn: { 'Fn::GetAtt': ['my-demo-service-dev_role', 'Arn'] },
182+
FcVersion: '3.0',
183+
},
184+
ServiceProtocol: 'FunctionCompute',
185+
},
186+
Tags: [{ Key: 'owner', Value: 'geek-fun' }],
187+
Visibility: 'PRIVATE',
188+
},
189+
Type: 'ALIYUN::ApiGateway::Api',
190+
},
191+
hello_fn: {
192+
Properties: {
193+
Code: { ZipFile: 'resolved-code' },
194+
EnvironmentVariables: { NODE_ENV: 'production' },
195+
FunctionName: 'hello_fn',
196+
Handler: 'index.handler',
197+
MemorySize: 128,
198+
Runtime: 'nodejs18',
199+
Timeout: 10,
200+
},
201+
Type: 'ALIYUN::FC3::Function',
202+
},
203+
'my-demo-service-dev_apigroup': {
204+
Properties: {
205+
GroupName: 'my-demo-service-dev_apigroup',
206+
Tags: [{ Key: 'owner', Value: 'geek-fun' }],
207+
},
208+
Type: 'ALIYUN::ApiGateway::Group',
209+
},
210+
'my-demo-service-dev_role': {
211+
Properties: {
212+
AssumeRolePolicyDocument: {
213+
Statement: [
214+
{
215+
Action: 'sts:AssumeRole',
216+
Effect: 'Allow',
217+
Principal: { Service: ['apigateway.aliyuncs.com'] },
218+
},
219+
],
220+
Version: '1',
221+
},
222+
Description: 'my-demo-service-dev role',
223+
Policies: [
224+
{
225+
PolicyDocument: {
226+
Statement: [{ Action: ['fc:InvokeFunction'], Effect: 'Allow', Resource: ['*'] }],
227+
Version: '1',
228+
},
229+
PolicyName: 'my-demo-service-dev-policy',
230+
},
231+
],
232+
RoleName: 'my-demo-service-dev-gateway-access-role',
233+
},
234+
Type: 'ALIYUN::RAM::Role',
235+
},
236+
},
237+
};
238+
239+
export const minimumIac = {
240+
service: 'my-demo-minimum-service',
241+
version: '0.0.1',
242+
provider: 'aliyun',
243+
244+
functions: [
245+
{
246+
key: 'hello_fn',
247+
name: 'hello_fn',
248+
runtime: 'nodejs18',
249+
handler: 'index.handler',
250+
code: 'artifact.zip',
251+
},
252+
],
253+
} as ServerlessIac;
254+
255+
export const minimumRos = {
256+
Description: 'my-demo-minimum-service stack',
257+
Metadata: { 'ALIYUN::ROS::Interface': { TemplateTags: ['Create by ROS CDK'] } },
258+
ROSTemplateFormatVersion: '2015-09-01',
259+
Resources: {
260+
hello_fn: {
261+
Properties: {
262+
Code: { ZipFile: 'resolved-code' },
263+
FunctionName: 'hello_fn',
264+
Handler: 'index.handler',
265+
Runtime: 'nodejs18',
266+
},
267+
Type: 'ALIYUN::FC3::Function',
268+
},
269+
},
270+
};
142271

143272
export const oneFcIac = {
144273
service: 'my-demo-service',

tests/stack/deploy.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import { deployStack } from '../../src/stack';
22
import { ActionContext } from '../../src/types';
33
import {
4+
minimumIac,
5+
minimumRos,
46
oneFcIac,
57
oneFcIacWithStage,
68
oneFcOneGatewayIac,
79
oneFcOneGatewayRos,
810
oneFcRos,
911
oneFcWithStageRos,
12+
referredServiceIac,
13+
referredServiceRos,
1014
} from '../fixtures/deployFixture';
1115
import { cloneDeep, set } from 'lodash';
1216

@@ -34,6 +38,16 @@ describe('Unit tests for stack deployment', () => {
3438
expect(mockedRosStackDeploy).toHaveBeenCalledWith(stackName, oneFcOneGatewayRos, { stackName });
3539
});
3640

41+
it('should deploy generated stack when minimum fields provided', async () => {
42+
const stackName = 'my-demo-minimum-stack';
43+
mockedRosStackDeploy.mockResolvedValueOnce(stackName);
44+
45+
await deployStack(stackName, minimumIac, { stackName } as ActionContext);
46+
47+
expect(mockedRosStackDeploy).toHaveBeenCalledTimes(1);
48+
expect(mockedRosStackDeploy).toHaveBeenCalledWith(stackName, minimumRos, { stackName });
49+
});
50+
3751
it('should deploy generated stack when only one FC specified', async () => {
3852
const stackName = 'my-demo-stack-fc-only';
3953
mockedRosStackDeploy.mockResolvedValueOnce(stackName);
@@ -75,4 +89,17 @@ describe('Unit tests for stack deployment', () => {
7589
options,
7690
);
7791
});
92+
it('should evaluate service name as pure string when it reference ${stage}', async () => {
93+
const options = { stackName: 'my-demo-stack-fc-with-stage-1', stage: 'dev' };
94+
mockedRosStackDeploy.mockResolvedValueOnce(options.stackName);
95+
96+
await deployStack(options.stackName, referredServiceIac, options as ActionContext);
97+
98+
expect(mockedRosStackDeploy).toHaveBeenCalledTimes(1);
99+
expect(mockedRosStackDeploy).toHaveBeenCalledWith(
100+
options.stackName,
101+
referredServiceRos,
102+
options,
103+
);
104+
});
78105
});

0 commit comments

Comments
 (0)