foreman_remote_execution 0.0.10 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -13
- data/README.md +14 -21
- data/app/assets/javascripts/template_invocation.js +29 -0
- data/app/controllers/job_invocations_controller.rb +13 -0
- data/app/controllers/job_templates_controller.rb +1 -1
- data/app/helpers/remote_execution_helper.rb +11 -4
- data/app/lib/actions/remote_execution/run_host_job.rb +19 -3
- data/app/lib/proxy_api/remote_execution_ssh.rb +14 -0
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +27 -1
- data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +26 -0
- data/app/models/host_status/execution_status.rb +49 -0
- data/app/models/job_invocation_composer.rb +10 -2
- data/app/models/job_template.rb +1 -0
- data/app/models/setting/remote_execution.rb +4 -1
- data/app/models/targeting.rb +5 -3
- data/app/views/job_invocations/_form.html.erb +4 -1
- data/app/views/job_invocations/_preview_hosts_list.html.erb +19 -0
- data/app/views/job_invocations/_preview_hosts_modal.html.erb +13 -0
- data/app/views/job_invocations/new.html.erb +1 -5
- data/app/views/job_invocations/show.js.erb +1 -0
- data/app/views/unattended/snippets/_remote_execution_ssh_keys.erb +18 -0
- data/config/routes.rb +1 -0
- data/db/migrate/20151013135415_add_pub_key_to_smart_proxy.rb +5 -0
- data/db/seeds.d/80-provision_templates.rb +21 -0
- data/lib/foreman_remote_execution/engine.rb +7 -6
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/functional/api/v2/job_invocations_controller_test.rb +2 -2
- data/test/unit/concerns/host_extensions_test.rb +29 -0
- data/test/unit/job_invocation_composer_test.rb +8 -3
- data/test/unit/job_template_test.rb +13 -0
- data/test/unit/targeting_test.rb +1 -1
- metadata +26 -19
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
ZGY2YjZkZGY4MzliZmMxMzg5Y2U2Yjg0NGRiNzQ5ZjZkZjA4YmIzYg==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9a83d62de427a930126dbdac52051e2a6ca6e0cc
|
4
|
+
data.tar.gz: a304bc71914618eb42fa1648b4f6ce3f2f31fceb
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
ZTgyOGFlYzc1ZDA5NzVmNzM1MmM5NjQ3YWU2ZmZjZDA2YTE0N2ZmNDQwMzNj
|
11
|
-
OGRiMmUxOTVjNzMwOTNhZDIwNGFkZjdiZWM5NjI0MWI5YjNiYTY=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
YzgyYzRmYTExZDlmNDczZGExMWQzN2UyM2FlMTYzY2RmODIyMmI3MjNkZWJj
|
14
|
-
OTY4YTIyNzg2ODI3NDgzYjk2ODAwNzcwMmYxNjAwMmQwMDlmMWFmNGUzOGM0
|
15
|
-
MDY1YjdkZjY2OWE3MjcwYzBlNTU2ZTNjMGE3MWZjN2VlMzNiYmQ=
|
6
|
+
metadata.gz: 9afcf6f187db3707ed4842ad877d8273c3bead70b25f0fb078dacc2888d0218cbed4d41c2b2e85e874cc88522946488915ff123883e12f507638c9ef1581f9ea
|
7
|
+
data.tar.gz: 8a38221109dea89d1b5c91f84179727e563b2f880a948b83029adda2df3b6ffddd058dcc5e5651cb32d06bc1c6ada3bc6ddcc6b15e83fb8c3f9db71296c8d371
|
data/README.md
CHANGED
@@ -5,36 +5,29 @@
|
|
5
5
|
|
6
6
|
# Foreman Remote Execution
|
7
7
|
|
8
|
-
A plugin bringing remote execution to the Foreman, completing the config
|
8
|
+
A plugin bringing remote execution to the Foreman, completing the config
|
9
9
|
management functionality with remote management functionality.
|
10
10
|
|
11
|
-
|
11
|
+
* Website: [theforeman.org](http://theforeman.org)
|
12
|
+
* Support: [Foreman support](http://theforeman.org/support.html)
|
12
13
|
|
13
|
-
|
14
|
-
for how to install Foreman plugins
|
14
|
+
## Features
|
15
15
|
|
16
|
-
|
16
|
+
* Visualize remote execution job process live
|
17
|
+

|
18
|
+
* Schedule or run jobs on hosts
|
19
|
+

|
20
|
+
* Create templates to customize your jobs
|
21
|
+

|
17
22
|
|
18
|
-
|
23
|
+
## Installation and usage
|
19
24
|
|
20
|
-
|
21
|
-
|
22
|
-
0. ``cd doc``
|
23
|
-
|
24
|
-
0. ``mkdir .bin`` # only first time
|
25
|
-
|
26
|
-
1. ``bundle install``
|
27
|
-
|
28
|
-
2. ``bundle exec rake plantuml_install`` to install prerequisites
|
29
|
-
|
30
|
-
3. ``bundle exec jekyll serve`` to see the rendered changes locally
|
31
|
-
|
32
|
-
4. ``bundle exec rake publish`` to publish the changes to Github pages
|
25
|
+
Check the Foreman manual [remote execution section](http://theforeman.org/plugins/foreman_remote_execution/)
|
33
26
|
|
34
27
|
## Links
|
35
28
|
|
36
|
-
* [
|
37
|
-
* [
|
29
|
+
* [Design document](http://theforeman.github.io/foreman_remote_execution/design/)
|
30
|
+
* [Issue tracker](http://projects.theforeman.org/projects/foreman_remote_execution)
|
38
31
|
|
39
32
|
## Contributing
|
40
33
|
|
@@ -24,6 +24,33 @@ function refresh_search_query(value){
|
|
24
24
|
$('textarea#targeting_search_query').val($('span#bookmark_query_map span#bookmark-' + id).data('query'));
|
25
25
|
}
|
26
26
|
|
27
|
+
function show_preview_hosts_modal() {
|
28
|
+
var modal_window = $('#previewHostsModal');
|
29
|
+
|
30
|
+
var form = $('form#job_invocation_form');
|
31
|
+
var data = form.serializeArray();
|
32
|
+
|
33
|
+
request = $.ajax({
|
34
|
+
data: data,
|
35
|
+
type: 'GET',
|
36
|
+
url: modal_window.attr('data-url'),
|
37
|
+
success: function(request) {
|
38
|
+
modal_window.find('.modal-body').html(request);
|
39
|
+
},
|
40
|
+
complete: function() {
|
41
|
+
modal_window.modal({'show': true});
|
42
|
+
modal_window.find('a[rel="popover-modal"]').popover();
|
43
|
+
}
|
44
|
+
});
|
45
|
+
}
|
46
|
+
|
47
|
+
function close_preview_hosts_modal() {
|
48
|
+
var modal_window = $('#previewHostsModal');
|
49
|
+
modal_window.modal('hide');
|
50
|
+
modal_window.removeData();
|
51
|
+
modal_window.find('.modal-body').html('');
|
52
|
+
}
|
53
|
+
|
27
54
|
function job_invocation_form_binds() {
|
28
55
|
$('input.job_template_selector').on('click', function () {
|
29
56
|
parent_fieldset = $(this).closest('fieldset');
|
@@ -40,6 +67,8 @@ function job_invocation_form_binds() {
|
|
40
67
|
|
41
68
|
$('button#refresh_execution_form').on('click', refresh_execution_form);
|
42
69
|
|
70
|
+
$('button#preview_hosts').on('click', show_preview_hosts_modal);
|
71
|
+
|
43
72
|
$('textarea#targeting_search_query').on('change', refresh_execution_form);
|
44
73
|
|
45
74
|
$('select#targeting_bookmark_id').on('change', refresh_search_query);
|
@@ -56,12 +56,25 @@ class JobInvocationsController < ApplicationController
|
|
56
56
|
@composer = JobInvocationComposer.new.compose_from_params(params)
|
57
57
|
end
|
58
58
|
|
59
|
+
def preview_hosts
|
60
|
+
composer = JobInvocationComposer.new.compose_from_params(params)
|
61
|
+
|
62
|
+
@hosts = composer.targeted_hosts.limit(Setting[:entries_per_page])
|
63
|
+
@additional = composer.targeted_hosts.count - Setting[:entries_per_page]
|
64
|
+
@dynamic = composer.targeting.dynamic?
|
65
|
+
@query = composer.displayed_search_query
|
66
|
+
|
67
|
+
render :partial => 'job_invocations/preview_hosts_list'
|
68
|
+
end
|
69
|
+
|
59
70
|
private
|
60
71
|
|
61
72
|
def action_permission
|
62
73
|
case params[:action]
|
63
74
|
when 'rerun'
|
64
75
|
'create'
|
76
|
+
when 'preview_hosts'
|
77
|
+
'create'
|
65
78
|
else
|
66
79
|
super
|
67
80
|
end
|
@@ -11,7 +11,7 @@ class JobTemplatesController < ::TemplatesController
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def preview
|
14
|
-
base = Host.authorized(:view_hosts)
|
14
|
+
base = Host.authorized(:view_hosts, Host)
|
15
15
|
host = params[:preview_host_id].present? ? base.find(params[:preview_host_id]) : base.first
|
16
16
|
@template.template = params[:template]
|
17
17
|
renderer = InputTemplateRenderer.new(@template, host)
|
@@ -11,7 +11,7 @@ module RemoteExecutionHelper
|
|
11
11
|
options = { :class => 'statistics-pie small', :expandable => true, :border => 0, :show_title => true }
|
12
12
|
|
13
13
|
if (bulk_task = invocation.last_task)
|
14
|
-
failed_tasks = bulk_task.sub_tasks.select { |sub_task|
|
14
|
+
failed_tasks = bulk_task.sub_tasks.select { |sub_task| task_failed? sub_task }
|
15
15
|
cancelled_tasks, failed_tasks = failed_tasks.partition { |task| task_cancelled? task }
|
16
16
|
success = bulk_task.output['success_count'] || 0
|
17
17
|
cancelled = cancelled_tasks.length
|
@@ -42,6 +42,10 @@ module RemoteExecutionHelper
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
+
def task_failed?(task)
|
46
|
+
%w(warning error).include? task.result
|
47
|
+
end
|
48
|
+
|
45
49
|
def task_cancelled?(task)
|
46
50
|
task.execution_plan.errors.map(&:exception).any? { |exception| exception.class == ::ForemanTasks::Task::TaskCancelledException }
|
47
51
|
end
|
@@ -77,7 +81,7 @@ module RemoteExecutionHelper
|
|
77
81
|
if task.nil?
|
78
82
|
[]
|
79
83
|
else
|
80
|
-
[display_link_if_authorized(_("Details"), hash_for_template_invocation_path(:id => task).merge(:auth_object => host, :permission => :
|
84
|
+
[display_link_if_authorized(_("Details"), hash_for_template_invocation_path(:id => task).merge(:auth_object => host, :permission => :view_hosts))]
|
81
85
|
end
|
82
86
|
end
|
83
87
|
|
@@ -86,17 +90,19 @@ module RemoteExecutionHelper
|
|
86
90
|
template_invocation.nil? ? _('N/A') : _(RemoteExecutionProvider.provider_for(template_invocation.template.provider_type))
|
87
91
|
end
|
88
92
|
|
93
|
+
# rubocop:disable Metrics/AbcSize
|
89
94
|
def job_invocation_task_buttons(task)
|
90
95
|
buttons = []
|
91
96
|
buttons << link_to(_('Refresh'), {}, :class => 'btn btn-default', :title => _('Refresh this page'))
|
92
|
-
if authorized_for(
|
97
|
+
if authorized_for(hash_for_new_job_invocation_path)
|
93
98
|
buttons << link_to(_("Rerun"), rerun_job_invocation_path(:id => task.locks.where(:resource_type => 'JobInvocation').first.resource),
|
94
99
|
:class => "btn btn-default",
|
95
100
|
:title => _('Rerun the job'))
|
96
101
|
end
|
97
|
-
if authorized_for(
|
102
|
+
if authorized_for(hash_for_new_job_invocation_path)
|
98
103
|
buttons << link_to(_("Rerun failed"), rerun_job_invocation_path(:id => task.locks.where(:resource_type => 'JobInvocation').first.resource, :failed_only => 1),
|
99
104
|
:class => "btn btn-default",
|
105
|
+
:disabled => !task.sub_tasks.any? { |sub_task| task_failed?(sub_task) },
|
100
106
|
:title => _('Rerun on failed hosts'))
|
101
107
|
end
|
102
108
|
if authorized_for(:permission => :view_foreman_tasks, :auth_object => task)
|
@@ -113,6 +119,7 @@ module RemoteExecutionHelper
|
|
113
119
|
end
|
114
120
|
return buttons
|
115
121
|
end
|
122
|
+
# rubocop:enable Metrics/AbcSize
|
116
123
|
|
117
124
|
def template_invocation_task_buttons(task)
|
118
125
|
buttons = []
|
@@ -6,8 +6,6 @@ module Actions
|
|
6
6
|
:link
|
7
7
|
end
|
8
8
|
|
9
|
-
include ::Dynflow::Action::Cancellable
|
10
|
-
|
11
9
|
def plan(job_invocation, host, template_invocation, proxy, connection_options = {})
|
12
10
|
action_subject(host, :job_name => job_invocation.job_name)
|
13
11
|
hostname = find_ip_or_hostname(host)
|
@@ -27,7 +25,16 @@ module Actions
|
|
27
25
|
link!(job_invocation)
|
28
26
|
link!(template_invocation)
|
29
27
|
|
30
|
-
|
28
|
+
provider = template_invocation.template.provider_type.to_s
|
29
|
+
plan_action(RunProxyCommand, proxy, hostname, script, { :connection_options => connection_options }.merge(provider_settings(provider, host)))
|
30
|
+
plan_self
|
31
|
+
end
|
32
|
+
|
33
|
+
def finalize(*args)
|
34
|
+
host = Host.find(input[:host][:id])
|
35
|
+
host.refresh_statuses
|
36
|
+
rescue => e
|
37
|
+
Foreman::Logging.exception "Could not update execution status for #{input[:host][:name]}", e
|
31
38
|
end
|
32
39
|
|
33
40
|
def humanized_output
|
@@ -55,6 +62,15 @@ module Actions
|
|
55
62
|
|
56
63
|
return host.fqdn
|
57
64
|
end
|
65
|
+
|
66
|
+
def provider_settings(provider, host)
|
67
|
+
case provider
|
68
|
+
when 'Ssh'
|
69
|
+
{ :ssh_user => host.params['remote_execution_ssh_user'] || Setting[:remote_execution_ssh_user] }
|
70
|
+
else
|
71
|
+
{}
|
72
|
+
end
|
73
|
+
end
|
58
74
|
end
|
59
75
|
end
|
60
76
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ::ProxyAPI
|
2
|
+
class RemoteExecutionSSH < ::ProxyAPI::Resource
|
3
|
+
def initialize(args)
|
4
|
+
@url = args[:url] + '/ssh'
|
5
|
+
super args
|
6
|
+
end
|
7
|
+
|
8
|
+
def pubkey
|
9
|
+
get('pubkey').strip
|
10
|
+
rescue => e
|
11
|
+
raise ProxyException.new(url, e, N_('Unable to fetch public key'))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -6,8 +6,30 @@ module ForemanRemoteExecution
|
|
6
6
|
alias_method_chain :build_required_interfaces, :remote_execution
|
7
7
|
alias_method_chain :reload, :remote_execution
|
8
8
|
alias_method_chain :becomes, :remote_execution
|
9
|
+
alias_method_chain :params, :remote_execution
|
9
10
|
|
10
11
|
has_many :targeting_hosts, :dependent => :destroy, :foreign_key => 'host_id'
|
12
|
+
|
13
|
+
has_one :execution_status_object, :class_name => 'HostStatus::ExecutionStatus', :foreign_key => 'host_id'
|
14
|
+
|
15
|
+
scoped_search :in => :execution_status_object, :on => :status, :rename => :'execution_status',
|
16
|
+
:complete_value => { :ok => HostStatus::ExecutionStatus::OK, :error => HostStatus::ExecutionStatus::ERROR }
|
17
|
+
end
|
18
|
+
|
19
|
+
def execution_status(options = {})
|
20
|
+
@execution_status ||= get_status(HostStatus::ExecutionStatus).to_status(options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def execution_status_label(options = {})
|
24
|
+
@execution_status_label ||= get_status(HostStatus::ExecutionStatus).to_label(options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def params_with_remote_execution
|
28
|
+
params = params_without_remote_execution
|
29
|
+
keys = remote_execution_ssh_keys
|
30
|
+
params['remote_execution_ssh_keys'] = keys unless keys.blank?
|
31
|
+
params['remote_execution_ssh_user'] = Setting[:remote_execution_ssh_user] unless params.key?('remote_execution_ssh_user')
|
32
|
+
params
|
11
33
|
end
|
12
34
|
|
13
35
|
def execution_interface
|
@@ -32,13 +54,17 @@ module ForemanRemoteExecution
|
|
32
54
|
proxies
|
33
55
|
end
|
34
56
|
|
57
|
+
def remote_execution_ssh_keys
|
58
|
+
remote_execution_proxies('Ssh').values.flatten.uniq.map { |proxy| proxy.pubkey }.compact.uniq
|
59
|
+
end
|
60
|
+
|
35
61
|
def drop_execution_interface_cache
|
36
62
|
@execution_interface = nil
|
37
63
|
end
|
38
64
|
|
39
65
|
def becomes_with_remote_execution(*args)
|
40
66
|
became = becomes_without_remote_execution(*args)
|
41
|
-
became.drop_execution_interface_cache
|
67
|
+
became.drop_execution_interface_cache if became.respond_to? :drop_execution_interface_cache
|
42
68
|
became
|
43
69
|
end
|
44
70
|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ForemanRemoteExecution
|
2
|
+
module SmartProxyExtensions
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
alias_method_chain :refresh, :remote_execution
|
7
|
+
end
|
8
|
+
|
9
|
+
def pubkey
|
10
|
+
self[:pubkey] || update_pubkey
|
11
|
+
end
|
12
|
+
|
13
|
+
def update_pubkey
|
14
|
+
return unless has_feature?('Ssh')
|
15
|
+
key = ::ProxyAPI::RemoteExecutionSSH.new(:url => url).pubkey
|
16
|
+
self.update_attribute(:pubkey, key) if key
|
17
|
+
key
|
18
|
+
end
|
19
|
+
|
20
|
+
def refresh_with_remote_execution
|
21
|
+
errors = refresh_without_remote_execution
|
22
|
+
update_pubkey
|
23
|
+
errors
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class HostStatus::ExecutionStatus < HostStatus::Status
|
2
|
+
OK = 0
|
3
|
+
ERROR = 1
|
4
|
+
|
5
|
+
def relevant?
|
6
|
+
execution_tasks.present?
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_status(options = {})
|
10
|
+
if last_stopped_task.nil? || last_stopped_task.result == 'success'
|
11
|
+
OK
|
12
|
+
else
|
13
|
+
ERROR
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_global(options = {})
|
18
|
+
if to_status(options) == ERROR
|
19
|
+
return HostStatus::Global::ERROR
|
20
|
+
else
|
21
|
+
return HostStatus::Global::OK
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.status_name
|
26
|
+
N_('Execution')
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_label(options = {})
|
30
|
+
case to_status(options)
|
31
|
+
when OK
|
32
|
+
execution_tasks.present? ? N_('Last execution succeeded') : N_('No execution finished yet')
|
33
|
+
when ERROR
|
34
|
+
N_('Last execution failed')
|
35
|
+
else
|
36
|
+
N_('Unknown execution status')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def last_stopped_task
|
43
|
+
@last_stopped_task ||= execution_tasks.order(:started_at).where(:state => 'stopped').last
|
44
|
+
end
|
45
|
+
|
46
|
+
def execution_tasks
|
47
|
+
ForemanTasks::Task::DynflowTask.for_action(Actions::RemoteExecution::RunHostJob).for_resource(host)
|
48
|
+
end
|
49
|
+
end
|
@@ -118,8 +118,16 @@ class JobInvocationComposer
|
|
118
118
|
Bookmark.authorized(:view_bookmarks).my_bookmarks.where(:controller => ['hosts', 'dashboard'])
|
119
119
|
end
|
120
120
|
|
121
|
+
def targeted_hosts
|
122
|
+
if displayed_search_query.blank?
|
123
|
+
Host.where('1 = 0')
|
124
|
+
else
|
125
|
+
Host.authorized(Targeting::RESOLVE_PERMISSION, Host).search_for(displayed_search_query)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
121
129
|
def targeted_hosts_count
|
122
|
-
|
130
|
+
targeted_hosts.count
|
123
131
|
rescue
|
124
132
|
0
|
125
133
|
end
|
@@ -232,6 +240,6 @@ class JobInvocationComposer
|
|
232
240
|
end
|
233
241
|
|
234
242
|
def validate_host_ids(ids)
|
235
|
-
Host.authorized(
|
243
|
+
Host.authorized(Targeting::RESOLVE_PERMISSION, Host).where(:id => ids).pluck(:id)
|
236
244
|
end
|
237
245
|
end
|
data/app/models/job_template.rb
CHANGED
@@ -13,7 +13,10 @@ class Setting::RemoteExecution < Setting
|
|
13
13
|
N_("Search for remote execution proxy outside of the proxies assigned to the host. " +
|
14
14
|
"If locations or organizations are enabled, the search will be limited to the host's " +
|
15
15
|
"organization or location."),
|
16
|
-
|
16
|
+
true),
|
17
|
+
self.set('remote_execution_ssh_user',
|
18
|
+
N_("Default user to use for SSH. You may override per host by setting a parameter called remote_execution_ssh_user."),
|
19
|
+
'root'),
|
17
20
|
].each { |s| self.create! s.update(:category => "Setting::RemoteExecution") }
|
18
21
|
end
|
19
22
|
|
data/app/models/targeting.rb
CHANGED
@@ -3,6 +3,7 @@ class Targeting < ActiveRecord::Base
|
|
3
3
|
STATIC_TYPE = 'static_query'
|
4
4
|
DYNAMIC_TYPE = 'dynamic_query'
|
5
5
|
TYPES = { STATIC_TYPE => N_('Static Query'), DYNAMIC_TYPE => N_('Dynamic Query') }
|
6
|
+
RESOLVE_PERMISSION = :view_hosts
|
6
7
|
|
7
8
|
belongs_to :user
|
8
9
|
belongs_to :bookmark
|
@@ -26,7 +27,7 @@ class Targeting < ActiveRecord::Base
|
|
26
27
|
self.search_query = bookmark.query if dynamic? && bookmark.present?
|
27
28
|
self.touch(:resolved_at)
|
28
29
|
self.save!
|
29
|
-
self.hosts = User.as(user.login) { Host.authorized(
|
30
|
+
self.hosts = User.as(user.login) { Host.authorized(RESOLVE_PERMISSION, Host).search_for(search_query) }
|
30
31
|
end
|
31
32
|
|
32
33
|
def dynamic?
|
@@ -49,8 +50,9 @@ class Targeting < ActiveRecord::Base
|
|
49
50
|
private
|
50
51
|
|
51
52
|
def bookmark_or_query_is_present
|
52
|
-
if bookmark.
|
53
|
-
errors.add :
|
53
|
+
if bookmark.blank? && search_query.blank?
|
54
|
+
errors.add :bookmark_id, _('Must select a bookmark or enter a search query')
|
55
|
+
errors.add :search_query, _('Must select a bookmark or enter a search query')
|
54
56
|
end
|
55
57
|
end
|
56
58
|
|
@@ -17,6 +17,9 @@
|
|
17
17
|
<%= button_tag(:type => 'button', :class => 'btn btn-default btn-sm', :title => _("Refresh"), :id => 'refresh_execution_form') do %>
|
18
18
|
<%= icon_text('refresh') %>
|
19
19
|
<% end %>
|
20
|
+
<%= button_tag(:type => 'button', :class => 'btn btn-default btn-sm', :title => _("Preview"), :id => 'preview_hosts') do %>
|
21
|
+
<%= icon_text('eye-open') %>
|
22
|
+
<% end %>
|
20
23
|
</div>
|
21
24
|
</div>
|
22
25
|
|
@@ -80,6 +83,6 @@
|
|
80
83
|
<%= text_f f, :start_before, :label => _('Start before') %>
|
81
84
|
</fieldset>
|
82
85
|
</div>
|
83
|
-
|
86
|
+
<%= render :partial => 'preview_hosts_modal' %>
|
84
87
|
<%= submit_or_cancel f %>
|
85
88
|
<% end %>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<% if @dynamic -%>
|
2
|
+
<div class="alert alert-info alert-dismissable">
|
3
|
+
<p><%= _("The final host list may change because the selected query is dynamic. It will be rerun during execution.") %></p>
|
4
|
+
</div>
|
5
|
+
<% end -%>
|
6
|
+
|
7
|
+
<% if @hosts.any? -%>
|
8
|
+
<ul>
|
9
|
+
<% @hosts.each do |host| -%>
|
10
|
+
<li><%= link_to h(host.name), host_path(host), :target => '_blank' %></li>
|
11
|
+
<% end -%>
|
12
|
+
|
13
|
+
<% if @additional > 0 -%>
|
14
|
+
<li><%= link_to(_("...and %{count} more" % {:count => @additional}), hosts_path(:search => @query, :page => 2), :target => '_blank') %></li>
|
15
|
+
<% end -%>
|
16
|
+
</ul>
|
17
|
+
<% else -%>
|
18
|
+
<h3><%= _("No hosts found.") %></h3>
|
19
|
+
<% end -%>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<!-- modal window -->
|
2
|
+
<div class="modal fade" id="previewHostsModal" role="dialog" aria-hidden="true" data-url="<%= preview_hosts_job_invocations_path %>">
|
3
|
+
<div class="modal-dialog">
|
4
|
+
<div class="modal-content">
|
5
|
+
<div class="modal-header">
|
6
|
+
<button type="button" class="close" onclick="close_preview_hosts_modal(); return false;"><span aria-hidden="true">×</span><span class="sr-only"><%= _('Close') %></span></button>
|
7
|
+
<h4 class="modal-title">Preview Hosts</h4>
|
8
|
+
</div>
|
9
|
+
<div class="modal-body" style="overflow-y: auto; max-height: 500px;">
|
10
|
+
</div>
|
11
|
+
</div>
|
12
|
+
</div>
|
13
|
+
</div>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<%#
|
2
|
+
kind: snippet
|
3
|
+
name: remote_execution_ssh_keys
|
4
|
+
%>
|
5
|
+
|
6
|
+
<% if @host.params['remote_execution_ssh_keys'].present? %>
|
7
|
+
<% ssh_user = @host.params['remote_execution_ssh_user'] || 'root' %>
|
8
|
+
<% ssh_path = "~#{ssh_user}/.ssh" %>
|
9
|
+
|
10
|
+
mkdir -p <%= ssh_path %>
|
11
|
+
|
12
|
+
cat << EOF >> <%= ssh_path %>/authorized_keys
|
13
|
+
<%= @host.params['remote_execution_ssh_keys'].join("\n") %>
|
14
|
+
EOF
|
15
|
+
|
16
|
+
chmod 700 <%= ssh_path %>
|
17
|
+
chmod 600 <%= ssh_path %>/authorized_keys
|
18
|
+
<% end %>
|
data/config/routes.rb
CHANGED
@@ -0,0 +1,21 @@
|
|
1
|
+
ProvisioningTemplate.without_auditing do
|
2
|
+
templates = [{:name => "remote_execution_ssh_keys", :source => "snippets/_remote_execution_ssh_keys.erb", :snippet => true}]
|
3
|
+
|
4
|
+
defaults = {:vendor => "Remote Execution", :default => true, :locked => true}
|
5
|
+
|
6
|
+
templates.each do |template|
|
7
|
+
next if ProvisioningTemplate.find_by_name(template[:name])
|
8
|
+
|
9
|
+
template.merge!(defaults)
|
10
|
+
|
11
|
+
t= ProvisioningTemplate.create({
|
12
|
+
:snippet => false,
|
13
|
+
:template => File.read(File.join("#{ForemanRemoteExecution::Engine.root}/app/views/unattended", template.delete(:source)))
|
14
|
+
}.merge(template))
|
15
|
+
|
16
|
+
t.organizations << (Organization.all - t.organizations)
|
17
|
+
t.locations << (Location.all - t.locations)
|
18
|
+
|
19
|
+
raise "Unable to create template #{t.name}: #{format_errors t}" if t.nil? || t.errors.any?
|
20
|
+
end
|
21
|
+
end
|
@@ -27,7 +27,7 @@ module ForemanRemoteExecution
|
|
27
27
|
|
28
28
|
initializer 'foreman_remote_execution.register_plugin', after: :finisher_hook do |_app|
|
29
29
|
Foreman::Plugin.register :foreman_remote_execution do
|
30
|
-
requires_foreman '>= 1.
|
30
|
+
requires_foreman '>= 1.10'
|
31
31
|
|
32
32
|
apipie_documented_controllers ["#{ForemanRemoteExecution::Engine.root}/app/controllers/api/v2/*.rb"]
|
33
33
|
|
@@ -42,7 +42,7 @@ module ForemanRemoteExecution
|
|
42
42
|
permission :destroy_job_templates, { :job_templates => [:destroy],
|
43
43
|
:'api/v2/job_templates' => [:destroy] }, :resource_type => 'JobTemplate'
|
44
44
|
permission :lock_job_templates, { :job_templates => [:lock, :unlock] }, :resource_type => 'JobTemplate'
|
45
|
-
permission :create_job_invocations, { :job_invocations => [:new, :create, :refresh, :rerun],
|
45
|
+
permission :create_job_invocations, { :job_invocations => [:new, :create, :refresh, :rerun, :preview_hosts],
|
46
46
|
'api/v2/job_invocations' => [:create] }, :resource_type => 'JobInvocation'
|
47
47
|
permission :view_job_invocations, { :job_invocations => [:index, :show, :auto_complete_search], :template_invocations => [:show],
|
48
48
|
'api/v2/job_invocations' => [:index, :show] }, :resource_type => 'JobInvocation'
|
@@ -64,6 +64,7 @@ module ForemanRemoteExecution
|
|
64
64
|
parent: :monitor_menu,
|
65
65
|
after: :audits
|
66
66
|
|
67
|
+
register_custom_status HostStatus::ExecutionStatus
|
67
68
|
# add dashboard widget
|
68
69
|
# widget 'foreman_remote_execution_widget', name: N_('Foreman plugin template widget'), sizex: 4, sizey: 1
|
69
70
|
end
|
@@ -103,10 +104,9 @@ module ForemanRemoteExecution
|
|
103
104
|
(Taxonomy.descendants + [Taxonomy]).each { |klass| klass.send(:include, ForemanRemoteExecution::TaxonomyExtensions) }
|
104
105
|
|
105
106
|
User.send(:include, ForemanRemoteExecution::UserExtensions)
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
end
|
107
|
+
|
108
|
+
Host::Managed.send(:include, ForemanRemoteExecution::HostExtensions)
|
109
|
+
Host::Managed.send(:include, ForemanTasks::Concerns::HostActionSubject)
|
110
110
|
|
111
111
|
(Nic::Base.descendants + [Nic::Base]).each do |klass|
|
112
112
|
klass.send(:include, ForemanRemoteExecution::NicExtensions)
|
@@ -115,6 +115,7 @@ module ForemanRemoteExecution
|
|
115
115
|
Bookmark.send(:include, ForemanRemoteExecution::BookmarkExtensions)
|
116
116
|
HostsHelper.send(:include, ForemanRemoteExecution::HostsHelperExtensions)
|
117
117
|
|
118
|
+
SmartProxy.send(:include, ForemanRemoteExecution::SmartProxyExtensions)
|
118
119
|
Subnet.send(:include, ForemanRemoteExecution::SubnetExtensions)
|
119
120
|
|
120
121
|
# We need to explicitly force to load the Task model due to Rails loader
|
@@ -29,7 +29,7 @@ module Api
|
|
29
29
|
|
30
30
|
invocation = ActiveSupport::JSON.decode(@response.body)
|
31
31
|
assert_equal attrs[:job_name], invocation['job_name']
|
32
|
-
assert_response
|
32
|
+
assert_response :success
|
33
33
|
end
|
34
34
|
|
35
35
|
test "should create valid with template_id" do
|
@@ -38,7 +38,7 @@ module Api
|
|
38
38
|
|
39
39
|
invocation = ActiveSupport::JSON.decode(@response.body)
|
40
40
|
assert_equal attrs[:job_name], invocation['job_name']
|
41
|
-
assert_response
|
41
|
+
assert_response :success
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
@@ -9,6 +9,35 @@ describe ForemanRemoteExecution::HostExtensions do
|
|
9
9
|
|
10
10
|
after { User.current = nil }
|
11
11
|
|
12
|
+
context 'ssh user' do
|
13
|
+
let(:host) { FactoryGirl.build(:host, :with_execution) }
|
14
|
+
let(:sshkey) { 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQ foo@example.com' }
|
15
|
+
|
16
|
+
before do
|
17
|
+
SmartProxy.any_instance.stubs(:pubkey).returns(sshkey)
|
18
|
+
Setting[:remote_execution_ssh_user] = 'root'
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'has ssh user in the parameters' do
|
22
|
+
host.params['remote_execution_ssh_user'].must_equal Setting[:remote_execution_ssh_user]
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'can override ssh user' do
|
26
|
+
host.host_parameters << FactoryGirl.build(:host_parameter, :name => 'remote_execution_ssh_user', :value => 'amy')
|
27
|
+
host.params['remote_execution_ssh_user'].must_equal 'amy'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'ssh keys' do
|
32
|
+
let(:host) { FactoryGirl.build(:host, :with_execution) }
|
33
|
+
let(:sshkey) { 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQ foo@example.com' }
|
34
|
+
|
35
|
+
it 'has ssh keys in the parameters' do
|
36
|
+
SmartProxy.any_instance.stubs(:pubkey).returns(sshkey)
|
37
|
+
host.remote_execution_ssh_keys.must_include sshkey
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
12
41
|
context 'host has multiple nics' do
|
13
42
|
let(:host) { FactoryGirl.build(:host, :with_execution) }
|
14
43
|
|
@@ -317,14 +317,14 @@ describe JobInvocationComposer do
|
|
317
317
|
end
|
318
318
|
|
319
319
|
describe '#targeted_hosts_count' do
|
320
|
+
let(:host) { FactoryGirl.create(:host) }
|
321
|
+
|
320
322
|
it 'obeys authorization' do
|
321
|
-
composer
|
323
|
+
composer.stubs(:displayed_search_query => "name = #{host.name}")
|
322
324
|
Host.expects(:authorized).with(:view_hosts, Host).returns(Host.scoped)
|
323
325
|
composer.targeted_hosts_count
|
324
326
|
end
|
325
327
|
|
326
|
-
let(:host) { FactoryGirl.create(:host) }
|
327
|
-
|
328
328
|
it 'searches hosts based on displayed_search_query' do
|
329
329
|
composer.stubs(:displayed_search_query => "name = #{host.name}")
|
330
330
|
composer.targeted_hosts_count.must_equal 1
|
@@ -334,6 +334,11 @@ describe JobInvocationComposer do
|
|
334
334
|
composer.stubs(:displayed_search_query => "name = ")
|
335
335
|
composer.targeted_hosts_count.must_equal 0
|
336
336
|
end
|
337
|
+
|
338
|
+
it 'returns 0 when no query is present' do
|
339
|
+
composer.stubs(:displayed_search_query => '')
|
340
|
+
composer.targeted_hosts_count.must_equal 0
|
341
|
+
end
|
337
342
|
end
|
338
343
|
|
339
344
|
describe '#template_invocation_input_value_for(input)' do
|
@@ -1,6 +1,19 @@
|
|
1
1
|
require 'test_plugin_helper'
|
2
2
|
|
3
3
|
describe JobTemplate do
|
4
|
+
context 'when creating a template' do
|
5
|
+
let(:job_template) { FactoryGirl.build(:job_template, :job_name => '') }
|
6
|
+
|
7
|
+
it 'needs a job_name' do
|
8
|
+
refute job_template.valid?
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'does not need a job_name if it is a snippet' do
|
12
|
+
job_template.snippet = true
|
13
|
+
assert job_template.valid?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
4
17
|
context 'cloning' do
|
5
18
|
let(:job_template) { FactoryGirl.build(:job_template, :with_input) }
|
6
19
|
|
data/test/unit/targeting_test.rb
CHANGED
@@ -38,7 +38,7 @@ describe Targeting do
|
|
38
38
|
context 'cannot create without search term or bookmark' do
|
39
39
|
before do
|
40
40
|
targeting.targeting_type = Targeting::DYNAMIC_TYPE
|
41
|
-
targeting.search_query =
|
41
|
+
targeting.search_query = ''
|
42
42
|
targeting.bookmark = nil
|
43
43
|
end
|
44
44
|
it { refute_valid targeting }
|
metadata
CHANGED
@@ -1,83 +1,83 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman_remote_execution
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Foreman Remote Execution team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-10
|
11
|
+
date: 2015-11-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: deface
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rails
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - ~>
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: 3.2.8
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - ~>
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 3.2.8
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: foreman-tasks
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - ~>
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: 0.7.6
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - ~>
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: 0.7.6
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rubocop
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rdoc
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- -
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- -
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
description: A plugin bringing remote execution to the Foreman, completing the config
|
@@ -127,10 +127,10 @@ extra_rdoc_files:
|
|
127
127
|
- README.md
|
128
128
|
- LICENSE
|
129
129
|
files:
|
130
|
-
- .gitignore
|
131
|
-
- .rubocop.yml
|
132
|
-
- .rubocop_todo.yml
|
133
|
-
- .tx/config
|
130
|
+
- ".gitignore"
|
131
|
+
- ".rubocop.yml"
|
132
|
+
- ".rubocop_todo.yml"
|
133
|
+
- ".tx/config"
|
134
134
|
- Gemfile
|
135
135
|
- LICENSE
|
136
136
|
- README.md
|
@@ -151,17 +151,20 @@ files:
|
|
151
151
|
- app/lib/actions/remote_execution/run_host_job.rb
|
152
152
|
- app/lib/actions/remote_execution/run_hosts_job.rb
|
153
153
|
- app/lib/actions/remote_execution/run_proxy_command.rb
|
154
|
+
- app/lib/proxy_api/remote_execution_ssh.rb
|
154
155
|
- app/mailers/.gitkeep
|
155
156
|
- app/models/concerns/foreman_remote_execution/bookmark_extensions.rb
|
156
157
|
- app/models/concerns/foreman_remote_execution/errors_flattener.rb
|
157
158
|
- app/models/concerns/foreman_remote_execution/foreman_tasks_task_extensions.rb
|
158
159
|
- app/models/concerns/foreman_remote_execution/host_extensions.rb
|
159
160
|
- app/models/concerns/foreman_remote_execution/nic_extensions.rb
|
161
|
+
- app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb
|
160
162
|
- app/models/concerns/foreman_remote_execution/subnet_extensions.rb
|
161
163
|
- app/models/concerns/foreman_remote_execution/taxonomy_extensions.rb
|
162
164
|
- app/models/concerns/foreman_remote_execution/template_extensions.rb
|
163
165
|
- app/models/concerns/foreman_remote_execution/template_relations.rb
|
164
166
|
- app/models/concerns/foreman_remote_execution/user_extensions.rb
|
167
|
+
- app/models/host_status/execution_status.rb
|
165
168
|
- app/models/input_template_renderer.rb
|
166
169
|
- app/models/job_invocation.rb
|
167
170
|
- app/models/job_invocation_api_composer.rb
|
@@ -197,6 +200,8 @@ files:
|
|
197
200
|
- app/views/job_invocations/_host_actions_td.html.erb
|
198
201
|
- app/views/job_invocations/_host_provider_td.html.erb
|
199
202
|
- app/views/job_invocations/_host_status_td.html.erb
|
203
|
+
- app/views/job_invocations/_preview_hosts_list.html.erb
|
204
|
+
- app/views/job_invocations/_preview_hosts_modal.html.erb
|
200
205
|
- app/views/job_invocations/_tab_hosts.html.erb
|
201
206
|
- app/views/job_invocations/_tab_overview.html.erb
|
202
207
|
- app/views/job_invocations/index.html.erb
|
@@ -219,6 +224,7 @@ files:
|
|
219
224
|
- app/views/templates/puppet_run_once.erb
|
220
225
|
- app/views/templates/run_command.erb
|
221
226
|
- app/views/templates/service_action.erb
|
227
|
+
- app/views/unattended/snippets/_remote_execution_ssh_keys.erb
|
222
228
|
- config/routes.rb
|
223
229
|
- db/migrate/20150612121541_add_job_template_to_template.rb
|
224
230
|
- db/migrate/20150616080015_create_template_input.rb
|
@@ -231,8 +237,10 @@ files:
|
|
231
237
|
- db/migrate/20150827144500_change_targeting_search_query_type.rb
|
232
238
|
- db/migrate/20150827152730_add_options_to_template_input.rb
|
233
239
|
- db/migrate/20150903192731_add_execution_to_interface.rb
|
240
|
+
- db/migrate/20151013135415_add_pub_key_to_smart_proxy.rb
|
234
241
|
- db/seeds.d/60-ssh_proxy_feature.rb
|
235
242
|
- db/seeds.d/70-job_templates.rb
|
243
|
+
- db/seeds.d/80-provision_templates.rb
|
236
244
|
- doc/.bin/.gitkeep
|
237
245
|
- doc/.gitignore
|
238
246
|
- doc/Gemfile
|
@@ -306,12 +314,12 @@ require_paths:
|
|
306
314
|
- lib
|
307
315
|
required_ruby_version: !ruby/object:Gem::Requirement
|
308
316
|
requirements:
|
309
|
-
- -
|
317
|
+
- - ">="
|
310
318
|
- !ruby/object:Gem::Version
|
311
319
|
version: '0'
|
312
320
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
313
321
|
requirements:
|
314
|
-
- -
|
322
|
+
- - ">="
|
315
323
|
- !ruby/object:Gem::Version
|
316
324
|
version: '0'
|
317
325
|
requirements: []
|
@@ -340,4 +348,3 @@ test_files:
|
|
340
348
|
- test/unit/targeting_test.rb
|
341
349
|
- test/unit/template_input_test.rb
|
342
350
|
- test/unit/template_invocation_input_value_test.rb
|
343
|
-
has_rdoc:
|