"
21 | exit 1
22 | fi
23 |
24 | echo "Updating version from $old_version to $new_version"
25 | echo -n " - package.json... "
26 | npm version "$new_version" --no-git-tag-version > /dev/null
27 | echo "done"
28 |
29 | echo -n " - FABLO_VERSION... "
30 | perl -i -pe "s/FABLO_VERSION=.*\\n/FABLO_VERSION=${new_version}\\n/g" fablo.sh
31 | perl -i -pe "s/FABLO_VERSION=.*\\n/FABLO_VERSION=${new_version}\\n/g" e2e/__snapshots__/*
32 | echo "done"
33 |
34 | echo -n " - JSON schema URL... "
35 | schema_update_pattern="s/download\/[0-9-.a-zA-Z]*\/schema.json/download\/${new_version}\/schema.json/g"
36 | if [ "$include_readme" = true ]; then
37 | perl -i -pe "$schema_update_pattern" README.md
38 | fi
39 | perl -i -pe "$schema_update_pattern" docs/sample.json
40 | perl -i -pe "$schema_update_pattern" docs/schema.json
41 | perl -i -pe "$schema_update_pattern" samples/*.json
42 | perl -i -pe "$schema_update_pattern" samples/*.yaml
43 | perl -i -pe "$schema_update_pattern" e2e/__snapshots__/*
44 | echo "done"
45 |
46 | if [ "$include_readme" = true ]; then
47 | echo -n " - download URL... "
48 | download_update_pattern="s/download\/[0-9-.a-zA-Z]*\/fablo.sh/download\/${new_version}\/fablo.sh/g"
49 | perl -i -pe "$download_update_pattern" README.md
50 | echo "done"
51 | fi
52 |
--------------------------------------------------------------------------------
/check-if-fablo-version-matches.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -euo
4 |
5 | source ./fablo.sh "help"
6 |
7 | FABLO_SCRIPT_VERSION="$FABLO_VERSION"
8 | FABLO_NODE_VERSION="$(jq -r .version package.json)"
9 |
10 | echo "Checking Fablo version"
11 | echo " FABLO_SCRIPT_VERSION: $FABLO_SCRIPT_VERSION"
12 | echo " FABLO_NODE_VERSION: $FABLO_NODE_VERSION"
13 |
14 | if [ "$FABLO_SCRIPT_VERSION" != "$FABLO_NODE_VERSION" ]; then
15 | echo "Error !"
16 | echo "Fablo version in 'fablo.sh' is not matching version in 'package.json'"
17 | exit 1
18 | fi
19 |
--------------------------------------------------------------------------------
/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | set -e
4 |
5 | executeYeomanCommand() {
6 | command_with_params=$1
7 |
8 | # cleanup yeoman files after execution
9 | # shellcheck disable=SC2064
10 | trap "rm -rf \"$yeoman_target_dir/.cache\" \"$yeoman_target_dir/.config\" \"$yeoman_target_dir/.npm\"" EXIT
11 |
12 | if [ "$(id -u)" = 0 ]; then
13 | # root user detected, running as yeoman user
14 | sudo chown -R yeoman:yeoman "$yeoman_target_dir"
15 | # shellcheck disable=SC2086
16 | (cd "$yeoman_target_dir" && sudo -E -u yeoman yo --no-insight $command_with_params)
17 | sudo chown -R root:root "$yeoman_target_dir"
18 | else
19 | # shellcheck disable=SC2086
20 | (cd "$yeoman_target_dir" && yo --no-insight $command_with_params)
21 | fi
22 | }
23 |
24 | formatGeneratedFiles() {
25 | # Additional script and yaml formatting
26 | #
27 | # Why? Yeoman output may contain some additional whitespaces or the formatting
28 | # might not be ideal. Keeping those whitespaces, however, might be useful
29 | # in templates to improve the brevity. That's why we need additional formatting.
30 | # Since the templates should obey good practices, we don't use linters here
31 | # (i.e. shellcheck and yamllint).
32 | echo "Formatting generated files"
33 | shfmt -i=2 -l -w "$yeoman_target_dir" >/dev/null
34 |
35 | for yaml in "$yeoman_target_dir"/**/*.yaml; do
36 |
37 | # the expansion failed, no yaml files found
38 | if [ "$yaml" = "$yeoman_target_dir/**/*.yaml" ]; then
39 | break
40 | fi
41 |
42 | # remove trailing spaces
43 | sed --in-place 's/[ \t]*$//' "$yaml"
44 |
45 | # remove duplicated empty/blank lines
46 | content="$(awk -v RS= -v ORS='\n\n' '1' "$yaml")"
47 | echo "$content" >"$yaml"
48 | done
49 | }
50 |
51 | yeoman_target_dir="/network/workspace"
52 | yeoman_command=${1:-Fablo:setup-network}
53 |
54 | # This part of output will be replaces with empty line. It breaks parsing of yeoman generator output.
55 | # See also: https://github.com/yeoman/generator/issues/1294
56 | annoying_yeoman_info="No change to package.json was detected. No package manager install will be executed."
57 |
58 | # Execute the command
59 | executeYeomanCommand "$yeoman_command" 2>&1 | sed "s/$annoying_yeoman_info//g"
60 |
61 | if echo "$yeoman_command" | grep "setup-network"; then
62 | formatGeneratedFiles
63 | fi
64 |
--------------------------------------------------------------------------------
/docs/CNAME:
--------------------------------------------------------------------------------
1 | fablo.io
--------------------------------------------------------------------------------
/docs/editor.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Fablo config editor
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
125 |
126 |
127 |
128 |
Fablo config editor
129 |
130 |
131 |
132 |
167 |
168 |
169 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Redirecting to main repo page
5 |
6 |
7 | Redirecting to https://github.com/hyperledger-labs/fablo...
8 |
9 |
10 |
--------------------------------------------------------------------------------
/docs/sample.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://github.com/hyperledger-labs/fablo/releases/download/2.2.0/schema.json",
3 | "global": {
4 | "fabricVersion": "2.3.2",
5 | "tls": false,
6 | "peerDevMode": false
7 | },
8 | "orgs": [
9 | {
10 | "organization": {
11 | "name": "Orderer",
12 | "mspName": "OrdererMSP",
13 | "domain": "root.com"
14 | },
15 | "orderers": [
16 | {
17 | "groupName": "group1",
18 | "prefix": "orderer",
19 | "type": "solo",
20 | "instances": 1
21 | }
22 | ]
23 | },
24 | {
25 | "organization": {
26 | "name": "Org1",
27 | "mspName": "Org1MSP",
28 | "domain": "org1.example.com"
29 | },
30 | "ca": {
31 | "prefix": "ca"
32 | },
33 | "peer": {
34 | "prefix": "peer",
35 | "instances": 2,
36 | "db": "LevelDb"
37 | }
38 | }
39 | ],
40 | "channels": [
41 | {
42 | "name": "my-channel1",
43 | "orgs": [
44 | {
45 | "name": "Org1",
46 | "peers": [
47 | "peer0",
48 | "peer1"
49 | ]
50 | }
51 | ]
52 | }
53 | ],
54 | "chaincodes": [
55 | {
56 | "name": "chaincode1",
57 | "version": "0.0.1",
58 | "lang": "node",
59 | "channel": "my-channel1",
60 | "init": "{\"Args\":[]}",
61 | "endorsement": "AND ('Org1MSP.member')",
62 | "directory": "./chaincode1",
63 | "privateData": [
64 | {
65 | "name": "privateCollectionOrg1",
66 | "orgNames": [
67 | "Org1"
68 | ]
69 | }
70 | ]
71 | }
72 | ]
73 | }
74 |
--------------------------------------------------------------------------------
/e2e-network/TEST_CASES.md:
--------------------------------------------------------------------------------
1 | # Test cases
2 |
3 | | Test case | 01-simple | 02-raft | 03-private | 04-snapshot | test-05-version3 | test-05-version3-BFT |
4 | | ------------------------- |:---------------:|:-----------:|:----------:|:------------------------:|:------------------:|:---------------------:|
5 | | Fabric versions | 2.4.7 | 2.3.2 | 2.4.7 | 2.3.3/2.4.2 | 3.0.0-beta | 3.0.0-beta |
6 | | TLS | no | yes | no | yes | yes | yes |
7 | | Channel capabilities | v2 | v2 | v2_5 | v2 | v3_0 | v3_0 |
8 | | Consensus | solo | RAFT | solo | RAFT | RAFT | BFT |
9 | | Orderer nodes | 1 | 3 | 1 | 1 | 3 | 4 |
10 | | Organizations | 1 | 2 | 2 | 1 | 1 | 1 |
11 | | CA database | SQLite | SQLite | SQLite | Postgres | SQLite | SQLite |
12 | | Peer database | LevelDB | LevelDB | LevelDB | CouchDB | LevelDB | LevelDB |
13 | | Peer count | 2 | 2, 2 | 2, 1 | 2 | 2 | 2 |
14 | | Channels | 1 | 2 | 1 | 1 | 1 | 1 |
15 | | Node chaincode | yes | yes | yes | yes | yes | yes |
16 | | Node chaincode upgrade | no | yes | no | no | no | no |
17 | | Node chaincode endorsement| OR | OR | OR, AND | default | OR | OR |
18 | | Private data | no | no | yes | yes | no | no |
19 | | Java chaincode | no | yes | no | no | no | no |
20 | | Go chaincode | no | no | no | no | no | no |
21 | | Tools | channel scripts | Fablo REST | - | Fablo REST, Explorer | - | - |
22 | | Other Fablo commands | init, reset | stop, start | - | snapshot, prune, restore | - | - |
23 |
--------------------------------------------------------------------------------
/e2e-network/docker/expect-ca-rest.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -eu
4 |
5 | rest_api_path="$1"
6 | token="${2:-""}"
7 | data="${3:-""}"
8 | expected="$4"
9 |
10 | if [ -z "$expected" ]; then
11 | echo "Usage: ./expect-ca-rest.sh "
12 | exit 1
13 | fi
14 |
15 | response="$(
16 | curl \
17 | -s \
18 | --request POST \
19 | --url "$rest_api_path" \
20 | --header 'Content-Type: application/json' \
21 | --header "Authorization: Bearer $token" \
22 | --data "$data"
23 | )"
24 |
25 | if echo "$response" | grep -F "$expected"; then
26 | # in case of success this is a single echo, since the response might be
27 | # required by other tests, without additional noise
28 | echo ""
29 | else
30 | label="$rest_api_path / $token / $data"
31 | echo ""
32 | echo "➜ testing: $label"
33 | echo "$response"
34 | echo "❌ failed (rest): $label | expected: $expected"
35 | fi
36 |
--------------------------------------------------------------------------------
/e2e-network/docker/expect-command.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | command="$1"
4 | expected="$2"
5 |
6 | if [ -z "$expected" ]; then
7 | echo "Usage: ./expect-command.sh [command] [expected value]"
8 | exit 1
9 | fi
10 |
11 | echo ""
12 | echo "➜ testing: $command"
13 |
14 | response="$(eval "$command" 2>&1)"
15 |
16 | echo "$response"
17 |
18 | if echo "$response" | grep -a -F "$expected"; then
19 | echo "✅ ok (cli): $command"
20 | else
21 | echo "❌ failed (cli): $command | expected: $expected"
22 | exit 1
23 | fi
24 |
--------------------------------------------------------------------------------
/e2e-network/docker/expect-invoke-cli.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | peers="$1"
4 | channel="$2"
5 | chaincode="$3"
6 | command="$4"
7 | expected="$5"
8 | transient_default="{}"
9 | transient="${6:-$transient_default}"
10 |
11 | if [ -z "$expected" ]; then
12 | echo "Usage: ./expect-invoke.sh [peer[,peer]] [channel] [chaincode] [command] [expected_substring] [transient_data]"
13 | exit 1
14 | fi
15 |
16 | label="Invoke $channel/$peers $command"
17 | echo ""
18 | echo "➜ testing: $label"
19 |
20 |
21 | response="$(
22 | "$FABLO_HOME/fablo.sh" chaincode invoke "$peers" "$channel" "$chaincode" "$command" "$transient"
23 | )"
24 |
25 | echo "$response"
26 |
27 | if echo "$response" | grep -F "$expected"; then
28 | echo "✅ ok (cli): $label"
29 | else
30 | echo "❌ failed (cli): $label | expected: $expected"
31 | exit 1
32 | fi
33 |
--------------------------------------------------------------------------------
/e2e-network/docker/expect-invoke-rest.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -eu
4 |
5 | rest_api_url="$(echo "$1" | awk '{print $1}')"
6 | access_token="$(echo "$1" | awk '{print $2}')"
7 | channel="$2"
8 | chaincode="$3"
9 | method="$4"
10 | args_json_array="$5"
11 | expected="$6"
12 | transient_default="{}"
13 | transient="${7:-$transient_default}"
14 |
15 | if [ -z "$expected" ]; then
16 | echo "Usage: ./expect-invoke.sh "
17 | exit 1
18 | fi
19 |
20 | label="Invoke $rest_api_url/invoke/$channel/$chaincode $method"
21 | echo ""
22 | echo "➜ testing: $label"
23 |
24 | if [ -z "$access_token" ]; then
25 | the_same_dir="$(cd "$(dirname "$0")" && pwd)"
26 | enroll_admin_response="$("$the_same_dir/expect-ca-rest.sh" "$rest_api_url/user/enroll" '' '{"id": "admin", "secret": "adminpw"}' "token")"
27 | echo "enroll admin response: $enroll_admin_response"
28 | access_token="$(echo "$enroll_admin_response" | jq -r '.token')"
29 | else
30 | echo "using provided token: $access_token"
31 | fi
32 |
33 | response=$(
34 | curl \
35 | -s \
36 | --request POST \
37 | --url "$rest_api_url/invoke/$channel/$chaincode" \
38 | --header "Authorization: Bearer $access_token" \
39 | --header 'Content-Type: application/json' \
40 | --data "{
41 | \"method\": \"$method\",
42 | \"args\": $args_json_array,
43 | \"transient\": $transient
44 | }"
45 | )
46 |
47 | echo "$response"
48 |
49 | if echo "$response" | grep -F "$expected"; then
50 | echo "✅ ok (rest): $label"
51 | else
52 | echo "❌ failed (rest): $label | expected: $expected"
53 | exit 1
54 | fi
55 |
--------------------------------------------------------------------------------
/e2e-network/docker/test-01-simple.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | TEST_TMP="$(rm -rf "$0.tmpdir" && mkdir -p "$0.tmpdir" && (cd "$0.tmpdir" && pwd))"
6 | TEST_LOGS="$(mkdir -p "$0.logs" && (cd "$0.logs" && pwd))"
7 | FABLO_HOME="$TEST_TMP/../../.."
8 |
9 | export FABLO_HOME
10 |
11 | networkUp() {
12 | "$FABLO_HOME/fablo-build.sh"
13 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" init node)
14 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" up)
15 | }
16 |
17 | dumpLogs() {
18 | echo "Saving logs of $1 to $TEST_LOGS/$1.log"
19 | mkdir -p "$TEST_LOGS"
20 | docker logs "$1" >"$TEST_LOGS/$1.log" 2>&1
21 | }
22 |
23 | networkDown() {
24 | rm -rf "$TEST_LOGS"
25 | (for name in $(docker ps --format '{{.Names}}'); do dumpLogs "$name"; done)
26 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" down)
27 | }
28 |
29 | waitForContainer() {
30 | sh "$TEST_TMP/../wait-for-container.sh" "$1" "$2"
31 | }
32 |
33 | waitForChaincode() {
34 | (cd "$TEST_TMP" && sh ../wait-for-chaincode.sh "$1" "$2" "$3" "$4")
35 | }
36 |
37 | expectInvoke() {
38 | (cd "$TEST_TMP" && sh ../expect-invoke-cli.sh "$1" "$2" "$3" "$4" "$5" "$6")
39 | }
40 |
41 | expectCommand() {
42 | sh "$TEST_TMP/../expect-command.sh" "$1" "$2"
43 | }
44 |
45 | trap networkDown EXIT
46 | trap 'networkDown ; echo "Test failed" ; exit 1' ERR SIGINT
47 |
48 | # start the network
49 | networkUp
50 |
51 | waitForContainer "orderer0.group1.orderer.example.com" "Created and started new.*my-channel1"
52 | waitForContainer "ca.org1.example.com" "Listening on http://0.0.0.0:7054"
53 | waitForContainer "peer0.org1.example.com" "Joining gossip network of channel my-channel1 with 1 organizations"
54 | waitForContainer "peer1.org1.example.com" "Joining gossip network of channel my-channel1 with 1 organizations"
55 | waitForContainer "peer0.org1.example.com" "Learning about the configured anchor peers of Org1MSP for channel my-channel1"
56 | waitForContainer "peer0.org1.example.com" "Anchor peer.*with same endpoint, skipping connecting to myself"
57 | waitForContainer "peer0.org1.example.com" "Membership view has changed. peers went online:.*peer1.org1.example.com:7042"
58 | waitForContainer "peer1.org1.example.com" "Learning about the configured anchor peers of Org1MSP for channel my-channel1"
59 | waitForContainer "peer1.org1.example.com" "Membership view has changed. peers went online:.*peer0.org1.example.com:7041"
60 |
61 | # Test simple chaincode
62 | expectInvoke "peer0.org1.example.com" "my-channel1" "chaincode1" \
63 | '{"Args":["KVContract:put", "name", "Willy Wonka"]}' \
64 | '{\"success\":\"OK\"}'
65 | expectInvoke "peer1.org1.example.com" "my-channel1" "chaincode1" \
66 | '{"Args":["KVContract:get", "name"]}' \
67 | '{\"success\":\"Willy Wonka\"}'
68 |
69 | # Verify channel query scripts
70 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" channel fetch newest my-channel1 org1 peer1)
71 | expectCommand "cat \"$TEST_TMP/newest.block\"" "KVContract:get"
72 |
73 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" channel fetch 4 my-channel1 org1 peer1 "another.block")
74 | expectCommand "cat \"$TEST_TMP/another.block\"" "KVContract:put"
75 |
76 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" channel fetch config my-channel1 org1 peer1 "channel-config.json")
77 | expectCommand "cat \"$TEST_TMP/channel-config.json\"" "\"mod_policy\": \"Admins\","
78 |
79 | expectCommand "(cd \"$TEST_TMP\" && \"$FABLO_HOME/fablo.sh\" channel getinfo my-channel1 org1 peer1)" "\"height\":6"
80 |
81 | # Reset and ensure the state is lost after reset
82 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" reset)
83 | waitForChaincode "peer0.org1.example.com" "my-channel1" "chaincode1" "0.0.1"
84 | waitForChaincode "peer1.org1.example.com" "my-channel1" "chaincode1" "0.0.1"
85 | expectInvoke "peer0.org1.example.com" "my-channel1" "chaincode1" \
86 | '{"Args":["KVContract:get", "name"]}' \
87 | '{\"error\":\"NOT_FOUND\"}'
88 |
89 | # Put some data again
90 | expectInvoke "peer0.org1.example.com" "my-channel1" "chaincode1" \
91 | '{"Args":["KVContract:put", "name", "James Bond"]}' \
92 | '{\"success\":\"OK\"}'
--------------------------------------------------------------------------------
/e2e-network/docker/test-02-raft-2orgs.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | TEST_TMP="$(rm -rf "$0.tmpdir" && mkdir -p "$0.tmpdir" && (cd "$0.tmpdir" && pwd))"
6 | TEST_LOGS="$(mkdir -p "$0.logs" && (cd "$0.logs" && pwd))"
7 | FABLO_HOME="$TEST_TMP/../../.."
8 |
9 | export FABLO_HOME
10 |
11 | CONFIG="$FABLO_HOME/samples/fablo-config-hlf2-2orgs-2chaincodes-raft.yaml"
12 |
13 | networkUp() {
14 | # separate generate and up is intentional just to check if it works
15 | "$FABLO_HOME/fablo-build.sh"
16 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" generate "$CONFIG")
17 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" up)
18 | }
19 |
20 | dumpLogs() {
21 | echo "Saving logs of $1 to $TEST_LOGS/$1.log"
22 | mkdir -p "$TEST_LOGS"
23 | docker logs "$1" >"$TEST_LOGS/$1.log" 2>&1
24 | }
25 |
26 | networkDown() {
27 | sleep 2
28 | rm -rf "$TEST_LOGS"
29 | (for name in $(docker ps --format '{{.Names}}'); do dumpLogs "$name"; done)
30 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" down)
31 | }
32 |
33 | waitForContainer() {
34 | sh "$TEST_TMP/../wait-for-container.sh" "$1" "$2"
35 | }
36 |
37 | waitForChaincode() {
38 | (cd "$TEST_TMP" && sh ../wait-for-chaincode.sh "$1" "$2" "$3" "$4")
39 | }
40 |
41 | expectInvokeRest() {
42 | sh "$TEST_TMP/../expect-invoke-rest.sh" "$1" "$2" "$3" "$4" "$5" "$6" "$7"
43 | }
44 |
45 | expectInvokeCli() {
46 | (cd "$TEST_TMP" && sh ../expect-invoke-cli.sh "$1" "$2" "$3" "$4" "$5" "$6")
47 | }
48 |
49 | trap networkDown EXIT
50 | trap 'networkDown ; echo "Test failed" ; exit 1' ERR SIGINT
51 |
52 | # start the network
53 | networkUp
54 |
55 | # check if orderers are ready
56 | waitForContainer "orderer0.group1.orderer1.com" "Starting Raft node channel=my-channel1"
57 | waitForContainer "orderer0.group1.orderer1.com" "Starting Raft node channel=my-channel2"
58 | waitForContainer "orderer1.group1.orderer1.com" "Starting Raft node channel=my-channel1"
59 | waitForContainer "orderer1.group1.orderer1.com" "Starting Raft node channel=my-channel2"
60 | waitForContainer "orderer2.group1.orderer1.com" "Starting Raft node channel=my-channel1"
61 | waitForContainer "orderer2.group1.orderer1.com" "Starting Raft node channel=my-channel2"
62 |
63 | waitForContainer "orderer0.group2.orderer2.com" "Created and started new channel my-channel3"
64 |
65 | # check if org1 is ready
66 | waitForContainer "ca.org1.example.com" "Listening on https://0.0.0.0:7054"
67 | waitForContainer "peer0.org1.example.com" "Joining gossip network of channel my-channel1 with 2 organizations"
68 | waitForContainer "peer0.org1.example.com" "Learning about the configured anchor peers of Org1MSP for channel my-channel1"
69 | waitForContainer "peer0.org1.example.com" "Anchor peer for channel my-channel1 with same endpoint, skipping connecting to myself"
70 | waitForContainer "peer0.org1.example.com" "Membership view has changed. peers went online:.*peer0.org2.example.com:7081"
71 | waitForContainer "peer1.org1.example.com" "Joining gossip network of channel my-channel2 with 2 organizations"
72 | waitForContainer "peer1.org1.example.com" "Learning about the configured anchor peers of Org1MSP for channel my-channel2"
73 | waitForContainer "peer1.org1.example.com" "Membership view has changed. peers went online:.*peer1.org2.example.com:7082"
74 |
75 | # check if org2 is ready
76 | waitForContainer "ca.org2.example.com" "Listening on https://0.0.0.0:7054"
77 | waitForContainer "peer0.org2.example.com" "Joining gossip network of channel my-channel1 with 2 organizations"
78 | waitForContainer "peer0.org2.example.com" "Learning about the configured anchor peers of Org2MSP for channel my-channel1"
79 | waitForContainer "peer0.org2.example.com" "Anchor peer for channel my-channel1 with same endpoint, skipping connecting to myself"
80 | waitForContainer "peer0.org2.example.com" "Membership view has changed. peers went online:.*peer0.org1.example.com:7061"
81 | waitForContainer "peer1.org2.example.com" "Joining gossip network of channel my-channel2 with 2 organizations"
82 | waitForContainer "peer1.org2.example.com" "Learning about the configured anchor peers of Org2MSP for channel my-channel2"
83 | waitForContainer "peer1.org2.example.com" "Anchor peer for channel my-channel2 with same endpoint, skipping connecting to myself"
84 | waitForContainer "peer1.org2.example.com" "Membership view has changed. peers went online:.*peer1.org1.example.com:7062"
85 |
86 | # check if chaincodes are instantiated on peers
87 | waitForChaincode "peer0.org1.example.com" "my-channel1" "chaincode1" "0.0.1"
88 | waitForChaincode "peer0.org2.example.com" "my-channel1" "chaincode1" "0.0.1"
89 | waitForChaincode "peer1.org1.example.com" "my-channel2" "chaincode2" "0.0.1"
90 | waitForChaincode "peer1.org2.example.com" "my-channel2" "chaincode2" "0.0.1"
91 |
92 | fablo_rest_org1="localhost:8802"
93 |
94 | # invoke Node chaincode
95 | expectInvokeRest "$fablo_rest_org1" "my-channel1" "chaincode1" \
96 | "KVContract:put" '["name", "Jack Sparrow"]' \
97 | '{"response":{"success":"OK"}}'
98 | expectInvokeCli "peer0.org2.example.com" "my-channel1" "chaincode1" \
99 | '{"Args":["KVContract:get", "name"]}' \
100 | '{\"success\":\"Jack Sparrow\"}'
101 |
102 | # invoke Java chaincode
103 | expectInvokeRest "$fablo_rest_org1" "my-channel2" "chaincode2" \
104 | "PokeballContract:createPokeball" '["id1", "Pokeball 1"]' \
105 | '{"response":""}'
106 | expectInvokeCli "peer1.org2.example.com" "my-channel2" "chaincode2" \
107 | '{"Args":["PokeballContract:readPokeball", "id1"]}' \
108 | '{\"value\":\"Pokeball 1\"}'
109 |
110 | # restart the network and wait for chaincodes
111 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" stop && "$FABLO_HOME/fablo.sh" start)
112 | waitForChaincode "peer0.org1.example.com" "my-channel1" "chaincode1" "0.0.1"
113 | waitForChaincode "peer0.org2.example.com" "my-channel1" "chaincode1" "0.0.1"
114 |
115 | # upgrade chaincode
116 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" chaincode upgrade "chaincode1" "0.0.2")
117 | waitForChaincode "peer0.org1.example.com" "my-channel1" "chaincode1" "0.0.2"
118 | waitForChaincode "peer0.org2.example.com" "my-channel1" "chaincode1" "0.0.2"
119 |
120 | # check if state is kept after update
121 | expectInvokeRest "$fablo_rest_org1" "my-channel1" "chaincode1" \
122 | "KVContract:get" '["name"]' \
123 | '{"response":{"success":"Jack Sparrow"}}'
124 |
--------------------------------------------------------------------------------
/e2e-network/docker/test-03-private-data.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | TEST_TMP="$(rm -rf "$0.tmpdir" && mkdir -p "$0.tmpdir" && (cd "$0.tmpdir" && pwd))"
6 | TEST_LOGS="$(mkdir -p "$0.logs" && (cd "$0.logs" && pwd))"
7 | FABLO_HOME="$TEST_TMP/../../.."
8 |
9 | export FABLO_HOME
10 |
11 | FABLO_CONFIG="$FABLO_HOME/samples/fablo-config-hlf2-2orgs-2chaincodes-private-data.yaml"
12 |
13 | networkUp() {
14 | "$FABLO_HOME/fablo-build.sh"
15 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" up "$FABLO_CONFIG")
16 | }
17 |
18 | dumpLogs() {
19 | echo "Saving logs of $1 to $TEST_LOGS/$1.log"
20 | mkdir -p "$TEST_LOGS"
21 | docker logs "$1" >"$TEST_LOGS/$1.log" 2>&1
22 | }
23 |
24 | networkDown() {
25 | rm -rf "$TEST_LOGS"
26 | (for name in $(docker ps --format '{{.Names}}'); do dumpLogs "$name"; done)
27 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" down)
28 | }
29 |
30 | waitForContainer() {
31 | sh "$TEST_TMP/../wait-for-container.sh" "$1" "$2"
32 | }
33 |
34 | waitForChaincode() {
35 | (cd "$TEST_TMP" && sh ../wait-for-chaincode.sh "$1" "$2" "$3" "$4")
36 | }
37 |
38 | expectInvoke() {
39 | (cd "$TEST_TMP" && sh ../expect-invoke-cli.sh "$1" "$2" "$3" "$4" "$5" "$6")
40 | }
41 |
42 | trap networkDown EXIT
43 | trap 'networkDown ; echo "Test failed" ; exit 1' ERR SIGINT
44 |
45 | # start the network
46 | networkUp
47 |
48 | # check if network is ready
49 | waitForContainer "orderer0.group1.orderer.example.com" "Created and started new channel my-channel1"
50 | waitForContainer "ca.org1.example.com" "Listening on http://0.0.0.0:7054"
51 | waitForContainer "peer0.org1.example.com" "Joining gossip network of channel my-channel1 with 2 organizations"
52 | waitForContainer "ca.org2.example.com" "Listening on http://0.0.0.0:7054"
53 | waitForContainer "peer0.org2.example.com" "Joining gossip network of channel my-channel1 with 2 organizations"
54 |
55 | waitForChaincode "peer0.org1.example.com" "my-channel1" "or-policy-chaincode" "0.0.1"
56 | waitForChaincode "peer0.org2.example.com" "my-channel1" "or-policy-chaincode" "0.0.1"
57 | waitForChaincode "peer0.org1.example.com" "my-channel1" "and-policy-chaincode" "0.0.1"
58 | waitForChaincode "peer0.org2.example.com" "my-channel1" "and-policy-chaincode" "0.0.1"
59 |
60 | sleep 3 # extra time needed: peers need to discover themselves before private data call.
61 |
62 | # Org1: Test chaincode with transient fields and private data
63 | expectInvoke "peer0.org1.example.com" "my-channel1" "or-policy-chaincode" \
64 | '{"Args":["KVContract:putPrivateMessage", "org1-collection"]}' \
65 | '{\"success\":\"OK\"}' \
66 | '{"message":"VmVyeSBzZWNyZXQgbWVzc2FnZQ=="}'
67 | expectInvoke "peer0.org1.example.com" "my-channel1" "or-policy-chaincode" \
68 | '{"Args":["KVContract:getPrivateMessage", "org1-collection"]}' \
69 | '{\"success\":\"Very secret message\"}'
70 | expectInvoke "peer0.org1.example.com" "my-channel1" "or-policy-chaincode" \
71 | '{"Args":["KVContract:verifyPrivateMessage", "org1-collection"]}' \
72 | '{\"success\":\"OK\"}' \
73 | '{"message":"VmVyeSBzZWNyZXQgbWVzc2FnZQ=="}'
74 |
75 | # Org2: Access private data from org1-collection
76 | expectInvoke "peer0.org2.example.com" "my-channel1" "or-policy-chaincode" \
77 | '{"Args":["KVContract:verifyPrivateMessage", "org1-collection"]}' \
78 | '{\"success\":\"OK\"}' \
79 | '{"message":"VmVyeSBzZWNyZXQgbWVzc2FnZQ=="}'
80 | expectInvoke "peer0.org2.example.com" "my-channel1" "or-policy-chaincode" \
81 | '{"Args":["KVContract:verifyPrivateMessage", "org1-collection"]}' \
82 | '{\"error\":\"VERIFICATION_FAILED\"}' \
83 | '{"message":"XXXXXSBzZWNyZXQgbWVzc2FnZQ=="}'
84 | expectInvoke "peer0.org2.example.com" "my-channel1" "or-policy-chaincode" \
85 | '{"Args":["KVContract:getPrivateMessage", "org1-collection"]}' \
86 | 'tx creator does not have read access permission on privatedata in chaincodeName:or-policy-chaincode collectionName: org1-collection'
87 | expectInvoke "peer0.org2.example.com" "my-channel1" "or-policy-chaincode" \
88 | '{"Args":["KVContract:putPrivateMessage", "org1-collection"]}' \
89 | 'tx creator does not have write access permission on privatedata in chaincodeName:or-policy-chaincode collectionName: org1-collection' \
90 | '{"message":"Q29ycnVwdGVkIG1lc3NhZ2U="}'
91 |
92 | # Org1 and Org2: Test chaincode with AND endorsement policy
93 | expectInvoke "peer0.org2.example.com,peer0.org1.example.com" "my-channel1" "and-policy-chaincode" \
94 | '{"Args":["KVContract:putPrivateMessage", "both-orgs-collection"]}' \
95 | '{\"success\":\"OK\"}' \
96 | '{"message":"QW5kIGFub3RoZXIgb25l"}'
97 | expectInvoke "peer0.org1.example.com,peer0.org2.example.com" "my-channel1" "and-policy-chaincode" \
98 | '{"Args":["KVContract:getPrivateMessage", "both-orgs-collection"]}' \
99 | '{\"success\":\"And another one\"}'
100 |
--------------------------------------------------------------------------------
/e2e-network/docker/test-04-snapshot.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | TEST_TMP="$(rm -rf "$0.tmpdir" && mkdir -p "$0.tmpdir" && (cd "$0.tmpdir" && pwd))"
6 | TEST_LOGS="$(mkdir -p "$0.logs" && (cd "$0.logs" && pwd))"
7 | FABLO_HOME="$TEST_TMP/../../.."
8 |
9 | export FABLO_HOME
10 |
11 | CONFIG="$FABLO_HOME/samples/fablo-config-hlf2-1org-1chaincode-raft-explorer.json"
12 |
13 | networkUp() {
14 | "$FABLO_HOME/fablo-build.sh"
15 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" up "$CONFIG")
16 | }
17 |
18 | dumpLogs() {
19 | echo "Saving logs of $1 to $TEST_LOGS/$1.log"
20 | mkdir -p "$TEST_LOGS"
21 | docker logs "$1" >"$TEST_LOGS/$1.log" 2>&1
22 | }
23 |
24 | networkDown() {
25 | sleep 2
26 | (for name in $(docker ps --format '{{.Names}}'); do dumpLogs "$name"; done)
27 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" down)
28 | }
29 |
30 | waitForContainer() {
31 | sh "$TEST_TMP/../wait-for-container.sh" "$1" "$2"
32 | }
33 |
34 | waitForChaincode() {
35 | (cd "$TEST_TMP" && sh ../wait-for-chaincode.sh "$1" "$2" "$3" "$4")
36 | }
37 |
38 | expectInvokeRest() {
39 | sh "$TEST_TMP/../expect-invoke-rest.sh" "$1" "$2" "$3" "$4" "$5" "$6" "$7"
40 | }
41 |
42 | expectCARest() {
43 | sh "$TEST_TMP/../expect-ca-rest.sh" "$1" "$2" "$3" "$4"
44 | }
45 |
46 | trap networkDown EXIT
47 | trap 'networkDown ; echo "Test failed" ; exit 1' ERR SIGINT
48 |
49 | # start the network
50 | networkUp
51 |
52 | # check if all nodes are ready
53 | waitForContainer "orderer0.group1.orderer.example.com" "Starting Raft node channel=my-channel1"
54 | waitForContainer "db.ca.org1.example.com" "database system is ready to accept connections"
55 | waitForContainer "ca.org1.example.com" "Listening on https://0.0.0.0:7054"
56 | waitForContainer "couchdb.peer0.org1.example.com" "Apache CouchDB has started. Time to relax."
57 | waitForContainer "peer0.org1.example.com" "Joining gossip network of channel my-channel1 with 1 organizations"
58 | waitForContainer "db.explorer.example.com" "database system is ready to accept connections" "200"
59 | waitForContainer "explorer.example.com" "Successfully created channel event hub for \[my-channel1\]" "200"
60 | waitForChaincode "peer0.org1.example.com" "my-channel1" "chaincode1" "0.0.1"
61 |
62 | fablo_rest_org1="localhost:8801"
63 | snapshot_name="fablo-snapshot-$(date -u +"%Y%m%d%H%M%S")"
64 |
65 | # register and enroll test user
66 | admin_token_response="$(expectCARest "$fablo_rest_org1/user/enroll" '' '{"id": "admin", "secret": "adminpw"}' 'token')"
67 | echo "$admin_token_response"
68 | admin_token="$(echo "$admin_token_response" | jq -r '.token')"
69 |
70 | register_response="$(expectCARest "$fablo_rest_org1/user/register" "$admin_token" '{"id": "gordon", "secret": "gordonpw"}' 'ok')"
71 | echo "$register_response"
72 |
73 | user_token_response="$(expectCARest "$fablo_rest_org1/user/enroll" '' '{"id": "gordon", "secret": "gordonpw"}' 'token')"
74 | echo "$user_token_response"
75 | user_token="$(echo "$user_token_response" | jq -r '.token')"
76 |
77 | # save some data
78 | expectInvokeRest "$fablo_rest_org1 $user_token" "my-channel1" "chaincode1" \
79 | "KVContract:put" '["name", "Mr Freeze"]' \
80 | '{"response":{"success":"OK"}}'
81 | expectInvokeRest "$fablo_rest_org1 $user_token" "my-channel1" "chaincode1" \
82 | "KVContract:putPrivateMessage" '["_implicit_org_Org1MSP"]' \
83 | '{"success":"OK"}' \
84 | '{"message":"RHIgVmljdG9yIEZyaWVz"}'
85 |
86 | # create snapshot
87 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" snapshot "$snapshot_name")
88 |
89 | # overwrite the data
90 | expectInvokeRest "$fablo_rest_org1 $user_token" "my-channel1" "chaincode1" \
91 | "KVContract:put" '["name", "Poison Ivy"]' \
92 | '{"response":{"success":"OK"}}'
93 | expectInvokeRest "$fablo_rest_org1 $user_token" "my-channel1" "chaincode1" \
94 | "KVContract:putPrivateMessage" '["_implicit_org_Org1MSP"]' \
95 | '{"success":"OK"}' \
96 | '{"message":"RHIgUGFtZWxhIElzbGV5"}'
97 |
98 | # verify it is updated
99 | expectInvokeRest "$fablo_rest_org1 $user_token" "my-channel1" "chaincode1" \
100 | "KVContract:get" '["name"]' \
101 | '{"response":{"success":"Poison Ivy"}}'
102 | expectInvokeRest "$fablo_rest_org1 $user_token" "my-channel1" "chaincode1" \
103 | "KVContract:getPrivateMessage" '["_implicit_org_Org1MSP"]' \
104 | '{"success":"RHIgUGFtZWxhIElzbGV5"}'
105 |
106 | # restore hook to update fabric version
107 | hook_command="perl -i -pe 's/FABRIC_VERSION=2\.3\.3/FABRIC_VERSION=2\.4\.2/g' ./fablo-target/fabric-docker/.env"
108 |
109 | # prune the network and restore from snapshot
110 | (
111 | cd "$TEST_TMP" &&
112 | "$FABLO_HOME/fablo.sh" prune &&
113 | "$FABLO_HOME/fablo.sh" restore "$snapshot_name" "$hook_command" &&
114 | "$FABLO_HOME/fablo.sh" start
115 | )
116 | waitForChaincode "peer0.org1.example.com" "my-channel1" "chaincode1" "0.0.1"
117 |
118 | sleep 5
119 |
120 | user_token_response="$(expectCARest "$fablo_rest_org1/user/enroll" '' '{"id": "gordon", "secret": "gordonpw"}' 'token')"
121 | echo "$user_token_response"
122 | user_token="$(echo "$user_token_response" | jq -r '.token')"
123 |
124 | # check if state is kept after restoration
125 | expectInvokeRest "$fablo_rest_org1 $user_token" "my-channel1" "chaincode1" \
126 | "KVContract:get" '["name"]' \
127 | '{"response":{"success":"Mr Freeze"}}'
128 | expectInvokeRest "$fablo_rest_org1 $user_token" "my-channel1" "chaincode1" \
129 | "KVContract:getPrivateMessage" '["_implicit_org_Org1MSP"]' \
130 | '{"success":"RHIgVmljdG9yIEZyaWVz"}'
131 |
--------------------------------------------------------------------------------
/e2e-network/docker/test-05-version3-BFT.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -eu
4 |
5 | TEST_TMP="$(rm -rf "$0.tmpdir" && mkdir -p "$0.tmpdir" && (cd "$0.tmpdir" && pwd))"
6 | TEST_LOGS="$(mkdir -p "$0.logs" && (cd "$0.logs" && pwd))"
7 | FABLO_HOME="$TEST_TMP/../../.."
8 |
9 | export FABLO_HOME
10 |
11 | CONFIG="$FABLO_HOME/samples/fablo-config-hlf3-bft-1orgs-1chaincode.json"
12 |
13 | networkUp() {
14 | "$FABLO_HOME/fablo-build.sh"
15 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" generate "$CONFIG")
16 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" up)
17 | }
18 |
19 | dumpLogs() {
20 | echo "Saving logs of $1 to $TEST_LOGS/$1.log"
21 | mkdir -p "$TEST_LOGS"
22 | docker logs "$1" >"$TEST_LOGS/$1.log" 2>&1
23 | }
24 |
25 | networkDown() {
26 | rm -rf "$TEST_LOGS"
27 | (for name in $(docker ps --format '{{.Names}}'); do dumpLogs "$name"; done)
28 | dumpLogs orderer0.group1.orderer.example.com
29 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" down)
30 | }
31 |
32 | waitForContainer() {
33 | sh "$TEST_TMP/../wait-for-container.sh" "$1" "$2"
34 | }
35 |
36 | waitForChaincode() {
37 | (cd "$TEST_TMP" && sh ../wait-for-chaincode.sh "$1" "$2" "$3" "$4")
38 | }
39 |
40 | expectInvoke() {
41 | (cd "$TEST_TMP" && sh ../expect-invoke-cli.sh "$1" "$2" "$3" "$4" "$5" "")
42 | }
43 |
44 | expectCommand() {
45 | sh "$TEST_TMP/../expect-command.sh" "$1" "$2"
46 | }
47 |
48 | trap networkDown EXIT
49 | trap 'networkDown ; echo "Test failed" ; exit 1' ERR SIGINT
50 |
51 | # start the network
52 | networkUp
53 |
54 | waitForContainer "orderer0.group1.orderer.example.com" "Channel created"
55 | waitForContainer "orderer1.group1.orderer.example.com" "Channel created"
56 | waitForContainer "orderer2.group1.orderer.example.com" "Channel created"
57 | waitForContainer "orderer3.group1.orderer.example.com" "Channel created"
58 | waitForContainer "ca.org1.example.com" "Listening on https://0.0.0.0:7054"
59 | waitForContainer "peer0.org1.example.com" "Joining gossip network of channel my-channel1 with 1 organizations"
60 | waitForContainer "peer1.org1.example.com" "Joining gossip network of channel my-channel1 with 1 organizations"
61 | waitForContainer "peer0.org1.example.com" "Learning about the configured anchor peers of Org1MSP for channel my-channel1"
62 | waitForContainer "peer0.org1.example.com" "Anchor peer.*with same endpoint, skipping connecting to myself"
63 | waitForContainer "peer0.org1.example.com" "Membership view has changed. peers went online:.*peer1.org1.example.com:7042"
64 | waitForContainer "peer1.org1.example.com" "Learning about the configured anchor peers of Org1MSP for channel my-channel1"
65 | waitForContainer "peer1.org1.example.com" "Membership view has changed. peers went online:.*peer0.org1.example.com:7041"
66 |
67 | # Test simple chaincode
68 | expectInvoke "peer0.org1.example.com" "my-channel1" "chaincode1" \
69 | '{"Args":["KVContract:put", "name", "Willy Wonka"]}' \
70 | '{\"success\":\"OK\"}'
71 | expectInvoke "peer1.org1.example.com" "my-channel1" "chaincode1" \
72 | '{"Args":["KVContract:get", "name"]}' \
73 | '{\"success\":\"Willy Wonka\"}'
74 |
75 | # Verify channel query scripts
76 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" channel fetch newest my-channel1 org1 peer1)
77 | expectCommand "cat \"$TEST_TMP/newest.block\"" "KVContract:get"
78 |
79 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" channel fetch 3 my-channel1 org1 peer1 "another.block")
80 | expectCommand "cat \"$TEST_TMP/another.block\"" "KVContract:put"
81 |
82 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" channel fetch config my-channel1 org1 peer1 "channel-config.json")
83 | expectCommand "cat \"$TEST_TMP/channel-config.json\"" "\"mod_policy\": \"Admins\","
84 |
85 | expectCommand "(cd \"$TEST_TMP\" && \"$FABLO_HOME/fablo.sh\" channel getinfo my-channel1 org1 peer1)" "\"height\":5"
86 |
87 | echo "🎉 Test passed! 🎉"
--------------------------------------------------------------------------------
/e2e-network/docker/test-05-version3.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -eu
4 |
5 | TEST_TMP="$(rm -rf "$0.tmpdir" && mkdir -p "$0.tmpdir" && (cd "$0.tmpdir" && pwd))"
6 | TEST_LOGS="$(mkdir -p "$0.logs" && (cd "$0.logs" && pwd))"
7 | FABLO_HOME="$TEST_TMP/../../.."
8 |
9 | export FABLO_HOME
10 |
11 | CONFIG="$FABLO_HOME/samples/fablo-config-hlf3-1orgs-1chaincode.json"
12 |
13 | networkUp() {
14 | "$FABLO_HOME/fablo-build.sh"
15 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" generate "$CONFIG")
16 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" up)
17 | }
18 |
19 | dumpLogs() {
20 | echo "Saving logs of $1 to $TEST_LOGS/$1.log"
21 | mkdir -p "$TEST_LOGS"
22 | docker logs "$1" >"$TEST_LOGS/$1.log" 2>&1
23 | }
24 |
25 | networkDown() {
26 | rm -rf "$TEST_LOGS"
27 | (for name in $(docker ps --format '{{.Names}}'); do dumpLogs "$name"; done)
28 | dumpLogs orderer0.group1.orderer.example.com
29 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" down)
30 | }
31 |
32 | waitForContainer() {
33 | sh "$TEST_TMP/../wait-for-container.sh" "$1" "$2"
34 | }
35 |
36 | waitForChaincode() {
37 | (cd "$TEST_TMP" && sh ../wait-for-chaincode.sh "$1" "$2" "$3" "$4")
38 | }
39 |
40 | expectInvoke() {
41 | (cd "$TEST_TMP" && sh ../expect-invoke-cli.sh "$1" "$2" "$3" "$4" "$5" "")
42 | }
43 |
44 | expectCommand() {
45 | sh "$TEST_TMP/../expect-command.sh" "$1" "$2"
46 | }
47 |
48 | trap networkDown EXIT
49 | trap 'networkDown ; echo "Test failed" ; exit 1' ERR SIGINT
50 |
51 | # start the network
52 | networkUp
53 |
54 | waitForContainer "orderer0.group1.orderer.example.com" "Starting raft node as part of a new channel channel=my-channel1"
55 | waitForContainer "ca.org1.example.com" "Listening on https://0.0.0.0:7054"
56 | waitForContainer "peer0.org1.example.com" "Joining gossip network of channel my-channel1 with 1 organizations"
57 | waitForContainer "peer1.org1.example.com" "Joining gossip network of channel my-channel1 with 1 organizations"
58 | waitForContainer "peer0.org1.example.com" "Learning about the configured anchor peers of Org1MSP for channel my-channel1"
59 | waitForContainer "peer0.org1.example.com" "Anchor peer.*with same endpoint, skipping connecting to myself"
60 | waitForContainer "peer0.org1.example.com" "Membership view has changed. peers went online:.*peer1.org1.example.com:7042"
61 | waitForContainer "peer1.org1.example.com" "Learning about the configured anchor peers of Org1MSP for channel my-channel1"
62 | waitForContainer "peer1.org1.example.com" "Membership view has changed. peers went online:.*peer0.org1.example.com:7041"
63 |
64 | # Test simple chaincode
65 | expectInvoke "peer0.org1.example.com" "my-channel1" "chaincode1" \
66 | '{"Args":["KVContract:put", "name", "Willy Wonka"]}' \
67 | '{\"success\":\"OK\"}'
68 | expectInvoke "peer1.org1.example.com" "my-channel1" "chaincode1" \
69 | '{"Args":["KVContract:get", "name"]}' \
70 | '{\"success\":\"Willy Wonka\"}'
71 |
72 | # Verify channel query scripts
73 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" channel fetch newest my-channel1 org1 peer1)
74 | expectCommand "cat \"$TEST_TMP/newest.block\"" "KVContract:get"
75 |
76 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" channel fetch 3 my-channel1 org1 peer1 "another.block")
77 | expectCommand "cat \"$TEST_TMP/another.block\"" "KVContract:put"
78 |
79 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" channel fetch config my-channel1 org1 peer1 "channel-config.json")
80 | expectCommand "cat \"$TEST_TMP/channel-config.json\"" "\"mod_policy\": \"Admins\","
81 |
82 | expectCommand "(cd \"$TEST_TMP\" && \"$FABLO_HOME/fablo.sh\" channel getinfo my-channel1 org1 peer1)" "\"height\":5"
83 |
84 | echo "🎉 Test passed! 🎉"
--------------------------------------------------------------------------------
/e2e-network/docker/wait-for-chaincode.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | peer=$1
4 | channel=$2
5 | chaincode=$3
6 | version=$4
7 | search_string="Name: $chaincode, Version: $version"
8 |
9 | if [ -z "$version" ]; then
10 | echo "Usage: ./wait-for-chaincode.sh [peer:port] [channel] [chaincode] [version]"
11 | exit 1
12 | fi
13 |
14 | listChaincodes() {
15 | "$FABLO_HOME/fablo.sh" chaincodes list "$peer" "$channel"
16 | }
17 |
18 | for i in $(seq 1 90); do
19 | echo "➜ verifying if chaincode ($chaincode/$version) is committed on $channel/$peer ($i)..."
20 | if listChaincodes 2>&1 | grep "$search_string"; then
21 | listChaincodes
22 | echo "✅ ok: Chaincode $chaincode/$version is ready on $channel/$peer!"
23 | exit 0
24 | else
25 | sleep 1
26 | fi
27 | done
28 |
29 | #timeout
30 | echo "❌ failed: Failed to verify chaincode $chaincode/$version on $channel/$peer"
31 | listChaincodes
32 | exit 1
33 |
--------------------------------------------------------------------------------
/e2e-network/docker/wait-for-container.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | container="$1"
4 | expected_message="$2"
5 | max_attempts="${3:-90}"
6 |
7 | end='\e[0m'
8 | darkGray='\e[90m'
9 |
10 | if [ -z "$expected_message" ]; then
11 | echo "Usage: ./wait-for-container.sh [container_name] [expected_message]"
12 | exit 1
13 | fi
14 |
15 | for i in $(seq 1 "$max_attempts"); do
16 | echo "➜ verifying if container $container logs contain ($i)... ${darkGray}'$expected_message'${end}"
17 |
18 | if docker logs "$container" 2>&1 | grep -q "$expected_message"; then
19 | echo "✅ ok: Container $container is ready!"
20 | exit 0
21 | else
22 | sleep 1
23 | fi
24 | done
25 |
26 | #timeout
27 | echo "❌ failed: Container $container logs does not contain ${darkGray}'$expected_message'${end}"
28 | echo "Last log messages:"
29 | docker logs "$container" | tail -n 30
30 | exit 1
31 |
--------------------------------------------------------------------------------
/e2e-network/k8s/expect-invoke-cli.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | user="$1"
6 | peer="$2"
7 | channel="$3"
8 | chaincode="$4"
9 | fcn="$5"
10 | key="$6"
11 | value="$7"
12 | expected="$8"
13 | config="$(find . -type f -iname 'org1.yaml')"
14 |
15 |
16 |
17 |
18 | if [ -z "$expected" ]; then
19 | echo "Usage: ./expect-invoke.sh [user] [peer] [chaincdoe] [channel] [fcn] [arg1] [arg2] [expected_substring]"
20 | exit 1
21 | fi
22 |
23 | label="Invoke $channel/$peer"
24 | echo ""
25 | echo "➜ testing: $label"
26 |
27 | response="$(
28 | kubectl hlf chaincode invoke \
29 | --config "$config" \
30 | --user "$user" \
31 | --peer "$peer" \
32 | --chaincode "$chaincode" \
33 | --channel "$channel" \
34 | --fcn "$fcn" \
35 | -a "$key" \
36 | ${value:+ -a "$value"} \
37 |
38 | # shellcheck disable=SC2188
39 | 2>&1
40 | )"
41 |
42 | echo "$response"
43 |
44 | if echo "$response" | grep -F "$expected"; then
45 | echo "✅ ok (cli): $label"
46 | else
47 | echo "❌ failed (cli): $label | expected: $expected"
48 | exit 1
49 | fi
50 |
--------------------------------------------------------------------------------
/e2e-network/k8s/test-01-simple-k8s.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | TEST_TMP="$(rm -rf "$0.tmpdir" && mkdir -p "$0.tmpdir" && (cd "$0.tmpdir" && pwd))"
6 | TEST_LOGS="$(mkdir -p "$0.logs" && (cd "$0.logs" && pwd))"
7 | FABLO_HOME="$TEST_TMP/../../.."
8 |
9 | networkUp() {
10 | "$FABLO_HOME/fablo-build.sh"
11 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" init kubernetes node)
12 | (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" up)
13 | }
14 |
15 | dumpLogs() {
16 | echo "Saving logs of $1 to $TEST_LOGS/$1.log"
17 | mkdir -p "$TEST_LOGS"
18 | docker logs "$1" >"$TEST_LOGS/$1.log" 2>&1
19 | }
20 |
21 | networkDown() {
22 | rm -rf "$TEST_LOGS"
23 | (cd "$TEST_TMP" && "$(find . -type f -iname 'fabric-k8s.sh')" down)
24 | }
25 |
26 | waitForContainer() {
27 | sh "$TEST_TMP/../wait-for-container.sh" "$1" "$2"
28 | }
29 |
30 | waitForChaincode() {
31 | sh "$TEST_TMP/../wait-for-chaincode.sh" "$1" "$2" "$3" "$4" "$5"
32 | }
33 |
34 | expectInvoke() {
35 | sh "$TEST_TMP/../expect-invoke-cli.sh" "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8"
36 | }
37 |
38 | trap networkDown EXIT
39 | trap 'networkDown ; echo "Test failed" ; exit 1' ERR SIGINT
40 |
41 | # start the network
42 | networkUp
43 |
44 | peer0="$(kubectl get pods | grep peer0 | tr -s ' ' | cut -d ':' -f 1 | cut -d ' ' -f 1 | head -n 1) peer"
45 | peer1="$(kubectl get pods | grep peer1 | tr -s ' ' | cut -d ':' -f 1 | cut -d ' ' -f 1 | head -n 1) peer"
46 | ca=$(kubectl get pods | grep org1-ca | tr -s ' ' | cut -d ':' -f 1 | cut -d ' ' -f 1)
47 | orderer=$(kubectl get pods | grep orderer-node | tr -s ' ' | cut -d ':' -f 1 | cut -d ' ' -f 1)
48 |
49 | waitForContainer "$orderer" "Starting raft node as part of a new channel channel=my-channel1 node=1"
50 | waitForContainer "$ca" "Listening on https://0.0.0.0:7054"
51 | waitForContainer "$peer0" "Joining gossip network of channel my-channel1 with 1 organizations"
52 | waitForContainer "$peer1" "Joining gossip network of channel my-channel1 with 1 organizations"
53 | waitForContainer "$peer0" "Learning about the configured anchor peers of Org1MSP for channel my-channel1"
54 | waitForContainer "$peer0" "Anchor peer.*with same endpoint, skipping connecting to myself"
55 | waitForContainer "$peer0" "Membership view has changed. peers went online:"
56 | waitForContainer "$peer1" "Learning about the configured anchor peers of Org1MSP for channel my-channel1"
57 | waitForContainer "$peer1" "Membership view has changed. peers went online:"
58 |
59 |
60 | #Test simple chaincode
61 | expectInvoke "admin" "peer1.default" "my-channel1" "chaincode1" \
62 | "put" "[\"name\"]" "Willy Wonka" "{\"success\":\"OK\"}"
63 | expectInvoke "admin" "peer1.default" "my-channel1" "chaincode1" \
64 | "get" "[\"name\"]" "" '{"success":"Willy Wonka"}'
65 |
66 |
67 | # Reset and ensure the state is lost after reset
68 | (cd "$TEST_TMP" && "$(find . -type f -iname 'fabric-k8s.sh')" reset)
69 | waitForChaincode "admin" "peer0.default" "my-channel1" "chaincode1" "1.0"
70 | waitForChaincode "admin" "peer1.default" "my-channel1" "chaincode1" "1.0"
71 |
72 | expectInvoke "admin" "peer1.default" "my-channel1" "chaincode1" \
73 | "get" "[\"name\"]" "" '{"error":"NOT_FOUND"}'
74 |
75 | # Put some data again
76 | expectInvoke "admin" "peer1.default" "my-channel1" "chaincode1" \
77 | "put" "[\"name\"]" "James Bond" "{\"success\":\"OK\"}"
78 |
--------------------------------------------------------------------------------
/e2e-network/k8s/wait-for-chaincode.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | user=$1
4 | peer=$2
5 | channel=$3
6 | chaincode=$4
7 | version=$5
8 | config="$(find . -type f -iname 'org1.yaml')"
9 |
10 | if [ -z "$version" ]; then
11 | echo "Usage: ./wait-for-chaincode.sh [user] [peer] [channel] [chaincode] [version]"
12 | exit 1
13 | fi
14 |
15 | listChaincodes() {
16 | kubectl hlf chaincode querycommitted \
17 | --config "$config" \
18 | --user "$user" \
19 | --peer "$peer" \
20 | --channel "$channel"
21 | }
22 |
23 | echo "➜ verifying if chaincode ($chaincode/$version) is committed on $channel/$peer ..."
24 |
25 | if listChaincodes 2>&1 | grep -q "$chaincode\|$version"; then
26 | echo "✅ ok: Chaincode $chaincode/$version is ready on $channel/$peer!"
27 | exit 0
28 | else
29 | sleep 1
30 | fi
31 |
32 | #timeout
33 | echo "❌ failed: Failed to verify chaincode $chaincode/$version on $channel/$peer"
34 | listChaincodes
35 | exit 1
36 |
--------------------------------------------------------------------------------
/e2e-network/k8s/wait-for-container.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | container="$1"
4 | expected_message="$2"
5 | max_attempts="${3:-10}"
6 |
7 | end="$(printf '\e[0m')"
8 | darkGray="$(printf '\e[90m')"
9 |
10 | if [ -z "$expected_message" ]; then
11 | echo "Usage: ./wait-for-container.sh [container_name] [expected_message]"
12 | exit 1
13 | fi
14 |
15 | for i in $(seq 1 "$max_attempts"); do
16 | echo "➜ verifying if container $container logs contain ($i)... ${darkGray}'$expected_message'${end}"
17 |
18 | # in some cases you need to pass two arguments at once
19 | # shellcheck disable=SC2086
20 | if kubectl logs $container 2>&1 | grep -q "$expected_message"; then
21 | echo "✅ ok: Container $container is ready!"
22 | exit 0
23 | else
24 | sleep 1
25 | fi
26 | done
27 |
28 | #timeout
29 | echo "❌ failed: Container $container logs does not contain ${darkGray}'$expected_message'${end}"
30 |
31 | exit 1
32 |
--------------------------------------------------------------------------------
/e2e/TestCommands.ts:
--------------------------------------------------------------------------------
1 | import { execSync } from "child_process";
2 |
3 | interface CommandOutput {
4 | status: number;
5 | output: string;
6 | outputJson: () => Record;
7 | }
8 |
9 | const commandOutput = (status: number, output: string): CommandOutput => ({
10 | status,
11 | output,
12 | outputJson() {
13 | try {
14 | return JSON.parse(output);
15 | } catch (e) {
16 | // eslint-disable-next-line no-console
17 | console.error(e);
18 | // eslint-disable-next-line no-console
19 | console.error(output);
20 | throw e;
21 | }
22 | },
23 | });
24 |
25 | const executeCommand = (c: string, noConsole = false): CommandOutput => {
26 | // eslint-disable-next-line no-console
27 | const log = !noConsole ? (out: string) => console.log(out) : () => ({});
28 | try {
29 | log(c);
30 | const output = execSync(c, { encoding: "utf-8" });
31 | log(output);
32 | return commandOutput(0, output);
33 | } catch (e) {
34 | const output = ((e as { output?: string[] }).output ?? []).join("");
35 | // eslint-disable-next-line no-console
36 | console.error(`Error executing command ${c}`, e, output);
37 | return commandOutput((e as { status: number }).status, output);
38 | }
39 | };
40 |
41 | class TestCommands {
42 | static success = (): unknown => expect.objectContaining({ status: 0 });
43 |
44 | static failure = (): unknown => expect.objectContaining({ status: 1 });
45 |
46 | readonly relativeRoot: string;
47 |
48 | constructor(readonly workdir: string) {
49 | this.relativeRoot = workdir.replace(/[^\/]+/g, "..");
50 | }
51 |
52 | execute(command: string): CommandOutput {
53 | return executeCommand(command);
54 | }
55 |
56 | fabloExec(command: string, noConsole = false): CommandOutput {
57 | return executeCommand(`cd ${this.workdir} && ${this.relativeRoot}/fablo.sh ${command}`, noConsole);
58 | }
59 |
60 | getFiles(dir?: string): string[] {
61 | return executeCommand(`find ${dir || this.workdir} -type f`)
62 | .output.split("\n")
63 | .filter((s) => !!s.length)
64 | .sort();
65 | }
66 |
67 | getFileContent(file: string): string {
68 | return executeCommand(`cat "${this.workdir}/${file}"`, true).output;
69 | }
70 |
71 | cleanupWorkdir(): void {
72 | execSync(`rm -rf ${this.workdir} ; mkdir -p ${this.workdir}`);
73 | }
74 | }
75 |
76 | export default TestCommands;
77 | export { CommandOutput };
78 |
--------------------------------------------------------------------------------
/e2e/create-test-cases.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | FABLO_HOME="$(dirname "$0")/.."
6 |
7 | (
8 | cd "$FABLO_HOME/samples"
9 | for f in fablo-config-*; do
10 | echo "import performTests from \"./performTests\";
11 |
12 | const config = \"samples/$f\";
13 |
14 | describe(config, () => {
15 | performTests(config);
16 | });" >"../e2e/${f}.test.ts"
17 | done
18 | )
19 |
--------------------------------------------------------------------------------
/e2e/extendConfig.test.ts:
--------------------------------------------------------------------------------
1 | import TestCommands from "./TestCommands";
2 | import parseFabloConfig from "../src/utils/parseFabloConfig";
3 | import extendConfig from "../src/extend-config/extendConfig";
4 |
5 | const commands = new TestCommands("e2e/__tmp__/extend-config-tests");
6 |
7 | describe("extend config", () => {
8 | beforeEach(() => commands.cleanupWorkdir());
9 |
10 | beforeAll(() => {
11 | process.env.FABLO_CONFIG = "";
12 | process.env.CHAINCODES_BASE_DIR = "";
13 | });
14 |
15 | afterAll(() => {
16 | delete process.env.FABLO_CONFIG;
17 | delete process.env.CHAINCODES_BASE_DIR;
18 | });
19 |
20 | const files = commands.getFiles("samples/*.json").concat(commands.getFiles("samples/*.yaml"));
21 |
22 | files.forEach((file) => {
23 | it(file, () => {
24 | const fileContent = commands.getFileContent(`${commands.relativeRoot}/${file}`);
25 | const json = parseFabloConfig(fileContent);
26 |
27 | // when
28 | const extended = extendConfig(json);
29 |
30 | // then
31 | expect(extended).toMatchSnapshot();
32 | });
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/e2e/fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts:
--------------------------------------------------------------------------------
1 | import performTests from "./performTests";
2 |
3 | const config = "samples/fablo-config-hlf2-1org-1chaincode-k8s.json";
4 |
5 | describe(config, () => {
6 | performTests(config);
7 | });
8 |
--------------------------------------------------------------------------------
/e2e/fablo-config-hlf2-1org-1chaincode-raft-explorer.json.test.ts:
--------------------------------------------------------------------------------
1 | import performTests from "./performTests";
2 |
3 | // TODO RENAME
4 | const config = "samples/fablo-config-hlf2-1org-1chaincode-raft-explorer.json";
5 |
6 | describe(config, () => {
7 | performTests(config);
8 | });
9 |
--------------------------------------------------------------------------------
/e2e/fablo-config-hlf2-1org-1chaincode.json.test.ts:
--------------------------------------------------------------------------------
1 | import performTests from "./performTests";
2 |
3 | const config = "samples/fablo-config-hlf2-1org-1chaincode.json";
4 |
5 | describe(config, () => {
6 | performTests(config);
7 | });
8 |
--------------------------------------------------------------------------------
/e2e/fablo-config-hlf2-2orgs-2chaincodes-private-data.yaml.test.ts:
--------------------------------------------------------------------------------
1 | import performTests from "./performTests";
2 |
3 | const config = "samples/fablo-config-hlf2-2orgs-2chaincodes-private-data.yaml";
4 |
5 | describe(config, () => {
6 | performTests(config);
7 | });
8 |
--------------------------------------------------------------------------------
/e2e/fablo-config-hlf2-2orgs-2chaincodes-raft.yaml.test.ts:
--------------------------------------------------------------------------------
1 | import performTests from "./performTests";
2 |
3 | const config = "samples/fablo-config-hlf2-2orgs-2chaincodes-raft.yaml";
4 |
5 | describe(config, () => {
6 | performTests(config);
7 | });
8 |
--------------------------------------------------------------------------------
/e2e/fablo-config-hlf2-3orgs-1chaincode-raft-explorer.json.test.ts:
--------------------------------------------------------------------------------
1 | import performTests from "./performTests";
2 |
3 | const config = "samples/fablo-config-hlf2-3orgs-1chaincode-raft-explorer.json";
4 |
5 | describe(config, () => {
6 | performTests(config);
7 | });
8 |
--------------------------------------------------------------------------------
/e2e/fablo-config-hlf3-1orgs-1chaincode.json.test.ts:
--------------------------------------------------------------------------------
1 | import performTests from "./performTests";
2 |
3 | const config = "samples/fablo-config-hlf3-1orgs-1chaincode.json";
4 |
5 | describe(config, () => {
6 | performTests(config);
7 | });
8 |
--------------------------------------------------------------------------------
/e2e/fablo-config-hlf3-bft-1orgs-1chaincode.json.test.ts:
--------------------------------------------------------------------------------
1 | import performTests from "./performTests";
2 |
3 | const config = "samples/fablo-config-hlf3-bft-1orgs-1chaincode.json";
4 |
5 | describe(config, () => {
6 | performTests(config);
7 | });
8 |
--------------------------------------------------------------------------------
/e2e/performTests.ts:
--------------------------------------------------------------------------------
1 | import TestCommands from "./TestCommands";
2 | import { resolve } from "path";
3 |
4 | const testFilesExistence = (config: string, files: string[]) => {
5 | it(`should create proper files from ${config}`, () => {
6 | expect(files).toMatchSnapshot();
7 | });
8 | };
9 |
10 | const testFilesContent = (commands: TestCommands, config: string, files: string[]) =>
11 | files.forEach((f) => {
12 | it(`should create proper ${f} from ${config}`, () => {
13 | const content = commands.getFileContent(`${commands.relativeRoot}/${f}`);
14 | const rootPath = resolve(__dirname + "/../");
15 | const cleaned = content
16 | .replace(/FABLO_BUILD=(.*?)(\n|$)/g, "FABLO_BUILD=\n")
17 | .replace(/FABLO_CONFIG=(.*?)(\n|$)/g, "FABLO_CONFIG=\n")
18 | .replace(/CHAINCODES_BASE_DIR=(.*?)(\n|$)/g, "CHAINCODES_BASE_DIR=\n")
19 | .replace(/COMPOSE_PROJECT_NAME=(.*?)(\n|$)/g, "COMPOSE_PROJECT_NAME=\n")
20 | .replace(new RegExp(rootPath, "g"), "");
21 | expect(cleaned).toMatchSnapshot();
22 | });
23 | });
24 |
25 | export default (config: string): void => {
26 | const commands = new TestCommands(`e2e/__tmp__/${config}.tmpdir`);
27 | commands.cleanupWorkdir();
28 | commands.fabloExec(`generate "${commands.relativeRoot}/${config}"`);
29 |
30 | const files = commands.getFiles();
31 | testFilesExistence(config, files);
32 | testFilesContent(commands, config, files);
33 | };
34 |
--------------------------------------------------------------------------------
/e2e/schemaFilesMatch.test.ts:
--------------------------------------------------------------------------------
1 | import { matchers } from "jest-json-schema";
2 | import TestCommands from "./TestCommands";
3 | import schema from "../docs/schema.json";
4 |
5 | expect.extend(matchers);
6 |
7 | const commands = new TestCommands("./e2e/__tmp__/schema-files-match-tests");
8 |
9 | describe("schema files match", () => {
10 | const files = commands.getFiles("samples/*.json");
11 |
12 | files.forEach((file) => {
13 | it(file, () => {
14 | // eslint-disable-next-line @typescript-eslint/no-var-requires
15 | const json = require(`../${file}`);
16 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
17 | // @ts-ignore
18 | expect(json).toMatchSchema(schema);
19 | });
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/fablo-build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -euo
4 |
5 | FABLO_HOME="$(cd "$(dirname "$0")" && pwd)"
6 | # shellcheck disable=2002
7 | FABLO_VERSION=$(cat "$FABLO_HOME"/package.json | jq -r '.version')
8 |
9 | COMMIT_HASH=$(git rev-parse --short HEAD)
10 | BUILD_DATE=$(date +'%Y-%m-%d-%H:%M:%S')
11 | VERSION_DETAILS="$BUILD_DATE-$COMMIT_HASH"
12 |
13 | echo "Building new image..."
14 | echo " FABLO_HOME: $FABLO_HOME"
15 | echo " FABLO_VERSION: $FABLO_VERSION"
16 | echo " VERSION_DETAILS: $VERSION_DETAILS"
17 |
18 | IMAGE_BASE_NAME="ghcr.io/fablo-io/fablo:$FABLO_VERSION"
19 |
20 | if [ "$(command -v nvm)" != "nvm" ] && [ -f ~/.nvm/nvm.sh ]; then
21 | set +e
22 | # shellcheck disable=SC2039
23 | source ~/.nvm/nvm.sh
24 | set -e
25 | fi
26 | if [ "$(command -v nvm)" = "nvm" ]; then
27 | set +u
28 | nvm install
29 | set -u
30 | fi
31 |
32 | npm install
33 | npm run build:dist
34 |
35 | # if --push is passed, then build for all platforms and push the image to the registry
36 | if [ "${1:-''}" = "--push" ]; then
37 | docker buildx build \
38 | --build-arg VERSION_DETAILS="$VERSION_DETAILS" \
39 | --platform linux/amd64,linux/arm64 \
40 | --tag "ghcr.io/fablo-io/fablo:$FABLO_VERSION" \
41 | --push \
42 | "$FABLO_HOME"
43 | else
44 | docker build \
45 | --build-arg VERSION_DETAILS="$VERSION_DETAILS" \
46 | --tag "$IMAGE_BASE_NAME" "$FABLO_HOME"
47 |
48 | docker tag "$IMAGE_BASE_NAME" "ghcr.io/fablo-io/fablo:$FABLO_VERSION"
49 | fi
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: "ts-jest",
3 | };
4 |
--------------------------------------------------------------------------------
/lint.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # This script runs linter for bash and YAML files in Fablo root and for
4 | # generated network configs in 'e2e/__tmp__' directory. It fails if generated
5 | # network configs are missing.
6 | #
7 | # Required libs: shellcheck and yamllint
8 |
9 | set -e
10 |
11 | FABLO_HOME="$(dirname "$0")"
12 | shellcheck "$FABLO_HOME"/*.sh
13 | shellcheck "$FABLO_HOME"/e2e-network/docker/*.sh
14 | shellcheck "$FABLO_HOME"/e2e-network/k8s/*.sh
15 |
16 | for config in samples/fablo-config-*; do
17 | network="$FABLO_HOME/e2e/__tmp__/${config}.tmpdir"
18 |
19 | if [ -z "$(ls -A "$network")" ]; then
20 | echo "Missing network $network"
21 | exit 1
22 | fi
23 |
24 | echo "Linting network $network"
25 |
26 | # shellcheck disable=2044
27 | for file in $(find "$network" -name "*.sh"); do
28 | shellcheck "$file"
29 | done
30 |
31 | yamllint "$network"
32 |
33 | done
34 |
--------------------------------------------------------------------------------
/logo-sygnet-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperledger-labs/fablo/683b8991a72c6731162d1a55383c00d8bf32d259/logo-sygnet-192.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "generator-fablo",
3 | "version": "2.2.0",
4 | "description": "Fablo is a simple tool to generate the Hyperledger Fabric blockchain network and run it on Docker. It supports RAFT and solo consensus protocols, multiple organizations and channels, chaincode installation and upgrade.",
5 | "author": "Piotr Hejwowski , Jakub Dzikowski ",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/hyperledger-labs/fablo.git"
9 | },
10 | "keywords": [
11 | "hyperledger fabric",
12 | "blockchain",
13 | "blockchain network",
14 | "config generator",
15 | "CI"
16 | ],
17 | "license": "Apache-2.0",
18 | "bugs": {
19 | "url": "https://github.com/hyperledger-labs/fablo/issues"
20 | },
21 | "homepage": "https://github.com/hyperledger-labs/fablo#readme",
22 | "scripts": {
23 | "clean": "rimraf generators",
24 | "build": "tsc",
25 | "build:dist": "npm run clean && tsc -p tsconfig-dist.json && npm run copydeps",
26 | "copydeps": "copyfiles --all --up 1 'src/*/templates/**' generators",
27 | "lint": "eslint --fix src e2e && madge --circular --warning src e2e && ejslint src",
28 | "test:unit": "jest src",
29 | "test:e2e": "jest e2e --runInBand",
30 | "test:e2e-update": "./fablo-build.sh && jest e2e --runInBand --updateSnapshot && ./lint.sh"
31 | },
32 | "dependencies": {
33 | "chalk": "^4.1.0",
34 | "got": "^11.8.5",
35 | "js-yaml": "^4.1.0",
36 | "jsonschema": "^1.2.6",
37 | "lodash": "^4.17.21",
38 | "winston": "^2.4.7",
39 | "yeoman-generator": "^5.10.0"
40 | },
41 | "devDependencies": {
42 | "@types/jest": "^27.0.1",
43 | "@types/jest-json-schema": "^2.1.3",
44 | "@types/js-yaml": "^4.0.1",
45 | "@types/lodash": "^4.14.168",
46 | "@types/node-fetch": "^2.5.10",
47 | "@types/yeoman-generator": "^5.2.14",
48 | "@typescript-eslint/eslint-plugin": "^4.22.0",
49 | "@typescript-eslint/parser": "^4.22.0",
50 | "copyfiles": "2.4.1",
51 | "ejs-lint": "^2.0.1",
52 | "eslint": "^7.24.0",
53 | "eslint-config-airbnb": "^18.2.1",
54 | "eslint-config-prettier": "^8.2.0",
55 | "eslint-import-resolver-typescript": "^2.4.0",
56 | "eslint-plugin-import": "^2.22.1",
57 | "eslint-plugin-json": "^2.1.2",
58 | "eslint-plugin-prettier": "^3.4.0",
59 | "jest": "^29.7.0",
60 | "jest-json-schema": "^6.1.0",
61 | "madge": "^4.0.2",
62 | "prettier": "^2.2.1",
63 | "rimraf": "^3.0.2",
64 | "ts-jest": "^29.1.4",
65 | "ts-loader": "^9.1.0",
66 | "ts-node": "^9.1.1",
67 | "typescript": "^4.2.4",
68 | "yeoman-test": "^5.1.0"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/samples/chaincodes/.gitignore:
--------------------------------------------------------------------------------
1 | */node_modules
2 |
--------------------------------------------------------------------------------
/samples/chaincodes/chaincode-java-simple/.fabricignore:
--------------------------------------------------------------------------------
1 | #
2 | # SPDX-License-Identifier: Apache2.0
3 | #
4 |
5 | /.classpath
6 | /.git/
7 | /.gradle/
8 | /.project
9 | /.settings/
10 | /bin/
11 | /build/
12 |
--------------------------------------------------------------------------------
/samples/chaincodes/chaincode-java-simple/.gitignore:
--------------------------------------------------------------------------------
1 | #
2 | # SPDX-License-Identifier: Apache2.0
3 | #
4 |
5 | /.classpath
6 | /.gradle/
7 | /.project
8 | /.settings/
9 | /bin/
10 | /build/
11 | /target
12 |
--------------------------------------------------------------------------------
/samples/chaincodes/chaincode-java-simple/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-License-Identifier: Apache2.0
3 | */
4 | plugins {
5 | id 'com.github.johnrengelman.shadow' version '7.0.0'
6 | id 'java'
7 | }
8 |
9 | version '0.0.1'
10 |
11 | sourceCompatibility = 1.8
12 |
13 | repositories {
14 | mavenLocal()
15 | mavenCentral()
16 | maven {
17 | url 'https://jitpack.io'
18 | }
19 | maven {
20 | url "https://nexus.hyperledger.org/content/repositories/snapshots/"
21 | }
22 | }
23 |
24 | dependencies {
25 | implementation group: 'org.hyperledger.fabric-chaincode-java', name: 'fabric-chaincode-shim', version: '2.3.0'
26 | implementation group: 'org.json', name: 'json', version: '20180813'
27 | testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2'
28 | testImplementation 'org.assertj:assertj-core:3.11.1'
29 | testImplementation 'org.mockito:mockito-core:2.+'
30 | }
31 |
32 | shadowJar {
33 | baseName = 'chaincode'
34 | version = null
35 | classifier = null
36 |
37 | manifest {
38 | attributes 'Main-Class': 'org.hyperledger.fabric.contract.ContractRouter'
39 | }
40 | }
41 |
42 | test {
43 | useJUnitPlatform()
44 | testLogging {
45 | events "passed", "skipped", "failed"
46 | }
47 | }
48 |
49 |
50 | tasks.withType(JavaCompile) {
51 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" << "-parameters"
52 | }
53 |
--------------------------------------------------------------------------------
/samples/chaincodes/chaincode-java-simple/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperledger-labs/fablo/683b8991a72c6731162d1a55383c00d8bf32d259/samples/chaincodes/chaincode-java-simple/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/samples/chaincodes/chaincode-java-simple/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/samples/chaincodes/chaincode-java-simple/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/samples/chaincodes/chaincode-java-simple/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/samples/chaincodes/chaincode-java-simple/settings.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-License-Identifier: Apache2.0
3 | */
4 | rootProject.name = 'PokeballContract'
5 |
6 |
--------------------------------------------------------------------------------
/samples/chaincodes/chaincode-java-simple/src/main/java/org/example/Pokeball.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-License-Identifier: Apache2.0
3 | */
4 |
5 | package org.example;
6 |
7 | import org.hyperledger.fabric.contract.annotation.DataType;
8 | import org.hyperledger.fabric.contract.annotation.Property;
9 | import org.json.JSONObject;
10 |
11 | @DataType()
12 | public class Pokeball {
13 |
14 | @Property()
15 | private String value;
16 |
17 | public Pokeball(){
18 | }
19 |
20 | public String getValue() {
21 | return value;
22 | }
23 |
24 | public void setValue(String value) {
25 | this.value = value;
26 | }
27 |
28 | public String toJSONString() {
29 | return new JSONObject(this).toString();
30 | }
31 |
32 | public static Pokeball fromJSONString(String json) {
33 | String value = new JSONObject(json).getString("value");
34 | Pokeball asset = new Pokeball();
35 | asset.setValue(value);
36 | return asset;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/samples/chaincodes/chaincode-java-simple/src/main/java/org/example/PokeballContract.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-License-Identifier: Apache2.0
3 | */
4 | package org.example;
5 |
6 | import org.hyperledger.fabric.contract.Context;
7 | import org.hyperledger.fabric.contract.ContractInterface;
8 | import org.hyperledger.fabric.contract.annotation.Contract;
9 | import org.hyperledger.fabric.contract.annotation.Default;
10 | import org.hyperledger.fabric.contract.annotation.Transaction;
11 | import org.hyperledger.fabric.contract.annotation.Contact;
12 | import org.hyperledger.fabric.contract.annotation.Info;
13 | import org.hyperledger.fabric.contract.annotation.License;
14 | import static java.nio.charset.StandardCharsets.UTF_8;
15 |
16 | @Contract(name = "PokeballContract",
17 | info = @Info(title = "Pokeball contract",
18 | description = "Pokeball implementation",
19 | version = "0.0.1",
20 | license =
21 | @License(name = "Apache2.0",
22 | url = ""),
23 | contact = @Contact(email = "PokeballContract@example.com",
24 | name = "PokeballContract",
25 | url = "http://PokeballContract.me")))
26 | @Default
27 | public class PokeballContract implements ContractInterface {
28 | public PokeballContract() {
29 |
30 | }
31 | @Transaction()
32 | public boolean pokeballExists(Context ctx, String pokeballId) {
33 | byte[] buffer = ctx.getStub().getState(pokeballId);
34 | return (buffer != null && buffer.length > 0);
35 | }
36 |
37 | @Transaction()
38 | public void createPokeball(Context ctx, String pokeballId, String value) {
39 | boolean exists = pokeballExists(ctx,pokeballId);
40 | if (exists) {
41 | throw new RuntimeException("The asset "+pokeballId+" already exists");
42 | }
43 | Pokeball asset = new Pokeball();
44 | asset.setValue(value);
45 | ctx.getStub().putState(pokeballId, asset.toJSONString().getBytes(UTF_8));
46 | }
47 |
48 | @Transaction()
49 | public Pokeball readPokeball(Context ctx, String pokeballId) {
50 | boolean exists = pokeballExists(ctx,pokeballId);
51 | if (!exists) {
52 | throw new RuntimeException("The asset "+pokeballId+" does not exist");
53 | }
54 |
55 | Pokeball newAsset = Pokeball.fromJSONString(new String(ctx.getStub().getState(pokeballId),UTF_8));
56 | return newAsset;
57 | }
58 |
59 | @Transaction()
60 | public void updatePokeball(Context ctx, String pokeballId, String newValue) {
61 | boolean exists = pokeballExists(ctx,pokeballId);
62 | if (!exists) {
63 | throw new RuntimeException("The asset "+pokeballId+" does not exist");
64 | }
65 | Pokeball asset = new Pokeball();
66 | asset.setValue(newValue);
67 |
68 | ctx.getStub().putState(pokeballId, asset.toJSONString().getBytes(UTF_8));
69 | }
70 |
71 | @Transaction()
72 | public void deletePokeball(Context ctx, String pokeballId) {
73 | boolean exists = pokeballExists(ctx,pokeballId);
74 | if (!exists) {
75 | throw new RuntimeException("The asset "+pokeballId+" does not exist");
76 | }
77 | ctx.getStub().delState(pokeballId);
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/samples/chaincodes/chaincode-kv-node/.nvmrc:
--------------------------------------------------------------------------------
1 | 16
2 |
--------------------------------------------------------------------------------
/samples/chaincodes/chaincode-kv-node/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18
2 |
3 | WORKDIR /usr/src/app
4 |
5 | COPY package*.json ./
6 | RUN npm install
7 |
8 | COPY . .
9 |
10 | EXPOSE 7052
11 |
12 | CMD ["npm", "run", "start:ccaas"]
13 |
--------------------------------------------------------------------------------
/samples/chaincodes/chaincode-kv-node/index.js:
--------------------------------------------------------------------------------
1 | const { Contract } = require("fabric-contract-api");
2 | const crypto = require("crypto");
3 |
4 | class KVContract extends Contract {
5 | constructor() {
6 | super("KVContract");
7 | }
8 |
9 | async instantiate() {
10 | // function that will be invoked on chaincode instantiation
11 | }
12 |
13 | async put(ctx, key, value) {
14 | await ctx.stub.putState(key, Buffer.from(value));
15 | return { success: "OK" };
16 | }
17 |
18 | async get(ctx, key) {
19 | const buffer = await ctx.stub.getState(key);
20 | if (!buffer || !buffer.length) return { error: "NOT_FOUND" };
21 | return { success: buffer.toString() };
22 | }
23 |
24 | async putPrivateMessage(ctx, collection) {
25 | const transient = ctx.stub.getTransient();
26 | const message = transient.get("message");
27 | await ctx.stub.putPrivateData(collection, "message", message);
28 | return { success: "OK" };
29 | }
30 |
31 | async getPrivateMessage(ctx, collection) {
32 | const message = await ctx.stub.getPrivateData(collection, "message");
33 | const messageString = message.toBuffer ? message.toBuffer().toString() : message.toString();
34 | return { success: messageString };
35 | }
36 |
37 | async verifyPrivateMessage(ctx, collection) {
38 | const transient = ctx.stub.getTransient();
39 | const message = transient.get("message");
40 | const messageString = message.toBuffer ? message.toBuffer().toString() : message.toString();
41 | const currentHash = crypto.createHash("sha256").update(messageString).digest("hex");
42 | const privateDataHash = (await ctx.stub.getPrivateDataHash(collection, "message")).toString("hex");
43 | if (privateDataHash !== currentHash) {
44 | return { error: "VERIFICATION_FAILED" };
45 | }
46 | return { success: "OK" };
47 | }
48 | }
49 |
50 | exports.contracts = [KVContract];
51 |
--------------------------------------------------------------------------------
/samples/chaincodes/chaincode-kv-node/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chaincode-kv-node",
3 | "version": "0.2.0",
4 | "main": "index.js",
5 | "engines": {
6 | "node": ">=8",
7 | "npm": ">=5"
8 | },
9 | "scripts": {
10 | "start": "fabric-chaincode-node start",
11 | "start:ccaas": "fabric-chaincode-node server --chaincode-address 0.0.0.0:7052 --chaincode-id \"chaincode1:0.0.1\"",
12 | "start:dev": "fabric-chaincode-node start --peer.address \"127.0.0.1:8541\" --chaincode-id-name \"chaincode1:0.0.1\" --tls.enabled false",
13 | "start:watch": "nodemon --exec \"npm run start:dev\"",
14 | "build": "echo \"No need to build the chaincode\"",
15 | "lint": "eslint . --fix --ext .js"
16 | },
17 | "dependencies": {
18 | "fabric-contract-api": "2.4.2",
19 | "fabric-shim": "2.4.2"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/samples/fablo-config-hlf2-1org-1chaincode-k8s.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://github.com/hyperledger-labs/fablo/releases/download/2.2.0/schema.json",
3 | "global": {
4 | "fabricVersion": "2.4.7",
5 | "tls": false,
6 | "engine": "kubernetes"
7 | },
8 | "orgs": [
9 | {
10 | "organization": {
11 | "name": "Orderer",
12 | "domain": "orderer.example.com"
13 | },
14 | "orderers": [
15 | {
16 | "groupName": "group1",
17 | "type": "solo",
18 | "instances": 1
19 | }
20 | ]
21 | },
22 | {
23 | "organization": {
24 | "name": "Org1",
25 | "domain": "org1.example.com"
26 | },
27 | "peer": {
28 | "instances": 2,
29 | "db": "LevelDb"
30 | }
31 | }
32 | ],
33 | "channels": [
34 | {
35 | "name": "my-channel1",
36 | "orgs": [
37 | {
38 | "name": "Org1",
39 | "peers": [
40 | "peer0",
41 | "peer1"
42 | ]
43 | }
44 | ]
45 | }
46 | ],
47 | "chaincodes": [
48 | {
49 | "name": "chaincode1",
50 | "version": "0.0.1",
51 | "lang": "node",
52 | "channel": "my-channel1",
53 | "directory": "./chaincodes/chaincode-kv-node"
54 | }
55 | ]
56 | }
57 |
--------------------------------------------------------------------------------
/samples/fablo-config-hlf2-1org-1chaincode-raft-explorer.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://github.com/hyperledger-labs/fablo/releases/download/2.2.0/schema.json",
3 | "global": {
4 | "fabricVersion": "2.3.3",
5 | "tls": true,
6 | "tools": {
7 | "explorer": true
8 | }
9 | },
10 | "orgs": [
11 | {
12 | "organization": {
13 | "name": "Orderer",
14 | "domain": "orderer.example.com"
15 | },
16 | "orderers": [
17 | {
18 | "groupName": "group1",
19 | "type": "raft",
20 | "instances": 1
21 | }
22 | ]
23 | },
24 | {
25 | "organization": {
26 | "name": "Org1",
27 | "domain": "org1.example.com"
28 | },
29 | "ca": {
30 | "db": "postgres"
31 | },
32 | "peer": {
33 | "instances": 1,
34 | "db": "CouchDb"
35 | },
36 | "tools": {
37 | "fabloRest": true
38 | }
39 | }
40 | ],
41 | "channels": [
42 | {
43 | "name": "my-channel1",
44 | "orgs": [
45 | {
46 | "name": "Org1",
47 | "peers": [
48 | "peer0"
49 | ]
50 | }
51 | ]
52 | }
53 | ],
54 | "chaincodes": [
55 | {
56 | "name": "chaincode1",
57 | "version": "0.0.1",
58 | "lang": "node",
59 | "channel": "my-channel1",
60 | "directory": "./chaincodes/chaincode-kv-node"
61 | }
62 | ],
63 | "hooks": {
64 | "postGenerate": "perl -i -pe 's/MaxMessageCount: 10/MaxMessageCount: 1/g' \"./fablo-target/fabric-config/configtx.yaml\""
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/samples/fablo-config-hlf2-1org-1chaincode.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://github.com/hyperledger-labs/fablo/releases/download/2.2.0/schema.json",
3 | "global": {
4 | "fabricVersion": "2.5.9",
5 | "tls": false
6 | },
7 | "orgs": [
8 | {
9 | "organization": {
10 | "name": "Orderer",
11 | "domain": "orderer.example.com"
12 | },
13 | "orderers": [
14 | {
15 | "groupName": "group1",
16 | "type": "solo",
17 | "instances": 1
18 | }
19 | ]
20 | },
21 | {
22 | "organization": {
23 | "name": "Org1",
24 | "domain": "org1.example.com"
25 | },
26 | "peer": {
27 | "instances": 2,
28 | "db": "LevelDb"
29 | }
30 | }
31 | ],
32 | "channels": [
33 | {
34 | "name": "my-channel1",
35 | "orgs": [
36 | {
37 | "name": "Org1",
38 | "peers": [
39 | "peer0",
40 | "peer1"
41 | ]
42 | }
43 | ]
44 | }
45 | ],
46 | "chaincodes": [
47 | {
48 | "name": "chaincode1",
49 | "version": "0.0.1",
50 | "lang": "node",
51 | "channel": "my-channel1",
52 | "directory": "./chaincodes/chaincode-kv-node"
53 | }
54 | ]
55 | }
56 |
--------------------------------------------------------------------------------
/samples/fablo-config-hlf2-2orgs-2chaincodes-private-data.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | "$schema": https://github.com/hyperledger-labs/fablo/releases/download/2.2.0/schema.json
3 | global:
4 | fabricVersion: 2.4.7
5 | tls: false
6 |
7 | orgs:
8 | - organization:
9 | name: Orderer
10 | domain: orderer.example.com
11 | orderers:
12 | - groupName: group1
13 | prefix: orderer
14 | type: solo
15 | instances: 1
16 | - organization:
17 | name: Org1
18 | domain: org1.example.com
19 | peer:
20 | instances: 2
21 | - organization:
22 | name: Org2
23 | domain: org2.example.com
24 | peer:
25 | instances: 1
26 | channels:
27 | - name: my-channel1
28 | orgs:
29 | - name: Org1
30 | peers:
31 | - peer0
32 | - peer1
33 | - name: Org2
34 | peers:
35 | - peer0
36 | chaincodes:
37 | - name: or-policy-chaincode
38 | version: 0.0.1
39 | lang: node
40 | channel: my-channel1
41 | init: '{"Args":[]}'
42 | endorsement: OR('Org1MSP.member', 'Org2MSP.member')
43 | directory: "./chaincodes/chaincode-kv-node"
44 | privateData:
45 | - name: org1-collection
46 | orgNames:
47 | - Org1
48 | - name: and-policy-chaincode
49 | version: 0.0.1
50 | lang: node
51 | channel: my-channel1
52 | endorsement: AND('Org1MSP.member', 'Org2MSP.member')
53 | directory: "./chaincodes/chaincode-kv-node"
54 | privateData:
55 | - name: both-orgs-collection
56 | orgNames:
57 | - Org1
58 | - Org2
59 |
--------------------------------------------------------------------------------
/samples/fablo-config-hlf2-2orgs-2chaincodes-raft.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | "$schema": https://github.com/hyperledger-labs/fablo/releases/download/2.2.0/schema.json
3 | global:
4 | fabricVersion: 2.4.3
5 | tls: true
6 | monitoring:
7 | loglevel: debug
8 | orgs:
9 | - organization:
10 | name: Orderer1
11 | domain: orderer1.com
12 | orderers:
13 | - groupName: group1
14 | type: raft
15 | instances: 3
16 | - organization:
17 | name: Orderer2
18 | domain: orderer2.com
19 | orderers:
20 | - groupName: group2
21 | type: solo
22 | instances: 1
23 | - organization:
24 | name: Org1
25 | domain: org1.example.com
26 | # this is the default configuration for peers that may be used in other orgs
27 | peer: &defaultPeerConfig
28 | prefix: peer
29 | instances: 2
30 | anchorPeerInstances: 2
31 | db: LevelDb
32 | tools:
33 | fabloRest: true
34 | - organization:
35 | name: Org2
36 | domain: org2.example.com
37 | peer: *defaultPeerConfig
38 | channels:
39 | - name: my-channel1
40 | orgs:
41 | - name: Org1
42 | peers:
43 | - peer0
44 | - name: Org2
45 | peers:
46 | - peer0
47 | - name: my-channel2
48 | orgs:
49 | - name: Org1
50 | peers:
51 | - peer1
52 | - name: Org2
53 | peers:
54 | - peer1
55 | - name: my-channel3
56 | ordererGroup: group2
57 | orgs:
58 | - name: Org1
59 | peers:
60 | - peer0
61 | - name: Org2
62 | peers:
63 | - peer1
64 | chaincodes:
65 | - name: chaincode1
66 | version: 0.0.1
67 | lang: node
68 | channel: my-channel1
69 | endorsement: OR ('Org1MSP.member', 'Org2MSP.member')
70 | directory: "./chaincodes/chaincode-kv-node"
71 | - name: chaincode2
72 | version: 0.0.1
73 | lang: java
74 | channel: my-channel2
75 | endorsement: OR ('Org1MSP.member', 'Org2MSP.member')
76 | directory: "./chaincodes/chaincode-java-simple"
77 | hooks:
78 | # changes MaxMessageCount to 1
79 | postGenerate: "perl -i -pe 's/MaxMessageCount: 10/MaxMessageCount: 1/g' \"./fablo-target/fabric-config/configtx.yaml\""
80 |
--------------------------------------------------------------------------------
/samples/fablo-config-hlf2-3orgs-1chaincode-raft-explorer.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://github.com/hyperledger-labs/fablo/releases/download/2.2.0/schema.json",
3 | "global": {
4 | "fabricVersion": "2.3.2",
5 | "tls": true,
6 | "tools": {
7 | "explorer": true
8 | }
9 | },
10 | "orgs": [
11 | {
12 | "organization": {
13 | "name": "Orderer",
14 | "domain": "orderer.example.com"
15 | },
16 | "orderers": [
17 | {
18 | "groupName": "group1",
19 | "type": "raft",
20 | "instances": 2
21 | }
22 | ]
23 | },
24 | {
25 | "organization": {
26 | "name": "Org1",
27 | "domain": "org1.example.com"
28 | },
29 | "peer": {
30 | "instances": 2,
31 | "db": "LevelDb"
32 | },
33 | "orderers": [
34 | {
35 | "groupName": "group1",
36 | "type": "raft",
37 | "instances": 2
38 | }
39 | ]
40 | },
41 | {
42 | "organization": {
43 | "name": "Org2",
44 | "domain": "org2.example.com"
45 | },
46 | "peer": {
47 | "instances": 2,
48 | "db": "LevelDb"
49 | },
50 | "orderers": [
51 | {
52 | "groupName": "group1",
53 | "type": "raft",
54 | "instances": 2
55 | }
56 | ]
57 | },
58 | {
59 | "organization": {
60 | "name": "Org3",
61 | "domain": "org3.example.com"
62 | },
63 | "peer": {
64 | "instances": 2,
65 | "db": "LevelDb"
66 | },
67 | "orderers": [
68 | {
69 | "groupName": "group1",
70 | "type": "raft",
71 | "instances": 2
72 | }
73 | ]
74 | }
75 | ],
76 | "channels": [
77 | {
78 | "name": "my-channel1",
79 | "orgs": [
80 | {
81 | "name": "Org1",
82 | "peers": [
83 | "peer0",
84 | "peer1"
85 | ]
86 | }
87 | ]
88 | },
89 | {
90 | "name": "my-channel2",
91 | "orgs": [
92 | {
93 | "name": "Org2",
94 | "peers": [
95 | "peer0",
96 | "peer1"
97 | ]
98 | }
99 | ]
100 | },
101 | {
102 | "name": "my-channel3",
103 | "orgs": [
104 | {
105 | "name": "Org1",
106 | "peers": [
107 | "peer0",
108 | "peer1"
109 | ]
110 | },
111 | {
112 | "name": "Org2",
113 | "peers": [
114 | "peer0",
115 | "peer1"
116 | ]
117 | }
118 | ]
119 | }
120 | ],
121 | "chaincodes": [
122 | {
123 | "name": "chaincode1",
124 | "version": "0.0.1",
125 | "lang": "node",
126 | "channel": "my-channel1",
127 | "directory": "./chaincodes/chaincode-kv-node"
128 | }
129 | ]
130 | }
131 |
--------------------------------------------------------------------------------
/samples/fablo-config-hlf3-1orgs-1chaincode.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://github.com/hyperledger-labs/fablo/releases/download/2.2.0/schema.json",
3 | "global": {
4 | "fabricVersion": "3.0.0",
5 | "tls": true,
6 | "monitoring": {
7 | "loglevel": "debug"
8 | }
9 | },
10 | "orgs": [
11 | {
12 | "organization": {
13 | "name": "Orderer",
14 | "domain": "orderer.example.com"
15 | },
16 | "orderers": [
17 | {
18 | "groupName": "group1",
19 | "type": "raft",
20 | "instances": 4
21 | }
22 | ]
23 | },
24 | {
25 | "organization": {
26 | "name": "Org1",
27 | "domain": "org1.example.com"
28 | },
29 | "peer": {
30 | "instances": 2,
31 | "db": "LevelDb"
32 | }
33 | }
34 | ],
35 | "channels": [
36 | {
37 | "name": "my-channel1",
38 | "orgs": [
39 | {
40 | "name": "Org1",
41 | "peers": [
42 | "peer0",
43 | "peer1"
44 | ]
45 | }
46 | ]
47 | }
48 | ],
49 | "chaincodes": [
50 | {
51 | "name": "chaincode1",
52 | "version": "0.0.1",
53 | "lang": "node",
54 | "channel": "my-channel1",
55 | "directory": "./chaincodes/chaincode-kv-node"
56 | }
57 | ]
58 | }
59 |
--------------------------------------------------------------------------------
/samples/fablo-config-hlf3-bft-1orgs-1chaincode.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://github.com/hyperledger-labs/fablo/releases/download/2.2.0/schema.json",
3 | "global": {
4 | "fabricVersion": "3.0.0-beta",
5 | "tls": true,
6 | "monitoring": {
7 | "loglevel": "debug"
8 | }
9 | },
10 | "orgs": [
11 | {
12 | "organization": {
13 | "name": "Orderer",
14 | "domain": "orderer.example.com"
15 | },
16 | "orderers": [
17 | {
18 | "groupName": "group1",
19 | "type": "BFT",
20 | "instances": 4
21 | }
22 |
23 | ]
24 | },
25 | {
26 | "organization": {
27 | "name": "Org1",
28 | "domain": "org1.example.com"
29 | },
30 | "peer": {
31 | "instances": 2,
32 | "db": "LevelDb"
33 | }
34 | }
35 | ],
36 | "channels": [
37 | {
38 | "name": "my-channel1",
39 | "orgs": [
40 | {
41 | "name": "Org1",
42 | "peers": [
43 | "peer0",
44 | "peer1"
45 | ]
46 | }
47 | ]
48 | }
49 | ],
50 | "chaincodes": [
51 | {
52 | "name": "chaincode1",
53 | "version": "0.0.1",
54 | "lang": "node",
55 | "channel": "my-channel1",
56 | "directory": "./chaincodes/chaincode-kv-node"
57 | }
58 | ]
59 | }
60 |
--------------------------------------------------------------------------------
/samples/gateway/node/.env.example:
--------------------------------------------------------------------------------
1 | CHANNEL_NAME=my-channel1
2 | CONTRACT_NAME=chaincode1
3 | PEER_GATEWAY_URL=localhost:7041
4 | PEER_ORG_NAME=peer0.org1.example.com
5 | MSP_ID=Org1MSP
6 | TLS_ROOT_CERT=../../../fablo-target/fabric-config/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
7 | CREDENTIALS=../../../fablo-target/fabric-config/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts/User1@org1.example.com-cert.pem
8 | PRIVATE_KEY_PEM=../../../fablo-target/fabric-config/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/priv-key.pem
9 |
10 |
--------------------------------------------------------------------------------
/samples/gateway/node/README.md:
--------------------------------------------------------------------------------
1 | # Purpose
2 | To provide an example connection of Fablo with Node.js.
3 |
4 | # Pre-requisites
5 | Docker
6 |
7 | Node >22
8 |
9 | Git
10 |
11 | # Instructions
12 | 1. (If Fablo is not already installed) Clone the Fablo repo with `https://github.com/hyperledger-labs/fablo.git` and then `cd fablo`.
13 | 2. Start Docker.
14 | 3. Run `fablo up samples/fablo-config-hlf3-1orgs-1chaincode.json`.
15 | 4. Once Fablo is running, run `cd samples/gateway/node`.
16 | 5. Now install the Node server's dependencies with `npm i`.
17 | 6. Now let's copy the environment example to a usable file `cp .env.example .env`.
18 | 7. Start the node server with `node --env-file=.env server.js`.
19 |
20 | You should see a response like this:
21 | ```
22 | Put result: {"success":"OK"}
23 | Get result: {"success":"2025-04-29T16:13:42.097Z"}`
24 | ```
25 |
26 |
--------------------------------------------------------------------------------
/samples/gateway/node/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-fablo-gateway",
3 | "version": "1.0.0",
4 | "description": "",
5 | "license": "ISC",
6 | "author": "",
7 | "main": "server.js",
8 | "type": "module",
9 | "engines" : {
10 | "node" : ">=22"
11 | },
12 | "scripts": {
13 | "test": "echo \"Error: no test specified\" && exit 1",
14 | "start": "node server.js"
15 | },
16 | "dependencies": {
17 | "@grpc/grpc-js": "^1.13.3",
18 | "@hyperledger/fabric-gateway": "^1.7.1"
19 | }
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/samples/gateway/node/server.js:
--------------------------------------------------------------------------------
1 | import * as grpc from '@grpc/grpc-js';
2 | import { connect, hash, signers } from '@hyperledger/fabric-gateway';
3 | import * as crypto from 'node:crypto';
4 | import { promises as fs } from 'node:fs';
5 | import { TextDecoder } from 'node:util';
6 |
7 | const utf8Decoder = new TextDecoder();
8 |
9 | async function connection() {
10 | const credentials = await fs.readFile(process.env.CREDENTIALS);
11 | const privateKeyPem = await fs.readFile(process.env.PRIVATE_KEY_PEM);
12 | const privateKey = crypto.createPrivateKey(privateKeyPem);
13 | const signer = signers.newPrivateKeySigner(privateKey);
14 | const tlsRootCert = await fs.readFile(
15 | process.env.TLS_ROOT_CERT,
16 | );
17 | const client = new grpc.Client(process.env.PEER_GATEWAY_URL, grpc.credentials.createSsl(tlsRootCert), {
18 | "grpc.ssl_target_name_override": process.env.PEER_ORG_NAME,
19 | });
20 | const gateway = connect({
21 | identity: { mspId: process.env.MSP_ID, credentials },
22 | signer,
23 | hash: hash.sha256,
24 | client,
25 | });
26 | try {
27 | const network = gateway.getNetwork(process.env.CHANNEL_NAME);
28 | const contract = network.getContract(process.env.CONTRACT_NAME);
29 | const putResult = await contract.submitTransaction('put', 'time', new Date().toISOString());
30 | console.log('Put result:', utf8Decoder.decode(putResult));
31 | const getResult = await contract.evaluateTransaction('get', 'time');
32 | console.log('Get result:', utf8Decoder.decode(getResult));
33 | } finally {
34 | gateway.close();
35 | client.close();
36 | }
37 | }
38 |
39 | connection().catch(console.error);
40 |
41 |
--------------------------------------------------------------------------------
/src/app/index.ts:
--------------------------------------------------------------------------------
1 | import * as Generator from "yeoman-generator";
2 |
3 | export default class extends Generator {
4 | displayInfo(): void {
5 | const url = "https://github.com/hyperledger-labs/fablo";
6 | console.log("This is main entry point for Yeoman app used in Fablo.");
7 | console.log("Visit the project page to get more information.");
8 | console.log(`---\n${url}\n---`);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/config.test.ts:
--------------------------------------------------------------------------------
1 | import { fabloVersion, getVersionFromSchemaUrl } from "./config";
2 |
3 | it("should get version from schema URL", () => {
4 | const url = "https://github.com/hyperledger-labs/fablo/releases/download/0.0.1/schema.json";
5 | const version = getVersionFromSchemaUrl(url);
6 | expect(version).toEqual("0.0.1");
7 | });
8 |
9 | it("should get current version in case of missing schema URL", () => {
10 | const version = getVersionFromSchemaUrl(undefined);
11 | expect(version).toEqual(fabloVersion);
12 | });
13 |
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2 | // @ts-ignore
3 | import { version as fabloVersion } from "../package.json";
4 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
5 | // @ts-ignore
6 | import * as schemaJson from "../docs/schema.json";
7 | import { Schema } from "jsonschema";
8 | import { version } from "./repositoryUtils";
9 |
10 | const schema: Schema = schemaJson as Schema;
11 |
12 | const supportedVersionPrefix = `${fabloVersion.split(".").slice(0, 2).join(".")}.`;
13 |
14 | const getVersionFromSchemaUrl = (url?: string): string => {
15 | const matches = (url || "").match(/\d+\.\d+\.\d+/g);
16 | return matches?.length ? matches[0] : fabloVersion;
17 | };
18 |
19 | const isFabloVersionSupported = (versionName: string): boolean => versionName.startsWith(supportedVersionPrefix);
20 |
21 | const versionsSupportingRaft = (v: string): boolean => version(v).isGreaterOrEqual("1.4.3");
22 |
23 | export {
24 | schema,
25 | fabloVersion,
26 | versionsSupportingRaft,
27 | getVersionFromSchemaUrl,
28 | isFabloVersionSupported,
29 | supportedVersionPrefix,
30 | };
31 |
--------------------------------------------------------------------------------
/src/extend-config/defaults.ts:
--------------------------------------------------------------------------------
1 | import { Capabilities, OrdererGroup, OrgConfig } from "../types/FabloConfigExtended";
2 |
3 | export default {
4 | global: {
5 | monitoring: {
6 | loglevel: "info",
7 | },
8 | tools: {
9 | explorer: false,
10 | },
11 | },
12 | organization: {
13 | mspName: (name: string): string => `${name}MSP`,
14 | },
15 | orderer: {
16 | prefix: "orderer",
17 | },
18 | ca: {
19 | prefix: "ca",
20 | db: "sqlite",
21 | },
22 | peer: {
23 | prefix: "peer",
24 | db: "LevelDb",
25 | anchorPeerInstances: (peerCount: number): number => peerCount,
26 | },
27 | channel: {
28 | ordererGroup: (ordererGroups: OrdererGroup[]): string => ordererGroups[0].name,
29 | },
30 | chaincode: {
31 | init: '{"Args":[]}',
32 | initRequired: false,
33 | endorsement(orgs: OrgConfig[], capabilities: Capabilities): string | undefined {
34 | return capabilities.isV2 ? undefined : `AND (${orgs.map((o) => `'${o.mspName}.member'`).join(", ")})`;
35 | },
36 | },
37 | };
38 |
--------------------------------------------------------------------------------
/src/extend-config/extendChaincodesConfig.ts:
--------------------------------------------------------------------------------
1 | import { ChaincodeJson } from "../types/FabloConfigJson";
2 | import { ChaincodeConfig, ChannelConfig, Global, PrivateCollectionConfig } from "../types/FabloConfigExtended";
3 | import defaults from "./defaults";
4 | import { version } from "../repositoryUtils";
5 |
6 | const createPrivateCollectionConfig = (
7 | fabricVersion: string,
8 | channel: ChannelConfig,
9 | name: string,
10 | collectionOrgNames: string[],
11 | ): PrivateCollectionConfig => {
12 | // Organizations that will host the actual data
13 | const collectionOrgs = (channel.orgs || []).filter((o) => !!collectionOrgNames.find((n) => n === o.name));
14 | if (collectionOrgs.length < collectionOrgNames.length) {
15 | throw new Error(`Cannot find all orgs for names ${collectionOrgNames}`);
16 | }
17 | const policy = `OR(${collectionOrgs.map((o) => `'${o.mspName}.member'`).join(",")})`;
18 |
19 | // We need to know the number of anchor peers per org in a channel to determine the following parameters:
20 | // - maxPeerCount -> all peers
21 | // - requiredPeerCount -> minimal number of anchor peers from one organization in a channel
22 | const anchorPeerCountsInChannel = channel.orgs.map((o) => (o.anchorPeers || []).length);
23 | const maxPeerCount = anchorPeerCountsInChannel.reduce((a, b) => a + b, 0);
24 | const requiredPeerCount = anchorPeerCountsInChannel.reduce((a, b) => Math.min(a, b), maxPeerCount);
25 |
26 | const memberOnlyRead = version(fabricVersion).isGreaterOrEqual("1.4.0") ? { memberOnlyRead: true } : {};
27 | const memberOnlyWrite = version(fabricVersion).isGreaterOrEqual("2.0.0") ? { memberOnlyWrite: true } : {};
28 |
29 | return {
30 | name,
31 | policy,
32 | requiredPeerCount,
33 | maxPeerCount,
34 | blockToLive: 0,
35 | ...memberOnlyRead,
36 | ...memberOnlyWrite,
37 | };
38 | };
39 |
40 | const extendChaincodesConfig = (
41 | chaincodes: ChaincodeJson[],
42 | transformedChannels: ChannelConfig[],
43 | network: Global,
44 | ): ChaincodeConfig[] => {
45 | return chaincodes.map((chaincode) => {
46 | const channel = transformedChannels.find((c) => c.name === chaincode.channel);
47 | if (!channel) throw new Error(`No matching channel with name '${chaincode.channel}'`);
48 |
49 | const initParams: { initRequired: boolean } | { init: string } = network.capabilities.isV2
50 | ? { initRequired: chaincode.initRequired || defaults.chaincode.initRequired }
51 | : { init: chaincode.init || defaults.chaincode.init };
52 |
53 | const endorsement = chaincode.endorsement ?? defaults.chaincode.endorsement(channel.orgs, network.capabilities);
54 |
55 | const privateData = (chaincode.privateData ?? []).map((d) =>
56 | createPrivateCollectionConfig(network.fabricVersion, channel, d.name, d.orgNames),
57 | );
58 | const privateDataConfigFile = privateData.length > 0 ? `collections/${chaincode.name}.json` : undefined;
59 |
60 | return {
61 | directory: chaincode.directory,
62 | name: chaincode.name,
63 | version: chaincode.version,
64 | lang: chaincode.lang,
65 | channel,
66 | ...initParams,
67 | endorsement,
68 | instantiatingOrg: channel.instantiatingOrg,
69 | privateDataConfigFile,
70 | privateData,
71 | };
72 | });
73 | };
74 |
75 | export default extendChaincodesConfig;
76 |
--------------------------------------------------------------------------------
/src/extend-config/extendChannelsConfig.ts:
--------------------------------------------------------------------------------
1 | import { ChannelConfig, OrdererGroup, OrgConfig } from "../types/FabloConfigExtended";
2 | import { ChannelJson } from "../types/FabloConfigJson";
3 | import * as _ from "lodash";
4 | import defaults from "./defaults";
5 |
6 | const filterToAvailablePeers = (orgTransformedFormat: OrgConfig, peersTransformedFormat: string[]) => {
7 | const filteredPeers = orgTransformedFormat.peers.filter((p) => peersTransformedFormat.includes(p.name));
8 | return {
9 | ...orgTransformedFormat,
10 | peers: filteredPeers,
11 | headPeer: filteredPeers[0],
12 | };
13 | };
14 |
15 | const extendChannelConfig = (
16 | channelJsonFormat: ChannelJson,
17 | orgsTransformed: OrgConfig[],
18 | ordererGroups: OrdererGroup[],
19 | ): ChannelConfig => {
20 | const channelName = channelJsonFormat.name;
21 | const profileName = _.chain(channelName).camelCase().upperFirst().value();
22 | const ordererGroupName = channelJsonFormat.ordererGroup ?? defaults.channel.ordererGroup(ordererGroups);
23 |
24 | const orgNames = channelJsonFormat.orgs.map((o) => o.name);
25 | const orgPeers = channelJsonFormat.orgs.map((o) => o.peers).reduce((a, b) => a.concat(b), []);
26 | const orgsForChannel = orgsTransformed
27 | .filter((org) => orgNames.includes(org.name))
28 | .map((org) => filterToAvailablePeers(org, orgPeers));
29 |
30 | const ordererGroup = ordererGroups.filter((group) => group.name == ordererGroupName)[0];
31 | const ordererHead = ordererGroup.ordererHeads[0];
32 |
33 | return {
34 | name: channelName,
35 | profileName,
36 | orgs: orgsForChannel,
37 | ordererGroup,
38 | ordererHead,
39 | instantiatingOrg: orgsForChannel[0],
40 | };
41 | };
42 |
43 | const extendChannelsConfig = (
44 | channelsJsonConfigFormat: ChannelJson[],
45 | orgsTransformed: OrgConfig[],
46 | ordererGroups: OrdererGroup[],
47 | ): ChannelConfig[] => channelsJsonConfigFormat.map((ch) => extendChannelConfig(ch, orgsTransformed, ordererGroups));
48 |
49 | export default extendChannelsConfig;
50 |
--------------------------------------------------------------------------------
/src/extend-config/extendConfig.ts:
--------------------------------------------------------------------------------
1 | import { FabloConfigJson } from "../types/FabloConfigJson";
2 | import { FabloConfigExtended } from "../types/FabloConfigExtended";
3 | import { extendOrgsConfig } from "./extendOrgsConfig";
4 | import extendGlobal from "./extendGlobal";
5 | import extendChannelsConfig from "./extendChannelsConfig";
6 | import extendChaincodesConfig from "./extendChaincodesConfig";
7 | import extendHooksConfig from "./extendHooksConfig";
8 | import { distinctOrdererHeads, mergeOrdererGroups } from "./mergeOrdererGroups";
9 |
10 | const extendConfig = (json: FabloConfigJson): FabloConfigExtended => {
11 | const {
12 | global: globalJson,
13 | orgs: orgsJson,
14 | channels: channelsJson,
15 | chaincodes: chaincodesJson,
16 | hooks: hooksJson,
17 | } = json;
18 |
19 | const global = extendGlobal(globalJson);
20 | const orgs = extendOrgsConfig(orgsJson, global);
21 | const ordererGroups = mergeOrdererGroups(orgs);
22 | const orderedHeadsDistinct = distinctOrdererHeads(ordererGroups);
23 |
24 | const channels = extendChannelsConfig(channelsJson, orgs, ordererGroups);
25 | const chaincodes = extendChaincodesConfig(chaincodesJson, channels, global);
26 | const hooks = extendHooksConfig(hooksJson);
27 |
28 | return {
29 | global,
30 | ordererGroups,
31 | orderedHeadsDistinct,
32 | orgs,
33 | channels,
34 | chaincodes,
35 | hooks,
36 | };
37 | };
38 |
39 | export default extendConfig;
40 |
--------------------------------------------------------------------------------
/src/extend-config/extendGlobal.ts:
--------------------------------------------------------------------------------
1 | // Used https://github.com/hyperledger/fabric/blob/v1.4.8/sampleconfig/configtx.yaml for values
2 | import { Capabilities, FabricVersions, Global } from "../types/FabloConfigExtended";
3 | import { version } from "../repositoryUtils";
4 | import { GlobalJson } from "../types/FabloConfigJson";
5 | import defaults from "./defaults";
6 |
7 | const getNetworkCapabilities = (fabricVersion: string): Capabilities => {
8 | if (version(fabricVersion).isGreaterOrEqual("2.5.0") && !version(fabricVersion).isGreaterOrEqual("3.0.0"))
9 | return { channel: "V2_0", orderer: "V2_0", application: "V2_5", isV2: true, isV3: false };
10 |
11 | if (version(fabricVersion).isGreaterOrEqual("3.0.0"))
12 | return { channel: "V3_0", orderer: "V2_0", application: "V2_5", isV2: false, isV3: true };
13 |
14 | return { channel: "V2_0", orderer: "V2_0", application: "V2_0", isV2: true, isV3: false };
15 | };
16 |
17 | const getVersions = (fabricVersion: string): FabricVersions => {
18 | const majorMinor = version(fabricVersion).takeMajorMinor();
19 |
20 | const fabricNodeenvExceptions: Record = {
21 | "2.4": "2.4.2",
22 | "2.4.1": "2.4.2",
23 | };
24 |
25 | const below3_0_0 = (v: string) => (v.startsWith("3.") ? "2.5" : v);
26 |
27 | const is_or_above3_0_0 = (v: string) => (v.startsWith("3.") ? "3.0.0" : v);
28 |
29 | return {
30 | fabricVersion,
31 | fabricToolsVersion: is_or_above3_0_0(fabricVersion),
32 | fabricCaVersion: version(fabricVersion).isGreaterOrEqual("1.4.10") ? "1.5.5" : fabricVersion,
33 | fabricCcenvVersion: fabricVersion,
34 | fabricBaseosVersion: version(fabricVersion).isGreaterOrEqual("2.0") ? fabricVersion : "0.4.9",
35 | fabricJavaenvVersion: below3_0_0(majorMinor),
36 | fabricNodeenvVersion: fabricNodeenvExceptions[fabricVersion] ?? below3_0_0(majorMinor),
37 | fabricRecommendedNodeVersion: version(fabricVersion).isGreaterOrEqual("2.4") ? "16" : "12",
38 | };
39 | };
40 |
41 | const getEnvVarOrThrow = (name: string): string => {
42 | const value = process.env[name];
43 | if (!value || !value.length) throw new Error(`Missing environment variable ${name}`);
44 | return value;
45 | };
46 |
47 | const getPathsFromEnv = () => ({
48 | fabloConfig: getEnvVarOrThrow("FABLO_CONFIG"),
49 | chaincodesBaseDir: getEnvVarOrThrow("CHAINCODES_BASE_DIR"),
50 | });
51 |
52 | const extendGlobal = (globalJson: GlobalJson): Global => {
53 | const engine = globalJson.engine ?? "docker";
54 |
55 | const monitoring = {
56 | loglevel: globalJson?.monitoring?.loglevel || defaults.global.monitoring.loglevel,
57 | };
58 |
59 | const explorer = !globalJson?.tools?.explorer
60 | ? {}
61 | : {
62 | explorer: { address: "explorer.example.com", port: 7010 },
63 | };
64 |
65 | return {
66 | ...globalJson,
67 | ...getVersions(globalJson.fabricVersion),
68 | engine,
69 | paths: getPathsFromEnv(),
70 | monitoring,
71 | capabilities: getNetworkCapabilities(globalJson.fabricVersion),
72 | tools: { ...explorer },
73 | };
74 | };
75 |
76 | export { getNetworkCapabilities };
77 | export default extendGlobal;
78 |
--------------------------------------------------------------------------------
/src/extend-config/extendHooksConfig.ts:
--------------------------------------------------------------------------------
1 | import { HooksJson } from "../types/FabloConfigJson";
2 | import { HooksConfig } from "../types/FabloConfigExtended";
3 |
4 | const extendHooksConfig = (hooksJson: HooksJson | undefined): HooksConfig => {
5 | const postGenerate = typeof hooksJson?.postGenerate === "string" ? hooksJson.postGenerate : "";
6 | return {
7 | postGenerate,
8 | };
9 | };
10 | export default extendHooksConfig;
11 |
--------------------------------------------------------------------------------
/src/extend-config/index.ts:
--------------------------------------------------------------------------------
1 | import * as Generator from "yeoman-generator";
2 | import parseFabloConfig from "../utils/parseFabloConfig";
3 | import extendConfig from "./extendConfig";
4 | import { getNetworkCapabilities } from "./extendGlobal";
5 |
6 | const ValidateGeneratorPath = require.resolve("../validate");
7 |
8 | class ExtendConfigGenerator extends Generator {
9 | constructor(args: string[], opts: Generator.GeneratorOptions) {
10 | super(args, opts);
11 | this.argument("fabloConfig", {
12 | type: String,
13 | optional: true,
14 | description: "Fablo config file path",
15 | default: "../../network/fablo-config.json",
16 | });
17 |
18 | this.composeWith(ValidateGeneratorPath, { arguments: [this.options.fabloConfig] });
19 | }
20 |
21 | async writing(): Promise {
22 | const fabloConfigPath = `${this.env.cwd}/${this.options.fabloConfig}`;
23 | const json = parseFabloConfig(this.fs.read(fabloConfigPath));
24 | const configExtended = extendConfig(json);
25 | console.log(JSON.stringify(configExtended, undefined, 2));
26 | }
27 | }
28 |
29 | export { extendConfig, getNetworkCapabilities };
30 | export default ExtendConfigGenerator;
31 |
--------------------------------------------------------------------------------
/src/extend-config/mergeOrdererGroups.ts:
--------------------------------------------------------------------------------
1 | import { OrdererConfig, OrdererGroup, OrgConfig } from "../types/FabloConfigExtended";
2 | import * as _ from "lodash";
3 |
4 | export const mergeOrdererGroups = (orgs: OrgConfig[]): OrdererGroup[] => {
5 | const ordererGroups = orgs.flatMap((o) => o.ordererGroups);
6 |
7 | const ordererGroupsGrouped: Record = _.groupBy(ordererGroups, (group) => group.name);
8 |
9 | return Object.values(ordererGroupsGrouped).flatMap((groupsWithSameGroupName) => {
10 | const orderers = groupsWithSameGroupName.flatMap((group) => group.orderers);
11 | const hostingOrgs = groupsWithSameGroupName.flatMap((group) => group.hostingOrgs);
12 | const ordererHeads = groupsWithSameGroupName.flatMap((group) => group.ordererHeads);
13 |
14 | return {
15 | ...groupsWithSameGroupName[0],
16 | hostingOrgs,
17 | orderers,
18 | ordererHeads,
19 | ordererHead: ordererHeads[0],
20 | };
21 | });
22 | };
23 |
24 | export const distinctOrdererHeads = (ordererGroups: OrdererGroup[]): OrdererConfig[] => {
25 | const allOrdererHeads = ordererGroups.flatMap((g) => g.ordererHeads);
26 | return _.unionBy(allOrdererHeads, (o) => o.domain);
27 | };
28 |
--------------------------------------------------------------------------------
/src/init/index.ts:
--------------------------------------------------------------------------------
1 | import * as Generator from "yeoman-generator";
2 | import * as chalk from "chalk";
3 | import { GlobalJson, FabloConfigJson } from "../types/FabloConfigJson";
4 |
5 | function getDefaultFabloConfig(): FabloConfigJson {
6 | return {
7 | $schema: "https://github.com/hyperledger-labs/fablo/releases/download/2.2.0/schema.json",
8 | global: {
9 | fabricVersion: "2.5.9",
10 | tls: false,
11 | peerDevMode: false,
12 | },
13 | orgs: [
14 | {
15 | organization: {
16 | name: "Orderer",
17 | domain: "orderer.example.com",
18 | mspName: "OrdererMSP",
19 | },
20 | ca: {
21 | prefix: "ca",
22 | db: "sqlite",
23 | },
24 | orderers: [
25 | {
26 | groupName: "group1",
27 | type: "solo",
28 | instances: 1,
29 | prefix: "orderer",
30 | },
31 | ],
32 | },
33 | {
34 | organization: {
35 | name: "Org1",
36 | domain: "org1.example.com",
37 | mspName: "Org1MSP",
38 | },
39 | ca: {
40 | prefix: "ca",
41 | db: "sqlite",
42 | },
43 | orderers: [],
44 | peer: {
45 | instances: 2,
46 | db: "LevelDb",
47 | prefix: "peer",
48 | },
49 | },
50 | ],
51 | channels: [
52 | {
53 | name: "my-channel1",
54 | orgs: [
55 | {
56 | name: "Org1",
57 | peers: ["peer0", "peer1"],
58 | },
59 | ],
60 | },
61 | ],
62 | chaincodes: [
63 | {
64 | name: "chaincode1",
65 | version: "0.0.1",
66 | lang: "node",
67 | channel: "my-channel1",
68 | directory: "./chaincodes/chaincode-kv-node",
69 | privateData: [],
70 | },
71 | ],
72 | hooks: {},
73 | };
74 | }
75 |
76 | export default class InitGenerator extends Generator {
77 | constructor(readonly args: string[], opts: Generator.GeneratorOptions) {
78 | super(args, opts);
79 | }
80 |
81 | async copySampleConfig(): Promise {
82 | let fabloConfigJson = getDefaultFabloConfig();
83 |
84 | const shouldInitWithNodeChaincode = this.args.length && this.args.find((v) => v === "node");
85 | if (shouldInitWithNodeChaincode) {
86 | console.log("Creating sample Node.js chaincode");
87 | this.fs.copy(this.templatePath("chaincodes"), this.destinationPath("chaincodes"));
88 | // force build on Node 12, since dev deps (@theledger/fabric-mock-stub) may not work on 16
89 | this.fs.write(this.destinationPath("chaincodes/chaincode-kv-node/.nvmrc"), "12");
90 | } else {
91 | fabloConfigJson = { ...fabloConfigJson, chaincodes: [] };
92 | }
93 |
94 | const shouldAddFabloRest = this.args.length && this.args.find((v) => v === "rest");
95 | if (shouldAddFabloRest) {
96 | const orgs = fabloConfigJson.orgs.map((org) => ({ ...org, tools: { fabloRest: true } }));
97 | fabloConfigJson = { ...fabloConfigJson, orgs };
98 | } else {
99 | const orgs = fabloConfigJson.orgs.map((org) => ({ ...org, tools: {} }));
100 | fabloConfigJson = { ...fabloConfigJson, orgs };
101 | }
102 |
103 | const shouldUseKubernetes = this.args.length && this.args.find((v) => v === "kubernetes" || v === "k8s");
104 | const shouldRunInDevMode = this.args.length && this.args.find((v) => v === "dev");
105 | const global: GlobalJson = {
106 | ...fabloConfigJson.global,
107 | engine: shouldUseKubernetes ? "kubernetes" : "docker",
108 | peerDevMode: !!shouldRunInDevMode,
109 | };
110 | fabloConfigJson = { ...fabloConfigJson, global };
111 |
112 | this.fs.write(this.destinationPath("fablo-config.json"), JSON.stringify(fabloConfigJson, undefined, 2));
113 |
114 | this.on("end", () => {
115 | console.log("===========================================================");
116 | console.log(chalk.bold("Sample config file created! :)"));
117 | console.log("You can start your network with 'fablo up' command");
118 | console.log("===========================================================");
119 | });
120 | }
121 | }
--------------------------------------------------------------------------------
/src/list-compatible-updates/index.ts:
--------------------------------------------------------------------------------
1 | import * as Generator from "yeoman-generator";
2 | import * as chalk from "chalk";
3 | import * as config from "../config";
4 | import * as repositoryUtils from "../repositoryUtils";
5 |
6 | export default class ListCompatibleUpdatesGenerator extends Generator {
7 | async checkForCompatibleUpdates(): Promise {
8 | const allNewerVersions = (await repositoryUtils.getAvailableTags()).filter(
9 | (name) => config.isFabloVersionSupported(name) && name > config.fabloVersion,
10 | );
11 |
12 | this._printVersions(allNewerVersions);
13 | }
14 |
15 | _printVersions(versionsToPrint: string[]): void {
16 | if (versionsToPrint.length > 0) {
17 | console.log(chalk.bold("====== !Compatible Fablo versions found! :) ============="));
18 | console.log(`${chalk.underline.bold("Compatible")} versions:`);
19 | versionsToPrint.forEach((version) => console.log(`- ${version}`));
20 | console.log("");
21 | console.log("To update just run command:");
22 | console.log(`\t${chalk.bold("fablo use [version]")}`);
23 | console.log(chalk.bold("==========================================================="));
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/list-versions/index.ts:
--------------------------------------------------------------------------------
1 | import * as Generator from "yeoman-generator";
2 | import * as config from "../config";
3 | import * as repositoryUtils from "../repositoryUtils";
4 |
5 | export default class ListVersionsGenerator extends Generator {
6 | async printAllVersions(): Promise {
7 | const allVersions = await repositoryUtils.getAvailableTags();
8 | const versionsSortedAndMarked = allVersions
9 | .map((v) => (v === config.fabloVersion ? `${v} <== current` : v))
10 | .map((v) => (config.isFabloVersionSupported(v) && !v.includes("current") ? `${v} (compatible)` : v));
11 |
12 | versionsSortedAndMarked.forEach((version) => console.log(`- ${version}`));
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/repositoryUtils.test.ts:
--------------------------------------------------------------------------------
1 | import { sortVersions, version } from "./repositoryUtils";
2 |
3 | describe("repositoryUtils", () => {
4 |
5 | it("should sort versions", () => {
6 | const unsorted = [
7 | "0.0.2",
8 | "0.0.2-unstable",
9 | "0.0.1",
10 | "0.1.11",
11 | "0.1.1",
12 | "0.1.1-unstable",
13 | "1.21.2",
14 | ];
15 |
16 | const expected = [
17 | "1.21.2",
18 | "0.1.11",
19 | "0.1.1",
20 | "0.0.2",
21 | "0.0.1",
22 | "0.1.1-unstable",
23 | "0.0.2-unstable",
24 | ];
25 |
26 | expect(sortVersions(unsorted)).toEqual(expected);
27 | });
28 |
29 | it("should place pre‑release (named) tags after the matching stable tag", () => {
30 | const unsorted = [
31 | "0.1.1-alpha",
32 | "0.1.1-beta",
33 | "0.1.2-alpha",
34 | "0.1.2",
35 | "0.1.1",
36 | ];
37 |
38 | const expected = [
39 | "0.1.2",
40 | "0.1.1",
41 | "0.1.2-alpha",
42 | "0.1.1-beta",
43 | "0.1.1-alpha",
44 | ];
45 |
46 | expect(sortVersions(unsorted)).toEqual(expected);
47 | });
48 |
49 | it("should handle multi digit fragments correctly", () => {
50 | const unsorted = ["1.9.9", "1.10.0", "1.9.10"];
51 | const expected = ["1.10.0", "1.9.10", "1.9.9"];
52 | expect(sortVersions(unsorted)).toEqual(expected);
53 | });
54 |
55 |
56 | it("should compare versions", () => {
57 | expect(version("1.4.0").isGreaterOrEqual("1.4.0")).toBe(true);
58 | expect(version("1.4.0").isGreaterOrEqual("1.4.1")).toBe(false);
59 | expect(version("1.4.0").isGreaterOrEqual("1.3.0")).toBe(true);
60 | expect(version("1.4.0").isGreaterOrEqual("2.1.0")).toBe(false);
61 | expect(version("3.0.0").isGreaterOrEqual("3.0.0-beta")).toBe(true);
62 | expect(version("3.0.0-beta").isGreaterOrEqual("3.0.0")).toBe(true);
63 | expect(version("3.0.0").isGreaterOrEqual("3.0.0")).toBe(true);
64 | expect(version("3.0.0").isGreaterOrEqual("3.0.1")).toBe(false);
65 | });
66 |
67 | it("should treat pre-release tags as equal to the corresponding stable tag for ≥ comparison", () => {
68 | expect(version("0.0.1-alpha").isGreaterOrEqual("0.0.1")).toBe(true);
69 | expect(version("0.0.1").isGreaterOrEqual("0.0.1-alpha")).toBe(true);
70 | });
71 |
72 |
73 | it("should check membership with isOneOf()", () => {
74 | const list = ["1.2.3", "2.0.0", "3.1.4-alpha"];
75 | expect(version("1.2.3").isOneOf(list)).toBe(true);
76 | expect(version("3.1.4").isOneOf(list)).toBe(false);
77 | });
78 |
79 |
80 | it("should take major.minor fragments only", () => {
81 | expect(version("10.5.7").takeMajorMinor()).toBe("10.5");
82 | expect(version("2.0.0-beta").takeMajorMinor()).toBe("2.0");
83 | });
84 | });
85 |
--------------------------------------------------------------------------------
/src/repositoryUtils.ts:
--------------------------------------------------------------------------------
1 | import got from "got";
2 |
3 | const repositoryTagsListUrl = `https://api.github.com/repos/hyperledger-labs/fablo/releases`;
4 |
5 | const incrementVersionFragment = (versionFragment: string) => {
6 | if (versionFragment.includes("-")) {
7 | const splitted = versionFragment.split("-");
8 | return `${+splitted[0] + 100000}-${splitted[1]}`;
9 | }
10 | return +versionFragment + 100000;
11 | };
12 |
13 | const incrementVersionFragments = (v: string) => v.split(".").map(incrementVersionFragment).join(".");
14 |
15 | const decrementVersionFragment = (incrementedVersionFragment: string) => {
16 | if (incrementedVersionFragment.includes("-")) {
17 | const splitted = incrementedVersionFragment.split("-");
18 | return `${+splitted[0] - 100000}-${splitted[1]}`;
19 | }
20 | return +incrementedVersionFragment - 100000;
21 | };
22 |
23 | const decrementVersionFragments = (v: string) => v.split(".").map(decrementVersionFragment).join(".");
24 |
25 | const namedVersionsAsLast = (a: string, b: string) => {
26 | if (/[a-z]/i.test(a) && !/[a-z]/i.test(b)) return -1;
27 | if (/[a-z]/i.test(b) && !/[a-z]/i.test(a)) return 1;
28 | if (a.toString() < b.toString()) return -1;
29 | if (a.toString() > b.toString()) return 1;
30 | return 0;
31 | };
32 |
33 | const sortVersions = (versions: string[]): string[] =>
34 | versions.map(incrementVersionFragments).sort(namedVersionsAsLast).map(decrementVersionFragments).reverse();
35 |
36 | interface Version {
37 | isGreaterOrEqual(v2: string): boolean;
38 | isOneOf(vs: string[]): boolean;
39 | takeMajorMinor(): string;
40 | }
41 |
42 | const version = (v: string): Version => ({
43 | isGreaterOrEqual(v2: string) {
44 | const vStd = incrementVersionFragments(v).split("-")[0];
45 | const v2Std = incrementVersionFragments(v2).split("-")[0];
46 | return vStd >= v2Std;
47 | },
48 | isOneOf(vs: string[]): boolean {
49 | return vs.includes(v);
50 | },
51 | takeMajorMinor(): string {
52 | const [major, minor] = v.split(".");
53 | return `${major}.${minor}`;
54 | },
55 | });
56 |
57 | const requestVersionNames = (): Promise<{ tag_name: string }[]> => {
58 | const params = {
59 | searchParams: {
60 | tag_status: "active",
61 | page_size: 1024,
62 | },
63 | };
64 | return got(repositoryTagsListUrl, params).json<{ tag_name: string }[]>();
65 | };
66 |
67 | const versionRegex = /^v\d+\.\d+\.\d+(-[a-zA-Z0-9-]+)?$/;
68 |
69 | const getAvailableTags = async (): Promise => {
70 | try {
71 | const versionNames = (await requestVersionNames())
72 | .filter((release) => versionRegex.test(release.tag_name))
73 | .map((release) => release.tag_name.slice(1));
74 | return sortVersions(versionNames);
75 | } catch (err) {
76 | console.log(`Could not check for updates. Url: '${repositoryTagsListUrl}' not available`);
77 | return [];
78 | }
79 | };
80 |
81 | export { getAvailableTags, sortVersions, version };
82 |
--------------------------------------------------------------------------------
/src/setup-docker/templates/fabric-config/.gitignore:
--------------------------------------------------------------------------------
1 | /config
2 | /crypto-config
3 |
--------------------------------------------------------------------------------
/src/setup-docker/templates/fabric-config/configtx-raft-template.yaml.ejs:
--------------------------------------------------------------------------------
1 | <% if (ordererGroup.consensus == 'etcdraft') { %>
2 | EtcdRaft:
3 | Consenters:<% ordererGroup.orderers.forEach(function(orderer) { %>
4 | - Host: <%= orderer.address %>
5 | Port: <%= orderer.port %>
6 | ClientTLSCert: crypto-config/peerOrganizations/<%= orderer.domain %>/peers/<%= orderer.address %>/tls/server.crt
7 | ServerTLSCert: crypto-config/peerOrganizations/<%= orderer.domain %>/peers/<%= orderer.address %>/tls/server.crt
8 | <% })} -%>
9 |
10 | <% if (ordererGroup.consensus == "BFT") { %>
11 | SmartBFT:
12 | RequestBatchMaxCount: 100
13 | RequestBatchMaxInterval: 50ms
14 | RequestForwardTimeout: 2s
15 | RequestComplainTimeout: 20s
16 | RequestAutoRemoveTimeout: 3m0s
17 | ViewChangeResendInterval: 5s
18 | ViewChangeTimeout: 20s
19 | LeaderHeartbeatTimeout: 1m0s
20 | CollectTimeout: 1s
21 | RequestBatchMaxBytes: 10485760
22 | IncomingMessageBufferSize: 200
23 | RequestPoolSize: 100000
24 | LeaderHeartbeatCount: 10
25 | ConsenterMapping:<% ordererGroup.orderers.forEach(function(orderer, index) { %>
26 | - ID: <%= index+1 %>
27 | Host: <%= orderer.address %>
28 | Port: <%= orderer.port %>
29 | MSPID: <%= orderer.orgMspName %>
30 | Identity: crypto-config/peerOrganizations/<%= orderer.domain %>/peers/<%= orderer.address %>/msp/signcerts/<%= orderer.address %>-cert.pem
31 | ClientTLSCert: crypto-config/peerOrganizations/<%= orderer.domain %>/peers/<%= orderer.address %>/tls/server.crt
32 | ServerTLSCert: crypto-config/peerOrganizations/<%= orderer.domain %>/peers/<%= orderer.address %>/tls/server.crt
33 | <% })} -%>
34 |
--------------------------------------------------------------------------------
/src/setup-docker/templates/fabric-config/crypto-config-org.yaml:
--------------------------------------------------------------------------------
1 | PeerOrgs:
2 | - Name: <%= org.name %>
3 | Domain: <%= org.domain %>
4 | Specs:
5 | <%_ org.ordererGroups.forEach(function(ordererGroup) { _%>
6 | <%_ ordererGroup.orderers.forEach(function(orderer) { _%>
7 | - Hostname: <%= orderer.name %>
8 | <%_ }) _%>
9 | <%_ }) _%>
10 | Template:
11 | Count: <%= org.peersCount %>
12 | Users:
13 | Count: 1
14 |
--------------------------------------------------------------------------------
/src/setup-docker/templates/fabric-config/explorer/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "network-configs": {
3 | <%_ orgs.forEach(function(org) { _%>
4 | "network-<%= org.name.toLowerCase() %>": {
5 | "name": "Network of <%= org.name %>",
6 | "profile": "/opt/explorer/app/platform/fabric/connection-profile/connection-profile-<%= org.name.toLowerCase() %>.json"
7 | },
8 | <%_ }) _%>
9 | },
10 | "license": "Apache-2.0"
11 | }
12 |
--------------------------------------------------------------------------------
/src/setup-docker/templates/fabric-docker.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -eu
4 |
5 | FABLO_NETWORK_ROOT="$(cd "$(dirname "$0")" && pwd)"
6 |
7 | source "$FABLO_NETWORK_ROOT/fabric-docker/scripts/base-help.sh"
8 | source "$FABLO_NETWORK_ROOT/fabric-docker/scripts/base-functions.sh"
9 | source "$FABLO_NETWORK_ROOT/fabric-docker/scripts/chaincode-functions.sh"
10 | source "$FABLO_NETWORK_ROOT/fabric-docker/channel-query-scripts.sh"
11 | source "$FABLO_NETWORK_ROOT/fabric-docker/snapshot-scripts.sh"
12 | source "$FABLO_NETWORK_ROOT/fabric-docker/commands-generated.sh"
13 | source "$FABLO_NETWORK_ROOT/fabric-docker/.env"
14 | source "$FABLO_NETWORK_ROOT/fabric-docker/chaincode-scripts.sh"
15 |
16 |
17 | networkUp() {
18 | generateArtifacts
19 | startNetwork
20 | generateChannelsArtifacts
21 | installChannels
22 | installChaincodes
23 | notifyOrgsAboutChannels
24 | printStartSuccessInfo
25 | }
26 |
27 | if [ "$1" = "up" ]; then
28 | networkUp
29 | elif [ "$1" = "down" ]; then
30 | networkDown
31 | elif [ "$1" = "reset" ]; then
32 | networkDown
33 | networkUp
34 | elif [ "$1" = "start" ]; then
35 | startNetwork
36 | elif [ "$1" = "stop" ]; then
37 | stopNetwork
38 | elif [ "$1" = "chaincodes" ] && [ "$2" = "install" ]; then
39 | installChaincodes
40 | elif [ "$1" = "chaincode" ] && [ "$2" = "install" ]; then
41 | installChaincode "$3" "$4"
42 | elif [ "$1" = "chaincode" ] && [ "$2" = "upgrade" ]; then
43 | upgradeChaincode "$3" "$4"
44 | elif [ "$1" = "chaincode" ] && [ "$2" = "dev" ]; then
45 | runDevModeChaincode "$3" "$4"
46 | elif [ "$1" = "chaincode" ] && [ "$2" = "invoke" ]; then
47 | chaincodeInvoke "$3" "$4" "$5" "$6" "$7"
48 | elif [ "$1" = "chaincodes" ] && [ "$2" = "list" ]; then
49 | chaincodeList "$3" "$4"
50 | elif [ "$1" = "channel" ]; then
51 | channelQuery "${@:2}"
52 | elif [ "$1" = "snapshot" ]; then
53 | createSnapshot "$2"
54 | elif [ "$1" = "clone-to" ]; then
55 | cloneSnapshot "$2" "${3:-""}"
56 | elif [ "$1" = "help" ]; then
57 | printHelp
58 | elif [ "$1" = "--help" ]; then
59 | printHelp
60 | else
61 | echo "No command specified"
62 | echo "Basic commands are: up, down, start, stop, reset"
63 | echo "To list channel query helper commands type: 'fablo channel --help'"
64 | echo "Also check: 'chaincode install'"
65 | echo "Use 'help' or '--help' for more information"
66 | fi
--------------------------------------------------------------------------------
/src/setup-docker/templates/fabric-docker/.env:
--------------------------------------------------------------------------------
1 | FABLO_VERSION=<%= fabloVersion %>
2 | FABLO_BUILD=<%= fabloBuild %>
3 | FABLO_REST_VERSION=<%= fabloRestVersion %>
4 | HYPERLEDGER_EXPLORER_VERSION=<%= hyperledgerExplorerVersion %>
5 |
6 | COUCHDB_VERSION=<%= couchDbVersion %>
7 | FABRIC_COUCHDB_VERSION=<%= fabricCouchDbVersion %>
8 |
9 | FABLO_CONFIG=<%= paths.fabloConfig %>
10 | CHAINCODES_BASE_DIR=<%= paths.chaincodesBaseDir %>
11 |
12 | COMPOSE_PROJECT_NAME=<%= composeNetworkName %>
13 | LOGGING_LEVEL=<%= global.monitoring.loglevel %>
14 |
15 | FABRIC_VERSION=<%= global.fabricVersion %>
16 | FABRIC_TOOLS_VERSION=<%= global.fabricToolsVersion %>
17 | FABRIC_CA_VERSION=<%= global.fabricCaVersion %>
18 | FABRIC_CA_POSTGRES_VERSION=<%= fabricCaPostgresVersion %>
19 | FABRIC_CCENV_VERSION=<%= global.fabricCcenvVersion %>
20 | FABRIC_BASEOS_VERSION=<%= global.fabricBaseosVersion %>
21 | FABRIC_JAVAENV_VERSION=<%= global.fabricJavaenvVersion %>
22 | FABRIC_NODEENV_VERSION=<%= global.fabricNodeenvVersion %>
23 | RECOMMENDED_NODE_VERSION=<%= global.fabricRecommendedNodeVersion %>
24 |
25 | ROOT_CA_ADMIN_NAME=admin
26 | ROOT_CA_ADMIN_PASSWORD=adminpw
27 | <% orgs.forEach(function(org){ %>
28 | <%= org.ca.caAdminNameVar %>=admin
29 | <%= org.ca.caAdminPassVar %>=adminpw
30 | <% }); %>
31 |
--------------------------------------------------------------------------------
/src/setup-docker/templates/fabric-docker/chaincode-scripts.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | chaincodeList() {
4 | if [ "$#" -ne 2 ]; then
5 | echo "Expected 2 parameters for chaincode list, but got: $*"
6 | exit 1
7 | <% orgs.forEach((org) => { org.peers.forEach((peer) => { %>
8 | elif [ "$1" = "<%= peer.address %>" ]; then
9 | <% if(!global.tls) { %>
10 | peerChaincodeList "<%= org.cli.address %>" "<%= peer.fullAddress %>" "$2" # $2 is channel name
11 | <% } else { %>
12 | peerChaincodeListTls "<%= org.cli.address %>" "<%= peer.fullAddress %>" "$2" "crypto-orderer/tlsca.<%= ordererGroups[0].ordererHeads[0].domain %>-cert.pem" # Third argument is channel name
13 | <% } %>
14 | <% })}) %>
15 | else
16 | echo "Fail to call listChaincodes. No peer or channel found. Provided peer: $1, channel: $2"
17 | exit 1
18 |
19 | fi
20 | }
21 |
22 | # Function to perform chaincode invoke. Accepts 5 parameters:
23 | # 1. comma-separated peers
24 | # 2. channel name
25 | # 3. chaincode name
26 | # 4. chaincode command
27 | # 5. transient data (optional)
28 | chaincodeInvoke() {
29 | if [ "$#" -ne 4 ] && [ "$#" -ne 5 ]; then
30 | echo "Expected 4 or 5 parameters for chaincode list, but got: $*"
31 | echo "Usage: fablo chaincode invoke [transient]"
32 | exit 1
33 | fi
34 |
35 | # Cli needs to be from the same org as the first peer
36 | <% orgs.forEach((org) => { -%>
37 | <% org.peers.forEach((peer) => { -%>
38 | if [[ "$1" == "<%= peer.address %>"* ]]; then
39 | cli="<%= org.cli.address %>"
40 | fi
41 | <% }) -%>
42 | <% }) -%>
43 |
44 | peer_addresses="$1"
45 | <% orgs.forEach((org) => { -%>
46 | <% org.peers.forEach((peer) => { -%>
47 | peer_addresses="${peer_addresses//<%= peer.address %>/<%= peer.fullAddress %>}"
48 | <% }) -%>
49 | <% }) -%>
50 |
51 | <% if (global.tls) { -%>
52 | peer_certs="$1"
53 | <% orgs.forEach((org) => { -%>
54 | <% org.peers.forEach((peer) => { -%>
55 | peer_certs="${peer_certs//<%= peer.address %>/crypto/peers/<%= peer.address %>/tls/ca.crt}"
56 | <% }) -%>
57 | <% }) -%>
58 | <% } -%>
59 |
60 | <% if(!global.tls) { -%>
61 | peerChaincodeInvoke "$cli" "$peer_addresses" "$2" "$3" "$4" "$5"
62 | <% } else { -%>
63 | <% channels.forEach((channel) => { -%>
64 | if [ "$2" = "<%= channel.name %>" ]; then
65 | ca_cert="crypto-orderer/tlsca.<%= ordererGroups[0].ordererHeads[0].domain %>-cert.pem"
66 | fi
67 | <% }) -%>
68 | peerChaincodeInvokeTls "$cli" "$peer_addresses" "$2" "$3" "$4" "$5" "$peer_certs" "$ca_cert"
69 | <% } -%>
70 | }
71 |
--------------------------------------------------------------------------------
/src/setup-docker/templates/fabric-docker/channel-query-scripts.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | source "$FABLO_NETWORK_ROOT/fabric-docker/scripts/channel-query-functions.sh"
4 |
5 | set -eu
6 |
7 | channelQuery() {
8 | if [ "$#" -eq 1 ]; then
9 | printChannelsHelp
10 | <% orgs.forEach((org) => { org.peers.forEach((peer) => { %>
11 | elif [ "$1" = "list" ] && [ "$2" = "<%= org.name.toLowerCase(); %>" ] && [ "$3" = "<%= peer.name %>" ]; then
12 | <% if(!global.tls) { %>
13 | peerChannelList "<%= org.cli.address %>" "<%= peer.fullAddress %>"
14 | <% } else { %>
15 | peerChannelListTls "<%= org.cli.address %>" "<%= peer.fullAddress %>" "crypto-orderer/tlsca.<%= ordererGroups[0].ordererHeads[0].domain %>-cert.pem"
16 | <% } %>
17 | <% })}) %>
18 |
19 | <% channels.forEach((channel) => { channel.orgs.forEach((org) => { org.peers.forEach((peer) => { %>
20 | elif [ "$1" = "getinfo" ] && [ "$2" = "<%= channel.name %>" ] && [ "$3" = "<%= org.name.toLowerCase(); %>" ] && [ "$4" = "<%= peer.name %>" ]; then
21 | <% if(!global.tls) { %>
22 | peerChannelGetInfo "<%= channel.name %>" "<%= org.cli.address %>" "<%= peer.fullAddress %>"
23 | <% } else { %>
24 | peerChannelGetInfoTls "<%= channel.name %>" "<%= org.cli.address %>" "<%= peer.fullAddress %>" "crypto-orderer/tlsca.<%= channel.ordererHead.domain %>-cert.pem"
25 | <% } %>
26 | elif [ "$1" = "fetch" ] && [ "$2" = "config" ] && [ "$3" = "<%= channel.name %>" ] && [ "$4" = "<%= org.name.toLowerCase(); %>" ] && [ "$5" = "<%= peer.name %>" ]; then
27 | TARGET_FILE=${6:-"$channel-config.json"}
28 | <% if(!global.tls) { %>
29 | peerChannelFetchConfig "<%= channel.name %>" "<%= org.cli.address %>" "$TARGET_FILE" "<%= peer.fullAddress %>"
30 | <% } else { %>
31 | peerChannelFetchConfigTls "<%= channel.name %>" "<%= org.cli.address %>" "$TARGET_FILE" "<%= peer.fullAddress %>" "crypto-orderer/tlsca.<%= channel.ordererHead.domain %>-cert.pem"
32 | <% } %>
33 | elif [ "$1" = "fetch" ] && [ "$3" = "<%= channel.name %>" ] && [ "$4" = "<%= org.name.toLowerCase(); %>" ] && [ "$5" = "<%= peer.name %>" ]; then
34 | BLOCK_NAME=$2
35 | TARGET_FILE=${6:-"$BLOCK_NAME.block"}
36 | <% if(!global.tls) { %>
37 | peerChannelFetchBlock "<%= channel.name %>" "<%= org.cli.address %>" "${BLOCK_NAME}" "<%= peer.fullAddress %>" "$TARGET_FILE"
38 | <% } else { %>
39 | peerChannelFetchBlockTls "<%= channel.name %>" "<%= org.cli.address %>" "${BLOCK_NAME}" "<%= peer.fullAddress %>" "crypto-orderer/tlsca.<%= channel.ordererHead.domain %>-cert.pem" "$TARGET_FILE"
40 | <% } %>
41 | <% })})}) %>
42 | else
43 | echo "$@"
44 | echo "$1, $2, $3, $4, $5, $6, $7, $#"
45 | printChannelsHelp
46 | fi
47 |
48 | }
49 |
50 | printChannelsHelp() {
51 | echo "Channel management commands:"
52 | echo ""
53 | <% orgs.forEach((org) => { org.peers.forEach((peer) => { %>
54 | echo "fablo channel list <%= org.name.toLowerCase(); %> <%= peer.name %>"
55 | echo -e "\t List channels on '<%= peer.name %>' of '<%= org.name %>'".
56 | echo ""
57 | <% })}) %>
58 | <% channels.forEach((channel) => { channel.orgs.forEach((org) => { org.peers.forEach((peer) => { %>
59 | echo "fablo channel getinfo <%= channel.name %> <%= org.name.toLowerCase(); %> <%= peer.name %>"
60 | echo -e "\t Get channel info on '<%= peer.name %>' of '<%= org.name %>'".
61 | echo ""
62 | echo "fablo channel fetch config <%= channel.name %> <%= org.name.toLowerCase(); %> <%= peer.name %> [file-name.json]"
63 | echo -e "\t Download latest config block and save it. Uses first peer '<%= peer.name %>' of '<%= org.name %>'".
64 | echo ""
65 | echo "fablo channel fetch <%= channel.name %> <%= org.name.toLowerCase(); %> <%= peer.name %> [file name]"
66 | echo -e "\t Fetch a block with given number and save it. Uses first peer '<%= peer.name %>' of '<%= org.name %>'".
67 | echo ""
68 | <% })})}) %>
69 | }
70 |
--------------------------------------------------------------------------------
/src/setup-docker/templates/fabric-docker/commands-generated/chaincode-dev-v2.sh:
--------------------------------------------------------------------------------
1 | <%/*
2 | Run chaincode in dev mode for V2 capabilities.
3 |
4 | Required template parameters:
5 | - chaincode
6 | */-%>
7 | <% chaincode.channel.orgs.forEach((org) => { -%>
8 | printHeadline "Approving '<%= chaincode.name %>' for <%= org.name %> (dev mode)" "U1F60E"
9 | chaincodeApprove <% -%>
10 | "<%= org.cli.address %>" <% -%>
11 | "<%= org.headPeer.fullAddress %>" <% -%>
12 | "<%= chaincode.channel.name %>" <% -%>
13 | "<%= chaincode.name %>" <% -%>
14 | "<%= chaincode.version %>" <% -%>
15 | "<%= chaincode.channel.ordererHead.fullAddress %>" <% -%>
16 | "<%- chaincode.endorsement || '' %>" <% -%>
17 | "false" <% -%>
18 | "" <% -%>
19 | "<%= chaincode.privateDataConfigFile || '' %>"
20 | <% }) -%>
21 | printItalics "Committing chaincode '<%= chaincode.name %>' on channel '<%= chaincode.channel.name %>' as '<%= chaincode.instantiatingOrg.name %>' (dev mode)" "U1F618"
22 | chaincodeCommit <% -%>
23 | "<%= chaincode.instantiatingOrg.cli.address %>" <% -%>
24 | "<%= chaincode.instantiatingOrg.headPeer.fullAddress %>" <% -%>
25 | "<%= chaincode.channel.name %>" <% -%>
26 | "<%= chaincode.name %>" <% -%>
27 | "<%= chaincode.version %>" <% -%>
28 | "<%= chaincode.channel.ordererHead.fullAddress %>" <% -%>
29 | "<%- chaincode.endorsement || '' %>" <% -%>
30 | "false" <% -%>
31 | "" <% -%>
32 | "<%= chaincode.channel.orgs.map((o) => o.headPeer.fullAddress).join(',') %>" <% -%>
33 | "" <% -%>
34 | "<%= chaincode.privateDataConfigFile || '' %>"
35 |
--------------------------------------------------------------------------------
/src/setup-docker/templates/fabric-docker/commands-generated/chaincode-install-v2.sh:
--------------------------------------------------------------------------------
1 | <%/*
2 | Chaincode install and upgrade for V2 capabilities.
3 |
4 | Required bash variables:
5 | - version
6 | Required template parameters:
7 | - chaincode
8 | - global
9 | */-%>
10 | printHeadline "Packaging chaincode '<%= chaincode.name %>'" "U1F60E"
11 | chaincodeBuild <% -%>
12 | "<%= chaincode.name %>" <% -%>
13 | "<%= chaincode.lang %>" <% -%>
14 | "$CHAINCODES_BASE_DIR/<%= chaincode.directory %>" <% -%>
15 | "<%= global.fabricRecommendedNodeVersion %>"
16 | chaincodePackage <% -%>
17 | "<%= chaincode.instantiatingOrg.cli.address %>" <% -%>
18 | "<%= chaincode.instantiatingOrg.headPeer.fullAddress %>" <% -%>
19 | "<%= chaincode.name %>" <% -%>
20 | "$version" <% -%>
21 | "<%= chaincode.lang %>" <% -%>
22 | <% chaincode.channel.orgs.forEach((org) => { -%>
23 | printHeadline "Installing '<%= chaincode.name %>' for <%= org.name %>" "U1F60E"
24 | <% org.peers.forEach((peer) => { -%>
25 | chaincodeInstall <% -%>
26 | "<%= org.cli.address %>" <% -%>
27 | "<%= peer.fullAddress %>" <% -%>
28 | "<%= chaincode.name %>" <% -%>
29 | "$version" <% -%>
30 | "<%= !global.tls ? '' : `crypto-orderer/tlsca.${chaincode.channel.ordererHead.domain}-cert.pem` %>"
31 | <% }) -%>
32 | chaincodeApprove <% -%>
33 | "<%= org.cli.address %>" <% -%>
34 | "<%= org.headPeer.fullAddress %>" <% -%>
35 | "<%= chaincode.channel.name %>" <% -%>
36 | "<%= chaincode.name %>" <% -%>
37 | "$version" <% -%>
38 | "<%= chaincode.channel.ordererHead.fullAddress %>" <% -%>
39 | "<%- chaincode.endorsement || '' %>" <% -%>
40 | "<%= `${chaincode.initRequired}` %>" <% -%>
41 | "<%= !global.tls ? '' : `crypto-orderer/tlsca.${chaincode.channel.ordererHead.domain}-cert.pem` %>" <% -%>
42 | "<%= chaincode.privateDataConfigFile || '' %>"
43 | <% }) -%>
44 | printItalics "Committing chaincode '<%= chaincode.name %>' on channel '<%= chaincode.channel.name %>' as '<%= chaincode.instantiatingOrg.name %>'" "U1F618"
45 | chaincodeCommit <% -%>
46 | "<%= chaincode.instantiatingOrg.cli.address %>" <% -%>
47 | "<%= chaincode.instantiatingOrg.headPeer.fullAddress %>" <% -%>
48 | "<%= chaincode.channel.name %>" <% -%>
49 | "<%= chaincode.name %>" <% -%>
50 | "$version" <% -%>
51 | "<%= chaincode.channel.ordererHead.fullAddress %>" <% -%>
52 | "<%- chaincode.endorsement || '' %>" <% -%>
53 | "<%= `${chaincode.initRequired}` %>" <% -%>
54 | "<%= !global.tls ? '' : `crypto-orderer/tlsca.${chaincode.channel.ordererHead.domain}-cert.pem` %>" <% -%>
55 | "<%= chaincode.channel.orgs.map((o) => o.headPeer.fullAddress).join(',') %>" <% -%>
56 | "<%= !global.tls ? '' : chaincode.channel.orgs.map(o => `crypto-peer/${o.headPeer.address}/tls/ca.crt`).join(',') %>" <% -%>
57 | "<%= chaincode.privateDataConfigFile || '' %>"
58 |
--------------------------------------------------------------------------------
/src/setup-docker/templates/fabric-docker/scripts/base-help.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | printHelp() {
4 | echo "Fablo is powered by SoftwareMill"
5 |
6 | echo ""
7 | echo "usage: ./fabric-docker.sh "
8 | echo ""
9 |
10 | echo "Commands: "
11 | echo ""
12 | echo "./fabric-docker.sh up"
13 | echo -e "\t Use for first run. Creates all needed artifacts (certs, genesis block) and starts network for the first time."
14 | echo -e "\t After 'up' commands start/stop are used to manage network and rerun to rerun it"
15 | echo ""
16 | echo "./fabric-docker.sh down"
17 | echo -e "\t Back to empty state - destorys created containers, prunes generated certificates, configs."
18 | echo ""
19 | echo "./fabric-docker.sh start"
20 | echo -e "\t Starts already created network."
21 | echo ""
22 | echo "./fabric-docker.sh stop"
23 | echo -e "\t Stops already running network."
24 | echo ""
25 | echo "./fabric-docker.sh reset"
26 | echo -e "\t Fresh start - it destroys whole network, certs, configs and then reruns everything."
27 | echo ""
28 | echo "./fabric-docker.sh channel --help"
29 | echo -e "\t Detailed help for channel management scripts."
30 | echo ""
31 | }
32 |
--------------------------------------------------------------------------------
/src/setup-docker/templates/fabric-docker/scripts/channel-query-functions.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | peerChannelList() {
4 | local CLI_NAME=$1
5 | local PEER_ADDRESS=$2
6 |
7 | echo "Listing channels using $CLI_NAME using peer $PEER_ADDRESS..."
8 | inputLog "CLI_NAME: $CLI_NAME"
9 | inputLog "PEER_ADDRESS: $PEER_ADDRESS"
10 |
11 | docker exec -e CORE_PEER_ADDRESS="$PEER_ADDRESS" "$CLI_NAME" peer channel list
12 | }
13 |
14 | peerChannelGetInfo() {
15 | local CHANNEL_NAME=$1
16 | local CLI_NAME=$2
17 | local PEER_ADDRESS=$3
18 |
19 | echo "Getting info about $CHANNEL_NAME using peer $PEER_ADDRESS..."
20 | inputLog "CHANNEL_NAME: $CHANNEL_NAME"
21 | inputLog "CLI_NAME: $CLI_NAME"
22 | inputLog "PEER_ADDRESS: $PEER_ADDRESS"
23 |
24 | docker exec -e CORE_PEER_ADDRESS="$PEER_ADDRESS" "$CLI_NAME" peer channel getinfo \
25 | -c "$CHANNEL_NAME"
26 | }
27 |
28 | peerChannelFetchConfig() {
29 | local CHANNEL_NAME=$1
30 | local CLI_NAME=$2
31 | local CONFIG_FILE_NAME=$3
32 | local PEER_ADDRESS=$4
33 |
34 | echo "Fetching config block from $CHANNEL_NAME using peer $PEER_ADDRESS..."
35 | inputLog "CHANNEL_NAME: $CHANNEL_NAME"
36 | inputLog "CLI_NAME: $CLI_NAME"
37 | inputLog "CONFIG_FILE_NAME: $CONFIG_FILE_NAME"
38 | inputLog "PEER_ADDRESS: $PEER_ADDRESS"
39 |
40 | docker exec "$CLI_NAME" mkdir -p /tmp/hyperledger/assets/
41 | docker exec \
42 | -e CORE_PEER_ADDRESS="$PEER_ADDRESS" \
43 | "$CLI_NAME" peer channel fetch config /tmp/hyperledger/assets/config_block_before.pb \
44 | -c "$CHANNEL_NAME"
45 |
46 | docker exec "$CLI_NAME" chmod 777 /tmp/hyperledger/assets/config_block_before.pb
47 | docker exec \
48 | -e CORE_PEER_ADDRESS="$PEER_ADDRESS" \
49 | "$CLI_NAME" configtxlator proto_decode \
50 | --input /tmp/hyperledger/assets/config_block_before.pb \
51 | --type common.Block | \
52 | jq .data.data[0].payload.data.config > "$CONFIG_FILE_NAME"
53 |
54 | docker exec "$CLI_NAME" rm -rf /tmp/hyperledger/assets/
55 | }
56 |
57 | peerChannelFetchBlock() {
58 | local CHANNEL_NAME="$1"
59 | local CLI_NAME="$2"
60 | local BLOCK_NAME="$3"
61 | local PEER_ADDRESS="$4"
62 | local TARGET_FILE="$5"
63 | local TEMP_FILE="/tmp/hyperledger/blocks/$BLOCK_NAME.block"
64 |
65 | echo "Fetching block $BLOCK_NAME from $CHANNEL_NAME using peer $PEER_ADDRESS..."
66 | inputLog "CHANNEL_NAME: $CHANNEL_NAME"
67 | inputLog "CLI_NAME: $CLI_NAME"
68 | inputLog "BLOCK_NAME: $BLOCK_NAME"
69 | inputLog "PEER_ADDRESS: $PEER_ADDRESS"
70 | inputLog "TARGET_FILE: $TARGET_FILE"
71 |
72 | docker exec "$CLI_NAME" mkdir -p /tmp/hyperledger/blocks/
73 |
74 | docker exec -e CORE_PEER_ADDRESS="$PEER_ADDRESS" \
75 | "$CLI_NAME" peer channel fetch "$BLOCK_NAME" "$TEMP_FILE" \
76 | -c "$CHANNEL_NAME"
77 |
78 | docker exec "$CLI_NAME" cat "$TEMP_FILE" > "$TARGET_FILE"
79 |
80 | docker exec "$CLI_NAME" rm -rf /tmp/hyperledger/blocks/
81 | }
82 |
83 | #=== TLS equivalents =========================================================
84 |
85 | peerChannelListTls() {
86 | local CLI_NAME=$1
87 | local PEER_ADDRESS=$2
88 | local CA_CERT=$3
89 |
90 | echo "Listing channels using $CLI_NAME using peer $PEER_ADDRESS (TLS)..."
91 | inputLog "CLI_NAME: $CLI_NAME"
92 | inputLog "PEER_ADDRESS: $PEER_ADDRESS"
93 |
94 | docker exec -e CORE_PEER_ADDRESS="$PEER_ADDRESS" "$CLI_NAME" peer channel list --tls --cafile "$CA_CERT"
95 | }
96 |
97 | peerChannelGetInfoTls() {
98 | local CHANNEL_NAME=$1
99 | local CLI_NAME=$2
100 | local PEER_ADDRESS=$3
101 | local CA_CERT=$4
102 |
103 |
104 | echo "Getting info about $CHANNEL_NAME using peer $PEER_ADDRESS (TLS)..."
105 | inputLog "CHANNEL_NAME: $CHANNEL_NAME"
106 | inputLog "CLI_NAME: $CLI_NAME"
107 | inputLog "PEER_ADDRESS: $PEER_ADDRESS"
108 |
109 | docker exec -e CORE_PEER_ADDRESS="$PEER_ADDRESS" "$CLI_NAME" peer channel getinfo \
110 | -c "$CHANNEL_NAME" --tls --cafile "$CA_CERT"
111 | }
112 |
113 | peerChannelFetchConfigTls() {
114 | local CHANNEL_NAME=$1
115 | local CLI_NAME=$2
116 | local CONFIG_FILE_NAME=$3
117 | local PEER_ADDRESS=$4
118 | local CA_CERT=$5
119 |
120 | echo "Fetching config block from $CHANNEL_NAME using peer $PEER_ADDRESS (TLS)..."
121 | inputLog "CHANNEL_NAME: $CHANNEL_NAME"
122 | inputLog "CLI_NAME: $CLI_NAME"
123 | inputLog "CONFIG_FILE_NAME: $CONFIG_FILE_NAME"
124 | inputLog "PEER_ADDRESS: $PEER_ADDRESS"
125 |
126 | docker exec "$CLI_NAME" mkdir -p /tmp/hyperledger/assets/
127 | docker exec \
128 | -e CORE_PEER_ADDRESS="$PEER_ADDRESS" \
129 | "$CLI_NAME" peer channel fetch config /tmp/hyperledger/assets/config_block_before.pb \
130 | -c "$CHANNEL_NAME" --tls --cafile "$CA_CERT"
131 |
132 | docker exec "$CLI_NAME" chmod 777 /tmp/hyperledger/assets/config_block_before.pb
133 | docker exec \
134 | -e CORE_PEER_ADDRESS="$PEER_ADDRESS" \
135 | "$CLI_NAME" configtxlator proto_decode \
136 | --input /tmp/hyperledger/assets/config_block_before.pb \
137 | --type common.Block | \
138 | jq .data.data[0].payload.data.config > "$CONFIG_FILE_NAME"
139 |
140 | docker exec "$CLI_NAME" rm -rf /tmp/hyperledger/assets/
141 | }
142 |
143 | peerChannelFetchBlockTls() {
144 | local CHANNEL_NAME="$1"
145 | local CLI_NAME="$2"
146 | local BLOCK_NAME="$3"
147 | local PEER_ADDRESS="$4"
148 | local CA_CERT="$5"
149 | local TARGET_FILE="$6"
150 | local TEMP_FILE="/tmp/hyperledger/blocks/$BLOCK_NAME.block"
151 |
152 | echo "Fetching block $BLOCK_NAME from $CHANNEL_NAME using peer $PEER_ADDRESS..."
153 | inputLog "CHANNEL_NAME: $CHANNEL_NAME"
154 | inputLog "CLI_NAME: $CLI_NAME"
155 | inputLog "BLOCK_NAME: $BLOCK_NAME"
156 | inputLog "PEER_ADDRESS: $PEER_ADDRESS"
157 | inputLog "TARGET_FILE: $TARGET_FILE"
158 |
159 | docker exec "$CLI_NAME" mkdir -p /tmp/hyperledger/blocks/
160 |
161 | docker exec -e CORE_PEER_ADDRESS="$PEER_ADDRESS" \
162 | "$CLI_NAME" peer channel fetch "$BLOCK_NAME" "$TEMP_FILE" \
163 | -c "$CHANNEL_NAME" --tls --cafile "$CA_CERT"
164 |
165 | docker exec "$CLI_NAME" cat "$TEMP_FILE" > "$TARGET_FILE"
166 |
167 | docker exec "$CLI_NAME" rm -rf /tmp/hyperledger/blocks/
168 | }
169 |
--------------------------------------------------------------------------------
/src/setup-docker/templates/fabric-docker/scripts/cli/channel_fns-v2.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -eu
4 |
5 | createChannelAndJoin() {
6 | local CHANNEL_NAME=$1
7 | local CORE_PEER_LOCALMSPID=$2
8 | local CORE_PEER_ADDRESS=$3
9 | local CORE_PEER_MSPCONFIGPATH=$(realpath "$4")
10 | local ORDERER_URL=$5
11 |
12 | local DIR_NAME=step-createChannelAndJoin-$CHANNEL_NAME-$CORE_PEER_ADDRESS
13 |
14 | echo "Creating channel with name: ${CHANNEL_NAME}"
15 | echo " Orderer: $ORDERER_URL"
16 | echo " CORE_PEER_LOCALMSPID: $CORE_PEER_LOCALMSPID"
17 | echo " CORE_PEER_ADDRESS: $CORE_PEER_ADDRESS"
18 | echo " CORE_PEER_MSPCONFIGPATH: $CORE_PEER_MSPCONFIGPATH"
19 |
20 | mkdir "$DIR_NAME" && cd "$DIR_NAME"
21 |
22 | cp /var/hyperledger/cli/config/"$CHANNEL_NAME".tx .
23 | peer channel create -o "${ORDERER_URL}" -c "${CHANNEL_NAME}" -f ./"$CHANNEL_NAME".tx
24 | peer channel join -b "${CHANNEL_NAME}".block
25 |
26 | rm -rf "$DIR_NAME"
27 | }
28 |
29 | createChannelAndJoinTls() {
30 | local CHANNEL_NAME=$1
31 | local CORE_PEER_LOCALMSPID=$2
32 | local CORE_PEER_ADDRESS=$3
33 | local CORE_PEER_MSPCONFIGPATH=$(realpath "$4")
34 | local CORE_PEER_TLS_MSPCONFIGPATH=$(realpath "$5")
35 | local TLS_CA_CERT_PATH=$(realpath "$6")
36 | local ORDERER_URL=$7
37 |
38 | local CORE_PEER_TLS_CERT_FILE=$CORE_PEER_TLS_MSPCONFIGPATH/client.crt
39 | local CORE_PEER_TLS_KEY_FILE=$CORE_PEER_TLS_MSPCONFIGPATH/client.key
40 | local CORE_PEER_TLS_ROOTCERT_FILE=$CORE_PEER_TLS_MSPCONFIGPATH/ca.crt
41 |
42 | local DIR_NAME=step-createChannelAndJoinTls-$CHANNEL_NAME-$CORE_PEER_ADDRESS
43 |
44 | echo "Creating channel with name (TLS): ${CHANNEL_NAME}"
45 | echo " Orderer: $ORDERER_URL"
46 | echo " CORE_PEER_LOCALMSPID: $CORE_PEER_LOCALMSPID"
47 | echo " CORE_PEER_ADDRESS: $CORE_PEER_ADDRESS"
48 | echo " CORE_PEER_MSPCONFIGPATH: $CORE_PEER_MSPCONFIGPATH"
49 | echo " TLS_CA_CERT_PATH is: $TLS_CA_CERT_PATH"
50 | echo " CORE_PEER_TLS_CERT_FILE: $CORE_PEER_TLS_CERT_FILE"
51 | echo " CORE_PEER_TLS_KEY_FILE: $CORE_PEER_TLS_KEY_FILE"
52 | echo " CORE_PEER_TLS_ROOTCERT_FILE: $CORE_PEER_TLS_ROOTCERT_FILE"
53 |
54 | mkdir "$DIR_NAME" && cd "$DIR_NAME"
55 |
56 | cp /var/hyperledger/cli/config/"$CHANNEL_NAME".tx .
57 |
58 | peer channel create -o "${ORDERER_URL}" -c "${CHANNEL_NAME}" -f ./"$CHANNEL_NAME".tx --tls --cafile "$TLS_CA_CERT_PATH"
59 | peer channel join -b "${CHANNEL_NAME}".block --tls --cafile "$TLS_CA_CERT_PATH"
60 |
61 |
62 | rm -rf "$DIR_NAME"
63 | }
64 |
65 | fetchChannelAndJoin() {
66 | local CHANNEL_NAME=$1
67 | local CORE_PEER_LOCALMSPID=$2
68 | local CORE_PEER_ADDRESS=$3
69 | local CORE_PEER_MSPCONFIGPATH=$(realpath "$4")
70 | local ORDERER_URL=$5
71 |
72 | local DIR_NAME=step-fetchChannelAndJoin-$CHANNEL_NAME-$CORE_PEER_ADDRESS
73 |
74 | echo "Fetching channel with name: ${CHANNEL_NAME}"
75 | echo " Orderer: $ORDERER_URL"
76 | echo " CORE_PEER_LOCALMSPID: $CORE_PEER_LOCALMSPID"
77 | echo " CORE_PEER_ADDRESS: $CORE_PEER_ADDRESS"
78 | echo " CORE_PEER_MSPCONFIGPATH: $CORE_PEER_MSPCONFIGPATH"
79 |
80 | mkdir "$DIR_NAME" && cd "$DIR_NAME"
81 |
82 | peer channel fetch newest -c "${CHANNEL_NAME}" --orderer "${ORDERER_URL}"
83 | peer channel join -b "${CHANNEL_NAME}"_newest.block
84 |
85 | rm -rf "$DIR_NAME"
86 | }
87 |
88 | fetchChannelAndJoinTls() {
89 | local CHANNEL_NAME=$1
90 | local CORE_PEER_LOCALMSPID=$2
91 | local CORE_PEER_ADDRESS=$3
92 | local CORE_PEER_MSPCONFIGPATH=$(realpath "$4")
93 | local CORE_PEER_TLS_MSPCONFIGPATH=$(realpath "$5")
94 | local TLS_CA_CERT_PATH=$(realpath "$6")
95 | local ORDERER_URL=$7
96 |
97 | local CORE_PEER_TLS_CERT_FILE=$CORE_PEER_TLS_MSPCONFIGPATH/client.crt
98 | local CORE_PEER_TLS_KEY_FILE=$CORE_PEER_TLS_MSPCONFIGPATH/client.key
99 | local CORE_PEER_TLS_ROOTCERT_FILE=$CORE_PEER_TLS_MSPCONFIGPATH/ca.crt
100 |
101 | local DIR_NAME=step-fetchChannelAndJoinTls-$CHANNEL_NAME-$CORE_PEER_ADDRESS
102 |
103 | echo "Fetching channel with name (TLS): ${CHANNEL_NAME}"
104 | echo " Orderer: $ORDERER_URL"
105 | echo " CORE_PEER_LOCALMSPID: $CORE_PEER_LOCALMSPID"
106 | echo " CORE_PEER_ADDRESS: $CORE_PEER_ADDRESS"
107 | echo " CORE_PEER_MSPCONFIGPATH: $CORE_PEER_MSPCONFIGPATH"
108 | echo " TLS_CA_CERT_PATH is: $TLS_CA_CERT_PATH"
109 | echo " CORE_PEER_TLS_CERT_FILE: $CORE_PEER_TLS_CERT_FILE"
110 | echo " CORE_PEER_TLS_KEY_FILE: $CORE_PEER_TLS_KEY_FILE"
111 | echo " CORE_PEER_TLS_ROOTCERT_FILE: $CORE_PEER_TLS_ROOTCERT_FILE"
112 |
113 | mkdir "$DIR_NAME" && cd "$DIR_NAME"
114 |
115 | peer channel fetch newest -c "${CHANNEL_NAME}" --orderer "${ORDERER_URL}" --tls --cafile "$TLS_CA_CERT_PATH"
116 | peer channel join -b "${CHANNEL_NAME}"_newest.block --tls --cafile "$TLS_CA_CERT_PATH"
117 |
118 | rm -rf "$DIR_NAME"
119 | }
120 |
--------------------------------------------------------------------------------
/src/setup-docker/templates/fabric-docker/scripts/cli/channel_fns-v3.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -eu
4 |
5 | createChannelAndJoin() {
6 | local CHANNEL_NAME=$1
7 |
8 | local CORE_PEER_LOCALMSPID=$2
9 | local CORE_PEER_ADDRESS=$3
10 | local CORE_PEER_MSPCONFIGPATH=$(realpath "$4")
11 |
12 | local ORDERER_URL=$5
13 |
14 | local DIR_NAME=step-createChannelAndJoin-$CHANNEL_NAME-$CORE_PEER_ADDRESS
15 |
16 | echo "Creating channel with name: ${CHANNEL_NAME}"
17 | echo " Orderer: $ORDERER_URL"
18 | echo " CORE_PEER_LOCALMSPID: $CORE_PEER_LOCALMSPID"
19 | echo " CORE_PEER_ADDRESS: $CORE_PEER_ADDRESS"
20 | echo " CORE_PEER_MSPCONFIGPATH: $CORE_PEER_MSPCONFIGPATH"
21 |
22 | mkdir "$DIR_NAME" && cd "$DIR_NAME"
23 |
24 | cp /var/hyperledger/cli/config/"$CHANNEL_NAME".pb .
25 |
26 | osnadmin channel join --channelID "${CHANNEL_NAME}" --config-block ./"$CHANNEL_NAME".pb -o "${ORDERER_URL}"
27 | cd .. && rm -rf "$DIR_NAME"
28 | }
29 |
30 | createChannelAndJoinTls() {
31 | local CHANNEL_NAME=$1
32 | local ORDERER_MSP_NAME=$2
33 | local ORDERER_ADMIN_ADDRESS=$3
34 | local ADMIN_TLS_SIGN_CERT=$(realpath "$4")
35 | local ADMIN_TLS_PRIVATE_KEY=$(realpath "$5")
36 | local TLS_CA_CERT_PATH=$(realpath "$6")
37 |
38 | local DIR_NAME=step-createChannelAndJoinTls-$CHANNEL_NAME-$ORDERER_MSP_NAME
39 |
40 | echo "Creating channel with name (TLS): ${CHANNEL_NAME}"
41 | echo " ORDERER_MSP_NAME: $ORDERER_MSP_NAME"
42 | echo " ORDERER_ADMIN_ADDRESS: $ORDERER_ADMIN_ADDRESS"
43 | echo " ADMIN_TLS_SIGN_CERT: $ADMIN_TLS_SIGN_CERT"
44 | echo " ADMIN_TLS_PRIVATE_KEY: $ADMIN_TLS_PRIVATE_KEY"
45 | echo " TLS_CA_CERT_PATH: $TLS_CA_CERT_PATH"
46 |
47 | if [ ! -d "$DIR_NAME" ]; then
48 | mkdir -p "$DIR_NAME"
49 | cp /var/hyperledger/cli/config/"$CHANNEL_NAME".pb "$DIR_NAME"
50 | fi
51 |
52 | osnadmin channel join \
53 | --channelID "${CHANNEL_NAME}" \
54 | --config-block "$DIR_NAME/$CHANNEL_NAME.pb" \
55 | -o "${ORDERER_ADMIN_ADDRESS}" \
56 | --client-cert "${ADMIN_TLS_SIGN_CERT}" \
57 | --client-key "${ADMIN_TLS_PRIVATE_KEY}" \
58 | --ca-file "${TLS_CA_CERT_PATH}"
59 |
60 | cd ..
61 | rm -rf "$DIR_NAME"
62 | }
63 |
64 | fetchChannelAndJoin() {
65 | local CHANNEL_NAME=$1
66 | local CORE_PEER_LOCALMSPID=$2
67 | local CORE_PEER_ADDRESS=$3
68 | local CORE_PEER_MSPCONFIGPATH=$(realpath "$4")
69 | local ORDERER_URL=$5
70 |
71 | local DIR_NAME=step-fetchChannelAndJoin-$CHANNEL_NAME-$CORE_PEER_ADDRESS
72 |
73 | echo "Fetching channel with name: ${CHANNEL_NAME}"
74 | echo " Orderer: $ORDERER_URL"
75 | echo " CORE_PEER_LOCALMSPID: $CORE_PEER_LOCALMSPID"
76 | echo " CORE_PEER_ADDRESS: $CORE_PEER_ADDRESS"
77 | echo " CORE_PEER_MSPCONFIGPATH: $CORE_PEER_MSPCONFIGPATH"
78 |
79 | mkdir "$DIR_NAME" && cd "$DIR_NAME"
80 |
81 | peer channel fetch newest -c "${CHANNEL_NAME}" --orderer "${ORDERER_URL}"
82 | peer channel join -b "${CHANNEL_NAME}"_newest.block
83 |
84 | cd .. && rm -rf "$DIR_NAME"
85 | }
86 |
87 | fetchChannelAndJoinTls() {
88 | local CHANNEL_NAME=$1
89 | local CORE_PEER_LOCALMSPID=$2
90 | local CORE_PEER_ADDRESS=$3
91 | local CORE_PEER_MSPCONFIGPATH=$(realpath "$4")
92 | local CORE_PEER_TLS_MSPCONFIGPATH=$(realpath "$5")
93 | local TLS_CA_CERT_PATH=$(realpath "$6")
94 | local ORDERER_URL=$7
95 |
96 | local CORE_PEER_TLS_CERT_FILE=$CORE_PEER_TLS_MSPCONFIGPATH/client.crt
97 | local CORE_PEER_TLS_KEY_FILE=$CORE_PEER_TLS_MSPCONFIGPATH/client.key
98 | local CORE_PEER_TLS_ROOTCERT_FILE=$CORE_PEER_TLS_MSPCONFIGPATH/ca.crt
99 |
100 | local DIR_NAME=step-fetchChannelAndJoinTls-$CHANNEL_NAME-$CORE_PEER_ADDRESS
101 |
102 | echo "Fetching channel with name (TLS): ${CHANNEL_NAME}"
103 | echo " Orderer: $ORDERER_URL"
104 | echo " CORE_PEER_LOCALMSPID: $CORE_PEER_LOCALMSPID"
105 | echo " CORE_PEER_ADDRESS: $CORE_PEER_ADDRESS"
106 | echo " CORE_PEER_MSPCONFIGPATH: $CORE_PEER_MSPCONFIGPATH"
107 | echo " TLS_CA_CERT_PATH is: $TLS_CA_CERT_PATH"
108 | echo " CORE_PEER_TLS_CERT_FILE: $CORE_PEER_TLS_CERT_FILE"
109 | echo " CORE_PEER_TLS_KEY_FILE: $CORE_PEER_TLS_KEY_FILE"
110 | echo " CORE_PEER_TLS_ROOTCERT_FILE: $CORE_PEER_TLS_ROOTCERT_FILE"
111 |
112 | mkdir "$DIR_NAME" && cd "$DIR_NAME"
113 |
114 | peer channel fetch newest -c "${CHANNEL_NAME}" --orderer "${ORDERER_URL}" --tls --cafile "$TLS_CA_CERT_PATH"
115 | peer channel join -b "${CHANNEL_NAME}"_newest.block --tls --cafile "$TLS_CA_CERT_PATH"
116 |
117 | cd .. && rm -rf "$DIR_NAME"
118 | }
119 |
--------------------------------------------------------------------------------
/src/setup-docker/templates/fabric-docker/snapshot-scripts.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | __getOrdererAndPeerNodes() {
4 | echo "
5 | <%_ orgs.forEach((org) => { _%>
6 | <%_ org.ordererGroups.forEach((g) => g.orderers.forEach((o) => { _%>
7 | <%= o.address %>
8 | <%_ })) _%>
9 | <%_ org.peers.forEach((p) => { _%>
10 | <%= p.address %>
11 | <%_ }) _%>
12 | <%_ }) _%>
13 | "
14 | }
15 |
16 | __getCASQLiteNodes() {
17 | echo "
18 | <%_ orgs.filter((org) => org.ca.db === 'sqlite').forEach((org) => { _%>
19 | <%= org.ca.address %>
20 | <%_ }) _%>
21 | "
22 | }
23 |
24 | __getCAPostgresNodes() {
25 | echo "
26 | <%_ orgs.filter((org) => org.ca.db === 'postgres').forEach((org) => { _%>
27 | db.<%= org.ca.address %>
28 | <%_ }) _%>
29 | "
30 | }
31 |
32 | __createSnapshot() {
33 | cd "$FABLO_NETWORK_ROOT/.."
34 | backup_dir="${1:-"snapshot-$(date -u +"%Y%m%d%H%M%S")"}"
35 |
36 | if [ -d "$backup_dir" ] && [ "$(ls -A "$backup_dir")" ]; then
37 | echo "Error: Directory '$backup_dir' already exists and is not empty!"
38 | exit 1
39 | fi
40 |
41 | mkdir -p "$backup_dir"
42 | cp -R ./fablo-target "$backup_dir/"
43 |
44 | for node in $(__getCASQLiteNodes); do
45 | echo "Saving state of $node..."
46 | mkdir -p "$backup_dir/$node"
47 | docker cp "$node:/etc/hyperledger/fabric-ca-server/fabric-ca-server.db" "$backup_dir/$node/fabric-ca-server.db"
48 | done
49 |
50 | for node in $(__getCAPostgresNodes); do
51 | echo "Saving state of $node..."
52 | mkdir -p "$backup_dir/$node/pg-data"
53 | docker exec "$node" pg_dump -c --if-exists -U postgres fabriccaserver >"$backup_dir/$node/fabriccaserver.sql"
54 | done
55 |
56 | for node in $(__getOrdererAndPeerNodes); do
57 | echo "Saving state of $node..."
58 | docker cp "$node:/var/hyperledger/production/" "$backup_dir/$node/"
59 | done
60 | }
61 |
62 | __cloneSnapshot() {
63 | cd "$FABLO_NETWORK_ROOT/.."
64 | target_dir="$1"
65 | hook_cmd="$2"
66 |
67 | if [ -d "$target_dir/fablo-target" ]; then
68 | echo "Error: Directory '$target_dir/fablo-target' already exists! Execute 'fablo prune' to remove the current network."
69 | exit 1
70 | fi
71 |
72 | cp -R ./fablo-target "$target_dir/fablo-target"
73 |
74 | if [ -n "$hook_cmd" ]; then
75 | echo "Executing pre-restore hook: '$hook_cmd'"
76 | (cd "$target_dir" && eval "$hook_cmd")
77 | fi
78 |
79 | (cd "$target_dir/fablo-target/fabric-docker" && docker compose up --no-start)
80 |
81 | for node in $(__getCASQLiteNodes); do
82 | echo "Restoring $node..."
83 | if [ ! -d "$node" ]; then
84 | echo "Warning: Cannot restore '$node', directory does not exist!"
85 | else
86 | docker cp "./$node/fabric-ca-server.db" "$node:/etc/hyperledger/fabric-ca-server/fabric-ca-server.db"
87 | fi
88 | done
89 |
90 | for node in $(__getCAPostgresNodes); do
91 | echo "Restoring $node..."
92 | if [ ! -d "$node" ]; then
93 | echo "Warning: Cannot restore '$node', directory does not exist!"
94 | else
95 | docker cp "./$node/fabriccaserver.sql" "$node:/docker-entrypoint-initdb.d/fabriccaserver.sql"
96 | fi
97 | done
98 |
99 | for node in $(__getOrdererAndPeerNodes); do
100 | echo "Restoring $node..."
101 | if [ ! -d "$node" ]; then
102 | echo "Warning: Cannot restore '$node', directory does not exist!"
103 | else
104 | docker cp "./$node/" "$node:/var/hyperledger/production/"
105 | fi
106 | done
107 | }
108 |
109 | createSnapshot() {
110 | (set -eu && __createSnapshot "$1")
111 | }
112 |
113 | cloneSnapshot() {
114 | (set -eu && __cloneSnapshot "$1" "$2")
115 | }
116 |
--------------------------------------------------------------------------------
/src/setup-docker/templates/hooks/post-generate.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # The code from this file was called after Fablo generated Hyperledger Fabric configuration
4 | echo "Executing post-generate hook"
5 |
6 | <%- hooks.postGenerate %>
7 |
--------------------------------------------------------------------------------
/src/setup-k8s/index.ts:
--------------------------------------------------------------------------------
1 | import * as Generator from "yeoman-generator";
2 | import * as config from "../config";
3 | import { getBuildInfo } from "../version/buildUtil";
4 | import parseFabloConfig from "../utils/parseFabloConfig";
5 | import { Capabilities, FabloConfigExtended, HooksConfig, Global, OrgConfig } from "../types/FabloConfigExtended";
6 | import { extendConfig } from "../extend-config/";
7 |
8 | const ValidateGeneratorPath = require.resolve("../validate");
9 |
10 | export default class SetupDockerGenerator extends Generator {
11 | constructor(args: string[], opts: Generator.GeneratorOptions) {
12 | super(args, opts);
13 | this.argument("fabloConfig", {
14 | type: String,
15 | optional: true,
16 | description: "Fablo config file path",
17 | default: "../../network/fablo-config.json",
18 | });
19 |
20 | this.composeWith(ValidateGeneratorPath, { arguments: [this.options.fabloConfig] });
21 | }
22 |
23 | async writing(): Promise {
24 | const fabloConfigPath = `${this.env.cwd}/${this.options.fabloConfig}`;
25 | const json = parseFabloConfig(this.fs.read(fabloConfigPath));
26 | const config = extendConfig(json);
27 | const { global, orgs } = config;
28 |
29 | const dateString = new Date()
30 | .toISOString()
31 | .substring(0, 16)
32 | .replace(/[^0-9]+/g, "");
33 | const composeNetworkName = `fablo_network_${dateString}`;
34 |
35 | console.log(`Used network config: ${fabloConfigPath}`);
36 | console.log(`Fabric version is: ${global.fabricVersion}`);
37 |
38 | this._copyGitIgnore();
39 |
40 | // ======= fabric-k8s ===========================================================
41 | this._copyEnvFile(global, orgs, composeNetworkName);
42 |
43 | // ======= scripts ==================================================================
44 | // this._copyCommandsGeneratedScript(config);
45 | this._copyCommandsGeneratedScript(config);
46 | this._copyUtilityScripts(config.global.capabilities);
47 |
48 | // ======= hooks ====================================================================
49 | this._copyHooks(config.hooks);
50 |
51 | this.on("end", () => {
52 | console.log("Done & done !!! Try the network out: ");
53 | console.log("-> fablo up - to start network");
54 | console.log("-> fablo help - to view all commands");
55 | });
56 | }
57 |
58 | _copyGitIgnore(): void {
59 | this.fs.copyTpl(this.templatePath("fabric-config/.gitignore"), this.destinationPath("fabric-config/.gitignore"));
60 | }
61 |
62 | _copyEnvFile(global: Global, orgsTransformed: OrgConfig[], composeNetworkName: string): void {
63 | const settings = {
64 | composeNetworkName,
65 | fabricCaVersion: global.fabricCaVersion,
66 | global,
67 | orgs: orgsTransformed,
68 | paths: global.paths,
69 | fabloVersion: config.fabloVersion,
70 | fabloBuild: getBuildInfo(),
71 | fabloRestVersion: "0.1.2",
72 | hyperledgerExplorerVersion: "1.1.8",
73 | fabricCouchDbVersion: "0.4.18",
74 | couchDbVersion: "3.1",
75 | fabricCaPostgresVersion: "14",
76 | };
77 | this.fs.copyTpl(this.templatePath("fabric-k8s/.env"), this.destinationPath("fabric-k8s/.env"), settings);
78 | }
79 |
80 | _copyCommandsGeneratedScript(config: FabloConfigExtended): void {
81 | this.fs.copyTpl(
82 | this.templatePath("fabric-k8s/scripts/base-functions.sh"),
83 | this.destinationPath("fabric-k8s/scripts/base-functions.sh"),
84 | config,
85 | );
86 | }
87 | _copyUtilityScripts(capabilities: Capabilities): void {
88 | this.fs.copyTpl(this.templatePath("fabric-k8s.sh"), this.destinationPath("fabric-k8s.sh"));
89 | this.fs.copyTpl(
90 | this.templatePath("fabric-k8s/scripts/base-help.sh"),
91 | this.destinationPath("fabric-k8s/scripts/base-help.sh"),
92 | );
93 |
94 | this.fs.copyTpl(
95 | this.templatePath("fabric-k8s/scripts/util.sh"),
96 | this.destinationPath("fabric-k8s/scripts/util.sh"),
97 | );
98 |
99 | this.fs.copyTpl(
100 | this.templatePath(`fabric-k8s/scripts/chaincode-functions.sh`),
101 | this.destinationPath("fabric-k8s/scripts/chaincode-functions.sh"),
102 | );
103 |
104 | this.fs.copyTpl(
105 | this.templatePath(`fabric-k8s/scripts/chaincode-functions-${capabilities.isV2 ? "v2" : "v1.4"}.sh`),
106 | this.destinationPath("fabric-k8s/scripts/chaincode-functions.sh"),
107 | );
108 | }
109 |
110 | _copyHooks(hooks: HooksConfig): void {
111 | this.fs.copyTpl(this.templatePath("hooks/post-generate.sh"), this.destinationPath("hooks/post-generate.sh"), {
112 | hooks,
113 | });
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/setup-k8s/templates/fabric-config/.gitignore:
--------------------------------------------------------------------------------
1 | /config
2 | /crypto-config
3 |
--------------------------------------------------------------------------------
/src/setup-k8s/templates/fabric-k8s.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | FABLO_NETWORK_ROOT="$(cd "$(dirname "$0")" && pwd)"
6 |
7 | source "$FABLO_NETWORK_ROOT/fabric-k8s/scripts/base-help.sh"
8 | source "$FABLO_NETWORK_ROOT/fabric-k8s/scripts/base-functions.sh"
9 | source "$FABLO_NETWORK_ROOT/fabric-k8s/scripts/chaincode-functions.sh"
10 | source "$FABLO_NETWORK_ROOT/fabric-k8s/.env"
11 |
12 | # location of generated configurations
13 | CONFIG_DIR="$FABLO_NETWORK_ROOT/fabric-config"
14 |
15 | RESETBG="$(printf '\e[0m\n')"
16 | BLUE="$(printf '\033[34m')"
17 |
18 | networkUp() {
19 | printHeadline "Checking dependencies..." "U1F984"
20 | verifyKubernetesConnectivity
21 | printHeadline "Starting Network..." "U1F984"
22 | deployPeer
23 | deployOrderer
24 | installChannels
25 | installChaincodes
26 | printHeadline "Done! Enjoy your fresh network" "U1F984"
27 | }
28 |
29 | networkDown() {
30 | printHeadline "Destroying network" "U1F913"
31 | destroyNetwork
32 | }
33 |
34 | if [ "$1" = "up" ]; then
35 | networkUp
36 | elif [ "$1" = "down" ]; then
37 | networkDown
38 | elif [ "$1" = "reset" ]; then
39 | networkDown
40 | sleep 60
41 | networkUp
42 | elif [ "$1" = "start" ]; then
43 | startNetwork
44 | elif [ "$1" = "stop" ]; then
45 | stopNetwork
46 | elif [ "$1" = "chaincodes" ] && [ "$2" = "install" ]; then
47 | installChaincodes
48 | elif [ "$1" = "chaincode" ] && [ "$2" = "install" ]; then
49 | installChaincode "$3" "$4"
50 | elif [ "$1" = "chaincode" ] && [ "$2" = "upgrade" ]; then
51 | upgradeChaincode "$3" "$4"
52 | elif [ "$1" = "chaincode" ] && [ "$2" = "dev" ]; then
53 | runDevModeChaincode "$3" "$4"
54 | elif [ "$1" = "channel" ]; then
55 | channelQuery "${@:2}"
56 | elif [ "$1" = "snapshot" ]; then
57 | createSnapshot "$2"
58 | elif [ "$1" = "clone-to" ]; then
59 | cloneSnapshot "$2" "${3:-""}"
60 | elif [ "$1" = "help" ]; then
61 | printHelp
62 | elif [ "$1" = "--help" ]; then
63 | printHelp
64 | else
65 | echo "No command specified"
66 | echo "Basic commands are: up, down, start, stop, reset"
67 | echo "To list channel query helper commands type: 'fablo channel --help'"
68 | echo "Also check: 'chaincode install'"
69 | echo "Use 'help' or '--help' for more information"
70 | fi
71 |
--------------------------------------------------------------------------------
/src/setup-k8s/templates/fabric-k8s/.env:
--------------------------------------------------------------------------------
1 | NAMESPACE=default
2 |
3 | REPOSITORY="https://kfsoftware.github.io/hlf-helm-charts"
4 | STORAGE_CLASS=$(kubectl describe sc | grep Name | tr -s ' ' | cut -d ':' -f 2 | cut -d ' ' -f 2)
5 |
6 | FABLO_VERSION=<%= fabloVersion %>
7 | FABLO_BUILD=<%= fabloBuild %>
8 | FABLO_CONFIG=<%= paths.fabloConfig %>
9 | ORDERER_IMAGE=hyperledger/fabric-orderer
10 | ORDERER_VERSION=<%= global.fabricVersion %>
11 | PEER_IMAGE=quay.io/kfsoftware/fabric-peer
12 | PEER_VERSION=2.4.1-v0.0.3
13 | # PEER_VERSION=<%= global.fabricVersion %>
14 | CA_IMAGE=hyperledger/fabric-ca
15 | CA_VERSION=<%= global.fabricCaVersion %>
16 | LOGGING_LEVEL=<%= global.monitoring.loglevel %>
17 |
18 | CHAINCODES_BASE_DIR=<%= paths.chaincodesBaseDir %>
19 |
20 | ROOT_CA_ADMIN_NAME=admin
21 | ROOT_CA_ADMIN_PASSWORD=adminpw
22 |
23 | <% orgs.forEach(function(org){ %>
24 | <%= org.ca.caAdminNameVar %>=admin
25 | <%= org.ca.caAdminPassVar %>=adminpw
26 | <% }); %>
--------------------------------------------------------------------------------
/src/setup-k8s/templates/fabric-k8s/scripts/base-help.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | printHelp() {
4 | echo "Fablo is powered by SoftwareMill"
5 |
6 | echo ""
7 | echo "usage: ./fabric-k8.sh "
8 | echo ""
9 |
10 | echo "Commands: "
11 | echo ""
12 | echo "./fabric-k8.sh up"
13 | echo -e "\t Use for first run. Creates all needed artifacts (certs, genesis block) and starts network for the first time."
14 | echo -e "\t After 'up' commands start/stop are used to manage network and rerun to rerun it"
15 | echo ""
16 | echo "./fabric-k8.sh down"
17 | echo -e "\t Back to empty state - destorys created containers, prunes generated certificates, configs."
18 | echo ""
19 | echo "./fabric-k8.sh channel --help"
20 | echo -e "\t Detailed help for channel management scripts."
21 | echo ""
22 | }
23 |
--------------------------------------------------------------------------------
/src/setup-k8s/templates/fabric-k8s/scripts/chaincode-functions-v2.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | buildAndInstallChaincode() {
4 | local CHAINCODE_NAME="$1"
5 | local PEER="$2"
6 | local CHAINCODE_LANG="$3"
7 | local CHAINCODE_DIR_PATH="$4"
8 | local CHAINCODE_VERSION="$5"
9 | local CHAINCODE_LABEL="${CHAINCODE_NAME}_$CHAINCODE_VERSION"
10 | local USER="$6"
11 | local CONFIG="$7"
12 |
13 | if [ -z "$CHAINCODE_NAME" ]; then
14 | echo "Error: chaincode name is not provided"
15 | exit 1
16 | fi
17 |
18 | if [ -z "$PEER" ]; then
19 | echo "Error: peer number is not provided e.g 0, 1"
20 | exit 1
21 | fi
22 |
23 | if [ -z "$CHAINCODE_LANG" ]; then
24 | echo "Error: chaincode language is not provided"
25 | exit 1
26 | fi
27 |
28 | if [ -z "$CHAINCODE_VERSION" ]; then
29 | echo "Error: chaincode version is not provided e.g 1.0, 2.0"
30 | exit 1
31 | fi
32 |
33 | kubectl hlf chaincode install --path="$CHAINCODE_DIR_PATH" \
34 | --config="$CONFIG" --language="$CHAINCODE_LANG" --label="$CHAINCODE_LABEL" --user="$USER" --peer="$PEER"
35 | }
36 |
37 | approveChaincode() {
38 | local CHAINCODE_NAME="$1"
39 | local PEER="$2"
40 | local CHAINCODE_VERSION="$3"
41 | local CHANNEL_NAME="$4"
42 | local USER="$5"
43 | local CONFIG="$6"
44 | local MSP="$7"
45 | local SEQUENCE
46 |
47 | SEQUENCE="$(kubectl hlf chaincode querycommitted --channel="$CHANNEL_NAME" --config="$CONFIG" --user="$USER" --peer="$PEER" 2>/dev/null | awk '{print $3}' | sed -n '2p' )"
48 | SEQUENCE=$((SEQUENCE +1))
49 |
50 | if [ -z "$CHAINCODE_NAME" ]; then
51 | echo "Error: chaincode name is not provided"
52 | exit 1
53 | fi
54 |
55 | if [ -z "$PEER" ]; then
56 | echo "Error: peer number is not provided e.g 0, 1"
57 | exit 1
58 | fi
59 |
60 | if [ -z "$CHAINCODE_VERSION" ]; then
61 | echo "Error: chaincode version is not provided e.g 1.0, 2.0"
62 | exit 1
63 | fi
64 |
65 | if [ -z "$CHANNEL_NAME" ]; then
66 | echo "Error: channel name is not provided"
67 | exit 1
68 | fi
69 |
70 | PACKAGE_ID="$(kubectl hlf chaincode queryinstalled --config="$CONFIG" --user="$USER" --peer="$PEER" | awk '{print $1}' | grep chaincode)"
71 |
72 | printItalics "Approving chaincode $CHAINCODE_NAME on $PEER" "U1F618"
73 |
74 | kubectl hlf chaincode approveformyorg --config="$CONFIG" --user="$USER" --peer="$PEER" \
75 | --package-id="$PACKAGE_ID" --version "$CHAINCODE_VERSION" --sequence "$SEQUENCE" --name="$CHAINCODE_NAME" \
76 | --policy="OR('$MSP.member')" --channel="$CHANNEL_NAME"
77 | }
78 |
79 | commitChaincode() {
80 |
81 | local CHAINCODE_NAME="$1"
82 | local PEER=$2
83 | local CHAINCODE_VERSION="$3"
84 | local CHANNEL_NAME="$4"
85 | local USER="$5"
86 | local CONFIG="$6"
87 | local MSP="$7"
88 | local SEQUENCE
89 |
90 | SEQUENCE="$(kubectl hlf chaincode querycommitted --channel="$CHANNEL_NAME" --config="$CONFIG" --user="$USER" --peer="$PEER" 2>/dev/null | awk '{print $3}' | sed -n '2p' )"
91 | SEQUENCE=$((SEQUENCE +1))
92 |
93 | if [ -z "$CHAINCODE_NAME" ]; then
94 | echo "Error: chaincode name is not provided"
95 | exit 1
96 | fi
97 |
98 | if [ -z "$PEER" ]; then
99 | echo "Error: peer number is not provided e.g 0, 1"
100 | exit 1
101 | fi
102 |
103 | if [ -z "$CHAINCODE_VERSION" ]; then
104 | echo "Error: chaincode version is not provided e.g 1.0, 2.0"
105 | exit 1
106 | fi
107 |
108 | if [ -z "$CHANNEL_NAME" ]; then
109 | echo "Error: channel name is not provided"
110 | exit 1
111 | fi
112 |
113 | PACKAGE_ID="$(kubectl hlf chaincode queryinstalled --config="$CONFIG" --user="$USER" --peer="$PEER" | awk '{print $1}' | grep chaincode)"
114 |
115 | kubectl hlf chaincode commit --config="$CONFIG" --user="$USER" --mspid="$MSP" \
116 | --version "$CHAINCODE_VERSION" --sequence "$SEQUENCE" --name="$CHAINCODE_NAME" \
117 | --policy="OR('$MSP.member')" --channel="$CHANNEL_NAME"
118 | }
119 |
--------------------------------------------------------------------------------
/src/setup-k8s/templates/fabric-k8s/scripts/chaincode-functions.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | buildAndInstallChaincode() {
4 | local CHAINCODE_NAME="$1"
5 | local PEER="$2"
6 | local CHAINCODE_LANG="$3"
7 | local CHAINCODE_DIR_PATH="$4"
8 | local CHAINCODE_VERSION="$5"
9 | local CHAINCODE_LABEL="${CHAINCODE_NAME}_$CHAINCODE_VERSION"
10 | local USER="$6"
11 | local CONFIG="$7"
12 |
13 | if [ -z "$CHAINCODE_NAME" ]; then
14 | echo "Error: chaincode name is not provided"
15 | exit 1
16 | fi
17 |
18 | if [ -z "$PEER" ]; then
19 | echo "Error: peer number is not provided e.g 0, 1"
20 | exit 1
21 | fi
22 |
23 | if [ -z "$CHAINCODE_LANG" ]; then
24 | echo "Error: chaincode language is not provided"
25 | exit 1
26 | fi
27 |
28 | if [ -z "$CHAINCODE_VERSION" ]; then
29 | echo "Error: chaincode version is not provided e.g 1.0, 2.0"
30 | exit 1
31 | fi
32 |
33 | kubectl hlf chaincode install --path="$CHAINCODE_DIR_PATH" \
34 | --config="$CONFIG" --language="$CHAINCODE_LANG" --label="$CHAINCODE_LABEL" --user="$USER" --peer="$PEER"
35 | }
36 |
37 | approveChaincode() {
38 | local CHAINCODE_NAME="$1"
39 | local PEER="$2"
40 | local CHAINCODE_VERSION="$3"
41 | local CHANNEL_NAME="$4"
42 | local USER="$5"
43 | local CONFIG="$6"
44 | local MSP="$7"
45 | local SEQUENCE
46 |
47 | SEQUENCE="$(kubectl hlf chaincode querycommitted --channel="$CHANNEL_NAME" --config="$CONFIG" --user="$USER" --peer="$PEER" | awk '{print $3}' | sed -n '2p' )"
48 | SEQUENCE=$((SEQUENCE +1))
49 |
50 | if [ -z "$CHAINCODE_NAME" ]; then
51 | echo "Error: chaincode name is not provided"
52 | exit 1
53 | fi
54 |
55 | if [ -z "$PEER" ]; then
56 | echo "Error: peer number is not provided e.g 0, 1"
57 | exit 1
58 | fi
59 |
60 | if [ -z "$CHAINCODE_VERSION" ]; then
61 | echo "Error: chaincode version is not provided e.g 1.0, 2.0"
62 | exit 1
63 | fi
64 |
65 | if [ -z "$CHANNEL_NAME" ]; then
66 | echo "Error: channel name is not provided"
67 | exit 1
68 | fi
69 |
70 | PACKAGE_ID="$(kubectl hlf chaincode queryinstalled --config="$CONFIG" --user="$USER" --peer="$PEER.$NAMESPACE" | awk '{print $1}' | grep chaincode)"
71 |
72 | printItalics "Approving chaincode $CHAINCODE_NAME on $PEER" "U1F618"
73 |
74 | kubectl hlf chaincode approveformyorg --config="$CONFIG" --user="$USER" --peer="$PEER" \
75 | --package-id="$PACKAGE_ID" --version "$CHAINCODE_VERSION" --sequence "$SEQUENCE" --name="$CHAINCODE_NAME" \
76 | --policy="OR('$MSP.member')" --channel="$CHANNEL_NAME"
77 | }
78 |
79 | commitChaincode() {
80 |
81 | local CHAINCODE_NAME="$1"
82 | local PEER=$2
83 | local CHAINCODE_VERSION="$3"
84 | local CHANNEL_NAME="$4"
85 | local USER="$5"
86 | local CONFIG="$6"
87 | local MSP="$7"
88 | local SEQUENCE
89 |
90 | SEQUENCE="$(kubectl hlf chaincode querycommitted --channel="$CHANNEL_NAME" --config="$CONFIG" --user="$USER" --peer="$PEER" | awk '{print $3}' | sed -n '2p' )"
91 | SEQUENCE=$((SEQUENCE +1))
92 |
93 | if [ -z "$CHAINCODE_NAME" ]; then
94 | echo "Error: chaincode name is not provided"
95 | exit 1
96 | fi
97 |
98 | if [ -z "$PEER" ]; then
99 | echo "Error: peer number is not provided e.g 0, 1"
100 | exit 1
101 | fi
102 |
103 | if [ -z "$CHAINCODE_VERSION" ]; then
104 | echo "Error: chaincode version is not provided e.g 1.0, 2.0"
105 | exit 1
106 | fi
107 |
108 | if [ -z "$CHANNEL_NAME" ]; then
109 | echo "Error: channel name is not provided"
110 | exit 1
111 | fi
112 |
113 | PACKAGE_ID="$(kubectl hlf chaincode queryinstalled --config="$CONFIG" --user="$USER" --peer="$PEER.$NAMESPACE" | awk '{print $1}' | grep chaincode)"
114 |
115 | kubectl hlf chaincode commit --config="$CONFIG" --user="$USER" --mspid="$MSP" \
116 | --version "$CHAINCODE_VERSION" --sequence "$SEQUENCE" --name="$CHAINCODE_NAME" \
117 | --policy="OR('$MSP.member')" --channel="$CHANNEL_NAME"
118 | }
119 |
--------------------------------------------------------------------------------
/src/setup-k8s/templates/fabric-k8s/scripts/util.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/bash
2 |
3 | retry() {
4 | local n=0
5 | local try=$1
6 | local cmd="${*:2}"
7 | [[ $# -le 1 ]]
8 |
9 | until [[ $n -ge $try ]]; do
10 | if [[ $cmd ]]; then
11 | break
12 | else
13 | echo $(printf '\033[31m') "Previous command FAILED"
14 | ((n++))
15 | echo $(printf '\033[34m') "RETRYING..."
16 | sleep 1
17 | fi
18 | done
19 | }
20 | retry "$*"
21 |
--------------------------------------------------------------------------------
/src/setup-k8s/templates/hooks/post-generate.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # The code from this file was called after Fablo generated Hyperledger Fabric configuration
4 | echo "Executing post-generate hook"
5 |
6 | <%- hooks.postGenerate %>
7 |
--------------------------------------------------------------------------------
/src/setup-network/index.ts:
--------------------------------------------------------------------------------
1 | import * as Generator from "yeoman-generator";
2 | import parseFabloConfig from "../utils/parseFabloConfig";
3 |
4 | const DockerGeneratorPath = require.resolve("../setup-docker");
5 | const KubernetesGeneratorPath = require.resolve("../setup-k8s");
6 |
7 | export default class SetupDockerGenerator extends Generator {
8 | constructor(args: string[], opts: Generator.GeneratorOptions) {
9 | super(args, opts);
10 | this.argument("fabloConfig", {
11 | type: String,
12 | optional: true,
13 | description: "Fablo config file path",
14 | default: "../../network/fablo-config.json",
15 | });
16 | }
17 |
18 | redirectToProperGenerator(): void {
19 | const fabloConfigPath = `${this.env.cwd}/${this.options.fabloConfig}`;
20 | const json = parseFabloConfig(this.fs.read(fabloConfigPath));
21 |
22 | if (json?.global?.engine === "kubernetes") {
23 | this.composeWith(KubernetesGeneratorPath);
24 | } else {
25 | this.composeWith(DockerGeneratorPath);
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/types/ExplorerConfig.ts:
--------------------------------------------------------------------------------
1 | import { OrgConfig } from "./FabloConfigExtended";
2 |
3 | interface ExplorerConfig {
4 | "network-configs": { [name: string]: NetworkConfig };
5 | license: string;
6 | }
7 |
8 | interface NetworkConfig {
9 | name: string;
10 | profile: string;
11 | }
12 |
13 | export function createExplorerConfig(orgs: OrgConfig[]): ExplorerConfig {
14 | const networkConfigs: { [name: string]: NetworkConfig } = {};
15 | orgs.forEach((o) => {
16 | const orgName = o.name.toLowerCase();
17 | networkConfigs[`network-${orgName}`] = {
18 | name: `Network of ${o.name}`,
19 | profile: `/opt/explorer/app/platform/fabric/connection-profile/connection-profile-${orgName}.json`,
20 | };
21 | });
22 | return {
23 | "network-configs": networkConfigs,
24 | license: "Apache-2.0",
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/src/types/FabloConfigExtended.ts:
--------------------------------------------------------------------------------
1 | export interface FabricVersions {
2 | fabricVersion: string;
3 | fabricToolsVersion: string;
4 | fabricCaVersion: string;
5 | fabricCcenvVersion: string;
6 | fabricBaseosVersion: string;
7 | fabricJavaenvVersion: string;
8 | fabricNodeenvVersion: string;
9 | fabricRecommendedNodeVersion: string;
10 | }
11 |
12 | interface CapabilitiesV2 {
13 | application: "V2_0";
14 | channel: "V2_0";
15 | orderer: "V2_0";
16 | isV2: true;
17 | isV3: false;
18 | }
19 |
20 | interface CapabilitiesV_2_5 {
21 | application: "V2_5";
22 | channel: "V2_0";
23 | orderer: "V2_0";
24 | isV2: true;
25 | isV3: false;
26 | }
27 |
28 | interface CapabilitiesV3_0 {
29 | application: "V2_5";
30 | channel: "V3_0";
31 | orderer: "V2_0";
32 | isV2: false;
33 | isV3: true;
34 | }
35 |
36 | export type Capabilities = CapabilitiesV2 | CapabilitiesV_2_5 | CapabilitiesV3_0;
37 |
38 | export interface Global extends FabricVersions {
39 | tls: boolean;
40 | engine: "kubernetes" | "docker";
41 | monitoring: { loglevel: string };
42 | paths: { fabloConfig: string; chaincodesBaseDir: string };
43 | capabilities: Capabilities;
44 | tools: { explorer?: ExplorerConfig };
45 | }
46 |
47 | export interface OrdererConfig {
48 | name: string;
49 | domain: string;
50 | address: string;
51 | adminPort: number;
52 | port: number;
53 | fullAddress: string;
54 | orgName: string;
55 | orgMspName: string;
56 | }
57 |
58 | export interface CAConfig {
59 | address: string;
60 | caAdminNameVar: string;
61 | caAdminPassVar: string;
62 | exposePort: number;
63 | fullAddress: string;
64 | port: number;
65 | prefix: string;
66 | db: "sqlite" | "postgres";
67 | }
68 |
69 | export interface PeerConfig {
70 | address: string;
71 | couchDbExposePort: number;
72 | db: PeerDbConfig;
73 | fullAddress: string;
74 | isAnchorPeer: boolean;
75 | name: string;
76 | port: number;
77 | gatewayEnabled: boolean;
78 | }
79 |
80 | export interface PeerDbConfig {
81 | type: "LevelDb" | "CouchDb";
82 | image?: string;
83 | }
84 |
85 | export interface CLIConfig {
86 | address: string;
87 | }
88 |
89 | export interface ChannelConfig {
90 | name: string;
91 | profileName: string;
92 | ordererGroup: OrdererGroup;
93 | ordererHead: OrdererConfig;
94 | orgs: OrgConfig[];
95 | instantiatingOrg: OrgConfig;
96 | }
97 |
98 | export interface PrivateCollectionConfig {
99 | name: string;
100 | policy: string;
101 | requiredPeerCount: number;
102 | maxPeerCount: number;
103 | blockToLive: number;
104 | memberOnlyRead?: boolean;
105 | memberOnlyWrite?: boolean;
106 | }
107 |
108 | export interface FabloRestLoggingConfig {
109 | info?: "console" | string;
110 | warn?: "console" | string;
111 | error?: "console" | string;
112 | debug?: "console" | string;
113 | }
114 |
115 | export interface FabloRestConfig {
116 | address: string;
117 | port: number;
118 | mspId: string;
119 | fabricCaUrl: string;
120 | fabricCaName: string;
121 | discoveryUrls: string;
122 | discoverySslTargetNameOverrides: string;
123 | discoveryTlsCaCertFiles: string;
124 | logging: FabloRestLoggingConfig;
125 | }
126 |
127 | export interface ExplorerConfig {
128 | address: string;
129 | port: number;
130 | }
131 |
132 | export interface OrgConfig {
133 | anchorPeers: PeerConfig[];
134 | bootstrapPeers: string;
135 | ca: CAConfig;
136 | cli: CLIConfig;
137 | cryptoConfigFileName: string;
138 | domain: string;
139 | headPeer?: PeerConfig;
140 | mspName: string;
141 | name: string;
142 | peers: PeerConfig[];
143 | peersCount: number;
144 | ordererGroups: OrdererGroup[];
145 | tools: { fabloRest?: FabloRestConfig; explorer?: ExplorerConfig };
146 | }
147 |
148 | export interface ChaincodeConfig {
149 | directory: string;
150 | name: string;
151 | version: string;
152 | lang: string;
153 | channel: ChannelConfig;
154 | init?: string;
155 | initRequired?: boolean;
156 | endorsement?: string;
157 | instantiatingOrg: OrgConfig;
158 | privateDataConfigFile?: string;
159 | privateData: PrivateCollectionConfig[];
160 | }
161 |
162 | export interface OrdererGroup {
163 | name: string;
164 | consensus: "solo" | "etcdraft" | "BFT";
165 | profileName: string;
166 | genesisBlockName: string;
167 | configtxOrdererDefaults: string;
168 | hostingOrgs: string[];
169 | orderers: OrdererConfig[];
170 | ordererHeads: OrdererConfig[];
171 | }
172 |
173 | export interface HooksConfig {
174 | postGenerate: string;
175 | }
176 |
177 | export interface FabloConfigExtended {
178 | global: Global;
179 | ordererGroups: OrdererGroup[];
180 | orderedHeadsDistinct: OrdererConfig[];
181 | orgs: OrgConfig[];
182 | channels: ChannelConfig[];
183 | chaincodes: ChaincodeConfig[];
184 | hooks: HooksConfig;
185 | }
186 |
--------------------------------------------------------------------------------
/src/types/FabloConfigJson.ts:
--------------------------------------------------------------------------------
1 | export interface GlobalJson {
2 | fabricVersion: string;
3 | tls: boolean;
4 | peerDevMode: boolean;
5 | engine?: "kubernetes" | "docker";
6 | monitoring?: { loglevel: string };
7 | tools?: { explorer?: boolean };
8 | }
9 |
10 | export interface OrganizationDetailsJson {
11 | name: string;
12 | mspName: string;
13 | domain: string;
14 | }
15 |
16 | export interface CAJson {
17 | prefix: string;
18 | db: "sqlite" | "postgres";
19 | }
20 |
21 | export interface OrdererJson {
22 | groupName: string;
23 | prefix: string;
24 | type: "solo" | "raft" | "BFT";
25 | instances: number;
26 | }
27 |
28 | export interface PeerJson {
29 | prefix: string;
30 | instances: number;
31 | db: "LevelDb" | "CouchDb";
32 | anchorPeerInstances?: number;
33 | }
34 |
35 | export interface OrgJson {
36 | organization: OrganizationDetailsJson;
37 | ca: CAJson;
38 | orderers: OrdererJson[] | undefined;
39 | peer?: PeerJson;
40 | tools?: { fabloRest?: boolean; explorer?: boolean };
41 | }
42 |
43 | export interface ChannelJson {
44 | name: string;
45 | ordererGroup?: string;
46 | orgs: { name: string; peers: string[] }[];
47 | }
48 |
49 | export interface PrivateDataJson {
50 | name: string;
51 | orgNames: string[];
52 | }
53 |
54 | export interface ChaincodeJson {
55 | name: string;
56 | version: string;
57 | lang: "node" | "java" | "golang";
58 | channel: string;
59 | init?: string;
60 | initRequired?: boolean;
61 | endorsement?: string;
62 | directory: string;
63 | privateData: PrivateDataJson[];
64 | }
65 |
66 | export interface HooksJson {
67 | postGenerate?: string;
68 | }
69 |
70 | export interface FabloConfigJson {
71 | $schema: string;
72 | global: GlobalJson;
73 | orgs: OrgJson[];
74 | channels: ChannelJson[];
75 | chaincodes: ChaincodeJson[];
76 | hooks: HooksJson;
77 | }
78 |
--------------------------------------------------------------------------------
/src/utils/parseFabloConfig.test.ts:
--------------------------------------------------------------------------------
1 | import parseFabloConfig from "./parseFabloConfig";
2 |
3 | describe("parseFabloConfig", () => {
4 | it("should parse valid JSON config", () => {
5 | const jsonConfig = `{
6 | "global": {
7 | "fabricVersion": "2.5.9",
8 | "tls": false,
9 | "engine": "docker"
10 | },
11 | "orgs": [
12 | {
13 | "organization": {
14 | "name": "Org1",
15 | "domain": "org1.example.com"
16 | }
17 | }
18 | ]
19 | }`;
20 |
21 | const result = parseFabloConfig(jsonConfig);
22 |
23 | expect(result).toEqual({
24 | global: {
25 | fabricVersion: "2.5.9",
26 | tls: false,
27 | engine: "docker"
28 | },
29 | orgs: [
30 | {
31 | organization: {
32 | name: "Org1",
33 | domain: "org1.example.com"
34 | }
35 | }
36 | ]
37 | });
38 | });
39 |
40 | it("should parse valid YAML config", () => {
41 | const yamlConfig = `
42 | global:
43 | fabricVersion: "2.5.9"
44 | tls: false
45 | engine: docker
46 | orgs:
47 | - organization:
48 | name: Org1
49 | domain: org1.example.com
50 | `;
51 |
52 | const result = parseFabloConfig(yamlConfig);
53 |
54 | expect(result).toEqual({
55 | global: {
56 | fabricVersion: "2.5.9",
57 | tls: false,
58 | engine: "docker"
59 | },
60 | orgs: [
61 | {
62 | organization: {
63 | name: "Org1",
64 | domain: "org1.example.com"
65 | }
66 | }
67 | ]
68 | });
69 | });
70 |
71 | it("should parse complex YAML config with multiple orgs and channels", () => {
72 | const yamlConfig = `
73 | global:
74 | fabricVersion: "2.4.7"
75 | tls: true
76 | engine: kubernetes
77 | orgs:
78 | - organization:
79 | name: Org1
80 | domain: org1.example.com
81 | peers:
82 | - name: peer0
83 | port: 7041
84 | - name: peer1
85 | port: 7042
86 | - organization:
87 | name: Org2
88 | domain: org2.example.com
89 | peers:
90 | - name: peer0
91 | port: 8041
92 | channels:
93 | - name: mychannel
94 | orgs: ["Org1", "Org2"]
95 | `;
96 |
97 | const result = parseFabloConfig(yamlConfig);
98 |
99 | expect(result).toEqual({
100 | global: {
101 | fabricVersion: "2.4.7",
102 | tls: true,
103 | engine: "kubernetes"
104 | },
105 | orgs: [
106 | {
107 | organization: {
108 | name: "Org1",
109 | domain: "org1.example.com",
110 | peers: [
111 | {
112 | name: "peer0",
113 | port: 7041
114 | },
115 | {
116 | name: "peer1",
117 | port: 7042
118 | }
119 | ]
120 | }
121 | },
122 | {
123 | organization: {
124 | name: "Org2",
125 | domain: "org2.example.com",
126 | peers: [
127 | {
128 | name: "peer0",
129 | port: 8041
130 | }
131 | ]
132 | }
133 | }
134 | ],
135 | channels: [
136 | {
137 | name: "mychannel",
138 | orgs: ["Org1", "Org2"]
139 | }
140 | ]
141 | });
142 | });
143 |
144 | it("should parse empty config", () => {
145 | const emptyConfig = `{}`;
146 | const result = parseFabloConfig(emptyConfig);
147 | expect(result).toEqual({});
148 | });
149 | });
150 |
--------------------------------------------------------------------------------
/src/utils/parseFabloConfig.ts:
--------------------------------------------------------------------------------
1 | import * as yaml from "js-yaml";
2 | import { FabloConfigJson } from "../types/FabloConfigJson";
3 |
4 | const parseFabloConfig = (str: string): FabloConfigJson => {
5 | try {
6 | return JSON.parse(str);
7 | } catch (e) {
8 | try {
9 | const yamlContent = yaml.load(str);
10 | return JSON.parse(JSON.stringify(yamlContent));
11 | } catch (e2) {
12 | throw new Error("Cannot parse file neither as JSON nor YAML file.");
13 | }
14 | }
15 | };
16 |
17 | export default parseFabloConfig;
18 |
--------------------------------------------------------------------------------
/src/version/buildUtil.ts:
--------------------------------------------------------------------------------
1 | import * as config from "../config";
2 |
3 | // eslint-disable-next-line @typescript-eslint/no-var-requires
4 | const getBuildInfo = (): Record => require("/fablo/version.json").buildInfo;
5 |
6 | const basicInfo = (): Record => {
7 | return {
8 | version: config.fabloVersion,
9 | build: getBuildInfo(),
10 | };
11 | };
12 |
13 | const fullInfo = (): Record => {
14 | return {
15 | version: config.fabloVersion,
16 | build: getBuildInfo(),
17 | supported: {
18 | fabloVersions: `${config.supportedVersionPrefix}x`,
19 | },
20 | };
21 | };
22 |
23 | export { getBuildInfo, basicInfo, fullInfo };
24 |
--------------------------------------------------------------------------------
/src/version/index.ts:
--------------------------------------------------------------------------------
1 | import * as Generator from "yeoman-generator";
2 | import { basicInfo, fullInfo } from "./buildUtil";
3 |
4 | export default class VersionGenerator extends Generator {
5 | constructor(args: string[], opts: Generator.GeneratorOptions) {
6 | super(args, opts);
7 | this.option("verbose", {
8 | type: Boolean,
9 | alias: "v",
10 | });
11 | }
12 |
13 | async printVersion(): Promise {
14 | if (typeof this.options.verbose !== "undefined") {
15 | console.log(JSON.stringify(fullInfo(), null, 2));
16 | } else {
17 | console.log(JSON.stringify(basicInfo(), null, 2));
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tsconfig-dist.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "noEmit": false,
6 | "rootDir": "./src",
7 | "outDir": "./generators"
8 | },
9 | "include": [
10 | "src"
11 | ],
12 | "exclude": [
13 | "node_modules",
14 | "generators",
15 | "src/*/templates",
16 | "**/*.test.ts",
17 | "**/*.spec.ts",
18 | "e2e"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "compilerOptions": {
4 | "noEmit": true,
5 | "rootDir": "./",
6 | "target": "es2019",
7 | "module": "commonjs",
8 | "sourceMap": true,
9 | "strict": true,
10 | "allowJs": true,
11 | "declaration": true,
12 | "declarationMap": true,
13 | "noUnusedLocals": true,
14 | "noUnusedParameters": true,
15 | "resolveJsonModule": true,
16 | "skipLibCheck": true
17 | },
18 | "include": [
19 | "src",
20 | "e2e"
21 | ],
22 | "exclude": [
23 | "node_modules",
24 | "generators"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------