diff --git a/app/controllers/api/authentications_controller.rb b/app/controllers/api/authentications_controller.rb index 6342961f103..9be15e1a527 100644 --- a/app/controllers/api/authentications_controller.rb +++ b/app/controllers/api/authentications_controller.rb @@ -9,6 +9,14 @@ def edit_resource(type, id, data) action_result(false, err.to_s) end + def create_resource(_type, _id, data) + manager_resource, attrs = validate_auth_attrs(data) + task_id = AuthenticationService.create_authentication_task(manager_resource, attrs) + action_result(true, 'Creating Authentication', :task_id => task_id) + rescue => err + action_result(false, err.to_s) + end + def delete_resource(type, id, _data = {}) auth = resource_search(id, type, collection_class(:authentications)) raise "Delete not supported for #{authentication_ident(auth)}" unless auth.respond_to?(:delete_in_provider_queue) @@ -33,5 +41,14 @@ def build_additional_fields :credential_types => ::Authentication.build_credential_options } end + + def validate_auth_attrs(data) + raise 'must supply a manager resource' unless data['manager_resource'] + attrs = data.dup.except('manager_resource') + manager_collection, manager_id = parse_href(data['manager_resource']['href']) + raise 'invalid manger_resource href specified' unless manager_collection && manager_id + manager_resource = resource_search(manager_id, manager_collection, collection_class(manager_collection)) + [manager_resource, attrs] + end end end diff --git a/app/controllers/api/subcollections/authentications.rb b/app/controllers/api/subcollections/authentications.rb index 0e81bea7a3e..1adfca23bdf 100644 --- a/app/controllers/api/subcollections/authentications.rb +++ b/app/controllers/api/subcollections/authentications.rb @@ -4,6 +4,13 @@ module Authentications def authentications_query_resource(object) object.respond_to?(:authentications) ? object.authentications : [] end + + def authentications_create_resource(parent, _type, _id, data) + task_id = AuthenticationService.create_authentication_task(parent.manager, data) + action_result(true, 'Creating Authentication', :task_id => task_id) + rescue => err + action_result(false, err.to_s) + end end end end diff --git a/app/models/authentication.rb b/app/models/authentication.rb index 096f40a45b5..1efae793dae 100644 --- a/app/models/authentication.rb +++ b/app/models/authentication.rb @@ -118,6 +118,15 @@ def self.build_credential_options end end + def self.class_from_request_data(data) + if !data.key?('type') || data['type'] == to_s + return self + end + type = descendants.find { |klass| klass.name == data['type'] } + raise _('Must be an Authentication type') unless type + type + end + private def set_credentials_changed_on diff --git a/config/api.yml b/config/api.yml index 111d76a87d3..e1c87e8de1a 100644 --- a/config/api.yml +++ b/config/api.yml @@ -246,6 +246,8 @@ :identifier: embedded_automation_manager_credentials_delete - :name: edit :identifier: embedded_automation_manager_credentials_edit + - :name: create + :identifier: embedded_automation_manager_credentials_add :resource_actions: :get: - :name: read @@ -262,6 +264,9 @@ :get: - :name: read :identifier: embedded_automation_manager_credentials_view + :post: + - :name: create + :identifier: embedded_automation_manager_credentials_add :automate: :description: Automate :options: @@ -592,6 +597,13 @@ :get: - :name: read :identifier: embedded_configuration_script_payload_view + :authentications_subcollection_actions: + :get: + - :name: read + :identifier: embedded_automation_manager_credentials_view + :post: + - :name: create + :identifier: embedded_automation_manager_credentials_add :configuration_script_sources: :description: Configuration Script Source :options: diff --git a/lib/services/api/authentication_service.rb b/lib/services/api/authentication_service.rb new file mode 100644 index 00000000000..b1a16ee41c6 --- /dev/null +++ b/lib/services/api/authentication_service.rb @@ -0,0 +1,10 @@ +module Api + class AuthenticationService + def self.create_authentication_task(manager_resource, attrs) + klass = ::Authentication.class_from_request_data(attrs) + # TODO: Temporary validation - remove + raise 'type not currently supported' unless klass.respond_to?(:create_in_provider_queue) + klass.create_in_provider_queue(manager_resource.id, attrs) + end + end +end diff --git a/spec/models/authentication_spec.rb b/spec/models/authentication_spec.rb index a6639d56af4..237ef8f194f 100644 --- a/spec/models/authentication_spec.rb +++ b/spec/models/authentication_spec.rb @@ -45,4 +45,28 @@ expect(described_class.new(:status => 'Error').retryable_status?).to be_truthy end end + + context '.class_from_request_data' do + it 'raises an error if it is not an authentication type' do + expect do + described_class.class_from_request_data('type' => 'ServiceTemplate') + end.to raise_error(RuntimeError, 'Must be an Authentication type') + end + + it 'returns self if no type is specified' do + expect(described_class.class_from_request_data({})).to eq(described_class) + end + + it 'returns self if Authentication is specified' do + expect(described_class.class_from_request_data('type' => 'Authentication')).to eq(described_class) + end + + it 'returns the specified type' do + data = { + 'type' => 'ManageIQ::Providers::EmbeddedAnsible::AutomationManager::MachineCredential' + } + expect(described_class.class_from_request_data(data)) + .to eq(ManageIQ::Providers::EmbeddedAnsible::AutomationManager::MachineCredential) + end + end end diff --git a/spec/requests/api/authentications_spec.rb b/spec/requests/api/authentications_spec.rb index eb88fa82b62..edb0f6cf7bf 100644 --- a/spec/requests/api/authentications_spec.rb +++ b/spec/requests/api/authentications_spec.rb @@ -52,6 +52,7 @@ end describe 'POST /api/authentications' do + let(:manager) { provider.managers.first } let(:params) do { :id => auth.id, @@ -176,6 +177,110 @@ expect(response).to have_http_status(:forbidden) end + + let(:create_params) do + { + :action => 'create', + :description => "Description", + :name => "A Credential", + :related => {}, + :type => 'ManageIQ::Providers::AnsibleTower::AutomationManager::Credential', + :manager_resource => { :href => providers_url(manager.id) } + } + end + + it 'requires a manager resource when creating an authentication' do + api_basic_authorize collection_action_identifier(:authentications, :create, :post) + + run_post(authentications_url, :action => 'create', :type => 'Authentication') + + expected = { + 'results' => [ + { 'success' => false, 'message' => 'must supply a manager resource' } + ] + } + expect(response).to have_http_status(:ok) + expect(response.parsed_body).to include(expected) + end + + it 'requires that the type support create_in_provider_queue' do + api_basic_authorize collection_action_identifier(:authentications, :create, :post) + + run_post(authentications_url, :action => 'create', :type => 'Authentication', :manager_resource => { :href => providers_url(manager.id) }) + + expected = { + 'results' => [ + { 'success' => false, 'message' => 'type not currently supported' } + ] + } + expect(response).to have_http_status(:ok) + expect(response.parsed_body).to include(expected) + end + + it 'can create an authentication' do + api_basic_authorize collection_action_identifier(:authentications, :create, :post) + + expected = { + 'results' => [a_hash_including( + 'success' => true, + 'message' => 'Creating Authentication', + 'task_id' => a_kind_of(Numeric) + )] + } + run_post(authentications_url, create_params) + + expect(response).to have_http_status(:ok) + expect(response.parsed_body).to include(expected) + end + + it 'can create authentications in bulk' do + api_basic_authorize collection_action_identifier(:authentications, :create, :post) + + expected = { + 'results' => [ + a_hash_including( + 'success' => true, + 'message' => 'Creating Authentication', + 'task_id' => a_kind_of(Numeric) + ), + a_hash_including( + 'success' => true, + 'message' => 'Creating Authentication', + 'task_id' => a_kind_of(Numeric) + ) + ] + } + run_post(authentications_url, :resources => [create_params, create_params]) + + expect(response).to have_http_status(:ok) + expect(response.parsed_body).to include(expected) + end + + it 'requires a valid manager_resource' do + api_basic_authorize collection_action_identifier(:authentications, :create, :post) + create_params[:manager_resource] = { :href => '1' } + + expected = { + 'results' => [ + a_hash_including( + 'success' => false, + 'message' => 'invalid manger_resource href specified', + ) + ] + } + run_post(authentications_url, :resources => [create_params]) + + expect(response).to have_http_status(:ok) + expect(response.parsed_body).to include(expected) + end + + it 'will forbid creation of an authentication without appropriate role' do + api_basic_authorize + + run_post(authentications_url, :action => 'create') + + expect(response).to have_http_status(:forbidden) + end end describe 'PUT /api/authentications/:id' do diff --git a/spec/requests/api/configuration_script_payloads_spec.rb b/spec/requests/api/configuration_script_payloads_spec.rb index 35c49003195..3c5adbc18f9 100644 --- a/spec/requests/api/configuration_script_payloads_spec.rb +++ b/spec/requests/api/configuration_script_payloads_spec.rb @@ -67,6 +67,83 @@ end end + describe 'POST /api/configuration_script_payloads/:id/authentications' do + let(:provider) { FactoryGirl.create(:provider_ansible_tower, :with_authentication) } + let(:manager) { provider.managers.first } + let(:playbook) { FactoryGirl.create(:configuration_script_payload, :manager => manager) } + let(:params) do + { + :action => 'create', + :description => "Description", + :name => "A Credential", + :related => {}, + :type => 'ManageIQ::Providers::AnsibleTower::AutomationManager::Credential', + :manager_resource => { :href => providers_url(manager.id) } + } + end + + it 'requires that the type support create_in_provider_queue' do + api_basic_authorize subcollection_action_identifier(:configuration_script_payloads, :authentications, :create) + + run_post("#{configuration_script_payloads_url(playbook.id)}/authentications", :type => 'Authentication') + + expected = { + 'results' => [ + { 'success' => false, 'message' => 'type not currently supported' } + ] + } + expect(response).to have_http_status(:ok) + expect(response.parsed_body).to include(expected) + end + + it 'creates a new authentication with an appropriate role' do + api_basic_authorize subcollection_action_identifier(:configuration_script_payloads, :authentications, :create) + + run_post("#{configuration_script_payloads_url(playbook.id)}/authentications", params) + + expected = { + 'results' => [a_hash_including( + 'success' => true, + 'message' => 'Creating Authentication', + 'task_id' => a_kind_of(Numeric) + )] + } + expect(response).to have_http_status(:ok) + expect(response.parsed_body).to include(expected) + end + + it 'can create multiple authentications with an appropriate role' do + api_basic_authorize subcollection_action_identifier(:configuration_script_payloads, :authentications, :create) + + run_post("#{configuration_script_payloads_url(playbook.id)}/authentications", :resources => [params, params]) + + expected = { + 'results' => [ + a_hash_including( + 'success' => true, + 'message' => 'Creating Authentication', + 'task_id' => a_kind_of(Numeric) + ), + a_hash_including( + 'success' => true, + 'message' => 'Creating Authentication', + 'task_id' => a_kind_of(Numeric) + ) + ] + } + expect(response).to have_http_status(:ok) + expect(response.parsed_body).to include(expected) + end + + it 'cannot create an authentication without appropriate role' do + api_basic_authorize + + run_post("#{configuration_script_payloads_url(playbook.id)}/authentications", :resources => [params]) + + expect(response).to have_http_status(:forbidden) + end + end + describe 'GET /api/configuration_script_payloads/:id/authentications/:id' do it 'returns a specific authentication' do authentication = FactoryGirl.create(:authentication)