@@ -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 )
492492end
493493
494- # Run the patching
494+ # Run the patching on the appropriate platforms
495+ ###############################################################################
496+
495497if 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'
565608elsif os [ 'family' ] == 'Debian'
566609 log . info 'Running apt'
0 commit comments