@volc-emr/emr-cli 0.1.1
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 +22 -0
- package/bin/emr-cli +3 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.js +116 -0
- package/dist/client/index.d.ts +17 -0
- package/dist/client/index.js +129 -0
- package/dist/cluster/index.d.ts +13 -0
- package/dist/cluster/index.js +174 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +21 -0
- package/dist/utils/auth.d.ts +7 -0
- package/dist/utils/auth.js +18 -0
- package/dist/utils/client-state.d.ts +12 -0
- package/dist/utils/client-state.js +80 -0
- package/dist/utils/config.d.ts +17 -0
- package/dist/utils/config.js +103 -0
- package/dist/utils/http.d.ts +13 -0
- package/dist/utils/http.js +76 -0
- package/dist/utils/volc/emr.d.ts +30 -0
- package/dist/utils/volc/emr.js +50 -0
- package/dist/utils/volc/openapi.d.ts +19 -0
- package/dist/utils/volc/openapi.js +122 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +6 -0
- package/package.json +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# @volc-emr/emr-cli
|
|
2
|
+
|
|
3
|
+
EMR Command Line Interface,面向 ECS 提交机的认证初始化、客户端部署、集群信息查询与集群配置同步工具。
|
|
4
|
+
|
|
5
|
+
## 安装(npmjs)
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @volc-emr/emr-cli@beta --registry https://registry.npmjs.org/
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 使用
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
emr-cli --help
|
|
15
|
+
emr-cli auth init
|
|
16
|
+
emr-cli cluster get --cluster-id emr-xxx --pretty --debug
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 说明
|
|
20
|
+
|
|
21
|
+
- `cluster get` 调用 Volc EMR OpenAPI `GetCluster`
|
|
22
|
+
- `--debug` 会输出 `RequestId` 与签名用的 `CanonicalRequest`,便于排查鉴权/权限问题
|
package/bin/emr-cli
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerAuthCommands = registerAuthCommands;
|
|
4
|
+
const config_1 = require("../utils/config");
|
|
5
|
+
// Local typings for `inquirer` can differ across package manager layouts; use
|
|
6
|
+
// a narrow runtime import here to keep the CLI compile-safe in this workspace.
|
|
7
|
+
const inquirer = require('inquirer');
|
|
8
|
+
async function promptAuthConfig(force = false) {
|
|
9
|
+
const current = (0, config_1.tryLoadConfig)();
|
|
10
|
+
if (!force &&
|
|
11
|
+
(current.ak || current.sk || current.region || current.clusterId)) {
|
|
12
|
+
const { confirmed } = await inquirer.prompt([
|
|
13
|
+
{
|
|
14
|
+
type: 'confirm',
|
|
15
|
+
name: 'confirmed',
|
|
16
|
+
message: '检测到已有认证配置,是否覆盖?',
|
|
17
|
+
default: false,
|
|
18
|
+
},
|
|
19
|
+
]);
|
|
20
|
+
if (!confirmed) {
|
|
21
|
+
console.info('已取消覆盖,保留现有配置。');
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return inquirer.prompt([
|
|
26
|
+
{
|
|
27
|
+
type: 'input',
|
|
28
|
+
name: 'ak',
|
|
29
|
+
message: '请输入 AccessKey (AK):',
|
|
30
|
+
default: current.ak,
|
|
31
|
+
validate: (input) => input?.trim() ? true : 'AccessKey 不能为空',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
type: 'password',
|
|
35
|
+
name: 'sk',
|
|
36
|
+
message: '请输入 SecretKey (SK):',
|
|
37
|
+
default: current.sk,
|
|
38
|
+
mask: '*',
|
|
39
|
+
validate: (input) => input?.trim() ? true : 'SecretKey 不能为空',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
type: 'input',
|
|
43
|
+
name: 'region',
|
|
44
|
+
message: '请输入默认 Region:',
|
|
45
|
+
default: current.region || 'cn-beijing',
|
|
46
|
+
validate: (input) => (input?.trim() ? true : 'Region 不能为空'),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
type: 'input',
|
|
50
|
+
name: 'clusterId',
|
|
51
|
+
message: '请输入默认 ClusterId [选填]:',
|
|
52
|
+
default: current.clusterId,
|
|
53
|
+
},
|
|
54
|
+
]);
|
|
55
|
+
}
|
|
56
|
+
function registerAuthCommands(program) {
|
|
57
|
+
const authCmd = program.command('auth').description('认证与配置管理');
|
|
58
|
+
authCmd
|
|
59
|
+
.command('init')
|
|
60
|
+
.description('初始化认证配置')
|
|
61
|
+
.option('--ak <ak>', 'AccessKey')
|
|
62
|
+
.option('--sk <sk>', 'SecretKey')
|
|
63
|
+
.option('--region <region>', '默认 Region')
|
|
64
|
+
.option('--cluster-id <clusterId>', '默认 ClusterId')
|
|
65
|
+
.option('--force', '强制覆盖已有配置')
|
|
66
|
+
.action(async (options) => {
|
|
67
|
+
let payload = {
|
|
68
|
+
ak: options.ak,
|
|
69
|
+
sk: options.sk,
|
|
70
|
+
region: options.region,
|
|
71
|
+
clusterId: options.clusterId,
|
|
72
|
+
};
|
|
73
|
+
if (!payload.ak || !payload.sk || !payload.region) {
|
|
74
|
+
const answers = await promptAuthConfig(Boolean(options.force));
|
|
75
|
+
if (!answers)
|
|
76
|
+
return;
|
|
77
|
+
payload = {
|
|
78
|
+
ak: answers.ak?.trim(),
|
|
79
|
+
sk: answers.sk?.trim(),
|
|
80
|
+
region: answers.region?.trim(),
|
|
81
|
+
clusterId: answers.clusterId?.trim() || undefined,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
(0, config_1.updateConfig)(payload);
|
|
85
|
+
console.info('✓ 认证配置已保存');
|
|
86
|
+
console.info(`配置文件位置: ${(0, config_1.getConfigPath)()}`);
|
|
87
|
+
});
|
|
88
|
+
authCmd
|
|
89
|
+
.command('show')
|
|
90
|
+
.description('查看当前认证配置')
|
|
91
|
+
.action(() => {
|
|
92
|
+
const config = (0, config_1.tryLoadConfig)();
|
|
93
|
+
console.info(`配置文件位置: ${(0, config_1.getConfigPath)()}`);
|
|
94
|
+
console.info(`AK: ${(0, config_1.maskSecret)(config.ak)}`);
|
|
95
|
+
console.info(`SK: ${(0, config_1.maskSecret)(config.sk)}`);
|
|
96
|
+
console.info(`Region: ${config.region || '未设置'}`);
|
|
97
|
+
console.info(`ClusterId: ${config.clusterId || '未设置'}`);
|
|
98
|
+
});
|
|
99
|
+
authCmd
|
|
100
|
+
.command('clear')
|
|
101
|
+
.description('清空本地认证配置')
|
|
102
|
+
.action(() => {
|
|
103
|
+
(0, config_1.clearConfig)();
|
|
104
|
+
console.info('✓ 本地认证配置已清空');
|
|
105
|
+
});
|
|
106
|
+
// Shortcut for first-time users
|
|
107
|
+
program
|
|
108
|
+
.command('init')
|
|
109
|
+
.description('初始化 EMR-CLI 认证配置')
|
|
110
|
+
.action(async () => {
|
|
111
|
+
await program.parseAsync(['node', 'emr-cli', 'auth', 'init'], {
|
|
112
|
+
from: 'user',
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
export interface InstallClientOptions {
|
|
3
|
+
dir?: string;
|
|
4
|
+
force?: boolean;
|
|
5
|
+
version?: string;
|
|
6
|
+
packageUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function installClient(options: InstallClientOptions): {
|
|
9
|
+
dir: string;
|
|
10
|
+
version: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function updateClient(options: InstallClientOptions): {
|
|
13
|
+
dir: string;
|
|
14
|
+
version: string;
|
|
15
|
+
updated: boolean;
|
|
16
|
+
};
|
|
17
|
+
export declare function registerClientCommands(program: Command): void;
|
|
@@ -0,0 +1,129 @@
|
|
|
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 () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.installClient = installClient;
|
|
40
|
+
exports.updateClient = updateClient;
|
|
41
|
+
exports.registerClientCommands = registerClientCommands;
|
|
42
|
+
const fs = __importStar(require("node:fs"));
|
|
43
|
+
const path = __importStar(require("node:path"));
|
|
44
|
+
const ora_1 = __importDefault(require("ora"));
|
|
45
|
+
const client_state_1 = require("../utils/client-state");
|
|
46
|
+
function installClient(options) {
|
|
47
|
+
const clientDir = (0, client_state_1.ensureClientDir)(options.dir);
|
|
48
|
+
const current = (0, client_state_1.readClientState)(clientDir);
|
|
49
|
+
if (current && !options.force) {
|
|
50
|
+
throw new Error('客户端已安装。如需覆盖安装,请使用 --force 参数。');
|
|
51
|
+
}
|
|
52
|
+
const version = options.version || 'latest';
|
|
53
|
+
const binDir = path.join(clientDir, 'bin');
|
|
54
|
+
const confDir = path.join(clientDir, 'conf');
|
|
55
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
56
|
+
fs.mkdirSync(confDir, { recursive: true });
|
|
57
|
+
fs.writeFileSync(path.join(binDir, 'emr-client.sh'), '#!/usr/bin/env bash\necho "EMR Client Ready"\n');
|
|
58
|
+
fs.writeFileSync(path.join(confDir, 'README.txt'), 'EMR 客户端配置目录\n');
|
|
59
|
+
(0, client_state_1.writeClientState)({
|
|
60
|
+
version,
|
|
61
|
+
installedAt: new Date().toISOString(),
|
|
62
|
+
packageUrl: options.packageUrl,
|
|
63
|
+
}, clientDir);
|
|
64
|
+
return { dir: clientDir, version };
|
|
65
|
+
}
|
|
66
|
+
function updateClient(options) {
|
|
67
|
+
const clientDir = (0, client_state_1.resolveClientDir)(options.dir);
|
|
68
|
+
const current = (0, client_state_1.readClientState)(clientDir);
|
|
69
|
+
if (!current) {
|
|
70
|
+
throw new Error('尚未安装客户端,请先执行 emr-cli client install');
|
|
71
|
+
}
|
|
72
|
+
const targetVersion = options.version || current.version;
|
|
73
|
+
if (current.version === targetVersion && !options.force) {
|
|
74
|
+
return { dir: clientDir, version: current.version, updated: false };
|
|
75
|
+
}
|
|
76
|
+
const result = installClient({
|
|
77
|
+
...options,
|
|
78
|
+
dir: clientDir,
|
|
79
|
+
force: true,
|
|
80
|
+
version: targetVersion,
|
|
81
|
+
});
|
|
82
|
+
return { ...result, updated: true };
|
|
83
|
+
}
|
|
84
|
+
function registerClientCommands(program) {
|
|
85
|
+
const clientCmd = program.command('client').description('客户端安装与更新');
|
|
86
|
+
clientCmd
|
|
87
|
+
.command('install')
|
|
88
|
+
.description('安装客户端软件包并初始化目录')
|
|
89
|
+
.option('--dir <dir>', '客户端安装目录')
|
|
90
|
+
.option('--version <version>', '安装版本', 'latest')
|
|
91
|
+
.option('--package-url <url>', '安装包地址(记录到元数据中)')
|
|
92
|
+
.option('--force', '覆盖已有安装')
|
|
93
|
+
.action(async (options) => {
|
|
94
|
+
const spinner = (0, ora_1.default)('开始安装 EMR 客户端...').start();
|
|
95
|
+
try {
|
|
96
|
+
const result = installClient(options);
|
|
97
|
+
spinner.succeed(`客户端安装完成: ${result.dir}`);
|
|
98
|
+
console.info(`当前版本: ${result.version}`);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
spinner.fail('客户端安装失败');
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
clientCmd
|
|
106
|
+
.command('update')
|
|
107
|
+
.description('更新客户端软件包')
|
|
108
|
+
.option('--dir <dir>', '客户端安装目录')
|
|
109
|
+
.option('--version <version>', '目标版本')
|
|
110
|
+
.option('--package-url <url>', '安装包地址(记录到元数据中)')
|
|
111
|
+
.option('--force', '即使版本一致也强制覆盖')
|
|
112
|
+
.action(async (options) => {
|
|
113
|
+
const spinner = (0, ora_1.default)('开始更新 EMR 客户端...').start();
|
|
114
|
+
try {
|
|
115
|
+
const result = updateClient(options);
|
|
116
|
+
if (!result.updated) {
|
|
117
|
+
spinner.succeed('当前客户端已是目标版本,无需更新');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
spinner.succeed(`客户端更新完成: ${result.dir}`);
|
|
121
|
+
console.info(`当前版本: ${result.version}`);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
spinner.fail('客户端更新失败');
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
export interface SyncClusterOptions {
|
|
3
|
+
clusterId?: string;
|
|
4
|
+
dir?: string;
|
|
5
|
+
debug?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function syncClusterConfig(options: SyncClusterOptions): Promise<{
|
|
8
|
+
clusterId: string;
|
|
9
|
+
outputDir: string;
|
|
10
|
+
requestId?: string;
|
|
11
|
+
canonicalRequest?: string;
|
|
12
|
+
}>;
|
|
13
|
+
export declare function registerClusterCommands(program: Command): void;
|
|
@@ -0,0 +1,174 @@
|
|
|
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 () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.syncClusterConfig = syncClusterConfig;
|
|
40
|
+
exports.registerClusterCommands = registerClusterCommands;
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
43
|
+
const ora_1 = __importDefault(require("ora"));
|
|
44
|
+
const auth_1 = require("../utils/auth");
|
|
45
|
+
const client_state_1 = require("../utils/client-state");
|
|
46
|
+
const emr_1 = require("../utils/volc/emr");
|
|
47
|
+
async function syncClusterConfig(options) {
|
|
48
|
+
const clientDir = (0, client_state_1.resolveClientDir)(options.dir);
|
|
49
|
+
if (!(0, client_state_1.isClientInstalled)(clientDir)) {
|
|
50
|
+
throw new Error('本地尚未安装客户端,请先执行 emr-cli client install');
|
|
51
|
+
}
|
|
52
|
+
const auth = (0, auth_1.resolveAuthConfig)({ clusterId: options.clusterId });
|
|
53
|
+
const clusterId = auth.clusterId;
|
|
54
|
+
if (!clusterId) {
|
|
55
|
+
throw new Error('未提供 ClusterId,请在命令行传入 --cluster-id 或先执行 emr-cli auth init 配置默认集群');
|
|
56
|
+
}
|
|
57
|
+
// Use OpenAPI GetCluster to validate cluster existence and permission.
|
|
58
|
+
const { resp: clusterResp, debug } = await (0, emr_1.volcEmrGetCluster)({
|
|
59
|
+
region: auth.region,
|
|
60
|
+
ak: auth.ak,
|
|
61
|
+
sk: auth.sk,
|
|
62
|
+
clusterId,
|
|
63
|
+
}, { debug: Boolean(options.debug) });
|
|
64
|
+
const requestId = clusterResp?.ResponseMetadata?.RequestId;
|
|
65
|
+
const apiErr = (0, emr_1.formatVolcOpenApiError)(clusterResp);
|
|
66
|
+
if (apiErr) {
|
|
67
|
+
if (apiErr.startsWith('InvalidClusterId')) {
|
|
68
|
+
throw new Error('目标集群不存在,请确认 ClusterId 是否正确');
|
|
69
|
+
}
|
|
70
|
+
if (apiErr.toLowerCase().includes('access denied')) {
|
|
71
|
+
throw new Error('对该集群没有权限(Access Denied)');
|
|
72
|
+
}
|
|
73
|
+
throw new Error(`GetCluster 调用失败:${apiErr}`);
|
|
74
|
+
}
|
|
75
|
+
const confDir = path.join(clientDir, 'conf');
|
|
76
|
+
fs.mkdirSync(confDir, { recursive: true });
|
|
77
|
+
const payload = {
|
|
78
|
+
clusterId,
|
|
79
|
+
region: auth.region,
|
|
80
|
+
// Persist a few useful fields for troubleshooting and later extensions.
|
|
81
|
+
clusterName: clusterResp?.Result?.ClusterName,
|
|
82
|
+
clusterType: clusterResp?.Result?.ClusterType,
|
|
83
|
+
releaseVersion: clusterResp?.Result?.ReleaseVersion,
|
|
84
|
+
securityMode: clusterResp?.Result?.SecurityMode,
|
|
85
|
+
syncedAt: new Date().toISOString(),
|
|
86
|
+
};
|
|
87
|
+
fs.writeFileSync(path.join(confDir, 'cluster.json'), JSON.stringify(payload, null, 2));
|
|
88
|
+
fs.writeFileSync(path.join(confDir, 'core-site.xml'), `<configuration><property><name>emr.cluster.id</name><value>${clusterId}</value></property></configuration>`);
|
|
89
|
+
return {
|
|
90
|
+
clusterId,
|
|
91
|
+
outputDir: confDir,
|
|
92
|
+
requestId,
|
|
93
|
+
canonicalRequest: debug?.canonicalRequest,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function registerClusterCommands(program) {
|
|
97
|
+
const clusterCmd = program.command('cluster').description('集群配置同步');
|
|
98
|
+
clusterCmd
|
|
99
|
+
.command('get')
|
|
100
|
+
.description('调用 OpenAPI GetCluster 获取集群详情')
|
|
101
|
+
.option('--cluster-id <clusterId>', '目标 ClusterId(不传则使用本地默认 ClusterId)')
|
|
102
|
+
.option('--pretty', '美化输出 JSON', false)
|
|
103
|
+
.option('--debug', '输出 requestId 和 canonical request 便于排查鉴权/权限问题', false)
|
|
104
|
+
.action(async (options) => {
|
|
105
|
+
const auth = (0, auth_1.resolveAuthConfig)({ clusterId: options.clusterId });
|
|
106
|
+
const clusterId = auth.clusterId;
|
|
107
|
+
if (!clusterId) {
|
|
108
|
+
throw new Error('未提供 ClusterId,请在命令行传入 --cluster-id 或先执行 emr-cli auth init 配置默认集群');
|
|
109
|
+
}
|
|
110
|
+
const spinner = (0, ora_1.default)('正在获取集群信息...').start();
|
|
111
|
+
try {
|
|
112
|
+
const { resp, debug } = await (0, emr_1.volcEmrGetCluster)({
|
|
113
|
+
region: auth.region,
|
|
114
|
+
ak: auth.ak,
|
|
115
|
+
sk: auth.sk,
|
|
116
|
+
clusterId,
|
|
117
|
+
}, { debug: Boolean(options.debug) });
|
|
118
|
+
const requestId = resp?.ResponseMetadata?.RequestId;
|
|
119
|
+
if (options.debug) {
|
|
120
|
+
console.info('');
|
|
121
|
+
console.info(`RequestId: ${requestId || 'unknown'}`);
|
|
122
|
+
console.info('CanonicalRequest:');
|
|
123
|
+
console.info(debug?.canonicalRequest || '(empty)');
|
|
124
|
+
console.info('');
|
|
125
|
+
}
|
|
126
|
+
const apiErr = (0, emr_1.formatVolcOpenApiError)(resp);
|
|
127
|
+
if (apiErr) {
|
|
128
|
+
if (apiErr.startsWith('InvalidClusterId')) {
|
|
129
|
+
throw new Error('目标集群不存在,请确认 ClusterId 是否正确');
|
|
130
|
+
}
|
|
131
|
+
if (apiErr.toLowerCase().includes('access denied')) {
|
|
132
|
+
console.error(apiErr);
|
|
133
|
+
throw new Error('对该集群没有权限(Access Denied)');
|
|
134
|
+
}
|
|
135
|
+
throw new Error(`GetCluster 调用失败:${apiErr}`);
|
|
136
|
+
}
|
|
137
|
+
spinner.succeed(`获取成功: ${clusterId}`);
|
|
138
|
+
const json = options.pretty
|
|
139
|
+
? JSON.stringify(resp, null, 2)
|
|
140
|
+
: JSON.stringify(resp);
|
|
141
|
+
console.info(json);
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
spinner.fail('获取集群信息失败');
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
clusterCmd
|
|
149
|
+
.command('sync')
|
|
150
|
+
.description('同步目标集群配置到本地客户端目录')
|
|
151
|
+
.option('--cluster-id <clusterId>', '目标 ClusterId')
|
|
152
|
+
.option('--dir <dir>', '客户端安装目录')
|
|
153
|
+
.option('--debug', '输出 requestId 和 canonical request 便于排查鉴权/权限问题', false)
|
|
154
|
+
.action(async (options) => {
|
|
155
|
+
const spinner = (0, ora_1.default)('开始同步集群配置...').start();
|
|
156
|
+
try {
|
|
157
|
+
const result = await syncClusterConfig(options);
|
|
158
|
+
if (options.debug) {
|
|
159
|
+
console.info('');
|
|
160
|
+
console.info(`RequestId: ${result.requestId || 'unknown'}`);
|
|
161
|
+
console.info('CanonicalRequest:');
|
|
162
|
+
console.info(result.canonicalRequest || '(empty)');
|
|
163
|
+
console.info('');
|
|
164
|
+
}
|
|
165
|
+
spinner.succeed(`集群配置同步完成: ${result.clusterId}`);
|
|
166
|
+
console.info(`配置目录: ${result.outputDir}`);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
spinner.fail('集群配置同步失败');
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=index.js.map
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const auth_1 = require("./auth");
|
|
6
|
+
const client_1 = require("./client");
|
|
7
|
+
const cluster_1 = require("./cluster");
|
|
8
|
+
const version_1 = require("./version");
|
|
9
|
+
const program = new commander_1.Command();
|
|
10
|
+
program
|
|
11
|
+
.name('emr-cli')
|
|
12
|
+
.description('EMR 命令行工具 - 提交机认证、客户端部署与集群配置同步工具')
|
|
13
|
+
.version(version_1.VERSION);
|
|
14
|
+
(0, auth_1.registerAuthCommands)(program);
|
|
15
|
+
(0, client_1.registerClientCommands)(program);
|
|
16
|
+
(0, cluster_1.registerClusterCommands)(program);
|
|
17
|
+
program.parse(process.argv);
|
|
18
|
+
if (!process.argv.slice(2).length) {
|
|
19
|
+
program.outputHelp();
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveAuthConfig = resolveAuthConfig;
|
|
4
|
+
const config_1 = require("./config");
|
|
5
|
+
function resolveAuthConfig(overrides = {}) {
|
|
6
|
+
const config = (0, config_1.tryLoadConfig)();
|
|
7
|
+
const result = {
|
|
8
|
+
ak: overrides.ak || config.ak || '',
|
|
9
|
+
sk: overrides.sk || config.sk || '',
|
|
10
|
+
region: overrides.region || config.region || '',
|
|
11
|
+
clusterId: overrides.clusterId || config.clusterId,
|
|
12
|
+
};
|
|
13
|
+
if (!result.ak || !result.sk || !result.region) {
|
|
14
|
+
throw new Error('认证配置缺失,请先执行 emr-cli auth init 完成 AccessKey、SecretKey 和 Region 配置');
|
|
15
|
+
}
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ClientInstallState {
|
|
2
|
+
version: string;
|
|
3
|
+
installedAt: string;
|
|
4
|
+
packageUrl?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function getDefaultClientDir(): string;
|
|
7
|
+
export declare function resolveClientDir(dir?: string): string;
|
|
8
|
+
export declare function getClientStatePath(dir?: string): string;
|
|
9
|
+
export declare function ensureClientDir(dir?: string): string;
|
|
10
|
+
export declare function readClientState(dir?: string): ClientInstallState | undefined;
|
|
11
|
+
export declare function writeClientState(state: ClientInstallState, dir?: string): void;
|
|
12
|
+
export declare function isClientInstalled(dir?: string): boolean;
|
|
@@ -0,0 +1,80 @@
|
|
|
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 () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getDefaultClientDir = getDefaultClientDir;
|
|
37
|
+
exports.resolveClientDir = resolveClientDir;
|
|
38
|
+
exports.getClientStatePath = getClientStatePath;
|
|
39
|
+
exports.ensureClientDir = ensureClientDir;
|
|
40
|
+
exports.readClientState = readClientState;
|
|
41
|
+
exports.writeClientState = writeClientState;
|
|
42
|
+
exports.isClientInstalled = isClientInstalled;
|
|
43
|
+
const fs = __importStar(require("node:fs"));
|
|
44
|
+
const node_os_1 = require("node:os");
|
|
45
|
+
const path = __importStar(require("node:path"));
|
|
46
|
+
const config_1 = require("./config");
|
|
47
|
+
function getDefaultClientDir() {
|
|
48
|
+
return path.join((0, node_os_1.homedir)(), '.emr-cli', 'client');
|
|
49
|
+
}
|
|
50
|
+
function resolveClientDir(dir) {
|
|
51
|
+
return dir || (0, config_1.tryLoadConfig)().clientInstallDir || getDefaultClientDir();
|
|
52
|
+
}
|
|
53
|
+
function getClientStatePath(dir) {
|
|
54
|
+
return path.join(resolveClientDir(dir), '.emr-cli-client.json');
|
|
55
|
+
}
|
|
56
|
+
function ensureClientDir(dir) {
|
|
57
|
+
const clientDir = resolveClientDir(dir);
|
|
58
|
+
fs.mkdirSync(clientDir, { recursive: true });
|
|
59
|
+
fs.mkdirSync(path.join(clientDir, 'conf'), { recursive: true });
|
|
60
|
+
return clientDir;
|
|
61
|
+
}
|
|
62
|
+
function readClientState(dir) {
|
|
63
|
+
const file = getClientStatePath(dir);
|
|
64
|
+
if (!fs.existsSync(file)) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
68
|
+
}
|
|
69
|
+
function writeClientState(state, dir) {
|
|
70
|
+
const clientDir = ensureClientDir(dir);
|
|
71
|
+
fs.writeFileSync(path.join(clientDir, '.emr-cli-client.json'), JSON.stringify(state, null, 2));
|
|
72
|
+
(0, config_1.updateConfig)({
|
|
73
|
+
clientInstallDir: clientDir,
|
|
74
|
+
installedClientVersion: state.version,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function isClientInstalled(dir) {
|
|
78
|
+
return Boolean(readClientState(dir));
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=client-state.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface EmrCliConfig {
|
|
2
|
+
ak?: string;
|
|
3
|
+
sk?: string;
|
|
4
|
+
region?: string;
|
|
5
|
+
clusterId?: string;
|
|
6
|
+
clientInstallDir?: string;
|
|
7
|
+
installedClientVersion?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function getConfigDir(): string;
|
|
10
|
+
export declare function getConfigPath(): string;
|
|
11
|
+
export declare function ensureConfigDir(): void;
|
|
12
|
+
export declare function loadConfig(): EmrCliConfig;
|
|
13
|
+
export declare function tryLoadConfig(): EmrCliConfig;
|
|
14
|
+
export declare function saveConfig(config: EmrCliConfig): void;
|
|
15
|
+
export declare function updateConfig(patch: Partial<EmrCliConfig>): EmrCliConfig;
|
|
16
|
+
export declare function clearConfig(): void;
|
|
17
|
+
export declare function maskSecret(value?: string): string;
|
|
@@ -0,0 +1,103 @@
|
|
|
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 () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getConfigDir = getConfigDir;
|
|
37
|
+
exports.getConfigPath = getConfigPath;
|
|
38
|
+
exports.ensureConfigDir = ensureConfigDir;
|
|
39
|
+
exports.loadConfig = loadConfig;
|
|
40
|
+
exports.tryLoadConfig = tryLoadConfig;
|
|
41
|
+
exports.saveConfig = saveConfig;
|
|
42
|
+
exports.updateConfig = updateConfig;
|
|
43
|
+
exports.clearConfig = clearConfig;
|
|
44
|
+
exports.maskSecret = maskSecret;
|
|
45
|
+
const fs = __importStar(require("node:fs"));
|
|
46
|
+
const node_os_1 = require("node:os");
|
|
47
|
+
const path = __importStar(require("node:path"));
|
|
48
|
+
function getConfigDir() {
|
|
49
|
+
return path.join((0, node_os_1.homedir)(), '.emr-cli');
|
|
50
|
+
}
|
|
51
|
+
function getConfigPath() {
|
|
52
|
+
return path.join(getConfigDir(), 'config.json');
|
|
53
|
+
}
|
|
54
|
+
function ensureConfigDir() {
|
|
55
|
+
const dir = getConfigDir();
|
|
56
|
+
if (!fs.existsSync(dir)) {
|
|
57
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function loadConfig() {
|
|
61
|
+
ensureConfigDir();
|
|
62
|
+
const file = getConfigPath();
|
|
63
|
+
if (!fs.existsSync(file)) {
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
67
|
+
const parsed = JSON.parse(content);
|
|
68
|
+
if (parsed && typeof parsed === 'object') {
|
|
69
|
+
return parsed;
|
|
70
|
+
}
|
|
71
|
+
throw new Error('配置文件格式不正确,请重新执行 emr-cli auth init 初始化配置');
|
|
72
|
+
}
|
|
73
|
+
function tryLoadConfig() {
|
|
74
|
+
try {
|
|
75
|
+
return loadConfig();
|
|
76
|
+
}
|
|
77
|
+
catch (_error) {
|
|
78
|
+
throw new Error('配置文件损坏或无法解析,请重新执行 emr-cli auth init 初始化配置');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function saveConfig(config) {
|
|
82
|
+
ensureConfigDir();
|
|
83
|
+
fs.writeFileSync(getConfigPath(), JSON.stringify(config, null, 2), {
|
|
84
|
+
mode: 0o600,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function updateConfig(patch) {
|
|
88
|
+
const config = tryLoadConfig();
|
|
89
|
+
const next = { ...config, ...patch };
|
|
90
|
+
saveConfig(next);
|
|
91
|
+
return next;
|
|
92
|
+
}
|
|
93
|
+
function clearConfig() {
|
|
94
|
+
saveConfig({});
|
|
95
|
+
}
|
|
96
|
+
function maskSecret(value) {
|
|
97
|
+
if (!value)
|
|
98
|
+
return '未设置';
|
|
99
|
+
if (value.length <= 8)
|
|
100
|
+
return '****';
|
|
101
|
+
return `${value.slice(0, 4)}...${value.slice(-4)}`;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface HttpResponse<T> {
|
|
2
|
+
statusCode: number;
|
|
3
|
+
headers: Record<string, string | string[] | undefined>;
|
|
4
|
+
data: T;
|
|
5
|
+
raw: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function httpsJsonRequest<T>(params: {
|
|
8
|
+
url: string;
|
|
9
|
+
method: 'POST' | 'GET';
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
body?: string;
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
}): Promise<HttpResponse<T>>;
|
|
@@ -0,0 +1,76 @@
|
|
|
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 () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.httpsJsonRequest = httpsJsonRequest;
|
|
37
|
+
const https = __importStar(require("node:https"));
|
|
38
|
+
const node_url_1 = require("node:url");
|
|
39
|
+
async function httpsJsonRequest(params) {
|
|
40
|
+
const { url, method, headers, body, timeoutMs = 30000 } = params;
|
|
41
|
+
const parsed = new node_url_1.URL(url);
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const req = https.request({
|
|
44
|
+
protocol: parsed.protocol,
|
|
45
|
+
hostname: parsed.hostname,
|
|
46
|
+
port: parsed.port ? Number(parsed.port) : undefined,
|
|
47
|
+
path: `${parsed.pathname}${parsed.search}`,
|
|
48
|
+
method,
|
|
49
|
+
headers,
|
|
50
|
+
timeout: timeoutMs,
|
|
51
|
+
}, res => {
|
|
52
|
+
const chunks = [];
|
|
53
|
+
res.on('data', chunk => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
|
|
54
|
+
res.on('end', () => {
|
|
55
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
56
|
+
const statusCode = res.statusCode || 0;
|
|
57
|
+
try {
|
|
58
|
+
const data = raw ? JSON.parse(raw) : {};
|
|
59
|
+
resolve({ statusCode, headers: res.headers, data, raw });
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
reject(new Error(`响应不是合法 JSON(status=${statusCode}):${raw.slice(0, 500)}`));
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
req.on('error', err => reject(err));
|
|
67
|
+
req.on('timeout', () => {
|
|
68
|
+
req.destroy(new Error(`请求超时(>${timeoutMs}ms)`));
|
|
69
|
+
});
|
|
70
|
+
if (body) {
|
|
71
|
+
req.write(body);
|
|
72
|
+
}
|
|
73
|
+
req.end();
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface VolcEmrGetClusterResponse {
|
|
2
|
+
ResponseMetadata?: {
|
|
3
|
+
RequestId?: string;
|
|
4
|
+
Action?: string;
|
|
5
|
+
Version?: string;
|
|
6
|
+
Service?: string;
|
|
7
|
+
Region?: string;
|
|
8
|
+
Error?: {
|
|
9
|
+
Code?: string;
|
|
10
|
+
Message?: string;
|
|
11
|
+
} | null;
|
|
12
|
+
};
|
|
13
|
+
Result?: any;
|
|
14
|
+
}
|
|
15
|
+
export interface VolcOpenApiDebugInfo {
|
|
16
|
+
url: string;
|
|
17
|
+
canonicalRequest: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function volcEmrGetCluster(params: {
|
|
20
|
+
region: string;
|
|
21
|
+
ak: string;
|
|
22
|
+
sk: string;
|
|
23
|
+
clusterId?: string;
|
|
24
|
+
}, options?: {
|
|
25
|
+
debug?: boolean;
|
|
26
|
+
}): Promise<{
|
|
27
|
+
resp: VolcEmrGetClusterResponse;
|
|
28
|
+
debug?: VolcOpenApiDebugInfo;
|
|
29
|
+
}>;
|
|
30
|
+
export declare function formatVolcOpenApiError(resp: VolcEmrGetClusterResponse): string | undefined;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.volcEmrGetCluster = volcEmrGetCluster;
|
|
4
|
+
exports.formatVolcOpenApiError = formatVolcOpenApiError;
|
|
5
|
+
const http_1 = require("../http");
|
|
6
|
+
const openapi_1 = require("./openapi");
|
|
7
|
+
async function volcEmrGetCluster(params, options) {
|
|
8
|
+
const host = `emr.${params.region}.volcengineapi.com`;
|
|
9
|
+
const query = {
|
|
10
|
+
Version: '2023-08-15',
|
|
11
|
+
Action: 'GetCluster',
|
|
12
|
+
};
|
|
13
|
+
const bodyObj = {};
|
|
14
|
+
if (params.clusterId) {
|
|
15
|
+
bodyObj.ClusterId = params.clusterId;
|
|
16
|
+
}
|
|
17
|
+
const body = JSON.stringify(bodyObj);
|
|
18
|
+
const signed = (0, openapi_1.signVolcOpenApiRequest)({
|
|
19
|
+
method: 'POST',
|
|
20
|
+
host,
|
|
21
|
+
path: '/',
|
|
22
|
+
region: params.region,
|
|
23
|
+
service: 'emr',
|
|
24
|
+
ak: params.ak,
|
|
25
|
+
sk: params.sk,
|
|
26
|
+
query,
|
|
27
|
+
body,
|
|
28
|
+
});
|
|
29
|
+
const resp = (await (0, http_1.httpsJsonRequest)({
|
|
30
|
+
url: signed.url,
|
|
31
|
+
method: 'POST',
|
|
32
|
+
headers: signed.headers,
|
|
33
|
+
body,
|
|
34
|
+
})).data;
|
|
35
|
+
return {
|
|
36
|
+
resp,
|
|
37
|
+
debug: options?.debug
|
|
38
|
+
? { url: signed.url, canonicalRequest: signed.canonicalRequest }
|
|
39
|
+
: undefined,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function formatVolcOpenApiError(resp) {
|
|
43
|
+
const err = resp?.ResponseMetadata?.Error;
|
|
44
|
+
if (!err || (!err.Code && !err.Message))
|
|
45
|
+
return undefined;
|
|
46
|
+
const code = err.Code || 'UnknownError';
|
|
47
|
+
const msg = err.Message || '';
|
|
48
|
+
return `${code}${msg ? `: ${msg}` : ''}`;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=emr.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface VolcOpenApiSignInput {
|
|
2
|
+
method: 'POST' | 'GET';
|
|
3
|
+
host: string;
|
|
4
|
+
path: string;
|
|
5
|
+
region: string;
|
|
6
|
+
service: string;
|
|
7
|
+
ak: string;
|
|
8
|
+
sk: string;
|
|
9
|
+
query: Record<string, string>;
|
|
10
|
+
body?: string;
|
|
11
|
+
date?: Date;
|
|
12
|
+
}
|
|
13
|
+
export declare function signVolcOpenApiRequest(input: VolcOpenApiSignInput): {
|
|
14
|
+
url: string;
|
|
15
|
+
headers: Record<string, string>;
|
|
16
|
+
xDate: string;
|
|
17
|
+
xContentSha256: string;
|
|
18
|
+
canonicalRequest: string;
|
|
19
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
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 () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.signVolcOpenApiRequest = signVolcOpenApiRequest;
|
|
37
|
+
const crypto = __importStar(require("node:crypto"));
|
|
38
|
+
const SAFE_BYTES = (() => {
|
|
39
|
+
const s = new Set();
|
|
40
|
+
for (let i = 0x61; i <= 0x7a; i++)
|
|
41
|
+
s.add(i); // a-z
|
|
42
|
+
for (let i = 0x41; i <= 0x5a; i++)
|
|
43
|
+
s.add(i); // A-Z
|
|
44
|
+
for (let i = 0x30; i <= 0x39; i++)
|
|
45
|
+
s.add(i); // 0-9
|
|
46
|
+
for (const c of ['-', '_', '.', '~'])
|
|
47
|
+
s.add(c.charCodeAt(0));
|
|
48
|
+
return s;
|
|
49
|
+
})();
|
|
50
|
+
function percentEncode(input) {
|
|
51
|
+
const buf = Buffer.from(input, 'utf8');
|
|
52
|
+
let out = '';
|
|
53
|
+
for (const b of buf) {
|
|
54
|
+
if (SAFE_BYTES.has(b)) {
|
|
55
|
+
out += String.fromCharCode(b);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
out += `%${b.toString(16).toUpperCase().padStart(2, '0')}`;
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
function sha256Hex(data) {
|
|
63
|
+
return crypto.createHash('sha256').update(data).digest('hex');
|
|
64
|
+
}
|
|
65
|
+
function hmacSha256(key, data) {
|
|
66
|
+
return crypto.createHmac('sha256', key).update(data, 'utf8').digest();
|
|
67
|
+
}
|
|
68
|
+
function formatXDate(date) {
|
|
69
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
70
|
+
const y = date.getUTCFullYear();
|
|
71
|
+
const m = pad(date.getUTCMonth() + 1);
|
|
72
|
+
const d = pad(date.getUTCDate());
|
|
73
|
+
const hh = pad(date.getUTCHours());
|
|
74
|
+
const mm = pad(date.getUTCMinutes());
|
|
75
|
+
const ss = pad(date.getUTCSeconds());
|
|
76
|
+
const shortDate = `${y}${m}${d}`;
|
|
77
|
+
return { xDate: `${shortDate}T${hh}${mm}${ss}Z`, shortDate };
|
|
78
|
+
}
|
|
79
|
+
function signVolcOpenApiRequest(input) {
|
|
80
|
+
const { method, host, path, region, service, ak, sk, query, body = '', date = new Date(), } = input;
|
|
81
|
+
const contentType = 'application/json; charset=utf-8';
|
|
82
|
+
const { xDate, shortDate } = formatXDate(date);
|
|
83
|
+
const xContentSha256 = sha256Hex(body ? Buffer.from(body, 'utf8') : Buffer.alloc(0));
|
|
84
|
+
const signedHeaders = 'content-type;host;x-content-sha256;x-date';
|
|
85
|
+
const sortedQueryKeys = Object.keys(query).sort();
|
|
86
|
+
const canonicalQuery = sortedQueryKeys
|
|
87
|
+
.map(k => `${percentEncode(k)}=${percentEncode(query[k] ?? '')}`)
|
|
88
|
+
.join('&');
|
|
89
|
+
const canonicalHeaders = `content-type:${contentType}\nhost:${host}\nx-content-sha256:${xContentSha256}\nx-date:${xDate}\n`;
|
|
90
|
+
const canonicalRequest = `${method}\n${path}\n${canonicalQuery}\n${canonicalHeaders}\n${signedHeaders}\n${xContentSha256}`;
|
|
91
|
+
const hashedCanonicalRequest = sha256Hex(canonicalRequest);
|
|
92
|
+
const credentialScope = `${shortDate}/${region}/${service}/request`;
|
|
93
|
+
const stringToSign = `HMAC-SHA256\n${xDate}\n${credentialScope}\n${hashedCanonicalRequest}`;
|
|
94
|
+
const kDate = hmacSha256(Buffer.from(sk, 'utf8'), shortDate);
|
|
95
|
+
const kRegion = hmacSha256(kDate, region);
|
|
96
|
+
const kService = hmacSha256(kRegion, service);
|
|
97
|
+
const signingKey = hmacSha256(kService, 'request');
|
|
98
|
+
const signature = crypto
|
|
99
|
+
.createHmac('sha256', signingKey)
|
|
100
|
+
.update(stringToSign, 'utf8')
|
|
101
|
+
.digest('hex');
|
|
102
|
+
const authorization = `HMAC-SHA256 Credential=${ak}/${credentialScope}, ` +
|
|
103
|
+
`SignedHeaders=${signedHeaders}, ` +
|
|
104
|
+
`Signature=${signature}`;
|
|
105
|
+
const url = `https://${host}${path}?${canonicalQuery}`;
|
|
106
|
+
return {
|
|
107
|
+
url,
|
|
108
|
+
xDate,
|
|
109
|
+
xContentSha256,
|
|
110
|
+
canonicalRequest,
|
|
111
|
+
headers: {
|
|
112
|
+
Authorization: authorization,
|
|
113
|
+
'X-Date': xDate,
|
|
114
|
+
'X-Content-Sha256': xContentSha256,
|
|
115
|
+
Host: host,
|
|
116
|
+
'Content-Type': contentType,
|
|
117
|
+
Accept: 'application/json',
|
|
118
|
+
'User-Agent': 'emr-cli',
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=openapi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const VERSION = "0.1.1";
|
package/dist/version.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@volc-emr/emr-cli",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "EMR Command Line Interface - 提交机认证、客户端部署、集群信息查询与集群配置同步工具",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"emr-cli": "bin/emr-cli"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public",
|
|
12
|
+
"registry": "https://registry.npmjs.org/"
|
|
13
|
+
},
|
|
14
|
+
"files": ["dist/**/*.js", "dist/**/*.d.ts", "bin/emr-cli", "README.md"],
|
|
15
|
+
"keywords": ["emr", "cli", "volcengine"],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"commander": "^11.1.0",
|
|
19
|
+
"inquirer": "^8.2.6",
|
|
20
|
+
"ora": "^5.4.1"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=16.0.0"
|
|
24
|
+
}
|
|
25
|
+
}
|