Skip to content

Commit ca22f4d

Browse files
authored
@W-21766828 add storage,multi realm usage and settings support (#309)
* @W-21766828 add storage,multi realm usage and settings support * fixing lint
1 parent f1e1512 commit ca22f4d

11 files changed

Lines changed: 968 additions & 86 deletions

File tree

docs/cli/sandbox.md

Lines changed: 148 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,79 @@ If detailed usage data is present (granular history, profiles, etc.), the comman
604604

605605
---
606606

607+
## b2c sandbox settings
608+
609+
Show effective OCAPI and WebDAV settings for a specific sandbox.
610+
611+
### Usage
612+
613+
```bash
614+
b2c sandbox settings <SANDBOXID>
615+
```
616+
617+
### Arguments
618+
619+
| Argument | Description | Required |
620+
|----------|-------------|----------|
621+
| `SANDBOXID` | Sandbox ID (UUID or realm-instance, e.g., `zzzv-123`) | Yes |
622+
623+
### Examples
624+
625+
```bash
626+
# Show settings summary for a sandbox
627+
b2c sandbox settings zzzz-001
628+
629+
# Output full settings payload as JSON
630+
b2c sandbox settings zzzz-001 --json
631+
```
632+
633+
### Output
634+
635+
When not using `--json`, the command prints:
636+
637+
- Number of OCAPI client entries
638+
- Number of WebDAV client entries
639+
- A short per-client breakdown for each settings type
640+
641+
---
642+
643+
## b2c sandbox storage
644+
645+
Show filesystem storage usage for a specific sandbox.
646+
647+
### Usage
648+
649+
```bash
650+
b2c sandbox storage <SANDBOXID>
651+
```
652+
653+
### Arguments
654+
655+
| Argument | Description | Required |
656+
|----------|-------------|----------|
657+
| `SANDBOXID` | Sandbox ID (UUID or realm-instance, e.g., `zzzv-123`) | Yes |
658+
659+
### Examples
660+
661+
```bash
662+
# Show storage table for a sandbox
663+
b2c sandbox storage zzzz-001
664+
665+
# Output raw storage response as JSON
666+
b2c sandbox storage zzzz-001 --json
667+
```
668+
669+
### Output
670+
671+
When not using `--json`, the command prints a table with one row per filesystem:
672+
673+
- Filesystem name
674+
- Total space (MB)
675+
- Used space (MB)
676+
- Used percentage
677+
678+
---
679+
607680
## Sandbox Aliases
608681

609682
Sandbox aliases let you access a sandbox via a custom hostname instead of the default instance hostname.
@@ -968,9 +1041,11 @@ For the complete response including all metadata, use the `--json` flag.
9681041
Realm commands operate at the **realm** level rather than on an individual sandbox. They are available as both `realm` topic commands and as `sandbox realm` subcommands:
9691042

9701043
- `b2c realm list` (`b2c sandbox realm list`)
1044+
- `b2c realm configuration` (`b2c sandbox realm configuration`)
9711045
- `b2c realm get` (`b2c sandbox realm get`)
9721046
- `b2c realm update` (`b2c sandbox realm update`)
9731047
- `b2c realm usage` (`b2c sandbox realm usage`)
1048+
- `b2c realm usages` (`b2c sandbox realm usages`)
9741049

9751050
### Required Access for Realm Commands
9761051

@@ -990,7 +1065,7 @@ b2c realm list [REALM]
9901065

9911066
| Argument | Description | Required |
9921067
|----------|-------------|----------|
993-
| `REALM` | Specific realm ID (four-letter ID) to get details for | No |
1068+
| `REALM` | Optional realm ID filter (four-letter ID) | No |
9941069

9951070
#### Examples
9961071

@@ -1005,7 +1080,35 @@ b2c realm list zzzz
10051080
b2c realm list --json
10061081
```
10071082

1008-
When `REALM` is omitted, the command discovers realms from the `/me` endpoint and then fetches configuration for each.
1083+
When `REALM` is omitted, the command discovers realms from the `/me` endpoint.
1084+
1085+
### b2c realm configuration
1086+
1087+
Get sandbox configuration for a specific realm.
1088+
1089+
#### Usage
1090+
1091+
```bash
1092+
b2c realm configuration <REALM>
1093+
```
1094+
1095+
#### Arguments
1096+
1097+
| Argument | Description | Required |
1098+
|----------|-------------|----------|
1099+
| `REALM` | Realm ID (four-letter ID) | Yes |
1100+
1101+
#### Examples
1102+
1103+
```bash
1104+
# Get realm sandbox configuration
1105+
b2c realm configuration zzzz
1106+
1107+
# JSON output
1108+
b2c realm configuration zzzz --json
1109+
```
1110+
1111+
When not using `--json`, the command prints configuration details such as emails, sandbox limits, TTL values, and start/stop schedulers.
10091112

10101113
### b2c realm get
10111114

@@ -1143,3 +1246,46 @@ When not using `--json`, the command prints a summary including:
11431246

11441247
If detailed usage is available, it prints a hint to re-run with `--json` for the full structure. If no usage data is returned for the requested period, it prints a friendly message instead of failing.
11451248

1249+
### b2c realm usages
1250+
1251+
Show usage information for multiple realms in one request.
1252+
1253+
#### Usage
1254+
1255+
```bash
1256+
b2c realm usages [FLAGS]
1257+
```
1258+
1259+
#### Flags
1260+
1261+
| Flag | Description |
1262+
|------|-------------|
1263+
| `--realm` | Realm IDs to include (repeat flag or provide comma-separated values) |
1264+
| `--from` | Earliest date to include in usage (ISO 8601) |
1265+
| `--to` | Latest date to include in usage (ISO 8601) |
1266+
| `--detailed-report` | Include detailed usage information in the response |
1267+
1268+
If `--realm` is omitted, the command auto-discovers realms from `/me` and queries usage for all discovered realms.
1269+
1270+
#### Examples
1271+
1272+
```bash
1273+
# Usage for all realms available to the current user
1274+
b2c realm usages
1275+
1276+
# Usage for two specific realms
1277+
b2c realm usages --realm zzzz --realm yyyy
1278+
1279+
# Usage for comma-separated realms and date range
1280+
b2c realm usages --realm zzzz,yyyy --from 2024-01-01 --to 2024-01-31
1281+
1282+
# Detailed report in JSON
1283+
b2c realm usages --detailed-report --json
1284+
```
1285+
1286+
When not using `--json`, the command prints one row per realm with summary metrics such as:
1287+
1288+
- Active / created / deleted sandbox counts
1289+
- Minutes up / minutes down
1290+
- Sandbox seconds
1291+
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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+
7+
import {Args} from '@oclif/core';
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 RealmConfigurationModel = OdsComponents['schemas']['RealmConfigurationModel'];
13+
type RealmConfigurationResponse = OdsComponents['schemas']['RealmConfigurationResponse'];
14+
15+
/**
16+
* Get realm sandbox configuration.
17+
*/
18+
export default class SandboxRealmConfiguration extends OdsCommand<typeof SandboxRealmConfiguration> {
19+
static aliases = ['ods:realm:configuration', 'realm:configuration'];
20+
21+
static args = {
22+
realm: Args.string({
23+
description: 'Realm ID (four-letter ID)',
24+
required: true,
25+
}),
26+
};
27+
28+
static description = withDocs(
29+
t('commands.realm.configuration.description', 'Get sandbox configuration for a realm'),
30+
'/cli/realm.html#b2c-realm-configuration',
31+
);
32+
33+
static enableJsonFlag = true;
34+
35+
static examples = ['<%= config.bin %> <%= command.id %> zzzz', '<%= config.bin %> <%= command.id %> zzzz --json'];
36+
37+
async run(): Promise<RealmConfigurationModel | RealmConfigurationResponse | undefined> {
38+
const {args} = await this.parse(SandboxRealmConfiguration);
39+
const realm = args.realm;
40+
41+
this.log(t('commands.realm.configuration.fetching', 'Fetching configuration for realm {{realm}}...', {realm}));
42+
43+
const result = await this.odsClient.GET('/realms/{realm}/configuration', {
44+
params: {path: {realm}},
45+
});
46+
47+
if (result.error) {
48+
this.error(
49+
t('commands.realm.configuration.error', 'Failed to fetch configuration for realm {{realm}}: {{message}}', {
50+
realm,
51+
message: getApiErrorMessage(result.error, result.response),
52+
}),
53+
);
54+
}
55+
56+
const data = (result.data as RealmConfigurationResponse | undefined)?.data;
57+
if (!data) {
58+
this.log(t('commands.realm.configuration.noData', 'No configuration data was returned for this realm.'));
59+
return undefined;
60+
}
61+
62+
if (this.jsonEnabled()) {
63+
return result.data as RealmConfigurationResponse;
64+
}
65+
66+
this.printConfiguration(data);
67+
return data;
68+
}
69+
70+
private printConfiguration(configuration: RealmConfigurationModel): void {
71+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
72+
const cfg = configuration as any;
73+
74+
console.log('Realm Configuration');
75+
console.log('───────────────────');
76+
77+
const maxTtlRaw = cfg.sandbox?.sandboxTTL?.maximum as number | undefined;
78+
const maxTtlDisplay = maxTtlRaw === undefined ? undefined : maxTtlRaw >= 2_147_483_647 ? '0' : String(maxTtlRaw);
79+
80+
const rows: Array<[string, string | undefined]> = [
81+
['Enabled', cfg.enabled === undefined ? undefined : String(cfg.enabled)],
82+
['Emails', Array.isArray(cfg.emails) ? cfg.emails.join(', ') : undefined],
83+
['Limits Enabled', cfg.sandbox?.limitsEnabled === undefined ? undefined : String(cfg.sandbox.limitsEnabled)],
84+
[
85+
'Total Sandboxes',
86+
cfg.sandbox?.totalNumberOfSandboxes === undefined ? undefined : String(cfg.sandbox.totalNumberOfSandboxes),
87+
],
88+
['Max Sandbox TTL', maxTtlDisplay],
89+
[
90+
'Default Sandbox TTL',
91+
cfg.sandbox?.sandboxTTL?.defaultValue === undefined ? undefined : String(cfg.sandbox.sandboxTTL.defaultValue),
92+
],
93+
[
94+
'Local Users Allowed',
95+
cfg.sandbox?.localUsersAllowed === undefined ? undefined : String(cfg.sandbox.localUsersAllowed),
96+
],
97+
];
98+
99+
for (const [label, value] of rows) {
100+
if (value !== undefined) {
101+
console.log(`${label}: ${value}`);
102+
}
103+
}
104+
105+
if (cfg.sandbox?.startScheduler) {
106+
console.log(`Start Scheduler: ${JSON.stringify(cfg.sandbox.startScheduler)}`);
107+
}
108+
109+
if (cfg.sandbox?.stopScheduler) {
110+
console.log(`Stop Scheduler: ${JSON.stringify(cfg.sandbox.stopScheduler)}`);
111+
}
112+
}
113+
}

packages/b2c-cli/src/commands/sandbox/realm/list.ts

Lines changed: 9 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,26 @@
66

77
import {Args} from '@oclif/core';
88
import {OdsCommand} from '@salesforce/b2c-tooling-sdk/cli';
9-
import {getApiErrorMessage, type OdsComponents} from '@salesforce/b2c-tooling-sdk';
9+
import {getApiErrorMessage} from '@salesforce/b2c-tooling-sdk';
1010
import {t, withDocs} from '../../../i18n/index.js';
1111

12-
type RealmConfigurationModel = OdsComponents['schemas']['RealmConfigurationModel'];
13-
14-
interface RealmWithUsage {
12+
interface RealmItem {
1513
realmId: string;
16-
configuration?: RealmConfigurationModel;
1714
}
1815

1916
interface RealmListResponse {
20-
realms: RealmWithUsage[];
17+
realms: RealmItem[];
2118
}
2219

2320
/**
24-
* List realms eligible for sandbox management, optionally including usage.
21+
* List realms eligible for sandbox management.
2522
*/
2623
export default class SandboxRealmList extends OdsCommand<typeof SandboxRealmList> {
2724
static aliases = ['ods:realm:list', 'realm:list'];
2825

2926
static args = {
3027
realm: Args.string({
31-
description: 'Specific realm ID (four-letter ID) to get details for',
28+
description: 'Optional realm ID filter (four-letter ID)',
3229
required: false,
3330
}),
3431
};
@@ -70,32 +67,7 @@ export default class SandboxRealmList extends OdsCommand<typeof SandboxRealmList
7067
realmIds = meResult.data.data.realms ?? [];
7168
}
7269

73-
const realms: RealmWithUsage[] = [];
74-
75-
for (const realmId of realmIds) {
76-
// Fetch configuration for each realm
77-
// eslint-disable-next-line no-await-in-loop -- Sequential API calls required
78-
const configResult = await this.odsClient.GET('/realms/{realm}/configuration', {
79-
params: {path: {realm: realmId}},
80-
});
81-
82-
if (configResult.error) {
83-
this.error(
84-
t('commands.realm.list.configError', 'Failed to fetch configuration for realm {{realm}}: {{message}}', {
85-
realm: realmId,
86-
message: getApiErrorMessage(configResult.error, configResult.response),
87-
}),
88-
);
89-
}
90-
91-
const realmEntry: RealmWithUsage = {
92-
realmId,
93-
94-
configuration: (configResult.data?.data as RealmConfigurationModel | undefined) ?? undefined,
95-
};
96-
97-
realms.push(realmEntry);
98-
}
70+
const realms: RealmItem[] = realmIds.map((realmId) => ({realmId}));
9971

10072
const response: RealmListResponse = {realms};
10173

@@ -110,16 +82,12 @@ export default class SandboxRealmList extends OdsCommand<typeof SandboxRealmList
11082

11183
// Human-readable output: simple table-like listing
11284

113-
console.log('Realm Enabled');
85+
console.log('Realm');
11486

115-
console.log('───── ───────');
87+
console.log('─────');
11688

11789
for (const realm of realms) {
118-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
119-
const configAny = realm.configuration as any;
120-
const enabled = configAny?.enabled ?? false;
121-
122-
console.log(`${realm.realmId.padEnd(5)} ${String(enabled).padEnd(7)}`);
90+
console.log(realm.realmId);
12391
}
12492

12593
return response;

0 commit comments

Comments
 (0)