Files
dokku/tests/unit/storage.bats
Jose Diaz-Gonzalez 179d09fdd5 fix: chown migrated legacy storage entries to dokku
`SaveEntry` and `touchMigrationFlag` wrote files via `os.WriteFile` and `os.Create` and never chowned them, so the install-time legacy-mount migration produced root-owned `legacy-*.json` files in `/var/lib/dokku/data/storage-registry/entries/`. The dokku user that runs `ps:rebuild` could not read them and every rebuild on a 0.37.x to 0.38.0 upgrade failed with permission denied. The companion `repairRegistryOwnership` pass in `TriggerInstall` rewrites ownership across the whole registry tree so installs that already ran the buggy code are fixed on the next package upgrade, since the per-app migration flag would otherwise cause `MigrateLegacyMounts` to skip the broken files forever.
2026-05-02 05:37:11 -04:00

409 lines
13 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) 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 '.[].volume_options'"
echo "output: $output"
echo "status: $status"
assert_success
assert_output_not_exists
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) storage:create / storage:list-entries / storage:destroy" {
run /bin/bash -c "dokku storage:create rdmtest-entry"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku storage:list-entries --format json | jq -r '.[].name' | grep '^rdmtest-entry$'"
echo "output: $output"
echo "status: $status"
assert_success
assert_output "rdmtest-entry"
run /bin/bash -c "dokku storage:info rdmtest-entry --format json | jq -r '.scheduler'"
echo "output: $output"
echo "status: $status"
assert_success
assert_output "docker-local"
run /bin/bash -c "dokku storage:destroy rdmtest-entry"
echo "output: $output"
echo "status: $status"
assert_success
}
@test "(storage) storage:create rejects invalid names" {
# underscore: rejected
run /bin/bash -c "dokku storage:create rdmtest_invalid"
echo "output: $output"
echo "status: $status"
assert_failure
# uppercase: rejected
run /bin/bash -c "dokku storage:create RdmTest"
echo "output: $output"
echo "status: $status"
assert_failure
# 46 chars: too long
long_name=$(printf 'a%.0s' {1..46})
run /bin/bash -c "dokku storage:create $long_name"
echo "output: $output"
echo "status: $status"
assert_failure
# legacy- prefix: reserved
run /bin/bash -c "dokku storage:create legacy-foo"
echo "output: $output"
echo "status: $status"
assert_failure
}
@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"
assert_success
run /bin/bash -c "dokku storage:destroy rdmtest-cache"
assert_success
}
@test "(storage) storage:destroy refuses to remove a still-mounted entry" {
run /bin/bash -c "dokku storage:create rdmtest-busy"
assert_success
run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-busy --container-dir /data"
assert_success
run /bin/bash -c "dokku storage:destroy rdmtest-busy"
echo "output: $output"
echo "status: $status"
assert_failure
assert_output_contains "still mounted"
run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-busy"
assert_success
run /bin/bash -c "dokku storage:destroy rdmtest-busy"
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"
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"
# 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"
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"
}