Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CRUD for embedded Ansible credentials #641

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions app/assets/javascripts/components/ansible-credential-options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
ManageIQ.angular.app.component('ansibleCredentialOptions', {
bindings: {
model: '=',
options: '<',
type: '<',
},

controllerAs: 'vm',

controller: ['$scope', function($scope) {
$scope.__ = __;

this.setOptions = function() {
this.current_options = this.options[this.type];
};

this.$onInit = function() {
this.setOptions();
};

this.$onChanges = function(changes) {
this.setOptions();
};
}],

template: [
'<div class="form-group" ng-repeat="(name, attr) in vm.current_options.attributes">',
'<label class="control-label col-md-2">',
'{{ __(attr.label) }}',
'</label>',
'<div ng-switch="attr.type" class="text">',
// password
'<div ng-switch-when="password" class="col-md-8">',
'<input type="password" class="form-control" title="{{ __(attr.help_text) }}" ng-model="vm.model[name]">',
'</div>',
// select
'<div ng-switch-when="choice" class="col-md-8">',
'<select pf-select ng-options="opt as opt for opt in attr.choices" class="form-control" ng-model="vm.model[name]" />',
'</div>',
// text
'<div ng-switch-when="string" class="col-md-8">',
'<input type="text" class="form-control" title="{{ __(attr.help_text) }}" maxlength="{{ attr.max_length }}" ng-model="vm.model[name]">',
'</div>',
// default (text)
'<div ng-switch-default class="col-md-8">',
'<input type="text" class="form-control" title="{{ __(attr.help_text) }}" ng-model="vm.model[name]">',
'</div>',
'</div>',
'</div>',
].join('\n')
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
ManageIQ.angular.app.controller('ansibleCredentialsFormController', ['$window', 'credentialId', 'miqService', 'API', function($window, credentialId, miqService, API) {
var vm = this;

var init = function() {
vm.credentialModel = {
id: null,
name: '',
type: '',
};

vm.credential_options = {};
vm.select_options = [];

vm.newRecord = credentialId === 'new';
vm.afterGet = false;
vm.model = 'credentialModel';
ManageIQ.angular.scope = vm;
vm.saveable = miqService.saveable;

miqService.sparkleOn();

// get credential specific options for all supported credential types
API.options('/api/authentications')
.then(getCredentialOptions)
.catch(miqService.handleFailure);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After #783 merge, the .catch will now catch the API failures, but those need to be visible to the user as flash messages.
Created a PR #805 that will display the error message flash.


if (credentialId !== 'new') {
API.get('/api/authentications/' + credentialId)
.then(getCredentialFormData)
.catch(miqService.handleFailure);
} else {
vm.select_options.push({'label':__('<Choose>'), 'value': ''});
// FIXME: this should go away once https://github.com/ManageIQ/manageiq/pull/14483 is merged
vm.credentialModel.organization = 1; // work-around, organization id needs to be filled in automatically by the backend

// credential creation requires manager_resource
API.get('/api/providers?collection_class=ManageIQ::Providers::EmbeddedAutomationManager')
.then(setManagerResource)
.catch(miqService.handleFailure);

vm.afterGet = true;
vm.modelCopy = angular.copy( vm.credentialModel );
miqService.sparkleOff();
}
};

vm.cancelClicked = function(angularForm) {
if (credentialId === 'new') {
getBack(__("Creation of new Credential was canceled by the user."), true);
} else {
getBack(sprintf(__("Edit of Credential \"%s\" was canceled by the user."), vm.credentialModel.name), true);
}
};

vm.resetClicked = function(angularForm) {
vm.credentialModel = angular.copy( vm.modelCopy );
angularForm.$setPristine(true);
miqService.miqFlash("warn", __("All changes have been reset"));
};

vm.saveClicked = function(angularForm) {
API.put('/api/authentications/' + credentialId, vm.credentialModel)
.then(getBack(sprintf(__("Modification of Credential \"%s\" has been successfully queued."), vm.credentialModel.name), false))
.catch(miqService.handleFailure);
};

vm.addClicked = function(angularForm) {
addCredentialKind();
API.post('/api/authentications/', vm.credentialModel)
.then(getBack(sprintf(__("Add of Credential \"%s\" has been successfully queued."), vm.credentialModel.name)))
.catch(miqService.handleFailure);
};

function getCredentialOptions(response) {
Object.assign(vm.credential_options, response.data.credential_types.embedded_ansible_credential_types);

for (var opt in vm.credential_options) {
vm.select_options.push({'value': opt, 'label': vm.credential_options[opt].label});
}
}

function getCredentialFormData(response) {
vm.credentialModel.id = response.id;
vm.credentialModel.name = response.name;
vm.credentialModel.type = response.type;

// we need to merge options and vm.credentialModel
for (var opt in response.options) {
var item = vm.credential_options[vm.credentialModel.type]['attributes'][opt];

// void the password fields first
if (item.hasOwnProperty('type') && item['type'] === 'password') {
vm.credentialModel[opt] = '';
} else {
vm.credentialModel[opt] = response.options[opt];
}
}

vm.modelCopy = angular.copy( vm.credentialModel );
vm.afterGet = true;
miqService.sparkleOff();
}

function getBack(message, warning = false, error = false) {
var url = '/ansible_credential/show_list' + '?flash_msg=' + message + '&escape=true';

if (warning) {
url += '&flash_warning=true&flash_error=false';
} else if (error) {
url += '&flash_warning=false&flash_error=true';
}

$window.location.href = url;
}

function setManagerResource(response) {
vm.credentialModel.manager_resource = { "href": response.resources[0].href };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm aware that the credential API backend changes are still not complete in many respects, and some of the code here is subject to change...
But I want to point one thing out here --
This API '/api/providers?collection_class=ManageIQ::Providers::EmbeddedAutomationManager' returns a collection of ManageIQ::Providers::EmbeddedAutomationManager providers.

So why set the manager_resource to the first resource (resources[0].href) specifically?

Does this need to be a dropdown that the user can pick a value from during credential creation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No dropdown. The concept is that there will be one embedded Ansible provider (that's
why the first resource specifically). Besides, having a dropdown would go against the idea
of Ansible being embedded, don't you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides, having a dropdown would go against the idea
of Ansible being embedded, don't you think?

Not sure I understand this.
The API returns only embedded Ansible providers...so we could have > 1 embedded Ansible providers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The point of embedded ansible is that it's embedded in the appliance, so there's only one, ever.

The API returns a list because it's the API and we want it consistent with all the other API calls, but AFAIK there can't ever be more than one embedded ansible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK there can't ever be more than one embedded ansible.

And that's the clarification I was looking for. Thanks @himdel

Copy link
Contributor

@AparnaKarve AparnaKarve Mar 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mzazrivec Agreed... I was thinking out loud here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jameswnl manager_ref seems to be a vital piece of information that drives the Credentials and the Repositories UI.
Would it be possible to send it as part of the OPTIONS API call (API.options('/api/authentications')) ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if I understand the needs of manager_ref being exposed here.

manager_ref only would be needed when communicating to Tower. And methods invoking on the model object (various Credential) would not need that. And the model object will know it's manager_ref when interacting with Tower.

Copy link
Contributor

@AparnaKarve AparnaKarve Mar 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jameswnl I actually meant the manager resource represented by resource_id in the authentications table (or the ems id of the embedded Ansible Provider). Not manager_ref... my apologies for the confusion.

We do need the manager resource to successfully create a queued task for adding a credential.
If you could send us the manager resource in the API.options('/api/authentications') call itself that would save us a GET call. Totally worth it, if that's doable.

It seems only logical to send the manager resource, which is a vital piece of information to the Credentials UI without which there would be no Credentials.
Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that the convention for OPTIONS call is to return usage of this API endpoint, i.e. like a help page. So it normally won't include info about any specific resource. @abellotti would be the best to answer this.

}

// FIXME: this should go away once https://github.com/ManageIQ/manageiq/pull/14483 is merged
// For creation, we need to send json like: {"kind": "scm", "type": "ManageIQ::Providers::EmbeddedAnsible::AutomationManager::ScmCredential"}
function addCredentialKind() {
vm.credentialModel.kind = vm.credential_options[vm.credentialModel.type].type;
}

init();
}]);
42 changes: 42 additions & 0 deletions app/controllers/ansible_credential_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class AnsibleCredentialController < ApplicationController
include Mixins::GenericShowMixin

menu_section :ansible
toolbar :ansible_credential

def self.display_methods
%w(repositories)
Expand All @@ -23,10 +24,51 @@ def display_repositories
nested_list("repository", ManageIQ::Providers::EmbeddedAutomationManager::ConfigurationScriptSource)
end

def button
if params[:pressed] == 'embedded_automation_manager_credentials_add'
javascript_redirect :action => 'new'
elsif params[:pressed] == 'embedded_automation_manager_credentials_edit'
javascript_redirect :action => 'edit', :id => from_cid(params[:miq_grid_checks])
elsif params[:pressed] == 'embedded_automation_manager_credentials_delete'
delete_credentials
end
end

def new
assert_privileges('embedded_automation_manager_credentials_add')
drop_breadcrumb(:name => _("Add a new Credential"), :url => "/ansible_credential/new")
@in_a_form = true
@id = 'new'
end

def edit
assert_privileges('embedded_automation_manager_credentials_edit')
auth = ManageIQ::Providers::EmbeddedAutomationManager::Authentication.find(params[:id].to_i)
drop_breadcrumb(:name => _("Edit a Credential \"%{name}\"") % {:name => auth.name},
:url => "/ansible_credential/edit/#{params[:id]}")
@in_a_form = true
@id = params[:id]
end

private

def textual_group_list
[%i(properties relationships options)]
end
helper_method :textual_group_list

def delete_credentials
checked = find_checked_items
checked[0] = params[:id] if checked.blank? && params[:id]
ManageIQ::Providers::EmbeddedAutomationManager::Authentication.where(:id => checked).each do |auth|
begin
auth.delete_in_provider_queue
add_flash(_("Deletion of Credential \"%{name}\" was successfully initiated.") % {:name => auth.name})
rescue => ex
add_flash(_("Unable to delete Credential \"%{name}\": %{details}") % {:name => auth.name, :details => ex}, :error)
end
end
session[:flash_msgs] = @flash_array
javascript_redirect :action => 'show_list'
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class ApplicationHelper::Toolbar::AnsibleCredentialCenter < ApplicationHelper::Toolbar::Basic
button_group('ansible_repository', [
select(
:ansible_credential_configuration,
'fa fa-cog fa-lg',
t = N_('Configuration'),
t,
:items => [
button(
:embedded_automation_manager_credentials_edit,
'pficon pficon-edit fa-lg',
t = N_('Edit this Credential'),
t,
:url => "/edit"),
button(
:embedded_automation_manager_credentials_delete,
'pficon pficon-delete fa-lg',
t = N_('Remove this Credential'),
t,
:url_parms => "&refresh=y",
:confirm => N_("Warning: The selected Credential will be permanently removed!")),
]
),
])
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
class ApplicationHelper::Toolbar::AnsibleCredentialsCenter < ApplicationHelper::Toolbar::Basic
button_group('embedded_ansible_credentials', [
select(
:embedded_ansible_credentials_configuration,
'fa fa-cog fa-lg',
t = N_('Configuration'),
t,
:items => [
button(
:embedded_automation_manager_credentials_add,
'pficon pficon-edit fa-lg',
t = N_('Add New Credential'),
t,
:url_parms => "new_div"),
button(
:embedded_automation_manager_credentials_edit,
'pficon pficon-edit fa-lg',
t = N_('Edit this Credential'),
t,
:enabled => false,
:onwhen => "1",
:url_parms => "edit_div"),
button(
:embedded_automation_manager_credentials_delete,
'pficon pficon-delete fa-lg',
t = N_('Remove selected Credentials'),
t,
:url_parms => "delete_div",
:enabled => false,
:onwhen => "1+",
:confirm => N_("Warning: The selected Credentials will be permanently removed!")),
]
)
])
end
47 changes: 47 additions & 0 deletions app/views/ansible_credential/_credential_form.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
%br
.form-horizontal
%form#form_div{'name' => 'angularForm',
'ng-controller' => 'ansibleCredentialsFormController as vm',
'ng-show' => 'vm.afterGet',
'ng-cloak' => '',
'miq-form' => true,
'model' => 'vm.credentialModel',
'model-copy' => 'vm.modelCopy',
'novalidate' => true}
= render :partial => "layouts/flash_msg"
.form-group{"ng-class" => "{'has-error': angularForm.name.$invalid}"}
%label.col-md-2.control-label
= _('Name')
.col-md-8
%input.form-control{:type => "text",
:name => "name",
"id" => "name",
'ng-model' => "vm.credentialModel.name",
:maxlength => MAX_NAME_LEN,
:required => true,
:checkchange => true,
"auto-focus" => ""}
%span.help-block{"ng-show" => "angularForm.name.$error.required"}
= _("Required")
.form-group
%label.col-md-2.control-label
= _('Credential type')
- if @id == 'new' # we don't allow changing auth. type when editing a resource
.col-md-8
%select{'ng-model' => 'vm.credentialModel.type',
'ng-options' => 'cred.value as cred.label for cred in vm.select_options',
'required' => 'true',
'pf-select' => 'true'}
- else
.col-md-8
{{ vm.credential_options[vm.credentialModel.type].label }}

%ansible-credential-options{:model => 'vm.credentialModel',
:options => 'vm.credential_options',
:type => 'vm.credentialModel.type'}

= render :partial => 'layouts/angular/generic_form_buttons'

:javascript
ManageIQ.angular.app.value('credentialId', '#{@id}');
miq_bootstrap('#form_div');
2 changes: 2 additions & 0 deletions app/views/ansible_credential/edit.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#main_div
= render :partial => 'credential_form', :locals => {:newRecord => false}
2 changes: 2 additions & 0 deletions app/views/ansible_credential/new.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#main_div
= render :partial => 'credential_form', :locals => {:newRecord => true}
6 changes: 5 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2010,11 +2010,15 @@
:get => %w(
download_data
download_summary_pdf
edit
new
show
show_list
),
:post => %w(
show_list)
button
show_list
)
},

:ansible_playbook => {
Expand Down