@meshxdata/fops 0.1.49 → 0.1.50

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 (30) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/package.json +1 -1
  3. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-core.js +347 -6
  4. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-data-bootstrap.js +421 -0
  5. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-flux.js +5 -179
  6. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-naming.js +14 -4
  7. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-postgres.js +171 -4
  8. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-storage.js +303 -8
  9. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks.js +2 -0
  10. package/src/plugins/bundled/fops-plugin-azure/lib/azure-auth.js +1 -1
  11. package/src/plugins/bundled/fops-plugin-azure/lib/azure-fleet-swarm.js +936 -0
  12. package/src/plugins/bundled/fops-plugin-azure/lib/azure-fleet.js +10 -918
  13. package/src/plugins/bundled/fops-plugin-azure/lib/azure-helpers.js +5 -0
  14. package/src/plugins/bundled/fops-plugin-azure/lib/azure-keyvault-keys.js +413 -0
  15. package/src/plugins/bundled/fops-plugin-azure/lib/azure-keyvault.js +14 -399
  16. package/src/plugins/bundled/fops-plugin-azure/lib/azure-ops-config.js +754 -0
  17. package/src/plugins/bundled/fops-plugin-azure/lib/azure-ops-knock.js +527 -0
  18. package/src/plugins/bundled/fops-plugin-azure/lib/azure-ops-ssh.js +427 -0
  19. package/src/plugins/bundled/fops-plugin-azure/lib/azure-ops.js +99 -1686
  20. package/src/plugins/bundled/fops-plugin-azure/lib/azure-provision-health.js +279 -0
  21. package/src/plugins/bundled/fops-plugin-azure/lib/azure-provision-init.js +186 -0
  22. package/src/plugins/bundled/fops-plugin-azure/lib/azure-provision.js +66 -444
  23. package/src/plugins/bundled/fops-plugin-azure/lib/azure-results.js +11 -0
  24. package/src/plugins/bundled/fops-plugin-azure/lib/azure-vm-lifecycle.js +5 -540
  25. package/src/plugins/bundled/fops-plugin-azure/lib/azure-vm-terraform.js +544 -0
  26. package/src/plugins/bundled/fops-plugin-azure/lib/commands/infra-cmds.js +75 -3
  27. package/src/plugins/bundled/fops-plugin-azure/lib/commands/test-cmds.js +227 -11
  28. package/src/plugins/bundled/fops-plugin-azure/lib/commands/vm-cmds.js +2 -1
  29. package/src/plugins/bundled/fops-plugin-azure/lib/pytest-parse.js +21 -0
  30. package/src/plugins/bundled/fops-plugin-foundation/index.js +309 -44
@@ -2,6 +2,17 @@ import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import chalk from "chalk";
5
+ import {
6
+ resolveVault, shortDate,
7
+ keyList, keyShow, keyCreate, keyRotate,
8
+ keyRotationPolicyShow, keyRotationPolicySet,
9
+ } from "./azure-keyvault-keys.js";
10
+
11
+ // Re-export key functions for backwards compatibility
12
+ export {
13
+ keyList, keyShow, keyCreate, keyRotate,
14
+ keyRotationPolicyShow, keyRotationPolicySet,
15
+ } from "./azure-keyvault-keys.js";
5
16
 
6
17
  const DIM = chalk.dim;
7
18
  const OK = chalk.green;
@@ -51,44 +62,7 @@ async function ensureAzAuth(execa, { subscription } = {}) {
51
62
  }
52
63
  }
53
64
 
54
- function shortDate(val) {
55
- if (!val) return "—";
56
- const d = typeof val === "number" ? new Date(val * 1000) : new Date(val);
57
- if (isNaN(d.getTime())) return String(val);
58
- return d.toLocaleString();
59
- }
60
-
61
- // ── Vault resolver ──────────────────────────────────────────────────────────
62
-
63
- async function resolveVault(execa, explicit, sub) {
64
- if (explicit) return explicit;
65
-
66
- hint("No --vault given, listing vaults…");
67
- const { stdout } = await execa("az", [
68
- "keyvault", "list", "--query", "[].{name:name, location:location}",
69
- "--output", "json", ...subArgs(sub),
70
- ], { timeout: 30000 });
71
- const vaults = JSON.parse(stdout);
72
-
73
- if (vaults.length === 0) {
74
- console.error(chalk.red("\n No Key Vaults found in this subscription.\n"));
75
- hint("Create one: fops azure keyvault create <name> --resource-group <rg> --location <region>\n");
76
- process.exit(1);
77
- }
78
- if (vaults.length === 1) {
79
- hint(`Using vault: ${vaults[0].name}`);
80
- return vaults[0].name;
81
- }
82
-
83
- const { resolveCliSrc } = await import("./azure-helpers.js");
84
- const { selectOption } = await import(resolveCliSrc("ui/confirm.js"));
85
- const selected = await selectOption(
86
- "Select Key Vault",
87
- vaults.map((v) => ({ label: `${v.name} ${DIM(v.location)}`, value: v.name })),
88
- );
89
- if (!selected) { console.log(DIM("\n Cancelled.\n")); process.exit(0); }
90
- return selected;
91
- }
65
+ // shortDate and resolveVault imported from azure-keyvault-keys.js
92
66
 
93
67
  async function resolveResourceGroup(execa, explicit, sub) {
94
68
  if (explicit) return explicit;
@@ -494,367 +468,8 @@ export async function secretDelete(opts = {}) {
494
468
  hint(`Recover: az keyvault secret recover --vault-name ${vault} --name ${name}\n`);
495
469
  }
496
470
 
497
- // ═══════════════════════════════════════════════════════════════════════════
498
- // Key operations (with auto-rotation)
499
- // ═══════════════════════════════════════════════════════════════════════════
500
-
501
- export async function keyList(opts = {}) {
502
- const execa = await lazyExeca();
503
- const sub = opts.profile;
504
- await ensureAzCli(execa);
505
- await ensureAzAuth(execa, { subscription: sub });
506
-
507
- const vault = await resolveVault(execa, opts.vault, sub);
508
-
509
- banner(`Keys in "${vault}"`);
510
-
511
- const { stdout, exitCode, stderr } = await execa("az", [
512
- "keyvault", "key", "list", "--vault-name", vault,
513
- "--output", "json", ...subArgs(sub),
514
- ], { timeout: 30000, reject: false });
515
-
516
- if (exitCode !== 0) {
517
- const msg = (stderr || "").includes("Forbidden")
518
- ? `Access denied — you need "Key Vault Crypto Officer" or "Key Vault Reader" role on "${vault}"`
519
- : (stderr || "").split("\n")[0];
520
- console.error(chalk.red(`\n ✗ ${msg}\n`));
521
- hint(`Grant access: az role assignment create --assignee <your-object-id> --role "Key Vault Crypto Officer" --scope $(az keyvault show --name ${vault} --query id -o tsv)`);
522
- console.log("");
523
- process.exit(1);
524
- }
525
-
526
- const keys = JSON.parse(stdout);
527
- if (!keys.length) {
528
- hint("No keys found.\n");
529
- return;
530
- }
531
-
532
- const nameWidth = Math.min(50, Math.max(20, ...keys.map((k) => {
533
- const name = k.kid?.split("/").pop() || k.name || "—";
534
- return name.length;
535
- })));
536
-
537
- for (const k of keys) {
538
- const name = k.kid?.split("/").pop() || k.name || "—";
539
- const enabled = k.attributes?.enabled !== false;
540
- const updated = shortDate(k.attributes?.updated || k.attributes?.created);
541
- const managed = k.managed ? DIM(" [managed]") : "";
542
- const status = enabled ? "" : WARN(" [disabled]");
543
-
544
- console.log(` ${chalk.bold.white(name.padEnd(nameWidth))} ${DIM(updated)}${status}${managed}`);
545
- }
546
- console.log(DIM(`\n ${keys.length} key(s)`));
547
- hint(`Details: fops azure keyvault key show <name> --vault ${vault}\n`);
548
- }
549
-
550
- export async function keyShow(opts = {}) {
551
- const execa = await lazyExeca();
552
- const sub = opts.profile;
553
- await ensureAzCli(execa);
554
- await ensureAzAuth(execa, { subscription: sub });
555
-
556
- const vault = await resolveVault(execa, opts.vault, sub);
557
- const { name } = opts;
558
- if (!name) {
559
- console.error(chalk.red("\n Key name is required.\n"));
560
- process.exit(1);
561
- }
562
-
563
- const [keyResult, policyResult] = await Promise.allSettled([
564
- execa("az", [
565
- "keyvault", "key", "show", "--vault-name", vault,
566
- "--name", name, "--output", "json", ...subArgs(sub),
567
- ], { timeout: 30000, reject: false }),
568
- execa("az", [
569
- "keyvault", "key", "rotation-policy", "show", "--vault-name", vault,
570
- "--name", name, "--output", "json", ...subArgs(sub),
571
- ], { timeout: 15000, reject: false }),
572
- ]);
573
-
574
- if (keyResult.status === "rejected" || keyResult.value.exitCode !== 0) {
575
- const stderr = keyResult.value?.stderr || keyResult.reason?.message || "";
576
- const msg = stderr.includes("KeyNotFound")
577
- ? `Key "${name}" not found in vault "${vault}"`
578
- : stderr.split("\n")[0];
579
- console.error(chalk.red(`\n ✗ ${msg}\n`));
580
- process.exit(1);
581
- }
582
-
583
- const k = JSON.parse(keyResult.value.stdout);
584
- const keyProps = k.key || {};
585
-
586
- banner(`Key — "${name}"`);
587
- kvLine("Vault", DIM(vault));
588
- kvLine("Name", chalk.bold.white(name));
589
- kvLine("Type", DIM(keyProps.kty || "—"));
590
- if (keyProps.kty?.startsWith("RSA")) kvLine("Size", DIM(String(keyProps.n ? Math.ceil(keyProps.n.length * 6 / 8) * 8 : "—")));
591
- if (keyProps.kty?.startsWith("EC")) kvLine("Curve", DIM(keyProps.crv || "—"));
592
- kvLine("Operations", DIM((keyProps.keyOps || []).join(", ") || "—"));
593
- kvLine("Enabled", k.attributes?.enabled !== false ? OK("true") : WARN("false"));
594
- kvLine("Created", DIM(shortDate(k.attributes?.created)));
595
- kvLine("Updated", DIM(shortDate(k.attributes?.updated)));
596
- if (k.attributes?.expires) kvLine("Expires", DIM(shortDate(k.attributes.expires)));
597
- kvLine("Version", DIM((k.key?.kid || k.kid || "").split("/").pop() || "—"));
598
-
599
- // Rotation policy
600
- console.log("");
601
- if (policyResult.status === "fulfilled" && policyResult.value.exitCode === 0) {
602
- const policy = JSON.parse(policyResult.value.stdout);
603
- const actions = policy.lifetimeActions || [];
604
- const expiry = policy.expiresIn || policy.attributes?.expiryTime;
605
-
606
- console.log(ACCENT(" Rotation Policy"));
607
- kvLine(" Expiry", DIM(expiry ? humanDuration(expiry) : "none"));
608
-
609
- if (actions.length === 0) {
610
- kvLine(" Actions", DIM("none"));
611
- }
612
- for (const a of actions) {
613
- const actionType = a.action?.type || a.action || "?";
614
- const trigger = (a.timeAfterCreate || a.trigger?.timeAfterCreate)
615
- ? `${humanDuration(a.timeAfterCreate || a.trigger?.timeAfterCreate)} after create`
616
- : (a.timeBeforeExpiry || a.trigger?.timeBeforeExpiry)
617
- ? `${humanDuration(a.timeBeforeExpiry || a.trigger?.timeBeforeExpiry)} before expiry`
618
- : "—";
619
- kvLine(` ${actionType}`, DIM(trigger));
620
- }
621
- } else {
622
- console.log(DIM(" Rotation Policy none"));
623
- hint(`Enable: fops azure keyvault key rotation-policy set ${name} --vault ${vault} --rotate-every P90D`);
624
- }
625
- console.log("");
626
- }
627
-
628
- export async function keyCreate(opts = {}) {
629
- const execa = await lazyExeca();
630
- const sub = opts.profile;
631
- await ensureAzCli(execa);
632
- await ensureAzAuth(execa, { subscription: sub });
633
-
634
- const vault = await resolveVault(execa, opts.vault, sub);
635
- const { name } = opts;
636
- if (!name) {
637
- console.error(chalk.red("\n Key name is required.\n"));
638
- process.exit(1);
639
- }
640
-
641
- const kty = opts.type || "RSA";
642
- const size = opts.size || (kty.startsWith("RSA") ? "2048" : undefined);
643
- const curve = opts.curve || (kty.startsWith("EC") ? "P-256" : undefined);
644
- const ops = opts.ops || "encrypt decrypt sign verify wrapKey unwrapKey";
645
-
646
- banner("Creating Key");
647
- kvLine("Name", chalk.bold.white(name));
648
- kvLine("Vault", DIM(vault));
649
- kvLine("Type", DIM(kty));
650
- if (size) kvLine("Size", DIM(size));
651
- if (curve) kvLine("Curve", DIM(curve));
652
- kvLine("Operations", DIM(ops));
653
- if (opts.rotateEvery) kvLine("Auto-rotate", DIM(humanDuration(opts.rotateEvery)));
654
- if (opts.expiresIn) kvLine("Expiry", DIM(humanDuration(opts.expiresIn)));
655
- console.log("");
656
-
657
- // 1. Create the key
658
- const args = [
659
- "keyvault", "key", "create", "--vault-name", vault,
660
- "--name", name, "--kty", kty, "--output", "json", ...subArgs(sub),
661
- ];
662
- if (size && kty.startsWith("RSA")) args.push("--size", size);
663
- if (curve && kty.startsWith("EC")) args.push("--curve", curve);
664
- if (opts.ops) args.push("--ops", ...ops.split(/[\s,]+/));
665
- if (opts.disabled) args.push("--disabled", "true");
666
- if (opts.expiresIn) args.push("--expires", computeExpiryDate(opts.expiresIn));
667
-
668
- const { stdout, exitCode, stderr } = await execa("az", args, { timeout: 30000, reject: false });
669
- if (exitCode !== 0) {
670
- console.error(chalk.red(`\n ✗ ${(stderr || "").split("\n")[0]}\n`));
671
- process.exit(1);
672
- }
673
-
674
- const k = JSON.parse(stdout);
675
- console.log(OK(` ✓ Key "${name}" created`));
676
- kvLine("Version", DIM(k.key?.kid?.split("/").pop() || "—"));
677
-
678
- // 2. Set rotation policy if requested
679
- if (opts.rotateEvery) {
680
- await setRotationPolicyInner(execa, vault, name, sub, {
681
- rotateEvery: opts.rotateEvery,
682
- expiresIn: opts.expiresIn,
683
- notifyBefore: opts.notifyBefore,
684
- });
685
- }
686
- console.log("");
687
- hint(`Show: fops azure keyvault key show ${name} --vault ${vault}`);
688
- hint(`Rotate: fops azure keyvault key rotate ${name} --vault ${vault}\n`);
689
- }
690
-
691
- export async function keyRotate(opts = {}) {
692
- const execa = await lazyExeca();
693
- const sub = opts.profile;
694
- await ensureAzCli(execa);
695
- await ensureAzAuth(execa, { subscription: sub });
696
-
697
- const vault = await resolveVault(execa, opts.vault, sub);
698
- const { name } = opts;
699
- if (!name) {
700
- console.error(chalk.red("\n Key name is required.\n"));
701
- process.exit(1);
702
- }
703
-
704
- hint(`Rotating key "${name}"…`);
705
- const { stdout, exitCode, stderr } = await execa("az", [
706
- "keyvault", "key", "rotate", "--vault-name", vault,
707
- "--name", name, "--output", "json", ...subArgs(sub),
708
- ], { timeout: 30000, reject: false });
709
-
710
- if (exitCode !== 0) {
711
- console.error(chalk.red(`\n ✗ ${(stderr || "").split("\n")[0]}\n`));
712
- process.exit(1);
713
- }
714
-
715
- const k = JSON.parse(stdout);
716
- console.log(OK(` ✓ Key "${name}" rotated — new version: ${k.key?.kid?.split("/").pop() || "?"}\n`));
717
- }
718
-
719
- export async function keyRotationPolicyShow(opts = {}) {
720
- const execa = await lazyExeca();
721
- const sub = opts.profile;
722
- await ensureAzCli(execa);
723
- await ensureAzAuth(execa, { subscription: sub });
724
-
725
- const vault = await resolveVault(execa, opts.vault, sub);
726
- const { name } = opts;
727
- if (!name) {
728
- console.error(chalk.red("\n Key name is required.\n"));
729
- process.exit(1);
730
- }
731
-
732
- const { stdout, exitCode, stderr } = await execa("az", [
733
- "keyvault", "key", "rotation-policy", "show", "--vault-name", vault,
734
- "--name", name, "--output", "json", ...subArgs(sub),
735
- ], { timeout: 15000, reject: false });
736
-
737
- if (exitCode !== 0) {
738
- console.error(chalk.red(`\n ✗ ${(stderr || "").split("\n")[0]}\n`));
739
- process.exit(1);
740
- }
741
-
742
- const policy = JSON.parse(stdout);
743
- const actions = policy.lifetimeActions || [];
744
- const expiry = policy.expiresIn || policy.attributes?.expiryTime;
745
-
746
- banner(`Rotation Policy — "${name}"`);
747
- kvLine("Vault", DIM(vault));
748
- kvLine("Key", chalk.bold.white(name));
749
- kvLine("Expiry", DIM(expiry ? humanDuration(expiry) : "none"));
750
- console.log("");
751
-
752
- if (actions.length === 0) {
753
- hint("No rotation actions configured.");
754
- } else {
755
- for (const a of actions) {
756
- const actionType = a.action?.type || a.action || "?";
757
- const trigger = (a.timeAfterCreate || a.trigger?.timeAfterCreate)
758
- ? `${humanDuration(a.timeAfterCreate || a.trigger?.timeAfterCreate)} after create`
759
- : (a.timeBeforeExpiry || a.trigger?.timeBeforeExpiry)
760
- ? `${humanDuration(a.timeBeforeExpiry || a.trigger?.timeBeforeExpiry)} before expiry`
761
- : "—";
762
- kvLine(` ${actionType}`, DIM(trigger));
763
- }
764
- }
765
- console.log("");
766
- hint(`Update: fops azure keyvault key rotation-policy set ${name} --vault ${vault} --rotate-every P90D`);
767
- hint(`Rotate now: fops azure keyvault key rotate ${name} --vault ${vault}\n`);
768
- }
769
-
770
- export async function keyRotationPolicySet(opts = {}) {
771
- const execa = await lazyExeca();
772
- const sub = opts.profile;
773
- await ensureAzCli(execa);
774
- await ensureAzAuth(execa, { subscription: sub });
775
-
776
- const vault = await resolveVault(execa, opts.vault, sub);
777
- const { name } = opts;
778
- if (!name) {
779
- console.error(chalk.red("\n Key name is required.\n"));
780
- process.exit(1);
781
- }
782
-
783
- if (!opts.rotateEvery) {
784
- console.error(chalk.red("\n --rotate-every <duration> is required (e.g. P90D, P30D, P1Y).\n"));
785
- hint("ISO 8601 durations: P30D = 30 days, P90D = 90 days, P6M = 6 months, P1Y = 1 year\n");
786
- process.exit(1);
787
- }
788
-
789
- banner(`Setting Rotation Policy — "${name}"`);
790
- kvLine("Vault", DIM(vault));
791
- kvLine("Key", chalk.bold.white(name));
792
- kvLine("Rotate every", DIM(humanDuration(opts.rotateEvery)));
793
- if (opts.expiresIn) kvLine("Key expiry", DIM(humanDuration(opts.expiresIn)));
794
- if (opts.notifyBefore) kvLine("Notify before", DIM(humanDuration(opts.notifyBefore)));
795
- console.log("");
796
-
797
- await setRotationPolicyInner(execa, vault, name, sub, opts);
798
- console.log("");
799
- }
800
-
801
- async function setRotationPolicyInner(execa, vault, name, sub, opts) {
802
- const lifetimeActions = [
803
- {
804
- trigger: { timeAfterCreate: opts.rotateEvery },
805
- action: { type: "Rotate" },
806
- },
807
- ];
808
-
809
- if (opts.notifyBefore || opts.expiresIn) {
810
- lifetimeActions.push({
811
- trigger: { timeBeforeExpiry: opts.notifyBefore || "P30D" },
812
- action: { type: "Notify" },
813
- });
814
- }
815
-
816
- const policy = { lifetimeActions };
817
- if (opts.expiresIn) {
818
- policy.attributes = { expiryTime: opts.expiresIn };
819
- }
820
-
821
- const policyJson = JSON.stringify(policy);
822
-
823
- const { exitCode, stderr } = await execa("az", [
824
- "keyvault", "key", "rotation-policy", "update", "--vault-name", vault,
825
- "--name", name, "--value", policyJson, "--output", "none", ...subArgs(sub),
826
- ], { timeout: 30000, reject: false });
827
-
828
- if (exitCode !== 0) {
829
- console.log(WARN(` ⚠ Rotation policy failed: ${(stderr || "").split("\n")[0]}`));
830
- hint("Ensure the vault uses premium SKU for HSM keys, and key type supports rotation.");
831
- return;
832
- }
833
-
834
- console.log(OK(` ✓ Rotation policy set — auto-rotate every ${humanDuration(opts.rotateEvery)}`));
835
- if (opts.expiresIn) console.log(OK(` ✓ Key expiry: ${humanDuration(opts.expiresIn)}`));
836
- }
837
-
838
- function humanDuration(iso) {
839
- if (!iso) return "—";
840
- const m = iso.match(/^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?$/i);
841
- if (!m) return iso;
842
- const parts = [];
843
- if (m[1]) parts.push(`${m[1]} year${m[1] === "1" ? "" : "s"}`);
844
- if (m[2]) parts.push(`${m[2]} month${m[2] === "1" ? "" : "s"}`);
845
- if (m[3]) parts.push(`${m[3]} day${m[3] === "1" ? "" : "s"}`);
846
- return parts.join(", ") || iso;
847
- }
848
-
849
- function computeExpiryDate(isoDuration) {
850
- const m = isoDuration.match(/^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?$/i);
851
- if (!m) return new Date(Date.now() + 365 * 86400000).toISOString().replace(/\.\d{3}Z$/, "Z");
852
- const d = new Date();
853
- if (m[1]) d.setFullYear(d.getFullYear() + Number(m[1]));
854
- if (m[2]) d.setMonth(d.getMonth() + Number(m[2]));
855
- if (m[3]) d.setDate(d.getDate() + Number(m[3]));
856
- return d.toISOString().replace(/\.\d{3}Z$/, "Z");
857
- }
471
+ // Key operations (keyList, keyShow, keyCreate, keyRotate, keyRotationPolicyShow, keyRotationPolicySet)
472
+ // are now in azure-keyvault-keys.js and re-exported above for backwards compatibility
858
473
 
859
474
  // ═══════════════════════════════════════════════════════════════════════════
860
475
  // .env.keyvault template sync (mirrors 1Password plugin pattern)