ManageIQ/manageiq-providers-ansible_tower

View on GitHub
lib/tasks_private/spec_helper.rake

Summary

Maintainability
Test Coverage
namespace :spec do
  desc "Populate expected Tower objects for casssettes and spec tests"
  task :populate_tower do
    tower_host = ENV['TOWER_URL'] ||"https://dev-ansible-tower3.example.com/api/v1/"
    id = ENV['TOWER_USER'] || 'testuser'
    password = ENV['TOWER_PASSWORD'] || 'secret'
    populate = PopulateTower.new(tower_host, id, password)

    populate.create_dataset
    populate.counts
    populate.to_file(ManageIQ::Providers::AnsibleTower::Engine.root.join("spec/support/tower_data.yml"))
  end

  desc "Get counts of various Tower objects"
  task :tower_counts do
    tower_host = ENV['TOWER_URL'] || "https://dev-ansible-tower3.example.com/api/v1/"
    id = ENV['TOWER_USER'] || 'testuser'
    password = ENV['TOWER_PASSWORD'] || 'secret'
    PopulateTower.new(tower_host, id, password).counts
  end
end

class PopulateTower
  # This is to create a set of objects in Tower and the objects will be captured in a vcr cassette for
  # refresh spec tests. If we need to update the cassette, we can update/rerun this script to modify the objects
  # and so spec expectations can be updated (hopefully) easily
  #
  # It will print out object counts that are needed for the spec
  # Sample output on console
  #
  # === Re-creating Tower objects ===
  #   deleting old spec_test_org: /api/v1/organizations/39/
  # Created name=spec_test_org               manager_ref/ems_ref= 40        url=/api/v1/organizations/40/
  # Created name=hello_scm_cred              manager_ref/ems_ref= 136       url=/api/v1/credentials/136/
  # Created name=hello_machine_cred          manager_ref/ems_ref= 137       url=/api/v1/credentials/137/
  # Created name=hello_vault_cred            manager_ref/ems_ref= 138       url=/api/v1/credentials/138/
  # Created name=hello_aws_cred              manager_ref/ems_ref= 139       url=/api/v1/credentials/139/
  # Created name=hello_network_cred          manager_ref/ems_ref= 140       url=/api/v1/credentials/140/
  # Created name=hello_inventory             manager_ref/ems_ref= 110       url=/api/v1/inventories/110/
  # Created name=hello_vm                    manager_ref/ems_ref= 249       url=/api/v1/hosts/249/
  # Created name=hello_repo                  manager_ref/ems_ref= 591       url=/api/v1/projects/591/
  #   deleting old hello_template: /api/v1/job_templates/589/
  # Created name=hello_template              manager_ref/ems_ref= 592       url=/api/v1/job_templates/592/
  #   deleting old hello_template_with_survey: /api/v1/job_templates/590/
  # Created name=hello_template_with_survey  manager_ref/ems_ref= 593       url=/api/v1/job_templates/593/
  # created /api/v1/job_templates/594/ survey_spec
  # Created name=failed_repo                 manager_ref/ems_ref= 594       url=/api/v1/projects/594/
  # Created name=jobless_repo                manager_ref/ems_ref= 595       url=/api/v1/projects/595/
  # === Object counts ===
  # configuration_script           (job_templates)     : 120
  # configuration_script_source    (projects)          : 32
  # configured_system              (hosts)             : 133
  # inventory_group                (inventories)       : 29
  # credential                     (credentials)       : 47
  # configuration_script_payload   (playbooks)         : 139
  #     hello_repo                                     : 61
  #
  require 'faraday'
  require 'faraday_middleware'

  MAX_TRIES = ENV["MAX_TRIES"] || 10
  TRY_SLEEP = ENV["TRY_SLEEP"] || 2
  DEL_SLEEP = ENV["DEL_SLEEP"] || 20

  def initialize(tower_host, id, password)
    @conn = Faraday.new(tower_host, :ssl => {:verify => false}) do |c|
      c.use(FaradayMiddleware::EncodeJson)
      c.use(FaradayMiddleware::FollowRedirects, :limit => 3, :standards_compliant => true)
      c.use(Faraday::Response::RaiseError)
      c.adapter(Faraday.default_adapter)
      c.basic_auth(id, password)
    end

    @tower_data = {}

    uri = '/api/v1/config'
    config = get_obj(uri)
    @version = Gem::Version.new(config['version'])
    @tower_data[:config] = { :version => config['version'] }

    uri = '/api/v1/me'
    me = get_obj(uri)
    @tower_data[:user] = { :id => me['results'].first['id'] }
  end

  def v3_2?
    @version >= Gem::Version.new('3.2')
  end

  def to_file(filename)
    File.write(filename, @tower_data.to_yaml)
  end

  def create_obj(uri, data)
    del_obj(uri, data[:name])
    obj = JSON.parse(@conn.post(uri, data).body)
    puts "Created name=#{obj['name'].ljust(27)} manager_ref/ems_ref=#{obj['id'].to_s.ljust(10)} url=#{obj['url']}"

    obj
  end

  def del_obj(uri, match_name)
    obj = get_obj(uri)

    if (item = obj['results'].find { |i| i['name'] == match_name })
      puts "Deleting old #{item['name']}: #{item['url']}"
      @conn.delete(item['url'])
      sleep(DEL_SLEEP) # without sleep, sometimes subsequent create will return 400. Seems the deletion has some delay in Tower
    elsif obj['next']
      del_obj(obj['next'], match_name)
    end
  end

  def get_obj(uri)
    JSON.parse(@conn.get(uri).body)
  end

  def try_get_obj_until(uri)
    current_try = 1
    loop do
      raise "Requested operation did not finish even after #{current_try} tries." if current_try > MAX_TRIES

      obj = get_obj(uri)
      return obj if yield obj

      current_try = current_try.succ
      sleep(TRY_SLEEP)
    end
  end

  def wait_for_project_update(project)
    last_update_uri = nil
    try_get_obj_until(project['url']) do |body|
      last_update_uri = body['related']['last_update']
      last_update_uri.present?
    end

    # Wait until the "hello_repo" update finishes.
    try_get_obj_until(last_update_uri) do |body|
      raise "“#{project['name']}” cloning failed." if body['failed']
      body['finished'].present?
    end
  end

  def create_dataset
    ssh_key_data = <<~PRIVATEKEY
      -----BEGIN RSA PRIVATE KEY-----
      MIIEowIBAAKCAQEArIIYuT+hC2dhPaSx68zTxsh5OJ3byVLNoX7urk8XU20OjlK4
      7++J7qqkHojXadRZrJI69/BFteqOpLr16fAuTdPnEV1dIolEApT9Gd5sEMb4SFFc
      QmZPtOCuFMRjweQBVqAFboUDpzp1Yosjyiw34JWaT8n2SVYjgFB/6SZt9/r/ZHjU
      qOnQi/VY1Zp6eWtjW+LpverzCDS7EAv06OLeu9CZtKLNl8DcgcCvCbuONPCsbaSv
      FtK8kw4Ev/oJvoYa3RbMphx9dfj8WB0xOcdlDmJLlvqw/iuBX0Ktslm/nADcPcxK
      sd37i8Ds2BRVIlr7F3Pblh77TIP+KWzM0lVs1wIDAQABAoIBAAdpj6ZmFYVn68W6
      TerT4kWoV40XO1prNGq8CYVz4Iy1Iur6ovesU0DuFB87wgXKGhBQODhvGo+2hGqP
      ngFvUI4HjOYyHM5fF40E2dtCs2IFKqXw2QYBX2tmPBSoW6D5KxWNyq31CTMmT+Ts
      FZ2aSMxdoUPMaci86smYq+ZYwGDnVfp2Da5G/GnvdmN+x51mMku5hETBMCOpR+n9
      Z4bYnayVGyLXBJvwhx3pdIprwzAvoiySFjp/tFk+knxiPK84dJ3tIfdtgXmf1Cp9
      pEqDQR3lnvwW0LrBG3c6MiJRlp+Pl3EOZNMLdmsaKODnInwO2U5BNPQuVHPdrObD
      1GXxcAECgYEA3xkFdbQ+I6QZH5OSNxPKRPcqcYuYewTwQKmiL3mSoICfV9dRNV3e
      ewQpcca7h9dcjTtdyx8PfvCNFR/uh/FhMw+kRXb4bdKDbDrKcQ9x23RFatbsgN14
      q90a6FaEOjOXf0TiTNqP/LTFry1x2r1ZCDLtVcg5zWM/iwUgrO5qOJkCgYEAxfMV
      ijLKtBg8Mbdhb2F29vIxMokZS++AhEjWuWl7d/DCApjCXzrfMaHnBC6b4Oppubkp
      i40KnkaaDSy03U4hpcSPoPONbv2Fw4o/88ml71DF44D7kXCIFjSMvPLEtU2qLl4z
      o4dHUSbtycBzn+wou+IdgPNqNnBYvl/eBNHvBu8CgYBQJ3M4uMtijsCgAasUsr2H
      Ta4oIVllSX7wHIIywGEX3V5idu+sVs9qLzKcuCQESDHuZBfstHoix1ZI8rIGkYi0
      ibghZP8Ypful1PGK8Vuc1wdhvVo3alrClKvoMb1ME+EoTp1ns1bsGh60M4Wma0Uj
      lviCS2/JBRF9Zxg4SWhMcQKBgQC3PLABv8a4M371HqXJLtWq/sLf3t1V15yF1888
      zxIGEw3kzXeQI7UcAp0Q1/xflV7NF0QH9EWSAhT0gR/jhEHNa0jxWsLfrTs3qTBO
      AanjAEhOssUs+phexcJJ3giNNBmG1pjClaVEz95qVgYyUa/bTBK3nZwCTLk5cRDa
      MWMsbQKBgCaNkKxH/gZBxVGbnjxbaxTGGq2TxNrKcKWEY4aIybcJ1kM0+UctHPy2
      ixDk3cLUN9/a24A9BI+3GkyuX9LmubW/HqmSErIxnw6fx8OGUsVc/oJxJFbJjXQv
      QS4PQZOVkJOn3sZr4hlMMLEKA7NSP9O9BiXCQIycrCDN6YlZ+0/c
      -----END RSA PRIVATE KEY-----
    PRIVATEKEY

    puts "=== Re-creating Tower objects ==="

    @tower_data[:items] = {}

    # create test organization
    uri = '/api/v1/organizations/'
    data = {
      :name        => 'spec_test_org',
      :description => 'for miq spec tests'
    }
    organization = create_obj(uri, data)
    @tower_data[:items][data[:name]] = { :id => organization['id'] }

    # create scm cred
    uri = '/api/v1/credentials/'
    data = {
      :name         => 'hello_scm_cred',
      :kind         => 'scm',
      :username     => 'admin',
      :password     => 'abc',
      :organization => organization['id']
    }
    scm_credential = create_obj(uri, data)

    # create machine cred
    data = {
      :name         => 'hello_machine_cred',
      :kind         => 'ssh',
      :username     => 'admin',
      :password     => 'abc',
      :organization => organization['id']
    }
    machine_credential = create_obj(uri, data)

    # create vault cred
    data = {
      :name           => 'hello_vault_cred',
      :kind           => 'ssh',
      :vault_password => 'abc',
      :organization   => organization['id']
    }
    _vault_credential = create_obj(uri, data)

    # create network cred
    data = {
      :name         => 'hello_network_cred',
      :kind         => 'net',
      :username     => 'admin',
      :password     => 'abc',
      :organization => organization['id']
    }
    network_credential = create_obj(uri, data)

    # create cloud aws cred
    data = {
      :name         => 'hello_aws_cred',
      :kind         => 'aws',
      :username     => 'ABC',
      :password     => 'abc',
      :organization => organization['id']
    }
    aws_credential = create_obj(uri, data)

    # create cloud openstack cred
    data = {
      :name         => 'hello_openstack_cred',
      :kind         => 'openstack',
      :username     => 'hello_rack',
      :password     => 'abc',
      :host         => 'openstack.com',
      :project      => 'hello_rack',
      :organization => organization['id']
    }
    _openstack_credential = create_obj(uri, data)

    # create cloud google cred
    data = {
      :name         => 'hello_gce_cred',
      :kind         => 'gce',
      :username     => 'hello_gce@gce.com',
      :ssh_key_data => ssh_key_data,
      :project      => 'squeamish-ossifrage-123',
      :organization => organization['id']
    }
    _gce_credential = create_obj(uri, data)

    # create cloud azure(RM) cred
    data = {
      :name         => 'hello_azure_cred',
      :kind         => 'azure_rm',
      :username     => 'admin',
      :password     => 'abc',
      :subscription => 'sub_id',
      :tenant       => 'ten_id',
      :secret       => 'my_secret',
      :client       => 'cli_id',
      :organization => organization['id']
    }
    _azure_rm_credential = create_obj(uri, data)

    # create cloud satellite6 cred
    data = {
      :name         => 'hello_sat_cred',
      :kind         => 'satellite6',
      :username     => 'admin',
      :password     => 'abc',
      :host         => 's1.sat.com',
      :organization => organization['id']
    }
    _sat6_credential = create_obj(uri, data)

    unless v3_2?
      # These Credential types were removed from v3.2.

      # create cloud rackspace cred
      data = {
        :name         => 'hello_rax_cred',
        :kind         => 'rax',
        :username     => 'admin',
        :password     => 'abc',
        :organization => organization['id']
      }
      _rax_credential = create_obj(uri, data)

      # create cloud azure(Classic) cred
      data = {
        :name         => 'hello_azure_classic_cred',
        :kind         => 'azure',
        :username     => 'admin',
        :ssh_key_data => ssh_key_data,
        :organization => organization['id']
      }
      _azure_classic_credential = create_obj(uri, data)
    end

    # create inventory
    uri = '/api/v1/inventories/'
    data = {
      :name         => 'hello_inventory',
      :description  => 'inventory for miq spec tests',
      :organization => organization['id']
    }
    inventory = create_obj(uri, data)
    @tower_data[:items][data[:name]] = { :id => inventory['id'] }

    # create a host
    uri = '/api/v1/hosts/'
    data = {
      :name        => 'hello_vm',
      :instance_id => '4233080d-7467-de61-76c9-c8307b6e4830',
      :inventory   => inventory['id']
    }
    host = create_obj(uri, data)
    @tower_data[:items][data[:name]] = { :id => host['id'] }

    # create a project
    uri = '/api/v1/projects/'
    data = {
      :name         => 'hello_repo',
      :scm_url      => 'https://github.com/jameswnl/ansible-examples',
      :scm_type     => 'git',
      :credential   => scm_credential['id'],
      :organization => organization['id']
    }
    hello_project = create_obj(uri, data)
    @tower_data[:items][data[:name]] = hello_project.slice('id', 'status').symbolize_keys

    # Wait for hello_project update to finish, it is necessary for creating a template
    wait_for_project_update(hello_project)

    # Wait until there is a update job for "hello_repo".
    uri = nil
    project = try_get_obj_until(hello_project['url']) do |body|
      uri = body['related']['last_update']
      uri.present?
    end

    # Wait until the "hello_repo" update finishes.
    last_update = try_get_obj_until(uri) do |body|
      raise "\"#{data[:name]}\" cloning failed." if body['failed']
      body['finished'].present?
    end

    @tower_data[:items][data[:name]][:status] = last_update['status']
    @tower_data[:items][data[:name]][:last_updated] = Time.parse(last_update['finished']).utc
    @tower_data[:items][data[:name]][:playbooks] = get_obj(project['related']['playbooks'])

    # create a job_template
    uri = '/api/v1/job_templates/'
    data = {
      :name               => 'hello_template',
      :description        => 'test job',
      :job_type           => 'run',
      :project            => hello_project['id'],
      :playbook           => 'hello_world.yml',
      :credential         => machine_credential['id'],
      :cloud_credential   => aws_credential['id'],
      :network_credential => network_credential['id'],
      :inventory          => inventory['id'],
      :organization       => organization['id']
    }
    template = create_obj(uri, data)
    @tower_data[:items][data[:name]] = { :id => template['id'] }

    # create a job_template with survey spec
    uri = '/api/v1/job_templates/'
    data = {
      :name           => 'hello_template_with_survey',
      :description    => 'test job with survey spec',
      :job_type       => 'run',
      :project        => hello_project['id'],
      :playbook       => 'hello_world.yml',
      :credential     => machine_credential['id'],
      :inventory      => inventory['id'],
      :survey_enabled => true,
      :organization   => organization['id']
    }
    template = create_obj(uri, data)
    @tower_data[:items][data[:name]] = { :id => template['id'] }

    # create survey spec
    uri = "/api/v1/job_templates/#{template['id']}/survey_spec/"
    data = {
      :name        => 'Simple Survey',
      :description => 'Description of the simple survey',
      :spec        => [{
        :type                 => 'text',
        :question_name        => 'example question',
        :question_description => 'What is your favorite color?',
        :variable             => 'favorite_color',
        :required             => false,
        :default              => 'blue'
      }]
    }
    @conn.post(uri, data)

    # create workflow job template
    uri = '/api/v1/workflow_job_templates/'
    data = {
      :name         => 'hello_workflow',
      :description  => 'test workflow',
      :inventory    => inventory['id'],
      :organization => organization['id']
    }
    workflow_template = create_obj(uri, data)
    @tower_data[:items][data[:name]] = { :id => workflow_template['id'] }

    # Create a project with failed update.
    uri = '/api/v1/projects/'
    data = {
      :name         => 'failed_repo',
      :scm_url      => 'https://github.com/jameswnl/ansible-examplez',
      :scm_type     => 'git',
      :credential   => scm_credential['id'],
      :organization => organization['id']
    }
    create_obj(uri, data)

    # Create a project without an update job.
    uri = '/api/v1/projects/'
    data = {
      :name         => 'jobless_repo',
      :scm_url      => 'https://github.com/jameswnl/ansible-examples',
      :scm_type     => 'git',
      :credential   => scm_credential['id'],
      :organization => organization['id']
    }
    jobless_project = create_obj(uri, data)

    last_update = wait_for_project_update(jobless_project)
    @conn.delete(last_update['url'])

    # Create and remove project - record an collect an ID of missing entity
    uri = '/api/v1/projects/'
    data = {
      :name         => 'nonexistent_repo',
      :scm_url      => 'https://github.com/jameswnl/ansible-examples',
      :scm_type     => 'git',
      :credential   => scm_credential['id'],
      :organization => organization['id']
    }
    nonexistent_project = create_obj(uri, data)
    @tower_data[:items][data[:name]] = { :id => nonexistent_project['id'] }

    sleep(DEL_SLEEP)

    del_obj(uri, data[:name])

    self
  end

  def counts
    # Watched record types
    record_types = {
      :job_templates          => :configuration_script,
      :workflow_job_templates => :configuration_script,
      :projects               => :configuration_script_source,
      :hosts                  => :configured_system,
      :inventories            => :inventory_group,
      :credentials            => :credential,
      :playbooks              => :configuration_script_payload
    }

    # Collect total counts for various object types
    @tower_data[:total_counts] = {}
    record_types.except(:playbooks).each_key do |tower_name|
      count = get_obj("/api/v1/#{tower_name}/")['count']
      @tower_data[:total_counts][tower_name] = count
    end

    # Collect counts of playbooks (total and per project)
    playbook_counts_per_project = {}
    watched_projects = %w(hello_repo)
    @tower_data[:total_counts][:playbooks] = 0

    uri = '/api/v1/projects/'
    while uri
      response = get_obj(uri)
      uri = response['next']
      response['results'].each do |result|
        playbook_count = get_obj(result['related']['playbooks']).count

        playbook_counts_per_project[result['name']] = playbook_count if watched_projects.include?(result['name'])
        @tower_data[:total_counts][:playbooks] += playbook_count
      end
    end

    # Report the counts
    puts "=== Object counts ==="
    record_types.each_pair do |tower_name, miq_name|
      label = "#{miq_name} (#{tower_name})"
      puts "#{label.ljust(60)} #{@tower_data[:total_counts][tower_name]}"
    end

    playbook_counts_per_project.each_pair do |project, count|
      label = "    #{project}"
      puts "#{label.ljust(60)} #{count}"
    end

    self
  end
end