diff --git a/docker/code-templates/nodejs/starter-contract/.gitignore b/docker/code-templates/nodejs/starter-contract/.gitignore new file mode 100644 index 0000000..cd4211e --- /dev/null +++ b/docker/code-templates/nodejs/starter-contract/.gitignore @@ -0,0 +1,2 @@ +node_modules +index.js \ No newline at end of file diff --git a/docker/code-templates/nodejs/starter-contract/package-lock.json b/docker/code-templates/nodejs/starter-contract/package-lock.json new file mode 100644 index 0000000..e5519ef --- /dev/null +++ b/docker/code-templates/nodejs/starter-contract/package-lock.json @@ -0,0 +1,41 @@ +{ + "name": "_projname_", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "_projname_", + "version": "1.0.0", + "dependencies": { + "@vercel/ncc": "0.34.0", + "hotpocket-nodejs-contract": "0.5.6" + } + }, + "node_modules/@vercel/ncc": { + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.34.0.tgz", + "integrity": "sha512-G9h5ZLBJ/V57Ou9vz5hI8pda/YQX5HQszCs3AmIus3XzsmRn/0Ptic5otD3xVST8QLKk7AMk7AqpsyQGN7MZ9A==", + "bin": { + "ncc": "dist/ncc/cli.js" + } + }, + "node_modules/hotpocket-nodejs-contract": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/hotpocket-nodejs-contract/-/hotpocket-nodejs-contract-0.5.6.tgz", + "integrity": "sha512-Q52cE5lYGoVGvdUl3cDYixAh27QxzFF6JFaOm1+HoXU36dsKxZfDeJLGCfnDf4XcgEytM2mPE9STWUV+mdHqEg==" + } + }, + "dependencies": { + "@vercel/ncc": { + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.34.0.tgz", + "integrity": "sha512-G9h5ZLBJ/V57Ou9vz5hI8pda/YQX5HQszCs3AmIus3XzsmRn/0Ptic5otD3xVST8QLKk7AMk7AqpsyQGN7MZ9A==" + }, + "hotpocket-nodejs-contract": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/hotpocket-nodejs-contract/-/hotpocket-nodejs-contract-0.5.6.tgz", + "integrity": "sha512-Q52cE5lYGoVGvdUl3cDYixAh27QxzFF6JFaOm1+HoXU36dsKxZfDeJLGCfnDf4XcgEytM2mPE9STWUV+mdHqEg==" + } + } +} diff --git a/docker/code-templates/nodejs/starter-contract/src/_projname_.js b/docker/code-templates/nodejs/starter-contract/src/_projname_.js index c8c7656..12fb1fc 100644 --- a/docker/code-templates/nodejs/starter-contract/src/_projname_.js +++ b/docker/code-templates/nodejs/starter-contract/src/_projname_.js @@ -7,7 +7,7 @@ const dataFile = 'datafile.txt' export class _projname_ { sendOutput; // This function must be wired up by the caller. - async handleRequest(userPublicKey, message, isReadOnly) { + async handleRequest(user, message, isReadOnly) { // This sample application defines two simple messages. 'get' and 'set'. // It's up to the application to decide the structure and contents of messages. @@ -16,7 +16,7 @@ export class _projname_ { // Retrieved previously saved data and return to the user. const data = await this.getData(); - await this.sendOutput(userPublicKey, { + await this.sendOutput(user, { type: 'data_result', data: data }) @@ -28,7 +28,7 @@ export class _projname_ { await this.setData(message.data); } else { - await this.sendOutput(userPublicKey, { + await this.sendOutput(user, { type: 'error', error: 'Set data not supported in readonly mode' }) @@ -36,7 +36,7 @@ export class _projname_ { } else { - await this.sendOutput(userPublicKey, { + await this.sendOutput(user, { type: 'error', error: 'Unknown message type' }) diff --git a/docker/code-templates/nodejs/starter-contract/src/contract.js b/docker/code-templates/nodejs/starter-contract/src/contract.js index a9d429d..9fa89e7 100644 --- a/docker/code-templates/nodejs/starter-contract/src/contract.js +++ b/docker/code-templates/nodejs/starter-contract/src/contract.js @@ -10,9 +10,7 @@ async function contract(ctx) { const app = new _projname_(); // Wire-up output emissions from the application before we pass user inputs to it. - app.sendOutput = async (userPublicKey, output) => { - // In HotPocket, each user is represented by their Ed25519 public key. - const user = ctx.users.find(userPublicKey); + app.sendOutput = async (user, output) => { await user.send(output) } @@ -40,7 +38,7 @@ async function contract(ctx) { const message = JSON.parse(buf); // Pass the JSON message to our application logic component. - await app.handleRequest(userPublicKey, message, isReadOnly); + await app.handleRequest(user, message, isReadOnly); } } } diff --git a/linux/hpdevkit.sh b/linux/hpdevkit.sh new file mode 100755 index 0000000..3fa07a7 --- /dev/null +++ b/linux/hpdevkit.sh @@ -0,0 +1,179 @@ +#!/bin/bash +globalPrefix="hpdevkit" +version="0.1.0" + +cluster="default" +clusterSize=$([ -z $HP_CLUSTER_SIZE ] && echo 1 || echo "$HP_CLUSTER_SIZE") +defaultNode=$([ -z $HP_DEFAULT_NODE ] && echo 1 || echo "$HP_DEFAULT_NODE") +devkitImage=$([ -z $HP_DEVKIT_IMAGE ] && echo "evernodedev/hpdevkit" || echo "$HP_DEVKIT_IMAGE") +instanceImage=$([ -z $HP_INSTANCE_IMAGE ] && echo "evernodedev/hotpocket:latest-ubt.20.04-njs.16" || echo "$HP_INSTANCE_IMAGE") + +volumeMount=/$globalPrefix\_vol +volume=$globalPrefix\_$cluster\_vol +network=$globalPrefix\_$cluster\_net +containerPrefix=$globalPrefix\_$cluster\_node +bundleMount=$volumeMount/contract_bundle +deploymentContainerName=$globalPrefix\_$cluster\_deploymgr +codegenContainerName=$globalPrefix\_codegen +configOverridesFile="hp.cfg.override" +codegenOutputDir="/codegen-output" + +function devKitContainer() { + command="docker $1 -it" + if [ ! -z "$NAME" ]; then + command+=" --name $NAME" + fi + + if [ ! -z "$DETACHED" ]; then + command+=" -d" + fi + + if [ ! -z "$AUTOREMOVE" ]; then + command+=" --rm" + fi + + if [ ! -z "$MOUNTSOCK" ]; then + command+=" --mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock" + fi + + if [ ! -z "$MOUNTVOLUME" ]; then + command+=" --mount type=volume,src=$volume,dst=$volumeMount" + fi + + if [ ! -z "$ENTRYPOINT" ]; then + command+=" --entrypoint $ENTRYPOINT" + else + command+=" --entrypoint /bin/bash" + fi + + command+=" -e CLUSTER=$cluster -e CLUSTER_SIZE=$clusterSize -e DEFAULT_NODE=$defaultNode -e VOLUME=$volume -e NETWORK=$network" + command+=" -e CONTAINER_PREFIX=$containerPrefix -e VOLUME_MOUNT=$volumeMount -e BUNDLE_MOUNT=$bundleMount -e HOTPOCKET_IMAGE=$instanceImage" + command+=" -e CONFIG_OVERRIDES_FILE=$configOverridesFile -e CODEGEN_OUTPUT=$codegenOutputDir" + + command+=" $devkitImage" + + if [ ! -z "$CMD" ]; then + if [ ! -z "$ENTRYPOINT" ]; then + command+=" $CMD" + else + command+=" -c '$CMD'" + fi + fi + + eval $command + lastExitedCode=$? + if [ ! -z "STATUS" ]; then + if [ $lastExitedCode -eq 0 ]; then + return 0 + else + return 1 + fi + fi +} + +function executeInContainer() { + if [ ! -z "$CONTAINERNAME" ]; then + cmd="docker exec -it $CONTAINERNAME /bin/bash -c '$1'" + ! eval $cmd && echo "Docker execution failed." + fi +} + +function initializeDeploymentCluster() { + docker inspect $deploymentContainerName &>/dev/null + if [[ $? -gt 0 ]]; then + echo "Initializing deployment cluster" + + # Stop cluster if running. Create cluster if not exists. + AUTOREMOVE="true" MOUNTSOCK="true" CMD="cluster stop ; cluster create" devKitContainer run + + # Spin up management container. + NAME="$deploymentContainerName" DETACHED="true" MOUNTSOCK="true" MOUNTVOLUME="true" devKitContainer run + + # Bind the instance mesh network config together. + CONTAINERNAME="$deploymentContainerName" executeInContainer "cluster bindmesh" + fi +} + +function teardownDeploymentCluster() { + docker stop $deploymentContainerName 2>/dev/null + docker rm $deploymentContainerName 2>/dev/null + AUTOREMOVE="true" MOUNTSOCK="true" CMD="cluster stop ; cluster destroy" devKitContainer run +} + +function deploy() { + + if [ ! -z $1 ]; then + path=$1 + initializeDeploymentCluster + + # If copying a directory, delete target bundle directory. If not create empty target bundle directory to copy a file. + prepareBundleDir=" " + if [[ -d $path ]]; then + prepareBundleDir="rm -rf $bundleMount" + + else + prepareBundleDir="mkdir -p $bundleMount && rm -rf $bundleMount/* $bundleMount/.??*" + fi + + CONTAINERNAME="$deploymentContainerName" executeInContainer "$prepareBundleDir" + docker cp $path "$deploymentContainerName:$bundleMount" + + # Sync contract bundle to all instance directories in the cluster. + CONTAINERNAME="$deploymentContainerName" executeInContainer "cluster stop ; cluster sync ; cluster start" + + if [ $defaultNode -gt 0 ]; then + echo "Streaming logs of node $defaultNode:" + CONTAINERNAME="$deploymentContainerName" executeInContainer "cluster logs $defaultNode" + fi + + else + echo "Please specify directory or file path to deploy." + fi +} + +function codeGenerator() { + platform=$1 + apptype=$2 + projName=$3 + if [[ -d $projName ]]; then + echo "Directory '$projName' already exists." + exit 1 + fi + + NAME="$codegenContainerName" ENTRYPOINT="codegen" STATUS="true" CMD="$platform $apptype $projName" devKitContainer run + lastExitedCode=$? + if [ $lastExitedCode == 0 ]; then + ! docker cp $codegenContainerName:$codegenOutputDir ./$projName && echo "Project '$projName' generation failed." && exit 1 + echo "Project '$projName' created." + fi + + docker rm $codegenContainerName &>/dev/null + +} + +echo "HotPocket devkit launcher ($version)" + +funcCommand=$1 +funcCommandError="Invalid command. Expected: deploy | clean | start | stop | logs | gen" + +if [ ! -z "$funcCommand" ]; then + if [ "$funcCommand" == "gen" ]; then + echo "Code generator" + codeGenerator $2 $3 $4 + else + echo "command: $funcCommand (cluster: $cluster)" + if [ "$funcCommand" == "deploy" ]; then + deploy $2 + elif [ "$funcCommand" == "clean" ]; then + teardownDeploymentCluster + elif [[ "$funcCommand" == "logs" || "$funcCommand" == "start" || "$funcCommand" == "stop" ]]; then + AUTOREMOVE="true" MOUNTSOCK="true" ENTRYPOINT="cluster" CMD="$1 $2" devKitContainer run + else + echo "$funcCommandError" + fi + fi +else + echo "$funcCommandError" +fi + +exit 0