@friggframework/admin-scripts 2.0.0--canary.517.9066dd6.0 → 2.0.0--canary.522.923dfae.0
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 +5 -12
- package/package.json +9 -6
- package/src/adapters/__tests__/aws-scheduler-adapter.test.js +35 -58
- package/src/adapters/__tests__/scheduler-adapter-factory.test.js +177 -37
- package/src/adapters/aws-scheduler-adapter.js +5 -12
- package/src/adapters/scheduler-adapter-factory.js +34 -14
- package/src/application/__tests__/admin-frigg-commands.test.js +19 -19
- package/src/application/__tests__/admin-script-base.test.js +84 -100
- package/src/application/__tests__/dry-run-http-interceptor.test.js +313 -0
- package/src/application/__tests__/dry-run-repository-wrapper.test.js +257 -0
- package/src/application/__tests__/schedule-management-use-case.test.js +276 -0
- package/src/application/__tests__/script-runner.test.js +17 -33
- package/src/application/admin-frigg-commands.js +32 -20
- package/src/application/admin-script-base.js +99 -20
- package/src/application/dry-run-http-interceptor.js +296 -0
- package/src/application/dry-run-repository-wrapper.js +261 -0
- package/src/application/schedule-management-use-case.js +230 -0
- package/src/application/script-runner.js +156 -46
- package/src/builtins/__tests__/integration-health-check.test.js +60 -67
- package/src/builtins/__tests__/oauth-token-refresh.test.js +37 -45
- package/src/builtins/integration-health-check.js +24 -23
- package/src/builtins/oauth-token-refresh.js +20 -19
- package/src/infrastructure/__tests__/admin-auth-middleware.test.js +95 -32
- package/src/infrastructure/__tests__/admin-script-router.test.js +49 -48
- package/src/infrastructure/admin-auth-middleware.js +43 -5
- package/src/infrastructure/admin-script-router.js +44 -64
- package/src/infrastructure/script-executor-handler.js +36 -15
- package/src/application/__tests__/validate-script-input.test.js +0 -196
- package/src/application/use-cases/__tests__/delete-schedule-use-case.test.js +0 -168
- package/src/application/use-cases/__tests__/get-effective-schedule-use-case.test.js +0 -114
- package/src/application/use-cases/__tests__/upsert-schedule-use-case.test.js +0 -201
- package/src/application/use-cases/delete-schedule-use-case.js +0 -108
- package/src/application/use-cases/get-effective-schedule-use-case.js +0 -78
- package/src/application/use-cases/index.js +0 -18
- package/src/application/use-cases/upsert-schedule-use-case.js +0 -127
- package/src/application/validate-script-input.js +0 -116
package/index.js
CHANGED
|
@@ -8,17 +8,11 @@
|
|
|
8
8
|
// Application Services
|
|
9
9
|
const { ScriptFactory, getScriptFactory, createScriptFactory } = require('./src/application/script-factory');
|
|
10
10
|
const { AdminScriptBase } = require('./src/application/admin-script-base');
|
|
11
|
-
const {
|
|
12
|
-
AdminScriptContext,
|
|
13
|
-
createAdminScriptContext,
|
|
14
|
-
// Legacy aliases (deprecated)
|
|
15
|
-
AdminFriggCommands,
|
|
16
|
-
createAdminFriggCommands,
|
|
17
|
-
} = require('./src/application/admin-frigg-commands');
|
|
11
|
+
const { AdminFriggCommands, createAdminFriggCommands } = require('./src/application/admin-frigg-commands');
|
|
18
12
|
const { ScriptRunner, createScriptRunner } = require('./src/application/script-runner');
|
|
19
13
|
|
|
20
14
|
// Infrastructure
|
|
21
|
-
const {
|
|
15
|
+
const { adminAuthMiddleware } = require('./src/infrastructure/admin-auth-middleware');
|
|
22
16
|
const { router, app, handler: routerHandler } = require('./src/infrastructure/admin-script-router');
|
|
23
17
|
const { handler: executorHandler } = require('./src/infrastructure/script-executor-handler');
|
|
24
18
|
|
|
@@ -36,6 +30,7 @@ const { AWSSchedulerAdapter } = require('./src/adapters/aws-scheduler-adapter');
|
|
|
36
30
|
const { LocalSchedulerAdapter } = require('./src/adapters/local-scheduler-adapter');
|
|
37
31
|
const {
|
|
38
32
|
createSchedulerAdapter,
|
|
33
|
+
detectSchedulerAdapterType,
|
|
39
34
|
} = require('./src/adapters/scheduler-adapter-factory');
|
|
40
35
|
|
|
41
36
|
module.exports = {
|
|
@@ -44,16 +39,13 @@ module.exports = {
|
|
|
44
39
|
ScriptFactory,
|
|
45
40
|
getScriptFactory,
|
|
46
41
|
createScriptFactory,
|
|
47
|
-
AdminScriptContext,
|
|
48
|
-
createAdminScriptContext,
|
|
49
|
-
// Legacy aliases (deprecated)
|
|
50
42
|
AdminFriggCommands,
|
|
51
43
|
createAdminFriggCommands,
|
|
52
44
|
ScriptRunner,
|
|
53
45
|
createScriptRunner,
|
|
54
46
|
|
|
55
47
|
// Infrastructure layer
|
|
56
|
-
|
|
48
|
+
adminAuthMiddleware,
|
|
57
49
|
router,
|
|
58
50
|
app,
|
|
59
51
|
routerHandler,
|
|
@@ -70,4 +62,5 @@ module.exports = {
|
|
|
70
62
|
AWSSchedulerAdapter,
|
|
71
63
|
LocalSchedulerAdapter,
|
|
72
64
|
createSchedulerAdapter,
|
|
65
|
+
detectSchedulerAdapterType,
|
|
73
66
|
};
|
package/package.json
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/admin-scripts",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.
|
|
4
|
+
"version": "2.0.0--canary.522.923dfae.0",
|
|
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--canary.
|
|
8
|
+
"@friggframework/core": "2.0.0--canary.522.923dfae.0",
|
|
9
9
|
"bcryptjs": "^2.4.3",
|
|
10
10
|
"express": "^4.18.2",
|
|
11
11
|
"lodash": "4.17.21",
|
|
12
|
+
"mongoose": "6.11.6",
|
|
12
13
|
"serverless-http": "^3.2.0",
|
|
13
14
|
"uuid": "^9.0.1"
|
|
14
15
|
},
|
|
15
16
|
"devDependencies": {
|
|
16
|
-
"@friggframework/eslint-config": "2.0.0--canary.
|
|
17
|
-
"@friggframework/prettier-config": "2.0.0--canary.
|
|
18
|
-
"@friggframework/test": "2.0.0--canary.
|
|
17
|
+
"@friggframework/eslint-config": "2.0.0--canary.522.923dfae.0",
|
|
18
|
+
"@friggframework/prettier-config": "2.0.0--canary.522.923dfae.0",
|
|
19
|
+
"@friggframework/test": "2.0.0--canary.522.923dfae.0",
|
|
20
|
+
"chai": "^4.3.6",
|
|
19
21
|
"eslint": "^8.22.0",
|
|
20
22
|
"jest": "^29.7.0",
|
|
21
23
|
"prettier": "^2.7.1",
|
|
24
|
+
"sinon": "^16.1.1",
|
|
22
25
|
"supertest": "^7.1.4"
|
|
23
26
|
},
|
|
24
27
|
"scripts": {
|
|
@@ -46,5 +49,5 @@
|
|
|
46
49
|
"maintenance",
|
|
47
50
|
"operations"
|
|
48
51
|
],
|
|
49
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "923dfaebed139e79450165c5e2ecf11ef7cbcd40"
|
|
50
53
|
}
|
|
@@ -18,28 +18,34 @@ jest.mock('@aws-sdk/client-scheduler', () => {
|
|
|
18
18
|
};
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
const defaultParams = {
|
|
22
|
-
targetLambdaArn: 'arn:aws:lambda:us-east-1:123456789012:function:admin-script-executor',
|
|
23
|
-
scheduleGroupName: 'frigg-admin-scripts',
|
|
24
|
-
roleArn: 'arn:aws:iam::123456789012:role/test-role',
|
|
25
|
-
};
|
|
26
|
-
|
|
27
21
|
describe('AWSSchedulerAdapter', () => {
|
|
28
22
|
let adapter;
|
|
29
23
|
let mockSend;
|
|
30
|
-
|
|
24
|
+
let originalEnv;
|
|
25
|
+
|
|
26
|
+
beforeAll(() => {
|
|
27
|
+
originalEnv = { ...process.env };
|
|
28
|
+
});
|
|
31
29
|
|
|
32
30
|
beforeEach(() => {
|
|
33
31
|
jest.clearAllMocks();
|
|
34
|
-
|
|
32
|
+
|
|
33
|
+
// Reset environment variables
|
|
34
|
+
process.env.AWS_REGION = 'us-east-1';
|
|
35
|
+
process.env.SCHEDULE_GROUP_NAME = 'test-schedule-group';
|
|
36
|
+
process.env.SCHEDULER_ROLE_ARN = 'arn:aws:iam::123456789012:role/test-role';
|
|
37
|
+
process.env.ADMIN_SCRIPT_LAMBDA_ARN = 'arn:aws:lambda:us-east-1:123456789012:function:test-executor';
|
|
35
38
|
|
|
36
39
|
const sdk = require('@aws-sdk/client-scheduler');
|
|
37
40
|
mockSend = sdk._mockSend;
|
|
38
41
|
|
|
39
|
-
adapter = new AWSSchedulerAdapter({
|
|
42
|
+
adapter = new AWSSchedulerAdapter({
|
|
43
|
+
targetLambdaArn: 'arn:aws:lambda:us-east-1:123456789012:function:admin-script-executor',
|
|
44
|
+
scheduleGroupName: 'frigg-admin-scripts',
|
|
45
|
+
});
|
|
40
46
|
});
|
|
41
47
|
|
|
42
|
-
|
|
48
|
+
afterAll(() => {
|
|
43
49
|
process.env = originalEnv;
|
|
44
50
|
});
|
|
45
51
|
|
|
@@ -54,46 +60,35 @@ describe('AWSSchedulerAdapter', () => {
|
|
|
54
60
|
});
|
|
55
61
|
|
|
56
62
|
describe('Constructor', () => {
|
|
57
|
-
it('should use provided configuration
|
|
58
|
-
process.env.AWS_REGION = 'eu-west-1';
|
|
63
|
+
it('should use provided configuration', () => {
|
|
59
64
|
const customAdapter = new AWSSchedulerAdapter({
|
|
65
|
+
region: 'eu-west-1',
|
|
60
66
|
targetLambdaArn: 'arn:aws:lambda:eu-west-1:123456789012:function:custom',
|
|
61
67
|
scheduleGroupName: 'custom-group',
|
|
62
|
-
roleArn: 'arn:aws:iam::123456789012:role/custom-role',
|
|
63
68
|
});
|
|
64
69
|
|
|
65
70
|
expect(customAdapter.region).toBe('eu-west-1');
|
|
66
71
|
expect(customAdapter.targetLambdaArn).toBe('arn:aws:lambda:eu-west-1:123456789012:function:custom');
|
|
67
72
|
expect(customAdapter.scheduleGroupName).toBe('custom-group');
|
|
68
|
-
expect(customAdapter.roleArn).toBe('arn:aws:iam::123456789012:role/custom-role');
|
|
69
73
|
});
|
|
70
74
|
|
|
71
|
-
it('should
|
|
72
|
-
|
|
73
|
-
expect(() => new AWSSchedulerAdapter({
|
|
74
|
-
...defaultParams,
|
|
75
|
-
})).toThrow('AWSSchedulerAdapter requires AWS_REGION environment variable');
|
|
76
|
-
});
|
|
75
|
+
it('should use environment variables as fallback', () => {
|
|
76
|
+
const envAdapter = new AWSSchedulerAdapter();
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
expect(()
|
|
80
|
-
|
|
81
|
-
roleArn: defaultParams.roleArn,
|
|
82
|
-
})).toThrow('AWSSchedulerAdapter requires targetLambdaArn');
|
|
78
|
+
expect(envAdapter.region).toBe('us-east-1');
|
|
79
|
+
expect(envAdapter.targetLambdaArn).toBe('arn:aws:lambda:us-east-1:123456789012:function:test-executor');
|
|
80
|
+
expect(envAdapter.scheduleGroupName).toBe('test-schedule-group');
|
|
83
81
|
});
|
|
84
82
|
|
|
85
|
-
it('should
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
it('should use defaults when no config or env vars', () => {
|
|
84
|
+
delete process.env.AWS_REGION;
|
|
85
|
+
delete process.env.SCHEDULE_GROUP_NAME;
|
|
86
|
+
delete process.env.ADMIN_SCRIPT_LAMBDA_ARN;
|
|
87
|
+
|
|
88
|
+
const defaultAdapter = new AWSSchedulerAdapter();
|
|
91
89
|
|
|
92
|
-
|
|
93
|
-
expect(()
|
|
94
|
-
targetLambdaArn: defaultParams.targetLambdaArn,
|
|
95
|
-
scheduleGroupName: defaultParams.scheduleGroupName,
|
|
96
|
-
})).toThrow('AWSSchedulerAdapter requires roleArn');
|
|
90
|
+
expect(defaultAdapter.region).toBe('us-east-1');
|
|
91
|
+
expect(defaultAdapter.scheduleGroupName).toBe('frigg-admin-scripts');
|
|
97
92
|
});
|
|
98
93
|
});
|
|
99
94
|
|
|
@@ -144,7 +139,7 @@ describe('AWSSchedulerAdapter', () => {
|
|
|
144
139
|
});
|
|
145
140
|
});
|
|
146
141
|
|
|
147
|
-
it('should configure target with Lambda ARN and
|
|
142
|
+
it('should configure target with Lambda ARN and role', async () => {
|
|
148
143
|
mockSend.mockResolvedValue({
|
|
149
144
|
ScheduleArn: 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
|
|
150
145
|
});
|
|
@@ -159,26 +154,6 @@ describe('AWSSchedulerAdapter', () => {
|
|
|
159
154
|
expect(command.params.Target.RoleArn).toBe('arn:aws:iam::123456789012:role/test-role');
|
|
160
155
|
});
|
|
161
156
|
|
|
162
|
-
it('should use roleArn from constructor, not process.env', async () => {
|
|
163
|
-
const customRoleArn = 'arn:aws:iam::999999999999:role/custom-scheduler-role';
|
|
164
|
-
const customAdapter = new AWSSchedulerAdapter({
|
|
165
|
-
...defaultParams,
|
|
166
|
-
roleArn: customRoleArn,
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
mockSend.mockResolvedValue({
|
|
170
|
-
ScheduleArn: 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
await customAdapter.createSchedule({
|
|
174
|
-
scriptName: 'test-script',
|
|
175
|
-
cronExpression: 'cron(0 0 * * ? *)',
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
const command = mockSend.mock.calls[0][0];
|
|
179
|
-
expect(command.params.Target.RoleArn).toBe(customRoleArn);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
157
|
it('should enable schedule by default', async () => {
|
|
183
158
|
mockSend.mockResolvedValue({
|
|
184
159
|
ScheduleArn: 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
|
|
@@ -326,7 +301,9 @@ describe('AWSSchedulerAdapter', () => {
|
|
|
326
301
|
|
|
327
302
|
describe('Lazy SDK loading', () => {
|
|
328
303
|
it('should load AWS SDK on first client access', () => {
|
|
329
|
-
const newAdapter = new AWSSchedulerAdapter({
|
|
304
|
+
const newAdapter = new AWSSchedulerAdapter({
|
|
305
|
+
targetLambdaArn: 'arn:aws:lambda:us-east-1:123456789012:function:test',
|
|
306
|
+
});
|
|
330
307
|
|
|
331
308
|
expect(newAdapter.scheduler).toBeNull();
|
|
332
309
|
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const {
|
|
2
|
+
createSchedulerAdapter,
|
|
3
|
+
detectSchedulerAdapterType,
|
|
4
|
+
} = require('../scheduler-adapter-factory');
|
|
2
5
|
const { AWSSchedulerAdapter } = require('../aws-scheduler-adapter');
|
|
3
6
|
const { LocalSchedulerAdapter } = require('../local-scheduler-adapter');
|
|
4
7
|
|
|
@@ -14,56 +17,71 @@ jest.mock('@aws-sdk/client-scheduler', () => ({
|
|
|
14
17
|
ListSchedulesCommand: jest.fn(),
|
|
15
18
|
}));
|
|
16
19
|
|
|
17
|
-
const awsAdapterParams = {
|
|
18
|
-
targetLambdaArn: 'arn:aws:lambda:us-east-1:123456789012:function:test',
|
|
19
|
-
scheduleGroupName: 'test-group',
|
|
20
|
-
roleArn: 'arn:aws:iam::123456789012:role/test-role',
|
|
21
|
-
};
|
|
22
|
-
|
|
23
20
|
describe('Scheduler Adapter Factory', () => {
|
|
24
|
-
|
|
21
|
+
let originalEnv;
|
|
22
|
+
|
|
23
|
+
beforeAll(() => {
|
|
24
|
+
originalEnv = { ...process.env };
|
|
25
|
+
});
|
|
25
26
|
|
|
26
27
|
beforeEach(() => {
|
|
27
|
-
|
|
28
|
+
// Reset environment variables
|
|
29
|
+
delete process.env.SCHEDULER_ADAPTER;
|
|
30
|
+
delete process.env.STAGE;
|
|
31
|
+
delete process.env.NODE_ENV;
|
|
28
32
|
});
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
afterAll(() => {
|
|
31
35
|
process.env = originalEnv;
|
|
32
36
|
});
|
|
33
37
|
|
|
34
38
|
describe('createSchedulerAdapter()', () => {
|
|
35
|
-
it('should
|
|
36
|
-
|
|
37
|
-
});
|
|
39
|
+
it('should create local adapter by default', () => {
|
|
40
|
+
const adapter = createSchedulerAdapter();
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
expect(()
|
|
42
|
+
expect(adapter).toBeInstanceOf(LocalSchedulerAdapter);
|
|
43
|
+
expect(adapter.getName()).toBe('local-cron');
|
|
41
44
|
});
|
|
42
45
|
|
|
43
|
-
it('should create local adapter when
|
|
46
|
+
it('should create local adapter when explicitly specified', () => {
|
|
44
47
|
const adapter = createSchedulerAdapter({ type: 'local' });
|
|
45
48
|
|
|
46
49
|
expect(adapter).toBeInstanceOf(LocalSchedulerAdapter);
|
|
47
|
-
expect(adapter.getName()).toBe('local-cron');
|
|
48
50
|
});
|
|
49
51
|
|
|
50
52
|
it('should create AWS adapter when type is "aws"', () => {
|
|
51
|
-
const adapter = createSchedulerAdapter({ type: 'aws'
|
|
53
|
+
const adapter = createSchedulerAdapter({ type: 'aws' });
|
|
52
54
|
|
|
53
55
|
expect(adapter).toBeInstanceOf(AWSSchedulerAdapter);
|
|
54
56
|
expect(adapter.getName()).toBe('aws-eventbridge-scheduler');
|
|
55
57
|
});
|
|
56
58
|
|
|
57
59
|
it('should create AWS adapter when type is "eventbridge"', () => {
|
|
58
|
-
const adapter = createSchedulerAdapter({ type: 'eventbridge'
|
|
60
|
+
const adapter = createSchedulerAdapter({ type: 'eventbridge' });
|
|
61
|
+
|
|
62
|
+
expect(adapter).toBeInstanceOf(AWSSchedulerAdapter);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should use SCHEDULER_ADAPTER env variable', () => {
|
|
66
|
+
process.env.SCHEDULER_ADAPTER = 'aws';
|
|
67
|
+
|
|
68
|
+
const adapter = createSchedulerAdapter();
|
|
59
69
|
|
|
60
70
|
expect(adapter).toBeInstanceOf(AWSSchedulerAdapter);
|
|
61
71
|
});
|
|
62
72
|
|
|
73
|
+
it('should allow explicit type to override env variable', () => {
|
|
74
|
+
process.env.SCHEDULER_ADAPTER = 'aws';
|
|
75
|
+
|
|
76
|
+
const adapter = createSchedulerAdapter({ type: 'local' });
|
|
77
|
+
|
|
78
|
+
expect(adapter).toBeInstanceOf(LocalSchedulerAdapter);
|
|
79
|
+
});
|
|
80
|
+
|
|
63
81
|
it('should handle case-insensitive type values', () => {
|
|
64
|
-
const adapter1 = createSchedulerAdapter({ type: 'AWS'
|
|
82
|
+
const adapter1 = createSchedulerAdapter({ type: 'AWS' });
|
|
65
83
|
const adapter2 = createSchedulerAdapter({ type: 'LOCAL' });
|
|
66
|
-
const adapter3 = createSchedulerAdapter({ type: 'EventBridge'
|
|
84
|
+
const adapter3 = createSchedulerAdapter({ type: 'EventBridge' });
|
|
67
85
|
|
|
68
86
|
expect(adapter1).toBeInstanceOf(AWSSchedulerAdapter);
|
|
69
87
|
expect(adapter2).toBeInstanceOf(LocalSchedulerAdapter);
|
|
@@ -73,29 +91,17 @@ describe('Scheduler Adapter Factory', () => {
|
|
|
73
91
|
it('should pass AWS configuration to AWS adapter', () => {
|
|
74
92
|
const config = {
|
|
75
93
|
type: 'aws',
|
|
94
|
+
region: 'eu-west-1',
|
|
76
95
|
targetLambdaArn: 'arn:aws:lambda:eu-west-1:123456789012:function:test',
|
|
77
96
|
scheduleGroupName: 'custom-group',
|
|
78
|
-
roleArn: 'arn:aws:iam::123456789012:role/custom-role',
|
|
79
97
|
};
|
|
80
98
|
|
|
81
99
|
const adapter = createSchedulerAdapter(config);
|
|
82
100
|
|
|
83
101
|
expect(adapter).toBeInstanceOf(AWSSchedulerAdapter);
|
|
84
|
-
expect(adapter.region).toBe('
|
|
102
|
+
expect(adapter.region).toBe('eu-west-1');
|
|
85
103
|
expect(adapter.targetLambdaArn).toBe('arn:aws:lambda:eu-west-1:123456789012:function:test');
|
|
86
104
|
expect(adapter.scheduleGroupName).toBe('custom-group');
|
|
87
|
-
expect(adapter.roleArn).toBe('arn:aws:iam::123456789012:role/custom-role');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('should pass roleArn through to AWS adapter', () => {
|
|
91
|
-
const adapter = createSchedulerAdapter({
|
|
92
|
-
type: 'aws',
|
|
93
|
-
...awsAdapterParams,
|
|
94
|
-
roleArn: 'arn:aws:iam::999999999999:role/scheduler-role',
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
expect(adapter).toBeInstanceOf(AWSSchedulerAdapter);
|
|
98
|
-
expect(adapter.roleArn).toBe('arn:aws:iam::999999999999:role/scheduler-role');
|
|
99
105
|
});
|
|
100
106
|
|
|
101
107
|
it('should ignore AWS config for local adapter', () => {
|
|
@@ -110,8 +116,142 @@ describe('Scheduler Adapter Factory', () => {
|
|
|
110
116
|
expect(adapter.region).toBeUndefined();
|
|
111
117
|
});
|
|
112
118
|
|
|
113
|
-
it('should
|
|
114
|
-
|
|
119
|
+
it('should handle unknown adapter type by creating local adapter', () => {
|
|
120
|
+
const adapter = createSchedulerAdapter({ type: 'unknown-type' });
|
|
121
|
+
|
|
122
|
+
expect(adapter).toBeInstanceOf(LocalSchedulerAdapter);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('detectSchedulerAdapterType()', () => {
|
|
127
|
+
it('should return "local" by default', () => {
|
|
128
|
+
const type = detectSchedulerAdapterType();
|
|
129
|
+
|
|
130
|
+
expect(type).toBe('local');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should return env SCHEDULER_ADAPTER when set', () => {
|
|
134
|
+
process.env.SCHEDULER_ADAPTER = 'aws';
|
|
135
|
+
|
|
136
|
+
const type = detectSchedulerAdapterType();
|
|
137
|
+
|
|
138
|
+
expect(type).toBe('aws');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should return "aws" for production stage', () => {
|
|
142
|
+
process.env.STAGE = 'production';
|
|
143
|
+
|
|
144
|
+
const type = detectSchedulerAdapterType();
|
|
145
|
+
|
|
146
|
+
expect(type).toBe('aws');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should return "aws" for prod stage', () => {
|
|
150
|
+
process.env.STAGE = 'prod';
|
|
151
|
+
|
|
152
|
+
const type = detectSchedulerAdapterType();
|
|
153
|
+
|
|
154
|
+
expect(type).toBe('aws');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should return "aws" for staging stage', () => {
|
|
158
|
+
process.env.STAGE = 'staging';
|
|
159
|
+
|
|
160
|
+
const type = detectSchedulerAdapterType();
|
|
161
|
+
|
|
162
|
+
expect(type).toBe('aws');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should return "aws" for stage stage', () => {
|
|
166
|
+
process.env.STAGE = 'stage';
|
|
167
|
+
|
|
168
|
+
const type = detectSchedulerAdapterType();
|
|
169
|
+
|
|
170
|
+
expect(type).toBe('aws');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should handle case-insensitive stage values', () => {
|
|
174
|
+
process.env.STAGE = 'PRODUCTION';
|
|
175
|
+
|
|
176
|
+
const type = detectSchedulerAdapterType();
|
|
177
|
+
|
|
178
|
+
expect(type).toBe('aws');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should return "local" for dev stage', () => {
|
|
182
|
+
process.env.STAGE = 'dev';
|
|
183
|
+
|
|
184
|
+
const type = detectSchedulerAdapterType();
|
|
185
|
+
|
|
186
|
+
expect(type).toBe('local');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should return "local" for development stage', () => {
|
|
190
|
+
process.env.STAGE = 'development';
|
|
191
|
+
|
|
192
|
+
const type = detectSchedulerAdapterType();
|
|
193
|
+
|
|
194
|
+
expect(type).toBe('local');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should return "local" for test stage', () => {
|
|
198
|
+
process.env.STAGE = 'test';
|
|
199
|
+
|
|
200
|
+
const type = detectSchedulerAdapterType();
|
|
201
|
+
|
|
202
|
+
expect(type).toBe('local');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should return "local" for local stage', () => {
|
|
206
|
+
process.env.STAGE = 'local';
|
|
207
|
+
|
|
208
|
+
const type = detectSchedulerAdapterType();
|
|
209
|
+
|
|
210
|
+
expect(type).toBe('local');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should use NODE_ENV as fallback for STAGE', () => {
|
|
214
|
+
delete process.env.STAGE;
|
|
215
|
+
process.env.NODE_ENV = 'production';
|
|
216
|
+
|
|
217
|
+
const type = detectSchedulerAdapterType();
|
|
218
|
+
|
|
219
|
+
expect(type).toBe('aws');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should prioritize explicit SCHEDULER_ADAPTER over auto-detection', () => {
|
|
223
|
+
process.env.SCHEDULER_ADAPTER = 'local';
|
|
224
|
+
process.env.STAGE = 'production';
|
|
225
|
+
|
|
226
|
+
const type = detectSchedulerAdapterType();
|
|
227
|
+
|
|
228
|
+
expect(type).toBe('local');
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe('Integration with createSchedulerAdapter', () => {
|
|
233
|
+
it('should auto-detect and create AWS adapter in production', () => {
|
|
234
|
+
process.env.STAGE = 'production';
|
|
235
|
+
|
|
236
|
+
const adapter = createSchedulerAdapter();
|
|
237
|
+
|
|
238
|
+
expect(adapter).toBeInstanceOf(AWSSchedulerAdapter);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should auto-detect and create local adapter in development', () => {
|
|
242
|
+
process.env.STAGE = 'development';
|
|
243
|
+
|
|
244
|
+
const adapter = createSchedulerAdapter();
|
|
245
|
+
|
|
246
|
+
expect(adapter).toBeInstanceOf(LocalSchedulerAdapter);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should allow explicit override of auto-detection', () => {
|
|
250
|
+
process.env.STAGE = 'production';
|
|
251
|
+
|
|
252
|
+
const adapter = createSchedulerAdapter({ type: 'local' });
|
|
253
|
+
|
|
254
|
+
expect(adapter).toBeInstanceOf(LocalSchedulerAdapter);
|
|
115
255
|
});
|
|
116
256
|
});
|
|
117
257
|
});
|
|
@@ -25,19 +25,12 @@ function loadSchedulerSDK() {
|
|
|
25
25
|
* Supports cron expressions, timezone configuration, and Lambda invocation.
|
|
26
26
|
*/
|
|
27
27
|
class AWSSchedulerAdapter extends SchedulerAdapter {
|
|
28
|
-
constructor({ credentials, targetLambdaArn, scheduleGroupName
|
|
28
|
+
constructor({ region, credentials, targetLambdaArn, scheduleGroupName } = {}) {
|
|
29
29
|
super();
|
|
30
|
-
|
|
31
|
-
if (!scheduleGroupName) throw new Error('AWSSchedulerAdapter requires scheduleGroupName');
|
|
32
|
-
if (!roleArn) throw new Error('AWSSchedulerAdapter requires roleArn');
|
|
33
|
-
// Region inherits from the service (set by Lambda runtime, same for all AWS resources)
|
|
34
|
-
const region = process.env.AWS_REGION;
|
|
35
|
-
if (!region) throw new Error('AWSSchedulerAdapter requires AWS_REGION environment variable');
|
|
36
|
-
this.region = region;
|
|
30
|
+
this.region = region || process.env.AWS_REGION || 'us-east-1';
|
|
37
31
|
this.credentials = credentials;
|
|
38
|
-
this.targetLambdaArn = targetLambdaArn;
|
|
39
|
-
this.scheduleGroupName = scheduleGroupName;
|
|
40
|
-
this.roleArn = roleArn;
|
|
32
|
+
this.targetLambdaArn = targetLambdaArn || process.env.ADMIN_SCRIPT_LAMBDA_ARN;
|
|
33
|
+
this.scheduleGroupName = scheduleGroupName || process.env.SCHEDULE_GROUP_NAME || 'frigg-admin-scripts';
|
|
41
34
|
this.scheduler = null;
|
|
42
35
|
}
|
|
43
36
|
|
|
@@ -68,7 +61,7 @@ class AWSSchedulerAdapter extends SchedulerAdapter {
|
|
|
68
61
|
FlexibleTimeWindow: { Mode: 'OFF' },
|
|
69
62
|
Target: {
|
|
70
63
|
Arn: this.targetLambdaArn,
|
|
71
|
-
RoleArn:
|
|
64
|
+
RoleArn: process.env.SCHEDULER_ROLE_ARN,
|
|
72
65
|
Input: JSON.stringify({
|
|
73
66
|
scriptName,
|
|
74
67
|
trigger: 'SCHEDULED',
|
|
@@ -6,44 +6,64 @@ const { LocalSchedulerAdapter } = require('./local-scheduler-adapter');
|
|
|
6
6
|
*
|
|
7
7
|
* Application Layer - Hexagonal Architecture
|
|
8
8
|
*
|
|
9
|
-
* Creates the appropriate scheduler adapter based on
|
|
10
|
-
*
|
|
9
|
+
* Creates the appropriate scheduler adapter based on configuration.
|
|
10
|
+
* Supports environment-based auto-detection and explicit configuration.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Create a scheduler adapter instance
|
|
15
15
|
*
|
|
16
|
-
* @param {Object} options - Configuration options
|
|
17
|
-
* @param {string} options.type - Adapter type ('aws', 'eventbridge', 'local')
|
|
16
|
+
* @param {Object} options - Configuration options
|
|
17
|
+
* @param {string} [options.type] - Adapter type ('aws', 'eventbridge', 'local')
|
|
18
|
+
* @param {string} [options.region] - AWS region (for AWS adapter)
|
|
18
19
|
* @param {Object} [options.credentials] - AWS credentials (for AWS adapter)
|
|
19
|
-
* @param {string} [options.targetLambdaArn] - Lambda ARN to invoke (
|
|
20
|
-
* @param {string} [options.scheduleGroupName] - EventBridge schedule group name (
|
|
21
|
-
* @param {string} [options.roleArn] - IAM role ARN for scheduler (required for AWS adapter)
|
|
20
|
+
* @param {string} [options.targetLambdaArn] - Lambda ARN to invoke (for AWS adapter)
|
|
21
|
+
* @param {string} [options.scheduleGroupName] - EventBridge schedule group name (for AWS adapter)
|
|
22
22
|
* @returns {SchedulerAdapter} Configured scheduler adapter
|
|
23
23
|
*/
|
|
24
24
|
function createSchedulerAdapter(options = {}) {
|
|
25
|
-
|
|
26
|
-
throw new Error('Scheduler adapter type is required. Configure in appDefinition.adminScripts.scheduler.type');
|
|
27
|
-
}
|
|
25
|
+
const adapterType = options.type || detectSchedulerAdapterType();
|
|
28
26
|
|
|
29
|
-
switch (
|
|
27
|
+
switch (adapterType.toLowerCase()) {
|
|
30
28
|
case 'aws':
|
|
31
29
|
case 'eventbridge':
|
|
32
30
|
return new AWSSchedulerAdapter({
|
|
31
|
+
region: options.region,
|
|
33
32
|
credentials: options.credentials,
|
|
34
33
|
targetLambdaArn: options.targetLambdaArn,
|
|
35
34
|
scheduleGroupName: options.scheduleGroupName,
|
|
36
|
-
roleArn: options.roleArn,
|
|
37
35
|
});
|
|
38
36
|
|
|
39
37
|
case 'local':
|
|
38
|
+
default:
|
|
40
39
|
return new LocalSchedulerAdapter();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Determine the appropriate scheduler adapter type based on environment
|
|
45
|
+
*
|
|
46
|
+
* @returns {string} Adapter type ('aws' or 'local')
|
|
47
|
+
*/
|
|
48
|
+
function detectSchedulerAdapterType() {
|
|
49
|
+
// If explicitly set, use that
|
|
50
|
+
if (process.env.SCHEDULER_ADAPTER) {
|
|
51
|
+
return process.env.SCHEDULER_ADAPTER;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Auto-detect based on environment
|
|
55
|
+
const stage = process.env.STAGE || process.env.NODE_ENV || 'local';
|
|
56
|
+
|
|
57
|
+
// Use AWS adapter in production/staging environments
|
|
58
|
+
if (['production', 'prod', 'staging', 'stage'].includes(stage.toLowerCase())) {
|
|
59
|
+
return 'aws';
|
|
44
60
|
}
|
|
61
|
+
|
|
62
|
+
// Use local adapter for dev/test/local
|
|
63
|
+
return 'local';
|
|
45
64
|
}
|
|
46
65
|
|
|
47
66
|
module.exports = {
|
|
48
67
|
createSchedulerAdapter,
|
|
68
|
+
detectSchedulerAdapterType,
|
|
49
69
|
};
|