Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: upload code zip file to bucket issue #22

Merged
merged 6 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,342 changes: 1,318 additions & 24 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"@alicloud/ros-cdk-ram": "^1.4.0",
"@alicloud/ros20190910": "^3.5.2",
"ajv": "^8.17.1",
"ali-oss": "^6.22.0",
"chalk": "^5.3.0",
"commander": "^12.1.0",
"i18n": "^0.15.1",
Expand All @@ -68,6 +69,7 @@
"yaml": "^2.6.1"
},
"devDependencies": {
"@types/ali-oss": "^6.16.11",
"@types/i18n": "^0.13.12",
"@types/jest": "^29.5.14",
"@types/lodash": "^4.17.13",
Expand Down
70 changes: 69 additions & 1 deletion src/common/rosClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ import ROS20190910, {
UpdateStackRequestParameters,
} from '@alicloud/ros20190910';
import { Config } from '@alicloud/openapi-client';
import { ActionContext } from '../types';
import OSS from 'ali-oss';
import { ActionContext, CdkAssets } from '../types';
import { logger } from './logger';
import { lang } from '../lang';
import path from 'node:path';
import { get, isEmpty } from 'lodash';

const client = new ROS20190910(
new Config({
Expand Down Expand Up @@ -196,3 +199,68 @@ export const rosStackDelete = async ({
throw new Error(JSON.stringify(err));
}
};

const ensureBucketExits = async (bucketName: string, ossClient: OSS) =>
await ossClient.getBucketInfo(bucketName).catch((err) => {
if (err.code === 'NoSuchBucket') {
logger.info(`Bucket: ${bucketName} not exists, creating...`);
return ossClient.putBucket(bucketName, {
storageClass: 'Standard',
acl: 'private',
dataRedundancyType: 'LRS',
} as OSS.PutBucketOptions);
} else {
throw err;
}
});

const getZipAssets = ({ files, rootPath }: CdkAssets, region: string) => {
const zipAssets = Object.entries(files)
.filter(([, fileItem]) => fileItem.source.path.endsWith('zip'))
.map(([, fileItem]) => ({
bucketName: get(
fileItem,
'destinations.current_account-current_region.bucketName',
'',
).replace('${ALIYUN::Region}', region),
source: `${rootPath}/${fileItem.source.path}`,
objectKey: get(fileItem, 'destinations.current_account-current_region.objectKey'),
}));

return !isEmpty(zipAssets) ? zipAssets : undefined;
};

export const publishAssets = async (assets: CdkAssets, context: ActionContext) => {
const zipAssets = getZipAssets(assets, context.region);

if (!zipAssets) {
logger.info('No assets to publish, skipped!');
return;
}

const bucketName = zipAssets[0].bucketName;

const client = new OSS({
region: `oss-${context.region}`,
accessKeyId: context.accessKeyId,
accessKeySecret: context.accessKeySecret,
bucket: bucketName,
});

await ensureBucketExits(bucketName, client);

const headers = {
'x-oss-storage-class': 'Standard',
'x-oss-object-acl': 'private',
'x-oss-forbid-overwrite': 'false',
} as OSS.PutObjectOptions;

await Promise.all(
zipAssets.map(async ({ source, objectKey }) => {
await client.put(objectKey, path.normalize(source), { headers });
logger.info(`Upload file: ${source}) to bucket: ${bucketName} successfully!`);
}),
);

return bucketName;
};
22 changes: 17 additions & 5 deletions src/stack/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import * as ros from '@alicloud/ros-cdk-core';
import fs from 'node:fs';

import { ActionContext, ServerlessIac } from '../types';
import { logger, ProviderEnum, rosStackDeploy } from '../common';
import { logger, ProviderEnum, publishAssets, rosStackDeploy } from '../common';
import { RosStack } from './rosStack';
import { RfsStack } from './rfsStack';
import { get } from 'lodash';

export const generateRosStackTemplate = (
stackName: string,
Expand All @@ -14,9 +16,17 @@ export const generateRosStackTemplate = (
new RosStack(app, iac, context);

const assembly = app.synth();
const stackArtifact = assembly.getStackByName(stackName);

return { template: stackArtifact.template };
const { template } = assembly.getStackByName(stackName);

const assetFolderPath = get(assembly.tryGetArtifact(`${stackName}.assets`), 'file', '');
const assetsFileBody = fs.readFileSync(assetFolderPath);
const assets = {
rootPath: assembly.directory,
...JSON.parse(assetsFileBody.toString('utf-8').trim()),
};

return { template, assets };
};

export const generateRfsStackTemplate = (
Expand All @@ -37,8 +47,10 @@ export const deployStack = async (
iac: ServerlessIac,
context: ActionContext,
) => {
const { template } = generateRosStackTemplate(stackName, iac, context);

const { template, assets } = generateRosStackTemplate(stackName, iac, context);
logger.info(`Deploying stack, publishing assets...`);
await publishAssets(assets, context);
logger.info(`Assets published! 🎉`);
await rosStackDeploy(stackName, template, context);
logger.info(`Stack deployed! 🎉`);
};
Expand Down
14 changes: 5 additions & 9 deletions src/stack/rosStack/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
replaceReference,
resolveCode,
} from '../../common';
import { RosFunction } from '@alicloud/ros-cdk-fc3/lib/fc3.generated';
import * as fc from '@alicloud/ros-cdk-fc3';
import * as oss from '@alicloud/ros-cdk-oss';
import { isEmpty } from 'lodash';
Expand Down Expand Up @@ -49,15 +48,16 @@ export const resolveFunctions = (
{
sources: fileSources!.map(({ source }) => source),
destinationBucket,
timeout: 300,
logMonitoring: false, // 是否开启日志监控,设为false则不开启
timeout: 3000,
logMonitoring: false,
},
true,
);
artifactsDeployment.addDependency(destinationBucket);
}
functions?.forEach((fnc) => {
const storeInBucket = readCodeSize(fnc.code) > CODE_ZIP_SIZE_LIMIT;
let code: RosFunction.CodeProperty = {
let code: fc.RosFunction.CodeProperty = {
zipFile: resolveCode(fnc.code),
};
if (storeInBucket) {
Expand All @@ -68,7 +68,7 @@ export const resolveFunctions = (
)?.objectKey,
};
}
const fcn = new fc.RosFunction(
new fc.RosFunction(
scope,
fnc.key,
{
Expand All @@ -82,9 +82,5 @@ export const resolveFunctions = (
},
true,
);
if (storeInBucket) {
fcn.addDependsOn(destinationBucket as unknown as ros.RosResource);
fcn.addDependsOn(artifactsDeployment as unknown as ros.RosResource);
}
});
};
19 changes: 19 additions & 0 deletions src/types/assets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export type CdkAssets = {
version: string;
rootPath: string;
dockerImages: Record<string, unknown>;
files: {
[key: string]: {
source: {
path: string;
packaging: string;
};
destinations: {
[key: string]: {
bucketName: string;
objectKey: string;
};
};
};
};
};
2 changes: 2 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export * from './domains/tag';
export * from './domains/vars';
export * from './domains/context';

export * from './assets';

export type ServerlessIacRaw = {
version: string;
provider: Provider;
Expand Down
8 changes: 5 additions & 3 deletions tests/fixtures/deployFixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,10 +481,11 @@ export const largeCodeRos = {
ServiceName: {
'Fn::GetAtt': ['FCServiceFormy-demo-service_artifacts_code_deployment', 'ServiceName'],
},
Timeout: 300,
Timeout: 3000,
},
Type: 'ALIYUN::FC::Function',
},

'FCRoleFormy-demo-service_artifacts_code_deployment': {
Properties: {
AssumeRolePolicyDocument: {
Expand Down Expand Up @@ -682,6 +683,7 @@ export const largeCodeRos = {
Type: 'ALIYUN::OSS::Bucket',
},
'my-demo-service_artifacts_code_deployment': {
DependsOn: ['my-demo-service_artifacts_bucket'],
Properties: {
Parameters: {
destinationBucket: {
Expand All @@ -690,7 +692,7 @@ export const largeCodeRos = {
retainOnCreate: false,
sources: [
{
bucket: { 'Fn::Sub': expect.stringContaining('assets-${ALIYUN::Region}') },
bucket: { 'Fn::Sub': expect.any(String) },
fileName: 'hello_fn/43cb4c356149762dbe507fc1baede172-large-artifact.zip',
objectKey: '2bfeafed8d3df0d44c235271cdf2aa7d908a3c2757af14a67d33d102847f46fd.zip',
},
Expand All @@ -699,7 +701,7 @@ export const largeCodeRos = {
ServiceToken: {
'Fn::GetAtt': ['FCFunctionFormy-demo-service_artifacts_code_deployment', 'ARN'],
},
Timeout: 300,
Timeout: 3000,
},
Type: 'ALIYUN::ROS::CustomResource',
},
Expand Down
27 changes: 22 additions & 5 deletions tests/stack/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,24 @@ import { cloneDeep, set } from 'lodash';

const mockedRosStackDeploy = jest.fn();
const mockedResolveCode = jest.fn();
const mockedPublishAssets = jest.fn();

jest.mock('../../src/common', () => ({
...jest.requireActual('../../src/common'),
rosStackDeploy: (...args: unknown[]) => mockedRosStackDeploy(...args),
publishAssets: (...args: unknown[]) => mockedPublishAssets(...args),
resolveCode: (path: string) => mockedResolveCode(path),
}));

describe('Unit tests for stack deployment', () => {
beforeEach(() => {
mockedResolveCode.mockReturnValueOnce('resolved-code');
mockedPublishAssets.mockResolvedValueOnce('published-assets-bucket');
});
afterEach(() => {
mockedRosStackDeploy.mockRestore();
mockedResolveCode.mockRestore();
mockedPublishAssets.mockRestore();
});

it('should deploy generated stack when iac is valid', async () => {
Expand Down Expand Up @@ -95,6 +100,7 @@ describe('Unit tests for stack deployment', () => {
options,
);
});

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);
Expand All @@ -109,11 +115,11 @@ describe('Unit tests for stack deployment', () => {
);
});

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

deployStack(
await deployStack(
stackName,
set(
cloneDeep(oneFcOneGatewayIac),
Expand All @@ -123,16 +129,27 @@ describe('Unit tests for stack deployment', () => {
{ stackName } as ActionContext,
);

expect(mockedResolveCode).toHaveBeenCalledTimes(1);
expect(mockedPublishAssets).toHaveBeenCalledTimes(1);
expect(mockedRosStackDeploy).toHaveBeenCalledTimes(1);
expect(mockedPublishAssets).toHaveBeenCalledWith(
expect.objectContaining({
dockerImages: {},
files: expect.any(Object),
rootPath: expect.any(String),
version: '7.0.0',
}),

{ stackName },
);
expect(mockedRosStackDeploy).toHaveBeenCalledWith(stackName, largeCodeRos, { stackName });
});

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

deployStack(stackName, esServerlessMinimumIac, { stackName } as ActionContext);
await deployStack(stackName, esServerlessMinimumIac, { stackName } as ActionContext);

expect(mockedRosStackDeploy).toHaveBeenCalledTimes(1);
expect(mockedRosStackDeploy).toHaveBeenCalledWith(stackName, esServerlessMinimumRos, {
Expand Down
Loading