@friggframework/serverless-plugin 2.0.0--canary.397.3862908.0 → 2.0.0--canary.398.e2147f7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +140 -0
- package/index.test.js +494 -0
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
const { spawn } = require("child_process");
|
|
2
|
+
const { BuildTimeDiscovery } = require("@friggframework/devtools/infrastructure/build-time-discovery");
|
|
2
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Frigg Serverless Plugin - Handles AWS discovery and local development setup
|
|
6
|
+
*/
|
|
3
7
|
class FriggServerlessPlugin {
|
|
8
|
+
/**
|
|
9
|
+
* Creates an instance of FriggServerlessPlugin
|
|
10
|
+
* @param {Object} serverless - Serverless framework instance
|
|
11
|
+
* @param {Object} options - Plugin options
|
|
12
|
+
*/
|
|
4
13
|
constructor(serverless, options) {
|
|
5
14
|
this.serverless = serverless;
|
|
6
15
|
this.options = options;
|
|
7
16
|
this.provider = serverless.getProvider("aws");
|
|
8
17
|
this.hooks = {
|
|
9
18
|
initialize: () => this.init(),
|
|
19
|
+
"before:package:initialize": () => this.beforePackageInitialize(),
|
|
10
20
|
"after:package:package": () => this.afterPackage(),
|
|
11
21
|
"before:deploy:deploy": () => this.beforeDeploy(),
|
|
12
22
|
};
|
|
13
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Asynchronous initialization for offline mode
|
|
26
|
+
* Creates SQS queues in localstack for local development
|
|
27
|
+
* @returns {Promise<void>}
|
|
28
|
+
*/
|
|
14
29
|
async asyncInit() {
|
|
15
30
|
this.serverless.cli.log("Initializing Frigg Serverless Plugin...");
|
|
16
31
|
console.log("Hello from Frigg Serverless Plugin!");
|
|
@@ -83,7 +98,129 @@ class FriggServerlessPlugin {
|
|
|
83
98
|
console.log("Running in online mode, doing nothing");
|
|
84
99
|
}
|
|
85
100
|
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Hook that runs before serverless package initialization
|
|
104
|
+
* Performs AWS resource discovery if needed
|
|
105
|
+
* @returns {Promise<void>}
|
|
106
|
+
*/
|
|
107
|
+
async beforePackageInitialize() {
|
|
108
|
+
this.serverless.cli.log("Running Frigg AWS discovery...");
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
// Check if we need AWS discovery based on the service configuration
|
|
112
|
+
const service = this.serverless.service;
|
|
113
|
+
const needsDiscovery = this.checkIfDiscoveryNeeded(service);
|
|
114
|
+
|
|
115
|
+
if (!needsDiscovery) {
|
|
116
|
+
this.serverless.cli.log("No AWS discovery needed for this configuration");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Get region from serverless configuration
|
|
121
|
+
const region = service.provider?.region || process.env.AWS_REGION || 'us-east-1';
|
|
122
|
+
|
|
123
|
+
// Create mock app definition from serverless service
|
|
124
|
+
const appDefinition = this.createAppDefinitionFromService(service);
|
|
125
|
+
|
|
126
|
+
// Run AWS discovery
|
|
127
|
+
const discovery = new BuildTimeDiscovery(region);
|
|
128
|
+
const resources = await discovery.preBuildHook(appDefinition, region);
|
|
129
|
+
|
|
130
|
+
if (resources) {
|
|
131
|
+
this.serverless.cli.log("AWS discovery completed successfully");
|
|
132
|
+
// Environment variables are already set by preBuildHook
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
} catch (error) {
|
|
136
|
+
this.serverless.cli.log("AWS discovery failed, continuing with deployment...");
|
|
137
|
+
console.warn("AWS Discovery Error:", error.message);
|
|
138
|
+
|
|
139
|
+
// Set fallback values to prevent deployment failures
|
|
140
|
+
const fallbackEnvVars = {
|
|
141
|
+
AWS_DISCOVERY_VPC_ID: 'vpc-fallback',
|
|
142
|
+
AWS_DISCOVERY_SECURITY_GROUP_ID: 'sg-fallback',
|
|
143
|
+
AWS_DISCOVERY_SUBNET_ID_1: 'subnet-fallback-1',
|
|
144
|
+
AWS_DISCOVERY_SUBNET_ID_2: 'subnet-fallback-2',
|
|
145
|
+
AWS_DISCOVERY_ROUTE_TABLE_ID: 'rtb-fallback',
|
|
146
|
+
AWS_DISCOVERY_KMS_KEY_ID: 'arn:aws:kms:*:*:key/*'
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
Object.assign(process.env, fallbackEnvVars);
|
|
150
|
+
this.serverless.cli.log("Using fallback values for AWS resources");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Check if AWS discovery is needed based on service configuration
|
|
156
|
+
* @param {Object} service - Serverless service configuration
|
|
157
|
+
* @returns {boolean} True if discovery is needed, false otherwise
|
|
158
|
+
*/
|
|
159
|
+
checkIfDiscoveryNeeded(service) {
|
|
160
|
+
// Check if VPC configuration is present
|
|
161
|
+
if (service.provider?.vpc) {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Check if KMS grants plugin is present
|
|
166
|
+
if (service.plugins?.includes('serverless-kms-grants')) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check if lambda layers are configured (SSM)
|
|
171
|
+
if (service.provider?.layers && service.provider.layers.length > 0) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Check for AWS discovery environment variable references
|
|
176
|
+
const serviceString = JSON.stringify(service);
|
|
177
|
+
if (serviceString.includes('AWS_DISCOVERY_')) {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Create a mock app definition from serverless service configuration
|
|
186
|
+
* @param {Object} service - Serverless service configuration
|
|
187
|
+
* @returns {Object} App definition object with vpc, encryption, and ssm configuration
|
|
188
|
+
*/
|
|
189
|
+
createAppDefinitionFromService(service) {
|
|
190
|
+
// Create a mock app definition from serverless service configuration
|
|
191
|
+
const appDefinition = {
|
|
192
|
+
integrations: [],
|
|
193
|
+
vpc: {},
|
|
194
|
+
encryption: {},
|
|
195
|
+
ssm: {}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// Check for VPC configuration
|
|
199
|
+
if (service.provider?.vpc) {
|
|
200
|
+
appDefinition.vpc.enable = true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Check for KMS configuration
|
|
204
|
+
if (service.plugins?.includes('serverless-kms-grants')) {
|
|
205
|
+
appDefinition.encryption.useDefaultKMSForFieldLevelEncryption = true;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Check for SSM configuration (lambda layers)
|
|
209
|
+
if (service.provider?.layers && service.provider.layers.some(layer =>
|
|
210
|
+
typeof layer === 'string' && layer.includes('AWS-Parameters-and-Secrets-Lambda-Extension'))) {
|
|
211
|
+
appDefinition.ssm.enable = true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return appDefinition;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Initialization hook (currently empty)
|
|
219
|
+
*/
|
|
86
220
|
init() {}
|
|
221
|
+
/**
|
|
222
|
+
* Hook that runs after serverless package
|
|
223
|
+
*/
|
|
87
224
|
afterPackage() {
|
|
88
225
|
console.log("After package hook called");
|
|
89
226
|
// // const queues = Object.keys(infrastructure.custom)
|
|
@@ -123,6 +260,9 @@ class FriggServerlessPlugin {
|
|
|
123
260
|
// // });
|
|
124
261
|
// });
|
|
125
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* Hook that runs before serverless deploy
|
|
265
|
+
*/
|
|
126
266
|
beforeDeploy() {
|
|
127
267
|
console.log("Before deploy hook called");
|
|
128
268
|
}
|
package/index.test.js
ADDED
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
const FriggServerlessPlugin = require('./index');
|
|
2
|
+
const { BuildTimeDiscovery } = require('@friggframework/devtools/infrastructure/build-time-discovery');
|
|
3
|
+
|
|
4
|
+
// Mock dependencies
|
|
5
|
+
jest.mock('@friggframework/devtools/infrastructure/build-time-discovery');
|
|
6
|
+
jest.mock('aws-sdk');
|
|
7
|
+
|
|
8
|
+
describe('FriggServerlessPlugin', () => {
|
|
9
|
+
let plugin;
|
|
10
|
+
let mockServerless;
|
|
11
|
+
let mockOptions;
|
|
12
|
+
let mockBuildTimeDiscovery;
|
|
13
|
+
const originalEnv = process.env;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
// Mock BuildTimeDiscovery
|
|
17
|
+
mockBuildTimeDiscovery = {
|
|
18
|
+
preBuildHook: jest.fn()
|
|
19
|
+
};
|
|
20
|
+
BuildTimeDiscovery.mockImplementation(() => mockBuildTimeDiscovery);
|
|
21
|
+
|
|
22
|
+
// Mock serverless object
|
|
23
|
+
mockServerless = {
|
|
24
|
+
cli: {
|
|
25
|
+
log: jest.fn()
|
|
26
|
+
},
|
|
27
|
+
service: {
|
|
28
|
+
provider: {
|
|
29
|
+
name: 'aws',
|
|
30
|
+
region: 'us-east-1'
|
|
31
|
+
},
|
|
32
|
+
plugins: [],
|
|
33
|
+
custom: {},
|
|
34
|
+
functions: {}
|
|
35
|
+
},
|
|
36
|
+
processedInput: {
|
|
37
|
+
commands: []
|
|
38
|
+
},
|
|
39
|
+
getProvider: jest.fn(() => ({})),
|
|
40
|
+
extendConfiguration: jest.fn()
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
mockOptions = {
|
|
44
|
+
stage: 'test'
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
plugin = new FriggServerlessPlugin(mockServerless, mockOptions);
|
|
48
|
+
|
|
49
|
+
// Reset environment
|
|
50
|
+
process.env = { ...originalEnv };
|
|
51
|
+
|
|
52
|
+
jest.clearAllMocks();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
process.env = originalEnv;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('constructor', () => {
|
|
60
|
+
it('should initialize plugin with correct hooks', () => {
|
|
61
|
+
expect(plugin.serverless).toBe(mockServerless);
|
|
62
|
+
expect(plugin.options).toBe(mockOptions);
|
|
63
|
+
expect(plugin.hooks).toEqual({
|
|
64
|
+
initialize: expect.any(Function),
|
|
65
|
+
'before:package:initialize': expect.any(Function),
|
|
66
|
+
'after:package:package': expect.any(Function),
|
|
67
|
+
'before:deploy:deploy': expect.any(Function)
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should get AWS provider from serverless', () => {
|
|
72
|
+
expect(mockServerless.getProvider).toHaveBeenCalledWith('aws');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('beforePackageInitialize', () => {
|
|
77
|
+
it('should run AWS discovery when VPC is configured', async () => {
|
|
78
|
+
mockServerless.service.provider.vpc = '${self:custom.vpc.${self:provider.stage}}';
|
|
79
|
+
const mockResources = {
|
|
80
|
+
defaultVpcId: 'vpc-12345678',
|
|
81
|
+
defaultKmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678'
|
|
82
|
+
};
|
|
83
|
+
mockBuildTimeDiscovery.preBuildHook.mockResolvedValue(mockResources);
|
|
84
|
+
|
|
85
|
+
await plugin.beforePackageInitialize();
|
|
86
|
+
|
|
87
|
+
expect(mockBuildTimeDiscovery.preBuildHook).toHaveBeenCalledWith(
|
|
88
|
+
expect.objectContaining({
|
|
89
|
+
vpc: { enable: true }
|
|
90
|
+
}),
|
|
91
|
+
'us-east-1'
|
|
92
|
+
);
|
|
93
|
+
expect(mockServerless.cli.log).toHaveBeenCalledWith('AWS discovery completed successfully');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should run AWS discovery when KMS grants plugin is present', async () => {
|
|
97
|
+
mockServerless.service.plugins = ['serverless-kms-grants'];
|
|
98
|
+
const mockResources = {
|
|
99
|
+
defaultKmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678'
|
|
100
|
+
};
|
|
101
|
+
mockBuildTimeDiscovery.preBuildHook.mockResolvedValue(mockResources);
|
|
102
|
+
|
|
103
|
+
await plugin.beforePackageInitialize();
|
|
104
|
+
|
|
105
|
+
expect(mockBuildTimeDiscovery.preBuildHook).toHaveBeenCalledWith(
|
|
106
|
+
expect.objectContaining({
|
|
107
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true }
|
|
108
|
+
}),
|
|
109
|
+
'us-east-1'
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should run AWS discovery when lambda layers are configured', async () => {
|
|
114
|
+
mockServerless.service.provider.layers = [
|
|
115
|
+
'arn:aws:lambda:us-east-1:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11'
|
|
116
|
+
];
|
|
117
|
+
const mockResources = {
|
|
118
|
+
defaultVpcId: 'vpc-12345678'
|
|
119
|
+
};
|
|
120
|
+
mockBuildTimeDiscovery.preBuildHook.mockResolvedValue(mockResources);
|
|
121
|
+
|
|
122
|
+
await plugin.beforePackageInitialize();
|
|
123
|
+
|
|
124
|
+
expect(mockBuildTimeDiscovery.preBuildHook).toHaveBeenCalledWith(
|
|
125
|
+
expect.objectContaining({
|
|
126
|
+
ssm: { enable: true }
|
|
127
|
+
}),
|
|
128
|
+
'us-east-1'
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should run AWS discovery when AWS_DISCOVERY_ environment variables are referenced', async () => {
|
|
133
|
+
mockServerless.service.custom.vpc = {
|
|
134
|
+
securityGroupIds: ['${env:AWS_DISCOVERY_SECURITY_GROUP_ID}']
|
|
135
|
+
};
|
|
136
|
+
const mockResources = {
|
|
137
|
+
defaultVpcId: 'vpc-12345678'
|
|
138
|
+
};
|
|
139
|
+
mockBuildTimeDiscovery.preBuildHook.mockResolvedValue(mockResources);
|
|
140
|
+
|
|
141
|
+
await plugin.beforePackageInitialize();
|
|
142
|
+
|
|
143
|
+
expect(mockBuildTimeDiscovery.preBuildHook).toHaveBeenCalled();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should skip discovery when no AWS discovery features are needed', async () => {
|
|
147
|
+
await plugin.beforePackageInitialize();
|
|
148
|
+
|
|
149
|
+
expect(mockBuildTimeDiscovery.preBuildHook).not.toHaveBeenCalled();
|
|
150
|
+
expect(mockServerless.cli.log).toHaveBeenCalledWith('No AWS discovery needed for this configuration');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should use custom region from serverless configuration', async () => {
|
|
154
|
+
mockServerless.service.provider.region = 'eu-west-1';
|
|
155
|
+
mockServerless.service.provider.vpc = '${self:custom.vpc}';
|
|
156
|
+
mockBuildTimeDiscovery.preBuildHook.mockResolvedValue({});
|
|
157
|
+
|
|
158
|
+
await plugin.beforePackageInitialize();
|
|
159
|
+
|
|
160
|
+
expect(mockBuildTimeDiscovery.preBuildHook).toHaveBeenCalledWith(
|
|
161
|
+
expect.any(Object),
|
|
162
|
+
'eu-west-1'
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should use AWS_REGION environment variable when region not in config', async () => {
|
|
167
|
+
process.env.AWS_REGION = 'ap-southeast-1';
|
|
168
|
+
delete mockServerless.service.provider.region;
|
|
169
|
+
mockServerless.service.provider.vpc = '${self:custom.vpc}';
|
|
170
|
+
mockBuildTimeDiscovery.preBuildHook.mockResolvedValue({});
|
|
171
|
+
|
|
172
|
+
await plugin.beforePackageInitialize();
|
|
173
|
+
|
|
174
|
+
expect(mockBuildTimeDiscovery.preBuildHook).toHaveBeenCalledWith(
|
|
175
|
+
expect.any(Object),
|
|
176
|
+
'ap-southeast-1'
|
|
177
|
+
);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should fall back to us-east-1 when no region specified', async () => {
|
|
181
|
+
delete mockServerless.service.provider.region;
|
|
182
|
+
delete process.env.AWS_REGION;
|
|
183
|
+
mockServerless.service.provider.vpc = '${self:custom.vpc}';
|
|
184
|
+
mockBuildTimeDiscovery.preBuildHook.mockResolvedValue({});
|
|
185
|
+
|
|
186
|
+
await plugin.beforePackageInitialize();
|
|
187
|
+
|
|
188
|
+
expect(mockBuildTimeDiscovery.preBuildHook).toHaveBeenCalledWith(
|
|
189
|
+
expect.any(Object),
|
|
190
|
+
'us-east-1'
|
|
191
|
+
);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should handle discovery failure gracefully with fallback values', async () => {
|
|
195
|
+
mockServerless.service.provider.vpc = '${self:custom.vpc}';
|
|
196
|
+
const error = new Error('AWS discovery failed');
|
|
197
|
+
mockBuildTimeDiscovery.preBuildHook.mockRejectedValue(error);
|
|
198
|
+
|
|
199
|
+
await plugin.beforePackageInitialize();
|
|
200
|
+
|
|
201
|
+
expect(mockServerless.cli.log).toHaveBeenCalledWith('AWS discovery failed, continuing with deployment...');
|
|
202
|
+
expect(mockServerless.cli.log).toHaveBeenCalledWith('Using fallback values for AWS resources');
|
|
203
|
+
|
|
204
|
+
// Check that fallback environment variables are set
|
|
205
|
+
expect(process.env.AWS_DISCOVERY_VPC_ID).toBe('vpc-fallback');
|
|
206
|
+
expect(process.env.AWS_DISCOVERY_SECURITY_GROUP_ID).toBe('sg-fallback');
|
|
207
|
+
expect(process.env.AWS_DISCOVERY_SUBNET_ID_1).toBe('subnet-fallback-1');
|
|
208
|
+
expect(process.env.AWS_DISCOVERY_SUBNET_ID_2).toBe('subnet-fallback-2');
|
|
209
|
+
expect(process.env.AWS_DISCOVERY_ROUTE_TABLE_ID).toBe('rtb-fallback');
|
|
210
|
+
expect(process.env.AWS_DISCOVERY_KMS_KEY_ID).toBe('arn:aws:kms:*:*:key/*');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should not set environment variables when discovery returns null', async () => {
|
|
214
|
+
mockServerless.service.provider.vpc = '${self:custom.vpc}';
|
|
215
|
+
mockBuildTimeDiscovery.preBuildHook.mockResolvedValue(null);
|
|
216
|
+
|
|
217
|
+
await plugin.beforePackageInitialize();
|
|
218
|
+
|
|
219
|
+
expect(process.env.AWS_DISCOVERY_VPC_ID).toBeUndefined();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('checkIfDiscoveryNeeded', () => {
|
|
224
|
+
it('should return true when VPC is configured', () => {
|
|
225
|
+
const service = {
|
|
226
|
+
provider: {
|
|
227
|
+
vpc: '${self:custom.vpc}'
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const result = plugin.checkIfDiscoveryNeeded(service);
|
|
232
|
+
|
|
233
|
+
expect(result).toBe(true);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should return true when serverless-kms-grants plugin is present', () => {
|
|
237
|
+
const service = {
|
|
238
|
+
provider: {},
|
|
239
|
+
plugins: ['serverless-kms-grants']
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const result = plugin.checkIfDiscoveryNeeded(service);
|
|
243
|
+
|
|
244
|
+
expect(result).toBe(true);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should return true when lambda layers are configured', () => {
|
|
248
|
+
const service = {
|
|
249
|
+
provider: {
|
|
250
|
+
layers: ['arn:aws:lambda:us-east-1:123:layer:test:1']
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const result = plugin.checkIfDiscoveryNeeded(service);
|
|
255
|
+
|
|
256
|
+
expect(result).toBe(true);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should return true when AWS_DISCOVERY_ environment variables are referenced', () => {
|
|
260
|
+
const service = {
|
|
261
|
+
provider: {},
|
|
262
|
+
custom: {
|
|
263
|
+
vpc: {
|
|
264
|
+
securityGroupIds: ['${env:AWS_DISCOVERY_SECURITY_GROUP_ID}']
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const result = plugin.checkIfDiscoveryNeeded(service);
|
|
270
|
+
|
|
271
|
+
expect(result).toBe(true);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('should return false when no discovery features are present', () => {
|
|
275
|
+
const service = {
|
|
276
|
+
provider: {},
|
|
277
|
+
plugins: ['serverless-offline']
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const result = plugin.checkIfDiscoveryNeeded(service);
|
|
281
|
+
|
|
282
|
+
expect(result).toBe(false);
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
describe('createAppDefinitionFromService', () => {
|
|
287
|
+
it('should create app definition with VPC enabled when VPC is configured', () => {
|
|
288
|
+
const service = {
|
|
289
|
+
provider: {
|
|
290
|
+
vpc: '${self:custom.vpc}'
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const result = plugin.createAppDefinitionFromService(service);
|
|
295
|
+
|
|
296
|
+
expect(result.vpc.enable).toBe(true);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('should create app definition with encryption enabled when KMS grants plugin is present', () => {
|
|
300
|
+
const service = {
|
|
301
|
+
plugins: ['serverless-kms-grants']
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const result = plugin.createAppDefinitionFromService(service);
|
|
305
|
+
|
|
306
|
+
expect(result.encryption.useDefaultKMSForFieldLevelEncryption).toBe(true);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should create app definition with SSM enabled when lambda layers include AWS extension', () => {
|
|
310
|
+
const service = {
|
|
311
|
+
provider: {
|
|
312
|
+
layers: [
|
|
313
|
+
'arn:aws:lambda:us-east-1:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11',
|
|
314
|
+
'arn:aws:lambda:us-east-1:123:layer:other:1'
|
|
315
|
+
]
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const result = plugin.createAppDefinitionFromService(service);
|
|
320
|
+
|
|
321
|
+
expect(result.ssm.enable).toBe(true);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('should create app definition with all features enabled', () => {
|
|
325
|
+
const service = {
|
|
326
|
+
provider: {
|
|
327
|
+
vpc: '${self:custom.vpc}',
|
|
328
|
+
layers: ['arn:aws:lambda:us-east-1:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11']
|
|
329
|
+
},
|
|
330
|
+
plugins: ['serverless-kms-grants']
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const result = plugin.createAppDefinitionFromService(service);
|
|
334
|
+
|
|
335
|
+
expect(result.vpc.enable).toBe(true);
|
|
336
|
+
expect(result.encryption.useDefaultKMSForFieldLevelEncryption).toBe(true);
|
|
337
|
+
expect(result.ssm.enable).toBe(true);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('should create minimal app definition when no features are configured', () => {
|
|
341
|
+
const service = {
|
|
342
|
+
provider: {}
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const result = plugin.createAppDefinitionFromService(service);
|
|
346
|
+
|
|
347
|
+
expect(result).toEqual({
|
|
348
|
+
integrations: [],
|
|
349
|
+
vpc: {},
|
|
350
|
+
encryption: {},
|
|
351
|
+
ssm: {}
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
describe('asyncInit (offline mode)', () => {
|
|
357
|
+
beforeEach(() => {
|
|
358
|
+
// Mock AWS SDK
|
|
359
|
+
const mockSQS = {
|
|
360
|
+
createQueue: jest.fn()
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
jest.doMock('aws-sdk', () => ({
|
|
364
|
+
SQS: jest.fn(() => mockSQS),
|
|
365
|
+
config: {
|
|
366
|
+
update: jest.fn()
|
|
367
|
+
}
|
|
368
|
+
}));
|
|
369
|
+
|
|
370
|
+
mockServerless.service.custom = {
|
|
371
|
+
TestQueue: 'test-queue-name',
|
|
372
|
+
AnotherQueue: 'another-queue-name'
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
mockServerless.service.provider.environment = {
|
|
376
|
+
TEST_QUEUE_URL: { Ref: 'TestQueue' },
|
|
377
|
+
ANOTHER_QUEUE_URL: { Ref: 'AnotherQueue' },
|
|
378
|
+
NORMAL_VAR: 'normal-value'
|
|
379
|
+
};
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('should create queues in offline mode', async () => {
|
|
383
|
+
mockServerless.processedInput.commands = ['offline'];
|
|
384
|
+
|
|
385
|
+
const AWS = require('aws-sdk');
|
|
386
|
+
const mockSQS = new AWS.SQS();
|
|
387
|
+
mockSQS.createQueue.mockImplementation((params, callback) => {
|
|
388
|
+
callback(null, { QueueUrl: `http://localhost:4566/000000000000/${params.QueueName}` });
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
await plugin.asyncInit();
|
|
392
|
+
|
|
393
|
+
expect(AWS.config.update).toHaveBeenCalledWith({
|
|
394
|
+
region: 'us-east-1',
|
|
395
|
+
endpoint: 'localhost:4566'
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
expect(mockSQS.createQueue).toHaveBeenCalledWith(
|
|
399
|
+
{ QueueName: 'test-queue-name' },
|
|
400
|
+
expect.any(Function)
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
expect(mockSQS.createQueue).toHaveBeenCalledWith(
|
|
404
|
+
{ QueueName: 'another-queue-name' },
|
|
405
|
+
expect.any(Function)
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
expect(mockServerless.extendConfiguration).toHaveBeenCalled();
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it('should skip queue creation in online mode', async () => {
|
|
412
|
+
mockServerless.processedInput.commands = ['deploy'];
|
|
413
|
+
|
|
414
|
+
await plugin.asyncInit();
|
|
415
|
+
|
|
416
|
+
const AWS = require('aws-sdk');
|
|
417
|
+
expect(AWS.config.update).not.toHaveBeenCalled();
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it('should handle queue creation errors', async () => {
|
|
421
|
+
mockServerless.processedInput.commands = ['offline'];
|
|
422
|
+
|
|
423
|
+
const AWS = require('aws-sdk');
|
|
424
|
+
const mockSQS = new AWS.SQS();
|
|
425
|
+
mockSQS.createQueue.mockImplementation((params, callback) => {
|
|
426
|
+
callback(new Error('Queue creation failed'));
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
await expect(plugin.asyncInit()).rejects.toThrow('Queue creation failed');
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
describe('hook methods', () => {
|
|
434
|
+
it('should have init method', () => {
|
|
435
|
+
expect(plugin.init).toBeDefined();
|
|
436
|
+
expect(typeof plugin.init).toBe('function');
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('should have afterPackage method', () => {
|
|
440
|
+
expect(plugin.afterPackage).toBeDefined();
|
|
441
|
+
expect(typeof plugin.afterPackage).toBe('function');
|
|
442
|
+
|
|
443
|
+
// Test that it logs correctly
|
|
444
|
+
const consoleSpy = jest.spyOn(console, 'log');
|
|
445
|
+
plugin.afterPackage();
|
|
446
|
+
expect(consoleSpy).toHaveBeenCalledWith('After package hook called');
|
|
447
|
+
consoleSpy.mockRestore();
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it('should have beforeDeploy method', () => {
|
|
451
|
+
expect(plugin.beforeDeploy).toBeDefined();
|
|
452
|
+
expect(typeof plugin.beforeDeploy).toBe('function');
|
|
453
|
+
|
|
454
|
+
// Test that it logs correctly
|
|
455
|
+
const consoleSpy = jest.spyOn(console, 'log');
|
|
456
|
+
plugin.beforeDeploy();
|
|
457
|
+
expect(consoleSpy).toHaveBeenCalledWith('Before deploy hook called');
|
|
458
|
+
consoleSpy.mockRestore();
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
describe('integration with BuildTimeDiscovery', () => {
|
|
463
|
+
it('should pass correct region to BuildTimeDiscovery constructor', async () => {
|
|
464
|
+
mockServerless.service.provider.region = 'eu-central-1';
|
|
465
|
+
mockServerless.service.provider.vpc = '${self:custom.vpc}';
|
|
466
|
+
mockBuildTimeDiscovery.preBuildHook.mockResolvedValue({});
|
|
467
|
+
|
|
468
|
+
await plugin.beforePackageInitialize();
|
|
469
|
+
|
|
470
|
+
expect(BuildTimeDiscovery).toHaveBeenCalledWith('eu-central-1');
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it('should pass correct app definition to preBuildHook', async () => {
|
|
474
|
+
mockServerless.service.provider.vpc = '${self:custom.vpc}';
|
|
475
|
+
mockServerless.service.plugins = ['serverless-kms-grants'];
|
|
476
|
+
mockServerless.service.provider.layers = [
|
|
477
|
+
'arn:aws:lambda:us-east-1:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11'
|
|
478
|
+
];
|
|
479
|
+
mockBuildTimeDiscovery.preBuildHook.mockResolvedValue({});
|
|
480
|
+
|
|
481
|
+
await plugin.beforePackageInitialize();
|
|
482
|
+
|
|
483
|
+
expect(mockBuildTimeDiscovery.preBuildHook).toHaveBeenCalledWith(
|
|
484
|
+
{
|
|
485
|
+
integrations: [],
|
|
486
|
+
vpc: { enable: true },
|
|
487
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
488
|
+
ssm: { enable: true }
|
|
489
|
+
},
|
|
490
|
+
'us-east-1'
|
|
491
|
+
);
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/serverless-plugin",
|
|
3
|
-
"version": "2.0.0--canary.
|
|
3
|
+
"version": "2.0.0--canary.398.e2147f7.0",
|
|
4
4
|
"description": "Plugin to help dynamically load frigg resources",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -11,5 +11,5 @@
|
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
14
|
-
"gitHead": "
|
|
14
|
+
"gitHead": "e2147f7f03122a5daa917429ba610c1a7c6ef819"
|
|
15
15
|
}
|