Skip to content

Commit e7700c7

Browse files
authored
fix(fabric-connector): cli image optimization (#3969)
Signed-off-by: Carlos Amaro <[email protected]>
1 parent 85b9b62 commit e7700c7

File tree

4 files changed

+117
-37
lines changed

4 files changed

+117
-37
lines changed
Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,70 @@
1+
# ========= BUILDER =========
12
ARG BASE_IMAGE=debian:bullseye-slim
23
FROM --platform=$BUILDPLATFORM ${BASE_IMAGE} AS builder
34

4-
# Install required tools and dependencies
5-
RUN apt-get update && apt-get install -y \
5+
ARG DEBIAN_FRONTEND=noninteractive
6+
7+
# Install dependencies in one layer
8+
RUN apt-get update && apt-get install -y --no-install-recommends \
69
git \
710
wget \
8-
build-essential \
911
curl \
10-
openjdk-17-jdk \
1112
ca-certificates \
13+
build-essential \
14+
openjdk-17-jdk-headless \
1215
tar \
1316
&& rm -rf /var/lib/apt/lists/*
1417

1518
# Install Go
1619
ARG GO_VERSION=1.23.11
1720
RUN ARCH=$(dpkg --print-architecture) && \
18-
if [ "$ARCH" = "amd64" ]; then GOARCH=amd64; \
19-
elif [ "$ARCH" = "arm64" ]; then GOARCH=arm64; \
20-
else echo "Unsupported architecture: $ARCH" && exit 1; fi && \
21-
wget https://go.dev/dl/go${GO_VERSION}.linux-${GOARCH}.tar.gz && \
21+
case "$ARCH" in \
22+
amd64) GOARCH=amd64 ;; \
23+
arm64) GOARCH=arm64 ;; \
24+
*) echo "Unsupported arch: $ARCH" && exit 1 ;; \
25+
esac && \
26+
wget -q https://go.dev/dl/go${GO_VERSION}.linux-${GOARCH}.tar.gz && \
2227
tar -C /usr/local -xzf go${GO_VERSION}.linux-${GOARCH}.tar.gz && \
2328
rm go${GO_VERSION}.linux-${GOARCH}.tar.gz
29+
ENV PATH="/usr/local/go/bin:${PATH}"
2430

25-
ENV PATH="/usr/local/go/bin:/go/bin:${PATH}"
26-
27-
# Install Node.js using NVM
28-
ENV NODE_VERSION=18.19.0
29-
RUN apt install -y curl
30-
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
31-
ENV NVM_DIR=/root/.nvm
32-
RUN . "$NVM_DIR/nvm.sh" && nvm install ${NODE_VERSION}
33-
RUN . "$NVM_DIR/nvm.sh" && nvm use v${NODE_VERSION}
34-
RUN . "$NVM_DIR/nvm.sh" && nvm alias default v${NODE_VERSION}
35-
ENV PATH="/root/.nvm/versions/node/v${NODE_VERSION}/bin/:${PATH}"
36-
RUN node --version
37-
RUN npm --version
31+
# Install Node.js (direct tarball, no NVM overhead)
32+
ARG NODE_VERSION=18.19.0
33+
RUN ARCH=$(dpkg --print-architecture) && \
34+
case "$ARCH" in \
35+
amd64) NODE_ARCH=x64 ;; \
36+
arm64) NODE_ARCH=arm64 ;; \
37+
*) echo "Unsupported arch: $ARCH" && exit 1 ;; \
38+
esac && \
39+
wget -qO- https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${NODE_ARCH}.tar.xz | tar -xJ -C /usr/local --strip-components=1
3840
RUN npm install -g typescript
3941

40-
# Set up environment variables
42+
# Install Hyperledger Fabric binaries and move them into /usr/local/bin
4143
ENV FABRIC_VERSION=2.5.6
44+
RUN curl -sSLO https://raw.githubusercontent.com/hyperledger/fabric/main/scripts/install-fabric.sh && \
45+
chmod +x install-fabric.sh && \
46+
./install-fabric.sh --fabric-version ${FABRIC_VERSION} binary && \
47+
mv bin/* /usr/local/bin && \
48+
rm -rf bin install-fabric.sh
4249

43-
# Set architecture variables for downloading Fabric binaries
44-
ARG TARGETARCH
45-
ENV ARCH=$TARGETARCH
50+
WORKDIR /chaincode
4651

47-
# Map Docker arch to Fabric arch and download Fabric binaries
48-
RUN curl -sSLO https://raw.githubusercontent.com/hyperledger/fabric/main/scripts/install-fabric.sh && chmod +x install-fabric.sh
52+
# ========= RUNTIME =========
53+
FROM debian:bullseye-slim
4954

50-
RUN ./install-fabric.sh --fabric-version ${FABRIC_VERSION} binary
55+
ARG DEBIAN_FRONTEND=noninteractive
56+
RUN apt-get update && apt-get install -y --no-install-recommends \
57+
openjdk-17-jre-headless \
58+
ca-certificates \
59+
&& rm -rf /var/lib/apt/lists/*
5160

61+
# Copy tools from builder (includes peer now)
62+
COPY --from=builder /usr/local /usr/local
63+
COPY --from=builder /chaincode /chaincode
5264

65+
ENV PATH="/usr/local/go/bin:/usr/local/bin:${PATH}"
5366

5467
WORKDIR /chaincode
5568

56-
5769
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
58-
CMD go version && node --version && npm --version && tsc --version && java -version || exit 1
70+
CMD go version && node --version && npm --version && tsc --version && java -version && peer version || exit 1

packages/cactus-plugin-ledger-connector-fabric/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,14 @@
7676
"http-status-codes": "2.1.4",
7777
"joi": "17.13.3",
7878
"jsrsasign": "11.0.0",
79+
"lodash": "4.17.21",
7980
"long": "5.2.3",
8081
"multer": "1.4.5-lts.1",
8182
"ngo": "2.7.0",
8283
"node-ssh": "13.1.0",
8384
"node-vault": "0.9.22",
8485
"openapi-types": "12.1.3",
86+
"p-retry": "4.6.1",
8587
"prom-client": "15.1.3",
8688
"run-time-error-cjs": "1.4.0",
8789
"rxjs": "7.8.1",
@@ -106,6 +108,7 @@
106108
"@types/fs-extra": "11.0.4",
107109
"@types/http-errors": "2.0.4",
108110
"@types/jsrsasign": "8.0.13",
111+
"@types/lodash": "4.14.172",
109112
"@types/multer": "1.4.7",
110113
"@types/node-vault": "0.9.13",
111114
"@types/sanitize-html": "2.9.5",

packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/compiler-tools/compiler-tools.ts

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,37 @@
11
import {
22
Bools,
3+
ILoggerOptions,
34
Logger,
45
LoggerProvider,
56
LogLevelDesc,
67
} from "@hyperledger/cactus-common";
78
import Joi from "joi";
89
import Docker, { Container, ContainerInfo } from "dockerode";
10+
import Dockerode from "dockerode";
911
import EventEmitter from "events";
1012
import { SSHExecCommandResponse } from "node-ssh";
1113
import { streamLogs } from "./containers";
14+
import pRetry from "p-retry";
15+
import throttle from "lodash/throttle";
1216

1317
export const CC_COMPILER_DEFAULT_OPTIONS = Object.freeze({
1418
containerImageVersion: "2025-08-12-d5365bf",
1519
containerImageName: "ghcr.io/hyperledger-cacti/cactus-connector-fabric-cli",
1620
dockerNetworkName: "bridge",
1721
});
1822

23+
export interface IDockerPullProgressDetail {
24+
readonly current: number;
25+
readonly total: number;
26+
}
27+
28+
export interface IDockerPullProgress {
29+
readonly status: "Downloading";
30+
readonly progressDetail: IDockerPullProgressDetail;
31+
readonly progress: string;
32+
readonly id: string;
33+
}
34+
1935
export interface ICompilerToolsOptions {
2036
containerImageVersion?: string;
2137
containerImageName?: string;
@@ -43,6 +59,7 @@ export class CompilerTools {
4359
public readonly emitContainerLogs: boolean;
4460

4561
private readonly log: Logger;
62+
private readonly level: LogLevelDesc;
4663
private container: Container | undefined;
4764
private containerId: string | undefined;
4865

@@ -71,8 +88,8 @@ export class CompilerTools {
7188
}
7289

7390
const label = "fabric-cc-compiler";
74-
const level = options.logLevel || "INFO";
75-
this.log = LoggerProvider.getOrCreate({ level, label });
91+
this.level = options.logLevel || "INFO";
92+
this.log = LoggerProvider.getOrCreate({ level: this.level, label });
7693
}
7794

7895
public getContainerImageName(): string {
@@ -160,7 +177,7 @@ export class CompilerTools {
160177

161178
if (!omitPull) {
162179
this.log.debug(`Pulling container image ${imageFqn} ...`);
163-
await this.pullContainerImage(imageFqn);
180+
await CompilerTools.pullImage(imageFqn, undefined, this.level);
164181
this.log.debug(`Pulled ${imageFqn} OK. Starting container...`);
165182
}
166183

@@ -299,25 +316,70 @@ export class CompilerTools {
299316
};
300317
}
301318

302-
private pullContainerImage(containerNameAndTag: string): Promise<unknown[]> {
319+
public static pullImage(
320+
imageFqn: string,
321+
options: Record<string, unknown> = {},
322+
logLevel?: LogLevelDesc,
323+
): Promise<unknown[]> {
324+
const defaultLoggerOptions: ILoggerOptions = {
325+
label: "containers#pullImage()",
326+
level: logLevel || "INFO",
327+
};
328+
const log = LoggerProvider.getOrCreate(defaultLoggerOptions);
329+
const task = () => CompilerTools.tryPullImage(imageFqn, options, logLevel);
330+
const retryOptions: pRetry.Options & { retries: number } = {
331+
retries: 6,
332+
onFailedAttempt: async (ex) => {
333+
log.debug(`Failed attempt at pulling container image ${imageFqn}`, ex);
334+
},
335+
};
336+
return pRetry(task, retryOptions);
337+
}
338+
339+
public static tryPullImage(
340+
imageFqn: string,
341+
options: Record<string, unknown> = {},
342+
logLevel?: LogLevelDesc,
343+
): Promise<unknown[]> {
303344
return new Promise((resolve, reject) => {
304-
const docker = new Docker();
305-
docker.pull(containerNameAndTag, (pullError: unknown, stream: never) => {
345+
const loggerOptions: ILoggerOptions = {
346+
label: "containers#tryPullImage()",
347+
level: logLevel || "INFO",
348+
};
349+
const log = LoggerProvider.getOrCreate(loggerOptions);
350+
351+
const docker = new Dockerode();
352+
353+
const progressPrinter = throttle((msg: IDockerPullProgress): void => {
354+
log.debug(JSON.stringify(msg.progress || msg.status));
355+
}, 1000);
356+
357+
const pullStreamStartedHandler = (
358+
pullError: unknown,
359+
stream: NodeJS.ReadableStream,
360+
) => {
306361
if (pullError) {
362+
log.error(`Could not even start ${imageFqn} pull:`, pullError);
307363
reject(pullError);
308364
} else {
365+
log.debug(`Started ${imageFqn} pull progress stream OK`);
309366
docker.modem.followProgress(
310367
stream,
311368
(progressError: unknown, output: unknown[]) => {
312369
if (progressError) {
370+
log.error(`Failed to finish ${imageFqn} pull:`, progressError);
313371
reject(progressError);
314372
} else {
373+
log.debug(`Finished ${imageFqn} pull completely OK`);
315374
resolve(output);
316375
}
317376
},
377+
(msg: IDockerPullProgress): void => progressPrinter(msg),
318378
);
319379
}
320-
});
380+
};
381+
382+
docker.pull(imageFqn, options, pullStreamStartedHandler);
321383
});
322384
}
323385
}

yarn.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8774,6 +8774,7 @@ __metadata:
87748774
"@types/fs-extra": "npm:11.0.4"
87758775
"@types/http-errors": "npm:2.0.4"
87768776
"@types/jsrsasign": "npm:8.0.13"
8777+
"@types/lodash": "npm:4.14.172"
87778778
"@types/multer": "npm:1.4.7"
87788779
"@types/node-vault": "npm:0.9.13"
87798780
"@types/sanitize-html": "npm:2.9.5"
@@ -8799,12 +8800,14 @@ __metadata:
87998800
internal-ip: "npm:6.2.0"
88008801
joi: "npm:17.13.3"
88018802
jsrsasign: "npm:11.0.0"
8803+
lodash: "npm:4.17.21"
88028804
long: "npm:5.2.3"
88038805
multer: "npm:1.4.5-lts.1"
88048806
ngo: "npm:2.7.0"
88058807
node-ssh: "npm:13.1.0"
88068808
node-vault: "npm:0.9.22"
88078809
openapi-types: "npm:12.1.3"
8810+
p-retry: "npm:4.6.1"
88088811
prom-client: "npm:15.1.3"
88098812
run-time-error-cjs: "npm:1.4.0"
88108813
rxjs: "npm:7.8.1"

0 commit comments

Comments
 (0)