From e5c27794b16ec8114ef9560a3389ad724c49ee98 Mon Sep 17 00:00:00 2001 From: sangwaboi Date: Wed, 21 May 2025 16:54:53 +0530 Subject: [PATCH 1/2] feat: add Apple Silicon (M1/M2/M3) support - Update Dockerfiles for ARM64, fix build dependencies, use named volumes Signed-off-by: Vishvendra --- Makefile | 33 +++++++++++-------- .../docker-compose.dev.yml | 12 +++---- .../agent/docker-rest-agent/Dockerfile.in | 28 ++++++++++++---- .../docker/common/api-engine/Dockerfile.in | 30 ++++++++++++++--- 4 files changed, 72 insertions(+), 31 deletions(-) diff --git a/Makefile b/Makefile index cdf03de6e..c00ab4576 100755 --- a/Makefile +++ b/Makefile @@ -29,11 +29,25 @@ YELLOW := $(shell tput -Txterm setaf 3) RESET := $(shell tput -Txterm sgr0) ARCH := $(shell uname -m) -# set the arm64 ARCH to amd64 for compatibility reason +# Handle architecture detection and platform settings ifeq ($(ARCH), arm64) - ARCH := amd64 + DOCKER_PLATFORM := linux/arm64 + # Use ARM64 specific base images if available, fallback to multi-arch + DOCKER_BASE_arm64 := python:3.8-slim +else ifeq ($(ARCH), x86_64) + DOCKER_PLATFORM := linux/amd64 + DOCKER_BASE_amd64 := python:3.8-slim +else + $(error "Architecture \"$(ARCH)\" is unsupported") endif +# Set the base image based on architecture +DOCKER_BASE := $(DOCKER_BASE_$(ARCH)) +BASE_VERSION ?= $(ARCH)-$(VERSION) + +# Add platform-specific build args +DOCKER_BUILD_ARGS := --platform $(DOCKER_PLATFORM) + #Set the source of PIP in docker agent image PIP=pip.conf.bak @@ -72,10 +86,6 @@ COMMON_DOCKER_IMAGES = api-engine nginx dashboard AGENT_DOCKER_IMAGES = ansible kubernetes DUMMY = .$(IMG_TAG) -ifeq ($(DOCKER_BASE), ) - $(error "Architecture \"$(ARCH)\" is unsupported") -endif - # Frontend needed SLASH:=/ REPLACE_SLASH:=\/ @@ -216,19 +226,16 @@ stop-docker-compose: images: api-engine docker-rest-agent fabric dashboard api-engine: - docker build -t hyperledger/cello-api-engine:latest -f build_image/docker/common/api-engine/Dockerfile.in ./ --platform linux/$(ARCH) + docker build $(DOCKER_BUILD_ARGS) -t hyperledger/cello-api-engine:latest -f build_image/docker/common/api-engine/Dockerfile.in ./ docker-rest-agent: - docker build -t hyperledger/cello-agent-docker:latest -f build_image/docker/agent/docker-rest-agent/Dockerfile.in ./ --build-arg pip=$(PIP) --platform linux/$(ARCH) + docker build $(DOCKER_BUILD_ARGS) -t hyperledger/cello-agent-docker:latest -f build_image/docker/agent/docker-rest-agent/Dockerfile.in ./ --build-arg pip=$(PIP) fabric: - docker build -t hyperledger/fabric:2.5.10 -f build_image/docker/cello-hlf/Dockerfile build_image/docker/cello-hlf/ + docker build $(DOCKER_BUILD_ARGS) -t hyperledger/fabric:2.5.10 -f build_image/docker/cello-hlf/Dockerfile build_image/docker/cello-hlf/ dashboard: - docker build -t hyperledger/cello-dashboard:latest -f build_image/docker/common/dashboard/Dockerfile.in ./ - - - + docker build $(DOCKER_BUILD_ARGS) -t hyperledger/cello-dashboard:latest -f build_image/docker/common/dashboard/Dockerfile.in ./ .PHONY: \ all \ diff --git a/bootup/docker-compose-files/docker-compose.dev.yml b/bootup/docker-compose-files/docker-compose.dev.yml index 7f28a27b5..02185b977 100644 --- a/bootup/docker-compose-files/docker-compose.dev.yml +++ b/bootup/docker-compose-files/docker-compose.dev.yml @@ -14,7 +14,7 @@ services: image: hyperledger/cello-dashboard container_name: cello-dashboard ports: - - "${DASHBOARD_SERVICE_PORT}:8081" + - "${DASHBOARD_SERVICE_PORT:-8081}:8081" networks: - cello-net depends_on: @@ -32,7 +32,7 @@ services: ports: - "5432:5432" volumes: - - ${CELLO_STORAGE_PATH:-/opt/cello}/pgdata:/var/lib/postgresql/data + - cello-postgres-data:/var/lib/postgresql/data networks: - cello-net @@ -58,7 +58,7 @@ services: ports: - "8080:8080" volumes: - - ${CELLO_STORAGE_PATH:-/opt/cello}:/opt/cello + - cello-api-engine-data:/opt/cello networks: - cello-net depends_on: @@ -77,7 +77,7 @@ services: - /var/run/docker.sock:/var/run/docker.sock environment: - DOCKER_URL=unix://var/run/docker.sock - - STORAGE_PATH=${CELLO_STORAGE_PATH:-/opt/cello}/hyperledger + - STORAGE_PATH=/opt/cello/hyperledger networks: - cello-net @@ -86,7 +86,7 @@ networks: name: cello-net volumes: - cello-api-engine: - cello-postgres: + cello-postgres-data: + cello-api-engine-data: cello-dashboard: cello-docker-agent: diff --git a/build_image/docker/agent/docker-rest-agent/Dockerfile.in b/build_image/docker/agent/docker-rest-agent/Dockerfile.in index acc20d8b2..85ead7d21 100644 --- a/build_image/docker/agent/docker-rest-agent/Dockerfile.in +++ b/build_image/docker/agent/docker-rest-agent/Dockerfile.in @@ -1,14 +1,28 @@ -FROM python:3.8 +FROM python:3.8-slim + +# Install build dependencies +RUN apt-get update && apt-get install -y \ + build-essential \ + python3-dev \ + libev-dev \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements and pip config COPY src/agent/docker-rest-agent/requirements.txt / -ARG pip=pip.conf.bak +ARG pip=pip.conf COPY src/agent/docker-rest-agent/pip.conf /root/.pip/$pip -RUN pip install -r /requirements.txt -RUN mkdir -p /var/www/server +# Install Python dependencies with pinned gevent version +RUN pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir "gevent==22.10.2" && \ + pip install --no-cache-dir -r /requirements.txt -COPY src/agent/docker-rest-agent/server.py /var/www/server -COPY src/agent/docker-rest-agent/gunicorn.conf.py /var/www/server +# Create server directory and copy files +RUN mkdir -p /var/www/server +COPY src/agent/docker-rest-agent/server.py /var/www/server/ +COPY src/agent/docker-rest-agent/gunicorn.conf.py /var/www/server/ WORKDIR /var/www/server -CMD ["gunicorn", "server:app", "-c", "./gunicorn.conf.py"] +# Run the server +CMD ["gunicorn", "--config", "gunicorn.conf.py", "server:app"] diff --git a/build_image/docker/common/api-engine/Dockerfile.in b/build_image/docker/common/api-engine/Dockerfile.in index dc625775f..53023fc7b 100644 --- a/build_image/docker/common/api-engine/Dockerfile.in +++ b/build_image/docker/common/api-engine/Dockerfile.in @@ -1,8 +1,19 @@ -FROM python:3.8 +FROM python:3.8-slim -# Install software +# Install software including PostgreSQL development packages and pkg-config for pygraphviz RUN apt-get update \ - && apt-get install -y gettext-base graphviz libgraphviz-dev vim \ + && apt-get install -y \ + gettext-base \ + graphviz \ + libgraphviz-dev \ + pkg-config \ + vim \ + curl \ + postgresql \ + postgresql-client \ + libpq-dev \ + gcc \ + python3-dev \ && apt-get autoclean \ && apt-get clean \ && apt-get autoremove && rm -rf /var/cache/apt/ @@ -11,14 +22,23 @@ RUN apt-get update \ WORKDIR /var/www/server # Install compiled code tools from Artifactory and copy it to opt folder. -RUN curl -L --retry 5 --retry-delay 3 "https://github.com/hyperledger/fabric/releases/download/v2.5.10/hyperledger-fabric-linux-amd64-2.5.10.tar.gz" | tar xz -C /opt/ +# Use platform-specific Fabric binary +ARG TARGETARCH +RUN if [ "$TARGETARCH" = "arm64" ]; then \ + curl -L --retry 5 --retry-delay 3 "https://github.com/hyperledger/fabric/releases/download/v2.5.10/hyperledger-fabric-linux-arm64-2.5.10.tar.gz" | tar xz -C /opt/; \ + else \ + curl -L --retry 5 --retry-delay 3 "https://github.com/hyperledger/fabric/releases/download/v2.5.10/hyperledger-fabric-linux-amd64-2.5.10.tar.gz" | tar xz -C /opt/; \ + fi # Copy source code to the working dir COPY src/api-engine ./ COPY template/node /opt/node # Install python dependencies -RUN pip3 install -r requirements.txt +# First upgrade pip to latest version +RUN pip3 install --no-cache-dir --upgrade pip && \ + pip3 install --no-cache-dir psycopg2-binary==2.8.4 && \ + pip3 install --no-cache-dir -r requirements.txt # Add uwsgi configuration file COPY build_image/docker/common/api-engine/server.ini /etc/uwsgi/apps-enabled/ From 32466e325a87ed9cf3c1dbb11d0296fa8def039e Mon Sep 17 00:00:00 2001 From: Vishvendra Date: Tue, 10 Jun 2025 10:35:21 +0530 Subject: [PATCH 2/2] fix: resolve flake8 linting errors and add chaincode invoke functionality - Fix code formatting issues and implement invoke endpoint Signed-off-by: Vishvendra --- .../api/lib/configtxgen/configtx.py | 6 +- .../api/lib/configtxgen/configtxgen.py | 4 +- .../api/lib/configtxlator/configtxlator.py | 7 +- src/api-engine/api/lib/peer/chaincode.py | 7 +- src/api-engine/api/lib/peer/channel.py | 4 +- ...003_alter_agent_name_alter_network_name.py | 23 +++++ ...004_alter_agent_name_alter_network_name.py | 23 +++++ ...005_alter_agent_name_alter_network_name.py | 23 +++++ .../api/routes/chaincode/serializers.py | 12 ++- src/api-engine/api/routes/chaincode/views.py | 87 +++++++++++++++++-- src/api-engine/api/routes/channel/views.py | 4 +- src/api-engine/api/utils/common.py | 30 +++---- 12 files changed, 193 insertions(+), 37 deletions(-) create mode 100644 src/api-engine/api/migrations/0003_alter_agent_name_alter_network_name.py create mode 100644 src/api-engine/api/migrations/0004_alter_agent_name_alter_network_name.py create mode 100644 src/api-engine/api/migrations/0005_alter_agent_name_alter_network_name.py diff --git a/src/api-engine/api/lib/configtxgen/configtx.py b/src/api-engine/api/lib/configtxgen/configtx.py index 83fd98851..7b75e904b 100644 --- a/src/api-engine/api/lib/configtxgen/configtx.py +++ b/src/api-engine/api/lib/configtxgen/configtx.py @@ -53,11 +53,11 @@ def create(self, name, consensus, orderers, peers, orderer_cfg=None, application for orderer in orderers: OrdererMSP = "OrdererMSP" OrdererOrg = dict(Name="Orderer", - ID= OrdererMSP, + ID=OrdererMSP, MSPDir='{}/{}/crypto-config/ordererOrganizations/{}/msp'.format(self.filepath, orderer["name"], orderer['name'].split(".", 1)[1]), Policies=dict(Readers=dict(Type="Signature", Rule="OR('{}.member')".format(OrdererMSP)), - Writers=dict(Type="Signature", Rule="OR('{}.member')".format(OrdererMSP)), - Admins=dict(Type="Signature", Rule="OR('{}.admin')".format(OrdererMSP))) + Writers=dict(Type="Signature", Rule="OR('{}.member')".format(OrdererMSP)), + Admins=dict(Type="Signature", Rule="OR('{}.admin')".format(OrdererMSP))) ) for host in orderer['hosts']: OrdererAddress.append('{}.{}:{}'.format(host['name'], orderer['name'].split(".", 1)[1], 7050)) diff --git a/src/api-engine/api/lib/configtxgen/configtxgen.py b/src/api-engine/api/lib/configtxgen/configtxgen.py index c46c4257c..c3341166d 100644 --- a/src/api-engine/api/lib/configtxgen/configtxgen.py +++ b/src/api-engine/api/lib/configtxgen/configtxgen.py @@ -48,7 +48,7 @@ def genesis(self, profile="", channelid="", outputblock="genesis.block"): except subprocess.CalledProcessError as e: err_msg = "configtxgen genesis fail! " - raise Exception(err_msg+str(e)) + raise Exception(err_msg + str(e)) except Exception as e: err_msg = "configtxgen genesis fail! " @@ -62,4 +62,4 @@ def anchorpeer(self, profile, channelid, outputblock): outputblock: outputblock return: """ - pass \ No newline at end of file + pass diff --git a/src/api-engine/api/lib/configtxlator/configtxlator.py b/src/api-engine/api/lib/configtxlator/configtxlator.py index d25fb42c5..9f5e1aa3a 100644 --- a/src/api-engine/api/lib/configtxlator/configtxlator.py +++ b/src/api-engine/api/lib/configtxlator/configtxlator.py @@ -7,6 +7,7 @@ import logging LOG = logging.getLogger(__name__) + class ConfigTxLator: """ Class represents configtxlator CLI. @@ -32,7 +33,7 @@ def proto_encode(self, input, type, output): "--type={}".format(type), "--output={}".format(output), ] - + LOG.info(" ".join(command)) call(command) @@ -57,7 +58,7 @@ def proto_decode(self, input, type, output): "--input={}".format(input), "--output={}".format(output), ] - + LOG.info(" ".join(command)) call(command) @@ -85,7 +86,7 @@ def compute_update(self, original, updated, channel_id, output): "--channel_id={}".format(channel_id), "--output={}".format(output), ] - + LOG.info(" ".join(command)) call(command) diff --git a/src/api-engine/api/lib/peer/chaincode.py b/src/api-engine/api/lib/peer/chaincode.py index af09b7d0f..c22e540d5 100644 --- a/src/api-engine/api/lib/peer/chaincode.py +++ b/src/api-engine/api/lib/peer/chaincode.py @@ -10,6 +10,7 @@ LOG = logging.getLogger(__name__) + class ChainCode(Command): def __init__(self, version=FABRIC_VERSION, peer=FABRIC_TOOL, **kwargs): self.peer = peer + "/peer" @@ -71,7 +72,7 @@ def lifecycle_query_installed(self, timeout): ] LOG.info(" ".join(command)) res = subprocess.Popen(command, shell=False, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = res.communicate() return_code = res.returncode @@ -165,7 +166,7 @@ def lifecycle_approve_for_my_org(self, orderer_url, channel_name, cc_name, "--tls", "--cafile", ORDERER_CA ] - + if init_flag: command.append("--init-required") if policy: @@ -433,4 +434,4 @@ def lifecycle_calculatepackageid(self, cc_path): return return_code, stderr except Exception as e: err_msg = "calculated chaincode packageid failed for {}!".format(e) - raise Exception(err_msg) \ No newline at end of file + raise Exception(err_msg) diff --git a/src/api-engine/api/lib/peer/channel.py b/src/api-engine/api/lib/peer/channel.py index 6bf9a257f..c4bba1a58 100644 --- a/src/api-engine/api/lib/peer/channel.py +++ b/src/api-engine/api/lib/peer/channel.py @@ -47,13 +47,13 @@ def create(self, channel, orderer_admin_url, block_path, time_out="90s"): ] LOG.info(" ".join(command)) - + res = subprocess.run(command, check=True) except subprocess.CalledProcessError as e: err_msg = "create channel failed for {}!".format(e) raise Exception(err_msg+str(e)) - + except Exception as e: err_msg = "create channel failed for {}!".format(e) raise Exception(err_msg) diff --git a/src/api-engine/api/migrations/0003_alter_agent_name_alter_network_name.py b/src/api-engine/api/migrations/0003_alter_agent_name_alter_network_name.py new file mode 100644 index 000000000..d26b2f85d --- /dev/null +++ b/src/api-engine/api/migrations/0003_alter_agent_name_alter_network_name.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.16 on 2025-05-27 20:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0002_alter_agent_name_alter_network_name'), + ] + + operations = [ + migrations.AlterField( + model_name='agent', + name='name', + field=models.CharField(default='agent-fbfa4c2afb5d4f649e63d694fa7815c5', help_text='Agent name, can be generated automatically.', max_length=64), + ), + migrations.AlterField( + model_name='network', + name='name', + field=models.CharField(default='netowrk-f2b8908f8a074896985b01bde0a2cb0e', help_text='network name, can be generated automatically.', max_length=64), + ), + ] diff --git a/src/api-engine/api/migrations/0004_alter_agent_name_alter_network_name.py b/src/api-engine/api/migrations/0004_alter_agent_name_alter_network_name.py new file mode 100644 index 000000000..fada51973 --- /dev/null +++ b/src/api-engine/api/migrations/0004_alter_agent_name_alter_network_name.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.16 on 2025-05-27 20:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0003_alter_agent_name_alter_network_name'), + ] + + operations = [ + migrations.AlterField( + model_name='agent', + name='name', + field=models.CharField(default='agent-cecf0eedaed740a385aef90f256e51bf', help_text='Agent name, can be generated automatically.', max_length=64), + ), + migrations.AlterField( + model_name='network', + name='name', + field=models.CharField(default='netowrk-bb67b507991f4bd1b5402414769d7274', help_text='network name, can be generated automatically.', max_length=64), + ), + ] diff --git a/src/api-engine/api/migrations/0005_alter_agent_name_alter_network_name.py b/src/api-engine/api/migrations/0005_alter_agent_name_alter_network_name.py new file mode 100644 index 000000000..04c5fe14b --- /dev/null +++ b/src/api-engine/api/migrations/0005_alter_agent_name_alter_network_name.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.16 on 2025-05-31 10:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0004_alter_agent_name_alter_network_name'), + ] + + operations = [ + migrations.AlterField( + model_name='agent', + name='name', + field=models.CharField(default='agent-e603b14257cf4c2ebe08ab2e7bc7380e', help_text='Agent name, can be generated automatically.', max_length=64), + ), + migrations.AlterField( + model_name='network', + name='name', + field=models.CharField(default='netowrk-e468644f181d4a3181e6558a37672762', help_text='network name, can be generated automatically.', max_length=64), + ), + ] diff --git a/src/api-engine/api/routes/chaincode/serializers.py b/src/api-engine/api/routes/chaincode/serializers.py index d51ab9b1a..be7de56eb 100644 --- a/src/api-engine/api/routes/chaincode/serializers.py +++ b/src/api-engine/api/routes/chaincode/serializers.py @@ -30,8 +30,9 @@ def validate(self, attrs): @staticmethod def extension_for_file(file): - extension = file.name.endswith('.tar.gz') - return extension + extension = file.name.endswith('.tar.gz') + return extension + class ChainCodeNetworkSerializer(serializers.Serializer): id = serializers.UUIDField(help_text="Network ID") @@ -69,3 +70,10 @@ class ChainCodeApproveForMyOrgBody(serializers.Serializer): class ChainCodeCommitBody(ChainCodeApproveForMyOrgBody): peer_list = serializers.ListField(allow_empty=False, required=True) + + +class ChainCodeInvokeBody(serializers.Serializer): + channel_name = serializers.CharField(max_length=128, required=True) + chaincode_name = serializers.CharField(max_length=128, required=True) + args = serializers.ListField(allow_empty=False, required=True) + init = serializers.BooleanField(required=False, default=False) diff --git a/src/api-engine/api/routes/chaincode/views.py b/src/api-engine/api/routes/chaincode/views.py index 867897c39..601f5d8f4 100644 --- a/src/api-engine/api/routes/chaincode/views.py +++ b/src/api-engine/api/routes/chaincode/views.py @@ -6,7 +6,10 @@ from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated import os -import tempfile, shutil, tarfile, json +import tempfile +import shutil +import tarfile +import json from drf_yasg.utils import swagger_auto_schema from api.config import FABRIC_CHAINCODE_STORE @@ -28,6 +31,7 @@ ChainCodeIDSerializer, ChainCodeCommitBody, ChainCodeApproveForMyOrgBody, + ChainCodeInvokeBody, ChaincodeListResponse ) from api.common import ok, err @@ -144,7 +148,7 @@ def package(self, request): if member.name.endswith("metadata.json"): metadata_file = member break - + if metadata_file is not None: # Extract the metadata file metadata_content = tar.extractfile(metadata_file).read().decode("utf-8") @@ -159,7 +163,7 @@ def package(self, request): # qs = Node.objects.filter(type="peer", organization=org) # if not qs.exists(): # return Response( - # err("at least 1 peer node is required for the chaincode package upload."), + # err("at least 1 peer node is required for the chaincode package upload."), # status=status.HTTP_400_BAD_REQUEST # ) # peer_node = qs.first() @@ -168,7 +172,7 @@ def package(self, request): # return_code, content = peer_channel_cli.lifecycle_calculatepackageid(temp_cc_path) # if (return_code != 0): # return Response( - # err("calculate packageid failed for {}.".format(content)), + # err("calculate packageid failed for {}.".format(content)), # status=status.HTTP_400_BAD_REQUEST # ) # packageid = content.strip() @@ -184,7 +188,7 @@ def package(self, request): cc = ChainCode.objects.filter(package_id=packageid) if cc.exists(): return Response( - err("package with id {} already exists.".format(packageid)), + err("package with id {} already exists.".format(packageid)), status=status.HTTP_400_BAD_REQUEST ) @@ -545,3 +549,76 @@ def query_committed(self, request): return Response( ok(chaincodes_commited), status=status.HTTP_200_OK ) + + @swagger_auto_schema( + method="post", + request_body=ChainCodeInvokeBody, + responses=with_common_response( + {status.HTTP_200_OK: "Chaincode invocation successful"} + ), + ) + @action(detail=False, methods=['post']) + def invoke(self, request): + """ + Invoke chaincode on the network + """ + serializer = ChainCodeInvokeBody(data=request.data) + if serializer.is_valid(raise_exception=True): + try: + channel_name = serializer.validated_data.get("channel_name") + chaincode_name = serializer.validated_data.get("chaincode_name") + args = serializer.validated_data.get("args") + init = serializer.validated_data.get("init", False) + + org = request.user.organization + qs = Node.objects.filter(type="peer", organization=org) + if not qs.exists(): + raise ResourceNotFound("No peer nodes found for organization") + + peer_node = qs.first() + envs = init_env_vars(peer_node, org) + + # Get orderer information + orderer_qs = Node.objects.filter(type="orderer", organization=org) + if not orderer_qs.exists(): + raise ResourceNotFound("No orderer nodes found for organization") + + orderer_node = orderer_qs.first() + orderer_url = f"{orderer_node.name}.{org.name.split('.', 1)[1]}:7050" + + # Get orderer TLS certificate + orderer_tls_dir = f"{CELLO_HOME}/{org.name}/crypto-config/ordererOrganizations/{org.name.split('.', 1)[1]}/orderers/{orderer_node.name}.{org.name.split('.', 1)[1]}/msp/tlscacerts" + orderer_tls_root_cert = "" + for _, _, files in os.walk(orderer_tls_dir): + if files: + orderer_tls_root_cert = os.path.join(orderer_tls_dir, files[0]) + break + + # Initialize chaincode client + peer_chaincode_cli = PeerChainCode(**envs) + + # Convert args to JSON format if they aren't already + json_args = json.dumps(args) if not isinstance(args, str) else args + + # Invoke chaincode + return_code, result = peer_chaincode_cli.invoke( + orderer_url, orderer_tls_root_cert, channel_name, + chaincode_name, json_args, init + ) + + if return_code != 0: + return Response( + err(f"Chaincode invocation failed: {result}"), + status=status.HTTP_400_BAD_REQUEST + ) + + return Response( + ok({"result": result, "message": "Chaincode invocation successful"}), + status=status.HTTP_200_OK + ) + + except Exception as e: + return Response( + err(f"Chaincode invocation failed: {str(e)}"), + status=status.HTTP_400_BAD_REQUEST + ) diff --git a/src/api-engine/api/routes/channel/views.py b/src/api-engine/api/routes/channel/views.py index 395fd61ed..2396b8282 100644 --- a/src/api-engine/api/routes/channel/views.py +++ b/src/api-engine/api/routes/channel/views.py @@ -436,7 +436,7 @@ def set_anchor_peer(name, org, anchor_peer, ordering_node): """ org_msp = '{}'.format(org.name.split(".", 1)[0].capitalize()) channel_artifacts_path = "{}/{}".format(CELLO_HOME, org.network.name) - + # Fetch the channel block from the orderer peer_channel_fetch(name, org, anchor_peer, ordering_node) @@ -446,7 +446,7 @@ def set_anchor_peer(name, org, anchor_peer, ordering_node): type="common.Block", output="{}/config_block.json".format(channel_artifacts_path), ) - + # Get the config data from the block json_filter( input="{}/config_block.json".format(channel_artifacts_path), diff --git a/src/api-engine/api/utils/common.py b/src/api-engine/api/utils/common.py index 6d0ddd89e..a2aa86cae 100644 --- a/src/api-engine/api/utils/common.py +++ b/src/api-engine/api/utils/common.py @@ -162,11 +162,11 @@ def to_dict(data): def json_filter(input, output, expression): """ Process JSON data using path expression similar to jq - + Args: input (str): JSON data or file path to JSON output (str): Path expression like ".data.data[0].payload.data.config" - + Returns: dict: Processed JSON data """ @@ -176,11 +176,11 @@ def json_filter(input, output, expression): data = json.load(f) else: data = input - + # parse the path expression path_parts = expression.strip('.').split('.') result = data - + for part in path_parts: # handle array index, like data[0] if '[' in part and ']' in part: @@ -189,7 +189,7 @@ def json_filter(input, output, expression): result = result[array_name][index] else: result = result[part] - + with open(output, 'w', encoding='utf-8') as f: json.dump(result, f, sort_keys=False, indent=4) @@ -210,25 +210,25 @@ def json_add_anchor_peer(input, output, anchor_peer_config, org_msp): data = json.load(f) else: data = input - + if "groups" not in data["channel_group"]: data["channel_group"]["groups"] = {} if "Application" not in data["channel_group"]["groups"]: data["channel_group"]["groups"]["Application"] = {"groups": {}} if org_msp not in data["channel_group"]["groups"]["Application"]["groups"]: data["channel_group"]["groups"]["Application"]["groups"][org_msp] = {"values": {}} - + data["channel_group"]["groups"]["Application"]["groups"][org_msp]["values"].update(anchor_peer_config) - + with open(output, 'w', encoding='utf-8') as f: json.dump(data, f, sort_keys=False, indent=4) - + LOG.info("jq '.channel_group.groups.Application.groups.Org1MSP.values += ... ' {} -> {}".format(input, output)) def json_create_envelope(input, output, channel): """ Create a config update envelope structure - + Args: input (str): Path to the config update JSON file output (str): Path to save the envelope JSON @@ -238,7 +238,7 @@ def json_create_envelope(input, output, channel): # Read the config update file with open(input, 'r', encoding='utf-8') as f: config_update = json.load(f) - + # Create the envelope structure envelope = { "payload": { @@ -253,13 +253,13 @@ def json_create_envelope(input, output, channel): } } } - + # Write the envelope to output file with open(output, 'w', encoding='utf-8') as f: json.dump(envelope, f, sort_keys=False, indent=4) - + LOG.info("echo 'payload ... ' | jq . > {}".format(output)) - + except Exception as e: LOG.error("Failed to create config update envelope: {}".format(str(e))) raise @@ -298,4 +298,4 @@ def init_env_vars(node, org): "FABRIC_CFG_PATH": "{}/{}/peers/{}/".format(dir_node, org_name, node.name + "." + org_name) } - return envs \ No newline at end of file + return envs