cloudburn 0.16.1 → 0.18.2
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 +23 -0
- package/dist/cli.js +47 -9
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -117,6 +117,25 @@ The discovery config equivalent is `discovery.fail-on`.
|
|
|
117
117
|
|
|
118
118
|
Discovery has a five-minute deadline. Use `--timeout <seconds>` to change it, for example `cloudburn discover --timeout 600`. If the deadline expires, the command exits with code 2 and reports the timeout.
|
|
119
119
|
|
|
120
|
+
Discovery reuses fresh, complete evidence from a per-user cache at `$XDG_CACHE_HOME/cloudburn/evidence`, or
|
|
121
|
+
`~/.cache/cloudburn/evidence` when `XDG_CACHE_HOME` is unset. Rules and configuration are evaluated on every run.
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
cloudburn discover --cache refresh
|
|
125
|
+
cloudburn discover --cache off
|
|
126
|
+
cloudburn discover --cache-dir /path/to/evidence --cache-context policy-revision-2
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`--cache refresh` collects current evidence and never silently falls back when collection fails. `--cache off` bypasses
|
|
130
|
+
persistent reads and writes. Customer evidence reuse requires a safe temporary AWS credential session scope or an
|
|
131
|
+
explicit `--cache-context` identifying the effective authorization and session-policy revision. An account ID or role
|
|
132
|
+
ARN alone is insufficient. Change the context when permissions change. Public pricing is cached independently of
|
|
133
|
+
customer evidence, and credentials are never stored.
|
|
134
|
+
|
|
135
|
+
Table output includes a freshness summary; `--format json` includes full evidence provenance and coverage. TTLs are
|
|
136
|
+
tunable starting policies rather than measured optimal defaults. See the [cache options](../../docs/reference/commands.md#discovery-evidence-cache)
|
|
137
|
+
and [SDK README](../sdk/README.md) for modes, defaults, and freshness tradeoffs.
|
|
138
|
+
|
|
120
139
|
The CLI targets one region per run. Multi-region discovery remains available through the SDK and still needs an AWS Resource Explorer aggregator plus an unfiltered default view in the aggregator region.
|
|
121
140
|
`CLDBRN-AWS-TAGGING-1` is opt-in and requires an accessible aggregator; a local-only setup cannot run account-wide tagging discovery.
|
|
122
141
|
`CLDBRN-AWS-LAMBDA-4` is opt-in and requires AWS Compute Optimizer enrollment.
|
|
@@ -139,6 +158,10 @@ access, or incomplete configurations produce diagnostics. The SDK exposes both t
|
|
|
139
158
|
`includeEvaluationResources: true`; the CLI prints finding identities and diagnostics.
|
|
140
159
|
`CLDBRN-AWS-SAGEMAKER-3` uses Cost Explorer coverage data and remains available when Cost Optimization Hub is unavailable.
|
|
141
160
|
Use `--debug` to print SDK and provider execution tracing to `stderr` without changing the normal `stdout` format.
|
|
161
|
+
On interactive terminals, discovery prints catalog and dataset progress plus provisional rule results to `stderr` as
|
|
162
|
+
rules finish. Each rule line includes its ID, status, and finding count. Provisional results describe work completed so
|
|
163
|
+
far; the final report and exit code are authoritative. Progress is silent when `stderr` is not a terminal or `--debug`
|
|
164
|
+
is enabled. `--format json` writes one final JSON result to `stdout`.
|
|
142
165
|
|
|
143
166
|
## Shell Completion
|
|
144
167
|
|
package/dist/cli.js
CHANGED
|
@@ -654,20 +654,34 @@ ${regionsTable}`;
|
|
|
654
654
|
const findingRows = projectFindingRows(response.result);
|
|
655
655
|
const diagnosticRows = projectDiagnosticRows(response.result);
|
|
656
656
|
const suppressedCount = response.result.suppressed?.length ?? 0;
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
657
|
+
const withScanSummary = (content) => {
|
|
658
|
+
const sections = [content];
|
|
659
|
+
if (suppressedCount > 0) {
|
|
660
|
+
sections.push(`Suppressed: ${suppressedCount}`);
|
|
661
|
+
}
|
|
662
|
+
const evidence = response.result.evidence ?? [];
|
|
663
|
+
if (evidence.length > 0) {
|
|
664
|
+
const cachedCount = evidence.filter((entry) => entry.source === "cache").length;
|
|
665
|
+
const incompleteCount = evidence.filter((entry) => !entry.complete).length;
|
|
666
|
+
const oldestObservation = evidence.map((entry) => entry.observedAt).sort()[0];
|
|
667
|
+
sections.push(
|
|
668
|
+
`Evidence: ${cachedCount} cached, ${evidence.length - cachedCount} collected; ${incompleteCount} incomplete.
|
|
669
|
+
Oldest observation: ${oldestObservation}`
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
return sections.join("\n\n");
|
|
673
|
+
};
|
|
660
674
|
if (findingRows.length === 0 && diagnosticRows.length === 0) {
|
|
661
|
-
return
|
|
675
|
+
return withScanSummary(suppressedCount > 0 ? "No active findings." : "No findings.");
|
|
662
676
|
}
|
|
663
677
|
if (findingRows.length === 0) {
|
|
664
|
-
return
|
|
678
|
+
return withScanSummary(`Diagnostics
|
|
665
679
|
${renderAsciiTable(diagnosticRows, diagnosticColumns)}`);
|
|
666
680
|
}
|
|
667
681
|
if (diagnosticRows.length === 0) {
|
|
668
|
-
return
|
|
682
|
+
return withScanSummary(renderAsciiTable(findingRows, scanColumns));
|
|
669
683
|
}
|
|
670
|
-
return
|
|
684
|
+
return withScanSummary(
|
|
671
685
|
`${renderAsciiTable(findingRows, scanColumns)}
|
|
672
686
|
|
|
673
687
|
Diagnostics
|
|
@@ -1059,6 +1073,8 @@ var registerConfigCommand = (program) => {
|
|
|
1059
1073
|
};
|
|
1060
1074
|
|
|
1061
1075
|
// src/commands/discover.ts
|
|
1076
|
+
import { homedir } from "os";
|
|
1077
|
+
import { join as join2 } from "path";
|
|
1062
1078
|
import {
|
|
1063
1079
|
assertSupportedAwsRegion,
|
|
1064
1080
|
CloudBurnClient
|
|
@@ -1095,6 +1111,13 @@ var resolveCliDiscoveryProgressLogger = (command) => {
|
|
|
1095
1111
|
return void 0;
|
|
1096
1112
|
}
|
|
1097
1113
|
return (event) => {
|
|
1114
|
+
if (event.kind === "rule") {
|
|
1115
|
+
process.stderr.write(
|
|
1116
|
+
`discover: rules ${event.completedRules}/${event.totalRules} (${event.ruleId}: ${event.status}, findings: ${event.findingCount}, provisional)
|
|
1117
|
+
`
|
|
1118
|
+
);
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1098
1121
|
process.stderr.write(
|
|
1099
1122
|
event.kind === "catalog" ? `discover: catalog ready with ${event.resourceCount} resources from ${event.searchRegion}
|
|
1100
1123
|
` : `discover: datasets ${event.completedDatasets}/${event.totalDatasets} loaded (${event.datasetKey})
|
|
@@ -1149,6 +1172,12 @@ var parseDiscoveryTimeout = (value) => {
|
|
|
1149
1172
|
}
|
|
1150
1173
|
return seconds;
|
|
1151
1174
|
};
|
|
1175
|
+
var parseCacheMode = (value) => {
|
|
1176
|
+
if (value === "normal" || value === "refresh" || value === "off") {
|
|
1177
|
+
return value;
|
|
1178
|
+
}
|
|
1179
|
+
throw new InvalidArgumentError3("Cache mode must be normal, refresh, or off.");
|
|
1180
|
+
};
|
|
1152
1181
|
var parseDiscoveryServiceList = (value) => validateServiceList("discovery", parseServiceList(value)) ?? [];
|
|
1153
1182
|
var describeInitializationMessage = (result) => {
|
|
1154
1183
|
const baseMessage = result.indexType === "aggregator" ? result.aggregatorAction === "promoted" ? `Promoted the existing local Resource Explorer index in ${result.aggregatorRegion} to the aggregator.` : result.aggregatorAction === "created" ? `Configured ${result.aggregatorRegion} as the Resource Explorer aggregator.` : result.coverage === "full" ? `Resource Explorer aggregator already exists in ${result.aggregatorRegion}.` : `Resource Explorer aggregator already exists in ${result.aggregatorRegion}, but only ${result.observedStatus.indexedRegionCount} of ${result.observedStatus.totalRegionCount} regions are indexed.` : result.status === "EXISTING" ? `Local Resource Explorer setup already exists in ${result.aggregatorRegion}.` : `Local Resource Explorer setup created in ${result.aggregatorRegion}.`;
|
|
@@ -1256,7 +1285,10 @@ var registerDiscoverCommand = (program) => {
|
|
|
1256
1285
|
"--region <region>",
|
|
1257
1286
|
"AWS region to discover. Defaults to the current AWS region from AWS_REGION when omitted.",
|
|
1258
1287
|
parseAwsRegion
|
|
1259
|
-
).option("--config <path>", "Explicit CloudBurn config file to load (required for config files in CI)").option("--timeout <seconds>", "Maximum discovery duration in seconds (default: 300).", parseDiscoveryTimeout).option(
|
|
1288
|
+
).option("--config <path>", "Explicit CloudBurn config file to load (required for config files in CI)").option("--timeout <seconds>", "Maximum discovery duration in seconds (default: 300).", parseDiscoveryTimeout).option("--cache <mode>", "Evidence cache mode: normal, refresh, or off (default: normal).", parseCacheMode).option(
|
|
1289
|
+
"--cache-dir <path>",
|
|
1290
|
+
"Evidence cache directory (default: $XDG_CACHE_HOME/cloudburn/evidence or ~/.cache/cloudburn/evidence)."
|
|
1291
|
+
).option("--cache-context <id>", "Effective authorization/session-policy revision for evidence reuse.").option(
|
|
1260
1292
|
"--enabled-rules <ruleIds>",
|
|
1261
1293
|
"Comma-separated rule IDs to enable. When set, CloudBurn checks only these rules. By default, AWS Core preset rules are enabled.",
|
|
1262
1294
|
parseRuleIdList
|
|
@@ -1276,6 +1308,11 @@ var registerDiscoverCommand = (program) => {
|
|
|
1276
1308
|
const configOverride = toDiscoveryConfigOverride(options);
|
|
1277
1309
|
const loadedConfig = await scanner.loadConfig(options.config);
|
|
1278
1310
|
const discoveryOptions = {
|
|
1311
|
+
cache: {
|
|
1312
|
+
mode: options.cache ?? "normal",
|
|
1313
|
+
directory: options.cacheDir ?? join2(process.env.XDG_CACHE_HOME ?? join2(homedir(), ".cache"), "cloudburn", "evidence"),
|
|
1314
|
+
...options.cacheContext === void 0 ? {} : { authorizationContext: options.cacheContext }
|
|
1315
|
+
},
|
|
1279
1316
|
target: resolveDiscoveryTarget(options.region)
|
|
1280
1317
|
};
|
|
1281
1318
|
if (configOverride !== void 0) {
|
|
@@ -1307,6 +1344,7 @@ var registerDiscoverCommand = (program) => {
|
|
|
1307
1344
|
[
|
|
1308
1345
|
"cloudburn discover",
|
|
1309
1346
|
"cloudburn discover --region eu-central-1",
|
|
1347
|
+
"cloudburn discover --cache refresh",
|
|
1310
1348
|
"cloudburn discover status",
|
|
1311
1349
|
"cloudburn discover init"
|
|
1312
1350
|
]
|
|
@@ -1566,7 +1604,7 @@ var isCliEntrypoint = (moduleUrl, argvEntry = process.argv[1]) => {
|
|
|
1566
1604
|
};
|
|
1567
1605
|
var createProgram = () => {
|
|
1568
1606
|
const program = createCliCommand();
|
|
1569
|
-
program.name("cloudburn").usage("[command]").description("Know what you spend. Fix what you waste.").version("0.
|
|
1607
|
+
program.name("cloudburn").usage("[command]").description("Know what you spend. Fix what you waste.").version("0.18.2").option("--debug", "Write execution trace messages to stderr").option("--format <format>", OUTPUT_FORMAT_OPTION_DESCRIPTION, parseOutputFormat);
|
|
1570
1608
|
configureCliHelp(program);
|
|
1571
1609
|
program.exitOverride();
|
|
1572
1610
|
registerCompletionCommand(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudburn",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.2",
|
|
4
4
|
"description": "Cloudburn CLI for cloud cost optimization",
|
|
5
5
|
"homepage": "https://cloudburn.io/docs",
|
|
6
6
|
"bugs": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"commander": "^13.1.0",
|
|
23
|
-
"@cloudburn/sdk": "0.
|
|
23
|
+
"@cloudburn/sdk": "0.35.2"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@biomejs/biome": "^2.5.3",
|