@friggframework/devtools 2.0.0--canary.622.eb44ae5.0 → 2.0.0--canary.625.27a7e7a.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/frigg-cli/__tests__/unit/commands/generate-iam.test.js +97 -0
- package/frigg-cli/deploy-command/index.js +44 -0
- package/frigg-cli/generate-command/index.js +2 -1
- package/frigg-cli/generate-iam-command.js +2 -1
- package/frigg-cli/index.js +16 -1
- package/frigg-cli/ssm-command/index.js +270 -0
- package/frigg-cli/ssm-command/index.test.js +301 -0
- package/infrastructure/__tests__/helpers/test-utils.js +3 -5
- package/infrastructure/__tests__/scoped-environment.test.js +126 -0
- package/infrastructure/domains/admin-scripts/admin-script-builder.js +17 -3
- package/infrastructure/domains/admin-scripts/admin-script-builder.test.js +45 -0
- package/infrastructure/domains/database/migration-builder.js +46 -9
- package/infrastructure/domains/database/migration-builder.test.js +101 -0
- package/infrastructure/domains/integration/integration-builder.js +65 -9
- package/infrastructure/domains/integration/integration-builder.test.js +95 -0
- package/infrastructure/domains/networking/vpc-builder.js +57 -7
- package/infrastructure/domains/networking/vpc-builder.test.js +41 -0
- package/infrastructure/domains/networking/vpc-resolver.js +12 -3
- package/infrastructure/domains/networking/vpc-resolver.test.js +40 -0
- package/infrastructure/domains/parameters/offload-utils.js +175 -0
- package/infrastructure/domains/parameters/offload-utils.test.js +193 -0
- package/infrastructure/domains/parameters/ssm-builder.js +60 -14
- package/infrastructure/domains/parameters/ssm-builder.test.js +145 -1
- package/infrastructure/domains/scheduler/scheduler-builder.js +44 -8
- package/infrastructure/domains/scheduler/scheduler-builder.test.js +118 -0
- package/infrastructure/domains/security/iam-generator.js +33 -1
- package/infrastructure/domains/security/iam-generator.test.js +73 -0
- package/infrastructure/domains/security/templates/frigg-deployment-iam-stack.yaml +15 -0
- package/infrastructure/domains/security/templates/iam-policy-full.json +7 -2
- package/infrastructure/domains/shared/builder-orchestrator.js +14 -0
- package/infrastructure/domains/shared/builder-orchestrator.test.js +45 -0
- package/infrastructure/domains/shared/environment-builder.js +48 -38
- package/infrastructure/domains/shared/environment-builder.test.js +113 -97
- package/infrastructure/domains/shared/function-environments.js +97 -0
- package/infrastructure/domains/shared/function-environments.test.js +146 -0
- package/infrastructure/infrastructure-composer.js +5 -0
- package/infrastructure/infrastructure-composer.test.js +69 -14
- package/infrastructure/integration.test.js +3 -5
- package/package.json +8 -7
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests for Environment Builder Service
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Tests environment variable extraction and building
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const {
|
|
8
|
-
getAppEnvironmentVars,
|
|
9
|
-
buildEnvironment,
|
|
10
|
-
} = require('./environment-builder');
|
|
7
|
+
const { getAppEnvironmentVars, buildEnvironment } = require('./environment-builder');
|
|
11
8
|
|
|
12
9
|
describe('Environment Builder', () => {
|
|
13
10
|
describe('getAppEnvironmentVars()', () => {
|
|
@@ -41,7 +38,7 @@ describe('Environment Builder', () => {
|
|
|
41
38
|
expect(result.DISABLED_VAR).toBeUndefined();
|
|
42
39
|
});
|
|
43
40
|
|
|
44
|
-
it(
|
|
41
|
+
it("ignores non-boolean values other than the meaningful 'ssm' marker", () => {
|
|
45
42
|
const appDefinition = {
|
|
46
43
|
environment: {
|
|
47
44
|
VALID: true,
|
|
@@ -111,39 +108,99 @@ describe('Environment Builder', () => {
|
|
|
111
108
|
});
|
|
112
109
|
});
|
|
113
110
|
|
|
114
|
-
describe(
|
|
115
|
-
|
|
116
|
-
const result = getAppEnvironmentVars({
|
|
117
|
-
telemetry: { exporter: { type: 'otlp' } },
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
expect(result.OTEL_EXPORTER_OTLP_ENDPOINT).toBe(
|
|
121
|
-
"${env:OTEL_EXPORTER_OTLP_ENDPOINT, ''}"
|
|
122
|
-
);
|
|
123
|
-
expect(result.OTEL_EXPORTER_OTLP_HEADERS).toBe(
|
|
124
|
-
"${env:OTEL_EXPORTER_OTLP_HEADERS, ''}"
|
|
125
|
-
);
|
|
126
|
-
});
|
|
111
|
+
describe("getAppEnvironmentVars() - 'ssm' offload", () => {
|
|
112
|
+
const originalSkipDiscovery = process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
127
113
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
});
|
|
134
|
-
expect(result.OTEL_EXPORTER_OTLP_ENDPOINT).toBeDefined();
|
|
114
|
+
afterEach(() => {
|
|
115
|
+
if (originalSkipDiscovery === undefined) {
|
|
116
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
117
|
+
} else {
|
|
118
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = originalSkipDiscovery;
|
|
135
119
|
}
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
it('
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("excludes 'ssm' keys when offload is active", () => {
|
|
123
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
124
|
+
const appDefinition = {
|
|
125
|
+
ssm: { enable: true },
|
|
126
|
+
environment: { FOO: 'ssm', BAR: true },
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const result = getAppEnvironmentVars(appDefinition);
|
|
130
|
+
|
|
131
|
+
expect(result.FOO).toBeUndefined();
|
|
132
|
+
expect(result.BAR).toBe("${env:BAR, ''}");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("falls back to env reference for 'ssm' keys when FRIGG_SKIP_AWS_DISCOVERY is set", () => {
|
|
136
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
137
|
+
const appDefinition = {
|
|
138
|
+
ssm: { enable: true },
|
|
139
|
+
environment: { FOO: 'ssm' },
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const result = getAppEnvironmentVars(appDefinition);
|
|
143
|
+
|
|
144
|
+
expect(result.FOO).toBe("${env:FOO, ''}");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("falls back to env reference for 'ssm' keys when ssm is disabled", () => {
|
|
148
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
149
|
+
const appDefinition = {
|
|
150
|
+
ssm: { enable: false },
|
|
151
|
+
environment: { FOO: 'ssm' },
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const result = getAppEnvironmentVars(appDefinition);
|
|
155
|
+
|
|
156
|
+
expect(result.FOO).toBe("${env:FOO, ''}");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('falls back to env reference for a key declared only in ssm.parameters when FRIGG_SKIP_AWS_DISCOVERY is set', () => {
|
|
160
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
161
|
+
const appDefinition = {
|
|
162
|
+
ssm: {
|
|
163
|
+
enable: true,
|
|
164
|
+
parameters: { HUBSPOT_CLIENT_SECRET: { type: 'SecureString' } },
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const result = getAppEnvironmentVars(appDefinition);
|
|
169
|
+
|
|
170
|
+
expect(result.HUBSPOT_CLIENT_SECRET).toBe("${env:HUBSPOT_CLIENT_SECRET, ''}");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('excludes a key declared only in ssm.parameters when offload is active', () => {
|
|
174
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
175
|
+
const appDefinition = {
|
|
176
|
+
ssm: {
|
|
177
|
+
enable: true,
|
|
178
|
+
parameters: { HUBSPOT_CLIENT_SECRET: { type: 'SecureString' } },
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const result = getAppEnvironmentVars(appDefinition);
|
|
183
|
+
|
|
184
|
+
expect(result.HUBSPOT_CLIENT_SECRET).toBeUndefined();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("does not double-process a key present in both environment:'ssm' and ssm.parameters", () => {
|
|
188
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
189
|
+
const appDefinition = {
|
|
190
|
+
ssm: {
|
|
191
|
+
enable: true,
|
|
192
|
+
parameters: { FOO: { type: 'SecureString' } },
|
|
193
|
+
},
|
|
194
|
+
environment: { FOO: 'ssm' },
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const result = getAppEnvironmentVars(appDefinition);
|
|
198
|
+
|
|
199
|
+
expect(result.FOO).toBeUndefined();
|
|
200
|
+
|
|
201
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
202
|
+
const fallbackResult = getAppEnvironmentVars(appDefinition);
|
|
203
|
+
expect(fallbackResult.FOO).toBe("${env:FOO, ''}");
|
|
147
204
|
});
|
|
148
205
|
});
|
|
149
206
|
|
|
@@ -154,10 +211,7 @@ describe('Environment Builder', () => {
|
|
|
154
211
|
};
|
|
155
212
|
const discoveredResources = {};
|
|
156
213
|
|
|
157
|
-
const result = buildEnvironment(
|
|
158
|
-
appEnvironmentVars,
|
|
159
|
-
discoveredResources
|
|
160
|
-
);
|
|
214
|
+
const result = buildEnvironment(appEnvironmentVars, discoveredResources);
|
|
161
215
|
|
|
162
216
|
expect(result.API_KEY).toBe("${env:API_KEY, ''}");
|
|
163
217
|
expect(result.STAGE).toBe('${self:provider.stage}');
|
|
@@ -172,14 +226,9 @@ describe('Environment Builder', () => {
|
|
|
172
226
|
kmsKeyId: 'arn:aws:kms:us-east-1:123456:key/abc-123',
|
|
173
227
|
};
|
|
174
228
|
|
|
175
|
-
const result = buildEnvironment(
|
|
176
|
-
appEnvironmentVars,
|
|
177
|
-
discoveredResources
|
|
178
|
-
);
|
|
229
|
+
const result = buildEnvironment(appEnvironmentVars, discoveredResources);
|
|
179
230
|
|
|
180
|
-
expect(result.KMS_KEY_ARN).toBe(
|
|
181
|
-
'arn:aws:kms:us-east-1:123456:key/abc-123'
|
|
182
|
-
);
|
|
231
|
+
expect(result.KMS_KEY_ARN).toBe('arn:aws:kms:us-east-1:123456:key/abc-123');
|
|
183
232
|
});
|
|
184
233
|
|
|
185
234
|
it('should prefer kmsKeyId over kmsKeyArn if both present', () => {
|
|
@@ -189,33 +238,22 @@ describe('Environment Builder', () => {
|
|
|
189
238
|
kmsKeyArn: 'arn:aws:kms:us-east-1:123456:key/secondary',
|
|
190
239
|
};
|
|
191
240
|
|
|
192
|
-
const result = buildEnvironment(
|
|
193
|
-
appEnvironmentVars,
|
|
194
|
-
discoveredResources
|
|
195
|
-
);
|
|
241
|
+
const result = buildEnvironment(appEnvironmentVars, discoveredResources);
|
|
196
242
|
|
|
197
243
|
// Implementation uses if/else-if, so kmsKeyId takes priority
|
|
198
|
-
expect(result.KMS_KEY_ARN).toBe(
|
|
199
|
-
'arn:aws:kms:us-east-1:123456:key/primary'
|
|
200
|
-
);
|
|
244
|
+
expect(result.KMS_KEY_ARN).toBe('arn:aws:kms:us-east-1:123456:key/primary');
|
|
201
245
|
});
|
|
202
246
|
|
|
203
247
|
it('should add database connection info if discovered', () => {
|
|
204
248
|
const appEnvironmentVars = {};
|
|
205
249
|
const discoveredResources = {
|
|
206
|
-
auroraClusterEndpoint:
|
|
207
|
-
'cluster.abc.us-east-1.rds.amazonaws.com',
|
|
250
|
+
auroraClusterEndpoint: 'cluster.abc.us-east-1.rds.amazonaws.com',
|
|
208
251
|
auroraPort: 5432,
|
|
209
252
|
};
|
|
210
253
|
|
|
211
|
-
const result = buildEnvironment(
|
|
212
|
-
appEnvironmentVars,
|
|
213
|
-
discoveredResources
|
|
214
|
-
);
|
|
254
|
+
const result = buildEnvironment(appEnvironmentVars, discoveredResources);
|
|
215
255
|
|
|
216
|
-
expect(result.DATABASE_HOST).toBe(
|
|
217
|
-
'cluster.abc.us-east-1.rds.amazonaws.com'
|
|
218
|
-
);
|
|
256
|
+
expect(result.DATABASE_HOST).toBe('cluster.abc.us-east-1.rds.amazonaws.com');
|
|
219
257
|
expect(result.DATABASE_PORT).toBe('5432');
|
|
220
258
|
});
|
|
221
259
|
|
|
@@ -225,10 +263,7 @@ describe('Environment Builder', () => {
|
|
|
225
263
|
auroraClusterEndpoint: 'cluster.example.com',
|
|
226
264
|
};
|
|
227
265
|
|
|
228
|
-
const result = buildEnvironment(
|
|
229
|
-
appEnvironmentVars,
|
|
230
|
-
discoveredResources
|
|
231
|
-
);
|
|
266
|
+
const result = buildEnvironment(appEnvironmentVars, discoveredResources);
|
|
232
267
|
|
|
233
268
|
expect(result.DATABASE_HOST).toBe('cluster.example.com');
|
|
234
269
|
expect(result.DATABASE_PORT).toBe('5432');
|
|
@@ -237,18 +272,12 @@ describe('Environment Builder', () => {
|
|
|
237
272
|
it('should add database secret ARN if discovered', () => {
|
|
238
273
|
const appEnvironmentVars = {};
|
|
239
274
|
const discoveredResources = {
|
|
240
|
-
databaseSecretArn:
|
|
241
|
-
'arn:aws:secretsmanager:us-east-1:123456:secret:db-secret',
|
|
275
|
+
databaseSecretArn: 'arn:aws:secretsmanager:us-east-1:123456:secret:db-secret',
|
|
242
276
|
};
|
|
243
277
|
|
|
244
|
-
const result = buildEnvironment(
|
|
245
|
-
appEnvironmentVars,
|
|
246
|
-
discoveredResources
|
|
247
|
-
);
|
|
278
|
+
const result = buildEnvironment(appEnvironmentVars, discoveredResources);
|
|
248
279
|
|
|
249
|
-
expect(result.DATABASE_SECRET_ARN).toBe(
|
|
250
|
-
'arn:aws:secretsmanager:us-east-1:123456:secret:db-secret'
|
|
251
|
-
);
|
|
280
|
+
expect(result.DATABASE_SECRET_ARN).toBe('arn:aws:secretsmanager:us-east-1:123456:secret:db-secret');
|
|
252
281
|
});
|
|
253
282
|
|
|
254
283
|
it('should combine all discovered resources', () => {
|
|
@@ -259,27 +288,19 @@ describe('Environment Builder', () => {
|
|
|
259
288
|
kmsKeyArn: 'arn:aws:kms:us-east-1:123456:key/abc',
|
|
260
289
|
auroraClusterEndpoint: 'db.example.com',
|
|
261
290
|
auroraPort: 3306,
|
|
262
|
-
databaseSecretArn:
|
|
263
|
-
'arn:aws:secretsmanager:us-east-1:123456:secret:db',
|
|
291
|
+
databaseSecretArn: 'arn:aws:secretsmanager:us-east-1:123456:secret:db',
|
|
264
292
|
};
|
|
265
293
|
|
|
266
|
-
const result = buildEnvironment(
|
|
267
|
-
appEnvironmentVars,
|
|
268
|
-
discoveredResources
|
|
269
|
-
);
|
|
294
|
+
const result = buildEnvironment(appEnvironmentVars, discoveredResources);
|
|
270
295
|
|
|
271
296
|
expect(result.CUSTOM_VAR).toBe("${env:CUSTOM_VAR, ''}");
|
|
272
297
|
expect(result.FRIGG_STACK).toBe('${self:service}');
|
|
273
298
|
expect(result.FRIGG_STAGE).toBe('${self:provider.stage}');
|
|
274
299
|
expect(result.FRIGG_REGION).toBe('${self:provider.region}');
|
|
275
|
-
expect(result.KMS_KEY_ARN).toBe(
|
|
276
|
-
'arn:aws:kms:us-east-1:123456:key/abc'
|
|
277
|
-
);
|
|
300
|
+
expect(result.KMS_KEY_ARN).toBe('arn:aws:kms:us-east-1:123456:key/abc');
|
|
278
301
|
expect(result.DATABASE_HOST).toBe('db.example.com');
|
|
279
302
|
expect(result.DATABASE_PORT).toBe('3306');
|
|
280
|
-
expect(result.DATABASE_SECRET_ARN).toBe(
|
|
281
|
-
'arn:aws:secretsmanager:us-east-1:123456:secret:db'
|
|
282
|
-
);
|
|
303
|
+
expect(result.DATABASE_SECRET_ARN).toBe('arn:aws:secretsmanager:us-east-1:123456:secret:db');
|
|
283
304
|
});
|
|
284
305
|
|
|
285
306
|
it('should handle empty discoveredResources', () => {
|
|
@@ -288,10 +309,7 @@ describe('Environment Builder', () => {
|
|
|
288
309
|
};
|
|
289
310
|
const discoveredResources = {};
|
|
290
311
|
|
|
291
|
-
const result = buildEnvironment(
|
|
292
|
-
appEnvironmentVars,
|
|
293
|
-
discoveredResources
|
|
294
|
-
);
|
|
312
|
+
const result = buildEnvironment(appEnvironmentVars, discoveredResources);
|
|
295
313
|
|
|
296
314
|
expect(result.API_KEY).toBe("${env:API_KEY, ''}");
|
|
297
315
|
expect(result.FRIGG_STACK).toBe('${self:service}');
|
|
@@ -315,13 +333,11 @@ describe('Environment Builder', () => {
|
|
|
315
333
|
auroraPort: 3306, // Number
|
|
316
334
|
};
|
|
317
335
|
|
|
318
|
-
const result = buildEnvironment(
|
|
319
|
-
appEnvironmentVars,
|
|
320
|
-
discoveredResources
|
|
321
|
-
);
|
|
336
|
+
const result = buildEnvironment(appEnvironmentVars, discoveredResources);
|
|
322
337
|
|
|
323
338
|
expect(result.DATABASE_PORT).toBe('3306'); // String
|
|
324
339
|
expect(typeof result.DATABASE_PORT).toBe('string');
|
|
325
340
|
});
|
|
326
341
|
});
|
|
327
342
|
});
|
|
343
|
+
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-function environment scoping (ADR-027)
|
|
3
|
+
*
|
|
4
|
+
* Builders emit `result.functionEnvironments` — a map of function name to
|
|
5
|
+
* env vars — instead of broadcasting framework vars app-wide through
|
|
6
|
+
* `result.environment`. The composer applies the merged map onto the final
|
|
7
|
+
* function definitions after every function (base + builder) exists.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Whether builders should scope framework env vars per function. Skipped in
|
|
12
|
+
* local mode: the serverless-plugin injects LocalStack queue URLs at
|
|
13
|
+
* provider level only, and function-level values would shadow them.
|
|
14
|
+
*
|
|
15
|
+
* @param {Object} appDefinition
|
|
16
|
+
* @returns {boolean}
|
|
17
|
+
*/
|
|
18
|
+
function isScopedEnvironmentActive(appDefinition = {}) {
|
|
19
|
+
if (process.env.FRIGG_SKIP_AWS_DISCOVERY === 'true') return false;
|
|
20
|
+
return appDefinition.lambda?.scopedEnvironment === true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Apply a merged functionEnvironments map onto the composed functions.
|
|
25
|
+
* A key a builder already set directly on a function wins (e.g. the
|
|
26
|
+
* admin-script router's own SCHEDULER_ROLE_ARN must not be clobbered by
|
|
27
|
+
* the integration-scheduler value). An unknown function name is a hard
|
|
28
|
+
* error — silently dropping a var would surface as a runtime failure.
|
|
29
|
+
*
|
|
30
|
+
* @param {Object} functions - definition.functions (mutated)
|
|
31
|
+
* @param {Object} functionEnvironments - { fnName: { KEY: value } }
|
|
32
|
+
*/
|
|
33
|
+
function applyFunctionEnvironments(functions, functionEnvironments = {}) {
|
|
34
|
+
for (const [fnName, env] of Object.entries(functionEnvironments)) {
|
|
35
|
+
const fn = functions[fnName];
|
|
36
|
+
if (!fn) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`functionEnvironments targets unknown function '${fnName}' (known: ${Object.keys(
|
|
39
|
+
functions
|
|
40
|
+
).join(', ')})`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
fn.environment = { ...env, ...(fn.environment || {}) };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Function names the integration builder creates for one integration.
|
|
49
|
+
* Wire contract: must stay in sync with
|
|
50
|
+
* IntegrationBuilder.createFunctionDefinitions — a drift here surfaces as a
|
|
51
|
+
* hard unknown-function error at compose time, not a silent var drop.
|
|
52
|
+
*
|
|
53
|
+
* @param {Object} integration - entry from appDefinition.integrations
|
|
54
|
+
* @returns {string[]}
|
|
55
|
+
*/
|
|
56
|
+
function getIntegrationFunctionNames(integration) {
|
|
57
|
+
const name = integration.Definition.name;
|
|
58
|
+
const names = [name, `${name}QueueWorker`];
|
|
59
|
+
|
|
60
|
+
const webhooks = integration.Definition.webhooks;
|
|
61
|
+
if (webhooks === true || webhooks?.enabled === true) {
|
|
62
|
+
names.splice(1, 0, `${name}Webhook`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (const [bindingKey, binding] of Object.entries(
|
|
66
|
+
integration.Definition.extensions || {}
|
|
67
|
+
)) {
|
|
68
|
+
const routes = binding?.extension?.routes || [];
|
|
69
|
+
if (routes.length === 0) continue;
|
|
70
|
+
names.push(`${name}__${String(bindingKey).replace(/[^A-Za-z0-9]/g, '')}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return names;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Admin-script functions, when the feature is on (mirrors
|
|
78
|
+
* AdminScriptBuilder.shouldExecute). Both can instantiate arbitrary
|
|
79
|
+
* integrations, so they belong in every integration's queue-URL consumer
|
|
80
|
+
* set.
|
|
81
|
+
*
|
|
82
|
+
* @param {Object} appDefinition
|
|
83
|
+
* @returns {string[]}
|
|
84
|
+
*/
|
|
85
|
+
function getAdminFunctionNames(appDefinition = {}) {
|
|
86
|
+
return Array.isArray(appDefinition.adminScripts) &&
|
|
87
|
+
appDefinition.adminScripts.length > 0
|
|
88
|
+
? ['adminScriptRouter', 'adminScriptExecutor']
|
|
89
|
+
: [];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = {
|
|
93
|
+
isScopedEnvironmentActive,
|
|
94
|
+
applyFunctionEnvironments,
|
|
95
|
+
getIntegrationFunctionNames,
|
|
96
|
+
getAdminFunctionNames,
|
|
97
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const {
|
|
2
|
+
isScopedEnvironmentActive,
|
|
3
|
+
applyFunctionEnvironments,
|
|
4
|
+
} = require('./function-environments');
|
|
5
|
+
|
|
6
|
+
describe('function-environments', () => {
|
|
7
|
+
const originalSkipDiscovery = process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
if (originalSkipDiscovery === undefined) {
|
|
11
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
12
|
+
} else {
|
|
13
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = originalSkipDiscovery;
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('isScopedEnvironmentActive', () => {
|
|
18
|
+
it('is true only when lambda.scopedEnvironment is set', () => {
|
|
19
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
20
|
+
expect(
|
|
21
|
+
isScopedEnvironmentActive({
|
|
22
|
+
lambda: { scopedEnvironment: true },
|
|
23
|
+
})
|
|
24
|
+
).toBe(true);
|
|
25
|
+
expect(isScopedEnvironmentActive({})).toBe(false);
|
|
26
|
+
expect(
|
|
27
|
+
isScopedEnvironmentActive({
|
|
28
|
+
lambda: { scopedEnvironment: false },
|
|
29
|
+
})
|
|
30
|
+
).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('is false in local mode: the serverless-plugin injects LocalStack queue URLs at provider level only', () => {
|
|
34
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
35
|
+
expect(
|
|
36
|
+
isScopedEnvironmentActive({
|
|
37
|
+
lambda: { scopedEnvironment: true },
|
|
38
|
+
})
|
|
39
|
+
).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('getIntegrationFunctionNames', () => {
|
|
44
|
+
const { getIntegrationFunctionNames } = require('./function-environments');
|
|
45
|
+
|
|
46
|
+
it('always includes the router and queue worker', () => {
|
|
47
|
+
expect(
|
|
48
|
+
getIntegrationFunctionNames({ Definition: { name: 'hubspot' } })
|
|
49
|
+
).toEqual(['hubspot', 'hubspotQueueWorker']);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('includes the webhook handler when webhooks are enabled', () => {
|
|
53
|
+
expect(
|
|
54
|
+
getIntegrationFunctionNames({
|
|
55
|
+
Definition: { name: 'hubspot', webhooks: true },
|
|
56
|
+
})
|
|
57
|
+
).toContain('hubspotWebhook');
|
|
58
|
+
expect(
|
|
59
|
+
getIntegrationFunctionNames({
|
|
60
|
+
Definition: { name: 'hubspot', webhooks: { enabled: true } },
|
|
61
|
+
})
|
|
62
|
+
).toContain('hubspotWebhook');
|
|
63
|
+
expect(
|
|
64
|
+
getIntegrationFunctionNames({
|
|
65
|
+
Definition: { name: 'hubspot', webhooks: { enabled: false } },
|
|
66
|
+
})
|
|
67
|
+
).not.toContain('hubspotWebhook');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('includes one function per routed extension binding, sanitized', () => {
|
|
71
|
+
const names = getIntegrationFunctionNames({
|
|
72
|
+
Definition: {
|
|
73
|
+
name: 'hubspot',
|
|
74
|
+
extensions: {
|
|
75
|
+
'my-ext': { extension: { routes: [{ path: '/x', method: 'GET' }] } },
|
|
76
|
+
routeless: { extension: { routes: [] } },
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
expect(names).toContain('hubspot__myext');
|
|
81
|
+
expect(names).not.toContain('hubspot__routeless');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('getAdminFunctionNames', () => {
|
|
86
|
+
const { getAdminFunctionNames } = require('./function-environments');
|
|
87
|
+
|
|
88
|
+
it('returns the admin functions only when admin scripts are configured', () => {
|
|
89
|
+
expect(
|
|
90
|
+
getAdminFunctionNames({ adminScripts: [{ Definition: { name: 'x' } }] })
|
|
91
|
+
).toEqual(['adminScriptRouter', 'adminScriptExecutor']);
|
|
92
|
+
expect(getAdminFunctionNames({})).toEqual([]);
|
|
93
|
+
expect(getAdminFunctionNames({ adminScripts: [] })).toEqual([]);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('applyFunctionEnvironments', () => {
|
|
98
|
+
const makeFunctions = () => ({
|
|
99
|
+
auth: { handler: 'auth.handler' },
|
|
100
|
+
hubspot: {
|
|
101
|
+
handler: 'hubspot.handler',
|
|
102
|
+
environment: { EXISTING: 'builder-set' },
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('assigns scoped env vars onto the target functions', () => {
|
|
107
|
+
const functions = makeFunctions();
|
|
108
|
+
applyFunctionEnvironments(functions, {
|
|
109
|
+
auth: { HUBSPOT_QUEUE_URL: 'url-1' },
|
|
110
|
+
hubspot: { HUBSPOT_QUEUE_URL: 'url-1' },
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(functions.auth.environment).toEqual({
|
|
114
|
+
HUBSPOT_QUEUE_URL: 'url-1',
|
|
115
|
+
});
|
|
116
|
+
expect(functions.hubspot.environment).toEqual({
|
|
117
|
+
EXISTING: 'builder-set',
|
|
118
|
+
HUBSPOT_QUEUE_URL: 'url-1',
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('never clobbers a key a builder already set directly on the function', () => {
|
|
123
|
+
const functions = makeFunctions();
|
|
124
|
+
applyFunctionEnvironments(functions, {
|
|
125
|
+
hubspot: { EXISTING: 'scoped-value', OTHER: 'x' },
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(functions.hubspot.environment.EXISTING).toBe('builder-set');
|
|
129
|
+
expect(functions.hubspot.environment.OTHER).toBe('x');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('throws on an unknown function name instead of silently dropping vars', () => {
|
|
133
|
+
expect(() =>
|
|
134
|
+
applyFunctionEnvironments(makeFunctions(), {
|
|
135
|
+
typoFunction: { A: '1' },
|
|
136
|
+
})
|
|
137
|
+
).toThrow(/typoFunction/);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('is a no-op for an empty map', () => {
|
|
141
|
+
const functions = makeFunctions();
|
|
142
|
+
applyFunctionEnvironments(functions, {});
|
|
143
|
+
expect(functions.auth.environment).toBeUndefined();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -20,6 +20,7 @@ const { SchedulerBuilder } = require('./domains/scheduler/scheduler-builder');
|
|
|
20
20
|
const { AdminScriptBuilder } = require('./domains/admin-scripts/admin-script-builder');
|
|
21
21
|
|
|
22
22
|
// Utilities
|
|
23
|
+
const { applyFunctionEnvironments } = require('./domains/shared/function-environments');
|
|
23
24
|
const { modifyHandlerPaths } = require('./domains/shared/utilities/handler-path-resolver');
|
|
24
25
|
const { createBaseDefinition } = require('./domains/shared/utilities/base-definition-factory');
|
|
25
26
|
const { ensurePrismaLayerExists } = require('./domains/shared/utilities/prisma-layer-manager');
|
|
@@ -75,6 +76,10 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
75
76
|
definition.provider.iamRoleStatements.push(...merged.iamStatements);
|
|
76
77
|
Object.assign(definition.provider.environment, merged.environment);
|
|
77
78
|
Object.assign(definition.functions, merged.functions);
|
|
79
|
+
applyFunctionEnvironments(
|
|
80
|
+
definition.functions,
|
|
81
|
+
merged.functionEnvironments
|
|
82
|
+
);
|
|
78
83
|
|
|
79
84
|
if (merged.vpcConfig) {
|
|
80
85
|
definition.provider.vpc = merged.vpcConfig;
|