-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[CICD-583] Split FLAGS variable into an array (#32)
* Split FLAGS variable into an array Rsync flags sometimes include whitespace in the flag's value. For example, --filter=':- .gitignore' includes whitespace between the filter rule and the file name. The single quotes surrounding the flag's value are meant to prevent splitting. Unfortunately, when bash expands the $FLAGS variable as part of the main rsync command it escapes the single quotes and then splits on the whitespace between :- and .gitignore. This was causing rsync to see the filter flag as two separate arguments, --filter=:- and .gitignore. Simply double-quoting $FLAGS string doesn't help because it prevents splitting between flags when multiple are provided. To fix this, we needed to split the FLAGS variable into an array while respecting single quotes. This pre-split array can be passed to rsync with double-quotes to prevent further splitting. Using the --filter example from before, our new strategy will insert --filter=':- .gitignore' as a single item in the FLAGS array which is then passed to rsync as '--filter=:- .gitignore'. * Add scripts to test FLAGS parsing and quoting strategies
- Loading branch information
1 parent
d440e9c
commit 30cdd52
Showing
9 changed files
with
198 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@wpengine/site-deploy": patch | ||
--- | ||
|
||
Fixes a bug that caused certain flags in the FLAGS option to be incorrectly parsed by rsync |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#!/bin/bash | ||
|
||
set -e | ||
|
||
# Function to parse FLAGS into an array | ||
# | ||
# Bash doesn't respect quotes when splitting the FLAGS string on whitespace | ||
# which can lead to incorrect splitting on arguments like --filter=':- .gitignore'. | ||
# | ||
# xargs does respect quotes, so we use that here to convert the string into a null-delimited | ||
# sequence of arguments. We then read that sequence into an array by splitting on the null character. | ||
# | ||
# https://superuser.com/questions/1529226/get-bash-to-respect-quotes-when-word-splitting-subshell-output | ||
parse_flags() { | ||
local flags="$1" | ||
FLAGS_ARRAY=() | ||
while IFS= read -r -d '' flag; do FLAGS_ARRAY+=("$flag"); done < <(echo "$flags" | xargs printf '%s\0') | ||
} | ||
|
||
print_deployment_info() { | ||
echo "Deploying your code to:" | ||
echo -e "\t${WPE_ENV_NAME}" | ||
echo -e "with the following ${#FLAGS_ARRAY[@]} rsync argument(s):" | ||
for flag in "${FLAGS_ARRAY[@]}"; do | ||
echo -e "\t$flag" | ||
done | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#!/bin/bash | ||
|
||
GREEN='\033[0;32m' | ||
RED='\033[0;31m' | ||
NC='\033[0m' # No Color |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
#!/bin/bash | ||
|
||
# Demonstrates the different ways to pass flags to rsync. | ||
# | ||
# When determining the correct way to pass flags to rsync, it is helpful to | ||
# understand how the shell interprets quotes and whitespace. This script runs | ||
# in debug mode to show how the shell interprets the flags for each test case. | ||
# | ||
# This is a stand-alone script. It is only meant for demonstration and does not | ||
# directly test code in this project. | ||
|
||
# Get the directory of the current script | ||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
|
||
source "${SCRIPT_DIR}/common.sh" | ||
|
||
set -x | ||
|
||
FLAGS="-avzr --filter=':- .gitignore' --exclude='.*'" | ||
FLAGS_ARRAY=("-avzr" "--filter=:- .gitignore" "--exclude='.*'") | ||
|
||
test_flags_no_quotes() { | ||
rsync $FLAGS --dry-run "$SCRIPT_DIR"/data . > /dev/null | ||
if [ $? -eq 0 ]; then | ||
echo -e "${GREEN}test_flags_no_quotes: Success${NC}" | ||
else | ||
echo -e "${RED}test_flags_no_quotes: Failure${NC}" | ||
fi | ||
} | ||
|
||
test_flags_double_quotes() { | ||
rsync "$FLAGS" --dry-run "$SCRIPT_DIR"/data . > /dev/null | ||
if [ $? -eq 0 ]; then | ||
echo -e "${GREEN}test_flags_double_quotes: Success${NC}" | ||
else | ||
echo -e "${RED}test_flags_double_quotes: Failure${NC}" | ||
fi | ||
} | ||
|
||
test_flags_array() { | ||
rsync "${FLAGS_ARRAY[@]}" --dry-run "$SCRIPT_DIR"/data . > /dev/null | ||
if [ $? -eq 0 ]; then | ||
echo -e "${GREEN}test_flags_array: Success${NC}" | ||
else | ||
echo -e "${RED}test_flags_array: Failure${NC}" | ||
fi | ||
} | ||
|
||
main() { | ||
test_flags_no_quotes | ||
test_flags_double_quotes | ||
test_flags_array | ||
} | ||
|
||
main |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
#!/bin/bash | ||
|
||
# Get the directory of the current script | ||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
|
||
source "${SCRIPT_DIR}/common.sh" | ||
source "${SCRIPT_DIR}/../functions.sh" | ||
|
||
# First argument represents the value of the FLAGS variable. | ||
# The rest of the arguments represent the expected values of the FLAGS_ARRAY. | ||
# | ||
# usage: test_parse_flags <FLAGS> <EXPECTED_FLAGS...> | ||
test_parse_flags() { | ||
local test_case=$1 | ||
shift | ||
local expected_args=("$@") | ||
|
||
parse_flags "$test_case" | ||
|
||
local actual_count="${#FLAGS_ARRAY[@]}" | ||
local expected_count="${#expected_args[@]}" | ||
|
||
if [[ "$actual_count" -ne "$expected_count" ]]; then | ||
echo -e "${RED}Test failed for FLAGS='$test_case': expected $expected_count arguments, got $actual_count." | ||
echo -e "\tActual arguments: ${FLAGS_ARRAY[*]}${NC}" | ||
return | ||
fi | ||
|
||
for i in "${!expected_args[@]}"; do | ||
if [[ "${FLAGS_ARRAY[$i]}" != "${expected_args[$i]}" ]]; then | ||
echo -e "${RED}Test failed for FLAGS='$test_case': expected '${expected_args[$i]}', got '${FLAGS_ARRAY[$i]}'.${NC}" | ||
return | ||
fi | ||
done | ||
|
||
echo -e "${GREEN}Test passed for FLAGS=\"$test_case\".${NC}" | ||
} | ||
|
||
# Test cases | ||
test_parse_flags \ | ||
"-azvr --inplace --exclude='.*'" \ | ||
"-azvr" \ | ||
"--inplace" \ | ||
"--exclude=.*" | ||
|
||
test_parse_flags \ | ||
'-azvr --inplace --exclude=".*"' \ | ||
"-azvr" \ | ||
"--inplace" \ | ||
"--exclude=.*" | ||
|
||
test_parse_flags \ | ||
"-azvr --filter=':- .gitignore' --exclude='.*'" \ | ||
"-azvr" \ | ||
"--filter=:- .gitignore" \ | ||
"--exclude=.*" | ||
|
||
test_parse_flags \ | ||
"-avzr --delete --filter='P /wp-uploads/**'" \ | ||
"-avzr" \ | ||
"--delete" \ | ||
"--filter=P /wp-uploads/**" | ||
|
||
test_parse_flags \ | ||
"-avzr --delete --exclude='\$dollar'" \ | ||
"-avzr" \ | ||
"--delete" \ | ||
"--exclude=\$dollar" | ||
|
||
test_parse_flags \ | ||
"-avzr --exclude='\`back-ticks\`'" \ | ||
"-avzr" \ | ||
"--exclude=\`back-ticks\`" | ||
|
||
test_parse_flags \ | ||
"-avzr --exclude='path\\with\\backslash'" \ | ||
"-avzr" \ | ||
"--exclude=path\\with\\backslash" |