@webda/aws 4.0.0-beta.3 → 4.0.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/lib/deployers/awsdeployer.d.ts +340 -0
  2. package/lib/deployers/awsdeployer.d.ts.map +1 -0
  3. package/lib/deployers/awsdeployer.js +673 -0
  4. package/lib/deployers/awsdeployer.js.map +1 -0
  5. package/lib/deployers/cloudformation.service.d.ts +438 -0
  6. package/lib/deployers/cloudformation.service.d.ts.map +1 -0
  7. package/lib/deployers/cloudformation.service.js +889 -0
  8. package/lib/deployers/cloudformation.service.js.map +1 -0
  9. package/lib/deployers/index.d.ts +5 -0
  10. package/lib/deployers/index.d.ts.map +1 -0
  11. package/lib/deployers/index.js +5 -0
  12. package/lib/deployers/index.js.map +1 -0
  13. package/lib/deployers/lambda-entrypoint.d.ts +34 -0
  14. package/lib/deployers/lambda-entrypoint.d.ts.map +1 -0
  15. package/lib/deployers/lambda-entrypoint.js +47 -0
  16. package/lib/deployers/lambda-entrypoint.js.map +1 -0
  17. package/lib/deployers/lambdapackager.service.d.ts +123 -0
  18. package/lib/deployers/lambdapackager.service.d.ts.map +1 -0
  19. package/lib/deployers/lambdapackager.service.js +149 -0
  20. package/lib/deployers/lambdapackager.service.js.map +1 -0
  21. package/lib/deployers/zip.d.ts +42 -0
  22. package/lib/deployers/zip.d.ts.map +1 -0
  23. package/lib/deployers/zip.js +129 -0
  24. package/lib/deployers/zip.js.map +1 -0
  25. package/lib/index.d.ts +1 -0
  26. package/lib/index.d.ts.map +1 -1
  27. package/lib/index.js +1 -0
  28. package/lib/index.js.map +1 -1
  29. package/lib/services/contributors.d.ts +1 -2
  30. package/lib/services/contributors.d.ts.map +1 -1
  31. package/lib/services/dynamodb.service.d.ts +2 -1
  32. package/lib/services/dynamodb.service.d.ts.map +1 -1
  33. package/lib/services/dynamodb.service.js +5 -4
  34. package/lib/services/dynamodb.service.js.map +1 -1
  35. package/lib/services/s3binary.service.d.ts.map +1 -1
  36. package/lib/services/s3binary.service.js +21 -20
  37. package/lib/services/s3binary.service.js.map +1 -1
  38. package/package.json +12 -14
  39. package/webda.module.json +698 -4
@@ -0,0 +1,673 @@
1
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
2
+ var useValue = arguments.length > 2;
3
+ for (var i = 0; i < initializers.length; i++) {
4
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
5
+ }
6
+ return useValue ? value : void 0;
7
+ };
8
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
9
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
10
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
11
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
12
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
13
+ var _, done = false;
14
+ for (var i = decorators.length - 1; i >= 0; i--) {
15
+ var context = {};
16
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
17
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
18
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
19
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
20
+ if (kind === "accessor") {
21
+ if (result === void 0) continue;
22
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
23
+ if (_ = accept(result.get)) descriptor.get = _;
24
+ if (_ = accept(result.set)) descriptor.set = _;
25
+ if (_ = accept(result.init)) initializers.unshift(_);
26
+ }
27
+ else if (_ = accept(result)) {
28
+ if (kind === "field") initializers.unshift(_);
29
+ else descriptor[key] = _;
30
+ }
31
+ }
32
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
33
+ done = true;
34
+ };
35
+ import { ACM } from "@aws-sdk/client-acm";
36
+ import { EC2 } from "@aws-sdk/client-ec2";
37
+ import { Route53 } from "@aws-sdk/client-route-53";
38
+ import { NotFound, S3 } from "@aws-sdk/client-s3";
39
+ import { STS } from "@aws-sdk/client-sts";
40
+ import { getDeploymentVariables, InstanceCache, resolveDeploymentVariables, Service, ServiceParameters, useCore, WebdaError } from "@webda/core";
41
+ import { WaitFor, WaitLinearDelay } from "@webda/utils";
42
+ import * as crypto from "node:crypto";
43
+ import { randomUUID } from "node:crypto";
44
+ import * as fs from "node:fs";
45
+ import * as path from "node:path";
46
+ import IamPolicyOptimizer from "iam-policy-optimizer";
47
+ import * as mime from "mime-types";
48
+ import { Route53Service } from "../services/route53.service.js";
49
+ /**
50
+ * Common parameters of the AWS deployers
51
+ *
52
+ * A deployer is declared as a unit of a deployment (`deployments/<name>.json`),
53
+ * its parameters can use the `${...}` deployment variables ({@link getDeploymentVariables})
54
+ */
55
+ export class AWSDeployerParameters extends ServiceParameters {
56
+ }
57
+ /**
58
+ * Abstract AWS Deployer
59
+ *
60
+ * It includes some basic utilities methods to be used by
61
+ * final deployers. A final deployer declares `implements Deployer` itself.
62
+ */
63
+ let AWSDeployer = (() => {
64
+ var _a;
65
+ let _classSuper = Service;
66
+ let _instanceExtraInitializers = [];
67
+ let _getAWSIdentity_decorators;
68
+ let _getDefaultVpc_decorators;
69
+ let _getZoneForDomainName_decorators;
70
+ let _getPolicyDocument_decorators;
71
+ return _a = class AWSDeployer extends _classSuper {
72
+ constructor() {
73
+ super(...arguments);
74
+ /**
75
+ * Parameters of the deployer with the deployment variables replaced and the defaults applied
76
+ *
77
+ * Computed by {@link AWSDeployer.prepare}
78
+ */
79
+ this.resources = __runInitializers(this, _instanceExtraInitializers);
80
+ }
81
+ /**
82
+ * Compute the resources from the parameters
83
+ *
84
+ * @param extra - additional deployment variables
85
+ * @returns the resources
86
+ */
87
+ async prepare(extra = {}) {
88
+ var _b;
89
+ this.resources = this.getRawParameters();
90
+ (_b = this.resources).name ?? (_b.name = this.getName());
91
+ // Defaults can use the deployment variables too
92
+ await this.defaultResources();
93
+ this.resources = resolveDeploymentVariables(this.resources, getDeploymentVariables(this, { resources: this.resources, ...extra }));
94
+ return this.resources;
95
+ }
96
+ /**
97
+ * Parameters as a plain object
98
+ * @returns a copy of the parameters
99
+ */
100
+ getRawParameters() {
101
+ const raw = JSON.parse(JSON.stringify(this.getParameters() ?? {}));
102
+ delete raw.watchers;
103
+ return raw;
104
+ }
105
+ /**
106
+ * Apply the default resources
107
+ */
108
+ async defaultResources() {
109
+ var _b, _c, _d, _e, _f;
110
+ (_b = this.resources).accessKeyId ?? (_b.accessKeyId = process.env["AWS_ACCESS_KEY_ID"]);
111
+ (_c = this.resources).secretAccessKey ?? (_c.secretAccessKey = process.env["AWS_SECRET_ACCESS_KEY"]);
112
+ (_d = this.resources).sessionToken ?? (_d.sessionToken = process.env["AWS_SESSION_TOKEN"]);
113
+ (_e = this.resources).region ?? (_e.region = process.env["AWS_DEFAULT_REGION"] || "us-east-1");
114
+ (_f = this.resources).endpoints ?? (_f.endpoints = {});
115
+ this.resources.Tags = this.transformMapTagsToArray(this.resources.Tags || {});
116
+ }
117
+ /**
118
+ * Configuration of an AWS client
119
+ *
120
+ * @param endpoint - the client, to use its custom endpoint
121
+ * @param region - force a region
122
+ * @returns the client configuration
123
+ */
124
+ getClientConfig(endpoint, region) {
125
+ const config = {
126
+ region: region ?? this.getRegion()
127
+ };
128
+ if (endpoint && this.resources.endpoints?.[endpoint]) {
129
+ config.endpoint = this.resources.endpoints[endpoint];
130
+ }
131
+ if (this.resources.accessKeyId && this.resources.secretAccessKey) {
132
+ config.credentials = {
133
+ accessKeyId: this.resources.accessKeyId,
134
+ secretAccessKey: this.resources.secretAccessKey,
135
+ sessionToken: this.resources.sessionToken
136
+ };
137
+ }
138
+ return config;
139
+ }
140
+ /**
141
+ * Return AWS region
142
+ * @returns the region
143
+ */
144
+ getRegion() {
145
+ return this.resources?.region ?? "us-east-1";
146
+ }
147
+ /**
148
+ * Return the current AWS Identity used
149
+ * @returns the identity
150
+ */
151
+ async getAWSIdentity() {
152
+ const sts = new STS(this.getClientConfig("STS"));
153
+ return sts.getCallerIdentity({});
154
+ }
155
+ /**
156
+ * Return the default VPC for the current region
157
+ * @returns the VPC id and its subnets, undefined if there is no default VPC
158
+ */
159
+ async getDefaultVpc() {
160
+ const defaultVpc = {
161
+ Id: "",
162
+ Subnets: []
163
+ };
164
+ const ec2 = new EC2(this.getClientConfig("EC2"));
165
+ const res = await ec2.describeVpcs({});
166
+ for (const vpc of res.Vpcs ?? []) {
167
+ if (vpc.IsDefault) {
168
+ defaultVpc.Id = vpc.VpcId;
169
+ break;
170
+ }
171
+ }
172
+ if (defaultVpc.Id === "") {
173
+ return undefined;
174
+ }
175
+ const res2 = await ec2.describeSubnets({
176
+ Filters: [
177
+ {
178
+ Name: "vpc-id",
179
+ Values: [defaultVpc.Id]
180
+ }
181
+ ]
182
+ });
183
+ defaultVpc.Subnets.push(...(res2.Subnets ?? []));
184
+ return defaultVpc;
185
+ }
186
+ /**
187
+ * Generate a MD5 in hex
188
+ * @param str - to hash
189
+ * @returns the hash
190
+ */
191
+ md5(str) {
192
+ return this.hash(str, "md5", "hex");
193
+ }
194
+ /**
195
+ * Hash the string
196
+ *
197
+ * @param str - to hash
198
+ * @param type - of hash
199
+ * @param format - hex or b64
200
+ * @returns the hash
201
+ */
202
+ hash(str, type = "md5", format = "hex") {
203
+ return crypto.createHash(type).update(str).digest(format);
204
+ }
205
+ /**
206
+ * Replace / by _ as theses ID are not allowed in AWS
207
+ *
208
+ * @param id - to replace
209
+ * @returns the AWS compatible id
210
+ */
211
+ _replaceForAWS(id) {
212
+ return id.replace(/\//g, "_");
213
+ }
214
+ /**
215
+ * Get a certificate or create it
216
+ *
217
+ * @param domain - to get certificate for
218
+ * @param region - of the certificate
219
+ * @returns the certificate
220
+ */
221
+ async getCertificate(domain, region = undefined) {
222
+ if (domain.endsWith(".")) {
223
+ domain = domain.substring(0, domain.length - 1);
224
+ }
225
+ const acm = new ACM(this.getClientConfig("ACM", region));
226
+ const params = {};
227
+ let res;
228
+ let certificate;
229
+ do {
230
+ res = await acm.listCertificates(params);
231
+ certificate = (res.CertificateSummaryList ?? []).filter(cert => cert.DomainName === domain).pop();
232
+ params.NextToken = res.NextToken;
233
+ } while (!certificate && res.NextToken);
234
+ // We did not find the certificate need to create one
235
+ if (!certificate) {
236
+ const zone = await this.getZoneForDomainName(domain);
237
+ if (!zone) {
238
+ throw new WebdaError.CodeError("ROUTE53_NOTFOUND", "Cannot create certificate as Route53 Zone was not found");
239
+ }
240
+ this.log("INFO", "Creating a certificate for", domain);
241
+ certificate = await this.doCreateCertificate(domain, zone, region);
242
+ }
243
+ return certificate;
244
+ }
245
+ /**
246
+ * Get the closest zone to the domain
247
+ *
248
+ * @param domain - to get zone for
249
+ * @returns the zone
250
+ */
251
+ async getZoneForDomainName(domain) {
252
+ return Route53Service.getZoneForDomainName(domain);
253
+ }
254
+ /**
255
+ * Transform a tag map into a tag array
256
+ *
257
+ * @param tags - to transform
258
+ * @returns the tags array
259
+ */
260
+ transformMapTagsToArray(tags) {
261
+ if (Array.isArray(tags)) {
262
+ return tags;
263
+ }
264
+ const res = [];
265
+ for (const i in tags) {
266
+ res.push({ Key: i, Value: tags[i] });
267
+ }
268
+ return res;
269
+ }
270
+ /**
271
+ * Transform a tag array into a tag map
272
+ *
273
+ * @param tags - to transform
274
+ * @returns the tags map
275
+ */
276
+ transformArrayTagsToMap(tags) {
277
+ if (!Array.isArray(tags)) {
278
+ return tags;
279
+ }
280
+ const res = {};
281
+ tags.forEach(t => (res[t.Key] = t.Value));
282
+ return res;
283
+ }
284
+ /**
285
+ * Take this.resources[key].Tags and add all remaining Tags from this.resources.Tags
286
+ *
287
+ * @param key - of the resources to add, or the tags themselves
288
+ * @returns the tags array
289
+ */
290
+ getDefaultTags(key = undefined) {
291
+ let Tags;
292
+ if (typeof key === "string") {
293
+ Tags = this.resources[key] ? this.transformMapTagsToArray(this.resources[key].Tags || []) : [];
294
+ }
295
+ else {
296
+ Tags = this.transformMapTagsToArray(key || []);
297
+ }
298
+ const defaults = this.resources.Tags || [];
299
+ if (defaults.length) {
300
+ const TagKeys = Tags.map(t => t.Key);
301
+ Tags.push(...defaults.filter(t => TagKeys.indexOf(t.Key) < 0));
302
+ }
303
+ return Tags;
304
+ }
305
+ /**
306
+ * Take this.resources[key].Tags and add all remaining Tags from this.resources.Tags
307
+ *
308
+ * @param key - of the resources to add
309
+ * @returns the tags map
310
+ */
311
+ getDefaultTagsAsMap(key = undefined) {
312
+ return this.transformArrayTagsToMap(this.getDefaultTags(key));
313
+ }
314
+ /**
315
+ * Return the S3 Tagging string
316
+ * @param key - of the resources to add
317
+ * @returns the url encoded tags
318
+ */
319
+ getDefaultTagsAsS3Tagging(key = undefined) {
320
+ return this.getDefaultTags(key)
321
+ .map(tag => `${encodeURIComponent(tag.Key)}=${encodeURIComponent(tag.Value)}`)
322
+ .join("&");
323
+ }
324
+ /**
325
+ * Wait for a condition with a linear delay
326
+ *
327
+ * @param callback - resolve when done, return true to stop
328
+ * @param retries - maximum number of tries
329
+ * @param title - of the wait
330
+ * @param delay - between tries in ms
331
+ * @returns the resolved value
332
+ */
333
+ async waitFor(callback, retries, title, delay) {
334
+ return WaitFor(callback, retries, title, this.logger, WaitLinearDelay(delay));
335
+ }
336
+ /**
337
+ * Create a certificate for a domain
338
+ * Will use Route 53 to do the validation
339
+ *
340
+ * @param domain - to create the certificate for
341
+ * @param zone - to create the validation record in
342
+ * @param region - of the certificate
343
+ * @returns the certificate
344
+ */
345
+ async doCreateCertificate(domain, zone, region) {
346
+ const acm = new ACM(this.getClientConfig("ACM", region));
347
+ if (domain.endsWith(".")) {
348
+ domain = domain.substring(0, domain.length - 1);
349
+ }
350
+ const params = {
351
+ DomainName: domain,
352
+ DomainValidationOptions: [
353
+ {
354
+ DomainName: domain,
355
+ ValidationDomain: domain
356
+ }
357
+ ],
358
+ ValidationMethod: "DNS",
359
+ IdempotencyToken: "Webda_" + this.md5(domain).substring(0, 26)
360
+ };
361
+ const certificate = await acm.requestCertificate(params);
362
+ let cert = await this.waitFor(async (resolve) => {
363
+ const res = await acm.describeCertificate({
364
+ CertificateArn: certificate.CertificateArn
365
+ });
366
+ if (res.Certificate.DomainValidationOptions && res.Certificate.DomainValidationOptions[0].ResourceRecord) {
367
+ resolve(res.Certificate);
368
+ return true;
369
+ }
370
+ return false;
371
+ }, 5, "Waiting for certificate challenge", 10000);
372
+ if (cert === undefined || cert.Status === "FAILED") {
373
+ throw new WebdaError.CodeError("ACM_VALIDATION_FAILED", "Certificate validation has failed");
374
+ }
375
+ if (cert.Status === "PENDING_VALIDATION") {
376
+ // On create need to wait
377
+ const record = cert.DomainValidationOptions[0].ResourceRecord;
378
+ this.log("INFO", "Need to validate certificate", cert.CertificateArn);
379
+ await this.createDNSEntry(record.Name, "CNAME", record.Value, zone);
380
+ // Waiting for certificate validation
381
+ cert = await this.waitFor(async (resolve, reject) => {
382
+ const res = await acm.describeCertificate({
383
+ CertificateArn: cert.CertificateArn
384
+ });
385
+ if (res.Certificate.Status === "ISSUED") {
386
+ resolve(res.Certificate);
387
+ return true;
388
+ }
389
+ if (res.Certificate.Status !== "PENDING_VALIDATION") {
390
+ reject(res.Certificate);
391
+ return true;
392
+ }
393
+ return false;
394
+ }, 10, "Waiting for certificate validation", 60000);
395
+ }
396
+ return cert;
397
+ }
398
+ /**
399
+ * Create DNS entry
400
+ *
401
+ * @param domain - to create
402
+ * @param type - of DNS
403
+ * @param value - the value of the record
404
+ * @param targetZone - the zone, found from the domain if not provided
405
+ */
406
+ async createDNSEntry(domain, type, value, targetZone = undefined) {
407
+ this.log("INFO", `Creating DNS entry ${domain} ${type} ${value}`);
408
+ const r53 = new Route53(this.getClientConfig("Route53"));
409
+ if (!domain.endsWith(".")) {
410
+ domain = domain + ".";
411
+ }
412
+ targetZone ?? (targetZone = await this.getZoneForDomainName(domain));
413
+ if (!targetZone) {
414
+ throw new WebdaError.CodeError("ROUTE53_NOTFOUND", "Domain is not handled on AWS");
415
+ }
416
+ await r53.changeResourceRecordSets({
417
+ HostedZoneId: targetZone.Id,
418
+ ChangeBatch: {
419
+ Changes: [
420
+ {
421
+ Action: "UPSERT",
422
+ ResourceRecordSet: {
423
+ Name: domain,
424
+ ResourceRecords: [
425
+ {
426
+ Value: value
427
+ }
428
+ ],
429
+ TTL: 360,
430
+ Type: type
431
+ }
432
+ }
433
+ ],
434
+ Comment: "webda-automated-deploiement"
435
+ }
436
+ });
437
+ }
438
+ /**
439
+ * Permissions needed by the deployed application itself
440
+ *
441
+ * @param accountId - the account
442
+ * @param region - the region
443
+ * @returns the policy statements
444
+ */
445
+ getARNPolicy(accountId, region) {
446
+ return [
447
+ {
448
+ Sid: "WebdaLog",
449
+ Effect: "Allow",
450
+ Action: ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
451
+ Resource: ["arn:aws:logs:" + region + ":" + accountId + ":*"]
452
+ }
453
+ ];
454
+ }
455
+ /**
456
+ * Generate the `PolicyDocument`
457
+ *
458
+ * It will browse all services for a method `getARNPolicy`
459
+ * Allowing you to write some specific Service or Bean that
460
+ * requires specific AWS permissions
461
+ *
462
+ * @param additionalStatements - statements to add
463
+ * @returns the optimized policy document
464
+ */
465
+ async getPolicyDocument(additionalStatements = []) {
466
+ const me = await this.getAWSIdentity();
467
+ const statements = [];
468
+ // Build policy
469
+ for (const service of this.getApplicationServices()) {
470
+ if (typeof service.getARNPolicy === "function") {
471
+ // Update to match recuring policy - might need to split if policy too big
472
+ const res = service.getARNPolicy(me.Account, this.getRegion());
473
+ if (Array.isArray(res)) {
474
+ statements.push(...res);
475
+ }
476
+ else {
477
+ statements.push(res);
478
+ }
479
+ }
480
+ }
481
+ statements.push(...this.getARNPolicy(me.Account, this.getRegion()));
482
+ const policyDocument = {
483
+ Version: "2012-10-17",
484
+ Statement: [...statements, ...additionalStatements]
485
+ };
486
+ const optimizer = IamPolicyOptimizer.default ?? IamPolicyOptimizer;
487
+ return optimizer.reducePolicyObject(policyDocument);
488
+ }
489
+ /**
490
+ * Services of the application, the deployers excluded
491
+ *
492
+ * @returns the services
493
+ */
494
+ getApplicationServices() {
495
+ return Object.values(useCore()?.getServices() ?? {}).filter(service => service && !(service instanceof _a));
496
+ }
497
+ /**
498
+ * Create a bucket if it does not exist
499
+ *
500
+ * @param Bucket - to create
501
+ */
502
+ async createBucket(Bucket) {
503
+ const s3 = this.getS3();
504
+ try {
505
+ await s3.headBucket({
506
+ Bucket
507
+ });
508
+ }
509
+ catch (err) {
510
+ if (err.name === "Forbidden") {
511
+ this.log("ERROR", "S3 bucket already exists in another account or you do not have permissions on it");
512
+ }
513
+ else if (err instanceof NotFound || err.name === "NotFound") {
514
+ this.log("INFO", "\tCreating S3 Bucket", Bucket);
515
+ await s3.createBucket({
516
+ Bucket
517
+ });
518
+ const Tags = this.getDefaultTags([]);
519
+ if (Tags.length) {
520
+ await s3.putBucketTagging({
521
+ Bucket,
522
+ Tagging: {
523
+ TagSet: Tags
524
+ }
525
+ });
526
+ }
527
+ }
528
+ }
529
+ }
530
+ /**
531
+ * S3 client
532
+ * @returns the client
533
+ */
534
+ getS3() {
535
+ return new S3({
536
+ ...this.getClientConfig("S3"),
537
+ forcePathStyle: this.resources.endpoints?.S3 !== undefined
538
+ });
539
+ }
540
+ /**
541
+ * Send a full folder (recursive) on bucket
542
+ *
543
+ * @param bucket - to send data to
544
+ * @param folder - path to local folder to send
545
+ * @param prefix - prefix on the bucket
546
+ */
547
+ async putFolderOnBucket(bucket, folder, prefix = "") {
548
+ const absFolder = path.resolve(folder);
549
+ const files = fs.readdirSync(absFolder, { recursive: true })
550
+ .map(f => path.join(absFolder, f))
551
+ .filter(f => fs.statSync(f).isFile());
552
+ await this.putFilesOnBucket(bucket,
553
+ // Replace \ by / for Windows system
554
+ files.map(f => ({
555
+ key: `${prefix}${path.relative(absFolder, f).replace(/\\/g, "/")}`,
556
+ src: f
557
+ })));
558
+ }
559
+ /**
560
+ * Find the common prefix between two strings
561
+ *
562
+ * Example
563
+ * ```
564
+ * commonPrefix("/test/plop1", "/templates/") => "/te"
565
+ * ```
566
+ *
567
+ * @param str1 - to compare
568
+ * @param str2 - to compare
569
+ * @returns the common prefix
570
+ */
571
+ commonPrefix(str1, str2) {
572
+ let res = "";
573
+ let i = 0;
574
+ while (i < str1.length && i < str2.length && str1[i] === str2[i]) {
575
+ res += str1[i];
576
+ i++;
577
+ }
578
+ return res;
579
+ }
580
+ /**
581
+ * Add files to a bucket
582
+ *
583
+ * It uses hash and ETag to avoid uploading files already present
584
+ *
585
+ * The files src can be either:
586
+ * - a string representing the local path
587
+ * - a Buffer with the dynamic content
588
+ *
589
+ * @param bucket - to send bucket
590
+ * @param files - to send
591
+ */
592
+ async putFilesOnBucket(bucket, files) {
593
+ const s3 = this.getS3();
594
+ if (!files.length) {
595
+ return;
596
+ }
597
+ files.forEach(f => {
598
+ if (f.src === undefined) {
599
+ throw new Error("Should have src and key defined");
600
+ }
601
+ else if (!f.key) {
602
+ f.key = path.relative(process.cwd(), f.src);
603
+ }
604
+ });
605
+ // Create the bucket
606
+ await this.createBucket(bucket);
607
+ // Retrieve current files to only upload the one we do not have
608
+ const currentFiles = {};
609
+ const Prefix = files.reduce((prev, cur) => this.commonPrefix(prev, cur.key), files[0].key);
610
+ const Params = {
611
+ Bucket: bucket,
612
+ Prefix,
613
+ MaxKeys: 1000,
614
+ ContinuationToken: undefined
615
+ };
616
+ do {
617
+ const res = await s3.listObjectsV2(Params);
618
+ (res.Contents ?? []).forEach(obj => {
619
+ currentFiles[obj.Key] = obj;
620
+ });
621
+ Params.ContinuationToken = res.NextContinuationToken;
622
+ } while (Params.ContinuationToken);
623
+ files = files.filter(info => {
624
+ if (typeof info.src === "string") {
625
+ const s3obj = currentFiles[info.key];
626
+ const stat = fs.statSync(info.src);
627
+ if (s3obj && stat.size === s3obj.Size) {
628
+ const md5 = `"${this.hash(fs.readFileSync(info.src), "md5", "hex")}"`;
629
+ if (md5 === s3obj.ETag) {
630
+ this.log("TRACE", "Skipping upload of", info.src, "file with same hash already on bucket");
631
+ return false;
632
+ }
633
+ }
634
+ }
635
+ return true;
636
+ });
637
+ const uuid = randomUUID();
638
+ this.logger.logProgressStart(uuid, files.length, "Uploading to S3 bucket " + bucket);
639
+ const Tagging = this.getDefaultTagsAsS3Tagging();
640
+ // Upload 5 files at a time
641
+ for (let i = 0; i < files.length; i += 5) {
642
+ await Promise.all(files.slice(i, i + 5).map(async (info) => {
643
+ // Need to have mimetype to serve the content correctly
644
+ const mimetype = info.mimetype || mime.contentType(path.extname(info.key)) || "application/octet-stream";
645
+ await s3.putObject({
646
+ Bucket: bucket,
647
+ Body: typeof info.src === "string" ? fs.createReadStream(info.src) : info.src,
648
+ Key: info.key,
649
+ ContentType: mimetype,
650
+ Tagging
651
+ });
652
+ this.logger.logProgressIncrement(1, uuid);
653
+ this.log("INFO", "Uploaded", typeof info.src === "string" ? info.src : "<dynamicContent>", "to", `s3://${bucket}/${info.key}`, "(" + mimetype + ")");
654
+ }));
655
+ }
656
+ }
657
+ },
658
+ (() => {
659
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
660
+ _getAWSIdentity_decorators = [InstanceCache()];
661
+ _getDefaultVpc_decorators = [InstanceCache()];
662
+ _getZoneForDomainName_decorators = [InstanceCache()];
663
+ _getPolicyDocument_decorators = [InstanceCache()];
664
+ __esDecorate(_a, null, _getAWSIdentity_decorators, { kind: "method", name: "getAWSIdentity", static: false, private: false, access: { has: obj => "getAWSIdentity" in obj, get: obj => obj.getAWSIdentity }, metadata: _metadata }, null, _instanceExtraInitializers);
665
+ __esDecorate(_a, null, _getDefaultVpc_decorators, { kind: "method", name: "getDefaultVpc", static: false, private: false, access: { has: obj => "getDefaultVpc" in obj, get: obj => obj.getDefaultVpc }, metadata: _metadata }, null, _instanceExtraInitializers);
666
+ __esDecorate(_a, null, _getZoneForDomainName_decorators, { kind: "method", name: "getZoneForDomainName", static: false, private: false, access: { has: obj => "getZoneForDomainName" in obj, get: obj => obj.getZoneForDomainName }, metadata: _metadata }, null, _instanceExtraInitializers);
667
+ __esDecorate(_a, null, _getPolicyDocument_decorators, { kind: "method", name: "getPolicyDocument", static: false, private: false, access: { has: obj => "getPolicyDocument" in obj, get: obj => obj.getPolicyDocument }, metadata: _metadata }, null, _instanceExtraInitializers);
668
+ if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
669
+ })(),
670
+ _a;
671
+ })();
672
+ export { AWSDeployer };
673
+ //# sourceMappingURL=awsdeployer.js.map