@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 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.103",
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.103",
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.103",
15
- "@friggframework/prettier-config": "2.0.0-next.103",
16
- "@friggframework/test": "2.0.0-next.103",
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": "9fc434b0c6e1323bd16be165d311b6880a23de12"
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 = `frigg-script-${scriptName}`;
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 = `frigg-script-${scriptName}`;
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 = `frigg-script-${scriptName}`;
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 = `frigg-script-${scriptName}`;
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
  };