puls-dev 0.2.7 → 0.2.9
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 +6 -0
- package/dist/core/config.js +11 -1
- package/dist/core/context.d.ts +14 -0
- package/dist/core/context.js +2 -0
- package/dist/core/decorators.d.ts +4 -0
- package/dist/core/decorators.js +56 -30
- package/dist/core/hooks.d.ts +21 -0
- package/dist/core/hooks.js +116 -0
- package/dist/core/hooks.test.d.ts +1 -0
- package/dist/core/hooks.test.js +194 -0
- package/dist/core/multiregion.test.d.ts +1 -0
- package/dist/core/multiregion.test.js +87 -0
- package/dist/core/output.d.ts +2 -0
- package/dist/core/output.js +9 -2
- package/dist/core/parallel.test.d.ts +1 -0
- package/dist/core/parallel.test.js +215 -0
- package/dist/core/parser.d.ts +10 -0
- package/dist/core/parser.js +140 -0
- package/dist/core/parser.test.d.ts +1 -0
- package/dist/core/parser.test.js +117 -0
- package/dist/core/production.test.d.ts +1 -0
- package/dist/core/production.test.js +189 -0
- package/dist/core/provisioner.d.ts +4 -0
- package/dist/core/provisioner.js +123 -0
- package/dist/core/resource.d.ts +23 -0
- package/dist/core/resource.js +54 -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 +41 -0
- package/dist/core/secret.js +105 -0
- package/dist/core/secret.test.d.ts +1 -0
- package/dist/core/secret.test.js +166 -0
- package/dist/core/stack.d.ts +4 -3
- package/dist/core/stack.js +322 -48
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/providers/aws/api.js +97 -17
- package/dist/providers/aws/ec2.d.ts +51 -0
- package/dist/providers/aws/ec2.js +331 -0
- package/dist/providers/aws/ec2.test.d.ts +1 -0
- package/dist/providers/aws/ec2.test.js +281 -0
- package/dist/providers/aws/index.d.ts +4 -0
- package/dist/providers/aws/index.js +4 -0
- package/dist/providers/aws/route53.d.ts +1 -0
- package/dist/providers/aws/route53.js +15 -2
- package/dist/providers/aws/route53.test.js +47 -0
- 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 +3 -1
- package/dist/providers/do/api.js +126 -27
- package/dist/providers/do/app.d.ts +26 -0
- package/dist/providers/do/app.js +124 -0
- package/dist/providers/do/app.test.d.ts +1 -0
- package/dist/providers/do/app.test.js +268 -0
- package/dist/providers/do/database.d.ts +44 -0
- package/dist/providers/do/database.js +208 -0
- package/dist/providers/do/database.test.d.ts +1 -0
- package/dist/providers/do/database.test.js +293 -0
- package/dist/providers/do/domain.d.ts +2 -0
- package/dist/providers/do/domain.js +30 -0
- package/dist/providers/do/domain.test.js +49 -0
- package/dist/providers/do/droplet.d.ts +9 -0
- package/dist/providers/do/droplet.js +146 -8
- package/dist/providers/do/droplet.test.js +228 -1
- package/dist/providers/do/firewall.d.ts +2 -1
- package/dist/providers/do/firewall.js +23 -9
- package/dist/providers/do/firewall.test.js +54 -0
- package/dist/providers/do/index.d.ts +11 -0
- package/dist/providers/do/index.js +8 -0
- package/dist/providers/do/spaces.d.ts +27 -0
- package/dist/providers/do/spaces.js +142 -0
- package/dist/providers/do/spaces.test.d.ts +1 -0
- package/dist/providers/do/spaces.test.js +180 -0
- package/dist/providers/do/spaces_api.d.ts +2 -0
- package/dist/providers/do/spaces_api.js +20 -0
- package/dist/providers/do/vpc.d.ts +30 -0
- package/dist/providers/do/vpc.js +128 -0
- package/dist/providers/do/vpc.test.d.ts +1 -0
- package/dist/providers/do/vpc.test.js +258 -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/clouddns.d.ts +1 -0
- package/dist/providers/gcp/clouddns.js +15 -2
- package/dist/providers/gcp/clouddns.test.js +45 -0
- package/dist/providers/gcp/index.d.ts +5 -1
- package/dist/providers/gcp/index.js +5 -1
- package/dist/providers/gcp/list.d.ts +2 -0
- package/dist/providers/gcp/list.js +55 -0
- package/dist/providers/gcp/secrets.js +1 -1
- 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 +48 -0
- package/dist/providers/gcp/vm.js +375 -0
- package/dist/providers/gcp/vm.test.d.ts +1 -0
- package/dist/providers/gcp/vm.test.js +321 -0
- package/dist/providers/proxmox/api.d.ts +1 -0
- package/dist/providers/proxmox/api.js +72 -16
- package/dist/providers/proxmox/index.d.ts +2 -0
- package/dist/providers/proxmox/index.js +2 -0
- package/dist/providers/proxmox/template.d.ts +44 -0
- package/dist/providers/proxmox/template.js +349 -0
- package/dist/providers/proxmox/template.test.d.ts +1 -0
- package/dist/providers/proxmox/template.test.js +179 -0
- package/dist/providers/proxmox/vm.d.ts +7 -4
- package/dist/providers/proxmox/vm.js +57 -102
- package/dist/providers/proxmox/vm.test.js +77 -0
- package/dist/types/inventory.d.ts +44 -1
- package/package.json +3 -1
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { test, describe, beforeEach, mock } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { EC2VMBuilder, parseAwsTagsForProvision, mergeAwsTagsForProvision } from "./ec2.js";
|
|
7
|
+
import { getFileHash } from "../proxmox/hash.js";
|
|
8
|
+
import { Config } from "../../core/config.js";
|
|
9
|
+
import { EC2Client } from "@aws-sdk/client-ec2";
|
|
10
|
+
describe("EC2VMBuilder Unit Tests", () => {
|
|
11
|
+
let originalSend;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
Config.set({
|
|
14
|
+
dryRun: false,
|
|
15
|
+
providers: {
|
|
16
|
+
aws: { region: "us-east-1" },
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
originalSend = EC2Client.prototype.send;
|
|
20
|
+
});
|
|
21
|
+
test("gracefully handles discovery when VM does not exist", async () => {
|
|
22
|
+
EC2Client.prototype.send = (async (command) => {
|
|
23
|
+
if (command.constructor.name === "DescribeInstancesCommand") {
|
|
24
|
+
return { Reservations: [] };
|
|
25
|
+
}
|
|
26
|
+
return {};
|
|
27
|
+
});
|
|
28
|
+
try {
|
|
29
|
+
const vm = new EC2VMBuilder("missing-vm");
|
|
30
|
+
const existing = await vm.discoveryPromise;
|
|
31
|
+
assert.strictEqual(existing, null);
|
|
32
|
+
assert.strictEqual(vm.resolvedInstanceId, undefined);
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
EC2Client.prototype.send = originalSend;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
test("discovers VM successfully when it exists", async () => {
|
|
39
|
+
EC2Client.prototype.send = (async (command) => {
|
|
40
|
+
if (command.constructor.name === "DescribeInstancesCommand") {
|
|
41
|
+
return {
|
|
42
|
+
Reservations: [
|
|
43
|
+
{
|
|
44
|
+
Instances: [
|
|
45
|
+
{
|
|
46
|
+
InstanceId: "i-1234567890abcdef0",
|
|
47
|
+
InstanceType: "t3.micro",
|
|
48
|
+
PublicIpAddress: "54.80.12.34",
|
|
49
|
+
State: { Name: "running" },
|
|
50
|
+
Tags: [{ Key: "Name", Value: "existing-vm" }],
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return {};
|
|
58
|
+
});
|
|
59
|
+
try {
|
|
60
|
+
const vm = new EC2VMBuilder("existing-vm");
|
|
61
|
+
const existing = await vm.discoveryPromise;
|
|
62
|
+
assert.ok(existing);
|
|
63
|
+
assert.strictEqual(existing.InstanceId, "i-1234567890abcdef0");
|
|
64
|
+
assert.strictEqual(await vm.out.id.get(), "i-1234567890abcdef0");
|
|
65
|
+
assert.strictEqual(await vm.out.ip.get(), "54.80.12.34");
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
EC2Client.prototype.send = originalSend;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
test("runs in dry-run mode safely and logs plan", async () => {
|
|
72
|
+
Config.set({ dryRun: true });
|
|
73
|
+
EC2Client.prototype.send = (async (command) => {
|
|
74
|
+
if (command.constructor.name === "DescribeInstancesCommand") {
|
|
75
|
+
return { Reservations: [] };
|
|
76
|
+
}
|
|
77
|
+
return {};
|
|
78
|
+
});
|
|
79
|
+
const originalLog = console.log;
|
|
80
|
+
let logOutput = "";
|
|
81
|
+
console.log = (...args) => {
|
|
82
|
+
logOutput += args.join(" ") + "\n";
|
|
83
|
+
};
|
|
84
|
+
try {
|
|
85
|
+
const vm = new EC2VMBuilder("dryrun-vm")
|
|
86
|
+
.instanceType("t3.medium")
|
|
87
|
+
.ami("ami-test123")
|
|
88
|
+
.provision("playbooks/nginx.yaml");
|
|
89
|
+
const result = await vm.deploy();
|
|
90
|
+
assert.strictEqual(result.name, "dryrun-vm");
|
|
91
|
+
assert.strictEqual(result.id, "PENDING");
|
|
92
|
+
assert.strictEqual(await vm.out.id.get(), "PENDING");
|
|
93
|
+
assert.strictEqual(await vm.out.ip.get(), "0.0.0.0");
|
|
94
|
+
assert.ok(logOutput.includes("🔍 [DRY RUN]"));
|
|
95
|
+
assert.ok(logOutput.includes("Plan: Create AWS EC2 Instance"));
|
|
96
|
+
assert.ok(logOutput.includes("Name: dryrun-vm"));
|
|
97
|
+
assert.ok(logOutput.includes("Instance Type: t3.medium"));
|
|
98
|
+
assert.ok(logOutput.includes("Source: AMI ami-test123"));
|
|
99
|
+
assert.ok(logOutput.includes("Provision: playbooks/nginx.yaml"));
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
console.log = originalLog;
|
|
103
|
+
EC2Client.prototype.send = originalSend;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
test("creates a new VM instance and runs playbooks successfully", async () => {
|
|
107
|
+
// 1. Create a dummy playbook file to check hash
|
|
108
|
+
const dummyPlaybookDir = join(tmpdir(), `puls-ec2-test-${Date.now()}`);
|
|
109
|
+
fs.mkdirSync(dummyPlaybookDir);
|
|
110
|
+
const playbookPath = join(dummyPlaybookDir, "setup.yaml");
|
|
111
|
+
fs.writeFileSync(playbookPath, "- hosts: all\n tasks:\n - name: Hello");
|
|
112
|
+
// Mock EC2 Client state transitions:
|
|
113
|
+
// First DescribeInstancesCommand returns empty, then returns running VM.
|
|
114
|
+
let describeCount = 0;
|
|
115
|
+
EC2Client.prototype.send = (async (command) => {
|
|
116
|
+
const name = command.constructor.name;
|
|
117
|
+
if (name === "DescribeInstancesCommand") {
|
|
118
|
+
describeCount++;
|
|
119
|
+
if (describeCount === 1) {
|
|
120
|
+
return { Reservations: [] };
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
Reservations: [
|
|
124
|
+
{
|
|
125
|
+
Instances: [
|
|
126
|
+
{
|
|
127
|
+
InstanceId: "i-999",
|
|
128
|
+
InstanceType: "t3.micro",
|
|
129
|
+
PublicIpAddress: "34.200.10.20",
|
|
130
|
+
State: { Name: "running" },
|
|
131
|
+
Tags: [
|
|
132
|
+
{ Key: "Name", Value: "new-ec2-vm" },
|
|
133
|
+
{ Key: "puls-provision", Value: `setup-yaml=${getFileHash(playbookPath)}` },
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (name === "RunInstancesCommand") {
|
|
142
|
+
return {
|
|
143
|
+
Instances: [
|
|
144
|
+
{
|
|
145
|
+
InstanceId: "i-999",
|
|
146
|
+
State: { Name: "pending" },
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return {};
|
|
152
|
+
});
|
|
153
|
+
try {
|
|
154
|
+
const vm = new EC2VMBuilder("new-ec2-vm")
|
|
155
|
+
.ami("ami-0c55b159cbfafe1f0")
|
|
156
|
+
.instanceType("t3.micro")
|
|
157
|
+
.sshPrivateKey("~/.ssh/id_rsa")
|
|
158
|
+
.provision(playbookPath);
|
|
159
|
+
// Mock SSH Port check and playbook run
|
|
160
|
+
mock.method(vm, "checkPort", async () => true);
|
|
161
|
+
const runProvisionSpy = mock.method(vm, "runProvisioner", async () => { });
|
|
162
|
+
const result = await vm.deploy();
|
|
163
|
+
assert.strictEqual(result.id, "i-999");
|
|
164
|
+
assert.strictEqual(result.ip, "34.200.10.20");
|
|
165
|
+
assert.strictEqual(await vm.out.ip.get(), "34.200.10.20");
|
|
166
|
+
assert.strictEqual(runProvisionSpy.mock.callCount(), 1);
|
|
167
|
+
assert.strictEqual(runProvisionSpy.mock.calls[0].arguments[0], "34.200.10.20");
|
|
168
|
+
assert.strictEqual(runProvisionSpy.mock.calls[0].arguments[1], playbookPath);
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
// Cleanup
|
|
172
|
+
EC2Client.prototype.send = originalSend;
|
|
173
|
+
try {
|
|
174
|
+
fs.unlinkSync(playbookPath);
|
|
175
|
+
fs.rmdirSync(dummyPlaybookDir);
|
|
176
|
+
}
|
|
177
|
+
catch { }
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
test("stops, resizes, and starts the VM when instanceType changes", async () => {
|
|
181
|
+
let state = "running";
|
|
182
|
+
let type = "t3.micro";
|
|
183
|
+
EC2Client.prototype.send = (async (command) => {
|
|
184
|
+
const name = command.constructor.name;
|
|
185
|
+
if (name === "DescribeInstancesCommand") {
|
|
186
|
+
return {
|
|
187
|
+
Reservations: [
|
|
188
|
+
{
|
|
189
|
+
Instances: [
|
|
190
|
+
{
|
|
191
|
+
InstanceId: "i-123",
|
|
192
|
+
InstanceType: type,
|
|
193
|
+
PublicIpAddress: "34.20.10.5",
|
|
194
|
+
State: { Name: state },
|
|
195
|
+
Tags: [{ Key: "Name", Value: "resize-vm" }],
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (name === "StopInstancesCommand") {
|
|
203
|
+
state = "stopped";
|
|
204
|
+
return {};
|
|
205
|
+
}
|
|
206
|
+
if (name === "ModifyInstanceAttributeCommand") {
|
|
207
|
+
type = command.input.InstanceType.Value;
|
|
208
|
+
return {};
|
|
209
|
+
}
|
|
210
|
+
if (name === "StartInstancesCommand") {
|
|
211
|
+
state = "running";
|
|
212
|
+
return {};
|
|
213
|
+
}
|
|
214
|
+
return {};
|
|
215
|
+
});
|
|
216
|
+
try {
|
|
217
|
+
const vm = new EC2VMBuilder("resize-vm").instanceType("t3.medium");
|
|
218
|
+
const result = await vm.deploy();
|
|
219
|
+
assert.strictEqual(result.id, "i-123");
|
|
220
|
+
assert.strictEqual(type, "t3.medium");
|
|
221
|
+
assert.strictEqual(state, "running");
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
EC2Client.prototype.send = originalSend;
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
test("terminates the VM successfully on destroy", async () => {
|
|
228
|
+
let terminated = false;
|
|
229
|
+
EC2Client.prototype.send = (async (command) => {
|
|
230
|
+
const name = command.constructor.name;
|
|
231
|
+
if (name === "DescribeInstancesCommand") {
|
|
232
|
+
return {
|
|
233
|
+
Reservations: [
|
|
234
|
+
{
|
|
235
|
+
Instances: [
|
|
236
|
+
{
|
|
237
|
+
InstanceId: "i-555",
|
|
238
|
+
InstanceType: "t3.micro",
|
|
239
|
+
State: { Name: "running" },
|
|
240
|
+
Tags: [{ Key: "Name", Value: "delete-vm" }],
|
|
241
|
+
},
|
|
242
|
+
],
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (name === "TerminateInstancesCommand") {
|
|
248
|
+
terminated = true;
|
|
249
|
+
return {};
|
|
250
|
+
}
|
|
251
|
+
return {};
|
|
252
|
+
});
|
|
253
|
+
try {
|
|
254
|
+
const vm = new EC2VMBuilder("delete-vm");
|
|
255
|
+
const result = await vm.destroy();
|
|
256
|
+
assert.strictEqual(result.destroyed, "delete-vm");
|
|
257
|
+
assert.ok(terminated);
|
|
258
|
+
}
|
|
259
|
+
finally {
|
|
260
|
+
EC2Client.prototype.send = originalSend;
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
test("parseAwsTagsForProvision correctly handles missing and formatted provision tags", () => {
|
|
264
|
+
assert.deepStrictEqual(parseAwsTagsForProvision(undefined), {});
|
|
265
|
+
assert.deepStrictEqual(parseAwsTagsForProvision([]), {});
|
|
266
|
+
const tags = [
|
|
267
|
+
{ Key: "Name", Value: "my-vm" },
|
|
268
|
+
{ Key: "puls-provision", Value: "setup-yaml=h123,configure-yaml=h456" },
|
|
269
|
+
];
|
|
270
|
+
const parsed = parseAwsTagsForProvision(tags);
|
|
271
|
+
assert.deepStrictEqual(parsed, {
|
|
272
|
+
"setup-yaml": "h123",
|
|
273
|
+
"configure-yaml": "h456",
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
test("mergeAwsTagsForProvision merges record mapping back into string", () => {
|
|
277
|
+
const hashes = { "nginx-yaml": "hash-abc", "sec-yaml": "hash-xyz" };
|
|
278
|
+
const merged = mergeAwsTagsForProvision(hashes);
|
|
279
|
+
assert.strictEqual(merged, "nginx-yaml=hash-abc,sec-yaml=hash-xyz");
|
|
280
|
+
});
|
|
281
|
+
});
|
|
@@ -10,6 +10,8 @@ import { SecretsBuilder } from "./secrets.js";
|
|
|
10
10
|
import { IAMRoleBuilder, IAMPolicyBuilder } from "./iam.js";
|
|
11
11
|
import { SNSTopicBuilder } from "./sns.js";
|
|
12
12
|
import { CloudWatchAlarmBuilder } from "./cloudwatch.js";
|
|
13
|
+
import { EC2VMBuilder } from "./ec2.js";
|
|
14
|
+
import { EC2TemplateBuilder } from "./template.js";
|
|
13
15
|
export declare const AWS: {
|
|
14
16
|
init: (opts: {
|
|
15
17
|
region: string;
|
|
@@ -27,5 +29,7 @@ export declare const AWS: {
|
|
|
27
29
|
IAMPolicy: (name: string) => IAMPolicyBuilder;
|
|
28
30
|
SNS: (name: string) => SNSTopicBuilder;
|
|
29
31
|
Alarm: (name: string) => CloudWatchAlarmBuilder;
|
|
32
|
+
EC2: (name: string) => EC2VMBuilder;
|
|
33
|
+
Template: (name: string) => EC2TemplateBuilder;
|
|
30
34
|
};
|
|
31
35
|
export * from "../../types/aws.js";
|
|
@@ -11,6 +11,8 @@ import { SecretsBuilder } from "./secrets.js";
|
|
|
11
11
|
import { IAMRoleBuilder, IAMPolicyBuilder } from "./iam.js";
|
|
12
12
|
import { SNSTopicBuilder } from "./sns.js";
|
|
13
13
|
import { CloudWatchAlarmBuilder } from "./cloudwatch.js";
|
|
14
|
+
import { EC2VMBuilder } from "./ec2.js";
|
|
15
|
+
import { EC2TemplateBuilder } from "./template.js";
|
|
14
16
|
export const AWS = {
|
|
15
17
|
init: (opts) => {
|
|
16
18
|
Config.set({
|
|
@@ -33,5 +35,7 @@ export const AWS = {
|
|
|
33
35
|
IAMPolicy: (name) => new IAMPolicyBuilder(name),
|
|
34
36
|
SNS: (name) => new SNSTopicBuilder(name),
|
|
35
37
|
Alarm: (name) => new CloudWatchAlarmBuilder(name),
|
|
38
|
+
EC2: (name) => new EC2VMBuilder(name),
|
|
39
|
+
Template: (name) => new EC2TemplateBuilder(name),
|
|
36
40
|
};
|
|
37
41
|
export * from "../../types/aws.js";
|
|
@@ -21,6 +21,7 @@ export declare class Route53Builder extends BaseBuilder {
|
|
|
21
21
|
cert(): ACMCertificateBuilder | undefined;
|
|
22
22
|
withWildcardSSL(): this;
|
|
23
23
|
register(contact?: RegistrantContact): this;
|
|
24
|
+
record(filePath: string): this;
|
|
24
25
|
record(name: string, type: "A" | "AAAA" | "CNAME" | "MX" | "TXT" | "NS" | "PTR" | "SRV" | "CAA" | "NAPTR" | "SPF", value: string, ttl?: number): this;
|
|
25
26
|
pointer(name: string, target: BaseBuilder): this;
|
|
26
27
|
deploy(): Promise<{
|
|
@@ -4,6 +4,7 @@ import { BaseBuilder } from "../../core/resource.js";
|
|
|
4
4
|
import { Output } from "../../core/output.js";
|
|
5
5
|
import { ACMCertificateBuilder } from "./acm.js";
|
|
6
6
|
import { getR53Client, getR53DomainsClient } from "./api.js";
|
|
7
|
+
import { loadRecordsFromFile } from "../../core/parser.js";
|
|
7
8
|
export class Route53Builder extends BaseBuilder {
|
|
8
9
|
out = {
|
|
9
10
|
zone: new Output(),
|
|
@@ -59,8 +60,20 @@ export class Route53Builder extends BaseBuilder {
|
|
|
59
60
|
this._registrantContact = contact;
|
|
60
61
|
return this;
|
|
61
62
|
}
|
|
62
|
-
record(
|
|
63
|
-
|
|
63
|
+
record(nameOrPath, type, value, ttl = 300) {
|
|
64
|
+
if (arguments.length === 1 && typeof nameOrPath === "string" && (nameOrPath.endsWith(".yaml") || nameOrPath.endsWith(".yml") || nameOrPath.endsWith(".json"))) {
|
|
65
|
+
const loaded = loadRecordsFromFile(nameOrPath);
|
|
66
|
+
for (const r of loaded) {
|
|
67
|
+
this.records.push({
|
|
68
|
+
name: r.name,
|
|
69
|
+
type: r.type,
|
|
70
|
+
value: r.value,
|
|
71
|
+
ttl: r.ttl ?? 300,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
this.records.push({ name: nameOrPath, type: type, value: value, ttl });
|
|
64
77
|
return this;
|
|
65
78
|
}
|
|
66
79
|
pointer(name, target) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { test, describe, beforeEach, afterEach } from 'node:test';
|
|
2
2
|
import assert from 'node:assert';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
3
5
|
import { Route53Client } from '@aws-sdk/client-route-53';
|
|
4
6
|
import { Route53DomainsClient } from '@aws-sdk/client-route-53-domains';
|
|
5
7
|
import { Route53Builder } from './route53.js';
|
|
@@ -226,4 +228,49 @@ describe('Route53Builder Unit Tests', () => {
|
|
|
226
228
|
assert.strictEqual(pollCall.length, 2);
|
|
227
229
|
assert.strictEqual(pollCall[0].input.OperationId, 'op-registration-abc');
|
|
228
230
|
});
|
|
231
|
+
test("loads records from a configuration file (JSON) successfully", async () => {
|
|
232
|
+
mockR53Responses['ListHostedZonesByNameCommand'] = {
|
|
233
|
+
HostedZones: [{ Id: '/hostedzone/Z123', Name: 'example.com.' }]
|
|
234
|
+
};
|
|
235
|
+
mockR53Responses['ChangeResourceRecordSetsCommand'] = {};
|
|
236
|
+
// Mock JSON file creation
|
|
237
|
+
const tempJsonPath = path.resolve(process.cwd(), "temp-route53-records.json");
|
|
238
|
+
const jsonContent = JSON.stringify([
|
|
239
|
+
{ name: "www", type: "CNAME", value: "lb.com", ttl: 120 },
|
|
240
|
+
{ name: "mail", type: "A", value: "1.1.1.1" }
|
|
241
|
+
]);
|
|
242
|
+
fs.writeFileSync(tempJsonPath, jsonContent, "utf-8");
|
|
243
|
+
try {
|
|
244
|
+
const builder = new Route53Builder("example.com")
|
|
245
|
+
.record("temp-route53-records.json")
|
|
246
|
+
.record("api", "A", "2.2.2.2"); // Hybrid programmatic record!
|
|
247
|
+
await builder.deploy();
|
|
248
|
+
const changeCall = r53Calls.find(c => c.commandName === 'ChangeResourceRecordSetsCommand');
|
|
249
|
+
assert.ok(changeCall);
|
|
250
|
+
const changes = changeCall.input.ChangeBatch.Changes;
|
|
251
|
+
assert.strictEqual(changes.length, 3);
|
|
252
|
+
assert.deepStrictEqual(changes[0].ResourceRecordSet, {
|
|
253
|
+
Name: "www",
|
|
254
|
+
Type: "CNAME",
|
|
255
|
+
TTL: 120,
|
|
256
|
+
ResourceRecords: [{ Value: "lb.com" }]
|
|
257
|
+
});
|
|
258
|
+
assert.deepStrictEqual(changes[1].ResourceRecordSet, {
|
|
259
|
+
Name: "mail",
|
|
260
|
+
Type: "A",
|
|
261
|
+
TTL: 300, // default
|
|
262
|
+
ResourceRecords: [{ Value: "1.1.1.1" }]
|
|
263
|
+
});
|
|
264
|
+
assert.deepStrictEqual(changes[2].ResourceRecordSet, {
|
|
265
|
+
Name: "api",
|
|
266
|
+
Type: "A",
|
|
267
|
+
TTL: 300,
|
|
268
|
+
ResourceRecords: [{ Value: "2.2.2.2" }]
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
finally {
|
|
272
|
+
if (fs.existsSync(tempJsonPath))
|
|
273
|
+
fs.unlinkSync(tempJsonPath);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
229
276
|
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { BaseBuilder } from "../../core/resource.js";
|
|
2
|
+
import { Output } from "../../core/output.js";
|
|
3
|
+
export declare class EC2TemplateBuilder extends BaseBuilder {
|
|
4
|
+
readonly out: {
|
|
5
|
+
amiId: Output<string>;
|
|
6
|
+
};
|
|
7
|
+
private _baseImage;
|
|
8
|
+
private _instanceType;
|
|
9
|
+
private _subnetId?;
|
|
10
|
+
private _securityGroupIds?;
|
|
11
|
+
private _sshPrivateKeyPath?;
|
|
12
|
+
private _keyName?;
|
|
13
|
+
private _provision;
|
|
14
|
+
constructor(name: string);
|
|
15
|
+
baseImage(amiId: string): this;
|
|
16
|
+
instanceType(type: string): this;
|
|
17
|
+
subnetId(id: string): this;
|
|
18
|
+
securityGroupIds(ids: string[]): this;
|
|
19
|
+
sshPrivateKey(path: string): this;
|
|
20
|
+
keyName(name: string): this;
|
|
21
|
+
provision(...playbookPaths: (string | string[])[]): this;
|
|
22
|
+
private discoverAMI;
|
|
23
|
+
protected checkPort(ip: string, port: number): Promise<boolean>;
|
|
24
|
+
protected runProvisioner(ip: string, script: string): Promise<void>;
|
|
25
|
+
deploy(): Promise<{
|
|
26
|
+
name: string;
|
|
27
|
+
amiId: any;
|
|
28
|
+
}>;
|
|
29
|
+
destroy(): Promise<{
|
|
30
|
+
destroyed: boolean;
|
|
31
|
+
} | {
|
|
32
|
+
destroyed: string;
|
|
33
|
+
}>;
|
|
34
|
+
}
|