diff --git a/index.d.ts b/index.d.ts index f1fd4c7..2d825f5 100644 --- a/index.d.ts +++ b/index.d.ts @@ -63,14 +63,38 @@ export declare namespace sandbox { interface KodoResource { type: 'kodo'; + access_key?: string; bucket: string; mount_path: string; prefix?: string; read_only?: boolean; + secret_key?: string; } type SandboxResource = GitRepositoryResource | KodoResource; + interface GitRepositoryResourceInfo { + type: 'github_repository'; + resource_id: string; + url: string; + mount_path: string; + } + + interface KodoResourceInfo { + type: 'kodo'; + resource_id: string; + bucket: string; + mount_path: string; + prefix?: string; + read_only?: boolean; + } + + type SandboxResourceInfo = GitRepositoryResourceInfo | KodoResourceInfo; + + interface SandboxResourcesResponse { + resources: SandboxResourceInfo[]; + } + type InjectionConditions = {[key: string]: string}; interface InjectionMatchOptions { @@ -146,6 +170,19 @@ export declare namespace sandbox { type TemplateBuildStatus = 'building' | 'waiting' | 'ready' | 'error' | 'uploaded'; + interface TemplateCreateOptions { + alias?: string; + buildConfig?: any; + cpuCount?: number; + /** Template build disk size in MiB. The minimum is 10240. */ + diskSizeMB?: number; + memoryMB?: number; + name?: string; + tags?: string[]; + teamID?: string; + [key: string]: any; + } + interface DefaultTemplateInfo extends TemplateInfo { buildStatus: TemplateBuildStatus; } @@ -454,7 +491,7 @@ export declare namespace sandbox { skipCache(): this; setStartCmd(command: string): this; setReadyCmd(command: string): this; - build(options?: SandboxClientOptions & {client?: SandboxClient; name?: string; alias?: string; tags?: string[]}): Promise; + build(options?: SandboxClientOptions & TemplateCreateOptions & {client?: SandboxClient}): Promise; } const Template: { @@ -483,6 +520,8 @@ export declare namespace sandbox { getSandboxLogs(sandboxID: string, options?: any): Promise; getSandbox(sandboxID: string): Promise; getSandboxInjections(sandboxID: string): Promise; + getSandboxResources(sandboxID: string): Promise; + updateGitRepositoryResourceToken(sandboxID: string, resourceID: string, authorizationToken: string): Promise; updateSandboxInjections(sandboxID: string, injections: SandboxInjection[]): Promise; updateSandboxGithubToken(sandboxID: string, authorizationToken: string): Promise; deleteSandbox(sandboxID: string): Promise; @@ -495,8 +534,8 @@ export declare namespace sandbox { updateSandbox(sandboxID: string, options?: any): Promise; refreshSandbox(sandboxID: string, options?: {duration?: number}): Promise; - createTemplate(options?: any): Promise; - createTemplateV3(options?: any): Promise; + createTemplate(options?: TemplateCreateOptions): Promise; + createTemplateV3(options?: TemplateCreateOptions): Promise; createTemplateV2(options?: any): Promise; getTemplateFiles(templateID: string, hash: string): Promise; listDefaultTemplates(): Promise; @@ -543,6 +582,8 @@ export declare namespace sandbox { client: SandboxClient; files: Filesystem; filesystem: Filesystem; + getResources(): Promise; + updateGitRepositoryResourceToken(resourceID: string, authorizationToken: string): Promise; commands: Commands; pty: Pty; git: Git; diff --git a/qiniu/sandbox/client.js b/qiniu/sandbox/client.js index e8c2bf7..86bce87 100644 --- a/qiniu/sandbox/client.js +++ b/qiniu/sandbox/client.js @@ -68,13 +68,29 @@ function normalizeSandboxCreateOptions (opts) { copyDefined(body, opts, 'injections'); } copyDefined(body, opts, 'resources'); + (body.resources || []).forEach(resource => { + if (!resource || resource.type !== 'kodo') { + return; + } + if ((resource.access_key === undefined) !== + (resource.secret_key === undefined)) { + throw new SandboxError( + 'access_key and secret_key must be provided together'); + } + if (resource.access_key !== undefined && + (!resource.access_key || !resource.secret_key)) { + throw new SandboxError( + 'access_key and secret_key must not be empty'); + } + }); return body; } function hasKodoResource (body) { return Array.isArray(body.resources) && body.resources.some(resource => { - return resource && resource.type === 'kodo'; + return resource && resource.type === 'kodo' && + (!resource.access_key || !resource.secret_key); }); } @@ -379,6 +395,29 @@ SandboxClient.prototype.getSandboxInjections = function (sandboxID) { return this._request('GET', `/sandboxes/${encodePath(sandboxID)}/injections`); }; +SandboxClient.prototype.getSandboxResources = function (sandboxID) { + if (!sandboxID) { + return Promise.reject(new SandboxError('sandboxID is required')); + } + return this._request('GET', `/sandboxes/${encodePath(sandboxID)}/resources`); +}; + +SandboxClient.prototype.updateGitRepositoryResourceToken = function (sandboxID, resourceID, authorizationToken) { + if (!sandboxID) { + return Promise.reject(new SandboxError('sandboxID is required')); + } + if (!resourceID) { + return Promise.reject(new SandboxError('resourceID is required')); + } + if (!authorizationToken) { + return Promise.reject(new SandboxError('authorizationToken is required')); + } + return this._request('PATCH', `/sandboxes/${encodePath(sandboxID)}/resources/${encodePath(resourceID)}`, { + body: { authorization_token: authorizationToken }, + empty: true + }); +}; + SandboxClient.prototype.updateSandboxInjections = function (sandboxID, injections) { if (!sandboxID) { return Promise.reject(new SandboxError('sandboxID is required')); diff --git a/qiniu/sandbox/sandbox.js b/qiniu/sandbox/sandbox.js index 85e263d..e8fa226 100644 --- a/qiniu/sandbox/sandbox.js +++ b/qiniu/sandbox/sandbox.js @@ -223,6 +223,15 @@ Sandbox.prototype.getInjections = function () { return this.client.getSandboxInjections(this.sandboxId); }; +Sandbox.prototype.getResources = function () { + return this.client.getSandboxResources(this.sandboxId); +}; + +Sandbox.prototype.updateGitRepositoryResourceToken = function (resourceID, authorizationToken) { + return this.client.updateGitRepositoryResourceToken( + this.sandboxId, resourceID, authorizationToken); +}; + Sandbox.prototype.updateInjections = function (injections) { return this.client.updateSandboxInjections(this.sandboxId, injections); }; diff --git a/test/sandbox_client.test.js b/test/sandbox_client.test.js index 067365f..15c5c96 100644 --- a/test/sandbox_client.test.js +++ b/test/sandbox_client.test.js @@ -1161,4 +1161,23 @@ describe('test sandbox client module', function () { ]); }); }); + it('uses API-key authentication for Kodo resources with inline credentials', function () { + const client = new qiniu.sandbox.SandboxClient({ apiKey: 'sandbox-key' }); + let requestOptions; + client._request = (method, path, options) => { + requestOptions = { method, path, options }; + return Promise.resolve({ sandboxID: 'sbx_1' }); + }; + + return client.createSandbox({ resources: [{ + type: 'kodo', + bucket: 'bucket', + mount_path: '/mnt/bucket', + access_key: 'resource-ak', + secret_key: 'resource-sk' + }] }).then(() => { + should.not.exist(requestOptions.options.authType); + requestOptions.options.body.resources[0].access_key.should.eql('resource-ak'); + }); + }); }); diff --git a/test/sandbox_template.test.js b/test/sandbox_template.test.js index c0f7bbb..abd7719 100644 --- a/test/sandbox_template.test.js +++ b/test/sandbox_template.test.js @@ -70,6 +70,50 @@ describe('test sandbox template module', function () { }); }); + it('lists sandbox resources and updates a Git repository token', function () { + return startServer((req, res) => { + res.setHeader('Content-Type', 'application/json'); + if (req.method === 'GET' && req.url === '/sandboxes/sbx_1/resources') { + res.statusCode = 200; + res.end(JSON.stringify({ resources: [{ + type: 'github_repository', + resource_id: 'res_1', + url: 'https://github.com/qiniu/nodejs-sdk.git', + mount_path: '/workspace/repo' + }] })); + return; + } + if (req.method === 'PATCH' && req.url === '/sandboxes/sbx_1/resources/res_1') { + res.statusCode = 204; + res.end(); + return; + } + res.statusCode = 404; + res.end(); + }).then(fixture => { + const client = new qiniu.sandbox.SandboxClient({ + apiKey: 'sandbox-key', + endpoint: fixture.endpoint + }); + return client.getSandboxResources('sbx_1') + .then(resources => { + resources.resources[0].resource_id.should.eql('res_1'); + return client.updateGitRepositoryResourceToken( + 'sbx_1', 'res_1', 'new-token'); + }) + .then(() => { + JSON.parse(fixture.requests[1].body).should.eql({ + authorization_token: 'new-token' + }); + }) + .then(() => closeServer(fixture.server), err => { + return closeServer(fixture.server).then(() => { + throw err; + }); + }); + }); + }); + it('maps template, build, tag, and access-token APIs', function () { return startServer((req, res) => { res.setHeader('Content-Type', 'application/json'); @@ -166,7 +210,10 @@ describe('test sandbox template module', function () { return client.listTemplates({ teamID: 'team' }) .then(() => client.listDefaultTemplates()) - .then(() => client.createTemplate({ name: 'node:v1' })) + .then(() => client.createTemplate({ + name: 'node:v1', + diskSizeMB: 15360 + })) .then(() => client.createTemplateV2({ alias: 'old' })) .then(() => client.getTemplate('tpl_1', { limit: 5, nextToken: 'n2' })) .then(template => { @@ -192,7 +239,10 @@ describe('test sandbox template module', function () { .then(() => client.getTemplateByAlias('base')) .then(() => client.rebuildTemplate('tpl_1', { name: 'again' })) .then(() => { - JSON.parse(fixture.requests[2].body).should.eql({ name: 'node:v1' }); + JSON.parse(fixture.requests[2].body).should.eql({ + name: 'node:v1', + diskSizeMB: 15360 + }); JSON.parse(fixture.requests[5].body).should.eql({ public: true }); fixture.requests[14].headers.authorization.should.eql('Bearer access-token'); }).then(() => closeServer(fixture.server), err => { diff --git a/test/sandbox_types.ts b/test/sandbox_types.ts index 3a23055..0fad431 100644 --- a/test/sandbox_types.ts +++ b/test/sandbox_types.ts @@ -108,6 +108,10 @@ async function useSandboxTypes () { .fromImage('ubuntu:22.04') .aptInstall(['git']) .runCmd('git --version'); + const templateCreateOptions: qiniu.sandbox.TemplateCreateOptions = { + name: 'typed-template:test', + diskSizeMB: 15360 + }; qiniu.sandbox.Template() .skipCache() .fromImage('registry.example.com/app:latest', { username: 'u', password: 'p' }) @@ -137,12 +141,16 @@ async function useSandboxTypes () { .gitClone('https://github.com/qiniu/nodejs-sdk.git', { branch: 'sandbox', depth: 1 }) .gitClone('https://github.com/qiniu/nodejs-sdk.git', '/src/sdk', { branch: 'sandbox', depth: 1 }) .runCmd(['echo one', 'echo two'], { user: 'root' }); - await template.build({ client, name: 'typed-template:test' }); + await template.build({ client, ...templateCreateOptions }); + await client.createTemplate(templateCreateOptions); await sandbox.updateNetwork({ allowOut: [qiniu.sandbox.ALL_TRAFFIC] }); const injections: qiniu.sandbox.SandboxInjection[] = (await sandbox.getInjections()).injections; await sandbox.updateInjections(injections); await sandbox.updateGithubToken('github-token'); const templateInfo: qiniu.sandbox.TemplateWithBuilds = await client.getTemplate('typed-template'); + const sandboxResources: qiniu.sandbox.SandboxResourcesResponse = await client.getSandboxResources('sandbox_1'); + const resourceInfo: qiniu.sandbox.SandboxResourceInfo | undefined = sandboxResources.resources[0]; + await client.updateGitRepositoryResourceToken('sandbox_1', 'res_1', 'token'); const templateNames: string[] = templateInfo.names; const isOwner: boolean = templateInfo.isOwner; const defaultTemplates = await client.listDefaultTemplates(); @@ -171,6 +179,9 @@ async function useSandboxTypes () { if (defaultTemplateBuildStatus) { defaultTemplateBuildStatus.length; } + if (resourceInfo && resourceInfo.type === 'github_repository') { + resourceInfo.resource_id.length; + } defaultTemplateIsTyped.valueOf(); defaultTemplateBuildStatusIsTyped.valueOf(); }