Skip to content
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
15,313 changes: 9,731 additions & 5,582 deletions package-lock.json

Large diffs are not rendered by default.

21 changes: 12 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,25 @@
"dependencies": {
"@alicloud/ims20190815": "^2.1.4",
"@alicloud/openapi-client": "^0.4.12",
"@alicloud/ros-cdk-apigateway": "^1.4.0",
"@alicloud/ros-cdk-core": "^1.5.0",
"@alicloud/ros-cdk-elasticsearchserverless": "^1.4.0",
"@alicloud/ros-cdk-fc3": "^1.5.0",
"@alicloud/ros-cdk-oss": "^1.4.0",
"@alicloud/ros-cdk-ossdeployment": "^1.4.0",
"@alicloud/ros-cdk-ram": "^1.4.0",
"@alicloud/ros-cdk-rds": "^1.5.0",
"@alicloud/ros-cdk-sls": "^1.5.0",
"@alicloud/ros-cdk-apigateway": "^1.6.0",
"@alicloud/ros-cdk-core": "^1.6.0",
"@alicloud/ros-cdk-elasticsearchserverless": "^1.6.0",
"@alicloud/ros-cdk-fc3": "^1.6.0",
"@alicloud/ros-cdk-oss": "^1.6.0",
"@alicloud/ros-cdk-ossdeployment": "^1.6.0",
"@alicloud/ros-cdk-ram": "^1.6.0",
"@alicloud/ros-cdk-rds": "^1.6.0",
"@alicloud/ros-cdk-sls": "^1.6.0",
"@alicloud/ros20190910": "^3.5.2",
"ajv": "^8.17.1",
"ali-oss": "^6.22.0",
"chalk": "^5.4.1",
"commander": "^12.1.0",
"i": "^0.3.7",
"i18n": "^0.15.1",
"jszip": "^3.10.1",
"lodash": "^4.17.21",
"npm": "^11.1.0",
"pino": "^9.5.0",
"pino-pretty": "^13.0.0",
"yaml": "^2.6.1"
Expand Down
1 change: 1 addition & 0 deletions src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './iacHelper';
export * from './constants';
export * from './imsClient';
export * from './base64';
export * from './rosAssets';
139 changes: 139 additions & 0 deletions src/common/rosAssets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { ISource } from '@alicloud/ros-cdk-ossdeployment/lib/source.cdk';
import fs from 'node:fs';
import * as ossDeployment from '@alicloud/ros-cdk-ossdeployment';
import path from 'node:path';
import JSZip from 'jszip';
import { logger } from './logger';
import { ActionContext, CdkAssets } from '../types';
import { get, isEmpty } from 'lodash';
import OSS from 'ali-oss';

const buildAssets = (rootPath: string, relativePath: string): Array<ISource> => {
const location = path.resolve(rootPath, relativePath);
if (!fs.existsSync(location)) {
throw new Error(`Location: ${location} is not exists!`);
}
if (fs.lstatSync(location).isFile()) {
return [
ossDeployment.Source.asset(
location,
{},
relativePath.substring(0, relativePath.lastIndexOf('/') + 1),
),
];
}
return fs
.readdirSync(location)
.map((file) => buildAssets(rootPath, `${relativePath}/${file}`.replace(/^\//, '')))
.flat();
};

export const getAssets = (location: string): Array<ISource> => {
return buildAssets(location, '');
};

const assembleFiles = (folder: string, zip: JSZip) => {
const files = fs.readdirSync(folder);
files.forEach((file) => {
const filePath = path.join(folder, file);
if (fs.lstatSync(filePath).isFile()) {
const content = fs.readFileSync(filePath);
zip.file(file, content);
} else {
const subZip = zip.folder(file);

Check warning on line 43 in src/common/rosAssets.ts

View check run for this annotation

Codecov / codecov/patch

src/common/rosAssets.ts#L43

Added line #L43 was not covered by tests
if (subZip) {
assembleFiles(filePath, subZip);

Check warning on line 45 in src/common/rosAssets.ts

View check run for this annotation

Codecov / codecov/patch

src/common/rosAssets.ts#L45

Added line #L45 was not covered by tests
}
}
});
};

const zipAssets = async (assetsPath: string) => {
const zip = new JSZip();
assembleFiles(assetsPath, zip);
const zipPath = `${assetsPath.replace(/\/$/, '').trim()}.zip`;
await zip
.generateAsync({ type: 'nodebuffer' })
.then((content) => {
fs.writeFileSync(zipPath, content);
logger.info(`Folder compressed to: ${zipPath}`);
})
.catch((e) => {
logger.error(`Failed to compress folder: ${e}`);
throw e;

Check warning on line 63 in src/common/rosAssets.ts

View check run for this annotation

Codecov / codecov/patch

src/common/rosAssets.ts#L62-L63

Added lines #L62 - L63 were not covered by tests
});
return zipPath;
};

const constructAssets = async ({ files, rootPath }: CdkAssets, region: string) => {
const assets = await Promise.all(
Object.entries(files)
.filter(([, fileItem]) => !fileItem.source.path.endsWith('.template.json'))
.map(async ([, fileItem]) => {
let sourcePath = `${rootPath}/${fileItem.source.path}`;
if (fileItem.source.packaging === 'zip') {
sourcePath = await zipAssets(`${rootPath}/${fileItem.source.path}`);
}
return {
bucketName: get(
fileItem,
'destinations.current_account-current_region.bucketName',
'',
).replace('${ALIYUN::Region}', region),
source: sourcePath,
objectKey: get(fileItem, 'destinations.current_account-current_region.objectKey'),
};
}),
);

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

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, {

Check warning on line 96 in src/common/rosAssets.ts

View check run for this annotation

Codecov / codecov/patch

src/common/rosAssets.ts#L95-L96

Added lines #L95 - L96 were not covered by tests
storageClass: 'Standard',
acl: 'private',
dataRedundancyType: 'LRS',
} as OSS.PutBucketOptions);
} else {
throw err;

Check warning on line 102 in src/common/rosAssets.ts

View check run for this annotation

Codecov / codecov/patch

src/common/rosAssets.ts#L102

Added line #L102 was not covered by tests
}
});

export const publishAssets = async (assets: CdkAssets, context: ActionContext) => {
const constructedAssets = await constructAssets(assets, context.region);

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

const bucketName = constructedAssets[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(
constructedAssets.map(async ({ source, objectKey }) => {
await client.put(objectKey, path.normalize(source), { headers });
logger.info(`Upload file: ${source}) to bucket: ${bucketName} successfully!`);
}),
);

return bucketName;
};
70 changes: 1 addition & 69 deletions src/common/rosClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@ import ROS20190910, {
UpdateStackRequestParameters,
} from '@alicloud/ros20190910';
import { Config } from '@alicloud/openapi-client';
import OSS from 'ali-oss';
import { ActionContext, CdkAssets } from '../types';
import { ActionContext } 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 @@ -199,68 +196,3 @@ 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;
};
31 changes: 31 additions & 0 deletions src/parser/bucketParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { BucketDomain, BucketRaw } from '../types';

export const parseBucket = (buckets: {
[key: string]: BucketRaw;
}): Array<BucketDomain> | undefined => {
if (!buckets) {
return undefined;
}
return Object.entries(buckets).map(([key, bucket]) => ({

Check warning on line 9 in src/parser/bucketParser.ts

View check run for this annotation

Codecov / codecov/patch

src/parser/bucketParser.ts#L9

Added line #L9 was not covered by tests
key,
name: bucket.name,
storage: bucket.storage,
versioning: bucket.versioning,
security: bucket.security
? {
force_delete: bucket.security.force_delete ?? false,
sse_algorithm: bucket.security.sse_algorithm,
sse_kms_master_key_id: bucket.security.sse_kms_master_key_id,
}
: undefined,

website: bucket.website
? {
code: bucket.website.code,
index: bucket.website.index ?? 'index.html',
error_page: bucket.website.error_page ?? '404.html',
error_code: bucket.website.error_code ?? 404,
}
: undefined,
}));
};
2 changes: 2 additions & 0 deletions src/parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { parseDatabase } from './databaseParser';
import { parseTag } from './tagParser';
import { parse } from 'yaml';
import { validateYaml } from '../validator';
import { parseBucket } from './bucketParser';

const validateExistence = (path: string) => {
if (!existsSync(path)) {
Expand All @@ -24,6 +25,7 @@ const transformYaml = (iacJson: ServerlessIacRaw): ServerlessIac => {
events: parseEvent(iacJson.events),
databases: parseDatabase(iacJson.databases),
tags: parseTag(iacJson.tags),
buckets: parseBucket(iacJson.buckets),
};
};

Expand Down
49 changes: 49 additions & 0 deletions src/stack/rosStack/bucket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { ActionContext, BucketDomain } from '../../types';
import * as oss from '@alicloud/ros-cdk-oss';
import * as ros from '@alicloud/ros-cdk-core';
import { getAssets, replaceReference } from '../../common';
import * as ossDeployment from '@alicloud/ros-cdk-ossdeployment';
import path from 'node:path';

export const resolveBuckets = (
scope: ros.Construct,
buckets: Array<BucketDomain> | undefined,
context: ActionContext,
) => {
if (!buckets) {
return undefined;
}
buckets.forEach((bucket) => {
const ossBucket = new oss.Bucket(scope, replaceReference(bucket.key, context), {
bucketName: replaceReference(bucket.name, context),
websiteConfigurationV2: bucket.website
? {
indexDocument: {
type: '0',
suffix: replaceReference(bucket.website.index, context),
supportSubDir: 'true',
},
errorDocument: {
httpStatus: `${replaceReference(bucket.website.error_code, context)}`,
key: replaceReference(bucket.website.error_page, context),
},
}
: undefined,
});
if (bucket.website?.code) {
const filePath = path.resolve(process.cwd(), replaceReference(bucket.website.code, context));
new ossDeployment.BucketDeployment(
scope,
`${replaceReference(bucket.key, context)}_bucket_code_deployment`,
{
sources: getAssets(filePath),
destinationBucket: ossBucket.attrName,
timeout: 3000,
logMonitoring: false,
retainOnCreate: false,
},
true,
);
}
});
};
3 changes: 3 additions & 0 deletions src/stack/rosStack/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { resolveStages } from './stage';
import { resloveVars } from './vars';
import { resolveDatabases } from './database';
import { resolveEvents } from './event';
import { resolveBuckets } from './bucket';

export * from './bootstrap';

Expand All @@ -32,5 +33,7 @@ export class RosStack extends ros.Stack {
resolveEvents(this, iac.events, iac.tags, context, this.service);
// Define Databases
resolveDatabases(this, iac.databases, context);
// Define Buckets
resolveBuckets(this, iac.buckets, context);
}
}
Loading