diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000000..451f8fa3a98 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,59 @@ +.git +*/*/node_modules +*/*/dist +*/*/build +whitepaper +docs +contribs +images +tools +packages/business-logic-plugin +packages/config +packages/ledger-plugin +packages/cactus-ledger-plugin +packages/routing-interface +packages/copyStaticAssets.ts +packages/jest.config.js + +.config.json +.nyc_output/ +dist/ +.DS_Store +node_modules/ +logs/ +jspm_packages/ +generated-sources/ +coverage/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# next.js build output +.next + +# vscode files +.vscode/* +!.vscode/template.launch.json + +# Introperability App specifics +examples/simple-asset-transfer/fabric/**/hfc-key-store/ + +bin/ +.tmp/ +lerna-debug.log +cactus-openapi-spec.json +cactus-openapi-spec-*.json +.npmrc +*.log diff --git a/README.md b/README.md index b0bbe4948d8..f660a3086c6 100644 --- a/README.md +++ b/README.md @@ -18,23 +18,20 @@ As blockchain technology proliferates, blockchain integration will become an inc ### Supply Chain Example -```sh -git clone https://github.com/hyperledger/cactus.git -cd cactus -npm install -npm run configure -cd examples/supply-chain-app/ -npm install --no-package-lock -cd ../../ -npm run build:dev -$ npm start:example-supply-chain -... -[2020-10-27T00:38:00.574Z] INFO (api-server): Cactus API reachable http://127.0.0.1:5000 -[2020-10-27T00:38:00.574Z] INFO (api-server): Cactus Cockpit reachable http://127.0.0.1:6000 -... -[2020-10-27T00:38:00.574Z] INFO (api-server): Cactus API reachable http://127.0.0.1:5100 -[2020-10-27T00:38:00.574Z] INFO (api-server): Cactus Cockpit reachable http://127.0.0.1:6100 -``` +1. Run the following command to pull up the container that will run the example application and the test ledgers as well: + ```sh + docker run \ + --rm \ + --privileged \ + -p 3000:3000 \ + -p 3100:3100 \ + -p 4000:4000 \ + -p 4100:4100 \ + hyperledger/cactus-example-supply-chain-app:2021-02-03-be1bc02 + ``` +2. Wait for the output to show the message `INFO (api-server): Cactus Cockpit reachable http://0.0.0.0:3100` +3. Visit http://localhost:3100 in a web browser with Javascript enabled +4. Use the graphical user interface to create data on both ledgers and observe that a consistent view of the data from different ledgers is provided. Once the last command has finished executing, open link printed on the console with a web browser of your choice diff --git a/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app-cli.ts b/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app-cli.ts index 14d30003dab..7881767c6ae 100755 --- a/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app-cli.ts +++ b/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app-cli.ts @@ -1,15 +1,24 @@ #!/usr/bin/env node +import { ConfigService } from "@hyperledger/cactus-cmd-api-server"; +import { LoggerProvider } from "@hyperledger/cactus-common"; import { ISupplyChainAppOptions, SupplyChainApp } from "./supply-chain-app"; -export async function launchApp(cliOpts?: any): Promise { - const appOptions: ISupplyChainAppOptions = { logLevel: "TRACE", ...cliOpts }; +export async function launchApp(): Promise { + const configService = new ConfigService(); + const config = configService.getOrCreate(); + const serverOptions = config.getProperties(); + LoggerProvider.setLogLevel(serverOptions.logLevel); + + const appOptions: ISupplyChainAppOptions = { + logLevel: serverOptions.logLevel, + }; const supplyChainApp = new SupplyChainApp(appOptions); try { await supplyChainApp.start(); } catch (ex) { console.error(`SupplyChainApp crashed. Existing...`, ex); - await supplyChainApp.stop(); + await supplyChainApp?.stop(); process.exit(-1); } } diff --git a/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts b/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts index 533c30f5c48..5020392f606 100644 --- a/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts +++ b/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts @@ -88,10 +88,10 @@ export class SupplyChainApp { const keychainIdB = "PluginKeychainMemory_B"; // Reserve the ports where the Cactus nodes will run API servers, GUI - const httpApiA = await Servers.startOnPreferredPort(5000); - const httpApiB = await Servers.startOnPreferredPort(5100); - const httpGuiA = await Servers.startOnPreferredPort(6000); - const httpGuiB = await Servers.startOnPreferredPort(6100); + const httpApiA = await Servers.startOnPort(4000, "0.0.0.0"); + const httpApiB = await Servers.startOnPort(4100, "0.0.0.0"); + const httpGuiA = await Servers.startOnPort(3000, "0.0.0.0"); + const httpGuiB = await Servers.startOnPort(3100, "0.0.0.0"); const addressInfoA = httpApiA.address() as AddressInfo; const nodeApiHostA = `http://localhost:${addressInfoA.port}`; @@ -299,27 +299,23 @@ export class SupplyChainApp { httpServerCockpit: Server, pluginRegistry: PluginRegistry, ): Promise { - const addressInfo = httpServerApi.address() as AddressInfo; + const addressInfoApi = httpServerApi.address() as AddressInfo; + const addressInfoCockpit = httpServerCockpit.address() as AddressInfo; const configService = new ConfigService(); - const apiServerOptions = configService.newExampleConfig(); - // FIXME: Plugin imports will only work once we have this merged and released in webpack - // https://github.com/webpack/webpack/pull/11316 - apiServerOptions.plugins = []; - - apiServerOptions.configFile = ""; - apiServerOptions.apiCorsDomainCsv = "*"; - apiServerOptions.apiPort = addressInfo.port; - apiServerOptions.apiTlsEnabled = false; - apiServerOptions.cockpitTlsEnabled = false; - apiServerOptions.cockpitPort = 0; - apiServerOptions.logLevel = this.options.logLevel || "INFO"; - apiServerOptions.cockpitWwwRoot = - "./node_modules/@hyperledger/cactus-example-supply-chain-frontend/www/"; - const config = configService.newExampleConfigConvict(apiServerOptions); + const config = configService.getOrCreate(); + const properties = config.getProperties(); + + properties.plugins = []; + properties.configFile = ""; + properties.apiPort = addressInfoApi.port; + properties.apiHost = addressInfoApi.address; + properties.cockpitHost = addressInfoCockpit.address; + properties.cockpitPort = addressInfoCockpit.port; + properties.logLevel = this.options.logLevel || "INFO"; const apiServer = new ApiServer({ - config: config.getProperties(), + config: properties, httpServerApi, httpServerCockpit, pluginRegistry, diff --git a/examples/supply-chain-app/.dockerignore b/examples/supply-chain-app/.dockerignore new file mode 100644 index 00000000000..8bb9095e74c --- /dev/null +++ b/examples/supply-chain-app/.dockerignore @@ -0,0 +1,48 @@ +.git +*/*/node_modules +*/*/dist +*/*/build + +.config.json +.nyc_output/ +dist/ +.DS_Store +node_modules/ +docs/main +logs/ +jspm_packages/ +generated-sources/ +coverage/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# next.js build output +.next + +# vscode files +.vscode/* +!.vscode/template.launch.json + +# Introperability App specifics +examples/simple-asset-transfer/fabric/**/hfc-key-store/ + +bin/ +.tmp/ +lerna-debug.log +cactus-openapi-spec.json +cactus-openapi-spec-*.json +.npmrc +*.log diff --git a/examples/supply-chain-app/Dockerfile b/examples/supply-chain-app/Dockerfile new file mode 100644 index 00000000000..834b58df02f --- /dev/null +++ b/examples/supply-chain-app/Dockerfile @@ -0,0 +1,139 @@ +FROM node:12.20.1-alpine3.12 as builder + +RUN apk update + +# Need JDK for the openapi-generator that is part of the build process +RUN apk add --no-cache openjdk8=8.252.09-r0 + +# Need git because some of our npm depedencies might be coming +# straight from github instead of being an npm package on npmjs.com. +RUN apk add --no-cache git + +# Some install scripts of the npm package fabric-network need python +RUN apk add --no-cache python3 py3-pip + +RUN npm install modclean -g + +WORKDIR / +RUN mkdir /app/ +WORKDIR /app/ +COPY ./ ./ +RUN npm ci +RUN ./node_modules/.bin/lerna clean --yes +RUN ./node_modules/.bin/lerna bootstrap +RUN npm run build:dev:backend +RUN npm run webpack:dev:web +RUN npm run build:dev:frontend -- --scope='@hyperledger/cactus-example-supply-chain-frontend' +RUN ./node_modules/.bin/lerna clean --yes +RUN ./node_modules/.bin/lerna bootstrap -- --production --no-optional + +# RUN modclean --run --patterns="default:*" --path ./examples/cactus-example-supply-chain-backend/ +# RUN modclean --run --patterns="default:*" --path ./examples/cactus-example-supply-chain-business-logic-plugin/ +# RUN modclean --run --patterns="default:*" --path ./examples/cactus-example-supply-chain-frontend/ +# RUN modclean --run --patterns="default:*" --path ./packages/cactus-api-client/ +# RUN modclean --run --patterns="default:*" --path ./packages/cactus-cmd-api-server/ +# RUN modclean --run --patterns="default:*" --path ./packages/cactus-cockpit/ +# RUN modclean --run --patterns="default:*" --path ./packages/cactus-common/ +# RUN modclean --run --patterns="default:*" --path ./packages/cactus-core-api/ +# RUN modclean --run --patterns="default:*" --path ./packages/cactus-core/ +# RUN modclean --run --patterns="default:*" --path ./packages/cactus-plugin-consortium-manual/ +# RUN modclean --run --patterns="default:*" --path ./packages/cactus-plugin-keychain-memory/ +# RUN modclean --run --patterns="default:*" --path ./packages/cactus-plugin-keychain-vault/ +# RUN modclean --run --patterns="default:*" --path ./packages/cactus-plugin-ledger-connector-besu/ +# RUN modclean --run --patterns="default:*" --path ./packages/cactus-plugin-ledger-connector-fabric/ +# RUN modclean --run --patterns="default:*" --path ./packages/cactus-plugin-ledger-connector-quorum/ +# RUN modclean --run --patterns="default:*" --path ./packages/cactus-test-tooling/ + +RUN rm -rf ./packages/cactus-test-plugin* +RUN rm -rf ./packages/cactus-test-cmd* +RUN rm -rf ./packages/cactus-test-api* +RUN rm -rf ./node_modules/ + +FROM docker:20.10.2-dind-rootless as runner + +USER root + +RUN apk update + +# Add bash for convenience, sh has less features +RUN apk add --no-cache bash=5.0.17-r0 + +# Need curl for healthchecks +RUN apk add --no-cache curl=7.69.1-r3 + +# The file binary is used to inspect exectubles when debugging container image issues +RUN apk add --no-cache file=5.38-r0 + +RUN apk add --no-cache nodejs=12.20.1-r0 +RUN apk add --no-cache npm=12.20.1-r0 + +RUN apk add --no-cache ca-certificates=20191127-r4 +RUN apk add --no-cache tzdata=2020f-r0 + +# Install supervisord because we need to run the docker daemon and also the fabric network +# meaning that we have multiple processes to run. +RUN apk add --no-cache supervisor=4.2.0-r0 + +ARG APP=/usr/src/app/ + +ENV TZ=Etc/UTC +ENV APP_USER=appuser + +RUN addgroup --system $APP_USER +RUN adduser --system $APP_USER -G $APP_USER +RUN addgroup $APP_USER rootless +RUN mkdir -p ${APP} + +COPY --chown=$APP_USER:$APP_USER --from=builder /app/ ${APP} + +RUN mkdir -p "${APP}/log/" +RUN chown -R $APP_USER:$APP_USER "${APP}/log/" + +# TODO: Can we hack it together so that the whole thing works rootless? +# USER $APP_USER + +WORKDIR ${APP} + +COPY --chown=${APP_USER}:${APP_USER} ./examples/supply-chain-app/healthcheck.sh / + +ENV CACTUS_NODE_ID=- +ENV CONSORTIUM_ID=- +ENV KEY_PAIR_PEM=- +ENV COCKPIT_WWW_ROOT=examples/cactus-example-supply-chain-frontend/www/ +ENV COCKPIT_TLS_ENABLED=false +ENV COCKPIT_CORS_DOMAIN_CSV=\* +ENV COCKPIT_MTLS_ENABLED=false +ENV COCKPIT_TLS_CERT_PEM=- +ENV COCKPIT_TLS_KEY_PEM=- +ENV COCKPIT_TLS_CLIENT_CA_PEM=- +ENV COCKPIT_HOST=0.0.0.0 +ENV COCKPIT_PORT=3000 +ENV API_MTLS_ENABLED=false +ENV API_TLS_ENABLED=false +ENV API_CORS_DOMAIN_CSV=\* +ENV API_TLS_CERT_PEM=- +ENV API_TLS_CLIENT_CA_PEM=- +ENV API_TLS_KEY_PEM=- +ENV API_HOST=0.0.0.0 +ENV API_PORT=4000 +ENV LOG_LEVEL=TRACE + +COPY examples/supply-chain-app/supervisord.conf /etc/supervisord.conf + +# supervisord web ui/dashboard +EXPOSE 9001 +# API #1 +EXPOSE 4000 +# API #2 +EXPOSE 4100 +# GUI #1 +EXPOSE 3000 +# GUI #2 +EXPOSE 3100 + +# Extend the parent image's entrypoint +# https://superuser.com/questions/1459466/can-i-add-an-additional-docker-entrypoint-script +ENTRYPOINT ["/usr/bin/supervisord"] +CMD ["--configuration", "/etc/supervisord.conf", "--nodaemon"] +HEALTHCHECK --interval=1s --timeout=5s --start-period=20s --retries=250 \ + CMD /usr/src/app/examples/supply-chain-app/healthcheck.sh diff --git a/examples/supply-chain-app/README.md b/examples/supply-chain-app/README.md index ea52f279e95..ba3f3814a7b 100644 --- a/examples/supply-chain-app/README.md +++ b/examples/supply-chain-app/README.md @@ -3,19 +3,28 @@ ## Usage -1. Build the project as instructed by the [BUILD.md](../../BUILD.md) file. -2. Execute the following while standing in the project root directory: +1. Execute the following from: ```sh - git clone https://github.com/hyperledger/cactus.git - cd cactus - npm install - npm run configure - cd examples/supply-chain-app - rm -rf node_modules/ - rm package-lock.json - npm install --no-package-lock - npm start + docker run --rm -it --privileged -p 3000:3000 -p 3100:3100 -p 4000:4000 -p 4100:4100 hyperledger/cactus-example-supply-chain-app:2021-02-03-be1bc02 ``` -3. Observe the example application pulling up +2. Observe the example application pulling up in the logs 1. the test ledger containers, - 2. a test consortium with multiple members and their Cactus nodes \ No newline at end of file + 2. a test consortium with multiple members and their Cactus nodes +3. Wait for the output to show the message `INFO (api-server): Cactus Cockpit reachable http://0.0.0.0:3100` +4. Visit http://0.0.0.0:3100 in your web browser with Javascript enabled + +## Building and running the container locally + +```sh +# Change directories to the project root + +# Build the dockar image and tag it as "scaeb" for supply chain app example backend +DOCKER_BUILDKIT=1 docker build -f ./examples/supply-chain-app/Dockerfile . -t scaeb + +# Run the built image with ports mapped to the host machine as you see fit +# The --privileged flag is required because we use Docker-in-Docker for pulling +# up ledger containers from within the container in order to have the example +# be completely self-contained where you don't need to worry about running +# multiple different ledgers jus this one container. +docker run --rm -it --privileged -p 3000:3000 -p 3100:3100 -p 4000:4000 -p 4100:4100 scaeb +``` diff --git a/examples/supply-chain-app/healthcheck.sh b/examples/supply-chain-app/healthcheck.sh new file mode 100755 index 00000000000..c0a0e586b0b --- /dev/null +++ b/examples/supply-chain-app/healthcheck.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +curl -v -i -X OPTIONS http://127.0.0.1:${COCKPIT_PORT} \ No newline at end of file diff --git a/examples/supply-chain-app/hooks/post_push b/examples/supply-chain-app/hooks/post_push new file mode 100755 index 00000000000..8f41b30ce4e --- /dev/null +++ b/examples/supply-chain-app/hooks/post_push @@ -0,0 +1,18 @@ +#!/bin/bash + + +SHORTHASH="$(git rev-parse --short HEAD)" +TODAYS_DATE="$(date +%F)" + +# +# We tag every image with today's date and also the git short hash +# Today's date helps humans quickly intuit which version is older/newer +# And the short hash helps identify the exact git revision that the image was +# built from in case you are chasing some exotic bug that requires this sort of +# rabbithole diving where you are down to comparing the images at this level. +# +DOCKER_TAG="$TODAYS_DATE-$SHORTHASH" + + +docker tag $IMAGE_NAME $DOCKER_REPO:$DOCKER_TAG +docker push $DOCKER_REPO:$DOCKER_TAG diff --git a/examples/supply-chain-app/supervisord.conf b/examples/supply-chain-app/supervisord.conf new file mode 100644 index 00000000000..958f2eb7145 --- /dev/null +++ b/examples/supply-chain-app/supervisord.conf @@ -0,0 +1,25 @@ +[supervisord] +logfile = /usr/src/app/log/supervisord.log +logfile_maxbytes = 50MB +logfile_backups=10 +loglevel = info + +[program:dockerd] +command=dockerd-entrypoint.sh +autostart=true +autorestart=true +stderr_logfile=/usr/src/app/log/dockerd.err.log +stdout_logfile=/usr/src/app/log/dockerd.out.log + +[program:supply-chain-app] +command=node /usr/src/app/examples/cactus-example-supply-chain-backend/dist/lib/main/typescript/supply-chain-app-cli.js +autostart=true +autorestart=unexpected +exitcodes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 + +[inet_http_server] +port = 0.0.0.0:9001