@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,77 @@
|
|
|
1
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
2
|
+
import {
|
|
3
|
+
ChangeResourceRecordSetsCommand,
|
|
4
|
+
ChangeResourceRecordSetsCommandInput,
|
|
5
|
+
ChangeResourceRecordSetsCommandOutput,
|
|
6
|
+
GetChangeCommandInput,
|
|
7
|
+
Route53Client,
|
|
8
|
+
waitUntilResourceRecordSetsChanged,
|
|
9
|
+
} from '@aws-sdk/client-route-53';
|
|
10
|
+
import { WaiterResult, WaiterState } from '@smithy/util-waiter';
|
|
11
|
+
|
|
12
|
+
export class Route53Ratchet {
|
|
13
|
+
constructor(
|
|
14
|
+
private route53: Route53Client,
|
|
15
|
+
private hostedZoneId: string,
|
|
16
|
+
) {
|
|
17
|
+
if (!this.route53) {
|
|
18
|
+
throw 'route53 may not be null';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public get route53Client(): Route53Client {
|
|
23
|
+
return this.route53;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public async changeCnameRecordTarget(
|
|
27
|
+
domainName: string,
|
|
28
|
+
target: string,
|
|
29
|
+
hostedZoneId: string = this.hostedZoneId,
|
|
30
|
+
ttlSeconds = 600,
|
|
31
|
+
): Promise<boolean> {
|
|
32
|
+
Logger.info('Updating %s to point to %s', domainName, target);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const params: ChangeResourceRecordSetsCommandInput = {
|
|
36
|
+
ChangeBatch: {
|
|
37
|
+
Changes: [
|
|
38
|
+
{
|
|
39
|
+
Action: 'UPSERT',
|
|
40
|
+
ResourceRecordSet: {
|
|
41
|
+
Name: domainName,
|
|
42
|
+
ResourceRecords: [
|
|
43
|
+
{
|
|
44
|
+
Value: target,
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
TTL: ttlSeconds,
|
|
48
|
+
Type: 'CNAME',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
HostedZoneId: hostedZoneId,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const result: ChangeResourceRecordSetsCommandOutput = await this.route53.send(new ChangeResourceRecordSetsCommand(params));
|
|
57
|
+
Logger.debug('Updated domain result: %j', result);
|
|
58
|
+
|
|
59
|
+
const waitParams: GetChangeCommandInput = {
|
|
60
|
+
Id: result.ChangeInfo.Id,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const waitResult: WaiterResult = await waitUntilResourceRecordSetsChanged({ client: this.route53, maxWaitTime: 300 }, waitParams);
|
|
64
|
+
Logger.debug('Wait responsed: %j', waitResult);
|
|
65
|
+
|
|
66
|
+
if (waitResult.state === WaiterState.SUCCESS) {
|
|
67
|
+
Logger.info('Updated %s to point to %s', domainName, hostedZoneId);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
} catch (err) {
|
|
71
|
+
Logger.warn('Error update CName for %s with value %s: %j', domainName, target, err);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
Logger.info('Cannot update %s to point to %s', domainName, target);
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { StoredRuntimeParameter } from './stored-runtime-parameter.js';
|
|
2
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
3
|
+
import { LoggerLevelName } from '@bitblit/ratchet-common/logger/logger-level-name';
|
|
4
|
+
import { RuntimeParameterRatchet } from './runtime-parameter-ratchet.js';
|
|
5
|
+
import { GlobalVariableOverrideRuntimeParameterProvider } from './global-variable-override-runtime-parameter-provider.js';
|
|
6
|
+
import { describe, expect, test } from 'vitest';
|
|
7
|
+
import { MemoryRuntimeParameterProvider } from './memory-runtime-parameter-provider.js';
|
|
8
|
+
|
|
9
|
+
const testEntry: StoredRuntimeParameter = { groupId: 'test', paramKey: 'test', paramValue: '15', ttlSeconds: 0.5 };
|
|
10
|
+
const testEntry2: StoredRuntimeParameter = { groupId: 'test', paramKey: 'test1', paramValue: '"not-overridden"', ttlSeconds: 0.5 };
|
|
11
|
+
|
|
12
|
+
describe('#globalVariableOverrideRuntimeParameterProvider', function () {
|
|
13
|
+
test('reads underlying entries', async () => {
|
|
14
|
+
Logger.setLevel(LoggerLevelName.silly);
|
|
15
|
+
//mockDynamoRatchet.fullyExecuteQuery.resolves([testEntry, testEntry2]);
|
|
16
|
+
const drpp: MemoryRuntimeParameterProvider = new MemoryRuntimeParameterProvider();
|
|
17
|
+
await drpp.writeParameter(testEntry);
|
|
18
|
+
await drpp.writeParameter(testEntry2);
|
|
19
|
+
const er: GlobalVariableOverrideRuntimeParameterProvider = new GlobalVariableOverrideRuntimeParameterProvider(drpp, {
|
|
20
|
+
globalTTL: 1,
|
|
21
|
+
separator: '.',
|
|
22
|
+
prefix: 'p-',
|
|
23
|
+
suffix: null,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
process.env[er.generateName('test', 'test1')] = '"override"';
|
|
27
|
+
const rpr: RuntimeParameterRatchet = new RuntimeParameterRatchet(er);
|
|
28
|
+
|
|
29
|
+
const root: StoredRuntimeParameter = await drpp.readParameter('test', 'test1');
|
|
30
|
+
const override: StoredRuntimeParameter = await er.readParameter('test', 'test1');
|
|
31
|
+
const fetched: string = await rpr.fetchParameter<string>('test', 'test1');
|
|
32
|
+
|
|
33
|
+
expect(root).not.toBeNull();
|
|
34
|
+
expect(override).not.toBeNull();
|
|
35
|
+
expect(fetched).not.toBeNull();
|
|
36
|
+
|
|
37
|
+
expect(root.paramValue).toEqual('"not-overridden"');
|
|
38
|
+
expect(override.paramValue).toEqual('"override"');
|
|
39
|
+
expect(fetched).toEqual('override');
|
|
40
|
+
}, 30_000);
|
|
41
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { RuntimeParameterProvider } from './runtime-parameter-provider.js';
|
|
2
|
+
import { StoredRuntimeParameter } from './stored-runtime-parameter.js';
|
|
3
|
+
import { RequireRatchet } from '@bitblit/ratchet-common/lang/require-ratchet';
|
|
4
|
+
import { ErrorRatchet } from '@bitblit/ratchet-common/lang/error-ratchet';
|
|
5
|
+
import { StringRatchet } from '@bitblit/ratchet-common/lang/string-ratchet';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Provides parameters for a runtime parameter from an global (ie, process.env or global.xx) variable, where the key follows
|
|
9
|
+
* a given format
|
|
10
|
+
*
|
|
11
|
+
* "Simple" because it forces all ttls to be the same value (ignores what is passed in) so that the
|
|
12
|
+
* envvar itself can be just a string instead of a complex value
|
|
13
|
+
*/
|
|
14
|
+
export class GlobalVariableOverrideRuntimeParameterProvider implements RuntimeParameterProvider {
|
|
15
|
+
private options: GlobalVariableOverrideRuntimeParameterProviderOptions = {
|
|
16
|
+
globalTTL: 1,
|
|
17
|
+
separator: '.',
|
|
18
|
+
prefix: 'RuntimeEnv-',
|
|
19
|
+
suffix: '',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
constructor(
|
|
23
|
+
private wrapped: RuntimeParameterProvider,
|
|
24
|
+
opts?: GlobalVariableOverrideRuntimeParameterProviderOptions,
|
|
25
|
+
) {
|
|
26
|
+
// They can be empty but they cannot be null
|
|
27
|
+
RequireRatchet.notNullOrUndefined(this.wrapped, 'wrapped');
|
|
28
|
+
RequireRatchet.notNullOrUndefined(global?.process?.env, '"process" not found - this only runs in Node, not the browser');
|
|
29
|
+
|
|
30
|
+
if (opts) {
|
|
31
|
+
this.options = opts;
|
|
32
|
+
}
|
|
33
|
+
RequireRatchet.notNullOrUndefined(this.options.globalTTL, 'this.options.globalTTL');
|
|
34
|
+
RequireRatchet.notNullOrUndefined(this.options.separator, 'this.options.separator');
|
|
35
|
+
RequireRatchet.true(this.options.globalTTL > 0, 'this.options.globalTTL must be larger than 0');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public generateName(groupId: string, paramKey: string): string {
|
|
39
|
+
return (
|
|
40
|
+
StringRatchet.trimToEmpty(this.options.prefix) +
|
|
41
|
+
groupId +
|
|
42
|
+
StringRatchet.trimToEmpty(this.options.separator) +
|
|
43
|
+
paramKey +
|
|
44
|
+
StringRatchet.trimToEmpty(this.options.suffix)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public async readParameter(groupId: string, paramKey: string): Promise<StoredRuntimeParameter> {
|
|
49
|
+
const asString = StringRatchet.trimToNull(process.env[this.generateName(groupId, paramKey)]);
|
|
50
|
+
if (asString && !StringRatchet.canParseAsJson(asString)) {
|
|
51
|
+
ErrorRatchet.throwFormattedErr(
|
|
52
|
+
'Cannot parse ENV override (%s / %s) as JSON - did you forget the quotes on a string?',
|
|
53
|
+
groupId,
|
|
54
|
+
paramKey,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
const rval: StoredRuntimeParameter = asString
|
|
58
|
+
? {
|
|
59
|
+
groupId: groupId,
|
|
60
|
+
paramKey: paramKey,
|
|
61
|
+
paramValue: asString,
|
|
62
|
+
ttlSeconds: this.options.globalTTL,
|
|
63
|
+
}
|
|
64
|
+
: await this.wrapped.readParameter(groupId, paramKey);
|
|
65
|
+
return rval;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public async readAllParametersForGroup(groupId: string): Promise<StoredRuntimeParameter[]> {
|
|
69
|
+
return this.wrapped.readAllParametersForGroup(groupId);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public async writeParameter(toStore: StoredRuntimeParameter): Promise<boolean> {
|
|
73
|
+
return this.wrapped.writeParameter(toStore);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface GlobalVariableOverrideRuntimeParameterProviderOptions {
|
|
78
|
+
globalTTL: number;
|
|
79
|
+
separator: string;
|
|
80
|
+
prefix?: string;
|
|
81
|
+
suffix?: string;
|
|
82
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { RuntimeParameterProvider } from './runtime-parameter-provider.js';
|
|
2
|
+
import { StoredRuntimeParameter } from './stored-runtime-parameter.js';
|
|
3
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Very simple class, basically here just for unit testing although I suppose you could
|
|
7
|
+
* use it for something else
|
|
8
|
+
*/
|
|
9
|
+
export class MemoryRuntimeParameterProvider implements RuntimeParameterProvider {
|
|
10
|
+
private _data: Promise<Record<string, StoredRuntimeParameter>>;
|
|
11
|
+
|
|
12
|
+
constructor(private inData?: Promise<Record<string, StoredRuntimeParameter>>) {
|
|
13
|
+
if (inData) {
|
|
14
|
+
this._data = inData;
|
|
15
|
+
} else {
|
|
16
|
+
this._data = Promise.resolve({});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public async readParameter(groupId: string, paramKey: string): Promise<StoredRuntimeParameter> {
|
|
21
|
+
Logger.silly('Reading %s / %s from underlying db', groupId, paramKey);
|
|
22
|
+
const d: Record<string, StoredRuntimeParameter> = await this._data;
|
|
23
|
+
return d[groupId + '::' + paramKey] ?? null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public async readAllParametersForGroup(groupId: string): Promise<StoredRuntimeParameter[]> {
|
|
27
|
+
const d: Record<string, StoredRuntimeParameter> = await this._data;
|
|
28
|
+
const out: StoredRuntimeParameter[] = [];
|
|
29
|
+
Object.keys(d).forEach((k) => {
|
|
30
|
+
if (k.startsWith(groupId)) {
|
|
31
|
+
out.push(d[k]);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public async writeParameter(toStore: StoredRuntimeParameter): Promise<boolean> {
|
|
38
|
+
const d: Record<string, StoredRuntimeParameter> = await this._data;
|
|
39
|
+
d[toStore.groupId + '::' + toStore.paramKey] = toStore;
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classes implementing RuntimeParameterProvider offer the ability to fetch a runtime parameter
|
|
3
|
+
* object from _somewhere_
|
|
4
|
+
* They do NOT handle local caching - the RuntimeParameterRatchet does that
|
|
5
|
+
*/
|
|
6
|
+
import { StoredRuntimeParameter } from './stored-runtime-parameter.js';
|
|
7
|
+
|
|
8
|
+
export interface RuntimeParameterProvider {
|
|
9
|
+
readParameter(groupId: string, paramKey: string): Promise<StoredRuntimeParameter>;
|
|
10
|
+
readAllParametersForGroup(groupId: string): Promise<StoredRuntimeParameter[]>;
|
|
11
|
+
writeParameter(param: StoredRuntimeParameter): Promise<boolean>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { RuntimeParameterRatchet } from './runtime-parameter-ratchet.js';
|
|
2
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
3
|
+
import { PromiseRatchet } from '@bitblit/ratchet-common/lang/promise-ratchet';
|
|
4
|
+
import { LoggerLevelName } from '@bitblit/ratchet-common/logger/logger-level-name';
|
|
5
|
+
import { StoredRuntimeParameter } from './stored-runtime-parameter.js';
|
|
6
|
+
import { MemoryRuntimeParameterProvider } from './memory-runtime-parameter-provider.js';
|
|
7
|
+
import { describe, expect, test } from 'vitest';
|
|
8
|
+
|
|
9
|
+
const testEntry: StoredRuntimeParameter = { groupId: 'test', paramKey: 'test', paramValue: '15', ttlSeconds: 0.5 };
|
|
10
|
+
const testEntry2: StoredRuntimeParameter = { groupId: 'test', paramKey: 'test1', paramValue: '20', ttlSeconds: 0.5 };
|
|
11
|
+
|
|
12
|
+
describe('#runtimeParameterRatchet', function () {
|
|
13
|
+
test('fetch and cache a runtime parameter', async () => {
|
|
14
|
+
Logger.setLevel(LoggerLevelName.silly);
|
|
15
|
+
const mp: MemoryRuntimeParameterProvider = new MemoryRuntimeParameterProvider();
|
|
16
|
+
const rpr: RuntimeParameterRatchet = new RuntimeParameterRatchet(mp);
|
|
17
|
+
|
|
18
|
+
const stored: StoredRuntimeParameter = await rpr.storeParameter('test', 'test1', 15, 0.5);
|
|
19
|
+
Logger.info('Stored : %j', stored);
|
|
20
|
+
|
|
21
|
+
const cache1: number = await rpr.fetchParameter<number>('test', 'test1');
|
|
22
|
+
const cache1a: number = await rpr.fetchParameter<number>('test', 'test1');
|
|
23
|
+
const cache1b: number = await rpr.fetchParameter<number>('test', 'test1');
|
|
24
|
+
expect(cache1).toEqual(15);
|
|
25
|
+
expect(cache1a).toEqual(15);
|
|
26
|
+
expect(cache1b).toEqual(15);
|
|
27
|
+
|
|
28
|
+
await PromiseRatchet.wait(1000);
|
|
29
|
+
|
|
30
|
+
const cache2: number = await rpr.fetchParameter<number>('test', 'test1');
|
|
31
|
+
expect(cache2).toEqual(15);
|
|
32
|
+
|
|
33
|
+
const cacheMiss: number = await rpr.fetchParameter<number>('test', 'test-miss');
|
|
34
|
+
expect(cacheMiss).toBeNull();
|
|
35
|
+
|
|
36
|
+
const cacheDefault: number = await rpr.fetchParameter<number>('test', 'test-miss', 27);
|
|
37
|
+
expect(cacheDefault).toEqual(27);
|
|
38
|
+
}, 30_000);
|
|
39
|
+
|
|
40
|
+
test('reads underlying entries', async () => {
|
|
41
|
+
Logger.setLevel(LoggerLevelName.silly);
|
|
42
|
+
const mrpp: MemoryRuntimeParameterProvider = new MemoryRuntimeParameterProvider();
|
|
43
|
+
await mrpp.writeParameter(testEntry);
|
|
44
|
+
await mrpp.writeParameter(testEntry2);
|
|
45
|
+
|
|
46
|
+
const rpr: RuntimeParameterRatchet = new RuntimeParameterRatchet(mrpp);
|
|
47
|
+
|
|
48
|
+
const vals: StoredRuntimeParameter[] = await rpr.readUnderlyingEntries('test');
|
|
49
|
+
|
|
50
|
+
expect(vals).not.toBeFalsy();
|
|
51
|
+
expect(vals.length).toEqual(2);
|
|
52
|
+
}, 30_000);
|
|
53
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { RequireRatchet } from '@bitblit/ratchet-common/lang/require-ratchet';
|
|
2
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
3
|
+
import { CachedStoredRuntimeParameter } from './cached-stored-runtime-parameter.js';
|
|
4
|
+
import { RuntimeParameterProvider } from './runtime-parameter-provider.js';
|
|
5
|
+
import { StoredRuntimeParameter } from './stored-runtime-parameter.js';
|
|
6
|
+
|
|
7
|
+
export class RuntimeParameterRatchet {
|
|
8
|
+
private cache: Map<string, CachedStoredRuntimeParameter> = new Map<string, CachedStoredRuntimeParameter>();
|
|
9
|
+
|
|
10
|
+
constructor(private provider: RuntimeParameterProvider) {
|
|
11
|
+
RequireRatchet.notNullOrUndefined(this.provider);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public async fetchParameter<T>(groupId: string, paramKey: string, defaultValue: T = null, forceFreshRead = false): Promise<T> {
|
|
15
|
+
Logger.debug('Reading parameter %s / %s / Force : %s', groupId, paramKey, forceFreshRead);
|
|
16
|
+
const cached: CachedStoredRuntimeParameter = this.cache.get(RuntimeParameterRatchet.toCacheStoreKey(groupId, paramKey));
|
|
17
|
+
|
|
18
|
+
let rval: T = null;
|
|
19
|
+
const now: number = new Date().getTime();
|
|
20
|
+
if (!forceFreshRead && !!cached) {
|
|
21
|
+
const oldest: number = cached.ttlSeconds ? now - cached.ttlSeconds * 1000 : 0;
|
|
22
|
+
if (cached.storedEpochMS > oldest) {
|
|
23
|
+
Logger.silly('Fetched %s / %s from cache', groupId, paramKey);
|
|
24
|
+
rval = JSON.parse(cached.paramValue);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (!rval) {
|
|
28
|
+
const temp: StoredRuntimeParameter = await this.readUnderlyingEntry(groupId, paramKey);
|
|
29
|
+
if (temp) {
|
|
30
|
+
this.addToCache(temp);
|
|
31
|
+
rval = JSON.parse(temp.paramValue);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
rval = rval || defaultValue;
|
|
36
|
+
|
|
37
|
+
return rval;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public async fetchAllParametersForGroup(groupId: string): Promise<Map<string, any>> {
|
|
41
|
+
const all: StoredRuntimeParameter[] = await this.readUnderlyingEntries(groupId);
|
|
42
|
+
const rval: Map<string, any> = new Map<string, any>();
|
|
43
|
+
all.forEach((t) => {
|
|
44
|
+
rval.set(t.paramKey, JSON.parse(t.paramValue));
|
|
45
|
+
this.addToCache(t);
|
|
46
|
+
});
|
|
47
|
+
return rval;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public async readUnderlyingEntry(groupId: string, paramKey: string): Promise<StoredRuntimeParameter> {
|
|
51
|
+
return this.provider.readParameter(groupId, paramKey);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public async readUnderlyingEntries(groupId: string): Promise<StoredRuntimeParameter[]> {
|
|
55
|
+
return this.provider.readAllParametersForGroup(groupId);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public async storeParameter(groupId: string, paramKey: string, paramValue: any, ttlSeconds: number): Promise<StoredRuntimeParameter> {
|
|
59
|
+
const toStore: StoredRuntimeParameter = {
|
|
60
|
+
groupId: groupId,
|
|
61
|
+
paramKey: paramKey,
|
|
62
|
+
paramValue: JSON.stringify(paramValue),
|
|
63
|
+
ttlSeconds: ttlSeconds,
|
|
64
|
+
};
|
|
65
|
+
const _wrote: boolean = await this.provider.writeParameter(toStore);
|
|
66
|
+
return this.provider.readParameter(groupId, paramKey);
|
|
67
|
+
}
|
|
68
|
+
private static toCacheStoreKey(groupId: string, paramKey: string): string {
|
|
69
|
+
return groupId + ':::' + paramKey;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private addToCache(temp: StoredRuntimeParameter): void {
|
|
73
|
+
if (temp) {
|
|
74
|
+
const now: number = new Date().getTime();
|
|
75
|
+
const toStore: CachedStoredRuntimeParameter = Object.assign({ storedEpochMS: now }, temp);
|
|
76
|
+
this.cache.set(RuntimeParameterRatchet.toCacheStoreKey(temp.groupId, temp.paramKey), toStore);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public clearCache(): void {
|
|
81
|
+
Logger.debug('Clearing runtime parameter cache');
|
|
82
|
+
this.cache = new Map<string, CachedStoredRuntimeParameter>();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { EnvironmentServiceProvider } from '../../environment/environment-service-provider.js';
|
|
2
|
+
import { S3CacheRatchet } from '../s3-cache-ratchet.js';
|
|
3
|
+
import { RequireRatchet } from '@bitblit/ratchet-common/lang/require-ratchet';
|
|
4
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
5
|
+
import { StringRatchet } from '@bitblit/ratchet-common/lang/string-ratchet';
|
|
6
|
+
import { StopWatch } from '@bitblit/ratchet-common/lang/stop-watch';
|
|
7
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
8
|
+
import { S3CacheRatchetLike } from '../s3-cache-ratchet-like.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Service for reading environmental variables from S3
|
|
12
|
+
*/
|
|
13
|
+
export class S3EnvironmentServiceProvider<T> implements EnvironmentServiceProvider<T> {
|
|
14
|
+
private ratchet: S3CacheRatchetLike;
|
|
15
|
+
|
|
16
|
+
public constructor(private cfg: S3EnvironmentServiceProviderConfig) {
|
|
17
|
+
RequireRatchet.notNullOrUndefined(cfg);
|
|
18
|
+
RequireRatchet.notNullOrUndefined(cfg.bucketName);
|
|
19
|
+
RequireRatchet.notNullOrUndefined(cfg.region);
|
|
20
|
+
RequireRatchet.true(!!cfg.s3Override || !!cfg.region, 'You must set either region or S3Override');
|
|
21
|
+
const s3: S3Client = cfg.s3Override || new S3Client({ region: cfg.region });
|
|
22
|
+
this.ratchet = new S3CacheRatchet(s3, cfg.bucketName);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public async fetchConfig(name: string): Promise<T> {
|
|
26
|
+
const readPath: string = StringRatchet.trimToEmpty(this.cfg.pathPrefix) + name + StringRatchet.trimToEmpty(this.cfg.pathSuffix);
|
|
27
|
+
Logger.silly('S3EnvironmentServiceProvider:Request to read config from : %s / %s', this.cfg.bucketName, readPath);
|
|
28
|
+
const sw: StopWatch = new StopWatch();
|
|
29
|
+
const rval: T = await this.ratchet.fetchCacheFileAsObject<T>(readPath);
|
|
30
|
+
sw.log();
|
|
31
|
+
return rval;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface S3EnvironmentServiceProviderConfig {
|
|
36
|
+
s3Override?: S3Client;
|
|
37
|
+
bucketName: string;
|
|
38
|
+
region?: string;
|
|
39
|
+
pathPrefix?: string;
|
|
40
|
+
pathSuffix?: string;
|
|
41
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { S3ExpiringCodeProvider, S3ExpiringCodeProviderFileWrapper } from './s3-expiring-code-provider.js';
|
|
2
|
+
import { PutObjectCommandOutput, PutObjectOutput } from '@aws-sdk/client-s3';
|
|
3
|
+
import { ExpiringCode } from '../../expiring-code/expiring-code.js';
|
|
4
|
+
import { beforeEach, describe, expect, test } from 'vitest';
|
|
5
|
+
import { mock, MockProxy } from 'vitest-mock-extended';
|
|
6
|
+
|
|
7
|
+
import { S3CacheRatchetLike } from '../s3-cache-ratchet-like.js';
|
|
8
|
+
|
|
9
|
+
let mockS3Ratchet: MockProxy<S3CacheRatchetLike>;
|
|
10
|
+
const testCode: ExpiringCode = { code: '12345', context: 'ctx', expiresEpochMS: Date.now() + 100_000, tags: ['tag1'] };
|
|
11
|
+
const testCode2: ExpiringCode = { code: '45678', context: 'ctx', expiresEpochMS: Date.now() + 100_000, tags: ['tag1'] };
|
|
12
|
+
|
|
13
|
+
describe('#S3ExpiringCodeProvider', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
mockS3Ratchet = mock<S3CacheRatchetLike>();
|
|
16
|
+
mockS3Ratchet.getDefaultBucket.mockReturnValue('TEST-BUCKET');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('Should fetch file', async () => {
|
|
20
|
+
const val: S3ExpiringCodeProvider = new S3ExpiringCodeProvider(mockS3Ratchet, 'test.json');
|
|
21
|
+
const output: S3ExpiringCodeProviderFileWrapper = await val.fetchFile();
|
|
22
|
+
|
|
23
|
+
expect(output).not.toBeFalsy();
|
|
24
|
+
expect(output.data).not.toBeFalsy();
|
|
25
|
+
expect(output.lastModifiedEpochMS).not.toBeFalsy();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('Should update file', async () => {
|
|
29
|
+
const val: S3ExpiringCodeProvider = new S3ExpiringCodeProvider(mockS3Ratchet, 'test.json');
|
|
30
|
+
mockS3Ratchet.writeObjectToCacheFile.mockResolvedValue({} as unknown as PutObjectCommandOutput);
|
|
31
|
+
|
|
32
|
+
const wrote: PutObjectOutput = await val.updateFile([testCode]);
|
|
33
|
+
|
|
34
|
+
expect(wrote).not.toBeFalsy();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('Should check code', async () => {
|
|
38
|
+
mockS3Ratchet.fetchCacheFileAsObject.mockResolvedValue({
|
|
39
|
+
data: [testCode],
|
|
40
|
+
lastModifiedEpochMS: 1234,
|
|
41
|
+
});
|
|
42
|
+
const val: S3ExpiringCodeProvider = new S3ExpiringCodeProvider(mockS3Ratchet, 'test.json');
|
|
43
|
+
|
|
44
|
+
const testValidCode: boolean = await val.checkCode('12345', 'ctx', false);
|
|
45
|
+
const testInvalidCode: boolean = await val.checkCode('09876', 'ctx', false);
|
|
46
|
+
|
|
47
|
+
expect(testValidCode).toBeTruthy();
|
|
48
|
+
expect(testInvalidCode).toBeFalsy();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('Should store code', async () => {
|
|
52
|
+
mockS3Ratchet.fetchCacheFileAsObject.mockResolvedValue({
|
|
53
|
+
data: [testCode],
|
|
54
|
+
lastModifiedEpochMS: 1234,
|
|
55
|
+
});
|
|
56
|
+
mockS3Ratchet.writeObjectToCacheFile.mockResolvedValue({} as unknown as PutObjectCommandOutput);
|
|
57
|
+
const val: S3ExpiringCodeProvider = new S3ExpiringCodeProvider(mockS3Ratchet, 'test.json');
|
|
58
|
+
|
|
59
|
+
const output: boolean = await val.storeCode(testCode2);
|
|
60
|
+
|
|
61
|
+
expect(output).toBeTruthy();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ExpiringCodeProvider } from '../../expiring-code/expiring-code-provider.js';
|
|
2
|
+
import { ExpiringCode } from '../../expiring-code/expiring-code.js';
|
|
3
|
+
import { RequireRatchet } from '@bitblit/ratchet-common/lang/require-ratchet';
|
|
4
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
5
|
+
import { PutObjectOutput } from '@aws-sdk/client-s3';
|
|
6
|
+
import { S3CacheRatchetLike } from '../s3-cache-ratchet-like.js';
|
|
7
|
+
|
|
8
|
+
/* An implementation that puts all the values in a single JSON file in S3
|
|
9
|
+
This won't scale well at all for any kind of serious load, but is the easiest
|
|
10
|
+
solution for a very low traffic website since it doesn't require setting up tables,
|
|
11
|
+
provisioning, etc
|
|
12
|
+
*/
|
|
13
|
+
export class S3ExpiringCodeProvider implements ExpiringCodeProvider {
|
|
14
|
+
constructor(
|
|
15
|
+
private s3CacheRatchet: S3CacheRatchetLike,
|
|
16
|
+
private keyName: string,
|
|
17
|
+
) {
|
|
18
|
+
RequireRatchet.notNullOrUndefined(s3CacheRatchet, 's3CacheRatchet');
|
|
19
|
+
RequireRatchet.notNullUndefinedOrOnlyWhitespaceString(s3CacheRatchet.getDefaultBucket(), 's3CacheRatchet.defaultBucket');
|
|
20
|
+
RequireRatchet.notNullUndefinedOrOnlyWhitespaceString(keyName, 'keyName');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public async fetchFile(): Promise<S3ExpiringCodeProviderFileWrapper> {
|
|
24
|
+
const rval: S3ExpiringCodeProviderFileWrapper = (await this.s3CacheRatchet.fetchCacheFileAsObject<S3ExpiringCodeProviderFileWrapper>(
|
|
25
|
+
this.keyName,
|
|
26
|
+
)) || {
|
|
27
|
+
data: [],
|
|
28
|
+
lastModifiedEpochMS: Date.now(),
|
|
29
|
+
};
|
|
30
|
+
return rval;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public async updateFile(vals: ExpiringCode[]): Promise<PutObjectOutput> {
|
|
34
|
+
const next: S3ExpiringCodeProviderFileWrapper = {
|
|
35
|
+
data: vals || [],
|
|
36
|
+
lastModifiedEpochMS: Date.now(),
|
|
37
|
+
};
|
|
38
|
+
next.data = next.data.filter((d) => d.expiresEpochMS > Date.now()); // Always strip out expired codes
|
|
39
|
+
Logger.info('Updating code file to %s codes', next.data.length);
|
|
40
|
+
const rval: PutObjectOutput = await this.s3CacheRatchet.writeObjectToCacheFile(this.keyName, next);
|
|
41
|
+
return rval;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public async checkCode(code: string, context: string, deleteOnMatch?: boolean): Promise<boolean> {
|
|
45
|
+
const val: S3ExpiringCodeProviderFileWrapper = await this.fetchFile();
|
|
46
|
+
const rval: ExpiringCode = val.data.find(
|
|
47
|
+
(d) => d?.code?.toUpperCase() === code?.toUpperCase() && d?.context?.toUpperCase() === context?.toUpperCase(),
|
|
48
|
+
);
|
|
49
|
+
if (rval) {
|
|
50
|
+
if (deleteOnMatch || rval.expiresEpochMS < Date.now()) {
|
|
51
|
+
Logger.info('Stripping used/expired code from the database');
|
|
52
|
+
const newData: ExpiringCode[] = val.data.filter((d) => d != rval);
|
|
53
|
+
await this.updateFile(newData);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return !!rval && rval.expiresEpochMS > Date.now();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public async storeCode(code: ExpiringCode): Promise<boolean> {
|
|
61
|
+
const old: S3ExpiringCodeProviderFileWrapper = await this.fetchFile();
|
|
62
|
+
old.data.push(code);
|
|
63
|
+
const wrote: PutObjectOutput = await this.updateFile(old.data);
|
|
64
|
+
return !!wrote;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface S3ExpiringCodeProviderFileWrapper {
|
|
69
|
+
data: ExpiringCode[];
|
|
70
|
+
lastModifiedEpochMS: number;
|
|
71
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { S3PrototypeDaoProvider } from './s3-prototype-dao-provider.js';
|
|
2
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
3
|
+
import { describe, expect, test } from 'vitest';
|
|
4
|
+
import { PrototypeDao } from '../../dao/prototype-dao.js';
|
|
5
|
+
import { S3CacheRatchet } from '../s3-cache-ratchet.js';
|
|
6
|
+
|
|
7
|
+
describe('#S3PrototypeDaoProvider', () => {
|
|
8
|
+
test.skip('Should save/load files', async () => {
|
|
9
|
+
const svc: PrototypeDao<TestPrototypeItem> = new PrototypeDao<TestPrototypeItem>(
|
|
10
|
+
new S3PrototypeDaoProvider(new S3CacheRatchet(new S3Client({}), 'some-bucket'), 'test-data.json'),
|
|
11
|
+
);
|
|
12
|
+
await svc.resetDatabase();
|
|
13
|
+
|
|
14
|
+
await svc.store({ fieldA: 1, fieldB: 'test1', type: 'a' });
|
|
15
|
+
await svc.store({ fieldA: 2, fieldB: 'test2', type: 'a' });
|
|
16
|
+
await svc.store({ fieldA: 2, fieldB: 'test3', type: 'b' });
|
|
17
|
+
await svc.store({ guid: 'forceGuid', fieldA: 4, fieldB: 'test4', type: 'c' });
|
|
18
|
+
|
|
19
|
+
const test1: TestPrototypeItem[] = await svc.fetchAll();
|
|
20
|
+
expect(test1.length).toBe(4);
|
|
21
|
+
|
|
22
|
+
const test2: TestPrototypeItem = await svc.fetchById('forceGuid');
|
|
23
|
+
expect(test2).not.toBeNull();
|
|
24
|
+
expect(test2.createdEpochMS).not.toBeNull();
|
|
25
|
+
expect(test2.updatedEpochMS).not.toBeNull();
|
|
26
|
+
|
|
27
|
+
const test3: TestPrototypeItem[] = await svc.searchByField('fieldA', 4);
|
|
28
|
+
expect(test3.length).toBe(1);
|
|
29
|
+
expect(test3[0].guid).toEqual('forceGuid');
|
|
30
|
+
|
|
31
|
+
const test4: TestPrototypeItem[] = await svc.searchByFieldMap({ type: 'a', fieldA: 2 });
|
|
32
|
+
expect(test4.length).toBe(1);
|
|
33
|
+
|
|
34
|
+
await svc.resetDatabase();
|
|
35
|
+
}, 300_000);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export interface TestPrototypeItem {
|
|
39
|
+
fieldA: number;
|
|
40
|
+
fieldB: string;
|
|
41
|
+
type: string;
|
|
42
|
+
createdEpochMS?: number;
|
|
43
|
+
updatedEpochMS?: number;
|
|
44
|
+
guid?: string;
|
|
45
|
+
}
|