ManageIQ/manageiq-automation_engine

View on GitHub
app/models/miq_ae_yaml_export.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
98%
class MiqAeYamlExport
  include Vmdb::Logging
  include MiqAeYamlImportExportMixin
  attr_accessor :namespace, :klass, :instance, :method, :zip
  NEW_LINE = "\n".freeze
  MANIFEST_FILE_NAME = '.manifest.yaml'.freeze
  FILE_EXTENSIONS = {'ruby' => '.rb', 'perl' => '.pl'}.freeze

  def initialize(domain, options)
    @domain        = domain
    @klass         = options['class']
    @namespace     = options['namespace']
    @options       = options
    @tenant        = @options['tenant']
    reset_manifest
  end

  private

  def write_model
    case export_level
    when "class"     then write_class(domain_object, @namespace, @klass.downcase)
    when "namespace" then write_namespace(domain_object, @namespace)
    else
      @domain == ALL_DOMAINS ? write_all_domains : write_domain(domain_object)
    end
    write_manifest(@options['export_as'].presence || @domain)
  end

  def export_level
    if @namespace.present? && @klass.present?
      "class"
    elsif @namespace.present?
      "namespace"
    else
      "domain"
    end
  end

  def write_domain(dom_obj)
    _log.info("Exporting domain:    <#{@domain}>")
    write_domain_file(dom_obj)
    write_all_namespaces(dom_obj)
  end

  def write_namespace(dom_obj, namespace)
    write_multipart_namespace_files(namespace) if namespace.split('/').count.positive?
    ns_obj = get_namespace_object(namespace)
    _log.info("Exporting domain:    <#{@domain}> namespace: <#{namespace}>")
    write_domain_file(dom_obj)
    write_namespace_file(ns_obj)
    write_all_classes(ns_obj)
    write_all_namespaces(ns_obj)
  end

  def write_multipart_namespace_files(namespace)
    parts = namespace.split("/").delete_if(&:blank?)
    parts.pop
    parts.each_with_object([]) do |ns, new_ns|
      new_ns << ns
      write_namespace_file(get_namespace_object(new_ns.join('/')))
    end
  end

  def write_class(dom_obj, namespace, class_name)
    ns_obj = get_namespace_object(namespace)
    class_obj = get_class_object(ns_obj, class_name)
    _log.info("Exporting domain:    <#{@domain}> ns: <#{namespace}> class: <#{class_name}>")
    write_domain_file(dom_obj)
    write_namespace_file(ns_obj)
    write_class_components(ns_obj.fqname, class_obj)
  end

  def setup_envelope(obj, obj_type)
    {'object_type' => obj_type,
     'version'     => VERSION,
     'object'      => {'attributes' => obj.export_attributes}}
  end

  def write_domain_file(domain_obj)
    if @options['export_as'].present?
      saved_domain    = domain_obj.name
      domain_obj.name = @options['export_as']
    end
    envelope_hash = setup_envelope(domain_obj, DOMAIN_OBJ_TYPE).to_yaml
    write_export_file('fqname'          => domain_obj.name,
                      'output_filename' => DOMAIN_YAML_FILENAME,
                      'export_data'     => envelope_hash,
                      'created_on'      => domain_obj.created_on,
                      'updated_on'      => domain_obj.updated_on)
    domain_obj.name = saved_domain if saved_domain
  end

  def write_namespace_file(ns_obj)
    _log.info("Exporting namespace: <#{ns_obj.fqname}>")
    envelope_hash = setup_envelope(ns_obj, NAMESPACE_OBJ_TYPE).to_yaml
    write_export_file('fqname'          => swap_domain_path(ns_obj.fqname),
                      'output_filename' => NAMESPACE_YAML_FILENAME,
                      'export_data'     => envelope_hash,
                      'created_on'      => ns_obj.created_on,
                      'updated_on'      => ns_obj.updated_on)
  end

  def domains
    @tenant ? @tenant.ae_domains : MiqAeDomain.all
  end

  def write_all_domains
    domains.each do |dom_obj|
      @domain = dom_obj.name
      write_domain(domain_object)
    end
  end

  def write_all_namespaces(parent_obj)
    parent_obj.ae_namespaces.each do |ns_obj|
      write_namespace_file(ns_obj)
      write_all_classes(ns_obj)
      write_all_namespaces(ns_obj)
    end
  end

  def write_all_classes(ns_obj)
    ns_obj.ae_classes.sort_by(&:name).each do |cls|
      write_class_components(ns_obj.fqname, cls)
    end
  end

  def write_class_components(ns_fqname, class_obj)
    _log.info("Exporting class:     <#{ns_fqname}/#{class_obj.name}>")
    write_class_schema(ns_fqname, class_obj)
    write_all_instances(ns_fqname, class_obj)
    write_all_methods(ns_fqname, class_obj)
  end

  def write_class_schema(ns_fqname, class_obj)
    envelope_hash = setup_envelope(class_obj, CLASS_OBJ_TYPE)
    envelope_hash['object']['schema'] = class_obj.export_schema
    write_export_file('fqname'          => swap_domain_path(ns_fqname),
                      'class_name'      => "#{class_obj.name}#{CLASS_DIR_SUFFIX}",
                      'output_filename' => CLASS_YAML_FILENAME,
                      'export_data'     => envelope_hash.to_yaml,
                      'created_on'      => class_obj.created_on,
                      'updated_on'      => class_obj.updated_on)
    @counts['classes'] += 1
  end

  def write_all_instances(ns_fqname, class_obj)
    export_file_hash = {'fqname'     => swap_domain_path(ns_fqname),
                        'class_name' => "#{class_obj.name}#{CLASS_DIR_SUFFIX}"}
    class_obj.ae_instances.sort_by(&:fqname).each do |inst|
      file_name = inst.name.dup
      file_name[0] = '_' if file_name[0] == '.'
      envelope_hash = setup_envelope(inst, INSTANCE_OBJ_TYPE)
      envelope_hash['object']['fields'] = inst.export_ae_fields
      export_file_hash['output_filename'] = "#{file_name}.yaml"
      export_file_hash['export_data'] = envelope_hash.to_yaml
      export_file_hash['created_on'] = inst.created_on
      export_file_hash['updated_on'] = inst.updated_on
      @counts['instances'] += 1
      write_export_file(export_file_hash)
    end
  end

  def write_all_methods(ns_fqname, class_obj)
    export_file_hash = {'fqname' => swap_domain_path(ns_fqname)}
    export_file_hash['class_name'] = "#{class_obj.name}#{CLASS_DIR_SUFFIX}/#{METHOD_FOLDER_NAME}"
    class_obj.ae_methods.sort_by(&:fqname).each do |meth_obj|
      export_file_hash['created_on'] = meth_obj.created_on
      export_file_hash['updated_on'] = meth_obj.updated_on
      if meth_obj.location == 'inline'
        write_method_file(meth_obj, export_file_hash)
      end
      write_method_attributes(meth_obj, export_file_hash)
    end
  end

  def write_method_attributes(method_obj, export_file_hash)
    envelope_hash = setup_envelope(method_obj, METHOD_OBJ_TYPE)
    convert_playbook_attributes(envelope_hash) if method_obj.location == "playbook"
    envelope_hash['object']['inputs'] = method_obj.method_inputs
    envelope_hash['object']['attributes'].delete('data') if method_obj.location == "inline"
    envelope_hash['object']['attributes'].delete('embedded_methods') if method_obj.embedded_methods.empty?
    envelope_hash['object']['attributes'].delete('options') if method_obj.options.empty?
    export_file_hash['output_filename'] = "#{method_obj.name}.yaml"
    export_file_hash['export_data']     = envelope_hash.to_yaml
    @counts['method_instances'] += 1
    write_export_file(export_file_hash)
  end

  def convert_playbook_attributes(envelope_hash)
    options_hash = envelope_hash['object']['attributes']['options']
    create_playbook_attributes_names(options_hash)
    options_hash.except!(:repository_id, :playbook_id, :credential_id, :vault_credential_id, :cloud_credential_id)
  end

  def create_playbook_attributes_names(options)
    ae_manager = ManageIQ::Providers::EmbeddedAnsible::AutomationManager
    options[:repository_name]       = AnsibleRepositoryController.model.find_by(:id => options[:repository_id])&.name if options[:repository_id]
    options[:playbook_name]         = ae_manager::Playbook.find_by(:id => options[:playbook_id])&.name                if options[:playbook_id]
    options[:credential_name]       = ae_manager::Credential.find_by(:id => options[:credential_id])&.name            if options[:credential_id]
    options[:vault_credential_name] = ae_manager::VaultCredential.find_by(:id => options[:vault_credential_id])&.name if options[:vault_credential_id]
    options[:cloud_credential_name] = ae_manager::CloudCredential.find_by(:id => options[:cloud_credential_id])&.name if options[:cloud_credential_id]
  end

  def write_method_file(method_obj, export_file_hash)
    @counts['method_files'] += 1
    export_file_hash['output_filename'] = get_method_filename(method_obj)
    if method_obj.data
      method_obj.data += NEW_LINE unless method_obj.data.end_with?(NEW_LINE)
    end
    export_file_hash['export_data'] = method_obj.data || ""
    write_export_file(export_file_hash)
  end

  def get_method_filename(method_obj)
    if method_obj['location'] == 'inline'
      "#{method_obj.name}#{FILE_EXTENSIONS[method_obj.language]}"
    elsif method_obj['location'] == 'uri'
      "#{method_obj.name}.uri"
    end
  end

  def write_export_file(export_hash)
    base_path = export_hash['fqname']
    base_path = File.join(base_path, export_hash['class_name']) unless export_hash['class_name'].nil?
    write_data(base_path, export_hash)
    add_to_manifest(base_path, export_hash)
  end

  def add_to_manifest(base_path, export_hash)
    parts = base_path.split('/')
    parts.shift
    fname = parts.empty? ? export_hash['output_filename'] : File.join(parts.join('/'), export_hash['output_filename'])
    @manifest[fname] = {'created_on' => export_hash['created_on'],
                        'updated_on' => export_hash['updated_on'],
                        'size'       => export_hash['export_data'].length,
                        'sha1'       => Digest::SHA1.base64digest(export_hash['export_data'])}
  end

  def write_manifest(domain_name)
    update_counts
    export_hash = {'output_filename' => MANIFEST_FILE_NAME, 'export_data' => @manifest.to_yaml}
    write_data(domain_name, export_hash)
    reset_manifest
  end

  def update_counts
    @manifest[DOMAIN_YAML_FILENAME] = @counts.merge(@manifest[DOMAIN_YAML_FILENAME])
  end

  def reset_manifest
    @manifest = {DOMAIN_YAML_FILENAME => {}}
    @counts   = Hash.new { |h, k| h[k] = 0 }
  end

  def domain_object
    raise MiqAeException::DomainNotAccessible, "Domain [#{@domain}] not accessible" unless domain_accessible?

    MiqAeDomain.lookup_by_fqname(@domain).tap do |dom|
      if dom.nil?
        _log.error("Domain: <#{@domain}> not found.")
        raise MiqAeException::DomainNotFound, "Domain [#{@domain}] not found in MiqAeDatastore"
      end
      raise MiqAeException::InvalidDomain, "Domain [#{@domain}] is not a valid domain" unless dom.domain?
    end
  end

  def get_namespace_object(namespace)
    fqname = File.join(@domain, namespace)
    MiqAeNamespace.lookup_by_fqname(fqname) || begin
      _log.error("Namespace: <#{fqname}> not found.")
      raise MiqAeException::NamespaceNotFound, "Namespace: [#{fqname}] not found in MiqAeDatastore"
    end
  end

  def get_class_object(ns_obj, klass)
    ns_obj.ae_classes.detect { |cls| cls.name.downcase == klass } || begin
      _log.error("Class: <#{klass}> not found in <#{ns_obj.fqname}>.")
      raise MiqAeException::ClassNotFound, "Class: [#{klass}] not found in [#{ns_obj.fqname}]"
    end
  end

  def swap_domain_path(fqname)
    return fqname if @options['export_as'].blank?

    fqname.gsub(/^\/#{@domain}/, @options['export_as'])
  end

  def domain_accessible?
    return true unless @tenant

    @tenant.ae_domains.any? { |dom| dom.name.casecmp(@domain).zero? }
  end
end