Skip to content

Commit cdb4517

Browse files
authored
fix: upload code zip file to bucket issue (#22)
fix: upload code zip file to bucket issue - use oss_deployment to upload code to the oss bucket for code size > 15MB Refs: #5 --------- Signed-off-by: seven <zilisheng1996@gmail.com>
1 parent bba9248 commit cdb4517

File tree

9 files changed

+1459
-47
lines changed

9 files changed

+1459
-47
lines changed

package-lock.json

Lines changed: 1318 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"@alicloud/ros-cdk-ram": "^1.4.0",
6060
"@alicloud/ros20190910": "^3.5.2",
6161
"ajv": "^8.17.1",
62+
"ali-oss": "^6.22.0",
6263
"chalk": "^5.3.0",
6364
"commander": "^12.1.0",
6465
"i18n": "^0.15.1",
@@ -68,6 +69,7 @@
6869
"yaml": "^2.6.1"
6970
},
7071
"devDependencies": {
72+
"@types/ali-oss": "^6.16.11",
7173
"@types/i18n": "^0.13.12",
7274
"@types/jest": "^29.5.14",
7375
"@types/lodash": "^4.17.13",

src/common/rosClient.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ import ROS20190910, {
1010
UpdateStackRequestParameters,
1111
} from '@alicloud/ros20190910';
1212
import { Config } from '@alicloud/openapi-client';
13-
import { ActionContext } from '../types';
13+
import OSS from 'ali-oss';
14+
import { ActionContext, CdkAssets } from '../types';
1415
import { logger } from './logger';
1516
import { lang } from '../lang';
17+
import path from 'node:path';
18+
import { get, isEmpty } from 'lodash';
1619

1720
const client = new ROS20190910(
1821
new Config({
@@ -196,3 +199,68 @@ export const rosStackDelete = async ({
196199
throw new Error(JSON.stringify(err));
197200
}
198201
};
202+
203+
const ensureBucketExits = async (bucketName: string, ossClient: OSS) =>
204+
await ossClient.getBucketInfo(bucketName).catch((err) => {
205+
if (err.code === 'NoSuchBucket') {
206+
logger.info(`Bucket: ${bucketName} not exists, creating...`);
207+
return ossClient.putBucket(bucketName, {
208+
storageClass: 'Standard',
209+
acl: 'private',
210+
dataRedundancyType: 'LRS',
211+
} as OSS.PutBucketOptions);
212+
} else {
213+
throw err;
214+
}
215+
});
216+
217+
const getZipAssets = ({ files, rootPath }: CdkAssets, region: string) => {
218+
const zipAssets = Object.entries(files)
219+
.filter(([, fileItem]) => fileItem.source.path.endsWith('zip'))
220+
.map(([, fileItem]) => ({
221+
bucketName: get(
222+
fileItem,
223+
'destinations.current_account-current_region.bucketName',
224+
'',
225+
).replace('${ALIYUN::Region}', region),
226+
source: `${rootPath}/${fileItem.source.path}`,
227+
objectKey: get(fileItem, 'destinations.current_account-current_region.objectKey'),
228+
}));
229+
230+
return !isEmpty(zipAssets) ? zipAssets : undefined;
231+
};
232+
233+
export const publishAssets = async (assets: CdkAssets, context: ActionContext) => {
234+
const zipAssets = getZipAssets(assets, context.region);
235+
236+
if (!zipAssets) {
237+
logger.info('No assets to publish, skipped!');
238+
return;
239+
}
240+
241+
const bucketName = zipAssets[0].bucketName;
242+
243+
const client = new OSS({
244+
region: `oss-${context.region}`,
245+
accessKeyId: context.accessKeyId,
246+
accessKeySecret: context.accessKeySecret,
247+
bucket: bucketName,
248+
});
249+
250+
await ensureBucketExits(bucketName, client);
251+
252+
const headers = {
253+
'x-oss-storage-class': 'Standard',
254+
'x-oss-object-acl': 'private',
255+
'x-oss-forbid-overwrite': 'false',
256+
} as OSS.PutObjectOptions;
257+
258+
await Promise.all(
259+
zipAssets.map(async ({ source, objectKey }) => {
260+
await client.put(objectKey, path.normalize(source), { headers });
261+
logger.info(`Upload file: ${source}) to bucket: ${bucketName} successfully!`);
262+
}),
263+
);
264+
265+
return bucketName;
266+
};

src/stack/deploy.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import * as ros from '@alicloud/ros-cdk-core';
2+
import fs from 'node:fs';
23

34
import { ActionContext, ServerlessIac } from '../types';
4-
import { logger, ProviderEnum, rosStackDeploy } from '../common';
5+
import { logger, ProviderEnum, publishAssets, rosStackDeploy } from '../common';
56
import { RosStack } from './rosStack';
67
import { RfsStack } from './rfsStack';
8+
import { get } from 'lodash';
79

810
export const generateRosStackTemplate = (
911
stackName: string,
@@ -14,9 +16,17 @@ export const generateRosStackTemplate = (
1416
new RosStack(app, iac, context);
1517

1618
const assembly = app.synth();
17-
const stackArtifact = assembly.getStackByName(stackName);
1819

19-
return { template: stackArtifact.template };
20+
const { template } = assembly.getStackByName(stackName);
21+
22+
const assetFolderPath = get(assembly.tryGetArtifact(`${stackName}.assets`), 'file', '');
23+
const assetsFileBody = fs.readFileSync(assetFolderPath);
24+
const assets = {
25+
rootPath: assembly.directory,
26+
...JSON.parse(assetsFileBody.toString('utf-8').trim()),
27+
};
28+
29+
return { template, assets };
2030
};
2131

2232
export const generateRfsStackTemplate = (
@@ -37,8 +47,10 @@ export const deployStack = async (
3747
iac: ServerlessIac,
3848
context: ActionContext,
3949
) => {
40-
const { template } = generateRosStackTemplate(stackName, iac, context);
41-
50+
const { template, assets } = generateRosStackTemplate(stackName, iac, context);
51+
logger.info(`Deploying stack, publishing assets...`);
52+
await publishAssets(assets, context);
53+
logger.info(`Assets published! 🎉`);
4254
await rosStackDeploy(stackName, template, context);
4355
logger.info(`Stack deployed! 🎉`);
4456
};

src/stack/rosStack/function.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
replaceReference,
77
resolveCode,
88
} from '../../common';
9-
import { RosFunction } from '@alicloud/ros-cdk-fc3/lib/fc3.generated';
109
import * as fc from '@alicloud/ros-cdk-fc3';
1110
import * as oss from '@alicloud/ros-cdk-oss';
1211
import { isEmpty } from 'lodash';
@@ -49,15 +48,16 @@ export const resolveFunctions = (
4948
{
5049
sources: fileSources!.map(({ source }) => source),
5150
destinationBucket,
52-
timeout: 300,
53-
logMonitoring: false, // 是否开启日志监控,设为false则不开启
51+
timeout: 3000,
52+
logMonitoring: false,
5453
},
5554
true,
5655
);
56+
artifactsDeployment.addDependency(destinationBucket);
5757
}
5858
functions?.forEach((fnc) => {
5959
const storeInBucket = readCodeSize(fnc.code) > CODE_ZIP_SIZE_LIMIT;
60-
let code: RosFunction.CodeProperty = {
60+
let code: fc.RosFunction.CodeProperty = {
6161
zipFile: resolveCode(fnc.code),
6262
};
6363
if (storeInBucket) {
@@ -68,7 +68,7 @@ export const resolveFunctions = (
6868
)?.objectKey,
6969
};
7070
}
71-
const fcn = new fc.RosFunction(
71+
new fc.RosFunction(
7272
scope,
7373
fnc.key,
7474
{
@@ -82,9 +82,5 @@ export const resolveFunctions = (
8282
},
8383
true,
8484
);
85-
if (storeInBucket) {
86-
fcn.addDependsOn(destinationBucket as unknown as ros.RosResource);
87-
fcn.addDependsOn(artifactsDeployment as unknown as ros.RosResource);
88-
}
8985
});
9086
};

src/types/assets.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export type CdkAssets = {
2+
version: string;
3+
rootPath: string;
4+
dockerImages: Record<string, unknown>;
5+
files: {
6+
[key: string]: {
7+
source: {
8+
path: string;
9+
packaging: string;
10+
};
11+
destinations: {
12+
[key: string]: {
13+
bucketName: string;
14+
objectKey: string;
15+
};
16+
};
17+
};
18+
};
19+
};

src/types/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export * from './domains/tag';
1212
export * from './domains/vars';
1313
export * from './domains/context';
1414

15+
export * from './assets';
16+
1517
export type ServerlessIacRaw = {
1618
version: string;
1719
provider: Provider;

tests/fixtures/deployFixture.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -481,10 +481,11 @@ export const largeCodeRos = {
481481
ServiceName: {
482482
'Fn::GetAtt': ['FCServiceFormy-demo-service_artifacts_code_deployment', 'ServiceName'],
483483
},
484-
Timeout: 300,
484+
Timeout: 3000,
485485
},
486486
Type: 'ALIYUN::FC::Function',
487487
},
488+
488489
'FCRoleFormy-demo-service_artifacts_code_deployment': {
489490
Properties: {
490491
AssumeRolePolicyDocument: {
@@ -682,6 +683,7 @@ export const largeCodeRos = {
682683
Type: 'ALIYUN::OSS::Bucket',
683684
},
684685
'my-demo-service_artifacts_code_deployment': {
686+
DependsOn: ['my-demo-service_artifacts_bucket'],
685687
Properties: {
686688
Parameters: {
687689
destinationBucket: {
@@ -690,7 +692,7 @@ export const largeCodeRos = {
690692
retainOnCreate: false,
691693
sources: [
692694
{
693-
bucket: { 'Fn::Sub': expect.stringContaining('assets-${ALIYUN::Region}') },
695+
bucket: { 'Fn::Sub': expect.any(String) },
694696
fileName: 'hello_fn/43cb4c356149762dbe507fc1baede172-large-artifact.zip',
695697
objectKey: '2bfeafed8d3df0d44c235271cdf2aa7d908a3c2757af14a67d33d102847f46fd.zip',
696698
},
@@ -699,7 +701,7 @@ export const largeCodeRos = {
699701
ServiceToken: {
700702
'Fn::GetAtt': ['FCFunctionFormy-demo-service_artifacts_code_deployment', 'ARN'],
701703
},
702-
Timeout: 300,
704+
Timeout: 3000,
703705
},
704706
Type: 'ALIYUN::ROS::CustomResource',
705707
},

tests/stack/deploy.test.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,24 @@ import { cloneDeep, set } from 'lodash';
1919

2020
const mockedRosStackDeploy = jest.fn();
2121
const mockedResolveCode = jest.fn();
22+
const mockedPublishAssets = jest.fn();
23+
2224
jest.mock('../../src/common', () => ({
2325
...jest.requireActual('../../src/common'),
2426
rosStackDeploy: (...args: unknown[]) => mockedRosStackDeploy(...args),
27+
publishAssets: (...args: unknown[]) => mockedPublishAssets(...args),
2528
resolveCode: (path: string) => mockedResolveCode(path),
2629
}));
2730

2831
describe('Unit tests for stack deployment', () => {
2932
beforeEach(() => {
3033
mockedResolveCode.mockReturnValueOnce('resolved-code');
34+
mockedPublishAssets.mockResolvedValueOnce('published-assets-bucket');
3135
});
3236
afterEach(() => {
3337
mockedRosStackDeploy.mockRestore();
3438
mockedResolveCode.mockRestore();
39+
mockedPublishAssets.mockRestore();
3540
});
3641

3742
it('should deploy generated stack when iac is valid', async () => {
@@ -95,6 +100,7 @@ describe('Unit tests for stack deployment', () => {
95100
options,
96101
);
97102
});
103+
98104
it('should evaluate service name as pure string when it reference ${ctx.stage}', async () => {
99105
const options = { stackName: 'my-demo-stack-fc-with-stage-1', stage: 'dev' };
100106
mockedRosStackDeploy.mockResolvedValueOnce(options.stackName);
@@ -109,11 +115,11 @@ describe('Unit tests for stack deployment', () => {
109115
);
110116
});
111117

112-
it('should create bucket and store code artifact to bucket when code size > 15MB', () => {
118+
it('should create bucket and store code artifact to bucket when code size > 15MB', async () => {
113119
const stackName = 'my-large-code-stack';
114120
mockedRosStackDeploy.mockResolvedValueOnce(stackName);
115121

116-
deployStack(
122+
await deployStack(
117123
stackName,
118124
set(
119125
cloneDeep(oneFcOneGatewayIac),
@@ -123,16 +129,27 @@ describe('Unit tests for stack deployment', () => {
123129
{ stackName } as ActionContext,
124130
);
125131

126-
expect(mockedResolveCode).toHaveBeenCalledTimes(1);
132+
expect(mockedPublishAssets).toHaveBeenCalledTimes(1);
133+
expect(mockedRosStackDeploy).toHaveBeenCalledTimes(1);
134+
expect(mockedPublishAssets).toHaveBeenCalledWith(
135+
expect.objectContaining({
136+
dockerImages: {},
137+
files: expect.any(Object),
138+
rootPath: expect.any(String),
139+
version: '7.0.0',
140+
}),
141+
142+
{ stackName },
143+
);
127144
expect(mockedRosStackDeploy).toHaveBeenCalledWith(stackName, largeCodeRos, { stackName });
128145
});
129146

130147
describe('unit test for deploy of databases', () => {
131-
it('should deploy elasticsearch serverless when database minimum fields provided', () => {
148+
it('should deploy elasticsearch serverless when database minimum fields provided', async () => {
132149
const stackName = 'my-demo-es-serverless-stack';
133150
mockedRosStackDeploy.mockResolvedValueOnce(stackName);
134151

135-
deployStack(stackName, esServerlessMinimumIac, { stackName } as ActionContext);
152+
await deployStack(stackName, esServerlessMinimumIac, { stackName } as ActionContext);
136153

137154
expect(mockedRosStackDeploy).toHaveBeenCalledTimes(1);
138155
expect(mockedRosStackDeploy).toHaveBeenCalledWith(stackName, esServerlessMinimumRos, {

0 commit comments

Comments
 (0)