UNPKG

@ethersphere/bee-factory

Version:

Orchestration CLI for spinning up local development Bee cluster with Docker

407 lines (406 loc) 18.5 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Docker = exports.ContainerType = exports.CONTRACT_LABEL_KEY_PREFIX = exports.BLOCKCHAIN_VERSION_LABEL_KEY = exports.WORKER_COUNT = exports.DEFAULT_IMAGE_PREFIX = exports.DEFAULT_ENV_PREFIX = void 0; const dockerode_1 = __importDefault(require("dockerode")); const error_1 = require("./error"); exports.DEFAULT_ENV_PREFIX = 'bee-factory'; exports.DEFAULT_IMAGE_PREFIX = 'bee-factory'; const BLOCKCHAIN_IMAGE_NAME_SUFFIX = '-blockchain'; const QUEEN_IMAGE_NAME_SUFFIX = '-queen'; const WORKER_IMAGE_NAME_SUFFIX = '-worker'; const NETWORK_NAME_SUFFIX = '-network'; exports.WORKER_COUNT = 4; exports.BLOCKCHAIN_VERSION_LABEL_KEY = 'org.ethswarm.beefactory.blockchain-version'; exports.CONTRACT_LABEL_KEY_PREFIX = 'org.ethswarm.beefactory.contracts.'; var ContainerType; (function (ContainerType) { ContainerType["QUEEN"] = "queen"; ContainerType["BLOCKCHAIN"] = "blockchain"; ContainerType["WORKER_1"] = "worker1"; ContainerType["WORKER_2"] = "worker2"; ContainerType["WORKER_3"] = "worker3"; ContainerType["WORKER_4"] = "worker4"; })(ContainerType = exports.ContainerType || (exports.ContainerType = {})); class Docker { constructor(console, envPrefix, imagePrefix, repo) { Object.defineProperty(this, "docker", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "console", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "runningContainers", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "envPrefix", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "imagePrefix", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "repo", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.docker = new dockerode_1.default(); this.console = console; this.runningContainers = []; this.envPrefix = envPrefix; this.imagePrefix = imagePrefix; this.repo = repo; } get networkName() { return `${this.envPrefix}${NETWORK_NAME_SUFFIX}`; } get blockchainName() { return `${this.envPrefix}${BLOCKCHAIN_IMAGE_NAME_SUFFIX}`; } blockchainImage(blockchainVersion) { if (!this.repo) throw new TypeError('Repo has to be defined!'); return `${this.repo}/${this.imagePrefix}${BLOCKCHAIN_IMAGE_NAME_SUFFIX}:${blockchainVersion}`; } get queenName() { return `${this.envPrefix}${QUEEN_IMAGE_NAME_SUFFIX}`; } queenImage(beeVersion) { if (!this.repo) throw new TypeError('Repo has to be defined!'); return `${this.repo}/${this.imagePrefix}${QUEEN_IMAGE_NAME_SUFFIX}:${beeVersion}`; } workerName(index) { return `${this.envPrefix}${WORKER_IMAGE_NAME_SUFFIX}-${index}`; } workerImage(beeVersion, workerNumber) { if (!this.repo) throw new TypeError('Repo has to be defined!'); return `${this.repo}/${this.imagePrefix}${WORKER_IMAGE_NAME_SUFFIX}-${workerNumber}:${beeVersion}`; } createNetwork() { return __awaiter(this, void 0, void 0, function* () { const networks = yield this.docker.listNetworks({ filters: { name: [this.networkName] } }); if (networks.length === 0) { yield this.docker.createNetwork({ Name: this.networkName }); } }); } startBlockchainNode(blockchainVersion, options) { return __awaiter(this, void 0, void 0, function* () { if (options.fresh) yield this.removeContainer(this.blockchainName); yield this.pullImageIfNotFound(this.blockchainImage(blockchainVersion)); const container = yield this.findOrCreateContainer(this.blockchainName, { Image: this.blockchainImage(blockchainVersion), name: this.blockchainName, ExposedPorts: { '9545/tcp': {}, }, AttachStderr: false, AttachStdout: false, HostConfig: { PortBindings: { '9545/tcp': [{ HostPort: '9545' }] }, NetworkMode: this.networkName, }, }); this.runningContainers.push(container); const state = yield container.inspect(); // If it is already running (because of whatever reason) we are not spawning new node if (!state.State.Running) { yield container.start(); } else { this.console.info('The blockchain container was already running, so not starting it again.'); } }); } startQueenNode(beeVersion, options) { return __awaiter(this, void 0, void 0, function* () { if (options.fresh) yield this.removeContainer(this.queenName); yield this.pullImageIfNotFound(this.queenImage(beeVersion)); const contractAddresses = yield this.getContractAddresses(this.queenImage(beeVersion)); const container = yield this.findOrCreateContainer(this.queenName, { Image: this.queenImage(beeVersion), name: this.queenName, ExposedPorts: { '1633/tcp': {}, '1634/tcp': {}, '1635/tcp': {}, }, Tty: true, Cmd: ['start'], Env: this.createBeeEnvParameters(contractAddresses), AttachStderr: false, AttachStdout: false, HostConfig: { NetworkMode: this.networkName, PortBindings: { '1633/tcp': [{ HostPort: '1633' }], '1634/tcp': [{ HostPort: '1634' }], '1635/tcp': [{ HostPort: '1635' }], }, }, }); this.runningContainers.push(container); const state = yield container.inspect(); // If it is already running (because of whatever reason) we are not spawning new node. // Already in `findOrCreateContainer` the container is verified that it was spawned with expected version. if (!state.State.Running) { yield container.start(); } else { this.console.info('The Queen node container was already running, so not starting it again.'); } }); } startWorkerNode(beeVersion, workerNumber, queenAddress, options) { return __awaiter(this, void 0, void 0, function* () { if (options.fresh) yield this.removeContainer(this.workerName(workerNumber)); yield this.pullImageIfNotFound(this.workerImage(beeVersion, workerNumber)); const contractAddresses = yield this.getContractAddresses(this.workerImage(beeVersion, workerNumber)); const container = yield this.findOrCreateContainer(this.workerName(workerNumber), { Image: this.workerImage(beeVersion, workerNumber), name: this.workerName(workerNumber), ExposedPorts: { '1633/tcp': {}, '1634/tcp': {}, '1635/tcp': {}, }, Cmd: ['start'], Env: this.createBeeEnvParameters(contractAddresses, queenAddress), AttachStderr: false, AttachStdout: false, HostConfig: { NetworkMode: this.networkName, PortBindings: { '1633/tcp': [{ HostPort: (1633 + workerNumber * 10000).toString() }], '1634/tcp': [{ HostPort: (1634 + workerNumber * 10000).toString() }], '1635/tcp': [{ HostPort: (1635 + workerNumber * 10000).toString() }], }, }, }); this.runningContainers.push(container); const state = yield container.inspect(); // If it is already running (because of whatever reason) we are not spawning new node if (!state.State.Running) { yield container.start(); } else { this.console.info('The Queen node container was already running, so not starting it again.'); } }); } logs(target, outputStream, follow = false, tail) { return __awaiter(this, void 0, void 0, function* () { const { container } = yield this.findContainer(this.getContainerName(target)); if (!container) { throw new Error('Queen container does not exists, even though it should have had!'); } const logs = yield container.logs({ stdout: true, stderr: true, follow, tail }); if (!follow) { outputStream.write(logs); } else { logs.pipe(outputStream); } }); } stopAll(allWithPrefix = false, deleteContainers = false) { return __awaiter(this, void 0, void 0, function* () { const containerProcessor = (container) => __awaiter(this, void 0, void 0, function* () { try { yield container.stop(); } catch (e) { // We ignore 304 that represents that the container is already stopped if (e.statusCode !== 304) { throw e; } } if (deleteContainers) { yield container.remove(); } }); this.console.info('Stopping all containers'); yield Promise.all(this.runningContainers.map(containerProcessor)); if (allWithPrefix) { const containers = yield this.docker.listContainers({ all: true }); yield Promise.all(containers .filter(container => container.Names.filter(n => n.startsWith('/' + this.envPrefix)).length >= 1) .map(container => this.docker.getContainer(container.Id)) .map(containerProcessor)); } }); } getBlockchainVersionFromQueenMetadata(beeVersion) { return __awaiter(this, void 0, void 0, function* () { // Lets pull the Queen's image if it is not present yield this.pullImageIfNotFound(this.queenImage(beeVersion)); const queenMetadata = yield this.docker.getImage(this.queenImage(beeVersion)).inspect(); const version = queenMetadata.Config.Labels[exports.BLOCKCHAIN_VERSION_LABEL_KEY]; if (!version) { throw new Error('Blockchain image version was not found in Queen image labels!'); } return version; }); } getAllStatus() { return __awaiter(this, void 0, void 0, function* () { return { queen: yield this.getStatusForContainer(ContainerType.QUEEN), blockchain: yield this.getStatusForContainer(ContainerType.BLOCKCHAIN), worker1: yield this.getStatusForContainer(ContainerType.WORKER_1), worker2: yield this.getStatusForContainer(ContainerType.WORKER_2), worker3: yield this.getStatusForContainer(ContainerType.WORKER_3), worker4: yield this.getStatusForContainer(ContainerType.WORKER_4), }; }); } removeContainer(name) { return __awaiter(this, void 0, void 0, function* () { this.console.info(`Removing container with name "${name}"`); const { container } = yield this.findContainer(name); // Container does not exist so nothing to delete if (!container) { return; } yield container.remove({ v: true, force: true }); }); } findOrCreateContainer(name, createOptions) { return __awaiter(this, void 0, void 0, function* () { const { container, image: foundImage } = yield this.findContainer(name); if (container) { this.console.info(`Container with name "${name}" found. Using it.`); if (foundImage !== createOptions.Image) { throw new error_1.ContainerImageConflictError(`Container with name "${name}" found but it was created with different image or image version then expected!`, foundImage, createOptions.Image); } return container; } this.console.info(`Container with name "${name}" not found. Creating new one.`); try { return yield this.docker.createContainer(createOptions); } catch (e) { // 404 is Image Not Found ==> pull the image if (e.statusCode !== 404) { throw e; } this.console.info(`Image ${createOptions.Image} not found. Pulling it.`); yield this.pullImageIfNotFound(createOptions.Image); return yield this.docker.createContainer(createOptions); } }); } findContainer(name) { return __awaiter(this, void 0, void 0, function* () { const containers = yield this.docker.listContainers({ all: true, filters: { name: [name] } }); if (containers.length === 0) { return {}; } if (containers.length > 1) { throw new Error(`Found ${containers.length} containers for name "${name}". Expected only one.`); } return { container: this.docker.getContainer(containers[0].Id), image: containers[0].Image }; }); } getStatusForContainer(name) { return __awaiter(this, void 0, void 0, function* () { const foundContainer = yield this.findContainer(this.getContainerName(name)); if (!foundContainer.container) { return 'not-found'; } const inspectStatus = yield foundContainer.container.inspect(); if (inspectStatus.State.Running) { return 'running'; } return 'exists'; }); } getContainerName(name) { switch (name) { case ContainerType.BLOCKCHAIN: return this.blockchainName; case ContainerType.QUEEN: return this.queenName; case ContainerType.WORKER_1: return this.workerName(1); case ContainerType.WORKER_2: return this.workerName(2); case ContainerType.WORKER_3: return this.workerName(3); case ContainerType.WORKER_4: return this.workerName(4); default: throw new Error('Unknown container!'); } } createBeeEnvParameters(contractAddresses, bootnode) { const options = Object.assign({ 'warmup-time': '0', 'debug-api-enable': 'true', verbosity: '4', 'swap-enable': 'true', mainnet: 'false', 'swap-endpoint': `http://${this.blockchainName}:9545`, password: 'password', 'network-id': '4020', 'full-node': 'true', 'welcome-message': 'You have found the queen of the beehive...', 'cors-allowed-origins': '*', 'postage-stamp-start-block': '1' }, contractAddresses); if (bootnode) { options.bootnode = bootnode; } // Env variables for Bee has form of `BEE_WARMUP_TIME`, so we need to transform it. return Object.entries(options).reduce((previous, current) => { const keyName = `BEE_${current[0].toUpperCase().replace(/-/g, '_')}`; previous.push(`${keyName}=${current[1]}`); return previous; }, []); } pullImageIfNotFound(name) { return __awaiter(this, void 0, void 0, function* () { try { yield this.docker.getImage(name).inspect(); } catch (e) { this.console.info(`Image ${name} not found locally, pulling it.`); const pullStream = yield this.docker.pull(name); yield new Promise(res => this.docker.modem.followProgress(pullStream, res)); } }); } getContractAddresses(imageName) { return __awaiter(this, void 0, void 0, function* () { const imageMetadata = yield this.docker.getImage(imageName).inspect(); const contractAddresses = {}; // @ts-ignore: Dockerode typings does not have iterator even though it is a simple object for (const [labelKey, labelValue] of Object.entries(imageMetadata.Config.Labels)) { if (labelKey.startsWith(exports.CONTRACT_LABEL_KEY_PREFIX)) { contractAddresses[labelKey.replace(exports.CONTRACT_LABEL_KEY_PREFIX, '')] = labelValue; } } return contractAddresses; }); } } exports.Docker = Docker;