# Integration test for issue #2295: Cache conflicts running multiple helmfile using the same chart in parallel # This test verifies that file locking prevents race conditions when multiple helmfile processes # try to pull the same OCI chart simultaneously. oci_parallel_pull_case_input_dir="${cases_dir}/oci-parallel-pull/input" config_file="helmfile.yaml" oci_parallel_pull_cache_dir="" oci_parallel_pull_output_dir="" # Cleanup function for this test cleanup_oci_parallel_pull() { if [ -n "${oci_parallel_pull_cache_dir}" ] && [ -d "${oci_parallel_pull_cache_dir}" ]; then rm -rf "${oci_parallel_pull_cache_dir}" fi if [ -n "${oci_parallel_pull_output_dir}" ] && [ -d "${oci_parallel_pull_output_dir}" ]; then rm -rf "${oci_parallel_pull_output_dir}" fi } trap cleanup_oci_parallel_pull EXIT test_start "oci-parallel-pull: verify file locking prevents race conditions (issue #2295)" # Create a temporary cache directory for this test oci_parallel_pull_cache_dir=$(mktemp -d) export HELMFILE_CACHE_HOME="${oci_parallel_pull_cache_dir}" # Create a temporary output directory for test outputs oci_parallel_pull_output_dir=$(mktemp -d) info "Using temporary cache directory: ${HELMFILE_CACHE_HOME}" info "Using temporary output directory: ${oci_parallel_pull_output_dir}" # Function to check if failure is due to registry issues (not a race condition bug) is_registry_error() { local output_dir="$1" # Check for common registry-related errors that are not race condition bugs if grep -iqE "(rate limit|too many requests|unauthorized|connection refused|timeout|no such host|i/o timeout)" "${output_dir}"/oci-parallel-*.out 2>/dev/null; then return 0 # true - it's a registry error fi return 1 # false - not a registry error } # Run multiple helmfile template commands in parallel using the same chart # This simulates the scenario described in issue #2295 info "Running 3 parallel helmfile template commands..." # Start all processes in background ${helmfile} -f ${oci_parallel_pull_case_input_dir}/${config_file} template > "${oci_parallel_pull_output_dir}/oci-parallel-1.out" 2>&1 & pid1=$! ${helmfile} -f ${oci_parallel_pull_case_input_dir}/${config_file} template > "${oci_parallel_pull_output_dir}/oci-parallel-2.out" 2>&1 & pid2=$! ${helmfile} -f ${oci_parallel_pull_case_input_dir}/${config_file} template > "${oci_parallel_pull_output_dir}/oci-parallel-3.out" 2>&1 & pid3=$! # Wait for all processes and capture exit codes # Note: We capture exit codes using "|| exit1=$?" pattern to prevent set -e from exiting # the script when wait returns non-zero (which happens when the process fails) exit1=0 exit2=0 exit3=0 wait $pid1 || exit1=$? wait $pid2 || exit2=$? wait $pid3 || exit3=$? info "Process 1 exit code: ${exit1}" info "Process 2 exit code: ${exit2}" info "Process 3 exit code: ${exit3}" # Check for the specific error from issue #2295 (race condition bug) # Use case-insensitive extended regex to catch variations from different tar/helm versions if grep -iqE "(failed to untar.*already exists|file or directory.*already exists|a file.*already exists)" "${oci_parallel_pull_output_dir}"/oci-parallel-*.out 2>/dev/null; then warn "Race condition detected! Found 'file already exists' error in output" cat "${oci_parallel_pull_output_dir}"/oci-parallel-*.out fail "oci-parallel-pull test failed due to race condition (issue #2295)" fi # Check for failures failed=0 if [ $exit1 -ne 0 ]; then warn "Process 1 failed:" cat "${oci_parallel_pull_output_dir}/oci-parallel-1.out" failed=1 fi if [ $exit2 -ne 0 ]; then warn "Process 2 failed:" cat "${oci_parallel_pull_output_dir}/oci-parallel-2.out" failed=1 fi if [ $exit3 -ne 0 ]; then warn "Process 3 failed:" cat "${oci_parallel_pull_output_dir}/oci-parallel-3.out" failed=1 fi if [ $failed -eq 1 ]; then # Check if this is a registry error (rate limit, network issue, etc.) # These are not bugs in helmfile, so we should skip the test rather than fail if is_registry_error "${oci_parallel_pull_output_dir}"; then warn "Test skipped due to external registry issues (rate limit, network, etc.)" warn "This is not a helmfile bug - the file locking mechanism cannot be tested" # Clean up and exit successfully to not fail CI on external issues cleanup_oci_parallel_pull trap - EXIT test_pass "oci-parallel-pull: skipped due to external registry issues" return 0 2>/dev/null || exit 0 fi fail "oci-parallel-pull test failed" fi # Verify the chart was cached if [ ! -d "${HELMFILE_CACHE_HOME}" ]; then fail "Cache directory was not created" fi # Check for lock files in the cache directory. # NOTE: Lock files are ephemeral and may be cleaned up immediately after helmfile processes complete. # Their absence does NOT mean locking was not used; this check is informational only. lock_files=$(find "${HELMFILE_CACHE_HOME}" -name "*.lock" 2>/dev/null | wc -l) info "Found ${lock_files} lock file(s) in cache directory" if [ "${lock_files}" -lt 1 ]; then warn "No lock files found - this does NOT mean locking was not used (lock files are ephemeral)" fi # Cleanup and restore the original trap cleanup_oci_parallel_pull trap - EXIT test_pass "oci-parallel-pull: file locking prevents race conditions"