namespace :katello do desc "Unify hosts that have registered with both fqdn and shortname.\ Run with HOSTS=foobar.example.com,foobar to run for specific hosts.\ Run with USE_NAME=true to match purely by name and not by mac address. Run with DRYRUN=true to not modify anything." task :unify_hosts => :environment do def main hostnames = ENV['HOSTS'].try(:split, ',') if hostnames.blank? unify_all elsif hostnames.count == 2 unify_two(hostnames[0], hostnames[1]) else puts "HOSTS specified but did not receive two. Usage: HOSTS=foobar.example.com,foobar" end end def unify_all passed = [] ::Host.unscoped.where("name like '%.%'").find_each do |host| next if passed.include?(host.name.downcase) passed << host.name.downcase puts "Skipping #{host.name}, more than one record." && next if ::Host.unscoped.where(:name => host.name).count > 1 short_host = find_match(host) unify(host, short_host) if short_host end end def unify_two(name1, name2) fail _("#{name1} specified twice") if name1 == name2 host1 = Host.find_by(:name => name1) host2 = Host.find_by(:name => name2) if host1.nil? || host2.nil? puts "Could not find both hosts #{host1} and #{host2}\n" elsif name1.starts_with?(name2) unify(host1, host2) elsif name2.starts_with?(name1) unify(host2, host1) else puts "#{host1} and #{host2} do not share a host shortname\n" end end def find_match(host) use_name = ENV['USE_NAME'].try(:downcase) == 'true' if use_name find_match_by_name(host) else find_match_by_mac(host) end end def find_match_by_name(host) short = host.name.split('.')[0] hosts = ::Host.where("name like '#{short}' or name ilike '#{host.name}'").where("id != #{host.id}").where("name != '#{host.name}'") if hosts.count > 1 puts "Found multiple name matches for #{host.name}; skipping: #{hosts.pluck(:name).join(', ')}" nil else hosts.first end end def find_match_by_mac(host) #first look based off mac address mac = host.primary_interface.try(:mac) if mac.blank? puts "Skipping #{host.name} due to blank primary interface mac\n" return end possible_matches = Nic::Base.where("host_id != #{host.id}").where(:mac => mac) possible_match = possible_matches.find { |nic| host.name.downcase.starts_with?(nic.host.name.downcase) } possible_match.try(:host) end def registered?(host) host.subscription_facet.try(:uuid) end #returns [provisioning_host, facet_host] def pick_hosts(host_one, host_two) if registered?(host_one) && registered?(host_two) puts "Both #{host_one.name} and #{host_two.name} are registered, skipping." nil elsif !registered?(host_one) && registered?(host_two) [host_one, host_two] elsif registered?(host_one) && !registered?(host_two) [host_two, host_one] else puts "Neither #{host_one.name} nor #{host_two.name} are registered, skipping." nil end end def unify(host_one, host_two) provisioning_host, facet_host = pick_hosts(host_one, host_two) return if provisioning_host.nil? || facet_host.nil? if facet_host.compute_resource puts "Host #{facet_host.name} is registered with subscription-manager but is assigned to a compute resource, please un-assign this host first." return end if facet_host.managed? puts "Host #{facet_host.name} is registered with subscription-manager but is managed, please un-manage this host first." return end puts "Unifying #{provisioning_host.name} with #{facet_host.name}\n" return if ENV['DRYRUN'] provisioning_host.subscription_facet.try(:destroy!) provisioning_host.content_facet.try(:destroy!) content_facet = facet_host.content_facet sub_facet = facet_host.subscription_facet if content_facet content_facet.host = provisioning_host content_facet.save! content_facet.update_errata_status end if sub_facet sub_facet.host = provisioning_host sub_facet.save! sub_facet.update_subscription_status end provisioning_host.name = provisioning_host.name.downcase unify_arf_reports(provisioning_host, facet_host) facet_host.reload.destroy! end def unify_arf_reports(fqdn_host, short_host) if short_host.try(:arf_reports) short_host.arf_reports.each do |report| report.host = fqdn_host report.save! end end end main end end