Skip to content

Commit d7b2eba

Browse files
committed
feat: add sandbox update command
Add `b2c sandbox update` command exposing the PATCH /sandboxes/{sandboxId} API to update sandbox TTL, auto-scheduling, tags, and notification emails.
1 parent b26ebeb commit d7b2eba

3 files changed

Lines changed: 211 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@salesforce/b2c-cli': minor
3+
---
4+
5+
Added `sandbox update` command to update sandbox TTL, auto-scheduling, tags, and notification emails via the PATCH API

docs/cli/sandbox.md

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ These commands were previously available as `b2c ods <command>`. The `ods` prefi
1212

1313
## Sandbox ID Formats
1414

15-
Commands that operate on a specific sandbox (`get`, `start`, `stop`, `restart`, `delete`) accept two ID formats:
15+
Commands that operate on a specific sandbox (`get`, `update`, `start`, `stop`, `restart`, `delete`) accept two ID formats:
1616

1717
| Format | Example | Description |
1818
|--------|---------|-------------|
@@ -481,6 +481,68 @@ b2c sandbox reset zzzv-123 --json
481481

482482
---
483483

484+
## b2c sandbox update
485+
486+
Update a sandbox's TTL, scheduling, tags, or notification emails.
487+
488+
### Usage
489+
490+
```bash
491+
b2c sandbox update <SANDBOXID> [FLAGS]
492+
```
493+
494+
### Arguments
495+
496+
| Argument | Description | Required |
497+
|----------|-------------|----------|
498+
| `SANDBOXID` | Sandbox ID (UUID or realm-instance, e.g., `zzzv-123`) | Yes |
499+
500+
### Flags
501+
502+
| Flag | Description |
503+
|------|-------------|
504+
| `--ttl` | Number of hours to add to sandbox lifetime (0 or less for infinite). Must adhere to the maximum TTL configuration together with previous extensions. |
505+
| `--auto-scheduled` / `--no-auto-scheduled` | Enable or disable automatic start/stop scheduling |
506+
| `--tags` | Comma-separated list of tags |
507+
| `--emails` | Comma-separated list of notification email addresses |
508+
509+
At least one flag is required.
510+
511+
### Examples
512+
513+
```bash
514+
# Extend sandbox lifetime by 48 hours
515+
b2c sandbox update zzzv-123 --ttl 48
516+
517+
# Set infinite lifetime
518+
b2c sandbox update zzzv-123 --ttl 0
519+
520+
# Enable auto-scheduling
521+
b2c sandbox update zzzv-123 --auto-scheduled
522+
523+
# Disable auto-scheduling
524+
b2c sandbox update zzzv-123 --no-auto-scheduled
525+
526+
# Set tags
527+
b2c sandbox update zzzv-123 --tags ci,nightly
528+
529+
# Set notification emails
530+
b2c sandbox update zzzv-123 --emails dev@example.com,qa@example.com
531+
532+
# Combine multiple updates
533+
b2c sandbox update zzzv-123 --ttl 48 --tags ci,nightly
534+
535+
# Output as JSON
536+
b2c sandbox update zzzv-123 --ttl 48 --json
537+
```
538+
539+
### Notes
540+
541+
- The `--ttl` value is added to the existing sandbox lifetime, not an absolute value. Together with previous extensions, it must adhere to the realm's maximum TTL configuration.
542+
- Setting `--ttl` to 0 or less gives the sandbox an infinite lifetime (subject to realm configuration).
543+
544+
---
545+
484546
## b2c sandbox usage
485547

486548
Show usage information for a specific sandbox over a date range.
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Copyright (c) 2025, Salesforce, Inc.
3+
* SPDX-License-Identifier: Apache-2
4+
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
import {Args, Flags, ux} from '@oclif/core';
7+
import cliui from 'cliui';
8+
import {OdsCommand} from '@salesforce/b2c-tooling-sdk/cli';
9+
import {getApiErrorMessage, type OdsComponents} from '@salesforce/b2c-tooling-sdk';
10+
import {t, withDocs} from '../../i18n/index.js';
11+
12+
type SandboxModel = OdsComponents['schemas']['SandboxModel'];
13+
type SandboxUpdateRequestModel = OdsComponents['schemas']['SandboxUpdateRequestModel'];
14+
15+
/**
16+
* Command to update an on-demand sandbox.
17+
*/
18+
export default class SandboxUpdate extends OdsCommand<typeof SandboxUpdate> {
19+
static aliases = ['ods:update'];
20+
21+
static args = {
22+
sandboxId: Args.string({
23+
description: 'Sandbox ID (UUID or realm-instance, e.g., abcd-123)',
24+
required: true,
25+
}),
26+
};
27+
28+
static description = withDocs(
29+
t('commands.sandbox.update.description', 'Update a sandbox (extend TTL, change scheduling, update tags or emails)'),
30+
'/cli/sandbox.html#b2c-sandbox-update',
31+
);
32+
33+
static enableJsonFlag = true;
34+
35+
static examples = [
36+
'<%= config.bin %> <%= command.id %> zzzv-123 --ttl 48',
37+
'<%= config.bin %> <%= command.id %> zzzv-123 --ttl 0',
38+
'<%= config.bin %> <%= command.id %> zzzv-123 --auto-scheduled',
39+
'<%= config.bin %> <%= command.id %> zzzv-123 --no-auto-scheduled',
40+
'<%= config.bin %> <%= command.id %> zzzv-123 --tags tag1,tag2',
41+
'<%= config.bin %> <%= command.id %> zzzv-123 --emails user@example.com,dev@example.com',
42+
'<%= config.bin %> <%= command.id %> zzzv-123 --ttl 48 --tags ci,nightly --json',
43+
];
44+
45+
static flags = {
46+
ttl: Flags.integer({
47+
description: 'Number of hours to add to sandbox lifetime (0 or less for infinite)',
48+
}),
49+
'auto-scheduled': Flags.boolean({
50+
description: 'Enable or disable automatic start/stop scheduling',
51+
allowNo: true,
52+
}),
53+
tags: Flags.string({
54+
description: 'Comma-separated list of tags',
55+
}),
56+
emails: Flags.string({
57+
description: 'Comma-separated list of notification email addresses',
58+
}),
59+
};
60+
61+
async run(): Promise<SandboxModel> {
62+
const sandboxId = await this.resolveSandboxId(this.args.sandboxId);
63+
const {ttl, 'auto-scheduled': autoScheduled, tags, emails} = this.flags;
64+
65+
// Require at least one update flag
66+
if (ttl === undefined && autoScheduled === undefined && tags === undefined && emails === undefined) {
67+
this.error('At least one update flag is required. Use --ttl, --auto-scheduled, --tags, or --emails.');
68+
}
69+
70+
const body: SandboxUpdateRequestModel = {};
71+
72+
if (ttl !== undefined) {
73+
body.ttl = ttl;
74+
}
75+
76+
if (autoScheduled !== undefined) {
77+
body.autoScheduled = autoScheduled;
78+
}
79+
80+
if (tags !== undefined) {
81+
body.tags = tags.split(',').map((tag) => tag.trim());
82+
}
83+
84+
if (emails !== undefined) {
85+
body.emails = emails.split(',').map((email) => email.trim());
86+
}
87+
88+
this.log(t('commands.sandbox.update.updating', 'Updating sandbox {{sandboxId}}...', {sandboxId}));
89+
90+
const result = await this.odsClient.PATCH('/sandboxes/{sandboxId}', {
91+
params: {
92+
path: {sandboxId},
93+
},
94+
body,
95+
});
96+
97+
if (!result.data?.data) {
98+
const message = getApiErrorMessage(result.error, result.response);
99+
this.error(`Failed to update sandbox: ${message}`);
100+
}
101+
102+
const sandbox = result.data.data;
103+
104+
this.log(t('commands.sandbox.update.success', 'Sandbox updated successfully'));
105+
106+
if (this.jsonEnabled()) {
107+
return sandbox;
108+
}
109+
110+
this.printSandboxSummary(sandbox);
111+
112+
return sandbox;
113+
}
114+
115+
private printSandboxSummary(sandbox: SandboxModel): void {
116+
const ui = cliui({width: process.stdout.columns || 80});
117+
118+
const fields: [string, string | undefined][] = [
119+
['ID', sandbox.id],
120+
['Realm', sandbox.realm],
121+
['Instance', sandbox.instance],
122+
['State', sandbox.state],
123+
['Auto Scheduled', sandbox.autoScheduled?.toString()],
124+
['EOL', sandbox.eol ? new Date(sandbox.eol).toLocaleString() : undefined],
125+
];
126+
127+
if (sandbox.tags && sandbox.tags.length > 0) {
128+
fields.push(['Tags', sandbox.tags.join(', ')]);
129+
}
130+
131+
if (sandbox.emails && sandbox.emails.length > 0) {
132+
fields.push(['Emails', sandbox.emails.join(', ')]);
133+
}
134+
135+
for (const [label, value] of fields) {
136+
if (value !== undefined) {
137+
ui.div({text: `${label}:`, width: 20, padding: [0, 2, 0, 0]}, {text: value, padding: [0, 0, 0, 0]});
138+
}
139+
}
140+
141+
ux.stdout(ui.toString());
142+
}
143+
}

0 commit comments

Comments
 (0)