berlioz
Version:
Berlioz - cloud deployment and migration services
722 lines (600 loc) • 20.9 kB
JavaScript
const PlantUml = require('./plantuml');
const _ = require('the-lodash');
const BaseItem = require('../berlioz-common/entities/base');
class RegistryDiagramGenerator
{
constructor(logger, registry)
{
this._logger = logger;
this._registry = registry;
this._renderServiceConsumerAgent = true;
this._serviceComponentBackground = '#LightBlue';
this._compiledServiceComponentBackground = '#B1A0C7';
this._publicToEndpointArrowColor = '#Crimson';
this._externalToEndpointArrowColor = '#OrangeRed';
this._internalToEndpointArrowColor = '#LightCoral';
this._toSecretArrowColor = '#3a4ec1';
}
generate()
{
this._uml = new PlantUml(this._logger);
this._processRegistry(this._registry);
return this._uml;
}
_processRegistry(registry)
{
this._consumesQueue = [];
this._publicClusterProvided = [];
this._outputCloudHeader();
for(var cluster of registry.clusters)
{
this._processCluster(cluster);
}
this._outputCloudFooter();
for(var consumes of this._consumesQueue)
{
this._processEndpointsConsumesFooter(consumes);
}
}
_processCluster(cluster)
{
this._outputClusterHeader(cluster);
for(var sector of cluster.sectors)
{
this._processSector(sector);
}
for(var provided of _.values(cluster.provides))
{
this._processClusterProvided(provided);
}
this._processLinks(cluster);
this._outputClusterFooter(cluster);
}
_processSector(sector)
{
this._outputSectorHeader(sector);
for(var db of sector.databases)
{
this._processDatabase(db);
}
for(var queue of sector.queues)
{
this._processQueue(queue);
}
for(var secret of sector.secrets)
{
this._processSecret(secret);
}
for(var lambda of sector.lambdas)
{
this._processLambda(lambda);
}
for(var service of sector.services)
{
this._processService(service);
}
for(var trigger of sector.triggers)
{
this._processTrigger(trigger);
}
this._outputSectorFooter(sector);
}
_processLinks(cluster)
{
for(var linkedObject of cluster.allLinks)
{
this._processConsumes(linkedObject)
}
}
_processDatabase(db)
{
this._outputDatabaseHeader(db);
this._outputDatabaseFooter(db);
}
_processQueue(queue)
{
this._outputQueueHeader(queue);
this._outputQueueFooter(queue);
}
_processSecret(secret)
{
this._outputSecretHeader(secret);
this._outputSecretFooter(secret);
}
_processLambda(lambda)
{
this._outputLambdaHeader(lambda);
this._outputLambdaFooter(lambda);
}
_processTrigger(trigger)
{
this._outputTriggerHeader(trigger);
this._outputTriggerFooter(trigger);
}
_processService(service)
{
this._outputServiceHeader(service);
for(var provided of _.values(service.provides))
{
this._processServiceProvided(provided);
}
for(var storage of service.storage)
{
this._processStorage(service, storage);
}
this._outputServiceFooter(service);
}
_processConsumes(item)
{
this._logger.verbose('[_processConsumes] source: %s', item.id);
this._handleConsumes(item.consumes, this._processEndpointsConsumes.bind(this))
this._handleConsumes(item.databasesConsumes, this._processDatabaseConsumes.bind(this))
this._handleConsumes(item.queuesConsumes, this._processQueueConsumes.bind(this))
this._handleConsumes(item.triggerConsumes, this._processTriggerConsumes.bind(this))
this._handleConsumes(item.lambdaConsumes, this._processLambdaConsumes.bind(this))
this._handleConsumes(item.secretsConsumes, this._processServiceSecretConsumes.bind(this))
}
_handleConsumes(consumes, handler)
{
if (!consumes) {
return;
}
for(var consumed of consumes)
{
handler(consumed);
}
}
_processServiceProvided(provided)
{
this._outputServiceProvidedHeader(provided);
this._outputServiceProvidedFooter(provided);
}
_processEndpointsConsumes(consumes)
{
this._consumesQueue.push(consumes);
}
_processEndpointsConsumesFooter(consumes)
{
this._outputServiceEndpointsConsumes(consumes);
}
_processDatabaseConsumes(consumes)
{
this._logger.info('[_processDatabaseConsumes] %s', consumes.id);
this._outputDatabaseConsumesHeader(consumes);
}
_processDatabaseConsumesFooter(consumes)
{
this._outputDatabaseConsumesFooter(consumes);
}
_processQueueConsumes(consumes)
{
this._outputQueueConsumesHeader(consumes);
}
_processTriggerConsumes(consumes)
{
this._outputTriggerConsumesHeader(consumes);
}
_processLambdaConsumes(consumes)
{
this._outputLambdaConsumesHeader(consumes);
}
_processServiceSecretConsumes(consumes)
{
this._outputServiceSecretConsumesHeader(consumes);
}
_processClusterProvided(provided)
{
if (provided.definition.public) {
this._publicClusterProvided.push(provided);
}
this._outputClusterProvidedHeader(provided);
this._outputClusterProvidedFooter(provided);
}
_processStorage(service, storage)
{
if (storage.kind == 'volume') {
this._outputStorage(service, storage);
}
}
_write(str)
{
this._uml.write(str);
}
/**************************************/
_normalize(str)
{
return str.replace(/-/g, '_');
}
/**************************************/
_outputCloudHeader()
{
this._write('cloud {');
}
_outputCloudFooter()
{
this._write('}');
var externalConsumes = 'EX_Consumer';
this._write('actor :Public User: as ' + externalConsumes);
for(var clusterProvided of this._publicClusterProvided)
{
var clusterInterfaceName = this._getClusterProvidedInterfaceName(clusterProvided.cluster, clusterProvided.name);
this._write(externalConsumes + ' --> ' + clusterInterfaceName + ' ' + this._publicToEndpointArrowColor);
// this._write(externalConsumes + ' -> ' + clusterInterfaceName + ' #DarkRed')
}
}
_outputSectorHeader(sector)
{
var sectorName = this._getSectorPackageName(sector);
this._write('frame "Sector: ' + sector.name + '" as ' + sectorName + ' {');
}
_outputSectorFooter(sector)
{
this._write('}')
}
_outputClusterHeader(cluster)
{
var clusterPackageName = this._getClusterPackageName(cluster);
this._write('node "Cluster: ' + cluster.name + '" as ' + clusterPackageName + ' {');
}
_outputClusterFooter(cluster)
{
this._write('}')
}
_outputDatabaseHeader(db)
{
var dbName = this._getDatabaseName(db);
this._write('database ' + dbName + '[');
this._write(db.kind);
this._write('====');
this._write(db.className + '::' + db.subClassName);
this._write('====');
this._write('**' + db.name + '**');
this._write('====');
if (db.hashKey) {
this._write('<&key> ' + db.hashKey.name);
}
if (db.rangeKey) {
this._write('....');
this._write('<&list> ' + db.rangeKey.name);
}
this._write(']');
}
_outputDatabaseFooter(db)
{
}
_outputQueueHeader(queue)
{
var queueName = this._getQueueName(queue);
this._write('queue ' + queueName + '[');
this._write(queue.kind);
this._write('====');
this._write(queue.className + '::' + queue.subClassName);
this._write('====');
this._write('**' + queue.name + '**');
this._write(']');
}
_outputQueueFooter(queue)
{
}
_outputSecretHeader(secret)
{
var secretName = this._getSecretName(secret);
this._write('artifact ' + secretName + '[');
this._write(secret.kind);
this._write('====');
this._write(secret.className + '::' + secret.subClassName);
this._write('====');
this._write('**' + secret.name + '**');
this._write(']');
}
_outputSecretFooter(secret)
{
}
_outputLambdaHeader(lambda)
{
var lambdaName = this._getLambdaName(lambda);
this._write('folder ' + lambdaName + ' [');
this._write('**' + lambda.name + '**');
this._write('----');
this._write('runtime: ' + lambda.runtime);
this._write(']');
}
_outputLambdaFooter(lambda)
{
}
_outputTriggerHeader(trigger)
{
var triggerName = this._getTriggerName(trigger);
this._write('artifact ' + triggerName + ' [');
this._write('**' + trigger.name + '**');
this._write('----');
this._write(trigger.schedule);
this._write(']');
}
_outputTriggerFooter(trigger)
{
}
_outputServiceHeader(service)
{
var servicePackageName = this._getServicePackageName(service);
var color;
if (service.isCompiled) {
color = this._compiledServiceComponentBackground
} else {
color = this._serviceComponentBackground
}
this._write('package "Service: ' + service.name + '" as ' + servicePackageName + ' ' + color + ' {')
if (this._renderServiceConsumerAgent) {
var serviceConsumesName = this._getServiceConsumerAgentName(service);
this._write('actor :Consumes: as ' + serviceConsumesName);
}
}
_outputServiceFooter(service)
{
this._write('}')
}
_outputServiceProvidedHeader(provided)
{
var info = '';
info += provided.name + '\\n';
info += '===' + '\\n';
info += 'Port: ' + provided.definition.port;
info += '\\n' + provided.definition.protocol;
var componentName = this._getProvidedComponentName(provided.service, provided.name);
this._write('component "' + info + '" as ' + componentName);
if (provided.loadBalance) {
var interfaceInfo = provided.name;
interfaceInfo += '-LoadBalancer <&wifi>';
var interfaceName = this._getServiceProvidedInterfaceName(provided.service.clusterName, provided.service.name, provided.name);
this._write('entity "' + interfaceInfo + '" as ' + interfaceName);
this._write(componentName + ' .. ' + interfaceName + ' #RoyalBlue');
}
}
_getServiceProvidedTarget(serviceId, providedName)
{
var service = this._registry.findById(serviceId);
if (!service) {
return 'MissingService';
}
var provided = service.provides[providedName];
if (!provided) {
return 'MissingEndpoint'
}
if (provided.loadBalance) {
return this._getServiceProvidedInterfaceName(service.clusterName, service.name, providedName);
} else {
return this._getProvidedComponentName(service, providedName);
}
}
_outputServiceProvidedFooter(provided)
{
if (this._renderServiceConsumerAgent) {
var serviceConsumesName = this._getServiceConsumerAgentName(provided.service);
var componentName = this._getProvidedComponentName(provided.service, provided.name);
this._write(serviceConsumesName + ' -[hidden]-> ' + componentName);
}
}
_outputClusterProvidedHeader(clusterProvided)
{
var interfaceName = this._getClusterProvidedInterfaceName(clusterProvided.cluster, clusterProvided.name);
var serviceInterfaceName = this._getServiceProvidedTarget(clusterProvided.serviceId, clusterProvided.definition.endpointName);
var color = '';
if (clusterProvided.definition.public) {
color = ' #red';
}
this._write('boundary "' + clusterProvided.name + '" as ' + interfaceName + color);
this._write(serviceInterfaceName + ' .. ' + interfaceName + ' #RoyalBlue');
}
_outputClusterProvidedFooter(clusterProvided)
{
}
_outputServiceEndpointsConsumes(consumes)
{
this._logger.verbose('[_outputServiceEndpointsConsumes] source: %s', consumes.source.id);
this._logger.verbose('[_outputServiceEndpointsConsumes] consumed target: %s, endpoint: %s', consumes.targetId, consumes.definition.endpoint);
var interfaceName;
var color = '';
if (consumes.targetKind == 'cluster') {
interfaceName = this._getClusterProvidedInterfaceName(consumes.targetId, consumes.targetEndpoint);
color = this._externalToEndpointArrowColor;
} else {
interfaceName = this._getServiceProvidedTarget(consumes.targetId, consumes.targetEndpoint);
color = this._internalToEndpointArrowColor;
}
var arrow = '-->';
if (consumes.isolation == 'instance') {
arrow = '--#'
}
this._renderConsumesLine(consumes, arrow, interfaceName, color);
}
_outputDatabaseConsumesHeader(consumes)
{
this._logger.verbose('[_outputDatabaseConsumesHeader] %s', consumes.id);
var databaseEntity = consumes.localTarget;
if (!databaseEntity) {
return;
}
var dbName = this._getDatabaseName(databaseEntity);
this._logger.verbose('[_outputDatabaseConsumesHeader] source: %s', consumes.source.id);
this._logger.verbose('[_outputDatabaseConsumesHeader] consumed database: %s', consumes.targetId);
this._renderConsumesLine(consumes, '-->', dbName, this._internalToEndpointArrowColor);
}
_outputDatabaseConsumesFooter(consumes)
{
}
_outputLambdaConsumesHeader(consumes)
{
var lambdaEntity = consumes.localTarget;
if (!lambdaEntity) {
return;
}
var lambdaName = this._getLambdaName(lambdaEntity);
this._logger.verbose('[_outputLambdaConsumesHeader] source: %s', consumes.source.id);
this._logger.verbose('[_outputLambdaConsumesHeader] consumed queue: %s', consumes.targetId);
this._renderConsumesLine(consumes, '-->', lambdaName, this._internalToEndpointArrowColor);
}
_outputTriggerConsumesHeader(consumes)
{
var triggerEntity = consumes.localTarget;
if (!triggerEntity) {
return;
}
var triggerName = this._getTriggerName(triggerEntity);
this._logger.verbose('[_outputTriggerConsumesHeader] source: %s', consumes.source.id);
this._logger.verbose('[_outputTriggerConsumesHeader] consumed queue: %s', consumes.targetId);
this._renderConsumesLine(consumes, '-->', triggerName, this._internalToEndpointArrowColor);
}
_outputQueueConsumesHeader(consumes)
{
var queueEntity = consumes.localTarget;
if (!queueEntity) {
return;
}
var queueName = this._getQueueName(queueEntity);
this._logger.verbose('[_outputQueueConsumesHeader] source: %s', consumes.source.id);
this._logger.verbose('[_outputQueueConsumesHeader] consumed queue: %s', consumes.targetId);
this._renderConsumesLine(consumes, this._determineConsumesArrow(consumes), queueName, this._internalToEndpointArrowColor);
}
_determineConsumesArrow(consumes)
{
var isPublisher = _.includes(consumes.actions, 'publish');
var isSubscriber = _.includes(consumes.actions, 'subscribe');
if (isPublisher && isSubscriber) {
return '<..>';
}
if (isSubscriber) {
return '<..';
}
if (isPublisher) {
return '..>';
}
return '-->';
}
_outputServiceSecretConsumesHeader(consumes)
{
this._logger.verbose('[_outputServiceSecretConsumesHeader] source: %s', consumes.source.id);
var secretEntity = consumes.localTarget;
if (!secretEntity) {
return;
}
var secretName = this._getSecretName(secretEntity);
this._logger.verbose('[_outputServiceSecretConsumesHeader] consumed secret: %s', consumes.targetId);
var arrowKind = '-(0)-';
var canEncrypt = _.includes(consumes.actions, 'encrypt');
var canDecrypt = _.includes(consumes.actions, 'decrypt');
if (canEncrypt && canDecrypt) {
arrowKind = '<-(0)->';
} else if (canEncrypt) {
arrowKind = '-0)->';
} else if (canDecrypt) {
arrowKind = '<-(0-';
} else {
arrowKind = '--';
}
this._renderConsumesLine(consumes, arrowKind, secretName, this._toSecretArrowColor);
}
_renderConsumesLine(consumes, arrowKind, targetName, color)
{
var interfaceName;
if (consumes.source.kind == "service") {
if (this._renderServiceConsumerAgent) {
interfaceName = this._getServiceConsumerAgentName(consumes.source);
} else {
interfaceName = this._getServicePackageName(consumes.source);
}
} else if (consumes.source.kind == "lambda"){
interfaceName = this._getLambdaName(consumes.source);
} else if (consumes.source.kind == "trigger"){
interfaceName = this._getTriggerName(consumes.source);
} else if (consumes.source.kind == "database"){
interfaceName = this._getDatabaseName(consumes.source);
} else {
return;
}
this._write(interfaceName + ' ' + arrowKind + ' ' + targetName + ' ' + color);
}
_outputStorage(service, storage)
{
var storageName = this._getStorageName(service, storage.path);
this._write('database ' + storageName + '[');
this._write(storage.path);
this._write('....');
this._write(storage.size);
this._write(']');
}
/***************************************************************/
_getDatabaseName(databaseEntity)
{
return this._getPackageName('DATABASE', databaseEntity);
}
_getQueueName(queueEntity)
{
return this._getPackageName('QUEUE', queueEntity);
}
_getSecretName(secretEntity)
{
return this._getPackageName('SECRET', secretEntity);
}
_getLambdaName(lambdaEntity)
{
return this._getPackageName('LA', lambdaEntity);
}
_getTriggerName(triggerEntity)
{
return this._getPackageName('TR', triggerEntity);
}
_getStorageName(service, storagePath)
{
return this._getPackageName('VOL', service, [storagePath.replace(/\//g, '_')]);
}
_getProvidedComponentName(service, endpointName)
{
return this._getPackageName('PROV', service, [endpointName]);
}
_getServicePackageName(service)
{
return this._getPackageName('SVC', service);
}
_getClusterPackageName(cluster)
{
return this._getPackageName('CL', cluster);
}
_getSectorPackageName(sector)
{
return this._getPackageName('SEC', sector);
}
_getServiceConsumerAgentName(service)
{
return this._getPackageName('SVCAGENT', service);
}
_getClusterProvidedInterfaceName(clusterOrId, endpointName)
{
return this._getPackageName('CI', clusterOrId, [endpointName]);
}
_getServiceProvidedInterfaceName(cluster, service, endpoint)
{
return 'I_' + this._normalize(cluster) + '_' + this._normalize(service) + '_' + this._normalize(endpoint);
}
_getPackageName(category, itemOrId, extras)
{
var components = [category];
var naming;
var kind;
if (itemOrId instanceof BaseItem) {
kind = itemOrId.kind;
naming = itemOrId.naming;
} else if (_.isString(itemOrId)) {
var info = BaseItem.breakID(itemOrId);
kind = info.kind;
naming = info.naming;
}
components.push(kind);
components = _.concat(components, naming);
if (extras) {
components = _.concat(components, extras);
}
components = components.map(x => this._normalize(x));
return components.join('_');
}
}
module.exports = RegistryDiagramGenerator;