From 4751732c2f19a7279da4d897eb302702457a2c6f Mon Sep 17 00:00:00 2001 From: kidneyweak <35759909+kidneyweakx@users.noreply.github.com> Date: Thu, 1 Dec 2022 17:13:24 +0800 Subject: [PATCH] Update: Quorum backup and run (#67) * Add backup commands * Fix remove files and network issues * Uppercase to lowercase * DockerCompose up and down * Add 2.0.1 new features docs * Modify codeowner Co-authored-by: Brian Huang --- .github/CODEOWNERS | 2 +- CHANGELOG.md | 17 ++ README.md | 3 +- docs/quorum/COMMANDS.md | 52 +++++- docs/quorum/EXAMPLE.md | 38 ++++- package-lock.json | 151 +++++++++++++++++- package.json | 10 +- src/quorum/command/backup.ts | 11 ++ src/quorum/command/backup/export.ts | 50 ++++++ src/quorum/command/backup/import.ts | 41 +++++ src/quorum/command/explorer/create.ts | 2 +- src/quorum/command/network/create.ts | 149 +++++++++-------- src/quorum/command/network/delete.ts | 2 +- src/quorum/command/network/down.ts | 33 ++++ src/quorum/command/network/up.ts | 51 ++++++ src/quorum/instance/bdkFile.ts | 55 +++++-- .../instance/infra/InfraRunner.interface.ts | 1 + src/quorum/instance/infra/docker/runner.ts | 10 ++ src/quorum/instance/member.ts | 11 +- src/quorum/instance/validator.ts | 11 +- .../explorerDockerComposeYaml.ts | 2 +- .../docker-compose/memberDockerCompose.ts | 4 +- .../validatorDockerComposeYaml.ts | 6 +- src/quorum/service/backup.ts | 82 ++++++++++ src/quorum/service/network.ts | 39 ++++- src/util/error.ts | 1 + src/util/utils.ts | 4 + 27 files changed, 725 insertions(+), 113 deletions(-) create mode 100644 src/quorum/command/backup.ts create mode 100644 src/quorum/command/backup/export.ts create mode 100644 src/quorum/command/backup/import.ts create mode 100644 src/quorum/command/network/down.ts create mode 100644 src/quorum/command/network/up.ts create mode 100644 src/quorum/service/backup.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f5855713..8cc51bc7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @SecondDim @yujugrace @kth-tw @RuiSiang @rc56 +* @kth-tw @Pianochicken @kidneyweakx diff --git a/CHANGELOG.md b/CHANGELOG.md index e004a036..ddd7f202 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,23 @@ All notable changes to BDK project will be documented here. [2.0.0]: https://github.com/cathayddt/bdk/releases/tag/v2.0.0 +[2.0.1]: https://github.com/cathayddt/bdk/releases/tag/v2.0.1 + +## [2.0.1][2.0.1] - 2022-12- + +### new + +- New Command: `bdk quorum network up` +- New Command: `bdk quorum network down` +- New Command: `bdk quorum backup import` +- New Command: `bdk quorum backup export` +- New Commands API Docs +- Fix remove files and network issues + +### changed +- Refactor: `bdk quorum network create` ask to delete exist files before creating +- Refactor: `bdk quorum network delete` delete node files + ## [2.0.0][2.0.0] - 2022-10- ### new diff --git a/README.md b/README.md index 9e2b6965..04ce6729 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,10 @@ bdk fabric network create -i | Latest | Stable | | ---------------- | ---------------- | -| [v2.0.0][v2.0.0] | [v1.0.4][v1.0.4] | +| [v2.0.1][v2.0.1] | [v1.0.4][v1.0.4] | [v1.0.4]: https://github.com/cathayddt/bdk/releases/tag/v1.0.4 +[v2.0.1]: https://github.com/cathayddt/bdk/releases/tag/v2.0.1 [更新內容 (Changelog)](CHANGELOG.md) diff --git a/docs/quorum/COMMANDS.md b/docs/quorum/COMMANDS.md index 4477cef8..1b1d8f7b 100644 --- a/docs/quorum/COMMANDS.md +++ b/docs/quorum/COMMANDS.md @@ -6,6 +6,7 @@ - [Network](#network) - [Explorer](#explorer) +- [Backup](#backup) ## Network @@ -17,9 +18,7 @@ Description: 產生 Quorum network 所需的相關設定檔案 | --------------------- | :-----: | ------------------------------ | :------: | ------- | | --help | boolean | Show help | | | | --version | boolean | Show version number | | | -| -i, --interactive | boolean | 是否使用 Cathay BDK 互動式問答 | | | - -## Explorer +| -i, --interactive | boolean | 是否使用 Cathay BDK 互動式問答 | | | ### `bdk quorum network delete` @@ -30,6 +29,28 @@ Description: 刪除現有的 Quorum Network. | --help | boolean | Show help | | | | --version | boolean | Show version number | | | +### `bdk quorum network up` + +Description: 啟動現有的 Quorum Network. + +| Options | Type | Description | Required | Default | +| --------------------- | :-----: | ------------------------------ | :------: | ------- | +| --help | boolean | Show help | | | +| --version | boolean | Show version number | | | +| -i, --interactive | boolean | 是否使用 Cathay BDK 互動式問答 | | | +| -a, --all | boolean | 是否啟動所有節點 | | | + +### `bdk quorum network down` + +Description: 停止現有的 Quorum Network. + +| Options | Type | Description | Required | Default | +| --------------------- | :-----: | ------------------------------ | :------: | ------- | +| --help | boolean | Show help | | | +| --version | boolean | Show version number | | | + +## Explorer + ### `bdk quorum explorer create` Description: 產生 Quorum Explorer 所需的相關設定檔案 @@ -47,4 +68,27 @@ Description: 刪除現有的 Quorum Explorer. | Options | Type | Description | Required | Default | | --------------------- | :-----: | ------------------------------ | :------: | ------- | | --help | boolean | Show help | | | -| --version | boolean | Show version number | | | \ No newline at end of file +| --version | boolean | Show version number | | | + +## Backup + +### `bdk quorum backup export` + +Description: 匯出現有的 Quorum Network. + +| Options | Type | Description | Required | Default | +| --------------------- | :-----: | ------------------------------ | :------: | ------- | +| --help | boolean | Show help | | | +| --version | boolean | Show version number | | | +| -i, --interactive | boolean | 是否使用 Cathay BDK 互動式問答 | | | +| -a, --all | boolean | 是否備份所有資料 | | | + +### `bdk quorum backup import` + +Description: 匯入現有的 Quorum Network. + +| Options | Type | Description | Required | Default | +| --------------------- | :-----: | ------------------------------ | :------: | ------- | +| --help | boolean | Show help | | | +| --version | boolean | Show version number | | | +| -i, --interactive | boolean | 是否使用 Cathay BDK 互動式問答 | | | diff --git a/docs/quorum/EXAMPLE.md b/docs/quorum/EXAMPLE.md index 8760b120..8f29def2 100644 --- a/docs/quorum/EXAMPLE.md +++ b/docs/quorum/EXAMPLE.md @@ -3,11 +3,11 @@ ## 目錄 - [建立 Blockchain network](#建立-blockchain-network) -- [部署 Chaincode](#部署-chaincode) +- [備份還原 Node](#備份還原-node) ## 建立 Blockchain network -Quorum 無須做過多的前置設定,只需確認 bdk 套件是否安裝完成即可。 +Quorum 無須做過多的前置設定,只需確認 BDK 套件是否安裝完成即可 ```bash bdk hello @@ -23,7 +23,7 @@ bdk hello bdk quorum network create -i ``` -依序輸入 chain id (預設為1337)、validator 以及 member 的數量,以及填入自己的錢包,如無填入(選擇 false ),則會提供一組公私鑰來作為使用 Quorum 網路的帳號,該組帳號會在創始區塊擁有代幣。 +依序輸入 chain id (預設為1337)、validator 以及 member 的數量,以及填入自己的錢包,如無填入(選擇 false ),則會提供一組公私鑰來作為使用 Quorum 網路的帳號,該組帳號會在創始區塊擁有代幣 ### 建立 Blockscout Explorer @@ -33,4 +33,34 @@ bdk quorum network create -i bdk quorum explorer create -i ``` -輸入 port ,並稍待片刻,即可使用區塊鏈瀏覽器 \ No newline at end of file +輸入 port ,並稍待片刻,即可使用區塊鏈瀏覽器 + +## 備份還原 Node + +Quorum 網路各節點啟動後,即可做備份的動作,BDK 備份工具可根據使用者的需求備份個別或全部節點,並將備份壓縮檔存入 `~/.bdk/quorum/backup` 資料夾中,達成多台機器還原環境的功能 + +### 備份現有的 Node (擇一備份) + +```bash +bdk quorum backup export --interactive +``` + +### 備份所有的 Node + +```bash +bdk quorum backup export --all +``` + +### 還原 Node + +透過 `bdk backup` 指令還原節點,可在清單中選擇需要還原的備份檔 + +```bash +bdk quorum backup import -i +``` + +還原後需透過以下指令,來啟動該備份的節點 + +```bash +bdk quorum network up --all +``` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3741ea82..c5a64c09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cathayddt/bdk", - "version": "2.0.0", + "version": "2.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@cathayddt/bdk", - "version": "2.0.0", + "version": "2.0.1", "license": "Apache-2.0", "dependencies": { "deep-object-diff": "^1.1.0", @@ -19,6 +19,7 @@ "prompts": "^2.4.0", "rlp": "^3.0.0", "string-format": "^2.0.0", + "tar": "^6.1.12", "winston": "^3.3.3", "yargs": "^16.0.0" }, @@ -37,6 +38,7 @@ "@types/sinon": "^9.0.8", "@types/source-map-support": "^0.5.3", "@types/string-format": "^2.0.0", + "@types/tar": "^6.1.3", "@types/triple-beam": "^1.3.2", "@types/yargs": "^15.0.5", "ericlint": "^1.1.3", @@ -1625,6 +1627,16 @@ "integrity": "sha512-mMwtmgN0ureESnJ3SuMM4W9lsi4CgOxs43YxNo14SDHgzJ+OPYO3yM7nOTJTh8x5YICseBdtrySUbvxnpb+NYQ==", "dev": true }, + "node_modules/@types/tar": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/tar/-/tar-6.1.3.tgz", + "integrity": "sha512-YzDOr5kdAeqS8dcO6NTTHTMJ44MUCBDoLEIyPtwEn7PssKqUYL49R1iCVJPeiPzPlKi6DbH33eZkpeJ27e4vHg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "minipass": "^3.3.5" + } + }, "node_modules/@types/triple-beam": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz", @@ -3713,6 +3725,17 @@ "node": ">=10" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4965,6 +4988,29 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, + "node_modules/minipass": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.5.tgz", + "integrity": "sha512-rQ/p+KfKBkeNwo04U15i+hOwoVBVmekmm/HcfTkTN2t9pbQKCMm4eN5gFeqgrrSp/kH/7BYYhTIHOxGqzbBPaA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -6458,6 +6504,22 @@ "node": ">=8" } }, + "node_modules/tar": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.12.tgz", + "integrity": "sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/tar-fs": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", @@ -6484,6 +6546,25 @@ "node": ">=6" } }, + "node_modules/tar/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -7097,8 +7178,7 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yargs": { "version": "16.2.0", @@ -8292,6 +8372,16 @@ "integrity": "sha512-mMwtmgN0ureESnJ3SuMM4W9lsi4CgOxs43YxNo14SDHgzJ+OPYO3yM7nOTJTh8x5YICseBdtrySUbvxnpb+NYQ==", "dev": true }, + "@types/tar": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/tar/-/tar-6.1.3.tgz", + "integrity": "sha512-YzDOr5kdAeqS8dcO6NTTHTMJ44MUCBDoLEIyPtwEn7PssKqUYL49R1iCVJPeiPzPlKi6DbH33eZkpeJ27e4vHg==", + "dev": true, + "requires": { + "@types/node": "*", + "minipass": "^3.3.5" + } + }, "@types/triple-beam": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz", @@ -9846,6 +9936,14 @@ "universalify": "^2.0.0" } }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -10755,6 +10853,23 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, + "minipass": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.5.tgz", + "integrity": "sha512-rQ/p+KfKBkeNwo04U15i+hOwoVBVmekmm/HcfTkTN2t9pbQKCMm4eN5gFeqgrrSp/kH/7BYYhTIHOxGqzbBPaA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, "mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -11880,6 +11995,31 @@ "has-flag": "^4.0.0" } }, + "tar": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.12.tgz", + "integrity": "sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + } + } + }, "tar-fs": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", @@ -12365,8 +12505,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yargs": { "version": "16.2.0", diff --git a/package.json b/package.json index d0db0ac3..54c6f5c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cathayddt/bdk", - "version": "2.0.0", + "version": "2.0.1", "description": "Blockchain Deploy Kit", "homepage": "https://github.com/cathayddt/bdk#readme", "bugs": { @@ -47,14 +47,15 @@ "dockerode": "^3.3.1", "dotenv": "^8.2.0", "envfile": "^6.14.0", + "ethers": "^5.6.5", "fs-extra": "^9.1.0", "js-yaml": "^4.1.0", "prompts": "^2.4.0", + "rlp": "^3.0.0", "string-format": "^2.0.0", + "tar": "^6.1.12", "winston": "^3.3.3", - "yargs": "^16.0.0", - "ethers": "^5.6.5", - "rlp": "^3.0.0" + "yargs": "^16.0.0" }, "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.1", @@ -68,6 +69,7 @@ "@types/sinon": "^9.0.8", "@types/source-map-support": "^0.5.3", "@types/string-format": "^2.0.0", + "@types/tar": "^6.1.3", "@types/triple-beam": "^1.3.2", "@types/yargs": "^15.0.5", "ericlint": "^1.1.3", diff --git a/src/quorum/command/backup.ts b/src/quorum/command/backup.ts new file mode 100644 index 00000000..5aac64d2 --- /dev/null +++ b/src/quorum/command/backup.ts @@ -0,0 +1,11 @@ +import { Argv } from 'yargs' + +export const command = 'backup' + +export const desc = '管理 Quorum backup 的指令' + +export const builder = (yargs: Argv) => { + return yargs.commandDir('backup').demandCommand() +} + +export const handler = {} diff --git a/src/quorum/command/backup/export.ts b/src/quorum/command/backup/export.ts new file mode 100644 index 00000000..39a9fe53 --- /dev/null +++ b/src/quorum/command/backup/export.ts @@ -0,0 +1,50 @@ +import { Argv, Arguments } from 'yargs' +import config from '../../config' +import Backup from '../../service/backup' +import { logger } from '../../../util' +import { onCancel, ParamsError } from '../../../util/error' +import prompts from 'prompts' + +export const command = 'export' + +export const desc = '匯出現有的 Quorum Network.' + +interface OptType { + interactive: boolean +} + +export const builder = (yargs: Argv) => { + return yargs + .example('bdk quorum backup export --interactive', 'Cathay BDK 互動式問答') + .example('bdk quorum backup export --all', '備份 BDK 資料夾下所有 Quorum Network 資料') + .option('interactive', { type: 'boolean', description: '是否使用 Cathay BDK 互動式問答', alias: 'i' }) + .option('all', { type: 'boolean', description: '是否備份所有資料', alias: 'a' }) +} + +export const handler = async (argv: Arguments) => { + const backup = new Backup(config) + + if (argv.all) { + await backup.exportAll() + logger.info('Quorum Network export all Successfully!') + } else if (argv.interactive) { + const node: string = await (async () => { + const nodeList = backup.getExportItems() + if (nodeList.length !== 0) { + return (await prompts({ + type: 'select', + name: 'node', + message: 'Which node you want to export?', + choices: nodeList, + }, { onCancel })).node + } else { + throw new ParamsError('Invalid params: Required node not exist') + } + })() + + await backup.export(node) + logger.info(`Quorum Network export ${node} Successfully!`) + } else { + throw new ParamsError('Invalid params: Required parameter missing') + } +} diff --git a/src/quorum/command/backup/import.ts b/src/quorum/command/backup/import.ts new file mode 100644 index 00000000..f3e0ab78 --- /dev/null +++ b/src/quorum/command/backup/import.ts @@ -0,0 +1,41 @@ +import { Argv, Arguments } from 'yargs' +import config from '../../config' +import Backup from '../../service/backup' +import { logger } from '../../../util' +import { onCancel, ParamsError } from '../../../util/error' +import prompts from 'prompts' + +export const command = 'import' + +export const desc = '匯入現有的 Quorum Network.' + +interface OptType { + interactive: boolean +} + +export const builder = (yargs: Argv) => { + return yargs + .example('bdk quorum backup import --interactive', 'Cathay BDK 互動式問答') + .option('interactive', { type: 'boolean', description: '是否使用 Cathay BDK 互動式問答', alias: 'i' }) +} + +export const handler = async (argv: Arguments) => { + const backup = new Backup(config) + + const archive: string = await (async () => { + const archiveList = backup.getBackupItems() + if (argv.interactive) { + return (await prompts({ + type: 'select', + name: 'archive', + message: 'Which backup file you want to import?', + choices: archiveList, + }, { onCancel })).archive + } else { + throw new ParamsError('Invalid params: Required parameter missing') + } + })() + + await backup.import(archive) + logger.info('Quorum Network import Successfully!') +} diff --git a/src/quorum/command/explorer/create.ts b/src/quorum/command/explorer/create.ts index 1cd388a8..562757a7 100644 --- a/src/quorum/command/explorer/create.ts +++ b/src/quorum/command/explorer/create.ts @@ -18,7 +18,7 @@ interface OptType { export const builder = (yargs: Argv) => { return yargs .example('bdk quorum network create --interactive', 'Cathay BDK 互動式問答') - .option('interactive', { type: 'boolean', description: '是否使用 Cathay BDK 互動式問答', alias: 'i', default: false }) + .option('interactive', { type: 'boolean', description: '是否使用 Cathay BDK 互動式問答', alias: 'i' }) } export const handler = async (argv: Arguments) => { diff --git a/src/quorum/command/network/create.ts b/src/quorum/command/network/create.ts index ba331d71..d7a86073 100644 --- a/src/quorum/command/network/create.ts +++ b/src/quorum/command/network/create.ts @@ -20,83 +20,104 @@ interface OptType { export const builder = (yargs: Argv) => { return yargs .example('bdk quorum network create --interactive', 'Cathay BDK 互動式問答') - .option('interactive', { type: 'boolean', description: '是否使用 Cathay BDK 互動式問答', alias: 'i', default: false }) + .option('interactive', { type: 'boolean', description: '是否使用 Cathay BDK 互動式問答', alias: 'i' }) } export const handler = async (argv: Arguments) => { const network = new Network(config) + // check bdkPath files exist or not (include useless file e.g. .DS_Store) + const confirm: boolean = await (async () => { + const fileList = network.getBdkFiles() + if (fileList.length !== 0) { + const confirmDelete = (await prompts({ + type: 'confirm', + name: 'value', + message: '⚠️ Detecting quorum nodes already exists. The following processes will remove all existing files. Continue?', + initial: false, + }, { onCancel })).value + if (confirmDelete) { + network.removeBdkFiles(fileList) + logger.info('✔ Remove all existing files!') + } + return confirmDelete + } else { + return true + } + })() - const networkCreate: NetworkCreateType = await (async () => { - if (argv.interactive) { - const { chainId, validatorNumber, memberNumber } = await prompts([ - { - type: 'number', - name: 'chainId', - message: 'What is your chain id?', - min: 0, - initial: 1337, - }, - { - type: 'number', - name: 'validatorNumber', - message: 'How many validator do you want?', - min: 1, - initial: 4, - }, - { - type: 'number', - name: 'memberNumber', - message: 'How many member do you want?', - min: 1, - initial: 1, - }, - ], { onCancel }) - - const { walletOwner } = await prompts({ - type: 'select', - name: 'walletOwner', - message: 'Do you already own a wallet?', - choices: [ + if (confirm) { + const networkCreate: NetworkCreateType = await (async () => { + if (argv.interactive) { + const { chainId, validatorNumber, memberNumber } = await prompts([ + { + type: 'number', + name: 'chainId', + message: 'What is your chain id?', + min: 0, + initial: 1337, + }, { - title: 'true', - value: true, + type: 'number', + name: 'validatorNumber', + message: 'How many validator do you want?', + min: 1, + initial: 4, }, { - title: 'false', - value: false, + type: 'number', + name: 'memberNumber', + message: 'How many member do you want?', + min: 1, + initial: 1, }, - ], - initial: 1, - }) + ], { onCancel }) - let walletAddress: string + const { walletOwner } = await prompts({ + type: 'select', + name: 'walletOwner', + message: 'Do you already own a wallet?', + choices: [ + { + title: 'true', + value: true, + }, + { + title: 'false', + value: false, + }, + ], + initial: 1, + }) - if (walletOwner) { - const { address } = await prompts({ - type: 'text', - name: 'address', - message: 'What is your wallet address?', - validate: walletAddress => ethers.utils.isAddress(walletAddress) ? true : 'Address not valid.', - }, { onCancel }) + let walletAddress: string - walletAddress = address - } else { - const { address, privateKey } = await network.createWalletAddress() - walletAddress = address - logger.info(`Your wallet address: 0x${walletAddress}`) - logger.info(`Wallet private key: ${privateKey}`) - } + if (walletOwner) { + const { address } = await prompts({ + type: 'text', + name: 'address', + message: 'What is your wallet address?', + validate: walletAddress => ethers.utils.isAddress(walletAddress) ? true : 'Address not valid.', + }, { onCancel }) - const alloc = [{ - account: walletAddress, - amount: '1000000000000000000000000000', - }] + walletAddress = address + } else { + const { address, privateKey } = await network.createWalletAddress() + walletAddress = address + logger.info(`Your wallet address: 0x${walletAddress}`) + logger.info(`Wallet private key: ${privateKey}`) + } - return { chainId, validatorNumber, memberNumber, alloc } - } - throw new ParamsError('Invalid params: Required parameter missing') - })() + const alloc = [{ + account: walletAddress, + amount: '1000000000000000000000000000', + }] + + return { chainId, validatorNumber, memberNumber, alloc } + } + throw new ParamsError('Invalid params: Required parameter missing') + })() - await network.create(networkCreate) - logger.info('Quorum Network Create Successfully!') + await network.create(networkCreate) + logger.info('Quorum Network Create Successfully!') + } } diff --git a/src/quorum/command/network/delete.ts b/src/quorum/command/network/delete.ts index 6cbb3900..6263389e 100644 --- a/src/quorum/command/network/delete.ts +++ b/src/quorum/command/network/delete.ts @@ -20,7 +20,7 @@ export const handler = async (argv: Arguments) => { const response = await prompts({ type: 'confirm', name: 'value', - message: 'Confirm to delete Quorum Network?', + message: '⚠️ The following processes will remove all existing files. Confirm to delete Quorum Network?', initial: false, }, { onCancel }) diff --git a/src/quorum/command/network/down.ts b/src/quorum/command/network/down.ts new file mode 100644 index 00000000..90aa5618 --- /dev/null +++ b/src/quorum/command/network/down.ts @@ -0,0 +1,33 @@ +import { Arguments } from 'yargs' +import config from '../../config' +import Network from '../../service/network' +import { logger, onCancel } from '../../../util' +import prompts from 'prompts' + +export const command = 'down' + +export const desc = '停止現有的 Quorum Network.' + +export const builder = {} + +export const handler = async (argv: Arguments) => { + logger.debug('exec network down', argv.$0) + + const network = new Network(config) + + let confirmDelete = true + + const response = await prompts({ + type: 'confirm', + name: 'value', + message: 'Confirm to down Quorum Network?', + initial: false, + }, { onCancel }) + + confirmDelete = response.value + + if (confirmDelete) { + await network.down() + logger.info('Quorum Network down Successfully!') + } +} diff --git a/src/quorum/command/network/up.ts b/src/quorum/command/network/up.ts new file mode 100644 index 00000000..1293fc53 --- /dev/null +++ b/src/quorum/command/network/up.ts @@ -0,0 +1,51 @@ +import { Argv, Arguments } from 'yargs' +import config from '../../config' +import Network from '../../service/network' +import { logger } from '../../../util' +import { onCancel, ParamsError } from '../../../util/error' +import prompts from 'prompts' + +export const command = 'up' + +export const desc = '啟動現有的 Quorum Network.' + +interface OptType { + interactive: boolean +} + +export const builder = (yargs: Argv) => { + return yargs + .example('bdk quorum network up --interactive', 'Cathay BDK 互動式問答') + .example('bdk quorum network up --all', '啟動 BDK 資料夾下現有的 Quorum Network') + .option('interactive', { type: 'boolean', description: '是否使用 Cathay BDK 互動式問答', alias: 'i' }) + .option('all', { type: 'boolean', description: '是否啟動所有節點', alias: 'a' }) +} + +export const handler = async (argv: Arguments) => { + const network = new Network(config) + + if (argv.all) { + await network.upAll() + logger.info('Quorum Network up all Successfully!') + } else if (argv.interactive) { + const node: string = await (async () => { + const nodeList = network.getUpExportItems() + + if (nodeList.length !== 0) { + return (await prompts({ + type: 'select', + name: 'node', + message: 'Which node you want to up?', + choices: nodeList, + }, { onCancel })).node + } else { + throw new ParamsError('Invalid params: Required node not exist') + } + })() + + await network.upService(node) + logger.info(`Quorum Network up ${node} Successfully!`) + } else { + throw new ParamsError('Invalid params: Required parameter missing') + } +} diff --git a/src/quorum/instance/bdkFile.ts b/src/quorum/instance/bdkFile.ts index 31328fa9..f7b4f5ef 100644 --- a/src/quorum/instance/bdkFile.ts +++ b/src/quorum/instance/bdkFile.ts @@ -14,12 +14,14 @@ export enum InstanceTypeEnum { export default class BdkFile { private config: Config private bdkPath: string + private backupPath: string private envPath: string private orgPath: string constructor (config: Config, networkName: string = config.networkName) { this.config = config this.bdkPath = `${config.infraConfig.bdkPath}/${networkName}` + this.backupPath = `${config.infraConfig.bdkPath}/backup` this.envPath = `${config.infraConfig.bdkPath}/.env` this.orgPath = '' } @@ -72,77 +74,102 @@ export default class BdkFile { } public createValidatorFolder (i: number) { - fs.mkdirSync(`${this.bdkPath}/Validator-${i}/data/keystore`, { recursive: true }) + fs.mkdirSync(`${this.bdkPath}/validator${i}/data/keystore`, { recursive: true }) } public copyGenesisJsonToValidator (i: number) { this.createValidatorFolder(i) - fs.copyFileSync(`${this.bdkPath}/artifacts/goQuorum/genesis.json`, `${this.bdkPath}/Validator-${i}/data/genesis.json`) + fs.copyFileSync(`${this.bdkPath}/artifacts/goQuorum/genesis.json`, `${this.bdkPath}/validator${i}/data/genesis.json`) } public copyStaticNodesJsonToValidator (i: number) { this.createValidatorFolder(i) - fs.copyFileSync(`${this.bdkPath}/artifacts/goQuorum/static-nodes.json`, `${this.bdkPath}/Validator-${i}/data/static-nodes.json`) + fs.copyFileSync(`${this.bdkPath}/artifacts/goQuorum/static-nodes.json`, `${this.bdkPath}/validator${i}/data/static-nodes.json`) } public copyPermissionedNodesJsonToValidator (i: number) { this.createValidatorFolder(i) - fs.copyFileSync(`${this.bdkPath}/artifacts/goQuorum/permissioned-nodes.json`, `${this.bdkPath}/Validator-${i}/data/permissioned-nodes.json`) + fs.copyFileSync(`${this.bdkPath}/artifacts/goQuorum/permissioned-nodes.json`, `${this.bdkPath}/validator${i}/data/permissioned-nodes.json`) } public copyPrivateKeyToValidator (i: number) { this.createValidatorFolder(i) - fs.copyFileSync(`${this.bdkPath}/artifacts/validator${i}/nodekey`, `${this.bdkPath}/Validator-${i}/data/nodekey`) + fs.copyFileSync(`${this.bdkPath}/artifacts/validator${i}/nodekey`, `${this.bdkPath}/validator${i}/data/nodekey`) } public copyPublicKeyToValidator (i: number) { this.createValidatorFolder(i) - fs.copyFileSync(`${this.bdkPath}/artifacts/validator${i}/nodekey.pub`, `${this.bdkPath}/Validator-${i}/data/nodekey.pub`) + fs.copyFileSync(`${this.bdkPath}/artifacts/validator${i}/nodekey.pub`, `${this.bdkPath}/validator${i}/data/nodekey.pub`) } public copyAddressToValidator (i: number) { this.createValidatorFolder(i) - fs.copyFileSync(`${this.bdkPath}/artifacts/validator${i}/address`, `${this.bdkPath}/Validator-${i}/data/address`) + fs.copyFileSync(`${this.bdkPath}/artifacts/validator${i}/address`, `${this.bdkPath}/validator${i}/data/address`) } public createMemberFolder (i: number) { - fs.mkdirSync(`${this.bdkPath}/Member-${i}/data/keystore`, { recursive: true }) + fs.mkdirSync(`${this.bdkPath}/member${i}/data/keystore`, { recursive: true }) + } + + public removeBdkFiles (path: string) { + fs.rmSync(`${this.bdkPath}/${path}`, { recursive: true, force: true }) } public copyGenesisJsonToMember (i: number) { this.createMemberFolder(i) - fs.copyFileSync(`${this.bdkPath}/artifacts/goQuorum/genesis.json`, `${this.bdkPath}/Member-${i}/data/genesis.json`) + fs.copyFileSync(`${this.bdkPath}/artifacts/goQuorum/genesis.json`, `${this.bdkPath}/member${i}/data/genesis.json`) } public copyStaticNodesJsonToMember (i: number) { this.createMemberFolder(i) - fs.copyFileSync(`${this.bdkPath}/artifacts/goQuorum/static-nodes.json`, `${this.bdkPath}/Member-${i}/data/static-nodes.json`) + fs.copyFileSync(`${this.bdkPath}/artifacts/goQuorum/static-nodes.json`, `${this.bdkPath}/member${i}/data/static-nodes.json`) } public copyPermissionedNodesJsonToMember (i: number) { this.createMemberFolder(i) - fs.copyFileSync(`${this.bdkPath}/artifacts/goQuorum/permissioned-nodes.json`, `${this.bdkPath}/Member-${i}/data/permissioned-nodes.json`) + fs.copyFileSync(`${this.bdkPath}/artifacts/goQuorum/permissioned-nodes.json`, `${this.bdkPath}/member${i}/data/permissioned-nodes.json`) } public copyPrivateKeyToMember (i: number) { this.createMemberFolder(i) - fs.copyFileSync(`${this.bdkPath}/artifacts/member${i}/nodekey`, `${this.bdkPath}/Member-${i}/data/nodekey`) + fs.copyFileSync(`${this.bdkPath}/artifacts/member${i}/nodekey`, `${this.bdkPath}/member${i}/data/nodekey`) } public copyPublicKeyToMember (i: number) { this.createMemberFolder(i) - fs.copyFileSync(`${this.bdkPath}/artifacts/member${i}/nodekey.pub`, `${this.bdkPath}/Member-${i}/data/nodekey.pub`) + fs.copyFileSync(`${this.bdkPath}/artifacts/member${i}/nodekey.pub`, `${this.bdkPath}/member${i}/data/nodekey.pub`) } public copyAddressToMember (i: number) { this.createMemberFolder(i) - fs.copyFileSync(`${this.bdkPath}/artifacts/member${i}/address`, `${this.bdkPath}/Member-${i}/data/address`) + fs.copyFileSync(`${this.bdkPath}/artifacts/member${i}/address`, `${this.bdkPath}/member${i}/data/address`) + } + + public createBackupFolder () { + fs.mkdirSync(`${this.backupPath}`, { recursive: true }) + } + + public createBackupTar (validatorTag: string, date: string) { + this.createBackupFolder() + return fs.createWriteStream(`${this.backupPath}/Backup_${validatorTag}_${date}.tar.gz`) } public getBdkPath () { return `${this.bdkPath}` } + public getExportFiles () { + return fs.readdirSync(this.bdkPath) + } + + public getBackupPath () { + return `${this.backupPath}` + } + + public getBackupFiles () { + return fs.readdirSync(this.backupPath) + } + public getExplorerDockerComposeYamlPath (): string { return `${this.getBdkPath()}/explorer-docker-compose.yaml` } diff --git a/src/quorum/instance/infra/InfraRunner.interface.ts b/src/quorum/instance/infra/InfraRunner.interface.ts index 868cb946..e259ad0d 100644 --- a/src/quorum/instance/infra/InfraRunner.interface.ts +++ b/src/quorum/instance/infra/InfraRunner.interface.ts @@ -22,6 +22,7 @@ export interface InfraRunner { runCommand(payload: DockerRunCommandType): Promise createContainerAndRun(payload: DockerRunCommandType): Promise upInBackground(dockerComposeFile: string): Promise + upServiceInBackground(dockerComposeFile: string, service: string): Promise downAndRemoveVolumes(dockerComposeFile: string): Promise restart(dockerComposeFile: string, service: string[]): Promise } diff --git a/src/quorum/instance/infra/docker/runner.ts b/src/quorum/instance/infra/docker/runner.ts index 25b17fc9..fe785699 100644 --- a/src/quorum/instance/infra/docker/runner.ts +++ b/src/quorum/instance/infra/docker/runner.ts @@ -142,6 +142,16 @@ export class Runner implements InfraRunner { return { stdout: this.runSpawnSync(['-f', dockerComposeFile, 'up', '-d']) } } + public upServiceInBackground = async (dockerComposeFile: string, service: string) => { + const networks = (YAML.load(fs.readFileSync(dockerComposeFile).toString()) as DockerComposeYamlInterface).networks + for (const network in networks) { + if (networks[network]?.external) { + await this.checkAndCreateNetwork(network) + } + } + return { stdout: this.runSpawnSync(['-f', dockerComposeFile, 'up', '-d', '--', service]) } + } + // eslint-disable-next-line require-await public downAndRemoveVolumes = async (dockerComposeFile: string) => { // 為保留其他infra的操作空間,此method的type為(dockerComposeFile: string): Promise,雖然裡面沒有await,仍用async diff --git a/src/quorum/instance/member.ts b/src/quorum/instance/member.ts index cad52586..a5b123ed 100644 --- a/src/quorum/instance/member.ts +++ b/src/quorum/instance/member.ts @@ -12,17 +12,22 @@ export default class Member extends AbstractInstance { } public async up (): Promise { - logger.debug('Explorer instance up') + logger.debug('Member instance up') return await this.infra.upInBackground(this.dockerComposePath) } + public async upOneService (service: string): Promise { + logger.debug(`Member instance ${service} up`) + return await this.infra.upServiceInBackground(this.dockerComposePath, service) + } + public async down (): Promise { - logger.debug('Explorer instance down') + logger.debug('Member instance down') return await this.infra.downAndRemoveVolumes(this.dockerComposePath) } public async restart (): Promise { - logger.debug('Explorer instance restart') + logger.debug('Member instance restart') return await this.infra.restart(this.dockerComposePath, [`explorer.${this.config.networkName}`]) } } diff --git a/src/quorum/instance/validator.ts b/src/quorum/instance/validator.ts index 8a6fa184..f392142e 100644 --- a/src/quorum/instance/validator.ts +++ b/src/quorum/instance/validator.ts @@ -12,17 +12,22 @@ export default class Validator extends AbstractInstance { } public async up (): Promise { - logger.debug('Explorer instance up') + logger.debug('Validator instance up') return await this.infra.upInBackground(this.dockerComposePath) } + public async upOneService (service: string): Promise { + logger.debug(`Validator instance ${service} up`) + return await this.infra.upServiceInBackground(this.dockerComposePath, service) + } + public async down (): Promise { - logger.debug('Explorer instance down') + logger.debug('Validator instance down') return await this.infra.downAndRemoveVolumes(this.dockerComposePath) } public async restart (): Promise { - logger.debug('Explorer instance restart') + logger.debug('Validator instance restart') return await this.infra.restart(this.dockerComposePath, [`explorer.${this.config.networkName}`]) } } diff --git a/src/quorum/model/yaml/docker-compose/explorerDockerComposeYaml.ts b/src/quorum/model/yaml/docker-compose/explorerDockerComposeYaml.ts index 757725f9..b1fe54da 100644 --- a/src/quorum/model/yaml/docker-compose/explorerDockerComposeYaml.ts +++ b/src/quorum/model/yaml/docker-compose/explorerDockerComposeYaml.ts @@ -36,7 +36,7 @@ class ExplorerDockerComposeYaml extends DockerComposeYaml { `${port}:4000`, ], networks: ['quorum-blockscout'], - volumes: [`${bdkPath}/Validator-0/data/geth.ipc:/root/geth.ipc`], + volumes: [`${bdkPath}/validator0/data/geth.ipc:/root/geth.ipc`], }, ) this.addService( diff --git a/src/quorum/model/yaml/docker-compose/memberDockerCompose.ts b/src/quorum/model/yaml/docker-compose/memberDockerCompose.ts index a978a037..60ca9a40 100644 --- a/src/quorum/model/yaml/docker-compose/memberDockerCompose.ts +++ b/src/quorum/model/yaml/docker-compose/memberDockerCompose.ts @@ -5,13 +5,13 @@ class MemberDockerComposeYaml extends DockerComposeYaml { this.addNetwork('quorum', {}) this.addService(`member${memberNum}`, { image: 'quorumengineering/quorum:22.7.0', - container_name: `member-${memberNum}`, + container_name: `member${memberNum}`, restart: 'always', environment: ['PRIVATE_CONFIG=ignore'], ports: [ `${port}:8545`, ], - volumes: [`${bdkPath}/Member-${memberNum}/data/:/data`], + volumes: [`${bdkPath}/member${memberNum}/data/:/data`], entrypoint: [ '/bin/sh', '-c', 'geth init --datadir /data /data/genesis.json;geth --datadir /data --networkid 1337 --nodiscover --verbosity 3 --syncmode full --nousb --http --http.addr 0.0.0.0 --http.port 8545 --http.corsdomain "*" --http.vhosts "*" --ws --ws.addr 0.0.0.0 --ws.port 8546 --ws.origins "*" --http.api admin,trace,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,istanbul,qbft --ws.api admin,trace,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,istanbul,qbft --port 30303', diff --git a/src/quorum/model/yaml/docker-compose/validatorDockerComposeYaml.ts b/src/quorum/model/yaml/docker-compose/validatorDockerComposeYaml.ts index fc19f169..d6563f26 100644 --- a/src/quorum/model/yaml/docker-compose/validatorDockerComposeYaml.ts +++ b/src/quorum/model/yaml/docker-compose/validatorDockerComposeYaml.ts @@ -5,14 +5,14 @@ class ValidatorDockerComposeYaml extends DockerComposeYaml { this.addNetwork('quorum', {}) this.addService(`validator${validatorNum}`, { image: 'quorumengineering/quorum:22.7.0', - container_name: `validator-${validatorNum}`, + container_name: `validator${validatorNum}`, restart: 'no', environment: ['PRIVATE_CONFIG=ignore'], ports: [ `${port}:8545`, ], - network_mode: 'quorum', - volumes: [`${bdkPath}/Validator-${validatorNum}/data/:/data`], + networks: ['quorum'], + volumes: [`${bdkPath}/validator${validatorNum}/data/:/data`], entrypoint: [ '/bin/sh', '-c', 'geth init --datadir /data /data/genesis.json; geth --datadir /data --networkid 1337 --nodiscover --verbosity 3 --syncmode full --nousb --mine --miner.threads 1 --miner.gasprice 0 --emitcheckpoints --http --http.addr 0.0.0.0 --http.port 8545 --http.corsdomain "*" --http.vhosts "*" --ws --ws.addr 0.0.0.0 --ws.port 8546 --ws.origins "*" --http.api admin,trace,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,istanbul,qbft --ws.api admin,trace,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,istanbul,qbft --port 30303', diff --git a/src/quorum/service/backup.ts b/src/quorum/service/backup.ts new file mode 100644 index 00000000..340cd6fd --- /dev/null +++ b/src/quorum/service/backup.ts @@ -0,0 +1,82 @@ +import { AbstractService } from './Service.abstract' +import tar from 'tar' +import fs from 'fs' +import { BackupError, tarDateFormat } from '../../util' + +export default class Backup extends AbstractService { + /** + * @description 匯出 quorum network 備份資料 + */ + public exportAll () { + const bdkPath = this.bdkFile.getBdkPath() + const createOpts = { + gzip: true, + cwd: bdkPath, + sync: true, + } + try { + tar + .c(createOpts, fs.readdirSync(bdkPath)) + .pipe(this.bdkFile.createBackupTar('All', tarDateFormat(new Date()))) + } catch (e: any) { + throw new BackupError(`[x] tar error: ${e.message}`) + } + } + + /** + * @description 匯出 quorum network 單一validator備份資料 + */ + public export (nodeName: string) { + const bdkPath = this.bdkFile.getBdkPath() + const createOpts = { + gzip: true, + cwd: bdkPath, + sync: true, + } + try { + tar + .c(createOpts, [`${nodeName}`, + 'artifacts', + 'member-docker-compose.yaml', + 'validator-docker-compose.yaml']) + .pipe(this.bdkFile.createBackupTar(`${nodeName}`, tarDateFormat(new Date()))) + } catch (e: any) { + throw new BackupError(`[x] tar compress error: ${e.message}`) + } + } + + /** + * @description 回傳 backup 資料夾所有的備份檔案 + */ + public getExportItems () { + const node = this.bdkFile.getExportFiles().filter(file => file.match(/(validator|member)[0-9]+/g)) + const nodeList = node.map(x => ({ title: x, value: x })) + return nodeList + } + + /** + * @description 回傳 backup 資料夾所有的備份檔案 + */ + public getBackupItems () { + const archive = this.bdkFile.getBackupFiles().filter(file => file.match(/[\w-]+\.tar/g)) + const archiveList = archive.map(x => ({ title: x, value: x })) + return archiveList + } + + /** + * @description 匯入 quorum network 備份資料 + */ + public import (tarFileName: string) { + const bdkPath = this.bdkFile.getBdkPath() + const backupPath = this.bdkFile.getBackupPath() + try { + fs.createReadStream(`${backupPath}/${tarFileName}`).pipe( + tar.x({ + cwd: bdkPath, + }), + ) + } catch (e: any) { + throw new BackupError(`[x] tar extract error: ${e.message}`) + } + } +} diff --git a/src/quorum/service/network.ts b/src/quorum/service/network.ts index 6ef79e4e..4aad3500 100644 --- a/src/quorum/service/network.ts +++ b/src/quorum/service/network.ts @@ -119,11 +119,29 @@ export default class Network extends AbstractService { // TODO: check quorum network create successfully } - public async delete () { + public async upService (service: string) { + if (service.match(/validator[\w-]+/g)) { + await (new ValidatorInstance(this.config, this.infra).upOneService(service)) + } else if (service.match(/member[\w-]+/g)) { + await (new MemberInstance(this.config, this.infra).upOneService(service)) + } + } + + public async upAll () { + await (new ValidatorInstance(this.config, this.infra).up()) + await (new MemberInstance(this.config, this.infra).up()) + } + + public async down () { await (new ValidatorInstance(this.config, this.infra).down()) await (new MemberInstance(this.config, this.infra).down()) } + public async delete () { + await this.down() + this.removeBdkFiles(this.getBdkFiles()) + } + /** @ignore */ private createKey (dir: string) { // TODO: use Shawn's code generate key @@ -149,4 +167,23 @@ export default class Network extends AbstractService { return { privateKey, publicKey, address } } + + /** @ignore */ + public getBdkFiles () { + return this.bdkFile.getExportFiles() + } + + /** @ignore */ + public getUpExportItems () { + const node = this.bdkFile.getExportFiles().filter(file => file.match(/(validator|member)[0-9]+/g)) + const nodeList = node.map(x => ({ title: x, value: x })) + return nodeList + } + + /** @ignore */ + public removeBdkFiles (files: string[]) { + for (const file of files) { + this.bdkFile.removeBdkFiles(file) + } + } } diff --git a/src/util/error.ts b/src/util/error.ts index cd654715..4dda8102 100644 --- a/src/util/error.ts +++ b/src/util/error.ts @@ -7,6 +7,7 @@ class BdkError extends Error {} export class ProcessError extends BdkError {} export class ParamsError extends BdkError {} export class DockerError extends BdkError {} +export class BackupError extends BdkError {} export class FabricContainerError extends BdkError { public stdout: string constructor (message: string, stdout: string) { diff --git a/src/util/utils.ts b/src/util/utils.ts index 3f5f02f9..0974882d 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -21,6 +21,10 @@ export function iterateFormat ( return item } +export function tarDateFormat (date: Date): string { + return date.toISOString().slice(0, 19).replace(/T/g, '_').replace(/:/g, '-') +} + export const randomFromArray = (x: Array) => x[Math.floor(Math.random() * x.length)] // export function iterateObject2 (obj: Map, callback: (x: string) => any) { // for (const key in obj) {