From 31f180c144b200f0a1e2f9fc9f3a9981337c4663 Mon Sep 17 00:00:00 2001 From: Julian <115516918+juliansommer@users.noreply.github.com> Date: Fri, 23 Jan 2026 08:23:01 +1000 Subject: [PATCH 1/6] fix: stop-instances edge case --- hive/daemon/daemon.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/hive/daemon/daemon.go b/hive/daemon/daemon.go index 3cc374019..0c643e804 100644 --- a/hive/daemon/daemon.go +++ b/hive/daemon/daemon.go @@ -1465,12 +1465,15 @@ func (d *Daemon) stopInstance(instances map[string]*vm.VM, deleteVolume bool) er // Wait for all shutdowns to finish wg.Wait() - // Unsubscribe from NATS subjects that match instances - for _, instance := range instances { - slog.Info("Unsubscribing from NATS subject", "instance", instance.ID) - d.natsSubscriptions[fmt.Sprintf("ec2.cmd.%s", instance.ID)].Unsubscribe() - // TODO: Remove redundant subscription if not used - //d.natsSubscriptions[fmt.Sprintf("ec2.describe.%s", instance.ID)].Unsubscribe() + // Only unsubscribe from NATS subjects when terminating (deleteVolume=true) + // For stop operations, keep the subscription so we can receive start commands + if deleteVolume { + for _, instance := range instances { + slog.Info("Unsubscribing from NATS subject", "instance", instance.ID) + d.natsSubscriptions[fmt.Sprintf("ec2.cmd.%s", instance.ID)].Unsubscribe() + // TODO: Remove redundant subscription if not used + //d.natsSubscriptions[fmt.Sprintf("ec2.describe.%s", instance.ID)].Unsubscribe() + } } return nil From 31158932b815433f72b463ccc35d96a9ae90254d Mon Sep 17 00:00:00 2001 From: Julian Sommer <115516918+juliansommer@users.noreply.github.com> Date: Fri, 23 Jan 2026 09:49:28 +1100 Subject: [PATCH 2/6] fix: run-instances in e2e --- go.mod | 5 + go.sum | 2 + hive/gateway/ec2.go | 2 +- tests/e2e/run-e2e.sh | 225 +++++++++++++++++++++++++------------------ 4 files changed, 137 insertions(+), 97 deletions(-) diff --git a/go.mod b/go.mod index c6a57453e..a198ec7bf 100644 --- a/go.mod +++ b/go.mod @@ -54,6 +54,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/minio/crc64nvme v1.1.1 // indirect github.com/minio/highwayhash v1.0.3 // indirect github.com/nats-io/jwt/v2 v2.7.4 // indirect github.com/nats-io/nkeys v0.4.11 // indirect @@ -109,3 +110,7 @@ tool ( golang.org/x/vuln/cmd/govulncheck honnef.co/go/tools/cmd/staticcheck ) + +replace github.com/mulgadc/viperblock => ../viperblock + +replace github.com/mulgadc/predastore => ../predastore diff --git a/go.sum b/go.sum index 41eede855..5e7f34ec7 100644 --- a/go.sum +++ b/go.sum @@ -122,6 +122,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI= +github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= github.com/mulgadc/predastore v0.1.5 h1:7SySkQQ5MQ1NTcKcB14pCUIJipq0YVgCJZeKM6ytrLM= diff --git a/hive/gateway/ec2.go b/hive/gateway/ec2.go index 7d513829d..c3af0898c 100644 --- a/hive/gateway/ec2.go +++ b/hive/gateway/ec2.go @@ -63,7 +63,7 @@ func (gw *GatewayConfig) EC2_Request(ctx *fiber.Ctx) error { } // Convert to XML - payload := utils.GenerateXMLPayload("RunInstanceResponse", output) + payload := utils.GenerateXMLPayload("RunInstancesResponse", output) xmlOutput, err = utils.MarshalToXML(payload) if err != nil { diff --git a/tests/e2e/run-e2e.sh b/tests/e2e/run-e2e.sh index e902b029b..6a7c960f0 100755 --- a/tests/e2e/run-e2e.sh +++ b/tests/e2e/run-e2e.sh @@ -5,9 +5,30 @@ set -e cleanup() { EXIT_CODE=$? if [ $EXIT_CODE -ne 0 ]; then - echo "=== Hive Service Log ===" + echo "" + echo "=== NATS Service Log ===" + if [ -f ~/hive/logs/nats.log ]; then + tail -100 ~/hive/logs/nats.log 2>/dev/null + fi + echo "" + echo "=== Predastore Service Log ===" + if [ -f ~/hive/logs/predastore.log ]; then + tail -100 ~/hive/logs/predastore.log 2>/dev/null + fi + echo "" + echo "=== Viperblock Service Log ===" + if [ -f ~/hive/logs/viperblock.log ]; then + tail -100 ~/hive/logs/viperblock.log 2>/dev/null + fi + echo "" + echo "=== Hive Daemon Log ===" if [ -f ~/hive/logs/hive.log ]; then - cat ~/hive/logs/hive.log 2>/dev/null + tail -200 ~/hive/logs/hive.log 2>/dev/null + fi + echo "" + echo "=== AWS Gateway Log ===" + if [ -f ~/hive/logs/awsgw.log ]; then + tail -200 ~/hive/logs/awsgw.log 2>/dev/null fi fi ./scripts/stop-dev.sh @@ -149,114 +170,126 @@ $AWS_EC2 describe-images --image-ids "$AMI_ID" | jq -e ".Images[0] | select(.Ima # Phase 5: Instance Lifecycle echo "Phase 5: Instance Lifecycle" # Launch a VM (run-instances) -# INSTANCE_ID=$($AWS_EC2 run-instances --image-id "$AMI_ID" --instance-type "$INSTANCE_TYPE" --key-name test-key-1 --query 'Instances[0].InstanceId' --output text) -# if [ -z "$INSTANCE_ID" ] || [ "$INSTANCE_ID" == "None" ]; then -# echo "Failed to launch instance" -# exit 1 -# fi -# echo "Launched Instance ID: $INSTANCE_ID" +echo "Running: aws ec2 run-instances --image-id $AMI_ID --instance-type $INSTANCE_TYPE --key-name test-key-1" +# Capture full output for debugging +set +e # Temporarily disable exit on error to capture output +RUN_OUTPUT=$($AWS_EC2 run-instances --image-id "$AMI_ID" --instance-type "$INSTANCE_TYPE" --key-name test-key-1 2>&1) +RUN_EXIT_CODE=$? +set -e # Re-enable exit on error +echo "Run instances exit code: $RUN_EXIT_CODE" +echo "Run instances output: $RUN_OUTPUT" +if [ $RUN_EXIT_CODE -ne 0 ]; then + echo "❌ Failed to launch instance - AWS CLI returned error" + exit 1 +fi +INSTANCE_ID=$(echo "$RUN_OUTPUT" | jq -r '.Instances[0].InstanceId') +if [ -z "$INSTANCE_ID" ] || [ "$INSTANCE_ID" == "None" ] || [ "$INSTANCE_ID" == "null" ]; then + echo "Failed to launch instance - no InstanceId in response" + exit 1 +fi +echo "Launched Instance ID: $INSTANCE_ID" # Poll until state is running (describe-instances) -# echo "Polling for instance running state..." -# COUNT=0 -# STATE="unknown" -# while [ $COUNT -lt 60 ]; do -# # Capture full output to check if instance even exists in the response -# DESCRIBE_OUTPUT=$($AWS_EC2 describe-instances --instance-ids "$INSTANCE_ID") || { -# echo "⚠️ Gateway request failed, retrying... ($COUNT/60)" -# sleep 5 -# COUNT=$((COUNT + 1)) -# continue -# } +echo "Polling for instance running state..." +COUNT=0 +STATE="unknown" +while [ $COUNT -lt 60 ]; do + # Capture full output to check if instance even exists in the response + DESCRIBE_OUTPUT=$($AWS_EC2 describe-instances --instance-ids "$INSTANCE_ID") || { + echo "⚠️ Gateway request failed, retrying... ($COUNT/60)" + sleep 5 + COUNT=$((COUNT + 1)) + continue + } -# if [ -z "$DESCRIBE_OUTPUT" ]; then -# echo "⚠️ Gateway returned empty response, retrying..." -# sleep 5 -# COUNT=$((COUNT + 1)) -# continue -# fi + if [ -z "$DESCRIBE_OUTPUT" ]; then + echo "⚠️ Gateway returned empty response, retrying..." + sleep 5 + COUNT=$((COUNT + 1)) + continue + fi -# # Extract state using jq -# STATE=$(echo "$DESCRIBE_OUTPUT" | jq -r '.Reservations[0].Instances[0].State.Name // "not-found"') + # Extract state using jq + STATE=$(echo "$DESCRIBE_OUTPUT" | jq -r '.Reservations[0].Instances[0].State.Name // "not-found"') -# echo "Instance state: $STATE" -# if [ "$STATE" == "running" ]; then -# break -# fi + echo "Instance state: $STATE" + if [ "$STATE" == "running" ]; then + break + fi -# if [ "$STATE" == "terminated" ]; then -# echo "❌ Instance terminated unexpectedly!" -# exit 1 -# fi + if [ "$STATE" == "terminated" ]; then + echo "❌ Instance terminated unexpectedly!" + exit 1 + fi -# sleep 5 -# COUNT=$((COUNT + 1)) -# done + sleep 5 + COUNT=$((COUNT + 1)) +done -# if [ "$STATE" != "running" ]; then -# echo "Instance failed to reach running state" -# exit 1 -# fi +if [ "$STATE" != "running" ]; then + echo "Instance failed to reach running state" + exit 1 +fi -# # Verify root volume attached to the instance (describe-volumes) -# VOLUME_ID=$($AWS_EC2 describe-volumes --query 'Volumes[0].VolumeId' --output text) -# if [ -z "$VOLUME_ID" ] || [ "$VOLUME_ID" == "None" ]; then -# echo "Failed to find volume for instance $INSTANCE_ID" -# exit 1 -# fi -# echo "Volume ID: $VOLUME_ID" +# Verify root volume attached to the instance (describe-volumes) +VOLUME_ID=$($AWS_EC2 describe-volumes --query 'Volumes[0].VolumeId' --output text) +if [ -z "$VOLUME_ID" ] || [ "$VOLUME_ID" == "None" ]; then + echo "Failed to find volume for instance $INSTANCE_ID" + exit 1 +fi +echo "Volume ID: $VOLUME_ID" -# # Stop instance (stop-instances) and verify transition to stopped (describe-instances) -# echo "Stopping instance..." -# $AWS_EC2 stop-instances --instance-ids "$INSTANCE_ID" -# COUNT=0 -# while [ $COUNT -lt 30 ]; do -# STATE=$($AWS_EC2 describe-instances --instance-ids "$INSTANCE_ID" --query 'Reservations[0].Instances[0].State.Name' --output text) -# echo "Instance state: $STATE" -# if [ "$STATE" == "stopped" ]; then -# break -# fi -# sleep 5 -# COUNT=$((COUNT + 1)) -# done +# Stop instance (stop-instances) and verify transition to stopped (describe-instances) +echo "Stopping instance..." +$AWS_EC2 stop-instances --instance-ids "$INSTANCE_ID" +COUNT=0 +while [ $COUNT -lt 30 ]; do + STATE=$($AWS_EC2 describe-instances --instance-ids "$INSTANCE_ID" --query 'Reservations[0].Instances[0].State.Name' --output text) + echo "Instance state: $STATE" + if [ "$STATE" == "stopped" ]; then + break + fi + sleep 5 + COUNT=$((COUNT + 1)) +done -# if [ "$STATE" != "stopped" ]; then -# echo "Instance failed to reach stopped state" -# exit 1 -# fi +if [ "$STATE" != "stopped" ]; then + echo "Instance failed to reach stopped state" + exit 1 +fi -# # Start instance (start-instances) and verify transition back to running (describe-instances) -# echo "Starting instance..." -# $AWS_EC2 start-instances --instance-ids "$INSTANCE_ID" -# COUNT=0 -# while [ $COUNT -lt 30 ]; do -# STATE=$($AWS_EC2 describe-instances --instance-ids "$INSTANCE_ID" --query 'Reservations[0].Instances[0].State.Name' --output text) -# echo "Instance state: $STATE" -# if [ "$STATE" == "running" ]; then -# break -# fi -# sleep 5 -# COUNT=$((COUNT + 1)) -# done +# Start instance (start-instances) and verify transition back to running (describe-instances) +echo "Starting instance..." +$AWS_EC2 start-instances --instance-ids "$INSTANCE_ID" +COUNT=0 +while [ $COUNT -lt 30 ]; do + STATE=$($AWS_EC2 describe-instances --instance-ids "$INSTANCE_ID" --query 'Reservations[0].Instances[0].State.Name' --output text) + echo "Instance state: $STATE" + if [ "$STATE" == "running" ]; then + break + fi + sleep 5 + COUNT=$((COUNT + 1)) +done -# if [ "$STATE" != "running" ]; then -# echo "Instance failed to reach running state after restart" -# exit 1 -# fi +if [ "$STATE" != "running" ]; then + echo "Instance failed to reach running state after restart" + exit 1 +fi -# # Terminate instance (terminate-instances) and verify termination (describe-instances) -# echo "Terminating instance..." -# $AWS_EC2 terminate-instances --instance-ids "$INSTANCE_ID" -# COUNT=0 -# while [ $COUNT -lt 30 ]; do -# STATE=$($AWS_EC2 describe-instances --instance-ids "$INSTANCE_ID" --query 'Reservations[0].Instances[0].State.Name' --output text) -# echo "Instance state: $STATE" -# if [ "$STATE" == "terminated" ] || [ "$STATE" == "None" ]; then -# break -# fi -# sleep 5 -# COUNT=$((COUNT + 1)) -# done +# Terminate instance (terminate-instances) and verify termination (describe-instances) +echo "Terminating instance..." +$AWS_EC2 terminate-instances --instance-ids "$INSTANCE_ID" +COUNT=0 +while [ $COUNT -lt 30 ]; do + STATE=$($AWS_EC2 describe-instances --instance-ids "$INSTANCE_ID" --query 'Reservations[0].Instances[0].State.Name' --output text) + echo "Instance state: $STATE" + if [ "$STATE" == "terminated" ] || [ "$STATE" == "None" ]; then + break + fi + sleep 5 + COUNT=$((COUNT + 1)) +done echo "E2E Test Completed Successfully" exit 0 From 0a4e0858f1590203354be34b3fd83eb1ff7a8002 Mon Sep 17 00:00:00 2001 From: Julian <115516918+juliansommer@users.noreply.github.com> Date: Fri, 23 Jan 2026 09:04:48 +1000 Subject: [PATCH 3/6] remove replace directive --- go.mod | 4 ---- 1 file changed, 4 deletions(-) diff --git a/go.mod b/go.mod index a198ec7bf..db354dc00 100644 --- a/go.mod +++ b/go.mod @@ -110,7 +110,3 @@ tool ( golang.org/x/vuln/cmd/govulncheck honnef.co/go/tools/cmd/staticcheck ) - -replace github.com/mulgadc/viperblock => ../viperblock - -replace github.com/mulgadc/predastore => ../predastore From b1ec9b2122da542cf68e6e481d351a5014bdeae9 Mon Sep 17 00:00:00 2001 From: Julian <115516918+juliansommer@users.noreply.github.com> Date: Fri, 23 Jan 2026 09:22:12 +1000 Subject: [PATCH 4/6] upgrade viperblock --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index db354dc00..c53758c17 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/google/uuid v1.6.0 github.com/kdomanski/iso9660 v0.4.0 github.com/mulgadc/predastore v0.1.5 - github.com/mulgadc/viperblock v0.1.4 + github.com/mulgadc/viperblock v0.1.5 github.com/nats-io/nats-server/v2 v2.11.6 github.com/nats-io/nats.go v1.43.0 github.com/pelletier/go-toml/v2 v2.2.4 diff --git a/go.sum b/go.sum index 5e7f34ec7..9ca9239a8 100644 --- a/go.sum +++ b/go.sum @@ -130,6 +130,8 @@ github.com/mulgadc/predastore v0.1.5 h1:7SySkQQ5MQ1NTcKcB14pCUIJipq0YVgCJZeKM6yt github.com/mulgadc/predastore v0.1.5/go.mod h1:BhMymRV3zMTu2S+5pJnNDDm5+AGakjhSc/PKX0q8Xfg= github.com/mulgadc/viperblock v0.1.4 h1:YnRHEC6XNbhnSk6S+ET3X7DTMRezdO3o1O3nO/R9Syg= github.com/mulgadc/viperblock v0.1.4/go.mod h1:JNooDHb6ZuPvNPWaf0gChlft/D8xs5jHJFln+P3B9lQ= +github.com/mulgadc/viperblock v0.1.5 h1:vS7ImGApQvxKuU1+fJNmwOycduwr9fx/xyofxWYKPcI= +github.com/mulgadc/viperblock v0.1.5/go.mod h1:JNooDHb6ZuPvNPWaf0gChlft/D8xs5jHJFln+P3B9lQ= github.com/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI= github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= github.com/nats-io/nats-server/v2 v2.11.6 h1:4VXRjbTUFKEB+7UoaKL3F5Y83xC7MxPoIONOnGgpkHw= From 6a87b62e06485b00df5c860d24fba2353e2c695f Mon Sep 17 00:00:00 2001 From: Julian <115516918+juliansommer@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:23:07 +1000 Subject: [PATCH 5/6] feat: cache Ubuntu image in e2e Docker build --- tests/e2e/Dockerfile.e2e | 12 ++++++++++++ tests/e2e/run-e2e.sh | 12 ++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/tests/e2e/Dockerfile.e2e b/tests/e2e/Dockerfile.e2e index 94768ed3a..c87926630 100644 --- a/tests/e2e/Dockerfile.e2e +++ b/tests/e2e/Dockerfile.e2e @@ -38,6 +38,18 @@ RUN --mount=type=cache,target=/root/go/pkg/mod \ (cd viperblock/nbd/libguestfs.org/nbdkit && go mod download) && \ (cd predastore && go mod download) +# Pre-download Ubuntu cloud image for e2e tests (cached in Docker layer) +# Placed before source copy so code changes don't invalidate this layer +RUN mkdir -p /root/images && \ + ARCH=$(dpkg --print-architecture) && \ + if [ "$ARCH" = "amd64" ]; then \ + curl -L -o /root/images/ubuntu-24.04.img \ + https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img; \ + elif [ "$ARCH" = "arm64" ]; then \ + curl -L -o /root/images/ubuntu-24.04.img \ + https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-arm64.img; \ + fi + # 3. Copy the rest of the source code COPY hive/ ./hive/ COPY viperblock/ ./viperblock/ diff --git a/tests/e2e/run-e2e.sh b/tests/e2e/run-e2e.sh index 6a7c960f0..afc712da5 100755 --- a/tests/e2e/run-e2e.sh +++ b/tests/e2e/run-e2e.sh @@ -151,10 +151,14 @@ else fi echo "Using image: $IMAGE_NAME" -# Import a reliable Ubuntu image and capture the AMI ID from the output -echo "Importing image $IMAGE_NAME..." -echo "May appear stalled here, just takes a while to import..." -IMPORT_LOG=$(./bin/hive admin images import --name "$IMAGE_NAME" --force 2>/dev/null) +# Import the pre-downloaded Ubuntu image using file-based import +echo "Importing pre-cached Ubuntu image..." +IMPORT_LOG=$(./bin/hive admin images import \ + --file /root/images/ubuntu-24.04.img \ + --arch "$ARCH" \ + --distro ubuntu \ + --version 24.04 \ + --force 2>/dev/null) AMI_ID=$(echo "$IMPORT_LOG" | grep -o 'ami-[a-z0-9]\+') if [ -z "$AMI_ID" ]; then From 0138b7c970d6826acae189d197eb76b36ba07690 Mon Sep 17 00:00:00 2001 From: Julian <115516918+juliansommer@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:26:33 +1000 Subject: [PATCH 6/6] move image download to before deps layer --- tests/e2e/Dockerfile.e2e | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/e2e/Dockerfile.e2e b/tests/e2e/Dockerfile.e2e index c87926630..9d1200ad4 100644 --- a/tests/e2e/Dockerfile.e2e +++ b/tests/e2e/Dockerfile.e2e @@ -23,6 +23,18 @@ ENV PATH="/usr/local/go/bin:/root/go/bin:${PATH}" # Set up working directory structure to match development layout WORKDIR /root/Development/mulga +# Pre-download Ubuntu cloud image for e2e tests (cached in Docker layer) +# Placed before dependency copy so go.mod changes don't invalidate this layer +RUN mkdir -p /root/images && \ + ARCH=$(dpkg --print-architecture) && \ + if [ "$ARCH" = "amd64" ]; then \ + curl -L -o /root/images/ubuntu-24.04.img \ + https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img; \ + elif [ "$ARCH" = "arm64" ]; then \ + curl -L -o /root/images/ubuntu-24.04.img \ + https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-arm64.img; \ + fi + # 1. Pre-copy dependency manifests for cache layer COPY hive/go.mod hive/go.sum ./hive/ COPY viperblock/go.mod viperblock/go.sum ./viperblock/ @@ -38,18 +50,6 @@ RUN --mount=type=cache,target=/root/go/pkg/mod \ (cd viperblock/nbd/libguestfs.org/nbdkit && go mod download) && \ (cd predastore && go mod download) -# Pre-download Ubuntu cloud image for e2e tests (cached in Docker layer) -# Placed before source copy so code changes don't invalidate this layer -RUN mkdir -p /root/images && \ - ARCH=$(dpkg --print-architecture) && \ - if [ "$ARCH" = "amd64" ]; then \ - curl -L -o /root/images/ubuntu-24.04.img \ - https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img; \ - elif [ "$ARCH" = "arm64" ]; then \ - curl -L -o /root/images/ubuntu-24.04.img \ - https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-arm64.img; \ - fi - # 3. Copy the rest of the source code COPY hive/ ./hive/ COPY viperblock/ ./viperblock/