UNPKG

9.82 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');
17
18const { AppOptionsName, DomainTypes, EnvOptionsName, LogLevel, OperationType, OrgOptionsName } = require('./Constants');
19const FileProcessorHelper = require('./FileProcessorHelper');
20const { getObjectByPicking, isEmpty, isNullOrUndefined } = require('./Utils');
21
22class AppFileProcessor {
23 constructor(options) {
24 this.cliManager = options.cliManager;
25 this.envFileProcessor = options.envFileProcessor;
26 this.serviceFileProcessor = options.serviceFileProcessor;
27 this.applicationsService = options.applicationsService;
28 this.environmentsService = options.environmentsService;
29 this.servicesService = options.servicesService;
30 }
31
32 process(options, done) {
33 const operationType = options.operation;
34 const configApp = options.parsedData;
35 if (operationType === OperationType.CREATE) {
36 this._createApp(configApp, options, done);
37 } else if (operationType === OperationType.UPDATE) {
38 this._updateApp(configApp, options, done);
39 } else {
40 return setImmediate(() => { done(new Error(`Operation type not supported: ${operationType}`)); });
41 }
42 }
43
44 _createApp(configApp, options, done) {
45 let app;
46
47 async.series([
48 (next) => {
49 const data = getObjectByPicking(configApp.settings, ['realtime', 'sessionTimeoutInSeconds']);
50 data.name = options.name;
51 const orgId = options[OrgOptionsName.ORG];
52 if (!isNullOrUndefined(orgId)) {
53 data.organizationId = orgId;
54 }
55
56 this.cliManager.log(LogLevel.INFO, `Creating app: ${data.name}`);
57 this.applicationsService.create(data, (err, result) => {
58 if (err) {
59 return next(err);
60 }
61
62 app = result;
63 next();
64 });
65 },
66 (next) => {
67 this._createServices({ services: configApp.services, appId: app.id }, next);
68 },
69 (next) => {
70 this._createInitialEnvs(app, configApp.environments, next);
71 }
72 ], (err) => {
73 if (err) {
74 return done(err);
75 }
76
77 done(null, { id: app.id });
78 });
79 }
80
81 /**
82 * Creates services.
83 * @param {Object} options
84 * @param {Object} options.services Services to be created in config format.
85 * @param {String} options.appId App ID.
86 * @param done
87 * @private
88 */
89 _createServices(options, done) {
90 if (isNullOrUndefined(options.services) || isEmpty(options.services)) {
91 return setImmediate(done);
92 }
93
94 const servicesNames = Object.keys(options.services);
95 async.eachSeries(
96 servicesNames,
97 (currName, next) => {
98 this.cliManager.log(LogLevel.INFO, `Creating service: ${currName}`);
99 this.serviceFileProcessor.process(
100 {
101 operation: OperationType.CREATE,
102 parsedData: options.services[currName],
103 name: currName,
104 domainId: options.appId,
105 domainType: DomainTypes.APP
106 },
107 next
108 );
109 },
110 done
111 );
112 }
113
114 _updateServices(options, done) {
115 const configServices = options.services;
116 if (isEmpty(configServices)) {
117 return setImmediate(done);
118 }
119
120 const existingServices = options.existingServices;
121 const operation = OperationType.UPDATE;
122
123 async.eachSeries(
124 configServices,
125 (currService, next) => {
126 const nameIdentifier = Object.keys(currService)[0];
127 const existingService = existingServices.find(x => x.name === nameIdentifier);
128 const updateData = currService[nameIdentifier];
129 updateData.name = updateData.name || nameIdentifier;
130 const processOptions = {
131 operation,
132 parsedData: updateData,
133 serviceId: existingService.id,
134 domainId: options.appId,
135 domainType: DomainTypes.APP
136 };
137 this.cliManager.log(LogLevel.INFO, `Updating service: ${nameIdentifier}`);
138 this.serviceFileProcessor.process(processOptions, next);
139 },
140 done
141 );
142 }
143
144 /**
145 * Creates environments for a first time in an app. Takes into account the default env created by the backend.
146 * @param {Object} app
147 * @param {String} app.id
148 * @param {Array} app.environments
149 * @param {Object} configEnvs
150 * @param done
151 * @private
152 */
153 _createInitialEnvs(app, configEnvs, done) {
154 if (isNullOrUndefined(configEnvs) || isEmpty(configEnvs)) {
155 return setImmediate(done);
156 }
157
158 const envNames = Object.keys(configEnvs);
159 const defaultEnvName = 'development';
160 const findDefEnv = x => x.name.toLowerCase() === defaultEnvName;
161
162 let updateDefaultEnv = true;
163 async.eachSeries(
164 envNames,
165 (currentName, next) => {
166 // the backend creates a default env (when creating an app) called Development
167 // update it first, so that users on plans that support 1 env per app don't get an error
168 const operation = updateDefaultEnv ? OperationType.UPDATE : OperationType.CREATE;
169 const options = {
170 operation,
171 name: currentName,
172 parsedData: configEnvs[currentName],
173 [AppOptionsName.APP]: app.id
174 };
175
176 if (updateDefaultEnv) {
177 options.env = app.environments.find(findDefEnv);
178 options.parsedData.name = currentName;
179 updateDefaultEnv = false;
180 }
181
182 this.envFileProcessor.process(options, next);
183 },
184 done
185 );
186 }
187
188 _createEnvs(appId, configEnvs, done) {
189 if (isNullOrUndefined(configEnvs) || isEmpty(configEnvs)) {
190 return setImmediate(done);
191 }
192
193 const envNames = Object.keys(configEnvs);
194
195 async.eachSeries(
196 envNames,
197 (currentName, next) => {
198 const options = {
199 operation: OperationType.CREATE,
200 name: currentName,
201 parsedData: configEnvs[currentName],
202 [AppOptionsName.APP]: appId
203 };
204 this.envFileProcessor.process(options, next);
205 },
206 done
207 );
208 }
209
210 /**
211 * Takes envs in config format. The ones that already exist in the backend are updated, the rest - created.
212 * @param {Object} app
213 * @param {String} app.id
214 * @param {Array} app.environments
215 * @param {Object} configEnvs
216 * @param done
217 * @returns {number | Number}
218 * @private
219 */
220 _modifyEnvs(app, configEnvs, done) {
221 if (isNullOrUndefined(configEnvs) || isEmpty(configEnvs)) {
222 return setImmediate(done);
223 }
224
225 const existingEnvs = app.environments;
226 const groupedEntities = FileProcessorHelper.groupEntitiesPerOperationType(existingEnvs, configEnvs);
227
228 async.series([
229 (next) => {
230 this._updateEnvs(existingEnvs, groupedEntities[OperationType.UPDATE], next);
231 },
232 (next) => {
233 this._createEnvs(app.id, groupedEntities[OperationType.CREATE], next);
234 }
235 ], done);
236 }
237
238 _updateEnvs(existingEnvs, configEnvs, done) {
239 const operation = OperationType.UPDATE;
240
241 async.eachSeries(
242 configEnvs,
243 (currEnv, next) => {
244 const nameIdentifier = Object.keys(currEnv)[0];
245 const env = existingEnvs.find(x => x.name === nameIdentifier);
246 const options = {
247 operation,
248 parsedData: currEnv[nameIdentifier],
249 [EnvOptionsName.ENV]: env
250 };
251 this.envFileProcessor.process(options, next);
252 },
253 done
254 );
255 }
256
257 _updateApp(configApp, options, done) {
258 const app = options[AppOptionsName.APP];
259 const appId = app.id;
260 let groupedServices;
261 let existingServices;
262
263 async.series([
264 (next) => {
265 let data = { name: app.name };
266 data = Object.assign(data, configApp.settings);
267 if (!isNullOrUndefined(app.organizationId)) {
268 data.organizationId = app.organizationId;
269 }
270
271 this.cliManager.log(LogLevel.INFO, `Updating app: ${data.name}`);
272 this.applicationsService.update(appId, data, next);
273 },
274 (next) => {
275 this.servicesService.getAllOwnedByApp(appId, false, (err, data) => {
276 if (err) {
277 return next(err);
278 }
279
280 existingServices = data;
281 const configServices = configApp.services || {};
282 groupedServices = FileProcessorHelper.groupEntitiesPerOperationType(existingServices, configServices);
283 next();
284 });
285 },
286 (next) => {
287 this._createServices({ services: groupedServices[OperationType.CREATE], appId }, next);
288 },
289 (next) => {
290 this._updateServices({ services: groupedServices[OperationType.UPDATE], existingServices, appId }, next);
291 },
292 (next) => {
293 this._modifyEnvs(app, configApp.environments, next);
294 }
295 ], (err) => {
296 if (err) {
297 return done(err);
298 }
299
300 done(null, { id: appId });
301 });
302 }
303}
304
305module.exports = AppFileProcessor;