feat: add storage directory mode and removal flags

`storage:create` and `storage:set` accept a `--mode` flag that sets the octal permissions of a docker-local host directory, and `storage:destroy` accepts a `--destroy-host-dir` flag that removes the directory along with its contents. A docker-local entry also honors `--reclaim-policy Delete` at destroy time now, matching how that policy governs a k3s PersistentVolume. Both are limited to the default `/var/lib/dokku/data/storage/<name>` location, the same restriction `--chown` already carries. `storage:set` applies `--chown` and `--mode` to the directory rather than only recording them.
This commit is contained in:
Jose Diaz-Gonzalez
2026-08-10 01:23:10 -04:00
parent 39df6e3855
commit 87b240054f
14 changed files with 830 additions and 71 deletions

View File

@@ -953,3 +953,309 @@ teardown() {
run /bin/bash -c "dokku storage:destroy rdmtest-rpt-keys --force"
assert_success
}
@test "(storage:create) --mode sets directory permissions" {
run /bin/bash -c "dokku storage:create --mode 0777 rdmtest-mode"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "stat -c '%a' $DOKKU_LIB_ROOT/data/storage/rdmtest-mode"
echo "output: $output"
echo "status: $status"
assert_success
assert_output "777"
# the mode is stored on the entry in its canonical 4-digit form
run /bin/bash -c "dokku storage:info rdmtest-mode --format json | jq -r '.mode'"
echo "output: $output"
echo "status: $status"
assert_success
assert_output "0777"
run /bin/bash -c "dokku storage:destroy rdmtest-mode --destroy-host-dir --force"
assert_success
}
@test "(storage:create) --mode re-applies on an existing directory" {
# the default mode assertion below only holds for a freshly created directory
rm -rf "$DOKKU_LIB_ROOT/data/storage/rdmtest-mode-converge"
run /bin/bash -c "dokku storage:create rdmtest-mode-converge"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "stat -c '%a' $DOKKU_LIB_ROOT/data/storage/rdmtest-mode-converge"
assert_success
assert_output "755"
# re-running create against the existing entry converges the directory
run /bin/bash -c "dokku storage:create --mode 0700 rdmtest-mode-converge"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "stat -c '%a' $DOKKU_LIB_ROOT/data/storage/rdmtest-mode-converge"
echo "output: $output"
echo "status: $status"
assert_success
assert_output "700"
run /bin/bash -c "dokku storage:destroy rdmtest-mode-converge --destroy-host-dir --force"
assert_success
}
@test "(storage:create) --mode accepts a 3 digit octal mode" {
run /bin/bash -c "dokku storage:create --mode 750 rdmtest-mode-short"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "stat -c '%a' $DOKKU_LIB_ROOT/data/storage/rdmtest-mode-short"
echo "output: $output"
echo "status: $status"
assert_success
assert_output "750"
run /bin/bash -c "dokku storage:info rdmtest-mode-short --format json | jq -r '.mode'"
assert_success
assert_output "0750"
run /bin/bash -c "dokku storage:destroy rdmtest-mode-short --destroy-host-dir --force"
assert_success
}
@test "(storage:create) --mode rejects an invalid value" {
run /bin/bash -c "dokku storage:create --mode 0888 rdmtest-mode-bad"
echo "output: $output"
echo "status: $status"
assert_failure
assert_output_contains "Unsupported directory mode"
run /bin/bash -c "dokku storage:create --mode u+rwx rdmtest-mode-bad"
echo "output: $output"
echo "status: $status"
assert_failure
assert_output_contains "Unsupported directory mode"
run /bin/bash -c "dokku storage:list-entries --format json | jq -r '.[].name' | grep '^rdmtest-mode-bad$' || true"
assert_output ""
}
@test "(storage:create) --mode rejects a non-default host path" {
custom_path="/tmp/rdmtest-mode-custom"
rm -rf "$custom_path"
run /bin/bash -c "dokku storage:create --mode 0777 rdmtest-mode-custom $custom_path"
echo "output: $output"
echo "status: $status"
assert_failure
assert_output_contains "--mode is only supported when the storage entry uses the default host path"
run /bin/bash -c "dokku storage:list-entries --format json | jq -r '.[].name' | grep '^rdmtest-mode-custom$' || true"
assert_output ""
rm -rf "$custom_path"
}
@test "(storage:create) --mode is rejected on a k3s entry" {
run /bin/bash -c "dokku storage:create --scheduler k3s --size 1Gi --mode 0777 rdmtest-mode-k3s"
echo "output: $output"
echo "status: $status"
assert_failure
assert_output_contains "does not accept --mode"
run /bin/bash -c "dokku storage:list-entries --format json | jq -r '.[].name' | grep '^rdmtest-mode-k3s$' || true"
assert_output ""
}
@test "(storage:set) --mode updates permissions on an existing entry" {
run /bin/bash -c "dokku storage:create --mode 0755 rdmtest-mode-set"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku storage:set rdmtest-mode-set --mode 0770"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "stat -c '%a' $DOKKU_LIB_ROOT/data/storage/rdmtest-mode-set"
echo "output: $output"
echo "status: $status"
assert_success
assert_output "770"
run /bin/bash -c "dokku storage:info rdmtest-mode-set --format json | jq -r '.mode'"
assert_success
assert_output "0770"
run /bin/bash -c "dokku storage:destroy rdmtest-mode-set --destroy-host-dir --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:destroy) leaves the host directory in place by default" {
run /bin/bash -c "dokku storage:create rdmtest-keep-dir"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku storage:destroy rdmtest-keep-dir --force"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "test -d $DOKKU_LIB_ROOT/data/storage/rdmtest-keep-dir"
echo "output: $output"
echo "status: $status"
assert_success
rm -rf "$DOKKU_LIB_ROOT/data/storage/rdmtest-keep-dir"
}
@test "(storage:destroy) --destroy-host-dir removes a non-empty host directory" {
run /bin/bash -c "dokku storage:create rdmtest-drop-dir"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "sudo touch $DOKKU_LIB_ROOT/data/storage/rdmtest-drop-dir/payload"
assert_success
run /bin/bash -c "dokku storage:destroy rdmtest-drop-dir --destroy-host-dir --force"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "test -d $DOKKU_LIB_ROOT/data/storage/rdmtest-drop-dir"
echo "output: $output"
echo "status: $status"
assert_failure
run /bin/bash -c "dokku storage:list-entries --format json | jq -r '.[].name' | grep '^rdmtest-drop-dir$' || true"
assert_output ""
}
@test "(storage:destroy) --reclaim-policy Delete removes the host directory" {
run /bin/bash -c "dokku storage:create --reclaim-policy Delete rdmtest-reclaim"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku storage:destroy rdmtest-reclaim --force"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "test -d $DOKKU_LIB_ROOT/data/storage/rdmtest-reclaim"
echo "output: $output"
echo "status: $status"
assert_failure
}
@test "(storage:create) --reclaim-policy Delete rejects a non-default host path" {
custom_path="/tmp/rdmtest-reclaim-custom"
rm -rf "$custom_path"
run /bin/bash -c "dokku storage:create --reclaim-policy Delete rdmtest-reclaim-custom $custom_path"
echo "output: $output"
echo "status: $status"
assert_failure
assert_output_contains "default host path"
run /bin/bash -c "dokku storage:list-entries --format json | jq -r '.[].name' | grep '^rdmtest-reclaim-custom$' || true"
assert_output ""
rm -rf "$custom_path"
}
@test "(storage:destroy) --destroy-host-dir refuses a non-default host path" {
custom_path="/tmp/rdmtest-drop-custom"
rm -rf "$custom_path"
mkdir -p "$custom_path"
run /bin/bash -c "dokku storage:create rdmtest-drop-custom $custom_path"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku storage:destroy rdmtest-drop-custom --destroy-host-dir --force"
echo "output: $output"
echo "status: $status"
assert_failure
assert_output_contains "--destroy-host-dir is only supported when the storage entry uses the default host path"
run /bin/bash -c "test -d $custom_path"
assert_success
run /bin/bash -c "dokku storage:destroy rdmtest-drop-custom --force"
assert_success
rm -rf "$custom_path"
}
@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"
}