├── docs └── cli.png ├── .releaserc.yml ├── license-checker-config.json ├── LICENSE.txt ├── README.md ├── src ├── nodes │ ├── peer.ts │ ├── orderer.ts │ ├── ca.ts │ └── node.ts ├── docker-helper.ts ├── index.ts ├── helpers.ts └── helpers.spec.ts ├── .travis.yml ├── tslint.json ├── .gitignore ├── package.json └── tsconfig.json /docs/cli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledgendary/blockchain-node-generator/alpha/docs/cli.png -------------------------------------------------------------------------------- /.releaserc.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | - +([0-9])?(.{+([0-9]),x}).x 3 | - name: alpha 4 | prerelease: true 5 | - name: beta 6 | prerelease: true 7 | - name: master 8 | 9 | verifyConditions: 10 | - "@semantic-release/github" 11 | - "@semantic-release/npm" 12 | generateNotes: [] 13 | prepare: [] 14 | publish: [] 15 | success: [] 16 | fail: [] 17 | -------------------------------------------------------------------------------- /license-checker-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": [".git/**", "coverage/**", ".DS_Store", "**/*.md", "**/*.yaml", "**/*.yml", ".releaserc"], 3 | "license": "LICENSE.txt", 4 | "licenseFormats": { 5 | "dotfile": { 6 | "eachLine": { 7 | "prepend": "# " 8 | } 9 | } 10 | }, 11 | "trailingWhitespace": "TRIM" 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Licensed under the Apache License, Version 2.0 (the "License"); 2 | you may not use this file except in compliance with the License. 3 | You may obtain a copy of the License at 4 | 5 | http://www.apache.org/licenses/LICENSE-2.0 6 | 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IBM Blockchain Platform node file generator 2 | 3 | blockchain-node-generator is an npm module that generates node files (.json) which are compatible with the IBM Blockchain Platform Visual Studio Code extension from local Docker networks. 4 | 5 | ## Install 6 | 7 | ```bash 8 | npm install -g blockchain-node-generator 9 | ``` 10 | 11 | ## Usage 12 | 13 | The module can be used with cli parameters 14 | 15 | ```bash 16 | blockchain-node-generator [--networkName=node_default] 17 | ``` 18 | 19 | Or with a cli interface 20 | 21 | ![How to use the CLI](docs/cli.png) 22 | 23 | This project is in no way affiliated with IBM 24 | 25 | ## Import into the IBM Blockchain Platform extension for Visual Studio Code 26 | 27 | Find more information [here](https://github.com/IBM-Blockchain/blockchain-vscode-extension#json-node-files) 28 | -------------------------------------------------------------------------------- /src/nodes/peer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | import { Node } from './node'; 15 | import { ContainerInfo, Container } from 'dockerode'; 16 | 17 | export class Peer extends Node { 18 | constructor(container: Container, containerInfo: ContainerInfo) { 19 | super(container, containerInfo, 'CORE_PEER_LOCALMSPID', 'CORE_PEER_TLS_ENABLED', 'CORE_PEER_TLS_ROOTCERT_FILE'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/nodes/orderer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | import { Container, ContainerInfo } from 'dockerode'; 15 | import { Node } from './node'; 16 | 17 | export class Orderer extends Node { 18 | constructor(container: Container, containerInfo: ContainerInfo) { 19 | super(container, containerInfo, 'ORDERER_GENERAL_LOCALMSPID', 'ORDERER_GENERAL_TLS_ENABLED', 'ORDERER_GENERAL_TLS_CERTIFICATE'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - lts/* 4 | 5 | deploy: 6 | - provider: script 7 | skip_cleanup: true 8 | script: 9 | - npm run semantic-release 10 | on: 11 | all_branches: true 12 | 13 | env: 14 | global: 15 | - secure: XTI8OI/S/J93sHGNw6jX6HShC4o3Tcqb0uZYMXXbO2Yk57UmPtUgOek0tYkG94nnO7O8pLnvPk8vCfeZhuW87KExzrES17xLHUkaFOgcgREPteoCY0tYMw+uSFLRz2XzKXN59s2OAbYpwt1K7NydjzH8N1ahRYpJLf6cvWJTkUucck6VJ9UPQaXpqHOZ7Zt3zhL7drpK+GuCnB2IFkg8dQixGSCtRnlEH1jpSqAILzY4OEn7rSbWZ6BdXNHwiZ0Axp/zY3WvCAeon1hChZztWo4tGzbQdnBQHaivbfrO81i/KQZspwLbj0oFhX4sq2qk+vhVqGpU7AqflUrtCNygm1N5bHrBjatLzDdBp4IFpD5zSFHtSGqP5cGJN42Ga661msCueHSOagP79yjIf4TbzcUVtBLR5mcrhC9P3kbQHXyPXf4hpYBB9/daXVGy+mTF6yc7EvqoojuFGhVpQjmnzzjIKLTqNG94X/4qf+cr4ug1FuT6725kY06gIv1P8aQ8PWZsLDUy8wSxPY1VEHHnumKs2O9UMFd9M1/0OKBi9yeNAW27n6nLJsC9umKdmEPHCUVjRBOmFcrlTk9ISROml1gYKMyJ4swnRqBJN5LUVbV4DiHhhMohZQ3F3NIzpv3TNuLSJ6eryO9OFx3w8j90/vANstUhWx1BlbQPtrFFToI= 16 | - secure: PT3YE6wHPLCoksOc05YLlvzARrX+e3qz1uVIR8itxobzNJwxxkxMv5QB8QycXHCea7jn+ii3iL04Syd3aOOiMA1TLWC1Xxd5kcS08XJuahWQkOPVBFVkeI+1XHIv9jFmJNdKFMkXYLcDN/vcxxTjUj2K5eZnOr9uuLa8kDV3j2TDvhvnbkgfXkR8QIzRcd0tdxNQfT12XTMM/oYkCtIoBoEZ4KoKBLpQQbqwspFlFJUFiE+tqjFpGJ4eH4mn4nAMzbLsIyhDG3LQKz+HQHdeIpTHkSonS8gou3SYY6OfWbEHcR3dOLkuw2Dm3k6bk9aBcDQiJFAPBnPb7fZ40Qgk+JWGj/4JjhDNPavbB8B2AhaeEVw2jpCJ5wN3PBlm0K4oqTgY9MhA/vGoDNVcUJDaQ5416uK7fz2+Jn/9uI0Be1/feuMFebX93EoPj/59ammJ3u1uOXMLWufhzfRQEZRLYI2HVBV2ofPQa4pv+J8FMdtsQ1m8AuDw3Pa6R5Rxb/c+vmNYRJb+tiStdybaXDF1Wpuvpw6WR3OTW/i1KvgYs8APYxKf78CCWOQdsHn/68Z6KU+S2v9rw04/quvi5MxdGhiluEKWzFAa0l39KdcpAJTbTYM6F+FkWOEQ92UTsI+Oho1xNQRi2hMC2nNeNO9MyEKP2DRLZuNYG5OS3fDo5HQ= 17 | -------------------------------------------------------------------------------- /src/nodes/ca.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | import { Node } from './node'; 15 | import { Container, ContainerInfo } from 'dockerode'; 16 | 17 | export class CA extends Node { 18 | constructor(container: Container, containerInfo: ContainerInfo) { 19 | super(container, containerInfo, '', 'FABRIC_CA_SERVER_TLS_ENABLED', 'FABRIC_CA_SERVER_TLS_CERTFILE'); 20 | } 21 | 22 | async generateConfig() { 23 | const def: any = await super.generateConfig(); 24 | def['ca_name'] = this.getContainerName(); 25 | return def; 26 | } 27 | 28 | async getMspId() { 29 | let name = super.getContainerName(); 30 | name = name.substr(name.indexOf('.') + 1); 31 | name = name.substr(0, name.indexOf('.')); 32 | name = kebabToPascal(name); 33 | return name.substr(0, name.length - 1) + name.charAt(name.length - 1).toUpperCase(); 34 | } 35 | } 36 | 37 | function kebabToPascal(str: string) { 38 | let strArray = str.split('-'); 39 | 40 | strArray = strArray.map((p: string) => { 41 | return p.charAt(0).toUpperCase() + p.substr(1); 42 | }); 43 | return strArray.join(''); 44 | } 45 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended" 4 | ], 5 | "rules": { 6 | "trailing-comma": [ 7 | false, 8 | { 9 | "multiline": "always", 10 | "singleline": "never" 11 | } 12 | ], 13 | "rulesDirectory": [ 14 | "./node_modules/codelyzer" 15 | ], 16 | "interface-name": [ 17 | false, 18 | "always-prefix" 19 | ], 20 | "indent": [ 21 | true, 22 | "spaces" 23 | ], 24 | "array-type": false, 25 | "prefer-for-of": false, 26 | "member-access": false, 27 | "no-console": [ 28 | false 29 | ], 30 | "forin": false, 31 | "max-line-length": false, 32 | "no-string-literal": false, 33 | "no-use-before-declare": true, 34 | "object-literal-sort-keys": false, 35 | "object-literal-shorthand": false, 36 | "ordered-imports": false, 37 | "quotemark": [ 38 | true, 39 | "single", 40 | "avoid-escape" 41 | ], 42 | "variable-name": [ 43 | true, 44 | "allow-leading-underscore", 45 | "allow-pascal-case", 46 | "ban-keywords", 47 | "check-format" 48 | ], 49 | "whitespace": [ 50 | true, 51 | "check-branch", 52 | "check-decl", 53 | "check-operator", 54 | "check-separator", 55 | "check-type" 56 | ], 57 | "no-floating-promises": true, 58 | "typedef": [ 59 | true 60 | ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/docker-helper.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | import Dockerode = require('dockerode'); 15 | 16 | export class DockerHelper { 17 | docker: Dockerode; 18 | 19 | constructor(socketPath: string) { 20 | this.docker = new Dockerode({socketPath}); 21 | } 22 | 23 | async list(): Promise { 24 | return new Promise((resolve, reject) => { 25 | this.docker.listContainers({all: true}, async (err, containerInfos) => { 26 | if (err) { 27 | return reject(err); 28 | } 29 | return resolve(containerInfos); 30 | }); 31 | }); 32 | } 33 | 34 | async getContainer(info: Dockerode.ContainerInfo) { 35 | return this.docker.getContainer(info.Id); 36 | } 37 | 38 | async getNetworks(): Promise { 39 | return new Promise((resolve, reject) => { 40 | this.docker.listNetworks({}, (err, networks: any) => { 41 | if (err) { 42 | return reject(err); 43 | } 44 | return resolve(networks); 45 | }); 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); 2 | # you may not use this file except in compliance with the License. 3 | # You may obtain a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # Logs 13 | logs 14 | *.log 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | lerna-debug.log* 19 | 20 | # Diagnostic reports (https://nodejs.org/api/report.html) 21 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 22 | 23 | # Runtime data 24 | pids 25 | *.pid 26 | *.seed 27 | *.pid.lock 28 | 29 | # Directory for instrumented libs generated by jscoverage/JSCover 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | coverage 34 | *.lcov 35 | 36 | # nyc test coverage 37 | .nyc_output 38 | 39 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 40 | .grunt 41 | 42 | # Bower dependency directory (https://bower.io/) 43 | bower_components 44 | 45 | # node-waf configuration 46 | .lock-wscript 47 | 48 | # Compiled binary addons (https://nodejs.org/api/addons.html) 49 | build/Release 50 | 51 | # Dependency directories 52 | node_modules/ 53 | jspm_packages/ 54 | 55 | # TypeScript v1 declaration files 56 | typings/ 57 | 58 | # TypeScript cache 59 | *.tsbuildinfo 60 | 61 | # Optional npm cache directory 62 | .npm 63 | 64 | # Optional eslint cache 65 | .eslintcache 66 | 67 | # Microbundle cache 68 | .rpt2_cache/ 69 | .rts2_cache_cjs/ 70 | .rts2_cache_es/ 71 | .rts2_cache_umd/ 72 | 73 | # Optional REPL history 74 | .node_repl_history 75 | 76 | # Output of 'npm pack' 77 | *.tgz 78 | 79 | # Yarn Integrity file 80 | .yarn-integrity 81 | 82 | # dotenv environment variables file 83 | .env 84 | .env.test 85 | 86 | # parcel-bundler cache (https://parceljs.org/) 87 | .cache 88 | 89 | # next.js build output 90 | .next 91 | 92 | # nuxt.js build output 93 | .nuxt 94 | 95 | # gatsby files 96 | .cache/ 97 | public 98 | 99 | # vuepress build output 100 | .vuepress/dist 101 | 102 | # Serverless directories 103 | .serverless/ 104 | 105 | # FuseBox cache 106 | .fusebox/ 107 | 108 | # DynamoDB Local files 109 | .dynamodb/ 110 | 111 | # TernJS port file 112 | .tern-port 113 | 114 | .DS_Store 115 | dist/ 116 | env.json 117 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blockchain-node-generator", 3 | "version": "0.0.1-beta", 4 | "description": "CLI tool that generates node files from a local network for IBM Blockchain Platform", 5 | "main": "dist/index.js", 6 | "bin": { 7 | "blockchain-node-generator": "dist/index.js" 8 | }, 9 | "scripts": { 10 | "test": "npm run lint && npm run unit", 11 | "unit": "nyc mocha -r ts-node/register 'src/**/*.spec.ts'", 12 | "lint": "npm run licensecheck && tslint -c tslint.json 'src/**/*.ts'", 13 | "build": "tsc", 14 | "build:watch": "tsc -w", 15 | "prepare": "npm run build", 16 | "licensecheck": "license-check-and-add check", 17 | "licenseadd": "license-check-and-add add", 18 | "start": "ts-node src/index.ts", 19 | "semantic-release": "semantic-release" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/liam-grace/blockchain-node-generator.git" 24 | }, 25 | "keywords": [ 26 | "ibm", 27 | "blockchain", 28 | "platform", 29 | "hyperledger", 30 | "fabric", 31 | "vscode", 32 | "extension" 33 | ], 34 | "author": "liam-grace", 35 | "license": "Apache-2.0", 36 | "bugs": { 37 | "url": "https://github.com/liam-grace/blockchain-node-generator/issues" 38 | }, 39 | "homepage": "https://github.com/liam-grace/blockchain-node-generator#readme", 40 | "devDependencies": { 41 | "@semantic-release/github": "^5.6.0-beta.2", 42 | "@semantic-release/npm": "^6.0.0-beta.1", 43 | "@types/chai-as-promised": "^7.1.2", 44 | "@types/dockerode": "^2.5.20", 45 | "@types/inquirer": "^6.5.0", 46 | "@types/mocha": "^5.2.7", 47 | "@types/node": "^12.7.9", 48 | "@types/sinon": "^7.5.0", 49 | "@types/sinon-as-promised": "^4.0.12", 50 | "@types/sinon-chai": "^3.2.3", 51 | "@types/yargs": "^13.0.3", 52 | "chai": "^4.2.0", 53 | "chai-as-promised": "^7.1.1", 54 | "istanbul": "^0.4.5", 55 | "license-check-and-add": "^3.0.3", 56 | "mocha": "^6.2.1", 57 | "nyc": "^14.1.1", 58 | "semantic-release": "^16.0.0-beta.25", 59 | "sinon": "^7.5.0", 60 | "sinon-as-promised": "^4.0.3", 61 | "sinon-chai": "^3.3.0", 62 | "standard-version": "^7.0.0", 63 | "ts-node": "^8.4.1", 64 | "tslint": "^5.20.0", 65 | "typescript": "^3.6.3" 66 | }, 67 | "dependencies": { 68 | "@types/source-map-support": "^0.5.3", 69 | "dockerode": "^3.0.2", 70 | "inquirer": "^7.0.0", 71 | "source-map-support": "^0.5.13", 72 | "yargs": "^14.2.0" 73 | }, 74 | "nyc": { 75 | "require": [ 76 | "ts-node/register" 77 | ], 78 | "extension": [ 79 | ".ts" 80 | ], 81 | "exclude": [ 82 | "coverage/**", 83 | "dist/**", 84 | "**/*.spec.ts", 85 | "test/**" 86 | ], 87 | "reporter": [ 88 | "text-summary", 89 | "html" 90 | ], 91 | "all": true, 92 | "check-coverage": true, 93 | "statements": 26.44, 94 | "branches": 32.81, 95 | "functions": 29.51, 96 | "lines": 28.05 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/nodes/node.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | import { getContainerImageType, getContainerName, getMspId, getPeerTLSCert, isTls, getContainerAddress, getProtocol, getNodeType } from '../helpers'; 15 | import { ContainerInfo, Container } from 'dockerode'; 16 | 17 | export class Node { 18 | container: Container; 19 | containerInfo: ContainerInfo; 20 | mspIDEnvVar: string; 21 | isTLSEnabledEnvVar: string; 22 | tlsCertPathEnvVar: string; 23 | 24 | constructor(container: Container, containerInfo: ContainerInfo, mspIDEnvVar: string, isTLSEnabledEnvVar: string, tlsCertPathEnvVar: string) { 25 | this.container = container; 26 | this.containerInfo = containerInfo; 27 | this.isTLSEnabledEnvVar = isTLSEnabledEnvVar; 28 | this.tlsCertPathEnvVar = tlsCertPathEnvVar; 29 | this.mspIDEnvVar = mspIDEnvVar; 30 | } 31 | 32 | async isTlsEnabled() { 33 | return isTls(this.container, this.isTLSEnabledEnvVar); 34 | } 35 | 36 | async getTlsRootCertificate() { 37 | return getPeerTLSCert(this.container, this.tlsCertPathEnvVar); 38 | } 39 | 40 | async getMspId() { 41 | return getMspId(this.container, this.mspIDEnvVar); 42 | } 43 | 44 | async getWalletName() { 45 | let mspId = await this.getMspId(); 46 | const mspIndex = mspId.indexOf('MSP'); 47 | if (mspIndex !== -1) { 48 | if (mspIndex > 0) { 49 | mspId = mspId.substr(0, mspIndex); 50 | } else { 51 | mspId = mspId.substr(mspIndex, mspId.length - 1); 52 | } 53 | } 54 | return mspId; 55 | } 56 | 57 | getIdentity() { 58 | return 'admin'; 59 | } 60 | 61 | getContainerName() { 62 | return getContainerName(this.containerInfo); 63 | } 64 | 65 | getContainerImageType() { 66 | return getContainerImageType(this.containerInfo); 67 | } 68 | 69 | getContainerAddress() { 70 | return getContainerAddress(this.containerInfo); 71 | } 72 | 73 | getProtocol() { 74 | return getProtocol(this.container, this.containerInfo, this.isTLSEnabledEnvVar); 75 | } 76 | 77 | getNodeType() { 78 | return getNodeType(this.containerInfo); 79 | } 80 | 81 | isRunningLocally() { 82 | const addressInfo = this.getContainerAddress(); 83 | if (addressInfo.indexOf('0.0.0.0') !== -1) { 84 | return true; 85 | } else { 86 | return false; 87 | } 88 | } 89 | 90 | async generateConfig() { 91 | const protocol = await this.getProtocol(); 92 | const addressInfo: any = this.getContainerAddress(); 93 | const fullAddress = protocol + '://' + addressInfo; 94 | const def: any = { 95 | name: this.getContainerName(), 96 | api_url: fullAddress, 97 | type: this.getNodeType(), 98 | msp_id: await this.getMspId(), 99 | pem: await this.getTlsRootCertificate(), 100 | wallet: await this.getWalletName(), 101 | identity: this.getIdentity() 102 | }; 103 | if (this.isRunningLocally()) { 104 | def['ssl_target_name_override'] = this.getContainerName(); 105 | } 106 | return def; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | import sourceMapSupport from 'source-map-support'; 17 | sourceMapSupport.install(); 18 | 19 | import { ContainerInfo } from 'dockerode'; 20 | import { writeFileSync } from 'fs'; 21 | import { DockerHelper } from './docker-helper'; 22 | import { CA } from './nodes/ca'; 23 | import { Orderer } from './nodes/orderer'; 24 | import { Peer } from './nodes/peer'; 25 | import inquirer = require('inquirer'); 26 | import yargs = require('yargs'); 27 | 28 | const argv: any = yargs.argv; 29 | const fileName = './env.json'; 30 | 31 | const defaultConfig = { 32 | socket: '/var/run/docker.sock', 33 | PEER_IMAGE_NAME: 'hyperledger/fabric-peer', 34 | ORDERER_IMAGE_NAME: 'hyperledger/fabric-orderer', 35 | CA_IMAGE_NAME: 'hyperledger/fabric-ca', 36 | env_path: argv.envFilePath ? argv.envFilePath : fileName, 37 | networkName: argv.networkName ? argv.networkName : 'node_default' 38 | }; 39 | 40 | const config = Object.assign({}, defaultConfig); 41 | 42 | async function main() { 43 | const newDocker = new DockerHelper(config.socket); 44 | await getDockerNetwork(newDocker); 45 | 46 | let list = await newDocker.list(); 47 | console.log(`\x1b[32m\u2714\x1b[0m Found ${list.length} docker containers.`); 48 | list = list.filter(filterByNetwork); 49 | const configs = []; 50 | 51 | const peers = list.filter(isPeer); 52 | const orderers = list.filter(isOrderer); 53 | const cas = list.filter(isCA); 54 | 55 | console.log(`\x1b[32m\u2714\x1b[0m Found ${peers.length} Peers.`); 56 | for (const containerInfo of peers) { 57 | const container = await newDocker.getContainer(containerInfo); 58 | const peer = new Peer(container, containerInfo); 59 | configs.push(peer.generateConfig()); 60 | } 61 | 62 | console.log(`\x1b[32m\u2714\x1b[0m Found ${orderers.length} Orderers.`); 63 | for (const containerInfo of orderers) { 64 | const container = await newDocker.getContainer(containerInfo); 65 | const peer = new Orderer(container, containerInfo); 66 | configs.push(peer.generateConfig()); 67 | } 68 | 69 | console.log(`\x1b[32m\u2714\x1b[0m Found ${cas.length} CAs.`); 70 | for (const containerInfo of cas) { 71 | const container = await newDocker.getContainer(containerInfo); 72 | const ca = new CA(container, containerInfo); 73 | configs.push(ca.generateConfig()); 74 | } 75 | console.debug('Waiting for promises to complete'); 76 | const nodes = await Promise.all(configs); 77 | console.log(`\x1b[32m\u2714\x1b[0m Promises complete. Writing to ${config.env_path}.`); 78 | writeFileSync(config.env_path, JSON.stringify(nodes)); 79 | console.log(`\x1b[32m\u2714\x1b[0m ${config.env_path} created.`); 80 | } 81 | 82 | function filterByNetwork(container: ContainerInfo) { 83 | const networkSettings = container.NetworkSettings; 84 | const networks = networkSettings.Networks; 85 | if (container.State !== 'running') { 86 | return false; 87 | } 88 | return !!networks[config.networkName]; 89 | } 90 | 91 | function isPeer(container: ContainerInfo): boolean { 92 | return container.Image.indexOf(config.PEER_IMAGE_NAME) !== -1; 93 | } 94 | 95 | function isOrderer(container: ContainerInfo): boolean { 96 | return container.Image.indexOf(config.ORDERER_IMAGE_NAME) !== -1; 97 | } 98 | 99 | function isCA(container: ContainerInfo): boolean { 100 | return container.Image.indexOf(config.CA_IMAGE_NAME) !== -1; 101 | } 102 | 103 | async function getDockerNetwork(docker: DockerHelper) { 104 | const networks = await docker.getNetworks(); 105 | const networkMap = new Map(); 106 | 107 | for (const net of networks) { 108 | networkMap.set(net.Name, net.Id); 109 | } 110 | let answers; 111 | if (!argv.networkName) { 112 | answers = await inquirer.prompt({ 113 | type: 'list', 114 | name: 'networkName', 115 | message: 'Which docker network do you want to use?', 116 | default: config.networkName, 117 | validate: (answer: string) => { 118 | return networkMap.has(answer); 119 | }, 120 | choices: Array.from(networkMap.keys()) 121 | } as any) as any; 122 | config.networkName = answers.networkName; 123 | } else { 124 | if (!networkMap.has(argv.networkName)) { 125 | throw new Error(`Network ${argv.networkName} does not exist`); 126 | } 127 | config.networkName = argv.networkName; 128 | } 129 | 130 | return answers; 131 | } 132 | 133 | main().catch(console.error); 134 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "./dist", /* Redirect output structure to the directory. */ 15 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "removeComments": true, /* Do not emit comments to output. */ 18 | // "noEmit": true, /* Do not emit outputs. */ 19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | 23 | /* Strict Type-Checking Options */ 24 | "strict": true, /* Enable all strict type-checking options. */ 25 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 26 | // "strictNullChecks": true, /* Enable strict null checks. */ 27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 28 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 29 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 30 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 31 | 32 | /* Additional Checks */ 33 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 34 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 35 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 36 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 37 | 38 | /* Module Resolution Options */ 39 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 40 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 41 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 42 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 43 | // "typeRoots": [], /* List of folders to include type definitions from. */ 44 | // "types": [], /* Type declaration files to be included in compilation. */ 45 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 46 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 47 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 48 | 49 | /* Source Map Options */ 50 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 51 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 52 | "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 53 | "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 54 | 55 | /* Experimental Options */ 56 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 57 | "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/helpers.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | import { ContainerInfo } from 'dockerode'; 15 | import Dockerode = require('dockerode'); 16 | 17 | const PEER_IMAGE_NAME = 'hyperledger/fabric-peer'; 18 | const ORDERER_IMAGE_NAME = 'hyperledger/fabric-orderer'; 19 | const CA_IMAGE_NAME = 'hyperledger/fabric-ca'; 20 | 21 | const nodeTypes: any = { 22 | [PEER_IMAGE_NAME]: 'fabric-peer', 23 | [ORDERER_IMAGE_NAME]: 'fabric-orderer', 24 | [CA_IMAGE_NAME]: 'fabric-ca' 25 | }; 26 | 27 | export async function isTls(container: Dockerode.Container, isTLSEnvVar: string): Promise { 28 | const command = `echo -n $${isTLSEnvVar}`; 29 | const result = await executeCommand(container, ['/bin/sh', '-c', command]); 30 | return result.toString('utf8') === 'true'; 31 | } 32 | 33 | export async function getProtocol(container: Dockerode.Container, containerInfo: ContainerInfo, isTLSEnvVar: string) { 34 | const isTLS = await isTls(container, isTLSEnvVar); 35 | const containerImageType = getContainerImageType(containerInfo); 36 | 37 | switch (containerImageType) { 38 | case PEER_IMAGE_NAME: 39 | case ORDERER_IMAGE_NAME: 40 | return isTLS ? 'grpcs' : 'grpc'; 41 | case CA_IMAGE_NAME: 42 | return isTLS ? 'https' : 'http'; 43 | } 44 | } 45 | 46 | export async function getPeerTLSCert(container: Dockerode.Container, tlsRootCertEnvVar: string): Promise { 47 | const command = `cat $${tlsRootCertEnvVar}`; 48 | console.log(container.id+" "+command); 49 | const file = await executeCommand(container, ['/bin/sh', '-c', command]); 50 | if (file.length === 0) { 51 | throw new Error('No cert file read'); 52 | } 53 | const pem = file.toString('base64'); 54 | return pem; 55 | } 56 | 57 | export async function getMspId(container: Dockerode.Container, mspIDEnvVar: string): Promise { 58 | const command = `echo -n $${mspIDEnvVar}`; 59 | const result = await executeCommand(container, ['/bin/sh', '-c', command]); 60 | return result.toString('utf8').trim(); 61 | } 62 | 63 | export function getContainerImageType(container: ContainerInfo): string { 64 | return (container as any).Image.split(':')[0]; 65 | } 66 | 67 | export function getContainerAddress(container: ContainerInfo): string { 68 | const ports = container.Ports; 69 | const port = ports[0]; 70 | let parts: string[] = []; 71 | if (port) { 72 | parts = [port.IP, port.PublicPort as any]; 73 | } 74 | return parts.join(':'); 75 | } 76 | 77 | export function getContainerName(container: ContainerInfo): string { 78 | const names: string[] = container.Names; 79 | const name: string = names[0]; 80 | return name.substr(1); 81 | } 82 | 83 | export function getNodeType(container: ContainerInfo): string { 84 | const name = getContainerImageType(container); 85 | return nodeTypes[name]; 86 | } 87 | 88 | export async function streamLog(stream: any): Promise { 89 | let data: string = ''; 90 | return new Promise((resolve, reject) => { 91 | stream.on('data', (d: any) => { 92 | d = d.filter((n: number) => { 93 | if ((n > 0x20 && n < 0x7F) || (n > 0xA1) || n === 0x0D || n === 0xA0 || n === 0x0A || n === 0x20) { 94 | return true; 95 | } else { 96 | return false; 97 | } 98 | }); 99 | data += d.toString('utf8'); 100 | }); 101 | stream.on('end', () => { 102 | if (!data) { 103 | resolve(Buffer.alloc(0)); 104 | } else { 105 | resolve(Buffer.from(data)); 106 | } 107 | }); 108 | stream.on('error', reject); 109 | }); 110 | } 111 | 112 | export async function executeCommand(container: Dockerode.Container, command: string[]): Promise { 113 | const options = { 114 | Cmd: command, 115 | AttachStdout: true, 116 | AttachStderr: false, 117 | statusCodes: { 118 | 200: true, 119 | 404: 'no such exec instance', 120 | 409: 'container is paused' 121 | } 122 | }; 123 | const data = await getData(container, options); 124 | 125 | return streamLog(data); 126 | } 127 | 128 | async function getExec(container: Dockerode.Container, options: any) { 129 | const exec: Dockerode.Exec = await new Promise((resolve, reject) => { 130 | container.exec(options, (err, ex) => { 131 | if (err) { 132 | return reject(err); 133 | } 134 | return resolve(ex); 135 | }); 136 | }); 137 | return exec; 138 | } 139 | 140 | async function getData(container: Dockerode.Container, options: any) { 141 | const exec = await getExec(container, options); 142 | const data = await new Promise((resolve, reject) => { 143 | // tslint:disable-next-line: no-floating-promises 144 | exec.start((err: any, stream: any) => { 145 | if (err) { 146 | return reject(err); 147 | } 148 | resolve(stream); 149 | }); 150 | }); 151 | return data; 152 | } 153 | -------------------------------------------------------------------------------- /src/helpers.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | import * as chai from 'chai'; 15 | import chaiAsPromised from 'chai-as-promised'; 16 | import { Container, ContainerInfo, Exec } from 'dockerode'; 17 | import * as sinon from 'sinon'; 18 | import sinonChai from 'sinon-chai'; 19 | import { Readable } from 'stream'; 20 | import * as helpers from './helpers'; 21 | 22 | chai.use(sinonChai); 23 | chai.use(chaiAsPromised); 24 | 25 | const expect = chai.expect; 26 | 27 | describe('Helpers', () => { 28 | let sandbox: sinon.SinonSandbox; 29 | let containerStub: sinon.SinonStubbedInstance; 30 | let execStub: sinon.SinonStubbedInstance; 31 | 32 | beforeEach(() => { 33 | sandbox = sinon.createSandbox(); 34 | containerStub = sandbox.createStubInstance(Container); 35 | execStub = sandbox.createStubInstance(Exec); 36 | }); 37 | 38 | afterEach(() => { 39 | sandbox.reset(); 40 | }); 41 | 42 | describe('#isTls', () => { 43 | let streamStub: Readable; 44 | 45 | beforeEach(() => { 46 | streamStub = new Readable(); 47 | streamStub._read = () => { /* do nothing */ }; 48 | }); 49 | 50 | it('should throw if the stream errors whilst data is being sent', async () => { 51 | containerStub.exec.yields(null, execStub); 52 | execStub.start.yields(null, streamStub); 53 | const promise = helpers.isTls(containerStub as Container, 'SOME_ENV_VAR'); 54 | 55 | await sleep(); 56 | streamStub.emit('data', Buffer.from('true')); 57 | streamStub.emit('error', new Error('Stream failed')); 58 | streamStub.emit('end'); 59 | await expect(promise).to.be.rejectedWith('Stream failed'); 60 | expect(containerStub.exec).to.have.been.calledWith({ 61 | AttachStderr: false, 62 | AttachStdout: true, 63 | Cmd: ['/bin/sh', '-c', 'echo -n $SOME_ENV_VAR'], 64 | statusCodes: { 200: true, 404: 'no such exec instance', 409: 'container is paused'} 65 | }); 66 | }); 67 | 68 | it('should throw if the stream errors', async () => { 69 | containerStub.exec.yields(null, execStub); 70 | execStub.start.yields(null, streamStub); 71 | const promise = helpers.isTls(containerStub as Container, 'SOME_ENV_VAR'); 72 | 73 | await sleep(); 74 | streamStub.emit('error', new Error('Stream failed')); 75 | streamStub.emit('end'); 76 | await expect(promise).to.be.rejectedWith('Stream failed'); 77 | expect(containerStub.exec).to.have.been.calledWith({ 78 | AttachStderr: false, 79 | AttachStdout: true, 80 | Cmd: ['/bin/sh', '-c', 'echo -n $SOME_ENV_VAR'], 81 | statusCodes: { 200: true, 404: 'no such exec instance', 409: 'container is paused'} 82 | }); 83 | }); 84 | 85 | it('should return the stringified result of a Buffer', async () => { 86 | containerStub.exec.yields(null, execStub); 87 | execStub.start.yields(null, streamStub); 88 | const promise = helpers.isTls(containerStub as Container, 'SOME_ENV_VAR'); 89 | 90 | await sleep(); 91 | streamStub.emit('data', Buffer.from('true')); 92 | streamStub.emit('end'); 93 | await expect(promise).to.eventually.equal(true); 94 | expect(containerStub.exec).to.have.been.calledWith({ 95 | AttachStderr: false, 96 | AttachStdout: true, 97 | Cmd: ['/bin/sh', '-c', 'echo -n $SOME_ENV_VAR'], 98 | statusCodes: { 200: true, 404: 'no such exec instance', 409: 'container is paused'} 99 | }); 100 | // tslint:disable-next-line:no-unused-expression 101 | expect(execStub.start).to.have.been.called; 102 | }); 103 | }); 104 | 105 | describe('#getProtocol', () => { 106 | let tlsStreamStub: Readable; 107 | beforeEach(() => { 108 | tlsStreamStub = new Readable(); 109 | tlsStreamStub._read = () => { /* do nothing */ }; 110 | }); 111 | 112 | it('should return grpcs if tls is on', async () => { 113 | containerStub.exec.onFirstCall().yields(null, execStub); 114 | execStub.start.onFirstCall().yields(null, tlsStreamStub); 115 | 116 | const promise = helpers.getProtocol( 117 | containerStub as Container, 118 | {Image: 'hyperledger/fabric-peer'} as ContainerInfo, 119 | 'SOME_ENV_VAR' 120 | ); 121 | 122 | await sleep(); 123 | tlsStreamStub.emit('data', Buffer.from('true')); 124 | tlsStreamStub.emit('end'); 125 | await expect(promise).to.eventually.equal('grpcs'); 126 | }); 127 | 128 | it('should return grpc if tls is off', async () => { 129 | containerStub.exec.onFirstCall().yields(null, execStub); 130 | execStub.start.onFirstCall().yields(null, tlsStreamStub); 131 | 132 | const promise = helpers.getProtocol( 133 | containerStub as Container, 134 | {Image: 'hyperledger/fabric-orderer'} as ContainerInfo, 135 | 'SOME_ENV_VAR' 136 | ); 137 | 138 | await sleep(); 139 | tlsStreamStub.emit('data', Buffer.from('false')); 140 | tlsStreamStub.emit('end'); 141 | await expect(promise).to.eventually.equal('grpc'); 142 | }); 143 | 144 | it('should return https if tls is on', async () => { 145 | containerStub.exec.onFirstCall().yields(null, execStub); 146 | execStub.start.onFirstCall().yields(null, tlsStreamStub); 147 | 148 | const promise = helpers.getProtocol( 149 | containerStub as Container, 150 | {Image: 'hyperledger/fabric-ca'} as ContainerInfo, 151 | 'SOME_ENV_VAR' 152 | ); 153 | 154 | await sleep(); 155 | tlsStreamStub.emit('data', Buffer.from('true')); 156 | tlsStreamStub.emit('end'); 157 | await expect(promise).to.eventually.equal('https'); 158 | }); 159 | 160 | it('should return http if tls is off', async () => { 161 | containerStub.exec.onFirstCall().yields(null, execStub); 162 | execStub.start.onFirstCall().yields(null, tlsStreamStub); 163 | 164 | const promise = helpers.getProtocol( 165 | containerStub as Container, 166 | {Image: 'hyperledger/fabric-ca'} as ContainerInfo, 167 | 'SOME_ENV_VAR' 168 | ); 169 | 170 | await sleep(); 171 | tlsStreamStub.emit('data', Buffer.from('false')); 172 | tlsStreamStub.emit('end'); 173 | await expect(promise).to.eventually.equal('http'); 174 | }); 175 | }); 176 | 177 | describe('#getPeerTLSCert', () => { 178 | let stream: Readable; 179 | beforeEach(() => { 180 | stream = new Readable(); 181 | stream._read = () => { /* do nothing */ }; 182 | }); 183 | 184 | it('should call execute with the correct parameters', async () => { 185 | containerStub.exec.onFirstCall().yields(null, execStub); 186 | execStub.start.onFirstCall().yields(null, stream); 187 | 188 | const promise = helpers.getPeerTLSCert(containerStub as Container, 'THIS_ENV_VAR'); 189 | await sleep(); 190 | const cert = Buffer.from('CERT'); 191 | stream.emit('data', cert); 192 | stream.emit('end'); 193 | await expect(promise).to.eventually.equal(cert.toString('base64')); 194 | 195 | expect(containerStub.exec).to.have.been.calledWith({ 196 | AttachStderr: false, 197 | AttachStdout: true, 198 | Cmd: ['/bin/sh', '-c', 'cat $THIS_ENV_VAR'], 199 | statusCodes: { 200: true, 404: 'no such exec instance', 409: 'container is paused'} 200 | }); 201 | }); 202 | 203 | it('should throw if no certificate file is read', async () => { 204 | containerStub.exec.onFirstCall().yields(null, execStub); 205 | execStub.start.onFirstCall().yields(null, stream); 206 | 207 | const promise = helpers.getPeerTLSCert(containerStub as Container, 'THIS_ENV_VAR'); 208 | await sleep(); 209 | const cert = Buffer.from(''); 210 | stream.emit('data', cert); 211 | stream.emit('end'); 212 | await expect(promise).to.be.rejectedWith('No cert file read'); 213 | }); 214 | }); 215 | 216 | describe('#getMspId', () => { 217 | let stream: Readable; 218 | beforeEach(() => { 219 | stream = new Readable(); 220 | stream._read = () => { /* do nothing */ }; 221 | }); 222 | 223 | it('should call execute with the correct parameters', async () => { 224 | containerStub.exec.onFirstCall().yields(null, execStub); 225 | execStub.start.onFirstCall().yields(null, stream); 226 | 227 | const promise = helpers.getMspId(containerStub as Container, 'MSP_ENV'); 228 | await sleep(); 229 | stream.emit('data', new Buffer('mspId')); 230 | stream.emit('end'); 231 | await expect(promise).to.eventually.equal('mspId'); 232 | 233 | expect(containerStub.exec).to.have.been.calledWith({ 234 | AttachStderr: false, 235 | AttachStdout: true, 236 | Cmd: ['/bin/sh', '-c', 'echo -n $MSP_ENV'], 237 | statusCodes: { 200: true, 404: 'no such exec instance', 409: 'container is paused'} 238 | }); 239 | }); 240 | }); 241 | 242 | describe('#getContainerImageType', () => { 243 | it('should get the image type', () => { 244 | const containerInfo = {Image: 'fabric-peer:1.4.4'} as ContainerInfo; 245 | expect(helpers.getContainerImageType(containerInfo)).to.equal('fabric-peer'); 246 | }); 247 | }); 248 | 249 | describe('#getContainerName', () => { 250 | it('should get the container name', () => { 251 | // Docker adds random character before the name 252 | const containerInfo = {Names: ['*ContainerName']} as ContainerInfo; 253 | expect(helpers.getContainerName(containerInfo)).to.equal('ContainerName'); 254 | }); 255 | 256 | it('should get the first container name', () => { 257 | const containerInfo = {Names: ['*ContainerName0', '*ContainerName1']} as ContainerInfo; 258 | expect(helpers.getContainerName(containerInfo)).to.equal('ContainerName0'); 259 | }); 260 | }); 261 | 262 | describe('#getNodeType', () => { 263 | it('should return the correct node type', () => { 264 | const containerInfo = {Image: 'hyperledger/fabric-peer:1.4.4'} as ContainerInfo; 265 | expect(helpers.getNodeType(containerInfo)).to.equal('fabric-peer'); 266 | }); 267 | }); 268 | 269 | describe('#getExec', () => { 270 | it('should throw if getting the exec fails', async () => { 271 | containerStub.exec.onFirstCall().yields(new Error('Exec fail'), null); 272 | 273 | await expect(helpers.getPeerTLSCert(containerStub as Container, 'MSP_ENV')).to.be.rejectedWith('Exec fail'); 274 | }); 275 | }); 276 | 277 | describe('#getData', () => { 278 | it('should throw if starting exec fails', async () => { 279 | containerStub.exec.onFirstCall().yields(null, execStub); 280 | execStub.start.onFirstCall().yields(new Error('Start fail'), null); 281 | 282 | await expect(helpers.getPeerTLSCert(containerStub as Container, 'MSP_ENV')).to.be.rejectedWith('Start fail'); 283 | }); 284 | }); 285 | 286 | describe('#getContainerAddress', () => { 287 | it('should return nothing if a port does not exist', () => { 288 | const containerInfo = {Ports: []}; 289 | expect(helpers.getContainerAddress(containerInfo as any)).to.equal(''); 290 | }); 291 | it('should return an array of port and IP', () => { 292 | const containerInfo = {Ports: [{IP: '0.0.0.0', PublicPort: '5000'}]}; 293 | expect(helpers.getContainerAddress(containerInfo as any)).to.equal('0.0.0.0:5000'); 294 | }); 295 | }); 296 | }); 297 | 298 | async function sleep(n = 1) { 299 | return new Promise((res: any) => setTimeout(res, n)); 300 | } 301 | --------------------------------------------------------------------------------