UNPKG

9.5 kBJavaScriptView Raw
1const jsYml = require('js-yaml');
2const fs = require('fs');
3const OptionsResolver = require('./options-resolver');
4const AddCorsMethods = require('./pipelines/add-cors-methods')
5const AddCorsParams = require('./pipelines/add-cors-params')
6const AddCorsHeader = require('./pipelines/add-cors-header')
7const WriteOutputFile = require('./pipelines/write-file-output')
8const AddIntegrations = require('./pipelines/add-integration')
9const AddCorsIntegration = require('./pipelines/add-cors-integrations')
10const AddCorsResponseParameters = require('./pipelines/add-cors-response-parameters')
11const AddAutoMockIntegration = require('./pipelines/add-automock-integrations')
12const ConfigureAutowire = require('./pipelines/create-proxy')
13const AddValidation = require('./pipelines/add-validation')
14const CreateIntegrationFile = require('./pipelines/create-integration-file')
15const Pipeline = require('./pipeline')
16
17function toArray(x) {
18 return Array.isArray(x) || x == null ? x : [x];
19}
20
21class MergeIntegrationPlugin {
22 constructor(serverless, options) {
23 this.serverless = serverless;
24 this.options = options;
25 this.commands = {
26 integration: {
27 usage: 'AWS Gateway Integration management for Serverless',
28 lifecycleEvents: ["help"],
29 commands: {
30 merge: {
31 usage: 'Merges swagger api definition with integration in yml files',
32 lifecycleEvents: [
33 'process'
34 ]
35 },
36 create: {
37 usage: 'Creates the required x-amazon-apigateway-integration blocks',
38 lifecycleEvents: [
39 'files'
40 ],
41 options: {
42 output: {
43 usage:
44 'Specify the output directory '
45 + '(e.g. "--output \'integration\'" or "-o \'integration\'")',
46 required: true,
47 shortcut: 'o',
48 type: 'string'
49 },
50 type: {
51 usage:
52 'Specify the type of integration '
53 + '(e.g. "--type \'http_proxy\'" or "-t \'http_proxy\'")',
54 required: true,
55 shortcut: 't',
56 type: 'string'
57 }
58 },
59 }
60 },
61 }
62 };
63
64 this.hooks = {
65 'integration:help': this.generateHelp.bind(this),
66 'integration:merge:process': this.processMergeCommand.bind(this),
67 'integration:create:files': this.createFilesCommand.bind(this),
68 'before:aws:package:finalize:mergeCustomProviderResources': this.processPackage.bind(this)
69 };
70
71 // relevant since sls v1.78.0
72 if (this.serverless.configSchemaHandler) {
73 const proxyNamePattern = '^[a-zA-Z0-9-_]+$'
74 const openApiIntegrationSchema = {
75 type: 'object',
76 properties: {
77 package: {type: 'string'},
78 inputFile: {type: 'string'},
79 inputDirectory: {type: 'string'},
80 mapping: {type: 'array'},
81 outputFile: {type: 'string'},
82 outputDirectory: {type: 'string'},
83 cors: {type: 'string'},
84 autoMock: {type: 'string'},
85 validation: {type: 'string'},
86 proxyManager: {
87 type: 'object',
88 properties: {
89 [proxyNamePattern] : {
90 type: 'object',
91 properties: {
92 'baseUrl': {type: 'string'},
93 'ignore': {type: 'string'},
94 'type': {type: 'string'},
95 }
96 }
97 }
98 },
99 apiResourceName: {type: 'string'},
100 },
101 required: ['inputFile'],
102 };
103 this.serverless.configSchemaHandler.defineTopLevelProperty(
104 'openApiIntegration',
105 {
106 oneOf: [
107 openApiIntegrationSchema,
108 {
109 type: 'array',
110 items: openApiIntegrationSchema
111 }
112 ]
113 },
114 );
115 }
116 }
117
118 generateHelp(command = "integration") {
119 this.serverless.cli.generateCommandsHelp([command]);
120 }
121
122 resolve() {
123 let configurations = toArray(this.serverless.configurationInput.openApiIntegration);
124
125 if (!configurations || !configurations.length) {
126 this.serverless.cli.log('Openapi Integration: missing configuration');
127 }
128
129 return configurations.map(configuration => {
130 if (Object.keys(configuration).length === 0) {
131 this.serverless.cli.log('Openapi Integration: missing configuration');
132 }
133
134 let optionsResolver = new OptionsResolver(configuration);
135 let configuredOptions = optionsResolver.resolve(this.options.stage);
136
137 if (this.options.output) {
138 configuredOptions.outputDirectory = this.options.output;
139 }
140
141 if (this.options.type) {
142 configuredOptions.type = this.options.type;
143 }
144 return configuredOptions;
145 });
146 }
147
148 process(options) {
149 let content = jsYml.load(fs.readFileSync(options.inputFullPath, 'utf8'))
150 const pipelineRunner = new Pipeline(options, content, this.serverless);
151 pipelineRunner
152 .step(new AddIntegrations())
153 .step(new ConfigureAutowire())
154 .step(new AddValidation())
155 .step(new AddAutoMockIntegration())
156 .step(new AddCorsMethods())
157 .step(new AddCorsParams())
158 .step(new AddCorsHeader())
159 .step(new AddCorsIntegration())
160 .step(new AddCorsResponseParameters())
161 .step(new WriteOutputFile())
162 }
163
164 processMergeCommand() {
165 for (const options of this.resolve()) {
166 this.process(options);
167 }
168 }
169
170 createFilesCommand() {
171 for (const options of this.resolve()) {
172 let content = jsYml.load(fs.readFileSync(options.inputFullPath, 'utf8'))
173 const pipelineRunner = new Pipeline(options, content, this.serverless);
174 pipelineRunner
175 .step(new AddIntegrations())
176 .step(new CreateIntegrationFile())
177 }
178 }
179
180 processPackage() {
181 const configurations = toArray(this.serverless.configurationInput.openApiIntegration);
182 if (!configurations || !configurations.length) {
183 return;
184 }
185
186 const outputPaths = new Set();
187
188 for (const configuration of configurations) {
189 let optionsResolver = new OptionsResolver(configuration);
190 const options = optionsResolver.resolve(this.options.stage);
191
192 if (!options.package) {
193 this.serverless.cli.log(`Openapi Integration: PROCESS & DEPLOY HOOK IS DEACTIVATED. Refer to manual for further information `);
194 return;
195 }
196
197 if (Object.entries(options).length === 0) {
198 this.serverless.cli.log(`Openapi Integration: No matching configuration available for the ${this.options.stage} stage `);
199 return;
200 }
201
202 if (!options.apiResourceName && configurations.length > 1) {
203 this.serverless.cli.log(`Openapi Integration: apiResourceName is mandatory when specifying multiple configurations`);
204 return;
205 }
206
207 if (outputPaths.has(options.outputFullPath)) {
208 this.serverless.cli.log(`Openapi Integration: conflicting output paths, make sure to use unique outputPath and/or outputFile for each configuration`);
209 return;
210 }
211 outputPaths.add(options.outputFullPath);
212
213 this.process(options);
214 this.addApiAGatewayBody(options.outputFullPath, options.apiResourceName)
215 }
216 }
217
218 addApiAGatewayBody(generatedApiSpecification, apiResourceName) {
219 let resources = this.serverless.service.resources.Resources;
220 Object.entries(resources).forEach(([resourceName, resource]) => {
221 if (resource.Type === 'AWS::ApiGateway::RestApi' &&
222 (!apiResourceName || resourceName === apiResourceName)) {
223 if (resource.hasOwnProperty('Properties') && !resource.Properties.hasOwnProperty('Body')) {
224 resource.Properties.Body = {}
225 }
226
227 resource.Properties.Body = jsYml.load(fs.readFileSync(generatedApiSpecification))
228 }
229
230 this.serverless.service.resources.Resources = resources;
231 });
232 }
233}
234
235module.exports = MergeIntegrationPlugin;