huaweicloud-devkit 1.0.2-next.2 → 1.0.2-next.21
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 +81 -51
- package/README.zh-CN.md +81 -49
- package/bin/setup.cjs +5 -3
- package/integrations/opencode/opencode.json +1 -4
- package/package.json +61 -44
- 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-cloud-find-skills/SKILL.md +13 -12
- package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +64 -17
- 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 +25 -25
- package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +45 -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 +127 -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 +17 -1
- 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 +56 -53
- 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 +108 -5
- 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 +20 -102
- package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +11 -4
- 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 +9 -9
- package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +24 -26
- package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +510 -10
- package/plugins/huaweicloud-core/src/search-market.mjs +46 -12
- package/plugins/huaweicloud-core/src/setup-cli.mjs +1251 -181
- package/plugins/huaweicloud-core/src/tools.mjs +600 -122
- 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
|
@@ -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',
|
|
@@ -2,8 +2,7 @@ import { getCredentials } from './hwlink-api.mjs';
|
|
|
2
2
|
import { getProxyDispatcher } from '../proxy/proxy-agent.mjs';
|
|
3
3
|
|
|
4
4
|
const HDKIT_BASE_URL =
|
|
5
|
-
process.env.HDKITSERVICE_ENDPOINT ||
|
|
6
|
-
'https://devkit.huaweicloud.com/rest/developer/server/hdkitservice/';
|
|
5
|
+
process.env.HDKITSERVICE_ENDPOINT || 'https://devkit.huaweicloud.com/rest/developer/server/hdkitservice/';
|
|
7
6
|
|
|
8
7
|
async function hdkitRequest(method, path, body, timeoutMs = 300000) {
|
|
9
8
|
const { ak, sk, securitytoken } = getCredentials();
|
|
@@ -30,8 +29,13 @@ async function hdkitRequest(method, path, body, timeoutMs = 300000) {
|
|
|
30
29
|
body: body ? JSON.stringify(body) : undefined,
|
|
31
30
|
signal: controller.signal,
|
|
32
31
|
};
|
|
33
|
-
if (dispatcher)
|
|
34
|
-
|
|
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
|
+
}
|
|
35
39
|
} finally {
|
|
36
40
|
clearTimeout(timer);
|
|
37
41
|
}
|
|
@@ -45,9 +49,7 @@ async function hdkitRequest(method, path, body, timeoutMs = 300000) {
|
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
if (!resp.ok) {
|
|
48
|
-
const err = new Error(
|
|
49
|
-
data.message || `hdkitservice error: ${data.code || resp.status}`
|
|
50
|
-
);
|
|
52
|
+
const err = new Error(data.message || `hdkitservice error: ${data.code || resp.status}`);
|
|
51
53
|
err.code = data.code;
|
|
52
54
|
err.status = resp.status;
|
|
53
55
|
err.traceId = data.traceId; // 后端实际返回驼峰 traceId
|
|
@@ -87,5 +89,3 @@ export async function hdkitCredentials(sessionId, devStageId, enableSts = true)
|
|
|
87
89
|
|
|
88
90
|
return await hdkitRequest('POST', 'credentials', body);
|
|
89
91
|
}
|
|
90
|
-
|
|
91
|
-
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { resolveCredentialsWithRuntime } from '../auth/credentials.mjs';
|
|
3
3
|
import { getProxyDispatcher } from '../proxy/proxy-agent.mjs';
|
|
4
4
|
|
|
5
5
|
const BASE_URL = process.env.HWLINK_ENDPOINT || 'https://devstation.myhuaweicloud.com';
|
|
@@ -14,9 +14,7 @@ function hmacSha256(key, data) {
|
|
|
14
14
|
|
|
15
15
|
function urlEncode(str) {
|
|
16
16
|
const hex = (c) => '%' + (c < 16 ? '0' : '') + c.toString(16).toUpperCase();
|
|
17
|
-
const noEscape = new Set(
|
|
18
|
-
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~'.split('')
|
|
19
|
-
);
|
|
17
|
+
const noEscape = new Set('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~'.split(''));
|
|
20
18
|
let out = '';
|
|
21
19
|
for (const ch of str) {
|
|
22
20
|
const c = ch.codePointAt(0);
|
|
@@ -54,9 +52,7 @@ function signRequest(method, path, query, body, ak, sk, securitytoken) {
|
|
|
54
52
|
.join('/') +
|
|
55
53
|
'/';
|
|
56
54
|
|
|
57
|
-
const signedHeaders = securitytoken
|
|
58
|
-
? 'host;x-sdk-date;x-security-token'
|
|
59
|
-
: 'host;x-sdk-date';
|
|
55
|
+
const signedHeaders = securitytoken ? 'host;x-sdk-date;x-security-token' : 'host;x-sdk-date';
|
|
60
56
|
const canonicalHeaders = securitytoken
|
|
61
57
|
? `host:${host}\nx-sdk-date:${ts}\nx-security-token:${securitytoken}\n`
|
|
62
58
|
: `host:${host}\nx-sdk-date:${ts}\n`;
|
|
@@ -64,14 +60,7 @@ function signRequest(method, path, query, body, ak, sk, securitytoken) {
|
|
|
64
60
|
const bodyStr = body ? JSON.stringify(body) : '';
|
|
65
61
|
const payloadHash = sha256Hex(bodyStr);
|
|
66
62
|
|
|
67
|
-
const canonicalRequest = [
|
|
68
|
-
method,
|
|
69
|
-
curi,
|
|
70
|
-
cqs,
|
|
71
|
-
canonicalHeaders,
|
|
72
|
-
signedHeaders,
|
|
73
|
-
payloadHash,
|
|
74
|
-
].join('\n');
|
|
63
|
+
const canonicalRequest = [method, curi, cqs, canonicalHeaders, signedHeaders, payloadHash].join('\n');
|
|
75
64
|
|
|
76
65
|
const stringToSign = `SDK-HMAC-SHA256\n${ts}\n${sha256Hex(canonicalRequest)}`;
|
|
77
66
|
const signature = hmacSha256(sk, stringToSign);
|
|
@@ -88,7 +77,7 @@ function signRequest(method, path, query, body, ak, sk, securitytoken) {
|
|
|
88
77
|
}
|
|
89
78
|
|
|
90
79
|
export function getCredentials() {
|
|
91
|
-
const credentials =
|
|
80
|
+
const credentials = resolveCredentialsWithRuntime();
|
|
92
81
|
return { ak: credentials.ak, sk: credentials.sk, securitytoken: credentials.securityToken };
|
|
93
82
|
}
|
|
94
83
|
|
|
@@ -100,7 +89,12 @@ async function apiGet(path, query, ak, sk, securitytoken) {
|
|
|
100
89
|
const url = `${BASE_URL}${fullPath}`;
|
|
101
90
|
const dispatcher = await getProxyDispatcher(url);
|
|
102
91
|
const fetchOpts = { headers };
|
|
103
|
-
if (dispatcher)
|
|
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
|
+
}
|
|
104
98
|
const resp = await fetch(url, fetchOpts);
|
|
105
99
|
return { status: resp.status, data: await resp.json() };
|
|
106
100
|
}
|
|
@@ -114,7 +108,12 @@ async function apiPost(path, body, ak, sk, securitytoken) {
|
|
|
114
108
|
headers: { ...headers, 'Content-Type': 'application/json' },
|
|
115
109
|
body: JSON.stringify(body),
|
|
116
110
|
};
|
|
117
|
-
if (dispatcher)
|
|
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
|
+
}
|
|
118
117
|
const resp = await fetch(url, fetchOpts);
|
|
119
118
|
return { status: resp.status, data: await resp.json() };
|
|
120
119
|
}
|
|
@@ -123,7 +122,9 @@ export async function createConnection(envId, ak, sk, securitytoken) {
|
|
|
123
122
|
const { status, data } = await apiPost(
|
|
124
123
|
`/open-api-public/v1/devenvs/${envId}/connections`,
|
|
125
124
|
{ source: 'CLI' },
|
|
126
|
-
ak,
|
|
125
|
+
ak,
|
|
126
|
+
sk,
|
|
127
|
+
securitytoken,
|
|
127
128
|
);
|
|
128
129
|
|
|
129
130
|
if (status !== 200 || data?.error_code !== '0000' || !data?.result?.connection_id) {
|
|
@@ -138,13 +139,12 @@ export async function createConnection(envId, ak, sk, securitytoken) {
|
|
|
138
139
|
const { data: getData } = await apiGet(
|
|
139
140
|
`/open-api-public/v1/devenvs/${envId}/connections/${connectionId}`,
|
|
140
141
|
{},
|
|
141
|
-
ak,
|
|
142
|
+
ak,
|
|
143
|
+
sk,
|
|
144
|
+
securitytoken,
|
|
142
145
|
);
|
|
143
146
|
|
|
144
|
-
if (
|
|
145
|
-
getData?.result?.connection_info?.url &&
|
|
146
|
-
getData.result.connection_info.extensions?.source != null
|
|
147
|
-
) {
|
|
147
|
+
if (getData?.result?.connection_info?.url && getData.result.connection_info.extensions?.source != null) {
|
|
148
148
|
const u = new URL(getData.result.connection_info.url);
|
|
149
149
|
u.searchParams.set('source', String(getData.result.connection_info.extensions.source));
|
|
150
150
|
process.stderr.write(`\rConnection ${connectionId} established (${i}s).\n`);
|
|
@@ -158,5 +158,3 @@ export async function createConnection(envId, ak, sk, securitytoken) {
|
|
|
158
158
|
|
|
159
159
|
throw new Error(`Timed out waiting for connection ${connectionId}`);
|
|
160
160
|
}
|
|
161
|
-
|
|
162
|
-
|
|
@@ -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()
|