diff --git a/app/models/manageiq/providers/ansible_tower/shared/automation_manager/credential.rb b/app/models/manageiq/providers/ansible_tower/shared/automation_manager/credential.rb index 4a15b4a206a..65ec577d3bb 100644 --- a/app/models/manageiq/providers/ansible_tower/shared/automation_manager/credential.rb +++ b/app/models/manageiq/providers/ansible_tower/shared/automation_manager/credential.rb @@ -12,10 +12,15 @@ def provider_collection(manager) def provider_params(params) params[:username] = params.delete(:userid) if params.include?(:userid) - params[:username] = params.delete('userid') if params.include?('userid') params[:kind] = self::TOWER_KIND params end + + def hide_secrets(params) + params.each_with_object({}) do |attr, h| + h[attr.first] = self::API_ATTRIBUTES[attr.first] && self::API_ATTRIBUTES[attr.first][:type] == :password ? '******' : attr.second + end + end end def provider_object(connection = nil) diff --git a/app/models/manageiq/providers/ansible_tower/shared/automation_manager/tower_api.rb b/app/models/manageiq/providers/ansible_tower/shared/automation_manager/tower_api.rb index 72b590397be..d814aba17e3 100644 --- a/app/models/manageiq/providers/ansible_tower/shared/automation_manager/tower_api.rb +++ b/app/models/manageiq/providers/ansible_tower/shared/automation_manager/tower_api.rb @@ -3,21 +3,36 @@ module ManageIQ::Providers::AnsibleTower::Shared::AutomationManager::TowerApi module ClassMethods def create_in_provider(manager_id, params) - params = provider_params(params) if respond_to? :provider_params + params = provider_params(params) if respond_to?(:provider_params) manager = ExtManagementSystem.find(manager_id) tower_object = provider_collection(manager).create!(params) refresh(manager) find_by!(:manager_id => manager.id, :manager_ref => tower_object.id) + rescue AnsibleTowerClient::ClientError, ActiveRecord::RecordNotFound => error + raise + ensure + notify('create_in_provider', manager.id, params, error.nil?) end def create_in_provider_queue(manager_id, params) manager = ExtManagementSystem.find(manager_id) - action = "Creating #{name} with name=#{params[:name] || params['name']}" + action = "Creating #{name} with name=#{params[:name]}" queue(manager.my_zone, nil, "create_in_provider", [manager_id, params], action) end private + def notify(op, manager_id, params, success) + params = hide_secrets(params) if respond_to?(:hide_secrets) + Notification.create( + :type => success ? :tower_op_success : :tower_op_failure, + :options => { + :op_name => "#{name.demodulize} #{op}", + :op_arg => params.to_s, + :tower => "Tower(manager_id: #{manager_id})" + } + ) + end def refresh(manager) # Get the record in our database @@ -47,12 +62,16 @@ def queue(zone, instance_id, method_name, args, action) def update_in_provider(params) params.delete(:task_id) # in case this is being called through update_in_provider_queue which will stick in a :task_id - params = self.class.provider_params(params) if self.class.respond_to? :provider_params + params = self.class.provider_params(params) if self.class.respond_to?(:provider_params) with_provider_object do |provider_object| provider_object.update_attributes!(params) end self.class.send('refresh', manager) reload + rescue AnsibleTowerClient::ClientError => error + raise + ensure + self.class.send('notify', 'update_in_provider', manager.id, params, error.nil?) end def update_in_provider_queue(params) @@ -63,6 +82,10 @@ def update_in_provider_queue(params) def delete_in_provider with_provider_object(&:destroy!) self.class.send('refresh', manager) + rescue AnsibleTowerClient::ClientError => error + raise + ensure + self.class.send('notify', 'delete_in_provider', manager.id, {:manager_ref => manager_ref}, error.nil?) end def delete_in_provider_queue diff --git a/db/fixtures/notification_types.yml b/db/fixtures/notification_types.yml index 336c55219b5..46394f4e759 100644 --- a/db/fixtures/notification_types.yml +++ b/db/fixtures/notification_types.yml @@ -139,3 +139,13 @@ :expires_in: 24.hours :level: :error :audience: global +- :name: tower_op_success + :message: 'The operation %{op_name} %{op_arg} on %{tower} completed successfully.' + :expires_in: 24.hours + :level: :success + :audience: global +- :name: tower_op_failure + :message: 'The operation %{op_name} %{op_arg} on %{tower} has failed to complete. Please check the logs for further details.' + :expires_in: 24.hours + :level: :success + :audience: global diff --git a/spec/support/ansible_shared/automation_manager/configuration_script_source.rb b/spec/support/ansible_shared/automation_manager/configuration_script_source.rb index fd7cdef3708..4a9ac31d0c7 100644 --- a/spec/support/ansible_shared/automation_manager/configuration_script_source.rb +++ b/spec/support/ansible_shared/automation_manager/configuration_script_source.rb @@ -26,20 +26,33 @@ } end - it ".create_in_provider" do + let(:expected_notify) do + { + :type => :tower_op_success, + :options => { + :op_name => "#{described_class.name.demodulize} create_in_provider", + :op_arg => params.to_s, + :tower => "Tower(manager_id: #{manager.id})" + } + } + end + + it ".create_in_provider to succeed and send notification" do expect(AnsibleTowerClient::Connection).to receive(:new).and_return(atc) store_new_project(project, manager) expect(EmsRefresh).to receive(:queue_refresh_task).and_return([finished_task]) expect(ExtManagementSystem).to receive(:find).with(manager.id).and_return(manager) - + expect(projects).to receive(:create!).with(params) + expect(Notification).to receive(:create).with(expected_notify) expect(described_class.create_in_provider(manager.id, params)).to be_a(described_class) end - it "not found during refresh" do + it ".create_in_provider to fail(not found during refresh) and send notification" do expect(AnsibleTowerClient::Connection).to receive(:new).and_return(atc) expect(EmsRefresh).to receive(:queue_refresh_task).and_return([finished_task]) expect(ExtManagementSystem).to receive(:find).with(manager.id).and_return(manager) - + expected_notify[:type] = :tower_op_failure + expect(Notification).to receive(:create).with(expected_notify) expect { described_class.create_in_provider(manager.id, params) }.to raise_error(ActiveRecord::RecordNotFound) end @@ -68,15 +81,34 @@ def store_new_project(project, manager) context "Delete through API" do let(:projects) { double("AnsibleTowerClient::Collection", :find => tower_project) } - let(:tower_project) { double("AnsibleTowerClient::Project", :destroy! => nil, :id => 1) } + let(:tower_project) { double("AnsibleTowerClient::Project", :destroy! => nil, :id => '1') } let(:project) { described_class.create!(:manager => manager, :manager_ref => tower_project.id) } + let(:expected_notify) do + { + :type => :tower_op_success, + :options => { + :op_name => "#{described_class.name.demodulize} delete_in_provider", + :op_arg => {:manager_ref => tower_project.id}.to_s, + :tower => "Tower(manager_id: #{manager.id})" + } + } + end - it "#delete_in_provider" do + it "#delete_in_provider to succeed and send notification" do expect(AnsibleTowerClient::Connection).to receive(:new).and_return(atc) expect(EmsRefresh).to receive(:queue_refresh_task).and_return([finished_task]) + expect(Notification).to receive(:create).with(expected_notify) project.delete_in_provider end + it "#delete_in_provider to fail (find the credential) and send notification" do + expect(AnsibleTowerClient::Connection).to receive(:new).and_return(atc) + allow(projects).to receive(:find).and_raise(AnsibleTowerClient::ClientError) + expected_notify[:type] = :tower_op_failure + expect(Notification).to receive(:create).with(expected_notify) + expect { project.delete_in_provider }.to raise_error(AnsibleTowerClient::ClientError) + end + it "#delete_in_provider_queue" do task_id = project.delete_in_provider_queue expect(MiqTask.find(task_id)).to have_attributes(:name => "Deleting #{described_class.name} with Tower internal reference=#{project.manager_ref}") @@ -96,13 +128,32 @@ def store_new_project(project, manager) let(:projects) { double("AnsibleTowerClient::Collection", :find => tower_project) } let(:tower_project) { double("AnsibleTowerClient::Project", :update_attributes! => {}, :id => 1) } let(:project) { described_class.create!(:manager => manager, :manager_ref => tower_project.id) } + let(:expected_notify) do + { + :type => :tower_op_success, + :options => { + :op_name => "#{described_class.name.demodulize} update_in_provider", + :op_arg => {}.to_s, + :tower => "Tower(manager_id: #{manager.id})" + } + } + end - it "#update_in_provider" do + it "#update_in_provider to succeed and send notification" do expect(AnsibleTowerClient::Connection).to receive(:new).and_return(atc) expect(EmsRefresh).to receive(:queue_refresh_task).and_return([finished_task]) + expect(Notification).to receive(:create).with(expected_notify) expect(project.update_in_provider({})).to be_a(described_class) end + it "#update_in_provider to fail (at update_attributes!) and send notification" do + expect(AnsibleTowerClient::Connection).to receive(:new).and_return(atc) + expect(tower_project).to receive(:update_attributes!).with({}).and_raise(AnsibleTowerClient::ClientError) + expected_notify[:type] = :tower_op_failure + expect(Notification).to receive(:create).with(expected_notify) + expect { project.update_in_provider({}) }.to raise_error(AnsibleTowerClient::ClientError) + end + it "#update_in_provider_queue" do task_id = project.update_in_provider_queue({}) expect(MiqTask.find(task_id)).to have_attributes(:name => "Updating #{described_class.name} with Tower internal reference=#{project.manager_ref}") diff --git a/spec/support/ansible_shared/automation_manager/credential.rb b/spec/support/ansible_shared/automation_manager/credential.rb index d8e5325ea17..0fa0d3cacb9 100644 --- a/spec/support/ansible_shared/automation_manager/credential.rb +++ b/spec/support/ansible_shared/automation_manager/credential.rb @@ -6,15 +6,13 @@ let(:api) { double("AnsibleTowerClient::Api", :credentials => credentials) } context "Create through API" do - let(:credentials) { double("AnsibleTowerClient::Collection", :create! => credential) } - let(:credential) { AnsibleTowerClient::Credential.new(nil, credential_json) } - + let(:credentials) { double("AnsibleTowerClient::Collection", :create! => credential) } + let(:credential) { AnsibleTowerClient::Credential.new(nil, credential_json) } let(:credential_json) do params.merge( :id => 10, ).stringify_keys.to_json end - let(:params) do { :description => "Description", @@ -23,29 +21,54 @@ :userid => 'john' } end - - it ".create_in_provider" do - expected_params = { + let(:expected_params) do + { :description => "Description", :name => "My Credential", :related => {}, :username => "john", :kind => described_class::TOWER_KIND } + end + let(:expected_notify_params) do + { + :description => "Description", + :name => "My Credential", + :related => {}, + :username => "john", + :kind => described_class::TOWER_KIND + } + end + let(:expected_notify) do + { + :type => :tower_op_success, + :options => { + :op_name => "#{described_class.name.demodulize} create_in_provider", + :op_arg => expected_params.to_s, + :tower => "Tower(manager_id: #{manager.id})" + } + } + end + + it ".create_in_provider to succeed and send notification" do expected_params[:organization] = 1 if described_class.name.include?("::EmbeddedAnsible::") expect(AnsibleTowerClient::Connection).to receive(:new).and_return(atc) store_new_credential(credential, manager) expect(EmsRefresh).to receive(:queue_refresh_task).and_return([finished_task]) expect(ExtManagementSystem).to receive(:find).with(manager.id).and_return(manager) expect(credentials).to receive(:create!).with(expected_params) + expect(Notification).to receive(:create).with(expected_notify) expect(described_class.create_in_provider(manager.id, params)).to be_a(described_class) end - it "not found during refresh" do + it ".create_in_provider to fail (not found during refresh) and send notification" do + expected_params[:organization] = 1 if described_class.name.include?("::EmbeddedAnsible::") expect(AnsibleTowerClient::Connection).to receive(:new).and_return(atc) expect(EmsRefresh).to receive(:queue_refresh_task).and_return([finished_task]) expect(ExtManagementSystem).to receive(:find).with(manager.id).and_return(manager) - + expect(credentials).to receive(:create!).with(expected_params) + expected_notify[:type] = :tower_op_failure + expect(Notification).to receive(:create).with(expected_notify).and_return(double(Notification)) expect { described_class.create_in_provider(manager.id, params) }.to raise_error(ActiveRecord::RecordNotFound) end @@ -73,15 +96,34 @@ def store_new_credential(credential, manager) context "Delete through API" do let(:credentials) { double("AnsibleTowerClient::Collection", :find => credential) } - let(:credential) { double("AnsibleTowerClient::Credential", :destroy! => nil, :id => 1) } + let(:credential) { double("AnsibleTowerClient::Credential", :destroy! => nil, :id => '1') } let(:ansible_cred) { described_class.create!(:resource => manager, :manager_ref => credential.id) } + let(:expected_notify) do + { + :type => :tower_op_success, + :options => { + :op_name => "#{described_class.name.demodulize} delete_in_provider", + :op_arg => {:manager_ref => credential.id}.to_s, + :tower => "Tower(manager_id: #{manager.id})" + } + } + end - it "#delete_in_provider" do + it "#delete_in_provider to succeed and send notification" do expect(AnsibleTowerClient::Connection).to receive(:new).and_return(atc) expect(EmsRefresh).to receive(:queue_refresh_task).and_return([finished_task]) + expect(Notification).to receive(:create).with(expected_notify) ansible_cred.delete_in_provider end + it "#delete_in_provider to fail (finding credential) and send notification" do + expect(AnsibleTowerClient::Connection).to receive(:new).and_return(atc) + allow(credentials).to receive(:find).and_raise(AnsibleTowerClient::ClientError) + expected_notify[:type] = :tower_op_failure + expect(Notification).to receive(:create).with(expected_notify) + expect { ansible_cred.delete_in_provider }.to raise_error(AnsibleTowerClient::ClientError) + end + it "#delete_in_provider_queue" do task_id = ansible_cred.delete_in_provider_queue expect(MiqTask.find(task_id)).to have_attributes(:name => "Deleting #{described_class.name} with Tower internal reference=#{ansible_cred.manager_ref}") @@ -98,20 +140,38 @@ def store_new_credential(credential, manager) end context "Update through API" do - let(:credentials) { double("AnsibleTowerClient::Collection", :find => credential) } - let(:credential) { double("AnsibleTowerClient::Credential", :update_attributes! => {}, :id => 1) } - let(:ansible_cred) { described_class.create!(:resource => manager, :manager_ref => credential.id) } - - it "#update_in_provider" do - expected_params = { - :username => 'john', - :kind => described_class::TOWER_KIND + let(:credentials) { double("AnsibleTowerClient::Collection", :find => credential) } + let(:credential) { double("AnsibleTowerClient::Credential", :id => 1) } + let(:ansible_cred) { described_class.create!(:resource => manager, :manager_ref => credential.id) } + let(:params) { {:userid => 'john'} } + let(:expected_params) { {:username => 'john', :kind => described_class::TOWER_KIND} } + let(:expected_notify) do + { + :type => :tower_op_success, + :options => { + :op_name => "#{described_class.name.demodulize} update_in_provider", + :op_arg => expected_params.to_s, + :tower => "Tower(manager_id: #{manager.id})" + } } + end + + it "#update_in_provider to succeed and send notification" do expected_params[:organization] = 1 if described_class.name.include?("::EmbeddedAnsible::") expect(AnsibleTowerClient::Connection).to receive(:new).and_return(atc) - expect(EmsRefresh).to receive(:queue_refresh_task).and_return([finished_task]) expect(credential).to receive(:update_attributes!).with(expected_params) - expect(ansible_cred.update_in_provider('userid' => 'john')).to be_a(described_class) + expect(EmsRefresh).to receive(:queue_refresh_task).and_return([finished_task]) + expect(Notification).to receive(:create).with(expected_notify) + expect(ansible_cred.update_in_provider(params)).to be_a(described_class) + end + + it "#update_in_provider to fail (doing update_attributes!) and send notification" do + expected_params[:organization] = 1 if described_class.name.include?("::EmbeddedAnsible::") + expect(AnsibleTowerClient::Connection).to receive(:new).and_return(atc) + expect(credential).to receive(:update_attributes!).with(expected_params).and_raise(AnsibleTowerClient::ClientError) + expected_notify[:type] = :tower_op_failure + expect(Notification).to receive(:create).with(expected_notify).and_return(double(Notification)) + expect { ansible_cred.update_in_provider(params) }.to raise_error(AnsibleTowerClient::ClientError) end it "#update_in_provider_queue" do