huaweicloud-devkit 1.0.3-dev.0 → 1.1.0-next.0
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/.agents/plugins/marketplace.json +18 -18
- package/README.md +114 -41
- package/README.zh-CN.md +114 -41
- package/bin/setup.cjs +7 -4
- package/integrations/opencode/opencode.json +1 -4
- package/package.json +63 -42
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +4 -17
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +4 -17
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +4 -17
- package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +30 -0
- package/plugins/huaweicloud-core/.mcp.json +2 -5
- package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +4 -18
- package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +32 -9
- package/plugins/huaweicloud-core/openclaw.plugin.json +20 -0
- package/plugins/huaweicloud-core/safety/policy.json +75 -99
- package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +41 -13
- package/plugins/huaweicloud-core/skills/huawei-apig/SKILL.md +34 -34
- package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +18 -18
- package/plugins/huaweicloud-core/skills/huawei-cbr/SKILL.md +19 -19
- package/plugins/huaweicloud-core/skills/huawei-cce/SKILL.md +34 -32
- package/plugins/huaweicloud-core/skills/huawei-cloud-eye/SKILL.md +30 -30
- package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +20 -20
- package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +31 -31
- package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +21 -20
- package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +39 -30
- package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +8 -1
- package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +6 -0
- package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +39 -36
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +15 -4
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +11 -9
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +13 -13
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +40 -39
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +12 -10
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +21 -20
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +19 -19
- package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +20 -20
- package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +27 -23
- package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +27 -27
- package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +65 -39
- package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +9 -9
- package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +33 -25
- package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +51 -44
- package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +19 -19
- package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +12 -7
- package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +13 -12
- package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +4 -4
- package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +63 -58
- package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +128 -42
- package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +26 -26
- package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +39 -39
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +13 -12
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +20 -19
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +15 -15
- package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +18 -18
- package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +13 -13
- package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +25 -2
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +27 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +65 -47
- package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +6 -6
- package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +15 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +0 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +33 -33
- package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +134 -2
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +77 -4
- package/plugins/huaweicloud-core/src/auth/service.mjs +2 -1
- package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +27 -8
- package/plugins/huaweicloud-core/src/icon-library.mjs +176 -0
- package/plugins/huaweicloud-core/src/mcp-server.mjs +31 -3
- package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +73 -0
- package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +94 -0
- package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +4 -1
- package/plugins/huaweicloud-core/src/safety-policy.mjs +123 -68
- package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +14 -10
- package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +35 -27
- package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +526 -8
- package/plugins/huaweicloud-core/src/search-market.mjs +55 -16
- package/plugins/huaweicloud-core/src/setup-cli.mjs +1942 -206
- package/plugins/huaweicloud-core/src/tools.mjs +678 -131
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +72 -38
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +10 -5
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +13 -16
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +12 -16
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +46 -31
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-tunnel-channel.mjs +268 -0
- package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
- package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +11 -5
- package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +0 -190
- package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +0 -124
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
4
5
|
import { evaluateCommandRisk, mergeRiskDecision } from './risk-rule-engine.mjs';
|
|
5
6
|
|
|
6
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -17,9 +18,13 @@ function regexFrom(pattern) {
|
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
function isSecretKeyName(key, policy = DEFAULT_POLICY) {
|
|
20
|
-
const normalized = String(key)
|
|
21
|
+
const normalized = String(key)
|
|
22
|
+
.toLowerCase()
|
|
23
|
+
.replace(/[^a-z0-9]/g, '');
|
|
21
24
|
if (
|
|
22
|
-
/access.*key|secret.*key|security.*token|xauth.*token|authorization|password|passwd|adminpass|credential|private.*key/.test(
|
|
25
|
+
/access.*key|secret.*key|security.*token|xauth.*token|authorization|password|passwd|adminpass|credential|private.*key/.test(
|
|
26
|
+
normalized,
|
|
27
|
+
)
|
|
23
28
|
) {
|
|
24
29
|
return true;
|
|
25
30
|
}
|
|
@@ -28,7 +33,10 @@ function isSecretKeyName(key, policy = DEFAULT_POLICY) {
|
|
|
28
33
|
|
|
29
34
|
function redactString(text) {
|
|
30
35
|
return String(text)
|
|
31
|
-
.replace(
|
|
36
|
+
.replace(
|
|
37
|
+
/((?:access[_-]?key|secret[_-]?key|security[_-]?token|x[_-]?auth[_-]?token|authorization|password|passwd|adminPass|credential)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,;]+)/gi,
|
|
38
|
+
'$1<redacted>',
|
|
39
|
+
)
|
|
32
40
|
.replace(/(AK|SK)\s*[:=]\s*("[^"]*"|'[^']*'|[^\s,;]+)/g, '$1=<redacted>');
|
|
33
41
|
}
|
|
34
42
|
|
|
@@ -78,15 +86,11 @@ function matchesAny(value, patterns) {
|
|
|
78
86
|
|
|
79
87
|
function hasWritePrefix(operation, policy) {
|
|
80
88
|
const normalized = String(operation);
|
|
81
|
-
return policy.writeOperationPrefixes.some((prefix) =>
|
|
82
|
-
new RegExp(`(^|[A-Za-z0-9])${prefix}`, 'i').test(normalized),
|
|
83
|
-
);
|
|
89
|
+
return policy.writeOperationPrefixes.some((prefix) => new RegExp(`(^|[A-Za-z0-9])${prefix}`, 'i').test(normalized));
|
|
84
90
|
}
|
|
85
91
|
|
|
86
92
|
function hasReadPrefix(operation, policy) {
|
|
87
|
-
return policy.readOperationPrefixes.some((prefix) =>
|
|
88
|
-
new RegExp(`^${prefix}`, 'i').test(operation),
|
|
89
|
-
);
|
|
93
|
+
return policy.readOperationPrefixes.some((prefix) => new RegExp(`^${prefix}`, 'i').test(operation));
|
|
90
94
|
}
|
|
91
95
|
|
|
92
96
|
function isLocalMetadataCommand(args) {
|
|
@@ -119,26 +123,31 @@ export function classifyHcloudArgs(args, options = {}) {
|
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
if (isLocalMetadataCommand(normalizedArgs)) {
|
|
122
|
-
return applyCommandRiskRules(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
return applyCommandRiskRules(
|
|
127
|
+
{
|
|
128
|
+
decision: 'allow',
|
|
129
|
+
risk: 'local_metadata',
|
|
130
|
+
reason: 'KooCLI local help and version commands are read-only and do not call Huawei Cloud resource APIs.',
|
|
131
|
+
service,
|
|
132
|
+
operation,
|
|
133
|
+
args: normalizedArgs,
|
|
134
|
+
},
|
|
135
|
+
normalizedArgs,
|
|
136
|
+
options,
|
|
137
|
+
);
|
|
130
138
|
}
|
|
131
139
|
|
|
132
140
|
if (service.toLowerCase() === 'configure') {
|
|
133
141
|
const subcommand = operation.toLowerCase();
|
|
134
142
|
if (
|
|
135
|
-
policy.blockedConfigureSubcommands.map((cmd) => cmd.toLowerCase()).includes(subcommand)
|
|
136
|
-
|
|
143
|
+
policy.blockedConfigureSubcommands.map((cmd) => cmd.toLowerCase()).includes(subcommand) &&
|
|
144
|
+
options.allowCredentialRead !== true
|
|
137
145
|
) {
|
|
138
146
|
return {
|
|
139
147
|
decision: 'deny',
|
|
140
148
|
risk: 'credential',
|
|
141
|
-
reason:
|
|
149
|
+
reason:
|
|
150
|
+
'Direct hcloud configure inspection may expose profile credentials. Use the redacted toolkit tools instead.',
|
|
142
151
|
};
|
|
143
152
|
}
|
|
144
153
|
}
|
|
@@ -160,7 +169,8 @@ export function classifyHcloudArgs(args, options = {}) {
|
|
|
160
169
|
}
|
|
161
170
|
|
|
162
171
|
const readOnly = hasReadPrefix(operation, policy);
|
|
163
|
-
const executionOps =
|
|
172
|
+
const executionOps =
|
|
173
|
+
/(^|\.)(Invoke|SyncInvoke|AsyncInvoke|Send|Trigger|Execute|Start|Reboot|Restart|Stop|Publish|Deploy)/i;
|
|
164
174
|
const isExecution = executionOps.test(operation) && !readOnly;
|
|
165
175
|
const isWrite = !readOnly && hasWritePrefix(operation, policy);
|
|
166
176
|
|
|
@@ -176,11 +186,30 @@ export function classifyHcloudArgs(args, options = {}) {
|
|
|
176
186
|
return {
|
|
177
187
|
decision: 'deny',
|
|
178
188
|
risk: 'write',
|
|
179
|
-
reason:
|
|
189
|
+
reason:
|
|
190
|
+
'Huawei Cloud write operation blocked until the agent presents a plan and receives explicit user approval.',
|
|
180
191
|
};
|
|
181
192
|
}
|
|
182
193
|
|
|
183
|
-
const obsutilWrites = [
|
|
194
|
+
const obsutilWrites = [
|
|
195
|
+
'mb',
|
|
196
|
+
'cp',
|
|
197
|
+
'mv',
|
|
198
|
+
'rm',
|
|
199
|
+
'delete',
|
|
200
|
+
'mkdir',
|
|
201
|
+
'sync',
|
|
202
|
+
'restore',
|
|
203
|
+
'chattri',
|
|
204
|
+
'bucketpolicy',
|
|
205
|
+
'lifecycle',
|
|
206
|
+
'cors',
|
|
207
|
+
'website',
|
|
208
|
+
'sign',
|
|
209
|
+
'share-add',
|
|
210
|
+
'share-update',
|
|
211
|
+
'share-rm',
|
|
212
|
+
];
|
|
184
213
|
const obsutilReads = ['ls', 'stat', 'cat', 'help', 'version'];
|
|
185
214
|
const isObs = service.toLowerCase() === 'obs' || service.toLowerCase() === 'hcloud obs';
|
|
186
215
|
const isObsWrite = isObs && obsutilWrites.includes(operation);
|
|
@@ -193,64 +222,86 @@ export function classifyHcloudArgs(args, options = {}) {
|
|
|
193
222
|
};
|
|
194
223
|
}
|
|
195
224
|
if (isObsRead) {
|
|
196
|
-
return applyCommandRiskRules(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
225
|
+
return applyCommandRiskRules(
|
|
226
|
+
{
|
|
227
|
+
decision: 'allow',
|
|
228
|
+
risk: 'read_only',
|
|
229
|
+
reason: 'OBS read-only operation.',
|
|
230
|
+
service,
|
|
231
|
+
operation,
|
|
232
|
+
args: normalizedArgs,
|
|
233
|
+
},
|
|
234
|
+
normalizedArgs,
|
|
235
|
+
options,
|
|
236
|
+
);
|
|
204
237
|
}
|
|
205
238
|
if (isObsWrite && options.allowWrites) {
|
|
206
|
-
return applyCommandRiskRules(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
239
|
+
return applyCommandRiskRules(
|
|
240
|
+
{
|
|
241
|
+
decision: 'allow',
|
|
242
|
+
risk: 'write',
|
|
243
|
+
reason: 'OBS write operation approved by user.',
|
|
244
|
+
service,
|
|
245
|
+
operation,
|
|
246
|
+
args: normalizedArgs,
|
|
247
|
+
},
|
|
248
|
+
normalizedArgs,
|
|
249
|
+
options,
|
|
250
|
+
);
|
|
214
251
|
}
|
|
215
252
|
|
|
216
253
|
if (isExecution && options.allowWrites) {
|
|
217
|
-
return applyCommandRiskRules(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
254
|
+
return applyCommandRiskRules(
|
|
255
|
+
{
|
|
256
|
+
decision: 'allow',
|
|
257
|
+
risk: 'execution',
|
|
258
|
+
reason: 'Huawei Cloud execution/trigger operation approved by user.',
|
|
259
|
+
service,
|
|
260
|
+
operation,
|
|
261
|
+
args: normalizedArgs,
|
|
262
|
+
},
|
|
263
|
+
normalizedArgs,
|
|
264
|
+
options,
|
|
265
|
+
);
|
|
225
266
|
}
|
|
226
267
|
|
|
227
268
|
if (isWrite && options.allowWrites) {
|
|
228
|
-
return applyCommandRiskRules(
|
|
269
|
+
return applyCommandRiskRules(
|
|
270
|
+
{
|
|
271
|
+
decision: 'allow',
|
|
272
|
+
risk: 'write',
|
|
273
|
+
reason: 'Huawei Cloud write operation approved by user.',
|
|
274
|
+
service,
|
|
275
|
+
operation,
|
|
276
|
+
args: normalizedArgs,
|
|
277
|
+
},
|
|
278
|
+
normalizedArgs,
|
|
279
|
+
options,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return applyCommandRiskRules(
|
|
284
|
+
{
|
|
229
285
|
decision: 'allow',
|
|
230
|
-
risk: '
|
|
231
|
-
reason:
|
|
286
|
+
risk: readOnly ? 'read_only' : 'unknown_read',
|
|
287
|
+
reason: readOnly
|
|
288
|
+
? 'Command appears to be a read-only Huawei Cloud operation.'
|
|
289
|
+
: 'Command does not match a known write or secret operation; treat output as untrusted and redact it.',
|
|
232
290
|
service,
|
|
233
291
|
operation,
|
|
234
292
|
args: normalizedArgs,
|
|
235
|
-
},
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
decision: 'allow',
|
|
240
|
-
risk: readOnly ? 'read_only' : 'unknown_read',
|
|
241
|
-
reason: readOnly
|
|
242
|
-
? 'Command appears to be a read-only Huawei Cloud operation.'
|
|
243
|
-
: 'Command does not match a known write or secret operation; treat output as untrusted and redact it.',
|
|
244
|
-
service,
|
|
245
|
-
operation,
|
|
246
|
-
args: normalizedArgs,
|
|
247
|
-
}, normalizedArgs, options);
|
|
293
|
+
},
|
|
294
|
+
normalizedArgs,
|
|
295
|
+
options,
|
|
296
|
+
);
|
|
248
297
|
}
|
|
249
298
|
|
|
250
299
|
function splitSimpleCommand(command) {
|
|
251
|
-
return
|
|
252
|
-
|
|
253
|
-
|
|
300
|
+
return (
|
|
301
|
+
String(command)
|
|
302
|
+
.match(/"[^"]*"|'[^']*'|\S+/g)
|
|
303
|
+
?.map((part) => part.replace(/^['"]|['"]$/g, '')) || []
|
|
304
|
+
);
|
|
254
305
|
}
|
|
255
306
|
|
|
256
307
|
export function classifyTextCommand(command, options = {}) {
|
|
@@ -261,11 +312,15 @@ export function classifyTextCommand(command, options = {}) {
|
|
|
261
312
|
return {
|
|
262
313
|
decision: 'deny',
|
|
263
314
|
risk: 'credential',
|
|
264
|
-
reason:
|
|
315
|
+
reason:
|
|
316
|
+
'Reading Huawei Cloud credential or profile files is blocked. Use redacted profile inspection tools instead.',
|
|
265
317
|
};
|
|
266
318
|
}
|
|
267
319
|
|
|
268
|
-
if (
|
|
320
|
+
if (
|
|
321
|
+
/(^|\s)(env|printenv|Get-ChildItem\s+Env:|gci\s+Env:|dir\s+Env:)/i.test(text) &&
|
|
322
|
+
/HUAWEICLOUD|HWC_|HCLOUD|OS_/i.test(text)
|
|
323
|
+
) {
|
|
269
324
|
return {
|
|
270
325
|
decision: 'deny',
|
|
271
326
|
risk: 'credential',
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { getCredentials } from './hwlink-api.mjs';
|
|
2
|
+
import { getProxyDispatcher } from '../proxy/proxy-agent.mjs';
|
|
2
3
|
|
|
3
4
|
const HDKIT_BASE_URL =
|
|
4
|
-
process.env.HDKITSERVICE_ENDPOINT ||
|
|
5
|
-
'https://devkit.huaweicloud.com/rest/developer/server/hdkitservice/';
|
|
5
|
+
process.env.HDKITSERVICE_ENDPOINT || 'https://devkit.huaweicloud.com/rest/developer/server/hdkitservice/';
|
|
6
6
|
|
|
7
7
|
async function hdkitRequest(method, path, body, timeoutMs = 300000) {
|
|
8
8
|
const { ak, sk, securitytoken } = getCredentials();
|
|
@@ -22,12 +22,20 @@ async function hdkitRequest(method, path, body, timeoutMs = 300000) {
|
|
|
22
22
|
|
|
23
23
|
let resp;
|
|
24
24
|
try {
|
|
25
|
-
|
|
25
|
+
const dispatcher = await getProxyDispatcher(url);
|
|
26
|
+
const fetchOpts = {
|
|
26
27
|
method,
|
|
27
28
|
headers,
|
|
28
29
|
body: body ? JSON.stringify(body) : undefined,
|
|
29
30
|
signal: controller.signal,
|
|
30
|
-
}
|
|
31
|
+
};
|
|
32
|
+
if (dispatcher) {
|
|
33
|
+
fetchOpts.dispatcher = dispatcher;
|
|
34
|
+
const { fetch: undiciFetch } = await import('undici');
|
|
35
|
+
resp = await undiciFetch(url, fetchOpts);
|
|
36
|
+
} else {
|
|
37
|
+
resp = await fetch(url, fetchOpts);
|
|
38
|
+
}
|
|
31
39
|
} finally {
|
|
32
40
|
clearTimeout(timer);
|
|
33
41
|
}
|
|
@@ -41,12 +49,10 @@ async function hdkitRequest(method, path, body, timeoutMs = 300000) {
|
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
if (!resp.ok) {
|
|
44
|
-
const err = new Error(
|
|
45
|
-
data.message || `hdkitservice error: ${data.code || resp.status}`
|
|
46
|
-
);
|
|
52
|
+
const err = new Error(data.message || `hdkitservice error: ${data.code || resp.status}`);
|
|
47
53
|
err.code = data.code;
|
|
48
54
|
err.status = resp.status;
|
|
49
|
-
err.traceId = data.
|
|
55
|
+
err.traceId = data.traceId; // 后端实际返回驼峰 traceId
|
|
50
56
|
throw err;
|
|
51
57
|
}
|
|
52
58
|
|
|
@@ -83,5 +89,3 @@ export async function hdkitCredentials(sessionId, devStageId, enableSts = true)
|
|
|
83
89
|
|
|
84
90
|
return await hdkitRequest('POST', 'credentials', body);
|
|
85
91
|
}
|
|
86
|
-
|
|
87
|
-
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import { resolveCredentialsWithRuntime } from '../auth/credentials.mjs';
|
|
4
|
+
import { getProxyDispatcher } from '../proxy/proxy-agent.mjs';
|
|
3
5
|
|
|
4
6
|
const BASE_URL = process.env.HWLINK_ENDPOINT || 'https://devstation.myhuaweicloud.com';
|
|
5
7
|
|
|
@@ -13,9 +15,7 @@ function hmacSha256(key, data) {
|
|
|
13
15
|
|
|
14
16
|
function urlEncode(str) {
|
|
15
17
|
const hex = (c) => '%' + (c < 16 ? '0' : '') + c.toString(16).toUpperCase();
|
|
16
|
-
const noEscape = new Set(
|
|
17
|
-
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~'.split('')
|
|
18
|
-
);
|
|
18
|
+
const noEscape = new Set('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~'.split(''));
|
|
19
19
|
let out = '';
|
|
20
20
|
for (const ch of str) {
|
|
21
21
|
const c = ch.codePointAt(0);
|
|
@@ -53,9 +53,7 @@ function signRequest(method, path, query, body, ak, sk, securitytoken) {
|
|
|
53
53
|
.join('/') +
|
|
54
54
|
'/';
|
|
55
55
|
|
|
56
|
-
const signedHeaders = securitytoken
|
|
57
|
-
? 'host;x-sdk-date;x-security-token'
|
|
58
|
-
: 'host;x-sdk-date';
|
|
56
|
+
const signedHeaders = securitytoken ? 'host;x-sdk-date;x-security-token' : 'host;x-sdk-date';
|
|
59
57
|
const canonicalHeaders = securitytoken
|
|
60
58
|
? `host:${host}\nx-sdk-date:${ts}\nx-security-token:${securitytoken}\n`
|
|
61
59
|
: `host:${host}\nx-sdk-date:${ts}\n`;
|
|
@@ -63,14 +61,7 @@ function signRequest(method, path, query, body, ak, sk, securitytoken) {
|
|
|
63
61
|
const bodyStr = body ? JSON.stringify(body) : '';
|
|
64
62
|
const payloadHash = sha256Hex(bodyStr);
|
|
65
63
|
|
|
66
|
-
const canonicalRequest = [
|
|
67
|
-
method,
|
|
68
|
-
curi,
|
|
69
|
-
cqs,
|
|
70
|
-
canonicalHeaders,
|
|
71
|
-
signedHeaders,
|
|
72
|
-
payloadHash,
|
|
73
|
-
].join('\n');
|
|
64
|
+
const canonicalRequest = [method, curi, cqs, canonicalHeaders, signedHeaders, payloadHash].join('\n');
|
|
74
65
|
|
|
75
66
|
const stringToSign = `SDK-HMAC-SHA256\n${ts}\n${sha256Hex(canonicalRequest)}`;
|
|
76
67
|
const signature = hmacSha256(sk, stringToSign);
|
|
@@ -87,7 +78,7 @@ function signRequest(method, path, query, body, ak, sk, securitytoken) {
|
|
|
87
78
|
}
|
|
88
79
|
|
|
89
80
|
export function getCredentials() {
|
|
90
|
-
const credentials =
|
|
81
|
+
const credentials = resolveCredentialsWithRuntime();
|
|
91
82
|
return { ak: credentials.ak, sk: credentials.sk, securitytoken: credentials.securityToken };
|
|
92
83
|
}
|
|
93
84
|
|
|
@@ -96,17 +87,35 @@ async function apiGet(path, query, ak, sk, securitytoken) {
|
|
|
96
87
|
const qs = sortedQs(query);
|
|
97
88
|
const fullPath = qs ? `${path}?${qs}` : path;
|
|
98
89
|
const headers = signRequest('GET', path, query, undefined, ak, sk, securitytoken);
|
|
99
|
-
const
|
|
90
|
+
const url = `${BASE_URL}${fullPath}`;
|
|
91
|
+
const dispatcher = await getProxyDispatcher(url);
|
|
92
|
+
const fetchOpts = { headers };
|
|
93
|
+
if (dispatcher) {
|
|
94
|
+
fetchOpts.dispatcher = dispatcher;
|
|
95
|
+
const { fetch: undiciFetch } = await import('undici');
|
|
96
|
+
const resp = await undiciFetch(url, fetchOpts);
|
|
97
|
+
return { status: resp.status, data: await resp.json() };
|
|
98
|
+
}
|
|
99
|
+
const resp = await fetch(url, fetchOpts);
|
|
100
100
|
return { status: resp.status, data: await resp.json() };
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
async function apiPost(path, body, ak, sk, securitytoken) {
|
|
104
104
|
const headers = signRequest('POST', path, {}, body, ak, sk, securitytoken);
|
|
105
|
-
const
|
|
105
|
+
const url = `${BASE_URL}${path}`;
|
|
106
|
+
const dispatcher = await getProxyDispatcher(url);
|
|
107
|
+
const fetchOpts = {
|
|
106
108
|
method: 'POST',
|
|
107
109
|
headers: { ...headers, 'Content-Type': 'application/json' },
|
|
108
110
|
body: JSON.stringify(body),
|
|
109
|
-
}
|
|
111
|
+
};
|
|
112
|
+
if (dispatcher) {
|
|
113
|
+
fetchOpts.dispatcher = dispatcher;
|
|
114
|
+
const { fetch: undiciFetch } = await import('undici');
|
|
115
|
+
const resp = await undiciFetch(url, fetchOpts);
|
|
116
|
+
return { status: resp.status, data: await resp.json() };
|
|
117
|
+
}
|
|
118
|
+
const resp = await fetch(url, fetchOpts);
|
|
110
119
|
return { status: resp.status, data: await resp.json() };
|
|
111
120
|
}
|
|
112
121
|
|
|
@@ -114,7 +123,9 @@ export async function createConnection(envId, ak, sk, securitytoken) {
|
|
|
114
123
|
const { status, data } = await apiPost(
|
|
115
124
|
`/open-api-public/v1/devenvs/${envId}/connections`,
|
|
116
125
|
{ source: 'CLI' },
|
|
117
|
-
ak,
|
|
126
|
+
ak,
|
|
127
|
+
sk,
|
|
128
|
+
securitytoken,
|
|
118
129
|
);
|
|
119
130
|
|
|
120
131
|
if (status !== 200 || data?.error_code !== '0000' || !data?.result?.connection_id) {
|
|
@@ -129,13 +140,12 @@ export async function createConnection(envId, ak, sk, securitytoken) {
|
|
|
129
140
|
const { data: getData } = await apiGet(
|
|
130
141
|
`/open-api-public/v1/devenvs/${envId}/connections/${connectionId}`,
|
|
131
142
|
{},
|
|
132
|
-
ak,
|
|
143
|
+
ak,
|
|
144
|
+
sk,
|
|
145
|
+
securitytoken,
|
|
133
146
|
);
|
|
134
147
|
|
|
135
|
-
if (
|
|
136
|
-
getData?.result?.connection_info?.url &&
|
|
137
|
-
getData.result.connection_info.extensions?.source != null
|
|
138
|
-
) {
|
|
148
|
+
if (getData?.result?.connection_info?.url && getData.result.connection_info.extensions?.source != null) {
|
|
139
149
|
const u = new URL(getData.result.connection_info.url);
|
|
140
150
|
u.searchParams.set('source', String(getData.result.connection_info.extensions.source));
|
|
141
151
|
process.stderr.write(`\rConnection ${connectionId} established (${i}s).\n`);
|
|
@@ -149,5 +159,3 @@ export async function createConnection(envId, ak, sk, securitytoken) {
|
|
|
149
159
|
|
|
150
160
|
throw new Error(`Timed out waiting for connection ${connectionId}`);
|
|
151
161
|
}
|
|
152
|
-
|
|
153
|
-
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import hashlib, http.server, json, os, sys
|
|
3
|
+
class UploadHandler(http.server.BaseHTTPRequestHandler):
|
|
4
|
+
def do_POST(self):
|
|
5
|
+
if self.path != '/upload':
|
|
6
|
+
self.send_error(404)
|
|
7
|
+
return
|
|
8
|
+
target_path = self.headers.get('X-Target-Path', '/workspace/upload.tar.gz')
|
|
9
|
+
content_length = int(self.headers.get('Content-Length', 0))
|
|
10
|
+
os.makedirs(os.path.dirname(target_path), exist_ok=True)
|
|
11
|
+
md5 = hashlib.md5()
|
|
12
|
+
bytes_written = 0
|
|
13
|
+
with open(target_path, 'wb') as f:
|
|
14
|
+
remaining = content_length
|
|
15
|
+
while remaining > 0:
|
|
16
|
+
chunk = self.rfile.read(min(remaining, 65536))
|
|
17
|
+
if not chunk:
|
|
18
|
+
break
|
|
19
|
+
f.write(chunk)
|
|
20
|
+
md5.update(chunk)
|
|
21
|
+
bytes_written += len(chunk)
|
|
22
|
+
remaining -= len(chunk)
|
|
23
|
+
result = json.dumps({'ok': True, 'path': target_path, 'bytes': bytes_written, 'md5': md5.hexdigest()})
|
|
24
|
+
self.send_response(200)
|
|
25
|
+
self.send_header('Content-Type', 'application/json')
|
|
26
|
+
self.send_header('Content-Length', str(len(result)))
|
|
27
|
+
self.end_headers()
|
|
28
|
+
self.wfile.write(result.encode())
|
|
29
|
+
def do_GET(self):
|
|
30
|
+
if self.path == '/health':
|
|
31
|
+
body = b'ok'
|
|
32
|
+
self.send_response(200)
|
|
33
|
+
self.send_header('Content-Length', str(len(body)))
|
|
34
|
+
self.end_headers()
|
|
35
|
+
self.wfile.write(body)
|
|
36
|
+
else:
|
|
37
|
+
self.send_error(404)
|
|
38
|
+
def log_message(self, format, *args):
|
|
39
|
+
pass
|
|
40
|
+
if __name__ == '__main__':
|
|
41
|
+
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8888
|
|
42
|
+
token = sys.argv[2] if len(sys.argv) > 2 else ''
|
|
43
|
+
server = http.server.HTTPServer(('127.0.0.1', port), UploadHandler)
|
|
44
|
+
server.upload_token = token
|
|
45
|
+
server.serve_forever()
|