mirror of
https://github.com/dokku/dokku.git
synced 2026-09-01 19:49:25 +02:00
`storage:set` now takes `<name> <property> [<value>]` like every other `:set` command, where omitting the value unsets the property. Previously it took flags and could not distinguish an empty value from an omitted one, so nothing it set could ever be cleared. The flag form keeps working and emits a deprecation warning. Annotations and labels move to `storage:annotations:set`, `storage:annotations:report`, `storage:labels:set`, and `storage:labels:report`, matching the `scheduler-k3s` equivalents. These operate on a single key, so clearing one leaves the rest in place rather than replacing the whole map as the `--annotation` and `--label` flags do.
833 lines
30 KiB
Bash
833 lines
30 KiB
Bash
#!/usr/bin/env bats
|
|
|
|
load test_helper
|
|
|
|
setup() {
|
|
global_setup
|
|
create_app
|
|
rm -rf "$DOKKU_LIB_ROOT/data/storage/rdmtestapp*"
|
|
}
|
|
|
|
teardown() {
|
|
destroy_app
|
|
global_teardown
|
|
}
|
|
|
|
@test "(storage) storage:help" {
|
|
run /bin/bash -c "dokku storage"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_output_contains "Manage mounted volumes"
|
|
help_output="$output"
|
|
|
|
run /bin/bash -c "dokku storage:help"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_output_contains "Manage mounted volumes"
|
|
assert_output "$help_output"
|
|
}
|
|
|
|
@test "(storage) storage:ensure-directory" {
|
|
run /bin/bash -c "test -d $DOKKU_LIB_ROOT/data/storage/$TEST_APP"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_failure
|
|
|
|
run /bin/bash -c "dokku storage:ensure-directory @"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_failure
|
|
|
|
run /bin/bash -c "dokku storage:ensure-directory $TEST_APP/"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_failure
|
|
|
|
run /bin/bash -c "dokku storage:ensure-directory $TEST_APP/$TEST_APP"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_failure
|
|
|
|
run /bin/bash -c "dokku storage:ensure-directory $TEST_APP"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output_contains "Setting directory ownership to 1000:1000" 0
|
|
assert_output_contains "Setting directory ownership to 2000:2000" 0
|
|
assert_output_contains "Setting directory ownership to 32767:32767" 1
|
|
|
|
run /bin/bash -c "test -d $DOKKU_LIB_ROOT/data/storage/$TEST_APP"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:ensure-directory $TEST_APP"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:ensure-directory --chown false $TEST_APP"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output_contains "Setting directory ownership to 1000:1000" 0
|
|
assert_output_contains "Setting directory ownership to 2000:2000" 0
|
|
assert_output_contains "Setting directory ownership to 32767:32767" 0
|
|
|
|
run /bin/bash -c "dokku storage:ensure-directory $TEST_APP --chown false"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output_contains "Setting directory ownership to 1000:1000" 0
|
|
assert_output_contains "Setting directory ownership to 2000:2000" 0
|
|
assert_output_contains "Setting directory ownership to 32767:32767" 0
|
|
|
|
run /bin/bash -c "dokku storage:ensure-directory --chown heroku $TEST_APP"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output_contains "Setting directory ownership to 1000:1000" 1
|
|
assert_output_contains "Setting directory ownership to 2000:2000" 0
|
|
assert_output_contains "Setting directory ownership to 32767:32767" 0
|
|
|
|
run /bin/bash -c "dokku storage:ensure-directory --chown paketo $TEST_APP"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output_contains "Setting directory ownership to 1000:1000" 0
|
|
assert_output_contains "Setting directory ownership to 2000:2000" 1
|
|
assert_output_contains "Setting directory ownership to 32767:32767" 0
|
|
|
|
run /bin/bash -c "dokku storage:ensure-directory --chown herokuish $TEST_APP"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output_contains "Setting directory ownership to 1000:1000" 0
|
|
assert_output_contains "Setting directory ownership to 2000:2000" 0
|
|
assert_output_contains "Setting directory ownership to 32767:32767" 1
|
|
}
|
|
|
|
@test "(storage) chown-storage-dir rejects out-of-bounds chown values" {
|
|
run /bin/bash -c "DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT $PLUGIN_AVAILABLE_PATH/storage/bin/chown-storage-dir $TEST_APP 65536"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_failure
|
|
assert_output_contains "Unsupported chown permissions"
|
|
|
|
run /bin/bash -c "DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT $PLUGIN_AVAILABLE_PATH/storage/bin/chown-storage-dir $TEST_APP 165535"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_failure
|
|
assert_output_contains "Unsupported chown permissions"
|
|
|
|
run /bin/bash -c "DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT $PLUGIN_AVAILABLE_PATH/storage/bin/chown-storage-dir $TEST_APP 231072"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_failure
|
|
assert_output_contains "Unsupported chown permissions"
|
|
|
|
run /bin/bash -c "DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT $PLUGIN_AVAILABLE_PATH/storage/bin/chown-storage-dir $TEST_APP -1"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_failure
|
|
assert_output_contains "Value must be a non-negative integer"
|
|
|
|
run /bin/bash -c "DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT $PLUGIN_AVAILABLE_PATH/storage/bin/chown-storage-dir $TEST_APP abc"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_failure
|
|
assert_output_contains "Value must be a non-negative integer"
|
|
}
|
|
|
|
@test "(storage) storage:mount, storage:list, storage:umount" {
|
|
run /bin/bash -c "dokku storage:mount $TEST_APP /tmp/mount:/mount"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku --quiet storage:list $TEST_APP"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "/tmp/mount:/mount"
|
|
|
|
run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '. | length'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "1"
|
|
|
|
run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[].host_path'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "/tmp/mount"
|
|
|
|
run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[].container_path'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "/mount"
|
|
|
|
run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r 'map(has(\"volume_options\") or has(\"readonly\")) | any'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "false"
|
|
|
|
run /bin/bash -c "dokku storage:mount $TEST_APP /tmp/mount:/mount"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_output " ! Mount path already exists."
|
|
assert_failure
|
|
|
|
run /bin/bash -c "dokku storage:unmount $TEST_APP /tmp/mount:/mount"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku --quiet storage:list $TEST_APP"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output_not_exists
|
|
|
|
run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '. | length'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "0"
|
|
|
|
run /bin/bash -c "dokku storage:unmount $TEST_APP /tmp/mount:/mount"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_output " ! Mount path does not exist."
|
|
assert_failure
|
|
|
|
run /bin/bash -c "dokku storage:mount $TEST_APP mount_volume:/mount"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku --quiet storage:list $TEST_APP"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "mount_volume:/mount"
|
|
}
|
|
|
|
@test "(storage:report) build/deploy/run mounts raw" {
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"storage-build-mounts\"'"
|
|
assert_success
|
|
assert_output ""
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"storage-deploy-mounts\"'"
|
|
assert_success
|
|
assert_output ""
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"storage-run-mounts\"'"
|
|
assert_success
|
|
assert_output ""
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '[keys[] | select(startswith(\"attachment.\"))] | length'"
|
|
assert_success
|
|
assert_output "0"
|
|
|
|
run /bin/bash -c "dokku storage:mount $TEST_APP /tmp/storage-mount:/mount"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"storage-deploy-mounts\"'"
|
|
assert_success
|
|
assert_output "-v /tmp/storage-mount:/mount"
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"storage-run-mounts\"'"
|
|
assert_success
|
|
assert_output "-v /tmp/storage-mount:/mount"
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"storage-build-mounts\"'"
|
|
assert_success
|
|
assert_output ""
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '[keys[] | select(startswith(\"attachment.\"))] | length'"
|
|
assert_success
|
|
assert_output "9"
|
|
|
|
run /bin/bash -c "dokku storage:unmount $TEST_APP /tmp/storage-mount:/mount"
|
|
assert_success
|
|
}
|
|
|
|
@test "(storage:report) emits per-attachment dotted keys" {
|
|
run /bin/bash -c "dokku storage:create rdmtest-rpt"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-rpt --container-dir /data --phase deploy --phase run --volume-options Z --volume-chown herokuish --volume-subpath uploads"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.entry-name\"'"
|
|
assert_success
|
|
assert_output "rdmtest-rpt"
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.host-path\"'"
|
|
assert_success
|
|
assert_output "$DOKKU_LIB_ROOT/data/storage/rdmtest-rpt"
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.container-path\"'"
|
|
assert_success
|
|
assert_output "/data"
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.phases\"'"
|
|
assert_success
|
|
assert_output "deploy,run"
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.process-type\"'"
|
|
assert_success
|
|
assert_output "_default_"
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.subpath\"'"
|
|
assert_success
|
|
assert_output "uploads"
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.readonly\"'"
|
|
assert_success
|
|
assert_output "false"
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.volume-options\"'"
|
|
assert_success
|
|
assert_output "Z"
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.volume-chown\"'"
|
|
assert_success
|
|
assert_output "herokuish"
|
|
|
|
# stdout output gains one line per field; verify a representative subset
|
|
run /bin/bash -c "dokku storage:report $TEST_APP"
|
|
assert_success
|
|
assert_output_contains "Storage attachment 1 entry name"
|
|
assert_output_contains "rdmtest-rpt" -1
|
|
assert_output_contains "Storage attachment 1 volume options"
|
|
assert_output_contains "Storage attachment 1 volume chown"
|
|
assert_output_contains "herokuish"
|
|
|
|
# info-flag lookup returns just the value
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --storage-attachment.1.volume-options"
|
|
assert_success
|
|
assert_output "Z"
|
|
|
|
run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-rpt --container-dir /data"
|
|
assert_success
|
|
run /bin/bash -c "dokku storage:destroy rdmtest-rpt --force"
|
|
assert_success
|
|
}
|
|
|
|
@test "(storage:report) multiple attachments cluster by index" {
|
|
run /bin/bash -c "dokku storage:create rdmtest-rpt-a"
|
|
assert_success
|
|
run /bin/bash -c "dokku storage:create rdmtest-rpt-b"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-rpt-a --container-dir /data --volume-options Z"
|
|
assert_success
|
|
run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-rpt-b --container-dir /cache --volume-options noexec,nosuid"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '[.\"attachment.1.entry-name\", .\"attachment.2.entry-name\"] | sort | join(\"|\")'"
|
|
assert_success
|
|
assert_output "rdmtest-rpt-a|rdmtest-rpt-b"
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '[.\"attachment.1.volume-options\", .\"attachment.2.volume-options\"] | sort | join(\"|\")'"
|
|
assert_success
|
|
assert_output "Z|noexec,nosuid"
|
|
|
|
# info-flag lookup against the second attachment
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --storage-attachment.2.entry-name"
|
|
assert_success
|
|
assert_output_contains "rdmtest-rpt"
|
|
|
|
run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-rpt-a --container-dir /data"
|
|
assert_success
|
|
run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-rpt-b --container-dir /cache"
|
|
assert_success
|
|
run /bin/bash -c "dokku storage:destroy rdmtest-rpt-a --force"
|
|
assert_success
|
|
run /bin/bash -c "dokku storage:destroy rdmtest-rpt-b --force"
|
|
assert_success
|
|
}
|
|
|
|
@test "(storage:report) degrades gracefully when an attachment entry is missing" {
|
|
run /bin/bash -c "dokku storage:create rdmtest-rpt-missing"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-rpt-missing --container-dir /data --volume-options Z"
|
|
assert_success
|
|
|
|
# delete the entry on disk so LoadEntry fails for the existing attachment
|
|
run /bin/bash -c "sudo rm -f $DOKKU_LIB_ROOT/data/storage-registry/entries/rdmtest-rpt-missing.json"
|
|
assert_success
|
|
|
|
# discard dokku's stderr so the LogWarn line doesn't pollute the jq output capture
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json 2>/dev/null | jq -r '[keys[] | select(startswith(\"attachment.\"))] | length'"
|
|
assert_success
|
|
assert_output "0"
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json 2>/dev/null | jq -r 'has(\"storage-build-mounts\") and has(\"storage-deploy-mounts\") and has(\"storage-run-mounts\")'"
|
|
assert_success
|
|
assert_output "true"
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP"
|
|
assert_success
|
|
assert_output_contains "Skipping attachment"
|
|
assert_output_contains "rdmtest-rpt-missing" -1
|
|
assert_output_contains "Storage build mounts"
|
|
assert_output_contains "Storage deploy mounts"
|
|
assert_output_contains "Storage run mounts"
|
|
|
|
# clean up the leftover attachment record so subsequent tests don't trip
|
|
run /bin/bash -c "sudo rm -f $DOKKU_LIB_ROOT/config/storage/$TEST_APP/attachments"
|
|
assert_success
|
|
}
|
|
|
|
@test "(storage) storage:create + storage:mount with named entry attaches multiple entries to one app" {
|
|
run /bin/bash -c "dokku storage:create rdmtest-data"
|
|
assert_success
|
|
run /bin/bash -c "dokku storage:create rdmtest-cache"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-data --container-dir /data"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-cache --container-dir /cache"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '. | length'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "2"
|
|
|
|
run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[].entry_name' | sort | xargs"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "rdmtest-cache rdmtest-data"
|
|
|
|
# cleanup
|
|
run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-data"
|
|
assert_success
|
|
run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-cache"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '. | length'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "0"
|
|
|
|
run /bin/bash -c "dokku storage:destroy rdmtest-data --force"
|
|
assert_success
|
|
run /bin/bash -c "dokku storage:destroy rdmtest-cache --force"
|
|
assert_success
|
|
}
|
|
|
|
@test "(storage:mount) --volume-options sets option on named-entry attachment" {
|
|
run /bin/bash -c "dokku storage:create rdmtest-opts"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-opts --container-dir /data --volume-options Z"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[].volume_options'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "Z"
|
|
|
|
run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[].container_path'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "/data"
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"storage-deploy-mounts\"'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output_contains ":Z"
|
|
|
|
# combine with --volume-readonly: rendered as ro,<options>
|
|
run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-opts --container-dir /ro --volume-options noexec,nosuid --volume-readonly"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"storage-deploy-mounts\"'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output_contains ":ro,noexec,nosuid"
|
|
|
|
# The JSON output of storage:list exposes readonly and volume_options
|
|
# as separate keys so external drift-detection tooling can compare
|
|
# them against the underlying attachment fields one for one.
|
|
run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[] | select(.container_path == \"/ro\") | .readonly'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "true"
|
|
|
|
run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[] | select(.container_path == \"/ro\") | .volume_options'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "noexec,nosuid"
|
|
|
|
# The /data mount has no readonly flag, so the key is absent.
|
|
run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[] | select(.container_path == \"/data\") | has(\"readonly\")'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "false"
|
|
|
|
# cleanup
|
|
run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-opts --container-dir /data"
|
|
assert_success
|
|
run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-opts --container-dir /ro"
|
|
assert_success
|
|
run /bin/bash -c "dokku storage:destroy rdmtest-opts --force"
|
|
assert_success
|
|
}
|
|
|
|
@test "(storage:mount) re-running against an existing (entry, container-dir) updates in place" {
|
|
run /bin/bash -c "dokku storage:create rdmtest-upsert"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-upsert --container-dir /data --volume-options Z"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
# Re-mount the same (entry, container-dir) with a different --volume-options.
|
|
run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-upsert --container-dir /data --volume-options noexec,nosuid"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
# Single attachment, not two.
|
|
run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '. | length'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "1"
|
|
|
|
# Latest --volume-options wins.
|
|
run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[].volume_options'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "noexec,nosuid"
|
|
|
|
# Re-mount with --volume-readonly added; readonly toggles on.
|
|
run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-upsert --container-dir /data --volume-options noexec,nosuid --volume-readonly"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[].readonly'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "true"
|
|
|
|
# Re-mount without --volume-readonly: mount-time fields are rewritten,
|
|
# not merged, so the readonly key disappears from the JSON output.
|
|
run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-upsert --container-dir /data --volume-options noexec,nosuid"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[] | has(\"readonly\")'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "false"
|
|
|
|
# A different --container-dir on the same entry is still a distinct attachment.
|
|
run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-upsert --container-dir /other"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '. | length'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "2"
|
|
|
|
# cleanup
|
|
run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-upsert --container-dir /data"
|
|
assert_success
|
|
run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-upsert --container-dir /other"
|
|
assert_success
|
|
run /bin/bash -c "dokku storage:destroy rdmtest-upsert --force"
|
|
assert_success
|
|
}
|
|
|
|
@test "(storage) storage:exec runs a non-interactive command and propagates exit code" {
|
|
run /bin/bash -c "dokku storage:create rdmtest-exec"
|
|
assert_success
|
|
|
|
# docker pull image up-front so the test isn't gated on registry latency.
|
|
run /bin/bash -c "docker pull alpine:3 >/dev/null"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:exec rdmtest-exec -- /bin/sh -c 'touch /data/marker && ls /data/marker'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output_contains "/data/marker"
|
|
|
|
run /bin/bash -c "dokku storage:exec rdmtest-exec -- /bin/sh -c 'exit 42'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_equal "$status" 42
|
|
|
|
run /bin/bash -c "dokku storage:destroy rdmtest-exec --force"
|
|
assert_success
|
|
}
|
|
|
|
@test "(storage) storage:migrate converts legacy docker-options -v lines into attachments" {
|
|
# Stage a legacy `-v` line directly via docker-options:add. This
|
|
# bypasses the new storage:mount path so the line lives in
|
|
# docker-options without a corresponding attachment, simulating an
|
|
# app upgraded from a pre-PR Dokku version.
|
|
run /bin/bash -c "dokku docker-options:add $TEST_APP deploy,run \"-v /tmp/legacy-mount:/legacy\""
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
# Before migration, storage:list shows nothing (attachment-only) and
|
|
# docker-options has the -v line.
|
|
run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '. | length'"
|
|
assert_success
|
|
assert_output "0"
|
|
|
|
run /bin/bash -c "dokku docker-options:report $TEST_APP --docker-options-deploy"
|
|
assert_success
|
|
assert_output_contains "-v /tmp/legacy-mount:/legacy"
|
|
|
|
# Run the migration for this app.
|
|
run /bin/bash -c "dokku storage:migrate $TEST_APP"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
# storage:list now surfaces the synthesized colon form.
|
|
run /bin/bash -c "dokku --quiet storage:list $TEST_APP"
|
|
assert_success
|
|
assert_output "/tmp/legacy-mount:/legacy"
|
|
|
|
# The synthesized entry shows up under the legacy- prefix.
|
|
run /bin/bash -c "dokku storage:list-entries --format json | jq -r '.[].name' | grep '^legacy-' | head -1"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
# Regression for #8557: the legacy-*.json file must be readable by the
|
|
# dokku user. The migration runs as root via the install trigger, so
|
|
# without an explicit chown the file lands as root:root and ps:rebuild
|
|
# fails with permission denied.
|
|
legacy_entry_name=$(dokku storage:list-entries --format json | jq -r '.[] | select(.name | startswith("legacy-")) | .name' | head -1)
|
|
run /bin/bash -c "stat -c '%U:%G' /var/lib/dokku/data/storage-registry/entries/${legacy_entry_name}.json"
|
|
assert_success
|
|
assert_output "dokku:dokku"
|
|
|
|
# docker-options no longer holds the -v line on either phase.
|
|
run /bin/bash -c "dokku docker-options:report $TEST_APP --docker-options-deploy"
|
|
assert_success
|
|
assert_output_not_contains "-v /tmp/legacy-mount:/legacy"
|
|
|
|
run /bin/bash -c "dokku docker-options:report $TEST_APP --docker-options-run"
|
|
assert_success
|
|
assert_output_not_contains "-v /tmp/legacy-mount:/legacy"
|
|
|
|
# Idempotency: re-running storage:migrate is a no-op (still one
|
|
# attachment, no errors).
|
|
run /bin/bash -c "dokku storage:migrate $TEST_APP"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '. | length'"
|
|
assert_success
|
|
assert_output "1"
|
|
|
|
# Property marker is set after a successful migration with mounts.
|
|
run /bin/bash -c "sudo test -f /var/lib/dokku/config/storage/$TEST_APP/legacy-mounts-migrated"
|
|
assert_success
|
|
run /bin/bash -c "sudo cat /var/lib/dokku/config/storage/$TEST_APP/legacy-mounts-migrated"
|
|
assert_success
|
|
assert_output "true"
|
|
|
|
# Cleanup: unmount + destroy the synthesized legacy entry. We need
|
|
# the entry name from list-entries since legacy-<hash> is content-derived.
|
|
legacy_name=$(dokku storage:list-entries --format json | jq -r '.[] | select(.name | startswith("legacy-")) | .name' | head -1)
|
|
if [[ -n "$legacy_name" ]]; then
|
|
run /bin/bash -c "dokku storage:unmount $TEST_APP $legacy_name --container-dir /legacy"
|
|
assert_success
|
|
run /bin/bash -c "dokku storage:destroy $legacy_name --force"
|
|
assert_success
|
|
fi
|
|
}
|
|
|
|
@test "(storage) storage:ensure-directory emits deprecation warning" {
|
|
run /bin/bash -c "dokku storage:ensure-directory $TEST_APP 2>&1"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output_contains "Deprecated:"
|
|
}
|
|
|
|
@test "(storage:migrate) leaves legacy-mounts-migrated unset for apps with no legacy -v lines" {
|
|
# Fresh app with no -v lines: storage:migrate must NOT write the
|
|
# legacy-mounts-migrated property. Distinguishes "never had legacy
|
|
# state" from "had legacy state, drained".
|
|
sudo rm -f "/var/lib/dokku/config/storage/$TEST_APP/legacy-mounts-migrated"
|
|
|
|
run /bin/bash -c "dokku storage:migrate $TEST_APP"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
run /bin/bash -c "sudo test -f /var/lib/dokku/config/storage/$TEST_APP/legacy-mounts-migrated"
|
|
assert_failure
|
|
}
|
|
|
|
@test "(storage:migrate) converts legacy migration flag file into property" {
|
|
# Stage an upgrade-from-old-code scenario: a leftover flag file under
|
|
# data/storage-registry/migrations/<app>. storage:migrate (which runs
|
|
# convertLegacyMigrationFlag before migrateApp) must drain it into
|
|
# the property and remove the file.
|
|
flag_dir="/var/lib/dokku/data/storage-registry/migrations"
|
|
sudo mkdir -p "$flag_dir"
|
|
sudo touch "$flag_dir/$TEST_APP"
|
|
sudo chown dokku:dokku "$flag_dir/$TEST_APP"
|
|
sudo rm -f "/var/lib/dokku/config/storage/$TEST_APP/legacy-mounts-migrated"
|
|
|
|
run /bin/bash -c "dokku storage:migrate $TEST_APP"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
run /bin/bash -c "sudo test -f /var/lib/dokku/config/storage/$TEST_APP/legacy-mounts-migrated"
|
|
assert_success
|
|
run /bin/bash -c "sudo cat /var/lib/dokku/config/storage/$TEST_APP/legacy-mounts-migrated"
|
|
assert_success
|
|
assert_output "true"
|
|
|
|
run /bin/bash -c "sudo test -e $flag_dir/$TEST_APP"
|
|
assert_failure
|
|
}
|
|
|
|
@test "(storage:report) emits new stripped JSON keys alongside legacy" {
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r 'has(\"build-mounts\") and has(\"storage-build-mounts\")'"
|
|
assert_success
|
|
assert_output "true"
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r 'has(\"deploy-mounts\") and has(\"storage-deploy-mounts\")'"
|
|
assert_success
|
|
assert_output "true"
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r 'has(\"run-mounts\") and has(\"storage-run-mounts\")'"
|
|
assert_success
|
|
assert_output "true"
|
|
|
|
# per-attachment dotted keys are emitted in both stripped and legacy forms too
|
|
run /bin/bash -c "dokku storage:create rdmtest-rpt-keys"
|
|
assert_success
|
|
run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-rpt-keys --container-dir /data"
|
|
assert_success
|
|
|
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r 'has(\"attachment.1.entry-name\") and has(\"storage-attachment.1.entry-name\")'"
|
|
assert_success
|
|
assert_output "true"
|
|
|
|
run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-rpt-keys --container-dir /data"
|
|
assert_success
|
|
run /bin/bash -c "dokku storage:destroy rdmtest-rpt-keys --force"
|
|
assert_success
|
|
}
|
|
|
|
@test "(storage) chmod-storage-dir rejects invalid modes" {
|
|
run /bin/bash -c "DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT $PLUGIN_AVAILABLE_PATH/storage/bin/chmod-storage-dir $TEST_APP 0888"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_failure
|
|
assert_output_contains "Unsupported directory mode"
|
|
|
|
run /bin/bash -c "DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT $PLUGIN_AVAILABLE_PATH/storage/bin/chmod-storage-dir $TEST_APP 07555"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_failure
|
|
assert_output_contains "Unsupported directory mode"
|
|
|
|
run /bin/bash -c "DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT $PLUGIN_AVAILABLE_PATH/storage/bin/chmod-storage-dir $TEST_APP u+rwx"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_failure
|
|
assert_output_contains "Unsupported directory mode"
|
|
|
|
run /bin/bash -c "DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT $PLUGIN_AVAILABLE_PATH/storage/bin/chmod-storage-dir '../escape' 0777"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_failure
|
|
assert_output_contains "Directory can only contain the following set of characters"
|
|
}
|
|
|
|
@test "(storage) destroy-storage-dir refuses a traversing directory name" {
|
|
run /bin/bash -c "DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT $PLUGIN_AVAILABLE_PATH/storage/bin/destroy-storage-dir '../escape'"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_failure
|
|
assert_output_contains "Directory can only contain the following set of characters"
|
|
|
|
# a missing directory is a no-op rather than an error
|
|
run /bin/bash -c "DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT $PLUGIN_AVAILABLE_PATH/storage/bin/destroy-storage-dir rdmtest-absent"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
}
|
|
|
|
@test "(storage) install trigger whitelists the storage directory helpers" {
|
|
run /bin/bash -c "test -f /etc/sudoers.d/dokku-storage"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
|
|
run /bin/bash -c "sudo grep -c 'storage/bin/chown-storage-dir' /etc/sudoers.d/dokku-storage"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "1"
|
|
|
|
run /bin/bash -c "sudo grep -c 'storage/bin/chmod-storage-dir' /etc/sudoers.d/dokku-storage"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "1"
|
|
|
|
run /bin/bash -c "sudo grep -c 'storage/bin/destroy-storage-dir' /etc/sudoers.d/dokku-storage"
|
|
echo "output: $output"
|
|
echo "status: $status"
|
|
assert_success
|
|
assert_output "1"
|
|
}
|