From 1e21dbd35eb1731e49e264eef5b11d81fd35903f Mon Sep 17 00:00:00 2001 From: Pereowei Daniel Date: Fri, 17 Jul 2026 18:39:48 +0200 Subject: [PATCH 1/5] Add FabricOps-backed Kubernetes engine Signed-off-by: Pereowei Daniel --- CHANGELOG.md | 14 +- README.md | 32 +- ...hlf2-1org-1chaincode-k8s.json.test.ts.snap | 830 ++++++++++-------- fablo.sh | 1 + ...fablo-config-hlf2-1org-1chaincode-k8s.json | 8 +- src/setup-k8s/fabricOpsManifest.ts | 195 ++++ src/setup-k8s/index.ts | 88 +- src/setup-k8s/templates/fabric-k8s.sh | 507 +++++++++-- src/setup-k8s/templates/fabric-k8s/.env | 26 - .../fabric-k8s/scripts/base-functions.sh | 194 ---- .../templates/fabric-k8s/scripts/base-help.sh | 22 - .../scripts/chaincode-functions-v2.sh | 123 --- .../fabric-k8s/scripts/chaincode-functions.sh | 118 --- .../templates/fabric-k8s/scripts/util.sh | 20 - 14 files changed, 1154 insertions(+), 1024 deletions(-) create mode 100644 src/setup-k8s/fabricOpsManifest.ts delete mode 100755 src/setup-k8s/templates/fabric-k8s/.env delete mode 100644 src/setup-k8s/templates/fabric-k8s/scripts/base-functions.sh delete mode 100644 src/setup-k8s/templates/fabric-k8s/scripts/base-help.sh delete mode 100644 src/setup-k8s/templates/fabric-k8s/scripts/chaincode-functions-v2.sh delete mode 100644 src/setup-k8s/templates/fabric-k8s/scripts/chaincode-functions.sh delete mode 100644 src/setup-k8s/templates/fabric-k8s/scripts/util.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 361c17efc..2ff1a714c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## Unreleased + +### Features +* Add experimental Kubernetes engine support backed by FabricOps. +* Route Kubernetes `fablo chaincode invoke` and `fablo chaincode query` through `fabricopsctl`. + +### Documentation +* Document that FabricOps-backed Kubernetes networks require `global.tls=true` when channels are declared. Docker-based local development networks can still use non-TLS configurations. + +### Notes +* The Kubernetes engine targets FabricOps `v0.1.1` or newer for channel participation, peer/orderer readiness, and CCaaS lifecycle fixes. +* Chaincode invoke/query support requires a `fabricopsctl` build with raw Fabric payload support. + ## 2.6.0 ### Features @@ -282,4 +295,3 @@ * Test generators with simple snapshot tests ([#5](https://github.com/hyperledger-labs/fablo/issues/5)) * Lint JS files ([#1](https://github.com/hyperledger-labs/fablo/issues/1)) * Run Yeoman generators inside Docker container - diff --git a/README.md b/README.md index 49f0b8706..05831ef78 100644 --- a/README.md +++ b/README.md @@ -703,7 +703,37 @@ chaincodes: ## Kubernetes support -TODO +Fablo can generate a FabricOps `FabricNetwork` manifest and use FabricOps as the +Kubernetes engine. This path is intended for Kubernetes-backed Fabric networks, +while the default Docker engine remains the local-development path. + +Requirements: + +* A Kubernetes cluster selected in your current `kubectl` context +* FabricOps installed in the target cluster +* `fabricopsctl` installed in `PATH` for `fablo chaincode invoke` and `fablo chaincode query` +* `global.engine` set to `kubernetes` +* CCaaS chaincodes with prebuilt `image` values +* `global.tls` set to `true` when channels are declared + +The TLS requirement is specific to the FabricOps-backed Kubernetes engine. +Docker-based Fablo networks can still use non-TLS development configurations, +but FabricOps uses production-shaped channel participation, admin, lifecycle, +and CCaaS flows that require TLS material once channels are bootstrapped. + +Example: + +```bash +fablo up samples/fablo-config-hlf2-1org-1chaincode-k8s.json +``` + +If FabricOps is not installed, the generated Kubernetes script stops before +applying the manifest and points to the FabricOps installation instructions. +For chaincode operations, install the FabricOps CLI: + +```bash +go install github.com/dpereowei/fabricops/cmd/fabricopsctl@latest +``` ## Other features diff --git a/e2e/__snapshots__/fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts.snap b/e2e/__snapshots__/fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts.snap index b1b23966b..e0fd23052 100644 --- a/e2e/__snapshots__/fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts.snap +++ b/e2e/__snapshots__/fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts.snap @@ -5,7 +5,7 @@ exports[`samples/fablo-config-hlf2-1org-1chaincode-k8s.json should create proper "$schema": "https://github.com/hyperledger-labs/fablo/releases/download/2.6.0/schema.json", "global": { "fabricVersion": "2.4.7", - "tls": false, + "tls": true, "engine": "kubernetes" }, "orgs": [ @@ -17,7 +17,7 @@ exports[`samples/fablo-config-hlf2-1org-1chaincode-k8s.json should create proper "orderers": [ { "groupName": "group1", - "type": "solo", + "type": "raft", "instances": 1 } ] @@ -51,9 +51,9 @@ exports[`samples/fablo-config-hlf2-1org-1chaincode-k8s.json should create proper { "name": "chaincode1", "version": "0.0.1", - "lang": "node", + "lang": "ccaas", "channel": "my-channel1", - "directory": "./chaincodes/chaincode-kv-node" + "image": "ghcr.io/fablo-io/fablo-sample-kv-node-chaincode:2.2.0" } ] } @@ -69,473 +69,529 @@ exports[`samples/fablo-config-hlf2-1org-1chaincode-k8s.json should create proper exports[`samples/fablo-config-hlf2-1org-1chaincode-k8s.json should create proper e2e/__tmp__/samples/fablo-config-hlf2-1org-1chaincode-k8s.json.tmpdir/fablo-target/fabric-k8s.sh from samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = ` "#!/usr/bin/env bash -set -e +set -euo pipefail FABLO_NETWORK_ROOT="$(cd "$(dirname "$0")" && pwd)" +FABRICOPS_MANIFEST="$FABLO_NETWORK_ROOT/fabric-k8s/fabricnetwork.yaml" +FABRICNETWORK_NAME="fablo-network" +NAMESPACE="\${NAMESPACE:-default}" +FABRICOPS_NAMESPACE="\${FABRICOPS_NAMESPACE:-fabricops-system}" +FABRICOPS_WAIT_TIMEOUT="\${FABRICOPS_WAIT_TIMEOUT:-20m}" +FABRICOPS_OPERATION_TIMEOUT="\${FABRICOPS_OPERATION_TIMEOUT:-180s}" +FABRICOPS_INSTALL_URL="https://github.com/dpereowei/FabricOps#installation" + +KUBECTL_ARGS=() +FABRICOPSCTL_ARGS=() +if [ -n "\${FABLO_KUBECONFIG:-}" ]; then + KUBECTL_ARGS+=(--kubeconfig "$FABLO_KUBECONFIG") + FABRICOPSCTL_ARGS+=(--kubeconfig "$FABLO_KUBECONFIG") +fi +if [ -n "\${FABLO_KUBECONTEXT:-}" ]; then + KUBECTL_ARGS+=(--context "$FABLO_KUBECONTEXT") + FABRICOPSCTL_ARGS+=(--context "$FABLO_KUBECONTEXT") +fi -source "$FABLO_NETWORK_ROOT/fabric-k8s/scripts/base-help.sh" -source "$FABLO_NETWORK_ROOT/fabric-k8s/scripts/base-functions.sh" -source "$FABLO_NETWORK_ROOT/fabric-k8s/scripts/chaincode-functions.sh" -source "$FABLO_NETWORK_ROOT/fabric-k8s/.env" - -# location of generated configurations -CONFIG_DIR="$FABLO_NETWORK_ROOT/fabric-config" - -RESETBG="$(printf '\\e[0m\\n')" -BLUE="$(printf '\\033[34m')" - -networkUp() { - printHeadline "Checking dependencies..." "U1F984" - verifyKubernetesConnectivity - printHeadline "Starting Network..." "U1F984" - deployPeer - deployOrderer - installChannels - installChaincodes - printHeadline "Done! Enjoy your fresh network" "U1F984" +printHelp() { + echo "usage: ./fabric-k8s.sh " + echo "" + echo "Commands:" + echo " up Apply the generated FabricOps FabricNetwork and wait until it is ready" + echo " start Re-apply the generated FabricOps FabricNetwork and wait until it is ready" + echo " down Delete the generated FabricOps FabricNetwork" + echo " reset Delete, then re-apply the generated FabricOps FabricNetwork" + echo " status Show the generated FabricOps FabricNetwork status" + echo " chaincode invoke [transient]" + echo " Invoke chaincode through FabricOps/fabricopsctl" + echo " chaincode query [transient]" + echo " Query chaincode through FabricOps/fabricopsctl" + echo " help Print this help" + echo "" + echo "Environment:" + echo " FABLO_KUBECONTEXT Kubeconfig context to target. Defaults to the current kubectl context." + echo " FABLO_KUBECONFIG Kubeconfig path. Defaults to kubectl/fabricopsctl defaults." + echo " NAMESPACE FabricNetwork namespace. Defaults to default." + echo " FABRICOPS_NAMESPACE FabricOps controller namespace. Defaults to fabricops-system." + echo " FABRICOPS_WAIT_TIMEOUT Readiness wait timeout. Defaults to 20m." + echo " FABRICOPS_OPERATION_TIMEOUT Chaincode operation timeout. Defaults to 180s." } -networkDown() { - printHeadline "Destroying network" "U1F913" - destroyNetwork +kubectlCmd() { + kubectl "\${KUBECTL_ARGS[@]}" "$@" } -if [ "$1" = "up" ]; then - networkUp -elif [ "$1" = "down" ]; then - networkDown -elif [ "$1" = "reset" ]; then - networkDown - sleep 60 - networkUp -elif [ "$1" = "start" ]; then - startNetwork -elif [ "$1" = "stop" ]; then - stopNetwork -elif [ "$1" = "chaincodes" ] && [ "$2" = "install" ]; then - installChaincodes -elif [ "$1" = "chaincode" ] && [ "$2" = "install" ]; then - installChaincode "$3" "$4" -elif [ "$1" = "chaincode" ] && [ "$2" = "upgrade" ]; then - upgradeChaincode "$3" "$4" -elif [ "$1" = "chaincode" ] && [ "$2" = "dev" ]; then - runDevModeChaincode "$3" "$4" -elif [ "$1" = "channel" ]; then - channelQuery "\${@:2}" -elif [ "$1" = "snapshot" ]; then - createSnapshot "$2" -elif [ "$1" = "clone-to" ]; then - cloneSnapshot "$2" "\${3:-""}" -elif [ "$1" = "help" ]; then - printHelp -elif [ "$1" = "--help" ]; then - printHelp -else - echo "No command specified" - echo "Basic commands are: up, down, start, stop, reset" - echo "To list channel query helper commands type: 'fablo channel --help'" - echo "Also check: 'chaincode install'" - echo "Use 'help' or '--help' for more information" -fi -" -`; - -exports[`samples/fablo-config-hlf2-1org-1chaincode-k8s.json should create proper e2e/__tmp__/samples/fablo-config-hlf2-1org-1chaincode-k8s.json.tmpdir/fablo-target/fabric-k8s/.env from samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = ` -"NAMESPACE=default - -REPOSITORY="https://kfsoftware.github.io/hlf-helm-charts" -STORAGE_CLASS=$(kubectl describe sc | grep Name | tr -s ' ' | cut -d ':' -f 2 | cut -d ' ' -f 2) - -FABLO_VERSION=2.6.0 -FABLO_BUILD= -FABLO_CONFIG= -ORDERER_IMAGE=hyperledger/fabric-orderer -ORDERER_VERSION=2.4.7 -PEER_IMAGE=quay.io/kfsoftware/fabric-peer -PEER_VERSION=2.4.1-v0.0.3 -# PEER_VERSION=2.4.7 -CA_IMAGE=hyperledger/fabric-ca -CA_VERSION=1.5.16 -LOGGING_LEVEL=info - -CHAINCODES_BASE_DIR= - -ROOT_CA_ADMIN_NAME=admin -ROOT_CA_ADMIN_PASSWORD=adminpw - - -ORDERER_CA_ADMIN_NAME=admin -ORDERER_CA_ADMIN_PASSWORD=adminpw - -ORG1_CA_ADMIN_NAME=admin -ORG1_CA_ADMIN_PASSWORD=adminpw -" -`; - -exports[`samples/fablo-config-hlf2-1org-1chaincode-k8s.json should create proper e2e/__tmp__/samples/fablo-config-hlf2-1org-1chaincode-k8s.json.tmpdir/fablo-target/fabric-k8s/scripts/base-functions.sh from samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = ` -"#!/usr/bin/env bash - -source "$FABLO_NETWORK_ROOT/fabric-k8s/scripts/util.sh" - -deployPeer() { - - printItalics "Deploying Org1 CA" "U1F984" - kubectl hlf ca create --image="$CA_IMAGE" --version="$CA_VERSION" --storage-class="$STORAGE_CLASS" --capacity=2Gi --name=org1-ca --enroll-id=org1 --enroll-pw="$ORG1_CA_ADMIN_PASSWORD" - sleep 3 - - while [[ $(kubectl get pods -l release=org1-ca -o 'jsonpath={..status.conditions[?(@.type=="Ready")].status}') != "True" ]]; do - sleep 5 - inputLog "waiting for CA" - done - - kubectl hlf ca register --name=org1-ca --user=peer --secret="$ORG1_CA_ADMIN_PASSWORD" --type=peer --enroll-id org1 --enroll-secret="$ORG1_CA_ADMIN_PASSWORD" --mspid Org1MSP - inputLog "registered Org1 -ca" - - printItalics "Deploying Peers" "U1F984" - sleep 10 - - kubectl hlf peer create --statedb=leveldb --version="$PEER_VERSION" --storage-class="$STORAGE_CLASS" --enroll-id=peer --mspid=Org1MSP \\ - --enroll-pw="$ORG1_CA_ADMIN_PASSWORD" --capacity=5Gi --name=peer0 --ca-name="org1-ca.$NAMESPACE" --k8s-builder=true --external-service-builder=false - - kubectl hlf peer create --statedb=leveldb --version="$PEER_VERSION" --storage-class="$STORAGE_CLASS" --enroll-id=peer --mspid=Org1MSP \\ - --enroll-pw="$ORG1_CA_ADMIN_PASSWORD" --capacity=5Gi --name=peer1 --ca-name="org1-ca.$NAMESPACE" --k8s-builder=true --external-service-builder=false - - while [[ $(kubectl get pods -l app=hlf-peer --output=jsonpath='{.items[*].status.containerStatuses[0].ready}') != "true true" ]]; do - sleep 5 - inputLog "waiting for peer nodes to be ready" - done +fabricopsctlCmd() { + local command="$1" + shift + fabricopsctl "$command" "\${FABRICOPSCTL_ARGS[@]}" "$@" } -deployOrderer() { - - printItalics "Deploying Orderers" "U1F984" - - kubectl hlf ca create --storage-class="$STORAGE_CLASS" --capacity=2Gi --name=orderer-ca --enroll-id=orderer --enroll-pw="$ORDERER_CA_ADMIN_PASSWORD" - while [[ $(kubectl get pods -l release=orderer-ca -o 'jsonpath={..status.conditions[?(@.type=="Ready")].status}') != "True" ]]; do - sleep 5 - inputLog "waiting for orderer-ca CA to be ready" "$RESETBG" - done - kubectl hlf ca register --name=orderer-ca --user="$ORDERER_CA_ADMIN_NAME" --secret="$ORDERER_CA_ADMIN_PASSWORD" --type=orderer --enroll-id=orderer --enroll-secret="$ORDERER_CA_ADMIN_PASSWORD" --mspid OrdererMSP && - inputLog "registered orderer-ca" - - kubectl hlf ordnode create --version="$ORDERER_VERSION" \\ - --storage-class="$STORAGE_CLASS" --enroll-id="$ORDERER_CA_ADMIN_NAME" --mspid=OrdererMSP \\ - --enroll-pw="$ORDERER_CA_ADMIN_PASSWORD" --capacity=2Gi --name=orderer-node --ca-name="orderer-ca.$NAMESPACE" - while [[ $(kubectl get pods -l app=hlf-ordnode -o 'jsonpath={..status.conditions[?(@.type=="Ready")].status}') != "True" ]]; do - sleep 5 - inputLog "waiting for orderer Node to be ready" - done - - kubectl hlf inspect --output "$CONFIG_DIR/ordservice.yaml" -o OrdererMSP - kubectl hlf ca enroll --name=orderer-ca --user="$ORDERER_CA_ADMIN_NAME" --secret="$ORDERER_CA_ADMIN_PASSWORD" --mspid OrdererMSP --ca-name ca --output "$CONFIG_DIR/admin-ordservice.yaml" && - kubectl hlf utils adduser --userPath="$CONFIG_DIR/admin-ordservice.yaml" --config="$CONFIG_DIR/ordservice.yaml" --username="$ORDERER_CA_ADMIN_NAME" --mspid=OrdererMSP - +requireFabricOpsctl() { + if ! command -v fabricopsctl >/dev/null 2>&1; then + echo "Error: fabricopsctl is required for Fablo Kubernetes chaincode operations" + echo "Install it with: go install github.com/dpereowei/fabricops/cmd/fabricopsctl@latest" + echo "Then rerun the command." + exit 1 + fi } -installChannels() { - - printItalics "Creating 'my-channel1' on /peer0" "U1F63B" - sleep 10 - kubectl hlf channel generate --output="$CONFIG_DIR/my-channel1.block" --name=my-channel1 --organizations Org1MSP --ordererOrganizations OrdererMSP && - kubectl hlf ca enroll --name=orderer-ca --namespace="$NAMESPACE" --user="$ORG1_CA_ADMIN_NAME" --secret="$ORG1_CA_ADMIN_PASSWORD" --mspid OrdererMSP --ca-name=tlsca --output "$CONFIG_DIR/admin-tls-ordservice.yaml" && - sleep 10 - - kubectl hlf ordnode join --block="$CONFIG_DIR/my-channel1.block" --name=orderer-node --namespace="$NAMESPACE" --identity="$CONFIG_DIR/admin-tls-ordservice.yaml" - - kubectl hlf ca register --name=org1-ca --user="$ORG1_CA_ADMIN_NAME" --secret="$ORG1_CA_ADMIN_PASSWORD" --type=admin --enroll-id org1 --enroll-secret="$ORG1_CA_ADMIN_PASSWORD" --mspid Org1MSP && - kubectl hlf ca enroll --name=org1-ca --user="$ORG1_CA_ADMIN_NAME" --secret="$ORG1_CA_ADMIN_PASSWORD" --mspid Org1MSP --ca-name ca --output "$CONFIG_DIR/peer-Org1.yaml" && - kubectl hlf inspect --output "$CONFIG_DIR/Org1.yaml" -o "Org1MSP" -o "OrdererMSP" && - kubectl hlf utils adduser --userPath="$CONFIG_DIR/peer-Org1.yaml" --config="$CONFIG_DIR/Org1.yaml" --username="$ORG1_CA_ADMIN_NAME" --mspid=Org1MSP && - sleep 10 - - printItalics "Joining 'my-channel1' on org1/peer0" "U1F638" - kubectl hlf channel join --name=my-channel1 --config="$CONFIG_DIR/Org1.yaml" --user="$ORG1_CA_ADMIN_NAME" -p="peer0.$NAMESPACE" - - printItalics "Joining 'my-channel1' on org1/peer1" "U1F638" - kubectl hlf channel join --name=my-channel1 --config="$CONFIG_DIR/Org1.yaml" --user="$ORG1_CA_ADMIN_NAME" -p="peer1.$NAMESPACE" - - # add anchor peers - - printItalics "Electing on org1/peer0 as Anchor peer" "U1F638" - kubectl hlf channel addanchorpeer --channel=my-channel1 --config="$CONFIG_DIR/Org1.yaml" --user="$ORG1_CA_ADMIN_NAME" --peer="peer0.$NAMESPACE" - - printItalics "Electing on org1/peer1 as Anchor peer" "U1F638" - kubectl hlf channel addanchorpeer --channel=my-channel1 --config="$CONFIG_DIR/Org1.yaml" --user="$ORG1_CA_ADMIN_NAME" --peer="peer1.$NAMESPACE" - +chaincodeSubmitterOrg() { + local first_peer="\${1%%,*}" + case "$first_peer" in + "peer0.org1.example.com") + echo "Org1" + return 0 + ;; + "peer1.org1.example.com") + echo "Org1" + return 0 + ;; + *) + echo "Unknown peer: $first_peer" >&2 + return 1 + ;; + esac } -installChaincodes() { - - printItalics "Installing chaincodes...." "U1F618" - - printItalics "Building chaincode chaincode1" "U1F618" - buildAndInstallChaincode "chaincode1" "peer0.$NAMESPACE" "node" "$CHAINCODES_BASE_DIR/chaincodes/chaincode-kv-node" "0.0.1" "$ORG1_CA_ADMIN_NAME" "$CONFIG_DIR/Org1.yaml" - - printItalics "Approving chaincode...." "U1F618" - approveChaincode "chaincode1" "peer0.$NAMESPACE" "0.0.1" "my-channel1" "$ORG1_CA_ADMIN_NAME" "$CONFIG_DIR/Org1.yaml" "Org1MSP" - - printItalics "Committing chaincode 'chaincode1' on channel 'my-channel1' " "U1F618" - - commitChaincode "chaincode1" "peer0.$NAMESPACE" "0.0.1" "my-channel1" "$ORG1_CA_ADMIN_NAME" "$CONFIG_DIR/Org1.yaml" "Org1MSP" - - printItalics "Building chaincode chaincode1" "U1F618" - buildAndInstallChaincode "chaincode1" "peer1.$NAMESPACE" "node" "$CHAINCODES_BASE_DIR/chaincodes/chaincode-kv-node" "0.0.1" "$ORG1_CA_ADMIN_NAME" "$CONFIG_DIR/Org1.yaml" +chaincodePeerRef() { + local peer_domain="$1" + case "$peer_domain" in + "peer0.org1.example.com") + echo "Org1/peer0" + return 0 + ;; + "peer1.org1.example.com") + echo "Org1/peer1" + return 0 + ;; + *) + echo "Unknown peer: $peer_domain" >&2 + return 1 + ;; + esac +} - printItalics "Approving chaincode...." "U1F618" - approveChaincode "chaincode1" "peer1.$NAMESPACE" "0.0.1" "my-channel1" "$ORG1_CA_ADMIN_NAME" "$CONFIG_DIR/Org1.yaml" "Org1MSP" +fabricNetworkPhase() { + kubectlCmd get \\ + -n "$NAMESPACE" \\ + "fabricnetworks.fabricops.io/$FABRICNETWORK_NAME" \\ + -o jsonpath='{.status.phase}' 2>/dev/null || true +} - printItalics "Committing chaincode 'chaincode1' on channel 'my-channel1' " "U1F618" +fabricNetworkGeneration() { + kubectlCmd get \\ + -n "$NAMESPACE" \\ + "fabricnetworks.fabricops.io/$FABRICNETWORK_NAME" \\ + -o jsonpath='{.metadata.generation}' 2>/dev/null || true +} - commitChaincode "chaincode1" "peer1.$NAMESPACE" "0.0.1" "my-channel1" "$ORG1_CA_ADMIN_NAME" "$CONFIG_DIR/Org1.yaml" "Org1MSP" +fabricNetworkReadyCondition() { + kubectlCmd get \\ + -n "$NAMESPACE" \\ + "fabricnetworks.fabricops.io/$FABRICNETWORK_NAME" \\ + -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || true +} +fabricNetworkReadyObservedGeneration() { + kubectlCmd get \\ + -n "$NAMESPACE" \\ + "fabricnetworks.fabricops.io/$FABRICNETWORK_NAME" \\ + -o jsonpath='{.status.conditions[?(@.type=="Ready")].observedGeneration}' 2>/dev/null || true } -destroyNetwork() { - kubectl delete fabricpeers.hlf.kungfusoftware.es --all-namespaces --all - kubectl delete fabriccas.hlf.kungfusoftware.es --all-namespaces --all - kubectl delete fabricorderernodes.hlf.kungfusoftware.es --all-namespaces --all - kubectl delete fabricchaincode.hlf.kungfusoftware.es --all-namespaces --all +fabricNetworkMessage() { + kubectlCmd get \\ + -n "$NAMESPACE" \\ + "fabricnetworks.fabricops.io/$FABRICNETWORK_NAME" \\ + -o jsonpath='{.status.message}' 2>/dev/null || true } -printHeadline() { - bold=$'\\e[1m' - end=$'\\e[0m' +fabricOpsWaitTimeoutSeconds() { + local value="$FABRICOPS_WAIT_TIMEOUT" - TEXT=$1 - EMOJI=$2 - printf "\${bold}============ %b %s %b ==============\${end}\\n" "\\\\$EMOJI" "$TEXT" "\\\\$EMOJI" -} + if [[ "$value" =~ ^([0-9]+)s$ ]]; then + echo "\${BASH_REMATCH[1]}" + return + fi -printItalics() { - italics=$'\\e[3m' - end=$'\\e[0m' + if [[ "$value" =~ ^([0-9]+)m$ ]]; then + echo "$((BASH_REMATCH[1] * 60))" + return + fi + + if [[ "$value" =~ ^([0-9]+)h$ ]]; then + echo "$((BASH_REMATCH[1] * 3600))" + return + fi - TEXT=$1 - EMOJI=$2 - printf "\${italics}==== %b %s %b ====\${end}\\n" "\\\\$EMOJI" "$TEXT" "\\\\$EMOJI" + echo "" } -inputLog() { - end=$'\\e[0m' - darkGray=$'\\e[90m' +printFabricNetworkDiagnostics() { + local message + message="$(fabricNetworkMessage)" - echo "\${darkGray} $1 \${end}" -} + if [ -n "$message" ]; then + echo "FabricNetwork status message:" + echo " $message" + fi -inputLogShort() { - end=$'\\e[0m' - darkGray=$'\\e[90m' + echo "FabricNetwork details:" + kubectlCmd get -n "$NAMESPACE" "fabricnetworks.fabricops.io/$FABRICNETWORK_NAME" -o wide || true - echo "\${darkGray} $1 \${end}" + echo "Recent FabricNetwork events:" + kubectlCmd get events \\ + -n "$NAMESPACE" \\ + --field-selector "involvedObject.kind=FabricNetwork,involvedObject.name=$FABRICNETWORK_NAME" \\ + --sort-by=.lastTimestamp || true } -verifyKubernetesConnectivity() { - echo "Verifying kubectl-hlf installation..." - if ! [[ $(command -v kubectl-hlf) ]]; then - echo "Error: Fablo could not detect kubectl hlf plugin. Ensure you have installed: - - kubectl - https://kubernetes.io/docs/tasks/tools/ - - helm - https://helm.sh/docs/intro/install/ - - krew - https://krew.sigs.k8s.io/docs/user-guide/setup/install/ - - hlf-operator along with krew hlf plugin - https://github.com/hyperledger-labs/hlf-operator#install-kubernetes-operator" - exit 1 +targetContext() { + if [ -n "\${FABLO_KUBECONTEXT:-}" ]; then + echo "$FABLO_KUBECONTEXT" else - echo " $(command -v kubectl-hlf)" + kubectlCmd config current-context 2>/dev/null || echo "" fi +} - if [ "$(kubectl get pods -l=app.kubernetes.io/name=hlf-operator -o jsonpath='{.items}')" = "[]" ]; then - echo "Error: hlf-operator is not running. You can install it with: - helm install hlf-operator --version=1.6.0 kfs/hlf-operator" - exit 1 +printTarget() { + echo "FabricOps target:" + echo " context: $(targetContext)" + if [ -n "\${FABLO_KUBECONFIG:-}" ]; then + echo " kubeconfig: $FABLO_KUBECONFIG" fi + echo " namespace: $NAMESPACE" + echo " operator namespace: $FABRICOPS_NAMESPACE" + echo " FabricNetwork: $FABRICNETWORK_NAME" + echo " manifest: $FABRICOPS_MANIFEST" +} - echo "Verifying default kubernetes cluster" - if ! kubectl get ns default >/dev/null 2>&1; then - printf "No K8 cluster detected\\n" >&2 +printInstallInstructions() { + echo "Install FabricOps first, then rerun this command." + echo "FabricOps installation instructions: $FABRICOPS_INSTALL_URL" +} + +requireKubectl() { + if ! command -v kubectl >/dev/null 2>&1; then + echo "Error: kubectl is required to apply a FabricOps network" exit 1 fi - - while [ "$(kubectl get pods -l=app.kubernetes.io/name=hlf-operator -o jsonpath='{.items[*].status.containerStatuses[0].ready}')" != "true" ]; do - sleep 5 - echo "$BLUE" "Waiting for Operator to be ready." "$RESETBG" - done } -" -`; - -exports[`samples/fablo-config-hlf2-1org-1chaincode-k8s.json should create proper e2e/__tmp__/samples/fablo-config-hlf2-1org-1chaincode-k8s.json.tmpdir/fablo-target/fabric-k8s/scripts/base-help.sh from samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = ` -"#!/usr/bin/env bash -printHelp() { - echo "Fablo is powered by SoftwareMill" - - echo "" - echo "usage: ./fabric-k8.sh " - echo "" - - echo "Commands: " - echo "" - echo "./fabric-k8.sh up" - echo -e "\\t Use for first run. Creates all needed artifacts (certs, genesis block) and starts network for the first time." - echo -e "\\t After 'up' commands start/stop are used to manage network and rerun to rerun it" - echo "" - echo "./fabric-k8.sh down" - echo -e "\\t Back to empty state - destorys created containers, prunes generated certificates, configs." - echo "" - echo "./fabric-k8.sh channel --help" - echo -e "\\t Detailed help for channel management scripts." - echo "" +verifyClusterAccess() { + if ! kubectlCmd cluster-info >/dev/null 2>&1; then + echo "Error: could not reach the Kubernetes cluster for context '$(targetContext)'" + exit 1 + fi } -" -`; -exports[`samples/fablo-config-hlf2-1org-1chaincode-k8s.json should create proper e2e/__tmp__/samples/fablo-config-hlf2-1org-1chaincode-k8s.json.tmpdir/fablo-target/fabric-k8s/scripts/chaincode-functions.sh from samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = ` -"#!/usr/bin/env bash +verifyFabricOps() { + requireKubectl -buildAndInstallChaincode() { - local CHAINCODE_NAME="$1" - local PEER="$2" - local CHAINCODE_LANG="$3" - local CHAINCODE_DIR_PATH="$4" - local CHAINCODE_VERSION="$5" - local USER="$6" - local CONFIG="$7" - local CHANNEL_NAME="$8" + if [ ! -f "$FABRICOPS_MANIFEST" ]; then + echo "Error: FabricOps manifest not found: $FABRICOPS_MANIFEST" + exit 1 + fi - local CHAINCODE_LABEL="\${CHANNEL_NAME}_\${CHAINCODE_NAME}_$CHAINCODE_VERSION" + verifyClusterAccess - if [ -z "$CHAINCODE_NAME" ]; then - echo "Error: chaincode name is not provided" + if ! kubectlCmd get crd fabricnetworks.fabricops.io >/dev/null 2>&1; then + echo "Error: FabricOps CRD 'fabricnetworks.fabricops.io' is not installed in this cluster" + printInstallInstructions exit 1 fi - if [ -z "$PEER" ]; then - echo "Error: peer number is not provided e.g 0, 1" + if ! kubectlCmd get namespace "$FABRICOPS_NAMESPACE" >/dev/null 2>&1; then + echo "Error: FabricOps controller namespace '$FABRICOPS_NAMESPACE' was not found" + printInstallInstructions exit 1 fi - if [ -z "$CHAINCODE_LANG" ]; then - echo "Error: chaincode language is not provided" + if ! kubectlCmd get deployment \\ + -n "$FABRICOPS_NAMESPACE" \\ + -l app.kubernetes.io/name=fabricops,control-plane=controller-manager \\ + -o name | grep -q .; then + echo "Error: FabricOps controller manager Deployment was not found in namespace '$FABRICOPS_NAMESPACE'" + printInstallInstructions exit 1 fi - if [ -z "$CHAINCODE_VERSION" ]; then - echo "Error: chaincode version is not provided e.g 1.0, 2.0" + if ! kubectlCmd wait \\ + -n "$FABRICOPS_NAMESPACE" \\ + --for=condition=Available \\ + --timeout=5s \\ + deployment \\ + -l app.kubernetes.io/name=fabricops,control-plane=controller-manager; then + echo "Error: FabricOps controller manager is installed but not Available in namespace '$FABRICOPS_NAMESPACE'" + echo "Check it with: kubectl -n $FABRICOPS_NAMESPACE get deployment -l app.kubernetes.io/name=fabricops,control-plane=controller-manager" exit 1 fi - kubectl hlf chaincode install --path="$CHAINCODE_DIR_PATH" \\ - --config="$CONFIG" --language="$CHAINCODE_LANG" --label="$CHAINCODE_LABEL" --user="$USER" --peer="$PEER" + kubectlCmd get namespace "$NAMESPACE" >/dev/null 2>&1 || kubectlCmd create namespace "$NAMESPACE" } -approveChaincode() { - local CHAINCODE_NAME="$1" - local PEER="$2" - local CHAINCODE_VERSION="$3" - local CHANNEL_NAME="$4" - local USER="$5" - local CONFIG="$6" - local MSP="$7" - local SEQUENCE - - local CHAINCODE_LABEL="\${CHANNEL_NAME}_\${CHAINCODE_NAME}_$CHAINCODE_VERSION" +waitForFabricNetwork() { + echo "Waiting for FabricNetwork '$FABRICNETWORK_NAME' to become Ready..." - SEQUENCE="$(kubectl hlf chaincode querycommitted --channel="$CHANNEL_NAME" --config="$CONFIG" --user="$USER" --peer="$PEER" 2>/dev/null | awk '{print $3}' | sed -n '2p')" - SEQUENCE=$((SEQUENCE + 1)) + local timeout_seconds + timeout_seconds="$(fabricOpsWaitTimeoutSeconds)" - if [ -z "$CHAINCODE_NAME" ]; then - echo "Error: chaincode name is not provided" - exit 1 - fi + if [ -z "$timeout_seconds" ]; then + if kubectlCmd wait \\ + -n "$NAMESPACE" \\ + --for=condition=Ready \\ + --timeout="$FABRICOPS_WAIT_TIMEOUT" \\ + "fabricnetworks.fabricops.io/$FABRICNETWORK_NAME"; then + return + fi - if [ -z "$PEER" ]; then - echo "Error: peer number is not provided e.g 0, 1" + printFabricNetworkDiagnostics exit 1 fi - if [ -z "$CHAINCODE_VERSION" ]; then - echo "Error: chaincode version is not provided e.g 1.0, 2.0" - exit 1 - fi + local deadline=$((SECONDS + timeout_seconds)) + + while [ "$SECONDS" -lt "$deadline" ]; do + local generation + local observed_generation + local phase + local ready_condition + generation="$(fabricNetworkGeneration)" + observed_generation="$(fabricNetworkReadyObservedGeneration)" + phase="$(fabricNetworkPhase)" + ready_condition="$(fabricNetworkReadyCondition)" + + if [ -z "$generation" ] || [ "$observed_generation" != "$generation" ]; then + sleep 5 + continue + fi - if [ -z "$CHANNEL_NAME" ]; then - echo "Error: channel name is not provided" - exit 1 - fi + if [ "$phase" = "Ready" ] || [ "$ready_condition" = "True" ]; then + echo "FabricNetwork '$FABRICNETWORK_NAME' is Ready." + return + fi - PACKAGE_ID="$(kubectl hlf chaincode queryinstalled --config="$CONFIG" --user="$USER" --peer="$PEER" | awk '{print $1}' | grep chaincode)" + if [ "$phase" = "Failed" ]; then + echo "Error: FabricNetwork '$FABRICNETWORK_NAME' failed to reconcile." + printFabricNetworkDiagnostics + exit 1 + fi - printItalics "Approving chaincode $CHAINCODE_NAME on $PEER" "U1F618" + sleep 5 + done - kubectl hlf chaincode approveformyorg --config="$CONFIG" --user="$USER" --peer="$PEER" \\ - --package-id="$PACKAGE_ID" --version "$CHAINCODE_VERSION" --sequence "$SEQUENCE" --name="$CHAINCODE_NAME" \\ - --policy="OR('$MSP.member')" --channel="$CHANNEL_NAME" + echo "Error: timed out waiting for FabricNetwork '$FABRICNETWORK_NAME' to become Ready." + printFabricNetworkDiagnostics + exit 1 } -commitChaincode() { - local CHAINCODE_NAME="$1" - local PEER="$2" - local CHAINCODE_VERSION="$3" - local CHANNEL_NAME="$4" - local USER="$5" - local CONFIG="$6" - local MSP="$7" - local SEQUENCE - - local CHAINCODE_LABEL="\${CHANNEL_NAME}_\${CHAINCODE_NAME}_$CHAINCODE_VERSION" +networkUp() { + printTarget + verifyFabricOps + kubectlCmd apply -n "$NAMESPACE" -f "$FABRICOPS_MANIFEST" + waitForFabricNetwork +} - SEQUENCE="$(kubectl hlf chaincode querycommitted --channel="$CHANNEL_NAME" --config="$CONFIG" --user="$USER" --peer="$PEER" 2>/dev/null | awk '{print $3}' | sed -n '2p')" - SEQUENCE=$((SEQUENCE + 1)) +networkDown() { + printTarget + requireKubectl + verifyClusterAccess + if ! kubectlCmd get crd fabricnetworks.fabricops.io >/dev/null 2>&1; then + echo "FabricOps CRD 'fabricnetworks.fabricops.io' is not installed. Nothing to delete." + return 0 + fi + if ! kubectlCmd get namespace "$NAMESPACE" >/dev/null 2>&1; then + echo "Namespace '$NAMESPACE' does not exist. Nothing to delete." + return 0 + fi + kubectlCmd delete -n "$NAMESPACE" -f "$FABRICOPS_MANIFEST" --ignore-not-found=true +} - if [ -z "$CHAINCODE_NAME" ]; then - echo "Error: chaincode name is not provided" - exit 1 +networkStatus() { + printTarget + requireKubectl + verifyClusterAccess + if command -v fabricopsctl >/dev/null 2>&1; then + fabricopsctlCmd status -n "$NAMESPACE" "$FABRICNETWORK_NAME" + else + kubectlCmd get -n "$NAMESPACE" "fabricnetworks.fabricops.io/$FABRICNETWORK_NAME" -o wide fi +} - if [ -z "$PEER" ]; then - echo "Error: peer number is not provided e.g 0, 1" +chaincodeInvoke() { + if [ "$#" -ne 4 ] && [ "$#" -ne 5 ]; then + echo "Expected 4 or 5 parameters for chaincode invoke, but got: $*" + echo "Usage: fablo chaincode invoke [transient]" exit 1 fi - if [ -z "$CHAINCODE_VERSION" ]; then - echo "Error: chaincode version is not provided e.g 1.0, 2.0" - exit 1 + requireFabricOpsctl + + local peers="$1" + local channel="$2" + local chaincode="$3" + local payload="$4" + local transient="\${5:-}" + local submitter_org + submitter_org="$(chaincodeSubmitterOrg "$peers")" + + local args=( + invoke + -n "$NAMESPACE" + --timeout "$FABRICOPS_OPERATION_TIMEOUT" + --org "$submitter_org" + --channel "$channel" + --chaincode "$chaincode" + --payload "$payload" + ) + + if [ -n "$transient" ]; then + args+=(--transient "$transient") fi - if [ -z "$CHANNEL_NAME" ]; then - echo "Error: channel name is not provided" + local peer_domain + local peer_ref + IFS=',' read -r -a peer_domains <<<"$peers" + for peer_domain in "\${peer_domains[@]}"; do + peer_ref="$(chaincodePeerRef "$peer_domain")" + args+=(--peer "$peer_ref") + done + + fabricopsctlCmd "\${args[@]}" "$FABRICNETWORK_NAME" +} + +chaincodeQuery() { + if [ "$#" -ne 4 ] && [ "$#" -ne 5 ]; then + echo "Expected 4 or 5 parameters for chaincode query, but got: $*" + echo "Usage: fablo chaincode query [transient]" exit 1 fi - PACKAGE_ID="$(kubectl hlf chaincode queryinstalled --config="$CONFIG" --user="$USER" --peer="$PEER" | awk '{print $1}' | grep chaincode)" + requireFabricOpsctl + + local peer="$1" + local channel="$2" + local chaincode="$3" + local payload="$4" + local transient="\${5:-}" + local submitter_org + local peer_ref + submitter_org="$(chaincodeSubmitterOrg "$peer")" + peer_ref="$(chaincodePeerRef "$peer")" + + local args=( + query + -n "$NAMESPACE" + --timeout "$FABRICOPS_OPERATION_TIMEOUT" + --org "$submitter_org" + --peer "$peer_ref" + --channel "$channel" + --chaincode "$chaincode" + --payload "$payload" + ) + + if [ -n "$transient" ]; then + args+=(--transient "$transient") + fi - kubectl hlf chaincode commit --config="$CONFIG" --user="$USER" --mspid="$MSP" \\ - --version "$CHAINCODE_VERSION" --sequence "$SEQUENCE" --name="$CHAINCODE_NAME" \\ - --policy="OR('$MSP.member')" --channel="$CHANNEL_NAME" + fabricopsctlCmd "\${args[@]}" "$FABRICNETWORK_NAME" } + +case "\${1:-}" in +up | start) + networkUp + ;; +down) + networkDown + ;; +reset) + networkDown + networkUp + ;; +status) + networkStatus + ;; +chaincode) + case "\${2:-}" in + invoke) + chaincodeInvoke "\${3:-}" "\${4:-}" "\${5:-}" "\${6:-}" "\${7:-}" + ;; + query) + chaincodeQuery "\${3:-}" "\${4:-}" "\${5:-}" "\${6:-}" "\${7:-}" + ;; + *) + echo "Unsupported Kubernetes chaincode command: \${2:-}" + echo "Supported commands are: chaincode invoke, chaincode query" + exit 1 + ;; + esac + ;; +help | --help | -h) + printHelp + ;; +*) + echo "No command specified" + printHelp + exit 1 + ;; +esac " `; -exports[`samples/fablo-config-hlf2-1org-1chaincode-k8s.json should create proper e2e/__tmp__/samples/fablo-config-hlf2-1org-1chaincode-k8s.json.tmpdir/fablo-target/fabric-k8s/scripts/util.sh from samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = ` -"#!/usr/bin/bash - -retry() { - local n=0 - local try=$1 - local cmd="\${*:2}" - [[ $# -le 1 ]] - - until [[ $n -ge $try ]]; do - if [[ $cmd ]]; then - break - else - echo $(printf '\\033[31m') "Previous command FAILED" - ((n++)) - echo $(printf '\\033[34m') "RETRYING..." - sleep 1 - fi - done -} -retry "$*" +exports[`samples/fablo-config-hlf2-1org-1chaincode-k8s.json should create proper e2e/__tmp__/samples/fablo-config-hlf2-1org-1chaincode-k8s.json.tmpdir/fablo-target/fabric-k8s/fabricnetwork.yaml from samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = ` +"apiVersion: fabricops.io/v1alpha1 +kind: FabricNetwork +metadata: + name: fablo-network + labels: + app.kubernetes.io/name: fabricops + app.kubernetes.io/managed-by: fablo +spec: + global: + fabricVersion: 2.4.7 + tls: true + jobs: + succeededHistoryTTLSeconds: 600 + orgs: + - organization: + name: Orderer + domain: orderer.example.com + mspName: OrdererMSP + ca: + db: sqlite + orderers: + - groupName: group1 + type: raft + instances: 1 + prefix: orderer + - organization: + name: Org1 + domain: org1.example.com + mspName: Org1MSP + ca: + db: sqlite + peer: + instances: 2 + db: LevelDb + prefix: peer + channels: + - name: my-channel1 + orgs: + - name: Org1 + peers: + - peer0 + - peer1 + chaincodes: + - name: chaincode1 + version: 0.0.1 + channel: my-channel1 + image: ghcr.io/fablo-io/fablo-sample-kv-node-chaincode:2.2.0 + initRequired: false + ccaas: + replicas: 1 + containerPort: 7052 + servicePort: 7052 + dialTimeout: 10s + imagePullPolicy: IfNotPresent " `; @@ -563,7 +619,7 @@ classDef subgraph_padding fill:none,stroke:none subgraph orderer_example_com [Organization: Orderer
orderer.example.com] subgraph orderer_example_com_padding [ ] direction RL - subgraph ord_group_group1_orderer0_group1_orderer_example_com [Orderer Group: group1
solo] + subgraph ord_group_group1_orderer0_group1_orderer_example_com [Orderer Group: group1
etcdraft] subgraph ord_group_group1_orderer0_group1_orderer_example_com_padding [ ] direction RL orderer0_group1_orderer_example_com[orderer0.group1.orderer.example.com] @@ -603,11 +659,7 @@ exports[`samples/fablo-config-hlf2-1org-1chaincode-k8s.json should create proper "e2e/__tmp__/samples/fablo-config-hlf2-1org-1chaincode-k8s.json.tmpdir/fablo-target/fablo-config.json", "e2e/__tmp__/samples/fablo-config-hlf2-1org-1chaincode-k8s.json.tmpdir/fablo-target/fabric-config/.gitignore", "e2e/__tmp__/samples/fablo-config-hlf2-1org-1chaincode-k8s.json.tmpdir/fablo-target/fabric-k8s.sh", - "e2e/__tmp__/samples/fablo-config-hlf2-1org-1chaincode-k8s.json.tmpdir/fablo-target/fabric-k8s/.env", - "e2e/__tmp__/samples/fablo-config-hlf2-1org-1chaincode-k8s.json.tmpdir/fablo-target/fabric-k8s/scripts/base-functions.sh", - "e2e/__tmp__/samples/fablo-config-hlf2-1org-1chaincode-k8s.json.tmpdir/fablo-target/fabric-k8s/scripts/base-help.sh", - "e2e/__tmp__/samples/fablo-config-hlf2-1org-1chaincode-k8s.json.tmpdir/fablo-target/fabric-k8s/scripts/chaincode-functions.sh", - "e2e/__tmp__/samples/fablo-config-hlf2-1org-1chaincode-k8s.json.tmpdir/fablo-target/fabric-k8s/scripts/util.sh", + "e2e/__tmp__/samples/fablo-config-hlf2-1org-1chaincode-k8s.json.tmpdir/fablo-target/fabric-k8s/fabricnetwork.yaml", "e2e/__tmp__/samples/fablo-config-hlf2-1org-1chaincode-k8s.json.tmpdir/fablo-target/hooks/post-generate.sh", "e2e/__tmp__/samples/fablo-config-hlf2-1org-1chaincode-k8s.json.tmpdir/fablo-target/hooks/post-start.sh", "e2e/__tmp__/samples/fablo-config-hlf2-1org-1chaincode-k8s.json.tmpdir/fablo-target/network-topology.mmd", diff --git a/fablo.sh b/fablo.sh index 0b30250ff..98738a494 100755 --- a/fablo.sh +++ b/fablo.sh @@ -330,6 +330,7 @@ executeFabloCommand() { "$FABLO_TARGET/fabric-docker.sh" "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" elif [ -f "$FABLO_TARGET/fabric-k8s.sh" ]; then echo "Executing Fablo Kubernetes command: $1" + chmod +x "$FABLO_TARGET/fabric-k8s.sh" || true "$FABLO_TARGET/fabric-k8s.sh" "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" else echo "Error: Corrupted Fablo target directory ($FABLO_TARGET)" diff --git a/samples/fablo-config-hlf2-1org-1chaincode-k8s.json b/samples/fablo-config-hlf2-1org-1chaincode-k8s.json index e6bac0b03..946ce2cbc 100644 --- a/samples/fablo-config-hlf2-1org-1chaincode-k8s.json +++ b/samples/fablo-config-hlf2-1org-1chaincode-k8s.json @@ -2,7 +2,7 @@ "$schema": "https://github.com/hyperledger-labs/fablo/releases/download/2.6.0/schema.json", "global": { "fabricVersion": "2.4.7", - "tls": false, + "tls": true, "engine": "kubernetes" }, "orgs": [ @@ -14,7 +14,7 @@ "orderers": [ { "groupName": "group1", - "type": "solo", + "type": "raft", "instances": 1 } ] @@ -48,9 +48,9 @@ { "name": "chaincode1", "version": "0.0.1", - "lang": "node", + "lang": "ccaas", "channel": "my-channel1", - "directory": "./chaincodes/chaincode-kv-node" + "image": "ghcr.io/fablo-io/fablo-sample-kv-node-chaincode:2.2.0" } ] } diff --git a/src/setup-k8s/fabricOpsManifest.ts b/src/setup-k8s/fabricOpsManifest.ts new file mode 100644 index 000000000..1f0885e61 --- /dev/null +++ b/src/setup-k8s/fabricOpsManifest.ts @@ -0,0 +1,195 @@ +import * as yaml from "js-yaml"; +import { FabloConfigJson } from "../types/FabloConfigJson"; +import { ChaincodeConfig, FabloConfigExtended } from "../types/FabloConfigExtended"; +import defaults from "../extend-config/defaults"; + +const FABRICOPS_API_VERSION = "fabricops.io/v1alpha1"; +const DEFAULT_FABRICNETWORK_NAME = "fablo-network"; +const DEFAULT_SUCCEEDED_JOB_HISTORY_TTL_SECONDS = 600; + +interface FabricOpsManifest { + apiVersion: string; + kind: "FabricNetwork"; + metadata: { + name: string; + labels: Record; + }; + spec: Record; +} + +const ordererTypeForFabricOps = (consensus: string): string => (consensus === "etcdraft" ? "raft" : consensus); + +const requireCcaasImages = (chaincodes: ChaincodeConfig[]): void => { + const unsupported = chaincodes.filter((chaincode) => chaincode.lang !== "ccaas" || !chaincode.image); + if (unsupported.length === 0) return; + + const names = unsupported.map((chaincode) => `${chaincode.channel.name}/${chaincode.name}`).join(", "); + throw new Error( + `FabricOps Kubernetes engine requires CCaaS chaincodes with image fields. Unsupported chaincodes: ${names}`, + ); +}; + +const requireFabricOpsCompatibleTopology = (json: FabloConfigJson, config: FabloConfigExtended): void => { + if (config.channels.length > 0 && !config.global.tls) { + throw new Error("FabricOps Kubernetes engine requires global.tls=true when channels are declared"); + } + + if (config.ordererGroups.length > 1) { + throw new Error("FabricOps Kubernetes engine currently supports one orderer group"); + } + + const orgsWithTools = json.orgs + .filter((org) => org.tools?.fabloRest || org.tools?.explorer) + .map((org) => org.organization.name); + if (orgsWithTools.length > 0 || json.global.tools?.explorer) { + throw new Error( + `FabricOps Kubernetes engine does not yet support Fablo REST or Explorer tools. Unsupported orgs: ${ + orgsWithTools.join(", ") || "global" + }`, + ); + } + + const chaincodesWithDevOptions = json.chaincodes + .filter((chaincode) => chaincode.chaincodeMountPath || chaincode.chaincodeStartCommand) + .map((chaincode) => `${chaincode.channel}/${chaincode.name}`); + if (chaincodesWithDevOptions.length > 0) { + throw new Error( + `FabricOps Kubernetes engine requires prebuilt CCaaS images and does not support chaincodeMountPath or chaincodeStartCommand. Unsupported chaincodes: ${chaincodesWithDevOptions.join( + ", ", + )}`, + ); + } +}; + +const withoutUndefined = (value: unknown): unknown => { + if (Array.isArray(value)) return value.map(withoutUndefined); + if (value === null || typeof value !== "object") return value; + + return Object.fromEntries( + Object.entries(value as Record) + .filter(([, v]) => v !== undefined) + .map(([k, v]) => [k, withoutUndefined(v)]), + ); +}; + +const policyOrgNames = (chaincode: ChaincodeConfig, policy: string): string[] => { + const orgNameByMsp = new Map(chaincode.channel.orgs.map((org) => [org.mspName, org.name])); + return ( + policy + .match(/'([^']+)\.member'/g) + ?.map((msp) => msp.replace(/^'/, "").replace(/\.member'$/, "")) + .map((mspName) => orgNameByMsp.get(mspName) ?? mspName) ?? [] + ); +}; + +const privateDataForFabricOps = (chaincode: ChaincodeConfig): Record[] => + chaincode.privateData.map( + (collection) => + withoutUndefined({ + name: collection.name, + orgNames: policyOrgNames(chaincode, collection.policy), + policy: collection.policy, + requiredPeerCount: collection.requiredPeerCount, + maxPeerCount: collection.maxPeerCount, + blockToLive: collection.blockToLive, + memberOnlyRead: collection.memberOnlyRead, + memberOnlyWrite: collection.memberOnlyWrite, + }) as Record, + ); + +const toFabricOpsManifest = (json: FabloConfigJson, config: FabloConfigExtended): FabricOpsManifest => { + requireCcaasImages(config.chaincodes); + requireFabricOpsCompatibleTopology(json, config); + + const orgsByName = new Map(json.orgs.map((org) => [org.organization.name, org])); + + return withoutUndefined({ + apiVersion: FABRICOPS_API_VERSION, + kind: "FabricNetwork", + metadata: { + name: DEFAULT_FABRICNETWORK_NAME, + labels: { + "app.kubernetes.io/name": "fabricops", + "app.kubernetes.io/managed-by": "fablo", + }, + }, + spec: withoutUndefined({ + global: { + fabricVersion: config.global.fabricVersion, + tls: config.global.tls, + jobs: { + succeededHistoryTTLSeconds: DEFAULT_SUCCEEDED_JOB_HISTORY_TTL_SECONDS, + }, + }, + orgs: config.orgs.map((org) => { + const orgJson = orgsByName.get(org.name); + return { + organization: { + name: org.name, + domain: org.domain, + mspName: org.mspName, + }, + ca: { + db: org.ca.db, + }, + ...(org.ordererGroups.length > 0 + ? { + orderers: org.ordererGroups.map((group) => { + const groupJson = orgJson?.orderers?.find((o) => o.groupName === group.name); + return { + groupName: group.name, + type: ordererTypeForFabricOps(group.consensus), + instances: group.orderers.length, + prefix: groupJson?.prefix ?? defaults.orderer.prefix, + }; + }), + } + : {}), + ...(org.peers.length > 0 + ? { + peer: { + instances: org.peers.length, + db: org.headPeer?.db.type ?? orgJson?.peer?.db ?? defaults.peer.db, + prefix: orgJson?.peer?.prefix ?? defaults.peer.prefix, + }, + } + : {}), + }; + }), + channels: config.channels.map((channel) => ({ + name: channel.name, + orgs: channel.orgs.map((org) => ({ + name: org.name, + peers: org.peers.map((peer) => peer.name), + })), + })), + chaincodes: config.chaincodes.map((chaincode) => + withoutUndefined({ + name: chaincode.name, + version: chaincode.version, + channel: chaincode.channel.name, + image: chaincode.image, + endorsementPolicy: chaincode.endorsement, + initRequired: chaincode.initRequired, + privateData: chaincode.privateData.length > 0 ? privateDataForFabricOps(chaincode) : undefined, + ccaas: { + replicas: 1, + containerPort: 7052, + servicePort: 7052, + dialTimeout: "10s", + imagePullPolicy: "IfNotPresent", + }, + }), + ), + }), + }) as FabricOpsManifest; +}; + +const toFabricOpsYaml = (json: FabloConfigJson, config: FabloConfigExtended): string => + `${yaml.dump(toFabricOpsManifest(json, config), { + lineWidth: 120, + noRefs: true, + sortKeys: false, + })}`; + +export { DEFAULT_FABRICNETWORK_NAME, toFabricOpsYaml }; diff --git a/src/setup-k8s/index.ts b/src/setup-k8s/index.ts index 3434c8f55..1b28a0641 100644 --- a/src/setup-k8s/index.ts +++ b/src/setup-k8s/index.ts @@ -1,12 +1,11 @@ import { Args, Command } from "@oclif/core"; -import * as config from "../config"; -import { getBuildInfo } from "../version/buildUtil"; import parseFabloConfig from "../utils/parseFabloConfig"; -import { Capabilities, FabloConfigExtended, HooksConfig, Global, OrgConfig } from "../types/FabloConfigExtended"; +import { FabloConfigExtended, HooksConfig } from "../types/FabloConfigExtended"; import { extendConfig } from "../commands/extend-config/"; import * as fs from "fs-extra"; import * as path from "path"; import { renderTemplate, getTemplatePath, getDestinationPath } from "../utils/templateUtils"; +import { DEFAULT_FABRICNETWORK_NAME, toFabricOpsYaml } from "./fabricOpsManifest"; export default class SetupK8s extends Command { static override description = "Setup Kubernetes network files from Fablo config"; @@ -37,25 +36,17 @@ export default class SetupK8s extends Command { const configContent = await fs.readFile(fabloConfigPath, "utf-8"); const json = parseFabloConfig(configContent); const configExtended = extendConfig(json); - const { global, orgs } = configExtended; - - const dateString = new Date() - .toISOString() - .substring(0, 16) - .replace(/[^0-9]+/g, ""); - const composeNetworkName = `fablo_network_${dateString}`; + const { global } = configExtended; this.log(`Used network config: ${fabloConfigPath}`); this.log(`Fabric version is: ${global.fabricVersion}`); + this.log("Generating FabricOps Kubernetes manifest..."); await this._copyGitIgnore(); - // ======= fabric-k8s =========================================================== - await this._copyEnvFile(global, orgs, composeNetworkName); - - // ======= scripts ================================================================== - await this._copyCommandsGeneratedScript(configExtended); - await this._copyUtilityScripts(configExtended.global.capabilities); + // ======= fabric-k8s ============================================================== + await this._createFabricOpsManifest(json, configExtended); + await this._copyFabricK8sScript(configExtended); // ======= hooks ==================================================================== await this._copyHooks(configExtended.hooks); @@ -70,7 +61,7 @@ export default class SetupK8s extends Command { this.log(`✅ Network topology exported to ${outputFile}`); this.log("Done & done !!! Try the network out: "); - this.log("-> fablo up - to start network"); + this.log("-> fablo up - to apply the FabricOps network"); this.log("-> fablo help - to view all commands"); } @@ -80,60 +71,22 @@ export default class SetupK8s extends Command { await renderTemplate(templatePath, destPath, {}); } - async _copyEnvFile(global: Global, orgsTransformed: OrgConfig[], composeNetworkName: string): Promise { - const settings = { - composeNetworkName, - fabricCaVersion: global.fabricCaVersion, - global, - orgs: orgsTransformed, - paths: global.paths, - fabloVersion: config.fabloVersion, - fabloBuild: getBuildInfo(), - fabloRestVersion: "0.2.0", - hyperledgerExplorerVersion: "1.1.8", - fabricCouchDbVersion: "0.4.18", - couchDbVersion: "3.1", - fabricCaPostgresVersion: "14", - }; - const templatePath = getTemplatePath(this.templatesDir, "fabric-k8s/.env"); - const destPath = getDestinationPath(this.outputDir, "fabric-k8s/.env"); - await renderTemplate(templatePath, destPath, settings); + async _createFabricOpsManifest( + json: ReturnType, + config: FabloConfigExtended, + ): Promise { + const destPath = getDestinationPath(this.outputDir, "fabric-k8s/fabricnetwork.yaml"); + await fs.ensureDir(path.dirname(destPath)); + await fs.writeFile(destPath, toFabricOpsYaml(json, config), "utf-8"); } - async _copyCommandsGeneratedScript(config: FabloConfigExtended): Promise { - const templatePath = getTemplatePath(this.templatesDir, "fabric-k8s/scripts/base-functions.sh"); - const destPath = getDestinationPath(this.outputDir, "fabric-k8s/scripts/base-functions.sh"); - await renderTemplate(templatePath, destPath, config as unknown as Record); - } - - async _copyUtilityScripts(capabilities: Capabilities): Promise { - // Copy fabric-k8s.sh + async _copyFabricK8sScript(config: FabloConfigExtended): Promise { const fabricK8sShTemplate = getTemplatePath(this.templatesDir, "fabric-k8s.sh"); const fabricK8sShDest = getDestinationPath(this.outputDir, "fabric-k8s.sh"); - await renderTemplate(fabricK8sShTemplate, fabricK8sShDest, {}); - - // Copy base-help.sh - const baseHelpTemplate = getTemplatePath(this.templatesDir, "fabric-k8s/scripts/base-help.sh"); - const baseHelpDest = getDestinationPath(this.outputDir, "fabric-k8s/scripts/base-help.sh"); - await renderTemplate(baseHelpTemplate, baseHelpDest, {}); - - // Copy util.sh - const utilTemplate = getTemplatePath(this.templatesDir, "fabric-k8s/scripts/util.sh"); - const utilDest = getDestinationPath(this.outputDir, "fabric-k8s/scripts/util.sh"); - await renderTemplate(utilTemplate, utilDest, {}); - - // Copy chaincode-functions.sh (base) - const chaincodeFunctionsBaseTemplate = getTemplatePath(this.templatesDir, "fabric-k8s/scripts/chaincode-functions.sh"); - const chaincodeFunctionsBaseDest = getDestinationPath(this.outputDir, "fabric-k8s/scripts/chaincode-functions.sh"); - await renderTemplate(chaincodeFunctionsBaseTemplate, chaincodeFunctionsBaseDest, {}); - - // Copy chaincode-functions script (v2 or v1.4) - this overwrites the base - const chaincodeFunctionsTemplate = getTemplatePath( - this.templatesDir, - `fabric-k8s/scripts/chaincode-functions-${capabilities.isV2 ? "v2" : "v1.4"}.sh`, - ); - const chaincodeFunctionsDest = getDestinationPath(this.outputDir, "fabric-k8s/scripts/chaincode-functions.sh"); - await renderTemplate(chaincodeFunctionsTemplate, chaincodeFunctionsDest, {}); + await renderTemplate(fabricK8sShTemplate, fabricK8sShDest, { + ...config, + fabricNetworkName: DEFAULT_FABRICNETWORK_NAME, + } as unknown as Record); } async _copyHooks(hooks: HooksConfig): Promise { @@ -145,4 +98,3 @@ export default class SetupK8s extends Command { } } } - diff --git a/src/setup-k8s/templates/fabric-k8s.sh b/src/setup-k8s/templates/fabric-k8s.sh index 289d324e4..698865efc 100755 --- a/src/setup-k8s/templates/fabric-k8s.sh +++ b/src/setup-k8s/templates/fabric-k8s.sh @@ -1,70 +1,461 @@ #!/usr/bin/env bash -set -e +set -euo pipefail FABLO_NETWORK_ROOT="$(cd "$(dirname "$0")" && pwd)" +FABRICOPS_MANIFEST="$FABLO_NETWORK_ROOT/fabric-k8s/fabricnetwork.yaml" +FABRICNETWORK_NAME="<%= fabricNetworkName %>" +NAMESPACE="${NAMESPACE:-default}" +FABRICOPS_NAMESPACE="${FABRICOPS_NAMESPACE:-fabricops-system}" +FABRICOPS_WAIT_TIMEOUT="${FABRICOPS_WAIT_TIMEOUT:-20m}" +FABRICOPS_OPERATION_TIMEOUT="${FABRICOPS_OPERATION_TIMEOUT:-180s}" +FABRICOPS_INSTALL_URL="https://github.com/dpereowei/FabricOps#installation" -source "$FABLO_NETWORK_ROOT/fabric-k8s/scripts/base-help.sh" -source "$FABLO_NETWORK_ROOT/fabric-k8s/scripts/base-functions.sh" -source "$FABLO_NETWORK_ROOT/fabric-k8s/scripts/chaincode-functions.sh" -source "$FABLO_NETWORK_ROOT/fabric-k8s/.env" +KUBECTL_ARGS=() +FABRICOPSCTL_ARGS=() +if [ -n "${FABLO_KUBECONFIG:-}" ]; then + KUBECTL_ARGS+=(--kubeconfig "$FABLO_KUBECONFIG") + FABRICOPSCTL_ARGS+=(--kubeconfig "$FABLO_KUBECONFIG") +fi +if [ -n "${FABLO_KUBECONTEXT:-}" ]; then + KUBECTL_ARGS+=(--context "$FABLO_KUBECONTEXT") + FABRICOPSCTL_ARGS+=(--context "$FABLO_KUBECONTEXT") +fi + +printHelp() { + echo "usage: ./fabric-k8s.sh " + echo "" + echo "Commands:" + echo " up Apply the generated FabricOps FabricNetwork and wait until it is ready" + echo " start Re-apply the generated FabricOps FabricNetwork and wait until it is ready" + echo " down Delete the generated FabricOps FabricNetwork" + echo " reset Delete, then re-apply the generated FabricOps FabricNetwork" + echo " status Show the generated FabricOps FabricNetwork status" + echo " chaincode invoke [transient]" + echo " Invoke chaincode through FabricOps/fabricopsctl" + echo " chaincode query [transient]" + echo " Query chaincode through FabricOps/fabricopsctl" + echo " help Print this help" + echo "" + echo "Environment:" + echo " FABLO_KUBECONTEXT Kubeconfig context to target. Defaults to the current kubectl context." + echo " FABLO_KUBECONFIG Kubeconfig path. Defaults to kubectl/fabricopsctl defaults." + echo " NAMESPACE FabricNetwork namespace. Defaults to default." + echo " FABRICOPS_NAMESPACE FabricOps controller namespace. Defaults to fabricops-system." + echo " FABRICOPS_WAIT_TIMEOUT Readiness wait timeout. Defaults to 20m." + echo " FABRICOPS_OPERATION_TIMEOUT Chaincode operation timeout. Defaults to 180s." +} + +kubectlCmd() { + kubectl "${KUBECTL_ARGS[@]}" "$@" +} + +fabricopsctlCmd() { + local command="$1" + shift + fabricopsctl "$command" "${FABRICOPSCTL_ARGS[@]}" "$@" +} + +requireFabricOpsctl() { + if ! command -v fabricopsctl >/dev/null 2>&1; then + echo "Error: fabricopsctl is required for Fablo Kubernetes chaincode operations" + echo "Install it with: go install github.com/dpereowei/fabricops/cmd/fabricopsctl@latest" + echo "Then rerun the command." + exit 1 + fi +} + +chaincodeSubmitterOrg() { + local first_peer="${1%%,*}" + case "$first_peer" in + <% orgs.forEach((org) => { -%> + <% org.peers.forEach((peer) => { -%> + "<%= peer.address %>") echo "<%= org.name %>"; return 0 ;; + <% }) -%> + <% }) -%> + *) + echo "Unknown peer: $first_peer" >&2 + return 1 + ;; + esac +} + +chaincodePeerRef() { + local peer_domain="$1" + case "$peer_domain" in + <% orgs.forEach((org) => { -%> + <% org.peers.forEach((peer) => { -%> + "<%= peer.address %>") echo "<%= org.name %>/<%= peer.name %>"; return 0 ;; + <% }) -%> + <% }) -%> + *) + echo "Unknown peer: $peer_domain" >&2 + return 1 + ;; + esac +} + +fabricNetworkPhase() { + kubectlCmd get \ + -n "$NAMESPACE" \ + "fabricnetworks.fabricops.io/$FABRICNETWORK_NAME" \ + -o jsonpath='{.status.phase}' 2>/dev/null || true +} + +fabricNetworkGeneration() { + kubectlCmd get \ + -n "$NAMESPACE" \ + "fabricnetworks.fabricops.io/$FABRICNETWORK_NAME" \ + -o jsonpath='{.metadata.generation}' 2>/dev/null || true +} + +fabricNetworkReadyCondition() { + kubectlCmd get \ + -n "$NAMESPACE" \ + "fabricnetworks.fabricops.io/$FABRICNETWORK_NAME" \ + -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || true +} + +fabricNetworkReadyObservedGeneration() { + kubectlCmd get \ + -n "$NAMESPACE" \ + "fabricnetworks.fabricops.io/$FABRICNETWORK_NAME" \ + -o jsonpath='{.status.conditions[?(@.type=="Ready")].observedGeneration}' 2>/dev/null || true +} + +fabricNetworkMessage() { + kubectlCmd get \ + -n "$NAMESPACE" \ + "fabricnetworks.fabricops.io/$FABRICNETWORK_NAME" \ + -o jsonpath='{.status.message}' 2>/dev/null || true +} + +fabricOpsWaitTimeoutSeconds() { + local value="$FABRICOPS_WAIT_TIMEOUT" + + if [[ "$value" =~ ^([0-9]+)s$ ]]; then + echo "${BASH_REMATCH[1]}" + return + fi + + if [[ "$value" =~ ^([0-9]+)m$ ]]; then + echo "$((BASH_REMATCH[1] * 60))" + return + fi + + if [[ "$value" =~ ^([0-9]+)h$ ]]; then + echo "$((BASH_REMATCH[1] * 3600))" + return + fi + + echo "" +} + +printFabricNetworkDiagnostics() { + local message + message="$(fabricNetworkMessage)" + + if [ -n "$message" ]; then + echo "FabricNetwork status message:" + echo " $message" + fi + + echo "FabricNetwork details:" + kubectlCmd get -n "$NAMESPACE" "fabricnetworks.fabricops.io/$FABRICNETWORK_NAME" -o wide || true + + echo "Recent FabricNetwork events:" + kubectlCmd get events \ + -n "$NAMESPACE" \ + --field-selector "involvedObject.kind=FabricNetwork,involvedObject.name=$FABRICNETWORK_NAME" \ + --sort-by=.lastTimestamp || true +} + +targetContext() { + if [ -n "${FABLO_KUBECONTEXT:-}" ]; then + echo "$FABLO_KUBECONTEXT" + else + kubectlCmd config current-context 2>/dev/null || echo "" + fi +} + +printTarget() { + echo "FabricOps target:" + echo " context: $(targetContext)" + if [ -n "${FABLO_KUBECONFIG:-}" ]; then + echo " kubeconfig: $FABLO_KUBECONFIG" + fi + echo " namespace: $NAMESPACE" + echo " operator namespace: $FABRICOPS_NAMESPACE" + echo " FabricNetwork: $FABRICNETWORK_NAME" + echo " manifest: $FABRICOPS_MANIFEST" +} + +printInstallInstructions() { + echo "Install FabricOps first, then rerun this command." + echo "FabricOps installation instructions: $FABRICOPS_INSTALL_URL" +} + +requireKubectl() { + if ! command -v kubectl >/dev/null 2>&1; then + echo "Error: kubectl is required to apply a FabricOps network" + exit 1 + fi +} + +verifyClusterAccess() { + if ! kubectlCmd cluster-info >/dev/null 2>&1; then + echo "Error: could not reach the Kubernetes cluster for context '$(targetContext)'" + exit 1 + fi +} + +verifyFabricOps() { + requireKubectl + + if [ ! -f "$FABRICOPS_MANIFEST" ]; then + echo "Error: FabricOps manifest not found: $FABRICOPS_MANIFEST" + exit 1 + fi + + verifyClusterAccess + + if ! kubectlCmd get crd fabricnetworks.fabricops.io >/dev/null 2>&1; then + echo "Error: FabricOps CRD 'fabricnetworks.fabricops.io' is not installed in this cluster" + printInstallInstructions + exit 1 + fi + + if ! kubectlCmd get namespace "$FABRICOPS_NAMESPACE" >/dev/null 2>&1; then + echo "Error: FabricOps controller namespace '$FABRICOPS_NAMESPACE' was not found" + printInstallInstructions + exit 1 + fi + + if ! kubectlCmd get deployment \ + -n "$FABRICOPS_NAMESPACE" \ + -l app.kubernetes.io/name=fabricops,control-plane=controller-manager \ + -o name | grep -q .; then + echo "Error: FabricOps controller manager Deployment was not found in namespace '$FABRICOPS_NAMESPACE'" + printInstallInstructions + exit 1 + fi + + if ! kubectlCmd wait \ + -n "$FABRICOPS_NAMESPACE" \ + --for=condition=Available \ + --timeout=5s \ + deployment \ + -l app.kubernetes.io/name=fabricops,control-plane=controller-manager; then + echo "Error: FabricOps controller manager is installed but not Available in namespace '$FABRICOPS_NAMESPACE'" + echo "Check it with: kubectl -n $FABRICOPS_NAMESPACE get deployment -l app.kubernetes.io/name=fabricops,control-plane=controller-manager" + exit 1 + fi + + kubectlCmd get namespace "$NAMESPACE" >/dev/null 2>&1 || kubectlCmd create namespace "$NAMESPACE" +} + +waitForFabricNetwork() { + echo "Waiting for FabricNetwork '$FABRICNETWORK_NAME' to become Ready..." + + local timeout_seconds + timeout_seconds="$(fabricOpsWaitTimeoutSeconds)" + + if [ -z "$timeout_seconds" ]; then + if kubectlCmd wait \ + -n "$NAMESPACE" \ + --for=condition=Ready \ + --timeout="$FABRICOPS_WAIT_TIMEOUT" \ + "fabricnetworks.fabricops.io/$FABRICNETWORK_NAME"; then + return + fi + + printFabricNetworkDiagnostics + exit 1 + fi + + local deadline=$((SECONDS + timeout_seconds)) + + while [ "$SECONDS" -lt "$deadline" ]; do + local generation + local observed_generation + local phase + local ready_condition + generation="$(fabricNetworkGeneration)" + observed_generation="$(fabricNetworkReadyObservedGeneration)" + phase="$(fabricNetworkPhase)" + ready_condition="$(fabricNetworkReadyCondition)" + + if [ -z "$generation" ] || [ "$observed_generation" != "$generation" ]; then + sleep 5 + continue + fi + + if [ "$phase" = "Ready" ] || [ "$ready_condition" = "True" ]; then + echo "FabricNetwork '$FABRICNETWORK_NAME' is Ready." + return + fi + + if [ "$phase" = "Failed" ]; then + echo "Error: FabricNetwork '$FABRICNETWORK_NAME' failed to reconcile." + printFabricNetworkDiagnostics + exit 1 + fi -# location of generated configurations -CONFIG_DIR="$FABLO_NETWORK_ROOT/fabric-config" + sleep 5 + done -RESETBG="$(printf '\e[0m\n')" -BLUE="$(printf '\033[34m')" + echo "Error: timed out waiting for FabricNetwork '$FABRICNETWORK_NAME' to become Ready." + printFabricNetworkDiagnostics + exit 1 +} networkUp() { - printHeadline "Checking dependencies..." "U1F984" - verifyKubernetesConnectivity - printHeadline "Starting Network..." "U1F984" - deployPeer - deployOrderer - installChannels - installChaincodes - printHeadline "Done! Enjoy your fresh network" "U1F984" + printTarget + verifyFabricOps + kubectlCmd apply -n "$NAMESPACE" -f "$FABRICOPS_MANIFEST" + waitForFabricNetwork } networkDown() { - printHeadline "Destroying network" "U1F913" - destroyNetwork -} - -if [ "$1" = "up" ]; then - networkUp -elif [ "$1" = "down" ]; then - networkDown -elif [ "$1" = "reset" ]; then - networkDown - sleep 60 - networkUp -elif [ "$1" = "start" ]; then - startNetwork -elif [ "$1" = "stop" ]; then - stopNetwork -elif [ "$1" = "chaincodes" ] && [ "$2" = "install" ]; then - installChaincodes -elif [ "$1" = "chaincode" ] && [ "$2" = "install" ]; then - installChaincode "$3" "$4" -elif [ "$1" = "chaincode" ] && [ "$2" = "upgrade" ]; then - upgradeChaincode "$3" "$4" -elif [ "$1" = "chaincode" ] && [ "$2" = "dev" ]; then - runDevModeChaincode "$3" "$4" -elif [ "$1" = "channel" ]; then - channelQuery "${@:2}" -elif [ "$1" = "snapshot" ]; then - createSnapshot "$2" -elif [ "$1" = "clone-to" ]; then - cloneSnapshot "$2" "${3:-""}" -elif [ "$1" = "help" ]; then - printHelp -elif [ "$1" = "--help" ]; then - printHelp -else - echo "No command specified" - echo "Basic commands are: up, down, start, stop, reset" - echo "To list channel query helper commands type: 'fablo channel --help'" - echo "Also check: 'chaincode install'" - echo "Use 'help' or '--help' for more information" -fi + printTarget + requireKubectl + verifyClusterAccess + if ! kubectlCmd get crd fabricnetworks.fabricops.io >/dev/null 2>&1; then + echo "FabricOps CRD 'fabricnetworks.fabricops.io' is not installed. Nothing to delete." + return 0 + fi + if ! kubectlCmd get namespace "$NAMESPACE" >/dev/null 2>&1; then + echo "Namespace '$NAMESPACE' does not exist. Nothing to delete." + return 0 + fi + kubectlCmd delete -n "$NAMESPACE" -f "$FABRICOPS_MANIFEST" --ignore-not-found=true +} + +networkStatus() { + printTarget + requireKubectl + verifyClusterAccess + if command -v fabricopsctl >/dev/null 2>&1; then + fabricopsctlCmd status -n "$NAMESPACE" "$FABRICNETWORK_NAME" + else + kubectlCmd get -n "$NAMESPACE" "fabricnetworks.fabricops.io/$FABRICNETWORK_NAME" -o wide + fi +} + +chaincodeInvoke() { + if [ "$#" -ne 4 ] && [ "$#" -ne 5 ]; then + echo "Expected 4 or 5 parameters for chaincode invoke, but got: $*" + echo "Usage: fablo chaincode invoke [transient]" + exit 1 + fi + + requireFabricOpsctl + + local peers="$1" + local channel="$2" + local chaincode="$3" + local payload="$4" + local transient="${5:-}" + local submitter_org + submitter_org="$(chaincodeSubmitterOrg "$peers")" + + local args=( + invoke + -n "$NAMESPACE" + --timeout "$FABRICOPS_OPERATION_TIMEOUT" + --org "$submitter_org" + --channel "$channel" + --chaincode "$chaincode" + --payload "$payload" + ) + + if [ -n "$transient" ]; then + args+=(--transient "$transient") + fi + + local peer_domain + local peer_ref + IFS=',' read -r -a peer_domains <<< "$peers" + for peer_domain in "${peer_domains[@]}"; do + peer_ref="$(chaincodePeerRef "$peer_domain")" + args+=(--peer "$peer_ref") + done + + fabricopsctlCmd "${args[@]}" "$FABRICNETWORK_NAME" +} + +chaincodeQuery() { + if [ "$#" -ne 4 ] && [ "$#" -ne 5 ]; then + echo "Expected 4 or 5 parameters for chaincode query, but got: $*" + echo "Usage: fablo chaincode query [transient]" + exit 1 + fi + + requireFabricOpsctl + + local peer="$1" + local channel="$2" + local chaincode="$3" + local payload="$4" + local transient="${5:-}" + local submitter_org + local peer_ref + submitter_org="$(chaincodeSubmitterOrg "$peer")" + peer_ref="$(chaincodePeerRef "$peer")" + + local args=( + query + -n "$NAMESPACE" + --timeout "$FABRICOPS_OPERATION_TIMEOUT" + --org "$submitter_org" + --peer "$peer_ref" + --channel "$channel" + --chaincode "$chaincode" + --payload "$payload" + ) + + if [ -n "$transient" ]; then + args+=(--transient "$transient") + fi + + fabricopsctlCmd "${args[@]}" "$FABRICNETWORK_NAME" +} + +case "${1:-}" in + up|start) + networkUp + ;; + down) + networkDown + ;; + reset) + networkDown + networkUp + ;; + status) + networkStatus + ;; + chaincode) + case "${2:-}" in + invoke) + chaincodeInvoke "${3:-}" "${4:-}" "${5:-}" "${6:-}" "${7:-}" + ;; + query) + chaincodeQuery "${3:-}" "${4:-}" "${5:-}" "${6:-}" "${7:-}" + ;; + *) + echo "Unsupported Kubernetes chaincode command: ${2:-}" + echo "Supported commands are: chaincode invoke, chaincode query" + exit 1 + ;; + esac + ;; + help|--help|-h) + printHelp + ;; + *) + echo "No command specified" + printHelp + exit 1 + ;; +esac diff --git a/src/setup-k8s/templates/fabric-k8s/.env b/src/setup-k8s/templates/fabric-k8s/.env deleted file mode 100755 index cc9dbba46..000000000 --- a/src/setup-k8s/templates/fabric-k8s/.env +++ /dev/null @@ -1,26 +0,0 @@ -NAMESPACE=default - -REPOSITORY="https://kfsoftware.github.io/hlf-helm-charts" -STORAGE_CLASS=$(kubectl describe sc | grep Name | tr -s ' ' | cut -d ':' -f 2 | cut -d ' ' -f 2) - -FABLO_VERSION=<%= fabloVersion %> -FABLO_BUILD=<%= fabloBuild %> -FABLO_CONFIG=<%= paths.fabloConfig %> -ORDERER_IMAGE=hyperledger/fabric-orderer -ORDERER_VERSION=<%= global.fabricVersion %> -PEER_IMAGE=quay.io/kfsoftware/fabric-peer -PEER_VERSION=2.4.1-v0.0.3 -# PEER_VERSION=<%= global.fabricVersion %> -CA_IMAGE=hyperledger/fabric-ca -CA_VERSION=<%= global.fabricCaVersion %> -LOGGING_LEVEL=<%= global.monitoring.loglevel %> - -CHAINCODES_BASE_DIR=<%= paths.chaincodesBaseDir %> - -ROOT_CA_ADMIN_NAME=admin -ROOT_CA_ADMIN_PASSWORD=adminpw - -<% orgs.forEach(function(org){ %> -<%= org.ca.caAdminNameVar %>=admin -<%= org.ca.caAdminPassVar %>=adminpw -<% }); %> \ No newline at end of file diff --git a/src/setup-k8s/templates/fabric-k8s/scripts/base-functions.sh b/src/setup-k8s/templates/fabric-k8s/scripts/base-functions.sh deleted file mode 100644 index 62093dda1..000000000 --- a/src/setup-k8s/templates/fabric-k8s/scripts/base-functions.sh +++ /dev/null @@ -1,194 +0,0 @@ -#!/usr/bin/env bash - -source "$FABLO_NETWORK_ROOT/fabric-k8s/scripts/util.sh" - - -deployPeer() { - - <% orgs.forEach((org) => { -%> - <% if(org.peers.length > 0 ) { -%> - - printItalics "Deploying <%= org.name %> CA" "U1F984" - kubectl hlf ca create --image="$CA_IMAGE" --version="$CA_VERSION" --storage-class="$STORAGE_CLASS" --capacity=2Gi --name=<%= org.name.toLowerCase() %>-<%= org.ca.prefix %> --enroll-id=<%= org.name.toLowerCase() %> --enroll-pw="$<%= org.ca.caAdminPassVar %>" - sleep 3 - - while [[ $(kubectl get pods -l release=<%= org.name.toLowerCase() %>-<%= org.ca.prefix %> -o 'jsonpath={..status.conditions[?(@.type=="Ready")].status}') != "True" ]]; do - sleep 5 - inputLog "waiting for CA" - done - - kubectl hlf ca register --name=<%= org.name.toLowerCase() %>-<%= org.ca.prefix %> --user=peer --secret="$<%= org.ca.caAdminPassVar %>" --type=peer --enroll-id <%= org.name.toLowerCase() %> --enroll-secret="$<%= org.ca.caAdminPassVar %>" --mspid <%= org.mspName %> - inputLog "registered <%= org.name %> -ca" - - printItalics "Deploying Peers" "U1F984" - sleep 10 - - <% orgs.forEach((org) => { org.peers.forEach((peer) => { %> - kubectl hlf peer create --statedb=<%= peer.db.type.toLowerCase() %> --version="$PEER_VERSION" --storage-class="$STORAGE_CLASS" --enroll-id=peer --mspid=<%= org.mspName %> \ - --enroll-pw="$<%= org.ca.caAdminPassVar %>" --capacity=5Gi --name=<%= peer.name %> --ca-name="<%= org.name.toLowerCase() %>-<%= org.ca.prefix %>.$NAMESPACE" --k8s-builder=true --external-service-builder=false - <% })}) %> - - <% } -%> - <% }) %> - - while [[ $(kubectl get pods -l app=hlf-peer --output=jsonpath='{.items[*].status.containerStatuses[0].ready}') != "true true" ]]; do - sleep 5 - inputLog "waiting for peer nodes to be ready" - done -} - -deployOrderer() { - - printItalics "Deploying Orderers" "U1F984" - - <% orgs.forEach((org) => { -%> - <% if(org.name == "Orderer") { -%> - kubectl hlf ca create --storage-class="$STORAGE_CLASS" --capacity=2Gi --name=<%= org.name.toLowerCase() %>-<%= org.ca.prefix %> --enroll-id=<%= org.name.toLowerCase() %> --enroll-pw="$<%= org.ca.caAdminPassVar %>" - while [[ $(kubectl get pods -l release=<%= org.name.toLowerCase() %>-<%= org.ca.prefix %> -o 'jsonpath={..status.conditions[?(@.type=="Ready")].status}') != "True" ]]; do - sleep 5 - inputLog "waiting for <%= org.name.toLowerCase() %>-<%= org.ca.prefix %> CA to be ready" "$RESETBG" - done - kubectl hlf ca register --name=<%= org.name.toLowerCase() %>-<%= org.ca.prefix %> --user="$<%= org.ca.caAdminNameVar %>" --secret="$<%= org.ca.caAdminPassVar %>" --type=orderer --enroll-id=<%= org.name.toLowerCase() %> --enroll-secret="$<%= org.ca.caAdminPassVar %>" --mspid <%= org.mspName %> && - inputLog "registered <%= org.name.toLowerCase() %>-<%= org.ca.prefix %>" - - kubectl hlf ordnode create --version="$ORDERER_VERSION" \ - --storage-class="$STORAGE_CLASS" --enroll-id="$<%= org.ca.caAdminNameVar %>" --mspid=<%= org.mspName %> \ - --enroll-pw="$<%= org.ca.caAdminPassVar %>" --capacity=2Gi --name=<%= org.name.toLowerCase() %>-node --ca-name="<%= org.name.toLowerCase() %>-<%= org.ca.prefix %>.$NAMESPACE" - while [[ $(kubectl get pods -l app=hlf-ordnode -o 'jsonpath={..status.conditions[?(@.type=="Ready")].status}') != "True" ]]; do - sleep 5 - inputLog "waiting for <%= org.name.toLowerCase() %> Node to be ready" - done - - kubectl hlf inspect --output "$CONFIG_DIR/ordservice.yaml" -o <%= org.mspName %> - kubectl hlf ca enroll --name=<%= org.name.toLowerCase() %>-<%= org.ca.prefix %> --user="$<%= org.ca.caAdminNameVar %>" --secret="$<%= org.ca.caAdminPassVar %>" --mspid <%= org.mspName %> --ca-name ca --output "$CONFIG_DIR/admin-ordservice.yaml" && - kubectl hlf utils adduser --userPath="$CONFIG_DIR/admin-ordservice.yaml" --config="$CONFIG_DIR/ordservice.yaml" --username="$<%= org.ca.caAdminNameVar %>" --mspid=<%= org.mspName %> - <% } -%> - <% }) %> -} - - -installChannels() { - - <% channels.forEach((channel) => { -%> - <% channel.orgs.forEach((org) => { -%> - printItalics "Creating '<%= channel.name %>' on /peer0" "U1F63B" - sleep 10 - kubectl hlf channel generate --output="$CONFIG_DIR/<%= channel.name %>.block" --name=<%= channel.name %> --organizations <%= org.mspName %> --ordererOrganizations <%= channel.ordererHead.orgMspName %> && - - kubectl hlf ca enroll --name=<%= channel.ordererHead.orgName.toLowerCase() %>-<%= org.ca.prefix %> --namespace="$NAMESPACE" --user="$<%= org.ca.caAdminNameVar %>" --secret="$<%= org.ca.caAdminPassVar %>" --mspid <%= channel.ordererHead.orgMspName %> --ca-name=tlsca --output "$CONFIG_DIR/admin-tls-ordservice.yaml" && - - sleep 10 - - kubectl hlf ordnode join --block="$CONFIG_DIR/<%= channel.name %>.block" --name=<%= channel.ordererHead.orgName.toLowerCase() %>-node --namespace="$NAMESPACE" --identity="$CONFIG_DIR/admin-tls-ordservice.yaml" - - kubectl hlf ca register --name=<%= org.name.toLowerCase() %>-<%= org.ca.prefix %> --user="$<%= org.ca.caAdminNameVar %>" --secret="$<%= org.ca.caAdminPassVar %>" --type=admin --enroll-id <%= org.name.toLowerCase() %> --enroll-secret="$<%= org.ca.caAdminPassVar %>" --mspid <%= org.mspName %> && - kubectl hlf ca enroll --name=<%= org.name.toLowerCase() %>-<%= org.ca.prefix %> --user="$<%= org.ca.caAdminNameVar %>" --secret="$<%= org.ca.caAdminPassVar %>" --mspid <%= org.mspName %> --ca-name ca --output "$CONFIG_DIR/peer-<%= org.name %>.yaml" && - kubectl hlf inspect --output "$CONFIG_DIR/<%= org.name %>.yaml" -o "<%= org.mspName %>" -o "<%= channel.ordererHead.orgMspName %>" && - kubectl hlf utils adduser --userPath="$CONFIG_DIR/peer-<%= org.name %>.yaml" --config="$CONFIG_DIR/<%= org.name %>.yaml" --username="$<%= org.ca.caAdminNameVar %>" --mspid=<%= org.mspName %> && - - sleep 10 - - <% org.peers.forEach((peer) => { %> - printItalics "Joining '<%= channel.name %>' on <%= org.name.toLowerCase() %>/<%= peer.name %>" "U1F638" - kubectl hlf channel join --name=<%= channel.name %> --config="$CONFIG_DIR/<%= org.name %>.yaml" --user="$<%= org.ca.caAdminNameVar %>" -p="<%= peer.name %>.$NAMESPACE" - <% }) %> - - # add anchor peers - <% org.anchorPeers.forEach((anchorpeer) => { %> - printItalics "Electing on <%= org.name.toLowerCase() %>/<%= anchorpeer.name %> as Anchor peer" "U1F638" - kubectl hlf channel addanchorpeer --channel=<%= channel.name %> --config="$CONFIG_DIR/<%= org.name %>.yaml" --user="$<%= org.ca.caAdminNameVar %>" --peer="<%= anchorpeer.name %>.$NAMESPACE" - <% }) %> - - <% }) -%> - <% }) -%> -} - - -installChaincodes() { - <% chaincodes.forEach((chaincode) => { -%> - - printItalics "Installing chaincodes...." "U1F618" - - <% orgs.forEach((org) => { org.peers.forEach((peer) => { %> - printItalics "Building chaincode <%= chaincode.name %>" "U1F618" - buildAndInstallChaincode "<%= chaincode.name %>" "<%= peer.name %>.$NAMESPACE" "<%= chaincode.lang %>" "$CHAINCODES_BASE_DIR/<%= chaincode.directory %>" "<%= chaincode.version %>" "$<%= org.ca.caAdminNameVar %>" "$CONFIG_DIR/<%= org.name %>.yaml" - - printItalics "Approving chaincode...." "U1F618" - approveChaincode "<%= chaincode.name %>" "<%= peer.name %>.$NAMESPACE" "<%= chaincode.version %>" "<%= chaincode.channel.name %>" "$<%= org.ca.caAdminNameVar %>" "$CONFIG_DIR/<%= org.name %>.yaml" "<%= org.mspName %>" - - printItalics "Committing chaincode '<%= chaincode.name %>' on channel '<%= chaincode.channel.name %>' " "U1F618" - - commitChaincode "<%= chaincode.name %>" "<%= peer.name %>.$NAMESPACE" "<%= chaincode.version %>" "<%= chaincode.channel.name %>" "$<%= org.ca.caAdminNameVar %>" "$CONFIG_DIR/<%= org.name %>.yaml" "<%= org.mspName %>" - <% })}) %> - <% }) %> -} - -destroyNetwork() { - kubectl delete fabricpeers.hlf.kungfusoftware.es --all-namespaces --all - kubectl delete fabriccas.hlf.kungfusoftware.es --all-namespaces --all - kubectl delete fabricorderernodes.hlf.kungfusoftware.es --all-namespaces --all - kubectl delete fabricchaincode.hlf.kungfusoftware.es --all-namespaces --all -} - -printHeadline() { - bold=$'\e[1m' - end=$'\e[0m' - - TEXT=$1 - EMOJI=$2 - printf "${bold}============ %b %s %b ==============${end}\n" "\\$EMOJI" "$TEXT" "\\$EMOJI" -} - -printItalics() { - italics=$'\e[3m' - end=$'\e[0m' - - TEXT=$1 - EMOJI=$2 - printf "${italics}==== %b %s %b ====${end}\n" "\\$EMOJI" "$TEXT" "\\$EMOJI" -} - -inputLog() { - end=$'\e[0m' - darkGray=$'\e[90m' - - echo "${darkGray} $1 ${end}" -} - -inputLogShort() { - end=$'\e[0m' - darkGray=$'\e[90m' - - echo "${darkGray} $1 ${end}" -} - -verifyKubernetesConnectivity() { - echo "Verifying kubectl-hlf installation..." - if ! [[ $(command -v kubectl-hlf) ]]; then - echo "Error: Fablo could not detect kubectl hlf plugin. Ensure you have installed: - - kubectl - https://kubernetes.io/docs/tasks/tools/ - - helm - https://helm.sh/docs/intro/install/ - - krew - https://krew.sigs.k8s.io/docs/user-guide/setup/install/ - - hlf-operator along with krew hlf plugin - https://github.com/hyperledger-labs/hlf-operator#install-kubernetes-operator" - exit 1 - else - echo " $(command -v kubectl-hlf)" - fi - - if [ "$(kubectl get pods -l=app.kubernetes.io/name=hlf-operator -o jsonpath='{.items}')" = "[]" ]; then - echo "Error: hlf-operator is not running. You can install it with: - helm install hlf-operator --version=1.6.0 kfs/hlf-operator" - exit 1 - fi - - echo "Verifying default kubernetes cluster" - if ! kubectl get ns default >/dev/null 2>&1; then - printf "No K8 cluster detected\n" >&2 - exit 1 - fi - - while [ "$(kubectl get pods -l=app.kubernetes.io/name=hlf-operator -o jsonpath='{.items[*].status.containerStatuses[0].ready}')" != "true" ]; do - sleep 5 - echo "$BLUE" "Waiting for Operator to be ready." "$RESETBG" - done -} diff --git a/src/setup-k8s/templates/fabric-k8s/scripts/base-help.sh b/src/setup-k8s/templates/fabric-k8s/scripts/base-help.sh deleted file mode 100644 index 8f18b590f..000000000 --- a/src/setup-k8s/templates/fabric-k8s/scripts/base-help.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -printHelp() { - echo "Fablo is powered by SoftwareMill" - - echo "" - echo "usage: ./fabric-k8.sh " - echo "" - - echo "Commands: " - echo "" - echo "./fabric-k8.sh up" - echo -e "\t Use for first run. Creates all needed artifacts (certs, genesis block) and starts network for the first time." - echo -e "\t After 'up' commands start/stop are used to manage network and rerun to rerun it" - echo "" - echo "./fabric-k8.sh down" - echo -e "\t Back to empty state - destorys created containers, prunes generated certificates, configs." - echo "" - echo "./fabric-k8.sh channel --help" - echo -e "\t Detailed help for channel management scripts." - echo "" -} diff --git a/src/setup-k8s/templates/fabric-k8s/scripts/chaincode-functions-v2.sh b/src/setup-k8s/templates/fabric-k8s/scripts/chaincode-functions-v2.sh deleted file mode 100644 index 8bd761996..000000000 --- a/src/setup-k8s/templates/fabric-k8s/scripts/chaincode-functions-v2.sh +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env bash - -buildAndInstallChaincode() { - local CHAINCODE_NAME="$1" - local PEER="$2" - local CHAINCODE_LANG="$3" - local CHAINCODE_DIR_PATH="$4" - local CHAINCODE_VERSION="$5" - local USER="$6" - local CONFIG="$7" - local CHANNEL_NAME="$8" - - local CHAINCODE_LABEL="${CHANNEL_NAME}_${CHAINCODE_NAME}_$CHAINCODE_VERSION" - - if [ -z "$CHAINCODE_NAME" ]; then - echo "Error: chaincode name is not provided" - exit 1 - fi - - if [ -z "$PEER" ]; then - echo "Error: peer number is not provided e.g 0, 1" - exit 1 - fi - - if [ -z "$CHAINCODE_LANG" ]; then - echo "Error: chaincode language is not provided" - exit 1 - fi - - if [ -z "$CHAINCODE_VERSION" ]; then - echo "Error: chaincode version is not provided e.g 1.0, 2.0" - exit 1 - fi - - kubectl hlf chaincode install --path="$CHAINCODE_DIR_PATH" \ - --config="$CONFIG" --language="$CHAINCODE_LANG" --label="$CHAINCODE_LABEL" --user="$USER" --peer="$PEER" -} - -approveChaincode() { - local CHAINCODE_NAME="$1" - local PEER="$2" - local CHAINCODE_VERSION="$3" - local CHANNEL_NAME="$4" - local USER="$5" - local CONFIG="$6" - local MSP="$7" - local SEQUENCE - - local CHAINCODE_LABEL="${CHANNEL_NAME}_${CHAINCODE_NAME}_$CHAINCODE_VERSION" - - SEQUENCE="$(kubectl hlf chaincode querycommitted --channel="$CHANNEL_NAME" --config="$CONFIG" --user="$USER" --peer="$PEER" 2>/dev/null | awk '{print $3}' | sed -n '2p' )" - SEQUENCE=$((SEQUENCE +1)) - - if [ -z "$CHAINCODE_NAME" ]; then - echo "Error: chaincode name is not provided" - exit 1 - fi - - if [ -z "$PEER" ]; then - echo "Error: peer number is not provided e.g 0, 1" - exit 1 - fi - - if [ -z "$CHAINCODE_VERSION" ]; then - echo "Error: chaincode version is not provided e.g 1.0, 2.0" - exit 1 - fi - - if [ -z "$CHANNEL_NAME" ]; then - echo "Error: channel name is not provided" - exit 1 - fi - - PACKAGE_ID="$(kubectl hlf chaincode queryinstalled --config="$CONFIG" --user="$USER" --peer="$PEER" | awk '{print $1}' | grep chaincode)" - - printItalics "Approving chaincode $CHAINCODE_NAME on $PEER" "U1F618" - - kubectl hlf chaincode approveformyorg --config="$CONFIG" --user="$USER" --peer="$PEER" \ - --package-id="$PACKAGE_ID" --version "$CHAINCODE_VERSION" --sequence "$SEQUENCE" --name="$CHAINCODE_NAME" \ - --policy="OR('$MSP.member')" --channel="$CHANNEL_NAME" -} - -commitChaincode() { - local CHAINCODE_NAME="$1" - local PEER="$2" - local CHAINCODE_VERSION="$3" - local CHANNEL_NAME="$4" - local USER="$5" - local CONFIG="$6" - local MSP="$7" - local SEQUENCE - - local CHAINCODE_LABEL="${CHANNEL_NAME}_${CHAINCODE_NAME}_$CHAINCODE_VERSION" - - SEQUENCE="$(kubectl hlf chaincode querycommitted --channel="$CHANNEL_NAME" --config="$CONFIG" --user="$USER" --peer="$PEER" 2>/dev/null | awk '{print $3}' | sed -n '2p' )" - SEQUENCE=$((SEQUENCE +1)) - - if [ -z "$CHAINCODE_NAME" ]; then - echo "Error: chaincode name is not provided" - exit 1 - fi - - if [ -z "$PEER" ]; then - echo "Error: peer number is not provided e.g 0, 1" - exit 1 - fi - - if [ -z "$CHAINCODE_VERSION" ]; then - echo "Error: chaincode version is not provided e.g 1.0, 2.0" - exit 1 - fi - - if [ -z "$CHANNEL_NAME" ]; then - echo "Error: channel name is not provided" - exit 1 - fi - - PACKAGE_ID="$(kubectl hlf chaincode queryinstalled --config="$CONFIG" --user="$USER" --peer="$PEER" | awk '{print $1}' | grep chaincode)" - - kubectl hlf chaincode commit --config="$CONFIG" --user="$USER" --mspid="$MSP" \ - --version "$CHAINCODE_VERSION" --sequence "$SEQUENCE" --name="$CHAINCODE_NAME" \ - --policy="OR('$MSP.member')" --channel="$CHANNEL_NAME" -} diff --git a/src/setup-k8s/templates/fabric-k8s/scripts/chaincode-functions.sh b/src/setup-k8s/templates/fabric-k8s/scripts/chaincode-functions.sh deleted file mode 100644 index 0d96d9071..000000000 --- a/src/setup-k8s/templates/fabric-k8s/scripts/chaincode-functions.sh +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env bash - -buildAndInstallChaincode() { - local CHAINCODE_NAME="$1" - local PEER="$2" - local CHAINCODE_LANG="$3" - local CHAINCODE_DIR_PATH="$4" - local CHAINCODE_VERSION="$5" - local CHAINCODE_LABEL="${CHAINCODE_NAME}_$CHAINCODE_VERSION" - local USER="$6" - local CONFIG="$7" - - if [ -z "$CHAINCODE_NAME" ]; then - echo "Error: chaincode name is not provided" - exit 1 - fi - - if [ -z "$PEER" ]; then - echo "Error: peer number is not provided e.g 0, 1" - exit 1 - fi - - if [ -z "$CHAINCODE_LANG" ]; then - echo "Error: chaincode language is not provided" - exit 1 - fi - - if [ -z "$CHAINCODE_VERSION" ]; then - echo "Error: chaincode version is not provided e.g 1.0, 2.0" - exit 1 - fi - - kubectl hlf chaincode install --path="$CHAINCODE_DIR_PATH" \ - --config="$CONFIG" --language="$CHAINCODE_LANG" --label="$CHAINCODE_LABEL" --user="$USER" --peer="$PEER" -} - -approveChaincode() { - local CHAINCODE_NAME="$1" - local PEER="$2" - local CHAINCODE_VERSION="$3" - local CHANNEL_NAME="$4" - local USER="$5" - local CONFIG="$6" - local MSP="$7" - local SEQUENCE - - SEQUENCE="$(kubectl hlf chaincode querycommitted --channel="$CHANNEL_NAME" --config="$CONFIG" --user="$USER" --peer="$PEER" | awk '{print $3}' | sed -n '2p' )" - SEQUENCE=$((SEQUENCE +1)) - - if [ -z "$CHAINCODE_NAME" ]; then - echo "Error: chaincode name is not provided" - exit 1 - fi - - if [ -z "$PEER" ]; then - echo "Error: peer number is not provided e.g 0, 1" - exit 1 - fi - - if [ -z "$CHAINCODE_VERSION" ]; then - echo "Error: chaincode version is not provided e.g 1.0, 2.0" - exit 1 - fi - - if [ -z "$CHANNEL_NAME" ]; then - echo "Error: channel name is not provided" - exit 1 - fi - - PACKAGE_ID="$(kubectl hlf chaincode queryinstalled --config="$CONFIG" --user="$USER" --peer="$PEER.$NAMESPACE" | awk '{print $1}' | grep chaincode)" - - printItalics "Approving chaincode $CHAINCODE_NAME on $PEER" "U1F618" - - kubectl hlf chaincode approveformyorg --config="$CONFIG" --user="$USER" --peer="$PEER" \ - --package-id="$PACKAGE_ID" --version "$CHAINCODE_VERSION" --sequence "$SEQUENCE" --name="$CHAINCODE_NAME" \ - --policy="OR('$MSP.member')" --channel="$CHANNEL_NAME" -} - -commitChaincode() { - - local CHAINCODE_NAME="$1" - local PEER=$2 - local CHAINCODE_VERSION="$3" - local CHANNEL_NAME="$4" - local USER="$5" - local CONFIG="$6" - local MSP="$7" - local SEQUENCE - - SEQUENCE="$(kubectl hlf chaincode querycommitted --channel="$CHANNEL_NAME" --config="$CONFIG" --user="$USER" --peer="$PEER" | awk '{print $3}' | sed -n '2p' )" - SEQUENCE=$((SEQUENCE +1)) - - if [ -z "$CHAINCODE_NAME" ]; then - echo "Error: chaincode name is not provided" - exit 1 - fi - - if [ -z "$PEER" ]; then - echo "Error: peer number is not provided e.g 0, 1" - exit 1 - fi - - if [ -z "$CHAINCODE_VERSION" ]; then - echo "Error: chaincode version is not provided e.g 1.0, 2.0" - exit 1 - fi - - if [ -z "$CHANNEL_NAME" ]; then - echo "Error: channel name is not provided" - exit 1 - fi - - PACKAGE_ID="$(kubectl hlf chaincode queryinstalled --config="$CONFIG" --user="$USER" --peer="$PEER.$NAMESPACE" | awk '{print $1}' | grep chaincode)" - - kubectl hlf chaincode commit --config="$CONFIG" --user="$USER" --mspid="$MSP" \ - --version "$CHAINCODE_VERSION" --sequence "$SEQUENCE" --name="$CHAINCODE_NAME" \ - --policy="OR('$MSP.member')" --channel="$CHANNEL_NAME" -} diff --git a/src/setup-k8s/templates/fabric-k8s/scripts/util.sh b/src/setup-k8s/templates/fabric-k8s/scripts/util.sh deleted file mode 100644 index 65fb79e2a..000000000 --- a/src/setup-k8s/templates/fabric-k8s/scripts/util.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/bash - -retry() { - local n=0 - local try=$1 - local cmd="${*:2}" - [[ $# -le 1 ]] - - until [[ $n -ge $try ]]; do - if [[ $cmd ]]; then - break - else - echo $(printf '\033[31m') "Previous command FAILED" - ((n++)) - echo $(printf '\033[34m') "RETRYING..." - sleep 1 - fi - done -} -retry "$*" From e39beb0bf993660322cbeebfcf7ff071bffce703 Mon Sep 17 00:00:00 2001 From: Pereowei Daniel Date: Fri, 17 Jul 2026 20:02:00 +0200 Subject: [PATCH 2/5] Add Kubernetes chaincode e2e smoke Signed-off-by: Pereowei Daniel --- .github/workflows/test-on-push.yml | 113 +++++++++--------- README.md | 4 + e2e-network/TEST_CASES.md | 41 +++---- e2e-network/k8s/expect-invoke-cli.sh | 42 +++---- e2e-network/k8s/expect-query-cli.sh | 35 ++++++ e2e-network/k8s/test-01-simple-k8s.sh | 92 +++++++------- ...hlf2-1org-1chaincode-k8s.json.test.ts.snap | 2 + src/setup-k8s/templates/fabric-k8s.sh | 2 + 8 files changed, 184 insertions(+), 147 deletions(-) mode change 100644 => 100755 e2e-network/k8s/expect-invoke-cli.sh create mode 100755 e2e-network/k8s/expect-query-cli.sh diff --git a/.github/workflows/test-on-push.yml b/.github/workflows/test-on-push.yml index 3fc89a5d9..7266e6413 100644 --- a/.github/workflows/test-on-push.yml +++ b/.github/workflows/test-on-push.yml @@ -44,60 +44,65 @@ jobs: - name: Lint run: npm run lint && ./lint.sh - # test-k8: - # needs: test-main - # runs-on: ubuntu-latest - # timeout-minutes: 30 - # steps: - # - # - name: Check out repository code - # uses: actions/checkout@v2 - # - # - name: install k8, hlf plugin and helm - # run: | - # curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.14.0/kind-linux-amd64 - # chmod +x ./kind - # sudo mv ./kind /usr/local/bin/kind - # kind create cluster --wait 5m - # curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 - # chmod 700 get_helm.sh - # ./get_helm.sh - # curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" - # chmod +x kubectl - # sudo mv ./kubectl /usr/local/bin/kubectl - # set -x; cd "$(mktemp -d)" && - # OS="$(uname | tr '[:upper:]' '[:lower:]')" && - # ARCH="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/\(arm\)\(64\)\?.*/\1\2/' -e 's/aarch64$/arm64/')" && - # KREW="krew-${OS}_${ARCH}" && - # curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/${KREW}.tar.gz" && - # tar zxvf "${KREW}.tar.gz" && - # ./"${KREW}" install krew - # export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH" - # kubectl krew install hlf - # - # if [ -f "~/.bashrc" ]; then - # echo 'export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc - # fi - # - # if [ -f "~/.zshrc" ]; then - # echo 'export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"' >> ~/.zshrc && source ~/.zshrc - # fi - # - # helm repo add kfs "https://kfsoftware.github.io/hlf-helm-charts" --force-update - # helm install hlf-operator --version=1.8.0 kfs/hlf-operator - # - # - name: Build Fablo - # run: | - # shellcheck --version && \ - # yamllint -v && \ - # npm install && \ - # npm run build && \ - # ./fablo-build.sh - # - # - name: fablo k8 tests - # run: | - # export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH" - # e2e-network/k8s/test-01-simple-k8s.sh + test-k8s: + needs: test-main + runs-on: ubuntu-latest + timeout-minutes: 60 + env: + KIND_CLUSTER: fablo-k8s-e2e + KIND_VERSION: v0.31.0 + FABRICOPS_VERSION: v0.1.2 + steps: + - name: Check out repository code + uses: actions/checkout@v2 + + - name: Downgrade Docker to v28 + uses: ./.github/actions/setup-docker-v28 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: "1.26.0" + + - name: Install kind + run: | + curl -Lo ./kind "https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-linux-amd64" + chmod +x ./kind + sudo mv ./kind /usr/local/bin/kind + kind create cluster --name "${KIND_CLUSTER}" --wait 5m + kubectl cluster-info + + - name: Install FabricOps + run: | + kubectl apply -f "https://github.com/dpereowei/FabricOps/releases/download/${FABRICOPS_VERSION}/install.yaml" + kubectl rollout status deployment/fabricops-controller-manager -n fabricops-system --timeout=180s + + - name: Install fabricopsctl + run: | + go install "github.com/dpereowei/fabricops/cmd/fabricopsctl@${FABRICOPS_VERSION}" + echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" + + - name: Build Fablo + run: | + shellcheck --version && \ + yamllint -v && \ + npm install && \ + ./fablo-build.sh + + - name: Test Kubernetes network + run: e2e-network/k8s/test-01-simple-k8s.sh + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: test-k8s + path: | + e2e-network/k8s/test-01-simple-k8s.sh.logs/* + e2e-network/k8s/test-01-simple-k8s.sh.tmpdir/fablo-target/**/* + + - name: Cleanup kind cluster + if: always() + run: kind delete cluster --name "${KIND_CLUSTER}" || true test-02-raft: needs: test-main diff --git a/README.md b/README.md index 05831ef78..3c19ee5b6 100644 --- a/README.md +++ b/README.md @@ -733,8 +733,12 @@ For chaincode operations, install the FabricOps CLI: ```bash go install github.com/dpereowei/fabricops/cmd/fabricopsctl@latest +export PATH="$(go env GOPATH)/bin:$PATH" ``` +If `go install` succeeds but `fabricopsctl` is still not found, make the PATH +export permanent in your shell profile, for example `~/.zshrc`. + ## Other features ### Connection profiles diff --git a/e2e-network/TEST_CASES.md b/e2e-network/TEST_CASES.md index 0d4c98dff..6308b4865 100644 --- a/e2e-network/TEST_CASES.md +++ b/e2e-network/TEST_CASES.md @@ -1,22 +1,23 @@ # Test cases -| Test case | 01-v2-simple | 02-v2-raft-2orgs | 03-v2-private-data | 04-v2-snapshot | test-05-v3 | test-06-v3-bft | -| -------------------------- | :-------------: | :--------------: | :----------------: | :----------------------: | :--------: | :------------: | -| Fabric versions | 2.5.12 | 2.4.3 | 2.5.12 | 2.5.12 | 3.1.0 | 3.1.0 | -| TLS | no | yes | no | yes | yes | yes | -| Channel capabilities | v2 | v2 | v2_5 | v2 | v3_0 | v3_0 | -| Consensus | solo | RAFT | solo | RAFT | RAFT | BFT | -| Orderer nodes | 1 | 3 | 1 | 1 | 3 | 4 | -| Organizations | 1 | 2 | 2 | 1 | 1 | 1 | -| CA database | SQLite | SQLite | SQLite | Postgres | SQLite | SQLite | -| Peer database | LevelDB | LevelDB | LevelDB | CouchDB | LevelDB | LevelDB | -| Peer count | 2 | 2, 2 | 2, 1 | 2 | 2 | 2 | -| Channels | 1 | 2 | 1 | 1 | 1 | 1 | -| Node chaincode | yes | yes | yes | yes | yes | yes | -| Node chaincode upgrade | no | yes | no | no | no | no | -| Node chaincode endorsement | OR | OR | OR, AND | default | OR | OR | -| Private data | no | no | yes | yes | no | no | -| Java chaincode | no | yes | no | no | no | no | -| Go chaincode | no | no | yes | no | yes | no | -| Tools | channel scripts | Fablo REST | - | Fablo REST, Explorer | - | - | -| Other Fablo commands | init, reset | stop, start | - | snapshot, prune, restore | - | - | +| Test case | 01-v2-simple | 02-v2-raft-2orgs | 03-v2-private-data | 04-v2-snapshot | test-05-v3 | test-06-v3-bft | k8s-01-simple | +| -------------------------- | :-------------: | :--------------: | :----------------: | :----------------------: | :--------: | :------------: | :-----------: | +| Fabric versions | 2.5.12 | 2.4.3 | 2.5.12 | 2.5.12 | 3.1.0 | 3.1.0 | 2.4.7 | +| Engine | Docker | Docker | Docker | Docker | Docker | Docker | Kubernetes | +| TLS | no | yes | no | yes | yes | yes | yes | +| Channel capabilities | v2 | v2 | v2_5 | v2 | v3_0 | v3_0 | v2 | +| Consensus | solo | RAFT | solo | RAFT | RAFT | BFT | RAFT | +| Orderer nodes | 1 | 3 | 1 | 1 | 3 | 4 | 1 | +| Organizations | 1 | 2 | 2 | 1 | 1 | 1 | 1 | +| CA database | SQLite | SQLite | SQLite | Postgres | SQLite | SQLite | - | +| Peer database | LevelDB | LevelDB | LevelDB | CouchDB | LevelDB | LevelDB | LevelDB | +| Peer count | 2 | 2, 2 | 2, 1 | 2 | 2 | 2 | 2 | +| Channels | 1 | 2 | 1 | 1 | 1 | 1 | 1 | +| Node chaincode | yes | yes | yes | yes | yes | yes | yes, CCaaS | +| Node chaincode upgrade | no | yes | no | no | no | no | no | +| Node chaincode endorsement | OR | OR | OR, AND | default | OR | OR | default | +| Private data | no | no | yes | yes | no | no | no | +| Java chaincode | no | yes | no | no | no | no | no | +| Go chaincode | no | no | yes | no | yes | no | no | +| Tools | channel scripts | Fablo REST | - | Fablo REST, Explorer | - | - | fabricopsctl | +| Other Fablo commands | init, reset | stop, start | - | snapshot, prune, restore | - | - | - | diff --git a/e2e-network/k8s/expect-invoke-cli.sh b/e2e-network/k8s/expect-invoke-cli.sh old mode 100644 new mode 100755 index 822f61327..bce2cc4d7 --- a/e2e-network/k8s/expect-invoke-cli.sh +++ b/e2e-network/k8s/expect-invoke-cli.sh @@ -2,42 +2,28 @@ set -e -user="$1" -peer="$2" -channel="$3" -chaincode="$4" -fcn="$5" -key="$6" -value="$7" -expected="$8" -config="$(find . -type f -iname 'org1.yaml')" - - - +peers="$1" +channel="$2" +chaincode="$3" +command="$4" +expected="$5" +transient="${6:-}" if [ -z "$expected" ]; then - echo "Usage: ./expect-invoke.sh [user] [peer] [chaincdoe] [channel] [fcn] [arg1] [arg2] [expected_substring]" + echo "Usage: ./expect-invoke.sh [peer[,peer]] [channel] [chaincode] [command] [expected_substring] [transient_data]" exit 1 fi -label="Invoke $channel/$peer" +label="Invoke $channel/$peers $command" echo "" echo "➜ testing: $label" -response="$( - kubectl hlf chaincode invoke \ - --config "$config" \ - --user "$user" \ - --peer "$peer" \ - --chaincode "$chaincode" \ - --channel "$channel" \ - --fcn "$fcn" \ - -a "$key" \ - ${value:+ -a "$value"} \ - - # shellcheck disable=SC2188 - 2>&1 -)" +args=(chaincode invoke "$peers" "$channel" "$chaincode" "$command") +if [ -n "$transient" ]; then + args+=("$transient") +fi + +response="$("$FABLO_HOME/fablo.sh" "${args[@]}")" echo "$response" diff --git a/e2e-network/k8s/expect-query-cli.sh b/e2e-network/k8s/expect-query-cli.sh new file mode 100755 index 000000000..8f3ebba9a --- /dev/null +++ b/e2e-network/k8s/expect-query-cli.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +set -e + +peers="$1" +channel="$2" +chaincode="$3" +command="$4" +expected="$5" +transient="${6:-}" + +if [ -z "$expected" ]; then + echo "Usage: ./expect-query.sh [peer[,peer]] [channel] [chaincode] [command] [expected_substring] [transient_data]" + exit 1 +fi + +label="Query $channel/$peers $command" +echo "" +echo "➜ testing: $label" + +args=(chaincode query "$peers" "$channel" "$chaincode" "$command") +if [ -n "$transient" ]; then + args+=("$transient") +fi + +response="$("$FABLO_HOME/fablo.sh" "${args[@]}")" + +echo "$response" + +if echo "$response" | grep -F "$expected"; then + echo "✅ ok (cli): $label" +else + echo "❌ failed (cli): $label | expected: $expected" + exit 1 +fi diff --git a/e2e-network/k8s/test-01-simple-k8s.sh b/e2e-network/k8s/test-01-simple-k8s.sh index c94b4782d..49d031a0f 100755 --- a/e2e-network/k8s/test-01-simple-k8s.sh +++ b/e2e-network/k8s/test-01-simple-k8s.sh @@ -5,73 +5,75 @@ set -e TEST_TMP="$(rm -rf "$0.tmpdir" && mkdir -p "$0.tmpdir" && (cd "$0.tmpdir" && pwd))" TEST_LOGS="$(mkdir -p "$0.logs" && (cd "$0.logs" && pwd))" FABLO_HOME="$TEST_TMP/../../.." +FABLO_K8S_CONFIG="$FABLO_HOME/samples/fablo-config-hlf2-1org-1chaincode-k8s.json" + +export FABLO_HOME +if command -v go >/dev/null 2>&1; then + export PATH="$(go env GOPATH)/bin:$PATH" +fi + +cleanup_done=false networkUp() { "$FABLO_HOME/fablo-build.sh" - (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" init kubernetes node) - (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" up) + (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" up "$FABLO_K8S_CONFIG") } dumpLogs() { - echo "Saving logs of $1 to $TEST_LOGS/$1.log" mkdir -p "$TEST_LOGS" - docker logs "$1" >"$TEST_LOGS/$1.log" 2>&1 + + kubectl get fabricnetworks.fabricops.io -A -o yaml >"$TEST_LOGS/fabricnetworks.yaml" 2>&1 || true + kubectl get pods,jobs,deployments,svc -A -o wide >"$TEST_LOGS/k8s-resources.txt" 2>&1 || true + kubectl get events -A --sort-by=.lastTimestamp >"$TEST_LOGS/k8s-events.txt" 2>&1 || true + + while read -r namespace pod; do + [ -n "$namespace" ] || continue + echo "Saving logs of $namespace/$pod to $TEST_LOGS/$namespace-$pod.log" + kubectl logs -n "$namespace" "$pod" --all-containers=true >"$TEST_LOGS/$namespace-$pod.log" 2>&1 || true + done < <(kubectl get pods -A --no-headers -o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name' 2>/dev/null || true) } networkDown() { - rm -rf "$TEST_LOGS" - (cd "$TEST_TMP" && "$(find . -type f -iname 'fabric-k8s.sh')" down) -} + if [ "$cleanup_done" = "true" ]; then + return + fi + cleanup_done=true -waitForContainer() { - sh "$TEST_TMP/../wait-for-container.sh" "$1" "$2" -} + dumpLogs -waitForChaincode() { - sh "$TEST_TMP/../wait-for-chaincode.sh" "$1" "$2" "$3" "$4" "$5" + if [ -d "$TEST_TMP/fablo-target" ]; then + (cd "$TEST_TMP" && "$FABLO_HOME/fablo.sh" down) || true + fi } expectInvoke() { - sh "$TEST_TMP/../expect-invoke-cli.sh" "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" + (cd "$TEST_TMP" && ../expect-invoke-cli.sh "$1" "$2" "$3" "$4" "$5" "${6:-}") +} + +expectQuery() { + (cd "$TEST_TMP" && ../expect-query-cli.sh "$1" "$2" "$3" "$4" "$5" "${6:-}") } trap networkDown EXIT trap 'networkDown ; echo "Test failed" ; exit 1' ERR SIGINT -# start the network networkUp -peer0="$(kubectl get pods | grep peer0 | tr -s ' ' | cut -d ':' -f 1 | cut -d ' ' -f 1 | head -n 1) peer" -peer1="$(kubectl get pods | grep peer1 | tr -s ' ' | cut -d ':' -f 1 | cut -d ' ' -f 1 | head -n 1) peer" -ca=$(kubectl get pods | grep org1-ca | tr -s ' ' | cut -d ':' -f 1 | cut -d ' ' -f 1) -orderer=$(kubectl get pods | grep orderer-node | tr -s ' ' | cut -d ':' -f 1 | cut -d ' ' -f 1) - -waitForContainer "$orderer" "Starting raft node as part of a new channel channel=my-channel1 node=1" -waitForContainer "$ca" "Listening on https://0.0.0.0:7054" -waitForContainer "$peer0" "Joining gossip network of channel my-channel1 with 1 organizations" -waitForContainer "$peer1" "Joining gossip network of channel my-channel1 with 1 organizations" -waitForContainer "$peer0" "Learning about the configured anchor peers of Org1MSP for channel my-channel1" -waitForContainer "$peer0" "Anchor peer.*with same endpoint, skipping connecting to myself" -waitForContainer "$peer0" "Membership view has changed. peers went online:" -waitForContainer "$peer1" "Learning about the configured anchor peers of Org1MSP for channel my-channel1" -waitForContainer "$peer1" "Membership view has changed. peers went online:" - - -#Test simple chaincode -expectInvoke "admin" "peer1.default" "my-channel1" "chaincode1" \ - "put" "[\"name\"]" "Willy Wonka" "{\"success\":\"OK\"}" -expectInvoke "admin" "peer1.default" "my-channel1" "chaincode1" \ - "get" "[\"name\"]" "" '{"success":"Willy Wonka"}' - +fabricopsctl wait -n default --timeout 20m fablo-network -# Reset and ensure the state is lost after reset -(cd "$TEST_TMP" && "$(find . -type f -iname 'fabric-k8s.sh')" reset) -waitForChaincode "admin" "peer0.default" "my-channel1" "chaincode1" "1.0" -waitForChaincode "admin" "peer1.default" "my-channel1" "chaincode1" "1.0" +# Test simple chaincode through both peers, one after the other. +expectInvoke "peer0.org1.example.com" "my-channel1" "chaincode1" \ + '{"Args":["KVContract:put", "name", "Willy Wonka"]}' \ + 'Chaincode invoke successful' +expectQuery "peer1.org1.example.com" "my-channel1" "chaincode1" \ + '{"Args":["KVContract:get", "name"]}' \ + 'Willy Wonka' -expectInvoke "admin" "peer1.default" "my-channel1" "chaincode1" \ - "get" "[\"name\"]" "" '{"error":"NOT_FOUND"}' +expectInvoke "peer1.org1.example.com" "my-channel1" "chaincode1" \ + '{"Args":["KVContract:put", "name", "Charlie Bucket"]}' \ + 'Chaincode invoke successful' +expectQuery "peer0.org1.example.com" "my-channel1" "chaincode1" \ + '{"Args":["KVContract:get", "name"]}' \ + 'Charlie Bucket' -# Put some data again -expectInvoke "admin" "peer1.default" "my-channel1" "chaincode1" \ - "put" "[\"name\"]" "James Bond" "{\"success\":\"OK\"}" +echo "Test passed ✅" diff --git a/e2e/__snapshots__/fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts.snap b/e2e/__snapshots__/fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts.snap index e0fd23052..4a74610e6 100644 --- a/e2e/__snapshots__/fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts.snap +++ b/e2e/__snapshots__/fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts.snap @@ -129,6 +129,8 @@ requireFabricOpsctl() { if ! command -v fabricopsctl >/dev/null 2>&1; then echo "Error: fabricopsctl is required for Fablo Kubernetes chaincode operations" echo "Install it with: go install github.com/dpereowei/fabricops/cmd/fabricopsctl@latest" + echo 'If go install succeeds but fabricopsctl is still not found, add Go binaries to PATH:' + echo ' export PATH="$(go env GOPATH)/bin:$PATH"' echo "Then rerun the command." exit 1 fi diff --git a/src/setup-k8s/templates/fabric-k8s.sh b/src/setup-k8s/templates/fabric-k8s.sh index 698865efc..b92caa4b4 100755 --- a/src/setup-k8s/templates/fabric-k8s.sh +++ b/src/setup-k8s/templates/fabric-k8s.sh @@ -60,6 +60,8 @@ requireFabricOpsctl() { if ! command -v fabricopsctl >/dev/null 2>&1; then echo "Error: fabricopsctl is required for Fablo Kubernetes chaincode operations" echo "Install it with: go install github.com/dpereowei/fabricops/cmd/fabricopsctl@latest" + echo 'If go install succeeds but fabricopsctl is still not found, add Go binaries to PATH:' + echo ' export PATH="$(go env GOPATH)/bin:$PATH"' echo "Then rerun the command." exit 1 fi From 52e6b9a39829c8232db2aa7a70264ad707a5be71 Mon Sep 17 00:00:00 2001 From: Pereowei Daniel Date: Fri, 17 Jul 2026 20:19:18 +0200 Subject: [PATCH 3/5] Stabilize generator tests after main merge Signed-off-by: Pereowei Daniel --- e2e/__snapshots__/extendConfig.test.ts.snap | 65 +++++++++----- ...hlf2-1org-1chaincode-k8s.json.test.ts.snap | 1 + jest.config.js | 11 +++ src/commands/export-network-topology/index.ts | 22 +++-- src/commands/extend-config/index.ts | 7 +- src/commands/setup-network/index.ts | 2 +- src/commands/validate/index.ts | 4 +- src/commands/version/index.ts | 1 - src/extend-config/extendChaincodesConfig.ts | 4 +- src/extend-config/extendGlobal.ts | 14 +-- src/list-versions/index.ts | 1 - src/setup-docker/index.ts | 88 ++++++++++--------- src/setup-k8s/templates/fabric-k8s.sh | 1 + src/utils/shellQuote.ts | 2 +- src/utils/templateUtils.ts | 1 - 15 files changed, 130 insertions(+), 94 deletions(-) diff --git a/e2e/__snapshots__/extendConfig.test.ts.snap b/e2e/__snapshots__/extendConfig.test.ts.snap index 6872f8dcb..9bf610c56 100644 --- a/e2e/__snapshots__/extendConfig.test.ts.snap +++ b/e2e/__snapshots__/extendConfig.test.ts.snap @@ -841,6 +841,8 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = { "chaincodes": [ { + "chaincodeMountPath": undefined, + "chaincodeStartCommand": undefined, "channel": { "instantiatingOrg": { "anchorPeers": [ @@ -932,7 +934,7 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = "name": "my-channel1", "ordererGroup": { "configtxOrdererDefaults": "Group1Defaults", - "consensus": "solo", + "consensus": "etcdraft", "genesisBlockName": "Group1Genesis.block", "hostingOrgs": [ "Orderer", @@ -941,7 +943,7 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = "ordererHead": { "address": "orderer0.group1.orderer.example.com", "adminPort": 7053, - "consensus": "solo", + "consensus": "etcdraft", "domain": "orderer.example.com", "fullAddress": "orderer0.group1.orderer.example.com:7030", "name": "orderer0.group1", @@ -953,7 +955,7 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = { "address": "orderer0.group1.orderer.example.com", "adminPort": 7053, - "consensus": "solo", + "consensus": "etcdraft", "domain": "orderer.example.com", "fullAddress": "orderer0.group1.orderer.example.com:7030", "name": "orderer0.group1", @@ -966,7 +968,7 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = { "address": "orderer0.group1.orderer.example.com", "adminPort": 7053, - "consensus": "solo", + "consensus": "etcdraft", "domain": "orderer.example.com", "fullAddress": "orderer0.group1.orderer.example.com:7030", "name": "orderer0.group1", @@ -980,7 +982,7 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = "ordererHead": { "address": "orderer0.group1.orderer.example.com", "adminPort": 7053, - "consensus": "solo", + "consensus": "etcdraft", "domain": "orderer.example.com", "fullAddress": "orderer0.group1.orderer.example.com:7030", "name": "orderer0.group1", @@ -1079,9 +1081,9 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = ], "profileName": "MyChannel1", }, - "directory": "chaincodes/chaincode-kv-node", + "directory": undefined, "endorsement": undefined, - "image": undefined, + "image": "ghcr.io/fablo-io/fablo-sample-kv-node-chaincode:2.2.0", "initRequired": false, "instantiatingOrg": { "anchorPeers": [ @@ -1170,9 +1172,26 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = "peersCount": 2, "tools": {}, }, - "lang": "node", + "lang": "ccaas", "name": "chaincode1", - "peerChaincodeInstances": [], + "peerChaincodeInstances": [ + { + "containerName": "ccaas_peer0.org1.example.com_my-channel1_chaincode1_0.0.1", + "orgDomain": "org1.example.com", + "orgName": "Org1", + "packageLabel": "Org1_my-channel1_chaincode1_0.0.1", + "peerAddress": "peer0.org1.example.com", + "port": 17041, + }, + { + "containerName": "ccaas_peer1.org1.example.com_my-channel1_chaincode1_0.0.1", + "orgDomain": "org1.example.com", + "orgName": "Org1", + "packageLabel": "Org1_my-channel1_chaincode1_0.0.1", + "peerAddress": "peer1.org1.example.com", + "port": 17042, + }, + ], "privateData": [], "privateDataConfigFile": undefined, "version": "0.0.1", @@ -1270,7 +1289,7 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = "name": "my-channel1", "ordererGroup": { "configtxOrdererDefaults": "Group1Defaults", - "consensus": "solo", + "consensus": "etcdraft", "genesisBlockName": "Group1Genesis.block", "hostingOrgs": [ "Orderer", @@ -1279,7 +1298,7 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = "ordererHead": { "address": "orderer0.group1.orderer.example.com", "adminPort": 7053, - "consensus": "solo", + "consensus": "etcdraft", "domain": "orderer.example.com", "fullAddress": "orderer0.group1.orderer.example.com:7030", "name": "orderer0.group1", @@ -1291,7 +1310,7 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = { "address": "orderer0.group1.orderer.example.com", "adminPort": 7053, - "consensus": "solo", + "consensus": "etcdraft", "domain": "orderer.example.com", "fullAddress": "orderer0.group1.orderer.example.com:7030", "name": "orderer0.group1", @@ -1304,7 +1323,7 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = { "address": "orderer0.group1.orderer.example.com", "adminPort": 7053, - "consensus": "solo", + "consensus": "etcdraft", "domain": "orderer.example.com", "fullAddress": "orderer0.group1.orderer.example.com:7030", "name": "orderer0.group1", @@ -1318,7 +1337,7 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = "ordererHead": { "address": "orderer0.group1.orderer.example.com", "adminPort": 7053, - "consensus": "solo", + "consensus": "etcdraft", "domain": "orderer.example.com", "fullAddress": "orderer0.group1.orderer.example.com:7030", "name": "orderer0.group1", @@ -1449,7 +1468,7 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = "fabloConfig": "", }, "peerImage": "hyperledger/fabric-peer:2.4.7", - "tls": false, + "tls": true, "tools": {}, "toolsImage": "hyperledger/fabric-tools:2.4.7", }, @@ -1461,7 +1480,7 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = { "address": "orderer0.group1.orderer.example.com", "adminPort": 7053, - "consensus": "solo", + "consensus": "etcdraft", "domain": "orderer.example.com", "fullAddress": "orderer0.group1.orderer.example.com:7030", "name": "orderer0.group1", @@ -1473,7 +1492,7 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = "ordererGroups": [ { "configtxOrdererDefaults": "Group1Defaults", - "consensus": "solo", + "consensus": "etcdraft", "genesisBlockName": "Group1Genesis.block", "hostingOrgs": [ "Orderer", @@ -1482,7 +1501,7 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = "ordererHead": { "address": "orderer0.group1.orderer.example.com", "adminPort": 7053, - "consensus": "solo", + "consensus": "etcdraft", "domain": "orderer.example.com", "fullAddress": "orderer0.group1.orderer.example.com:7030", "name": "orderer0.group1", @@ -1494,7 +1513,7 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = { "address": "orderer0.group1.orderer.example.com", "adminPort": 7053, - "consensus": "solo", + "consensus": "etcdraft", "domain": "orderer.example.com", "fullAddress": "orderer0.group1.orderer.example.com:7030", "name": "orderer0.group1", @@ -1507,7 +1526,7 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = { "address": "orderer0.group1.orderer.example.com", "adminPort": 7053, - "consensus": "solo", + "consensus": "etcdraft", "domain": "orderer.example.com", "fullAddress": "orderer0.group1.orderer.example.com:7030", "name": "orderer0.group1", @@ -1544,7 +1563,7 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = "ordererGroups": [ { "configtxOrdererDefaults": "Group1Defaults", - "consensus": "solo", + "consensus": "etcdraft", "genesisBlockName": "Group1Genesis.block", "hostingOrgs": [ "Orderer", @@ -1554,7 +1573,7 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = { "address": "orderer0.group1.orderer.example.com", "adminPort": 7053, - "consensus": "solo", + "consensus": "etcdraft", "domain": "orderer.example.com", "fullAddress": "orderer0.group1.orderer.example.com:7030", "name": "orderer0.group1", @@ -1567,7 +1586,7 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = { "address": "orderer0.group1.orderer.example.com", "adminPort": 7053, - "consensus": "solo", + "consensus": "etcdraft", "domain": "orderer.example.com", "fullAddress": "orderer0.group1.orderer.example.com:7030", "name": "orderer0.group1", diff --git a/e2e/__snapshots__/fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts.snap b/e2e/__snapshots__/fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts.snap index 4a74610e6..9f716631f 100644 --- a/e2e/__snapshots__/fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts.snap +++ b/e2e/__snapshots__/fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts.snap @@ -130,6 +130,7 @@ requireFabricOpsctl() { echo "Error: fabricopsctl is required for Fablo Kubernetes chaincode operations" echo "Install it with: go install github.com/dpereowei/fabricops/cmd/fabricopsctl@latest" echo 'If go install succeeds but fabricopsctl is still not found, add Go binaries to PATH:' + # shellcheck disable=SC2016 echo ' export PATH="$(go env GOPATH)/bin:$PATH"' echo "Then rerun the command." exit 1 diff --git a/jest.config.js b/jest.config.js index 08e581c52..7b2f004d5 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,3 +1,14 @@ module.exports = { + modulePathIgnorePatterns: [ + "/dist/", + "/e2e/__tmp__/", + "/e2e-network/.*\\.tmpdir/", + ], preset: "ts-jest", + testPathIgnorePatterns: [ + "/node_modules/", + "/dist/", + "/e2e/__tmp__/", + "/e2e-network/.*\\.tmpdir/", + ], }; diff --git a/src/commands/export-network-topology/index.ts b/src/commands/export-network-topology/index.ts index fd32c3b5b..88c0e655b 100644 --- a/src/commands/export-network-topology/index.ts +++ b/src/commands/export-network-topology/index.ts @@ -1,4 +1,4 @@ -import { Args, Command } from '@oclif/core' +import { Args, Command } from "@oclif/core"; import parseFabloConfig from "../../utils/parseFabloConfig"; import extendConfig from "../../extend-config/extendConfig"; import { generateMermaidDiagram } from "../../export-network-topology/generateMermaidDiagram"; @@ -7,9 +7,9 @@ import * as fs from "fs"; import * as path from "path"; export default class ExportNetworkTopology extends Command { - static override description = 'exports the network topology to a Mermaid file' - private fabloConfigPath: string = ""; - private outputFile: string = ""; + static override description = "exports the network topology to a Mermaid file"; + private fabloConfigPath = ""; + private outputFile = ""; static override args = { config: Args.string({ @@ -20,15 +20,14 @@ export default class ExportNetworkTopology extends Command { description: "(optional) Path to the output Mermaid file. Defaults to network-topology.mmd", required: false, }), - - } + }; async writing(): Promise { try { if (!fs.existsSync(this.fabloConfigPath)) { throw new Error(`Configuration file not found: ${this.fabloConfigPath}`); } - const configContent = fs.readFileSync(this.fabloConfigPath, 'utf-8'); + const configContent = fs.readFileSync(this.fabloConfigPath, "utf-8"); if (!configContent) { throw new Error(`Failed to read configuration file: ${this.fabloConfigPath}`); } @@ -42,7 +41,6 @@ export default class ExportNetworkTopology extends Command { } fs.writeFileSync(this.outputFile, mermaidDiagram); this.log(`✅ Network topology exported to ${this.outputFile}`); - } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "An unknown error occurred"; this.log(`❌ Error: ${errorMessage}`); @@ -51,20 +49,20 @@ export default class ExportNetworkTopology extends Command { } public async run(): Promise { - const { args } = await this.parse(ExportNetworkTopology) + const { args } = await this.parse(ExportNetworkTopology); let configArg = args.config; let outputArg = args.output; if (configArg && !outputArg) { - if (configArg.endsWith('.mmd')) { + if (configArg.endsWith(".mmd")) { outputArg = configArg; configArg = undefined; } } - const finalConfig = configArg || 'fablo-config.json'; - const finalOutput = outputArg || 'network-topology.mmd'; + const finalConfig = configArg || "fablo-config.json"; + const finalOutput = outputArg || "network-topology.mmd"; this.fabloConfigPath = path.isAbsolute(finalConfig) ? finalConfig : path.resolve(process.cwd(), finalConfig); this.outputFile = path.isAbsolute(finalOutput) ? finalOutput : path.resolve(process.cwd(), finalOutput); diff --git a/src/commands/extend-config/index.ts b/src/commands/extend-config/index.ts index a36898e55..cd07086db 100644 --- a/src/commands/extend-config/index.ts +++ b/src/commands/extend-config/index.ts @@ -6,8 +6,6 @@ import parseFabloConfig from "../../utils/parseFabloConfig"; import extendConfig from "../../extend-config/extendConfig"; import { getNetworkCapabilities } from "../../extend-config/extendGlobal"; - - export default class ExtendConfig extends Command { static override description = "Reads a Fablo config file, extends it, and prints the result"; @@ -18,7 +16,7 @@ export default class ExtendConfig extends Command { required: false, }), }; - async writing (args: { config?: string }): Promise { + async writing(args: { config?: string }): Promise { const configPath = args?.config ?? "fablo-config.json"; const fabloConfigPath = path.isAbsolute(configPath) ? configPath : path.join(process.cwd(), configPath); const json = parseFabloConfig(fs.readFileSync(fabloConfigPath).toString()); @@ -28,10 +26,7 @@ export default class ExtendConfig extends Command { public async run(): Promise { const { args } = await this.parse(ExtendConfig); await this.writing(args); - } - } export { extendConfig, getNetworkCapabilities }; - diff --git a/src/commands/setup-network/index.ts b/src/commands/setup-network/index.ts index 011bf8fb8..b1a65fdde 100644 --- a/src/commands/setup-network/index.ts +++ b/src/commands/setup-network/index.ts @@ -40,4 +40,4 @@ export default class SetupNetwork extends Command { this.error(`Error setting up network: ${(error as Error).message}`); } } -} \ No newline at end of file +} diff --git a/src/commands/validate/index.ts b/src/commands/validate/index.ts index 3e12aa7d0..3b3eed867 100644 --- a/src/commands/validate/index.ts +++ b/src/commands/validate/index.ts @@ -382,7 +382,9 @@ export default class Validate extends Command { const channel = channels.find((ch) => ch.name === chaincode.channel); if (!channel) return []; - return channel.orgs.map((org) => createPackageLabel(org.name, chaincode.channel, chaincode.name, chaincode.version)); + return channel.orgs.map((org) => + createPackageLabel(org.name, chaincode.channel, chaincode.name, chaincode.version), + ); }); const duplicatedPackageLabels = findDuplicatedItems(packageLabels.map((label) => `_${label}`)); diff --git a/src/commands/version/index.ts b/src/commands/version/index.ts index 410fc0724..e4f9af211 100644 --- a/src/commands/version/index.ts +++ b/src/commands/version/index.ts @@ -21,4 +21,3 @@ export default class Version extends Command { } } } - diff --git a/src/extend-config/extendChaincodesConfig.ts b/src/extend-config/extendChaincodesConfig.ts index 472145235..f808ef534 100644 --- a/src/extend-config/extendChaincodesConfig.ts +++ b/src/extend-config/extendChaincodesConfig.ts @@ -61,7 +61,9 @@ const checkUniquePackageLabels = (chaincodes: ChaincodeConfig[]): void => { packageLabels.forEach((packageLabel) => { if (seen.has(packageLabel)) { - throw new Error(`Duplicate chaincode package label '${packageLabel}' found. Chaincode package labels must be unique.`); + throw new Error( + `Duplicate chaincode package label '${packageLabel}' found. Chaincode package labels must be unique.`, + ); } seen.add(packageLabel); }); diff --git a/src/extend-config/extendGlobal.ts b/src/extend-config/extendGlobal.ts index cdd61d15f..b1f142e26 100644 --- a/src/extend-config/extendGlobal.ts +++ b/src/extend-config/extendGlobal.ts @@ -100,13 +100,15 @@ const extendGlobal = (globalJson: GlobalJson): Global => { const explorer = !globalJson?.tools?.explorer ? {} : { - explorer: { address: "explorer.example.com", port: 7010 }, - }; + explorer: { address: "explorer.example.com", port: 7010 }, + }; - const paths = process.env.FABLO_CONFIG ? getPathsFromEnv() : { - fabloConfig: "fablo-config.json", - chaincodesBaseDir: ".", - }; + const paths = process.env.FABLO_CONFIG + ? getPathsFromEnv() + : { + fabloConfig: "fablo-config.json", + chaincodesBaseDir: ".", + }; return { ...globalJsonRest, diff --git a/src/list-versions/index.ts b/src/list-versions/index.ts index c63348e75..6eefde814 100644 --- a/src/list-versions/index.ts +++ b/src/list-versions/index.ts @@ -1,4 +1,3 @@ - import * as config from "../config"; import * as repositoryUtils from "../repositoryUtils"; diff --git a/src/setup-docker/index.ts b/src/setup-docker/index.ts index 30ee7f35c..475b28c24 100644 --- a/src/setup-docker/index.ts +++ b/src/setup-docker/index.ts @@ -133,9 +133,15 @@ export default class SetupDocker extends Command { for (const org of orgsTransformed) { const connectionProfile = createConnectionProfile(global, org, orgsTransformed, channels, ordererGroups); const orgName = org.name.toLowerCase(); - const jsonPath = getDestinationPath(this.outputDir, `fabric-config/connection-profiles/connection-profile-${orgName}.json`); - const yamlPath = getDestinationPath(this.outputDir, `fabric-config/connection-profiles/connection-profile-${orgName}.yaml`); - + const jsonPath = getDestinationPath( + this.outputDir, + `fabric-config/connection-profiles/connection-profile-${orgName}.json`, + ); + const yamlPath = getDestinationPath( + this.outputDir, + `fabric-config/connection-profiles/connection-profile-${orgName}.yaml`, + ); + await fs.ensureDir(path.dirname(jsonPath)); await fs.writeJSON(jsonPath, connectionProfile, { spaces: 2 }); await fs.writeFile(yamlPath, yaml.dump(connectionProfile), "utf-8"); @@ -153,46 +159,49 @@ export default class SetupDocker extends Command { } } + async _createExplorerMaterial( + global: Global, + orgsTransformed: OrgConfig[], + channels: ChannelConfig[], + ): Promise { + try { + // Create explorer directory first + const explorerDir = getDestinationPath(this.outputDir, "fabric-config/explorer"); + await fs.ensureDir(explorerDir); + + if (!explorerDir) { + this.log("Error: Explorer directory path is undefined"); + return; + } - async _createExplorerMaterial(global: Global, orgsTransformed: OrgConfig[], channels: ChannelConfig[]): Promise { - try { - // Create explorer directory first - const explorerDir = getDestinationPath(this.outputDir, "fabric-config/explorer"); - await fs.ensureDir(explorerDir); - - if(!explorerDir){ - this.log("Error: Explorer directory path is undefined"); - return; - } - - const orgs = orgsTransformed.filter((o) => o.anchorPeers.length > 0); - const orgWithChannels = pairOrgWithChannels(orgs, channels); - - for (const p of orgWithChannels) { - if (global.tools?.explorer !== undefined || p.org.tools?.explorer !== undefined) { - const connectionProfile = createExplorerConnectionProfile(global, p, orgsTransformed); - const orgName = p.org.name.toLowerCase(); - const connectionProfilePath = getDestinationPath( - this.outputDir, - `fabric-config/explorer/connection-profile-${orgName}.json`, - ); - - const configPath = getDestinationPath(this.outputDir, `fabric-config/explorer/config-${orgName}.json`); - await fs.ensureDir(path.dirname(connectionProfilePath)); - await fs.writeJSON(connectionProfilePath, connectionProfile, { spaces: 2 }); - await fs.writeJSON(configPath, createExplorerConfig([p.org]), { spaces: 2 }); + const orgs = orgsTransformed.filter((o) => o.anchorPeers.length > 0); + const orgWithChannels = pairOrgWithChannels(orgs, channels); + + for (const p of orgWithChannels) { + if (global.tools?.explorer !== undefined || p.org.tools?.explorer !== undefined) { + const connectionProfile = createExplorerConnectionProfile(global, p, orgsTransformed); + const orgName = p.org.name.toLowerCase(); + const connectionProfilePath = getDestinationPath( + this.outputDir, + `fabric-config/explorer/connection-profile-${orgName}.json`, + ); + + const configPath = getDestinationPath(this.outputDir, `fabric-config/explorer/config-${orgName}.json`); + await fs.ensureDir(path.dirname(connectionProfilePath)); + await fs.writeJSON(connectionProfilePath, connectionProfile, { spaces: 2 }); + await fs.writeJSON(configPath, createExplorerConfig([p.org]), { spaces: 2 }); + } } - } - const globalConfigPath = getDestinationPath(this.outputDir, "fabric-config/explorer/config-global.json"); - if (!globalConfigPath) { - this.error("Error: Global config path is undefined"); + const globalConfigPath = getDestinationPath(this.outputDir, "fabric-config/explorer/config-global.json"); + if (!globalConfigPath) { + this.error("Error: Global config path is undefined"); + } + await fs.writeJSON(globalConfigPath, createExplorerConfig(orgWithChannels.map((p) => p.org)), { spaces: 2 }); + } catch (error: any) { + this.error("Error creating explorer material: " + error.message); } - await fs.writeJSON(globalConfigPath, createExplorerConfig(orgWithChannels.map((p) => p.org)), { spaces: 2 }); - } catch (error: any) { - this.error("Error creating explorer material: " + error.message); } -} async _copyDockerComposeEnv(global: Global, orgsTransformed: OrgConfig[], composeNetworkName: string): Promise { const settings = { composeNetworkName, @@ -233,7 +242,7 @@ export default class SetupDocker extends Command { await renderTemplate(templatePath, destPath, { ...(config as unknown as Record), shellQuote, - }) + }); } } @@ -298,4 +307,3 @@ export default class SetupDocker extends Command { } } } - diff --git a/src/setup-k8s/templates/fabric-k8s.sh b/src/setup-k8s/templates/fabric-k8s.sh index b92caa4b4..a2c6a59e9 100755 --- a/src/setup-k8s/templates/fabric-k8s.sh +++ b/src/setup-k8s/templates/fabric-k8s.sh @@ -61,6 +61,7 @@ requireFabricOpsctl() { echo "Error: fabricopsctl is required for Fablo Kubernetes chaincode operations" echo "Install it with: go install github.com/dpereowei/fabricops/cmd/fabricopsctl@latest" echo 'If go install succeeds but fabricopsctl is still not found, add Go binaries to PATH:' + # shellcheck disable=SC2016 echo ' export PATH="$(go env GOPATH)/bin:$PATH"' echo "Then rerun the command." exit 1 diff --git a/src/utils/shellQuote.ts b/src/utils/shellQuote.ts index 02a4ce0f1..a4178f6dd 100644 --- a/src/utils/shellQuote.ts +++ b/src/utils/shellQuote.ts @@ -4,4 +4,4 @@ export function shellQuote(value: unknown): string { .replace(/"/g, '\\"') .replace(/\$/g, "\\$") .replace(/`/g, "\\`")}"`; -} \ No newline at end of file +} diff --git a/src/utils/templateUtils.ts b/src/utils/templateUtils.ts index 5437d607f..af26bdac9 100644 --- a/src/utils/templateUtils.ts +++ b/src/utils/templateUtils.ts @@ -50,4 +50,3 @@ export function getTemplatePath(templatesDir: string, templateFile: string): str export function getDestinationPath(outputDir: string, destinationFile: string): string { return path.join(outputDir, destinationFile); } - From c3818e2b84dd54d5d4de083ed7acfd5865171342 Mon Sep 17 00:00:00 2001 From: Pereowei Daniel Date: Thu, 13 Aug 2026 21:26:06 +0200 Subject: [PATCH 4/5] Update FabricOps references to Lab repo Signed-off-by: Pereowei Daniel --- .github/workflows/test-on-push.yml | 6 +++--- README.md | 2 +- .../fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts.snap | 4 ++-- src/setup-k8s/templates/fabric-k8s.sh | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test-on-push.yml b/.github/workflows/test-on-push.yml index 7266e6413..80405f6a1 100644 --- a/.github/workflows/test-on-push.yml +++ b/.github/workflows/test-on-push.yml @@ -51,7 +51,7 @@ jobs: env: KIND_CLUSTER: fablo-k8s-e2e KIND_VERSION: v0.31.0 - FABRICOPS_VERSION: v0.1.2 + FABRICOPS_VERSION: v0.2.0 steps: - name: Check out repository code uses: actions/checkout@v2 @@ -74,12 +74,12 @@ jobs: - name: Install FabricOps run: | - kubectl apply -f "https://github.com/dpereowei/FabricOps/releases/download/${FABRICOPS_VERSION}/install.yaml" + kubectl apply -f "https://github.com/LF-Decentralized-Trust-labs/FabricOps/releases/download/${FABRICOPS_VERSION}/install.yaml" kubectl rollout status deployment/fabricops-controller-manager -n fabricops-system --timeout=180s - name: Install fabricopsctl run: | - go install "github.com/dpereowei/fabricops/cmd/fabricopsctl@${FABRICOPS_VERSION}" + go install "github.com/LF-Decentralized-Trust-labs/FabricOps/cmd/fabricopsctl@${FABRICOPS_VERSION}" echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" - name: Build Fablo diff --git a/README.md b/README.md index 3c19ee5b6..eafd04523 100644 --- a/README.md +++ b/README.md @@ -732,7 +732,7 @@ applying the manifest and points to the FabricOps installation instructions. For chaincode operations, install the FabricOps CLI: ```bash -go install github.com/dpereowei/fabricops/cmd/fabricopsctl@latest +go install github.com/LF-Decentralized-Trust-labs/FabricOps/cmd/fabricopsctl@latest export PATH="$(go env GOPATH)/bin:$PATH" ``` diff --git a/e2e/__snapshots__/fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts.snap b/e2e/__snapshots__/fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts.snap index 9f716631f..8cde6e1db 100644 --- a/e2e/__snapshots__/fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts.snap +++ b/e2e/__snapshots__/fablo-config-hlf2-1org-1chaincode-k8s.json.test.ts.snap @@ -78,7 +78,7 @@ NAMESPACE="\${NAMESPACE:-default}" FABRICOPS_NAMESPACE="\${FABRICOPS_NAMESPACE:-fabricops-system}" FABRICOPS_WAIT_TIMEOUT="\${FABRICOPS_WAIT_TIMEOUT:-20m}" FABRICOPS_OPERATION_TIMEOUT="\${FABRICOPS_OPERATION_TIMEOUT:-180s}" -FABRICOPS_INSTALL_URL="https://github.com/dpereowei/FabricOps#installation" +FABRICOPS_INSTALL_URL="https://github.com/LF-Decentralized-Trust-labs/FabricOps#installation" KUBECTL_ARGS=() FABRICOPSCTL_ARGS=() @@ -128,7 +128,7 @@ fabricopsctlCmd() { requireFabricOpsctl() { if ! command -v fabricopsctl >/dev/null 2>&1; then echo "Error: fabricopsctl is required for Fablo Kubernetes chaincode operations" - echo "Install it with: go install github.com/dpereowei/fabricops/cmd/fabricopsctl@latest" + echo "Install it with: go install github.com/LF-Decentralized-Trust-labs/FabricOps/cmd/fabricopsctl@latest" echo 'If go install succeeds but fabricopsctl is still not found, add Go binaries to PATH:' # shellcheck disable=SC2016 echo ' export PATH="$(go env GOPATH)/bin:$PATH"' diff --git a/src/setup-k8s/templates/fabric-k8s.sh b/src/setup-k8s/templates/fabric-k8s.sh index a2c6a59e9..541bcb820 100755 --- a/src/setup-k8s/templates/fabric-k8s.sh +++ b/src/setup-k8s/templates/fabric-k8s.sh @@ -9,7 +9,7 @@ NAMESPACE="${NAMESPACE:-default}" FABRICOPS_NAMESPACE="${FABRICOPS_NAMESPACE:-fabricops-system}" FABRICOPS_WAIT_TIMEOUT="${FABRICOPS_WAIT_TIMEOUT:-20m}" FABRICOPS_OPERATION_TIMEOUT="${FABRICOPS_OPERATION_TIMEOUT:-180s}" -FABRICOPS_INSTALL_URL="https://github.com/dpereowei/FabricOps#installation" +FABRICOPS_INSTALL_URL="https://github.com/LF-Decentralized-Trust-labs/FabricOps#installation" KUBECTL_ARGS=() FABRICOPSCTL_ARGS=() @@ -59,7 +59,7 @@ fabricopsctlCmd() { requireFabricOpsctl() { if ! command -v fabricopsctl >/dev/null 2>&1; then echo "Error: fabricopsctl is required for Fablo Kubernetes chaincode operations" - echo "Install it with: go install github.com/dpereowei/fabricops/cmd/fabricopsctl@latest" + echo "Install it with: go install github.com/LF-Decentralized-Trust-labs/FabricOps/cmd/fabricopsctl@latest" echo 'If go install succeeds but fabricopsctl is still not found, add Go binaries to PATH:' # shellcheck disable=SC2016 echo ' export PATH="$(go env GOPATH)/bin:$PATH"' From 5f0e1c71c114a84d7491e89957d511f10cbc0eb4 Mon Sep 17 00:00:00 2001 From: Pereowei Daniel Date: Thu, 13 Aug 2026 22:11:30 +0200 Subject: [PATCH 5/5] Update Kubernetes extend config snapshot Signed-off-by: Pereowei Daniel --- e2e/__snapshots__/extendConfig.test.ts.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/__snapshots__/extendConfig.test.ts.snap b/e2e/__snapshots__/extendConfig.test.ts.snap index cfb3df201..36be8eb5f 100644 --- a/e2e/__snapshots__/extendConfig.test.ts.snap +++ b/e2e/__snapshots__/extendConfig.test.ts.snap @@ -1470,7 +1470,7 @@ exports[`extend config samples/fablo-config-hlf2-1org-1chaincode-k8s.json 1`] = }, "peerImage": "hyperledger/fabric-peer:2.4.7", "provider": "fabric", - "tls": false, + "tls": true, "tools": {}, "toolsImage": "hyperledger/fabric-tools:2.4.7", },