From 80f39cfbd541d15e2475bf8b5c7c7b9e07d83143 Mon Sep 17 00:00:00 2001 From: Nick LaMuro Date: Wed, 17 Jul 2019 11:49:27 -0500 Subject: [PATCH 1/7] [ansible_runner] write_password_file -> write_config_files Changes `Ansible::Runner::Credential#write_password_file` to `Ansible::Runner::Credential#write_config_file` since this allows it to be more generic and have this method support multiple file types. For example, machine credential not only writes a `env/password` file, but also a `env/ssh_key` file. But in some of the cloud credentials, there are cloud specific files that are generated, so this allows the same interface to be reused, and then the logic to be implemented in private subclass specific methods. Machine credential has been updated to show how that implementation can be used. --- lib/ansible/runner.rb | 2 +- lib/ansible/runner/credential.rb | 2 +- .../runner/credential/machine_credential.rb | 27 ++++++++++--------- .../credential/generic_credential_spec.rb | 4 +-- .../credential/machine_credential_spec.rb | 8 +++--- 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/lib/ansible/runner.rb b/lib/ansible/runner.rb index 19835d8d80f..8c77a8ab344 100644 --- a/lib/ansible/runner.rb +++ b/lib/ansible/runner.rb @@ -263,7 +263,7 @@ def credentials_info(credentials, base_dir) env_vars.merge!(cred.env_vars) extra_vars.merge!(cred.extra_vars) - cred.write_password_file + cred.write_config_files end [command_line, env_vars, extra_vars] diff --git a/lib/ansible/runner/credential.rb b/lib/ansible/runner/credential.rb index 45f4b5bb83c..30813a97f88 100644 --- a/lib/ansible/runner/credential.rb +++ b/lib/ansible/runner/credential.rb @@ -31,7 +31,7 @@ def extra_vars {} end - def write_password_file + def write_config_files end private diff --git a/lib/ansible/runner/credential/machine_credential.rb b/lib/ansible/runner/credential/machine_credential.rb index c4958e5f824..35d36948332 100644 --- a/lib/ansible/runner/credential/machine_credential.rb +++ b/lib/ansible/runner/credential/machine_credential.rb @@ -12,16 +12,9 @@ def command_line end end - def write_password_file - password_hash = { - "^SSH [pP]assword:" => auth.password, - "^BECOME [pP]assword:" => auth.become_password, - "^Enter passphrase for [a-zA-Z0-9\-\/]+\/ssh_key_data:" => auth.ssh_key_unlock - }.delete_blanks - - File.write(password_file, password_hash.to_yaml) if password_hash.present? - - write_ssh_key if auth.auth_key.present? + def write_config_files + write_password_file + write_ssh_key_file end private @@ -36,8 +29,18 @@ def become_args } end - def write_ssh_key - File.write(ssh_key_file, auth.auth_key) + def write_password_file + password_hash = { + "^SSH [pP]assword:" => auth.password, + "^BECOME [pP]assword:" => auth.become_password, + "^Enter passphrase for [a-zA-Z0-9\-\/]+\/ssh_key_data:" => auth.ssh_key_unlock + }.delete_blanks + + File.write(password_file, password_hash.to_yaml) if password_hash.present? + end + + def write_ssh_key_file + File.write(ssh_key_file, auth.auth_key) if auth.auth_key.present? end end end diff --git a/spec/lib/ansible/runner/credential/generic_credential_spec.rb b/spec/lib/ansible/runner/credential/generic_credential_spec.rb index 16e28d12e0f..cc5de93bd27 100644 --- a/spec/lib/ansible/runner/credential/generic_credential_spec.rb +++ b/spec/lib/ansible/runner/credential/generic_credential_spec.rb @@ -31,9 +31,9 @@ expect(cred.extra_vars).to eq({}) end - it "#write_password_file does not write a file" do + it "#write_config_files does not write a file" do password_file = File.join(@base_dir, "env", "passwords") - cred.write_password_file + cred.write_config_files expect(File.exist?(password_file)).to be_falsey end end diff --git a/spec/lib/ansible/runner/credential/machine_credential_spec.rb b/spec/lib/ansible/runner/credential/machine_credential_spec.rb index 4517f6b7930..22be280497f 100644 --- a/spec/lib/ansible/runner/credential/machine_credential_spec.rb +++ b/spec/lib/ansible/runner/credential/machine_credential_spec.rb @@ -67,7 +67,7 @@ expect(cred.extra_vars).to eq({}) end - describe "#write_password_file" do + describe "#write_config_files" do let(:password_file) { File.join(@base_dir, "env", "passwords") } let(:key_file) { File.join(@base_dir, "env", "ssh_key") } @@ -76,7 +76,7 @@ def password_hash end it "writes out both the passwords file and the key file" do - cred.write_password_file + cred.write_config_files expect(password_hash).to eq( "^SSH [pP]assword:" => "secret", @@ -89,7 +89,7 @@ def password_hash it "doesn't create the password file if there are no passwords" do auth.update!(:password => nil, :become_password => nil, :auth_key_password => nil) - cred.write_password_file + cred.write_config_files expect(File.exist?(password_file)).to be_falsey end @@ -97,7 +97,7 @@ def password_hash password = '!compli-cat"ed&pass,"wor;d' auth.update!(:password => password) - cred.write_password_file + cred.write_config_files expect(password_hash["^SSH [pP]assword:"]).to eq(password) end From f6e3a824837d67a44acb92aacb7c93507a43ac75 Mon Sep 17 00:00:00 2001 From: Nick LaMuro Date: Tue, 16 Jul 2019 17:29:33 -0500 Subject: [PATCH 2/7] [ansible_runner] Add AmazonCredential --- .../runner/credential/amazon_credential.rb | 21 +++++ .../credential/amazon_credential_spec.rb | 79 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 lib/ansible/runner/credential/amazon_credential.rb create mode 100644 spec/lib/ansible/runner/credential/amazon_credential_spec.rb diff --git a/lib/ansible/runner/credential/amazon_credential.rb b/lib/ansible/runner/credential/amazon_credential.rb new file mode 100644 index 00000000000..ade7042658e --- /dev/null +++ b/lib/ansible/runner/credential/amazon_credential.rb @@ -0,0 +1,21 @@ +module Ansible + class Runner + class AmazonCredential < Credential + def self.auth_type + "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::AmazonCredential" + end + + # Modeled off of aws injectors for awx: + # + # https://github.com/ansible/awx/blob/1242ee2b/awx/main/models/credential/injectors.py#L11-L15 + # + def env_vars + { + "AWS_ACCESS_KEY_ID" => auth.userid || "", + "AWS_SECRET_ACCESS_KEY" => auth.password || "", + "AWS_SECURITY_TOKEN" => auth.auth_key + }.delete_nils + end + end + end +end diff --git a/spec/lib/ansible/runner/credential/amazon_credential_spec.rb b/spec/lib/ansible/runner/credential/amazon_credential_spec.rb new file mode 100644 index 00000000000..867fea23723 --- /dev/null +++ b/spec/lib/ansible/runner/credential/amazon_credential_spec.rb @@ -0,0 +1,79 @@ +require 'ansible/runner' +require 'ansible/runner/credential' + +RSpec.describe Ansible::Runner::AmazonCredential do + it ".auth_type is the correct Authentication sub-class" do + expect(described_class.auth_type).to eq("ManageIQ::Providers::EmbeddedAnsible::AutomationManager::AmazonCredential") + end + + context "with a credential object" do + around do |example| + Dir.mktmpdir("ansible-runner-credential-test") do |dir| + @base_dir = dir + example.run + end + end + + let(:auth) { FactoryBot.create(:embedded_ansible_amazon_credential, auth_attributes) } + let(:auth_attributes) do + { + :userid => "manageiq-aws", + :password => "aws_secret", + :auth_key => "key_data" + } + end + + let(:cred) { described_class.new(auth.id, @base_dir) } + + describe "#command_line" do + it "returns an empty hash" do + expect(cred.command_line).to eq({}) + end + end + + # Modeled off of aws injectors for awx: + # + # https://github.com/ansible/awx/blob/1242ee2b/awx/main/models/credential/injectors.py#L11-L15 + # + describe "#env_vars" do + it "sets AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY" do + auth.update!(:auth_key => nil) + expected = { + "AWS_ACCESS_KEY_ID" => "manageiq-aws", + "AWS_SECRET_ACCESS_KEY" => "aws_secret" + } + expect(cred.env_vars).to eq(expected) + end + + it "defaults AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY to ''" do + auth.update!(:userid => nil, :password => nil, :auth_key => nil) + expected = { + "AWS_ACCESS_KEY_ID" => "", + "AWS_SECRET_ACCESS_KEY" => "" + } + expect(cred.env_vars).to eq(expected) + end + + it "adds AWS_SECURITY_TOKEN if present" do + expected = { + "AWS_ACCESS_KEY_ID" => "manageiq-aws", + "AWS_SECRET_ACCESS_KEY" => "aws_secret", + "AWS_SECURITY_TOKEN" => "key_data" + } + expect(cred.env_vars).to eq(expected) + end + end + + describe "#extra_vars" do + it "returns an empty hash" do + expect(cred.extra_vars).to eq({}) + end + end + + describe "#write_config_files" do + it "no-ops" do + expect(cred.write_config_files).to be_nil + end + end + end +end From 7577172e9d8f3115cd62cd66a9102583e585aecf Mon Sep 17 00:00:00 2001 From: Nick LaMuro Date: Tue, 16 Jul 2019 19:01:47 -0500 Subject: [PATCH 3/7] [ansible_runner] Add AzureCredential --- .../runner/credential/azure_credential.rb | 33 +++++ spec/factories/authentication.rb | 4 + .../credential/azure_credential_spec.rb | 139 ++++++++++++++++++ 3 files changed, 176 insertions(+) create mode 100644 lib/ansible/runner/credential/azure_credential.rb create mode 100644 spec/lib/ansible/runner/credential/azure_credential_spec.rb diff --git a/lib/ansible/runner/credential/azure_credential.rb b/lib/ansible/runner/credential/azure_credential.rb new file mode 100644 index 00000000000..3c90f41f0e6 --- /dev/null +++ b/lib/ansible/runner/credential/azure_credential.rb @@ -0,0 +1,33 @@ +module Ansible + class Runner + class AzureCredential < Credential + def self.auth_type + "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::AzureCredential" + end + + # Modeled off of azure injectors for awx: + # + # https://github.com/ansible/awx/blob/1242ee2b/awx/main/models/credential/injectors.py#L45-L60 + # + # NOTE: We don't currently support the AZURE_CLOUD_ENVIRONMENT variable + # as a configurable option. + # + def env_vars + if auth.options && auth.options[:client].present? && auth.options[:tenant].present? + { + "AZURE_CLIENT_ID" => (auth.options || {})[:client], + "AZURE_TENANT" => (auth.options || {})[:tenant], + "AZURE_SECRET" => auth.auth_key || "", + "AZURE_SUBSCRIPTION_ID" => (auth.options || {})[:subscription] || "" + } + else + { + "AZURE_AD_USER" => auth.userid || "", + "AZURE_PASSWORD" => auth.password || "", + "AZURE_SUBSCRIPTION_ID" => (auth.options || {})[:subscription] || "" + } + end + end + end + end +end diff --git a/spec/factories/authentication.rb b/spec/factories/authentication.rb index db6a181d164..89f5975de51 100644 --- a/spec/factories/authentication.rb +++ b/spec/factories/authentication.rb @@ -83,6 +83,10 @@ :parent => :embedded_ansible_credential, :class => "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::AmazonCredential" + factory :embedded_ansible_azure_credential, + :parent => :embedded_ansible_credential, + :class => "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::AzureCredential" + factory :embedded_ansible_machine_credential, :parent => :embedded_ansible_credential, :class => "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::MachineCredential" diff --git a/spec/lib/ansible/runner/credential/azure_credential_spec.rb b/spec/lib/ansible/runner/credential/azure_credential_spec.rb new file mode 100644 index 00000000000..535e1362d4a --- /dev/null +++ b/spec/lib/ansible/runner/credential/azure_credential_spec.rb @@ -0,0 +1,139 @@ +require 'ansible/runner' +require 'ansible/runner/credential' + +RSpec.describe Ansible::Runner::AzureCredential do + it ".auth_type is the correct Authentication sub-class" do + expect(described_class.auth_type).to eq("ManageIQ::Providers::EmbeddedAnsible::AutomationManager::AzureCredential") + end + + context "with a credential object" do + around do |example| + Dir.mktmpdir("ansible-runner-credential-test") do |dir| + @base_dir = dir + example.run + end + end + + let(:auth) { FactoryBot.create(:embedded_ansible_azure_credential, auth_attributes) } + let(:auth_attributes) do + { + :userid => "manageiq-azure", + :password => "azure_password", + :auth_key => "client_secret", + :options => { + :client => "client_id", + :tenant => "tenant_id", + :subscription => "subscription_id" + } + } + end + + let(:cred) { described_class.new(auth.id, @base_dir) } + + describe "#command_line" do + it "returns an empty hash" do + expect(cred.command_line).to eq({}) + end + end + + # Modeled off of azure injectors for awx: + # + # https://github.com/ansible/awx/blob/1242ee2b/awx/main/models/credential/injectors.py#L45-L60 + # + describe "#env_vars" do + context "client_id and tenant_id present" do + let(:auth_attributes) do + { + :auth_key => "client_secret", + :options => { + :client => "client_id", + :tenant => "tenant_id" + } + } + end + + it "sets AZURE_CLIENT_ID, AZURE_TENANT, and AZURE_SECRET" do + expected = { + "AZURE_CLIENT_ID" => "client_id", + "AZURE_TENANT" => "tenant_id", + "AZURE_SECRET" => "client_secret", + "AZURE_SUBSCRIPTION_ID" => "" + } + expect(cred.env_vars).to eq(expected) + end + + it "defaults AZURE_SECRET to '' if missing" do + auth.update!(:auth_key => nil) + expected = { + "AZURE_CLIENT_ID" => "client_id", + "AZURE_TENANT" => "tenant_id", + "AZURE_SECRET" => "", + "AZURE_SUBSCRIPTION_ID" => "" + } + expect(cred.env_vars).to eq(expected) + end + + it "adds AZURE_SUBSCRIPTION_ID if present" do + auth.update!(:options => auth.options.merge(:subscription => "subscription_id")) + expected = { + "AZURE_CLIENT_ID" => "client_id", + "AZURE_TENANT" => "tenant_id", + "AZURE_SECRET" => "client_secret", + "AZURE_SUBSCRIPTION_ID" => "subscription_id" + } + expect(cred.env_vars).to eq(expected) + end + end + + context "client_id and tenant_id missing" do + let(:auth_attributes) do + { + :userid => "manageiq-azure", + :password => "azure_password" + } + end + + it "sets AZURE_AD_USER and AZURE_PASSWORD" do + expected = { + "AZURE_AD_USER" => "manageiq-azure", + "AZURE_PASSWORD" => "azure_password", + "AZURE_SUBSCRIPTION_ID" => "" + } + expect(cred.env_vars).to eq(expected) + end + + it "defaults AZURE_AD_USER and AZURE_PASSWORD to ''" do + auth.update!(:userid => nil, :password => nil) + expected = { + "AZURE_AD_USER" => "", + "AZURE_PASSWORD" => "", + "AZURE_SUBSCRIPTION_ID" => "" + } + expect(cred.env_vars).to eq(expected) + end + + it "adds AWS_SECURITY_TOKEN if present" do + auth.update!(:options => { :subscription => "subscription_id" }) + expected = { + "AZURE_AD_USER" => "manageiq-azure", + "AZURE_PASSWORD" => "azure_password", + "AZURE_SUBSCRIPTION_ID" => "subscription_id" + } + expect(cred.env_vars).to eq(expected) + end + end + end + + describe "#extra_vars" do + it "returns an empty hash" do + expect(cred.extra_vars).to eq({}) + end + end + + describe "#write_config_files" do + it "no-ops" do + expect(cred.write_config_files).to be_nil + end + end + end +end From ef4a40afad0883454bedec709fe05933c2d38d5f Mon Sep 17 00:00:00 2001 From: Nick LaMuro Date: Tue, 16 Jul 2019 18:24:26 -0500 Subject: [PATCH 4/7] [ansible_runner] Add GoogleCredential --- .../runner/credential/google_credential.rb | 43 +++++++ spec/factories/authentication.rb | 4 + .../credential/google_credential_spec.rb | 114 ++++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 lib/ansible/runner/credential/google_credential.rb create mode 100644 spec/lib/ansible/runner/credential/google_credential_spec.rb diff --git a/lib/ansible/runner/credential/google_credential.rb b/lib/ansible/runner/credential/google_credential.rb new file mode 100644 index 00000000000..a815938237c --- /dev/null +++ b/lib/ansible/runner/credential/google_credential.rb @@ -0,0 +1,43 @@ +module Ansible + class Runner + class GoogleCredential < Credential + def self.auth_type + "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::GoogleCredential" + end + + # Modeled off of gce injectors for awx: + # + # https://github.com/ansible/awx/blob/1242ee2b/awx/main/models/credential/injectors.py#L18-L42 + # + def env_vars + { + "GCE_EMAIL" => auth.userid || "", + "GCE_PROJECT" => auth.project || "", + "GCE_CREDENTIALS_FILE_PATH" => gce_credentials_file + } + end + + def write_config_files + write_gce_credentials_file + end + + private + + def write_gce_credentials_file + json_data = { + :type => "service_account", + :private_key => auth.auth_key || "", + :client_email => auth.userid || "", + :project_id => auth.project || "" + } + + File.write(gce_credentials_file, json_data.to_json) + File.chmod(0o0600, gce_credentials_file) + end + + def gce_credentials_file + File.join(base_dir, "gce_credentials") + end + end + end +end diff --git a/spec/factories/authentication.rb b/spec/factories/authentication.rb index 89f5975de51..89cf97e2664 100644 --- a/spec/factories/authentication.rb +++ b/spec/factories/authentication.rb @@ -87,6 +87,10 @@ :parent => :embedded_ansible_credential, :class => "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::AzureCredential" + factory :embedded_ansible_google_credential, + :parent => :embedded_ansible_credential, + :class => "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::GoogleCredential" + factory :embedded_ansible_machine_credential, :parent => :embedded_ansible_credential, :class => "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::MachineCredential" diff --git a/spec/lib/ansible/runner/credential/google_credential_spec.rb b/spec/lib/ansible/runner/credential/google_credential_spec.rb new file mode 100644 index 00000000000..44101c50959 --- /dev/null +++ b/spec/lib/ansible/runner/credential/google_credential_spec.rb @@ -0,0 +1,114 @@ +require 'ansible/runner' +require 'ansible/runner/credential' + +RSpec.describe Ansible::Runner::GoogleCredential do + it ".auth_type is the correct Authentication sub-class" do + expect(described_class.auth_type).to eq("ManageIQ::Providers::EmbeddedAnsible::AutomationManager::GoogleCredential") + end + + context "with a credential object" do + around do |example| + Dir.mktmpdir("ansible-runner-credential-test") do |dir| + @base_dir = dir + example.run + end + end + + let(:auth) { FactoryBot.create(:embedded_ansible_google_credential, auth_attributes) } + let(:auth_attributes) do + { + :userid => "manageiq@gmail.com", + :auth_key => "key_data", + :options => { :project => "google_project" } + } + end + + let(:cred) { described_class.new(auth.id, @base_dir) } + + describe "#command_line" do + it "returns an empty hash" do + expect(cred.command_line).to eq({}) + end + end + + # Modeled off of gce injectors for awx: + # + # https://github.com/ansible/awx/blob/1242ee2b/awx/main/models/credential/injectors.py#L11-L15 + # + describe "#env_vars" do + it "sets GCE_EMAIL, GCE_PROJECT, and GCE_CREDENTIALS_FILE_PATH" do + filename = File.join(@base_dir, "gce_credentials") + expected = { + "GCE_EMAIL" => "manageiq@gmail.com", + "GCE_PROJECT" => "google_project", + "GCE_CREDENTIALS_FILE_PATH" => filename + } + expect(cred.env_vars).to eq(expected) + end + + it "defaults GCE_EMAIL and GCE_PROJECT to '' if missing" do + filename = File.join(@base_dir, "gce_credentials") + auth.update!(:userid => nil, :options => nil) + + expected = { + "GCE_EMAIL" => "", + "GCE_PROJECT" => "", + "GCE_CREDENTIALS_FILE_PATH" => filename + } + expect(cred.env_vars).to eq(expected) + end + end + + describe "#extra_vars" do + it "returns an empty hash" do + expect(cred.extra_vars).to eq({}) + end + end + + describe "#write_config_files" do + it "writes the the env/gce_credentials to a file" do + cred.write_config_files + + actual_data = JSON.parse(File.read(File.join(@base_dir, "gce_credentials"))) + expected_data = { + "type" => "service_account", + "private_key" => "key_data", + "client_email" => "manageiq@gmail.com", + "project_id" => "google_project", + } + + expect(expected_data).to eq(actual_data) + end + + it "files in empty data with emtpy strings (matching awx implementation)" do + auth.update!(:auth_key => nil, :userid => nil, :options => nil) + cred.write_config_files + + actual_data = JSON.parse(File.read(File.join(@base_dir, "gce_credentials"))) + expected_data = { + "type" => "service_account", + "private_key" => "", + "client_email" => "", + "project_id" => "", + } + + expect(expected_data).to eq(actual_data) + end + + it "handles empty options hash" do + auth.update!(:options => {}) + cred.write_config_files + + actual_data = JSON.parse(File.read(File.join(@base_dir, "gce_credentials"))) + expected_data = { + "type" => "service_account", + "private_key" => "key_data", + "client_email" => "manageiq@gmail.com", + "project_id" => "", + } + + expect(expected_data).to eq(actual_data) + end + end + end +end From 9fcb382131c9f56dd126b74d8398007eb45f8bf8 Mon Sep 17 00:00:00 2001 From: Nick LaMuro Date: Tue, 16 Jul 2019 19:31:46 -0500 Subject: [PATCH 5/7] [ansible_runner] Add OpenstackCredential --- .../runner/credential/openstack_credential.rb | 47 +++++++ spec/factories/authentication.rb | 4 + .../credential/openstack_credential_spec.rb | 123 ++++++++++++++++++ 3 files changed, 174 insertions(+) create mode 100644 lib/ansible/runner/credential/openstack_credential.rb create mode 100644 spec/lib/ansible/runner/credential/openstack_credential_spec.rb diff --git a/lib/ansible/runner/credential/openstack_credential.rb b/lib/ansible/runner/credential/openstack_credential.rb new file mode 100644 index 00000000000..44f334580b8 --- /dev/null +++ b/lib/ansible/runner/credential/openstack_credential.rb @@ -0,0 +1,47 @@ +module Ansible + class Runner + class OpenstackCredential < Credential + def self.auth_type + "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::OpenstackCredential" + end + + # Modeled off of openstack injectors for awx: + # + # https://github.com/ansible/awx/blob/1242ee2b/awx/main/models/credential/injectors.py#L70-L96 + # + def env_vars + { "OS_CLIENT_CONFIG_FILE" => os_credentials_file } + end + + def write_config_files + write_os_credentials_file + end + + private + + def write_os_credentials_file + openstack_data = { + "clouds" => { + "devstack" => { + "verify" => false, # NOTE: We don't have a way of configuring this currently + "auth" => { + "auth_url" => auth.host || "", + "username" => auth.userid || "", + "password" => auth.password || "", + "project_name" => auth.project || "", + "domain_name" => auth.domain + }.delete_nils + } + } + } + + File.write(os_credentials_file, openstack_data.to_yaml) + File.chmod(0o0600, os_credentials_file) + end + + def os_credentials_file + File.join(base_dir, "os_credentials") + end + end + end +end diff --git a/spec/factories/authentication.rb b/spec/factories/authentication.rb index 89cf97e2664..6ca84182dcb 100644 --- a/spec/factories/authentication.rb +++ b/spec/factories/authentication.rb @@ -103,6 +103,10 @@ :parent => :embedded_ansible_credential, :class => "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::CloudCredential" + factory :embedded_ansible_openstack_credential, + :parent => :embedded_ansible_credential, + :class => "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::OpenstackCredential" + factory :embedded_ansible_scm_credential, :parent => :embedded_ansible_credential, :class => "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::ScmCredential" diff --git a/spec/lib/ansible/runner/credential/openstack_credential_spec.rb b/spec/lib/ansible/runner/credential/openstack_credential_spec.rb new file mode 100644 index 00000000000..32046c01693 --- /dev/null +++ b/spec/lib/ansible/runner/credential/openstack_credential_spec.rb @@ -0,0 +1,123 @@ +require 'ansible/runner' +require 'ansible/runner/credential' + +RSpec.describe Ansible::Runner::OpenstackCredential do + it ".auth_type is the correct Authentication sub-class" do + expect(described_class.auth_type).to eq("ManageIQ::Providers::EmbeddedAnsible::AutomationManager::OpenstackCredential") + end + + context "with a credential object" do + around do |example| + Dir.mktmpdir("ansible-runner-credential-test") do |dir| + @base_dir = dir + example.run + end + end + + let(:auth) { FactoryBot.create(:embedded_ansible_openstack_credential, auth_attributes) } + let(:auth_attributes) do + { + :userid => "manageiq-openstack", + :password => "openstack_password", + :options => { + :host => "http://fat.openstacks.example.com", + :project => "project" + } + } + end + + let(:cred) { described_class.new(auth.id, @base_dir) } + + describe "#command_line" do + it "returns an empty hash" do + expect(cred.command_line).to eq({}) + end + end + + # Modeled off of openstack injectors for awx: + # + # https://github.com/ansible/awx/blob/1242ee2b/awx/main/models/credential/injectors.py#L70-L96 + # + describe "#env_vars" do + it "sets OS_CLIENT_CONFIG_FILE" do + filename = File.join(@base_dir, "os_credentials") + expected = { "OS_CLIENT_CONFIG_FILE" => filename } + expect(cred.env_vars).to eq(expected) + end + end + + describe "#extra_vars" do + it "returns an empty hash" do + expect(cred.extra_vars).to eq({}) + end + end + + describe "#write_config_files" do + it "writes the YAML data to a file" do + cred.write_config_files + + actual_data = YAML.load_file(File.join(@base_dir, "os_credentials")) + expected_data = { + "clouds" => { + "devstack" => { + "verify" => false, + "auth" => { + "auth_url" => "http://fat.openstacks.example.com", + "username" => "manageiq-openstack", + "password" => "openstack_password", + "project_name" => "project" + } + } + } + } + + expect(expected_data).to eq(actual_data) + end + + it "files in empty data with emtpy strings (matching awx implementation)" do + auth.update!(:userid => nil, :password => nil, :options => nil) + cred.write_config_files + + actual_data = YAML.load_file(File.join(@base_dir, "os_credentials")) + expected_data = { + "clouds" => { + "devstack" => { + "verify" => false, + "auth" => { + "auth_url" => "", + "username" => "", + "password" => "", + "project_name" => "" + } + } + } + } + + expect(expected_data).to eq(actual_data) + end + + it "handles empty options hash" do + auth.update!(:options => auth.options.merge(:domain => "domain")) + cred.write_config_files + + actual_data = YAML.load_file(File.join(@base_dir, "os_credentials")) + expected_data = { + "clouds" => { + "devstack" => { + "verify" => false, + "auth" => { + "auth_url" => "http://fat.openstacks.example.com", + "username" => "manageiq-openstack", + "password" => "openstack_password", + "project_name" => "project", + "domain_name" => "domain" + } + } + } + } + + expect(expected_data).to eq(actual_data) + end + end + end +end From 0a4463af576dae9d38338549e4eb4c940f4bcdb5 Mon Sep 17 00:00:00 2001 From: Nick LaMuro Date: Tue, 16 Jul 2019 20:45:33 -0500 Subject: [PATCH 6/7] [ansible_runner] Add RhvCredential --- .../runner/credential/rhv_credential.rb | 55 ++++++++ spec/factories/authentication.rb | 4 + .../runner/credential/rhv_credential_spec.rb | 118 ++++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100644 lib/ansible/runner/credential/rhv_credential.rb create mode 100644 spec/lib/ansible/runner/credential/rhv_credential_spec.rb diff --git a/lib/ansible/runner/credential/rhv_credential.rb b/lib/ansible/runner/credential/rhv_credential.rb new file mode 100644 index 00000000000..7cfbf889450 --- /dev/null +++ b/lib/ansible/runner/credential/rhv_credential.rb @@ -0,0 +1,55 @@ +module Ansible + class Runner + class RhvCredential < Credential + def self.auth_type + "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::RhvCredential" + end + + # Modeled off of rhv injectors for awx: + # + # https://github.com/ansible/awx/blob/1242ee2b/awx/main/models/credential/__init__.py#L1035-L1054 + # + def env_vars + { + "OVIRT_INI_PATH" => ovirt_ini_file, + "OVIRT_URL" => auth.host || "", + "OVIRT_USERNAME" => auth.userid || "", + "OVIRT_PASSWORD" => auth.password || "", + } + end + + def write_config_files + write_ovirt_ini_file + end + + private + + def write_ovirt_ini_file + ovirt_data = %W[ + [ovirt] + ovirt_url=#{auth.host} + ovirt_username=#{auth.userid} + ovirt_password=#{auth.password} + ] + + # NOTE: We currently DO NOT support ca_file support as is in `awx`. + # + # ansible/awx ref: + # + # https://github.com/ansible/awx/blob/1242ee2b/awx/main/models/credential/__init__.py#L1046 + # + # To add, we need to update the GoogleCredential::API_OPTIONS in + # app/models and add the following line here: + # + # ovirt_data << "ovirt_ca_file=#{auth.auth_key}" if auth.auth_key + + File.write(ovirt_ini_file, ovirt_data.join("\n")) + File.chmod(0o0600, ovirt_ini_file) + end + + def ovirt_ini_file + File.join(base_dir, "ovirt.ini") + end + end + end +end diff --git a/spec/factories/authentication.rb b/spec/factories/authentication.rb index 6ca84182dcb..1a0a98ab296 100644 --- a/spec/factories/authentication.rb +++ b/spec/factories/authentication.rb @@ -107,6 +107,10 @@ :parent => :embedded_ansible_credential, :class => "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::OpenstackCredential" + factory :embedded_ansible_rhv_credential, + :parent => :embedded_ansible_credential, + :class => "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::RhvCredential" + factory :embedded_ansible_scm_credential, :parent => :embedded_ansible_credential, :class => "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::ScmCredential" diff --git a/spec/lib/ansible/runner/credential/rhv_credential_spec.rb b/spec/lib/ansible/runner/credential/rhv_credential_spec.rb new file mode 100644 index 00000000000..ce25e042d3f --- /dev/null +++ b/spec/lib/ansible/runner/credential/rhv_credential_spec.rb @@ -0,0 +1,118 @@ +require 'ansible/runner' +require 'ansible/runner/credential' + +RSpec.describe Ansible::Runner::RhvCredential do + it ".auth_type is the correct Authentication sub-class" do + expect(described_class.auth_type).to eq("ManageIQ::Providers::EmbeddedAnsible::AutomationManager::RhvCredential") + end + + context "with a credential object" do + around do |example| + Dir.mktmpdir("ansible-runner-credential-test") do |dir| + @base_dir = dir + example.run + end + end + + let(:auth) { FactoryBot.create(:embedded_ansible_rhv_credential, auth_attributes) } + let(:auth_attributes) do + { + :userid => "manageiq-rhv", + :password => "rhv_password", + :options => { :host => "rhv_host" } + } + end + + let(:cred) { described_class.new(auth.id, @base_dir) } + + describe "#command_line" do + it "returns an empty hash" do + expect(cred.command_line).to eq({}) + end + end + + # Modeled off of rhv injectors for awx: + # + # https://github.com/ansible/awx/blob/1242ee2b/awx/main/models/credential/__init__.py#L1035-L1054 + # + describe "#env_vars" do + it "sets OVIRT_INI_PATH, OVIRT_URL, OVIRT_USERNAME, and OVIRT_PASSWORD" do + filename = File.join(@base_dir, "ovirt.ini") + expected = { + "OVIRT_INI_PATH" => filename, + "OVIRT_URL" => "rhv_host", + "OVIRT_USERNAME" => "manageiq-rhv", + "OVIRT_PASSWORD" => "rhv_password" + } + + expect(cred.env_vars).to eq(expected) + end + + it "defaults OVIRT_URL, OVIRT_USERNAME, and OVIRT_PASSWORD to ''" do + auth.update!(:userid => nil, :password => nil, :options => nil) + + filename = File.join(@base_dir, "ovirt.ini") + expected = { + "OVIRT_INI_PATH" => filename, + "OVIRT_URL" => "", + "OVIRT_USERNAME" => "", + "OVIRT_PASSWORD" => "" + } + + expect(cred.env_vars).to eq(expected) + end + end + + describe "#extra_vars" do + it "returns an empty hash" do + expect(cred.extra_vars).to eq({}) + end + end + + describe "#write_config_files" do + it "writes the ini file" do + cred.write_config_files + + actual_data = File.read(File.join(@base_dir, "ovirt.ini")) + expected_data = <<~OVIRT_INI.strip + [ovirt] + ovirt_url=rhv_host + ovirt_username=manageiq-rhv + ovirt_password=rhv_password + OVIRT_INI + + expect(expected_data).to eq(actual_data) + end + + it "fills in empty data (matching awx implementation)" do + auth.update!(:userid => nil, :password => nil, :options => nil) + cred.write_config_files + + actual_data = File.read(File.join(@base_dir, "ovirt.ini")) + expected_data = <<~OVIRT_INI.strip + [ovirt] + ovirt_url= + ovirt_username= + ovirt_password= + OVIRT_INI + + expect(expected_data).to eq(actual_data) + end + + it "handles empty options hash" do + auth.update!(:options => {}) + cred.write_config_files + + actual_data = File.read(File.join(@base_dir, "ovirt.ini")) + expected_data = <<~OVIRT_INI.strip + [ovirt] + ovirt_url= + ovirt_username=manageiq-rhv + ovirt_password=rhv_password + OVIRT_INI + + expect(expected_data).to eq(actual_data) + end + end + end +end From bf698f836e5090071a9f9f6d9f4a136f75489fcf Mon Sep 17 00:00:00 2001 From: Nick LaMuro Date: Tue, 16 Jul 2019 21:30:05 -0500 Subject: [PATCH 7/7] [ansible_runner] Add VmwareCredential --- .../runner/credential/vmware_credential.rb | 23 +++++ spec/factories/authentication.rb | 4 + .../credential/vmware_credential_spec.rb | 83 +++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 lib/ansible/runner/credential/vmware_credential.rb create mode 100644 spec/lib/ansible/runner/credential/vmware_credential_spec.rb diff --git a/lib/ansible/runner/credential/vmware_credential.rb b/lib/ansible/runner/credential/vmware_credential.rb new file mode 100644 index 00000000000..06529a456ee --- /dev/null +++ b/lib/ansible/runner/credential/vmware_credential.rb @@ -0,0 +1,23 @@ +module Ansible + class Runner + class VmwareCredential < Credential + def self.auth_type + "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::VmwareCredential" + end + + # Modeled off of vmware injectors for awx: + # + # https://github.com/ansible/awx/blob/1242ee2b/awx/main/models/credential/injectors.py#L63-L67 + # + # NOTE: The VMWARE_VALIDATE_CERTS is currently not supported. + # + def env_vars + { + "VMWARE_USER" => auth.userid || "", + "VMWARE_PASSWORD" => auth.password || "", + "VMWARE_HOST" => auth.host || "" + } + end + end + end +end diff --git a/spec/factories/authentication.rb b/spec/factories/authentication.rb index 1a0a98ab296..17ed0508a55 100644 --- a/spec/factories/authentication.rb +++ b/spec/factories/authentication.rb @@ -115,6 +115,10 @@ :parent => :embedded_ansible_credential, :class => "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::ScmCredential" + factory :embedded_ansible_vmware_credential, + :parent => :embedded_ansible_credential, + :class => "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::VmwareCredential" + factory :auth_key_pair_cloud, :class => "ManageIQ::Providers::CloudManager::AuthKeyPair" factory :auth_key_pair_amazon, :class => "ManageIQ::Providers::Amazon::CloudManager::AuthKeyPair" factory :auth_key_pair_openstack, :class => "ManageIQ::Providers::Openstack::CloudManager::AuthKeyPair" diff --git a/spec/lib/ansible/runner/credential/vmware_credential_spec.rb b/spec/lib/ansible/runner/credential/vmware_credential_spec.rb new file mode 100644 index 00000000000..89df69749d5 --- /dev/null +++ b/spec/lib/ansible/runner/credential/vmware_credential_spec.rb @@ -0,0 +1,83 @@ +require 'ansible/runner' +require 'ansible/runner/credential' + +RSpec.describe Ansible::Runner::VmwareCredential do + it ".auth_type is the correct Authentication sub-class" do + expect(described_class.auth_type).to eq("ManageIQ::Providers::EmbeddedAnsible::AutomationManager::VmwareCredential") + end + + context "with a credential object" do + around do |example| + Dir.mktmpdir("ansible-runner-credential-test") do |dir| + @base_dir = dir + example.run + end + end + + let(:auth) { FactoryBot.create(:embedded_ansible_vmware_credential, auth_attributes) } + let(:auth_attributes) do + { + :userid => "manageiq-vmware", + :password => "vmware_secret", + :options => { + :host => "vmware_host" + } + } + end + + let(:cred) { described_class.new(auth.id, @base_dir) } + + describe "#command_line" do + it "returns an empty hash" do + expect(cred.command_line).to eq({}) + end + end + + # Modeled off of vmware injectors for awx: + # + # https://github.com/ansible/awx/blob/1242ee2b/awx/main/models/credential/injectors.py#L63-L67 + # + describe "#env_vars" do + it "sets VMWARE_USER, VMWARE_PASSWORD, and VMWARE_HOST" do + expected = { + "VMWARE_USER" => "manageiq-vmware", + "VMWARE_PASSWORD" => "vmware_secret", + "VMWARE_HOST" => "vmware_host" + } + expect(cred.env_vars).to eq(expected) + end + + it "defaults VMWARE_USER, VMWARE_PASSWORD, and VMWARE_HOST to '' if missing" do + auth.update!(:userid => nil, :password => nil, :options => nil) + expected = { + "VMWARE_USER" => "", + "VMWARE_PASSWORD" => "", + "VMWARE_HOST" => "" + } + expect(cred.env_vars).to eq(expected) + end + + it "handles empty options hash" do + auth.update!(:options => {}) + expected = { + "VMWARE_USER" => "manageiq-vmware", + "VMWARE_PASSWORD" => "vmware_secret", + "VMWARE_HOST" => "" + } + expect(cred.env_vars).to eq(expected) + end + end + + describe "#extra_vars" do + it "returns an empty hash" do + expect(cred.extra_vars).to eq({}) + end + end + + describe "#write_config_files" do + it "no-ops" do + expect(cred.write_config_files).to be_nil + end + end + end +end