@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.
Files changed (36) hide show
  1. package/CHANGELOG.md +183 -0
  2. package/package.json +1 -1
  3. package/src/commands/lifecycle.js +101 -5
  4. package/src/commands/setup.js +45 -4
  5. package/src/plugins/bundled/fops-plugin-azure/index.js +29 -0
  6. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-core.js +1185 -0
  7. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-flux.js +1180 -0
  8. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-ingress.js +393 -0
  9. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-naming.js +104 -0
  10. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-network.js +296 -0
  11. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-postgres.js +768 -0
  12. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-reconcilers.js +538 -0
  13. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-secrets.js +849 -0
  14. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-stacks.js +643 -0
  15. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-state.js +145 -0
  16. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-storage.js +496 -0
  17. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-terraform.js +1032 -0
  18. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks.js +155 -4245
  19. package/src/plugins/bundled/fops-plugin-azure/lib/azure-keyvault.js +186 -0
  20. package/src/plugins/bundled/fops-plugin-azure/lib/azure-ops.js +29 -0
  21. package/src/plugins/bundled/fops-plugin-azure/lib/azure-results.js +78 -0
  22. package/src/plugins/bundled/fops-plugin-azure/lib/azure.js +1 -1
  23. package/src/plugins/bundled/fops-plugin-azure/lib/commands/infra-cmds.js +758 -0
  24. package/src/plugins/bundled/fops-plugin-azure/lib/commands/registry-cmds.js +250 -0
  25. package/src/plugins/bundled/fops-plugin-azure/lib/commands/test-cmds.js +52 -1
  26. package/src/plugins/bundled/fops-plugin-azure/lib/commands/vm-cmds.js +10 -0
  27. package/src/plugins/bundled/fops-plugin-foundation/lib/apply.js +3 -2
  28. package/src/plugins/bundled/fops-plugin-foundation/lib/helpers.js +21 -0
  29. package/src/plugins/bundled/fops-plugin-foundation/lib/tools-read.js +3 -5
  30. package/src/ui/tui/App.js +13 -13
  31. package/src/web/dist/assets/index-NXC8Hvnp.css +1 -0
  32. package/src/web/dist/assets/index-QH1N4ejK.js +112 -0
  33. package/src/web/dist/index.html +2 -2
  34. package/src/web/server.js +4 -4
  35. package/src/web/dist/assets/index-BphVaAUd.css +0 -1
  36. 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";