@stacksjs/ts-cloud 0.7.4 → 0.7.6
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/aws/index.js +259 -0
- package/dist/bin/cli.js +316 -316
- package/dist/chunk-0wxyppza.js +146 -0
- package/dist/chunk-3knnr7wh.js +601 -0
- package/dist/chunk-3rsfns7x.js +10427 -0
- package/dist/chunk-93hjhs78.js +1752 -0
- package/dist/chunk-arsh1g5h.js +1749 -0
- package/dist/chunk-b82pbxyp.js +1572 -0
- package/dist/chunk-c6rgvg1j.js +46124 -0
- package/dist/chunk-d2p5n2aq.js +23 -0
- package/dist/chunk-d7p84vz5.js +1699 -0
- package/dist/chunk-dpkk640m.js +4392 -0
- package/dist/chunk-eq08r166.js +8 -0
- package/dist/chunk-hsk6fe6x.js +371 -0
- package/dist/chunk-mj1tmhte.js +13 -0
- package/dist/chunk-p6309384.js +12828 -0
- package/dist/chunk-pegd7rmf.js +10 -0
- package/dist/chunk-pqfzdg68.js +12 -0
- package/dist/chunk-qpj3edwz.js +601 -0
- package/dist/chunk-tjjgajbh.js +1032 -0
- package/dist/chunk-tnztxpcb.js +630 -0
- package/dist/chunk-tt4kxske.js +1788 -0
- package/dist/chunk-v0bahtg2.js +4 -0
- package/dist/chunk-vd87cpvn.js +1754 -0
- package/dist/chunk-zn0nxxa8.js +1779 -0
- package/dist/deploy/index.js +87 -0
- package/dist/dns/index.js +31 -0
- package/dist/drivers/hetzner/provision.d.ts +53 -0
- package/dist/drivers/index.d.ts +5 -1
- package/dist/drivers/index.js +98 -0
- package/dist/drivers/shared/remote-exec.d.ts +47 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4383 -91358
- package/dist/push/index.js +509 -0
- package/dist/ui/index.html +3 -3
- package/dist/ui/server/actions.html +3 -3
- package/dist/ui/server/backups.html +3 -3
- package/dist/ui/server/database.html +3 -3
- package/dist/ui/server/deployments.html +3 -3
- package/dist/ui/server/firewall.html +3 -3
- package/dist/ui/server/logs.html +3 -3
- package/dist/ui/server/services.html +3 -3
- package/dist/ui/server/sites.html +3 -3
- package/dist/ui/server/ssh-keys.html +3 -3
- package/dist/ui/server/terminal.html +3 -3
- package/dist/ui/server/workers.html +3 -3
- package/dist/ui/serverless/alarms.html +3 -3
- package/dist/ui/serverless/assets.html +3 -3
- package/dist/ui/serverless/data.html +3 -3
- package/dist/ui/serverless/deployments.html +3 -3
- package/dist/ui/serverless/functions.html +3 -3
- package/dist/ui/serverless/logs.html +3 -3
- package/dist/ui/serverless/queues.html +3 -3
- package/dist/ui/serverless/scheduler.html +3 -3
- package/dist/ui/serverless/secrets.html +3 -3
- package/dist/ui/serverless/traces.html +3 -3
- package/dist/ui/serverless.html +3 -3
- package/package.json +3 -3
|
@@ -0,0 +1,1032 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AWSClient
|
|
3
|
+
} from "./chunk-arsh1g5h.js";
|
|
4
|
+
|
|
5
|
+
// src/aws/cloudfront.ts
|
|
6
|
+
class CloudFrontClient {
|
|
7
|
+
client;
|
|
8
|
+
constructor(profile) {
|
|
9
|
+
this.client = new AWSClient;
|
|
10
|
+
}
|
|
11
|
+
async createInvalidation(options) {
|
|
12
|
+
const callerReference = options.callerReference || Date.now().toString();
|
|
13
|
+
const invalidationBatchXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
14
|
+
<InvalidationBatch>
|
|
15
|
+
<Paths>
|
|
16
|
+
<Quantity>${options.paths.length}</Quantity>
|
|
17
|
+
<Items>
|
|
18
|
+
${options.paths.map((path) => `<Path>${path}</Path>`).join(`
|
|
19
|
+
`)}
|
|
20
|
+
</Items>
|
|
21
|
+
</Paths>
|
|
22
|
+
<CallerReference>${callerReference}</CallerReference>
|
|
23
|
+
</InvalidationBatch>`;
|
|
24
|
+
const result = await this.client.request({
|
|
25
|
+
service: "cloudfront",
|
|
26
|
+
region: "us-east-1",
|
|
27
|
+
method: "POST",
|
|
28
|
+
path: `/2020-05-31/distribution/${options.distributionId}/invalidation`,
|
|
29
|
+
body: invalidationBatchXml,
|
|
30
|
+
headers: {
|
|
31
|
+
"Content-Type": "application/xml"
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
Id: result.Id || result.Invalidation?.Id,
|
|
36
|
+
Status: result.Status || result.Invalidation?.Status || "InProgress",
|
|
37
|
+
CreateTime: result.CreateTime || result.Invalidation?.CreateTime || new Date().toISOString()
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async getInvalidation(distributionId, invalidationId) {
|
|
41
|
+
const result = await this.client.request({
|
|
42
|
+
service: "cloudfront",
|
|
43
|
+
region: "us-east-1",
|
|
44
|
+
method: "GET",
|
|
45
|
+
path: `/2020-05-31/distribution/${distributionId}/invalidation/${invalidationId}`
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
Id: result.Id || result.Invalidation?.Id,
|
|
49
|
+
Status: result.Status || result.Invalidation?.Status,
|
|
50
|
+
CreateTime: result.CreateTime || result.Invalidation?.CreateTime
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
async listInvalidations(distributionId) {
|
|
54
|
+
const result = await this.client.request({
|
|
55
|
+
service: "cloudfront",
|
|
56
|
+
region: "us-east-1",
|
|
57
|
+
method: "GET",
|
|
58
|
+
path: `/2020-05-31/distribution/${distributionId}/invalidation`
|
|
59
|
+
});
|
|
60
|
+
const invalidations = [];
|
|
61
|
+
if (result.InvalidationSummary) {
|
|
62
|
+
const summaries = Array.isArray(result.InvalidationSummary) ? result.InvalidationSummary : [result.InvalidationSummary];
|
|
63
|
+
invalidations.push(...summaries.map((item) => ({
|
|
64
|
+
Id: item.Id,
|
|
65
|
+
Status: item.Status,
|
|
66
|
+
CreateTime: item.CreateTime
|
|
67
|
+
})));
|
|
68
|
+
}
|
|
69
|
+
return invalidations;
|
|
70
|
+
}
|
|
71
|
+
async waitForInvalidation(distributionId, invalidationId) {
|
|
72
|
+
const maxAttempts = 60;
|
|
73
|
+
let attempts = 0;
|
|
74
|
+
while (attempts < maxAttempts) {
|
|
75
|
+
const invalidation = await this.getInvalidation(distributionId, invalidationId);
|
|
76
|
+
if (invalidation.Status === "Completed") {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
80
|
+
attempts++;
|
|
81
|
+
}
|
|
82
|
+
throw new Error(`Timeout waiting for invalidation ${invalidationId} to complete`);
|
|
83
|
+
}
|
|
84
|
+
async listDistributions() {
|
|
85
|
+
const result = await this.client.request({
|
|
86
|
+
service: "cloudfront",
|
|
87
|
+
region: "us-east-1",
|
|
88
|
+
method: "GET",
|
|
89
|
+
path: "/2020-05-31/distribution"
|
|
90
|
+
});
|
|
91
|
+
const distributions = [];
|
|
92
|
+
const distList = result.DistributionList || result;
|
|
93
|
+
const items = distList.Items;
|
|
94
|
+
let summaries = [];
|
|
95
|
+
if (Array.isArray(items)) {
|
|
96
|
+
summaries = items;
|
|
97
|
+
} else if (items?.DistributionSummary) {
|
|
98
|
+
summaries = Array.isArray(items.DistributionSummary) ? items.DistributionSummary : [items.DistributionSummary];
|
|
99
|
+
}
|
|
100
|
+
distributions.push(...summaries.map((item) => ({
|
|
101
|
+
Id: item.Id,
|
|
102
|
+
ARN: item.ARN,
|
|
103
|
+
Status: item.Status,
|
|
104
|
+
DomainName: item.DomainName,
|
|
105
|
+
Aliases: item.Aliases || undefined,
|
|
106
|
+
Enabled: item.Enabled === "true" || item.Enabled === true
|
|
107
|
+
})));
|
|
108
|
+
return distributions;
|
|
109
|
+
}
|
|
110
|
+
async getDistribution(distributionId) {
|
|
111
|
+
const result = await this.client.request({
|
|
112
|
+
service: "cloudfront",
|
|
113
|
+
region: "us-east-1",
|
|
114
|
+
method: "GET",
|
|
115
|
+
path: `/2020-05-31/distribution/${distributionId}`
|
|
116
|
+
});
|
|
117
|
+
const dist = result.Distribution || result;
|
|
118
|
+
return {
|
|
119
|
+
Id: dist.Id,
|
|
120
|
+
ARN: dist.ARN,
|
|
121
|
+
Status: dist.Status,
|
|
122
|
+
DomainName: dist.DomainName,
|
|
123
|
+
Aliases: dist.DistributionConfig?.Aliases?.Items || dist.Aliases?.Items || [],
|
|
124
|
+
Enabled: dist.DistributionConfig?.Enabled === "true" || dist.DistributionConfig?.Enabled === true
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
async getDistributionConfig(distributionId) {
|
|
128
|
+
const result = await this.client.request({
|
|
129
|
+
service: "cloudfront",
|
|
130
|
+
region: "us-east-1",
|
|
131
|
+
method: "GET",
|
|
132
|
+
path: `/2020-05-31/distribution/${distributionId}/config`
|
|
133
|
+
});
|
|
134
|
+
return {
|
|
135
|
+
ETag: result.ETag || "",
|
|
136
|
+
DistributionConfig: result.DistributionConfig || result
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
async invalidateAll(distributionId) {
|
|
140
|
+
return this.createInvalidation({
|
|
141
|
+
distributionId,
|
|
142
|
+
paths: ["/*"]
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
async invalidatePaths(distributionId, paths) {
|
|
146
|
+
const formattedPaths = paths.map((path) => path.startsWith("/") ? path : `/${path}`);
|
|
147
|
+
return this.createInvalidation({
|
|
148
|
+
distributionId,
|
|
149
|
+
paths: formattedPaths
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
async invalidatePattern(distributionId, pattern) {
|
|
153
|
+
const path = pattern.startsWith("/") ? pattern : `/${pattern}`;
|
|
154
|
+
return this.createInvalidation({
|
|
155
|
+
distributionId,
|
|
156
|
+
paths: [path]
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
async invalidateAfterDeployment(options) {
|
|
160
|
+
const { distributionId, changedPaths, invalidateAll = false, wait = false } = options;
|
|
161
|
+
let result;
|
|
162
|
+
if (invalidateAll || !changedPaths || changedPaths.length === 0) {
|
|
163
|
+
result = await this.invalidateAll(distributionId);
|
|
164
|
+
} else {
|
|
165
|
+
result = await this.invalidatePaths(distributionId, changedPaths);
|
|
166
|
+
}
|
|
167
|
+
if (wait) {
|
|
168
|
+
await this.waitForInvalidation(distributionId, result.Id);
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
invalidationId: result.Id,
|
|
172
|
+
status: result.Status
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
async findDistributionByDomain(domain) {
|
|
176
|
+
const distributions = await this.listDistributions();
|
|
177
|
+
const found = distributions.find((dist) => {
|
|
178
|
+
if (dist.DomainName === domain) {
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
if (dist.Aliases?.Items && dist.Aliases.Items.includes(domain)) {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
return false;
|
|
185
|
+
});
|
|
186
|
+
return found || null;
|
|
187
|
+
}
|
|
188
|
+
async batchInvalidate(distributionIds, paths = ["/*"]) {
|
|
189
|
+
const results = await Promise.all(distributionIds.map(async (distributionId) => {
|
|
190
|
+
const result = await this.createInvalidation({
|
|
191
|
+
distributionId,
|
|
192
|
+
paths
|
|
193
|
+
});
|
|
194
|
+
return {
|
|
195
|
+
distributionId,
|
|
196
|
+
invalidationId: result.Id,
|
|
197
|
+
status: result.Status
|
|
198
|
+
};
|
|
199
|
+
}));
|
|
200
|
+
return results;
|
|
201
|
+
}
|
|
202
|
+
async updateCustomErrorResponses(options) {
|
|
203
|
+
const { distributionId, customErrorResponses } = options;
|
|
204
|
+
const getResult = await this.client.request({
|
|
205
|
+
service: "cloudfront",
|
|
206
|
+
region: "us-east-1",
|
|
207
|
+
method: "GET",
|
|
208
|
+
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
209
|
+
returnHeaders: true
|
|
210
|
+
});
|
|
211
|
+
const etag = getResult.headers?.etag || getResult.headers?.ETag || "";
|
|
212
|
+
const currentConfig = getResult.body?.DistributionConfig || (getResult.body?.Enabled !== undefined ? getResult.body : undefined) || getResult.DistributionConfig;
|
|
213
|
+
if (!currentConfig || currentConfig.Enabled === undefined) {
|
|
214
|
+
throw new Error("Failed to get current distribution config");
|
|
215
|
+
}
|
|
216
|
+
if (customErrorResponses.length === 0) {
|
|
217
|
+
currentConfig.CustomErrorResponses = {
|
|
218
|
+
Quantity: 0
|
|
219
|
+
};
|
|
220
|
+
} else {
|
|
221
|
+
currentConfig.CustomErrorResponses = {
|
|
222
|
+
Quantity: customErrorResponses.length,
|
|
223
|
+
Items: {
|
|
224
|
+
CustomErrorResponse: customErrorResponses.map((err) => ({
|
|
225
|
+
ErrorCode: err.errorCode,
|
|
226
|
+
...err.responsePagePath && { ResponsePagePath: err.responsePagePath },
|
|
227
|
+
...err.responseCode && { ResponseCode: err.responseCode },
|
|
228
|
+
...err.errorCachingMinTTL !== undefined && { ErrorCachingMinTTL: err.errorCachingMinTTL }
|
|
229
|
+
}))
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
const configXml = this.buildDistributionConfigXml(currentConfig);
|
|
234
|
+
const result = await this.client.request({
|
|
235
|
+
service: "cloudfront",
|
|
236
|
+
region: "us-east-1",
|
|
237
|
+
method: "PUT",
|
|
238
|
+
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
239
|
+
body: configXml,
|
|
240
|
+
headers: {
|
|
241
|
+
"Content-Type": "application/xml",
|
|
242
|
+
"If-Match": etag
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
const dist = result.Distribution || result;
|
|
246
|
+
return {
|
|
247
|
+
Distribution: {
|
|
248
|
+
Id: dist.Id,
|
|
249
|
+
ARN: dist.ARN,
|
|
250
|
+
Status: dist.Status,
|
|
251
|
+
DomainName: dist.DomainName,
|
|
252
|
+
Aliases: dist.DistributionConfig?.Aliases?.Items || [],
|
|
253
|
+
Enabled: dist.DistributionConfig?.Enabled === "true" || dist.DistributionConfig?.Enabled === true
|
|
254
|
+
},
|
|
255
|
+
ETag: result.ETag || ""
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
async removeCustomErrorResponses(distributionId) {
|
|
259
|
+
return this.updateCustomErrorResponses({
|
|
260
|
+
distributionId,
|
|
261
|
+
customErrorResponses: []
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
async updateDistribution(options) {
|
|
265
|
+
const { distributionId, aliases, certificateArn, comment } = options;
|
|
266
|
+
const getResult = await this.client.request({
|
|
267
|
+
service: "cloudfront",
|
|
268
|
+
region: "us-east-1",
|
|
269
|
+
method: "GET",
|
|
270
|
+
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
271
|
+
returnHeaders: true
|
|
272
|
+
});
|
|
273
|
+
const etag = getResult.headers?.etag || getResult.headers?.ETag || "";
|
|
274
|
+
const currentConfig = getResult.body?.DistributionConfig || (getResult.body?.Enabled !== undefined ? getResult.body : undefined) || getResult.DistributionConfig;
|
|
275
|
+
if (!currentConfig || currentConfig.Enabled === undefined) {
|
|
276
|
+
throw new Error("Failed to get current distribution config");
|
|
277
|
+
}
|
|
278
|
+
if (aliases && aliases.length > 0) {
|
|
279
|
+
currentConfig.Aliases = {
|
|
280
|
+
Quantity: aliases.length,
|
|
281
|
+
Items: { Item: aliases }
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
if (certificateArn) {
|
|
285
|
+
currentConfig.ViewerCertificate = {
|
|
286
|
+
ACMCertificateArn: certificateArn,
|
|
287
|
+
SSLSupportMethod: "sni-only",
|
|
288
|
+
MinimumProtocolVersion: "TLSv1.2_2021",
|
|
289
|
+
CertificateSource: "acm"
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
if (comment) {
|
|
293
|
+
currentConfig.Comment = comment;
|
|
294
|
+
}
|
|
295
|
+
const configXml = this.buildDistributionConfigXml(currentConfig);
|
|
296
|
+
const result = await this.client.request({
|
|
297
|
+
service: "cloudfront",
|
|
298
|
+
region: "us-east-1",
|
|
299
|
+
method: "PUT",
|
|
300
|
+
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
301
|
+
body: configXml,
|
|
302
|
+
headers: {
|
|
303
|
+
"Content-Type": "application/xml",
|
|
304
|
+
"If-Match": etag
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
const dist = result.Distribution || result;
|
|
308
|
+
return {
|
|
309
|
+
Distribution: {
|
|
310
|
+
Id: dist.Id,
|
|
311
|
+
ARN: dist.ARN,
|
|
312
|
+
Status: dist.Status,
|
|
313
|
+
DomainName: dist.DomainName,
|
|
314
|
+
Aliases: aliases ? { Quantity: aliases.length, Items: aliases } : { Quantity: 0, Items: [] },
|
|
315
|
+
Enabled: dist.DistributionConfig?.Enabled === "true" || dist.DistributionConfig?.Enabled === true
|
|
316
|
+
},
|
|
317
|
+
ETag: result.ETag || ""
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
buildDistributionConfigXml(config) {
|
|
321
|
+
const escapeXml = (str) => {
|
|
322
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
323
|
+
};
|
|
324
|
+
const arrayChildNames = {
|
|
325
|
+
Items: "",
|
|
326
|
+
Methods: "Method",
|
|
327
|
+
Headers: "Name",
|
|
328
|
+
Cookies: "Name",
|
|
329
|
+
QueryStringCacheKeys: "Name",
|
|
330
|
+
TrustedKeyGroups: "KeyGroup",
|
|
331
|
+
TrustedSigners: "AwsAccountNumber",
|
|
332
|
+
LambdaFunctionAssociations: "LambdaFunctionAssociation",
|
|
333
|
+
FunctionAssociations: "FunctionAssociation",
|
|
334
|
+
CacheBehaviors: "CacheBehavior",
|
|
335
|
+
CustomErrorResponses: "CustomErrorResponse",
|
|
336
|
+
GeoRestriction: "Location"
|
|
337
|
+
};
|
|
338
|
+
const itemsChildNames = {
|
|
339
|
+
Origins: "Origin",
|
|
340
|
+
Aliases: "CNAME",
|
|
341
|
+
AllowedMethods: "Method",
|
|
342
|
+
CachedMethods: "Method",
|
|
343
|
+
CustomErrorResponses: "CustomErrorResponse",
|
|
344
|
+
CacheBehaviors: "CacheBehavior"
|
|
345
|
+
};
|
|
346
|
+
const buildXmlElement = (name, value, indent = "", parentContext = "") => {
|
|
347
|
+
if (value === null || value === undefined) {
|
|
348
|
+
return "";
|
|
349
|
+
}
|
|
350
|
+
if (name.startsWith("@_") || name === "?xml") {
|
|
351
|
+
return "";
|
|
352
|
+
}
|
|
353
|
+
if (typeof value === "boolean") {
|
|
354
|
+
return `${indent}<${name}>${value}</${name}>
|
|
355
|
+
`;
|
|
356
|
+
}
|
|
357
|
+
if (typeof value === "number" || typeof value === "string") {
|
|
358
|
+
return `${indent}<${name}>${escapeXml(String(value))}</${name}>
|
|
359
|
+
`;
|
|
360
|
+
}
|
|
361
|
+
if (Array.isArray(value)) {
|
|
362
|
+
const childName = arrayChildNames[name] || name.replace(/s$/, "");
|
|
363
|
+
return value.map((item) => buildXmlElement(childName, item, indent, name)).join("");
|
|
364
|
+
}
|
|
365
|
+
if (typeof value === "object") {
|
|
366
|
+
if (name === "Items") {
|
|
367
|
+
const childElementName = itemsChildNames[parentContext] || "";
|
|
368
|
+
const keys = Object.keys(value).filter((k) => !k.startsWith("@_"));
|
|
369
|
+
if (keys.length === 1 && !Array.isArray(value[keys[0]])) {
|
|
370
|
+
const childKey = keys[0];
|
|
371
|
+
const childValue = value[childKey];
|
|
372
|
+
if (typeof childValue === "string") {
|
|
373
|
+
return `${indent}<Items>
|
|
374
|
+
${indent} <${childKey}>${escapeXml(childValue)}</${childKey}>
|
|
375
|
+
${indent}</Items>
|
|
376
|
+
`;
|
|
377
|
+
} else if (typeof childValue === "object" && !Array.isArray(childValue)) {
|
|
378
|
+
return `${indent}<Items>
|
|
379
|
+
${buildXmlElement(childKey, childValue, indent + " ", name)}${indent}</Items>
|
|
380
|
+
`;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (keys.length === 1 && Array.isArray(value[keys[0]])) {
|
|
384
|
+
const childKey = keys[0];
|
|
385
|
+
const childArray = value[childKey];
|
|
386
|
+
let children2 = "";
|
|
387
|
+
for (const item of childArray) {
|
|
388
|
+
if (typeof item === "string") {
|
|
389
|
+
children2 += `${indent} <${childKey}>${escapeXml(item)}</${childKey}>
|
|
390
|
+
`;
|
|
391
|
+
} else {
|
|
392
|
+
children2 += buildXmlElement(childKey, item, indent + " ", name);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return `${indent}<Items>
|
|
396
|
+
${children2}${indent}</Items>
|
|
397
|
+
`;
|
|
398
|
+
}
|
|
399
|
+
if (Array.isArray(value)) {
|
|
400
|
+
let children2 = "";
|
|
401
|
+
const childName = childElementName || "Item";
|
|
402
|
+
for (const item of value) {
|
|
403
|
+
if (typeof item === "string") {
|
|
404
|
+
children2 += `${indent} <${childName}>${escapeXml(item)}</${childName}>
|
|
405
|
+
`;
|
|
406
|
+
} else {
|
|
407
|
+
children2 += buildXmlElement(childName, item, indent + " ", name);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return `${indent}<Items>
|
|
411
|
+
${children2}${indent}</Items>
|
|
412
|
+
`;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
let children = "";
|
|
416
|
+
for (const [key, val] of Object.entries(value)) {
|
|
417
|
+
if (!key.startsWith("@_")) {
|
|
418
|
+
children += buildXmlElement(key, val, indent + " ", name);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (children === "") {
|
|
422
|
+
return `${indent}<${name}/>
|
|
423
|
+
`;
|
|
424
|
+
}
|
|
425
|
+
return `${indent}<${name}>
|
|
426
|
+
${children}${indent}</${name}>
|
|
427
|
+
`;
|
|
428
|
+
}
|
|
429
|
+
return "";
|
|
430
|
+
};
|
|
431
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
432
|
+
<DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
|
|
433
|
+
${Object.entries(config).filter(([k]) => !k.startsWith("@_")).map(([key, val]) => buildXmlElement(key, val, " ", "DistributionConfig")).join("")}</DistributionConfig>`;
|
|
434
|
+
}
|
|
435
|
+
async ensureDynamicHttpMethods(distributionId) {
|
|
436
|
+
const getResult = await this.client.request({
|
|
437
|
+
service: "cloudfront",
|
|
438
|
+
region: "us-east-1",
|
|
439
|
+
method: "GET",
|
|
440
|
+
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
441
|
+
returnHeaders: true
|
|
442
|
+
});
|
|
443
|
+
const etag = getResult.headers?.etag || getResult.headers?.ETag || "";
|
|
444
|
+
const currentConfig = getResult.body?.DistributionConfig || getResult.DistributionConfig || getResult.body;
|
|
445
|
+
if (!currentConfig) {
|
|
446
|
+
throw new Error("Failed to get current distribution config");
|
|
447
|
+
}
|
|
448
|
+
let changed = false;
|
|
449
|
+
const dynamicMethods = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"];
|
|
450
|
+
const parseAllowedMethods = (allowedMethods) => {
|
|
451
|
+
if (!allowedMethods)
|
|
452
|
+
return [];
|
|
453
|
+
const items = allowedMethods.Items;
|
|
454
|
+
if (!items)
|
|
455
|
+
return [];
|
|
456
|
+
if (Array.isArray(items))
|
|
457
|
+
return items.map(String);
|
|
458
|
+
if (typeof items === "object" && items.Method) {
|
|
459
|
+
const method = items.Method;
|
|
460
|
+
return Array.isArray(method) ? method.map(String) : [String(method)];
|
|
461
|
+
}
|
|
462
|
+
if (items.Item) {
|
|
463
|
+
return Array.isArray(items.Item) ? items.Item.map(String) : [String(items.Item)];
|
|
464
|
+
}
|
|
465
|
+
return [];
|
|
466
|
+
};
|
|
467
|
+
const defaultBehavior = currentConfig.DefaultCacheBehavior;
|
|
468
|
+
if (defaultBehavior) {
|
|
469
|
+
const allowed = parseAllowedMethods(defaultBehavior.AllowedMethods);
|
|
470
|
+
if (!allowed.includes("POST")) {
|
|
471
|
+
defaultBehavior.AllowedMethods = {
|
|
472
|
+
Quantity: dynamicMethods.length,
|
|
473
|
+
Items: { Method: dynamicMethods },
|
|
474
|
+
CachedMethods: {
|
|
475
|
+
Quantity: 2,
|
|
476
|
+
Items: { Method: ["GET", "HEAD"] }
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
changed = true;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if (!changed) {
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
const configXml = this.buildDistributionConfigXml(currentConfig);
|
|
486
|
+
await this.client.request({
|
|
487
|
+
service: "cloudfront",
|
|
488
|
+
region: "us-east-1",
|
|
489
|
+
method: "PUT",
|
|
490
|
+
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
491
|
+
body: configXml,
|
|
492
|
+
headers: {
|
|
493
|
+
"Content-Type": "application/xml",
|
|
494
|
+
"If-Match": etag
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
async addAliases(distributionId, aliases, certificateArn) {
|
|
500
|
+
return this.updateDistribution({
|
|
501
|
+
distributionId,
|
|
502
|
+
aliases,
|
|
503
|
+
certificateArn
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
async createFunction(options) {
|
|
507
|
+
const { name, code, comment = "", runtime = "cloudfront-js-2.0" } = options;
|
|
508
|
+
const functionXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
509
|
+
<CreateFunctionRequest xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
|
|
510
|
+
<Name>${name}</Name>
|
|
511
|
+
<FunctionConfig>
|
|
512
|
+
<Comment>${comment}</Comment>
|
|
513
|
+
<Runtime>${runtime}</Runtime>
|
|
514
|
+
</FunctionConfig>
|
|
515
|
+
<FunctionCode>${Buffer.from(code).toString("base64")}</FunctionCode>
|
|
516
|
+
</CreateFunctionRequest>`;
|
|
517
|
+
const result = await this.client.request({
|
|
518
|
+
service: "cloudfront",
|
|
519
|
+
region: "us-east-1",
|
|
520
|
+
method: "POST",
|
|
521
|
+
path: "/2020-05-31/function",
|
|
522
|
+
body: functionXml,
|
|
523
|
+
headers: {
|
|
524
|
+
"Content-Type": "application/xml"
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
const func = result.FunctionSummary || result;
|
|
528
|
+
return {
|
|
529
|
+
FunctionARN: func.FunctionMetadata?.FunctionARN || func.FunctionARN,
|
|
530
|
+
Name: func.Name || name,
|
|
531
|
+
Stage: func.FunctionMetadata?.Stage || "DEVELOPMENT",
|
|
532
|
+
ETag: result.ETag || ""
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
async listFunctions() {
|
|
536
|
+
const result = await this.client.request({
|
|
537
|
+
service: "cloudfront",
|
|
538
|
+
region: "us-east-1",
|
|
539
|
+
method: "GET",
|
|
540
|
+
path: "/2020-05-31/function"
|
|
541
|
+
});
|
|
542
|
+
const functions = [];
|
|
543
|
+
const items = result.FunctionList?.Items?.FunctionSummary;
|
|
544
|
+
if (items) {
|
|
545
|
+
const list = Array.isArray(items) ? items : [items];
|
|
546
|
+
for (const item of list) {
|
|
547
|
+
functions.push({
|
|
548
|
+
Name: item.Name,
|
|
549
|
+
FunctionARN: item.FunctionMetadata?.FunctionARN,
|
|
550
|
+
Stage: item.FunctionMetadata?.Stage,
|
|
551
|
+
CreatedTime: item.FunctionMetadata?.CreatedTime,
|
|
552
|
+
LastModifiedTime: item.FunctionMetadata?.LastModifiedTime
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return functions;
|
|
557
|
+
}
|
|
558
|
+
async getFunction(name, stage = "LIVE") {
|
|
559
|
+
try {
|
|
560
|
+
const result = await this.client.request({
|
|
561
|
+
service: "cloudfront",
|
|
562
|
+
region: "us-east-1",
|
|
563
|
+
method: "GET",
|
|
564
|
+
path: `/2020-05-31/function/${name}`,
|
|
565
|
+
queryParams: { Stage: stage },
|
|
566
|
+
returnHeaders: true
|
|
567
|
+
});
|
|
568
|
+
const func = result.body?.FunctionSummary || result.FunctionSummary || result.body || result;
|
|
569
|
+
return {
|
|
570
|
+
FunctionARN: func.FunctionMetadata?.FunctionARN,
|
|
571
|
+
Name: func.Name || name,
|
|
572
|
+
Stage: func.FunctionMetadata?.Stage || stage,
|
|
573
|
+
ETag: result.headers?.etag || result.ETag || "",
|
|
574
|
+
FunctionCode: func.FunctionCode
|
|
575
|
+
};
|
|
576
|
+
} catch (err) {
|
|
577
|
+
if (err.message?.includes("404") || err.message?.includes("NoSuchFunctionExists")) {
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
throw err;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
async publishFunction(nameOrOptions, etag) {
|
|
584
|
+
let name;
|
|
585
|
+
let functionETag;
|
|
586
|
+
if (typeof nameOrOptions === "object") {
|
|
587
|
+
name = nameOrOptions.Name;
|
|
588
|
+
functionETag = nameOrOptions.IfMatch;
|
|
589
|
+
} else {
|
|
590
|
+
name = nameOrOptions;
|
|
591
|
+
functionETag = etag;
|
|
592
|
+
}
|
|
593
|
+
if (!functionETag) {
|
|
594
|
+
const func2 = await this.getFunction(name, "DEVELOPMENT");
|
|
595
|
+
if (!func2) {
|
|
596
|
+
throw new Error(`Function ${name} not found`);
|
|
597
|
+
}
|
|
598
|
+
functionETag = func2.ETag;
|
|
599
|
+
}
|
|
600
|
+
const result = await this.client.request({
|
|
601
|
+
service: "cloudfront",
|
|
602
|
+
region: "us-east-1",
|
|
603
|
+
method: "POST",
|
|
604
|
+
path: `/2020-05-31/function/${name}/publish`,
|
|
605
|
+
headers: {
|
|
606
|
+
"If-Match": functionETag
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
const func = result.FunctionSummary || result;
|
|
610
|
+
return {
|
|
611
|
+
FunctionARN: func.FunctionMetadata?.FunctionARN,
|
|
612
|
+
Stage: func.FunctionMetadata?.Stage || "LIVE",
|
|
613
|
+
FunctionSummary: func
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
async describeFunction(options) {
|
|
617
|
+
const { Name, Stage = "DEVELOPMENT" } = options;
|
|
618
|
+
const result = await this.client.request({
|
|
619
|
+
service: "cloudfront",
|
|
620
|
+
region: "us-east-1",
|
|
621
|
+
method: "GET",
|
|
622
|
+
path: `/2020-05-31/function/${Name}/describe`,
|
|
623
|
+
queryParams: { Stage },
|
|
624
|
+
returnHeaders: true
|
|
625
|
+
});
|
|
626
|
+
return {
|
|
627
|
+
ETag: result.headers?.etag || result.ETag || "",
|
|
628
|
+
FunctionSummary: result.body?.FunctionSummary || result.FunctionSummary || result.body || result
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
async updateFunction(options) {
|
|
632
|
+
const { Name, FunctionCode, FunctionConfig, IfMatch } = options;
|
|
633
|
+
const functionXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
634
|
+
<UpdateFunctionRequest xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
|
|
635
|
+
<FunctionConfig>
|
|
636
|
+
<Comment>${FunctionConfig.Comment}</Comment>
|
|
637
|
+
<Runtime>${FunctionConfig.Runtime}</Runtime>
|
|
638
|
+
</FunctionConfig>
|
|
639
|
+
<FunctionCode>${Buffer.from(FunctionCode).toString("base64")}</FunctionCode>
|
|
640
|
+
</UpdateFunctionRequest>`;
|
|
641
|
+
const result = await this.client.request({
|
|
642
|
+
service: "cloudfront",
|
|
643
|
+
region: "us-east-1",
|
|
644
|
+
method: "PUT",
|
|
645
|
+
path: `/2020-05-31/function/${Name}`,
|
|
646
|
+
body: functionXml,
|
|
647
|
+
headers: {
|
|
648
|
+
"Content-Type": "application/xml",
|
|
649
|
+
"If-Match": IfMatch
|
|
650
|
+
},
|
|
651
|
+
returnHeaders: true
|
|
652
|
+
});
|
|
653
|
+
return {
|
|
654
|
+
ETag: result.headers?.etag || result.ETag || "",
|
|
655
|
+
FunctionSummary: result.body?.FunctionSummary || result.FunctionSummary || result.body || result
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
async deleteFunction(name, etag) {
|
|
659
|
+
let functionETag = etag;
|
|
660
|
+
if (!functionETag) {
|
|
661
|
+
const func = await this.getFunction(name, "DEVELOPMENT");
|
|
662
|
+
if (!func) {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
functionETag = func.ETag;
|
|
666
|
+
}
|
|
667
|
+
await this.client.request({
|
|
668
|
+
service: "cloudfront",
|
|
669
|
+
region: "us-east-1",
|
|
670
|
+
method: "DELETE",
|
|
671
|
+
path: `/2020-05-31/function/${name}`,
|
|
672
|
+
headers: {
|
|
673
|
+
"If-Match": functionETag
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
async createIndexRewriteFunction(name) {
|
|
678
|
+
const code = `function handler(event) {
|
|
679
|
+
const request = event.request;
|
|
680
|
+
var uri = request.uri;
|
|
681
|
+
|
|
682
|
+
// Check if the request is for a directory (ends with /)
|
|
683
|
+
if (uri.endsWith('/')) {
|
|
684
|
+
request.uri += 'index.html';
|
|
685
|
+
}
|
|
686
|
+
// Check if the request doesn't have a file extension
|
|
687
|
+
else if (!uri.includes('.')) {
|
|
688
|
+
// Add trailing slash to redirect to directory
|
|
689
|
+
request.uri += '/index.html';
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
return request;
|
|
693
|
+
}`;
|
|
694
|
+
return this.createFunction({
|
|
695
|
+
name,
|
|
696
|
+
code,
|
|
697
|
+
comment: "Rewrite directory requests to index.html for S3 static sites",
|
|
698
|
+
runtime: "cloudfront-js-2.0"
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
async listOriginAccessControls() {
|
|
702
|
+
const result = await this.client.request({
|
|
703
|
+
service: "cloudfront",
|
|
704
|
+
region: "us-east-1",
|
|
705
|
+
method: "GET",
|
|
706
|
+
path: "/2020-05-31/origin-access-control"
|
|
707
|
+
});
|
|
708
|
+
const items = [];
|
|
709
|
+
if (result.OriginAccessControlList?.Items?.OriginAccessControlSummary) {
|
|
710
|
+
const summaries = Array.isArray(result.OriginAccessControlList.Items.OriginAccessControlSummary) ? result.OriginAccessControlList.Items.OriginAccessControlSummary : [result.OriginAccessControlList.Items.OriginAccessControlSummary];
|
|
711
|
+
items.push(...summaries.map((item) => ({
|
|
712
|
+
Id: item.Id,
|
|
713
|
+
Name: item.Name,
|
|
714
|
+
Description: item.Description,
|
|
715
|
+
SigningProtocol: item.SigningProtocol,
|
|
716
|
+
SigningBehavior: item.SigningBehavior,
|
|
717
|
+
OriginAccessControlOriginType: item.OriginAccessControlOriginType
|
|
718
|
+
})));
|
|
719
|
+
}
|
|
720
|
+
return items;
|
|
721
|
+
}
|
|
722
|
+
async createOriginAccessControl(options) {
|
|
723
|
+
const {
|
|
724
|
+
name,
|
|
725
|
+
description = `OAC for ${name}`,
|
|
726
|
+
signingProtocol = "sigv4",
|
|
727
|
+
signingBehavior = "always",
|
|
728
|
+
originType = "s3"
|
|
729
|
+
} = options;
|
|
730
|
+
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
731
|
+
<OriginAccessControlConfig xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
|
|
732
|
+
<Name>${name}</Name>
|
|
733
|
+
<Description>${description}</Description>
|
|
734
|
+
<SigningProtocol>${signingProtocol}</SigningProtocol>
|
|
735
|
+
<SigningBehavior>${signingBehavior}</SigningBehavior>
|
|
736
|
+
<OriginAccessControlOriginType>${originType}</OriginAccessControlOriginType>
|
|
737
|
+
</OriginAccessControlConfig>`;
|
|
738
|
+
const result = await this.client.request({
|
|
739
|
+
service: "cloudfront",
|
|
740
|
+
region: "us-east-1",
|
|
741
|
+
method: "POST",
|
|
742
|
+
path: "/2020-05-31/origin-access-control",
|
|
743
|
+
body,
|
|
744
|
+
headers: {
|
|
745
|
+
"Content-Type": "application/xml"
|
|
746
|
+
},
|
|
747
|
+
returnHeaders: true
|
|
748
|
+
});
|
|
749
|
+
const oac = result.body?.OriginAccessControl || result.OriginAccessControl || result.body || result;
|
|
750
|
+
return {
|
|
751
|
+
Id: oac.Id,
|
|
752
|
+
Name: oac.OriginAccessControlConfig?.Name || name,
|
|
753
|
+
Description: oac.OriginAccessControlConfig?.Description || description,
|
|
754
|
+
SigningProtocol: oac.OriginAccessControlConfig?.SigningProtocol || signingProtocol,
|
|
755
|
+
SigningBehavior: oac.OriginAccessControlConfig?.SigningBehavior || signingBehavior,
|
|
756
|
+
OriginAccessControlOriginType: oac.OriginAccessControlConfig?.OriginAccessControlOriginType || originType,
|
|
757
|
+
ETag: result.headers?.etag || result.ETag || ""
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
async findOrCreateOriginAccessControl(name) {
|
|
761
|
+
const oacs = await this.listOriginAccessControls();
|
|
762
|
+
const existing = oacs.find((oac) => oac.Name === name);
|
|
763
|
+
if (existing) {
|
|
764
|
+
return { Id: existing.Id, Name: existing.Name, isNew: false };
|
|
765
|
+
}
|
|
766
|
+
const created = await this.createOriginAccessControl({ name });
|
|
767
|
+
return { Id: created.Id, Name: created.Name, isNew: true };
|
|
768
|
+
}
|
|
769
|
+
async createDistributionForS3(options) {
|
|
770
|
+
const {
|
|
771
|
+
bucketName,
|
|
772
|
+
bucketRegion,
|
|
773
|
+
originAccessControlId,
|
|
774
|
+
aliases = [],
|
|
775
|
+
certificateArn,
|
|
776
|
+
defaultRootObject = "index.html",
|
|
777
|
+
comment = `Distribution for ${bucketName}`,
|
|
778
|
+
priceClass = "PriceClass_100",
|
|
779
|
+
enabled = true
|
|
780
|
+
} = options;
|
|
781
|
+
const originId = `S3-${bucketName}`;
|
|
782
|
+
const s3DomainName = `${bucketName}.s3.${bucketRegion}.amazonaws.com`;
|
|
783
|
+
const callerReference = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
784
|
+
let aliasesXml = "<Aliases><Quantity>0</Quantity></Aliases>";
|
|
785
|
+
if (aliases.length > 0) {
|
|
786
|
+
aliasesXml = `<Aliases>
|
|
787
|
+
<Quantity>${aliases.length}</Quantity>
|
|
788
|
+
<Items>
|
|
789
|
+
${aliases.map((a) => `<CNAME>${a}</CNAME>`).join(`
|
|
790
|
+
`)}
|
|
791
|
+
</Items>
|
|
792
|
+
</Aliases>`;
|
|
793
|
+
}
|
|
794
|
+
let viewerCertificateXml = `<ViewerCertificate>
|
|
795
|
+
<CloudFrontDefaultCertificate>true</CloudFrontDefaultCertificate>
|
|
796
|
+
</ViewerCertificate>`;
|
|
797
|
+
if (certificateArn && aliases.length > 0) {
|
|
798
|
+
viewerCertificateXml = `<ViewerCertificate>
|
|
799
|
+
<ACMCertificateArn>${certificateArn}</ACMCertificateArn>
|
|
800
|
+
<SSLSupportMethod>sni-only</SSLSupportMethod>
|
|
801
|
+
<MinimumProtocolVersion>TLSv1.2_2021</MinimumProtocolVersion>
|
|
802
|
+
<CertificateSource>acm</CertificateSource>
|
|
803
|
+
</ViewerCertificate>`;
|
|
804
|
+
}
|
|
805
|
+
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
806
|
+
<DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
|
|
807
|
+
<CallerReference>${callerReference}</CallerReference>
|
|
808
|
+
<Comment>${comment}</Comment>
|
|
809
|
+
<DefaultRootObject>${defaultRootObject}</DefaultRootObject>
|
|
810
|
+
<Origins>
|
|
811
|
+
<Quantity>1</Quantity>
|
|
812
|
+
<Items>
|
|
813
|
+
<Origin>
|
|
814
|
+
<Id>${originId}</Id>
|
|
815
|
+
<DomainName>${s3DomainName}</DomainName>
|
|
816
|
+
<OriginPath></OriginPath>
|
|
817
|
+
<S3OriginConfig>
|
|
818
|
+
<OriginAccessIdentity></OriginAccessIdentity>
|
|
819
|
+
</S3OriginConfig>
|
|
820
|
+
<OriginAccessControlId>${originAccessControlId}</OriginAccessControlId>
|
|
821
|
+
</Origin>
|
|
822
|
+
</Items>
|
|
823
|
+
</Origins>
|
|
824
|
+
<DefaultCacheBehavior>
|
|
825
|
+
<TargetOriginId>${originId}</TargetOriginId>
|
|
826
|
+
<ViewerProtocolPolicy>redirect-to-https</ViewerProtocolPolicy>
|
|
827
|
+
<AllowedMethods>
|
|
828
|
+
<Quantity>2</Quantity>
|
|
829
|
+
<Items>
|
|
830
|
+
<Method>GET</Method>
|
|
831
|
+
<Method>HEAD</Method>
|
|
832
|
+
</Items>
|
|
833
|
+
<CachedMethods>
|
|
834
|
+
<Quantity>2</Quantity>
|
|
835
|
+
<Items>
|
|
836
|
+
<Method>GET</Method>
|
|
837
|
+
<Method>HEAD</Method>
|
|
838
|
+
</Items>
|
|
839
|
+
</CachedMethods>
|
|
840
|
+
</AllowedMethods>
|
|
841
|
+
<Compress>true</Compress>
|
|
842
|
+
<CachePolicyId>658327ea-f89d-4fab-a63d-7e88639e58f6</CachePolicyId>
|
|
843
|
+
</DefaultCacheBehavior>
|
|
844
|
+
${aliasesXml}
|
|
845
|
+
${viewerCertificateXml}
|
|
846
|
+
<PriceClass>${priceClass}</PriceClass>
|
|
847
|
+
<Enabled>${enabled}</Enabled>
|
|
848
|
+
<HttpVersion>http2and3</HttpVersion>
|
|
849
|
+
<IsIPV6Enabled>true</IsIPV6Enabled>
|
|
850
|
+
<CustomErrorResponses>
|
|
851
|
+
<Quantity>1</Quantity>
|
|
852
|
+
<Items>
|
|
853
|
+
<CustomErrorResponse>
|
|
854
|
+
<ErrorCode>403</ErrorCode>
|
|
855
|
+
<ResponsePagePath>/index.html</ResponsePagePath>
|
|
856
|
+
<ResponseCode>200</ResponseCode>
|
|
857
|
+
<ErrorCachingMinTTL>300</ErrorCachingMinTTL>
|
|
858
|
+
</CustomErrorResponse>
|
|
859
|
+
</Items>
|
|
860
|
+
</CustomErrorResponses>
|
|
861
|
+
</DistributionConfig>`;
|
|
862
|
+
const result = await this.client.request({
|
|
863
|
+
service: "cloudfront",
|
|
864
|
+
region: "us-east-1",
|
|
865
|
+
method: "POST",
|
|
866
|
+
path: "/2020-05-31/distribution",
|
|
867
|
+
body,
|
|
868
|
+
headers: {
|
|
869
|
+
"Content-Type": "application/xml"
|
|
870
|
+
},
|
|
871
|
+
returnHeaders: true
|
|
872
|
+
});
|
|
873
|
+
const dist = result.body?.Distribution || result.Distribution || result.body || result;
|
|
874
|
+
return {
|
|
875
|
+
Id: dist.Id,
|
|
876
|
+
ARN: dist.ARN,
|
|
877
|
+
DomainName: dist.DomainName,
|
|
878
|
+
Status: dist.Status,
|
|
879
|
+
ETag: result.headers?.etag || result.ETag || ""
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
static getS3BucketPolicyForCloudFront(bucketName, distributionArn) {
|
|
883
|
+
return {
|
|
884
|
+
Version: "2012-10-17",
|
|
885
|
+
Statement: [
|
|
886
|
+
{
|
|
887
|
+
Sid: "AllowCloudFrontServicePrincipal",
|
|
888
|
+
Effect: "Allow",
|
|
889
|
+
Principal: {
|
|
890
|
+
Service: "cloudfront.amazonaws.com"
|
|
891
|
+
},
|
|
892
|
+
Action: "s3:GetObject",
|
|
893
|
+
Resource: `arn:aws:s3:::${bucketName}/*`,
|
|
894
|
+
Condition: {
|
|
895
|
+
StringEquals: {
|
|
896
|
+
"AWS:SourceArn": distributionArn
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
]
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
async waitForDistributionDeployed(distributionId, maxAttempts = 60) {
|
|
904
|
+
for (let i = 0;i < maxAttempts; i++) {
|
|
905
|
+
const dist = await this.getDistribution(distributionId);
|
|
906
|
+
if (dist.Status === "Deployed") {
|
|
907
|
+
return true;
|
|
908
|
+
}
|
|
909
|
+
await new Promise((resolve) => setTimeout(resolve, 30000));
|
|
910
|
+
}
|
|
911
|
+
return false;
|
|
912
|
+
}
|
|
913
|
+
async disableDistribution(distributionId) {
|
|
914
|
+
const getResult = await this.client.request({
|
|
915
|
+
service: "cloudfront",
|
|
916
|
+
region: "us-east-1",
|
|
917
|
+
method: "GET",
|
|
918
|
+
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
919
|
+
returnHeaders: true
|
|
920
|
+
});
|
|
921
|
+
const etag = getResult.headers?.etag || getResult.headers?.ETag || "";
|
|
922
|
+
const currentConfig = getResult.body?.DistributionConfig || (getResult.body?.Enabled !== undefined ? getResult.body : undefined) || getResult.DistributionConfig;
|
|
923
|
+
if (!currentConfig || currentConfig.Enabled === undefined) {
|
|
924
|
+
throw new Error("Failed to get current distribution config");
|
|
925
|
+
}
|
|
926
|
+
currentConfig.Enabled = false;
|
|
927
|
+
const configXml = this.buildDistributionConfigXml(currentConfig);
|
|
928
|
+
const result = await this.client.request({
|
|
929
|
+
service: "cloudfront",
|
|
930
|
+
region: "us-east-1",
|
|
931
|
+
method: "PUT",
|
|
932
|
+
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
933
|
+
body: configXml,
|
|
934
|
+
headers: {
|
|
935
|
+
"Content-Type": "application/xml",
|
|
936
|
+
"If-Match": etag
|
|
937
|
+
},
|
|
938
|
+
returnHeaders: true
|
|
939
|
+
});
|
|
940
|
+
return { ETag: result.headers?.etag || result.headers?.ETag || result.ETag || "" };
|
|
941
|
+
}
|
|
942
|
+
async deleteDistribution(distributionId, etag) {
|
|
943
|
+
let etagToUse = etag || "";
|
|
944
|
+
if (!etagToUse) {
|
|
945
|
+
const getResult = await this.client.request({
|
|
946
|
+
service: "cloudfront",
|
|
947
|
+
region: "us-east-1",
|
|
948
|
+
method: "GET",
|
|
949
|
+
path: `/2020-05-31/distribution/${distributionId}`,
|
|
950
|
+
returnHeaders: true
|
|
951
|
+
});
|
|
952
|
+
etagToUse = getResult.headers?.etag || getResult.headers?.ETag || "";
|
|
953
|
+
}
|
|
954
|
+
await this.client.request({
|
|
955
|
+
service: "cloudfront",
|
|
956
|
+
region: "us-east-1",
|
|
957
|
+
method: "DELETE",
|
|
958
|
+
path: `/2020-05-31/distribution/${distributionId}`,
|
|
959
|
+
headers: {
|
|
960
|
+
"If-Match": etagToUse
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
async waitForDistributionDisabled(distributionId, maxAttempts = 60) {
|
|
965
|
+
for (let i = 0;i < maxAttempts; i++) {
|
|
966
|
+
const dist = await this.getDistribution(distributionId);
|
|
967
|
+
if (dist.Status === "Deployed" && !dist.Enabled) {
|
|
968
|
+
return true;
|
|
969
|
+
}
|
|
970
|
+
await new Promise((resolve) => setTimeout(resolve, 30000));
|
|
971
|
+
}
|
|
972
|
+
return false;
|
|
973
|
+
}
|
|
974
|
+
async removeAlias(distributionId, alias) {
|
|
975
|
+
const getResult = await this.client.request({
|
|
976
|
+
service: "cloudfront",
|
|
977
|
+
region: "us-east-1",
|
|
978
|
+
method: "GET",
|
|
979
|
+
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
980
|
+
returnHeaders: true
|
|
981
|
+
});
|
|
982
|
+
const etag = getResult.headers?.etag || getResult.headers?.ETag || "";
|
|
983
|
+
const currentConfig = getResult.body?.DistributionConfig || (getResult.body?.Enabled !== undefined ? getResult.body : undefined) || getResult.DistributionConfig;
|
|
984
|
+
if (!currentConfig || currentConfig.Enabled === undefined) {
|
|
985
|
+
throw new Error("Failed to get current distribution config");
|
|
986
|
+
}
|
|
987
|
+
let items = [];
|
|
988
|
+
if (currentConfig.Aliases?.Items) {
|
|
989
|
+
if (Array.isArray(currentConfig.Aliases.Items)) {
|
|
990
|
+
items = currentConfig.Aliases.Items;
|
|
991
|
+
} else if (typeof currentConfig.Aliases.Items === "object") {
|
|
992
|
+
const cname = currentConfig.Aliases.Items.CNAME;
|
|
993
|
+
if (typeof cname === "string") {
|
|
994
|
+
items = [cname];
|
|
995
|
+
} else if (Array.isArray(cname)) {
|
|
996
|
+
items = cname;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
if (items.length === 0) {
|
|
1001
|
+
throw new Error(`Distribution has no aliases to remove`);
|
|
1002
|
+
}
|
|
1003
|
+
const newItems = items.filter((a) => a !== alias);
|
|
1004
|
+
if (newItems.length === items.length) {
|
|
1005
|
+
throw new Error(`Alias ${alias} not found in distribution`);
|
|
1006
|
+
}
|
|
1007
|
+
currentConfig.Aliases.Quantity = newItems.length;
|
|
1008
|
+
currentConfig.Aliases.Items = newItems.length > 0 ? newItems : undefined;
|
|
1009
|
+
if (newItems.length === 0) {
|
|
1010
|
+
currentConfig.ViewerCertificate = {
|
|
1011
|
+
CloudFrontDefaultCertificate: true,
|
|
1012
|
+
MinimumProtocolVersion: "TLSv1.2_2021"
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
const configXml = this.buildDistributionConfigXml(currentConfig);
|
|
1016
|
+
const result = await this.client.request({
|
|
1017
|
+
service: "cloudfront",
|
|
1018
|
+
region: "us-east-1",
|
|
1019
|
+
method: "PUT",
|
|
1020
|
+
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
1021
|
+
body: configXml,
|
|
1022
|
+
headers: {
|
|
1023
|
+
"Content-Type": "application/xml",
|
|
1024
|
+
"If-Match": etag
|
|
1025
|
+
},
|
|
1026
|
+
returnHeaders: true
|
|
1027
|
+
});
|
|
1028
|
+
return { ETag: result.headers?.etag || result.headers?.ETag || result.ETag || "" };
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
export { CloudFrontClient };
|