diff --git a/packages/cactus-test-tooling/src/main/typescript/common/build-container-image.ts b/packages/cactus-test-tooling/src/main/typescript/common/build-container-image.ts new file mode 100644 index 00000000000..e73e16a515c --- /dev/null +++ b/packages/cactus-test-tooling/src/main/typescript/common/build-container-image.ts @@ -0,0 +1,64 @@ +import Docker, { ImageBuildContext, ImageBuildOptions } from "dockerode"; + +import { LoggerProvider, LogLevelDesc } from "@hyperledger/cactus-common"; + +export interface IBuildContainerImageRequest { + readonly logLevel: LogLevelDesc; + readonly buildDir: Readonly; + readonly imageFile: Readonly; + readonly imageTag: Readonly; + readonly dockerEngine?: Readonly; + readonly dockerodeImageBuildOptions?: Partial; + readonly dockerodeImageBuildContext?: Partial; +} + +export async function buildContainerImage( + req: Readonly, +): Promise { + if (!req) { + throw new Error("Expected arg req to be truthy."); + } + if (!req.buildDir) { + throw new Error("Expected arg req.buildDir to be truthy."); + } + if (!req.imageFile) { + throw new Error("Expected arg req.imageFile to be truthy."); + } + const logLevel: LogLevelDesc = req.logLevel || "INFO"; + const dockerEngine = req.dockerEngine || new Docker(); + + const log = LoggerProvider.getOrCreate({ + label: "build-container-image", + level: logLevel, + }); + + const imageBuildOptions: ImageBuildOptions = { + ...req.dockerodeImageBuildOptions, + t: req.imageTag, + }; + log.debug("imageBuildOptions=%o", imageBuildOptions); + + const imageBuildContext: ImageBuildContext = { + context: req.buildDir, + src: [req.imageFile, "."], + ...req.dockerodeImageBuildContext, + }; + log.debug("imageBuildContext=%o", imageBuildContext); + + const stream = await dockerEngine.buildImage( + imageBuildContext, + imageBuildOptions, + ); + + stream.on("data", (data: unknown) => { + if (data instanceof Buffer) { + log.debug("[Build]: ", data.toString("utf-8")); + } + }); + + await new Promise((resolve, reject) => { + dockerEngine.modem.followProgress(stream, (err, res) => + err ? reject(err) : resolve(res), + ); + }); +} diff --git a/packages/cactus-test-tooling/src/main/typescript/corda/build-image-connector-corda-server.ts b/packages/cactus-test-tooling/src/main/typescript/corda/build-image-connector-corda-server.ts new file mode 100644 index 00000000000..1bd88625181 --- /dev/null +++ b/packages/cactus-test-tooling/src/main/typescript/corda/build-image-connector-corda-server.ts @@ -0,0 +1,52 @@ +import path from "node:path"; +import { buildContainerImage } from "../public-api"; +import { LoggerProvider, LogLevelDesc } from "@hyperledger/cactus-common"; + +export interface IBuildImageConnectorCordaServerResponse { + readonly imageName: Readonly; + readonly imageVersion: Readonly; + /** + * The concatenation of `imageName` a colon character and `imageVersion`. + */ + readonly imageTag: Readonly; +} + +export interface IBuildImageConnectorCordaServerRequest { + readonly logLevel?: Readonly; +} + +export async function buildImageConnectorCordaServer( + req: IBuildImageConnectorCordaServerRequest, +): Promise { + if (!req) { + throw new Error("Expected arg req to be truthy."); + } + const logLevel: LogLevelDesc = req.logLevel || "WARN"; + const log = LoggerProvider.getOrCreate({ + level: logLevel, + label: "build-image-connector-corda-server.ts", + }); + const projectRoot = path.join(__dirname, "../../../../../../../"); + + const buildDirRel = + "./packages/cactus-plugin-ledger-connector-corda/src/main-server/"; + + const buildDirAbs = path.join(projectRoot, buildDirRel); + + log.info("Invoking container build with build dir: %s", buildDirAbs); + + const imageName = "cccs"; + const imageVersion = "latest"; + const imageTag = `${imageName}:${imageVersion}`; + + await buildContainerImage({ + buildDir: buildDirAbs, + imageFile: "Dockerfile", + imageTag, + logLevel: logLevel, + }); + + log.info("Building Corda v4 JVM Connector finished OK"); + + return { imageName, imageVersion, imageTag }; +} diff --git a/packages/cactus-test-tooling/src/main/typescript/corda/build-image-corda-all-in-one-v4-12.ts b/packages/cactus-test-tooling/src/main/typescript/corda/build-image-corda-all-in-one-v4-12.ts new file mode 100644 index 00000000000..55226c427b3 --- /dev/null +++ b/packages/cactus-test-tooling/src/main/typescript/corda/build-image-corda-all-in-one-v4-12.ts @@ -0,0 +1,49 @@ +import path from "node:path"; +import { buildContainerImage } from "../public-api"; +import { LoggerProvider, LogLevelDesc } from "@hyperledger/cactus-common"; + +export interface IBuildImageCordaAllInOneV412Response { + readonly imageName: Readonly; + readonly imageVersion: Readonly; + /** + * The concatenation of `imageName` a colon character and `imageVersion`. + */ + readonly imageTag: Readonly; +} + +export interface IBuildImageCordaAllInOneV412Request { + readonly logLevel?: Readonly; +} + +export async function buildImageCordaAllInOneV412( + req: IBuildImageCordaAllInOneV412Request, +): Promise { + if (!req) { + throw new Error("Expected arg req to be truthy."); + } + const logLevel: LogLevelDesc = req.logLevel || "WARN"; + const log = LoggerProvider.getOrCreate({ + level: logLevel, + label: "build-image-connector-corda-server.ts", + }); + const projectRoot = path.join(__dirname, "../../../../../../../"); + + const buildDirRel = "./tools/docker/corda-all-in-one/corda-v4_12/"; + + const buildDirAbs = path.join(projectRoot, buildDirRel); + + log.info("Invoking container build with build dir: %s", buildDirAbs); + + const imageName = "caio412"; + const imageVersion = "latest"; + const imageTag = `${imageName}:${imageVersion}`; + + await buildContainerImage({ + buildDir: buildDirAbs, + imageFile: "Dockerfile", + imageTag, + logLevel: logLevel, + }); + + return { imageName, imageVersion, imageTag }; +} diff --git a/packages/cactus-test-tooling/src/main/typescript/public-api.ts b/packages/cactus-test-tooling/src/main/typescript/public-api.ts index 1145088bbbd..6b9dee61f37 100755 --- a/packages/cactus-test-tooling/src/main/typescript/public-api.ts +++ b/packages/cactus-test-tooling/src/main/typescript/public-api.ts @@ -194,3 +194,20 @@ export { FABRIC_25_LTS_FABRIC_SAMPLES__ORDERER_TLS_ROOTCERT_FILE_ORG_2, IFabricOrgEnvInfo, } from "./fabric/fabric-samples-env-constants"; + +export { + IBuildContainerImageRequest, + buildContainerImage, +} from "./common/build-container-image"; + +export { + IBuildImageConnectorCordaServerRequest, + IBuildImageConnectorCordaServerResponse, + buildImageConnectorCordaServer, +} from "./corda/build-image-connector-corda-server"; + +export { + IBuildImageCordaAllInOneV412Request, + IBuildImageCordaAllInOneV412Response, + buildImageCordaAllInOneV412, +} from "./corda/build-image-corda-all-in-one-v4-12"; diff --git a/tools/docker/corda-all-in-one/README.md b/tools/docker/corda-all-in-one/README.md new file mode 100644 index 00000000000..7ba5dd13316 --- /dev/null +++ b/tools/docker/corda-all-in-one/README.md @@ -0,0 +1,128 @@ +# cactus-corda-all-in-one + +> This docker image is for `testing` and `development` only. +> Do NOT use in production! + +## Usage + +### Build and Run Image Locally + +```sh +DOCKER_BUILDKIT=1 docker build ./tools/docker/corda-all-in-one/ -t caio +docker run --rm --privileged caio +``` + +# cactus-corda-4-8-all-in-one + +> This docker image is for `testing` and `development` only. +> Do NOT use in production! + +## Usage + +### Build and Run Image Locally + +```sh +DOCKER_BUILDKIT=1 docker build ./tools/docker/corda-all-in-one/ -f ./tools/docker/corda-all-in-one/corda-v4_8/Dockerfile -t caio48 +docker run --rm --privileged caio48 +``` + +# cactus-corda-4-8-all-in-one-flowdb + +> This docker image is for `testing` and `development` only. +> Do NOT use in production! + +## Customization + +`build.gradle` file from this sample has defined a single node called PartyA. It was modified to deploy the same nodes as in the obligation sample to make it work with our CordaTestLedger: +- Notary +- ParticipantA +- ParticipantB +- ParticipantC + +## Usage + +### Build and Run Image Locally + +```sh +DOCKER_BUILDKIT=1 docker build ./tools/docker/corda-all-in-one/corda-v4_8-flowdb/ -t caio48-flowdb +docker run --rm --privileged caio48-flowdb +``` + +# cactus-corda-4-12-all-in-one + +> This docker image is for `testing` and `development` only. +> Do NOT use in production! + +## Usage + +### Build and Run Image Locally + +```sh +DOCKER_BUILDKIT=1 docker build ./tools/docker/corda-all-in-one/corda-v4_12/ -f ./tools/docker/corda-all-in-one/corda-v4_12/Dockerfile -t caio412 +docker run --rm --privileged caio412 +``` + + +# cactus-corda-5-all-in-one-solar + +> This docker image is for `testing` and `development` only. +> Do NOT use in production! + +## Usage + +### Build and Run Image Locally + +```sh +DOCKER_BUILDKIT=1 docker build ./tools/docker/corda-all-in-one/corda-v5/ -f ./tools/docker/corda-all-in-one/corda-v5/Dockerfile -t caio5 +docker run --privileged caio5 +``` + +### Install Application and Testing + +Open container CLI: + +```sh +docker exec -it /bin/sh +``` + +In container CLI, run this command to install the sample application on the network: + +```sh +/root/bin/corda-cli/bin/corda-cli package install -n solar-system /corda5-solarsystem-contracts-demo/solar-system.cpb +``` + +To check that everything works correctly, start a flow with the following curl command: + +```sh +curl -u earthling:password --insecure -X POST "https://localhost:12112/api/v1/flowstarter/startflow" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"rpcStartFlowRequest\":{\"clientId\":\"launchpad-1\",\"flowName\":\"net.corda.solarsystem.flows.LaunchProbeFlow\",\"parameters\":{\"parametersInJson\":\"{\\\"message\\\": \\\"Hello Mars\\\", \\\"target\\\": \\\"C=GB, L=FOURTH, O=MARS, OU=PLANET\\\", \\\"planetaryOnly\\\":\\\"true\\\"}\"}}}" +``` +If the command is successful, it returns a 200 response, including the flowId (a uuid) and the clientId, like the following: +```json +{ + "flowId":{ + "uuid":"9c8d5b46-be92-4be8-9569-76cb3e41cde9" + }, + "clientId":"launchpad-1" +} +``` +Using the field value ```flowId``` from the answer above, you can check the flow status: +```sh +curl -u earthling:password --insecure -X GET "https://localhost:12112/api/v1/flowstarter/flowoutcome/" -H "accept: application/json" +``` +It returns a 200 response, which includes these items in the response body: + +- Flow status +- Signatures of both parties +- ID of the state + +Sample of response: +```json +{ + "status":"COMPLETED", + "resultJson":"{ \n \"txId\" : \"SHA-256:882FCCFA0CE08FEC4F90A8BBC8B8FBC1DE3CBDA8DBED4D6562E0922234B87E4F\",\n \"outputStates\" : [\"{\\\"message\\\":\\\"Hello Mars\\\",\\\"planetaryOnly\\\":true,\\\"launcher\\\":\\\"OU\\u003dPLANET, O\\u003dEARTH, L\\u003dTHIRD, C\\u003dIE\\\",\\\"target\\\":\\\"OU\\u003dPLANET, O\\u003dMARS, L\\u003dFOURTH, C\\u003dGB\\\",\\\"linearId\\\":\\\"31800d11-b518-4fb7-a18e-18cc1c64a4ff\\\"}\"], \n \"signatures\": [\"ijMOjsLWxihWLnfxw7DoIv1gpHFaSAs+VfGSS5qaI1Z4cZu96riAo1uEFSbeskZTt2eGNwv05IP3dS08AjLRCA==\", \"2yRNwdrqKU6/lrUfgmaiXxdPYHjXxfXIYlEL8RHU2aNGQPUVXmc+jbsaNxbcig7Fs0kck28JreuUwn1lJOZODw==\"]\n}", + "exceptionDigest":null +} +``` + + +