@friggframework/devtools 2.0.0-next.11 → 2.0.0-next.110
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/README.md +1289 -0
- package/frigg-cli/__tests__/unit/commands/build.test.js +279 -0
- package/frigg-cli/__tests__/unit/commands/db-setup.test.js +649 -0
- package/frigg-cli/__tests__/unit/commands/deploy.test.js +320 -0
- package/frigg-cli/__tests__/unit/commands/doctor.test.js +309 -0
- package/frigg-cli/__tests__/unit/commands/generate-iam.test.js +97 -0
- package/frigg-cli/__tests__/unit/commands/install.test.js +400 -0
- package/frigg-cli/__tests__/unit/commands/ui.test.js +346 -0
- package/frigg-cli/__tests__/unit/dependencies.test.js +74 -0
- package/frigg-cli/__tests__/unit/utils/database-validator.test.js +397 -0
- package/frigg-cli/__tests__/unit/utils/error-messages.test.js +345 -0
- package/frigg-cli/__tests__/unit/version-detection.test.js +171 -0
- package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
- package/frigg-cli/__tests__/utils/prisma-mock.js +194 -0
- package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
- package/frigg-cli/__tests__/utils/test-setup.js +287 -0
- package/frigg-cli/auth-command/CLAUDE.md +293 -0
- package/frigg-cli/auth-command/README.md +450 -0
- package/frigg-cli/auth-command/api-key-flow.js +153 -0
- package/frigg-cli/auth-command/auth-tester.js +344 -0
- package/frigg-cli/auth-command/credential-storage.js +182 -0
- package/frigg-cli/auth-command/index.js +256 -0
- package/frigg-cli/auth-command/json-schema-form.js +67 -0
- package/frigg-cli/auth-command/module-loader.js +172 -0
- package/frigg-cli/auth-command/oauth-callback-server.js +431 -0
- package/frigg-cli/auth-command/oauth-flow.js +195 -0
- package/frigg-cli/auth-command/utils/browser.js +30 -0
- package/frigg-cli/build-command/index.js +45 -12
- package/frigg-cli/db-setup-command/index.js +246 -0
- package/frigg-cli/deploy-command/SPEC-DEPLOY-DRY-RUN.md +981 -0
- package/frigg-cli/deploy-command/index.js +339 -23
- package/frigg-cli/doctor-command/index.js +335 -0
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +301 -0
- package/frigg-cli/generate-command/azure-generator.js +43 -0
- package/frigg-cli/generate-command/gcp-generator.js +47 -0
- package/frigg-cli/generate-command/index.js +333 -0
- package/frigg-cli/generate-command/terraform-generator.js +555 -0
- package/frigg-cli/generate-iam-command.js +119 -0
- package/frigg-cli/index.js +189 -1
- package/frigg-cli/index.test.js +1 -4
- package/frigg-cli/init-command/backend-first-handler.js +756 -0
- package/frigg-cli/init-command/index.js +93 -0
- package/frigg-cli/init-command/template-handler.js +143 -0
- package/frigg-cli/install-command/index.js +1 -4
- package/frigg-cli/jest.config.js +124 -0
- package/frigg-cli/package.json +63 -0
- package/frigg-cli/repair-command/index.js +564 -0
- package/frigg-cli/ssm-command/index.js +308 -0
- package/frigg-cli/ssm-command/index.test.js +318 -0
- package/frigg-cli/start-command/index.js +118 -5
- package/frigg-cli/start-command/start-command.test.js +297 -0
- package/frigg-cli/test/init-command.test.js +180 -0
- package/frigg-cli/test/npm-registry.test.js +319 -0
- package/frigg-cli/ui-command/index.js +154 -0
- package/frigg-cli/utils/app-resolver.js +319 -0
- package/frigg-cli/utils/backend-path.js +16 -17
- package/frigg-cli/utils/database-validator.js +167 -0
- package/frigg-cli/utils/error-messages.js +329 -0
- package/frigg-cli/utils/npm-registry.js +167 -0
- package/frigg-cli/utils/process-manager.js +199 -0
- package/frigg-cli/utils/repo-detection.js +405 -0
- package/infrastructure/ARCHITECTURE.md +487 -0
- package/infrastructure/CLAUDE.md +481 -0
- package/infrastructure/HEALTH.md +468 -0
- package/infrastructure/README.md +540 -0
- package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
- package/infrastructure/__tests__/helpers/test-utils.js +275 -0
- package/infrastructure/__tests__/postgres-config.test.js +914 -0
- package/infrastructure/__tests__/scoped-environment.test.js +126 -0
- package/infrastructure/__tests__/ssm-preload-node-options.test.js +79 -0
- package/infrastructure/__tests__/template-generation.test.js +687 -0
- package/infrastructure/create-frigg-infrastructure.js +129 -20
- package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
- package/infrastructure/docs/PRE-DEPLOYMENT-HEALTH-CHECK-SPEC.md +1317 -0
- package/infrastructure/docs/WEBSOCKET-CONFIGURATION.md +105 -0
- package/infrastructure/docs/deployment-instructions.md +268 -0
- package/infrastructure/docs/generate-iam-command.md +278 -0
- package/infrastructure/docs/iam-policy-templates.md +193 -0
- package/infrastructure/domains/admin-scripts/admin-script-builder.js +567 -0
- package/infrastructure/domains/admin-scripts/admin-script-builder.test.js +1017 -0
- package/infrastructure/domains/admin-scripts/index.js +5 -0
- package/infrastructure/domains/database/aurora-builder.js +857 -0
- package/infrastructure/domains/database/aurora-builder.test.js +960 -0
- package/infrastructure/domains/database/aurora-discovery.js +87 -0
- package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
- package/infrastructure/domains/database/aurora-resolver.js +210 -0
- package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
- package/infrastructure/domains/database/migration-builder.js +757 -0
- package/infrastructure/domains/database/migration-builder.test.js +454 -0
- package/infrastructure/domains/database/migration-resolver.js +163 -0
- package/infrastructure/domains/database/migration-resolver.test.js +337 -0
- package/infrastructure/domains/health/application/ports/IPropertyReconciler.js +164 -0
- package/infrastructure/domains/health/application/ports/IResourceDetector.js +129 -0
- package/infrastructure/domains/health/application/ports/IResourceImporter.js +142 -0
- package/infrastructure/domains/health/application/ports/IStackRepository.js +131 -0
- package/infrastructure/domains/health/application/ports/index.js +26 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
- package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +152 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.test.js +343 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +535 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.test.js +376 -0
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +213 -0
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +441 -0
- package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
- package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
- package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
- package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
- package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
- package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
- package/infrastructure/domains/health/domain/entities/issue.js +299 -0
- package/infrastructure/domains/health/domain/entities/issue.test.js +528 -0
- package/infrastructure/domains/health/domain/entities/property-mismatch.js +108 -0
- package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +275 -0
- package/infrastructure/domains/health/domain/entities/resource.js +159 -0
- package/infrastructure/domains/health/domain/entities/resource.test.js +432 -0
- package/infrastructure/domains/health/domain/entities/stack-health-report.js +306 -0
- package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +601 -0
- package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
- package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
- package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
- package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
- package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
- package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.js +248 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +504 -0
- package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
- package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
- package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +234 -0
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +431 -0
- package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
- package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
- package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
- package/infrastructure/domains/health/domain/value-objects/health-score.js +138 -0
- package/infrastructure/domains/health/domain/value-objects/health-score.test.js +267 -0
- package/infrastructure/domains/health/domain/value-objects/property-mutability.js +161 -0
- package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +198 -0
- package/infrastructure/domains/health/domain/value-objects/resource-state.js +167 -0
- package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +196 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +192 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +262 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +784 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +1133 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +565 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +554 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.js +318 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.test.js +398 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +777 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +580 -0
- package/infrastructure/domains/integration/integration-builder.js +656 -0
- package/infrastructure/domains/integration/integration-builder.test.js +1064 -0
- package/infrastructure/domains/integration/integration-resolver.js +170 -0
- package/infrastructure/domains/integration/integration-resolver.test.js +369 -0
- package/infrastructure/domains/integration/websocket-builder.js +69 -0
- package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
- package/infrastructure/domains/networking/vpc-builder.js +2101 -0
- package/infrastructure/domains/networking/vpc-builder.test.js +2001 -0
- package/infrastructure/domains/networking/vpc-discovery.js +184 -0
- package/infrastructure/domains/networking/vpc-discovery.test.js +368 -0
- package/infrastructure/domains/networking/vpc-resolver.js +514 -0
- package/infrastructure/domains/networking/vpc-resolver.test.js +841 -0
- package/infrastructure/domains/parameters/offload-utils.js +190 -0
- package/infrastructure/domains/parameters/offload-utils.test.js +193 -0
- package/infrastructure/domains/parameters/ssm-builder.js +132 -0
- package/infrastructure/domains/parameters/ssm-builder.test.js +346 -0
- package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
- package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
- package/infrastructure/domains/scheduler/scheduler-builder.js +247 -0
- package/infrastructure/domains/scheduler/scheduler-builder.test.js +118 -0
- package/infrastructure/domains/security/iam-generator.js +851 -0
- package/infrastructure/domains/security/iam-generator.test.js +287 -0
- package/infrastructure/domains/security/kms-builder.js +415 -0
- package/infrastructure/domains/security/kms-builder.test.js +392 -0
- package/infrastructure/domains/security/kms-discovery.js +80 -0
- package/infrastructure/domains/security/kms-discovery.test.js +177 -0
- package/infrastructure/domains/security/kms-resolver.js +96 -0
- package/infrastructure/domains/security/kms-resolver.test.js +216 -0
- package/infrastructure/domains/security/templates/frigg-deployment-iam-stack.yaml +418 -0
- package/infrastructure/domains/security/templates/iam-policy-basic.json +218 -0
- package/infrastructure/domains/security/templates/iam-policy-full.json +293 -0
- package/infrastructure/domains/shared/base-builder.js +112 -0
- package/infrastructure/domains/shared/base-resolver.js +186 -0
- package/infrastructure/domains/shared/base-resolver.test.js +305 -0
- package/infrastructure/domains/shared/builder-orchestrator.js +226 -0
- package/infrastructure/domains/shared/builder-orchestrator.test.js +258 -0
- package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
- package/infrastructure/domains/shared/cloudformation-discovery.js +681 -0
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +1320 -0
- package/infrastructure/domains/shared/environment-builder.js +192 -0
- package/infrastructure/domains/shared/environment-builder.test.js +529 -0
- package/infrastructure/domains/shared/function-environments.js +97 -0
- package/infrastructure/domains/shared/function-environments.test.js +146 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.js +579 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +416 -0
- package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
- package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
- package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
- package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
- package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
- package/infrastructure/domains/shared/resource-discovery.enhanced.test.js +306 -0
- package/infrastructure/domains/shared/resource-discovery.js +256 -0
- package/infrastructure/domains/shared/resource-discovery.test.js +757 -0
- package/infrastructure/domains/shared/types/app-definition.js +225 -0
- package/infrastructure/domains/shared/types/discovery-result.js +106 -0
- package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
- package/infrastructure/domains/shared/types/index.js +46 -0
- package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
- package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +409 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +319 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.js +134 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +268 -0
- package/infrastructure/domains/shared/utilities/nested-node-modules.js +53 -0
- package/infrastructure/domains/shared/utilities/nested-node-modules.test.js +61 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +159 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +444 -0
- package/infrastructure/domains/shared/validation/env-validator.js +78 -0
- package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
- package/infrastructure/domains/shared/validation/plugin-validator.js +187 -0
- package/infrastructure/domains/shared/validation/plugin-validator.test.js +323 -0
- package/infrastructure/esbuild.config.js +53 -0
- package/infrastructure/infrastructure-composer.js +168 -0
- package/infrastructure/infrastructure-composer.test.js +2042 -0
- package/infrastructure/integration.test.js +381 -0
- package/infrastructure/scripts/build-prisma-layer.js +701 -0
- package/infrastructure/scripts/build-prisma-layer.test.js +170 -0
- package/infrastructure/scripts/build-time-discovery.js +238 -0
- package/infrastructure/scripts/build-time-discovery.test.js +379 -0
- package/infrastructure/scripts/run-discovery.js +110 -0
- package/infrastructure/scripts/verify-prisma-layer.js +72 -0
- package/management-ui/README.md +203 -0
- package/package.json +48 -16
- package/test/index.js +2 -4
- package/test/mock-api.js +1 -3
- package/test/mock-integration.js +4 -14
- package/.eslintrc.json +0 -3
- package/CHANGELOG.md +0 -132
- package/infrastructure/serverless-template.js +0 -283
- package/infrastructure/webpack.config.js +0 -20
- package/test/auther-definition-tester.js +0 -125
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSM Offload Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared helpers for the SSM Parameter Store offload feature. A variable is
|
|
5
|
+
* "offloaded" when its value lives in Parameter Store and is fetched by the
|
|
6
|
+
* runtime at cold start instead of being baked into the Lambda environment
|
|
7
|
+
* (which counts against the hard 4KB env-var limit).
|
|
8
|
+
*
|
|
9
|
+
* Consumed by the SsmBuilder, the environment builder, and the
|
|
10
|
+
* `frigg ssm push` CLI command so they all agree on the offload key set and
|
|
11
|
+
* parameter naming.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const ENV_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/;
|
|
15
|
+
|
|
16
|
+
const DEFAULT_PARAMETER_PREFIX =
|
|
17
|
+
'/frigg/${self:service}/${self:provider.stage}';
|
|
18
|
+
|
|
19
|
+
// INIT-phase preload shipped in @friggframework/core, present at this path in
|
|
20
|
+
// every skipEsbuild handler (which packages the full node_modules tree).
|
|
21
|
+
// LAMBDA_TASK_ROOT = /var/task.
|
|
22
|
+
const SSM_PRELOAD_PATH =
|
|
23
|
+
'/var/task/node_modules/@friggframework/core/core/ssm-preload.mjs';
|
|
24
|
+
|
|
25
|
+
// NODE_OPTIONS value that loads the INIT preload. Set ONLY on skipEsbuild
|
|
26
|
+
// functions (esbuild-bundled functions do not package the .mjs, and a missing
|
|
27
|
+
// --import target is a fatal Node startup error). No `${env:NODE_OPTIONS}`
|
|
28
|
+
// prefix: that source resolves the deploy host's shell at package time, which
|
|
29
|
+
// would leak an unrelated value into every Lambda.
|
|
30
|
+
const SSM_PRELOAD_NODE_OPTIONS = `--import file://${SSM_PRELOAD_PATH}`;
|
|
31
|
+
|
|
32
|
+
// Keys the framework itself sets (from discovered resources or the base
|
|
33
|
+
// template) or that the loader/bypass handlers depend on before SSM values
|
|
34
|
+
// are available. Never offloadable.
|
|
35
|
+
const FRAMEWORK_ENV_BLOCKLIST = new Set([
|
|
36
|
+
'STAGE',
|
|
37
|
+
'FRIGG_STACK',
|
|
38
|
+
'FRIGG_STAGE',
|
|
39
|
+
'FRIGG_REGION',
|
|
40
|
+
'KMS_KEY_ARN',
|
|
41
|
+
'AES_KEY',
|
|
42
|
+
'AES_KEY_ID',
|
|
43
|
+
'DATABASE_URL',
|
|
44
|
+
'DATABASE_HOST',
|
|
45
|
+
'DATABASE_PORT',
|
|
46
|
+
'DATABASE_USER',
|
|
47
|
+
'DATABASE_PASSWORD',
|
|
48
|
+
'DATABASE_SECRET_ARN',
|
|
49
|
+
'DB_TYPE',
|
|
50
|
+
'MONGO_URI',
|
|
51
|
+
'SECRET_ARN',
|
|
52
|
+
'SSM_PARAMETER_PREFIX',
|
|
53
|
+
'FRIGG_SSM_OFFLOADED_KEYS',
|
|
54
|
+
'FRIGG_SSM_CACHE_TTL',
|
|
55
|
+
'WORKER_FUNCTION_NAME',
|
|
56
|
+
// Reserved AWS Lambda runtime variables
|
|
57
|
+
'_HANDLER',
|
|
58
|
+
'_X_AMZN_TRACE_ID',
|
|
59
|
+
'AWS_DEFAULT_REGION',
|
|
60
|
+
'AWS_EXECUTION_ENV',
|
|
61
|
+
'AWS_REGION',
|
|
62
|
+
'AWS_LAMBDA_FUNCTION_NAME',
|
|
63
|
+
'AWS_LAMBDA_FUNCTION_MEMORY_SIZE',
|
|
64
|
+
'AWS_LAMBDA_FUNCTION_VERSION',
|
|
65
|
+
'AWS_LAMBDA_INITIALIZATION_TYPE',
|
|
66
|
+
'AWS_LAMBDA_LOG_GROUP_NAME',
|
|
67
|
+
'AWS_LAMBDA_LOG_STREAM_NAME',
|
|
68
|
+
'AWS_ACCESS_KEY',
|
|
69
|
+
'AWS_ACCESS_KEY_ID',
|
|
70
|
+
'AWS_SECRET_ACCESS_KEY',
|
|
71
|
+
'AWS_SESSION_TOKEN',
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Union of keys marked for SSM offload, sorted for deterministic output.
|
|
76
|
+
* Sources: appDefinition.environment values === 'ssm' and
|
|
77
|
+
* appDefinition.ssm.parameters keys. Does NOT filter invalid/blocklisted
|
|
78
|
+
* keys — use validateOffloadConfig() to surface those as errors instead of
|
|
79
|
+
* silently dropping them.
|
|
80
|
+
*
|
|
81
|
+
* @param {Object} appDefinition
|
|
82
|
+
* @returns {string[]}
|
|
83
|
+
*/
|
|
84
|
+
function getOffloadedKeys(appDefinition = {}) {
|
|
85
|
+
const keys = new Set();
|
|
86
|
+
|
|
87
|
+
for (const [key, value] of Object.entries(
|
|
88
|
+
appDefinition.environment || {}
|
|
89
|
+
)) {
|
|
90
|
+
if (value === 'ssm') keys.add(key);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const key of Object.keys(appDefinition.ssm?.parameters || {})) {
|
|
94
|
+
keys.add(key);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return [...keys].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Parameter path prefix under which offloaded values are stored. The default
|
|
102
|
+
* uses serverless variables resolved at package time and stays inside the
|
|
103
|
+
* *frigg* scope of the generated deployment IAM policies.
|
|
104
|
+
*
|
|
105
|
+
* @param {Object} appDefinition
|
|
106
|
+
* @returns {string}
|
|
107
|
+
*/
|
|
108
|
+
function getParameterPrefix(appDefinition = {}) {
|
|
109
|
+
const prefix =
|
|
110
|
+
appDefinition.ssm?.parameterPrefix || DEFAULT_PARAMETER_PREFIX;
|
|
111
|
+
return prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Resolve the parameter prefix to a concrete path outside a serverless
|
|
116
|
+
* build (e.g. in the CLI), substituting the serverless variables the
|
|
117
|
+
* default prefix uses.
|
|
118
|
+
*
|
|
119
|
+
* @param {Object} appDefinition
|
|
120
|
+
* @param {string} stage
|
|
121
|
+
* @returns {string}
|
|
122
|
+
*/
|
|
123
|
+
function resolveParameterPrefix(appDefinition = {}, stage) {
|
|
124
|
+
return getParameterPrefix(appDefinition)
|
|
125
|
+
.replaceAll('${self:service}', appDefinition.name)
|
|
126
|
+
.replaceAll('${self:provider.stage}', stage);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Whether the offload feature is active for this build: SSM enabled, at
|
|
131
|
+
* least one key marked, and not running in local mode.
|
|
132
|
+
*
|
|
133
|
+
* @param {Object} appDefinition
|
|
134
|
+
* @returns {boolean}
|
|
135
|
+
*/
|
|
136
|
+
function isSsmOffloadActive(appDefinition = {}) {
|
|
137
|
+
if (process.env.FRIGG_SKIP_AWS_DISCOVERY === 'true') return false;
|
|
138
|
+
if (appDefinition.ssm?.enable !== true) return false;
|
|
139
|
+
return getOffloadedKeys(appDefinition).length > 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Validate the offload configuration. Returns errors (not warnings) for
|
|
144
|
+
* misconfigurations that would silently lose variables at runtime.
|
|
145
|
+
*
|
|
146
|
+
* @param {Object} appDefinition
|
|
147
|
+
* @returns {{errors: string[]}}
|
|
148
|
+
*/
|
|
149
|
+
function validateOffloadConfig(appDefinition = {}) {
|
|
150
|
+
const errors = [];
|
|
151
|
+
const offloadedKeys = getOffloadedKeys(appDefinition);
|
|
152
|
+
|
|
153
|
+
if (offloadedKeys.length === 0) {
|
|
154
|
+
return { errors };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (appDefinition.ssm?.enable !== true) {
|
|
158
|
+
errors.push(
|
|
159
|
+
`Environment keys are marked for SSM offload (${offloadedKeys.join(
|
|
160
|
+
', '
|
|
161
|
+
)}) but ssm.enable is not true. Set ssm: { enable: true } in the app definition.`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
for (const key of offloadedKeys) {
|
|
166
|
+
if (FRAMEWORK_ENV_BLOCKLIST.has(key)) {
|
|
167
|
+
errors.push(
|
|
168
|
+
`${key} is a framework-managed environment variable and cannot be offloaded to SSM.`
|
|
169
|
+
);
|
|
170
|
+
} else if (!ENV_NAME_PATTERN.test(key)) {
|
|
171
|
+
errors.push(
|
|
172
|
+
`'${key}' is not a valid environment variable name (expected ${ENV_NAME_PATTERN}); it cannot be offloaded to SSM.`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return { errors };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports = {
|
|
181
|
+
getOffloadedKeys,
|
|
182
|
+
getParameterPrefix,
|
|
183
|
+
resolveParameterPrefix,
|
|
184
|
+
isSsmOffloadActive,
|
|
185
|
+
validateOffloadConfig,
|
|
186
|
+
FRAMEWORK_ENV_BLOCKLIST,
|
|
187
|
+
DEFAULT_PARAMETER_PREFIX,
|
|
188
|
+
SSM_PRELOAD_PATH,
|
|
189
|
+
SSM_PRELOAD_NODE_OPTIONS,
|
|
190
|
+
};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
const {
|
|
2
|
+
getOffloadedKeys,
|
|
3
|
+
getParameterPrefix,
|
|
4
|
+
isSsmOffloadActive,
|
|
5
|
+
validateOffloadConfig,
|
|
6
|
+
FRAMEWORK_ENV_BLOCKLIST,
|
|
7
|
+
} = require('./offload-utils');
|
|
8
|
+
|
|
9
|
+
describe('offload-utils', () => {
|
|
10
|
+
const originalSkipDiscovery = process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
if (originalSkipDiscovery === undefined) {
|
|
14
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
15
|
+
} else {
|
|
16
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = originalSkipDiscovery;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('getOffloadedKeys', () => {
|
|
21
|
+
it('returns empty array when nothing is marked', () => {
|
|
22
|
+
expect(getOffloadedKeys({})).toEqual([]);
|
|
23
|
+
expect(
|
|
24
|
+
getOffloadedKeys({ environment: { MY_VAR: true } })
|
|
25
|
+
).toEqual([]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("collects environment keys marked 'ssm'", () => {
|
|
29
|
+
const keys = getOffloadedKeys({
|
|
30
|
+
environment: { B_VAR: 'ssm', A_VAR: 'ssm', PLAIN: true },
|
|
31
|
+
});
|
|
32
|
+
expect(keys).toEqual(['A_VAR', 'B_VAR']);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('collects ssm.parameters keys', () => {
|
|
36
|
+
const keys = getOffloadedKeys({
|
|
37
|
+
ssm: {
|
|
38
|
+
enable: true,
|
|
39
|
+
parameters: { MY_SECRET: { type: 'SecureString' } },
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
expect(keys).toEqual(['MY_SECRET']);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('unions and dedupes both sources, sorted', () => {
|
|
46
|
+
const keys = getOffloadedKeys({
|
|
47
|
+
environment: { SHARED: 'ssm', ENV_ONLY: 'ssm' },
|
|
48
|
+
ssm: {
|
|
49
|
+
enable: true,
|
|
50
|
+
parameters: {
|
|
51
|
+
SHARED: { type: 'SecureString' },
|
|
52
|
+
PARAM_ONLY: { type: 'String' },
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
expect(keys).toEqual(['ENV_ONLY', 'PARAM_ONLY', 'SHARED']);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('getParameterPrefix', () => {
|
|
61
|
+
it('defaults to the frigg-scoped serverless-variable path', () => {
|
|
62
|
+
expect(getParameterPrefix({})).toBe(
|
|
63
|
+
'/frigg/${self:service}/${self:provider.stage}'
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('honors ssm.parameterPrefix override', () => {
|
|
68
|
+
expect(
|
|
69
|
+
getParameterPrefix({ ssm: { parameterPrefix: '/custom/x' } })
|
|
70
|
+
).toBe('/custom/x');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('strips a trailing slash from the override', () => {
|
|
74
|
+
expect(
|
|
75
|
+
getParameterPrefix({ ssm: { parameterPrefix: '/custom/x/' } })
|
|
76
|
+
).toBe('/custom/x');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('resolveParameterPrefix', () => {
|
|
81
|
+
const { resolveParameterPrefix } = require('./offload-utils');
|
|
82
|
+
|
|
83
|
+
it('resolves the default prefix with service and stage', () => {
|
|
84
|
+
expect(
|
|
85
|
+
resolveParameterPrefix({ name: 'my-app' }, 'prod')
|
|
86
|
+
).toBe('/frigg/my-app/prod');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('resolves serverless tokens inside a custom prefix', () => {
|
|
90
|
+
expect(
|
|
91
|
+
resolveParameterPrefix(
|
|
92
|
+
{
|
|
93
|
+
name: 'my-app',
|
|
94
|
+
ssm: {
|
|
95
|
+
parameterPrefix:
|
|
96
|
+
'/x/${self:service}/${self:provider.stage}',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
'dev'
|
|
100
|
+
)
|
|
101
|
+
).toBe('/x/my-app/dev');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('passes literal custom prefixes through', () => {
|
|
105
|
+
expect(
|
|
106
|
+
resolveParameterPrefix(
|
|
107
|
+
{ name: 'my-app', ssm: { parameterPrefix: '/plain/path' } },
|
|
108
|
+
'dev'
|
|
109
|
+
)
|
|
110
|
+
).toBe('/plain/path');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('isSsmOffloadActive', () => {
|
|
115
|
+
const offloadApp = {
|
|
116
|
+
ssm: { enable: true },
|
|
117
|
+
environment: { MY_SECRET: 'ssm' },
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
it('is true when ssm enabled and offload set non-empty', () => {
|
|
121
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
122
|
+
expect(isSsmOffloadActive(offloadApp)).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('is false without ssm.enable', () => {
|
|
126
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
127
|
+
expect(
|
|
128
|
+
isSsmOffloadActive({ environment: { MY_SECRET: 'ssm' } })
|
|
129
|
+
).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('is false when nothing is offloaded', () => {
|
|
133
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
134
|
+
expect(isSsmOffloadActive({ ssm: { enable: true } })).toBe(false);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('is false in local mode (FRIGG_SKIP_AWS_DISCOVERY)', () => {
|
|
138
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
139
|
+
expect(isSsmOffloadActive(offloadApp)).toBe(false);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('validateOffloadConfig', () => {
|
|
144
|
+
it('passes for a clean offload config', () => {
|
|
145
|
+
const { errors } = validateOffloadConfig({
|
|
146
|
+
ssm: { enable: true },
|
|
147
|
+
environment: { MY_SECRET: 'ssm' },
|
|
148
|
+
});
|
|
149
|
+
expect(errors).toEqual([]);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('errors when offload markers exist without ssm.enable', () => {
|
|
153
|
+
const { errors } = validateOffloadConfig({
|
|
154
|
+
environment: { MY_SECRET: 'ssm' },
|
|
155
|
+
});
|
|
156
|
+
expect(errors.join(' ')).toMatch(/ssm\.enable/);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('errors on framework-managed (blocklisted) keys', () => {
|
|
160
|
+
const { errors } = validateOffloadConfig({
|
|
161
|
+
ssm: { enable: true },
|
|
162
|
+
environment: { DATABASE_URL: 'ssm' },
|
|
163
|
+
});
|
|
164
|
+
expect(errors.join(' ')).toMatch(/DATABASE_URL/);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('errors on keys that are not valid env var names', () => {
|
|
168
|
+
const { errors } = validateOffloadConfig({
|
|
169
|
+
ssm: {
|
|
170
|
+
enable: true,
|
|
171
|
+
parameters: { 'api-keys/foo': { type: 'String' } },
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
expect(errors.join(' ')).toMatch(/api-keys\/foo/);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('blocklist covers the framework-managed keys', () => {
|
|
179
|
+
for (const key of [
|
|
180
|
+
'STAGE',
|
|
181
|
+
'FRIGG_STACK',
|
|
182
|
+
'KMS_KEY_ARN',
|
|
183
|
+
'DATABASE_URL',
|
|
184
|
+
'DATABASE_SECRET_ARN',
|
|
185
|
+
'SECRET_ARN',
|
|
186
|
+
'SSM_PARAMETER_PREFIX',
|
|
187
|
+
'FRIGG_SSM_OFFLOADED_KEYS',
|
|
188
|
+
'AWS_REGION',
|
|
189
|
+
]) {
|
|
190
|
+
expect(FRAMEWORK_ENV_BLOCKLIST.has(key)).toBe(true);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSM Parameter Store Builder
|
|
3
|
+
*
|
|
4
|
+
* Domain Layer - Hexagonal Architecture
|
|
5
|
+
*
|
|
6
|
+
* Responsible for:
|
|
7
|
+
* - Configuring IAM permissions for SSM Parameter Store access
|
|
8
|
+
* - Setting up SSM parameter references for Lambda functions
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { InfrastructureBuilder, ValidationResult } = require('../shared/base-builder');
|
|
12
|
+
const {
|
|
13
|
+
getOffloadedKeys,
|
|
14
|
+
getParameterPrefix,
|
|
15
|
+
isSsmOffloadActive,
|
|
16
|
+
validateOffloadConfig,
|
|
17
|
+
} = require('./offload-utils');
|
|
18
|
+
|
|
19
|
+
class SsmBuilder extends InfrastructureBuilder {
|
|
20
|
+
constructor() {
|
|
21
|
+
super();
|
|
22
|
+
this.name = 'SsmBuilder';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
shouldExecute(appDefinition) {
|
|
26
|
+
// Skip SSM in local mode (when FRIGG_SKIP_AWS_DISCOVERY is set)
|
|
27
|
+
// SSM Parameter Store is an AWS-specific service that should only be used in production
|
|
28
|
+
if (process.env.FRIGG_SKIP_AWS_DISCOVERY === 'true') {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return appDefinition.ssm?.enable === true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
validate(appDefinition) {
|
|
36
|
+
const result = new ValidationResult();
|
|
37
|
+
|
|
38
|
+
if (!appDefinition.ssm) {
|
|
39
|
+
result.addError('SSM configuration is missing');
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Validate parameters if provided
|
|
44
|
+
if (appDefinition.ssm.parameters) {
|
|
45
|
+
if (typeof appDefinition.ssm.parameters !== 'object' || Array.isArray(appDefinition.ssm.parameters)) {
|
|
46
|
+
result.addError('ssm.parameters must be an object (not an array)');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const error of validateOffloadConfig(appDefinition).errors) {
|
|
51
|
+
result.addError(error);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Build SSM configuration
|
|
59
|
+
*/
|
|
60
|
+
async build(appDefinition, discoveredResources) {
|
|
61
|
+
console.log(`\n[${this.name}] Configuring SSM Parameter Store...`);
|
|
62
|
+
|
|
63
|
+
const result = {
|
|
64
|
+
iamStatements: [],
|
|
65
|
+
environment: {},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const ssmActions = [
|
|
69
|
+
'ssm:GetParameter',
|
|
70
|
+
'ssm:GetParameters',
|
|
71
|
+
'ssm:GetParametersByPath',
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
const offloadActive = isSsmOffloadActive(appDefinition);
|
|
75
|
+
|
|
76
|
+
// Broad read grant, unless the app opts into prefix-only access while
|
|
77
|
+
// offload is active.
|
|
78
|
+
if (!(appDefinition.ssm.restrictIamToPrefix === true && offloadActive)) {
|
|
79
|
+
result.iamStatements.push({
|
|
80
|
+
Effect: 'Allow',
|
|
81
|
+
Action: ssmActions,
|
|
82
|
+
Resource: {
|
|
83
|
+
'Fn::Sub': 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*',
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
console.log(' ✅ SSM Parameter Store IAM permissions added');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (offloadActive) {
|
|
90
|
+
const prefix = getParameterPrefix(appDefinition);
|
|
91
|
+
const offloadedKeys = getOffloadedKeys(appDefinition);
|
|
92
|
+
|
|
93
|
+
result.environment.SSM_PARAMETER_PREFIX = prefix;
|
|
94
|
+
result.environment.FRIGG_SSM_OFFLOADED_KEYS = offloadedKeys.join(',');
|
|
95
|
+
|
|
96
|
+
// The INIT-phase preload (NODE_OPTIONS=--import) that populates
|
|
97
|
+
// process.env before app modules load is set per-function on
|
|
98
|
+
// skipEsbuild handlers only — see infrastructure-composer.js. It
|
|
99
|
+
// cannot go here (provider.environment is global) because a missing
|
|
100
|
+
// --import target fatally aborts Node startup, and esbuild-bundled
|
|
101
|
+
// functions do not package the preload file.
|
|
102
|
+
|
|
103
|
+
// Prefix-scoped read grant. Built as a plain serverless string (not
|
|
104
|
+
// Fn::Sub) because the prefix contains serverless variables like
|
|
105
|
+
// ${self:service} that Fn::Sub would reject as bad substitution keys.
|
|
106
|
+
result.iamStatements.push({
|
|
107
|
+
Effect: 'Allow',
|
|
108
|
+
Action: ssmActions,
|
|
109
|
+
Resource: `arn:aws:ssm:\${self:provider.region}:\${aws:accountId}:parameter${prefix}/*`,
|
|
110
|
+
});
|
|
111
|
+
console.log(
|
|
112
|
+
` ✅ SSM offload enabled for ${offloadedKeys.length} variable(s) under ${prefix}`
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (appDefinition.ssm.kmsKeyArn) {
|
|
116
|
+
result.iamStatements.push({
|
|
117
|
+
Effect: 'Allow',
|
|
118
|
+
Action: ['kms:Decrypt'],
|
|
119
|
+
Resource: appDefinition.ssm.kmsKeyArn,
|
|
120
|
+
});
|
|
121
|
+
console.log(' ✅ KMS decrypt permission added for offloaded parameters');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log(`[${this.name}] ✅ SSM configuration completed`);
|
|
126
|
+
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = { SsmBuilder };
|
|
132
|
+
|