Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion examples/sandbox_list_connect.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
const {
qiniu,
sandboxClient,
sandboxTemplate,
cleanupSandbox,
runExample
} = require('./sandbox_common');

runExample(() => {
const client = sandboxClient();
const template = sandboxTemplate();
let sandbox;

return qiniu.sandbox.Sandbox.create({
client,
template,
timeout: 300,
metadata: {
example: 'sandbox_list_connect'
Expand All @@ -20,7 +23,10 @@ runExample(() => {
console.log('Created:', sandbox.sandboxId);
return qiniu.sandbox.Sandbox.list({
client,
limit: 10
limit: 10,
query: {
template: [template]
}
});
}).then(items => {
console.log('List result:', Array.isArray(items) ? items.map(item => item.sandboxId || item.sandboxID) : items);
Expand Down
28 changes: 16 additions & 12 deletions examples/sandbox_request_injections.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,29 @@ const {

runExample(() => {
let sandbox;
const httpInjection = {
type: 'http',
baseUrl: 'https://httpbin.org/bearer',
ifHeaders: {
'X-Sandbox-Example': 'qiniu-nodejs-sdk'
},
headers: {
Authorization: `Bearer ${env('QINIU_SANDBOX_HTTP_INJECTION_TOKEN', 'real_token')}`
}
};

return createSandboxAndWait({
injections: [
{
type: 'http',
baseUrl: 'https://httpbin.org/bearer',
ifHeaders: {
'X-Sandbox-Example': 'qiniu-nodejs-sdk'
},
headers: {
Authorization: `Bearer ${env('QINIU_SANDBOX_HTTP_INJECTION_TOKEN', 'real_token')}`
}
}
],
injections: [httpInjection],
metadata: {
example: 'sandbox_request_injections'
}
}).then(created => {
sandbox = created;
return sandbox.getInjections();
}).then(result => {
console.log('Current runtime injections:', result.injections);
return sandbox.updateInjections([httpInjection]);
}).then(() => {
return sandbox.commands.run('curl --max-time 20 -sSL https://httpbin.org/bearer -H "Authorization: Bearer fake_token" -H "X-Sandbox-Example: qiniu-nodejs-sdk"', {
timeout: 30000
});
Expand Down
3 changes: 3 additions & 0 deletions examples/sandbox_resources.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ function runGitResourceExample () {
return sandbox.waitForReady({ interval: 3000, timeout: 180000 });
}).then(() => {
console.log('Git resource sandbox ready:', sandbox.sandboxId);
return sandbox.updateGithubToken(token);
}).then(() => {
console.log('GitHub token updated for the running sandbox.');
return sandbox.commands.run(`ls -la ${shellQuote(mountPath)} | head -20`);
}).then(result => {
console.log(result.stdout);
Expand Down
8 changes: 7 additions & 1 deletion examples/sandbox_templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ runExample(() => {
}
const id = first.templateID || first.template_id || first.id || sandboxTemplate();
return client.getTemplate(id).then(detail => {
console.log('First template detail:', detail.templateID || detail.template_id || id);
console.log('First template detail:', {
templateID: detail.templateID || detail.template_id || id,
names: detail.names,
aliases: detail.aliases,
isOwner: detail.isOwner,
buildCount: Array.isArray(detail.builds) ? detail.builds.length : 0
});
});
}).then(() => {
const advanced = qiniu.sandbox.Template()
Expand Down
26 changes: 25 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,24 @@ export declare namespace sandbox {
type Injection = HttpInjection | OpenaiInjection | AnthropicInjection | GeminiInjection | QiniuInjection | GithubInjection;
type SandboxInjection = InjectionById | InjectionRuleReference | Injection;

interface SandboxInjectionsResponse {
injections: SandboxInjection[];
}

interface TemplateInfo {
templateID: string;
public: boolean;
/** @deprecated use names instead */
aliases: string[];
names: string[];
[key: string]: any;
}

interface TemplateWithBuilds extends TemplateInfo {
isOwner: boolean;
builds: any[];
}

interface InjectionRule {
ruleID?: string;
id?: string;
Expand Down Expand Up @@ -456,6 +474,9 @@ export declare namespace sandbox {
getSandboxesMetrics(sandboxIDs: string[] | any): Promise<any>;
getSandboxLogs(sandboxID: string, options?: any): Promise<any>;
getSandbox(sandboxID: string): Promise<any>;
getSandboxInjections(sandboxID: string): Promise<SandboxInjectionsResponse>;
updateSandboxInjections(sandboxID: string, injections: SandboxInjection[]): Promise<null>;
updateSandboxGithubToken(sandboxID: string, authorizationToken: string): Promise<null>;
deleteSandbox(sandboxID: string): Promise<null>;
killSandbox(sandboxID: string): Promise<null>;
getSandboxMetrics(sandboxID: string, options?: any): Promise<any>;
Expand All @@ -472,7 +493,7 @@ export declare namespace sandbox {
getTemplateFiles(templateID: string, hash: string): Promise<any>;
listDefaultTemplates(): Promise<any>;
listTemplates(options?: any): Promise<any>;
getTemplate(templateID: string, options?: any): Promise<any>;
getTemplate(templateID: string, options?: any): Promise<TemplateWithBuilds>;
deleteTemplate(templateID: string): Promise<null>;
updateTemplate(templateID: string, options?: any): Promise<any>;
startTemplateBuildV2(templateID: string, buildID: string, options?: any): Promise<null>;
Expand Down Expand Up @@ -536,6 +557,9 @@ export declare namespace sandbox {
betaPause(): Promise<null>;
connect(options?: SandboxConnectOptions): Promise<this>;
getInfo(): Promise<any>;
getInjections(): Promise<SandboxInjectionsResponse>;
updateInjections(injections: SandboxInjection[]): Promise<null>;
updateGithubToken(authorizationToken: string): Promise<null>;
getMetrics(options?: any): Promise<any>;
getLogs(options?: any): Promise<any>;
createSnapshot(options?: any): Promise<SnapshotInfo>;
Expand Down
59 changes: 59 additions & 0 deletions qiniu/sandbox/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,25 @@ function normalizeSandboxListOptions (opts) {
if (query && query.state) {
opts.state = query.state;
}
if (query && query.template) {
const normalizeTemplate = template => {
if (typeof template === 'string') {
return template;
}
return template && (template.templateID || template.template_id || template.id);
};
if (Array.isArray(query.template)) {
const templates = query.template.map(normalizeTemplate).filter(Boolean);
if (templates.length) {
opts.template = templates;
}
} else {
const template = normalizeTemplate(query.template);
if (template) {
opts.template = template;
}
}
}
Comment thread
miclle marked this conversation as resolved.
return opts;
}

Expand Down Expand Up @@ -276,6 +295,46 @@ SandboxClient.prototype.getSandbox = function (sandboxID) {
return this._request('GET', `/sandboxes/${encodePath(sandboxID)}`);
};

SandboxClient.prototype.getSandboxInjections = function (sandboxID) {
if (!sandboxID) {
return Promise.reject(new SandboxError('sandboxID is required'));
}
return this._request('GET', `/sandboxes/${encodePath(sandboxID)}/injections`);
};

SandboxClient.prototype.updateSandboxInjections = function (sandboxID, injections) {
if (!sandboxID) {
return Promise.reject(new SandboxError('sandboxID is required'));
}
if (!Array.isArray(injections)) {
return Promise.reject(new SandboxError('injections must be an array'));
}
if (injections.some(injection => !injection || typeof injection !== 'object' || Array.isArray(injection))) {
return Promise.reject(new SandboxError('injections must contain objects'));
}
return this._request('PUT', `/sandboxes/${encodePath(sandboxID)}/injections`, {
body: {
injections: injections.map(normalizeInjection)
},
empty: true
});
Comment thread
miclle marked this conversation as resolved.
};

SandboxClient.prototype.updateSandboxGithubToken = function (sandboxID, authorizationToken) {
if (!sandboxID) {
return Promise.reject(new SandboxError('sandboxID is required'));
}
if (!authorizationToken) {
return Promise.reject(new SandboxError('authorizationToken is required'));
}
return this._request('PUT', `/sandboxes/${encodePath(sandboxID)}/github-token`, {
body: {
authorization_token: authorizationToken
},
empty: true
});
};
Comment thread
miclle marked this conversation as resolved.
Comment thread
miclle marked this conversation as resolved.

SandboxClient.prototype.deleteSandbox = function (sandboxID) {
if (!sandboxID) {
return Promise.reject(new SandboxError('sandboxID is required'));
Expand Down
12 changes: 12 additions & 0 deletions qiniu/sandbox/sandbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,18 @@ Sandbox.prototype.getInfo = function () {
return this.client.getSandbox(this.sandboxId);
};

Sandbox.prototype.getInjections = function () {
return this.client.getSandboxInjections(this.sandboxId);
};

Sandbox.prototype.updateInjections = function (injections) {
return this.client.updateSandboxInjections(this.sandboxId, injections);
};

Sandbox.prototype.updateGithubToken = function (authorizationToken) {
return this.client.updateSandboxGithubToken(this.sandboxId, authorizationToken);
};

Sandbox.prototype.refreshEnvdTokenIfNeeded = function () {
if (this.envdAccessToken) {
return Promise.resolve(this);
Expand Down
113 changes: 113 additions & 0 deletions test/sandbox_client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,119 @@ describe('test sandbox client module', function () {
});
});

it('maps sandbox template filters and runtime injection APIs', function () {
return startServer((req, res) => {
res.setHeader('Content-Type', 'application/json');
if (req.method === 'GET' && req.url === '/v2/sandboxes?limit=5&template=tpl_1%2Ctpl_2%2Ctpl_3%2Ctpl_4') {
res.statusCode = 200;
res.end(JSON.stringify([]));
return;
}
if (req.method === 'GET' && req.url === '/sandboxes/sbx_runtime/injections') {
res.statusCode = 200;
res.end(JSON.stringify({
injections: [{ type: 'github', token: 'ghp_****' }]
}));
return;
}
if (req.method === 'PUT' && req.url === '/sandboxes/sbx_runtime/injections') {
res.statusCode = 204;
res.end();
return;
}
if (req.method === 'PUT' && req.url === '/sandboxes/sbx_runtime/github-token') {
res.statusCode = 204;
res.end();
return;
}
res.statusCode = 404;
res.end(JSON.stringify({ message: req.method + ' ' + req.url }));
}).then(fixture => {
const client = new qiniu.sandbox.SandboxClient({
endpoint: fixture.endpoint,
apiKey: 'sandbox-key'
});

return client.listSandboxesV2({
limit: 5,
query: {
template: [
'tpl_1',
{ templateID: 'tpl_2' },
{ template_id: 'tpl_3' },
{ id: 'tpl_4' },
null
]
}
}).then(() => client.getSandboxInjections('sbx_runtime'))
.then(result => {
result.injections[0].token.should.eql('ghp_****');
return client.updateSandboxInjections('sbx_runtime', [{
type: 'openai',
apiKey: 'secret',
baseUrl: 'https://api.openai.com/v1/*'
}]);
})
.then(() => client.updateSandboxGithubToken('sbx_runtime', 'github-token'))
.then(() => {
JSON.parse(fixture.requests[2].body).should.eql({
injections: [{
type: 'openai',
api_key: 'secret',
base_url: 'https://api.openai.com/v1/*'
}]
});
JSON.parse(fixture.requests[3].body).should.eql({
authorization_token: 'github-token'
});
}).then(() => closeServer(fixture.server), err => {
return closeServer(fixture.server).then(() => {
throw err;
});
});
});
});

it('validates runtime injection API arguments before sending requests', function () {
return startServer((req, res) => {
res.statusCode = 404;
res.end();
}).then(fixture => {
const client = new qiniu.sandbox.SandboxClient({
endpoint: fixture.endpoint,
apiKey: 'sandbox-key'
});
const calls = [
() => client.getSandboxInjections(),
() => client.updateSandboxInjections(undefined, []),
() => client.updateSandboxGithubToken(undefined, 'github-token'),
() => client.updateSandboxInjections('sbx_runtime', { type: 'id', id: 'rule_1' }),
() => client.updateSandboxGithubToken('sbx_runtime'),
() => client.updateSandboxInjections('sbx_runtime', [null])
];

return calls.reduce((promise, call, index) => {
return promise.then(() => call().then(() => {
throw new Error('expected runtime injection argument validation to fail');
}, err => {
err.name.should.eql('SandboxError');
const expected = index < 3
? 'sandboxID is required'
: (index === 3
? 'injections must be an array'
: (index === 4 ? 'authorizationToken is required' : 'injections must contain objects'));
err.message.should.eql(expected);
}));
}, Promise.resolve()).then(() => {
fixture.requests.should.have.length(0);
}).then(() => closeServer(fixture.server), err => {
return closeServer(fixture.server).then(() => {
throw err;
});
});
});
});

it('surfaces sandbox API string errors and default connect timeout', function () {
return startServer((req, res) => {
if (req.method === 'POST' && req.url === '/sandboxes/sbx_default/connect') {
Expand Down
Loading
Loading