@adobe/aio-lib-sandbox 0.1.0-alpha.7 → 0.1.0-alpha.8
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/README.md +7 -0
- package/package.json +1 -1
- package/src/Sandbox.js +16 -2
- package/test/Sandbox.test.js +112 -0
package/README.md
CHANGED
|
@@ -74,12 +74,19 @@ const { Sandbox } = require('@adobe/aio-lib-sandbox')
|
|
|
74
74
|
const sandbox = await Sandbox.create({
|
|
75
75
|
name: 'my-sandbox',
|
|
76
76
|
type: 'cpu:default',
|
|
77
|
+
idleTimeout: 900,
|
|
77
78
|
maxLifetime: 3600,
|
|
78
79
|
ports: [3000, 8080],
|
|
79
80
|
envs: { API_KEY: 'your-api-key' }
|
|
80
81
|
})
|
|
81
82
|
```
|
|
82
83
|
|
|
84
|
+
#### Sandbox lifetime model
|
|
85
|
+
|
|
86
|
+
A sandbox is always deleted when `maxLifetime` has elapsed. It will also be deleted after the `idleTimeout` has elapsed, if there has been no activity.
|
|
87
|
+
|
|
88
|
+
To keep a sandbox alive, send at least one command or check the status every `idleTimeout` seconds.
|
|
89
|
+
|
|
83
90
|
### Get Status
|
|
84
91
|
|
|
85
92
|
```js
|
package/package.json
CHANGED
package/src/Sandbox.js
CHANGED
|
@@ -42,6 +42,7 @@ class Sandbox {
|
|
|
42
42
|
this.status = options.status
|
|
43
43
|
this.cluster = options.cluster
|
|
44
44
|
this.region = options.region
|
|
45
|
+
this.idleTimeout = options.idleTimeout
|
|
45
46
|
this.maxLifetime = options.maxLifetime
|
|
46
47
|
|
|
47
48
|
this.namespace = options.namespace
|
|
@@ -70,7 +71,10 @@ class Sandbox {
|
|
|
70
71
|
* @param {string} [options.name] sandbox display name
|
|
71
72
|
* @param {string} [options.type] sandbox type (default: `'cpu:default'`)
|
|
72
73
|
* @param {string|object} [options.size] sandbox size tier (name or spec object)
|
|
73
|
-
* @param {number} [options.
|
|
74
|
+
* @param {number} [options.idleTimeout] seconds of inactivity before the sandbox is terminated
|
|
75
|
+
* (default: 900, max: 10800). The idle timer resets on every WebSocket message or status-check
|
|
76
|
+
* request.
|
|
77
|
+
* @param {number} [options.maxLifetime] maximum lifetime in seconds (default: 3600, max: 10800)
|
|
74
78
|
* @param {number[]} [options.ports] TCP ports to expose via preview URLs (default: `[]`)
|
|
75
79
|
* @param {object} [options.envs] environment variables to inject into the sandbox
|
|
76
80
|
* @param {object} [options.policy] network policy (e.g. egress allowlist)
|
|
@@ -84,6 +88,7 @@ class Sandbox {
|
|
|
84
88
|
name: options.name,
|
|
85
89
|
size: normalizeSize(options.size),
|
|
86
90
|
type: options.type || 'cpu:default',
|
|
91
|
+
idleTimeout: options.idleTimeout || 900,
|
|
87
92
|
maxLifetime: options.maxLifetime || 3600
|
|
88
93
|
}
|
|
89
94
|
|
|
@@ -105,6 +110,7 @@ class Sandbox {
|
|
|
105
110
|
status: payload.status,
|
|
106
111
|
cluster: payload.cluster,
|
|
107
112
|
region: payload.region,
|
|
113
|
+
idleTimeout: payload.idleTimeout,
|
|
108
114
|
maxLifetime: payload.maxLifetime,
|
|
109
115
|
previewUrls: parsePreviewUrls(payload.previewUrls),
|
|
110
116
|
managementEndpoint: payload.managementEndpoint || null,
|
|
@@ -124,17 +130,23 @@ class Sandbox {
|
|
|
124
130
|
* Credentials are read from the environment automatically.
|
|
125
131
|
* Any value passed explicitly in `options` overrides the environment.
|
|
126
132
|
*
|
|
133
|
+
* Pass the management endpoint so the request is sent to the correct host;
|
|
134
|
+
* falls back to `options.apiHost` when omitted.
|
|
135
|
+
*
|
|
127
136
|
* @param {string} sandboxId the sandbox ID to look up
|
|
128
137
|
* @param {object} [options] credential overrides
|
|
129
138
|
* @param {string} [options.apiHost] Runtime API host
|
|
130
139
|
* @param {string} [options.namespace] Runtime namespace
|
|
131
140
|
* @param {string} [options.auth] Runtime API key
|
|
141
|
+
* @param {string} [options.managementEndpoint] per-sandbox management endpoint returned by
|
|
142
|
+
* `Sandbox.create()`. Falls back to `apiHost` otherwise.
|
|
132
143
|
* @returns {Promise<Sandbox>} sandbox instance with `status` populated (not WebSocket-connected)
|
|
133
144
|
*/
|
|
134
145
|
static async get (sandboxId, options = {}) {
|
|
135
146
|
console.warn('[aio-lib-sandbox] alpha — APIs may change without notice')
|
|
136
147
|
const creds = resolveCredentials(options)
|
|
137
|
-
const
|
|
148
|
+
const base = options.managementEndpoint || creds.apiHost
|
|
149
|
+
const url = `${base}/api/v1/namespaces/${creds.namespace}/sandboxes/${sandboxId}`
|
|
138
150
|
const payload = await apiRequest('GET', url, creds.apiKey)
|
|
139
151
|
|
|
140
152
|
return new Sandbox({
|
|
@@ -143,7 +155,9 @@ class Sandbox {
|
|
|
143
155
|
status: payload.status,
|
|
144
156
|
cluster: payload.cluster,
|
|
145
157
|
region: payload.region,
|
|
158
|
+
idleTimeout: payload.idleTimeout,
|
|
146
159
|
maxLifetime: payload.maxLifetime,
|
|
160
|
+
managementEndpoint: payload.managementEndpoint || options.managementEndpoint || null,
|
|
147
161
|
previewUrls: parsePreviewUrls(payload.previewUrls),
|
|
148
162
|
namespace: creds.namespace,
|
|
149
163
|
apiHost: creds.apiHost,
|
package/test/Sandbox.test.js
CHANGED
|
@@ -342,6 +342,98 @@ describe('Sandbox', () => {
|
|
|
342
342
|
await expect(Sandbox.create({ name: 'no-creds' })).rejects.toThrow(SandboxInitializationError)
|
|
343
343
|
})
|
|
344
344
|
|
|
345
|
+
test('sends default idleTimeout 900 and maxLifetime 3600 when not specified', async () => {
|
|
346
|
+
const mockFetch = jest.fn().mockResolvedValue({
|
|
347
|
+
ok: true,
|
|
348
|
+
json: () => Promise.resolve({
|
|
349
|
+
sandboxId: 'sb-defaults',
|
|
350
|
+
wsEndpoint: 'wss://runtime.example.net/api/v1/namespaces/ns/sandboxes/sb-defaults/exec',
|
|
351
|
+
status: 'ready',
|
|
352
|
+
token: 'tok-defaults',
|
|
353
|
+
idleTimeout: 900,
|
|
354
|
+
maxLifetime: 3600
|
|
355
|
+
})
|
|
356
|
+
})
|
|
357
|
+
global.fetch = mockFetch
|
|
358
|
+
|
|
359
|
+
const createPromise = Sandbox.create({
|
|
360
|
+
name: 'defaults-sandbox',
|
|
361
|
+
apiHost: 'https://runtime.example.net',
|
|
362
|
+
namespace: 'ns',
|
|
363
|
+
auth: 'uuid:key'
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
await new Promise(resolve => setImmediate(resolve))
|
|
367
|
+
sockets[0].open()
|
|
368
|
+
sockets[0].message({ type: 'auth.ok', sandboxId: 'sb-defaults' })
|
|
369
|
+
await createPromise
|
|
370
|
+
|
|
371
|
+
const body = JSON.parse(mockFetch.mock.calls[0][1].body)
|
|
372
|
+
expect(body.idleTimeout).toBe(900)
|
|
373
|
+
expect(body.maxLifetime).toBe(3600)
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
test('forwards explicit idleTimeout in the request body', async () => {
|
|
377
|
+
const mockFetch = jest.fn().mockResolvedValue({
|
|
378
|
+
ok: true,
|
|
379
|
+
json: () => Promise.resolve({
|
|
380
|
+
sandboxId: 'sb-idle',
|
|
381
|
+
wsEndpoint: 'wss://runtime.example.net/api/v1/namespaces/ns/sandboxes/sb-idle/exec',
|
|
382
|
+
status: 'ready',
|
|
383
|
+
token: 'tok-idle',
|
|
384
|
+
idleTimeout: 1800,
|
|
385
|
+
maxLifetime: 3600
|
|
386
|
+
})
|
|
387
|
+
})
|
|
388
|
+
global.fetch = mockFetch
|
|
389
|
+
|
|
390
|
+
const createPromise = Sandbox.create({
|
|
391
|
+
name: 'idle-sandbox',
|
|
392
|
+
apiHost: 'https://runtime.example.net',
|
|
393
|
+
namespace: 'ns',
|
|
394
|
+
auth: 'uuid:key',
|
|
395
|
+
idleTimeout: 1800
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
await new Promise(resolve => setImmediate(resolve))
|
|
399
|
+
sockets[0].open()
|
|
400
|
+
sockets[0].message({ type: 'auth.ok', sandboxId: 'sb-idle' })
|
|
401
|
+
await createPromise
|
|
402
|
+
|
|
403
|
+
const body = JSON.parse(mockFetch.mock.calls[0][1].body)
|
|
404
|
+
expect(body.idleTimeout).toBe(1800)
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
test('stores idleTimeout from the create response on the instance', async () => {
|
|
408
|
+
const mockFetch = jest.fn().mockResolvedValue({
|
|
409
|
+
ok: true,
|
|
410
|
+
json: () => Promise.resolve({
|
|
411
|
+
sandboxId: 'sb-store',
|
|
412
|
+
wsEndpoint: 'wss://runtime.example.net/api/v1/namespaces/ns/sandboxes/sb-store/exec',
|
|
413
|
+
status: 'ready',
|
|
414
|
+
token: 'tok-store',
|
|
415
|
+
idleTimeout: 1800,
|
|
416
|
+
maxLifetime: 3600
|
|
417
|
+
})
|
|
418
|
+
})
|
|
419
|
+
global.fetch = mockFetch
|
|
420
|
+
|
|
421
|
+
const createPromise = Sandbox.create({
|
|
422
|
+
name: 'store-sandbox',
|
|
423
|
+
apiHost: 'https://runtime.example.net',
|
|
424
|
+
namespace: 'ns',
|
|
425
|
+
auth: 'uuid:key',
|
|
426
|
+
idleTimeout: 1800
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
await new Promise(resolve => setImmediate(resolve))
|
|
430
|
+
sockets[0].open()
|
|
431
|
+
sockets[0].message({ type: 'auth.ok', sandboxId: 'sb-store' })
|
|
432
|
+
const sandbox = await createPromise
|
|
433
|
+
|
|
434
|
+
expect(sandbox.idleTimeout).toBe(1800)
|
|
435
|
+
})
|
|
436
|
+
|
|
345
437
|
test('falls back to buildWebSocketEndpoint when wsEndpoint absent', async () => {
|
|
346
438
|
const mockFetch = jest.fn().mockResolvedValue({
|
|
347
439
|
ok: true,
|
|
@@ -394,6 +486,26 @@ describe('Sandbox', () => {
|
|
|
394
486
|
expect(sandbox.cluster).toBe('cluster-b')
|
|
395
487
|
})
|
|
396
488
|
|
|
489
|
+
test('stores idleTimeout from the get response on the instance', async () => {
|
|
490
|
+
global.fetch = jest.fn().mockResolvedValue({
|
|
491
|
+
ok: true,
|
|
492
|
+
json: () => Promise.resolve({
|
|
493
|
+
sandboxId: 'sb-get-idle',
|
|
494
|
+
status: 'running',
|
|
495
|
+
idleTimeout: 1200,
|
|
496
|
+
maxLifetime: 3600
|
|
497
|
+
})
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
const sandbox = await Sandbox.get('sb-get-idle', {
|
|
501
|
+
apiHost: 'https://runtime.example.net',
|
|
502
|
+
namespace: 'ns',
|
|
503
|
+
auth: 'uuid:key'
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
expect(sandbox.idleTimeout).toBe(1200)
|
|
507
|
+
})
|
|
508
|
+
|
|
397
509
|
test('throws SandboxNotFoundError on 404', async () => {
|
|
398
510
|
global.fetch = jest.fn().mockResolvedValue({
|
|
399
511
|
ok: false,
|