Skip to content

Commit 4a0c799

Browse files
fix: keep RPM changelog entries in descending date order on re-bump (#1550)
* fix: keep RPM changelog entries in descending date order on re-bump Port of the 5.0.0 fix (#1549) to 4.14.9. Re-bumping an existing version updated its date in place instead of repositioning it, so a newer date could end up below older entries and rpmbuild rejected the spec (%changelog not in descending chronological order). The entry is now removed and reinserted at the position matching its date. Also fixes date/sed portability on macOS (BSD date/sed vs GNU) and makes an invalid date abort the script instead of writing the error text into the changelog. This branch didn't have the sed_inplace helper yet, so it's added here too (ported from 5.0.0). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Ian Yenien Serrano <63758389+yenienserrano@users.noreply.github.com> * fix: make repository_bumper.sh's remaining sed -i calls portable The rest of the script's raw sed -i calls (VERSION.json, package.json, workflow branch refs, base-packages Dockerfile, README.md, rendering service snapshot, CHANGELOG.md) had the same GNU-only sed -i issue already fixed for the changelog updates, breaking on macOS (BSD sed) with errors like "command i expects \ followed by text" or "\1 not defined in the RE". Switched them all to the existing sed_inplace helper so the whole script runs end-to-end on macOS. Also fixed VERSION.json/package.json field extraction, which used the GNU-only \s regex shorthand (unsupported by BSD sed), replaced with the portable [[:space:]] POSIX class. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Ian Yenien Serrano <63758389+yenienserrano@users.noreply.github.com> --------- Signed-off-by: Ian Yenien Serrano <63758389+yenienserrano@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 7dac202 commit 4a0c799

1 file changed

Lines changed: 116 additions & 39 deletions

File tree

tools/repository_bumper.sh

Lines changed: 116 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,40 @@ log() {
3434
echo "[${timestamp}] ${message}" | tee -a "$LOG_FILE"
3535
}
3636

37+
# Function to perform portable sed in-place editing
38+
sed_inplace() {
39+
local options=""
40+
local pattern=""
41+
local file=""
42+
43+
# Parse arguments to handle options like -E
44+
while [[ $# -gt 0 ]]; do
45+
case $1 in
46+
-E|-r)
47+
options="$options $1"
48+
shift
49+
;;
50+
*)
51+
if [ -z "$pattern" ]; then
52+
pattern="$1"
53+
elif [ -z "$file" ]; then
54+
file="$1"
55+
fi
56+
shift
57+
;;
58+
esac
59+
done
60+
61+
# Detect OS and use appropriate sed syntax
62+
if [[ "$OSTYPE" == "darwin"* ]]; then
63+
# macOS (BSD sed) requires empty string after -i
64+
sed -i '' $options "$pattern" "$file"
65+
else
66+
# Linux (GNU sed) doesn't require anything after -i
67+
sed -i $options "$pattern" "$file"
68+
fi
69+
}
70+
3771
# Function to show usage
3872
usage() {
3973
echo "Usage: $0 [--version VERSION --stage STAGE | --tag] [--date DATE] [--help]"
@@ -134,7 +168,7 @@ pre_update_checks() {
134168

135169
# Attempt to extract version from VERSION.json using sed
136170
log "Attempting to extract current version from $VERSION_FILE using sed..."
137-
CURRENT_VERSION=$(sed -n 's/^\s*"version"\s*:\s*"\([^"]*\)".*$/\1/p' "$VERSION_FILE" | head -n 1) # head -n 1 ensures only the first match is taken
171+
CURRENT_VERSION=$(sed -n 's/^[[:space:]]*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*$/\1/p' "$VERSION_FILE" | head -n 1) # head -n 1 ensures only the first match is taken
138172

139173
# Check if sed successfully extracted a version
140174
if [ -z "$CURRENT_VERSION" ]; then
@@ -151,7 +185,7 @@ pre_update_checks() {
151185

152186
# Attempt to extract stage from VERSION.json using sed
153187
log "Attempting to extract current stage from $VERSION_FILE using sed..."
154-
CURRENT_STAGE=$(sed -n 's/^\s*"stage"\s*:\s*"\([^"]*\)".*$/\1/p' "$VERSION_FILE" | head -n 1) # head -n 1 ensures only the first match is taken
188+
CURRENT_STAGE=$(sed -n 's/^[[:space:]]*"stage"[[:space:]]*:[[:space:]]*"\([^"]*\)".*$/\1/p' "$VERSION_FILE" | head -n 1) # head -n 1 ensures only the first match is taken
155189

156190
# Check if sed successfully extracted a stage
157191
if [ -z "$CURRENT_STAGE" ]; then
@@ -168,7 +202,7 @@ pre_update_checks() {
168202

169203
# Attempt to extract current revision from package.json using sed
170204
log "Attempting to extract current revision from $PACKAGE_JSON using sed..."
171-
CURRENT_REVISION=$(sed -n '/"wazuh": {/,/}/ s/^\s*"revision"\s*:\s*"\([^"]*\)".*$/\1/p' "$PACKAGE_JSON" | head -n 1)
205+
CURRENT_REVISION=$(sed -n '/"wazuh": {/,/}/ s/^[[:space:]]*"revision"[[:space:]]*:[[:space:]]*"\([^"]*\)".*$/\1/p' "$PACKAGE_JSON" | head -n 1)
172206

173207
if [ -z "$CURRENT_REVISION" ]; then
174208
log "ERROR: Failed to extract 'revision' from $PACKAGE_JSON using sed. Check file format or key presence."
@@ -226,7 +260,7 @@ compare_versions_and_set_revision() {
226260
# Versions are identical (Major, Minor, Patch are equal)
227261
log "New version ($VERSION) is identical to current version ($CURRENT_VERSION)."
228262
log "Attempting to extract current revision from $PACKAGE_JSON using sed (Note: This is fragile)"
229-
local current_revision_val=$(sed -n 's/^\s*"revision"\s*:\s*"\([^"]*\)".*$/\1/p' "$PACKAGE_JSON" | head -n 1)
263+
local current_revision_val=$(sed -n 's/^[[:space:]]*"revision"[[:space:]]*:[[:space:]]*"\([^"]*\)".*$/\1/p' "$PACKAGE_JSON" | head -n 1)
230264
# Check if sed successfully extracted a revision
231265
if [ -z "$current_revision_val" ]; then
232266
log "ERROR: Failed to extract 'revision' from $PACKAGE_JSON using sed. Check file format or key presence."
@@ -263,13 +297,13 @@ update_root_version_json() {
263297

264298
# Update version in VERSION.json
265299
if [ -n "$VERSION" ] && [ "$CURRENT_VERSION" != "$VERSION" ]; then
266-
sed -i "s/^\s*\"version\"\s*:\s*\"[^\"]*\"/ \"version\": \"$VERSION\"/" "$VERSION_FILE"
300+
sed_inplace "s/^[[:space:]]*\"version\"[[:space:]]*:[[:space:]]*\"[^\"]*\"/ \"version\": \"$VERSION\"/" "$VERSION_FILE"
267301
modified=true
268302
fi
269303

270304
# Update stage in VERSION.json
271305
if [ -n "$STAGE" ] && [ "$CURRENT_STAGE" != "$STAGE" ]; then
272-
sed -i "s/^\s*\"stage\"\s*:\s*\"[^\"]*\"/ \"stage\": \"$STAGE\"/" "$VERSION_FILE"
306+
sed_inplace "s/^[[:space:]]*\"stage\"[[:space:]]*:[[:space:]]*\"[^\"]*\"/ \"stage\": \"$STAGE\"/" "$VERSION_FILE"
273307
modified=true
274308
fi
275309

@@ -298,15 +332,15 @@ update_package_json() {
298332
# Note: This sed command assumes a specific formatting and might be fragile.
299333
# It looks for the block starting with a line containing "wazuh": { and ending with the next line containing only }
300334
# Within that block, it replaces the value on the line starting with "version":
301-
sed -i "/\"wazuh\": {/,/}/ s/^\(\s*\"version\"\s*:\s*\)\"[^\"]*\"/\1\"$VERSION\"/" "$PACKAGE_JSON"
335+
sed_inplace "/\"wazuh\": {/,/}/ s/^\([[:space:]]*\"version\"[[:space:]]*:[[:space:]]*\)\"[^\"]*\"/\1\"$VERSION\"/" "$PACKAGE_JSON"
302336
modified=true
303337
fi
304338

305339
# Update revision in package.json
306340
if [[ "$CURRENT_REVISION" != "$REVISION" ]]; then
307341
log "Attempting to update revision to $REVISION within 'wazuh' object in $PACKAGE_JSON"
308342
# Similar sed command for the revision line within the same block
309-
sed -i "/\"wazuh\": {/,/}/ s/^\(\s*\"revision\"\s*:\s*\)\"[^\"]*\"/\1\"$REVISION\"/" "$PACKAGE_JSON"
343+
sed_inplace "/\"wazuh\": {/,/}/ s/^\([[:space:]]*\"revision\"[[:space:]]*:[[:space:]]*\)\"[^\"]*\"/\1\"$REVISION\"/" "$PACKAGE_JSON"
310344
modified=true
311345
fi
312346

@@ -331,7 +365,7 @@ update_changelog() {
331365
# This is significantly less reliable than using jq.
332366
log "Attempting to extract .version from $VERSION_FILE using sed (Note: This is fragile)"
333367
# Extract OpenSearch Dashboards version from package.json (first occurrence of "version")
334-
OPENSEARCH_VERSION=$(sed -n 's/^\s*"version"\s*:\s*"\([^"]*\)".*$/\1/p' "$PACKAGE_JSON" | head -n 1)
368+
OPENSEARCH_VERSION=$(sed -n 's/^[[:space:]]*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*$/\1/p' "$PACKAGE_JSON" | head -n 1)
335369
if [ -z "$OPENSEARCH_VERSION" ] || [ "$OPENSEARCH_VERSION" == "null" ]; then
336370
log "ERROR: Could not extract pluginPlatform.version from $PACKAGE_JSON for changelog"
337371
exit 1
@@ -350,7 +384,7 @@ update_changelog() {
350384
if [ -n "$STAGE" ]; then
351385
log "Changelog entry for this version and OpenSearch Dashboards version exists. Updating revision only."
352386
# Use sed to update only the revision number in the header
353-
sed -i -E "s|(${changelog_header_regex})|${changelog_header}${REVISION}|" "$changelog_file" &&
387+
sed_inplace -E "s|(${changelog_header_regex})|${changelog_header}${REVISION}|" "$changelog_file" &&
354388
log "CHANGELOG.md revision updated successfully." || {
355389
log "ERROR: Failed to update revision in $changelog_file"
356390
exit 1
@@ -395,7 +429,7 @@ update_build_workflow() {
395429
if grep -qE '\.yml@[^"[:space:]]+' "$WAZUH_DASHBOARD_PLUGINS_WORKFLOW_FILE"; then
396430
log "Pattern found in $(basename $WAZUH_DASHBOARD_PLUGINS_WORKFLOW_FILE). Attempting update..."
397431
# If the pattern exists, perform the substitution
398-
sed -i -E "s/(\.yml@)[^\"[:space:]]+/\1${replacement}/g" "$WAZUH_DASHBOARD_PLUGINS_WORKFLOW_FILE"
432+
sed_inplace -E "s/(\.yml@)[^\"[:space:]]+/\1${replacement}/g" "$WAZUH_DASHBOARD_PLUGINS_WORKFLOW_FILE"
399433
modified=true
400434
else
401435
log "Pattern not found in $(basename $WAZUH_DASHBOARD_PLUGINS_WORKFLOW_FILE). Skipping update."
@@ -418,14 +452,14 @@ update_base_package_dockerfile() {
418452
if grep -qE "$branch_pattern_regex" "$DOCKERFILE_FOR_BASE_PACKAGES"; then
419453
log "Pattern '$branch_pattern_regex' found in $(basename $DOCKERFILE_FOR_BASE_PACKAGES). Attempting update..."
420454
# Perform the substitution
421-
sed -i -E "s/${branch_pattern_regex}/\1${VERSION}/g" "$DOCKERFILE_FOR_BASE_PACKAGES"
455+
sed_inplace -E "s/${branch_pattern_regex}/\1${VERSION}/g" "$DOCKERFILE_FOR_BASE_PACKAGES"
422456
modified=true
423457
else
424458
log "Pattern '$branch_pattern_regex' not found in $(basename $DOCKERFILE_FOR_BASE_PACKAGES). Skipping update for this pattern."
425459
fi
426460

427461
# Update all occurrences of wazuh-packages-to-base:x.y.z with wazuh-packages-to-base:$VERSION
428-
sed -i -E "s/(wazuh-packages-to-base:)$VERSION_PATTERN/\1${VERSION}/g" "$DOCKERFILE_FOR_BASE_PACKAGES" && modified=true
462+
sed_inplace -E "s/(wazuh-packages-to-base:)$VERSION_PATTERN/\1${VERSION}/g" "$DOCKERFILE_FOR_BASE_PACKAGES" && modified=true
429463

430464
if [[ $modified == true ]]; then
431465
log "Successfully updated $(basename $DOCKERFILE_FOR_BASE_PACKAGES)"
@@ -447,7 +481,7 @@ update_readme_for_base_packages() {
447481
if grep -qE "$app_pattern_regex" "$README_FOR_BASE_PACKAGES"; then
448482
log "Pattern '$app_pattern_regex' found in $(basename $README_FOR_BASE_PACKAGES). Attempting update..."
449483
# If the pattern exists, perform the substitution and set modified to true
450-
sed -i -E "s/${app_pattern_regex}/\1${VERSION}/g" "$README_FOR_BASE_PACKAGES"
484+
sed_inplace -E "s/${app_pattern_regex}/\1${VERSION}/g" "$README_FOR_BASE_PACKAGES"
451485
modified=true
452486
else
453487
log "Pattern '$app_pattern_regex' not found in $(basename $README_FOR_BASE_PACKAGES). Skipping update for this pattern."
@@ -461,7 +495,7 @@ update_readme_for_base_packages() {
461495
if grep -qE "$base_pattern_regex" "$README_FOR_BASE_PACKAGES"; then
462496
log "Pattern '$base_pattern_regex' found in $(basename $README_FOR_BASE_PACKAGES). Attempting update..."
463497
# If the pattern exists, perform the substitution and set modified to true
464-
sed -i -E "s/${base_pattern_regex}/\1${VERSION}/g" "$README_FOR_BASE_PACKAGES"
498+
sed_inplace -E "s/${base_pattern_regex}/\1${VERSION}/g" "$README_FOR_BASE_PACKAGES"
465499
modified=true
466500
else
467501
log "Pattern '$base_pattern_regex' not found in $(basename $README_FOR_BASE_PACKAGES). Skipping update for this pattern."
@@ -475,7 +509,7 @@ update_readme_for_base_packages() {
475509
if grep -qE "$security_pattern_regex" "$README_FOR_BASE_PACKAGES"; then
476510
log "Pattern '$security_pattern_regex' found in $(basename $README_FOR_BASE_PACKAGES). Attempting update..."
477511
# If the pattern exists, perform the substitution and set modified to true
478-
sed -i -E "s/${security_pattern_regex}/\1${VERSION}/g" "$README_FOR_BASE_PACKAGES"
512+
sed_inplace -E "s/${security_pattern_regex}/\1${VERSION}/g" "$README_FOR_BASE_PACKAGES"
479513
modified=true
480514
else
481515
log "Pattern '$security_pattern_regex' not found in $(basename $README_FOR_BASE_PACKAGES). Skipping update for this pattern."
@@ -489,7 +523,7 @@ update_readme_for_base_packages() {
489523
if grep -qE "$readme_example_pattern_regex" "$README_FOR_BASE_PACKAGES"; then
490524
log "Pattern '$readme_example_pattern_regex' found in $(basename $README_FOR_BASE_PACKAGES). Attempting update..."
491525
# If the pattern exists, perform the substitution and set modified to true
492-
sed -i -E "s/${readme_example_pattern_regex}/\1${VERSION}/g" "$README_FOR_BASE_PACKAGES"
526+
sed_inplace -E "s/${readme_example_pattern_regex}/\1${VERSION}/g" "$README_FOR_BASE_PACKAGES"
493527
modified=true
494528
else
495529
log "Pattern '$readme_example_pattern_regex' not found in $(basename $README_FOR_BASE_PACKAGES). Skipping update for this pattern."
@@ -514,7 +548,7 @@ update_rendering_service_test_snap() {
514548
if grep -qE "$pattern_regex" "$rendering_service_test_snap"; then
515549
log "Pattern '$pattern_regex' found in $(basename $rendering_service_test_snap). Attempting update..."
516550
# If the pattern exists, perform the substitution
517-
sed -i -E "s/${pattern_regex}/\1${VERSION}/g" "$rendering_service_test_snap"
551+
sed_inplace -E "s/${pattern_regex}/\1${VERSION}/g" "$rendering_service_test_snap"
518552
log "Successfully updated rendering service test snapshot."
519553
else
520554
log "Pattern '$pattern_regex' not found in $(basename $rendering_service_test_snap). Skipping update for this pattern."
@@ -528,21 +562,37 @@ update_rendering_service_test_snap() {
528562
# Function to convert date from yyyy-mm-dd to RPM format (e.g., "Thu Sep 04 2025")
529563
convert_date_to_rpm_format() {
530564
local input_date="$1"
531-
# Use date command to convert and format with English locale
532-
LC_ALL=C date -d "$input_date" "+%a %b %d %Y" 2>/dev/null || {
533-
log "ERROR: Invalid date format: $input_date"
534-
exit 1
535-
}
565+
# Use date command to convert and format with English locale (GNU date on Linux, BSD date on macOS)
566+
if [[ "$OSTYPE" == "darwin"* ]]; then
567+
LC_ALL=C date -j -f "%Y-%m-%d" "$input_date" "+%a %b %d %Y" 2>/dev/null && return 0
568+
else
569+
LC_ALL=C date -d "$input_date" "+%a %b %d %Y" 2>/dev/null && return 0
570+
fi
571+
log "ERROR: Invalid date format: $input_date"
572+
return 1
573+
}
574+
575+
# Function to convert an RPM changelog date (e.g. "Thu Sep 10 2026") to epoch seconds, for ordering comparisons
576+
rpm_date_to_epoch() {
577+
local rpm_date="$1"
578+
if [[ "$OSTYPE" == "darwin"* ]]; then
579+
LC_ALL=C date -j -f "%a %b %d %Y" "$rpm_date" "+%s" 2>/dev/null
580+
else
581+
LC_ALL=C date -d "$rpm_date" "+%s" 2>/dev/null
582+
fi
536583
}
537584

538585
# Function to convert date from yyyy-mm-dd to Debian RFC 2822 format (e.g., "Thu, 04 Sep 2025 12:00:00 +0000")
539586
convert_date_to_deb_format() {
540587
local input_date="$1"
541-
# Use date command to convert and format with English locale
542-
LC_ALL=C date -d "$input_date" "+%a, %d %b %Y 12:00:00 +0000" 2>/dev/null || {
543-
log "ERROR: Invalid date format: $input_date"
544-
exit 1
545-
}
588+
# Use date command to convert and format with English locale (GNU date on Linux, BSD date on macOS)
589+
if [[ "$OSTYPE" == "darwin"* ]]; then
590+
LC_ALL=C date -j -f "%Y-%m-%d" "$input_date" "+%a, %d %b %Y 12:00:00 +0000" 2>/dev/null && return 0
591+
else
592+
LC_ALL=C date -d "$input_date" "+%a, %d %b %Y 12:00:00 +0000" 2>/dev/null && return 0
593+
fi
594+
log "ERROR: Invalid date format: $input_date"
595+
return 1
546596
}
547597

548598
# Function to update RPM changelog
@@ -560,24 +610,50 @@ update_rpm_changelog() {
560610

561611
log "Updating RPM changelog..."
562612

563-
local rpm_date=$(convert_date_to_rpm_format "$DATE")
613+
local rpm_date
614+
rpm_date=$(convert_date_to_rpm_format "$DATE") || exit 1
564615
local changelog_entry="* $rpm_date support <info@wazuh.com> - $VERSION"
565616
local more_info_entry="- More info: https://documentation.wazuh.com/current/release-notes/release-$(echo $VERSION | tr '.' '-').html"
566617

618+
local new_epoch
619+
new_epoch=$(rpm_date_to_epoch "$rpm_date") || exit 1
620+
567621
# Check if entry already exists
568-
if grep -q "* .* support <info@wazuh.com> - $VERSION" "$RPM_CHANGELOG"; then
569-
log "RPM changelog entry for version $VERSION already exists. Updating date..."
570-
# Update existing entry date
571-
sed -i "s/^\* .* support <info@wazuh.com> - $VERSION$/$changelog_entry/" "$RPM_CHANGELOG"
572-
log "Successfully updated RPM changelog date for version $VERSION"
622+
if grep -q "^\* .* support <info@wazuh.com> - $VERSION\$" "$RPM_CHANGELOG"; then
623+
log "RPM changelog entry for version $VERSION already exists. Repositioning it with the updated date..."
624+
# Remove the existing entry (its changelog line and following "More info" line);
625+
# it gets reinserted below at the position matching its (possibly new) date, so
626+
# entries stay in descending order regardless of where the old entry used to sit.
627+
sed_inplace "/^\* .* support <info@wazuh.com> - $VERSION\$/,+1d" "$RPM_CHANGELOG"
573628
else
574629
log "Adding new RPM changelog entry for version $VERSION..."
575-
# Find the %changelog line and add new entry after it
576-
sed -i "/^%changelog$/a\\
630+
fi
631+
632+
# Entries must stay in descending date order (rpmbuild enforces it), so insert the
633+
# entry right before the first existing entry whose date is not newer than it.
634+
local insert_before_line=""
635+
local line_num=0
636+
while IFS= read -r line; do
637+
line_num=$((line_num + 1))
638+
if [[ "$line" =~ ^\*\ (.*)\ support ]]; then
639+
local existing_epoch
640+
existing_epoch=$(rpm_date_to_epoch "${BASH_REMATCH[1]}") || continue
641+
if [ "$existing_epoch" -le "$new_epoch" ]; then
642+
insert_before_line=$line_num
643+
break
644+
fi
645+
fi
646+
done < "$RPM_CHANGELOG"
647+
648+
if [ -n "$insert_before_line" ]; then
649+
sed_inplace "${insert_before_line}i\\
577650
$changelog_entry\\
578651
$more_info_entry" "$RPM_CHANGELOG"
579-
log "Successfully added new RPM changelog entry for version $VERSION"
652+
else
653+
# New entry is older than every existing one (or there are no entries yet): goes last
654+
printf '%s\n%s\n' "$changelog_entry" "$more_info_entry" >> "$RPM_CHANGELOG"
580655
fi
656+
log "Successfully updated RPM changelog entry for version $VERSION"
581657
}
582658

583659
# Function to update Debian changelog
@@ -595,7 +671,8 @@ update_deb_changelog() {
595671

596672
log "Updating Debian changelog..."
597673

598-
local deb_date=$(convert_date_to_deb_format "$DATE")
674+
local deb_date
675+
deb_date=$(convert_date_to_deb_format "$DATE") || exit 1
599676
local package_version="$VERSION-RELEASE"
600677
local changelog_header="wazuh-dashboard ($package_version) stable; urgency=low"
601678
local more_info_entry=" * More info: https://documentation.wazuh.com/current/release-notes/release-$(echo $VERSION | tr '.' '-').html"
@@ -609,7 +686,7 @@ update_deb_changelog() {
609686
log "Debian changelog entry for version $VERSION already exists. Updating date..."
610687
# Update existing entry date
611688
# Find the line with the version and then find the next maintainer line to update
612-
sed -i "/wazuh-dashboard ($escaped_package_version)/,/^ *-- Wazuh, Inc/ s|^ *-- Wazuh, Inc <info@wazuh.com> .*|$maintainer_line|" "$DEB_CHANGELOG"
689+
sed_inplace "/wazuh-dashboard ($escaped_package_version)/,/^ *-- Wazuh, Inc/ s|^ *-- Wazuh, Inc <info@wazuh.com> .*|$maintainer_line|" "$DEB_CHANGELOG"
613690
log "Successfully updated Debian changelog date for version $VERSION"
614691
else
615692
log "Adding new Debian changelog entry for version $VERSION..."

0 commit comments

Comments
 (0)