UNPKG

14.5 kBJavaScriptView Raw
1/**
2 * Defines the MetaDeploymentManager class.
3 *
4 * @author Luke Chavers <luke@c2cschools.com>
5 * @since 5.1.30
6 * @license See LICENSE.md for details about licensing.
7 * @copyright 2019 C2C Schools, LLC
8 */
9
10"use strict";
11
12const BaseGenerator = require( "../util/BaseGenerator" );
13
14/**
15 * This class is used to manage metadata deployment during CI/CD build processes.
16 *
17 * @memberOf Deploy
18 * @extends Util.BaseGenerator
19 */
20class MetaDeploymentManager extends BaseGenerator {
21
22 /**
23 * This is the main entry point for execution of this meta
24 * deployment manager.
25 *
26 * @access public
27 * @returns {Promise}
28 */
29 execute() {
30
31 // Locals
32 let me = this;
33
34 // Deps
35 const _ = me.$dep("lodash");
36 const bb = me.$dep("bluebird");
37
38 // Tell someone...
39 me.$log( "notice", "start", "The Metadata Deployment Manager is starting up ...");
40
41 // Deploy to each target..
42 return bb.mapSeries( me.modules,
43
44 function( module, index ) {
45
46 me.$log("info", "module.execute", "Deferring to Module: " + module.constructor.name );
47 return module.execute();
48
49 }
50
51 ).then(
52
53 function( allModuleResults ) {
54
55 me.$log( "notice", "end", "Metadata deployment complete; exiting.");
56
57 }
58
59 );
60
61 }
62
63
64 //<editor-fold desc="--- Config Loading (deploy-config.yml) --------------">
65
66
67
68
69 /**
70 * Resolves the expected, absolute, path for the config/meta-deploy.yml file.
71 *
72 * @access public
73 * @returns {string}
74 */
75 get deployConfigPath() {
76
77 // Locals
78 let me = this;
79
80 // Deps
81 const PATH = me.$dep("path");
82
83 // Resolve the path and return it
84 return PATH.join( me.serviceRootPath, "config/meta-deploy.yml" );
85
86 }
87
88 /**
89 * Loads configuration data from config/meta-deploy.yml and returns the
90 * full configuration as an object.
91 *
92 * @access public
93 * @throws Error if the meta-deploy.yml file cannot be found or read.
94 * @returns {object}
95 */
96 get deployConfig() {
97
98 // Locals
99 let me = this;
100 let config;
101
102 // Deps
103 const TIPE = me.$dep("tipe");
104
105 // Caching...
106 if( me._deployConfig !== undefined ) {
107 return me._deployConfig;
108 }
109
110 // Read meta-deploy.yml
111 try {
112 config = this.loadYamlFile( this.deployConfigPath );
113 } catch( err ) {
114 me.$throw("deploy.config.syntax.error", err, "Could not read 'config/meta-deploy.yml'" );
115 }
116
117 // Require that the meta-deploy.yml file has at least one item
118 // defined in the "DeployTargets" property (array).
119 if( config.DeployTargets === undefined || TIPE( config.DeployTargets ) !== "array" ||
120 config.DeployTargets.length === 0 ) {
121
122 me.$throw("deploy.config.no.targets", "MetaDeploymentManager requires at least one deployment target! (DeployTargets)" );
123
124 }
125
126 // Require that the meta-deploy.yml file has at least one item
127 // defined in the "Modules" property (array).
128 if( config.Modules === undefined || TIPE( config.Modules ) !== "array" ||
129 config.Modules.length === 0 ) {
130
131 me.$throw("deploy.config.no.modules", "MetaDeploymentManager requires at least one module to be defined! (Modules)" );
132
133 }
134
135 // Replace variables in config data
136 config = me._processConfigVariables( config );
137
138 // Cache the config
139 me._deployConfig = config;
140
141 // .. and return it..
142 return config;
143
144 }
145
146
147
148
149 //</editor-fold>
150
151 //<editor-fold desc="--- Config Variables --------------------------------">
152
153
154
155
156 /**
157 * Finds and reads the contents of the services' package.json file, with
158 * caching.
159 *
160 * @access public
161 * @throws Error if the package.json cannot be found, or read.
162 * @returns {object}
163 */
164 get packageData() {
165
166 // Locals
167 let me = this;
168
169 // Caching...
170 if( me._packageData === undefined ) {
171
172 // Depds
173 const PATH = me.$dep("path");
174
175 // Resolve the expected path to package.json
176 let pkgPath = PATH.join( me.serviceRootPath, "package.json" );
177
178 // Read package.json
179 try {
180 me._packageData = require( pkgPath );
181 } catch( err ) {
182 me.$throw("missing.package-json", "Failed to load the services' package.json file" );
183 }
184
185
186 }
187
188 // All done..
189 return me._packageData;
190
191 }
192
193 /**
194 * Resolves the name of the service by looking at the contents of
195 * package.json and returning the 'version' field (verbatim).
196 *
197 * @access public
198 * @throws Error if the 'version' field cannot be found in the package.json file,
199 * or if it appears to be invalid.
200 * @returns {string}
201 */
202 get versionFull() {
203
204 // Locals
205 let me = this;
206
207 // Deps
208 const TIPE = me.$dep("tipe");
209
210 // Load the contents of package.json
211 let pkg = me.packageData;
212
213 // Ensure that the 'version' property is provided and
214 // appears to be valid.
215 if( pkg.version === undefined || TIPE( pkg.version ) !== "string" ||
216 pkg.version === "" ) {
217
218 me.$throw("missing.version", "Missing or Invalid 'version' specified in package.json" );
219
220 }
221
222 // All done..
223 return pkg.version;
224
225 }
226
227 /**
228 * Resolves the MAJOR version of the service by looking at the contents of
229 * package.json and parsing the first number in the 'version' property.
230 *
231 * @access public
232 * @throws Error if the major version cannot be resolved.
233 * @returns {string}
234 */
235 get versionMajor() {
236
237 let me = this;
238 let fv = me.versionFull;
239 let spl = fv.split(".");
240
241 if( spl.length === 0 ) {
242 me.$throw("invalid.version", "Could not resolve service major version from package.json" );
243 }
244
245 return spl[0];
246
247 }
248
249 /**
250 * Resolves the MINOR version of the service by looking at the contents of
251 * package.json and parsing the second number in the 'version' property.
252 *
253 * Note: If the MINOR version cannot be resolved, this function will return
254 * the literal string "0" (zero).
255 *
256 * @access public
257 * @returns {string}
258 */
259 get versionMinor() {
260
261 // Locals
262 let me = this;
263
264 // Fetch the full version string
265 let fv = me.versionFull;
266
267 // Split the full version string
268 let spl = fv.split(".");
269
270 // Check for our MINOR version
271 if( spl.length < 2 ) {
272
273 // It could not be found, return "0"
274 return "0";
275
276 } else {
277
278 // Found the MINOR version, return it as a string..
279 return spl[1];
280
281 }
282
283 }
284
285 /**
286 * Resolves the REVISION version of the service by looking at the contents of
287 * package.json and parsing the third number in the 'version' property.
288 *
289 * Note: If the REVISION version cannot be resolved, this function will return
290 * the literal string "0" (zero).
291 *
292 * @access public
293 * @returns {string}
294 */
295 get versionRevision() {
296
297 // Locals
298 let me = this;
299
300 // Fetch the full version string
301 let fv = me.versionFull;
302
303 // Split the full version string
304 let spl = fv.split(".");
305
306 // Check for our REVISION version
307 if( spl.length < 3 ) {
308
309 // It could not be found, return "0"
310 return "0";
311
312 } else {
313
314 // Found the REVISION version, return it as a string..
315 return spl[2];
316
317 }
318
319 }
320
321 /**
322 * Resolves the name of the service by looking at the contents of
323 * package.json and returning the 'name' field.
324 *
325 * @access public
326 * @throws Error if the 'name' field cannot be found in the package.json file,
327 * or if it appears to be invalid.
328 * @returns {string}
329 */
330 get serviceName() {
331
332 // Locals
333 let me = this;
334
335 // Deps
336 const TIPE = me.$dep("tipe");
337
338 // Fetch the package.json contents..
339 let pkg = me.packageData;
340
341 // Ensure we have a 'name' field
342 if( pkg.name === undefined || TIPE( pkg.name ) !== "string" ||
343 pkg.name === "" ) {
344
345 me.$throw("missing.service.name", "Missing or Invalid service 'name' specified in package.json" );
346
347 }
348
349 return pkg.name;
350
351 }
352
353 /**
354 * Resolves the current Git branch by looking for and reading the
355 * local .git/HEAD file.
356 *
357 * @access public
358 * @throws Error If the file could not be found, read, or if the contents
359 * are not recognized as valid.
360 * @returns {string}
361 */
362 get gitBranch() {
363
364 // Locals
365 let me = this;
366
367 // Dependencies
368 const FS = me.$dep("fs");
369 const PATH = me.$dep("path");
370 const _ = me.$dep("lodash");
371
372 // Resolve the expected path to .git/HEAD
373 let gitHeadPath = PATH.join( me.serviceRootPath, ".git/HEAD" );
374 let gitHeadContents, gitHeadSplit, gitBranch;
375
376 // Read the .git/HEAD file
377 try {
378 gitHeadContents = FS.readFileSync( gitHeadPath, "utf8");
379 } catch( err ) {
380 me.$throw("missing.git.head", "Missing .git/HEAD file, which is required to resolve the current Git branch" );
381 }
382
383 // Parse out invalid characters
384 gitHeadContents = gitHeadContents.replace( /[^a-zA-Z0-9\-\:\/\.]+/g, "" );
385
386 // Simple check for validity (this is not very comprehensive)
387 if( !_.startsWith( gitHeadContents, "ref:" ) || gitHeadContents.indexOf("/") === -1 ) {
388 me.$throw("invalid.git.head", "Could not resolve the current Git Branch, the contents of .git/HEAD were not recognized." );
389 }
390
391 // Reduce the contents of .git/HEAD to a simple branch name..
392 gitHeadSplit = gitHeadContents.split("/" );
393 gitBranch = gitHeadSplit[ ( gitHeadSplit.length - 1 ) ];
394
395 // All done, return the branch name..
396 return gitBranch;
397
398 }
399
400 /**
401 * Replaces variables (e.g. "${serviceName}") in the metadata deployment
402 * configuration for the service ("config/meta-deploy.yml").
403 *
404 * @param config
405 * @returns {{}}
406 * @private
407 */
408 _processConfigVariables( config ) {
409
410 // Locals
411 let me = this;
412
413 let vars = {
414 versionMajor: me.versionMajor,
415 versionMinor: me.versionMinor,
416 versionRevision: me.versionRevision,
417 versionFull: me.versionFull,
418 serviceName: me.serviceName,
419 gitBranch: me.gitBranch
420 };
421
422
423
424 return me._parseObjectForVariables( config, vars );
425
426 }
427
428
429
430
431 //</editor-fold>
432
433 //<editor-fold desc="--- Modules -----------------------------------------">
434
435
436
437
438 /**
439 * Returns the 'Modules' property from the `deployConfig` object, after
440 * a bit of parsing.
441 *
442 * @see deployConfig
443 * @returns {array}
444 */
445 get moduleConfigs() {
446
447 // Locals
448 let me = this;
449 let moduleConfig;
450
451 // Deps
452 const _ = me.$dep("lodash");
453
454 // Caching
455 if( me._moduleConfigs === undefined ) {
456
457 // Get the full deploy config
458 let config = me.deployConfig;
459
460 // Init Cache Object
461 me._moduleConfigs = [];
462
463 // Copy enabled modules (include: true) over to the
464 // internal module config cache object..
465 _.each( config.Modules, function( module, index ) {
466
467 if( module.include !== undefined && module.include === true ) {
468
469 // We no longer need this property...
470 delete module.include;
471
472 // Copy it over..
473 me._moduleConfigs.push( module );
474
475 } else {
476
477 if( module.type !== undefined ) {
478
479 me.$log("warning", "module.disabled", "An instance of a '" + module.type + "' deployment module is disabled and will not be executed!");
480
481 }
482
483 }
484
485 });
486
487 }
488
489 // All done..
490 return me._moduleConfigs;
491
492 }
493
494 /**
495 * Instantiates (as needed) and returns all of the "Module" helper
496 * objects (children of ./module/BaseMetaDeployModule) based on the provided
497 * `Modules` configuration in meta-deploy.yml.
498 *
499 * @access public
500 * @returns {BaseMetaDeployModule[]}
501 */
502 get modules() {
503
504 // Locals
505 let me = this;
506
507 // Deps
508 const _ = me.$dep("lodash");
509
510 // Caching
511 if( me._modules !== undefined ) {
512 return me._modules;
513 }
514
515 // Tell someone ...
516 me.$log("info", "module.init", "Initializing Modules ..." );
517
518 // Get the module config..
519 let moduleConfigs = me.moduleConfigs;
520
521 // Init cache object
522 me._modules = [];
523
524 // Instantiate each module
525 _.each( moduleConfigs, function( cfg ) {
526
527 // Extract the module's class name
528 let className = cfg.type;
529 delete cfg.type;
530
531 // Attach this class to the config
532 cfg.metaDeploymentManager = me;
533
534 // Attach the deploy targets to the config
535 cfg.targets = me.targets;
536
537 // Instantiate the module class/object
538 let mod = me.$spawn( "microservicesLib", "deploy/module/" + className, cfg );
539
540 // Persist the target class/object
541 me._modules.push( mod );
542
543 });
544
545 // All done
546 return me._modules;
547
548 }
549
550
551
552
553 //</editor-fold>
554
555 //<editor-fold desc="--- Targets -----------------------------------------">
556
557
558
559
560 /**
561 * Returns the 'DeployTargets' property from the `deployConfig` object,
562 * after a bit of parsing.
563 *
564 * @access public
565 * @see deployConfig
566 * @returns {array}
567 */
568 get targetConfigs() {
569 return this.deployConfig.DeployTargets;
570 }
571
572 /**
573 * Instantiates (as needed) and returns all of the "Deploy Target" helper
574 * objects (children of ./target/BaseMetaDeployTarget) based on the provided
575 * `DeployTargets` configuration in meta-deploy.yml.
576 *
577 * @access public
578 * @returns {BaseMetaDeployTarget[]}
579 */
580 get targets() {
581
582 // Locals
583 let me = this;
584
585 // Deps
586 const _ = me.$dep("lodash");
587
588 // Caching
589 if( me._targets !== undefined ) {
590 return me._targets;
591 }
592
593 // Tell someone ...
594 me.$log("info", "target.init", "Initializing Deploy Targets ..." );
595
596 // Get the target configs..
597 let targetConfigs = me.targetConfigs;
598
599 // Init cache object
600 me._targets = [];
601
602 // Instantiate each target
603 _.each( targetConfigs, function( cfg ) {
604
605 // Extract the target's class name
606 let className = cfg.type;
607 delete cfg.type;
608
609 // Attach this class to the config
610 cfg.metaDeploymentManager = me;
611
612 // Instantiate the target class/object
613 let target = me.$spawn( "microservicesLib", "deploy/target/" + className, cfg );
614
615 // Persist the target class/object
616 me._targets.push( target );
617
618 });
619
620 // All done
621 return me._targets;
622
623 }
624
625
626
627
628 //</editor-fold>
629
630}
631
632module.exports = MetaDeploymentManager;
633
634
635/*
636
637// Dependencies
638const YAML = me.$dep( "js-yaml" );
639const FS = me.$dep( "fs" );
640
641================================================================================
642
643static loadYamlFile( absPath, property ) {
644non-static: loadYamlFile( absPath, property ) {
645
646================================================================================
647
648// Create a "DebugHelper"...
649let deb = me.$spawn( "commonLib", "util/DebugHelper" );
650
651// Dump the config data
652deb.dbg( resolvedConfig, true, 1 );
653
654// ---- alt
655
656get $debugger() {
657$inspect( varToInspect, output, indent, title )
658
659================================================================================
660
661return me._loadCommonConfigFile( "provider.yml", "provider" );
662
663================================================================================
664
665me.$log(
666 "trace",
667 "Instantiating class ('" + name + "')"
668);
669
670================================================================================
671
672_getDirContentsRecursive( absPathToRead, initialStore )
673
674================================================================================
675
676_parseStringForVariables( str, vars ) {
677
678 */
\No newline at end of file