Skip to content

Commit 6cb98fa

Browse files
committed
hotstack-os: Replace sed/perl with Python for config processing
The previous sed-based config processing failed when replacement strings contained newlines (e.g., multiple DNS servers). Attempted perl solution also had issues with special characters and delimiter conflicts. This commit replaces the shell-based text processing with a Python script that handles all edge cases naturally: - Multi-line replacement strings (DNS server lists) - Special characters (pipes, quotes, backslashes) - No complex shell quoting or escaping needed - Proper error handling and reporting - Clear argparse interface for debugging The Python script (process-configs.py) provides: - Robust string replacement using str.replace() - Support for all config file types (.conf, .ini, .cfg, .yaml, .example) - Individual file error tracking - Clean integration with existing shell scripts This fixes the "unterminated 's' command" errors and malformed transport_url issues seen in CI environments. Assisted-By: Claude Code - claude-4.5-sonnet Signed-off-by: Harald Jensås <hjensas@redhat.com>
1 parent 229508b commit 6cb98fa

File tree

2 files changed

+139
-38
lines changed

2 files changed

+139
-38
lines changed

devsetup/hotstack-os/scripts/common.sh

Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -774,44 +774,9 @@ process_config_files() {
774774

775775
echo -n "Processing ${description}... "
776776

777-
# Configuration file patterns to process
778-
local file_patterns="\\( \
779-
-name '*.conf' -o \
780-
-name '*.ini' -o \
781-
-name '*.cfg' -o \
782-
-name '*.yaml' -o \
783-
-name '*.example' \
784-
\\)"
785-
786-
# Build perl substitution commands from remaining arguments (pairs of search/replace)
787-
# Perl handles multi-line replacements properly, unlike sed
788-
local perl_substitutions=""
789-
while [ $# -gt 1 ]; do
790-
local search=$1
791-
local replace=$2
792-
# Use \Q...\E to quote the search string (literal match, no regex)
793-
if [ -n "$perl_substitutions" ]; then
794-
perl_substitutions+="; "
795-
fi
796-
perl_substitutions+="s|\Q${search}\E|${replace}|g"
797-
shift 2
798-
done
799-
800-
# Find all config files
801-
local config_files
802-
config_files=$(eval "find '$dir' -type f $file_patterns")
803-
804-
# Process each config file and track failures
805-
local failed=0
806-
local file
807-
for file in $config_files; do
808-
if ! perl -i -pe "${perl_substitutions}" "$file" 2>/dev/null; then
809-
echo -e "\n${RED}${NC} Failed to process: $file" >&2
810-
failed=1
811-
fi
812-
done
813-
814-
if [ $failed -eq 1 ]; then
777+
# Use Python script for robust config processing
778+
# Python handles multi-line replacements and special characters naturally
779+
if ! "$SCRIPT_DIR/process-configs.py" "$dir" "$@"; then
815780
echo -e "${RED}${NC}"
816781
return 1
817782
fi
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#!/usr/bin/env python3
2+
# Copyright Red Hat, Inc.
3+
# All Rights Reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6+
# not use this file except in compliance with the License. You may obtain
7+
# a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
# License for the specific language governing permissions and limitations
15+
# under the License.
16+
17+
"""
18+
Process configuration files by performing string substitutions.
19+
20+
This script replaces placeholder strings in configuration files with their
21+
actual values. It handles multi-line replacements and special characters
22+
properly, unlike shell-based approaches.
23+
"""
24+
25+
import sys
26+
import argparse
27+
from pathlib import Path
28+
29+
30+
# File extensions to process
31+
CONFIG_EXTENSIONS = {".conf", ".ini", ".cfg", ".yaml", ".yml", ".example"}
32+
33+
34+
def find_config_files(config_dir):
35+
"""
36+
Find all configuration files in the given directory.
37+
38+
Args:
39+
config_dir: Directory to search for config files
40+
41+
Returns:
42+
List of Path objects for config files
43+
"""
44+
config_path = Path(config_dir)
45+
if not config_path.exists():
46+
print(f"Error: Config directory {config_dir} does not exist", file=sys.stderr)
47+
sys.exit(1)
48+
49+
config_files = []
50+
for ext in CONFIG_EXTENSIONS:
51+
config_files.extend(config_path.rglob(f"*{ext}"))
52+
53+
return config_files
54+
55+
56+
def process_single_file(config_file, replacements):
57+
"""
58+
Process a single config file with the given replacements.
59+
60+
Args:
61+
config_file: Path object for the config file
62+
replacements: List of (search, replace) tuples
63+
"""
64+
try:
65+
content = config_file.read_text()
66+
modified = False
67+
68+
# Apply all replacements
69+
for search, replace in replacements:
70+
if search in content:
71+
content = content.replace(search, replace)
72+
modified = True
73+
74+
# Write back if modified
75+
if modified:
76+
config_file.write_text(content)
77+
78+
except Exception as e:
79+
print(f"Error processing {config_file}: {e}", file=sys.stderr)
80+
sys.exit(1)
81+
82+
83+
def process_config_files(config_dir, replacements):
84+
"""
85+
Process all config files in the directory with the given replacements.
86+
87+
Args:
88+
config_dir: Directory containing config files
89+
replacements: List of (search, replace) tuples
90+
"""
91+
# Find all config files
92+
config_files = find_config_files(config_dir)
93+
94+
if not config_files:
95+
print(f"Warning: No config files found in {config_dir}", file=sys.stderr)
96+
return
97+
98+
# Process each file
99+
for config_file in config_files:
100+
process_single_file(config_file, replacements)
101+
102+
103+
def main():
104+
parser = argparse.ArgumentParser(
105+
description="Process configuration files by performing string substitutions.",
106+
epilog="Replacement arguments must be provided in pairs: search1 replace1 search2 replace2 ...",
107+
)
108+
109+
parser.add_argument(
110+
"config_dir", help="Directory containing configuration files to process"
111+
)
112+
113+
parser.add_argument(
114+
"replacements",
115+
nargs="*",
116+
help="Search/replace pairs (must be even number of arguments)",
117+
)
118+
119+
args = parser.parse_args()
120+
121+
# Validate replacement pairs
122+
if len(args.replacements) % 2 != 0:
123+
parser.error("Replacement arguments must come in pairs (search replace)")
124+
125+
# Build replacement list
126+
replacements = []
127+
for i in range(0, len(args.replacements), 2):
128+
search = args.replacements[i]
129+
replace = args.replacements[i + 1]
130+
replacements.append((search, replace))
131+
132+
process_config_files(args.config_dir, replacements)
133+
134+
135+
if __name__ == "__main__":
136+
main()

0 commit comments

Comments
 (0)