fix(k8s): support control-plane tests on slim runner
All checks were successful
Check scripts syntax / check-scripts-syntax (push) Successful in 2s
Test Kubernetes backup scripts / test-automated-nfs-backup (push) Successful in 2s
Test Kubernetes backup scripts / test-k3s-control-plane-config-backup (push) Successful in 3s

This commit is contained in:
2026-07-21 18:13:42 -03:00
parent 260b0196f5
commit 26984db5a6
4 changed files with 46 additions and 2 deletions

View File

@@ -20,5 +20,7 @@ jobs:
steps: steps:
- name: Check out repository code - name: Check out repository code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install GNU coreutils and findutils for control-plane tests
run: apk add --no-cache coreutils findutils
- name: Run k3s control-plane configuration backup tests - name: Run k3s control-plane configuration backup tests
run: bash k8s/test/test-backup-k3s-control-plane-config.sh run: bash k8s/test/test-backup-k3s-control-plane-config.sh

View File

@@ -33,7 +33,7 @@ Useful overrides:
- Source paths: `K3S_CONFIG_DIR`, `K3S_SERVER_DIR`, `K3S_SERVICE_FILES` (colon-separated service configuration files). - Source paths: `K3S_CONFIG_DIR`, `K3S_SERVER_DIR`, `K3S_SERVICE_FILES` (colon-separated service configuration files).
- Destination and identity: `REMOTE_USER`, `REMOTE_HOST`, `REMOTE_DIR`, `REMOTE_IDENTITY_FILE`, `SSH_PORT`. - Destination and identity: `REMOTE_USER`, `REMOTE_HOST`, `REMOTE_DIR`, `REMOTE_IDENTITY_FILE`, `SSH_PORT`.
- Archive and locking: `ARCHIVE_PREFIX`, `ARCHIVE_TS_FORMAT`, `LOCAL_TMP_PARENT`, `LOCK_PATH`, `CLEANUP_KEEP_COUNT` (default `4`; `0` disables deletion). - Archive and locking: `ARCHIVE_PREFIX`, `ARCHIVE_TS_FORMAT`, `LOCAL_TMP_PARENT`, `LOCK_PATH`, `CLEANUP_KEEP_COUNT` (default `4`; `0` disables deletion).
- Binaries: `ZIP_BIN`, `UNZIP_BIN`, `SSH_BIN`, `SCP_BIN`, `CURL_BIN`, `FLOCK_BIN`, `MKTEMP_BIN`, `DATE_BIN`, `REMOTE_BASH_BIN`. - Binaries: `ZIP_BIN`, `UNZIP_BIN`, `SSH_BIN`, `SCP_BIN`, `CURL_BIN`, `FLOCK_BIN`, `MKTEMP_BIN`, `DATE_BIN`, `REALPATH_BIN`, `REMOTE_BASH_BIN`. `REALPATH_BIN` must provide GNU `realpath` `-e` and `-m` options.
- Notifications: `NOTIFY_SUCCESS_URL` (default `http://notify.haven/template/notify/backup`), `NOTIFY_FAILURE_URL` (default `http://notify.haven/template/notify/error`), `NOTIFY_TITLE`, `NOTIFY_ASSET`, and `NOTIFY_CALLER`. Set either URL to an empty value to disable it. Failed notifications only generate a warning; they do not change an otherwise successful backup result. - Notifications: `NOTIFY_SUCCESS_URL` (default `http://notify.haven/template/notify/backup`), `NOTIFY_FAILURE_URL` (default `http://notify.haven/template/notify/error`), `NOTIFY_TITLE`, `NOTIFY_ASSET`, and `NOTIFY_CALLER`. Set either URL to an empty value to disable it. Failed notifications only generate a warning; they do not change an otherwise successful backup result.
## Environment Variables ## Environment Variables

View File

@@ -238,6 +238,10 @@ add_archive_entry_if_present() {
local relative_path local relative_path
local protected_path local protected_path
if [[ "$absolute_path" =~ (^|/)(\.|\.\.)(/|$) ]]; then
die "Configured source path is not safe to archive"
fi
if [[ ! -e "$absolute_path" && ! -L "$absolute_path" ]]; then if [[ ! -e "$absolute_path" && ! -L "$absolute_path" ]]; then
return 0 return 0
fi fi

View File

@@ -35,6 +35,7 @@ path_exists() { [[ -e "$1" ]]; }
path_does_not_exist() { [[ ! -e "$1" ]]; } path_does_not_exist() { [[ ! -e "$1" ]]; }
file_contains() { grep -Fq -- "$2" "$1"; } file_contains() { grep -Fq -- "$2" "$1"; }
file_lacks() { [[ ! -e "$1" ]] || ! grep -Fq -- "$2" "$1"; } file_lacks() { [[ ! -e "$1" ]] || ! grep -Fq -- "$2" "$1"; }
file_is_empty() { [[ ! -s "$1" ]]; }
file_line_count_is() { [[ "$(grep -Fc -- "$2" "$1")" -eq "$3" ]]; } file_line_count_is() { [[ "$(grep -Fc -- "$2" "$1")" -eq "$3" ]]; }
matching_archive_count_is() { matching_archive_count_is() {
@@ -278,6 +279,9 @@ newline_archive="${success_remote}/k3s-control-plane-config_2026-06-01_12-00-00.
touch -t "202606011200" "$newline_archive" touch -t "202606011200" "$newline_archive"
: > "$newline_archive" : > "$newline_archive"
touch -t "202606011200" "$newline_archive" touch -t "202606011200" "$newline_archive"
tab_archive="${success_remote}/k3s-control-plane-config_2026-05-01_12-00-00.zip"$'\tlegacy.zip'
: > "$tab_archive"
touch -t "202605011200" "$tab_archive"
: > "${success_remote}/unrelated.zip" : > "${success_remote}/unrelated.zip"
: > "${TMPD}/curl_trace.log" : > "${TMPD}/curl_trace.log"
: > "${TMPD}/unzip_trace.log" : > "${TMPD}/unzip_trace.log"
@@ -303,6 +307,7 @@ assert "retention leaves exactly four newest matching zip archives" matching_arc
assert "retention removes the two oldest matching zip archives" path_does_not_exist "${success_remote}/k3s-control-plane-config_2026-07-01_12-00-00.zip" assert "retention removes the two oldest matching zip archives" path_does_not_exist "${success_remote}/k3s-control-plane-config_2026-07-01_12-00-00.zip"
assert "retention removes the next-oldest matching zip archive" path_does_not_exist "${success_remote}/k3s-control-plane-config_2026-07-02_12-00-00.zip" assert "retention removes the next-oldest matching zip archive" path_does_not_exist "${success_remote}/k3s-control-plane-config_2026-07-02_12-00-00.zip"
assert "retention removes matching old archives with newlines in their filenames" path_does_not_exist "$newline_archive" assert "retention removes matching old archives with newlines in their filenames" path_does_not_exist "$newline_archive"
assert "retention removes matching old archives with tabs in their filenames" path_does_not_exist "$tab_archive"
assert "retention keeps the standalone unrelated.zip sentinel" path_exists "${success_remote}/unrelated.zip" assert "retention keeps the standalone unrelated.zip sentinel" path_exists "${success_remote}/unrelated.zip"
assert "retention keeps unrelated legacy .7z backups" path_exists "${success_remote}/k8s-backup_2026-07-01.7z" assert "retention keeps unrelated legacy .7z backups" path_exists "${success_remote}/k8s-backup_2026-07-01.7z"
assert "retention keeps unrelated zip backups" path_exists "${success_remote}/other-prefix_2026-07-01.zip" assert "retention keeps unrelated zip backups" path_exists "${success_remote}/other-prefix_2026-07-01.zip"
@@ -332,12 +337,45 @@ set -e
assert "unsafe source override exits non-zero" is_nonzero "$source_scope_exit_code" assert "unsafe source override exits non-zero" is_nonzero "$source_scope_exit_code"
assert "unsafe source override reports the source-scope error" file_contains "${source_scope_dir}/run.log" "Configured source path is not safe to archive" assert "unsafe source override reports the source-scope error" file_contains "${source_scope_dir}/run.log" "Configured source path is not safe to archive"
assert "unsafe source override is rejected before zip runs" file_lacks "${TMPD}/zip_invocations.log" "server/db/.." assert "unsafe source override is rejected before zip runs" file_is_empty "${TMPD}/zip_invocations.log"
assert "unsafe source override archive excludes server database content" file_lacks "$source_scope_archive" "state.db" assert "unsafe source override archive excludes server database content" file_lacks "$source_scope_archive" "state.db"
assert "unsafe source override archive excludes kine socket" file_lacks "$source_scope_archive" "kine.sock" assert "unsafe source override archive excludes kine socket" file_lacks "$source_scope_archive" "kine.sock"
assert "unsafe source override does not publish an archive" directory_is_empty "$source_scope_remote" assert "unsafe source override does not publish an archive" directory_is_empty "$source_scope_remote"
assert "unsafe source override removes local temporary state" directory_is_empty "$source_scope_local_tmp" assert "unsafe source override removes local temporary state" directory_is_empty "$source_scope_local_tmp"
# The source path and the server directory can be overridden independently;
# protected database paths must still be derived from the configured source.
source_scope_mismatch_dir="${TMPD}/source-scope-mismatch"
source_scope_mismatch_fixture="${source_scope_mismatch_dir}/fixture"
source_scope_mismatch_remote="${source_scope_mismatch_dir}/remote"
source_scope_mismatch_local_tmp="${source_scope_mismatch_dir}/local-tmp"
source_scope_mismatch_unrelated_server="${source_scope_mismatch_fixture}/unrelated-server"
source_scope_mismatch_archive="${source_scope_mismatch_remote}/k3s-control-plane-config_2026-07-21_12-00-00.zip"
mkdir -p "$source_scope_mismatch_remote" "$source_scope_mismatch_local_tmp"
create_source_fixture "$source_scope_mismatch_fixture"
mkdir -p "$source_scope_mismatch_unrelated_server"
: > "${TMPD}/zip_invocations.log"
set +e
run_backup \
"$source_scope_mismatch_fixture" \
"$source_scope_mismatch_remote" \
"$source_scope_mismatch_local_tmp" \
"${source_scope_mismatch_dir}/run.log" \
"K3S_CONFIG_DIR=${source_scope_mismatch_fixture}/var/lib/rancher/k3s/server/db/.." \
"K3S_SERVER_DIR=${source_scope_mismatch_unrelated_server}"
source_scope_mismatch_exit_code=$?
set -e
assert "mismatched source override exits non-zero" is_nonzero "$source_scope_mismatch_exit_code"
assert "mismatched source override reports the source-scope error" file_contains "${source_scope_mismatch_dir}/run.log" "Configured source path is not safe to archive"
assert "mismatched source override is rejected before zip runs" file_is_empty "${TMPD}/zip_invocations.log"
assert "mismatched source override archive excludes server database content" file_lacks "$source_scope_mismatch_archive" "state.db"
assert "mismatched source override archive excludes kine socket" file_lacks "$source_scope_mismatch_archive" "kine.sock"
assert "mismatched source override does not publish an archive" path_does_not_exist "$source_scope_mismatch_archive"
assert "mismatched source override leaves the remote directory empty" directory_is_empty "$source_scope_mismatch_remote"
assert "mismatched source override removes local temporary state" directory_is_empty "$source_scope_mismatch_local_tmp"
# Upload failure: the partial remote temporary file is removed, no final archive # Upload failure: the partial remote temporary file is removed, no final archive
# is published, local temporary data is removed, and one safe error notification # is published, local temporary data is removed, and one safe error notification
# is attempted. # is attempted.