@meshxdata/fops 0.1.44 → 0.1.46
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/CHANGELOG.md +183 -0
- package/package.json +1 -1
- package/src/commands/lifecycle.js +101 -5
- package/src/commands/setup.js +45 -4
- package/src/plugins/bundled/fops-plugin-azure/index.js +29 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-core.js +1185 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-flux.js +1180 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-ingress.js +393 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-naming.js +104 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-network.js +296 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-postgres.js +768 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-reconcilers.js +538 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-secrets.js +849 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-stacks.js +643 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-state.js +145 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-storage.js +496 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-terraform.js +1032 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks.js +155 -4245
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-keyvault.js +186 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-ops.js +29 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-results.js +78 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure.js +1 -1
- package/src/plugins/bundled/fops-plugin-azure/lib/commands/infra-cmds.js +758 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/commands/registry-cmds.js +250 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/commands/test-cmds.js +52 -1
- package/src/plugins/bundled/fops-plugin-azure/lib/commands/vm-cmds.js +10 -0
- package/src/plugins/bundled/fops-plugin-foundation/lib/apply.js +3 -2
- package/src/plugins/bundled/fops-plugin-foundation/lib/helpers.js +21 -0
- package/src/plugins/bundled/fops-plugin-foundation/lib/tools-read.js +3 -5
- package/src/ui/tui/App.js +13 -13
- package/src/web/dist/assets/index-NXC8Hvnp.css +1 -0
- package/src/web/dist/assets/index-QH1N4ejK.js +112 -0
- package/src/web/dist/index.html +2 -2
- package/src/web/server.js +4 -4
- package/src/web/dist/assets/index-BphVaAUd.css +0 -1
- package/src/web/dist/assets/index-CSckLzuG.js +0 -129
|
@@ -1253,6 +1253,192 @@ export async function keyvaultSetup(opts = {}) {
|
|
|
1253
1253
|
console.log(chalk.bold.green(`\n Setup complete! Run: fops azure keyvault sync\n`));
|
|
1254
1254
|
}
|
|
1255
1255
|
|
|
1256
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1257
|
+
// Network configuration (VNet integration, service endpoints)
|
|
1258
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1259
|
+
|
|
1260
|
+
export async function networkShow(opts = {}) {
|
|
1261
|
+
const execa = await lazyExeca();
|
|
1262
|
+
const sub = opts.profile;
|
|
1263
|
+
await ensureAzCli(execa);
|
|
1264
|
+
await ensureAzAuth(execa, { subscription: sub });
|
|
1265
|
+
|
|
1266
|
+
const vault = await resolveVault(execa, opts.vault, sub);
|
|
1267
|
+
|
|
1268
|
+
const { stdout } = await execa("az", [
|
|
1269
|
+
"keyvault", "show", "--name", vault, "--output", "json", ...subArgs(sub),
|
|
1270
|
+
], { timeout: 30000 });
|
|
1271
|
+
|
|
1272
|
+
const v = JSON.parse(stdout);
|
|
1273
|
+
const props = v.properties || {};
|
|
1274
|
+
const networkRules = props.networkAcls || {};
|
|
1275
|
+
|
|
1276
|
+
banner(`Network Config — "${vault}"`);
|
|
1277
|
+
kvLine("Public Access", props.publicNetworkAccess === "Disabled" ? OK("disabled") : WARN("enabled"));
|
|
1278
|
+
kvLine("Default Action", networkRules.defaultAction === "Deny" ? OK("Deny") : WARN(networkRules.defaultAction || "Allow"));
|
|
1279
|
+
kvLine("Bypass", DIM(networkRules.bypass || "AzureServices"));
|
|
1280
|
+
|
|
1281
|
+
const ipRules = networkRules.ipRules || [];
|
|
1282
|
+
const vnetRules = networkRules.virtualNetworkRules || [];
|
|
1283
|
+
|
|
1284
|
+
console.log("");
|
|
1285
|
+
if (ipRules.length > 0) {
|
|
1286
|
+
console.log(ACCENT(" IP Rules"));
|
|
1287
|
+
for (const r of ipRules) {
|
|
1288
|
+
console.log(` ${DIM("•")} ${r.value}`);
|
|
1289
|
+
}
|
|
1290
|
+
} else {
|
|
1291
|
+
kvLine("IP Rules", DIM("none"));
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
if (vnetRules.length > 0) {
|
|
1295
|
+
console.log(ACCENT(" VNet Rules"));
|
|
1296
|
+
for (const r of vnetRules) {
|
|
1297
|
+
const subnetId = r.id || "";
|
|
1298
|
+
const parts = subnetId.split("/");
|
|
1299
|
+
const vnet = parts[parts.indexOf("virtualNetworks") + 1] || "";
|
|
1300
|
+
const subnet = parts[parts.indexOf("subnets") + 1] || "";
|
|
1301
|
+
console.log(` ${DIM("•")} ${vnet}/${subnet}`);
|
|
1302
|
+
}
|
|
1303
|
+
} else {
|
|
1304
|
+
kvLine("VNet Rules", DIM("none"));
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
console.log("");
|
|
1308
|
+
hint("Add VNet: fops azure keyvault network add-vnet --vault <name> --vnet <vnet> --subnet <subnet>");
|
|
1309
|
+
hint("Private: fops azure keyvault network private --vault <name>\n");
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
export async function networkAddVnet(opts = {}) {
|
|
1313
|
+
const execa = await lazyExeca();
|
|
1314
|
+
const sub = opts.profile;
|
|
1315
|
+
await ensureAzCli(execa);
|
|
1316
|
+
await ensureAzAuth(execa, { subscription: sub });
|
|
1317
|
+
|
|
1318
|
+
const vault = await resolveVault(execa, opts.vault, sub);
|
|
1319
|
+
|
|
1320
|
+
if (!opts.vnet || !opts.subnet) {
|
|
1321
|
+
console.error(chalk.red("\n --vnet and --subnet are required.\n"));
|
|
1322
|
+
process.exit(1);
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
const rg = opts.resourceGroup || opts.vnetResourceGroup;
|
|
1326
|
+
|
|
1327
|
+
// 1. Add service endpoint to subnet if not present
|
|
1328
|
+
hint(`Adding Microsoft.KeyVault service endpoint to ${opts.vnet}/${opts.subnet}...`);
|
|
1329
|
+
|
|
1330
|
+
const subnetArgs = [
|
|
1331
|
+
"network", "vnet", "subnet", "update",
|
|
1332
|
+
"--vnet-name", opts.vnet,
|
|
1333
|
+
"-n", opts.subnet,
|
|
1334
|
+
"--service-endpoints", "Microsoft.KeyVault",
|
|
1335
|
+
"--output", "none",
|
|
1336
|
+
...subArgs(sub),
|
|
1337
|
+
];
|
|
1338
|
+
if (rg) subnetArgs.push("-g", rg);
|
|
1339
|
+
|
|
1340
|
+
const { exitCode: seCode, stderr: seErr } = await execa("az", subnetArgs, { timeout: 120000, reject: false });
|
|
1341
|
+
if (seCode !== 0) {
|
|
1342
|
+
console.log(WARN(` ⚠ Service endpoint: ${(seErr || "").split("\n")[0]}`));
|
|
1343
|
+
} else {
|
|
1344
|
+
console.log(OK(" ✓ Service endpoint added"));
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
// 2. Get subnet resource ID
|
|
1348
|
+
const subnetShowArgs = [
|
|
1349
|
+
"network", "vnet", "subnet", "show",
|
|
1350
|
+
"--vnet-name", opts.vnet,
|
|
1351
|
+
"-n", opts.subnet,
|
|
1352
|
+
"--query", "id", "-o", "tsv",
|
|
1353
|
+
...subArgs(sub),
|
|
1354
|
+
];
|
|
1355
|
+
if (rg) subnetShowArgs.push("-g", rg);
|
|
1356
|
+
|
|
1357
|
+
const { stdout: subnetId, exitCode: sidCode, stderr: sidErr } = await execa("az", subnetShowArgs, { timeout: 30000, reject: false });
|
|
1358
|
+
if (sidCode !== 0 || !subnetId?.trim()) {
|
|
1359
|
+
console.error(chalk.red(`\n ✗ Could not find subnet: ${(sidErr || "").split("\n")[0]}\n`));
|
|
1360
|
+
process.exit(1);
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// 3. Add VNet rule to Key Vault
|
|
1364
|
+
hint(`Adding VNet rule to Key Vault "${vault}"...`);
|
|
1365
|
+
|
|
1366
|
+
const { exitCode, stderr } = await execa("az", [
|
|
1367
|
+
"keyvault", "network-rule", "add",
|
|
1368
|
+
"--name", vault,
|
|
1369
|
+
"--subnet", subnetId.trim(),
|
|
1370
|
+
"--output", "none",
|
|
1371
|
+
...subArgs(sub),
|
|
1372
|
+
], { timeout: 60000, reject: false });
|
|
1373
|
+
|
|
1374
|
+
if (exitCode !== 0) {
|
|
1375
|
+
console.error(chalk.red(`\n ✗ ${(stderr || "").split("\n")[0]}\n`));
|
|
1376
|
+
process.exit(1);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
console.log(OK(` ✓ VNet rule added: ${opts.vnet}/${opts.subnet}`));
|
|
1380
|
+
hint(`\nSet to private: fops azure keyvault network private --vault ${vault}\n`);
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
export async function networkPrivate(opts = {}) {
|
|
1384
|
+
const execa = await lazyExeca();
|
|
1385
|
+
const sub = opts.profile;
|
|
1386
|
+
await ensureAzCli(execa);
|
|
1387
|
+
await ensureAzAuth(execa, { subscription: sub });
|
|
1388
|
+
|
|
1389
|
+
const vault = await resolveVault(execa, opts.vault, sub);
|
|
1390
|
+
|
|
1391
|
+
hint(`Setting "${vault}" to private (deny public access)...`);
|
|
1392
|
+
|
|
1393
|
+
const { exitCode, stderr } = await execa("az", [
|
|
1394
|
+
"keyvault", "update",
|
|
1395
|
+
"--name", vault,
|
|
1396
|
+
"--default-action", "Deny",
|
|
1397
|
+
"--bypass", "AzureServices",
|
|
1398
|
+
"--output", "none",
|
|
1399
|
+
...subArgs(sub),
|
|
1400
|
+
], { timeout: 60000, reject: false });
|
|
1401
|
+
|
|
1402
|
+
if (exitCode !== 0) {
|
|
1403
|
+
console.error(chalk.red(`\n ✗ ${(stderr || "").split("\n")[0]}\n`));
|
|
1404
|
+
process.exit(1);
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
console.log(OK(` ✓ Key Vault "${vault}" is now private`));
|
|
1408
|
+
console.log(DIM(" Default action: Deny"));
|
|
1409
|
+
console.log(DIM(" Bypass: AzureServices"));
|
|
1410
|
+
hint("\nAccess limited to allowed VNets/IPs and Azure services.\n");
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
export async function networkPublic(opts = {}) {
|
|
1414
|
+
const execa = await lazyExeca();
|
|
1415
|
+
const sub = opts.profile;
|
|
1416
|
+
await ensureAzCli(execa);
|
|
1417
|
+
await ensureAzAuth(execa, { subscription: sub });
|
|
1418
|
+
|
|
1419
|
+
const vault = await resolveVault(execa, opts.vault, sub);
|
|
1420
|
+
|
|
1421
|
+
const { resolveCliSrc } = await import("./azure-helpers.js");
|
|
1422
|
+
const { confirm } = await import(resolveCliSrc("ui/confirm.js"));
|
|
1423
|
+
const yes = await confirm(` Make Key Vault "${vault}" publicly accessible?`);
|
|
1424
|
+
if (!yes) { console.log(DIM("\n Cancelled.\n")); return; }
|
|
1425
|
+
|
|
1426
|
+
const { exitCode, stderr } = await execa("az", [
|
|
1427
|
+
"keyvault", "update",
|
|
1428
|
+
"--name", vault,
|
|
1429
|
+
"--default-action", "Allow",
|
|
1430
|
+
"--output", "none",
|
|
1431
|
+
...subArgs(sub),
|
|
1432
|
+
], { timeout: 60000, reject: false });
|
|
1433
|
+
|
|
1434
|
+
if (exitCode !== 0) {
|
|
1435
|
+
console.error(chalk.red(`\n ✗ ${(stderr || "").split("\n")[0]}\n`));
|
|
1436
|
+
process.exit(1);
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
console.log(WARN(` ⚠ Key Vault "${vault}" is now public`));
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1256
1442
|
// ── Config helpers ──────────────────────────────────────────────────────────
|
|
1257
1443
|
|
|
1258
1444
|
function readFopsConfig() {
|
|
@@ -2136,6 +2136,35 @@ export async function azureLogs(service, opts = {}) {
|
|
|
2136
2136
|
], { stdio: "inherit", reject: false });
|
|
2137
2137
|
}
|
|
2138
2138
|
|
|
2139
|
+
// ── restart ─────────────────────────────────────────────────────────────────
|
|
2140
|
+
|
|
2141
|
+
export async function azureRestart(service, opts = {}) {
|
|
2142
|
+
const state = requireVmState(opts.vmName);
|
|
2143
|
+
const ip = state.publicIp;
|
|
2144
|
+
if (!ip) { console.error(chalk.red("\n No IP. Is the VM running?\n")); process.exit(1); }
|
|
2145
|
+
|
|
2146
|
+
await knockForVm(state);
|
|
2147
|
+
|
|
2148
|
+
const { execa } = await import("execa");
|
|
2149
|
+
const adminUser = DEFAULTS.adminUser;
|
|
2150
|
+
|
|
2151
|
+
const svcArg = service ? (service.startsWith("foundation-") ? service : `foundation-${service}`) : "";
|
|
2152
|
+
const label = svcArg || "all services";
|
|
2153
|
+
console.log(chalk.cyan(` Restarting ${label} on ${state.vmName || ip}...`));
|
|
2154
|
+
|
|
2155
|
+
const { exitCode } = await execa("ssh", [
|
|
2156
|
+
"-o", "StrictHostKeyChecking=no",
|
|
2157
|
+
`${adminUser}@${ip}`,
|
|
2158
|
+
`cd /opt/foundation-compose && sudo docker compose restart ${svcArg}`,
|
|
2159
|
+
], { stdio: "inherit", reject: false });
|
|
2160
|
+
|
|
2161
|
+
if (exitCode === 0) {
|
|
2162
|
+
console.log(chalk.green(` ✓ ${label} restarted`));
|
|
2163
|
+
} else {
|
|
2164
|
+
console.error(chalk.red(` ✗ restart failed (exit ${exitCode})`));
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2139
2168
|
// ── grant-admin ─────────────────────────────────────────────────────────────
|
|
2140
2169
|
|
|
2141
2170
|
export async function azureGrantAdmin(opts = {}) {
|
|
@@ -235,6 +235,79 @@ export async function resultsPush(target, qaResult, opts = {}) {
|
|
|
235
235
|
return { blob, account: store.account };
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
+
// ── Remove ──────────────────────────────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
export async function resultsRemove(target, opts = {}) {
|
|
241
|
+
const execa = await lazyExeca();
|
|
242
|
+
await ensureAzCli(execa);
|
|
243
|
+
await ensureAzAuth(execa, { subscription: opts.subscription });
|
|
244
|
+
|
|
245
|
+
// 1. Clear local state
|
|
246
|
+
const state = readState();
|
|
247
|
+
const vm = state.azure?.vms?.[target];
|
|
248
|
+
if (vm?.qa) {
|
|
249
|
+
delete vm.qa;
|
|
250
|
+
saveState(state);
|
|
251
|
+
console.log(OK(` ✓ Local QA state cleared for "${target}"`));
|
|
252
|
+
} else {
|
|
253
|
+
console.log(DIM(` No local QA state found for "${target}"`));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// 2. Delete blobs from storage
|
|
257
|
+
const store = getResultsConfig();
|
|
258
|
+
if (!store?.account) {
|
|
259
|
+
console.log(DIM(" No results storage configured — skipping blob deletion."));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const prefix = `${BLOB_PREFIX}/${target}/`;
|
|
264
|
+
let blobs;
|
|
265
|
+
try {
|
|
266
|
+
const { stdout } = await execa("az", [
|
|
267
|
+
"storage", "blob", "list",
|
|
268
|
+
"--account-name", store.account,
|
|
269
|
+
"--container-name", CONTAINER_NAME,
|
|
270
|
+
"--prefix", prefix,
|
|
271
|
+
"--query", "[].name",
|
|
272
|
+
"--output", "json",
|
|
273
|
+
...AUTH,
|
|
274
|
+
...subArgs(opts.subscription),
|
|
275
|
+
], { timeout: 30000 });
|
|
276
|
+
blobs = JSON.parse(stdout || "[]");
|
|
277
|
+
} catch (err) {
|
|
278
|
+
console.log(WARN(` ⚠ Could not list blobs: ${err.message}`));
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (blobs.length === 0) {
|
|
283
|
+
console.log(DIM(` No stored results found in blob storage for "${target}"`));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
console.log(chalk.cyan(`\n Deleting ${blobs.length} result(s) from ${store.account}/${CONTAINER_NAME}/${prefix}…\n`));
|
|
288
|
+
|
|
289
|
+
let deleted = 0;
|
|
290
|
+
for (const blobName of blobs) {
|
|
291
|
+
try {
|
|
292
|
+
await execa("az", [
|
|
293
|
+
"storage", "blob", "delete",
|
|
294
|
+
"--account-name", store.account,
|
|
295
|
+
"--container-name", CONTAINER_NAME,
|
|
296
|
+
"--name", blobName,
|
|
297
|
+
...AUTH,
|
|
298
|
+
"--output", "none",
|
|
299
|
+
...subArgs(opts.subscription),
|
|
300
|
+
], { timeout: 15000 });
|
|
301
|
+
console.log(` ${DIM(`✗ ${blobName}`)}`);
|
|
302
|
+
deleted++;
|
|
303
|
+
} catch (err) {
|
|
304
|
+
console.log(WARN(` ⚠ Failed to delete ${blobName}: ${err.message}`));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
console.log(OK(`\n ✓ Removed ${deleted}/${blobs.length} result(s) for "${target}"\n`));
|
|
309
|
+
}
|
|
310
|
+
|
|
238
311
|
// ── List ────────────────────────────────────────────────────────────────────
|
|
239
312
|
|
|
240
313
|
export async function resultsList(opts = {}) {
|
|
@@ -348,6 +421,11 @@ export async function resultsShow(opts = {}) {
|
|
|
348
421
|
process.exit(1);
|
|
349
422
|
}
|
|
350
423
|
|
|
424
|
+
if (opts.json) {
|
|
425
|
+
console.log(JSON.stringify(result, null, 2));
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
351
429
|
banner(`Test Result: ${target}`);
|
|
352
430
|
kvLine("Passed", result.passed ? OK("yes") : ERR("no"));
|
|
353
431
|
kvLine("Exit code", String(result.exitCode ?? "–"));
|
|
@@ -48,7 +48,7 @@ export {
|
|
|
48
48
|
export {
|
|
49
49
|
azureStatus, azureTrinoStatus, azureSsh, azureSshWhitelistMe, azurePortForward, azureSshAdminAdd, azureVmCheck, azureAgent, azureOpenAiDebugVm,
|
|
50
50
|
azureDeploy, azurePull, azureDeployVersion, azureRunUp, azureConfig, azureConfigVersions, azureUpdate,
|
|
51
|
-
azureLogs, azureGrantAdmin, azureContext,
|
|
51
|
+
azureLogs, azureRestart, azureGrantAdmin, azureContext,
|
|
52
52
|
azureList, azureApply,
|
|
53
53
|
azureKnock, azureKnockClose, azureKnockDisable, azureKnockVerify, azureKnockFix,
|
|
54
54
|
} from "./azure-ops.js";
|