UNPKG

berlioz

Version:

Berlioz - cloud deployment and migration services

1,085 lines (935 loc) 33 kB
const Promise = require('the-promise'); const _ = require('the-lodash'); const Path = require('path'); const fs = require('fs'); const os = require('os'); const ip = require('ip'); const ModelProcessor = require('processing-tools/model-processor') const ClusterProcessor = require('./cluster-processor'); const TaskMetadataStore = require('./task-metadata-store'); class LocalProcessor extends ModelProcessor { constructor(logger, repoStore, docker, screen, shell, environment, storage) { super(logger); this._repoStore = repoStore; this._docker = docker; this._screen = screen; this._shell = shell; this._environment = environment; this._storage = storage; this._clusterProcessor = null; this._berliozAgentExternalIpAddress = null; this._berliozAgentExternalPort = null; this._berliozCommon = null; this._providers = {}; this._nativeProcessor = null; this._gcpServiceAPIs = []; this._modelsDirLocation = __dirname; this._metaContext.docker = this._docker; this._metaContext.screen = this._screen; this._metaContext.aws = null; this._metaContext.autoconfigAwsObject = this.autoconfigAwsObject.bind(this) this._metaContext.deployment = 'local' + this._constructHostName(); this._logger.info('LOCAL DEPLOYMENT: %s...', this._metaContext.deployment); this.setupStage('prepare-common-images', () => this._prepareCommonImages()); this._repositories = {}; this._providedRepositories = {}; this.setupStage('iteration-init', () => { this._logger.info('MASSAGED REPOSITORIES: ', this._repositories); this.setSingleStageData('repositories', this._repositories); return this._outputDataToFile('repositories', this._repositories); }); this.setupStage('single-stage-deploy', () => { this._iterationStages.preSetup = ['preSetup']; this._iterationStages.iterationInit = ['prepare-common-images', 'iteration-init', 'setup-docker-network'] ; this._iterationStages.constructDesired = 'construct-desired'; this._iterationStages.stabilizeCurrent = ['reserve-network-resources', 'massage-key-aliases']; // this._iterationStages.postExtractCurrent = null; this._iterationStages.preProcessDelta = ['output-other-config']; this._iterationStages.postProcessDelta = ['massage-key-aliases', 'fetch-berlioz-agent-task', 'deploy-task-metadata']; return this.runStage('process-iteration'); }); this.setupStage('single-stage-undeploy', () => { this._iterationStages.preSetup = ['preSetup']; this._iterationStages.iterationInit = 'iteration-init'; this._iterationStages.constructDesired = ['construct-undeploy', 'cleanup-metadata-processor']; this._iterationStages.stabilizeCurrent = ['reserve-network-resources', 'massage-key-aliases']; // this._iterationStages.postExtractCurrent = null; this._iterationStages.postProcessDelta = ['massage-key-aliases', 'fetch-berlioz-agent-task', 'deploy-task-metadata']; return this.runStage('process-iteration'); }); this.setupStage('preSetup', () => { return this._preSetup(); }); this.setupStage('setup-docker-network', () => { return this._setupDockerNetwork(); }); this.setupStage('construct-desired', () => { return this._constructDesiredConfig(); }); this.setupStage('construct-undeploy', () => { return this._constructUndeployConfig(); }); this.setupStage('deploy-task-metadata', () => { return this._deployTaskMetadata(); }); this.setupStage('reserve-network-resources', () => { return this._reserveNetworkResources(); }); this.setupStage('fetch-berlioz-agent-task', () => { return this._fetchBerliozAgentTask(); }); this.setupStage('massage-key-aliases', () => { return this._massageKeyAliases(); }); this.setupStage('cleanup-metadata-processor', () => { return this._cleanupMetadataProcessor(); }); this.setupStage('output-other-config', () => { return this._outputOtherConfig(); }); } get agentHostName() { return 'agent1.main.berlioz'; } get agentPort() { return '55555'; } get agentHostPort() { return this.agentHostName + ':' + this.agentPort; } get berliozAgentExternalIpAddress() { return this._berliozAgentExternalIpAddress; } get berliozAgentExternalPort() { return this._berliozAgentExternalPort; } get berliozAgentExternalHostPort() { if (!this.berliozAgentExternalIpAddress) { return null; } if (!this.berliozAgentExternalPort) { return null; } return this.berliozAgentExternalIpAddress + ':' + this.berliozAgentExternalPort; } tableImplementation(value) { this._tableImpl = value; } setEndpointProcessor(value) { this._endpointProcessor = value; } setPeersFetcher(value) { this._peersFetcher = value; } setMetadataProcessor(value) { this._metadataProcessor = value; } get endpointProcessor() { return this._endpointProcessor; } get clusterProcessor() { return this._clusterProcessor; } get peersFetcher() { return this._peersFetcher; } get metadataProcessor() { return this._metadataProcessor; } get nativeProcessor() { return this._nativeProcessor; } _constructHostName() { var hostname = os.hostname(); this._logger.info('HOSTNAME: %s...', hostname); var dotIndex = hostname.indexOf('.'); if (dotIndex >= 0) { hostname = hostname.substr(0, dotIndex); } hostname = hostname.replace(/-/g, ''); hostname = hostname.replace(/_/g, ''); hostname = hostname.replace(/:/g, ''); hostname = hostname.replace(/\./g, ''); return hostname; } get deploymentName() { return this._metaContext.deployment; } get clusterEntity() { return this._clusterEntity; } get clusterName() { return this.clusterEntity.name; } get repoStore() { return this._repoStore; } get awsClient() { return this._awsClient; } get gcpClient() { return this._gcpClient; } get hasAwsProvider() { return 'aws' in this._providers; } get hasGcpProvider() { return 'gcp' in this._providers; } get storage() { return this._storage; } makeQuick(value) { this._isQuick = value; } providerConfig(name, config, client) { this._logger.info('Provider Config - %s: ', name, config); this._providers[name] = { name: name, config: config, client: client }; } getRootWorkingPath() { return this._storage.getConfigDir("deployment"); } getServiceWorkingPath(serviceEntity) { var p = this.getRootWorkingPath(); for(var x of serviceEntity.naming) { p = Path.resolve(p, x); } return p; } getNewDeploymentWorkingPath() { var p = this._storage.getConfigDir("deployment-config"); //this.getRootWorkingPath(); var p = Path.resolve(p, this.clusterName); return p; } getTaskMetadataWorkingPath(subdirOrNone) { var p = this._storage.getConfigDir("deployment-config"); //this.getRootWorkingPath(); p = Path.resolve(p, this.clusterName); if (subdirOrNone) { p = Path.resolve(p, subdirOrNone); } return p; } // getItemWorkingPath(item, otherPathParts) // { // var p = this.getRootWorkingPath(); // var p = Path.resolve(p, this.clusterName); // var myName = item.dn; // myName = myName.replace(/\\/g, ""); // myName = myName.replace(/\//g, ""); // myName = myName.replace(/:/g, "-"); // var p = Path.resolve(p, myName); // if (otherPathParts) { // for(var x of otherPathParts) // { // p = Path.resolve(p, x); // } // } // return p; // } getServiceEndpointWorkingPath(serviceEntity, endpoint) { return Path.resolve(this.getServiceWorkingPath(serviceEntity), "endpoints", endpoint); } writeToFile(filePath, contents) { return this.storage.writeToFile(filePath, contents); } readFromFile(filePath) { return this.storage.readFromFile(filePath); } setBerliozCommon(value) { this._berliozCommon = value; } perform(stageAction) { return Promise.resolve() .then(() => this._perform(stageAction, 3)) ; } addToDeploymentConfig(name, path, contents) { var deploymentConfig = this._getDeploymentConfig(name); deploymentConfig.config.contents[path] = contents; } _getDeploymentConfig(name) { var naming = [name]; var x = this._desiredConfig.find('deployment-config', naming); if (x) { return x; } x = this._desiredConfig.section('deployment-config').create(naming); x.config.contents = { }; return x; } _perform(stageAction, count) { if (count <= 0) { return; } this._logger.info('***********************************************************'); this._logger.info('***********************************************************'); this._logger.info('***********************************************************'); return this.runStage(stageAction) .then(result => { this._logger.info('[_perform] %s completed. Run result: ', stageAction, result); this._lastDeltaConfig = this.singleStageData['finalDelta']; var shouldRetry = false; if (!result) { shouldRetry = true; } else { if (result.needMore) { shouldRetry = true; } } // TODO: TEMP shouldRetry = false; if (shouldRetry) { this._logger.info('[_perform] NEED MORE STAGES TO PROCESS'); return this._perform(stageAction, count - 1); } return result; }); } _prepareCommonImages() { if (!this._repositories) { this._repositories = {}; } return Promise.resolve() .then(() => this._prepareHelperImage('load-balancer', 'million12/haproxy')) .then(() => this._clusterProcessor.prepareCommonImages(this._repositories)) .then(() => { if (this._providedRepositories) { this._repositories = _.defaults(this._repositories, this._providedRepositories); } }) } _getHelperImageId(name) { return 'helper-service://' + name; } getImage(id) { if (id in this._repositories) { return this._repositories[id]; } return null; } getHelperImage(name) { return this.getImage(this._getHelperImageId(name)) } getImageInfo(imageName) { return Promise.resolve() .then(() => this._docker.pullImage(imageName, this._isQuick)) .then(() => this._docker.getImage(imageName)) .then(result => ({ kind: 'docker', name: imageName, digest: result.Id })); } _prepareHelperImage(name, imageName) { var id = this._getHelperImageId(name) if (id in this._repositories) { return; } return Promise.resolve() .then(() => this.getImageInfo(imageName)) .then(result => { this._repositories[id] = result }); } cluster(cluster) { if (!cluster._registry) { throw new Error('Cluster not linked to registry'); } this._clusterEntity = cluster; this._metaContext.cluster = cluster.name; this._outputEntity(cluster); for(var service of cluster.services) { this._outputEntity(service); for(var provided of _.values(service.provides)) { this._outputEntity(provided); this._logger.info('END %s', provided.id); } this._logger.info('END %s', service.id); } this._logger.info('END %s', cluster.id); for(var policy of cluster._registry.policies) { this._outputEntity(policy); } this._clusterProcessor = new ClusterProcessor(this, this._logger, cluster); } repositories(value) { this._providedRepositories = _.clone(value); this._logger.info('PROVIDED REPOSITORIES: ', value); } _outputEntity(entity) { var data = []; entity.extractData(data); this._logger.info('BEGIN %s', entity.id); for(var row of data) { this._logger.info(' %s = %s', row[0], row[1]); } } _fetchBerliozAgentTask() { var agentFilters = { 'berlioz:kind': 'task', 'berlioz:cluster': 'berlioz', 'berlioz:sector': 'main', 'berlioz:service': 'agent' }; return Promise.resolve() .then(() => this._docker.listContainersByKind(agentFilters)) .then(containers => { if (containers.length == 0) { return; } if (containers.length > 1) { this._logger.error('[_fetchBerliozAgentTask] ', containers); throw new Error('Too many Berlioz Agents found.'); } var container = containers[0]; this._berliozAgentExternalIpAddress = this.getDockerHost(); this._berliozAgentExternalPort = _.get(container, 'NetworkSettings.Ports["' + this.agentPort + '/tcp"][0].HostPort', null); this._logger.info('[_fetchBerliozAgentTask] BerliozAgentIpAddress: ', this._berliozAgentExternalIpAddress); this._logger.info('[_fetchBerliozAgentTask] BerliozAgentExternalPort: ', this._berliozAgentExternalPort); }); } getDockerHost() { return this._docker.host; // var val = this._environment.getValue('BERLIOZ_DOCKER_HOST'); // if (val) // { // return val; // } // return '127.0.0.1'; } _deployTaskMetadata() { this._taskMetaStore = new TaskMetadataStore(this, this._logger.sublogger('TaskMetaStore'), this._configMeta, this.clusterEntity, this._currentConfig); return Promise.resolve() .then(() => this._taskMetaStore.extract()) .then(() => this._outputTaskMetadataRepos()) .then(() => this._taskMetaStore.deploy()) ; } _setupEndpointsInStore(name, dict) { if (!dict || _.keys(dict).length == 0) { this._repoStore.delete(name, [this.clusterName]); } else { this._repoStore.set(name, [this.clusterName], dict); } } _outputTaskMetadataRepos() { for(var name of this._taskMetaStore.repoStore.repos) { var repo = this._taskMetaStore.repoStore.getRepository(name); this.setSingleStageData('taskmeta-' + name, repo); } return this._taskMetaStore.outputRepositories(); } _outputOtherConfig() { return Promise.resolve() .then(() => this.nativeProcessor.debugWriteConfig()) ; } _cloneFromCurrentConfig() { return Promise.resolve() .then(() => { if (this._currentConfig) { var tasksToClone = []; this._clusterProcessor.extractTasksToClone(this._currentConfig, tasksToClone); return Promise.serial(_.values(tasksToClone), x => { this._logger.info('[_cloneFromCurrentConfig] %s', x.dn); return this._desiredConfig.section('task').cloneSingleItemFrom(x, true); }) } }) } _constructDesiredConfig() { this._nativeProcessor.setupDesiredConfig(this._desiredConfig); this._logger.info('***********************************************************'); this._logger.info('*************** CONSTRUCT DESIRED *************************'); this._logger.info('***********************************************************'); return Promise.resolve() .then(() => this._cloneFromCurrentConfig()) .then(() => this.nativeProcessor.constructBaseConfig()) .then(() => this._clusterProcessor.constructConfig(this._desiredConfig)) ; } _constructUndeployConfig() { return Promise.resolve() .then(() => this.nativeProcessor.constructBaseConfig()) } _finalizeSetup() { return Promise.resolve() .then(() => { if (this._clusterProcessor) { return this._clusterProcessor.finalizeSetup(); } }); } _reserveNetworkResources() { this._reservedHostPorts = {}; this._taskBindings = {}; return this._docker.listContainers() .then(containers => Promise.serial(containers, x => this._reserveContainerResources(x))) .then(() => this._outputDataToFile('initial_reservedHostPorts', this._reservedHostPorts)) .then(() => this._outputDataToFile('initial_taskBindings', this._taskBindings)) ; } _cleanupMetadataProcessor() { this._logger.info('[_cleanupMetadataProcessor] ...') return Promise.resolve() .then(() => this._metadataProcessor.cleanup()); } _massageKeyAliases() { if (!this._currentConfig) { return; } return this._clusterProcessor._massageKeyAliases(this._currentConfig); } _outputDataToFile(name, data) { return this._logger.outputFile(this._iterationNumber + '_' + name + '.json', data); } _reserveContainerResources(container) { var kind = this.getContainerLabel(container, 'berlioz:kind'); var naming = this.parseContainerTaskNaming(container); var itemDn = null; if (kind && naming) { itemDn = this._configMeta.constructDn(kind, naming); } for(var binding of this.parseContainerPortBingings(container)) { this._reservedHostPorts[binding.hostPort] = true; if (itemDn) { this.reserveBindingForTask(itemDn, binding.protocol, binding.port, binding.hostPort); } } } reserveBindingForTask(taskDn, protocol, port, hostPort) { this._logger.info('[reserveBindingForTask] task: %s, protocol: %s, port: %s => %s', taskDn, protocol, port, hostPort); port = parseInt(port); hostPort = parseInt(hostPort); if (!this._taskBindings[taskDn]) { this._taskBindings[taskDn] = {}; } if (!this._taskBindings[taskDn][protocol]) { this._taskBindings[taskDn][protocol] = {}; } this._taskBindings[taskDn][protocol][port] = hostPort; } fetchTaskHostPort(taskDn, protocol, port) { this._logger.info('[fetchTaskHostPort] task: %s, protocol: %s, port: %s', taskDn, protocol, port); port = parseInt(port); var hostPort = this._fetchCurrentBindingForTask(taskDn, protocol, port); if (hostPort != null) { return hostPort; } for(hostPort = 40000; hostPort < 50000; hostPort++) { if (!this._reservedHostPorts[hostPort]) { this._reservedHostPorts[hostPort] = true; this.reserveBindingForTask(taskDn, protocol, port, hostPort); return hostPort; } } } _fetchCurrentBindingForTask(taskDn, protocol, port) { if (!this._taskBindings[taskDn]) { return null; } if (!this._taskBindings[taskDn][protocol]) { return null } if (!this._taskBindings[taskDn][protocol][port]) { return null; } return this._taskBindings[taskDn][protocol][port]; } getContainerLabel(obj, name) { if (!obj.Config) { return null; } var value = obj.Config.Labels[name]; return value; } parseContainerTaskNaming(obj) { var kind = this.getContainerLabel(obj, 'berlioz:kind'); var naming = null; if (kind == 'task') { naming = [ this.getContainerLabel(obj, 'berlioz:cluster'), this.getContainerLabel(obj, 'berlioz:sector'), this.getContainerLabel(obj, 'berlioz:service'), parseInt(this.getContainerLabel(obj, 'berlioz:identity')) ]; } else if (kind == 'load-balancer') { naming = [ this.getContainerLabel(obj, 'berlioz:cluster'), this.getContainerLabel(obj, 'berlioz:sector'), this.getContainerLabel(obj, 'berlioz:service'), this.getContainerLabel(obj, 'berlioz:endpoint') ]; } if (!naming) { return null; } if (_.some(naming, x => (x == null))) { return null; } return naming; } autoconfigAwsObject(item, action) { if (!this._metaContext.aws) { this._screen.error('AWS profile is not set up. Cannot configure %s', item.dn); this._screen.error('Please setup AWS profile using "berlioz local account --profile <value>" command.'); this._screen.info() return false; } return true; } parseContainerPortBingings(obj) { var bindings = []; for(var bindingStr of _.keys(obj.HostConfig.PortBindings)) { var hostPort = obj.HostConfig.PortBindings[bindingStr][0].HostPort; var i = bindingStr.indexOf('/'); var port = bindingStr.substring(0, i); var protocol = bindingStr.substring(i + 1); bindings.push({ protocol: protocol, port: port, hostPort: hostPort }); } return bindings; } getContainerIp(task) { if (!task) { return null; } if (!task.obj) { return null; } return _.get(task.obj, "NetworkSettings.Networks.berlioz.IPAddress", null); } saveEncryptionKeys(allKeys) { this._logger.info('---- saveEncryptionKeys: ', allKeys) this._allEncryptionKeys = allKeys; } findEncryptionKey(tags) { this._logger.info('---- findEncryptionKey. tags: ', tags) if (!this._allEncryptionKeys) { return null; } var myKeys = this._allEncryptionKeys.filter(x => this._kmsHasTags(x, tags)); if (myKeys.length == 0) { return null; } var existingKeys = myKeys.filter(x => x.KeyState != 'PendingDeletion'); if (existingKeys.length > 0) { return _.head(existingKeys); } return _.head(myKeys); } _kmsHasTags(keyObj, tags) { for(var tag of _.keys(tags)) { if (tags[tag] != keyObj.Tags[tag]) { return false; } } return true; } getGcpServiceAPIs() { return this._gcpServiceAPIs; } _preSetup() { this._customModelsDirLocations = []; this._nativeProcessor = this._berliozCommon.newNativeProcessor(this._logger, this); this._nativeProcessor.setupRepositories(this._repositories); return Promise.resolve() .then(() => this._setupGcp()) .then(() => this._setupAws()) } _getMyPublicIp() { if (this._myPublicIp) { return Promise.resolve(this._myPublicIp); } const publicIp = require('public-ip'); return publicIp.v4() .then(result => { this._logger.info("[_getMyPublicIp] MY IP: ", result) this._myPublicIp = result; return this._myPublicIp; }); } _setupGcp() { if (!this.hasGcpProvider) { return; } this._gcpMandatoryServiceAPIs = [ 'cloudresourcemanager.googleapis.com' ]; this._gcpServiceAPIs = [ 'firestore.googleapis.com', 'datastore.googleapis.com', 'sqladmin.googleapis.com', 'storage-api.googleapis.com', 'pubsub.googleapis.com' // 'cloudresourcemanager.googleapis.com', // 'cloudfunctions.googleapis.com', // 'cloudkms.googleapis.com' ] this._customModelsDirLocations.push(this._berliozCommon.getModelsDir('gcp')); var provider = this._providers['gcp']; this._gcpClient = provider.client; this._metaContext.gcp = this.gcpClient; this._metaContext.sourceRegion = this.gcpClient.sourceRegion; this._metaContext.shortSourceRegion = _.replaceAll(this.gcpClient.sourceRegion, '-', ''); this._metaContext.region = this.gcpClient.region; this._metaContext.shortRegion = _.replaceAll(this.gcpClient.region, '-', ''); this._metaContext.zone = this.gcpClient.zone; this._metaContext.gcpAccountId = provider.config.credentials.project_id; this._nativeProcessor.setupProviderPeerConfigHandler('gcp', (item) => { var resolvedItem = item.resolved; if (resolvedItem) { if (resolvedItem.meta.name == 'gcp-sql') { if (resolvedItem.runtime) { if (resolvedItem.runtime.host) { return { host: resolvedItem.runtime.host } } } return {}; } } return { credentials: { client_email: provider.config.credentials.client_email, private_key: provider.config.credentials.private_key }, projectId: provider.config.credentials.project_id }; }); this._nativeProcessor.setupCustomHandler('gcp-sql', ({item}) => { return this._getMyPublicIp() .then(result => { this._logger.info('[setupCustomHandler] %s...', item.dn, item.config) item.config.config.settings['ipConfiguration'] = { ipv4Enabled: true, authorizedNetworks: [ { "kind": "sql#aclEntry", "value": result, "name": "local" } ] } }) }); var nativeProcessorScope = { metaContext: this._metaContext, providerKind: 'gcp', deployment: this.deploymentName, gcpAccountId: provider.config.credentials.project_id, sourceRegion: this._metaContext.sourceRegion, shortSourceRegion: this._metaContext.shortSourceRegion, region: this._metaContext.region, shortRegion: this._metaContext.shortRegion, zone: this.gcpClient.zone, projectId: provider.config.credentials.project_id, gcp: this._gcpClient, gcpMandatoryServiceAPIs: this._gcpMandatoryServiceAPIs } return Promise.resolve() .then(() => this._nativeProcessor.setupScope(nativeProcessorScope)) .then(() => this._nativeProcessor.init()) } _setupAws() { if (!this.hasAwsProvider) { return; } var provider = this._providers['aws']; if (provider.config.profile) { this._screen.info('Using AWS profile: %s', provider.config.profile); } else { this._screen.warn('No AWS profile provided. AWS native resources like DynamoDB and Kinesis would not be provisioned.'); this._screen.warn('Set up AWS profile using \"berlioz local account\".'); } this._awsClient = provider.client this._metaContext.aws = this.awsClient; this._metaContext.region = this.awsClient.region; this._metaContext.shortRegion = _.replaceAll(this.awsClient.region, '-', ''); this._metaContext.zone = this.awsClient.region; this._nativeProcessor.setupProviderPeerConfigHandler('aws', (item) => { return { region: provider.config.region, credentials: provider.config.credentials }; }); this._nativeProcessor.setupScope({ providerKind: 'gcp', deployment: this.deploymentName, // awsAccountId: provider.config.credentials.project_id, region: provider.config.region }) } _setupDockerNetwork() { return Promise.resolve() .then(() => this._setupContainerNetwork()) .then(() => this._fetchContainerAddresses()) } allocateContainerAddress() { for(var x = ip.toLong(this._networkSubnet.firstAddress) + 1; x <= ip.toLong(this._networkSubnet.lastAddress); x++) { var addressStr = ip.fromLong(x); if (!this._usedIpAddresses[addressStr]) { this._usedIpAddresses[addressStr] = true; return addressStr; } } throw new Error('Could not allocate address.'); } _setupContainerNetwork() { return this._docker.fetchNetwork("berlioz") .then(network => { this._networkSubnet = ip.cidrSubnet(network.IPAM.Config[0].Subnet); this._logger.info("Network: ", this._networkSubnet) }) } _fetchContainerAddresses() { return this._docker.listContainers() .then(results => { return results.map(x => _.get(x, "NetworkSettings.Networks.berlioz.IPAddress", null)) }) .then(results => { return results.filter(x => x) }) .then(results => { this._usedIpAddresses = _.makeDict(results, x => x, x => true); this._logger.info("Used Addresses: ", this._usedIpAddresses) // throw new Error(JSON.stringify(this._usedIpAddresses)) }); } getContainerLabels(obj) { if (!obj) { return {}; } var labels = {}; for(var key of _.keys(obj.Config.Labels)) { if (_.startsWith(key, 'berlioz:')) { labels[key] = obj.Config.Labels[key]; } } return labels; } getServiceAccountItem(email) { return null; } splitNaming(name, level, result) { if (!result) { result = []; } if (level === undefined) { level = 1; } if (level == 0) { if (name.length > 0) { result.push(name); } return result; } var index = name.indexOf('-'); if (index != -1) { result.push(name.substr(0, index)); return this.splitNaming(name.substr(index + 1), level - 1, result); } else { if (name.length > 0) { result.push(name); } return result; } } } module.exports = LocalProcessor;