@friggframework/devtools 2.0.0-next.35 → 2.0.0-next.37
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/deploy-command/index.js +150 -12
- package/infrastructure/README.md +114 -104
- package/infrastructure/env-validator.js +77 -0
- package/infrastructure/iam-generator.js +110 -2
- package/infrastructure/iam-policy-basic.json +5 -1
- package/infrastructure/iam-policy-full.json +5 -1
- package/infrastructure/serverless-template.js +73 -23
- package/package.json +6 -6
|
@@ -1,26 +1,155 @@
|
|
|
1
|
-
const { spawn
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
// Configuration constants
|
|
6
|
+
const PATHS = {
|
|
7
|
+
APP_DEFINITION: 'index.js',
|
|
8
|
+
INFRASTRUCTURE: 'infrastructure.js'
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const COMMANDS = {
|
|
12
|
+
SERVERLESS: 'serverless'
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Constructs filtered environment variables for serverless deployment
|
|
17
|
+
* @param {string[]} appDefinedVariables - Array of environment variable names from app definition
|
|
18
|
+
* @returns {Object} Filtered environment variables object
|
|
19
|
+
*/
|
|
20
|
+
function buildFilteredEnvironment(appDefinedVariables) {
|
|
21
|
+
return {
|
|
22
|
+
// Essential system variables needed to run serverless
|
|
23
|
+
PATH: process.env.PATH,
|
|
24
|
+
HOME: process.env.HOME,
|
|
25
|
+
USER: process.env.USER,
|
|
26
|
+
|
|
27
|
+
// AWS credentials and configuration (all AWS_ prefixed variables)
|
|
28
|
+
...Object.fromEntries(
|
|
29
|
+
Object.entries(process.env).filter(([key]) =>
|
|
30
|
+
key.startsWith('AWS_')
|
|
31
|
+
)
|
|
32
|
+
),
|
|
33
|
+
|
|
34
|
+
// App-defined environment variables
|
|
35
|
+
...Object.fromEntries(
|
|
36
|
+
appDefinedVariables
|
|
37
|
+
.map((key) => [key, process.env[key]])
|
|
38
|
+
.filter(([_, value]) => value !== undefined)
|
|
39
|
+
),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Loads and parses the app definition from index.js
|
|
45
|
+
* @returns {Object|null} App definition object or null if not found
|
|
46
|
+
*/
|
|
47
|
+
function loadAppDefinition() {
|
|
48
|
+
const appDefPath = path.join(process.cwd(), PATHS.APP_DEFINITION);
|
|
49
|
+
|
|
50
|
+
if (!fs.existsSync(appDefPath)) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const { Definition } = require(appDefPath);
|
|
56
|
+
return Definition;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.warn('Could not load appDefinition environment config:', error.message);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Extracts environment variable names from app definition
|
|
65
|
+
* @param {Object} appDefinition - App definition object
|
|
66
|
+
* @returns {string[]} Array of environment variable names
|
|
67
|
+
*/
|
|
68
|
+
function extractEnvironmentVariables(appDefinition) {
|
|
69
|
+
if (!appDefinition?.environment) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log('🔧 Loading environment configuration from appDefinition...');
|
|
74
|
+
|
|
75
|
+
const appDefinedVariables = Object.keys(appDefinition.environment).filter(
|
|
76
|
+
(key) => appDefinition.environment[key] === true
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
console.log(` Found ${appDefinedVariables.length} environment variables: ${appDefinedVariables.join(', ')}`);
|
|
80
|
+
return appDefinedVariables;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Handles environment validation warnings
|
|
85
|
+
* @param {Object} validation - Validation result object
|
|
86
|
+
* @param {Object} options - Deploy command options
|
|
87
|
+
*/
|
|
88
|
+
function handleValidationWarnings(validation, options) {
|
|
89
|
+
if (validation.missing.length === 0 || options.skipEnvValidation) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
6
92
|
|
|
7
|
-
|
|
8
|
-
console.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
93
|
+
console.warn(`⚠️ Warning: Missing ${validation.missing.length} environment variables: ${validation.missing.join(', ')}`);
|
|
94
|
+
console.warn(' These variables are optional and deployment will continue');
|
|
95
|
+
console.warn(' Run with --skip-env-validation to bypass this check');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Validates environment variables and builds filtered environment
|
|
100
|
+
* @param {Object} appDefinition - App definition object
|
|
101
|
+
* @param {Object} options - Deploy command options
|
|
102
|
+
* @returns {Object} Filtered environment variables
|
|
103
|
+
*/
|
|
104
|
+
function validateAndBuildEnvironment(appDefinition, options) {
|
|
105
|
+
if (!appDefinition) {
|
|
106
|
+
return buildFilteredEnvironment([]);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const appDefinedVariables = extractEnvironmentVariables(appDefinition);
|
|
110
|
+
|
|
111
|
+
// Try to use the env-validator if available
|
|
112
|
+
try {
|
|
113
|
+
const { validateEnvironmentVariables } = require('@friggframework/devtools/infrastructure/env-validator');
|
|
114
|
+
const validation = validateEnvironmentVariables(appDefinition);
|
|
115
|
+
|
|
116
|
+
handleValidationWarnings(validation, options);
|
|
117
|
+
return buildFilteredEnvironment(appDefinedVariables);
|
|
118
|
+
|
|
119
|
+
} catch (validatorError) {
|
|
120
|
+
// Validator not available, do basic validation
|
|
121
|
+
const missingVariables = appDefinedVariables.filter((variable) => !process.env[variable]);
|
|
122
|
+
|
|
123
|
+
if (missingVariables.length > 0) {
|
|
124
|
+
console.warn(`⚠️ Warning: Missing ${missingVariables.length} environment variables: ${missingVariables.join(', ')}`);
|
|
125
|
+
console.warn(' These variables are optional and deployment will continue');
|
|
126
|
+
console.warn(' Set them in your CI/CD environment or .env file if needed');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return buildFilteredEnvironment(appDefinedVariables);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Executes the serverless deployment command
|
|
135
|
+
* @param {Object} environment - Environment variables to pass to serverless
|
|
136
|
+
* @param {Object} options - Deploy command options
|
|
137
|
+
*/
|
|
138
|
+
function executeServerlessDeployment(environment, options) {
|
|
139
|
+
console.log('🚀 Deploying serverless application...');
|
|
140
|
+
|
|
12
141
|
const serverlessArgs = [
|
|
13
142
|
'deploy',
|
|
14
143
|
'--config',
|
|
15
|
-
|
|
144
|
+
PATHS.INFRASTRUCTURE,
|
|
16
145
|
'--stage',
|
|
17
146
|
options.stage,
|
|
18
147
|
];
|
|
19
148
|
|
|
20
|
-
const childProcess = spawn(
|
|
21
|
-
cwd:
|
|
149
|
+
const childProcess = spawn(COMMANDS.SERVERLESS, serverlessArgs, {
|
|
150
|
+
cwd: path.resolve(process.cwd()),
|
|
22
151
|
stdio: 'inherit',
|
|
23
|
-
env:
|
|
152
|
+
env: environment,
|
|
24
153
|
});
|
|
25
154
|
|
|
26
155
|
childProcess.on('error', (error) => {
|
|
@@ -34,4 +163,13 @@ async function deployCommand(options) {
|
|
|
34
163
|
});
|
|
35
164
|
}
|
|
36
165
|
|
|
166
|
+
async function deployCommand(options) {
|
|
167
|
+
console.log('Deploying the serverless application...');
|
|
168
|
+
|
|
169
|
+
const appDefinition = loadAppDefinition();
|
|
170
|
+
const environment = validateAndBuildEnvironment(appDefinition, options);
|
|
171
|
+
|
|
172
|
+
executeServerlessDeployment(environment, options);
|
|
173
|
+
}
|
|
174
|
+
|
|
37
175
|
module.exports = { deployCommand };
|
package/infrastructure/README.md
CHANGED
|
@@ -27,7 +27,7 @@ infrastructure/
|
|
|
27
27
|
├── AWS-DISCOVERY-TROUBLESHOOTING.md # AWS discovery troubleshooting
|
|
28
28
|
├── DEPLOYMENT-INSTRUCTIONS.md # General deployment instructions
|
|
29
29
|
├── README-TESTING.md # Testing strategy documentation
|
|
30
|
-
├──
|
|
30
|
+
├──
|
|
31
31
|
├── cloudformation/ # CloudFormation templates
|
|
32
32
|
│ ├── monitoring-infrastructure.yaml # Enhanced monitoring (Phase 3)
|
|
33
33
|
│ ├── cdn-infrastructure.yaml # CDN and UI distribution (Phase 3)
|
|
@@ -60,71 +60,79 @@ infrastructure/
|
|
|
60
60
|
#### 1. Serverless Template Generator (`serverless-template.js`)
|
|
61
61
|
|
|
62
62
|
Generates complete serverless.yml configurations with:
|
|
63
|
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
63
|
+
|
|
64
|
+
- VPC configuration and resource discovery
|
|
65
|
+
- KMS encryption for field-level encryption
|
|
66
|
+
- SSM Parameter Store integration
|
|
67
|
+
- Integration-specific functions and queues
|
|
68
|
+
- WebSocket support for real-time features
|
|
68
69
|
|
|
69
70
|
#### 2. AWS Discovery (`aws-discovery.js`)
|
|
70
71
|
|
|
71
72
|
Automatically discovers existing AWS resources:
|
|
72
|
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
73
|
+
|
|
74
|
+
- Default VPC and security groups
|
|
75
|
+
- Private subnets for Lambda functions
|
|
76
|
+
- Customer-managed KMS keys
|
|
77
|
+
- Route tables for VPC endpoints
|
|
76
78
|
|
|
77
79
|
#### 3. Build-Time Discovery (`build-time-discovery.js`)
|
|
78
80
|
|
|
79
81
|
Integrates AWS discovery into the build process:
|
|
80
|
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
82
|
+
|
|
83
|
+
- Pre-build hook for serverless deployments
|
|
84
|
+
- Environment variable injection
|
|
85
|
+
- Template variable replacement
|
|
86
|
+
- Error handling and fallback values
|
|
84
87
|
|
|
85
88
|
### Phase 3 Infrastructure
|
|
86
89
|
|
|
87
90
|
#### 1. Enhanced Monitoring (`cloudformation/monitoring-infrastructure.yaml`)
|
|
88
91
|
|
|
89
92
|
Production-ready monitoring with:
|
|
90
|
-
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
93
|
+
|
|
94
|
+
- Code generation service monitoring
|
|
95
|
+
- UI distribution monitoring
|
|
96
|
+
- Advanced CloudWatch dashboards
|
|
97
|
+
- Custom metrics and alarms
|
|
94
98
|
|
|
95
99
|
#### 2. CDN Infrastructure (`cloudformation/cdn-infrastructure.yaml`)
|
|
96
100
|
|
|
97
101
|
CloudFront distribution for UI packages:
|
|
98
|
-
|
|
99
|
-
-
|
|
100
|
-
-
|
|
101
|
-
-
|
|
102
|
+
|
|
103
|
+
- S3 bucket for multi-framework UI packages
|
|
104
|
+
- CloudFront distribution with custom domains
|
|
105
|
+
- Lambda function for package deployment
|
|
106
|
+
- API Gateway for package management
|
|
102
107
|
|
|
103
108
|
#### 3. Code Generation Infrastructure (`cloudformation/codegen-infrastructure.yaml`)
|
|
104
109
|
|
|
105
110
|
Serverless code generation platform:
|
|
106
|
-
|
|
107
|
-
-
|
|
108
|
-
-
|
|
109
|
-
-
|
|
110
|
-
-
|
|
111
|
+
|
|
112
|
+
- SQS queue for generation requests
|
|
113
|
+
- Lambda function with AI/ML integration
|
|
114
|
+
- DynamoDB tracking table
|
|
115
|
+
- S3 storage for templates and generated code
|
|
116
|
+
- ElastiCache for template caching
|
|
111
117
|
|
|
112
118
|
#### 4. Advanced Alerting (`cloudformation/alerting-infrastructure.yaml`)
|
|
113
119
|
|
|
114
120
|
Multi-channel alerting system:
|
|
115
|
-
|
|
116
|
-
-
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
-
|
|
121
|
+
|
|
122
|
+
- Multiple SNS topics for alert severity levels
|
|
123
|
+
- Lambda function for alert processing
|
|
124
|
+
- PagerDuty and Slack integration
|
|
125
|
+
- Composite alarms for system health
|
|
126
|
+
- Advanced metrics collection
|
|
120
127
|
|
|
121
128
|
#### 5. Deployment Pipeline (`cloudformation/deployment-pipeline.yaml`)
|
|
122
129
|
|
|
123
130
|
CI/CD pipeline for automated deployments:
|
|
124
|
-
|
|
125
|
-
-
|
|
126
|
-
-
|
|
127
|
-
-
|
|
131
|
+
|
|
132
|
+
- CodePipeline with GitHub integration
|
|
133
|
+
- CodeBuild projects for backend and UI
|
|
134
|
+
- Multi-stage deployment workflow
|
|
135
|
+
- Integration testing and approval gates
|
|
128
136
|
|
|
129
137
|
## Configuration Options
|
|
130
138
|
|
|
@@ -135,7 +143,7 @@ const appDefinition = {
|
|
|
135
143
|
// Basic configuration
|
|
136
144
|
name: 'my-frigg-app',
|
|
137
145
|
provider: 'aws',
|
|
138
|
-
|
|
146
|
+
|
|
139
147
|
// VPC configuration
|
|
140
148
|
vpc: {
|
|
141
149
|
enable: true,
|
|
@@ -144,22 +152,22 @@ const appDefinition = {
|
|
|
144
152
|
subnetIds: [...], // Optional: custom subnets
|
|
145
153
|
enableVPCEndpoints: true // Optional: create VPC endpoints
|
|
146
154
|
},
|
|
147
|
-
|
|
155
|
+
|
|
148
156
|
// KMS encryption
|
|
149
157
|
encryption: {
|
|
150
158
|
useDefaultKMSForFieldLevelEncryption: true
|
|
151
159
|
},
|
|
152
|
-
|
|
160
|
+
|
|
153
161
|
// SSM Parameter Store
|
|
154
162
|
ssm: {
|
|
155
163
|
enable: true
|
|
156
164
|
},
|
|
157
|
-
|
|
165
|
+
|
|
158
166
|
// WebSocket support (Phase 3)
|
|
159
167
|
websockets: {
|
|
160
168
|
enable: true
|
|
161
169
|
},
|
|
162
|
-
|
|
170
|
+
|
|
163
171
|
// Integrations
|
|
164
172
|
integrations: [
|
|
165
173
|
{ Definition: { name: 'hubspot' } },
|
|
@@ -195,10 +203,8 @@ SERVICE_NAME=my-frigg-app
|
|
|
195
203
|
const { composeServerlessDefinition } = require('./serverless-template');
|
|
196
204
|
|
|
197
205
|
const appDefinition = {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
{ Definition: { name: 'hubspot' } }
|
|
201
|
-
]
|
|
206
|
+
name: 'my-app',
|
|
207
|
+
integrations: [{ Definition: { name: 'hubspot' } }],
|
|
202
208
|
};
|
|
203
209
|
|
|
204
210
|
const serverlessConfig = await composeServerlessDefinition(appDefinition);
|
|
@@ -209,13 +215,11 @@ const serverlessConfig = await composeServerlessDefinition(appDefinition);
|
|
|
209
215
|
|
|
210
216
|
```javascript
|
|
211
217
|
const appDefinition = {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
{ Definition: { name: 'salesforce' } }
|
|
218
|
-
]
|
|
218
|
+
name: 'secure-app',
|
|
219
|
+
vpc: { enable: true },
|
|
220
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
221
|
+
ssm: { enable: true },
|
|
222
|
+
integrations: [{ Definition: { name: 'salesforce' } }],
|
|
219
223
|
};
|
|
220
224
|
|
|
221
225
|
const serverlessConfig = await composeServerlessDefinition(appDefinition);
|
|
@@ -225,12 +229,10 @@ const serverlessConfig = await composeServerlessDefinition(appDefinition);
|
|
|
225
229
|
|
|
226
230
|
```javascript
|
|
227
231
|
const appDefinition = {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
{ Definition: { name: 'slack' } }
|
|
233
|
-
]
|
|
232
|
+
name: 'realtime-app',
|
|
233
|
+
websockets: { enable: true },
|
|
234
|
+
vpc: { enable: true },
|
|
235
|
+
integrations: [{ Definition: { name: 'slack' } }],
|
|
234
236
|
};
|
|
235
237
|
|
|
236
238
|
const serverlessConfig = await composeServerlessDefinition(appDefinition);
|
|
@@ -259,19 +261,21 @@ npm test -- --watch
|
|
|
259
261
|
### Test Categories
|
|
260
262
|
|
|
261
263
|
1. **Unit Tests**: Test individual components
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
264
|
+
|
|
265
|
+
- AWS discovery utilities
|
|
266
|
+
- Serverless template generation
|
|
267
|
+
- IAM policy generation
|
|
265
268
|
|
|
266
269
|
2. **Integration Tests**: Test end-to-end workflows
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
+
|
|
271
|
+
- Complete discovery and template generation
|
|
272
|
+
- Plugin integration
|
|
273
|
+
- Phase 3 infrastructure validation
|
|
270
274
|
|
|
271
275
|
3. **Performance Tests**: Validate infrastructure limits
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
276
|
+
- CloudFormation template sizes
|
|
277
|
+
- Resource count limits
|
|
278
|
+
- Cross-stack dependencies
|
|
275
279
|
|
|
276
280
|
### Mock Data
|
|
277
281
|
|
|
@@ -279,11 +283,12 @@ Tests use mock AWS resources to avoid real AWS API calls:
|
|
|
279
283
|
|
|
280
284
|
```javascript
|
|
281
285
|
const mockAWSResources = {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
286
|
+
defaultVpcId: 'vpc-12345678',
|
|
287
|
+
defaultSecurityGroupId: 'sg-12345678',
|
|
288
|
+
privateSubnetId1: 'subnet-private-1',
|
|
289
|
+
privateSubnetId2: 'subnet-private-2',
|
|
290
|
+
defaultKmsKeyId:
|
|
291
|
+
'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012',
|
|
287
292
|
};
|
|
288
293
|
```
|
|
289
294
|
|
|
@@ -293,18 +298,18 @@ const mockAWSResources = {
|
|
|
293
298
|
|
|
294
299
|
The infrastructure requires specific IAM permissions for AWS resource discovery and deployment:
|
|
295
300
|
|
|
296
|
-
-
|
|
297
|
-
-
|
|
298
|
-
-
|
|
299
|
-
-
|
|
300
|
-
-
|
|
301
|
-
-
|
|
302
|
-
-
|
|
303
|
-
-
|
|
304
|
-
-
|
|
305
|
-
-
|
|
306
|
-
-
|
|
307
|
-
-
|
|
301
|
+
- **EC2**: Describe VPCs, subnets, security groups, route tables
|
|
302
|
+
- **KMS**: List keys, describe keys
|
|
303
|
+
- **STS**: Get caller identity
|
|
304
|
+
- **CloudFormation**: Full access for stack operations
|
|
305
|
+
- **Lambda**: Function management
|
|
306
|
+
- **API Gateway**: API management
|
|
307
|
+
- **S3**: Bucket and object operations (including tagging)
|
|
308
|
+
- **DynamoDB**: Table operations
|
|
309
|
+
- **SQS**: Queue operations
|
|
310
|
+
- **SNS**: Topic operations
|
|
311
|
+
- **CloudWatch**: Metrics and alarms
|
|
312
|
+
- **IAM**: Role and policy management
|
|
308
313
|
|
|
309
314
|
### Best Practices
|
|
310
315
|
|
|
@@ -348,6 +353,8 @@ serverless print
|
|
|
348
353
|
aws cloudformation validate-template --template-body file://template.json
|
|
349
354
|
```
|
|
350
355
|
|
|
356
|
+
- **Connectivity to external services (e.g., databases):** If your Lambda functions in a VPC cannot connect to external services, ensure that the `FriggLambdaSecurityGroup` has the correct **egress** rules to allow outbound traffic on the required ports (e.g., port 27017 for MongoDB).
|
|
357
|
+
|
|
351
358
|
#### Infrastructure Test Failures
|
|
352
359
|
|
|
353
360
|
```bash
|
|
@@ -364,19 +371,22 @@ npm run test:debug
|
|
|
364
371
|
### Performance Optimization
|
|
365
372
|
|
|
366
373
|
#### Lambda Cold Starts
|
|
367
|
-
|
|
368
|
-
-
|
|
369
|
-
-
|
|
374
|
+
|
|
375
|
+
- Use provisioned concurrency for critical functions
|
|
376
|
+
- Optimize function size and dependencies
|
|
377
|
+
- Monitor cold start metrics
|
|
370
378
|
|
|
371
379
|
#### VPC Performance
|
|
372
|
-
|
|
373
|
-
-
|
|
374
|
-
-
|
|
380
|
+
|
|
381
|
+
- Use VPC endpoints to reduce NAT Gateway costs
|
|
382
|
+
- Monitor ENI creation/deletion times
|
|
383
|
+
- Consider Lambda@Edge for global distribution
|
|
375
384
|
|
|
376
385
|
#### Cost Optimization
|
|
377
|
-
|
|
378
|
-
-
|
|
379
|
-
-
|
|
386
|
+
|
|
387
|
+
- Use S3 Intelligent Tiering
|
|
388
|
+
- Configure CloudWatch log retention
|
|
389
|
+
- Monitor and alert on unexpected usage
|
|
380
390
|
|
|
381
391
|
## Contributing
|
|
382
392
|
|
|
@@ -405,17 +415,17 @@ npm run test:debug
|
|
|
405
415
|
|
|
406
416
|
## Support
|
|
407
417
|
|
|
408
|
-
-
|
|
409
|
-
-
|
|
410
|
-
-
|
|
411
|
-
-
|
|
412
|
-
-
|
|
418
|
+
- **Documentation**: See `PHASE3-DEPLOYMENT-GUIDE.md` for detailed deployment instructions
|
|
419
|
+
- **Testing**: See `README-TESTING.md` for testing strategy
|
|
420
|
+
- **Troubleshooting**: See `AWS-DISCOVERY-TROUBLESHOOTING.md` for common issues
|
|
421
|
+
- **Issues**: Create GitHub issues for bugs and feature requests
|
|
422
|
+
- **Discussions**: Use GitHub Discussions for questions and ideas
|
|
413
423
|
|
|
414
424
|
## Related Documentation
|
|
415
425
|
|
|
416
|
-
-
|
|
417
|
-
-
|
|
418
|
-
-
|
|
419
|
-
-
|
|
420
|
-
-
|
|
421
|
-
-
|
|
426
|
+
- [Phase 3 Deployment Guide](./PHASE3-DEPLOYMENT-GUIDE.md)
|
|
427
|
+
- [Testing Strategy](./README-TESTING.md)
|
|
428
|
+
- [AWS Discovery Troubleshooting](./AWS-DISCOVERY-TROUBLESHOOTING.md)
|
|
429
|
+
- [IAM Policy Templates](./IAM-POLICY-TEMPLATES.md)
|
|
430
|
+
- [VPC Configuration](./VPC-CONFIGURATION.md)
|
|
431
|
+
- [WebSocket Configuration](./WEBSOCKET-CONFIGURATION.md)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment variable validator for Frigg applications
|
|
3
|
+
* Validates that required environment variables are present based on appDefinition
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Validate environment variables against appDefinition
|
|
8
|
+
* @param {Object} AppDefinition - Application definition with environment config
|
|
9
|
+
* @returns {Object} Validation results with valid, missing, and warnings arrays
|
|
10
|
+
*/
|
|
11
|
+
const validateEnvironmentVariables = (AppDefinition) => {
|
|
12
|
+
const results = {
|
|
13
|
+
valid: [],
|
|
14
|
+
missing: [],
|
|
15
|
+
warnings: [],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
if (!AppDefinition.environment) {
|
|
19
|
+
return results;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
console.log('🔍 Validating environment variables...');
|
|
23
|
+
|
|
24
|
+
for (const [key, value] of Object.entries(AppDefinition.environment)) {
|
|
25
|
+
if (value === true) {
|
|
26
|
+
if (process.env[key]) {
|
|
27
|
+
results.valid.push(key);
|
|
28
|
+
} else {
|
|
29
|
+
results.missing.push(key);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Special handling for certain variables
|
|
35
|
+
if (results.missing.includes('NODE_ENV')) {
|
|
36
|
+
results.warnings.push('NODE_ENV not set, defaulting to "production"');
|
|
37
|
+
// Remove from missing since it has a default
|
|
38
|
+
results.missing = results.missing.filter((v) => v !== 'NODE_ENV');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Report results
|
|
42
|
+
if (results.valid.length > 0) {
|
|
43
|
+
console.log(
|
|
44
|
+
` ✅ Valid: ${results.valid.length} environment variables found`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (results.missing.length > 0) {
|
|
49
|
+
console.log(` ⚠️ Missing: ${results.missing.join(', ')}`);
|
|
50
|
+
results.warnings.push(
|
|
51
|
+
`Missing ${results.missing.length} environment variables. These should be set in your CI/CD environment or .env file`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (results.warnings.length > 0) {
|
|
56
|
+
results.warnings.forEach((warning) => {
|
|
57
|
+
console.log(` ⚠️ ${warning}`);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return results;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Check if all required environment variables are present
|
|
66
|
+
* @param {Object} AppDefinition - Application definition
|
|
67
|
+
* @returns {boolean} True if all required variables are present
|
|
68
|
+
*/
|
|
69
|
+
const hasAllRequiredEnvVars = (AppDefinition) => {
|
|
70
|
+
const results = validateEnvironmentVariables(AppDefinition);
|
|
71
|
+
return results.missing.length === 0;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
module.exports = {
|
|
75
|
+
validateEnvironmentVariables,
|
|
76
|
+
hasAllRequiredEnvVars,
|
|
77
|
+
};
|
|
@@ -180,6 +180,114 @@ function generateIAMCloudFormation(appDefinition, options = {}) {
|
|
|
180
180
|
};
|
|
181
181
|
|
|
182
182
|
// Add Core Deployment Policy (always needed)
|
|
183
|
+
const coreActions = [
|
|
184
|
+
// CloudFormation permissions
|
|
185
|
+
'cloudformation:CreateStack',
|
|
186
|
+
'cloudformation:UpdateStack',
|
|
187
|
+
'cloudformation:DeleteStack',
|
|
188
|
+
'cloudformation:DescribeStacks',
|
|
189
|
+
'cloudformation:DescribeStackEvents',
|
|
190
|
+
'cloudformation:DescribeStackResources',
|
|
191
|
+
'cloudformation:DescribeStackResource',
|
|
192
|
+
'cloudformation:ListStackResources',
|
|
193
|
+
'cloudformation:GetTemplate',
|
|
194
|
+
'cloudformation:DescribeChangeSet',
|
|
195
|
+
'cloudformation:CreateChangeSet',
|
|
196
|
+
'cloudformation:DeleteChangeSet',
|
|
197
|
+
'cloudformation:ExecuteChangeSet',
|
|
198
|
+
'cloudformation:ValidateTemplate',
|
|
199
|
+
|
|
200
|
+
// Lambda permissions
|
|
201
|
+
'lambda:CreateFunction',
|
|
202
|
+
'lambda:UpdateFunctionCode',
|
|
203
|
+
'lambda:UpdateFunctionConfiguration',
|
|
204
|
+
'lambda:DeleteFunction',
|
|
205
|
+
'lambda:GetFunction',
|
|
206
|
+
'lambda:ListFunctions',
|
|
207
|
+
'lambda:PublishVersion',
|
|
208
|
+
'lambda:CreateAlias',
|
|
209
|
+
'lambda:UpdateAlias',
|
|
210
|
+
'lambda:DeleteAlias',
|
|
211
|
+
'lambda:GetAlias',
|
|
212
|
+
'lambda:AddPermission',
|
|
213
|
+
'lambda:RemovePermission',
|
|
214
|
+
'lambda:GetPolicy',
|
|
215
|
+
'lambda:PutProvisionedConcurrencyConfig',
|
|
216
|
+
'lambda:DeleteProvisionedConcurrencyConfig',
|
|
217
|
+
'lambda:PutConcurrency',
|
|
218
|
+
'lambda:DeleteConcurrency',
|
|
219
|
+
'lambda:TagResource',
|
|
220
|
+
'lambda:UntagResource',
|
|
221
|
+
'lambda:ListVersionsByFunction',
|
|
222
|
+
|
|
223
|
+
// IAM permissions
|
|
224
|
+
'iam:CreateRole',
|
|
225
|
+
'iam:DeleteRole',
|
|
226
|
+
'iam:GetRole',
|
|
227
|
+
'iam:PassRole',
|
|
228
|
+
'iam:PutRolePolicy',
|
|
229
|
+
'iam:DeleteRolePolicy',
|
|
230
|
+
'iam:GetRolePolicy',
|
|
231
|
+
'iam:AttachRolePolicy',
|
|
232
|
+
'iam:DetachRolePolicy',
|
|
233
|
+
'iam:TagRole',
|
|
234
|
+
'iam:UntagRole',
|
|
235
|
+
'iam:ListPolicyVersions',
|
|
236
|
+
|
|
237
|
+
// S3 permissions
|
|
238
|
+
's3:CreateBucket',
|
|
239
|
+
's3:PutObject',
|
|
240
|
+
's3:GetObject',
|
|
241
|
+
's3:DeleteObject',
|
|
242
|
+
's3:PutBucketPolicy',
|
|
243
|
+
's3:PutBucketVersioning',
|
|
244
|
+
's3:PutBucketPublicAccessBlock',
|
|
245
|
+
's3:GetBucketLocation',
|
|
246
|
+
's3:ListBucket',
|
|
247
|
+
|
|
248
|
+
// SQS permissions
|
|
249
|
+
'sqs:CreateQueue',
|
|
250
|
+
'sqs:DeleteQueue',
|
|
251
|
+
'sqs:GetQueueAttributes',
|
|
252
|
+
'sqs:SetQueueAttributes',
|
|
253
|
+
'sqs:GetQueueUrl',
|
|
254
|
+
'sqs:TagQueue',
|
|
255
|
+
'sqs:UntagQueue',
|
|
256
|
+
|
|
257
|
+
// SNS permissions
|
|
258
|
+
'sns:CreateTopic',
|
|
259
|
+
'sns:DeleteTopic',
|
|
260
|
+
'sns:GetTopicAttributes',
|
|
261
|
+
'sns:SetTopicAttributes',
|
|
262
|
+
'sns:Subscribe',
|
|
263
|
+
'sns:Unsubscribe',
|
|
264
|
+
'sns:ListSubscriptionsByTopic',
|
|
265
|
+
'sns:TagResource',
|
|
266
|
+
'sns:UntagResource',
|
|
267
|
+
|
|
268
|
+
// CloudWatch and Logs permissions
|
|
269
|
+
'cloudwatch:PutMetricAlarm',
|
|
270
|
+
'cloudwatch:DeleteAlarms',
|
|
271
|
+
'cloudwatch:DescribeAlarms',
|
|
272
|
+
'logs:CreateLogGroup',
|
|
273
|
+
'logs:CreateLogStream',
|
|
274
|
+
'logs:DeleteLogGroup',
|
|
275
|
+
'logs:DescribeLogGroups',
|
|
276
|
+
'logs:DescribeLogStreams',
|
|
277
|
+
'logs:FilterLogEvents',
|
|
278
|
+
'logs:PutLogEvents',
|
|
279
|
+
'logs:PutRetentionPolicy',
|
|
280
|
+
|
|
281
|
+
// API Gateway permissions
|
|
282
|
+
'apigateway:POST',
|
|
283
|
+
'apigateway:PUT',
|
|
284
|
+
'apigateway:DELETE',
|
|
285
|
+
'apigateway:GET',
|
|
286
|
+
'apigateway:PATCH',
|
|
287
|
+
'apigateway:TagResource',
|
|
288
|
+
'apigateway:UntagResource',
|
|
289
|
+
];
|
|
290
|
+
|
|
183
291
|
const coreStatements = [
|
|
184
292
|
{
|
|
185
293
|
Sid: 'CloudFormationFriggStacks',
|
|
@@ -374,6 +482,8 @@ function generateIAMCloudFormation(appDefinition, options = {}) {
|
|
|
374
482
|
'apigateway:DELETE',
|
|
375
483
|
'apigateway:GET',
|
|
376
484
|
'apigateway:PATCH',
|
|
485
|
+
'apigateway:TagResource',
|
|
486
|
+
'apigateway:UntagResource',
|
|
377
487
|
],
|
|
378
488
|
Resource: [
|
|
379
489
|
'arn:aws:apigateway:*::/restapis',
|
|
@@ -397,8 +507,6 @@ function generateIAMCloudFormation(appDefinition, options = {}) {
|
|
|
397
507
|
'arn:aws:apigateway:*::/apis/*',
|
|
398
508
|
'arn:aws:apigateway:*::/apis/*/stages',
|
|
399
509
|
'arn:aws:apigateway:*::/apis/*/stages/*',
|
|
400
|
-
'arn:aws:apigateway:*::/apis/*/mappings',
|
|
401
|
-
'arn:aws:apigateway:*::/apis/*/mappings/*',
|
|
402
510
|
'arn:aws:apigateway:*::/domainnames',
|
|
403
511
|
'arn:aws:apigateway:*::/domainnames/*',
|
|
404
512
|
'arn:aws:apigateway:*::/domainnames/*/apimappings',
|
|
@@ -199,13 +199,17 @@
|
|
|
199
199
|
"apigateway:PUT",
|
|
200
200
|
"apigateway:DELETE",
|
|
201
201
|
"apigateway:GET",
|
|
202
|
-
"apigateway:PATCH"
|
|
202
|
+
"apigateway:PATCH",
|
|
203
|
+
"apigateway:TagResource",
|
|
204
|
+
"apigateway:UntagResource"
|
|
203
205
|
],
|
|
204
206
|
"Resource": [
|
|
205
207
|
"arn:aws:apigateway:*::/restapis",
|
|
206
208
|
"arn:aws:apigateway:*::/restapis/*",
|
|
207
209
|
"arn:aws:apigateway:*::/apis",
|
|
208
210
|
"arn:aws:apigateway:*::/apis/*",
|
|
211
|
+
"arn:aws:apigateway:*::/apis/*/stages",
|
|
212
|
+
"arn:aws:apigateway:*::/apis/*/stages/*",
|
|
209
213
|
"arn:aws:apigateway:*::/domainnames",
|
|
210
214
|
"arn:aws:apigateway:*::/domainnames/*"
|
|
211
215
|
]
|
|
@@ -199,13 +199,17 @@
|
|
|
199
199
|
"apigateway:PUT",
|
|
200
200
|
"apigateway:DELETE",
|
|
201
201
|
"apigateway:GET",
|
|
202
|
-
"apigateway:PATCH"
|
|
202
|
+
"apigateway:PATCH",
|
|
203
|
+
"apigateway:TagResource",
|
|
204
|
+
"apigateway:UntagResource"
|
|
203
205
|
],
|
|
204
206
|
"Resource": [
|
|
205
207
|
"arn:aws:apigateway:*::/restapis",
|
|
206
208
|
"arn:aws:apigateway:*::/restapis/*",
|
|
207
209
|
"arn:aws:apigateway:*::/apis",
|
|
208
210
|
"arn:aws:apigateway:*::/apis/*",
|
|
211
|
+
"arn:aws:apigateway:*::/apis/*/stages",
|
|
212
|
+
"arn:aws:apigateway:*::/apis/*/stages/*",
|
|
209
213
|
"arn:aws:apigateway:*::/domainnames",
|
|
210
214
|
"arn:aws:apigateway:*::/domainnames/*"
|
|
211
215
|
]
|
|
@@ -16,6 +16,68 @@ const shouldRunDiscovery = (AppDefinition) => {
|
|
|
16
16
|
);
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Extract environment variables from AppDefinition
|
|
21
|
+
* @param {Object} AppDefinition - Application definition
|
|
22
|
+
* @returns {Object} Environment variables to set in serverless
|
|
23
|
+
*/
|
|
24
|
+
const getAppEnvironmentVars = (AppDefinition) => {
|
|
25
|
+
const envVars = {};
|
|
26
|
+
|
|
27
|
+
// AWS Lambda reserved environment variables that cannot be set (from official AWS docs)
|
|
28
|
+
const reservedVars = new Set([
|
|
29
|
+
'_HANDLER',
|
|
30
|
+
'_X_AMZN_TRACE_ID',
|
|
31
|
+
'AWS_DEFAULT_REGION',
|
|
32
|
+
'AWS_EXECUTION_ENV',
|
|
33
|
+
'AWS_REGION',
|
|
34
|
+
'AWS_LAMBDA_FUNCTION_NAME',
|
|
35
|
+
'AWS_LAMBDA_FUNCTION_MEMORY_SIZE',
|
|
36
|
+
'AWS_LAMBDA_FUNCTION_VERSION',
|
|
37
|
+
'AWS_LAMBDA_INITIALIZATION_TYPE',
|
|
38
|
+
'AWS_LAMBDA_LOG_GROUP_NAME',
|
|
39
|
+
'AWS_LAMBDA_LOG_STREAM_NAME',
|
|
40
|
+
'AWS_ACCESS_KEY',
|
|
41
|
+
'AWS_ACCESS_KEY_ID',
|
|
42
|
+
'AWS_SECRET_ACCESS_KEY',
|
|
43
|
+
'AWS_SESSION_TOKEN',
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
if (AppDefinition.environment) {
|
|
47
|
+
console.log('📋 Loading environment variables from appDefinition...');
|
|
48
|
+
const envKeys = [];
|
|
49
|
+
const skippedKeys = [];
|
|
50
|
+
|
|
51
|
+
for (const [key, value] of Object.entries(AppDefinition.environment)) {
|
|
52
|
+
if (value === true) {
|
|
53
|
+
if (reservedVars.has(key)) {
|
|
54
|
+
skippedKeys.push(key);
|
|
55
|
+
} else {
|
|
56
|
+
envVars[key] = `\${env:${key}, ''}`;
|
|
57
|
+
envKeys.push(key);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (envKeys.length > 0) {
|
|
63
|
+
console.log(
|
|
64
|
+
` Found ${
|
|
65
|
+
envKeys.length
|
|
66
|
+
} environment variables: ${envKeys.join(', ')}`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
if (skippedKeys.length > 0) {
|
|
70
|
+
console.log(
|
|
71
|
+
` ⚠️ Skipped ${
|
|
72
|
+
skippedKeys.length
|
|
73
|
+
} reserved AWS Lambda variables: ${skippedKeys.join(', ')}`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return envVars;
|
|
79
|
+
};
|
|
80
|
+
|
|
19
81
|
/**
|
|
20
82
|
* Find the actual path to node_modules directory
|
|
21
83
|
* Tries multiple methods to locate node_modules:
|
|
@@ -388,6 +450,13 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
388
450
|
CidrIp: '0.0.0.0/0',
|
|
389
451
|
Description: 'DNS UDP',
|
|
390
452
|
},
|
|
453
|
+
{
|
|
454
|
+
IpProtocol: 'tcp',
|
|
455
|
+
FromPort: 27017,
|
|
456
|
+
ToPort: 27017,
|
|
457
|
+
CidrIp: '0.0.0.0/0',
|
|
458
|
+
Description: 'MongoDB outbound',
|
|
459
|
+
},
|
|
391
460
|
],
|
|
392
461
|
Tags: [
|
|
393
462
|
{
|
|
@@ -555,17 +624,8 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
555
624
|
}
|
|
556
625
|
}
|
|
557
626
|
|
|
558
|
-
//
|
|
559
|
-
|
|
560
|
-
const envKeys = Object.keys(process.env || {}).sort();
|
|
561
|
-
console.log(
|
|
562
|
-
'Frigg deploy env keys (sample):',
|
|
563
|
-
envKeys.slice(0, 30),
|
|
564
|
-
`... total=${envKeys.length}`
|
|
565
|
-
);
|
|
566
|
-
} catch (e) {
|
|
567
|
-
console.log('Frigg deploy env keys: <unavailable>', e?.message);
|
|
568
|
-
}
|
|
627
|
+
// Get environment variables from appDefinition
|
|
628
|
+
const appEnvironmentVars = getAppEnvironmentVars(AppDefinition);
|
|
569
629
|
|
|
570
630
|
const definition = {
|
|
571
631
|
frameworkVersion: '>=3.17.0',
|
|
@@ -588,18 +648,8 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
588
648
|
environment: {
|
|
589
649
|
STAGE: '${opt:stage, "dev"}',
|
|
590
650
|
AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1,
|
|
591
|
-
//
|
|
592
|
-
|
|
593
|
-
...Object.fromEntries(
|
|
594
|
-
Object.entries(process.env)
|
|
595
|
-
.filter(([key]) => key.startsWith('FRIGG__'))
|
|
596
|
-
.map(([key, value]) => [
|
|
597
|
-
key.replace('FRIGG__', ''),
|
|
598
|
-
value,
|
|
599
|
-
])
|
|
600
|
-
),
|
|
601
|
-
// Also include essential non-prefixed variables
|
|
602
|
-
...(process.env.NODE_ENV && { NODE_ENV: process.env.NODE_ENV }),
|
|
651
|
+
// Add environment variables from appDefinition
|
|
652
|
+
...appEnvironmentVars,
|
|
603
653
|
// Add discovered resources to environment if available
|
|
604
654
|
...(discoveredResources.defaultVpcId && {
|
|
605
655
|
AWS_DISCOVERY_VPC_ID: discoveredResources.defaultVpcId,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/devtools",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0-next.
|
|
4
|
+
"version": "2.0.0-next.37",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-ec2": "^3.835.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.835.0",
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"@babel/eslint-parser": "^7.18.9",
|
|
10
10
|
"@babel/parser": "^7.25.3",
|
|
11
11
|
"@babel/traverse": "^7.25.3",
|
|
12
|
-
"@friggframework/schemas": "2.0.0-next.
|
|
13
|
-
"@friggframework/test": "2.0.0-next.
|
|
12
|
+
"@friggframework/schemas": "2.0.0-next.37",
|
|
13
|
+
"@friggframework/test": "2.0.0-next.37",
|
|
14
14
|
"@hapi/boom": "^10.0.1",
|
|
15
15
|
"@inquirer/prompts": "^5.3.8",
|
|
16
16
|
"axios": "^1.7.2",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"serverless-http": "^2.7.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@friggframework/eslint-config": "2.0.0-next.
|
|
36
|
-
"@friggframework/prettier-config": "2.0.0-next.
|
|
35
|
+
"@friggframework/eslint-config": "2.0.0-next.37",
|
|
36
|
+
"@friggframework/prettier-config": "2.0.0-next.37",
|
|
37
37
|
"prettier": "^2.7.1",
|
|
38
38
|
"serverless": "3.39.0",
|
|
39
39
|
"serverless-dotenv-plugin": "^6.0.0",
|
|
@@ -65,5 +65,5 @@
|
|
|
65
65
|
"publishConfig": {
|
|
66
66
|
"access": "public"
|
|
67
67
|
},
|
|
68
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "39f0a48fa6bd17c4b8c5caba58c88edc6d6fdd1f"
|
|
69
69
|
}
|