@friggframework/admin-scripts 2.0.0-next.103 → 2.0.0-next.105
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/index.js +14 -0
- package/package.json +6 -6
- package/src/adapters/__tests__/aws-scheduler-adapter.test.js +54 -0
- package/src/adapters/__tests__/scheduler-adapter-factory.test.js +49 -0
- package/src/adapters/aws-scheduler-adapter.js +21 -9
- package/src/adapters/scheduler-adapter-factory.js +40 -0
- package/src/application/__tests__/report-runner.test.js +428 -0
- package/src/application/admin-script-context.js +12 -0
- package/src/application/report-runner.js +273 -0
- package/src/infrastructure/__tests__/report-executor-handler.test.js +314 -0
- package/src/infrastructure/__tests__/report-router.test.js +525 -0
- package/src/infrastructure/bootstrap.js +95 -15
- package/src/infrastructure/report-executor-handler.js +202 -0
- package/src/infrastructure/report-router.js +421 -0
package/index.js
CHANGED
|
@@ -16,6 +16,10 @@ const {
|
|
|
16
16
|
ScriptRunner,
|
|
17
17
|
createScriptRunner,
|
|
18
18
|
} = require('./src/application/script-runner');
|
|
19
|
+
const {
|
|
20
|
+
ReportRunner,
|
|
21
|
+
createReportRunner,
|
|
22
|
+
} = require('./src/application/report-runner');
|
|
19
23
|
|
|
20
24
|
// Infrastructure
|
|
21
25
|
const {
|
|
@@ -26,9 +30,15 @@ const {
|
|
|
26
30
|
app,
|
|
27
31
|
handler: routerHandler,
|
|
28
32
|
} = require('./src/infrastructure/admin-script-router');
|
|
33
|
+
const {
|
|
34
|
+
handler: reportRouterHandler,
|
|
35
|
+
} = require('./src/infrastructure/report-router');
|
|
29
36
|
const {
|
|
30
37
|
handler: executorHandler,
|
|
31
38
|
} = require('./src/infrastructure/script-executor-handler');
|
|
39
|
+
const {
|
|
40
|
+
handler: reportExecutorHandler,
|
|
41
|
+
} = require('./src/infrastructure/report-executor-handler');
|
|
32
42
|
|
|
33
43
|
// Adapters
|
|
34
44
|
const { SchedulerAdapter } = require('./src/adapters/scheduler-adapter');
|
|
@@ -48,13 +58,17 @@ module.exports = {
|
|
|
48
58
|
createAdminScriptContext,
|
|
49
59
|
ScriptRunner,
|
|
50
60
|
createScriptRunner,
|
|
61
|
+
ReportRunner,
|
|
62
|
+
createReportRunner,
|
|
51
63
|
|
|
52
64
|
// Infrastructure layer
|
|
53
65
|
validateAdminApiKey,
|
|
54
66
|
router,
|
|
55
67
|
app,
|
|
56
68
|
routerHandler,
|
|
69
|
+
reportRouterHandler,
|
|
57
70
|
executorHandler,
|
|
71
|
+
reportExecutorHandler,
|
|
58
72
|
|
|
59
73
|
// Adapters
|
|
60
74
|
SchedulerAdapter,
|
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/admin-scripts",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0-next.
|
|
4
|
+
"version": "2.0.0-next.105",
|
|
5
5
|
"description": "Admin Script Runner for Frigg - Execute maintenance and operational scripts in hosted environments",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@aws-sdk/client-scheduler": "^3.588.0",
|
|
8
|
-
"@friggframework/core": "2.0.0-next.
|
|
8
|
+
"@friggframework/core": "2.0.0-next.105",
|
|
9
9
|
"@hapi/boom": "^10.0.1",
|
|
10
10
|
"express": "^4.18.2",
|
|
11
11
|
"serverless-http": "^3.2.0"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@friggframework/eslint-config": "2.0.0-next.
|
|
15
|
-
"@friggframework/prettier-config": "2.0.0-next.
|
|
16
|
-
"@friggframework/test": "2.0.0-next.
|
|
14
|
+
"@friggframework/eslint-config": "2.0.0-next.105",
|
|
15
|
+
"@friggframework/prettier-config": "2.0.0-next.105",
|
|
16
|
+
"@friggframework/test": "2.0.0-next.105",
|
|
17
17
|
"eslint": "^8.22.0",
|
|
18
18
|
"jest": "^29.7.0",
|
|
19
19
|
"prettier": "^2.7.1",
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"maintenance",
|
|
45
45
|
"operations"
|
|
46
46
|
],
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "d200bd6b1385a33736b67f48e3aa5ac3f5e167c8"
|
|
48
48
|
}
|
|
@@ -422,6 +422,60 @@ describe('AWSSchedulerAdapter', () => {
|
|
|
422
422
|
});
|
|
423
423
|
});
|
|
424
424
|
|
|
425
|
+
describe('report parameterization (namePrefix + buildInput)', () => {
|
|
426
|
+
const reportParams = {
|
|
427
|
+
targetLambdaArn:
|
|
428
|
+
'arn:aws:lambda:us-east-1:123456789012:function:report-executor',
|
|
429
|
+
scheduleGroupName: 'frigg-admin-scripts',
|
|
430
|
+
roleArn: 'arn:aws:iam::123456789012:role/test-role',
|
|
431
|
+
namePrefix: 'frigg-report-',
|
|
432
|
+
buildInput: ({ input }) => ({
|
|
433
|
+
reportName: 'integrations',
|
|
434
|
+
mode: 'snapshot',
|
|
435
|
+
trigger: 'SCHEDULED',
|
|
436
|
+
params: input || {},
|
|
437
|
+
}),
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
it('targets the report executor and emits a report-shaped message', async () => {
|
|
441
|
+
const reportAdapter = new AWSSchedulerAdapter({ ...reportParams });
|
|
442
|
+
mockSend.mockResolvedValue({
|
|
443
|
+
ScheduleArn:
|
|
444
|
+
'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-report-integrations',
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
const result = await reportAdapter.createSchedule({
|
|
448
|
+
scriptName: 'integrations',
|
|
449
|
+
cronExpression: 'cron(0 6 * * ? *)',
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
expect(result.scheduleName).toBe('frigg-report-integrations');
|
|
453
|
+
|
|
454
|
+
const command = mockSend.mock.calls[0][0];
|
|
455
|
+
expect(command.params.Name).toBe('frigg-report-integrations');
|
|
456
|
+
expect(command.params.Target.Arn).toBe(
|
|
457
|
+
'arn:aws:lambda:us-east-1:123456789012:function:report-executor'
|
|
458
|
+
);
|
|
459
|
+
expect(JSON.parse(command.params.Target.Input)).toEqual({
|
|
460
|
+
reportName: 'integrations',
|
|
461
|
+
mode: 'snapshot',
|
|
462
|
+
trigger: 'SCHEDULED',
|
|
463
|
+
params: {},
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('deletes the report schedule by its prefixed name', async () => {
|
|
468
|
+
const reportAdapter = new AWSSchedulerAdapter({ ...reportParams });
|
|
469
|
+
mockSend.mockResolvedValue({});
|
|
470
|
+
|
|
471
|
+
await reportAdapter.deleteSchedule('integrations');
|
|
472
|
+
|
|
473
|
+
const command = mockSend.mock.calls[0][0];
|
|
474
|
+
expect(command._type).toBe('DeleteScheduleCommand');
|
|
475
|
+
expect(command.params.Name).toBe('frigg-report-integrations');
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
|
|
425
479
|
describe('Lazy SDK loading', () => {
|
|
426
480
|
it('should load AWS SDK on first client access', () => {
|
|
427
481
|
const newAdapter = new AWSSchedulerAdapter({ ...defaultParams });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const {
|
|
2
2
|
createSchedulerAdapter,
|
|
3
3
|
createSchedulerAdapterFromEnv,
|
|
4
|
+
createReportSchedulerAdapterFromEnv,
|
|
4
5
|
} = require('../scheduler-adapter-factory');
|
|
5
6
|
const { AWSSchedulerAdapter } = require('../aws-scheduler-adapter');
|
|
6
7
|
const { LocalSchedulerAdapter } = require('../local-scheduler-adapter');
|
|
@@ -185,4 +186,52 @@ describe('Scheduler Adapter Factory', () => {
|
|
|
185
186
|
expect(adapter.roleArn).toBe(awsAdapterParams.roleArn);
|
|
186
187
|
});
|
|
187
188
|
});
|
|
189
|
+
|
|
190
|
+
describe('createReportSchedulerAdapterFromEnv()', () => {
|
|
191
|
+
it('builds an AWS adapter targeting the report executor with a report-shaped message', () => {
|
|
192
|
+
process.env.SCHEDULER_PROVIDER = 'aws';
|
|
193
|
+
process.env.REPORT_EXECUTOR_LAMBDA_ARN =
|
|
194
|
+
'arn:aws:lambda:us-east-1:123456789012:function:report-executor';
|
|
195
|
+
process.env.REPORT_SCHEDULE_GROUP = 'frigg-admin-scripts';
|
|
196
|
+
process.env.SCHEDULER_ROLE_ARN = awsAdapterParams.roleArn;
|
|
197
|
+
|
|
198
|
+
const adapter = createReportSchedulerAdapterFromEnv({
|
|
199
|
+
reportName: 'integrations',
|
|
200
|
+
mode: 'snapshot',
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
expect(adapter).toBeInstanceOf(AWSSchedulerAdapter);
|
|
204
|
+
expect(adapter.targetLambdaArn).toBe(
|
|
205
|
+
'arn:aws:lambda:us-east-1:123456789012:function:report-executor'
|
|
206
|
+
);
|
|
207
|
+
expect(adapter.scheduleNameFor('integrations')).toBe(
|
|
208
|
+
'frigg-report-integrations'
|
|
209
|
+
);
|
|
210
|
+
expect(adapter.buildInput({ input: { a: 1 } })).toEqual({
|
|
211
|
+
reportName: 'integrations',
|
|
212
|
+
mode: 'snapshot',
|
|
213
|
+
trigger: 'SCHEDULED',
|
|
214
|
+
params: { a: 1 },
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('throws 503 when SCHEDULER_PROVIDER is unset in a deployed Lambda', () => {
|
|
219
|
+
delete process.env.SCHEDULER_PROVIDER;
|
|
220
|
+
process.env.AWS_LAMBDA_FUNCTION_NAME = 'report-router';
|
|
221
|
+
|
|
222
|
+
let error;
|
|
223
|
+
try {
|
|
224
|
+
createReportSchedulerAdapterFromEnv({
|
|
225
|
+
reportName: 'x',
|
|
226
|
+
mode: 'snapshot',
|
|
227
|
+
});
|
|
228
|
+
} catch (e) {
|
|
229
|
+
error = e;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
expect(error).toBeDefined();
|
|
233
|
+
expect(error.isBoom).toBe(true);
|
|
234
|
+
expect(error.output.statusCode).toBe(503);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
188
237
|
});
|
|
@@ -28,12 +28,22 @@ function loadSchedulerSDK() {
|
|
|
28
28
|
* Implements scheduling using AWS EventBridge Scheduler.
|
|
29
29
|
* Supports cron expressions, timezone configuration, and Lambda invocation.
|
|
30
30
|
*/
|
|
31
|
+
// Prefix and input builder are parameterized so one adapter can target either the script or report executor.
|
|
32
|
+
const DEFAULT_NAME_PREFIX = 'frigg-script-';
|
|
33
|
+
const defaultBuildInput = ({ scriptName, input }) => ({
|
|
34
|
+
scriptName,
|
|
35
|
+
trigger: 'SCHEDULED',
|
|
36
|
+
params: input || {},
|
|
37
|
+
});
|
|
38
|
+
|
|
31
39
|
class AWSSchedulerAdapter extends SchedulerAdapter {
|
|
32
40
|
constructor({
|
|
33
41
|
credentials,
|
|
34
42
|
targetLambdaArn,
|
|
35
43
|
scheduleGroupName,
|
|
36
44
|
roleArn,
|
|
45
|
+
namePrefix,
|
|
46
|
+
buildInput,
|
|
37
47
|
} = {}) {
|
|
38
48
|
super();
|
|
39
49
|
if (!targetLambdaArn)
|
|
@@ -52,9 +62,15 @@ class AWSSchedulerAdapter extends SchedulerAdapter {
|
|
|
52
62
|
this.targetLambdaArn = targetLambdaArn;
|
|
53
63
|
this.scheduleGroupName = scheduleGroupName;
|
|
54
64
|
this.roleArn = roleArn;
|
|
65
|
+
this.namePrefix = namePrefix || DEFAULT_NAME_PREFIX;
|
|
66
|
+
this.buildInput = buildInput || defaultBuildInput;
|
|
55
67
|
this.scheduler = null;
|
|
56
68
|
}
|
|
57
69
|
|
|
70
|
+
scheduleNameFor(scriptName) {
|
|
71
|
+
return `${this.namePrefix}${scriptName}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
58
74
|
getSchedulerClient() {
|
|
59
75
|
if (!this.scheduler) {
|
|
60
76
|
loadSchedulerSDK();
|
|
@@ -72,7 +88,7 @@ class AWSSchedulerAdapter extends SchedulerAdapter {
|
|
|
72
88
|
|
|
73
89
|
async createSchedule({ scriptName, cronExpression, timezone, input }) {
|
|
74
90
|
const client = this.getSchedulerClient();
|
|
75
|
-
const scheduleName =
|
|
91
|
+
const scheduleName = this.scheduleNameFor(scriptName);
|
|
76
92
|
|
|
77
93
|
const scheduleParams = {
|
|
78
94
|
Name: scheduleName,
|
|
@@ -83,11 +99,7 @@ class AWSSchedulerAdapter extends SchedulerAdapter {
|
|
|
83
99
|
Target: {
|
|
84
100
|
Arn: this.targetLambdaArn,
|
|
85
101
|
RoleArn: this.roleArn,
|
|
86
|
-
Input: JSON.stringify({
|
|
87
|
-
scriptName,
|
|
88
|
-
trigger: 'SCHEDULED',
|
|
89
|
-
params: input || {},
|
|
90
|
-
}),
|
|
102
|
+
Input: JSON.stringify(this.buildInput({ scriptName, input })),
|
|
91
103
|
},
|
|
92
104
|
State: 'ENABLED',
|
|
93
105
|
};
|
|
@@ -116,7 +128,7 @@ class AWSSchedulerAdapter extends SchedulerAdapter {
|
|
|
116
128
|
|
|
117
129
|
async deleteSchedule(scriptName) {
|
|
118
130
|
const client = this.getSchedulerClient();
|
|
119
|
-
const scheduleName =
|
|
131
|
+
const scheduleName = this.scheduleNameFor(scriptName);
|
|
120
132
|
|
|
121
133
|
await client.send(
|
|
122
134
|
new DeleteScheduleCommand({
|
|
@@ -128,7 +140,7 @@ class AWSSchedulerAdapter extends SchedulerAdapter {
|
|
|
128
140
|
|
|
129
141
|
async setScheduleEnabled(scriptName, enabled) {
|
|
130
142
|
const client = this.getSchedulerClient();
|
|
131
|
-
const scheduleName =
|
|
143
|
+
const scheduleName = this.scheduleNameFor(scriptName);
|
|
132
144
|
|
|
133
145
|
// Get the current schedule first to preserve all settings
|
|
134
146
|
const getCommand = new GetScheduleCommand({
|
|
@@ -167,7 +179,7 @@ class AWSSchedulerAdapter extends SchedulerAdapter {
|
|
|
167
179
|
|
|
168
180
|
async getSchedule(scriptName) {
|
|
169
181
|
const client = this.getSchedulerClient();
|
|
170
|
-
const scheduleName =
|
|
182
|
+
const scheduleName = this.scheduleNameFor(scriptName);
|
|
171
183
|
|
|
172
184
|
const response = await client.send(
|
|
173
185
|
new GetScheduleCommand({
|
|
@@ -39,6 +39,8 @@ function createSchedulerAdapter(options = {}) {
|
|
|
39
39
|
targetLambdaArn: options.targetLambdaArn,
|
|
40
40
|
scheduleGroupName: options.scheduleGroupName,
|
|
41
41
|
roleArn: options.roleArn,
|
|
42
|
+
namePrefix: options.namePrefix,
|
|
43
|
+
buildInput: options.buildInput,
|
|
42
44
|
});
|
|
43
45
|
|
|
44
46
|
case 'local':
|
|
@@ -79,7 +81,45 @@ function createSchedulerAdapterFromEnv() {
|
|
|
79
81
|
});
|
|
80
82
|
}
|
|
81
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Resolve and build a scheduler adapter that targets the report executor Lambda.
|
|
86
|
+
*
|
|
87
|
+
* A distinct name prefix keeps report schedules from colliding with script
|
|
88
|
+
* schedules in the shared EventBridge group.
|
|
89
|
+
*
|
|
90
|
+
* @param {Object} params
|
|
91
|
+
* @param {string} params.reportName - Registered report name (also the ScriptSchedule key).
|
|
92
|
+
* @param {string} params.mode - Run mode for the scheduled invocation (e.g. 'snapshot').
|
|
93
|
+
* @returns {SchedulerAdapter}
|
|
94
|
+
* @throws {Boom.Boom} 503 when SCHEDULER_PROVIDER is unset in a deployed Lambda.
|
|
95
|
+
*/
|
|
96
|
+
function createReportSchedulerAdapterFromEnv({ reportName, mode }) {
|
|
97
|
+
const type =
|
|
98
|
+
process.env.SCHEDULER_PROVIDER ||
|
|
99
|
+
(process.env.AWS_LAMBDA_FUNCTION_NAME ? null : 'local');
|
|
100
|
+
if (!type) {
|
|
101
|
+
throw Boom.serverUnavailable(
|
|
102
|
+
'SCHEDULER_PROVIDER is not configured. Set it (e.g. "aws") via appDefinition.admin.enableScheduling.'
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return createSchedulerAdapter({
|
|
107
|
+
type,
|
|
108
|
+
targetLambdaArn: process.env.REPORT_EXECUTOR_LAMBDA_ARN,
|
|
109
|
+
scheduleGroupName: process.env.REPORT_SCHEDULE_GROUP,
|
|
110
|
+
roleArn: process.env.SCHEDULER_ROLE_ARN,
|
|
111
|
+
namePrefix: 'frigg-report-',
|
|
112
|
+
buildInput: ({ input }) => ({
|
|
113
|
+
reportName,
|
|
114
|
+
mode,
|
|
115
|
+
trigger: 'SCHEDULED',
|
|
116
|
+
params: input || {},
|
|
117
|
+
}),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
82
121
|
module.exports = {
|
|
83
122
|
createSchedulerAdapter,
|
|
84
123
|
createSchedulerAdapterFromEnv,
|
|
124
|
+
createReportSchedulerAdapterFromEnv,
|
|
85
125
|
};
|