@friggframework/devtools 2.0.0--canary.398.e2147f7.0 → 2.0.0--canary.398.c9e5d61.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/build-command/index.js +25 -2
- package/frigg-cli/deploy-command/index.js +26 -2
- package/frigg-cli/generate-iam-command.js +115 -0
- package/frigg-cli/index.js +11 -1
- package/infrastructure/AWS-DISCOVERY-TROUBLESHOOTING.md +245 -0
- package/infrastructure/AWS-IAM-CREDENTIAL-NEEDS.md +23 -3
- package/infrastructure/DEPLOYMENT-INSTRUCTIONS.md +268 -0
- package/infrastructure/GENERATE-IAM-DOCS.md +253 -0
- package/infrastructure/WEBSOCKET-CONFIGURATION.md +105 -0
- package/infrastructure/aws-discovery.js +24 -3
- package/infrastructure/build-time-discovery.js +10 -1
- package/infrastructure/frigg-deployment-iam-stack.yaml +370 -0
- package/infrastructure/iam-generator.js +644 -0
- package/infrastructure/iam-generator.test.js +169 -0
- package/infrastructure/run-discovery.js +108 -0
- package/infrastructure/serverless-template.js +47 -20
- package/package.json +6 -5
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
const { generateIAMCloudFormation, getFeatureSummary } = require('./iam-generator');
|
|
2
|
+
|
|
3
|
+
describe('IAM Generator', () => {
|
|
4
|
+
describe('getFeatureSummary', () => {
|
|
5
|
+
it('should detect all features when enabled', () => {
|
|
6
|
+
const appDefinition = {
|
|
7
|
+
name: 'test-app',
|
|
8
|
+
integrations: ['Integration1', 'Integration2'],
|
|
9
|
+
vpc: { enable: true },
|
|
10
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
11
|
+
ssm: { enable: true },
|
|
12
|
+
websockets: { enable: true }
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const summary = getFeatureSummary(appDefinition);
|
|
16
|
+
|
|
17
|
+
expect(summary.appName).toBe('test-app');
|
|
18
|
+
expect(summary.integrationCount).toBe(2);
|
|
19
|
+
expect(summary.features.core).toBe(true);
|
|
20
|
+
expect(summary.features.vpc).toBe(true);
|
|
21
|
+
expect(summary.features.kms).toBe(true);
|
|
22
|
+
expect(summary.features.ssm).toBe(true);
|
|
23
|
+
expect(summary.features.websockets).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should detect minimal features when disabled', () => {
|
|
27
|
+
const appDefinition = {
|
|
28
|
+
integrations: []
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const summary = getFeatureSummary(appDefinition);
|
|
32
|
+
|
|
33
|
+
expect(summary.appName).toBe('Unnamed Frigg App');
|
|
34
|
+
expect(summary.integrationCount).toBe(0);
|
|
35
|
+
expect(summary.features.core).toBe(true);
|
|
36
|
+
expect(summary.features.vpc).toBe(false);
|
|
37
|
+
expect(summary.features.kms).toBe(false);
|
|
38
|
+
expect(summary.features.ssm).toBe(false);
|
|
39
|
+
expect(summary.features.websockets).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('generateIAMCloudFormation', () => {
|
|
44
|
+
it('should generate valid CloudFormation YAML', () => {
|
|
45
|
+
const appDefinition = {
|
|
46
|
+
name: 'test-app',
|
|
47
|
+
integrations: [],
|
|
48
|
+
vpc: { enable: false },
|
|
49
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: false },
|
|
50
|
+
ssm: { enable: false },
|
|
51
|
+
websockets: { enable: false }
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const yaml = generateIAMCloudFormation(appDefinition);
|
|
55
|
+
|
|
56
|
+
expect(yaml).toContain('AWSTemplateFormatVersion');
|
|
57
|
+
expect(yaml).toContain('FriggDeploymentUser');
|
|
58
|
+
expect(yaml).toContain('FriggCoreDeploymentPolicy');
|
|
59
|
+
expect(yaml).toContain('FriggDiscoveryPolicy');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should include VPC policy when VPC is enabled', () => {
|
|
63
|
+
const appDefinition = {
|
|
64
|
+
name: 'test-app',
|
|
65
|
+
integrations: [],
|
|
66
|
+
vpc: { enable: true }
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const yaml = generateIAMCloudFormation(appDefinition);
|
|
70
|
+
|
|
71
|
+
expect(yaml).toContain('FriggVPCPolicy');
|
|
72
|
+
expect(yaml).toContain('CreateVPCPermissions');
|
|
73
|
+
expect(yaml).toContain('EnableVPCSupport');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should include KMS policy when encryption is enabled', () => {
|
|
77
|
+
const appDefinition = {
|
|
78
|
+
name: 'test-app',
|
|
79
|
+
integrations: [],
|
|
80
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true }
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const yaml = generateIAMCloudFormation(appDefinition);
|
|
84
|
+
|
|
85
|
+
expect(yaml).toContain('FriggKMSPolicy');
|
|
86
|
+
expect(yaml).toContain('CreateKMSPermissions');
|
|
87
|
+
expect(yaml).toContain('EnableKMSSupport');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should include SSM policy when SSM is enabled', () => {
|
|
91
|
+
const appDefinition = {
|
|
92
|
+
name: 'test-app',
|
|
93
|
+
integrations: [],
|
|
94
|
+
ssm: { enable: true }
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const yaml = generateIAMCloudFormation(appDefinition);
|
|
98
|
+
|
|
99
|
+
expect(yaml).toContain('FriggSSMPolicy');
|
|
100
|
+
expect(yaml).toContain('CreateSSMPermissions');
|
|
101
|
+
expect(yaml).toContain('EnableSSMSupport');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should set correct default parameter values based on features', () => {
|
|
105
|
+
const appDefinition = {
|
|
106
|
+
name: 'test-app',
|
|
107
|
+
integrations: [],
|
|
108
|
+
vpc: { enable: true },
|
|
109
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: false },
|
|
110
|
+
ssm: { enable: true }
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const yaml = generateIAMCloudFormation(appDefinition);
|
|
114
|
+
|
|
115
|
+
// Check parameter defaults match the enabled features
|
|
116
|
+
expect(yaml).toContain('Default: true'); // VPC enabled
|
|
117
|
+
expect(yaml).toContain('Default: false'); // KMS disabled
|
|
118
|
+
// SSM should be true
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should include all core permissions', () => {
|
|
122
|
+
const appDefinition = {
|
|
123
|
+
name: 'test-app',
|
|
124
|
+
integrations: []
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const yaml = generateIAMCloudFormation(appDefinition);
|
|
128
|
+
|
|
129
|
+
// Check for core permissions
|
|
130
|
+
expect(yaml).toContain('cloudformation:CreateStack');
|
|
131
|
+
expect(yaml).toContain('cloudformation:ListStackResources');
|
|
132
|
+
expect(yaml).toContain('lambda:CreateFunction');
|
|
133
|
+
expect(yaml).toContain('iam:CreateRole');
|
|
134
|
+
expect(yaml).toContain('s3:CreateBucket');
|
|
135
|
+
expect(yaml).toContain('sqs:CreateQueue');
|
|
136
|
+
expect(yaml).toContain('sns:CreateTopic');
|
|
137
|
+
expect(yaml).toContain('logs:CreateLogGroup');
|
|
138
|
+
expect(yaml).toContain('apigateway:POST');
|
|
139
|
+
expect(yaml).toContain('lambda:ListVersionsByFunction');
|
|
140
|
+
expect(yaml).toContain('iam:ListPolicyVersions');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should include internal-error-queue pattern in SQS resources', () => {
|
|
144
|
+
const appDefinition = {
|
|
145
|
+
name: 'test-app',
|
|
146
|
+
integrations: []
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const yaml = generateIAMCloudFormation(appDefinition);
|
|
150
|
+
|
|
151
|
+
expect(yaml).toContain('internal-error-queue-*');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should generate outputs section', () => {
|
|
155
|
+
const appDefinition = {
|
|
156
|
+
name: 'test-app',
|
|
157
|
+
integrations: []
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const yaml = generateIAMCloudFormation(appDefinition);
|
|
161
|
+
|
|
162
|
+
expect(yaml).toContain('Outputs:');
|
|
163
|
+
expect(yaml).toContain('DeploymentUserArn:');
|
|
164
|
+
expect(yaml).toContain('AccessKeyId:');
|
|
165
|
+
expect(yaml).toContain('SecretAccessKeyCommand:');
|
|
166
|
+
expect(yaml).toContain('CredentialsSecretArn:');
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pre-build script to run AWS discovery and set environment variables
|
|
5
|
+
* This should be run before serverless commands that need AWS resource discovery
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { BuildTimeDiscovery } = require('./build-time-discovery');
|
|
9
|
+
const { findNearestBackendPackageJson } = require('@friggframework/core');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
async function runDiscovery() {
|
|
13
|
+
try {
|
|
14
|
+
console.log('🔍 Starting AWS resource discovery...');
|
|
15
|
+
|
|
16
|
+
// Find the backend package.json to get AppDefinition
|
|
17
|
+
const backendPath = findNearestBackendPackageJson();
|
|
18
|
+
if (!backendPath) {
|
|
19
|
+
console.log('⚠️ No backend package.json found, skipping discovery');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const backendDir = path.dirname(backendPath);
|
|
24
|
+
const backendFilePath = path.join(backendDir, 'index.js');
|
|
25
|
+
|
|
26
|
+
if (!require('fs').existsSync(backendFilePath)) {
|
|
27
|
+
console.log('⚠️ No backend/index.js found, skipping discovery');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Load the app definition
|
|
32
|
+
const backend = require(backendFilePath);
|
|
33
|
+
const appDefinition = backend.Definition;
|
|
34
|
+
|
|
35
|
+
if (!appDefinition) {
|
|
36
|
+
console.log('⚠️ No Definition found in backend/index.js, skipping discovery');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check if discovery is needed
|
|
41
|
+
const needsDiscovery = appDefinition.vpc?.enable ||
|
|
42
|
+
appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption ||
|
|
43
|
+
appDefinition.ssm?.enable;
|
|
44
|
+
|
|
45
|
+
if (!needsDiscovery) {
|
|
46
|
+
console.log('ℹ️ No AWS discovery needed based on app definition');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log('📋 App requires AWS discovery for:');
|
|
51
|
+
if (appDefinition.vpc?.enable) console.log(' ✅ VPC support');
|
|
52
|
+
if (appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption) console.log(' ✅ KMS encryption');
|
|
53
|
+
if (appDefinition.ssm?.enable) console.log(' ✅ SSM parameters');
|
|
54
|
+
|
|
55
|
+
// Run discovery
|
|
56
|
+
const discovery = new BuildTimeDiscovery();
|
|
57
|
+
const resources = await discovery.preBuildHook(appDefinition, process.env.AWS_REGION || 'us-east-1');
|
|
58
|
+
|
|
59
|
+
if (resources) {
|
|
60
|
+
console.log('✅ AWS discovery completed successfully!');
|
|
61
|
+
console.log(` VPC: ${resources.defaultVpcId}`);
|
|
62
|
+
console.log(` Subnets: ${resources.privateSubnetId1}, ${resources.privateSubnetId2}`);
|
|
63
|
+
console.log(` Public Subnet: ${resources.publicSubnetId}`);
|
|
64
|
+
console.log(` Security Group: ${resources.defaultSecurityGroupId}`);
|
|
65
|
+
console.log(` Route Table: ${resources.privateRouteTableId}`);
|
|
66
|
+
console.log(` KMS Key: ${resources.defaultKmsKeyId}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error('❌ AWS discovery failed:', error.message);
|
|
71
|
+
console.error('');
|
|
72
|
+
|
|
73
|
+
// Check if this is an AWS SDK missing error
|
|
74
|
+
if (error.message.includes('Cannot find module') && error.message.includes('@aws-sdk')) {
|
|
75
|
+
console.error('🚨 AWS SDK not installed!');
|
|
76
|
+
console.error('');
|
|
77
|
+
console.error('💡 Install AWS SDK dependencies:');
|
|
78
|
+
console.error(' npm install @aws-sdk/client-ec2 @aws-sdk/client-kms @aws-sdk/client-sts');
|
|
79
|
+
console.error('');
|
|
80
|
+
} else {
|
|
81
|
+
console.error('🚨 Discovery is required because your AppDefinition has these features enabled:');
|
|
82
|
+
if (appDefinition.vpc?.enable) console.error(' ❌ VPC support (vpc.enable: true)');
|
|
83
|
+
if (appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption) console.error(' ❌ KMS encryption (encryption.useDefaultKMSForFieldLevelEncryption: true)');
|
|
84
|
+
if (appDefinition.ssm?.enable) console.error(' ❌ SSM parameters (ssm.enable: true)');
|
|
85
|
+
console.error('');
|
|
86
|
+
console.error('💡 To fix this issue:');
|
|
87
|
+
console.error(' 1. Check AWS credentials: aws sts get-caller-identity');
|
|
88
|
+
console.error(' 2. Verify IAM permissions (see AWS-IAM-CREDENTIAL-NEEDS.md)');
|
|
89
|
+
console.error(' 3. Ensure default VPC exists: aws ec2 describe-vpcs');
|
|
90
|
+
console.error(' 4. Check AWS region: aws configure get region');
|
|
91
|
+
console.error('');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.error('🔧 Or disable features in backend/index.js:');
|
|
95
|
+
console.error(' vpc: { enable: false }');
|
|
96
|
+
console.error(' encryption: { useDefaultKMSForFieldLevelEncryption: false }');
|
|
97
|
+
console.error(' ssm: { enable: false }');
|
|
98
|
+
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Run if called directly
|
|
104
|
+
if (require.main === module) {
|
|
105
|
+
runDiscovery();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = { runDiscovery };
|
|
@@ -434,6 +434,8 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
434
434
|
* @param {Object} [AppDefinition.vpc] - VPC configuration
|
|
435
435
|
* @param {Object} [AppDefinition.encryption] - KMS encryption configuration
|
|
436
436
|
* @param {Object} [AppDefinition.ssm] - SSM parameter store configuration
|
|
437
|
+
* @param {Object} [AppDefinition.websockets] - WebSocket configuration
|
|
438
|
+
* @param {boolean} [AppDefinition.websockets.enable=false] - Enable WebSocket support for live update streaming
|
|
437
439
|
* @returns {Object} Complete serverless framework configuration
|
|
438
440
|
*/
|
|
439
441
|
const composeServerlessDefinition = (AppDefinition) => {
|
|
@@ -514,26 +516,6 @@ const composeServerlessDefinition = (AppDefinition) => {
|
|
|
514
516
|
},
|
|
515
517
|
},
|
|
516
518
|
functions: {
|
|
517
|
-
defaultWebsocket: {
|
|
518
|
-
handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
|
|
519
|
-
events: [
|
|
520
|
-
{
|
|
521
|
-
websocket: {
|
|
522
|
-
route: '$connect',
|
|
523
|
-
},
|
|
524
|
-
},
|
|
525
|
-
{
|
|
526
|
-
websocket: {
|
|
527
|
-
route: '$default',
|
|
528
|
-
},
|
|
529
|
-
},
|
|
530
|
-
{
|
|
531
|
-
websocket: {
|
|
532
|
-
route: '$disconnect',
|
|
533
|
-
},
|
|
534
|
-
},
|
|
535
|
-
],
|
|
536
|
-
},
|
|
537
519
|
auth: {
|
|
538
520
|
handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
|
|
539
521
|
events: [
|
|
@@ -923,6 +905,51 @@ const composeServerlessDefinition = (AppDefinition) => {
|
|
|
923
905
|
definition.custom[queueReference] = queueName;
|
|
924
906
|
}
|
|
925
907
|
|
|
908
|
+
// Check if AWS discovery environment variables are missing and error
|
|
909
|
+
const needsDiscovery = AppDefinition.vpc?.enable ||
|
|
910
|
+
AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption ||
|
|
911
|
+
AppDefinition.ssm?.enable;
|
|
912
|
+
|
|
913
|
+
if (needsDiscovery && !process.env.AWS_DISCOVERY_VPC_ID) {
|
|
914
|
+
console.error('❌ AWS discovery environment variables not found');
|
|
915
|
+
console.error('');
|
|
916
|
+
console.error('🚨 Your AppDefinition requires AWS discovery but variables are missing:');
|
|
917
|
+
if (AppDefinition.vpc?.enable) console.error(' ❌ VPC support (vpc.enable: true)');
|
|
918
|
+
if (AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption) console.error(' ❌ KMS encryption (encryption.useDefaultKMSForFieldLevelEncryption: true)');
|
|
919
|
+
if (AppDefinition.ssm?.enable) console.error(' ❌ SSM parameters (ssm.enable: true)');
|
|
920
|
+
console.error('');
|
|
921
|
+
console.error('💡 Run AWS discovery before building:');
|
|
922
|
+
console.error(' node node_modules/@friggframework/devtools/infrastructure/run-discovery.js');
|
|
923
|
+
console.error('');
|
|
924
|
+
console.error('🔧 Or use the build command which runs discovery automatically:');
|
|
925
|
+
console.error(' npx frigg build');
|
|
926
|
+
throw new Error('AWS discovery required but environment variables not found');
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// Add websocket function if enabled
|
|
930
|
+
if (AppDefinition.websockets?.enable === true) {
|
|
931
|
+
definition.functions.defaultWebsocket = {
|
|
932
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
|
|
933
|
+
events: [
|
|
934
|
+
{
|
|
935
|
+
websocket: {
|
|
936
|
+
route: '$connect',
|
|
937
|
+
},
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
websocket: {
|
|
941
|
+
route: '$default',
|
|
942
|
+
},
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
websocket: {
|
|
946
|
+
route: '$disconnect',
|
|
947
|
+
},
|
|
948
|
+
},
|
|
949
|
+
],
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
|
|
926
953
|
// Modify handler paths to point to the correct node_modules location
|
|
927
954
|
definition.functions = modifyHandlerPaths(definition.functions);
|
|
928
955
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/devtools",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.398.
|
|
4
|
+
"version": "2.0.0--canary.398.c9e5d61.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@babel/eslint-parser": "^7.18.9",
|
|
7
7
|
"@babel/parser": "^7.25.3",
|
|
8
8
|
"@babel/traverse": "^7.25.3",
|
|
9
|
-
"@friggframework/test": "2.0.0--canary.398.
|
|
9
|
+
"@friggframework/test": "2.0.0--canary.398.c9e5d61.0",
|
|
10
10
|
"@hapi/boom": "^10.0.1",
|
|
11
11
|
"@inquirer/prompts": "^5.3.8",
|
|
12
12
|
"axios": "^1.7.2",
|
|
@@ -23,12 +23,13 @@
|
|
|
23
23
|
"express": "^4.19.2",
|
|
24
24
|
"express-async-handler": "^1.2.0",
|
|
25
25
|
"fs-extra": "^11.2.0",
|
|
26
|
+
"js-yaml": "^4.1.0",
|
|
26
27
|
"lodash": "4.17.21",
|
|
27
28
|
"serverless-http": "^2.7.0"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
|
-
"@friggframework/eslint-config": "2.0.0--canary.398.
|
|
31
|
-
"@friggframework/prettier-config": "2.0.0--canary.398.
|
|
31
|
+
"@friggframework/eslint-config": "2.0.0--canary.398.c9e5d61.0",
|
|
32
|
+
"@friggframework/prettier-config": "2.0.0--canary.398.c9e5d61.0",
|
|
32
33
|
"prettier": "^2.7.1",
|
|
33
34
|
"serverless": "3.39.0",
|
|
34
35
|
"serverless-dotenv-plugin": "^6.0.0",
|
|
@@ -60,5 +61,5 @@
|
|
|
60
61
|
"publishConfig": {
|
|
61
62
|
"access": "public"
|
|
62
63
|
},
|
|
63
|
-
"gitHead": "
|
|
64
|
+
"gitHead": "c9e5d614b0abcc3a1066ffa87da17ada71dc8f9d"
|
|
64
65
|
}
|