ManageIQ/manageiq-automation_engine

View on GitHub
app/models/miq_ae_datastore/xml_import.rb

Summary

Maintainability
B
4 hrs
Test Coverage
B
87%
module MiqAeDatastore
  class XmlImport
    include Vmdb::Logging
    def self.process_class(input)
      fields    = input.delete("MiqAeSchema")
      instances = input.delete("MiqAeInstance")
      methods   = input.delete("MiqAeMethod")

      # Create the AEClass
      aec = MiqAeClass.create(input)

      Benchmark.realtime_block(:process_class_schema_time) do
        # Find or Create the AEFields
        unless fields.nil?
          aec.ae_fields = process_fields(fields)
          unless instances.nil?
            aec.ae_instances = instances.collect { |i| process_instance(i, aec) }
          end
        end
      end

      Benchmark.realtime_block(:process_class_methods_time) do
        unless methods.nil?
          aec.ae_methods = methods.collect { |m| process_method(m) }
        end
      end

      Benchmark.realtime_block(:process_class_save_time) do
        _log.info("Importing Class: #{aec.fqname}")
        aec.save!
      end
    end

    def self.process_method(input)
      fields             = input.delete("MiqAeField")
      input["data"]      = input.delete("content")
      input["data"]&.strip!
      aem = MiqAeMethod.new(input)

      # Find or Create the Method Input Definitions
      aem.inputs = process_method_inputs(fields) unless fields.nil?
      aem
    end

    def self.process_method_inputs(fields)
      priority = 0
      inputs = []
      fields.each do |f|
        priority += 1
        f["priority"] = priority unless f.include?("priority")
        default_value = f.delete("content")
        f["default_value"] = default_value.strip unless f.key?("default_value") || default_value.nil?
        inputs << MiqAeField.new(f)
      end
      inputs
    end

    def self.process_fields(fields)
      priority = 0
      ae_fields = []
      fields.each do |field|
        field["MiqAeField"].each do |f|
          priority += 1
          f["message"] ||= MiqAeField.default('message')
          f["priority"] ||= priority
          f['substitute'] = MiqAeField.default('substitute') unless %w[true false].include?(f['substitute'])
          f['substitute'] = true  if f['substitute'] == 'true'
          f['substitute'] = false if f['substitute'] == 'false'
          default_value = f.delete("content")
          f["default_value"] = default_value.strip unless f.key?("default_value") || default_value.nil?

          unless f["collect"].blank?
            f["collect"] = f["collect"].first["content"] if f["collect"].kind_of?(Array)
            f["collect"] = REXML::Text.unnormalize(f["collect"].strip)
          end

          %w[on_entry on_exit on_error max_retries max_time].each do |k|
            f[k] = REXML::Text.unnormalize(f[k].strip) unless f[k].blank?
          end

          ae_fields << MiqAeField.new(f)
        end
      end
      ae_fields
    end

    def self.process_instance(input, aec)
      fields = input.delete("MiqAeField")
      input.delete("content")
      aei = MiqAeInstance.new(input)
      aei.ae_class = aec
      fields&.each { |f| process_field_value(aei, f) }
      aei
    end

    def self.process_field_value(aei, field)
      options = {}
      fname = field["name"]
      ae_field = aei.ae_class.ae_fields.detect { |f| fname.casecmp(f.name).zero? }
      raise MiqAeException::FieldNotFound, "Field [#{fname}] not found in MiqAeDatastore" if ae_field.nil?

      options[:ae_field] = ae_field
      value = field["value"] || field["content"]
      value.strip! unless value.blank?
      options[:value] = value
      %w[collect on_entry on_exit on_error max_retries max_time].each do |key|
        next if field[key].blank?

        options[key.to_sym] = REXML::Text.unnormalize(field[key].strip)
      end
      aei.ae_values << MiqAeValue.new(options)
    end

    def self.process_button(input)
      puts "Button Input: #{input.inspect}"
      CustomButton.create_or_update_from_hash(input)
    end

    def self.check_version(version)
      version.to_s >= MiqAeDatastore::XML_VERSION_MIN_SUPPORTED
    end

    def self.load_xml(filename, domain_name = nil)
      _, t = Benchmark.realtime_block(:total_load_xml_time) do
        classes = buttons = nil
        Benchmark.realtime_block(:xml_load_time) do
          require 'xml/xml_hash'
          doc = XmlHash.load(filename)
          version = doc.children[0].attributes[:version]
          _log.info("  with version '#{version}'")
          raise "Unsupported version '#{version}'.  Must be at least '#{MiqAeDatastore::XML_VERSION_MIN_SUPPORTED}'." unless check_version(version)

          classes = doc.to_h(:symbols => false)["MiqAeClass"]
          buttons = doc.to_h(:symbols => false)["MiqAeButton"]
        end

        create_domain(domain_name) if domain_name
        ae_namespaces = {}
        Benchmark.realtime_block(:datastore_import_time) do
          classes&.each do |c|
            namespace = c.delete("namespace")
            next if namespace == '$'

            namespace = File.join(domain_name, namespace) if domain_name && namespace != "$"
            ae_namespaces[namespace] ||= Benchmark.realtime_block(:build_namespaces) do
              MiqAeNamespace.find_or_create_by_fqname(namespace)
            end.first
            c["ae_namespace"] = ae_namespaces[namespace]
            process_class(c)
          end

          buttons&.each { |b| process_button(b) }
        end
      end

      _log.info("Automate Datastore Import complete: #{t.inspect}")
    end

    def self.create_domain(domain)
      MiqAeDomain.lookup_by_fqname(domain) ||
        MiqAeDomain.create!(:enabled => true, :priority => 100, :tenant => Tenant.root_tenant, :name => domain)
    end

    def self.load_xml_file(filename, domain)
      File.open(filename, 'r') { |handle| load_xml(handle, domain) }
    end

    def self.load_file(filename, domain)
      _log.info("Importing file '#{filename}'")
      ext = File.extname(filename).downcase
      case ext
      when ".xml" then load_xml_file(filename, domain)
      else raise "Unhandled File Extension [#{ext}] when trying to load #{filename}"
      end
      _log.info("Import complete")
    end
  end
end