foreman_remote_execution 1.4.5 → 1.4.6
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 -5
- data/.hound.yml +13 -0
- data/.rubocop.yml +9 -0
- data/app/controllers/api/v2/job_invocations_controller.rb +49 -6
- data/app/controllers/job_invocations_controller.rb +29 -1
- data/app/helpers/remote_execution_helper.rb +5 -5
- data/app/lib/actions/remote_execution/run_host_job.rb +32 -20
- data/app/lib/actions/remote_execution/run_hosts_job.rb +13 -8
- data/app/models/foreign_input_set.rb +1 -1
- data/app/models/job_invocation.rb +23 -1
- data/app/models/job_invocation_composer.rb +25 -3
- data/app/models/job_template.rb +2 -2
- data/app/models/remote_execution_feature.rb +22 -10
- data/app/models/setting/remote_execution.rb +34 -10
- data/app/models/ssh_execution_provider.rb +8 -0
- data/app/models/template_input.rb +1 -1
- data/app/views/api/v2/job_templates/base.json.rabl +1 -1
- data/app/views/job_invocations/_form.html.erb +11 -5
- data/app/views/job_invocations/_tab_hosts.html.erb +1 -1
- data/app/views/job_invocations/index.html.erb +1 -1
- data/app/views/job_templates/index.html.erb +1 -1
- data/app/views/remote_execution_features/index.html.erb +1 -1
- data/app/views/template_inputs/_invocation_form.html.erb +2 -2
- data/app/views/template_invocations/show.html.erb +1 -1
- data/config/routes.rb +5 -0
- data/db/migrate/20160113162007_expand_all_template_invocations.rb +1 -1
- data/db/migrate/20171129103615_add_secrets_to_job_invocations.rb +6 -0
- data/db/migrate/20180202072115_add_notification_builder_to_remote_execution_feature.rb +5 -0
- data/db/migrate/20180202123215_add_feature_id_to_job_invocation.rb +6 -0
- data/db/migrate/20180226095631_change_task_id_to_uuid.rb +31 -0
- data/foreman_remote_execution.gemspec +1 -1
- data/lib/foreman_remote_execution/engine.rb +3 -1
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/factories/foreman_remote_execution_factories.rb +6 -0
- data/test/functional/api/v2/job_invocations_controller_test.rb +139 -32
- data/test/functional/job_invocations_controller_test.rb +49 -0
- data/test/unit/actions/run_hosts_job_test.rb +10 -6
- data/test/unit/concerns/foreman_tasks_cleaner_extensions_test.rb +1 -1
- data/test/unit/job_invocation_composer_test.rb +43 -1
- metadata +14 -10
- data/app/models/concerns/foreman_remote_execution/exportable.rb +0 -71
- data/test/unit/concerns/exportable_test.rb +0 -88
data/app/models/job_template.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
class JobTemplate < ::Template
|
2
|
-
include
|
2
|
+
include ::Exportable
|
3
3
|
|
4
4
|
class NonUniqueInputsError < Foreman::Exception
|
5
5
|
end
|
6
6
|
|
7
|
-
attr_exportable :
|
7
|
+
attr_exportable :job_category, :description_format, :template_inputs,
|
8
8
|
:foreign_input_sets, :provider_type, :kind => ->(template) { template.class.name.underscore }
|
9
9
|
|
10
10
|
include Authorizable
|
@@ -1,9 +1,11 @@
|
|
1
1
|
class RemoteExecutionFeature < ApplicationRecord
|
2
|
-
VALID_OPTIONS = [:provided_inputs, :description, :host_action_button].freeze
|
2
|
+
VALID_OPTIONS = [:provided_inputs, :description, :host_action_button, :notification_builder].freeze
|
3
3
|
validates :label, :name, :presence => true, :uniqueness => true
|
4
4
|
|
5
5
|
belongs_to :job_template
|
6
6
|
|
7
|
+
audited :only => :job_template_id
|
8
|
+
|
7
9
|
extend FriendlyId
|
8
10
|
friendly_id :label
|
9
11
|
|
@@ -27,19 +29,29 @@ class RemoteExecutionFeature < ApplicationRecord
|
|
27
29
|
options[:host_action_button] = false unless options.key?(:host_action_button)
|
28
30
|
|
29
31
|
feature = self.find_by(label: label)
|
32
|
+
builder = options[:notification_builder] ? options[:notification_builder].to_s : nil
|
30
33
|
|
31
|
-
attributes = { :name => name,
|
34
|
+
attributes = { :name => name,
|
35
|
+
:provided_input_names => options[:provided_inputs],
|
36
|
+
:description => options[:description],
|
37
|
+
:host_action_button => options[:host_action_button],
|
38
|
+
:notification_builder => builder }
|
32
39
|
# in case DB does not have the attribute created yet but plugin initializer registers the feature, we need to skip this attribute
|
33
|
-
|
34
|
-
|
40
|
+
attrs = [ :host_action_button, :notification_builder ]
|
41
|
+
attrs.each do |attr|
|
42
|
+
unless self.attribute_names.include?(attr.to_s)
|
43
|
+
attributes.delete(attr)
|
44
|
+
end
|
35
45
|
end
|
36
46
|
|
37
|
-
|
38
|
-
feature
|
39
|
-
|
40
|
-
|
41
|
-
|
47
|
+
self.without_auditing do
|
48
|
+
if feature.nil?
|
49
|
+
feature = self.create!({ :label => label }.merge(attributes))
|
50
|
+
else
|
51
|
+
feature.attributes = attributes
|
52
|
+
feature.save if feature.changed?
|
53
|
+
end
|
54
|
+
return feature
|
42
55
|
end
|
43
|
-
return feature
|
44
56
|
end
|
45
57
|
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
class Setting::RemoteExecution < Setting
|
2
2
|
|
3
|
+
::Setting::BLANK_ATTRS.concat %w{remote_execution_ssh_password remote_execution_ssh_key_passphrase}
|
4
|
+
|
5
|
+
# rubocop:disable Metrics/MethodLength
|
3
6
|
def self.load_defaults
|
4
7
|
# Check the table exists
|
5
8
|
return unless super
|
@@ -9,42 +12,63 @@ class Setting::RemoteExecution < Setting
|
|
9
12
|
[
|
10
13
|
self.set('remote_execution_fallback_proxy',
|
11
14
|
N_('Search the host for any proxy with Remote Execution, useful when the host has no subnet or the subnet does not have an execution proxy'),
|
12
|
-
false
|
15
|
+
false,
|
16
|
+
N_('Fallback to Any Proxy')),
|
13
17
|
self.set('remote_execution_global_proxy',
|
14
18
|
N_('Search for remote execution proxy outside of the proxies assigned to the host. ' +
|
15
19
|
"If locations or organizations are enabled, the search will be limited to the host's " +
|
16
20
|
'organization or location.'),
|
17
|
-
true
|
21
|
+
true,
|
22
|
+
N_('Enable Global Proxy')),
|
18
23
|
self.set('remote_execution_without_proxy',
|
19
24
|
N_('When enabled, the remote execution will try to run the commands directly, when no
|
20
25
|
proxy with remote execution feature is configured for the host.'),
|
21
|
-
false
|
26
|
+
false,
|
27
|
+
N_('Fallback Without Proxy')),
|
22
28
|
self.set('remote_execution_ssh_user',
|
23
29
|
N_('Default user to use for SSH. You may override per host by setting a parameter called remote_execution_ssh_user.'),
|
24
|
-
'root'
|
30
|
+
'root',
|
31
|
+
N_('SSH User')),
|
25
32
|
self.set('remote_execution_effective_user',
|
26
33
|
N_('Default user to use for executing the script. If the user differs from the SSH user, su or sudo is used to switch the user.'),
|
27
|
-
'root'
|
34
|
+
'root',
|
35
|
+
N_('Effective User')),
|
28
36
|
self.set('remote_execution_effective_user_method',
|
29
37
|
N_('What command should be used to switch to the effective user. One of %s') % SSHExecutionProvider::EFFECTIVE_USER_METHODS.inspect,
|
30
38
|
'sudo',
|
31
|
-
'
|
39
|
+
N_('Effective User Method'),
|
32
40
|
nil,
|
33
41
|
{ :collection => proc { Hash[SSHExecutionProvider::EFFECTIVE_USER_METHODS.map { |method| [method, method] }] } }),
|
34
42
|
self.set('remote_execution_sync_templates',
|
35
43
|
N_('Whether we should sync templates from disk when running db:seed.'),
|
36
|
-
true
|
44
|
+
true,
|
45
|
+
N_('Sync Job Templates')),
|
37
46
|
self.set('remote_execution_ssh_port',
|
38
47
|
N_('Port to use for SSH communication. Default port 22. You may override per host by setting a parameter called remote_execution_ssh_port.'),
|
39
|
-
'22'
|
48
|
+
'22',
|
49
|
+
N_('SSH Port')),
|
40
50
|
self.set('remote_execution_connect_by_ip',
|
41
51
|
N_('Should the ip addresses on host interfaces be preferred over the fqdn? '\
|
42
52
|
'It is useful, when DNS not resolving the fqdns properly. You may override this per host by setting a parameter called remote_execution_connect_by_ip.'),
|
43
|
-
false
|
53
|
+
false,
|
54
|
+
N_('Connect by IP')),
|
55
|
+
self.set('remote_execution_ssh_password',
|
56
|
+
N_('Default password to use for SSH. You may override per host by setting a parameter called remote_execution_ssh_password'),
|
57
|
+
nil,
|
58
|
+
N_('Default SSH password'),
|
59
|
+
nil,
|
60
|
+
{ :encrypted => true }),
|
61
|
+
self.set('remote_execution_ssh_key_passphrase',
|
62
|
+
N_('Default key passphrase to use for SSH. You may override per host by setting a parameter called remote_execution_ssh_key_passphrase'),
|
63
|
+
nil,
|
64
|
+
N_('Default SSH key passphrase'),
|
65
|
+
nil,
|
66
|
+
{ :encrypted => true })
|
44
67
|
].each { |s| self.create! s.update(:category => 'Setting::RemoteExecution') }
|
45
68
|
end
|
46
69
|
|
47
70
|
true
|
48
71
|
end
|
49
|
-
|
72
|
+
# rubocop:enable AbcSize
|
73
|
+
# rubocop:enable Metrics/MethodLength
|
50
74
|
end
|
@@ -15,6 +15,14 @@ class SSHExecutionProvider < RemoteExecutionProvider
|
|
15
15
|
true
|
16
16
|
end
|
17
17
|
|
18
|
+
def ssh_password(host)
|
19
|
+
host_setting(host, :remote_execution_ssh_password)
|
20
|
+
end
|
21
|
+
|
22
|
+
def ssh_key_passphrase(host)
|
23
|
+
host_setting(host, :remote_execution_ssh_key_passphrase)
|
24
|
+
end
|
25
|
+
|
18
26
|
private
|
19
27
|
|
20
28
|
def ssh_user(host)
|
@@ -7,6 +7,7 @@
|
|
7
7
|
<%= form_for @composer.job_invocation, :html => {'data-refresh-url' => refresh_job_invocations_path, :id => 'job_invocation_form'} do |f| %>
|
8
8
|
|
9
9
|
<%= selectable_f f, :job_category, @composer.available_job_categories, {}, :label => _('Job category') %>
|
10
|
+
<%= f.hidden_field(:remote_execution_feature_id, :value => @composer.remote_execution_feature_id) %>
|
10
11
|
|
11
12
|
<% selected_templates_per_provider = {} %>
|
12
13
|
<% @composer.displayed_provider_types.each do |provider_type| %>
|
@@ -72,13 +73,13 @@
|
|
72
73
|
|
73
74
|
<div class="advanced hidden">
|
74
75
|
<% if job_template.effective_user.overridable? %>
|
75
|
-
<%= text_f job_template_fields, :effective_user, :value => @composer.template_invocation(job_template).try(:effective_user), :label => _('Effective user'), :
|
76
|
+
<%= text_f job_template_fields, :effective_user, :value => @composer.template_invocation(job_template).try(:effective_user), :label => _('Effective user'), :label_help => N_("A user to be used for executing the script. If it differs from the SSH user, su or sudo is used to switch the accounts.") %>
|
76
77
|
<% end %>
|
77
78
|
<%= render :partial => 'description_fields', :locals => { :f => f, :job_template => job_template, :disabled => job_template != selected_templates_per_provider[provider_type] } %>
|
78
79
|
</div>
|
79
80
|
|
80
81
|
<div class="advanced hidden">
|
81
|
-
<%= number_f job_template_fields, :execution_timeout_interval, :value => f.object.execution_timeout_interval || job_template.execution_timeout_interval, :label => _('Timeout to kill'), :
|
82
|
+
<%= number_f job_template_fields, :execution_timeout_interval, :value => f.object.execution_timeout_interval || job_template.execution_timeout_interval, :label => _('Timeout to kill'), :label_help => N_('Time in seconds from the start on the remote host after which the job should be killed.') %>
|
82
83
|
</div>
|
83
84
|
<% end %>
|
84
85
|
</fieldset>
|
@@ -90,12 +91,17 @@
|
|
90
91
|
<% end %>
|
91
92
|
|
92
93
|
<div class="advanced hidden">
|
93
|
-
<%=
|
94
|
-
<%=
|
94
|
+
<%= password_f f, :password, :placeholder => '*****', :label => _('Password'), :label_help => N_('Password is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution.') %>
|
95
|
+
<%= password_f f, :key_passphrase, :placeholder => '*****', :label => _('Private key passphrase'), :label_help => N_('Key passhprase is only applicable for SSH provider. Other providers ignore this field. <br> Passphrase is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution.') %>
|
96
|
+
</div>
|
97
|
+
|
98
|
+
<div class="advanced hidden">
|
99
|
+
<%= number_f f, :concurrency_level, :label => _('Concurrency level'), :placeholder => 'N', :min => 1, :label_help => N_("Run at most N tasks at a time") %>
|
100
|
+
<%= number_f f, :time_span, :label => _('Time span'), :placeholder => 'N', :min => 1, :label_help => N_("Distribute execution over N seconds") %>
|
95
101
|
</div>
|
96
102
|
|
97
103
|
<div class="form-group advanced hidden">
|
98
|
-
<%= add_label({ :label => _('Type of query'), :label_help => _("Type has impact on when is the query
|
104
|
+
<%= add_label({ :label => _('Type of query'), :label_help => _("Type has impact on when is the query evaluated to hosts.<br><ul><li><b>Static</b> - evaluates just after you submit this form</li><li><b>Dynamic</b> - evaluates just before the execution is started, so if it's planed in future, targeted hosts set may change before it</li></ul>") }, f, :targetting_type) %>
|
99
105
|
|
100
106
|
<div class="col-md-4">
|
101
107
|
<%= radio_button_f targeting_fields, :targeting_type, :value => Targeting::STATIC_TYPE, :text => _(Targeting::TYPES[Targeting::STATIC_TYPE]) %>
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
<% title_actions(job_invocations_buttons) %>
|
4
4
|
|
5
|
-
<table class="
|
5
|
+
<table class="<%= table_css_classes('table-condensed table-fixed') %>">
|
6
6
|
<thead>
|
7
7
|
<tr>
|
8
8
|
<th><%= sort :description, :as => _('Description') %></th>
|
@@ -7,7 +7,7 @@
|
|
7
7
|
link_to_function(_('Import'), 'show_import_job_template_modal();', :class => 'btn btn-default'),
|
8
8
|
new_link(_("New Job Template"))) %>
|
9
9
|
|
10
|
-
<table class="
|
10
|
+
<table class="<%= table_css_classes('table-two-pane table-fixed') %>">
|
11
11
|
<thead>
|
12
12
|
<tr>
|
13
13
|
<th class="col-md-6"><%= sort :name, :as => s_("JobTemplate|Name") %></th>
|
@@ -1,7 +1,7 @@
|
|
1
1
|
<%= input_values_fields.fields_for input.id.to_s, composer.template_invocation_input_value_for(job_template, input) do |input_fields| %>
|
2
2
|
<% unless input.options.blank? %>
|
3
|
-
<%= selectable_f input_fields, :value, input.options_array, {:include_blank => !input.required}, :
|
3
|
+
<%= selectable_f input_fields, :value, input.options_array, { :include_blank => !input.required }, :label_help => input.description, :require => input.required, :label => input.name, :id => input.name, :onchange => "regenerate_description(this);" %>
|
4
4
|
<% else %>
|
5
|
-
<%= textarea_f input_fields, :value, :label => input.name, :
|
5
|
+
<%= textarea_f input_fields, :value, :label => input.name, :label_help => input.description, :required => input.required, :rows => 2, :onchange => "regenerate_description(this);", :id => input.name %>
|
6
6
|
<% end %>
|
7
7
|
<% end %>
|
@@ -9,7 +9,7 @@
|
|
9
9
|
link_to_function(_('Toggle STDERR'), '$("div.line.stderr").toggle()', :class => 'btn btn-default'),
|
10
10
|
link_to_function(_('Toggle STDOUT'), '$("div.line.stdout").toggle()', :class => 'btn btn-default'),
|
11
11
|
link_to_function(_('Toggle DEBUG'), '$("div.line.debug").toggle()', :class => 'btn btn-default')) %>
|
12
|
-
<%= button_group(template_invocation_task_buttons(@template_invocation_task)) %>
|
12
|
+
<%= button_group(template_invocation_task_buttons(@template_invocation_task, @template_invocation.job_invocation)) %>
|
13
13
|
</div>
|
14
14
|
</div>
|
15
15
|
|
data/config/routes.rb
CHANGED
@@ -24,6 +24,7 @@ Rails.application.routes.draw do
|
|
24
24
|
end
|
25
25
|
member do
|
26
26
|
get 'rerun'
|
27
|
+
post 'cancel'
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
@@ -42,6 +43,10 @@ Rails.application.routes.draw do
|
|
42
43
|
resources :hosts, :only => :none do
|
43
44
|
get '/', :to => 'job_invocations#output'
|
44
45
|
end
|
46
|
+
member do
|
47
|
+
post 'cancel'
|
48
|
+
post 'rerun'
|
49
|
+
end
|
45
50
|
end
|
46
51
|
|
47
52
|
resources :job_templates, :except => [:new, :edit] do
|
@@ -14,7 +14,7 @@ class ExpandAllTemplateInvocations < ActiveRecord::Migration[4.2]
|
|
14
14
|
FakeTemplateInvocation.update_all 'host_id = NULL'
|
15
15
|
|
16
16
|
# expand all pattern template invocations and link RunHostJob
|
17
|
-
JobInvocation.joins(:targeting).where("#{Targeting.table_name}.resolved_at IS NOT NULL").includes([
|
17
|
+
JobInvocation.joins(:targeting).where("#{Targeting.table_name}.resolved_at IS NOT NULL").includes([:pattern_template_invocations, :targeting]).each do |job_invocation|
|
18
18
|
job_invocation.pattern_template_invocations.each do |pattern_template_invocation|
|
19
19
|
job_invocation.targeting.hosts.each do |host|
|
20
20
|
task = job_invocation.sub_tasks.find do |sub_task|
|
@@ -0,0 +1,6 @@
|
|
1
|
+
class AddFeatureIdToJobInvocation < ActiveRecord::Migration[4.2]
|
2
|
+
def change
|
3
|
+
add_column :job_invocations, :remote_execution_feature_id, :integer, :index => true
|
4
|
+
add_foreign_key :job_invocations, :remote_execution_features, :column => :remote_execution_feature_id
|
5
|
+
end
|
6
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class ChangeTaskIdToUuid < ActiveRecord::Migration[4.2]
|
2
|
+
def up
|
3
|
+
if on_postgresql?
|
4
|
+
change_table :job_invocations do |t|
|
5
|
+
t.change :task_id, :uuid, :using => 'task_id::uuid'
|
6
|
+
end
|
7
|
+
|
8
|
+
change_table :template_invocations do |t|
|
9
|
+
t.change :run_host_job_task_id, :uuid, :using => 'run_host_job_task_id::uuid'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def down
|
15
|
+
if on_postgresql?
|
16
|
+
change_table :job_invocations do |t|
|
17
|
+
t.change :task_id, :string, :limit => 255
|
18
|
+
end
|
19
|
+
|
20
|
+
change_table :template_invocations do |t|
|
21
|
+
t.change :run_host_job_task_id, :string, :limit => 255
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def on_postgresql?
|
29
|
+
ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
30
|
+
end
|
31
|
+
end
|
@@ -26,7 +26,7 @@ Gem::Specification.new do |s|
|
|
26
26
|
s.add_dependency 'deface'
|
27
27
|
s.add_dependency 'dynflow', '~> 0.8.26'
|
28
28
|
s.add_dependency 'foreman_remote_execution_core'
|
29
|
-
s.add_dependency 'foreman-tasks', '
|
29
|
+
s.add_dependency 'foreman-tasks', '~> 0.12'
|
30
30
|
|
31
31
|
s.add_development_dependency 'factory_bot_rails', '~> 4.8.0'
|
32
32
|
s.add_development_dependency 'rubocop'
|
@@ -53,10 +53,11 @@ module ForemanRemoteExecution
|
|
53
53
|
:'api/v2/job_templates' => [:destroy] }, :resource_type => 'JobTemplate'
|
54
54
|
permission :lock_job_templates, { :job_templates => [:lock, :unlock] }, :resource_type => 'JobTemplate'
|
55
55
|
permission :create_job_invocations, { :job_invocations => [:new, :create, :refresh, :rerun, :preview_hosts],
|
56
|
-
'api/v2/job_invocations' => [:create] }, :resource_type => 'JobInvocation'
|
56
|
+
'api/v2/job_invocations' => [:create, :rerun] }, :resource_type => 'JobInvocation'
|
57
57
|
permission :view_job_invocations, { :job_invocations => [:index, :show, :auto_complete_search], :template_invocations => [:show],
|
58
58
|
'api/v2/job_invocations' => [:index, :show, :output] }, :resource_type => 'JobInvocation'
|
59
59
|
permission :create_template_invocations, {}, :resource_type => 'TemplateInvocation'
|
60
|
+
permission :cancel_job_invocations, { :job_invocations => [:cancel], 'api/v2/job_invocations' => [:cancel] }, :resource_type => 'JobInvocation'
|
60
61
|
# this permissions grants user to get auto completion hints when setting up filters
|
61
62
|
permission :filter_autocompletion_for_template_invocation, { :template_invocations => [ :auto_complete_search, :index ] },
|
62
63
|
:resource_type => 'TemplateInvocation'
|
@@ -71,6 +72,7 @@ module ForemanRemoteExecution
|
|
71
72
|
:view_smart_proxies
|
72
73
|
].freeze
|
73
74
|
MANAGER_PERMISSIONS = USER_PERMISSIONS + [
|
75
|
+
:cancel_job_invocations,
|
74
76
|
:destroy_job_templates,
|
75
77
|
:edit_job_templates,
|
76
78
|
:create_job_templates,
|
@@ -45,7 +45,13 @@ FactoryBot.define do
|
|
45
45
|
after(:build) do |invocation, evaluator|
|
46
46
|
invocation.pattern_template_invocations << FactoryBot.build(:template_invocation)
|
47
47
|
end
|
48
|
+
end
|
48
49
|
|
50
|
+
trait :with_failed_task do
|
51
|
+
after(:build) do |invocation, _evaluator|
|
52
|
+
invocation.template_invocations << FactoryBot.build(:template_invocation, :with_failed_task, :with_host)
|
53
|
+
invocation.task = FactoryBot.build(:some_task)
|
54
|
+
end
|
49
55
|
end
|
50
56
|
|
51
57
|
trait :with_task do
|
@@ -23,45 +23,77 @@ module Api
|
|
23
23
|
assert_equal template['job_category'], @invocation.job_category
|
24
24
|
end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
context 'creation' do
|
27
|
+
setup do
|
28
|
+
@attrs = { :job_category => @template.job_category,
|
29
|
+
:name => 'RandomName',
|
30
|
+
:job_template_id => @template.id,
|
31
|
+
:targeting_type => 'static_query',
|
32
|
+
:search_query => 'foobar' }
|
33
|
+
end
|
30
34
|
|
31
|
-
|
32
|
-
|
33
|
-
assert_response :success
|
34
|
-
end
|
35
|
+
test 'should create valid with job_template_id' do
|
36
|
+
post :create, params: { job_invocation: @attrs }
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
invocation = ActiveSupport::JSON.decode(@response.body)
|
39
|
+
assert_equal @attrs[:job_category], invocation['job_category']
|
40
|
+
assert_response :success
|
41
|
+
end
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
|
43
|
+
test 'should create with description format overridden' do
|
44
|
+
@attrs[:description_format] = 'format'
|
45
|
+
post :create, params: { job_invocation: @attrs }
|
44
46
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
:search_query => 'foobar', :recurrence => {:cron_line => '5 * * * *'}}
|
47
|
+
invocation = ActiveSupport::JSON.decode(@response.body)
|
48
|
+
assert_equal @attrs[:description_format], invocation['description']
|
49
|
+
end
|
49
50
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
51
|
+
test 'should create with recurrence' do
|
52
|
+
@attrs[:recurrence] = { cron_line: '5 * * * *' }
|
53
|
+
post :create, params: { job_invocation: @attrs }
|
54
|
+
invocation = ActiveSupport::JSON.decode(@response.body)
|
55
|
+
assert_equal invocation['mode'], 'recurring'
|
56
|
+
assert_response :success
|
57
|
+
end
|
55
58
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
59
|
+
test 'should create with schedule' do
|
60
|
+
@attrs[:scheduling] = { start_at: Time.now.to_s }
|
61
|
+
post :create, params: { job_invocation: @attrs }
|
62
|
+
invocation = ActiveSupport::JSON.decode(@response.body)
|
63
|
+
assert_equal invocation['mode'], 'future'
|
64
|
+
assert_response :success
|
65
|
+
end
|
60
66
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
67
|
+
context 'with_feature' do
|
68
|
+
setup do
|
69
|
+
@feature = FactoryBot.create(:remote_execution_feature,
|
70
|
+
:job_template => @template)
|
71
|
+
@attrs = {
|
72
|
+
feature: @feature.label
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
test 'host ids as array of FQDNs' do
|
77
|
+
host = FactoryBot.create(:host)
|
78
|
+
@attrs[:host_ids] = [host.fqdn]
|
79
|
+
post :create, params: { job_invocation: @attrs }
|
80
|
+
assert_response :success
|
81
|
+
end
|
82
|
+
|
83
|
+
test 'host ids as array of IDs' do
|
84
|
+
host = FactoryBot.create(:host)
|
85
|
+
host2 = FactoryBot.create(:host)
|
86
|
+
@attrs[:host_ids] = [host.id, host2.id]
|
87
|
+
post :create, params: { job_invocation: @attrs }
|
88
|
+
assert_response :success
|
89
|
+
end
|
90
|
+
|
91
|
+
test 'search_query' do
|
92
|
+
@attrs[:host_ids] = 'name = testfqdn'
|
93
|
+
post :create, params: { job_invocation: @attrs }
|
94
|
+
assert_response :success
|
95
|
+
end
|
96
|
+
end
|
65
97
|
end
|
66
98
|
|
67
99
|
test 'should provide output for delayed task' do
|
@@ -74,6 +106,81 @@ module Api
|
|
74
106
|
assert_equal result['output'], []
|
75
107
|
assert_response :success
|
76
108
|
end
|
109
|
+
|
110
|
+
test 'should cancel a job' do
|
111
|
+
@invocation.task.expects(:cancellable?).returns(true)
|
112
|
+
@invocation.task.expects(:cancel).returns(true)
|
113
|
+
JobInvocation.expects(:from_param).with(@invocation.id.to_s).returns(@invocation)
|
114
|
+
post :cancel, :params => { :id => @invocation.id }
|
115
|
+
result = ActiveSupport::JSON.decode(@response.body)
|
116
|
+
assert_equal result['cancelled'], true
|
117
|
+
assert_equal result['id'], @invocation.id
|
118
|
+
|
119
|
+
assert_response :success
|
120
|
+
end
|
121
|
+
|
122
|
+
test 'should abort a job' do
|
123
|
+
@invocation.task.expects(:cancellable?).returns(true)
|
124
|
+
@invocation.task.expects(:abort).returns(true)
|
125
|
+
JobInvocation.expects(:from_param).with(@invocation.id.to_s).returns(@invocation)
|
126
|
+
post :cancel, :params => { :id => @invocation.id, :force => true }
|
127
|
+
result = ActiveSupport::JSON.decode(@response.body)
|
128
|
+
assert_equal result['cancelled'], true
|
129
|
+
assert_equal result['id'], @invocation.id
|
130
|
+
|
131
|
+
assert_response :success
|
132
|
+
end
|
133
|
+
|
134
|
+
test 'should error when trying to cancel a stopped job' do
|
135
|
+
@invocation.task.expects(:cancellable?).returns(false)
|
136
|
+
JobInvocation.expects(:from_param).with(@invocation.id.to_s).returns(@invocation)
|
137
|
+
post :cancel, :params => { :id => @invocation.id }
|
138
|
+
assert_response 422
|
139
|
+
end
|
140
|
+
|
141
|
+
test 'should rerun' do
|
142
|
+
JobInvocation.any_instance.expects(:generate_description)
|
143
|
+
JobInvocationComposer.any_instance
|
144
|
+
.expects(:validate_job_category)
|
145
|
+
.with(@invocation.job_category)
|
146
|
+
.returns(@invocation.job_category)
|
147
|
+
post :rerun, params: { :id => @invocation.id }
|
148
|
+
assert_response :success
|
149
|
+
result = ActiveSupport::JSON.decode(@response.body)
|
150
|
+
targeting = Targeting.find(result['targeting_id'])
|
151
|
+
targeting.user_id.must_equal users(:admin).id
|
152
|
+
targeting.search_query.must_equal @invocation.targeting.search_query
|
153
|
+
end
|
154
|
+
|
155
|
+
test 'should not raise an exception when reruning failed has no hosts' do
|
156
|
+
JobInvocation.any_instance.expects(:generate_description)
|
157
|
+
JobInvocationComposer.any_instance
|
158
|
+
.expects(:validate_job_category)
|
159
|
+
.with(@invocation.job_category)
|
160
|
+
.returns(@invocation.job_category)
|
161
|
+
post :rerun, params: { :id => @invocation.id, :failed_only => true }
|
162
|
+
assert_response :success
|
163
|
+
result = ActiveSupport::JSON.decode(@response.body)
|
164
|
+
targeting = Targeting.find(result['targeting_id'])
|
165
|
+
targeting.user_id.must_equal users(:admin).id
|
166
|
+
targeting.search_query.must_equal 'name ^ ()'
|
167
|
+
end
|
168
|
+
|
169
|
+
test 'should rerun failed only' do
|
170
|
+
@invocation = FactoryBot.create(:job_invocation, :with_template, :with_failed_task)
|
171
|
+
JobInvocation.any_instance.expects(:generate_description)
|
172
|
+
JobInvocationComposer.any_instance
|
173
|
+
.expects(:validate_job_category)
|
174
|
+
.with(@invocation.job_category)
|
175
|
+
.returns(@invocation.job_category)
|
176
|
+
post :rerun, params: { :id => @invocation.id, :failed_only => true }
|
177
|
+
assert_response :success
|
178
|
+
result = ActiveSupport::JSON.decode(@response.body)
|
179
|
+
targeting = Targeting.find(result['targeting_id'])
|
180
|
+
hostnames = @invocation.template_invocations.map { |ti| ti.host.name }
|
181
|
+
targeting.user_id.must_equal users(:admin).id
|
182
|
+
targeting.search_query.must_equal "name ^ (#{hostnames.join(',')})"
|
183
|
+
end
|
77
184
|
end
|
78
185
|
end
|
79
186
|
end
|