@geek-fun/serverlessinsight 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -1
- package/dist/package.json +15 -4
- package/dist/src/commands/deploy.js +18 -0
- package/dist/src/commands/index.js +21 -4
- package/dist/src/commands/validate.js +12 -0
- package/dist/src/common/actionContext.js +22 -0
- package/dist/src/common/index.d.ts +6 -0
- package/dist/src/common/index.js +22 -0
- package/dist/src/common/printer.js +12 -0
- package/dist/src/common/rosClient.js +104 -0
- package/dist/src/iac/iacSchema.js +127 -0
- package/dist/src/iac/index.d.ts +2 -0
- package/dist/src/iac/index.js +18 -0
- package/dist/src/iac/parse.js +34 -0
- package/dist/src/stack/deploy.js +154 -0
- package/dist/src/stack/index.d.ts +1 -0
- package/dist/src/stack/index.js +5 -0
- package/dist/src/types.js +7 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +15 -4
- package/Dockerfile +0 -19
package/README.md
CHANGED
|
@@ -1,17 +1,61 @@
|
|
|
1
1
|
# ServerlessInsight
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
[](https://github.com/geek-fun/serverlessinsight/actions/workflows/node.yml)
|
|
4
|
+
[](https://github.com/geek-fun/serverlessinsight/actions/workflows/release.yml)
|
|
5
|
+
[](https://badge.fury.io/js/@geek-fun%2Fserverlessinsight)
|
|
6
|
+
[](https://snyk.io/test/github/geek-fun/serverlessinsight)
|
|
7
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
8
|
+
[](https://codecov.io/gh/geek-fun/serverlessinsight)
|
|
3
9
|
|
|
4
10
|
Full life cycle cross providers serverless application management for your fast-growing business.
|
|
5
11
|
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
### prerequisites
|
|
15
|
+
|
|
16
|
+
- Node.js 16.x
|
|
17
|
+
|
|
18
|
+
### Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g @geek-fun/serverlessinsight
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Usage
|
|
6
25
|
|
|
26
|
+
```bash
|
|
27
|
+
si -h
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
#### Initialize a new project
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
si init
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### Deploy the application
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
si deploy
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
#### Remove the application
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
si remove
|
|
46
|
+
```
|
|
7
47
|
|
|
8
48
|
## Development
|
|
49
|
+
|
|
9
50
|
Link ServerlessInsight globally to use the CLI tool.
|
|
51
|
+
|
|
10
52
|
```bash
|
|
11
53
|
npm run build
|
|
12
54
|
npm link
|
|
13
55
|
```
|
|
56
|
+
|
|
14
57
|
run the command then:
|
|
58
|
+
|
|
15
59
|
```bash
|
|
16
60
|
si -h
|
|
17
61
|
```
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geek-fun/serverlessinsight",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Full life cycle cross providers serverless application management for your fast-growing business.",
|
|
5
5
|
"homepage": "https://serverlessinsight.geekfun.club",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"test:ci": "jest --runInBand --ci --coverage --coverageReporters json-summary text html lcov",
|
|
14
14
|
"build": "tsc --build",
|
|
15
15
|
"lint:fix": "eslint --fix ./",
|
|
16
|
-
"lint:check": "eslint ./"
|
|
16
|
+
"lint:check": "eslint ./",
|
|
17
|
+
"prepare": "husky"
|
|
17
18
|
},
|
|
18
19
|
"repository": {
|
|
19
20
|
"type": "git",
|
|
@@ -48,9 +49,18 @@
|
|
|
48
49
|
"function"
|
|
49
50
|
],
|
|
50
51
|
"dependencies": {
|
|
52
|
+
"@alicloud/openapi-client": "^0.4.11",
|
|
53
|
+
"@alicloud/ros-cdk-apigateway": "^1.2.0",
|
|
54
|
+
"@alicloud/ros-cdk-core": "^1.2.0",
|
|
55
|
+
"@alicloud/ros-cdk-fc": "^1.2.0",
|
|
56
|
+
"@alicloud/ros-cdk-ram": "^1.2.0",
|
|
57
|
+
"@alicloud/ros20190910": "^3.4.3",
|
|
58
|
+
"ajv": "^8.17.1",
|
|
59
|
+
"chalk": "^4.1.2",
|
|
51
60
|
"commander": "^11.1.0",
|
|
52
61
|
"i18n": "^0.15.1",
|
|
53
|
-
"pino": "^8.17.2"
|
|
62
|
+
"pino": "^8.17.2",
|
|
63
|
+
"yaml": "^2.5.1"
|
|
54
64
|
},
|
|
55
65
|
"devDependencies": {
|
|
56
66
|
"@types/i18n": "^0.13.10",
|
|
@@ -61,10 +71,11 @@
|
|
|
61
71
|
"eslint": "^8.56.0",
|
|
62
72
|
"eslint-config-prettier": "^9.1.0",
|
|
63
73
|
"eslint-plugin-prettier": "^5.1.3",
|
|
74
|
+
"husky": "^9.1.6",
|
|
64
75
|
"jest": "^29.7.0",
|
|
65
76
|
"prettier": "^3.2.4",
|
|
66
77
|
"ts-jest": "^29.1.1",
|
|
67
78
|
"ts-node": "^10.9.2",
|
|
68
|
-
"typescript": "^5.
|
|
79
|
+
"typescript": "^5.6.2"
|
|
69
80
|
}
|
|
70
81
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deploy = void 0;
|
|
4
|
+
const stack_1 = require("../stack");
|
|
5
|
+
const common_1 = require("../common");
|
|
6
|
+
const iac_1 = require("../iac");
|
|
7
|
+
const actionContext_1 = require("../common/actionContext");
|
|
8
|
+
const deploy = async (stackName, options) => {
|
|
9
|
+
const context = (0, actionContext_1.constructActionContext)({ location: options.location });
|
|
10
|
+
common_1.printer.info(`Deploying stack context: ${JSON.stringify(context)}...`);
|
|
11
|
+
common_1.printer.info('Validating yaml...');
|
|
12
|
+
const iac = (0, iac_1.parseYaml)(context.iacLocation);
|
|
13
|
+
common_1.printer.success('Yaml is valid! 🎉');
|
|
14
|
+
common_1.printer.info('Deploying stack...');
|
|
15
|
+
await (0, stack_1.deployStack)(stackName, iac, context);
|
|
16
|
+
common_1.printer.success('Stack deployed! 🎉');
|
|
17
|
+
};
|
|
18
|
+
exports.deploy = deploy;
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
const commander_1 = require("commander");
|
|
5
5
|
const lang_1 = require("../lang");
|
|
6
|
-
const
|
|
7
|
-
const
|
|
6
|
+
const common_1 = require("../common");
|
|
7
|
+
const validate_1 = require("./validate");
|
|
8
|
+
const deploy_1 = require("./deploy");
|
|
8
9
|
const program = new commander_1.Command();
|
|
9
|
-
program.name('si').description('CLI for ServerlessInsight').version((0,
|
|
10
|
+
program.name('si').description('CLI for ServerlessInsight').version((0, common_1.getVersion)());
|
|
10
11
|
program
|
|
11
12
|
.command('show')
|
|
12
13
|
.description('show string')
|
|
@@ -15,7 +16,23 @@ program
|
|
|
15
16
|
.option('-s, --separator <char>', 'separator character', ',')
|
|
16
17
|
.action((str, options) => {
|
|
17
18
|
const limit = options.first ? 1 : undefined;
|
|
18
|
-
|
|
19
|
+
common_1.logger.debug({ limit, first: options.first, separator: options.separator }, 'log command info');
|
|
19
20
|
console.log(`${str} ${options.first} ${options.separator} ${lang_1.lang.__('hello')}`);
|
|
20
21
|
});
|
|
22
|
+
program
|
|
23
|
+
.command('validate [stackName]')
|
|
24
|
+
.description('validate serverless Iac yaml')
|
|
25
|
+
.option('-f, --file <path>', 'specify the yaml file')
|
|
26
|
+
.action((stackName, options) => {
|
|
27
|
+
common_1.logger.debug('log command info');
|
|
28
|
+
(0, validate_1.validate)(options.file);
|
|
29
|
+
});
|
|
30
|
+
program
|
|
31
|
+
.command('deploy <stackName>')
|
|
32
|
+
.description('deploy serverless Iac yaml')
|
|
33
|
+
.option('-f, --file <path>', 'specify the yaml file')
|
|
34
|
+
.action(async (stackName, options) => {
|
|
35
|
+
common_1.logger.debug('log command info');
|
|
36
|
+
await (0, deploy_1.deploy)(stackName, { location: options.file });
|
|
37
|
+
});
|
|
21
38
|
program.parse();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validate = void 0;
|
|
4
|
+
const iac_1 = require("../iac");
|
|
5
|
+
const common_1 = require("../common");
|
|
6
|
+
const actionContext_1 = require("../common/actionContext");
|
|
7
|
+
const validate = (location) => {
|
|
8
|
+
const context = (0, actionContext_1.constructActionContext)({ location });
|
|
9
|
+
(0, iac_1.parseYaml)(context.iacLocation);
|
|
10
|
+
common_1.printer.success('Yaml is valid! 🎉');
|
|
11
|
+
};
|
|
12
|
+
exports.validate = validate;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.constructActionContext = void 0;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const constructActionContext = (config) => {
|
|
9
|
+
return {
|
|
10
|
+
region: config?.region ?? process.env.ROS_REGION_ID ?? process.env.ALIYUN_REGION ?? 'cn-hangzhou',
|
|
11
|
+
accessKeyId: config?.accessKeyId ?? process.env.ALIYUN_ACCESS_KEY_ID,
|
|
12
|
+
accessKeySecret: config?.accessKeySecret ?? process.env.ALIYUN_ACCESS_KEY_SECRET,
|
|
13
|
+
securityToken: config?.securityToken ?? process.env.ALIYUN_SECURITY_TOKEN,
|
|
14
|
+
iacLocation: (() => {
|
|
15
|
+
const projectRoot = node_path_1.default.resolve(process.cwd());
|
|
16
|
+
return config?.location
|
|
17
|
+
? node_path_1.default.resolve(projectRoot, config?.location)
|
|
18
|
+
: node_path_1.default.resolve(projectRoot, 'serverless-insight.yml');
|
|
19
|
+
})(),
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
exports.constructActionContext = constructActionContext;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./printer"), exports);
|
|
18
|
+
__exportStar(require("./provider"), exports);
|
|
19
|
+
__exportStar(require("./logger"), exports);
|
|
20
|
+
__exportStar(require("./getVersion"), exports);
|
|
21
|
+
__exportStar(require("./rosClient"), exports);
|
|
22
|
+
__exportStar(require("./actionContext"), exports);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.printer = void 0;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
exports.printer = {
|
|
9
|
+
success: (message) => console.log(`${chalk_1.default.blue('ServerlessInsight')}: ${chalk_1.default.green(message)}`),
|
|
10
|
+
info: (message) => console.log(`${chalk_1.default.blue('ServerlessInsight')}: ${message}`),
|
|
11
|
+
error: (message) => console.log(`${chalk_1.default.bgRed(chalk_1.default.black('ServerlessInsight'))}: ${chalk_1.default.red(message)}`),
|
|
12
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.rosStackDeploy = void 0;
|
|
30
|
+
const tea_util_1 = __importDefault(require("@alicloud/tea-util"));
|
|
31
|
+
const ros20190910_1 = __importStar(require("@alicloud/ros20190910"));
|
|
32
|
+
const openapi_client_1 = require("@alicloud/openapi-client");
|
|
33
|
+
const printer_1 = require("./printer");
|
|
34
|
+
const client = new ros20190910_1.default(new openapi_client_1.Config({
|
|
35
|
+
accessKeyId: process.env.ALIYUN_ACCESS_KEY_ID,
|
|
36
|
+
accessKeySecret: process.env.ALIYUN_ACCESS_KEY_SECRET,
|
|
37
|
+
regionId: process.env.ALIYUN_REGION,
|
|
38
|
+
disableRollback: false,
|
|
39
|
+
}));
|
|
40
|
+
const createStack = async (stackName, templateBody, context) => {
|
|
41
|
+
const parameters = context.parameters?.map((parameter) => new ros20190910_1.CreateStackRequestParameters({
|
|
42
|
+
parameterKey: tea_util_1.default.assertAsString(parameter.key),
|
|
43
|
+
parameterValue: tea_util_1.default.assertAsString(parameter.value),
|
|
44
|
+
}));
|
|
45
|
+
const createStackRequest = new ros20190910_1.CreateStackRequest({
|
|
46
|
+
regionId: context.region,
|
|
47
|
+
stackName,
|
|
48
|
+
templateBody: JSON.stringify(templateBody),
|
|
49
|
+
parameters,
|
|
50
|
+
tags: context.tags?.map((tag) => new ros20190910_1.CreateStackRequestTags(tag)),
|
|
51
|
+
});
|
|
52
|
+
console.log('createStackRequest:', createStackRequest);
|
|
53
|
+
const response = await client.createStack(createStackRequest);
|
|
54
|
+
console.log(`创建中,资源栈ID:${response.body?.stackId}`);
|
|
55
|
+
return response.body?.stackId;
|
|
56
|
+
};
|
|
57
|
+
const updateStack = async (stackId, templateBody, context) => {
|
|
58
|
+
const parameters = context.parameters?.map((parameter) => new ros20190910_1.CreateStackRequestParameters({
|
|
59
|
+
parameterKey: tea_util_1.default.assertAsString(parameter.key),
|
|
60
|
+
parameterValue: tea_util_1.default.assertAsString(parameter.value),
|
|
61
|
+
}));
|
|
62
|
+
const createStackRequest = new ros20190910_1.UpdateStackRequest({
|
|
63
|
+
regionId: context.region,
|
|
64
|
+
stackId,
|
|
65
|
+
templateBody: JSON.stringify(templateBody),
|
|
66
|
+
parameters,
|
|
67
|
+
tags: context.tags?.map((tag) => new ros20190910_1.CreateStackRequestTags(tag)),
|
|
68
|
+
});
|
|
69
|
+
const response = await client.updateStack(createStackRequest);
|
|
70
|
+
console.log(`更新中,资源栈ID:${response.body?.stackId}`);
|
|
71
|
+
return response.body?.stackId;
|
|
72
|
+
};
|
|
73
|
+
const getStackByName = async (stackName, region) => {
|
|
74
|
+
const result = await client.listStacks(new ros20190910_1.ListStacksRequest({
|
|
75
|
+
regionId: region,
|
|
76
|
+
pageSize: 10,
|
|
77
|
+
pageNumber: 1,
|
|
78
|
+
stackName: [stackName],
|
|
79
|
+
}));
|
|
80
|
+
if (result.statusCode === 200) {
|
|
81
|
+
return result.body?.stacks?.[0];
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const rosStackDeploy = async (stackName, templateBody, context) => {
|
|
88
|
+
const stackInfo = await getStackByName(stackName, context.region);
|
|
89
|
+
if (stackInfo) {
|
|
90
|
+
const { Status: stackStatus } = stackInfo;
|
|
91
|
+
if (stackStatus?.indexOf('IN_PROGRESS') >= 0) {
|
|
92
|
+
printer_1.printer.error(`fail to update stack, because stack status is ${stackStatus}`);
|
|
93
|
+
throw new Error(`fail to update stack, because stack status is ${stackStatus}`);
|
|
94
|
+
}
|
|
95
|
+
printer_1.printer.info(`Update stack: ${stackName} deploying... `);
|
|
96
|
+
return await updateStack(stackInfo.stackId, templateBody, context);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// create stack
|
|
100
|
+
printer_1.printer.info(`Create stack: ${stackName} deploying... `);
|
|
101
|
+
return await createStack(stackName, templateBody, context);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
exports.rosStackDeploy = rosStackDeploy;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.validateYaml = void 0;
|
|
7
|
+
const ajv_1 = __importDefault(require("ajv"));
|
|
8
|
+
const ajv = new ajv_1.default({ allowUnionTypes: true, strict: false, allErrors: true });
|
|
9
|
+
const schema = {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
version: { type: 'string', enum: ['0.0.0', '0.0.1'] },
|
|
13
|
+
provider: { type: 'string', enum: ['aliyun', 'huawei'] },
|
|
14
|
+
service: { type: 'string' },
|
|
15
|
+
vars: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
additionalProperties: {
|
|
18
|
+
type: ['string', 'number', 'boolean'],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
stages: {
|
|
22
|
+
type: 'object',
|
|
23
|
+
patternProperties: {
|
|
24
|
+
'.*': {
|
|
25
|
+
type: 'object',
|
|
26
|
+
additionalProperties: {
|
|
27
|
+
type: ['string', 'number', 'boolean'],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
tags: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
additionalProperties: {
|
|
35
|
+
type: ['string', 'number', 'boolean'],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
functions: {
|
|
39
|
+
type: 'object',
|
|
40
|
+
patternProperties: {
|
|
41
|
+
'.*': {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: {
|
|
44
|
+
name: { type: 'string' },
|
|
45
|
+
runtime: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
enum: [
|
|
48
|
+
'nodejs20',
|
|
49
|
+
'nodejs18',
|
|
50
|
+
'nodejs16',
|
|
51
|
+
'nodejs14',
|
|
52
|
+
'nodejs12',
|
|
53
|
+
'nodejs10',
|
|
54
|
+
'nodejs8',
|
|
55
|
+
'python3.10',
|
|
56
|
+
'python3.9',
|
|
57
|
+
'python3',
|
|
58
|
+
'PHP 7.2',
|
|
59
|
+
'Java 11',
|
|
60
|
+
'.NET Core 3.1',
|
|
61
|
+
'Go 1.x',
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
handler: { type: 'string' },
|
|
65
|
+
code: { type: 'string' },
|
|
66
|
+
memory: { type: 'number' },
|
|
67
|
+
timeout: { type: 'number' },
|
|
68
|
+
environment: {
|
|
69
|
+
type: 'object',
|
|
70
|
+
additionalProperties: {
|
|
71
|
+
type: ['string', 'number', 'boolean'],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
additionalProperties: false,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
events: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
patternProperties: {
|
|
82
|
+
'.*': {
|
|
83
|
+
type: 'object',
|
|
84
|
+
properties: {
|
|
85
|
+
name: { type: 'string' },
|
|
86
|
+
type: { type: 'string', enum: ['API_GATEWAY'] },
|
|
87
|
+
triggers: {
|
|
88
|
+
type: 'array',
|
|
89
|
+
items: {
|
|
90
|
+
method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'DELETE', 'ANY'] },
|
|
91
|
+
path: { type: 'string' },
|
|
92
|
+
backend: { type: 'string' },
|
|
93
|
+
required: ['method', 'path', 'backend'],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
required: ['name', 'type', 'triggers'],
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
required: ['version', 'provider', 'service'],
|
|
103
|
+
additionalProperties: false,
|
|
104
|
+
};
|
|
105
|
+
class IacSchemaErrors extends Error {
|
|
106
|
+
constructor(errors) {
|
|
107
|
+
super(`Invalid yaml`);
|
|
108
|
+
this.schemaErrors = errors.map((error) => ({
|
|
109
|
+
instancePath: error.instancePath,
|
|
110
|
+
schemaPath: error.schemaPath,
|
|
111
|
+
message: error.message,
|
|
112
|
+
allowedValues: error.params?.allowedValues,
|
|
113
|
+
type: error.keyword,
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
get errors() {
|
|
117
|
+
return this.schemaErrors;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const validateYaml = (iacJson) => {
|
|
121
|
+
const validate = ajv.compile(schema);
|
|
122
|
+
const valid = validate(iacJson);
|
|
123
|
+
if (!valid)
|
|
124
|
+
throw new IacSchemaErrors(validate.errors);
|
|
125
|
+
return true;
|
|
126
|
+
};
|
|
127
|
+
exports.validateYaml = validateYaml;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./parse"), exports);
|
|
18
|
+
__exportStar(require("./iacSchema"), exports);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseYaml = void 0;
|
|
4
|
+
const yaml_1 = require("yaml");
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
6
|
+
const iacSchema_1 = require("./iacSchema");
|
|
7
|
+
const mapToArr = (obj) => {
|
|
8
|
+
return Object.entries(obj).map(([key, value]) => typeof value === 'string' ? { [key]: value } : { key, ...value });
|
|
9
|
+
};
|
|
10
|
+
const validateExistence = (path) => {
|
|
11
|
+
if (!(0, node_fs_1.existsSync)(path)) {
|
|
12
|
+
throw new Error(`File does not exist at path: ${path}`);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const transformYaml = (iacJson) => {
|
|
16
|
+
return {
|
|
17
|
+
service: iacJson.service,
|
|
18
|
+
version: iacJson.version,
|
|
19
|
+
provider: iacJson.provider,
|
|
20
|
+
vars: iacJson.vars,
|
|
21
|
+
stages: iacJson.stages,
|
|
22
|
+
functions: mapToArr(iacJson.functions),
|
|
23
|
+
events: mapToArr(iacJson.events),
|
|
24
|
+
tags: mapToArr(iacJson.tags),
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
const parseYaml = (yamlPath) => {
|
|
28
|
+
validateExistence(yamlPath);
|
|
29
|
+
const yamlContent = (0, node_fs_1.readFileSync)(yamlPath, 'utf8');
|
|
30
|
+
const iacJson = (0, yaml_1.parse)(yamlContent);
|
|
31
|
+
(0, iacSchema_1.validateYaml)(iacJson);
|
|
32
|
+
return transformYaml(iacJson);
|
|
33
|
+
};
|
|
34
|
+
exports.parseYaml = parseYaml;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.deployStack = exports.IacStack = void 0;
|
|
30
|
+
const ros = __importStar(require("@alicloud/ros-cdk-core"));
|
|
31
|
+
const fc = __importStar(require("@alicloud/ros-cdk-fc"));
|
|
32
|
+
const agw = __importStar(require("@alicloud/ros-cdk-apigateway"));
|
|
33
|
+
const ram = __importStar(require("@alicloud/ros-cdk-ram"));
|
|
34
|
+
const types_1 = require("../types");
|
|
35
|
+
const common_1 = require("../common");
|
|
36
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const resolveCode = (location) => {
|
|
39
|
+
const filePath = node_path_1.default.resolve(process.cwd(), location);
|
|
40
|
+
const fileContent = fs.readFileSync(filePath);
|
|
41
|
+
return fileContent.toString('base64');
|
|
42
|
+
};
|
|
43
|
+
class IacStack extends ros.Stack {
|
|
44
|
+
constructor(scope, iac, context) {
|
|
45
|
+
super(scope, iac.service);
|
|
46
|
+
new ros.RosInfo(this, ros.RosInfo.description, `${iac.service} stack`);
|
|
47
|
+
const service = new fc.RosService(this, `${iac.service}-service`, {
|
|
48
|
+
serviceName: `${iac.service}-service`,
|
|
49
|
+
}, true);
|
|
50
|
+
iac.functions.forEach((fnc) => {
|
|
51
|
+
const func = new fc.RosFunction(this, fnc.key, {
|
|
52
|
+
functionName: fnc.name,
|
|
53
|
+
serviceName: service.serviceName,
|
|
54
|
+
handler: fnc.handler,
|
|
55
|
+
runtime: fnc.runtime,
|
|
56
|
+
memorySize: fnc.memory,
|
|
57
|
+
timeout: fnc.timeout,
|
|
58
|
+
environmentVariables: fnc.environment,
|
|
59
|
+
code: {
|
|
60
|
+
zipFile: resolveCode(fnc.code),
|
|
61
|
+
},
|
|
62
|
+
}, true);
|
|
63
|
+
func.addDependsOn(service);
|
|
64
|
+
});
|
|
65
|
+
const apiGateway = iac.events.find((event) => event.type === types_1.EventTypes.API_GATEWAY);
|
|
66
|
+
if (apiGateway) {
|
|
67
|
+
const gatewayAccessRole = new ram.RosRole(this, `${iac.service}_role`, {
|
|
68
|
+
roleName: `${iac.service}-gateway-access-role`,
|
|
69
|
+
description: `${iac.service} role`,
|
|
70
|
+
assumeRolePolicyDocument: {
|
|
71
|
+
version: '1',
|
|
72
|
+
statement: [
|
|
73
|
+
{
|
|
74
|
+
action: 'sts:AssumeRole',
|
|
75
|
+
effect: 'Allow',
|
|
76
|
+
principal: {
|
|
77
|
+
service: ['apigateway.aliyuncs.com'],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
policies: [
|
|
83
|
+
{
|
|
84
|
+
policyName: `${iac.service}-policy`,
|
|
85
|
+
policyDocument: {
|
|
86
|
+
version: '1',
|
|
87
|
+
statement: [
|
|
88
|
+
{
|
|
89
|
+
action: ['fc:InvokeFunction'],
|
|
90
|
+
effect: 'Allow',
|
|
91
|
+
// @TODO implement at least permission granting
|
|
92
|
+
resource: ['*'],
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
}, true);
|
|
99
|
+
const apiGatewayGroup = new agw.RosGroup(this, `${iac.service}_apigroup`, {
|
|
100
|
+
groupName: `${iac.service}_apigroup`,
|
|
101
|
+
}, true);
|
|
102
|
+
iac.events
|
|
103
|
+
.filter((event) => event.type === types_1.EventTypes.API_GATEWAY)
|
|
104
|
+
.forEach((event) => {
|
|
105
|
+
event.triggers.forEach((trigger) => {
|
|
106
|
+
const key = `${trigger.method}_${trigger.path}`.toLowerCase().replace(/\//g, '_');
|
|
107
|
+
const api = new agw.RosApi(this, `${event.key}_api_${key}`, {
|
|
108
|
+
apiName: `${event.name}_api_${key}`,
|
|
109
|
+
groupId: apiGatewayGroup.attrGroupId,
|
|
110
|
+
visibility: 'PRIVATE',
|
|
111
|
+
requestConfig: {
|
|
112
|
+
requestProtocol: 'HTTP',
|
|
113
|
+
requestHttpMethod: trigger.method,
|
|
114
|
+
requestPath: trigger.path,
|
|
115
|
+
requestMode: 'PASSTHROUGH',
|
|
116
|
+
},
|
|
117
|
+
serviceConfig: {
|
|
118
|
+
serviceProtocol: 'FunctionCompute',
|
|
119
|
+
functionComputeConfig: {
|
|
120
|
+
fcRegionId: context.region,
|
|
121
|
+
serviceName: service.serviceName,
|
|
122
|
+
functionName: trigger.backend,
|
|
123
|
+
roleArn: gatewayAccessRole.attrArn,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
resultSample: 'ServerlessInsight resultSample',
|
|
127
|
+
resultType: 'JSON',
|
|
128
|
+
}, true);
|
|
129
|
+
api.addDependsOn(apiGatewayGroup);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
exports.IacStack = IacStack;
|
|
136
|
+
const generateStackTemplate = (stackName, iac, context) => {
|
|
137
|
+
const app = new ros.App();
|
|
138
|
+
new IacStack(app, iac, context);
|
|
139
|
+
const assembly = app.synth();
|
|
140
|
+
const stackArtifact = assembly.getStackByName(stackName);
|
|
141
|
+
const parameters = Object.entries(stackArtifact.parameters).map(([key, value]) => ({
|
|
142
|
+
key,
|
|
143
|
+
value,
|
|
144
|
+
}));
|
|
145
|
+
return { template: stackArtifact.template, parameters };
|
|
146
|
+
};
|
|
147
|
+
const deployStack = async (stackName, iac, context) => {
|
|
148
|
+
common_1.printer.info(`Deploying stack... ${JSON.stringify(iac)}`);
|
|
149
|
+
const { template, parameters } = generateStackTemplate(stackName, iac, context);
|
|
150
|
+
console.log('Generated ROS YAML:', JSON.stringify({ template, parameters }));
|
|
151
|
+
await (0, common_1.rosStackDeploy)(stackName, template, { ...context, parameters });
|
|
152
|
+
common_1.printer.info(`Stack deployed! 🎉`);
|
|
153
|
+
};
|
|
154
|
+
exports.deployStack = deployStack;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { deployStack } from './deploy';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["../src/index.ts","../src/types.ts","../src/commands/deploy.ts","../src/commands/index.ts","../src/commands/validate.ts","../src/common/actionContext.ts","../src/common/getVersion.ts","../src/common/index.ts","../src/common/logger.ts","../src/common/printer.ts","../src/common/provider.ts","../src/common/rosClient.ts","../src/iac/iacSchema.ts","../src/iac/index.ts","../src/iac/parse.ts","../src/lang/index.ts","../src/stack/deploy.ts","../src/stack/index.ts"],"version":"5.6.2"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geek-fun/serverlessinsight",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Full life cycle cross providers serverless application management for your fast-growing business.",
|
|
5
5
|
"homepage": "https://serverlessinsight.geekfun.club",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"test:ci": "jest --runInBand --ci --coverage --coverageReporters json-summary text html lcov",
|
|
14
14
|
"build": "tsc --build",
|
|
15
15
|
"lint:fix": "eslint --fix ./",
|
|
16
|
-
"lint:check": "eslint ./"
|
|
16
|
+
"lint:check": "eslint ./",
|
|
17
|
+
"prepare": "husky"
|
|
17
18
|
},
|
|
18
19
|
"repository": {
|
|
19
20
|
"type": "git",
|
|
@@ -48,9 +49,18 @@
|
|
|
48
49
|
"function"
|
|
49
50
|
],
|
|
50
51
|
"dependencies": {
|
|
52
|
+
"@alicloud/openapi-client": "^0.4.11",
|
|
53
|
+
"@alicloud/ros-cdk-apigateway": "^1.2.0",
|
|
54
|
+
"@alicloud/ros-cdk-core": "^1.2.0",
|
|
55
|
+
"@alicloud/ros-cdk-fc": "^1.2.0",
|
|
56
|
+
"@alicloud/ros-cdk-ram": "^1.2.0",
|
|
57
|
+
"@alicloud/ros20190910": "^3.4.3",
|
|
58
|
+
"ajv": "^8.17.1",
|
|
59
|
+
"chalk": "^4.1.2",
|
|
51
60
|
"commander": "^11.1.0",
|
|
52
61
|
"i18n": "^0.15.1",
|
|
53
|
-
"pino": "^8.17.2"
|
|
62
|
+
"pino": "^8.17.2",
|
|
63
|
+
"yaml": "^2.5.1"
|
|
54
64
|
},
|
|
55
65
|
"devDependencies": {
|
|
56
66
|
"@types/i18n": "^0.13.10",
|
|
@@ -61,10 +71,11 @@
|
|
|
61
71
|
"eslint": "^8.56.0",
|
|
62
72
|
"eslint-config-prettier": "^9.1.0",
|
|
63
73
|
"eslint-plugin-prettier": "^5.1.3",
|
|
74
|
+
"husky": "^9.1.6",
|
|
64
75
|
"jest": "^29.7.0",
|
|
65
76
|
"prettier": "^3.2.4",
|
|
66
77
|
"ts-jest": "^29.1.1",
|
|
67
78
|
"ts-node": "^10.9.2",
|
|
68
|
-
"typescript": "^5.
|
|
79
|
+
"typescript": "^5.6.2"
|
|
69
80
|
}
|
|
70
81
|
}
|
package/Dockerfile
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
FROM node:16.17.1-alpine@sha256:4e36c3dee7c32cef5bfce5bc1b5013d1c3cc542cfdefde2a545ec641e7c94243 AS builder
|
|
2
|
-
WORKDIR /app
|
|
3
|
-
|
|
4
|
-
ENV NODE_ENV=development
|
|
5
|
-
COPY ./package.json .
|
|
6
|
-
COPY ./package-lock.json .
|
|
7
|
-
RUN npm install
|
|
8
|
-
COPY ./tsconfig.json .
|
|
9
|
-
COPY ./src ./src
|
|
10
|
-
RUN npm run build
|
|
11
|
-
|
|
12
|
-
FROM node:16.17.1-alpine@sha256:4e36c3dee7c32cef5bfce5bc1b5013d1c3cc542cfdefde2a545ec641e7c94243
|
|
13
|
-
WORKDIR /app
|
|
14
|
-
|
|
15
|
-
ENV NODE_ENV=production
|
|
16
|
-
COPY --from=builder /app/dist .
|
|
17
|
-
COPY ./package.json .
|
|
18
|
-
COPY ./package-lock.json .
|
|
19
|
-
RUN npm install
|