UNPKG

13.3 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2018, Kinvey, Inc. All rights reserved.
3 *
4 * This software is licensed to you under the Kinvey terms of service located at
5 * http://www.kinvey.com/terms-of-use. By downloading, accessing and/or using this
6 * software, you hereby accept such terms of service (and any agreement referenced
7 * therein) and agree that you have read, understand and agree to be bound by such
8 * terms of service and are of legal age to agree to such terms with Kinvey.
9 *
10 * This software contains valuable confidential and proprietary information of
11 * KINVEY, INC and is subject to applicable licensing agreements.
12 * Unauthorized reproduction, transmission or distribution of this file and its
13 * contents is a violation of applicable laws.
14 */
15
16const async = require('async');
17const cloneDeep = require('lodash.clonedeep');
18
19const { BackendServiceType, CLIRuntimeToAPIRuntime, ConfigFiles, DomainTypes, Errors, LogLevel, OperationType } = require('./Constants');
20const FileProcessorHelper = require('./FileProcessorHelper');
21const { getObjectByOmitting, getObjectByPicking, isEmpty, isNullOrUndefined } = require('./Utils');
22
23const flexType = 'flex';
24const rapidDataType = 'rapidData';
25
26class ServiceFileProcessor {
27 constructor(options) {
28 this.cliManager = options.cliManager;
29 this.servicesService = options.servicesService;
30 }
31
32 /**
33 * Process service configuration.
34 * @param {Object} options
35 * @param {String} [options.name] Service name
36 * @param {Object} options.parsedData Data to process
37 * @param {String} options.domainId ID of service's owner
38 * @param {Constants.DomainTypes} options.domainType Type of service's owner/scope - app or org
39 * @param done
40 */
41 process(options, done) {
42 const operationType = options.operation;
43 const source = options.parsedData;
44 if (operationType === OperationType.CREATE) {
45 this._createService(source, options, done);
46 } else if (operationType === OperationType.UPDATE) {
47 this._updateService(source, options, done);
48 } else {
49 return setImmediate(() => { done(new Error(`Operation type not supported: ${operationType}`)); });
50 }
51 }
52
53 _createService(source, options, done) {
54 const serviceType = source.type;
55 if (serviceType === ConfigFiles.ServiceType.FLEX_INTERNAL || serviceType === ConfigFiles.ServiceType.FLEX_EXTERNAL) {
56 this._createFlexService(source, options, done);
57 } else {
58 this._createRapidDataService(source, options, done);
59 }
60 }
61
62 static _buildAccess({ domainId, domainType }) {
63 const writers = {};
64 if (domainType === DomainTypes.APP) {
65 writers.apps = [domainId];
66 } else {
67 writers.organizations = [domainId];
68 }
69
70 return { writers };
71 }
72
73 _createFlexService(source, options, done) {
74 let service;
75
76 async.series([
77 (next) => {
78 const data = {
79 name: options.name,
80 type: source.type === ConfigFiles.ServiceType.FLEX_INTERNAL ? BackendServiceType.FLEX_INTERNAL : BackendServiceType.FLEX_EXTERNAL,
81 access: ServiceFileProcessor._buildAccess(options)
82 };
83
84 if (!isNullOrUndefined(source.description)) {
85 data.description = source.description;
86 }
87
88 this.servicesService.create(data, (err, result) => {
89 if (err) {
90 return done(err);
91 }
92
93 service = result;
94 next();
95 });
96 },
97 (next) => {
98 if (isNullOrUndefined(source.environments) || isEmpty(source.environments)) {
99 return setImmediate(next);
100 }
101
102 this._createSvcEnvs(service.id, flexType, source.environments, next);
103 }
104 ], (err) => {
105 if (err) {
106 return done(err);
107 }
108
109 done(null, service);
110 });
111 }
112
113 _createFlexSvcEnv(serviceId, svcEnvName, source, done) {
114 const data = {
115 name: svcEnvName,
116 secret: source.secret,
117 environmentVariables: source.environmentVariables
118 };
119
120 if (source.description) {
121 data.description = source.description;
122 }
123
124 if (source.host) {
125 data.host = source.host;
126 }
127
128 if (source.runtime) {
129 data.runtime = CLIRuntimeToAPIRuntime[source.runtime];
130 }
131
132 this.servicesService.createSvcEnv(serviceId, data, (err, svcEnv) => {
133 if (err) {
134 return done(err);
135 }
136
137 const result = { svcEnvId: svcEnv.id };
138 this._deployToSvcEnv(serviceId, svcEnv.id, svcEnv.name, source, (err) => {
139 if (err) {
140 return done(err);
141 }
142
143 done(null, result);
144 });
145 });
146 }
147
148 _createRapidDataSvcEnv(serviceId, svcEnvName, source, done) {
149 const data = Object.assign({}, source);
150 data.name = svcEnvName;
151 this.servicesService.createSvcEnv(serviceId, data, done);
152 }
153
154 _createRapidDataService(source, options, done) {
155 let service;
156
157 async.series([
158 (next) => {
159 const data = ServiceFileProcessor._buildRapidDataServiceObjectWoEnv(source, options.name, {}, options.domainId, options.domainType);
160 this.servicesService.create(data, (err, createdService) => {
161 if (err) {
162 return next(err);
163 }
164
165 service = createdService;
166 next();
167 });
168 },
169 (next) => {
170 this._createSvcEnvs(service.id, rapidDataType, source.environments, next);
171 }
172 ], (err) => {
173 if (err) {
174 return done(err);
175 }
176
177 done(null, service);
178 });
179 }
180
181 _updateService(source, options, done) {
182 const serviceType = source.type;
183 if (serviceType === ConfigFiles.ServiceType.FLEX_INTERNAL || serviceType === ConfigFiles.ServiceType.FLEX_EXTERNAL) {
184 this._modifyFlexService(source, options, done);
185 } else {
186 this._modifyRapidDataService(source, options, done);
187 }
188 }
189
190 _modifyFlexService(source, options, done) {
191 const serviceId = options.serviceId;
192 let updateData;
193 let jobId;
194
195 async.series([
196 (next) => {
197 this.servicesService.getById(serviceId, (err, originalService) => {
198 if (err) {
199 return next(err);
200 }
201
202 const data = {
203 name: source.name || originalService.name,
204 type: originalService.type // cannot be updated but required from the backend
205 };
206
207 if (!isNullOrUndefined(source.description)) {
208 data.description = source.description;
209 }
210
211 updateData = Object.assign({}, data, { access: originalService.access });
212
213 next();
214 });
215 },
216 (next) => {
217 this.servicesService.update(updateData, serviceId, next);
218 },
219 (next) => {
220 this._modifySvcEnvs(serviceId, flexType, source.environments, next);
221 }
222 ], (err) => {
223 if (err) {
224 return done(err);
225 }
226
227 done(null, { id: serviceId });
228 });
229 }
230
231 _modifySvcEnvs(serviceId, serviceType, source, done) {
232 if (isNullOrUndefined(source) || isEmpty(source)) {
233 return setImmediate(done);
234 }
235
236 let groupedSvcEnvs = {};
237 let originalSvcEnvs;
238
239 async.series([
240 (next) => {
241 this.servicesService.getServiceEnvs(serviceId, (err, data) => {
242 if (err) {
243 return done(err);
244 }
245
246 originalSvcEnvs = data;
247 groupedSvcEnvs = FileProcessorHelper.groupEntitiesPerOperationType(originalSvcEnvs, source);
248 next();
249 });
250 },
251 (next) => {
252 this._updateSvcEnvs(serviceId, serviceType, originalSvcEnvs, groupedSvcEnvs[OperationType.UPDATE], next);
253 },
254 (next) => {
255 this._createSvcEnvs(serviceId, serviceType, groupedSvcEnvs[OperationType.CREATE], next);
256 }
257 ], done);
258 }
259
260 _createSvcEnvs(serviceId, serviceType, svcEnvs, done) {
261 if (isNullOrUndefined(svcEnvs) || isEmpty(svcEnvs)) {
262 return setImmediate(done);
263 }
264
265 const svcEnvNames = Object.keys(svcEnvs);
266 async.eachSeries(
267 svcEnvNames,
268 (currEnvName, next) => {
269 const currEnv = svcEnvs[currEnvName];
270 this.cliManager.log(LogLevel.INFO, `Creating svc env: ${currEnvName}`);
271 if (serviceType === flexType) {
272 this._createFlexSvcEnv(serviceId, currEnvName, currEnv, next);
273 } else {
274 this._createRapidDataSvcEnv(serviceId, currEnvName, currEnv, next);
275 }
276 },
277 done
278 );
279 }
280
281 _updateSvcEnvs(serviceId, serviceType, originalSvcEnvs, svcEnvsToUpdate, done) {
282 async.eachSeries(
283 svcEnvsToUpdate,
284 (currEnv, next) => {
285 const currName = Object.keys(currEnv)[0];
286 const originalSvcEnv = originalSvcEnvs.find(x => x.name === currName);
287 this.cliManager.log(LogLevel.INFO, `Updating svc env: ${currName}`);
288 if (serviceType === flexType) {
289 this._updateFlexSvcEnv(serviceId, currName, originalSvcEnv, currEnv[currName], next);
290 } else {
291 this._updateRapidDataSvcEnv(serviceId, currName, originalSvcEnv, currEnv[currName], next);
292 }
293 },
294 done
295 );
296 }
297
298 _deployToSvcEnv(serviceId, svcEnvId, svcEnvName, source, done) {
299 const dir = source.sourcePath;
300 if (!dir) {
301 return setImmediate(done);
302 }
303
304 const deployOpts = {
305 serviceId,
306 svcEnvId,
307 dir,
308 schemaVersion: this.cliManager.config.defaultSchemaVersion
309 };
310 this.servicesService.deployFlexProject(deployOpts, (err, id) => {
311 if (err) {
312 if (err.name === Errors.DeploymentVersionTooLow.NAME) {
313 this.cliManager.log(LogLevel.WARN, `Skipped deploy for svc env with identifier '${svcEnvId}'(${svcEnvName}): ${err.message}`);
314 return done();
315 }
316
317 return done(err);
318 }
319
320 const infoMsg = `Initiated deploy to service with identifier '${serviceId}' and svc env with identifier '${svcEnvId}'(${svcEnvName}). Job ID: ${id}`;
321 this.cliManager.log(LogLevel.INFO, infoMsg);
322 done(null, id);
323 });
324 }
325
326 _updateFlexSvcEnv(serviceId, svcEnvName, originalSvcEnv, svcEnvToUpdate, done) {
327 let updateData = {};
328 updateData.name = svcEnvToUpdate.name || svcEnvName;
329 if (svcEnvToUpdate.runtime) {
330 svcEnvToUpdate.runtime = CLIRuntimeToAPIRuntime[svcEnvToUpdate.runtime];
331 }
332
333 updateData = Object.assign(updateData, svcEnvToUpdate);
334 delete updateData.sourcePath;
335 updateData.host = updateData.host || originalSvcEnv.host;
336
337 this.servicesService.updateSvcEnv(serviceId, originalSvcEnv.id, updateData, (err, result) => {
338 if (err) {
339 return done(err);
340 }
341
342 this._deployToSvcEnv(serviceId, originalSvcEnv.id, result.name, svcEnvToUpdate, done);
343 });
344 }
345
346 _updateRapidDataSvcEnv(serviceId, svcEnvName, originalSvcEnv, svcEnvToUpdate, done) {
347 let updateData = {};
348 updateData.name = svcEnvToUpdate.name || svcEnvName;
349 updateData = Object.assign(updateData, svcEnvToUpdate);
350 this.servicesService.updateSvcEnv(serviceId, originalSvcEnv.id, updateData, done);
351 }
352
353 _modifyRapidDataService(source, options, done) {
354 const serviceId = options.serviceId;
355 let updateData;
356
357 async.series([
358 (next) => {
359 this.servicesService.getById(serviceId, (err, originalService) => {
360 if (err) {
361 return next(err);
362 }
363
364 updateData = ServiceFileProcessor._buildRapidDataServiceObjectWoEnv(source, null, originalService);
365
366 next();
367 });
368 },
369 (next) => {
370 this.servicesService.update(updateData, serviceId, next);
371 },
372 (next) => {
373 this._modifySvcEnvs(serviceId, rapidDataType, source.environments, next);
374 }
375 ], (err) => {
376 if (err) {
377 return done(err);
378 }
379
380 done(null, { id: serviceId });
381 });
382 }
383
384 static _buildRapidDataServiceObjectWoEnv(source, name, original, domainId, domainType) {
385 original = original || {}; // eslint-disable-line no-param-reassign
386 const result = getObjectByOmitting(source, ['configType', 'schemaVersion', 'type', 'environments']);
387 result.name = name || source.name || original.name;
388
389 if (source.baseConfig) {
390 result.baseConfig = source.baseConfig;
391 result.type = BackendServiceType.REST;
392 } else {
393 result.type = ConfigFiles.ConfigToBackendServiceType[source.type];
394 if (source.type === ConfigFiles.ServiceType.DATA_DIRECT) {
395 result.baseConfig = ConfigFiles.BaseConfigType.DATA_DIRECT;
396 } else if (source.type === ConfigFiles.ServiceType.POKIT_DOK) {
397 result.baseConfig = ConfigFiles.BaseConfigType.POKIT_DOK;
398 }
399 }
400
401 if (isNullOrUndefined(result.description)) {
402 delete result.description;
403 }
404
405 if (original.access) { // it's an update, hence access cannot change
406 result.access = Object.assign({}, original.access);
407 } else {
408 result.access = ServiceFileProcessor._buildAccess({ domainId, domainType });
409 }
410
411 return result;
412 }
413}
414
415module.exports = ServiceFileProcessor;