huaweicloud-devkit 1.0.3-dev.0 → 1.0.3-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 +99 -41
- package/README.zh-CN.md +99 -41
- package/bin/setup.cjs +5 -3
- package/integrations/opencode/opencode.json +1 -4
- package/package.json +61 -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 +123 -2
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +77 -4
- package/plugins/huaweicloud-core/src/auth/service.mjs +1 -1
- package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +25 -7
- package/plugins/huaweicloud-core/src/icon-library.mjs +171 -0
- package/plugins/huaweicloud-core/src/mcp-server.mjs +30 -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 +122 -68
- package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +14 -10
- package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +34 -27
- package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +521 -8
- package/plugins/huaweicloud-core/src/search-market.mjs +53 -14
- package/plugins/huaweicloud-core/src/setup-cli.mjs +1741 -215
- package/plugins/huaweicloud-core/src/tools.mjs +667 -123
- 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 +12 -15
- 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 +267 -0
- package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
- package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +9 -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
|
@@ -17,9 +17,13 @@ function regexFrom(pattern) {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
function isSecretKeyName(key, policy = DEFAULT_POLICY) {
|
|
20
|
-
const normalized = String(key)
|
|
20
|
+
const normalized = String(key)
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.replace(/[^a-z0-9]/g, '');
|
|
21
23
|
if (
|
|
22
|
-
/access.*key|secret.*key|security.*token|xauth.*token|authorization|password|passwd|adminpass|credential|private.*key/.test(
|
|
24
|
+
/access.*key|secret.*key|security.*token|xauth.*token|authorization|password|passwd|adminpass|credential|private.*key/.test(
|
|
25
|
+
normalized,
|
|
26
|
+
)
|
|
23
27
|
) {
|
|
24
28
|
return true;
|
|
25
29
|
}
|
|
@@ -28,7 +32,10 @@ function isSecretKeyName(key, policy = DEFAULT_POLICY) {
|
|
|
28
32
|
|
|
29
33
|
function redactString(text) {
|
|
30
34
|
return String(text)
|
|
31
|
-
.replace(
|
|
35
|
+
.replace(
|
|
36
|
+
/((?:access[_-]?key|secret[_-]?key|security[_-]?token|x[_-]?auth[_-]?token|authorization|password|passwd|adminPass|credential)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,;]+)/gi,
|
|
37
|
+
'$1<redacted>',
|
|
38
|
+
)
|
|
32
39
|
.replace(/(AK|SK)\s*[:=]\s*("[^"]*"|'[^']*'|[^\s,;]+)/g, '$1=<redacted>');
|
|
33
40
|
}
|
|
34
41
|
|
|
@@ -78,15 +85,11 @@ function matchesAny(value, patterns) {
|
|
|
78
85
|
|
|
79
86
|
function hasWritePrefix(operation, policy) {
|
|
80
87
|
const normalized = String(operation);
|
|
81
|
-
return policy.writeOperationPrefixes.some((prefix) =>
|
|
82
|
-
new RegExp(`(^|[A-Za-z0-9])${prefix}`, 'i').test(normalized),
|
|
83
|
-
);
|
|
88
|
+
return policy.writeOperationPrefixes.some((prefix) => new RegExp(`(^|[A-Za-z0-9])${prefix}`, 'i').test(normalized));
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
function hasReadPrefix(operation, policy) {
|
|
87
|
-
return policy.readOperationPrefixes.some((prefix) =>
|
|
88
|
-
new RegExp(`^${prefix}`, 'i').test(operation),
|
|
89
|
-
);
|
|
92
|
+
return policy.readOperationPrefixes.some((prefix) => new RegExp(`^${prefix}`, 'i').test(operation));
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
function isLocalMetadataCommand(args) {
|
|
@@ -119,26 +122,31 @@ export function classifyHcloudArgs(args, options = {}) {
|
|
|
119
122
|
}
|
|
120
123
|
|
|
121
124
|
if (isLocalMetadataCommand(normalizedArgs)) {
|
|
122
|
-
return applyCommandRiskRules(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
125
|
+
return applyCommandRiskRules(
|
|
126
|
+
{
|
|
127
|
+
decision: 'allow',
|
|
128
|
+
risk: 'local_metadata',
|
|
129
|
+
reason: 'KooCLI local help and version commands are read-only and do not call Huawei Cloud resource APIs.',
|
|
130
|
+
service,
|
|
131
|
+
operation,
|
|
132
|
+
args: normalizedArgs,
|
|
133
|
+
},
|
|
134
|
+
normalizedArgs,
|
|
135
|
+
options,
|
|
136
|
+
);
|
|
130
137
|
}
|
|
131
138
|
|
|
132
139
|
if (service.toLowerCase() === 'configure') {
|
|
133
140
|
const subcommand = operation.toLowerCase();
|
|
134
141
|
if (
|
|
135
|
-
policy.blockedConfigureSubcommands.map((cmd) => cmd.toLowerCase()).includes(subcommand)
|
|
136
|
-
|
|
142
|
+
policy.blockedConfigureSubcommands.map((cmd) => cmd.toLowerCase()).includes(subcommand) &&
|
|
143
|
+
options.allowCredentialRead !== true
|
|
137
144
|
) {
|
|
138
145
|
return {
|
|
139
146
|
decision: 'deny',
|
|
140
147
|
risk: 'credential',
|
|
141
|
-
reason:
|
|
148
|
+
reason:
|
|
149
|
+
'Direct hcloud configure inspection may expose profile credentials. Use the redacted toolkit tools instead.',
|
|
142
150
|
};
|
|
143
151
|
}
|
|
144
152
|
}
|
|
@@ -160,7 +168,8 @@ export function classifyHcloudArgs(args, options = {}) {
|
|
|
160
168
|
}
|
|
161
169
|
|
|
162
170
|
const readOnly = hasReadPrefix(operation, policy);
|
|
163
|
-
const executionOps =
|
|
171
|
+
const executionOps =
|
|
172
|
+
/(^|\.)(Invoke|SyncInvoke|AsyncInvoke|Send|Trigger|Execute|Start|Reboot|Restart|Stop|Publish|Deploy)/i;
|
|
164
173
|
const isExecution = executionOps.test(operation) && !readOnly;
|
|
165
174
|
const isWrite = !readOnly && hasWritePrefix(operation, policy);
|
|
166
175
|
|
|
@@ -176,11 +185,30 @@ export function classifyHcloudArgs(args, options = {}) {
|
|
|
176
185
|
return {
|
|
177
186
|
decision: 'deny',
|
|
178
187
|
risk: 'write',
|
|
179
|
-
reason:
|
|
188
|
+
reason:
|
|
189
|
+
'Huawei Cloud write operation blocked until the agent presents a plan and receives explicit user approval.',
|
|
180
190
|
};
|
|
181
191
|
}
|
|
182
192
|
|
|
183
|
-
const obsutilWrites = [
|
|
193
|
+
const obsutilWrites = [
|
|
194
|
+
'mb',
|
|
195
|
+
'cp',
|
|
196
|
+
'mv',
|
|
197
|
+
'rm',
|
|
198
|
+
'delete',
|
|
199
|
+
'mkdir',
|
|
200
|
+
'sync',
|
|
201
|
+
'restore',
|
|
202
|
+
'chattri',
|
|
203
|
+
'bucketpolicy',
|
|
204
|
+
'lifecycle',
|
|
205
|
+
'cors',
|
|
206
|
+
'website',
|
|
207
|
+
'sign',
|
|
208
|
+
'share-add',
|
|
209
|
+
'share-update',
|
|
210
|
+
'share-rm',
|
|
211
|
+
];
|
|
184
212
|
const obsutilReads = ['ls', 'stat', 'cat', 'help', 'version'];
|
|
185
213
|
const isObs = service.toLowerCase() === 'obs' || service.toLowerCase() === 'hcloud obs';
|
|
186
214
|
const isObsWrite = isObs && obsutilWrites.includes(operation);
|
|
@@ -193,64 +221,86 @@ export function classifyHcloudArgs(args, options = {}) {
|
|
|
193
221
|
};
|
|
194
222
|
}
|
|
195
223
|
if (isObsRead) {
|
|
196
|
-
return applyCommandRiskRules(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
224
|
+
return applyCommandRiskRules(
|
|
225
|
+
{
|
|
226
|
+
decision: 'allow',
|
|
227
|
+
risk: 'read_only',
|
|
228
|
+
reason: 'OBS read-only operation.',
|
|
229
|
+
service,
|
|
230
|
+
operation,
|
|
231
|
+
args: normalizedArgs,
|
|
232
|
+
},
|
|
233
|
+
normalizedArgs,
|
|
234
|
+
options,
|
|
235
|
+
);
|
|
204
236
|
}
|
|
205
237
|
if (isObsWrite && options.allowWrites) {
|
|
206
|
-
return applyCommandRiskRules(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
238
|
+
return applyCommandRiskRules(
|
|
239
|
+
{
|
|
240
|
+
decision: 'allow',
|
|
241
|
+
risk: 'write',
|
|
242
|
+
reason: 'OBS write operation approved by user.',
|
|
243
|
+
service,
|
|
244
|
+
operation,
|
|
245
|
+
args: normalizedArgs,
|
|
246
|
+
},
|
|
247
|
+
normalizedArgs,
|
|
248
|
+
options,
|
|
249
|
+
);
|
|
214
250
|
}
|
|
215
251
|
|
|
216
252
|
if (isExecution && options.allowWrites) {
|
|
217
|
-
return applyCommandRiskRules(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
253
|
+
return applyCommandRiskRules(
|
|
254
|
+
{
|
|
255
|
+
decision: 'allow',
|
|
256
|
+
risk: 'execution',
|
|
257
|
+
reason: 'Huawei Cloud execution/trigger operation approved by user.',
|
|
258
|
+
service,
|
|
259
|
+
operation,
|
|
260
|
+
args: normalizedArgs,
|
|
261
|
+
},
|
|
262
|
+
normalizedArgs,
|
|
263
|
+
options,
|
|
264
|
+
);
|
|
225
265
|
}
|
|
226
266
|
|
|
227
267
|
if (isWrite && options.allowWrites) {
|
|
228
|
-
return applyCommandRiskRules(
|
|
268
|
+
return applyCommandRiskRules(
|
|
269
|
+
{
|
|
270
|
+
decision: 'allow',
|
|
271
|
+
risk: 'write',
|
|
272
|
+
reason: 'Huawei Cloud write operation approved by user.',
|
|
273
|
+
service,
|
|
274
|
+
operation,
|
|
275
|
+
args: normalizedArgs,
|
|
276
|
+
},
|
|
277
|
+
normalizedArgs,
|
|
278
|
+
options,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return applyCommandRiskRules(
|
|
283
|
+
{
|
|
229
284
|
decision: 'allow',
|
|
230
|
-
risk: '
|
|
231
|
-
reason:
|
|
285
|
+
risk: readOnly ? 'read_only' : 'unknown_read',
|
|
286
|
+
reason: readOnly
|
|
287
|
+
? 'Command appears to be a read-only Huawei Cloud operation.'
|
|
288
|
+
: 'Command does not match a known write or secret operation; treat output as untrusted and redact it.',
|
|
232
289
|
service,
|
|
233
290
|
operation,
|
|
234
291
|
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);
|
|
292
|
+
},
|
|
293
|
+
normalizedArgs,
|
|
294
|
+
options,
|
|
295
|
+
);
|
|
248
296
|
}
|
|
249
297
|
|
|
250
298
|
function splitSimpleCommand(command) {
|
|
251
|
-
return
|
|
252
|
-
|
|
253
|
-
|
|
299
|
+
return (
|
|
300
|
+
String(command)
|
|
301
|
+
.match(/"[^"]*"|'[^']*'|\S+/g)
|
|
302
|
+
?.map((part) => part.replace(/^['"]|['"]$/g, '')) || []
|
|
303
|
+
);
|
|
254
304
|
}
|
|
255
305
|
|
|
256
306
|
export function classifyTextCommand(command, options = {}) {
|
|
@@ -261,11 +311,15 @@ export function classifyTextCommand(command, options = {}) {
|
|
|
261
311
|
return {
|
|
262
312
|
decision: 'deny',
|
|
263
313
|
risk: 'credential',
|
|
264
|
-
reason:
|
|
314
|
+
reason:
|
|
315
|
+
'Reading Huawei Cloud credential or profile files is blocked. Use redacted profile inspection tools instead.',
|
|
265
316
|
};
|
|
266
317
|
}
|
|
267
318
|
|
|
268
|
-
if (
|
|
319
|
+
if (
|
|
320
|
+
/(^|\s)(env|printenv|Get-ChildItem\s+Env:|gci\s+Env:|dir\s+Env:)/i.test(text) &&
|
|
321
|
+
/HUAWEICLOUD|HWC_|HCLOUD|OS_/i.test(text)
|
|
322
|
+
) {
|
|
269
323
|
return {
|
|
270
324
|
decision: 'deny',
|
|
271
325
|
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,6 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { resolveCredentialsWithRuntime } from '../auth/credentials.mjs';
|
|
3
|
+
import { getProxyDispatcher } from '../proxy/proxy-agent.mjs';
|
|
3
4
|
|
|
4
5
|
const BASE_URL = process.env.HWLINK_ENDPOINT || 'https://devstation.myhuaweicloud.com';
|
|
5
6
|
|
|
@@ -13,9 +14,7 @@ function hmacSha256(key, data) {
|
|
|
13
14
|
|
|
14
15
|
function urlEncode(str) {
|
|
15
16
|
const hex = (c) => '%' + (c < 16 ? '0' : '') + c.toString(16).toUpperCase();
|
|
16
|
-
const noEscape = new Set(
|
|
17
|
-
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~'.split('')
|
|
18
|
-
);
|
|
17
|
+
const noEscape = new Set('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~'.split(''));
|
|
19
18
|
let out = '';
|
|
20
19
|
for (const ch of str) {
|
|
21
20
|
const c = ch.codePointAt(0);
|
|
@@ -53,9 +52,7 @@ function signRequest(method, path, query, body, ak, sk, securitytoken) {
|
|
|
53
52
|
.join('/') +
|
|
54
53
|
'/';
|
|
55
54
|
|
|
56
|
-
const signedHeaders = securitytoken
|
|
57
|
-
? 'host;x-sdk-date;x-security-token'
|
|
58
|
-
: 'host;x-sdk-date';
|
|
55
|
+
const signedHeaders = securitytoken ? 'host;x-sdk-date;x-security-token' : 'host;x-sdk-date';
|
|
59
56
|
const canonicalHeaders = securitytoken
|
|
60
57
|
? `host:${host}\nx-sdk-date:${ts}\nx-security-token:${securitytoken}\n`
|
|
61
58
|
: `host:${host}\nx-sdk-date:${ts}\n`;
|
|
@@ -63,14 +60,7 @@ function signRequest(method, path, query, body, ak, sk, securitytoken) {
|
|
|
63
60
|
const bodyStr = body ? JSON.stringify(body) : '';
|
|
64
61
|
const payloadHash = sha256Hex(bodyStr);
|
|
65
62
|
|
|
66
|
-
const canonicalRequest = [
|
|
67
|
-
method,
|
|
68
|
-
curi,
|
|
69
|
-
cqs,
|
|
70
|
-
canonicalHeaders,
|
|
71
|
-
signedHeaders,
|
|
72
|
-
payloadHash,
|
|
73
|
-
].join('\n');
|
|
63
|
+
const canonicalRequest = [method, curi, cqs, canonicalHeaders, signedHeaders, payloadHash].join('\n');
|
|
74
64
|
|
|
75
65
|
const stringToSign = `SDK-HMAC-SHA256\n${ts}\n${sha256Hex(canonicalRequest)}`;
|
|
76
66
|
const signature = hmacSha256(sk, stringToSign);
|
|
@@ -87,7 +77,7 @@ function signRequest(method, path, query, body, ak, sk, securitytoken) {
|
|
|
87
77
|
}
|
|
88
78
|
|
|
89
79
|
export function getCredentials() {
|
|
90
|
-
const credentials =
|
|
80
|
+
const credentials = resolveCredentialsWithRuntime();
|
|
91
81
|
return { ak: credentials.ak, sk: credentials.sk, securitytoken: credentials.securityToken };
|
|
92
82
|
}
|
|
93
83
|
|
|
@@ -96,17 +86,35 @@ async function apiGet(path, query, ak, sk, securitytoken) {
|
|
|
96
86
|
const qs = sortedQs(query);
|
|
97
87
|
const fullPath = qs ? `${path}?${qs}` : path;
|
|
98
88
|
const headers = signRequest('GET', path, query, undefined, ak, sk, securitytoken);
|
|
99
|
-
const
|
|
89
|
+
const url = `${BASE_URL}${fullPath}`;
|
|
90
|
+
const dispatcher = await getProxyDispatcher(url);
|
|
91
|
+
const fetchOpts = { headers };
|
|
92
|
+
if (dispatcher) {
|
|
93
|
+
fetchOpts.dispatcher = dispatcher;
|
|
94
|
+
const { fetch: undiciFetch } = await import('undici');
|
|
95
|
+
const resp = await undiciFetch(url, fetchOpts);
|
|
96
|
+
return { status: resp.status, data: await resp.json() };
|
|
97
|
+
}
|
|
98
|
+
const resp = await fetch(url, fetchOpts);
|
|
100
99
|
return { status: resp.status, data: await resp.json() };
|
|
101
100
|
}
|
|
102
101
|
|
|
103
102
|
async function apiPost(path, body, ak, sk, securitytoken) {
|
|
104
103
|
const headers = signRequest('POST', path, {}, body, ak, sk, securitytoken);
|
|
105
|
-
const
|
|
104
|
+
const url = `${BASE_URL}${path}`;
|
|
105
|
+
const dispatcher = await getProxyDispatcher(url);
|
|
106
|
+
const fetchOpts = {
|
|
106
107
|
method: 'POST',
|
|
107
108
|
headers: { ...headers, 'Content-Type': 'application/json' },
|
|
108
109
|
body: JSON.stringify(body),
|
|
109
|
-
}
|
|
110
|
+
};
|
|
111
|
+
if (dispatcher) {
|
|
112
|
+
fetchOpts.dispatcher = dispatcher;
|
|
113
|
+
const { fetch: undiciFetch } = await import('undici');
|
|
114
|
+
const resp = await undiciFetch(url, fetchOpts);
|
|
115
|
+
return { status: resp.status, data: await resp.json() };
|
|
116
|
+
}
|
|
117
|
+
const resp = await fetch(url, fetchOpts);
|
|
110
118
|
return { status: resp.status, data: await resp.json() };
|
|
111
119
|
}
|
|
112
120
|
|
|
@@ -114,7 +122,9 @@ export async function createConnection(envId, ak, sk, securitytoken) {
|
|
|
114
122
|
const { status, data } = await apiPost(
|
|
115
123
|
`/open-api-public/v1/devenvs/${envId}/connections`,
|
|
116
124
|
{ source: 'CLI' },
|
|
117
|
-
ak,
|
|
125
|
+
ak,
|
|
126
|
+
sk,
|
|
127
|
+
securitytoken,
|
|
118
128
|
);
|
|
119
129
|
|
|
120
130
|
if (status !== 200 || data?.error_code !== '0000' || !data?.result?.connection_id) {
|
|
@@ -129,13 +139,12 @@ export async function createConnection(envId, ak, sk, securitytoken) {
|
|
|
129
139
|
const { data: getData } = await apiGet(
|
|
130
140
|
`/open-api-public/v1/devenvs/${envId}/connections/${connectionId}`,
|
|
131
141
|
{},
|
|
132
|
-
ak,
|
|
142
|
+
ak,
|
|
143
|
+
sk,
|
|
144
|
+
securitytoken,
|
|
133
145
|
);
|
|
134
146
|
|
|
135
|
-
if (
|
|
136
|
-
getData?.result?.connection_info?.url &&
|
|
137
|
-
getData.result.connection_info.extensions?.source != null
|
|
138
|
-
) {
|
|
147
|
+
if (getData?.result?.connection_info?.url && getData.result.connection_info.extensions?.source != null) {
|
|
139
148
|
const u = new URL(getData.result.connection_info.url);
|
|
140
149
|
u.searchParams.set('source', String(getData.result.connection_info.extensions.source));
|
|
141
150
|
process.stderr.write(`\rConnection ${connectionId} established (${i}s).\n`);
|
|
@@ -149,5 +158,3 @@ export async function createConnection(envId, ak, sk, securitytoken) {
|
|
|
149
158
|
|
|
150
159
|
throw new Error(`Timed out waiting for connection ${connectionId}`);
|
|
151
160
|
}
|
|
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()
|