Skip to content

Commit 243b8ad

Browse files
authored
Merge pull request #282 from rwaffen/fix_rhel
refactor: use dnf instead of yum, fix rhel output, replace regexp, add more docs, remove rhel 5 code
2 parents c2de389 + 4aa2c17 commit 243b8ad

1 file changed

Lines changed: 108 additions & 65 deletions

File tree

tasks/patch_server.rb

Lines changed: 108 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def pending_reboot_linux(log, starttime)
138138
return true if status != 0
139139
else
140140
log.warn 'needs-restarting command not found, cannot determine if reboot is required'
141-
log.warn 'please install the yum-util/dnf-utils package to enable this functionality'
141+
log.warn 'please install the dnf-utils package to enable this functionality'
142142
end
143143

144144
return false
@@ -491,76 +491,119 @@ def gather_facts(log, starttime)
491491
exit(0)
492492
end
493493

494-
# Run the patching
494+
# Run the patching on the appropriate platforms
495+
###############################################################################
496+
495497
if os['family'] == 'RedHat'
496-
log.info 'Running yum upgrade'
498+
log.info 'Running dnf upgrade'
497499
log.debug "Timeout value set to : #{timeout}"
498-
yum_end = ''
499-
yum_exitcode, yum_output, yum_error = run_with_timeout("yum #{yum_params} #{securityflag} upgrade -y", timeout)
500-
err(yum_exitcode, 'os_patching/yum', "yum upgrade returned non-zero (#{yum_exitcode}) : #{yum_output}\n#{yum_error}", starttime) if yum_exitcode != 0
501-
502-
if os['release']['major'].to_i > 5
503-
# Capture the yum job ID
504-
log.info 'Getting yum job ID'
505-
job = ''
506-
yum_id, stderr, status = Open3.capture3('yum history')
507-
err(status, 'os_patching/yum', stderr, starttime) if status != 0
508-
yum_id.split("\n").each do |line|
509-
# Quite the regex. This pulls out fields 1 & 3 from the first info line
510-
# from `yum history`, which look like this :
511-
# ID | Login user | Date and time | 8< SNIP >8
512-
# ------------------------------------------------------ 8< SNIP >8
513-
# 69 | System <unset> | 2018-09-17 17:18 | 8< SNIP >8
514-
matchdata = line.to_s.match(/^\s+(\d+)\s*\|\s*[\w\-<>,= ]*\|\s*([\d:\- ]*)/)
515-
next unless matchdata
516-
job = matchdata[1]
517-
yum_end = matchdata[2]
518-
break
519-
end
520500

521-
# Fail if we didn't capture a job ID
522-
err(1, 'os_patching/yum', 'yum job ID not found', starttime) if job.empty?
523-
524-
# Fail if we didn't capture a job time
525-
err(1, 'os_patching/yum', 'yum job time not found', starttime) if yum_end.empty?
526-
527-
# Check that the first yum history entry was after the yum_start time
528-
# we captured. Append ':59' to the date as yum history only gives the
529-
# minute and if yum bails, it will usually be pretty quick
530-
parsed_end = Time.parse(yum_end + ':59').iso8601
531-
err(1, 'os_patching/yum', 'Yum did not appear to run', starttime) if parsed_end < starttime
532-
533-
# Capture the yum return code
534-
log.debug "Getting yum return code for job #{job}"
535-
yum_status, stderr, status = Open3.capture3("yum history info #{job}")
536-
yum_return = ''
537-
err(status, 'os_patching/yum', stderr, starttime) if status != 0
538-
yum_status.split("\n").each do |line|
539-
matchdata = line.match(/^Return-Code\s+:\s+(.*)$/)
540-
next unless matchdata
541-
yum_return = matchdata[1]
542-
break
543-
end
501+
dnf_exitcode, dnf_output, dnf_error = run_with_timeout("dnf #{yum_params} #{securityflag} upgrade -y", timeout)
502+
503+
err(dnf_exitcode, 'os_patching/dnf', "dnf upgrade returned non-zero (#{dnf_exitcode}) : #{dnf_output}\n#{dnf_error}", starttime) if dnf_exitcode != 0
504+
505+
# Capture the dnf job ID
506+
log.info 'Getting dnf job ID'
507+
job_id = nil
508+
job_date = nil
509+
510+
dnf_history, stderr, status = Open3.capture3('dnf history')
511+
err(status, 'os_patching/dnf', stderr, starttime) if status != 0
512+
513+
dnf_history.split("\n").each do |line|
514+
# get `dnf history`, which look like this :
515+
#
516+
# ID | Command line | Date and time | Action(s) | Altered
517+
# ----------------------------------------------------------------------------------------
518+
# 12 | upgrade -y | 2026-03-26 11:24 | Upgrade | 1
519+
# 11 | downgrade openvox-agent-8.24.2-1.el8 | 2026-03-26 11:22 | Downgrade | 1
520+
#
521+
# Search for the first line with "upgrade -y" which should be our patching run, and pull out the job ID and date from that line.
522+
#
523+
next unless line.include?('upgrade -y')
524+
525+
# split the line into fields and pull out the job ID and date.
526+
# The fields are separated by '|' characters, but there may be multiple spaces around them,
527+
# so we split on '|' and then strip whitespace from the resulting fields.
528+
fields = line.split('|').map(&:strip)
529+
job_id = fields[0]
530+
job_date = fields[2]
544531

545-
err(status, 'os_patching/yum', 'yum return code not found', starttime) if yum_return.empty?
546-
547-
pkg_hash = {}
548-
# Pull out the updated package list from yum history
549-
log.debug "Getting updated package list for job #{job}"
550-
updated_packages, stderr, status = Open3.capture3("yum history info #{job}")
551-
err(status, 'os_patching/yum', stderr, starttime) if status != 0
552-
updated_packages.split("\n").each do |line|
553-
matchdata = line.match(/^\s+(Installed|Install|Upgraded|Erased|Updated)\s+(\S+)\s/)
554-
next unless matchdata
555-
pkg_hash[matchdata[2]] = matchdata[1]
556-
end
557-
else
558-
yum_return = 'Assumed successful - further details not available on RHEL5'
559-
job = 'Unsupported on RHEL5'
560-
pkg_hash = {}
532+
break
533+
end
534+
535+
log.debug "Captured dnf job ID : #{job_id}"
536+
log.debug "Captured dnf job date : #{job_date}"
537+
538+
# Fail if we didn't capture a job ID
539+
err(1, 'os_patching/dnf', 'dnf job ID not found', starttime) if job_id.nil?
540+
541+
# Fail if we didn't capture a job time
542+
err(1, 'os_patching/dnf', 'dnf job time not found', starttime) if job_date.nil?
543+
544+
# Check that the first dnf history entry was after the dnf_start time
545+
# we captured. Append ':59' to the date as dnf history only gives the
546+
# minute and if dnf bails, it will usually be pretty quick
547+
parsed_end = Time.parse(job_date + ':59').iso8601
548+
err(1, 'os_patching/dnf', 'DNF did not appear to run', starttime) if parsed_end < starttime
549+
550+
# Capture the dnf return code
551+
log.debug "Getting dnf return code for job #{job_id}"
552+
553+
# Example output of `dnf history info <job_id>` :
554+
#
555+
# Transaction ID : 12
556+
# Begin time : Thu Mar 26 11:24:18 2026
557+
# Begin rpmdb : 485:f7aac331cf34d853f41f365d90ebec3de52f633e
558+
# End time : Thu Mar 26 11:24:24 2026 (6 seconds)
559+
# End rpmdb : 485:6cecf20abc141842a1fc3d31e6cfb72a5588e76c
560+
# User : root <root>
561+
# Return-Code : Success
562+
# Releasever : 8
563+
# Command Line : upgrade -y
564+
# Comment :
565+
# Packages Altered:
566+
# Upgrade openvox-agent-8.25.0-1.el8.x86_64 @openvox8
567+
# Upgraded openvox-agent-8.24.2-1.el8.x86_64 @@System
568+
#
569+
job_status, stderr, status = Open3.capture3("dnf history info #{job_id}")
570+
dnf_return = nil
571+
572+
err(status, 'os_patching/dnf', stderr, starttime) if status != 0
573+
574+
job_status.split("\n").each do |line|
575+
next unless line.start_with?('Return-Code')
576+
577+
# Split the line into fields and pull out the return code.
578+
# The fields are separated by ':' characters, but there may be multiple spaces around them,
579+
# so we split on ':' and then strip whitespace from the resulting fields.
580+
# There might also be multiple colons in the return code if there is an error,
581+
# so we limit the split to 2 fields to ensure we capture the whole return code.
582+
dnf_return = line.split(':', 2).last.strip
583+
584+
break
585+
end
586+
587+
err(status, 'os_patching/dnf', 'dnf return code not found', starttime) if dnf_return.nil?
588+
589+
pkg_hash = {}
590+
# Pull out the updated package list from dnf history
591+
log.debug "Getting updated package list for job #{job_id}"
592+
593+
updated_packages, stderr, status = Open3.capture3("dnf history info #{job_id}")
594+
err(status, 'os_patching/dnf', stderr, starttime) if status != 0
595+
596+
updated_packages.split("\n").each do |line|
597+
next unless line.strip.start_with?('Erased', 'Install', 'Removed', 'Updated', 'Upgraded')
598+
599+
# Split the line into fields and pull out the action and package name.
600+
# The fields are separated by spaces, but there may be multiple spaces around them,
601+
# so we split on spaces and then strip whitespace from the resulting fields
602+
action, pkg_name, _source = line.split.map(&:strip)
603+
pkg_hash[pkg_name] = action
561604
end
562605

563-
output(yum_return, reboot, security_only, 'Patching complete', pkg_hash, output, job, pinned_pkgs, starttime, log)
606+
output(dnf_return, reboot, security_only, 'Patching complete', pkg_hash, job_status.split("\n"), job_id, pinned_pkgs, starttime, log)
564607
log.info 'Patching complete'
565608
elsif os['family'] == 'Debian'
566609
log.info 'Running apt'

0 commit comments

Comments
 (0)