puls-dev 0.2.8 → 0.3.0

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 (66) hide show
  1. package/dist/core/checker.js +71 -0
  2. package/dist/core/config.d.ts +5 -0
  3. package/dist/core/config.js +12 -1
  4. package/dist/core/context.d.ts +14 -0
  5. package/dist/core/context.js +2 -0
  6. package/dist/core/decorators.d.ts +2 -0
  7. package/dist/core/decorators.js +8 -14
  8. package/dist/core/group.test.d.ts +1 -0
  9. package/dist/core/group.test.js +94 -0
  10. package/dist/core/parallel.test.d.ts +1 -0
  11. package/dist/core/parallel.test.js +215 -0
  12. package/dist/core/production.test.d.ts +1 -0
  13. package/dist/core/production.test.js +189 -0
  14. package/dist/core/provisioner.js +29 -11
  15. package/dist/core/resource.d.ts +8 -0
  16. package/dist/core/resource.js +45 -0
  17. package/dist/core/retry.d.ts +9 -0
  18. package/dist/core/retry.js +28 -0
  19. package/dist/core/retry.test.d.ts +1 -0
  20. package/dist/core/retry.test.js +66 -0
  21. package/dist/core/secret.d.ts +2 -1
  22. package/dist/core/secret.js +12 -2
  23. package/dist/core/stack.js +381 -75
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +1 -0
  26. package/dist/providers/aws/api.js +97 -17
  27. package/dist/providers/aws/ec2.d.ts +3 -0
  28. package/dist/providers/aws/ec2.js +37 -3
  29. package/dist/providers/aws/ec2.test.js +5 -3
  30. package/dist/providers/aws/index.d.ts +2 -0
  31. package/dist/providers/aws/index.js +2 -0
  32. package/dist/providers/aws/secrets.js +20 -3
  33. package/dist/providers/aws/template.d.ts +34 -0
  34. package/dist/providers/aws/template.js +252 -0
  35. package/dist/providers/aws/template.test.d.ts +1 -0
  36. package/dist/providers/aws/template.test.js +208 -0
  37. package/dist/providers/do/api.d.ts +2 -0
  38. package/dist/providers/do/api.js +124 -26
  39. package/dist/providers/do/droplet.js +14 -0
  40. package/dist/providers/firebase/api.js +92 -29
  41. package/dist/providers/firebase/list.d.ts +2 -0
  42. package/dist/providers/firebase/list.js +25 -0
  43. package/dist/providers/gcp/api.js +88 -14
  44. package/dist/providers/gcp/index.d.ts +3 -1
  45. package/dist/providers/gcp/index.js +3 -1
  46. package/dist/providers/gcp/list.d.ts +2 -0
  47. package/dist/providers/gcp/list.js +55 -0
  48. package/dist/providers/gcp/secrets.js +21 -4
  49. package/dist/providers/gcp/template.d.ts +32 -0
  50. package/dist/providers/gcp/template.js +252 -0
  51. package/dist/providers/gcp/template.test.d.ts +1 -0
  52. package/dist/providers/gcp/template.test.js +227 -0
  53. package/dist/providers/gcp/vm.d.ts +3 -0
  54. package/dist/providers/gcp/vm.js +46 -3
  55. package/dist/providers/proxmox/api.d.ts +1 -0
  56. package/dist/providers/proxmox/api.js +72 -16
  57. package/dist/providers/proxmox/index.d.ts +3 -1
  58. package/dist/providers/proxmox/index.js +14 -1
  59. package/dist/providers/proxmox/template.d.ts +44 -0
  60. package/dist/providers/proxmox/template.js +350 -0
  61. package/dist/providers/proxmox/template.test.d.ts +1 -0
  62. package/dist/providers/proxmox/template.test.js +215 -0
  63. package/dist/providers/proxmox/vm.d.ts +3 -0
  64. package/dist/providers/proxmox/vm.js +43 -11
  65. package/dist/types/inventory.d.ts +44 -1
  66. package/package.json +2 -2
@@ -7,6 +7,7 @@ import { Output } from "../../core/output.js";
7
7
  import { getPMClient } from "./api.js";
8
8
  import { getFileHash, parseProvisionMetadata, mergeProvisionMetadata } from "./hash.js";
9
9
  import { checkPort, runProvisioner } from "../../core/provisioner.js";
10
+ import { resourceContextStorage } from "../../core/context.js";
10
11
  export class VMBuilder extends BaseBuilder {
11
12
  out = {
12
13
  ip: new Output(),
@@ -16,6 +17,7 @@ export class VMBuilder extends BaseBuilder {
16
17
  resolvedNode = null;
17
18
  resolvedIp = null;
18
19
  _image;
20
+ _templateSource;
19
21
  _cores = 2;
20
22
  _memory = 2048;
21
23
  _provision = [];
@@ -41,7 +43,8 @@ export class VMBuilder extends BaseBuilder {
41
43
  const config = await pm.get(`/nodes/${match.node}/qemu/${match.vmid}/config`);
42
44
  match.description = config.description ?? "";
43
45
  }
44
- catch {
46
+ catch (err) {
47
+ console.warn(` ⚠️ Could not fetch VM config for ${match.vmid}: ${err.message}`);
45
48
  match.description = "";
46
49
  }
47
50
  }
@@ -57,6 +60,11 @@ export class VMBuilder extends BaseBuilder {
57
60
  this._image = os;
58
61
  return this;
59
62
  }
63
+ fromTemplate(template) {
64
+ this._templateSource = template;
65
+ this.dependsOn(template);
66
+ return this;
67
+ }
60
68
  cores(n) {
61
69
  this._cores = n;
62
70
  return this;
@@ -178,6 +186,8 @@ export class VMBuilder extends BaseBuilder {
178
186
  console.log(` 📝 [PLAN] Create VM "${this.name}"`);
179
187
  if (this._image)
180
188
  console.log(` └─ Image: ${this._image}`);
189
+ if (this._templateSource)
190
+ console.log(` └─ Template: ${this._templateSource.name}`);
181
191
  console.log(` └─ Cores: ${this._cores} Memory: ${this._memory} MB Machine: ${this._machine}`);
182
192
  if (this._vlan)
183
193
  console.log(` └─ VLAN: ${this._vlan}`);
@@ -191,19 +201,27 @@ export class VMBuilder extends BaseBuilder {
191
201
  return { name: this.name, vmid: "PENDING" };
192
202
  }
193
203
  // Find the template - match by VMID (numeric string) or name substring
204
+ let sourceVmid;
205
+ if (this._templateSource) {
206
+ const v = await this._templateSource.out.vmid.get();
207
+ sourceVmid = String(v);
208
+ }
209
+ else if (this._image) {
210
+ sourceVmid = String(this._image);
211
+ }
194
212
  const resources = await pm.get("/cluster/resources?type=vm");
195
- const isVmid = this._image && /^\d+$/.test(this._image);
196
- const template = this._image
213
+ const isVmid = sourceVmid && /^\d+$/.test(sourceVmid);
214
+ const template = sourceVmid
197
215
  ? (resources ?? []).find((r) => r.template === 1 &&
198
216
  (isVmid
199
- ? String(r.vmid) === this._image
200
- : r.name?.includes(this._image)))
217
+ ? String(r.vmid) === sourceVmid
218
+ : r.name?.includes(sourceVmid)))
201
219
  : null;
202
- if (this._image && !template) {
203
- throw new Error(`No Proxmox template found matching "${this._image}". ` +
220
+ if (sourceVmid && !template) {
221
+ throw new Error(`No Proxmox template found matching "${sourceVmid}". ` +
204
222
  (isVmid
205
- ? `Check that VMID ${this._image} exists and is marked as a template.`
206
- : `Create a template whose name contains "${this._image}".`));
223
+ ? `Check that VMID ${sourceVmid} exists and is marked as a template.`
224
+ : `Create a template whose name contains "${sourceVmid}".`));
207
225
  }
208
226
  // Resolve target node: explicit → cluster-aware (online & max free RAM) → configured nodes list → template's node → API discovery
209
227
  let node = this._node;
@@ -248,15 +266,16 @@ export class VMBuilder extends BaseBuilder {
248
266
  const storage = this._storage ?? "rbd_pool";
249
267
  if (template) {
250
268
  console.log(` 📋 Cloning template "${template.name}" (vmid=${template.vmid}) → "${this.name}" (vmid=${newVmid})`);
251
- const taskId = await pm.post(`/nodes/${node}/qemu/${template.vmid}/clone`, {
269
+ const taskId = await pm.post(`/nodes/${template.node || node}/qemu/${template.vmid}/clone`, {
252
270
  newid: newVmid,
253
271
  name: this.name,
254
272
  full: 1,
255
273
  storage,
256
274
  format: "raw",
275
+ target: node,
257
276
  });
258
277
  // Clone is async - wait for the Proxmox task to finish before configuring
259
- await this.waitForTask(node, taskId, pm);
278
+ await this.waitForTask(template.node || node, taskId, pm);
260
279
  }
261
280
  else {
262
281
  console.log(` 🆕 Creating blank VM "${this.name}" (vmid=${newVmid})`);
@@ -358,6 +377,19 @@ export class VMBuilder extends BaseBuilder {
358
377
  if (this._replace) {
359
378
  await this.destroyVmByName(this._replace, pm);
360
379
  }
380
+ const context = resourceContextStorage.getStore();
381
+ if (context && context.hosts) {
382
+ const activeIp = this.resolvedIp ?? "0.0.0.0";
383
+ if (!context.hosts.some(h => h.name === this.name)) {
384
+ context.hosts.push({
385
+ name: this.name,
386
+ ip: activeIp,
387
+ user: "root",
388
+ sshKey: this.sshKeyPath(),
389
+ provider: "proxmox"
390
+ });
391
+ }
392
+ }
361
393
  return { name: this.name, vmid: this.resolvedVmid, ip: this.resolvedIp };
362
394
  }
363
395
  async resolveExistingIp(node, vmid, pm) {
@@ -75,13 +75,56 @@ export interface AwsInventory {
75
75
  rdsInstances: AwsRdsInstance[];
76
76
  hostedZones: AwsHostedZone[];
77
77
  }
78
+ export interface GcpVM {
79
+ name: string;
80
+ zone: string;
81
+ machineType: string;
82
+ status: string;
83
+ ip: string;
84
+ }
85
+ export interface GcpCloudSQL {
86
+ name: string;
87
+ engine: string;
88
+ tier: string;
89
+ status: string;
90
+ }
91
+ export interface GcpCloudRun {
92
+ name: string;
93
+ region: string;
94
+ url: string;
95
+ }
96
+ export interface GcpCloudDNS {
97
+ name: string;
98
+ dnsName: string;
99
+ }
100
+ export interface GcpInventory {
101
+ vms: GcpVM[];
102
+ rdsInstances: GcpCloudSQL[];
103
+ distributions: GcpCloudRun[];
104
+ hostedZones: GcpCloudDNS[];
105
+ }
106
+ export interface FirebaseHosting {
107
+ site: string;
108
+ }
109
+ export interface FirebaseFunction {
110
+ name: string;
111
+ region: string;
112
+ entryPoint: string;
113
+ runtime: string;
114
+ }
115
+ export interface FirebaseInventory {
116
+ hostingSites: FirebaseHosting[];
117
+ functions: FirebaseFunction[];
118
+ }
78
119
  export interface InventoryError {
79
- provider: "proxmox" | "do" | "aws";
120
+ provider: "proxmox" | "do" | "aws" | "gcp" | "firebase";
80
121
  message: string;
81
122
  }
82
123
  export interface InventoryResult {
83
124
  proxmox?: ProxmoxInventory;
84
125
  do?: DoInventory;
85
126
  aws?: AwsInventory;
127
+ gcp?: GcpInventory;
128
+ firebase?: FirebaseInventory;
86
129
  errors: InventoryError[];
87
130
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "puls-dev",
3
- "version": "0.2.8",
3
+ "version": "0.3.0",
4
4
  "description": "Intent-driven infrastructure-as-code with eager discovery and no state files.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -101,4 +101,4 @@
101
101
  "reflect-metadata": "^0.2.2",
102
102
  "undici": "^8.3.0"
103
103
  }
104
- }
104
+ }