@webda/aws 4.0.0-beta.2 → 4.0.0-beta.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/README.md +64 -7
- package/lib/deployers/awsdeployer.d.ts +340 -0
- package/lib/deployers/awsdeployer.d.ts.map +1 -0
- package/lib/deployers/awsdeployer.js +673 -0
- package/lib/deployers/awsdeployer.js.map +1 -0
- package/lib/deployers/cloudformation.service.d.ts +438 -0
- package/lib/deployers/cloudformation.service.d.ts.map +1 -0
- package/lib/deployers/cloudformation.service.js +889 -0
- package/lib/deployers/cloudformation.service.js.map +1 -0
- package/lib/deployers/index.d.ts +4 -254
- package/lib/deployers/index.d.ts.map +1 -1
- package/lib/deployers/index.js +4 -542
- package/lib/deployers/index.js.map +1 -1
- package/lib/deployers/lambda-entrypoint.d.ts +33 -1
- package/lib/deployers/lambda-entrypoint.d.ts.map +1 -1
- package/lib/deployers/lambda-entrypoint.js +44 -10
- package/lib/deployers/lambda-entrypoint.js.map +1 -1
- package/lib/deployers/lambdapackager.service.d.ts +123 -0
- package/lib/deployers/lambdapackager.service.d.ts.map +1 -0
- package/lib/deployers/lambdapackager.service.js +149 -0
- package/lib/deployers/lambdapackager.service.js.map +1 -0
- package/lib/deployers/zip.d.ts +42 -0
- package/lib/deployers/zip.d.ts.map +1 -0
- package/lib/deployers/zip.js +129 -0
- package/lib/deployers/zip.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/services/aws-parameters.d.ts +33 -0
- package/lib/services/aws-parameters.d.ts.map +1 -0
- package/lib/services/aws-parameters.js +18 -0
- package/lib/services/aws-parameters.js.map +1 -0
- package/lib/services/awscommands.service.d.ts +39 -0
- package/lib/services/awscommands.service.d.ts.map +1 -0
- package/lib/services/awscommands.service.js +127 -0
- package/lib/services/awscommands.service.js.map +1 -0
- package/lib/services/cloudwatchlogger.service.d.ts +131 -0
- package/lib/services/cloudwatchlogger.service.d.ts.map +1 -0
- package/lib/services/{cloudwatchlogger.js → cloudwatchlogger.service.js} +60 -28
- package/lib/services/cloudwatchlogger.service.js.map +1 -0
- package/lib/services/contributors.d.ts +36 -0
- package/lib/services/contributors.d.ts.map +1 -0
- package/lib/services/contributors.js +2 -0
- package/lib/services/contributors.js.map +1 -0
- package/lib/services/dynamodb.service.d.ts +298 -0
- package/lib/services/dynamodb.service.d.ts.map +1 -0
- package/lib/services/dynamodb.service.js +986 -0
- package/lib/services/dynamodb.service.js.map +1 -0
- package/lib/services/index.d.ts +11 -31
- package/lib/services/index.d.ts.map +1 -1
- package/lib/services/index.js +11 -8
- package/lib/services/index.js.map +1 -1
- package/lib/services/lambdacaller.service.d.ts +81 -0
- package/lib/services/lambdacaller.service.d.ts.map +1 -0
- package/lib/services/lambdacaller.service.js +75 -0
- package/lib/services/lambdacaller.service.js.map +1 -0
- package/lib/services/lambdaserver.service.d.ts +127 -0
- package/lib/services/lambdaserver.service.d.ts.map +1 -0
- package/lib/services/lambdaserver.service.js +294 -0
- package/lib/services/lambdaserver.service.js.map +1 -0
- package/lib/services/{route53.d.ts → route53.service.d.ts} +21 -12
- package/lib/services/route53.service.d.ts.map +1 -0
- package/lib/services/{route53.js → route53.service.js} +25 -28
- package/lib/services/route53.service.js.map +1 -0
- package/lib/services/s3binary.service.d.ts +215 -0
- package/lib/services/s3binary.service.d.ts.map +1 -0
- package/lib/services/{s3binary.js → s3binary.service.js} +93 -55
- package/lib/services/s3binary.service.js.map +1 -0
- package/lib/services/secretsmanager.service.d.ts +102 -0
- package/lib/services/secretsmanager.service.d.ts.map +1 -0
- package/lib/services/{secretsmanager.js → secretsmanager.service.js} +40 -22
- package/lib/services/secretsmanager.service.js.map +1 -0
- package/lib/services/{sqsqueue.d.ts → sqsqueue.service.d.ts} +60 -35
- package/lib/services/sqsqueue.service.d.ts.map +1 -0
- package/lib/services/{sqsqueue.js → sqsqueue.service.js} +53 -28
- package/lib/services/sqsqueue.service.js.map +1 -0
- package/package.json +56 -64
- package/webda.module.json +1425 -264
- package/lib/deployers/cloudformation.d.ts +0 -317
- package/lib/deployers/cloudformation.d.ts.map +0 -1
- package/lib/deployers/cloudformation.js +0 -837
- package/lib/deployers/cloudformation.js.map +0 -1
- package/lib/deployers/cloudformation.spec.d.ts +0 -2
- package/lib/deployers/cloudformation.spec.d.ts.map +0 -1
- package/lib/deployers/cloudformation.spec.js +0 -530
- package/lib/deployers/cloudformation.spec.js.map +0 -1
- package/lib/deployers/index.spec.d.ts +0 -9
- package/lib/deployers/index.spec.d.ts.map +0 -1
- package/lib/deployers/index.spec.js +0 -595
- package/lib/deployers/index.spec.js.map +0 -1
- package/lib/deployers/lambdapackager.d.ts +0 -28
- package/lib/deployers/lambdapackager.d.ts.map +0 -1
- package/lib/deployers/lambdapackager.js +0 -26
- package/lib/deployers/lambdapackager.js.map +0 -1
- package/lib/deployers/lambdapackager.spec.d.ts +0 -2
- package/lib/deployers/lambdapackager.spec.d.ts.map +0 -1
- package/lib/deployers/lambdapackager.spec.js +0 -44
- package/lib/deployers/lambdapackager.spec.js.map +0 -1
- package/lib/index.spec.d.ts +0 -11
- package/lib/index.spec.d.ts.map +0 -1
- package/lib/index.spec.js +0 -42
- package/lib/index.spec.js.map +0 -1
- package/lib/services/aws-mixin.d.ts +0 -25
- package/lib/services/aws-mixin.d.ts.map +0 -1
- package/lib/services/aws-mixin.js +0 -18
- package/lib/services/aws-mixin.js.map +0 -1
- package/lib/services/cloudwatchlogger.d.ts +0 -82
- package/lib/services/cloudwatchlogger.d.ts.map +0 -1
- package/lib/services/cloudwatchlogger.js.map +0 -1
- package/lib/services/cloudwatchlogger.spec.d.ts +0 -2
- package/lib/services/cloudwatchlogger.spec.d.ts.map +0 -1
- package/lib/services/cloudwatchlogger.spec.js +0 -104
- package/lib/services/cloudwatchlogger.spec.js.map +0 -1
- package/lib/services/dynamodb.d.ts +0 -174
- package/lib/services/dynamodb.d.ts.map +0 -1
- package/lib/services/dynamodb.js +0 -711
- package/lib/services/dynamodb.js.map +0 -1
- package/lib/services/dynamodb.spec.d.ts +0 -24
- package/lib/services/dynamodb.spec.d.ts.map +0 -1
- package/lib/services/dynamodb.spec.js +0 -344
- package/lib/services/dynamodb.spec.js.map +0 -1
- package/lib/services/lambdacaller.d.ts +0 -69
- package/lib/services/lambdacaller.d.ts.map +0 -1
- package/lib/services/lambdacaller.js +0 -63
- package/lib/services/lambdacaller.js.map +0 -1
- package/lib/services/lambdacaller.spec.d.ts +0 -2
- package/lib/services/lambdacaller.spec.d.ts.map +0 -1
- package/lib/services/lambdacaller.spec.js +0 -70
- package/lib/services/lambdacaller.spec.js.map +0 -1
- package/lib/services/lambdaserver.d.ts +0 -87
- package/lib/services/lambdaserver.d.ts.map +0 -1
- package/lib/services/lambdaserver.js +0 -263
- package/lib/services/lambdaserver.js.map +0 -1
- package/lib/services/lambdaserver.spec.d.ts +0 -2
- package/lib/services/lambdaserver.spec.d.ts.map +0 -1
- package/lib/services/lambdaserver.spec.js +0 -417
- package/lib/services/lambdaserver.spec.js.map +0 -1
- package/lib/services/route53.d.ts.map +0 -1
- package/lib/services/route53.js.map +0 -1
- package/lib/services/route53.spec.d.ts +0 -2
- package/lib/services/route53.spec.d.ts.map +0 -1
- package/lib/services/route53.spec.js +0 -102
- package/lib/services/route53.spec.js.map +0 -1
- package/lib/services/s3binary.d.ts +0 -163
- package/lib/services/s3binary.d.ts.map +0 -1
- package/lib/services/s3binary.js.map +0 -1
- package/lib/services/s3binary.spec.d.ts +0 -2
- package/lib/services/s3binary.spec.d.ts.map +0 -1
- package/lib/services/s3binary.spec.js +0 -284
- package/lib/services/s3binary.spec.js.map +0 -1
- package/lib/services/secretsmanager.d.ts +0 -81
- package/lib/services/secretsmanager.d.ts.map +0 -1
- package/lib/services/secretsmanager.js.map +0 -1
- package/lib/services/secretsmanager.spec.d.ts +0 -2
- package/lib/services/secretsmanager.spec.d.ts.map +0 -1
- package/lib/services/secretsmanager.spec.js +0 -66
- package/lib/services/secretsmanager.spec.js.map +0 -1
- package/lib/services/sqsqueue.d.ts.map +0 -1
- package/lib/services/sqsqueue.js.map +0 -1
- package/lib/services/sqsqueue.spec.d.ts +0 -2
- package/lib/services/sqsqueue.spec.d.ts.map +0 -1
- package/lib/services/sqsqueue.spec.js +0 -157
- package/lib/services/sqsqueue.spec.js.map +0 -1
- package/lib/shell.d.ts +0 -13
- package/lib/shell.d.ts.map +0 -1
- package/lib/shell.js +0 -42
- package/lib/shell.js.map +0 -1
- package/webda.shell.json +0 -11
|
@@ -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
|