@bitblit/ratchet-aws 6.0.146-alpha → 6.0.147-alpha
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/package.json +5 -4
- package/src/batch/aws-batch-background-processor.spec.ts +22 -0
- package/src/batch/aws-batch-background-processor.ts +71 -0
- package/src/batch/aws-batch-ratchet.spec.ts +42 -0
- package/src/batch/aws-batch-ratchet.ts +70 -0
- package/src/build/ratchet-aws-info.ts +19 -0
- package/src/cache/memory-storage-provider.ts +39 -0
- package/src/cache/simple-cache-object-wrapper.ts +11 -0
- package/src/cache/simple-cache-read-options.ts +9 -0
- package/src/cache/simple-cache-storage-provider.ts +15 -0
- package/src/cache/simple-cache.spec.ts +42 -0
- package/src/cache/simple-cache.ts +81 -0
- package/src/cloudwatch/cloud-watch-log-group-ratchet.spec.ts +26 -0
- package/src/cloudwatch/cloud-watch-log-group-ratchet.ts +105 -0
- package/src/cloudwatch/cloud-watch-logs-ratchet.spec.ts +123 -0
- package/src/cloudwatch/cloud-watch-logs-ratchet.ts +232 -0
- package/src/cloudwatch/cloud-watch-metrics-ratchet.spec.ts +30 -0
- package/src/cloudwatch/cloud-watch-metrics-ratchet.ts +98 -0
- package/src/dao/example-prototype-dao-item.ts +8 -0
- package/src/dao/memory-prototype-dao-provider.ts +16 -0
- package/src/dao/prototype-dao-config.ts +8 -0
- package/src/dao/prototype-dao-db.ts +4 -0
- package/src/dao/prototype-dao-provider.ts +6 -0
- package/src/dao/prototype-dao.spec.ts +33 -0
- package/src/dao/prototype-dao.ts +110 -0
- package/src/dao/s3-simple-dao.ts +96 -0
- package/src/dao/simple-dao-item.ts +13 -0
- package/src/dynamodb/dynamo-ratchet-like.ts +61 -0
- package/src/dynamodb/dynamo-ratchet.spec.ts +206 -0
- package/src/dynamodb/dynamo-ratchet.ts +850 -0
- package/src/dynamodb/dynamo-table-ratchet.spec.ts +23 -0
- package/src/dynamodb/dynamo-table-ratchet.ts +189 -0
- package/src/dynamodb/hash-spreader.spec.ts +22 -0
- package/src/dynamodb/hash-spreader.ts +89 -0
- package/src/dynamodb/impl/dynamo-db-storage-provider.spec.ts +60 -0
- package/src/dynamodb/impl/dynamo-db-storage-provider.ts +140 -0
- package/src/dynamodb/impl/dynamo-db-sync-lock.spec.ts +41 -0
- package/src/dynamodb/impl/dynamo-db-sync-lock.ts +78 -0
- package/src/dynamodb/impl/dynamo-expiring-code-provider.ts +31 -0
- package/src/dynamodb/impl/dynamo-runtime-parameter-provider.spec.ts +65 -0
- package/src/dynamodb/impl/dynamo-runtime-parameter-provider.ts +44 -0
- package/src/ec2/ec2-ratchet.spec.ts +45 -0
- package/src/ec2/ec2-ratchet.ts +169 -0
- package/src/ecr/ecr-unused-image-cleaner-options.ts +9 -0
- package/src/ecr/ecr-unused-image-cleaner-output.ts +8 -0
- package/src/ecr/ecr-unused-image-cleaner-repository-output.ts +10 -0
- package/src/ecr/ecr-unused-image-cleaner.spec.ts +40 -0
- package/src/ecr/ecr-unused-image-cleaner.ts +183 -0
- package/src/ecr/retained-image-descriptor.ts +7 -0
- package/src/ecr/retained-image-reason.ts +4 -0
- package/src/ecr/used-image-finder.ts +6 -0
- package/src/ecr/used-image-finders/aws-batch-used-image-finder.ts +40 -0
- package/src/ecr/used-image-finders/lambda-used-image-finder.ts +51 -0
- package/src/environment/cascade-environment-service-provider.ts +28 -0
- package/src/environment/env-var-environment-service-provider.ts +36 -0
- package/src/environment/environment-service-config.ts +7 -0
- package/src/environment/environment-service-provider.ts +7 -0
- package/src/environment/environment-service.spec.ts +41 -0
- package/src/environment/environment-service.ts +89 -0
- package/src/environment/fixed-environment-service-provider.ts +26 -0
- package/src/environment/ssm-environment-service-provider.spec.ts +18 -0
- package/src/environment/ssm-environment-service-provider.ts +71 -0
- package/src/expiring-code/expiring-code-params.ts +7 -0
- package/src/expiring-code/expiring-code-provider.ts +6 -0
- package/src/expiring-code/expiring-code-ratchet.spec.ts +10 -0
- package/src/expiring-code/expiring-code-ratchet.ts +44 -0
- package/src/expiring-code/expiring-code.ts +6 -0
- package/src/iam/aws-credentials-ratchet.ts +25 -0
- package/src/lambda/lambda-event-detector.ts +55 -0
- package/src/lambda/lambda-event-type-guards.ts +38 -0
- package/src/model/cloud-watch-metrics-minute-level-dynamo-count-request.ts +18 -0
- package/src/model/dynamo-count-result.ts +8 -0
- package/src/route53/route-53-ratchet.ts +77 -0
- package/src/runtime-parameter/cached-stored-runtime-parameter.ts +5 -0
- package/src/runtime-parameter/global-variable-override-runtime-parameter-provider.spec.ts +41 -0
- package/src/runtime-parameter/global-variable-override-runtime-parameter-provider.ts +82 -0
- package/src/runtime-parameter/memory-runtime-parameter-provider.ts +42 -0
- package/src/runtime-parameter/runtime-parameter-provider.ts +12 -0
- package/src/runtime-parameter/runtime-parameter-ratchet.spec.ts +53 -0
- package/src/runtime-parameter/runtime-parameter-ratchet.ts +84 -0
- package/src/runtime-parameter/stored-runtime-parameter.ts +6 -0
- package/src/s3/expanded-file-children.ts +5 -0
- package/src/s3/impl/s3-environment-service-provider.ts +41 -0
- package/src/s3/impl/s3-expiring-code-provider.spec.ts +63 -0
- package/src/s3/impl/s3-expiring-code-provider.ts +71 -0
- package/src/s3/impl/s3-prototype-dao-provider.spec.ts +45 -0
- package/src/s3/impl/s3-prototype-dao-provider.ts +37 -0
- package/src/s3/impl/s3-remote-file-tracking-provider-options.ts +6 -0
- package/src/s3/impl/s3-remote-file-tracking-provider.spec.ts +67 -0
- package/src/s3/impl/s3-remote-file-tracking-provider.ts +157 -0
- package/src/s3/impl/s3-storage-provider.spec.ts +32 -0
- package/src/s3/impl/s3-storage-provider.ts +60 -0
- package/src/s3/s3-cache-ratchet-like.ts +64 -0
- package/src/s3/s3-cache-ratchet.spec.ts +150 -0
- package/src/s3/s3-cache-ratchet.ts +476 -0
- package/src/s3/s3-location-sync-ratchet.ts +207 -0
- package/src/s3/s3-ratchet.spec.ts +26 -0
- package/src/s3/s3-ratchet.ts +26 -0
- package/src/ses/ses-mail-sending-provider.ts +85 -0
- package/src/sns/sns-ratchet.spec.ts +24 -0
- package/src/sns/sns-ratchet.ts +52 -0
- package/src/sync-lock/memory-sync-lock.ts +48 -0
- package/src/sync-lock/sync-lock-provider.ts +5 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { UsedImageFinder } from '../used-image-finder.js';
|
|
2
|
+
import { RequireRatchet } from '@bitblit/ratchet-common/lang/require-ratchet';
|
|
3
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
4
|
+
import { StringRatchet } from '@bitblit/ratchet-common/lang/string-ratchet';
|
|
5
|
+
import {
|
|
6
|
+
FunctionConfiguration,
|
|
7
|
+
GetFunctionCommand,
|
|
8
|
+
GetFunctionCommandOutput,
|
|
9
|
+
LambdaClient,
|
|
10
|
+
ListFunctionsCommand,
|
|
11
|
+
ListFunctionsCommandInput,
|
|
12
|
+
} from '@aws-sdk/client-lambda';
|
|
13
|
+
import { ListFunctionsResponse } from '@aws-sdk/client-lambda/dist-types/models';
|
|
14
|
+
|
|
15
|
+
export class LambdaUsedImageFinder implements UsedImageFinder {
|
|
16
|
+
constructor(private lambda: LambdaClient) {
|
|
17
|
+
RequireRatchet.notNullOrUndefined(lambda, 'lambda');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public async findUsedImageUris(): Promise<string[]> {
|
|
21
|
+
const rval: Set<string> = new Set<string>();
|
|
22
|
+
const fns: FunctionConfiguration[] = await this.fetchFunctions();
|
|
23
|
+
Logger.info('Found %d functions', fns.length);
|
|
24
|
+
for (const fn of fns) {
|
|
25
|
+
//for (let i = 0; i < fns.length; i++) {
|
|
26
|
+
if (fn.PackageType === 'Image') {
|
|
27
|
+
const out: GetFunctionCommandOutput = await this.lambda.send(new GetFunctionCommand({ FunctionName: fn.FunctionName }));
|
|
28
|
+
if (out.Code.RepositoryType === 'ECR' && out.Code.ImageUri) {
|
|
29
|
+
rval.add(out.Code.ImageUri);
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
Logger.info('Skipping zip packaged function: %s', fn.FunctionName);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return Array.from(rval);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public async fetchFunctions(): Promise<FunctionConfiguration[]> {
|
|
39
|
+
let rval: any[] = [];
|
|
40
|
+
const cmd: ListFunctionsCommandInput = {};
|
|
41
|
+
let resp: ListFunctionsResponse = null;
|
|
42
|
+
|
|
43
|
+
do {
|
|
44
|
+
resp = await this.lambda.send(new ListFunctionsCommand(cmd));
|
|
45
|
+
rval = rval.concat(resp.Functions);
|
|
46
|
+
cmd.Marker = resp.NextMarker;
|
|
47
|
+
} while (StringRatchet.trimToNull(cmd.Marker));
|
|
48
|
+
|
|
49
|
+
return rval;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { RequireRatchet } from '@bitblit/ratchet-common/lang/require-ratchet';
|
|
2
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
3
|
+
import { EnvironmentServiceProvider } from './environment-service-provider.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Reads a series of providers, returning the first non-null, non-error
|
|
7
|
+
*/
|
|
8
|
+
export class CascadeEnvironmentServiceProvider<T> implements EnvironmentServiceProvider<T> {
|
|
9
|
+
public constructor(private providers: EnvironmentServiceProvider<T>[]) {
|
|
10
|
+
RequireRatchet.notNullOrUndefined(providers);
|
|
11
|
+
RequireRatchet.true(providers.length > 0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public async fetchConfig(name: string): Promise<T> {
|
|
15
|
+
Logger.silly('CascadeEnvironmentServiceProvider fetch for %s', name);
|
|
16
|
+
let rval: T = null;
|
|
17
|
+
for (let i = 0; i < this.providers.length && !rval; i++) {
|
|
18
|
+
try {
|
|
19
|
+
rval = await this.providers[i].fetchConfig(name);
|
|
20
|
+
} catch (err) {
|
|
21
|
+
Logger.error('Provider %d failed - trying next : %s', i, err, err);
|
|
22
|
+
rval = null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return rval;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { RequireRatchet } from '@bitblit/ratchet-common/lang/require-ratchet';
|
|
2
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
3
|
+
import { ErrorRatchet } from '@bitblit/ratchet-common/lang/error-ratchet';
|
|
4
|
+
import { StringRatchet } from '@bitblit/ratchet-common/lang/string-ratchet';
|
|
5
|
+
import { EnvironmentServiceProvider } from './environment-service-provider.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Service for reading environmental variables
|
|
9
|
+
* Also hides the decryption detail from higher up servicess
|
|
10
|
+
*/
|
|
11
|
+
export class EnvVarEnvironmentServiceProvider<T> implements EnvironmentServiceProvider<T> {
|
|
12
|
+
public constructor(private envVarName: string) {
|
|
13
|
+
RequireRatchet.notNullOrUndefined(envVarName);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public async fetchConfig(): Promise<T> {
|
|
17
|
+
Logger.silly('EnvVarEnvironmentServiceProvider fetch for %s', this.envVarName);
|
|
18
|
+
|
|
19
|
+
let rval: T = null;
|
|
20
|
+
const src: Record<string, any> = process ? process.env : global ? global : {};
|
|
21
|
+
const toParse: string = StringRatchet.trimToNull(src[this.envVarName]);
|
|
22
|
+
|
|
23
|
+
// If we reach here with a string result, try to parse it
|
|
24
|
+
if (toParse) {
|
|
25
|
+
try {
|
|
26
|
+
rval = JSON.parse(toParse);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
Logger.error('Failed to read env - null or invalid JSON : %s : %s', err, toParse, err);
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
ErrorRatchet.throwFormattedErr('Could not find env var with name : %s', this.envVarName);
|
|
33
|
+
}
|
|
34
|
+
return rval;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { EnvironmentService } from './environment-service.js';
|
|
2
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
3
|
+
import { FixedEnvironmentServiceProvider } from './fixed-environment-service-provider.js';
|
|
4
|
+
import { describe, expect, test } from 'vitest';
|
|
5
|
+
|
|
6
|
+
const fixed: FixedEnvironmentServiceProvider<any> = FixedEnvironmentServiceProvider.fromRecord<any>({ a: 'b', c: 5 });
|
|
7
|
+
|
|
8
|
+
describe('#environmentService', function () {
|
|
9
|
+
test('should throw exception on missing environment values', async () => {
|
|
10
|
+
try {
|
|
11
|
+
const es = new EnvironmentService<any>(fixed);
|
|
12
|
+
const _vals: any = await es.getConfig('i_do_not_exist');
|
|
13
|
+
this.bail('Should not have returned a value');
|
|
14
|
+
} catch (err) {
|
|
15
|
+
expect(err).toBeTruthy();
|
|
16
|
+
Logger.info('Success - threw %s', err);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('should find a valid value', async () => {
|
|
21
|
+
const es = new EnvironmentService<any>(fixed);
|
|
22
|
+
const vals: any = await es.getConfig('c');
|
|
23
|
+
expect(vals).toBeTruthy();
|
|
24
|
+
});
|
|
25
|
+
/*
|
|
26
|
+
test.skip('should load config from s3', async () => {
|
|
27
|
+
Logger.setLevel(LoggerLevelName.silly);
|
|
28
|
+
const bucket: string = 'xxx';
|
|
29
|
+
const path: string = 'yyy';
|
|
30
|
+
|
|
31
|
+
const es: EnvironmentService<any> = new EnvironmentService<any>(
|
|
32
|
+
new S3EnvironmentServiceProvider({ bucketName: bucket, region: 'us-east-1' })
|
|
33
|
+
);
|
|
34
|
+
const vals: any = await es.getConfig(path);
|
|
35
|
+
const vals1: any = await es.getConfig(path);
|
|
36
|
+
const vals2: any = await es.getConfig(path);
|
|
37
|
+
expect(vals).toBeTruthy();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
*/
|
|
41
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { RequireRatchet } from '@bitblit/ratchet-common/lang/require-ratchet';
|
|
2
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
3
|
+
import { ErrorRatchet } from '@bitblit/ratchet-common/lang/error-ratchet';
|
|
4
|
+
import { PromiseRatchet } from '@bitblit/ratchet-common/lang/promise-ratchet';
|
|
5
|
+
import { EnvironmentServiceProvider } from './environment-service-provider.js';
|
|
6
|
+
import { EnvironmentServiceConfig } from './environment-service-config.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Wraps up a EnvironmentServiceProvider and provides caching and retry-on-failure logic
|
|
10
|
+
*/
|
|
11
|
+
export class EnvironmentService<T> {
|
|
12
|
+
private readPromiseCache = new Map<string, Promise<any>>();
|
|
13
|
+
|
|
14
|
+
public static defaultEnvironmentServiceConfig(): EnvironmentServiceConfig {
|
|
15
|
+
const rval: EnvironmentServiceConfig = {
|
|
16
|
+
maxRetries: 3,
|
|
17
|
+
backoffMultiplierMS: 500,
|
|
18
|
+
};
|
|
19
|
+
return rval;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
constructor(
|
|
23
|
+
private provider: EnvironmentServiceProvider<T>,
|
|
24
|
+
private cfg: EnvironmentServiceConfig = EnvironmentService.defaultEnvironmentServiceConfig(),
|
|
25
|
+
) {
|
|
26
|
+
RequireRatchet.notNullOrUndefined(provider);
|
|
27
|
+
RequireRatchet.notNullOrUndefined(cfg);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public async getConfig(name: string): Promise<T> {
|
|
31
|
+
Logger.silly('EnvService:Request to read config %s', name);
|
|
32
|
+
if (!this.readPromiseCache.has(name)) {
|
|
33
|
+
Logger.silly('EnvService: Nothing in cache - adding');
|
|
34
|
+
this.readPromiseCache.set(name, this.getConfigUncached(name));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return this.readPromiseCache.get(name);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public async fetchConfigValueByPath<T>(cfgName: string, path: string[]): Promise<T> {
|
|
41
|
+
RequireRatchet.notNullUndefinedOrOnlyWhitespaceString(cfgName);
|
|
42
|
+
RequireRatchet.notNullUndefinedOrEmptyArray(path);
|
|
43
|
+
|
|
44
|
+
const env: any = await this.getConfig(cfgName);
|
|
45
|
+
if (!env) {
|
|
46
|
+
throw ErrorRatchet.fErr('No config found with name ', cfgName);
|
|
47
|
+
}
|
|
48
|
+
let rval: any = env;
|
|
49
|
+
for (const p of path) {
|
|
50
|
+
rval = rval ? rval[p] : null;
|
|
51
|
+
}
|
|
52
|
+
if (!rval) {
|
|
53
|
+
throw ErrorRatchet.fErr('No value found in %s for path %s', cfgName, path);
|
|
54
|
+
}
|
|
55
|
+
return rval;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private async getConfigUncached(name: string): Promise<T> {
|
|
59
|
+
let tryCount: number = 1;
|
|
60
|
+
let rval: T = null;
|
|
61
|
+
|
|
62
|
+
while (!rval && tryCount < this.cfg.maxRetries) {
|
|
63
|
+
tryCount++;
|
|
64
|
+
Logger.silly('Attempting fetch of %s', name);
|
|
65
|
+
try {
|
|
66
|
+
rval = await this.provider.fetchConfig(name);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
const waitMS: number = tryCount * this.cfg.backoffMultiplierMS;
|
|
69
|
+
Logger.info(
|
|
70
|
+
'Error attempting to fetch config %s (try %d of %d, waiting %s MS): %s',
|
|
71
|
+
name,
|
|
72
|
+
tryCount,
|
|
73
|
+
this.cfg.maxRetries,
|
|
74
|
+
waitMS,
|
|
75
|
+
err,
|
|
76
|
+
err,
|
|
77
|
+
);
|
|
78
|
+
await PromiseRatchet.wait(waitMS);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!rval) {
|
|
83
|
+
ErrorRatchet.throwFormattedErr('Was unable to fetch config %s even after %d retries', name, this.cfg.maxRetries);
|
|
84
|
+
}
|
|
85
|
+
return rval;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { RequireRatchet } from '@bitblit/ratchet-common/lang/require-ratchet';
|
|
2
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
3
|
+
import { EnvironmentServiceProvider } from './environment-service-provider.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Forces in a single object as the environment
|
|
7
|
+
*/
|
|
8
|
+
export class FixedEnvironmentServiceProvider<T> implements EnvironmentServiceProvider<T> {
|
|
9
|
+
public constructor(private value: Map<string, T>) {
|
|
10
|
+
RequireRatchet.notNullOrUndefined(value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public static fromRecord<T>(record: Record<string, T>): FixedEnvironmentServiceProvider<T> {
|
|
14
|
+
const m: Map<string, T> = new Map<string, T>();
|
|
15
|
+
Object.keys(record).forEach((k) => {
|
|
16
|
+
m.set(k, record[k]);
|
|
17
|
+
});
|
|
18
|
+
return new FixedEnvironmentServiceProvider<T>(m);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public async fetchConfig(name: string): Promise<T> {
|
|
22
|
+
Logger.silly('FixedEnvironmentServiceProvider fetch for %s', name);
|
|
23
|
+
const rval: T = this.value.get(name);
|
|
24
|
+
return rval;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { EnvironmentService } from './environment-service.js';
|
|
2
|
+
import { describe, expect, test } from 'vitest';
|
|
3
|
+
import { SsmEnvironmentServiceProvider } from './ssm-environment-service-provider.js';
|
|
4
|
+
import { AwsCredentialsRatchet } from '../iam/aws-credentials-ratchet';
|
|
5
|
+
|
|
6
|
+
describe('#ssmEnvironmentService', function () {
|
|
7
|
+
test.skip('should throw exception on missing environment values', async () => {
|
|
8
|
+
const profile: string = 'profile'; //pluma';
|
|
9
|
+
const config: string = 'Configuration'; //Pluma
|
|
10
|
+
AwsCredentialsRatchet.applySetProfileEnvironmentalVariable(profile);
|
|
11
|
+
const environmentService: EnvironmentService<any> = new EnvironmentService<any>(new SsmEnvironmentServiceProvider(), {
|
|
12
|
+
maxRetries: 5,
|
|
13
|
+
backoffMultiplierMS: 500,
|
|
14
|
+
});
|
|
15
|
+
const cfg: any = await environmentService.getConfig(config);
|
|
16
|
+
expect(cfg).not.toBeNull;
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { RequireRatchet } from '@bitblit/ratchet-common/lang/require-ratchet';
|
|
2
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
3
|
+
import { ErrorRatchet } from '@bitblit/ratchet-common/lang/error-ratchet';
|
|
4
|
+
import { PromiseRatchet } from '@bitblit/ratchet-common/lang/promise-ratchet';
|
|
5
|
+
import { StringRatchet } from '@bitblit/ratchet-common/lang/string-ratchet';
|
|
6
|
+
import { GetParameterCommand, GetParameterCommandOutput, ParameterNotFound, SSMClient } from '@aws-sdk/client-ssm';
|
|
7
|
+
import { EnvironmentServiceProvider } from './environment-service-provider.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Service for reading environmental variables
|
|
11
|
+
* Also hides the decryption detail from higher up services
|
|
12
|
+
*/
|
|
13
|
+
export class SsmEnvironmentServiceProvider<T> implements EnvironmentServiceProvider<T> {
|
|
14
|
+
private ssm: SSMClient;
|
|
15
|
+
public constructor(
|
|
16
|
+
private region = 'us-east-1',
|
|
17
|
+
private ssmEncrypted = true,
|
|
18
|
+
) {
|
|
19
|
+
RequireRatchet.notNullOrUndefined(region);
|
|
20
|
+
RequireRatchet.notNullOrUndefined(ssmEncrypted);
|
|
21
|
+
this.ssm = new SSMClient({ region: this.region });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public async fetchConfig(name: string): Promise<T> {
|
|
25
|
+
Logger.silly('SsmEnvironmentServiceProvider fetch for %s', name);
|
|
26
|
+
const params = {
|
|
27
|
+
Name: name /* required */,
|
|
28
|
+
WithDecryption: this.ssmEncrypted,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
let rval: T = null;
|
|
32
|
+
let toParse: string = null;
|
|
33
|
+
try {
|
|
34
|
+
const value: GetParameterCommandOutput = await this.ssm.send(new GetParameterCommand(params));
|
|
35
|
+
toParse = StringRatchet.trimToNull(value?.Parameter?.Value);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
if (err instanceof ParameterNotFound) {
|
|
38
|
+
const errMsg: string = Logger.warn('AWS could not find parameter %s in region %s - are you using the right AWS key?', name, this.region
|
|
39
|
+
|
|
40
|
+
);
|
|
41
|
+
throw new Error(errMsg);
|
|
42
|
+
} else if (err.name === 'CredentialsProviderError') {
|
|
43
|
+
Logger.warn('Token is expired - if on SSO cli try "aws sso login"');
|
|
44
|
+
throw err;
|
|
45
|
+
} else if ((ErrorRatchet.safeStringifyErr(err) || '').toLowerCase().indexOf('throttl') > -1) {
|
|
46
|
+
// CAW 2023-10-26 : Ok, so in AWS Sdk v3 there is no ThrottlingException for SSM (although there
|
|
47
|
+
// are for other services like Route53...) so maybe they just don't throw this any more but that
|
|
48
|
+
// seems unlikely so I'm putting this in just as belt-and-suspenders... if they really never throttle
|
|
49
|
+
// any more then this will just never get called
|
|
50
|
+
Logger.warn('Throttled while trying to read parameters - waiting 1 second before allowing retry');
|
|
51
|
+
await PromiseRatchet.wait(1_000);
|
|
52
|
+
} else {
|
|
53
|
+
Logger.error('Final environment fetch error (cannot retry) : %s', err, err);
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// If we reach here with a string result, try to parse it
|
|
59
|
+
if (toParse) {
|
|
60
|
+
try {
|
|
61
|
+
rval = JSON.parse(toParse);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
Logger.error('Failed to read env - null or invalid JSON : %s : %s', err, toParse, err);
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
throw ErrorRatchet.fErr('Could not find system parameter with name : %s in this account', name);
|
|
68
|
+
}
|
|
69
|
+
return rval;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ExpiringCodeRatchet } from './expiring-code-ratchet.js';
|
|
2
|
+
import { ExpiringCode } from './expiring-code.js';
|
|
3
|
+
import { describe, expect, test } from 'vitest';
|
|
4
|
+
|
|
5
|
+
describe('#ExpiringCodeRatchet', () => {
|
|
6
|
+
test('Should generate valid codes', async () => {
|
|
7
|
+
const output: ExpiringCode = ExpiringCodeRatchet.generateCode({ context: 'test', length: 5, timeToLiveSeconds: 10, alphabet: 'A' });
|
|
8
|
+
expect(output.code).toEqual('AAAAA');
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { RequireRatchet } from '@bitblit/ratchet-common/lang/require-ratchet';
|
|
2
|
+
import { StringRatchet } from '@bitblit/ratchet-common/lang/string-ratchet';
|
|
3
|
+
import { ExpiringCodeProvider } from './expiring-code-provider.js';
|
|
4
|
+
import { ExpiringCodeParams } from './expiring-code-params.js';
|
|
5
|
+
import { ExpiringCode } from './expiring-code.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Supports creating and checking single use codes
|
|
9
|
+
*/
|
|
10
|
+
export class ExpiringCodeRatchet {
|
|
11
|
+
constructor(private provider: ExpiringCodeProvider) {}
|
|
12
|
+
|
|
13
|
+
public static generateCode(params: ExpiringCodeParams): ExpiringCode {
|
|
14
|
+
RequireRatchet.notNullOrUndefined(params, 'params');
|
|
15
|
+
RequireRatchet.notNullOrUndefined(params.context, 'params.context');
|
|
16
|
+
RequireRatchet.notNullOrUndefined(params.length, 'params.length');
|
|
17
|
+
RequireRatchet.notNullOrUndefined(params.alphabet, 'params.alphabet');
|
|
18
|
+
|
|
19
|
+
let code: string = '';
|
|
20
|
+
|
|
21
|
+
while (code.length < params.length) {
|
|
22
|
+
code += params.alphabet.charAt(Math.floor(params.alphabet.length * Math.random()));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const rval: ExpiringCode = {
|
|
26
|
+
code: code,
|
|
27
|
+
context: params.context,
|
|
28
|
+
tags: params.tags,
|
|
29
|
+
expiresEpochMS: Date.now() + params.timeToLiveSeconds * 1000,
|
|
30
|
+
};
|
|
31
|
+
return rval;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public async createNewCode(params: ExpiringCodeParams): Promise<ExpiringCode> {
|
|
35
|
+
const value: ExpiringCode = ExpiringCodeRatchet.generateCode(params);
|
|
36
|
+
const rval: boolean = await this.provider.storeCode(value);
|
|
37
|
+
return rval ? value : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public async checkCode(code: string, context: string, deleteOnMatch?: boolean): Promise<boolean> {
|
|
41
|
+
const rval: boolean = await this.provider.checkCode(StringRatchet.trimToEmpty(code), StringRatchet.trimToEmpty(context), deleteOnMatch);
|
|
42
|
+
return rval;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ErrorRatchet } from '@bitblit/ratchet-common/lang/error-ratchet';
|
|
2
|
+
import { StringRatchet } from '@bitblit/ratchet-common/lang/string-ratchet';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Ratchet for manipulating credentials
|
|
6
|
+
*
|
|
7
|
+
* Mainly here so I don't have to remember certain specific AWS environmental variable names
|
|
8
|
+
*/
|
|
9
|
+
export class AwsCredentialsRatchet {
|
|
10
|
+
// Empty constructor prevents instantiation
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
12
|
+
private constructor() {}
|
|
13
|
+
|
|
14
|
+
public static applySetProfileEnvironmentalVariable(newProfile: string): void {
|
|
15
|
+
if (process.env) {
|
|
16
|
+
if (StringRatchet.trimToNull(newProfile)) {
|
|
17
|
+
process.env['AWS_PROFILE'] = newProfile;
|
|
18
|
+
} else {
|
|
19
|
+
ErrorRatchet.throwFormattedErr('Cannot set profile to null/empty string');
|
|
20
|
+
}
|
|
21
|
+
} else {
|
|
22
|
+
ErrorRatchet.throwFormattedErr('Cannot set profile - not in a node environment - process missing');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Simple switch to see what kind of event I am looking at
|
|
2
|
+
|
|
3
|
+
export class LambdaEventDetector {
|
|
4
|
+
public static isValidCronEvent(event: any): boolean {
|
|
5
|
+
return event && event.source == 'aws.events' && event.resources && event.resources.length > 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
public static isValidSnsEvent(event: any): boolean {
|
|
9
|
+
return event && event.Records && event.Records.length > 0 && event.Records[0].EventSource == 'aws:sns';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public static isValidSqsEvent(event: any): boolean {
|
|
13
|
+
return event && event.Records && event.Records.length > 0 && event.Records[0].eventSource == 'aws:sqs';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public static isValidDynamoDBEvent(event: any): boolean {
|
|
17
|
+
return event && event.Records && event.Records.length > 0 && event.Records[0].eventSource == 'aws:dynamodb';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public static isValidS3Event(event: any): boolean {
|
|
21
|
+
return event && event.Records && event.Records.length > 0 && event.Records[0].eventSource == 'aws:s3';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public static isValidApiGatewayV2WithRequestContextEvent(event: any): boolean {
|
|
25
|
+
return event && event.rawPath && event.requestContext && event.routeKey;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public static isValidApiGatewayEvent(event: any): boolean {
|
|
29
|
+
return event && event.httpMethod && event.path && event.requestContext;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public static isValidApiGatewayAuthorizerEvent(event: any): boolean {
|
|
33
|
+
return event && event.authorizationToken && event.methodArn;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public static isSingleCronEvent(event: any): boolean {
|
|
37
|
+
return this.isValidCronEvent(event) && LambdaEventDetector.isSingleEntryEvent(event, 'resources');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public static isSingleSnsEvent(event: any): boolean {
|
|
41
|
+
return this.isValidSnsEvent(event) && LambdaEventDetector.isSingleEntryEvent(event);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public static isSingleDynamoDBEvent(event: any): boolean {
|
|
45
|
+
return this.isValidDynamoDBEvent(event) && LambdaEventDetector.isSingleEntryEvent(event);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public static isSingleS3Event(event: any): boolean {
|
|
49
|
+
return this.isValidS3Event(event) && LambdaEventDetector.isSingleEntryEvent(event);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public static isSingleEntryEvent(event: any, entryName: string = 'Records'): boolean {
|
|
53
|
+
return event && event[entryName] && event[entryName] instanceof Array && event[entryName].length === 1;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Simple switch to see what kind of event I am looking at
|
|
2
|
+
|
|
3
|
+
import { APIGatewayEvent, APIGatewayProxyEventV2, DynamoDBStreamEvent, S3Event, ScheduledEvent, SNSEvent, SQSEvent } from 'aws-lambda';
|
|
4
|
+
import { LambdaEventDetector } from './lambda-event-detector.js';
|
|
5
|
+
|
|
6
|
+
export class LambdaEventTypeGuards {
|
|
7
|
+
public static isValidCronEvent(event: any): event is ScheduledEvent {
|
|
8
|
+
return LambdaEventDetector.isValidCronEvent(event);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public static isValidSqsEvent(event: any): event is SQSEvent {
|
|
12
|
+
return LambdaEventDetector.isValidSqsEvent(event);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public static isValidSnsEvent(event: any): event is SNSEvent {
|
|
16
|
+
return LambdaEventDetector.isValidSnsEvent(event);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public static isValidDynamoDBEvent(event: any): event is DynamoDBStreamEvent {
|
|
20
|
+
return LambdaEventDetector.isValidDynamoDBEvent(event);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public static isValidS3Event(event: any): event is S3Event {
|
|
24
|
+
return LambdaEventDetector.isValidS3Event(event);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public static isValidApiGatewayV2WithRequestContextEvent(event: any): event is APIGatewayProxyEventV2 {
|
|
28
|
+
return LambdaEventDetector.isValidApiGatewayV2WithRequestContextEvent(event);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public static isValidApiGatewayEvent(event: any): event is APIGatewayEvent {
|
|
32
|
+
return LambdaEventDetector.isValidApiGatewayEvent(event);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public static isValidApiGatewayAuthorizerEvent(event: any): boolean {
|
|
36
|
+
return LambdaEventDetector.isValidApiGatewayAuthorizerEvent(event);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Service for interacting with cloudwatch
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { KeyValue } from '@bitblit/ratchet-common/lang/key-value';
|
|
6
|
+
import { DynamoRatchet } from '../dynamodb/dynamo-ratchet.js';
|
|
7
|
+
import { QueryCommandInput, ScanCommandInput } from '@aws-sdk/lib-dynamodb';
|
|
8
|
+
|
|
9
|
+
export interface CloudWatchMetricsMinuteLevelDynamoCountRequest {
|
|
10
|
+
dynamoRatchet: DynamoRatchet;
|
|
11
|
+
query: QueryCommandInput;
|
|
12
|
+
scan: ScanCommandInput;
|
|
13
|
+
|
|
14
|
+
minuteUTC: string; // Format yyyy-MM-dd HH:mm
|
|
15
|
+
namespace: string;
|
|
16
|
+
metric: string;
|
|
17
|
+
dims: KeyValue<any>[];
|
|
18
|
+
}
|