@stacksjs/ts-cloud 0.9.3 → 0.9.4
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 +1 -269
- package/dist/bin/chunk-00kg80qg.js +1 -0
- package/dist/bin/{chunk-hzqw8xm5.js → chunk-1k5kszjp.js} +1 -1
- package/dist/bin/{chunk-9v39615t.js → chunk-21e0mavm.js} +1 -1
- package/dist/bin/{chunk-waayf9ay.js → chunk-2empps47.js} +1 -1
- package/dist/bin/{chunk-14cwr6eh.js → chunk-3vqfexc4.js} +1 -1
- package/dist/bin/chunk-5ectjq0b.js +1 -0
- package/dist/bin/{chunk-b5gd39s8.js → chunk-5erkxpt7.js} +4 -4
- package/dist/bin/{chunk-41x17hw1.js → chunk-5mrtzemj.js} +1 -1
- package/dist/bin/{chunk-p8q9bq9g.js → chunk-5n9fmabb.js} +1 -1
- package/dist/bin/{chunk-gqf6jsmy.js → chunk-9knpg30y.js} +1 -1
- package/dist/bin/{chunk-r3kv0vm9.js → chunk-ddk4rt4j.js} +1 -1
- package/dist/bin/{chunk-avm7tnwe.js → chunk-e0rkrq70.js} +1 -1
- package/dist/bin/{chunk-ry030a6b.js → chunk-etrb03mt.js} +1 -1
- package/dist/bin/{chunk-8v7ddyvf.js → chunk-k9t9wvms.js} +1 -1
- package/dist/bin/{chunk-92mea34a.js → chunk-m7dgskt1.js} +1 -1
- package/dist/bin/{chunk-3r4fb59r.js → chunk-mz0wbbsj.js} +1 -1
- package/dist/bin/{chunk-b9qbp8j1.js → chunk-qk9qgepa.js} +1 -1
- package/dist/bin/chunk-rxcvf10f.js +7364 -0
- package/dist/bin/{chunk-r9hjwcj9.js → chunk-tga3hhwk.js} +1 -1
- package/dist/bin/{chunk-ynjvbphf.js → chunk-tx0tqq2k.js} +1 -1
- package/dist/bin/{chunk-57y1zxbf.js → chunk-vm3q5gf2.js} +1 -1
- package/dist/bin/{chunk-bz828v8q.js → chunk-y7s5r8y6.js} +1 -1
- package/dist/bin/{chunk-sgyffpgm.js → chunk-zmegvjp8.js} +1 -1
- package/dist/bin/cli.js +14 -14
- package/dist/bin/dashboard-server.js +1 -1
- package/dist/chunk-0ccta6z6.js +6 -0
- package/dist/chunk-0k1cs6pp.js +1 -0
- package/dist/chunk-1egjn203.js +704 -0
- package/dist/chunk-2wx29qga.js +5 -0
- package/dist/chunk-5m5rqmv7.js +1 -0
- package/dist/chunk-8ea5022g.js +114 -0
- package/dist/chunk-8xqn3c0w.js +2 -0
- package/dist/chunk-b4esxhmw.js +2 -0
- package/dist/chunk-b8e5wzwn.js +44 -0
- package/dist/chunk-bggt4n6e.js +2 -0
- package/dist/chunk-cwkg37rs.js +37 -0
- package/dist/chunk-dskvqt67.js +7355 -0
- package/dist/chunk-gahsdmxf.js +1617 -0
- package/dist/chunk-gnv3r2jg.js +1 -0
- package/dist/chunk-hg3c48cp.js +34 -0
- package/dist/chunk-m5azeva3.js +2 -0
- package/dist/chunk-r3qf56b3.js +92 -0
- package/dist/chunk-sdqa9ghv.js +141 -0
- package/dist/chunk-svrvvcm7.js +1 -0
- package/dist/chunk-v0p3hpc3.js +60 -0
- package/dist/chunk-wdzjbecg.js +50 -0
- package/dist/chunk-ww8k3g7j.js +1 -0
- package/dist/chunk-xmqfc62w.js +1 -0
- package/dist/chunk-xrebj679.js +435 -0
- package/dist/chunk-xt3s24vr.js +2 -0
- package/dist/chunk-y21zk0pn.js +4 -0
- package/dist/deploy/index.js +1 -168
- package/dist/dns/index.js +1 -41
- package/dist/drivers/index.js +1 -154
- package/dist/index.js +100 -6129
- package/dist/protection/index.js +1 -89
- package/dist/push/index.js +1 -509
- package/dist/spend/index.js +1 -173
- package/dist/ui/404.html +1 -1
- package/dist/ui/accept-invitation.html +1 -1
- package/dist/ui/access-denied.html +1 -1
- package/dist/ui/account/automation.html +6 -6
- package/dist/ui/account/security.html +2 -2
- package/dist/ui/applications/compose.html +6 -6
- package/dist/ui/applications/new.html +2 -2
- package/dist/ui/data/backups.html +5 -5
- package/dist/ui/data/services.html +5 -5
- package/dist/ui/data/volumes.html +6 -6
- package/dist/ui/index.html +6 -6
- package/dist/ui/infrastructure/topology.html +5 -5
- package/dist/ui/integrations.html +1 -1
- package/dist/ui/operations/alerts.html +5 -5
- package/dist/ui/operations/configuration.html +5 -5
- package/dist/ui/operations/jobs.html +5 -5
- package/dist/ui/operations/maintenance.html +5 -5
- package/dist/ui/operations/observability.html +6 -6
- package/dist/ui/operations/previews.html +6 -6
- package/dist/ui/operations/queue.html +6 -6
- package/dist/ui/operations/regions.html +6 -6
- package/dist/ui/operations/releases.html +6 -6
- package/dist/ui/operations/spend.html +6 -6
- package/dist/ui/operations/workloads.html +6 -6
- package/dist/ui/security.html +1 -1
- package/dist/ui/server/actions.html +5 -5
- package/dist/ui/server/activity.html +1 -1
- package/dist/ui/server/capacity.html +6 -6
- package/dist/ui/server/database.html +6 -6
- package/dist/ui/server/deployments.html +6 -6
- package/dist/ui/server/diagnostics.html +2 -2
- package/dist/ui/server/firewall.html +6 -6
- package/dist/ui/server/fleet.html +6 -6
- package/dist/ui/server/logs.html +6 -6
- package/dist/ui/server/metrics.html +6 -6
- package/dist/ui/server/security.html +2 -2
- package/dist/ui/server/services.html +1 -1
- package/dist/ui/server/sites.html +6 -6
- package/dist/ui/server/ssh-keys.html +6 -6
- package/dist/ui/server/team.html +6 -6
- package/dist/ui/server/terminal.html +1 -1
- package/dist/ui/serverless/alarms.html +5 -5
- package/dist/ui/serverless/assets.html +2 -2
- package/dist/ui/serverless/cost.html +1 -1
- package/dist/ui/serverless/data.html +6 -6
- package/dist/ui/serverless/deployments.html +2 -2
- package/dist/ui/serverless/firewall.html +1 -1
- package/dist/ui/serverless/functions.html +6 -6
- package/dist/ui/serverless/logs.html +6 -6
- package/dist/ui/serverless/metrics.html +2 -2
- package/dist/ui/serverless/queues.html +6 -6
- package/dist/ui/serverless/secrets.html +6 -6
- package/dist/ui/serverless/traces.html +6 -6
- package/dist/ui/serverless.html +5 -5
- package/package.json +3 -3
- package/dist/bin/chunk-jpjamjey.js +0 -6935
- package/dist/bin/chunk-m2zwtapr.js +0 -1
- package/dist/bin/chunk-rtczra13.js +0 -1
- package/dist/chunk-01d86gt1.js +0 -1324
- package/dist/chunk-050r7ycy.js +0 -46516
- package/dist/chunk-1c8dbv57.js +0 -7278
- package/dist/chunk-29rse4p1.js +0 -12203
- package/dist/chunk-32e7ya18.js +0 -642
- package/dist/chunk-4cjrg98a.js +0 -597
- package/dist/chunk-50jpda9q.js +0 -371
- package/dist/chunk-6a1t7fnq.js +0 -12121
- package/dist/chunk-728jvrr0.js +0 -1701
- package/dist/chunk-7m60qnc8.js +0 -1762
- package/dist/chunk-aa2dkn7b.js +0 -205
- package/dist/chunk-d48vtdj4.js +0 -433
- package/dist/chunk-f55nkpvb.js +0 -8
- package/dist/chunk-fgk8cqew.js +0 -152
- package/dist/chunk-he9a874b.js +0 -769
- package/dist/chunk-k2yyatfw.js +0 -10
- package/dist/chunk-k4px0bnv.js +0 -23
- package/dist/chunk-nsgqsmrh.js +0 -1779
- package/dist/chunk-q0st3se8.js +0 -42033
- package/dist/chunk-qr803fye.js +0 -2122
- package/dist/chunk-rds0sy87.js +0 -1771
- package/dist/chunk-ttz2ra5j.js +0 -12
- package/dist/chunk-v0bahtg2.js +0 -4
- package/dist/chunk-va2yd85b.js +0 -200
- package/dist/chunk-x9s0cqfx.js +0 -13
- package/dist/chunk-zqtpg06c.js +0 -1752
package/dist/chunk-01d86gt1.js
DELETED
|
@@ -1,1324 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AWSClient
|
|
3
|
-
} from "./chunk-zqtpg06c.js";
|
|
4
|
-
|
|
5
|
-
// src/aws/cloudfront.ts
|
|
6
|
-
class CloudFrontClient {
|
|
7
|
-
client;
|
|
8
|
-
constructor(profile, transport) {
|
|
9
|
-
this.client = transport ?? new AWSClient(undefined, { profile });
|
|
10
|
-
}
|
|
11
|
-
collectionItems(value, child) {
|
|
12
|
-
const items = value?.Items ?? value?.[child];
|
|
13
|
-
if (!items)
|
|
14
|
-
return [];
|
|
15
|
-
if (Array.isArray(items))
|
|
16
|
-
return items;
|
|
17
|
-
const nested = items[child] ?? items.Item;
|
|
18
|
-
if (nested === undefined)
|
|
19
|
-
return typeof items === "object" && (items.Id || items.PathPattern) ? [items] : [];
|
|
20
|
-
return Array.isArray(nested) ? nested : [nested];
|
|
21
|
-
}
|
|
22
|
-
validateExistingOriginInput(distributionId, input) {
|
|
23
|
-
if (!/^[A-Z0-9]{8,32}$/.test(distributionId))
|
|
24
|
-
throw new Error("CloudFront distribution ID is invalid");
|
|
25
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(input.id))
|
|
26
|
-
throw new Error("CloudFront origin ID is invalid");
|
|
27
|
-
if (!/^(?=.{1,253}$)(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/.test(input.domainName))
|
|
28
|
-
throw new Error("CloudFront origin must be a hostname without a scheme or path");
|
|
29
|
-
const pathPattern = input.pathPattern.startsWith("/") ? input.pathPattern : `/${input.pathPattern}`;
|
|
30
|
-
if (pathPattern === "/" || pathPattern === "/*" || !/^\/[A-Za-z0-9_.*?~!$&'()+,;=:@%/-]{1,255}$/.test(pathPattern))
|
|
31
|
-
throw new Error("CloudFront path pattern must be a non-default absolute pattern");
|
|
32
|
-
if (input.originPath && (!input.originPath.startsWith("/") || input.originPath.includes("..")))
|
|
33
|
-
throw new Error("CloudFront origin path must be absolute and traversal-free");
|
|
34
|
-
if (Object.entries(input.customHeaders ?? {}).some(([name, value]) => !/^[A-Za-z0-9-]{1,64}$/.test(name) || !value || value.length > 1024))
|
|
35
|
-
throw new Error("CloudFront custom origin headers are invalid");
|
|
36
|
-
return { ...input, pathPattern };
|
|
37
|
-
}
|
|
38
|
-
async upsertExistingDistributionOrigin(distributionId, rawInput) {
|
|
39
|
-
const input = this.validateExistingOriginInput(distributionId, rawInput);
|
|
40
|
-
const getResult = await this.client.request({
|
|
41
|
-
service: "cloudfront",
|
|
42
|
-
region: "us-east-1",
|
|
43
|
-
method: "GET",
|
|
44
|
-
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
45
|
-
returnHeaders: true
|
|
46
|
-
});
|
|
47
|
-
const etag = getResult.headers?.etag || getResult.headers?.ETag || getResult.ETag || "";
|
|
48
|
-
const config = getResult.body?.DistributionConfig || getResult.DistributionConfig || getResult.body;
|
|
49
|
-
if (!config || config.Enabled === undefined || !etag)
|
|
50
|
-
throw new Error("Failed to read an ETag-protected CloudFront distribution config");
|
|
51
|
-
const origins = this.collectionItems(config.Origins, "Origin");
|
|
52
|
-
const behaviors = this.collectionItems(config.CacheBehaviors, "CacheBehavior");
|
|
53
|
-
const existingOrigin = origins.find((value) => String(value.Id) === input.id);
|
|
54
|
-
const existingBehavior = behaviors.find((value) => String(value.PathPattern) === input.pathPattern);
|
|
55
|
-
if (existingOrigin && String(existingOrigin.DomainName) !== input.domainName && !input.replaceExisting)
|
|
56
|
-
throw new Error(`Origin ${input.id} already targets ${existingOrigin.DomainName}; pass replaceExisting only after reviewing the live config`);
|
|
57
|
-
if (existingBehavior && String(existingBehavior.TargetOriginId) !== input.id && !input.replaceExisting)
|
|
58
|
-
throw new Error(`Path ${input.pathPattern} already targets ${existingBehavior.TargetOriginId}; pass replaceExisting only after reviewing the live config`);
|
|
59
|
-
const origin = {
|
|
60
|
-
Id: input.id,
|
|
61
|
-
DomainName: input.domainName,
|
|
62
|
-
OriginPath: input.originPath ?? "",
|
|
63
|
-
CustomHeaders: {
|
|
64
|
-
Quantity: Object.keys(input.customHeaders ?? {}).length,
|
|
65
|
-
...Object.keys(input.customHeaders ?? {}).length ? {
|
|
66
|
-
Items: {
|
|
67
|
-
OriginCustomHeader: Object.entries(input.customHeaders).map(([HeaderName, HeaderValue]) => ({
|
|
68
|
-
HeaderName,
|
|
69
|
-
HeaderValue
|
|
70
|
-
}))
|
|
71
|
-
}
|
|
72
|
-
} : {}
|
|
73
|
-
},
|
|
74
|
-
CustomOriginConfig: {
|
|
75
|
-
HTTPPort: 80,
|
|
76
|
-
HTTPSPort: 443,
|
|
77
|
-
OriginProtocolPolicy: input.protocolPolicy ?? "https-only",
|
|
78
|
-
OriginSslProtocols: { Quantity: 1, Items: { SslProtocol: ["TLSv1.2"] } },
|
|
79
|
-
OriginReadTimeout: 30,
|
|
80
|
-
OriginKeepaliveTimeout: 5
|
|
81
|
-
},
|
|
82
|
-
ConnectionAttempts: 3,
|
|
83
|
-
ConnectionTimeout: 10,
|
|
84
|
-
...input.originAccessControlId ? { OriginAccessControlId: input.originAccessControlId } : {}
|
|
85
|
-
};
|
|
86
|
-
const methods = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"];
|
|
87
|
-
const behavior = {
|
|
88
|
-
PathPattern: input.pathPattern,
|
|
89
|
-
TargetOriginId: input.id,
|
|
90
|
-
ViewerProtocolPolicy: "redirect-to-https",
|
|
91
|
-
Compress: true,
|
|
92
|
-
CachePolicyId: "4135ea2d-6df8-44a3-9df3-4b5a84be39ad",
|
|
93
|
-
OriginRequestPolicyId: "b689b0a8-53d0-40ab-baf2-68738e2966ac",
|
|
94
|
-
AllowedMethods: {
|
|
95
|
-
Quantity: methods.length,
|
|
96
|
-
Items: { Method: methods },
|
|
97
|
-
CachedMethods: { Quantity: 2, Items: { Method: ["GET", "HEAD"] } }
|
|
98
|
-
},
|
|
99
|
-
SmoothStreaming: false,
|
|
100
|
-
FieldLevelEncryptionId: "",
|
|
101
|
-
TrustedSigners: { Enabled: false, Quantity: 0 },
|
|
102
|
-
TrustedKeyGroups: { Enabled: false, Quantity: 0 },
|
|
103
|
-
LambdaFunctionAssociations: { Quantity: 0 },
|
|
104
|
-
FunctionAssociations: { Quantity: 0 }
|
|
105
|
-
};
|
|
106
|
-
const nextOrigins = existingOrigin ? origins.map((value) => String(value.Id) === input.id ? origin : value) : [...origins, origin];
|
|
107
|
-
const nextBehaviors = existingBehavior ? behaviors.map((value) => String(value.PathPattern) === input.pathPattern ? behavior : value) : [...behaviors, behavior];
|
|
108
|
-
const changed = JSON.stringify(origins) !== JSON.stringify(nextOrigins) || JSON.stringify(behaviors) !== JSON.stringify(nextBehaviors);
|
|
109
|
-
if (!changed || input.dryRun)
|
|
110
|
-
return {
|
|
111
|
-
distributionId,
|
|
112
|
-
originId: input.id,
|
|
113
|
-
domainName: input.domainName,
|
|
114
|
-
pathPattern: input.pathPattern,
|
|
115
|
-
changed,
|
|
116
|
-
applied: false,
|
|
117
|
-
etag
|
|
118
|
-
};
|
|
119
|
-
config.Origins = { Quantity: nextOrigins.length, Items: { Origin: nextOrigins } };
|
|
120
|
-
config.CacheBehaviors = {
|
|
121
|
-
Quantity: nextBehaviors.length,
|
|
122
|
-
...nextBehaviors.length ? { Items: { CacheBehavior: nextBehaviors } } : {}
|
|
123
|
-
};
|
|
124
|
-
const result = await this.client.request({
|
|
125
|
-
service: "cloudfront",
|
|
126
|
-
region: "us-east-1",
|
|
127
|
-
method: "PUT",
|
|
128
|
-
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
129
|
-
body: this.buildDistributionConfigXml(config),
|
|
130
|
-
headers: { "Content-Type": "application/xml", "If-Match": etag }
|
|
131
|
-
});
|
|
132
|
-
return {
|
|
133
|
-
distributionId,
|
|
134
|
-
originId: input.id,
|
|
135
|
-
domainName: input.domainName,
|
|
136
|
-
pathPattern: input.pathPattern,
|
|
137
|
-
changed: true,
|
|
138
|
-
applied: true,
|
|
139
|
-
etag: result.ETag || result.headers?.etag || ""
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
async removeExistingDistributionOrigin(distributionId, rawInput) {
|
|
143
|
-
const input = this.validateExistingOriginInput(distributionId, rawInput);
|
|
144
|
-
const getResult = await this.client.request({
|
|
145
|
-
service: "cloudfront",
|
|
146
|
-
region: "us-east-1",
|
|
147
|
-
method: "GET",
|
|
148
|
-
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
149
|
-
returnHeaders: true
|
|
150
|
-
});
|
|
151
|
-
const etag = getResult.headers?.etag || getResult.headers?.ETag || getResult.ETag || "";
|
|
152
|
-
const config = getResult.body?.DistributionConfig || getResult.DistributionConfig || getResult.body;
|
|
153
|
-
if (!config || config.Enabled === undefined || !etag)
|
|
154
|
-
throw new Error("Failed to read an ETag-protected CloudFront distribution config");
|
|
155
|
-
const origins = this.collectionItems(config.Origins, "Origin");
|
|
156
|
-
const behaviors = this.collectionItems(config.CacheBehaviors, "CacheBehavior");
|
|
157
|
-
const origin = origins.find((value) => String(value.Id) === input.id);
|
|
158
|
-
const behavior = behaviors.find((value) => String(value.PathPattern) === input.pathPattern);
|
|
159
|
-
if (origin && String(origin.DomainName) !== input.domainName)
|
|
160
|
-
throw new Error(`Origin ${input.id} no longer targets ${input.domainName}; refusing ambiguous removal`);
|
|
161
|
-
if (behavior && String(behavior.TargetOriginId) !== input.id)
|
|
162
|
-
throw new Error(`Path ${input.pathPattern} no longer targets ${input.id}; refusing ambiguous removal`);
|
|
163
|
-
if (!behavior)
|
|
164
|
-
return {
|
|
165
|
-
distributionId,
|
|
166
|
-
originId: input.id,
|
|
167
|
-
domainName: input.domainName,
|
|
168
|
-
pathPattern: input.pathPattern,
|
|
169
|
-
changed: false,
|
|
170
|
-
applied: false,
|
|
171
|
-
originRemoved: false,
|
|
172
|
-
etag
|
|
173
|
-
};
|
|
174
|
-
const nextBehaviors = behaviors.filter((value) => String(value.PathPattern) !== input.pathPattern);
|
|
175
|
-
const stillReferenced = String(config.DefaultCacheBehavior?.TargetOriginId) === input.id || nextBehaviors.some((value) => String(value.TargetOriginId) === input.id);
|
|
176
|
-
const nextOrigins = stillReferenced ? origins : origins.filter((value) => String(value.Id) !== input.id);
|
|
177
|
-
if (input.dryRun)
|
|
178
|
-
return {
|
|
179
|
-
distributionId,
|
|
180
|
-
originId: input.id,
|
|
181
|
-
domainName: input.domainName,
|
|
182
|
-
pathPattern: input.pathPattern,
|
|
183
|
-
changed: true,
|
|
184
|
-
applied: false,
|
|
185
|
-
originRemoved: !stillReferenced && !!origin,
|
|
186
|
-
etag
|
|
187
|
-
};
|
|
188
|
-
config.Origins = { Quantity: nextOrigins.length, Items: { Origin: nextOrigins } };
|
|
189
|
-
config.CacheBehaviors = {
|
|
190
|
-
Quantity: nextBehaviors.length,
|
|
191
|
-
...nextBehaviors.length ? { Items: { CacheBehavior: nextBehaviors } } : {}
|
|
192
|
-
};
|
|
193
|
-
const result = await this.client.request({
|
|
194
|
-
service: "cloudfront",
|
|
195
|
-
region: "us-east-1",
|
|
196
|
-
method: "PUT",
|
|
197
|
-
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
198
|
-
body: this.buildDistributionConfigXml(config),
|
|
199
|
-
headers: { "Content-Type": "application/xml", "If-Match": etag }
|
|
200
|
-
});
|
|
201
|
-
return {
|
|
202
|
-
distributionId,
|
|
203
|
-
originId: input.id,
|
|
204
|
-
domainName: input.domainName,
|
|
205
|
-
pathPattern: input.pathPattern,
|
|
206
|
-
changed: true,
|
|
207
|
-
applied: true,
|
|
208
|
-
originRemoved: !stillReferenced && !!origin,
|
|
209
|
-
etag: result.ETag || result.headers?.etag || ""
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
async createInvalidation(options) {
|
|
213
|
-
const callerReference = options.callerReference || Date.now().toString();
|
|
214
|
-
const invalidationBatchXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
215
|
-
<InvalidationBatch>
|
|
216
|
-
<Paths>
|
|
217
|
-
<Quantity>${options.paths.length}</Quantity>
|
|
218
|
-
<Items>
|
|
219
|
-
${options.paths.map((path) => `<Path>${path}</Path>`).join(`
|
|
220
|
-
`)}
|
|
221
|
-
</Items>
|
|
222
|
-
</Paths>
|
|
223
|
-
<CallerReference>${callerReference}</CallerReference>
|
|
224
|
-
</InvalidationBatch>`;
|
|
225
|
-
const result = await this.client.request({
|
|
226
|
-
service: "cloudfront",
|
|
227
|
-
region: "us-east-1",
|
|
228
|
-
method: "POST",
|
|
229
|
-
path: `/2020-05-31/distribution/${options.distributionId}/invalidation`,
|
|
230
|
-
body: invalidationBatchXml,
|
|
231
|
-
headers: {
|
|
232
|
-
"Content-Type": "application/xml"
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
return {
|
|
236
|
-
Id: result.Id || result.Invalidation?.Id,
|
|
237
|
-
Status: result.Status || result.Invalidation?.Status || "InProgress",
|
|
238
|
-
CreateTime: result.CreateTime || result.Invalidation?.CreateTime || new Date().toISOString()
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
async getInvalidation(distributionId, invalidationId) {
|
|
242
|
-
const result = await this.client.request({
|
|
243
|
-
service: "cloudfront",
|
|
244
|
-
region: "us-east-1",
|
|
245
|
-
method: "GET",
|
|
246
|
-
path: `/2020-05-31/distribution/${distributionId}/invalidation/${invalidationId}`
|
|
247
|
-
});
|
|
248
|
-
return {
|
|
249
|
-
Id: result.Id || result.Invalidation?.Id,
|
|
250
|
-
Status: result.Status || result.Invalidation?.Status,
|
|
251
|
-
CreateTime: result.CreateTime || result.Invalidation?.CreateTime
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
async listInvalidations(distributionId) {
|
|
255
|
-
const result = await this.client.request({
|
|
256
|
-
service: "cloudfront",
|
|
257
|
-
region: "us-east-1",
|
|
258
|
-
method: "GET",
|
|
259
|
-
path: `/2020-05-31/distribution/${distributionId}/invalidation`
|
|
260
|
-
});
|
|
261
|
-
const invalidations = [];
|
|
262
|
-
if (result.InvalidationSummary) {
|
|
263
|
-
const summaries = Array.isArray(result.InvalidationSummary) ? result.InvalidationSummary : [result.InvalidationSummary];
|
|
264
|
-
invalidations.push(...summaries.map((item) => ({
|
|
265
|
-
Id: item.Id,
|
|
266
|
-
Status: item.Status,
|
|
267
|
-
CreateTime: item.CreateTime
|
|
268
|
-
})));
|
|
269
|
-
}
|
|
270
|
-
return invalidations;
|
|
271
|
-
}
|
|
272
|
-
async waitForInvalidation(distributionId, invalidationId) {
|
|
273
|
-
const maxAttempts = 60;
|
|
274
|
-
let attempts = 0;
|
|
275
|
-
while (attempts < maxAttempts) {
|
|
276
|
-
const invalidation = await this.getInvalidation(distributionId, invalidationId);
|
|
277
|
-
if (invalidation.Status === "Completed") {
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
281
|
-
attempts++;
|
|
282
|
-
}
|
|
283
|
-
throw new Error(`Timeout waiting for invalidation ${invalidationId} to complete`);
|
|
284
|
-
}
|
|
285
|
-
async listDistributions() {
|
|
286
|
-
const result = await this.client.request({
|
|
287
|
-
service: "cloudfront",
|
|
288
|
-
region: "us-east-1",
|
|
289
|
-
method: "GET",
|
|
290
|
-
path: "/2020-05-31/distribution"
|
|
291
|
-
});
|
|
292
|
-
const distributions = [];
|
|
293
|
-
const distList = result.DistributionList || result;
|
|
294
|
-
const items = distList.Items;
|
|
295
|
-
let summaries = [];
|
|
296
|
-
if (Array.isArray(items)) {
|
|
297
|
-
summaries = items;
|
|
298
|
-
} else if (items?.DistributionSummary) {
|
|
299
|
-
summaries = Array.isArray(items.DistributionSummary) ? items.DistributionSummary : [items.DistributionSummary];
|
|
300
|
-
}
|
|
301
|
-
distributions.push(...summaries.map((item) => ({
|
|
302
|
-
Id: item.Id,
|
|
303
|
-
ARN: item.ARN,
|
|
304
|
-
Status: item.Status,
|
|
305
|
-
DomainName: item.DomainName,
|
|
306
|
-
Aliases: item.Aliases || undefined,
|
|
307
|
-
Enabled: item.Enabled === "true" || item.Enabled === true
|
|
308
|
-
})));
|
|
309
|
-
return distributions;
|
|
310
|
-
}
|
|
311
|
-
async getDistribution(distributionId) {
|
|
312
|
-
const result = await this.client.request({
|
|
313
|
-
service: "cloudfront",
|
|
314
|
-
region: "us-east-1",
|
|
315
|
-
method: "GET",
|
|
316
|
-
path: `/2020-05-31/distribution/${distributionId}`
|
|
317
|
-
});
|
|
318
|
-
const dist = result.Distribution || result;
|
|
319
|
-
return {
|
|
320
|
-
Id: dist.Id,
|
|
321
|
-
ARN: dist.ARN,
|
|
322
|
-
Status: dist.Status,
|
|
323
|
-
DomainName: dist.DomainName,
|
|
324
|
-
Aliases: dist.DistributionConfig?.Aliases?.Items || dist.Aliases?.Items || [],
|
|
325
|
-
Enabled: dist.DistributionConfig?.Enabled === "true" || dist.DistributionConfig?.Enabled === true
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
async getDistributionConfig(distributionId) {
|
|
329
|
-
const result = await this.client.request({
|
|
330
|
-
service: "cloudfront",
|
|
331
|
-
region: "us-east-1",
|
|
332
|
-
method: "GET",
|
|
333
|
-
path: `/2020-05-31/distribution/${distributionId}/config`
|
|
334
|
-
});
|
|
335
|
-
return {
|
|
336
|
-
ETag: result.ETag || "",
|
|
337
|
-
DistributionConfig: result.DistributionConfig || result
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
async invalidateAll(distributionId) {
|
|
341
|
-
return this.createInvalidation({
|
|
342
|
-
distributionId,
|
|
343
|
-
paths: ["/*"]
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
async invalidatePaths(distributionId, paths) {
|
|
347
|
-
const formattedPaths = paths.map((path) => path.startsWith("/") ? path : `/${path}`);
|
|
348
|
-
return this.createInvalidation({
|
|
349
|
-
distributionId,
|
|
350
|
-
paths: formattedPaths
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
async invalidatePattern(distributionId, pattern) {
|
|
354
|
-
const path = pattern.startsWith("/") ? pattern : `/${pattern}`;
|
|
355
|
-
return this.createInvalidation({
|
|
356
|
-
distributionId,
|
|
357
|
-
paths: [path]
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
async invalidateAfterDeployment(options) {
|
|
361
|
-
const { distributionId, changedPaths, invalidateAll = false, wait = false } = options;
|
|
362
|
-
let result;
|
|
363
|
-
if (invalidateAll || !changedPaths || changedPaths.length === 0) {
|
|
364
|
-
result = await this.invalidateAll(distributionId);
|
|
365
|
-
} else {
|
|
366
|
-
result = await this.invalidatePaths(distributionId, changedPaths);
|
|
367
|
-
}
|
|
368
|
-
if (wait) {
|
|
369
|
-
await this.waitForInvalidation(distributionId, result.Id);
|
|
370
|
-
}
|
|
371
|
-
return {
|
|
372
|
-
invalidationId: result.Id,
|
|
373
|
-
status: result.Status
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
async findDistributionByDomain(domain) {
|
|
377
|
-
const distributions = await this.listDistributions();
|
|
378
|
-
const found = distributions.find((dist) => {
|
|
379
|
-
if (dist.DomainName === domain) {
|
|
380
|
-
return true;
|
|
381
|
-
}
|
|
382
|
-
if (dist.Aliases?.Items && dist.Aliases.Items.includes(domain)) {
|
|
383
|
-
return true;
|
|
384
|
-
}
|
|
385
|
-
return false;
|
|
386
|
-
});
|
|
387
|
-
return found || null;
|
|
388
|
-
}
|
|
389
|
-
async batchInvalidate(distributionIds, paths = ["/*"]) {
|
|
390
|
-
const results = await Promise.all(distributionIds.map(async (distributionId) => {
|
|
391
|
-
const result = await this.createInvalidation({
|
|
392
|
-
distributionId,
|
|
393
|
-
paths
|
|
394
|
-
});
|
|
395
|
-
return {
|
|
396
|
-
distributionId,
|
|
397
|
-
invalidationId: result.Id,
|
|
398
|
-
status: result.Status
|
|
399
|
-
};
|
|
400
|
-
}));
|
|
401
|
-
return results;
|
|
402
|
-
}
|
|
403
|
-
async updateCustomErrorResponses(options) {
|
|
404
|
-
const { distributionId, customErrorResponses } = options;
|
|
405
|
-
const getResult = await this.client.request({
|
|
406
|
-
service: "cloudfront",
|
|
407
|
-
region: "us-east-1",
|
|
408
|
-
method: "GET",
|
|
409
|
-
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
410
|
-
returnHeaders: true
|
|
411
|
-
});
|
|
412
|
-
const etag = getResult.headers?.etag || getResult.headers?.ETag || "";
|
|
413
|
-
const currentConfig = getResult.body?.DistributionConfig || (getResult.body?.Enabled !== undefined ? getResult.body : undefined) || getResult.DistributionConfig;
|
|
414
|
-
if (!currentConfig || currentConfig.Enabled === undefined) {
|
|
415
|
-
throw new Error("Failed to get current distribution config");
|
|
416
|
-
}
|
|
417
|
-
if (customErrorResponses.length === 0) {
|
|
418
|
-
currentConfig.CustomErrorResponses = {
|
|
419
|
-
Quantity: 0
|
|
420
|
-
};
|
|
421
|
-
} else {
|
|
422
|
-
currentConfig.CustomErrorResponses = {
|
|
423
|
-
Quantity: customErrorResponses.length,
|
|
424
|
-
Items: {
|
|
425
|
-
CustomErrorResponse: customErrorResponses.map((err) => ({
|
|
426
|
-
ErrorCode: err.errorCode,
|
|
427
|
-
...err.responsePagePath && { ResponsePagePath: err.responsePagePath },
|
|
428
|
-
...err.responseCode && { ResponseCode: err.responseCode },
|
|
429
|
-
...err.errorCachingMinTTL !== undefined && { ErrorCachingMinTTL: err.errorCachingMinTTL }
|
|
430
|
-
}))
|
|
431
|
-
}
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
const configXml = this.buildDistributionConfigXml(currentConfig);
|
|
435
|
-
const result = await this.client.request({
|
|
436
|
-
service: "cloudfront",
|
|
437
|
-
region: "us-east-1",
|
|
438
|
-
method: "PUT",
|
|
439
|
-
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
440
|
-
body: configXml,
|
|
441
|
-
headers: {
|
|
442
|
-
"Content-Type": "application/xml",
|
|
443
|
-
"If-Match": etag
|
|
444
|
-
}
|
|
445
|
-
});
|
|
446
|
-
const dist = result.Distribution || result;
|
|
447
|
-
return {
|
|
448
|
-
Distribution: {
|
|
449
|
-
Id: dist.Id,
|
|
450
|
-
ARN: dist.ARN,
|
|
451
|
-
Status: dist.Status,
|
|
452
|
-
DomainName: dist.DomainName,
|
|
453
|
-
Aliases: dist.DistributionConfig?.Aliases?.Items || [],
|
|
454
|
-
Enabled: dist.DistributionConfig?.Enabled === "true" || dist.DistributionConfig?.Enabled === true
|
|
455
|
-
},
|
|
456
|
-
ETag: result.ETag || ""
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
async removeCustomErrorResponses(distributionId) {
|
|
460
|
-
return this.updateCustomErrorResponses({
|
|
461
|
-
distributionId,
|
|
462
|
-
customErrorResponses: []
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
async updateDistribution(options) {
|
|
466
|
-
const { distributionId, aliases, certificateArn, comment } = options;
|
|
467
|
-
const getResult = await this.client.request({
|
|
468
|
-
service: "cloudfront",
|
|
469
|
-
region: "us-east-1",
|
|
470
|
-
method: "GET",
|
|
471
|
-
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
472
|
-
returnHeaders: true
|
|
473
|
-
});
|
|
474
|
-
const etag = getResult.headers?.etag || getResult.headers?.ETag || "";
|
|
475
|
-
const currentConfig = getResult.body?.DistributionConfig || (getResult.body?.Enabled !== undefined ? getResult.body : undefined) || getResult.DistributionConfig;
|
|
476
|
-
if (!currentConfig || currentConfig.Enabled === undefined) {
|
|
477
|
-
throw new Error("Failed to get current distribution config");
|
|
478
|
-
}
|
|
479
|
-
if (aliases && aliases.length > 0) {
|
|
480
|
-
currentConfig.Aliases = {
|
|
481
|
-
Quantity: aliases.length,
|
|
482
|
-
Items: { Item: aliases }
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
if (certificateArn) {
|
|
486
|
-
currentConfig.ViewerCertificate = {
|
|
487
|
-
ACMCertificateArn: certificateArn,
|
|
488
|
-
SSLSupportMethod: "sni-only",
|
|
489
|
-
MinimumProtocolVersion: "TLSv1.2_2021",
|
|
490
|
-
CertificateSource: "acm"
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
if (comment) {
|
|
494
|
-
currentConfig.Comment = comment;
|
|
495
|
-
}
|
|
496
|
-
const configXml = this.buildDistributionConfigXml(currentConfig);
|
|
497
|
-
const result = await this.client.request({
|
|
498
|
-
service: "cloudfront",
|
|
499
|
-
region: "us-east-1",
|
|
500
|
-
method: "PUT",
|
|
501
|
-
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
502
|
-
body: configXml,
|
|
503
|
-
headers: {
|
|
504
|
-
"Content-Type": "application/xml",
|
|
505
|
-
"If-Match": etag
|
|
506
|
-
}
|
|
507
|
-
});
|
|
508
|
-
const dist = result.Distribution || result;
|
|
509
|
-
return {
|
|
510
|
-
Distribution: {
|
|
511
|
-
Id: dist.Id,
|
|
512
|
-
ARN: dist.ARN,
|
|
513
|
-
Status: dist.Status,
|
|
514
|
-
DomainName: dist.DomainName,
|
|
515
|
-
Aliases: aliases ? { Quantity: aliases.length, Items: aliases } : { Quantity: 0, Items: [] },
|
|
516
|
-
Enabled: dist.DistributionConfig?.Enabled === "true" || dist.DistributionConfig?.Enabled === true
|
|
517
|
-
},
|
|
518
|
-
ETag: result.ETag || ""
|
|
519
|
-
};
|
|
520
|
-
}
|
|
521
|
-
buildDistributionConfigXml(config) {
|
|
522
|
-
const escapeXml = (str) => {
|
|
523
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
524
|
-
};
|
|
525
|
-
const arrayChildNames = {
|
|
526
|
-
Items: "",
|
|
527
|
-
Methods: "Method",
|
|
528
|
-
Headers: "Name",
|
|
529
|
-
Cookies: "Name",
|
|
530
|
-
QueryStringCacheKeys: "Name",
|
|
531
|
-
TrustedKeyGroups: "KeyGroup",
|
|
532
|
-
TrustedSigners: "AwsAccountNumber",
|
|
533
|
-
LambdaFunctionAssociations: "LambdaFunctionAssociation",
|
|
534
|
-
FunctionAssociations: "FunctionAssociation",
|
|
535
|
-
CacheBehaviors: "CacheBehavior",
|
|
536
|
-
CustomErrorResponses: "CustomErrorResponse",
|
|
537
|
-
GeoRestriction: "Location"
|
|
538
|
-
};
|
|
539
|
-
const itemsChildNames = {
|
|
540
|
-
Origins: "Origin",
|
|
541
|
-
Aliases: "CNAME",
|
|
542
|
-
AllowedMethods: "Method",
|
|
543
|
-
CachedMethods: "Method",
|
|
544
|
-
CustomErrorResponses: "CustomErrorResponse",
|
|
545
|
-
CacheBehaviors: "CacheBehavior"
|
|
546
|
-
};
|
|
547
|
-
const buildXmlElement = (name, value, indent = "", parentContext = "") => {
|
|
548
|
-
if (value === null || value === undefined) {
|
|
549
|
-
return "";
|
|
550
|
-
}
|
|
551
|
-
if (name.startsWith("@_") || name === "?xml") {
|
|
552
|
-
return "";
|
|
553
|
-
}
|
|
554
|
-
if (typeof value === "boolean") {
|
|
555
|
-
return `${indent}<${name}>${value}</${name}>
|
|
556
|
-
`;
|
|
557
|
-
}
|
|
558
|
-
if (typeof value === "number" || typeof value === "string") {
|
|
559
|
-
return `${indent}<${name}>${escapeXml(String(value))}</${name}>
|
|
560
|
-
`;
|
|
561
|
-
}
|
|
562
|
-
if (Array.isArray(value)) {
|
|
563
|
-
const childName = arrayChildNames[name] || name.replace(/s$/, "");
|
|
564
|
-
return value.map((item) => buildXmlElement(childName, item, indent, name)).join("");
|
|
565
|
-
}
|
|
566
|
-
if (typeof value === "object") {
|
|
567
|
-
if (name === "Items") {
|
|
568
|
-
const childElementName = itemsChildNames[parentContext] || "";
|
|
569
|
-
const keys = Object.keys(value).filter((k) => !k.startsWith("@_"));
|
|
570
|
-
if (keys.length === 1 && !Array.isArray(value[keys[0]])) {
|
|
571
|
-
const childKey = keys[0];
|
|
572
|
-
const childValue = value[childKey];
|
|
573
|
-
if (typeof childValue === "string") {
|
|
574
|
-
return `${indent}<Items>
|
|
575
|
-
${indent} <${childKey}>${escapeXml(childValue)}</${childKey}>
|
|
576
|
-
${indent}</Items>
|
|
577
|
-
`;
|
|
578
|
-
} else if (typeof childValue === "object" && !Array.isArray(childValue)) {
|
|
579
|
-
return `${indent}<Items>
|
|
580
|
-
${buildXmlElement(childKey, childValue, indent + " ", name)}${indent}</Items>
|
|
581
|
-
`;
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
if (keys.length === 1 && Array.isArray(value[keys[0]])) {
|
|
585
|
-
const childKey = keys[0];
|
|
586
|
-
const childArray = value[childKey];
|
|
587
|
-
let children2 = "";
|
|
588
|
-
for (const item of childArray) {
|
|
589
|
-
if (typeof item === "string") {
|
|
590
|
-
children2 += `${indent} <${childKey}>${escapeXml(item)}</${childKey}>
|
|
591
|
-
`;
|
|
592
|
-
} else {
|
|
593
|
-
children2 += buildXmlElement(childKey, item, indent + " ", name);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
return `${indent}<Items>
|
|
597
|
-
${children2}${indent}</Items>
|
|
598
|
-
`;
|
|
599
|
-
}
|
|
600
|
-
if (Array.isArray(value)) {
|
|
601
|
-
let children2 = "";
|
|
602
|
-
const childName = childElementName || "Item";
|
|
603
|
-
for (const item of value) {
|
|
604
|
-
if (typeof item === "string") {
|
|
605
|
-
children2 += `${indent} <${childName}>${escapeXml(item)}</${childName}>
|
|
606
|
-
`;
|
|
607
|
-
} else {
|
|
608
|
-
children2 += buildXmlElement(childName, item, indent + " ", name);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
return `${indent}<Items>
|
|
612
|
-
${children2}${indent}</Items>
|
|
613
|
-
`;
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
let children = "";
|
|
617
|
-
for (const [key, val] of Object.entries(value)) {
|
|
618
|
-
if (!key.startsWith("@_")) {
|
|
619
|
-
children += buildXmlElement(key, val, indent + " ", name);
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
if (children === "") {
|
|
623
|
-
return `${indent}<${name}/>
|
|
624
|
-
`;
|
|
625
|
-
}
|
|
626
|
-
return `${indent}<${name}>
|
|
627
|
-
${children}${indent}</${name}>
|
|
628
|
-
`;
|
|
629
|
-
}
|
|
630
|
-
return "";
|
|
631
|
-
};
|
|
632
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
633
|
-
<DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
|
|
634
|
-
${Object.entries(config).filter(([k]) => !k.startsWith("@_")).map(([key, val]) => buildXmlElement(key, val, " ", "DistributionConfig")).join("")}</DistributionConfig>`;
|
|
635
|
-
}
|
|
636
|
-
async ensureDynamicHttpMethods(distributionId) {
|
|
637
|
-
const getResult = await this.client.request({
|
|
638
|
-
service: "cloudfront",
|
|
639
|
-
region: "us-east-1",
|
|
640
|
-
method: "GET",
|
|
641
|
-
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
642
|
-
returnHeaders: true
|
|
643
|
-
});
|
|
644
|
-
const etag = getResult.headers?.etag || getResult.headers?.ETag || "";
|
|
645
|
-
const currentConfig = getResult.body?.DistributionConfig || getResult.DistributionConfig || getResult.body;
|
|
646
|
-
if (!currentConfig) {
|
|
647
|
-
throw new Error("Failed to get current distribution config");
|
|
648
|
-
}
|
|
649
|
-
let changed = false;
|
|
650
|
-
const dynamicMethods = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"];
|
|
651
|
-
const parseAllowedMethods = (allowedMethods) => {
|
|
652
|
-
if (!allowedMethods)
|
|
653
|
-
return [];
|
|
654
|
-
const items = allowedMethods.Items;
|
|
655
|
-
if (!items)
|
|
656
|
-
return [];
|
|
657
|
-
if (Array.isArray(items))
|
|
658
|
-
return items.map(String);
|
|
659
|
-
if (typeof items === "object" && items.Method) {
|
|
660
|
-
const method = items.Method;
|
|
661
|
-
return Array.isArray(method) ? method.map(String) : [String(method)];
|
|
662
|
-
}
|
|
663
|
-
if (items.Item) {
|
|
664
|
-
return Array.isArray(items.Item) ? items.Item.map(String) : [String(items.Item)];
|
|
665
|
-
}
|
|
666
|
-
return [];
|
|
667
|
-
};
|
|
668
|
-
const defaultBehavior = currentConfig.DefaultCacheBehavior;
|
|
669
|
-
if (defaultBehavior) {
|
|
670
|
-
const allowed = parseAllowedMethods(defaultBehavior.AllowedMethods);
|
|
671
|
-
if (!allowed.includes("POST")) {
|
|
672
|
-
defaultBehavior.AllowedMethods = {
|
|
673
|
-
Quantity: dynamicMethods.length,
|
|
674
|
-
Items: { Method: dynamicMethods },
|
|
675
|
-
CachedMethods: {
|
|
676
|
-
Quantity: 2,
|
|
677
|
-
Items: { Method: ["GET", "HEAD"] }
|
|
678
|
-
}
|
|
679
|
-
};
|
|
680
|
-
changed = true;
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
if (!changed) {
|
|
684
|
-
return false;
|
|
685
|
-
}
|
|
686
|
-
const configXml = this.buildDistributionConfigXml(currentConfig);
|
|
687
|
-
await this.client.request({
|
|
688
|
-
service: "cloudfront",
|
|
689
|
-
region: "us-east-1",
|
|
690
|
-
method: "PUT",
|
|
691
|
-
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
692
|
-
body: configXml,
|
|
693
|
-
headers: {
|
|
694
|
-
"Content-Type": "application/xml",
|
|
695
|
-
"If-Match": etag
|
|
696
|
-
}
|
|
697
|
-
});
|
|
698
|
-
return true;
|
|
699
|
-
}
|
|
700
|
-
async addAliases(distributionId, aliases, certificateArn) {
|
|
701
|
-
return this.updateDistribution({
|
|
702
|
-
distributionId,
|
|
703
|
-
aliases,
|
|
704
|
-
certificateArn
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
async createFunction(options) {
|
|
708
|
-
const { name, code, comment = "", runtime = "cloudfront-js-2.0" } = options;
|
|
709
|
-
const functionXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
710
|
-
<CreateFunctionRequest xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
|
|
711
|
-
<Name>${name}</Name>
|
|
712
|
-
<FunctionConfig>
|
|
713
|
-
<Comment>${comment}</Comment>
|
|
714
|
-
<Runtime>${runtime}</Runtime>
|
|
715
|
-
</FunctionConfig>
|
|
716
|
-
<FunctionCode>${Buffer.from(code).toString("base64")}</FunctionCode>
|
|
717
|
-
</CreateFunctionRequest>`;
|
|
718
|
-
const result = await this.client.request({
|
|
719
|
-
service: "cloudfront",
|
|
720
|
-
region: "us-east-1",
|
|
721
|
-
method: "POST",
|
|
722
|
-
path: "/2020-05-31/function",
|
|
723
|
-
body: functionXml,
|
|
724
|
-
headers: {
|
|
725
|
-
"Content-Type": "application/xml"
|
|
726
|
-
}
|
|
727
|
-
});
|
|
728
|
-
const func = result.FunctionSummary || result;
|
|
729
|
-
return {
|
|
730
|
-
FunctionARN: func.FunctionMetadata?.FunctionARN || func.FunctionARN,
|
|
731
|
-
Name: func.Name || name,
|
|
732
|
-
Stage: func.FunctionMetadata?.Stage || "DEVELOPMENT",
|
|
733
|
-
ETag: result.ETag || ""
|
|
734
|
-
};
|
|
735
|
-
}
|
|
736
|
-
async listFunctions() {
|
|
737
|
-
const result = await this.client.request({
|
|
738
|
-
service: "cloudfront",
|
|
739
|
-
region: "us-east-1",
|
|
740
|
-
method: "GET",
|
|
741
|
-
path: "/2020-05-31/function"
|
|
742
|
-
});
|
|
743
|
-
const functions = [];
|
|
744
|
-
const items = result.FunctionList?.Items?.FunctionSummary;
|
|
745
|
-
if (items) {
|
|
746
|
-
const list = Array.isArray(items) ? items : [items];
|
|
747
|
-
for (const item of list) {
|
|
748
|
-
functions.push({
|
|
749
|
-
Name: item.Name,
|
|
750
|
-
FunctionARN: item.FunctionMetadata?.FunctionARN,
|
|
751
|
-
Stage: item.FunctionMetadata?.Stage,
|
|
752
|
-
CreatedTime: item.FunctionMetadata?.CreatedTime,
|
|
753
|
-
LastModifiedTime: item.FunctionMetadata?.LastModifiedTime
|
|
754
|
-
});
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
return functions;
|
|
758
|
-
}
|
|
759
|
-
async getFunction(name, stage = "LIVE") {
|
|
760
|
-
try {
|
|
761
|
-
const result = await this.client.request({
|
|
762
|
-
service: "cloudfront",
|
|
763
|
-
region: "us-east-1",
|
|
764
|
-
method: "GET",
|
|
765
|
-
path: `/2020-05-31/function/${name}`,
|
|
766
|
-
queryParams: { Stage: stage },
|
|
767
|
-
returnHeaders: true
|
|
768
|
-
});
|
|
769
|
-
const func = result.body?.FunctionSummary || result.FunctionSummary || result.body || result;
|
|
770
|
-
return {
|
|
771
|
-
FunctionARN: func.FunctionMetadata?.FunctionARN,
|
|
772
|
-
Name: func.Name || name,
|
|
773
|
-
Stage: func.FunctionMetadata?.Stage || stage,
|
|
774
|
-
ETag: result.headers?.etag || result.ETag || "",
|
|
775
|
-
FunctionCode: func.FunctionCode
|
|
776
|
-
};
|
|
777
|
-
} catch (err) {
|
|
778
|
-
if (err.message?.includes("404") || err.message?.includes("NoSuchFunctionExists")) {
|
|
779
|
-
return null;
|
|
780
|
-
}
|
|
781
|
-
throw err;
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
async publishFunction(nameOrOptions, etag) {
|
|
785
|
-
let name;
|
|
786
|
-
let functionETag;
|
|
787
|
-
if (typeof nameOrOptions === "object") {
|
|
788
|
-
name = nameOrOptions.Name;
|
|
789
|
-
functionETag = nameOrOptions.IfMatch;
|
|
790
|
-
} else {
|
|
791
|
-
name = nameOrOptions;
|
|
792
|
-
functionETag = etag;
|
|
793
|
-
}
|
|
794
|
-
if (!functionETag) {
|
|
795
|
-
const func2 = await this.getFunction(name, "DEVELOPMENT");
|
|
796
|
-
if (!func2) {
|
|
797
|
-
throw new Error(`Function ${name} not found`);
|
|
798
|
-
}
|
|
799
|
-
functionETag = func2.ETag;
|
|
800
|
-
}
|
|
801
|
-
const result = await this.client.request({
|
|
802
|
-
service: "cloudfront",
|
|
803
|
-
region: "us-east-1",
|
|
804
|
-
method: "POST",
|
|
805
|
-
path: `/2020-05-31/function/${name}/publish`,
|
|
806
|
-
headers: {
|
|
807
|
-
"If-Match": functionETag
|
|
808
|
-
}
|
|
809
|
-
});
|
|
810
|
-
const func = result.FunctionSummary || result;
|
|
811
|
-
return {
|
|
812
|
-
FunctionARN: func.FunctionMetadata?.FunctionARN,
|
|
813
|
-
Stage: func.FunctionMetadata?.Stage || "LIVE",
|
|
814
|
-
FunctionSummary: func
|
|
815
|
-
};
|
|
816
|
-
}
|
|
817
|
-
async describeFunction(options) {
|
|
818
|
-
const { Name, Stage = "DEVELOPMENT" } = options;
|
|
819
|
-
const result = await this.client.request({
|
|
820
|
-
service: "cloudfront",
|
|
821
|
-
region: "us-east-1",
|
|
822
|
-
method: "GET",
|
|
823
|
-
path: `/2020-05-31/function/${Name}/describe`,
|
|
824
|
-
queryParams: { Stage },
|
|
825
|
-
returnHeaders: true
|
|
826
|
-
});
|
|
827
|
-
return {
|
|
828
|
-
ETag: result.headers?.etag || result.ETag || "",
|
|
829
|
-
FunctionSummary: result.body?.FunctionSummary || result.FunctionSummary || result.body || result
|
|
830
|
-
};
|
|
831
|
-
}
|
|
832
|
-
async updateFunction(options) {
|
|
833
|
-
const { Name, FunctionCode, FunctionConfig, IfMatch } = options;
|
|
834
|
-
const functionXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
835
|
-
<UpdateFunctionRequest xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
|
|
836
|
-
<FunctionConfig>
|
|
837
|
-
<Comment>${FunctionConfig.Comment}</Comment>
|
|
838
|
-
<Runtime>${FunctionConfig.Runtime}</Runtime>
|
|
839
|
-
</FunctionConfig>
|
|
840
|
-
<FunctionCode>${Buffer.from(FunctionCode).toString("base64")}</FunctionCode>
|
|
841
|
-
</UpdateFunctionRequest>`;
|
|
842
|
-
const result = await this.client.request({
|
|
843
|
-
service: "cloudfront",
|
|
844
|
-
region: "us-east-1",
|
|
845
|
-
method: "PUT",
|
|
846
|
-
path: `/2020-05-31/function/${Name}`,
|
|
847
|
-
body: functionXml,
|
|
848
|
-
headers: {
|
|
849
|
-
"Content-Type": "application/xml",
|
|
850
|
-
"If-Match": IfMatch
|
|
851
|
-
},
|
|
852
|
-
returnHeaders: true
|
|
853
|
-
});
|
|
854
|
-
return {
|
|
855
|
-
ETag: result.headers?.etag || result.ETag || "",
|
|
856
|
-
FunctionSummary: result.body?.FunctionSummary || result.FunctionSummary || result.body || result
|
|
857
|
-
};
|
|
858
|
-
}
|
|
859
|
-
async deleteFunction(name, etag) {
|
|
860
|
-
let functionETag = etag;
|
|
861
|
-
if (!functionETag) {
|
|
862
|
-
const func = await this.getFunction(name, "DEVELOPMENT");
|
|
863
|
-
if (!func) {
|
|
864
|
-
return;
|
|
865
|
-
}
|
|
866
|
-
functionETag = func.ETag;
|
|
867
|
-
}
|
|
868
|
-
await this.client.request({
|
|
869
|
-
service: "cloudfront",
|
|
870
|
-
region: "us-east-1",
|
|
871
|
-
method: "DELETE",
|
|
872
|
-
path: `/2020-05-31/function/${name}`,
|
|
873
|
-
headers: {
|
|
874
|
-
"If-Match": functionETag
|
|
875
|
-
}
|
|
876
|
-
});
|
|
877
|
-
}
|
|
878
|
-
async createIndexRewriteFunction(name) {
|
|
879
|
-
const code = `function handler(event) {
|
|
880
|
-
const request = event.request;
|
|
881
|
-
var uri = request.uri;
|
|
882
|
-
|
|
883
|
-
// Check if the request is for a directory (ends with /)
|
|
884
|
-
if (uri.endsWith('/')) {
|
|
885
|
-
request.uri += 'index.html';
|
|
886
|
-
}
|
|
887
|
-
// Check if the request doesn't have a file extension
|
|
888
|
-
else if (!uri.includes('.')) {
|
|
889
|
-
// Add trailing slash to redirect to directory
|
|
890
|
-
request.uri += '/index.html';
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
return request;
|
|
894
|
-
}`;
|
|
895
|
-
return this.createFunction({
|
|
896
|
-
name,
|
|
897
|
-
code,
|
|
898
|
-
comment: "Rewrite directory requests to index.html for S3 static sites",
|
|
899
|
-
runtime: "cloudfront-js-2.0"
|
|
900
|
-
});
|
|
901
|
-
}
|
|
902
|
-
async listOriginAccessControls() {
|
|
903
|
-
const result = await this.client.request({
|
|
904
|
-
service: "cloudfront",
|
|
905
|
-
region: "us-east-1",
|
|
906
|
-
method: "GET",
|
|
907
|
-
path: "/2020-05-31/origin-access-control"
|
|
908
|
-
});
|
|
909
|
-
const items = [];
|
|
910
|
-
if (result.OriginAccessControlList?.Items?.OriginAccessControlSummary) {
|
|
911
|
-
const summaries = Array.isArray(result.OriginAccessControlList.Items.OriginAccessControlSummary) ? result.OriginAccessControlList.Items.OriginAccessControlSummary : [result.OriginAccessControlList.Items.OriginAccessControlSummary];
|
|
912
|
-
items.push(...summaries.map((item) => ({
|
|
913
|
-
Id: item.Id,
|
|
914
|
-
Name: item.Name,
|
|
915
|
-
Description: item.Description,
|
|
916
|
-
SigningProtocol: item.SigningProtocol,
|
|
917
|
-
SigningBehavior: item.SigningBehavior,
|
|
918
|
-
OriginAccessControlOriginType: item.OriginAccessControlOriginType
|
|
919
|
-
})));
|
|
920
|
-
}
|
|
921
|
-
return items;
|
|
922
|
-
}
|
|
923
|
-
async createOriginAccessControl(options) {
|
|
924
|
-
const {
|
|
925
|
-
name,
|
|
926
|
-
description = `OAC for ${name}`,
|
|
927
|
-
signingProtocol = "sigv4",
|
|
928
|
-
signingBehavior = "always",
|
|
929
|
-
originType = "s3"
|
|
930
|
-
} = options;
|
|
931
|
-
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
932
|
-
<OriginAccessControlConfig xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
|
|
933
|
-
<Name>${name}</Name>
|
|
934
|
-
<Description>${description}</Description>
|
|
935
|
-
<SigningProtocol>${signingProtocol}</SigningProtocol>
|
|
936
|
-
<SigningBehavior>${signingBehavior}</SigningBehavior>
|
|
937
|
-
<OriginAccessControlOriginType>${originType}</OriginAccessControlOriginType>
|
|
938
|
-
</OriginAccessControlConfig>`;
|
|
939
|
-
const result = await this.client.request({
|
|
940
|
-
service: "cloudfront",
|
|
941
|
-
region: "us-east-1",
|
|
942
|
-
method: "POST",
|
|
943
|
-
path: "/2020-05-31/origin-access-control",
|
|
944
|
-
body,
|
|
945
|
-
headers: {
|
|
946
|
-
"Content-Type": "application/xml"
|
|
947
|
-
},
|
|
948
|
-
returnHeaders: true
|
|
949
|
-
});
|
|
950
|
-
const oac = result.body?.OriginAccessControl || result.OriginAccessControl || result.body || result;
|
|
951
|
-
return {
|
|
952
|
-
Id: oac.Id,
|
|
953
|
-
Name: oac.OriginAccessControlConfig?.Name || name,
|
|
954
|
-
Description: oac.OriginAccessControlConfig?.Description || description,
|
|
955
|
-
SigningProtocol: oac.OriginAccessControlConfig?.SigningProtocol || signingProtocol,
|
|
956
|
-
SigningBehavior: oac.OriginAccessControlConfig?.SigningBehavior || signingBehavior,
|
|
957
|
-
OriginAccessControlOriginType: oac.OriginAccessControlConfig?.OriginAccessControlOriginType || originType,
|
|
958
|
-
ETag: result.headers?.etag || result.ETag || ""
|
|
959
|
-
};
|
|
960
|
-
}
|
|
961
|
-
async findOrCreateOriginAccessControl(name, originType = "s3") {
|
|
962
|
-
const oacs = await this.listOriginAccessControls();
|
|
963
|
-
const existing = oacs.find((oac) => oac.Name === name && oac.OriginAccessControlOriginType === originType);
|
|
964
|
-
if (existing) {
|
|
965
|
-
return { Id: existing.Id, Name: existing.Name, isNew: false };
|
|
966
|
-
}
|
|
967
|
-
const created = await this.createOriginAccessControl({ name, originType });
|
|
968
|
-
return { Id: created.Id, Name: created.Name, isNew: true };
|
|
969
|
-
}
|
|
970
|
-
async createDistributionForS3(options) {
|
|
971
|
-
const {
|
|
972
|
-
bucketName,
|
|
973
|
-
bucketRegion,
|
|
974
|
-
originAccessControlId,
|
|
975
|
-
aliases = [],
|
|
976
|
-
certificateArn,
|
|
977
|
-
defaultRootObject = "index.html",
|
|
978
|
-
comment = `Distribution for ${bucketName}`,
|
|
979
|
-
priceClass = "PriceClass_100",
|
|
980
|
-
enabled = true,
|
|
981
|
-
trustedKeyGroupIds = [],
|
|
982
|
-
mediaDelivery = false
|
|
983
|
-
} = options;
|
|
984
|
-
const originId = `S3-${bucketName}`;
|
|
985
|
-
const s3DomainName = `${bucketName}.s3.${bucketRegion}.amazonaws.com`;
|
|
986
|
-
const callerReference = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
987
|
-
const trustedKeyGroupsXml = trustedKeyGroupIds.length > 0 ? `<TrustedKeyGroups>
|
|
988
|
-
<Enabled>true</Enabled>
|
|
989
|
-
<Quantity>${trustedKeyGroupIds.length}</Quantity>
|
|
990
|
-
<Items>${trustedKeyGroupIds.map((id) => `<KeyGroup>${id}</KeyGroup>`).join("")}</Items>
|
|
991
|
-
</TrustedKeyGroups>` : `<TrustedKeyGroups><Enabled>false</Enabled><Quantity>0</Quantity></TrustedKeyGroups>`;
|
|
992
|
-
let aliasesXml = "<Aliases><Quantity>0</Quantity></Aliases>";
|
|
993
|
-
if (aliases.length > 0) {
|
|
994
|
-
aliasesXml = `<Aliases>
|
|
995
|
-
<Quantity>${aliases.length}</Quantity>
|
|
996
|
-
<Items>
|
|
997
|
-
${aliases.map((a) => `<CNAME>${a}</CNAME>`).join(`
|
|
998
|
-
`)}
|
|
999
|
-
</Items>
|
|
1000
|
-
</Aliases>`;
|
|
1001
|
-
}
|
|
1002
|
-
let viewerCertificateXml = `<ViewerCertificate>
|
|
1003
|
-
<CloudFrontDefaultCertificate>true</CloudFrontDefaultCertificate>
|
|
1004
|
-
</ViewerCertificate>`;
|
|
1005
|
-
if (certificateArn && aliases.length > 0) {
|
|
1006
|
-
viewerCertificateXml = `<ViewerCertificate>
|
|
1007
|
-
<ACMCertificateArn>${certificateArn}</ACMCertificateArn>
|
|
1008
|
-
<SSLSupportMethod>sni-only</SSLSupportMethod>
|
|
1009
|
-
<MinimumProtocolVersion>TLSv1.2_2021</MinimumProtocolVersion>
|
|
1010
|
-
<CertificateSource>acm</CertificateSource>
|
|
1011
|
-
</ViewerCertificate>`;
|
|
1012
|
-
}
|
|
1013
|
-
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1014
|
-
<DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
|
|
1015
|
-
<CallerReference>${callerReference}</CallerReference>
|
|
1016
|
-
<Comment>${comment}</Comment>
|
|
1017
|
-
<DefaultRootObject>${defaultRootObject}</DefaultRootObject>
|
|
1018
|
-
<Origins>
|
|
1019
|
-
<Quantity>1</Quantity>
|
|
1020
|
-
<Items>
|
|
1021
|
-
<Origin>
|
|
1022
|
-
<Id>${originId}</Id>
|
|
1023
|
-
<DomainName>${s3DomainName}</DomainName>
|
|
1024
|
-
<OriginPath></OriginPath>
|
|
1025
|
-
<S3OriginConfig>
|
|
1026
|
-
<OriginAccessIdentity></OriginAccessIdentity>
|
|
1027
|
-
</S3OriginConfig>
|
|
1028
|
-
<OriginAccessControlId>${originAccessControlId}</OriginAccessControlId>
|
|
1029
|
-
</Origin>
|
|
1030
|
-
</Items>
|
|
1031
|
-
</Origins>
|
|
1032
|
-
<DefaultCacheBehavior>
|
|
1033
|
-
<TargetOriginId>${originId}</TargetOriginId>
|
|
1034
|
-
<ViewerProtocolPolicy>redirect-to-https</ViewerProtocolPolicy>
|
|
1035
|
-
<AllowedMethods>
|
|
1036
|
-
<Quantity>2</Quantity>
|
|
1037
|
-
<Items>
|
|
1038
|
-
<Method>GET</Method>
|
|
1039
|
-
<Method>HEAD</Method>
|
|
1040
|
-
</Items>
|
|
1041
|
-
<CachedMethods>
|
|
1042
|
-
<Quantity>2</Quantity>
|
|
1043
|
-
<Items>
|
|
1044
|
-
<Method>GET</Method>
|
|
1045
|
-
<Method>HEAD</Method>
|
|
1046
|
-
</Items>
|
|
1047
|
-
</CachedMethods>
|
|
1048
|
-
</AllowedMethods>
|
|
1049
|
-
<Compress>true</Compress>
|
|
1050
|
-
<CachePolicyId>658327ea-f89d-4fab-a63d-7e88639e58f6</CachePolicyId>
|
|
1051
|
-
${trustedKeyGroupsXml}
|
|
1052
|
-
</DefaultCacheBehavior>
|
|
1053
|
-
${aliasesXml}
|
|
1054
|
-
${viewerCertificateXml}
|
|
1055
|
-
<PriceClass>${priceClass}</PriceClass>
|
|
1056
|
-
<Enabled>${enabled}</Enabled>
|
|
1057
|
-
<HttpVersion>http2and3</HttpVersion>
|
|
1058
|
-
<IsIPV6Enabled>true</IsIPV6Enabled>
|
|
1059
|
-
${mediaDelivery ? "<CustomErrorResponses><Quantity>0</Quantity></CustomErrorResponses>" : `<CustomErrorResponses>
|
|
1060
|
-
<Quantity>1</Quantity>
|
|
1061
|
-
<Items>
|
|
1062
|
-
<CustomErrorResponse>
|
|
1063
|
-
<ErrorCode>403</ErrorCode>
|
|
1064
|
-
<ResponsePagePath>/index.html</ResponsePagePath>
|
|
1065
|
-
<ResponseCode>200</ResponseCode>
|
|
1066
|
-
<ErrorCachingMinTTL>300</ErrorCachingMinTTL>
|
|
1067
|
-
</CustomErrorResponse>
|
|
1068
|
-
</Items>
|
|
1069
|
-
</CustomErrorResponses>`}
|
|
1070
|
-
</DistributionConfig>`;
|
|
1071
|
-
const result = await this.client.request({
|
|
1072
|
-
service: "cloudfront",
|
|
1073
|
-
region: "us-east-1",
|
|
1074
|
-
method: "POST",
|
|
1075
|
-
path: "/2020-05-31/distribution",
|
|
1076
|
-
body,
|
|
1077
|
-
headers: {
|
|
1078
|
-
"Content-Type": "application/xml"
|
|
1079
|
-
},
|
|
1080
|
-
returnHeaders: true
|
|
1081
|
-
});
|
|
1082
|
-
const dist = result.body?.Distribution || result.Distribution || result.body || result;
|
|
1083
|
-
return {
|
|
1084
|
-
Id: dist.Id,
|
|
1085
|
-
ARN: dist.ARN,
|
|
1086
|
-
DomainName: dist.DomainName,
|
|
1087
|
-
Status: dist.Status,
|
|
1088
|
-
ETag: result.headers?.etag || result.ETag || ""
|
|
1089
|
-
};
|
|
1090
|
-
}
|
|
1091
|
-
async createMediaDistributionForS3(options) {
|
|
1092
|
-
return this.createDistributionForS3({
|
|
1093
|
-
...options,
|
|
1094
|
-
defaultRootObject: "",
|
|
1095
|
-
mediaDelivery: true,
|
|
1096
|
-
comment: options.comment ?? `Media distribution for ${options.bucketName}`
|
|
1097
|
-
});
|
|
1098
|
-
}
|
|
1099
|
-
async listPublicKeys() {
|
|
1100
|
-
const result = await this.client.request({
|
|
1101
|
-
service: "cloudfront",
|
|
1102
|
-
region: "us-east-1",
|
|
1103
|
-
method: "GET",
|
|
1104
|
-
path: "/2020-05-31/public-key"
|
|
1105
|
-
});
|
|
1106
|
-
const values = this.collectionItems(result.PublicKeyList ?? result, "PublicKeySummary");
|
|
1107
|
-
return values.map((value) => ({
|
|
1108
|
-
Id: String(value.Id),
|
|
1109
|
-
Name: String(value.Name),
|
|
1110
|
-
Comment: value.Comment ? String(value.Comment) : undefined
|
|
1111
|
-
}));
|
|
1112
|
-
}
|
|
1113
|
-
async createPublicKey(options) {
|
|
1114
|
-
if (!/^[A-Za-z0-9_-]{1,128}$/.test(options.name))
|
|
1115
|
-
throw new TypeError("CloudFront public key name is invalid");
|
|
1116
|
-
if (!options.encodedKey.includes("BEGIN PUBLIC KEY"))
|
|
1117
|
-
throw new TypeError("CloudFront public key must be PEM encoded");
|
|
1118
|
-
const escape = (value2) => value2.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
1119
|
-
const result = await this.client.request({
|
|
1120
|
-
service: "cloudfront",
|
|
1121
|
-
region: "us-east-1",
|
|
1122
|
-
method: "POST",
|
|
1123
|
-
path: "/2020-05-31/public-key",
|
|
1124
|
-
headers: { "Content-Type": "application/xml" },
|
|
1125
|
-
body: `<PublicKeyConfig xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/"><CallerReference>${escape(options.callerReference ?? `${Date.now()}-${options.name}`)}</CallerReference><Name>${escape(options.name)}</Name><EncodedKey>${escape(options.encodedKey)}</EncodedKey><Comment>${escape(options.comment ?? "")}</Comment></PublicKeyConfig>`
|
|
1126
|
-
});
|
|
1127
|
-
const value = result.PublicKey ?? result;
|
|
1128
|
-
return {
|
|
1129
|
-
Id: String(value.Id),
|
|
1130
|
-
Name: String(value.PublicKeyConfig?.Name ?? options.name),
|
|
1131
|
-
EncodedKey: String(value.PublicKeyConfig?.EncodedKey ?? options.encodedKey),
|
|
1132
|
-
Comment: String(value.PublicKeyConfig?.Comment ?? options.comment ?? "")
|
|
1133
|
-
};
|
|
1134
|
-
}
|
|
1135
|
-
async listKeyGroups() {
|
|
1136
|
-
const result = await this.client.request({
|
|
1137
|
-
service: "cloudfront",
|
|
1138
|
-
region: "us-east-1",
|
|
1139
|
-
method: "GET",
|
|
1140
|
-
path: "/2020-05-31/key-group"
|
|
1141
|
-
});
|
|
1142
|
-
const values = this.collectionItems(result.KeyGroupList ?? result, "KeyGroupSummary");
|
|
1143
|
-
return values.map((value) => ({
|
|
1144
|
-
Id: String(value.KeyGroup?.Id ?? value.Id),
|
|
1145
|
-
Name: String(value.KeyGroup?.KeyGroupConfig?.Name ?? value.KeyGroupConfig?.Name ?? value.Name),
|
|
1146
|
-
Items: this.collectionItems(value.KeyGroup?.KeyGroupConfig?.Items ?? value.KeyGroupConfig?.Items ?? value.Items, "Item").map(String),
|
|
1147
|
-
Comment: value.KeyGroup?.KeyGroupConfig?.Comment ?? value.KeyGroupConfig?.Comment ?? value.Comment
|
|
1148
|
-
}));
|
|
1149
|
-
}
|
|
1150
|
-
async createKeyGroup(options) {
|
|
1151
|
-
if (!/^[A-Za-z0-9_-]{1,128}$/.test(options.name))
|
|
1152
|
-
throw new TypeError("CloudFront key group name is invalid");
|
|
1153
|
-
const publicKeyIds = [...new Set(options.publicKeyIds)];
|
|
1154
|
-
if (publicKeyIds.length === 0)
|
|
1155
|
-
throw new TypeError("CloudFront key group requires at least one public key");
|
|
1156
|
-
const escape = (value2) => value2.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
1157
|
-
const items = publicKeyIds.map((id) => `<Item>${escape(id)}</Item>`).join("");
|
|
1158
|
-
const result = await this.client.request({
|
|
1159
|
-
service: "cloudfront",
|
|
1160
|
-
region: "us-east-1",
|
|
1161
|
-
method: "POST",
|
|
1162
|
-
path: "/2020-05-31/key-group",
|
|
1163
|
-
headers: { "Content-Type": "application/xml" },
|
|
1164
|
-
body: `<KeyGroupConfig xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/"><Name>${escape(options.name)}</Name><Items>${items}</Items><Comment>${escape(options.comment ?? "")}</Comment></KeyGroupConfig>`
|
|
1165
|
-
});
|
|
1166
|
-
const value = result.KeyGroup ?? result;
|
|
1167
|
-
return {
|
|
1168
|
-
Id: String(value.Id),
|
|
1169
|
-
Name: String(value.KeyGroupConfig?.Name ?? options.name),
|
|
1170
|
-
Items: publicKeyIds,
|
|
1171
|
-
Comment: String(value.KeyGroupConfig?.Comment ?? options.comment ?? "")
|
|
1172
|
-
};
|
|
1173
|
-
}
|
|
1174
|
-
static getS3BucketPolicyForCloudFront(bucketName, distributionArn) {
|
|
1175
|
-
return {
|
|
1176
|
-
Version: "2012-10-17",
|
|
1177
|
-
Statement: [
|
|
1178
|
-
{
|
|
1179
|
-
Sid: "AllowCloudFrontServicePrincipal",
|
|
1180
|
-
Effect: "Allow",
|
|
1181
|
-
Principal: {
|
|
1182
|
-
Service: "cloudfront.amazonaws.com"
|
|
1183
|
-
},
|
|
1184
|
-
Action: "s3:GetObject",
|
|
1185
|
-
Resource: `arn:aws:s3:::${bucketName}/*`,
|
|
1186
|
-
Condition: {
|
|
1187
|
-
StringEquals: {
|
|
1188
|
-
"AWS:SourceArn": distributionArn
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
]
|
|
1193
|
-
};
|
|
1194
|
-
}
|
|
1195
|
-
async waitForDistributionDeployed(distributionId, maxAttempts = 60) {
|
|
1196
|
-
for (let i = 0;i < maxAttempts; i++) {
|
|
1197
|
-
const dist = await this.getDistribution(distributionId);
|
|
1198
|
-
if (dist.Status === "Deployed") {
|
|
1199
|
-
return true;
|
|
1200
|
-
}
|
|
1201
|
-
await new Promise((resolve) => setTimeout(resolve, 30000));
|
|
1202
|
-
}
|
|
1203
|
-
return false;
|
|
1204
|
-
}
|
|
1205
|
-
async disableDistribution(distributionId) {
|
|
1206
|
-
const getResult = await this.client.request({
|
|
1207
|
-
service: "cloudfront",
|
|
1208
|
-
region: "us-east-1",
|
|
1209
|
-
method: "GET",
|
|
1210
|
-
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
1211
|
-
returnHeaders: true
|
|
1212
|
-
});
|
|
1213
|
-
const etag = getResult.headers?.etag || getResult.headers?.ETag || "";
|
|
1214
|
-
const currentConfig = getResult.body?.DistributionConfig || (getResult.body?.Enabled !== undefined ? getResult.body : undefined) || getResult.DistributionConfig;
|
|
1215
|
-
if (!currentConfig || currentConfig.Enabled === undefined) {
|
|
1216
|
-
throw new Error("Failed to get current distribution config");
|
|
1217
|
-
}
|
|
1218
|
-
currentConfig.Enabled = false;
|
|
1219
|
-
const configXml = this.buildDistributionConfigXml(currentConfig);
|
|
1220
|
-
const result = await this.client.request({
|
|
1221
|
-
service: "cloudfront",
|
|
1222
|
-
region: "us-east-1",
|
|
1223
|
-
method: "PUT",
|
|
1224
|
-
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
1225
|
-
body: configXml,
|
|
1226
|
-
headers: {
|
|
1227
|
-
"Content-Type": "application/xml",
|
|
1228
|
-
"If-Match": etag
|
|
1229
|
-
},
|
|
1230
|
-
returnHeaders: true
|
|
1231
|
-
});
|
|
1232
|
-
return { ETag: result.headers?.etag || result.headers?.ETag || result.ETag || "" };
|
|
1233
|
-
}
|
|
1234
|
-
async deleteDistribution(distributionId, etag) {
|
|
1235
|
-
let etagToUse = etag || "";
|
|
1236
|
-
if (!etagToUse) {
|
|
1237
|
-
const getResult = await this.client.request({
|
|
1238
|
-
service: "cloudfront",
|
|
1239
|
-
region: "us-east-1",
|
|
1240
|
-
method: "GET",
|
|
1241
|
-
path: `/2020-05-31/distribution/${distributionId}`,
|
|
1242
|
-
returnHeaders: true
|
|
1243
|
-
});
|
|
1244
|
-
etagToUse = getResult.headers?.etag || getResult.headers?.ETag || "";
|
|
1245
|
-
}
|
|
1246
|
-
await this.client.request({
|
|
1247
|
-
service: "cloudfront",
|
|
1248
|
-
region: "us-east-1",
|
|
1249
|
-
method: "DELETE",
|
|
1250
|
-
path: `/2020-05-31/distribution/${distributionId}`,
|
|
1251
|
-
headers: {
|
|
1252
|
-
"If-Match": etagToUse
|
|
1253
|
-
}
|
|
1254
|
-
});
|
|
1255
|
-
}
|
|
1256
|
-
async waitForDistributionDisabled(distributionId, maxAttempts = 60) {
|
|
1257
|
-
for (let i = 0;i < maxAttempts; i++) {
|
|
1258
|
-
const dist = await this.getDistribution(distributionId);
|
|
1259
|
-
if (dist.Status === "Deployed" && !dist.Enabled) {
|
|
1260
|
-
return true;
|
|
1261
|
-
}
|
|
1262
|
-
await new Promise((resolve) => setTimeout(resolve, 30000));
|
|
1263
|
-
}
|
|
1264
|
-
return false;
|
|
1265
|
-
}
|
|
1266
|
-
async removeAlias(distributionId, alias) {
|
|
1267
|
-
const getResult = await this.client.request({
|
|
1268
|
-
service: "cloudfront",
|
|
1269
|
-
region: "us-east-1",
|
|
1270
|
-
method: "GET",
|
|
1271
|
-
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
1272
|
-
returnHeaders: true
|
|
1273
|
-
});
|
|
1274
|
-
const etag = getResult.headers?.etag || getResult.headers?.ETag || "";
|
|
1275
|
-
const currentConfig = getResult.body?.DistributionConfig || (getResult.body?.Enabled !== undefined ? getResult.body : undefined) || getResult.DistributionConfig;
|
|
1276
|
-
if (!currentConfig || currentConfig.Enabled === undefined) {
|
|
1277
|
-
throw new Error("Failed to get current distribution config");
|
|
1278
|
-
}
|
|
1279
|
-
let items = [];
|
|
1280
|
-
if (currentConfig.Aliases?.Items) {
|
|
1281
|
-
if (Array.isArray(currentConfig.Aliases.Items)) {
|
|
1282
|
-
items = currentConfig.Aliases.Items;
|
|
1283
|
-
} else if (typeof currentConfig.Aliases.Items === "object") {
|
|
1284
|
-
const cname = currentConfig.Aliases.Items.CNAME;
|
|
1285
|
-
if (typeof cname === "string") {
|
|
1286
|
-
items = [cname];
|
|
1287
|
-
} else if (Array.isArray(cname)) {
|
|
1288
|
-
items = cname;
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
if (items.length === 0) {
|
|
1293
|
-
throw new Error(`Distribution has no aliases to remove`);
|
|
1294
|
-
}
|
|
1295
|
-
const newItems = items.filter((a) => a !== alias);
|
|
1296
|
-
if (newItems.length === items.length) {
|
|
1297
|
-
throw new Error(`Alias ${alias} not found in distribution`);
|
|
1298
|
-
}
|
|
1299
|
-
currentConfig.Aliases.Quantity = newItems.length;
|
|
1300
|
-
currentConfig.Aliases.Items = newItems.length > 0 ? newItems : undefined;
|
|
1301
|
-
if (newItems.length === 0) {
|
|
1302
|
-
currentConfig.ViewerCertificate = {
|
|
1303
|
-
CloudFrontDefaultCertificate: true,
|
|
1304
|
-
MinimumProtocolVersion: "TLSv1.2_2021"
|
|
1305
|
-
};
|
|
1306
|
-
}
|
|
1307
|
-
const configXml = this.buildDistributionConfigXml(currentConfig);
|
|
1308
|
-
const result = await this.client.request({
|
|
1309
|
-
service: "cloudfront",
|
|
1310
|
-
region: "us-east-1",
|
|
1311
|
-
method: "PUT",
|
|
1312
|
-
path: `/2020-05-31/distribution/${distributionId}/config`,
|
|
1313
|
-
body: configXml,
|
|
1314
|
-
headers: {
|
|
1315
|
-
"Content-Type": "application/xml",
|
|
1316
|
-
"If-Match": etag
|
|
1317
|
-
},
|
|
1318
|
-
returnHeaders: true
|
|
1319
|
-
});
|
|
1320
|
-
return { ETag: result.headers?.etag || result.headers?.ETag || result.ETag || "" };
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
export { CloudFrontClient };
|