@umccr/htsget-lambda 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +89 -0
- package/bin/htsget-stack.d.ts +6 -0
- package/bin/htsget-stack.js +59 -0
- package/bin/htsget-stack.ts +31 -0
- package/bin/settings.d.ts +5 -0
- package/bin/settings.js +15 -0
- package/bin/settings.ts +13 -0
- package/cdk.json +29 -0
- package/docs/config/CONFIG.md +101 -0
- package/docs/examples/CROSS_ACCOUNT_SETUP.md +88 -0
- package/index.d.ts +2 -0
- package/index.js +19 -0
- package/index.ts +2 -0
- package/lib/config.d.ts +240 -0
- package/lib/config.js +3 -0
- package/lib/config.ts +270 -0
- package/lib/htsget-lambda.d.ts +36 -0
- package/lib/htsget-lambda.js +363 -0
- package/lib/htsget-lambda.ts +534 -0
- package/package.json +30 -0
- package/tsconfig.json +10 -0
- package/typedoc.json +11 -0
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Aws,
|
|
7
|
+
CfnOutput,
|
|
8
|
+
Duration,
|
|
9
|
+
RemovalPolicy,
|
|
10
|
+
SecretValue,
|
|
11
|
+
Stack,
|
|
12
|
+
} from "aws-cdk-lib";
|
|
13
|
+
import { Construct } from "constructs";
|
|
14
|
+
|
|
15
|
+
import { UserPool } from "aws-cdk-lib/aws-cognito";
|
|
16
|
+
import {
|
|
17
|
+
ManagedPolicy,
|
|
18
|
+
PolicyStatement,
|
|
19
|
+
Role,
|
|
20
|
+
ServicePrincipal,
|
|
21
|
+
} from "aws-cdk-lib/aws-iam";
|
|
22
|
+
import { Architecture } from "aws-cdk-lib/aws-lambda";
|
|
23
|
+
import {
|
|
24
|
+
Certificate,
|
|
25
|
+
CertificateValidation,
|
|
26
|
+
ICertificate,
|
|
27
|
+
} from "aws-cdk-lib/aws-certificatemanager";
|
|
28
|
+
import {
|
|
29
|
+
ARecord,
|
|
30
|
+
HostedZone,
|
|
31
|
+
IHostedZone,
|
|
32
|
+
RecordTarget,
|
|
33
|
+
} from "aws-cdk-lib/aws-route53";
|
|
34
|
+
import { ApiGatewayv2DomainProperties } from "aws-cdk-lib/aws-route53-targets";
|
|
35
|
+
import { RustFunction } from "cargo-lambda-cdk";
|
|
36
|
+
import path from "path";
|
|
37
|
+
import { HttpLambdaIntegration } from "aws-cdk-lib/aws-apigatewayv2-integrations";
|
|
38
|
+
import {
|
|
39
|
+
CorsHttpMethod,
|
|
40
|
+
DomainName,
|
|
41
|
+
HttpApi,
|
|
42
|
+
HttpMethod,
|
|
43
|
+
HttpRoute,
|
|
44
|
+
HttpRouteKey,
|
|
45
|
+
IHttpApi,
|
|
46
|
+
} from "aws-cdk-lib/aws-apigatewayv2";
|
|
47
|
+
import { HttpJwtAuthorizer } from "aws-cdk-lib/aws-apigatewayv2-authorizers";
|
|
48
|
+
import {
|
|
49
|
+
BlockPublicAccess,
|
|
50
|
+
Bucket,
|
|
51
|
+
BucketEncryption,
|
|
52
|
+
} from "aws-cdk-lib/aws-s3";
|
|
53
|
+
import { BucketDeployment, Source } from "aws-cdk-lib/aws-s3-deployment";
|
|
54
|
+
import { Secret } from "aws-cdk-lib/aws-secretsmanager";
|
|
55
|
+
import {
|
|
56
|
+
CorsConifg,
|
|
57
|
+
HtsgetConfig,
|
|
58
|
+
HtsgetLambdaProps,
|
|
59
|
+
JwtConfig,
|
|
60
|
+
} from "./config";
|
|
61
|
+
import { exec } from "cargo-lambda-cdk/lib/util";
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @ignore
|
|
65
|
+
* Construct used to deploy htsget-lambda.
|
|
66
|
+
*/
|
|
67
|
+
export class HtsgetLambda extends Construct {
|
|
68
|
+
constructor(scope: Construct, id: string, props: HtsgetLambdaProps) {
|
|
69
|
+
super(scope, id);
|
|
70
|
+
|
|
71
|
+
props.htsgetConfig ??= {
|
|
72
|
+
locations: [],
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
let httpApi: IHttpApi;
|
|
76
|
+
if (props.httpApi !== undefined) {
|
|
77
|
+
httpApi = props.httpApi;
|
|
78
|
+
} else {
|
|
79
|
+
if (props.domain === undefined) {
|
|
80
|
+
throw Error("domain must be defined if httpApi is not specified");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
httpApi = this.createHttpApi(
|
|
84
|
+
props.domain,
|
|
85
|
+
props.jwt,
|
|
86
|
+
props.cors,
|
|
87
|
+
props.subDomain,
|
|
88
|
+
props.hostedZone,
|
|
89
|
+
props.certificateArn,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let lambdaRole: Role | undefined;
|
|
94
|
+
if (props.role == undefined) {
|
|
95
|
+
lambdaRole = HtsgetLambda.createRole(this, id, props.roleName);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
props.buildEnvironment ??= {};
|
|
99
|
+
|
|
100
|
+
const htsgetLambda = new RustFunction(this, "Function", {
|
|
101
|
+
gitRemote: "https://github.com/umccr/htsget-rs",
|
|
102
|
+
gitForceClone: props.gitForceClone,
|
|
103
|
+
gitReference: props.gitReference,
|
|
104
|
+
functionName: props.functionName,
|
|
105
|
+
binaryName: "htsget-lambda",
|
|
106
|
+
bundling: {
|
|
107
|
+
environment: {
|
|
108
|
+
RUSTFLAGS: "-C target-cpu=neoverse-n1",
|
|
109
|
+
CARGO_PROFILE_RELEASE_LTO: "true",
|
|
110
|
+
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "1",
|
|
111
|
+
AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH: "true",
|
|
112
|
+
...props.buildEnvironment,
|
|
113
|
+
},
|
|
114
|
+
cargoLambdaFlags: props.cargoLambdaFlags ?? [
|
|
115
|
+
HtsgetLambda.resolveFeatures(
|
|
116
|
+
props.htsgetConfig,
|
|
117
|
+
props.copyTestData ?? false,
|
|
118
|
+
),
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
memorySize: 128,
|
|
122
|
+
timeout: Duration.seconds(28),
|
|
123
|
+
architecture: Architecture.ARM_64,
|
|
124
|
+
role: lambdaRole ?? props.role,
|
|
125
|
+
vpc: props.vpc,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
let bucket: Bucket | undefined = undefined;
|
|
129
|
+
let privateKey: Secret | undefined = undefined;
|
|
130
|
+
let publicKey: Secret | undefined = undefined;
|
|
131
|
+
if (props.copyTestData) {
|
|
132
|
+
[bucket, privateKey, publicKey] = this.setupTestData(
|
|
133
|
+
props.gitReference,
|
|
134
|
+
props.bucketName,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (lambdaRole !== undefined) {
|
|
139
|
+
HtsgetLambda.setPermissions(
|
|
140
|
+
lambdaRole,
|
|
141
|
+
props.htsgetConfig,
|
|
142
|
+
bucket,
|
|
143
|
+
privateKey,
|
|
144
|
+
publicKey,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const env = HtsgetLambda.configToEnv(
|
|
149
|
+
props.htsgetConfig,
|
|
150
|
+
props.cors,
|
|
151
|
+
bucket,
|
|
152
|
+
privateKey,
|
|
153
|
+
publicKey,
|
|
154
|
+
);
|
|
155
|
+
for (const key in env) {
|
|
156
|
+
htsgetLambda.addEnvironment(key, env[key]);
|
|
157
|
+
}
|
|
158
|
+
htsgetLambda.addEnvironment("RUST_LOG", "trace");
|
|
159
|
+
|
|
160
|
+
const httpIntegration = new HttpLambdaIntegration(
|
|
161
|
+
"Integration",
|
|
162
|
+
htsgetLambda,
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
[HttpMethod.GET, HttpMethod.POST].map((method) => {
|
|
166
|
+
const path = "/{proxy+}";
|
|
167
|
+
new HttpRoute(this, `${method}${path}`, {
|
|
168
|
+
httpApi: httpApi,
|
|
169
|
+
routeKey: HttpRouteKey.with(path, method),
|
|
170
|
+
integration: httpIntegration,
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
if (httpApi.defaultAuthorizer === undefined) {
|
|
175
|
+
console.warn(
|
|
176
|
+
"This will create an instance of htsget-rs that is public! Anyone will be able to query the server without authorization.",
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Determine the correct features based on the locations.
|
|
183
|
+
*/
|
|
184
|
+
public static resolveFeatures(
|
|
185
|
+
config: HtsgetConfig,
|
|
186
|
+
bucketSetup: boolean,
|
|
187
|
+
): string {
|
|
188
|
+
const features = [];
|
|
189
|
+
|
|
190
|
+
if (
|
|
191
|
+
config.locations?.some((location) =>
|
|
192
|
+
location.location.startsWith("s3://"),
|
|
193
|
+
) ||
|
|
194
|
+
bucketSetup
|
|
195
|
+
) {
|
|
196
|
+
features.push("aws");
|
|
197
|
+
}
|
|
198
|
+
if (
|
|
199
|
+
config.locations?.some(
|
|
200
|
+
(location) =>
|
|
201
|
+
location.location.startsWith("http://") ||
|
|
202
|
+
location.location.startsWith("https://"),
|
|
203
|
+
)
|
|
204
|
+
) {
|
|
205
|
+
features.push("url");
|
|
206
|
+
}
|
|
207
|
+
if (
|
|
208
|
+
config.locations?.some(
|
|
209
|
+
(location) =>
|
|
210
|
+
location.private_key !== undefined ||
|
|
211
|
+
location.public_key !== undefined,
|
|
212
|
+
) ||
|
|
213
|
+
bucketSetup
|
|
214
|
+
) {
|
|
215
|
+
features.push("experimental");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return features.length === 0
|
|
219
|
+
? "--all-features"
|
|
220
|
+
: `--features ${features.join(",")}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Create a bucket and copy test data if configured.
|
|
225
|
+
*/
|
|
226
|
+
private setupTestData(
|
|
227
|
+
gitReference?: string,
|
|
228
|
+
bucketName?: string,
|
|
229
|
+
): [Bucket, Secret, Secret] {
|
|
230
|
+
const gitRemote = "https://github.com/umccr/htsget-rs";
|
|
231
|
+
const latestCommit = exec("git", [
|
|
232
|
+
"ls-remote",
|
|
233
|
+
gitRemote,
|
|
234
|
+
gitReference ?? "HEAD",
|
|
235
|
+
])
|
|
236
|
+
.stdout.toString()
|
|
237
|
+
.split(/(\s+)/)[0];
|
|
238
|
+
const localPath = join(tmpdir(), latestCommit);
|
|
239
|
+
|
|
240
|
+
const bucket = new Bucket(this, "Bucket", {
|
|
241
|
+
blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
|
|
242
|
+
encryption: BucketEncryption.S3_MANAGED,
|
|
243
|
+
enforceSSL: true,
|
|
244
|
+
removalPolicy: RemovalPolicy.RETAIN,
|
|
245
|
+
bucketName,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Copy data from upstream htsget repo
|
|
249
|
+
const localDataPath = path.join(localPath, "data");
|
|
250
|
+
new BucketDeployment(this, "DeployFiles", {
|
|
251
|
+
sources: [
|
|
252
|
+
Source.asset(path.join(localDataPath, "c4gh")),
|
|
253
|
+
Source.asset(path.join(localDataPath, "bam")),
|
|
254
|
+
Source.asset(path.join(localDataPath, "cram")),
|
|
255
|
+
Source.asset(path.join(localDataPath, "vcf")),
|
|
256
|
+
Source.asset(path.join(localDataPath, "bcf")),
|
|
257
|
+
],
|
|
258
|
+
destinationBucket: bucket,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const keyDir = path.join(localPath, "data", "c4gh", "keys");
|
|
262
|
+
const privateKey = new Secret(this, "PrivateKey", {
|
|
263
|
+
secretStringValue: SecretValue.unsafePlainText(
|
|
264
|
+
readFileSync(path.join(keyDir, "bob.sec")).toString(),
|
|
265
|
+
),
|
|
266
|
+
removalPolicy: RemovalPolicy.DESTROY,
|
|
267
|
+
});
|
|
268
|
+
const publicKey = new Secret(this, "PublicKey", {
|
|
269
|
+
secretStringValue: SecretValue.unsafePlainText(
|
|
270
|
+
readFileSync(path.join(keyDir, "alice.pub")).toString(),
|
|
271
|
+
),
|
|
272
|
+
removalPolicy: RemovalPolicy.DESTROY,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
new CfnOutput(this, "BucketName", { value: bucket.bucketName });
|
|
276
|
+
|
|
277
|
+
return [bucket, privateKey, publicKey];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Set permissions for the Lambda role.
|
|
282
|
+
*/
|
|
283
|
+
public static setPermissions(
|
|
284
|
+
role: Role,
|
|
285
|
+
config: HtsgetConfig,
|
|
286
|
+
bucket?: Bucket,
|
|
287
|
+
privateKey?: Secret,
|
|
288
|
+
publicKey?: Secret,
|
|
289
|
+
) {
|
|
290
|
+
role.addManagedPolicy(
|
|
291
|
+
ManagedPolicy.fromAwsManagedPolicyName(
|
|
292
|
+
"service-role/AWSLambdaBasicExecutionRole",
|
|
293
|
+
),
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
const locations = config.locations ?? [];
|
|
297
|
+
// Add any "s3://" locations to policy.
|
|
298
|
+
const buckets = locations.flatMap((location) => {
|
|
299
|
+
if (location.location.startsWith("s3://")) {
|
|
300
|
+
return [location.location.split("/")[2]];
|
|
301
|
+
} else {
|
|
302
|
+
return [];
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
if (bucket !== undefined) {
|
|
306
|
+
buckets.push(bucket.bucketName);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const bucketPolicy = new PolicyStatement({
|
|
310
|
+
actions: ["s3:GetObject"],
|
|
311
|
+
resources: buckets.map((bucket) => `arn:aws:s3:::${bucket}/*`),
|
|
312
|
+
});
|
|
313
|
+
if (bucketPolicy.resources.length !== 0) {
|
|
314
|
+
role.addToPolicy(bucketPolicy);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Add any keys from the locations.
|
|
318
|
+
const keys = locations.flatMap((location) => {
|
|
319
|
+
const keys: [boolean, string][] = [];
|
|
320
|
+
if (location.private_key !== undefined) {
|
|
321
|
+
keys.push([false, location.private_key]);
|
|
322
|
+
}
|
|
323
|
+
if (location.public_key !== undefined) {
|
|
324
|
+
keys.push([false, location.public_key]);
|
|
325
|
+
}
|
|
326
|
+
return keys;
|
|
327
|
+
});
|
|
328
|
+
if (privateKey !== undefined) {
|
|
329
|
+
keys.push([true, privateKey.secretArn]);
|
|
330
|
+
}
|
|
331
|
+
if (publicKey !== undefined) {
|
|
332
|
+
keys.push([true, publicKey.secretArn]);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const secretPolicy = new PolicyStatement({
|
|
336
|
+
actions: ["secretsmanager:GetSecretValue"],
|
|
337
|
+
resources: keys.map(([arn, key]) => {
|
|
338
|
+
if (arn) {
|
|
339
|
+
return key;
|
|
340
|
+
} else {
|
|
341
|
+
return `arn:aws:secretsmanager:${Aws.REGION}:${Aws.ACCOUNT_ID}:secret:${key}-*`;
|
|
342
|
+
}
|
|
343
|
+
}),
|
|
344
|
+
});
|
|
345
|
+
if (secretPolicy.resources.length !== 0) {
|
|
346
|
+
role.addToPolicy(secretPolicy);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Creates a lambda role with the configured permissions.
|
|
352
|
+
*/
|
|
353
|
+
public static createRole(
|
|
354
|
+
scope: Construct,
|
|
355
|
+
id: string,
|
|
356
|
+
roleName?: string,
|
|
357
|
+
): Role {
|
|
358
|
+
return new Role(scope, "Role", {
|
|
359
|
+
assumedBy: new ServicePrincipal("lambda.amazonaws.com"),
|
|
360
|
+
description: "Lambda execution role for " + id,
|
|
361
|
+
roleName,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Create stateful config related to the httpApi and the API itself.
|
|
367
|
+
*/
|
|
368
|
+
private createHttpApi(
|
|
369
|
+
domain: string,
|
|
370
|
+
jwtAuthorizer?: JwtConfig,
|
|
371
|
+
config?: CorsConifg,
|
|
372
|
+
subDomain?: string,
|
|
373
|
+
hostedZone?: IHostedZone,
|
|
374
|
+
certificateArn?: string,
|
|
375
|
+
): HttpApi {
|
|
376
|
+
// Add an authorizer if auth is required.
|
|
377
|
+
let authorizer: HttpJwtAuthorizer | undefined = undefined;
|
|
378
|
+
if (jwtAuthorizer !== undefined) {
|
|
379
|
+
// If the cog user pool id is not specified, create a new one.
|
|
380
|
+
if (jwtAuthorizer.cogUserPoolId === undefined) {
|
|
381
|
+
const pool = new UserPool(this, "UserPool");
|
|
382
|
+
jwtAuthorizer.cogUserPoolId = pool.userPoolId;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
authorizer = new HttpJwtAuthorizer(
|
|
386
|
+
"HtsgetAuthorizer",
|
|
387
|
+
`https://cognito-idp.${Stack.of(this).region}.amazonaws.com/${jwtAuthorizer.cogUserPoolId}`,
|
|
388
|
+
{
|
|
389
|
+
identitySource: ["$request.header.Authorization"],
|
|
390
|
+
jwtAudience: jwtAuthorizer.audience ?? [],
|
|
391
|
+
},
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
let zone: IHostedZone;
|
|
396
|
+
if (hostedZone === undefined) {
|
|
397
|
+
zone = HostedZone.fromLookup(this, "HostedZone", {
|
|
398
|
+
domainName: domain,
|
|
399
|
+
});
|
|
400
|
+
} else {
|
|
401
|
+
zone = hostedZone;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const url = `${subDomain ?? "htsget"}.${domain}`;
|
|
405
|
+
let certificate: ICertificate;
|
|
406
|
+
if (certificateArn !== undefined) {
|
|
407
|
+
certificate = Certificate.fromCertificateArn(
|
|
408
|
+
this,
|
|
409
|
+
"Certificate",
|
|
410
|
+
certificateArn,
|
|
411
|
+
);
|
|
412
|
+
} else {
|
|
413
|
+
certificate = new Certificate(this, "Certificate", {
|
|
414
|
+
domainName: url,
|
|
415
|
+
validation: CertificateValidation.fromDns(hostedZone),
|
|
416
|
+
certificateName: url,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const domainName = new DomainName(this, "DomainName", {
|
|
421
|
+
certificate: certificate,
|
|
422
|
+
domainName: url,
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
new ARecord(this, "ARecord", {
|
|
426
|
+
zone,
|
|
427
|
+
recordName: subDomain ?? "htsget",
|
|
428
|
+
target: RecordTarget.fromAlias(
|
|
429
|
+
new ApiGatewayv2DomainProperties(
|
|
430
|
+
domainName.regionalDomainName,
|
|
431
|
+
domainName.regionalHostedZoneId,
|
|
432
|
+
),
|
|
433
|
+
),
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
return new HttpApi(this, "HtsGetApiGateway", {
|
|
437
|
+
defaultAuthorizer: authorizer,
|
|
438
|
+
defaultDomainMapping: {
|
|
439
|
+
domainName: domainName,
|
|
440
|
+
},
|
|
441
|
+
corsPreflight: {
|
|
442
|
+
allowCredentials: config?.allowCredentials ?? false,
|
|
443
|
+
allowHeaders: config?.allowHeaders ?? ["*"],
|
|
444
|
+
allowMethods: config?.allowMethods ?? [CorsHttpMethod.ANY],
|
|
445
|
+
allowOrigins: config?.allowOrigins ?? ["*"],
|
|
446
|
+
exposeHeaders: config?.exposeHeaders ?? ["*"],
|
|
447
|
+
maxAge: config?.maxAge ?? Duration.days(30),
|
|
448
|
+
},
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Convert JSON config to htsget-rs env representation.
|
|
454
|
+
*/
|
|
455
|
+
public static configToEnv(
|
|
456
|
+
config: HtsgetConfig,
|
|
457
|
+
corsConfig?: CorsConifg,
|
|
458
|
+
bucket?: Bucket,
|
|
459
|
+
privateKey?: Secret,
|
|
460
|
+
publicKey?: Secret,
|
|
461
|
+
): Record<string, string> {
|
|
462
|
+
const toHtsgetEnv = (value: unknown) => {
|
|
463
|
+
return JSON.stringify(value)
|
|
464
|
+
.replaceAll(new RegExp(/"( )*:( )*/g), "=")
|
|
465
|
+
.replaceAll('"', "");
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
const out: Record<string, string | undefined> = {};
|
|
469
|
+
const locations = config.locations ?? [];
|
|
470
|
+
|
|
471
|
+
if (bucket !== undefined) {
|
|
472
|
+
locations.push({
|
|
473
|
+
location: `s3://${bucket.bucketName}`,
|
|
474
|
+
private_key: privateKey?.secretArn,
|
|
475
|
+
public_key: publicKey?.secretArn,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
let locationsEnv: string | undefined = locations
|
|
480
|
+
.map((location) => {
|
|
481
|
+
return toHtsgetEnv({
|
|
482
|
+
location: location.location,
|
|
483
|
+
...(location.private_key !== undefined &&
|
|
484
|
+
location.public_key !== undefined && {
|
|
485
|
+
keys: {
|
|
486
|
+
kind: "SecretsManager",
|
|
487
|
+
private: location.private_key,
|
|
488
|
+
public: location.public_key,
|
|
489
|
+
},
|
|
490
|
+
}),
|
|
491
|
+
});
|
|
492
|
+
})
|
|
493
|
+
.join(",");
|
|
494
|
+
locationsEnv = `[${locationsEnv}]`;
|
|
495
|
+
|
|
496
|
+
if (
|
|
497
|
+
locationsEnv == "[]" &&
|
|
498
|
+
config.environment_override?.HTSGET_LOCATIONS === undefined
|
|
499
|
+
) {
|
|
500
|
+
locationsEnv = undefined;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
out.HTSGET_LOCATIONS = locationsEnv;
|
|
504
|
+
out.HTSGET_TICKET_SERVER_CORS_ALLOW_CREDENTIALS =
|
|
505
|
+
corsConfig?.allowCredentials?.toString();
|
|
506
|
+
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
|
|
507
|
+
out.HTSGET_TICKET_SERVER_CORS_ALLOW_HEADERS = `[${corsConfig?.allowHeaders?.join(",") as string}]`;
|
|
508
|
+
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
|
|
509
|
+
out.HTSGET_TICKET_SERVER_CORS_ALLOW_METHODS = `[${corsConfig?.allowMethods?.join(",") as string}]`;
|
|
510
|
+
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
|
|
511
|
+
out.HTSGET_TICKET_SERVER_CORS_ALLOW_ORIGINS = `[${corsConfig?.allowOrigins?.join(",") as string}]`;
|
|
512
|
+
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
|
|
513
|
+
out.HTSGET_TICKET_SERVER_CORS_EXPOSE_HEADERS = `[${corsConfig?.exposeHeaders?.join(",") as string}]`;
|
|
514
|
+
out.HTSGET_TICKET_SERVER_CORS_MAX_AGE = corsConfig?.maxAge
|
|
515
|
+
?.toSeconds()
|
|
516
|
+
.toString();
|
|
517
|
+
|
|
518
|
+
for (const key in config.service_info) {
|
|
519
|
+
out[`HTSGET_SERVICE_INFO_${key.toUpperCase()}`] = toHtsgetEnv(
|
|
520
|
+
config.service_info[key],
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
for (const key in config.environment_override) {
|
|
524
|
+
out[key] = toHtsgetEnv(config.environment_override[key]);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
Object.keys(out).forEach(
|
|
528
|
+
(key) =>
|
|
529
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
530
|
+
(out[key] == `[undefined]` || out[key] == "[]") && delete out[key],
|
|
531
|
+
);
|
|
532
|
+
return out as Record<string, string>;
|
|
533
|
+
}
|
|
534
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"bin": {
|
|
3
|
+
"htsget_app": "bin/htsget-lambda.js"
|
|
4
|
+
},
|
|
5
|
+
"devDependencies": {
|
|
6
|
+
"@types/node": "^24.10.7",
|
|
7
|
+
"aws-cdk": "^2.1100.3",
|
|
8
|
+
"aws-cdk-lib": "^2.234.1",
|
|
9
|
+
"constructs": "10.4.4",
|
|
10
|
+
"typedoc": "^0.28.16",
|
|
11
|
+
"typedoc-plugin-markdown": "^4.9.0",
|
|
12
|
+
"typescript": "5.9.3"
|
|
13
|
+
},
|
|
14
|
+
"name": "@umccr/htsget-lambda",
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"aws-cdk-lib": "^2.112.0"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"cargo-lambda-cdk": "^0.0.36"
|
|
20
|
+
},
|
|
21
|
+
"version": "0.9.6",
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"cdk": "cdk",
|
|
25
|
+
"prettier": "prettier",
|
|
26
|
+
"run": "cdk deploy",
|
|
27
|
+
"watch": "tsc -w",
|
|
28
|
+
"typedoc": "typedoc --gitRevision main --plugin typedoc-plugin-markdown"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/tsconfig.json
ADDED
package/typedoc.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"flattenOutputFiles": true,
|
|
3
|
+
"outputFileStrategy": "modules",
|
|
4
|
+
"interfacePropertiesFormat": "table",
|
|
5
|
+
"entryPoints": ["index.ts"],
|
|
6
|
+
"plugin": "typedoc-plugin-markdown",
|
|
7
|
+
"readme": "none",
|
|
8
|
+
"entryFileName": "CONFIG.md",
|
|
9
|
+
"hideGroupHeadings": true,
|
|
10
|
+
"out": "docs/config"
|
|
11
|
+
}
|