module Katello class ProductContentImporter #Example product_content json structure # { # "content":{ # "uuid":"4028f9f7677f3f4c0167a30cc92c5d55", # "id":"4010", # "type":"file", # "label":"rhel-6-server-satellite-5.7-isos", # "name":"Red Hat Satellite 5.7 (ISOs)", # "vendor":"Red Hat", # "contentUrl":"/content/dist/rhel/server/6/$releasever/$basearch/satellite/5.7/iso", # "requiredTags":"rhel-6-server", # "gpgUrl":"file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release", # "modifiedProductIds":[], # "arches":"x86_64", # "requiredProductIds":[], # "metadataExpire":1, # "releaseVer":null # }, # "enabled":false # } # attr_reader :content_url_updated def initialize(cp_products = []) @contents_to_create = [] @product_contents_to_create = [] @product_mapping = {} @content_url_updated = [] @cp_products = cp_products end def add_product_content(product, product_content_json) @product_mapping[product] = product_content_json.map(&:with_indifferent_access) end def find_product_for_content(content_id) prod = @cp_products.find do |prod_json| next if prod_json&.[]('productContent').blank? prod_json['productContent'].any? do |product_content_json| product_content_json&.dig("content", "id") == content_id end end ::Katello::Product.find_by(cp_id: prod["id"]) if prod end def fetch_product_contents_to_move(product, prod_contents_json) content_ids = prod_contents_json.map { |pc| pc[:content][:id] } # Identify if there are any product_content that should not be # part of this product. product_contents_to_delete_or_move = product. product_contents. joins(:content). where.not(content: { cp_content_id: content_ids }) # Identify if product content actually moved between 2 different products product_contents_to_delete_or_move.select do |pc| content_exists?(product.organization, pc.content) end end def handle_product_moves(product, prod_contents_json) product_contents_to_move = fetch_product_contents_to_move(product, prod_contents_json) moved_product_contents = [] product_contents_to_move.each do |pc| content = pc.content root_repo = product.root_repositories.find_by(content_id: content.cp_content_id) actual_product = find_product_for_content(content.cp_content_id) if actual_product.present? && root_repo.present? && root_repo.product != actual_product root_repo.update!(product_id: actual_product.id) pc.update!(product_id: actual_product.id) moved_product_contents << pc else pc.destroy! end end product.reload unless product_contents_to_move.blank? moved_product_contents end def import return if @product_mapping.blank? moved_product_contents = [] @product_mapping.each do |product, prod_contents_json| moved_product_contents += handle_product_moves(product, prod_contents_json) existing_product_contents = product.product_contents.to_a prod_contents_json.each do |prod_content_json| content = create_or_update_content(product, prod_content_json) existing_content_map[content.cp_content_id] = content if content.new_record? create_or_update_product_content(product, existing_product_contents, content, prod_content_json[:enabled]) end end ::Katello::Content.import(@contents_to_create, recursive: true) ::Katello::ProductContent.import(fetch_product_contents_to_create(moved_product_contents)) end private def existing_content_map if @existing_content_map.nil? @existing_content_map = {} Katello::Content.where(:organization_id => @product_mapping.keys.first.organization.id).to_a.each do |content| @existing_content_map[content[:cp_content_id]] = content end end @existing_content_map end private def content_exists?(org, content) Resources::Candlepin::Content.get(org.label, content.cp_content_id) true rescue RestClient::NotFound false end private def fetch_product_contents_to_create(moved_product_contents) @product_contents_to_create.select do |pc| moved_product_contents.none? { |mpc| mpc.content_id == pc.content_id && mpc.product_id == pc.product_id } end end private def create_or_update_content(product, prod_content_json) content = existing_content_map[prod_content_json[:content][:id].to_s] if content update_content(content, prod_content_json) else #content does not exist content = ::Katello::Content.new(convert_content_attributes(product, prod_content_json)) @contents_to_create << content end content end private def create_or_update_product_content(product, existing_product_contents, content, new_enabled_value) product_content = existing_product_contents.find { |pc| pc.content_id == content.id } if product_content update_product_content(product_content, new_enabled_value) else #product_content does not exist if content.new_record? content.product_contents << ::Katello::ProductContent.new(:product_id => product.id, :enabled => new_enabled_value) else @product_contents_to_create << ::Katello::ProductContent.new(:product_id => product.id, :content_id => content.id, :enabled => new_enabled_value) end end end private def convert_content_attributes(product, product_content_json) content = product_content_json[:content] { cp_content_id: content[:id], name: content[:name], label: content[:label], content_type: content[:type], vendor: content[:vendor], gpg_url: content[:gpgUrl], content_url: content[:contentUrl], organization_id: product.organization_id, } end #cannot use activerecord-improt to update content, as we rely on after_update callback private def update_content(content, prod_content_json) attrs_to_update = {} new_name = prod_content_json[:content][:name] attrs_to_update[:name] = new_name if content.name != new_name new_url = prod_content_json[:content][:contentUrl] if content.content_url != new_url if content.can_update_to_url?(new_url) attrs_to_update[:content_url] = new_url @content_url_updated << content else Rails.logger.warn(_("Substitution Mismatch. Unable to update for content: (%{content}). From [%{content_url}] To [%{new_url}].") % { content: content.inspect, content_url: content.content_url, new_url: new_url }) end end content.update!(attrs_to_update) unless attrs_to_update.blank? end private def update_product_content(product_content, new_enabled_value) product_content.update!(enabled: new_enabled_value) if product_content.enabled != new_enabled_value end end end