Skip to content

Commit b443800

Browse files
authored
Merge pull request #451 from miclle/sandbox
feat(sandbox): add sandbox module
2 parents 7c22513 + 32685e9 commit b443800

47 files changed

Lines changed: 10774 additions & 2 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Copy this file to .env in the project root before running sandbox examples
2+
# or sandbox integration tests.
3+
4+
# Sandbox API key auth.
5+
QINIU_SANDBOX_API_KEY=
6+
7+
# Optional custom endpoint.
8+
QINIU_SANDBOX_ENDPOINT=
9+
10+
# Optional template alias or ID. Defaults to base.
11+
QINIU_SANDBOX_TEMPLATE=base
12+
13+
# Required only for injection-rule and Kodo resource examples.
14+
QINIU_SANDBOX_ACCESS_KEY=
15+
QINIU_SANDBOX_SECRET_KEY=
16+
17+
# Optional Git remote examples.
18+
GIT_REPO_URL=
19+
GIT_USERNAME=
20+
GIT_PASSWORD=
21+
22+
# Optional Git repository resource example.
23+
GITHUB_TOKEN=
24+
QINIU_SANDBOX_GIT_MOUNT_PATH=/workspace/repo
25+
26+
# Optional Kodo resource example.
27+
QINIU_SANDBOX_KODO_BUCKET=
28+
QINIU_SANDBOX_KODO_MOUNT_PATH=/workspace/kodo
29+
QINIU_SANDBOX_KODO_PREFIX=
30+
31+
# Optional request injection examples.
32+
QINIU_SANDBOX_HTTP_INJECTION_TOKEN=real_token
33+
QINIU_SANDBOX_OPENAI_API_KEY=

.github/workflows/ci-test.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ jobs:
2424
- name: Run cases
2525
run: |
2626
npm run check-type
27-
nyc --reporter=lcov npm test
27+
nyc --reporter=lcov --include 'qiniu/sandbox/**/*.js' npm run test:sandbox
28+
if [ -n "$QINIU_ACCESS_KEY" ] && [ -n "$QINIU_SECRET_KEY" ] && [ -n "$QINIU_TEST_BUCKET" ] && [ -n "$QINIU_TEST_DOMAIN" ]; then
29+
nyc --no-clean --reporter=lcov npm test
30+
else
31+
npm test
32+
fi
2833
env:
2934
QINIU_ACCESS_KEY: ${{ secrets.QINIU_ACCESS_KEY }}
3035
QINIU_SECRET_KEY: ${{ secrets.QINIU_SECRET_KEY }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ coverage/
2727
yarn.lock
2828

2929
.claude/settings.local.json
30+
.env

examples/sandbox_common.js

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
4+
const qiniu = require('../index');
5+
const { shellQuote } = require('../qiniu/sandbox/util');
6+
7+
function loadDotEnvIfPresent () {
8+
const files = [
9+
path.join(process.cwd(), '.env')
10+
];
11+
12+
files.forEach(filepath => {
13+
if (!fs.existsSync(filepath)) {
14+
return;
15+
}
16+
17+
fs.readFileSync(filepath, 'utf8')
18+
.split(/\r?\n/)
19+
.forEach(line => {
20+
line = line.trim();
21+
if (!line || line[0] === '#') {
22+
return;
23+
}
24+
const index = line.indexOf('=');
25+
if (index < 0) {
26+
return;
27+
}
28+
const key = line.slice(0, index).trim();
29+
let value = line.slice(index + 1).trim();
30+
if (
31+
(value[0] === '"' && value[value.length - 1] === '"') ||
32+
(value[0] === '\'' && value[value.length - 1] === '\'')
33+
) {
34+
value = value.slice(1, -1);
35+
}
36+
if (process.env[key] === undefined) {
37+
process.env[key] = value;
38+
}
39+
});
40+
});
41+
}
42+
43+
function env (key, fallback) {
44+
return process.env[key] || fallback;
45+
}
46+
47+
function requiredEnv (key) {
48+
if (process.env[key]) {
49+
return process.env[key];
50+
}
51+
throw new Error(`Please set ${key}`);
52+
}
53+
54+
function sandboxEndpoint () {
55+
return env('QINIU_SANDBOX_ENDPOINT');
56+
}
57+
58+
function sandboxApiKey () {
59+
return requiredEnv('QINIU_SANDBOX_API_KEY');
60+
}
61+
62+
function sandboxTemplate () {
63+
return env('QINIU_SANDBOX_TEMPLATE', 'base');
64+
}
65+
66+
function sandboxClient (options) {
67+
options = Object.assign({
68+
endpoint: sandboxEndpoint(),
69+
apiKey: sandboxApiKey()
70+
}, options || {});
71+
return new qiniu.sandbox.SandboxClient(options);
72+
}
73+
74+
function sandboxMac () {
75+
const accessKey = requiredEnv('QINIU_SANDBOX_ACCESS_KEY');
76+
const secretKey = requiredEnv('QINIU_SANDBOX_SECRET_KEY');
77+
return new qiniu.auth.digest.Mac(accessKey, secretKey);
78+
}
79+
80+
function createSandboxAndWait (options, pollOptions) {
81+
const client = options && options.client ? options.client : sandboxClient();
82+
const params = Object.assign({
83+
client,
84+
template: sandboxTemplate(),
85+
timeout: 300
86+
}, options || {});
87+
88+
return qiniu.sandbox.Sandbox.create(params).then(sandbox => {
89+
console.log('Sandbox created:', sandbox.sandboxId);
90+
return sandbox.waitForReady(Object.assign({
91+
interval: 3000,
92+
timeout: 180000
93+
}, pollOptions || {})).then(info => {
94+
console.log('Sandbox ready:', sandbox.sandboxId, info.state || '');
95+
return sandbox;
96+
});
97+
});
98+
}
99+
100+
function cleanupSandbox (sandbox) {
101+
if (!sandbox) {
102+
return Promise.resolve();
103+
}
104+
return sandbox.kill()
105+
.then(() => {
106+
console.log('Sandbox killed:', sandbox.sandboxId);
107+
}, err => {
108+
console.log('Failed to kill sandbox:', sandbox.sandboxId, err.message);
109+
});
110+
}
111+
112+
function runExample (fn) {
113+
loadDotEnvIfPresent();
114+
Promise.resolve()
115+
.then(fn)
116+
.catch(err => {
117+
console.error(err && err.stack ? err.stack : err);
118+
process.exitCode = 1;
119+
});
120+
}
121+
122+
module.exports = {
123+
qiniu,
124+
shellQuote,
125+
env,
126+
requiredEnv,
127+
sandboxEndpoint,
128+
sandboxApiKey,
129+
sandboxTemplate,
130+
sandboxClient,
131+
sandboxMac,
132+
createSandboxAndWait,
133+
cleanupSandbox,
134+
runExample
135+
};

examples/sandbox_create.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const {
2+
qiniu,
3+
sandboxClient,
4+
sandboxTemplate,
5+
cleanupSandbox,
6+
runExample
7+
} = require('./sandbox_common');
8+
9+
runExample(() => {
10+
const client = sandboxClient();
11+
let sandbox;
12+
13+
return qiniu.sandbox.Sandbox.create(sandboxTemplate(), {
14+
client,
15+
timeout: 300,
16+
metadata: {
17+
example: 'sandbox_create'
18+
}
19+
}).then(created => {
20+
sandbox = created;
21+
console.log('Sandbox created:', sandbox.sandboxId);
22+
console.log('Template:', sandbox.info.templateID || sandbox.info.template_id || sandboxTemplate());
23+
return cleanupSandbox(sandbox);
24+
});
25+
});

examples/sandbox_envd.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
const {
2+
shellQuote,
3+
createSandboxAndWait,
4+
cleanupSandbox,
5+
runExample
6+
} = require('./sandbox_common');
7+
8+
runExample(() => {
9+
let sandbox;
10+
const workdir = '/tmp/qiniu-nodejs-sdk-envd';
11+
const filePath = `${workdir}/hello.txt`;
12+
13+
return createSandboxAndWait({
14+
metadata: {
15+
example: 'sandbox_envd'
16+
}
17+
}).then(created => {
18+
sandbox = created;
19+
console.log('Public host for port 8080:', sandbox.getHost(8080));
20+
return sandbox.commands.run(`mkdir -p ${shellQuote(workdir)}`);
21+
}).then(result => {
22+
console.log('mkdir exit:', result.exitCode);
23+
return sandbox.files.write(filePath, 'Hello from Qiniu Node.js SDK\n');
24+
}).then(entry => {
25+
console.log('Wrote:', entry.path || entry);
26+
return sandbox.files.readText(filePath);
27+
}).then(text => {
28+
console.log('ReadText:', JSON.stringify(text));
29+
return sandbox.files.writeFiles([
30+
{ path: `${workdir}/batch-a.txt`, data: 'file A content\n' },
31+
{ path: `${workdir}/batch-b.txt`, data: 'file B content\n' }
32+
]);
33+
}).then(entries => {
34+
console.log('WriteFiles:', entries.map(item => item.path));
35+
return sandbox.files.read(`${workdir}/batch-a.txt`, { format: 'bytes' });
36+
}).then(bytes => {
37+
console.log('Read bytes:', bytes.length);
38+
return sandbox.files.list(workdir);
39+
}).then(entries => {
40+
console.log('List:', entries.map(item => `${item.type}:${item.name}`));
41+
return sandbox.files.exists(filePath);
42+
}).then(exists => {
43+
console.log('Exists:', exists);
44+
return sandbox.files.getInfo(filePath);
45+
}).then(info => {
46+
console.log('GetInfo:', info.name, info.size);
47+
return sandbox.files.rename(`${workdir}/batch-b.txt`, `${workdir}/batch-b-renamed.txt`);
48+
}).then(info => {
49+
console.log('Renamed to:', info.path);
50+
return sandbox.files.remove(`${workdir}/batch-b-renamed.txt`);
51+
}).then(() => {
52+
console.log('Removed renamed file');
53+
return sandbox.commands.run('echo $MY_VAR && pwd', {
54+
cwd: workdir,
55+
envs: {
56+
MY_VAR: 'sandbox-value'
57+
}
58+
});
59+
}).then(result => {
60+
console.log('Command exit:', result.exitCode);
61+
console.log('stdout:\n' + result.stdout);
62+
return cleanupSandbox(sandbox);
63+
}, err => {
64+
return cleanupSandbox(sandbox).then(() => {
65+
throw err;
66+
});
67+
});
68+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const {
2+
createSandboxAndWait,
3+
cleanupSandbox,
4+
runExample
5+
} = require('./sandbox_common');
6+
7+
runExample(() => {
8+
let sandbox;
9+
const filePath = '/tmp/qiniu-nodejs-sdk-encoding.txt';
10+
11+
return createSandboxAndWait({
12+
metadata: {
13+
example: 'sandbox_filesystem_encoding'
14+
}
15+
}).then(created => {
16+
sandbox = created;
17+
return sandbox.files.write(filePath, 'hello with octet-stream and gzip\n', {
18+
useOctetStream: true,
19+
gzip: true
20+
});
21+
}).then(entry => {
22+
console.log('Wrote:', entry.path || entry);
23+
return sandbox.files.read(filePath, {
24+
gzip: true
25+
});
26+
}).then(text => {
27+
console.log('Read:', JSON.stringify(text));
28+
return cleanupSandbox(sandbox);
29+
}, err => {
30+
return cleanupSandbox(sandbox).then(() => {
31+
throw err;
32+
});
33+
});
34+
});

0 commit comments

Comments
 (0)