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.
- package/dist/core/checker.js +71 -0
- package/dist/core/config.d.ts +5 -0
- package/dist/core/config.js +12 -1
- package/dist/core/context.d.ts +14 -0
- package/dist/core/context.js +2 -0
- package/dist/core/decorators.d.ts +2 -0
- package/dist/core/decorators.js +8 -14
- package/dist/core/group.test.d.ts +1 -0
- package/dist/core/group.test.js +94 -0
- package/dist/core/parallel.test.d.ts +1 -0
- package/dist/core/parallel.test.js +215 -0
- package/dist/core/production.test.d.ts +1 -0
- package/dist/core/production.test.js +189 -0
- package/dist/core/provisioner.js +29 -11
- package/dist/core/resource.d.ts +8 -0
- package/dist/core/resource.js +45 -0
- package/dist/core/retry.d.ts +9 -0
- package/dist/core/retry.js +28 -0
- package/dist/core/retry.test.d.ts +1 -0
- package/dist/core/retry.test.js +66 -0
- package/dist/core/secret.d.ts +2 -1
- package/dist/core/secret.js +12 -2
- package/dist/core/stack.js +381 -75
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/providers/aws/api.js +97 -17
- package/dist/providers/aws/ec2.d.ts +3 -0
- package/dist/providers/aws/ec2.js +37 -3
- package/dist/providers/aws/ec2.test.js +5 -3
- package/dist/providers/aws/index.d.ts +2 -0
- package/dist/providers/aws/index.js +2 -0
- package/dist/providers/aws/secrets.js +20 -3
- package/dist/providers/aws/template.d.ts +34 -0
- package/dist/providers/aws/template.js +252 -0
- package/dist/providers/aws/template.test.d.ts +1 -0
- package/dist/providers/aws/template.test.js +208 -0
- package/dist/providers/do/api.d.ts +2 -0
- package/dist/providers/do/api.js +124 -26
- package/dist/providers/do/droplet.js +14 -0
- package/dist/providers/firebase/api.js +92 -29
- package/dist/providers/firebase/list.d.ts +2 -0
- package/dist/providers/firebase/list.js +25 -0
- package/dist/providers/gcp/api.js +88 -14
- package/dist/providers/gcp/index.d.ts +3 -1
- package/dist/providers/gcp/index.js +3 -1
- package/dist/providers/gcp/list.d.ts +2 -0
- package/dist/providers/gcp/list.js +55 -0
- package/dist/providers/gcp/secrets.js +21 -4
- package/dist/providers/gcp/template.d.ts +32 -0
- package/dist/providers/gcp/template.js +252 -0
- package/dist/providers/gcp/template.test.d.ts +1 -0
- package/dist/providers/gcp/template.test.js +227 -0
- package/dist/providers/gcp/vm.d.ts +3 -0
- package/dist/providers/gcp/vm.js +46 -3
- package/dist/providers/proxmox/api.d.ts +1 -0
- package/dist/providers/proxmox/api.js +72 -16
- package/dist/providers/proxmox/index.d.ts +3 -1
- package/dist/providers/proxmox/index.js +14 -1
- package/dist/providers/proxmox/template.d.ts +44 -0
- package/dist/providers/proxmox/template.js +350 -0
- package/dist/providers/proxmox/template.test.d.ts +1 -0
- package/dist/providers/proxmox/template.test.js +215 -0
- package/dist/providers/proxmox/vm.d.ts +3 -0
- package/dist/providers/proxmox/vm.js +43 -11
- package/dist/types/inventory.d.ts +44 -1
- 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 =
|
|
196
|
-
const template =
|
|
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) ===
|
|
200
|
-
: r.name?.includes(
|
|
217
|
+
? String(r.vmid) === sourceVmid
|
|
218
|
+
: r.name?.includes(sourceVmid)))
|
|
201
219
|
: null;
|
|
202
|
-
if (
|
|
203
|
-
throw new Error(`No Proxmox template found matching "${
|
|
220
|
+
if (sourceVmid && !template) {
|
|
221
|
+
throw new Error(`No Proxmox template found matching "${sourceVmid}". ` +
|
|
204
222
|
(isVmid
|
|
205
|
-
? `Check that VMID ${
|
|
206
|
-
: `Create a template whose name contains "${
|
|
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.
|
|
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
|
+
}
|