foreman_remote_execution 1.5.4 → 1.5.5
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/app/controllers/api/v2/job_invocations_controller.rb +22 -12
- data/app/controllers/job_templates_controller.rb +7 -2
- data/app/helpers/remote_execution_helper.rb +7 -1
- data/app/models/input_template_renderer.rb +1 -1
- data/app/models/job_invocation_composer.rb +10 -1
- data/app/models/job_template.rb +4 -2
- data/app/views/job_invocations/_form.html.erb +10 -2
- data/app/views/overrides/subnets/_rex_tab_pane.html.erb +1 -1
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/factories/foreman_remote_execution_factories.rb +5 -7
- data/test/functional/api/v2/job_invocations_controller_test.rb +24 -0
- data/test/functional/api/v2/job_templates_controller_test.rb +1 -0
- data/test/functional/job_templates_controller_test.rb +31 -0
- data/test/unit/job_invocation_composer_test.rb +18 -0
- data/test/unit/job_template_test.rb +24 -3
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9494abeb23995efb0f837561fd1b1bd84fbd7ef37060081ea573d2dde16bd74b
|
4
|
+
data.tar.gz: afbf23769a88fecbbcb7197249b2261a046b568be38699c35a12210ab6cb13e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28dd96a2c75c9e94f71a6613fac13ecbf7938d46fdfc0d5208833073149a3e60c84de6a7ee357ca22318abae29a7deb85afb77377ff462ee71aade08cf7ec0ca
|
7
|
+
data.tar.gz: 5d012baa162db923230e7d7c0f0dc03a315075ce79f3bc64b8cc1236c328061c2ff839f97c56849157c303fc5e758376ecab15d65137cf206aabe6159d7025cf
|
@@ -82,17 +82,15 @@ module Api
|
|
82
82
|
return
|
83
83
|
end
|
84
84
|
task = @nested_obj.sub_task_for_host(@host)
|
85
|
-
refresh =
|
86
|
-
|
85
|
+
refresh = true
|
86
|
+
line_sets = []
|
87
87
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
if line_sets.blank?
|
92
|
-
render :json => {:refresh => refresh, :output => []}
|
93
|
-
else
|
94
|
-
render :json => {:refresh => refresh, :output => line_sets }
|
88
|
+
if task
|
89
|
+
refresh = task.pending?
|
90
|
+
line_sets = output_lines_since task, params[:since]
|
95
91
|
end
|
92
|
+
|
93
|
+
render :json => { :refresh => refresh, :output => line_sets || [] }
|
96
94
|
end
|
97
95
|
|
98
96
|
api :POST, '/job_invocations/:id/cancel', N_('Cancel job invocation')
|
@@ -113,9 +111,14 @@ module Api
|
|
113
111
|
param :failed_only, :bool
|
114
112
|
def rerun
|
115
113
|
composer = JobInvocationComposer.from_job_invocation(@job_invocation, params)
|
116
|
-
composer.
|
117
|
-
|
118
|
-
|
114
|
+
if composer.rerun_possible?
|
115
|
+
composer.trigger!
|
116
|
+
@job_invocation = composer.job_invocation
|
117
|
+
process_response @job_invocation
|
118
|
+
else
|
119
|
+
render :json => { :error => _('Could not rerun job %{id} because its template could not be found') % { :id => composer.reruns } },
|
120
|
+
:status => 404
|
121
|
+
end
|
119
122
|
end
|
120
123
|
|
121
124
|
private
|
@@ -163,6 +166,13 @@ module Api
|
|
163
166
|
job_invocation_params[:inputs].to_hash
|
164
167
|
)
|
165
168
|
end
|
169
|
+
|
170
|
+
def output_lines_since(task, time)
|
171
|
+
since = time.to_f if time.present?
|
172
|
+
line_sets = task.main_action.live_output
|
173
|
+
line_sets = line_sets.drop_while { |o| o['timestamp'].to_f <= since } if since
|
174
|
+
line_sets
|
175
|
+
end
|
166
176
|
end
|
167
177
|
end
|
168
178
|
end
|
@@ -19,10 +19,15 @@ class JobTemplatesController < ::TemplatesController
|
|
19
19
|
host = params[:preview_host_id].present? ? base.find(params[:preview_host_id]) : base.first
|
20
20
|
@template.template = params[:template]
|
21
21
|
renderer = InputTemplateRenderer.new(@template, host)
|
22
|
-
|
22
|
+
output = renderer.preview
|
23
|
+
if output
|
23
24
|
render :plain => output
|
24
25
|
else
|
25
|
-
render
|
26
|
+
render status: :not_acceptable,
|
27
|
+
plain: _(
|
28
|
+
'Problem with previewing the template: %{error}. Note that you must save template input changes before you try to preview it.' %
|
29
|
+
{:error => renderer.error_message}
|
30
|
+
)
|
26
31
|
end
|
27
32
|
end
|
28
33
|
|
@@ -161,7 +161,7 @@ module RemoteExecutionHelper
|
|
161
161
|
|
162
162
|
def preview_box(template_invocation, target)
|
163
163
|
renderer = InputTemplateRenderer.new(template_invocation.template, target, template_invocation)
|
164
|
-
if (preview = renderer.preview)
|
164
|
+
if (preview = (load_template_from_task(template_invocation, target) || renderer.preview))
|
165
165
|
content_tag :pre, preview
|
166
166
|
elsif target.nil?
|
167
167
|
alert :text => _('Could not render the preview because no host matches the search query.'),
|
@@ -234,4 +234,10 @@ module RemoteExecutionHelper
|
|
234
234
|
end
|
235
235
|
end
|
236
236
|
end
|
237
|
+
|
238
|
+
def load_template_from_task(template_invocation, target)
|
239
|
+
task = template_invocation.job_invocation.sub_task_for_host(target)
|
240
|
+
return if task.nil?
|
241
|
+
task.execution_plan.actions[1].try(:input) { |input| input['script'] }
|
242
|
+
end
|
237
243
|
end
|
@@ -58,7 +58,7 @@ class InputTemplateRenderer
|
|
58
58
|
def render_template(template_name, input_values = {}, options = {})
|
59
59
|
options.assert_valid_keys(:with_foreign_input_set)
|
60
60
|
with_foreign_input_set = options.fetch(:with_foreign_input_set, true)
|
61
|
-
template = @template.class.authorized("view_#{@template.class.to_s.underscore}").find_by(:name => template_name)
|
61
|
+
template = @template.class.authorized("view_#{@template.class.to_s.underscore.pluralize}").find_by(:name => template_name)
|
62
62
|
unless template
|
63
63
|
self.error_message = _('included template \'%s\' not found') % template_name
|
64
64
|
raise error_message
|
@@ -186,7 +186,8 @@ class JobInvocationComposer
|
|
186
186
|
:concurrency_control => concurrency_control_params,
|
187
187
|
:execution_timeout_interval => job_invocation.execution_timeout_interval,
|
188
188
|
:remote_execution_feature_id => job_invocation.remote_execution_feature_id,
|
189
|
-
:template_invocations => template_invocations_params
|
189
|
+
:template_invocations => template_invocations_params,
|
190
|
+
:reruns => job_invocation.id }.with_indifferent_access
|
190
191
|
end
|
191
192
|
|
192
193
|
private
|
@@ -297,6 +298,7 @@ class JobInvocationComposer
|
|
297
298
|
end
|
298
299
|
|
299
300
|
attr_accessor :params, :job_invocation, :host_ids, :search_query
|
301
|
+
attr_reader :reruns
|
300
302
|
delegate :job_category, :remote_execution_feature_id, :pattern_template_invocations, :template_invocations, :targeting, :triggering, :to => :job_invocation
|
301
303
|
|
302
304
|
def initialize(params, set_defaults = false)
|
@@ -304,6 +306,7 @@ class JobInvocationComposer
|
|
304
306
|
@set_defaults = set_defaults
|
305
307
|
@job_invocation = JobInvocation.new
|
306
308
|
@job_invocation.task_group = JobInvocationTaskGroup.new
|
309
|
+
@reruns = params[:reruns]
|
307
310
|
compose
|
308
311
|
|
309
312
|
@host_ids = validate_host_ids(params[:host_ids])
|
@@ -342,6 +345,8 @@ class JobInvocationComposer
|
|
342
345
|
job_invocation.key_passphrase = params[:key_passphrase]
|
343
346
|
job_invocation.sudo_password = params[:sudo_password]
|
344
347
|
|
348
|
+
job_invocation.job_category = nil unless rerun_possible?
|
349
|
+
|
345
350
|
self
|
346
351
|
end
|
347
352
|
# rubocop:enable Metrics/AbcSize
|
@@ -470,6 +475,10 @@ class JobInvocationComposer
|
|
470
475
|
job_invocation.pattern_template_invocations.map(&:template_id)
|
471
476
|
end
|
472
477
|
|
478
|
+
def rerun_possible?
|
479
|
+
!(reruns && job_invocation.pattern_template_invocations.empty?)
|
480
|
+
end
|
481
|
+
|
473
482
|
private
|
474
483
|
|
475
484
|
# builds input values for a given templates id based on params
|
data/app/models/job_template.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
class JobTemplate < ::Template
|
2
2
|
audited
|
3
3
|
include ::Exportable
|
4
|
+
include ::TemplateTax
|
4
5
|
|
5
6
|
class NonUniqueInputsError < Foreman::Exception
|
6
7
|
end
|
7
8
|
|
8
9
|
attr_exportable :job_category, :description_format, :template_inputs,
|
9
|
-
:foreign_input_sets, :provider_type,
|
10
|
+
:foreign_input_sets, :provider_type,
|
11
|
+
{ :kind => ->(template) { template.class.name.underscore } }.merge(taxonomy_exportable)
|
10
12
|
|
11
13
|
include Authorizable
|
12
14
|
extend FriendlyId
|
@@ -134,7 +136,7 @@ class JobTemplate < ::Template
|
|
134
136
|
|
135
137
|
def generate_description_format
|
136
138
|
if description_format.blank?
|
137
|
-
generated_description = '%{
|
139
|
+
generated_description = '%{template_name}'
|
138
140
|
unless template_inputs_with_foreign.empty?
|
139
141
|
inputs = template_inputs_with_foreign.map(&:name).map { |name| %{#{name}="%{#{name}}"} }.join(' ')
|
140
142
|
generated_description << " with inputs #{inputs}"
|
@@ -6,7 +6,15 @@
|
|
6
6
|
|
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
|
+
<% unless @composer.rerun_possible? %>
|
10
|
+
<%= alert :text => _('Could not rerun job %{id} because its template could not be found') % { :id => @composer.reruns },
|
11
|
+
:class => 'alert alert-block alert-danger has-error',
|
12
|
+
:close => false %>
|
13
|
+
<% end %>
|
14
|
+
|
15
|
+
<%= selectable_f f, :job_category, @composer.available_job_categories,
|
16
|
+
{ :selected => @composer.job_invocation.job_category, :include_blank => @composer.job_invocation.job_category.nil? },
|
17
|
+
:label => _('Job category') %>
|
10
18
|
<%= f.hidden_field(:remote_execution_feature_id, :value => @composer.remote_execution_feature_id) %>
|
11
19
|
|
12
20
|
<% selected_templates_per_provider = {} %>
|
@@ -116,5 +124,5 @@
|
|
116
124
|
|
117
125
|
<%= render :partial => 'preview_hosts_modal' %>
|
118
126
|
|
119
|
-
<%= submit_or_cancel f, false, :cancel_path => job_invocations_path %>
|
127
|
+
<%= submit_or_cancel f, false, :cancel_path => job_invocations_path, :disabled => !@composer.rerun_possible? %>
|
120
128
|
<% end %>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<div class="tab-pane" id="rex_proxies">
|
2
2
|
<%= fields_for :subnet do |f| %>
|
3
|
-
<%= multiple_selects f, :remote_execution_proxies, SmartProxy.authorized.with_features(*RemoteExecutionProvider.provider_names), @subnet.remote_execution_proxy_ids, {:label => _("Proxies"), :help_inline => _("Select as many remote execution proxies as applicable for this subnet. When multiple proxies with the same provider are added, actions will be load balanced among them.")} %>
|
3
|
+
<%= multiple_selects f, :remote_execution_proxies, SmartProxy.authorized.with_features(*RemoteExecutionProvider.provider_names).distinct, @subnet.remote_execution_proxy_ids, {:label => _("Proxies"), :help_inline => _("Select as many remote execution proxies as applicable for this subnet. When multiple proxies with the same provider are added, actions will be load balanced among them.")} %>
|
4
4
|
<% end %>
|
5
5
|
</div>
|
@@ -1,14 +1,12 @@
|
|
1
1
|
FactoryBot.define do
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
f.provider_type 'SSH'
|
2
|
+
factory :job_template do
|
3
|
+
sequence(:name) { |n| "Job template #{n}" }
|
4
|
+
sequence(:job_category) { |n| "Job name #{n}" }
|
5
|
+
template 'id'
|
6
|
+
provider_type 'SSH'
|
8
7
|
organizations { [Organization.find_by(name: 'Organization 1')] } if SETTINGS[:organizations_enabled]
|
9
8
|
locations { [Location.find_by(name: 'Location 1')] } if SETTINGS[:locations_enabled]
|
10
9
|
|
11
|
-
|
12
10
|
trait :with_input do
|
13
11
|
after(:build) do |template, evaluator|
|
14
12
|
template.template_inputs << FactoryBot.build(:template_input)
|
@@ -6,6 +6,11 @@ module Api
|
|
6
6
|
setup do
|
7
7
|
@invocation = FactoryBot.create(:job_invocation, :with_template, :with_task)
|
8
8
|
@template = FactoryBot.create(:job_template, :with_input)
|
9
|
+
|
10
|
+
# Without this the template in template_invocations and in pattern_template_invocations
|
11
|
+
# would belong to different job_categories, causing trouble when trying to rerun
|
12
|
+
@invocation.job_category = @invocation.pattern_template_invocations.first.template.job_category
|
13
|
+
@invocation.save!
|
9
14
|
end
|
10
15
|
|
11
16
|
test 'should get index' do
|
@@ -107,6 +112,16 @@ module Api
|
|
107
112
|
assert_response :success
|
108
113
|
end
|
109
114
|
|
115
|
+
test 'should provide empty output for host which does not have a task yet' do
|
116
|
+
host = @invocation.template_invocations_hosts.first
|
117
|
+
JobInvocation.any_instance.expects(:sub_task_for_host).returns(nil)
|
118
|
+
get :output, params: { :job_invocation_id => @invocation.id, :host_id => host.id }
|
119
|
+
result = ActiveSupport::JSON.decode(@response.body)
|
120
|
+
assert_equal result['refresh'], true
|
121
|
+
assert_equal result['output'], []
|
122
|
+
assert_response :success
|
123
|
+
end
|
124
|
+
|
110
125
|
test 'should cancel a job' do
|
111
126
|
@invocation.task.expects(:cancellable?).returns(true)
|
112
127
|
@invocation.task.expects(:cancel).returns(true)
|
@@ -168,6 +183,8 @@ module Api
|
|
168
183
|
|
169
184
|
test 'should rerun failed only' do
|
170
185
|
@invocation = FactoryBot.create(:job_invocation, :with_template, :with_failed_task)
|
186
|
+
@invocation.job_category = @invocation.pattern_template_invocations.first.template.job_category
|
187
|
+
@invocation.save!
|
171
188
|
JobInvocation.any_instance.expects(:generate_description)
|
172
189
|
JobInvocationComposer.any_instance
|
173
190
|
.expects(:validate_job_category)
|
@@ -181,6 +198,13 @@ module Api
|
|
181
198
|
targeting.user_id.must_equal users(:admin).id
|
182
199
|
targeting.search_query.must_equal "name ^ (#{hostnames.join(',')})"
|
183
200
|
end
|
201
|
+
|
202
|
+
test 'should return 404 if template is not found' do
|
203
|
+
@invocation.job_category = 'Missing category'
|
204
|
+
@invocation.save!
|
205
|
+
post :rerun, params: { :id => @invocation.id }
|
206
|
+
assert_response 404
|
207
|
+
end
|
184
208
|
end
|
185
209
|
end
|
186
210
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_plugin_helper'
|
4
|
+
|
5
|
+
class JobTemplatesControllerTest < ActionController::TestCase
|
6
|
+
context '#preview' do
|
7
|
+
let(:template) { FactoryBot.create(:job_template) }
|
8
|
+
let(:host) { FactoryBot.create(:host, :managed) }
|
9
|
+
|
10
|
+
test 'should render a preview version of a template' do
|
11
|
+
post :preview, params: { job_template: template.to_param, template: 'uptime' }, session: set_session_user
|
12
|
+
assert_response :success
|
13
|
+
end
|
14
|
+
|
15
|
+
test 'should render a preview version of a template for a specific host' do
|
16
|
+
post :preview, params: {
|
17
|
+
job_template: template.to_param,
|
18
|
+
template: '<%= @host.name %>',
|
19
|
+
preview_host_id: host.id
|
20
|
+
}, session: set_session_user
|
21
|
+
assert_response :success
|
22
|
+
assert_equal host.name, @response.body
|
23
|
+
end
|
24
|
+
|
25
|
+
test 'should render a error message when template has errors' do
|
26
|
+
InputTemplateRenderer.any_instance.stubs(:render).returns(false)
|
27
|
+
post :preview, params: { job_template: template.to_param }, session: set_session_user
|
28
|
+
assert_response :not_acceptable
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -156,6 +156,24 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
156
156
|
end
|
157
157
|
end
|
158
158
|
|
159
|
+
describe '#rerun_possible?' do
|
160
|
+
it 'is true when not rerunning' do
|
161
|
+
composer.must_be :rerun_possible?
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'is true when rerunning with pattern tempalte invocations' do
|
165
|
+
composer.expects(:reruns).returns(1)
|
166
|
+
composer.job_invocation.expects(:pattern_template_invocations).returns([1])
|
167
|
+
composer.must_be :rerun_possible?
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'is false when rerunning without pattern template invocations' do
|
171
|
+
composer.expects(:reruns).returns(1)
|
172
|
+
composer.job_invocation.expects(:pattern_template_invocations).returns([])
|
173
|
+
composer.wont_be :rerun_possible?
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
159
177
|
describe '#selected_job_templates' do
|
160
178
|
it 'returns no template if none was selected through params' do
|
161
179
|
composer.selected_job_templates.must_be_empty
|
@@ -44,14 +44,14 @@ class JobTemplateTest < ActiveSupport::TestCase
|
|
44
44
|
end
|
45
45
|
|
46
46
|
it 'uses the job name as description_format if not set or blank and has no inputs' do
|
47
|
-
minimal_template.generate_description_format.must_equal '%{
|
47
|
+
minimal_template.generate_description_format.must_equal '%{template_name}'
|
48
48
|
minimal_template.description_format = ''
|
49
|
-
minimal_template.generate_description_format.must_equal '%{
|
49
|
+
minimal_template.generate_description_format.must_equal '%{template_name}'
|
50
50
|
end
|
51
51
|
|
52
52
|
it 'generates the description_format if not set or blank and has inputs' do
|
53
53
|
input_name = template.template_inputs.first.name
|
54
|
-
expected_result = %(%{
|
54
|
+
expected_result = %(%{template_name} with inputs #{input_name}="%{#{input_name}}")
|
55
55
|
template.generate_description_format.must_equal expected_result
|
56
56
|
template.description_format = ''
|
57
57
|
template.generate_description_format.must_equal expected_result
|
@@ -267,6 +267,11 @@ class JobTemplateTest < ActiveSupport::TestCase
|
|
267
267
|
imported.name.must_equal old_name
|
268
268
|
imported.template_inputs.first.to_export.must_equal exportable_template.template_inputs.first.to_export
|
269
269
|
end
|
270
|
+
|
271
|
+
it 'has taxonomies in metadata' do
|
272
|
+
assert_equal 'Organization 1', exportable_template.to_export["organizations"].first
|
273
|
+
assert_equal 'Location 1', exportable_template.to_export["locations"].first
|
274
|
+
end
|
270
275
|
end
|
271
276
|
|
272
277
|
context 'there is existing template invocation of a job template' do
|
@@ -290,4 +295,20 @@ class JobTemplateTest < ActiveSupport::TestCase
|
|
290
295
|
refute_valid job_template
|
291
296
|
end
|
292
297
|
end
|
298
|
+
|
299
|
+
context 'rendering' do
|
300
|
+
it 'renders nested template as a non-admin user' do
|
301
|
+
inner = FactoryBot.create(:job_template)
|
302
|
+
template_invocation = FactoryBot.create(:template_invocation)
|
303
|
+
template_invocation.template.template = "<wrap><%= render_template('#{inner.name}') %></wrap>"
|
304
|
+
template_invocation.template.save!
|
305
|
+
|
306
|
+
setup_user('view', 'job_templates')
|
307
|
+
renderer = InputTemplateRenderer.new template_invocation.template,
|
308
|
+
template_invocation.host,
|
309
|
+
template_invocation
|
310
|
+
result = renderer.render
|
311
|
+
result.must_equal "<wrap>#{inner.template}</wrap>"
|
312
|
+
end
|
313
|
+
end
|
293
314
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman_remote_execution
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.5.
|
4
|
+
version: 1.5.5
|
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: 2018-
|
11
|
+
date: 2018-08-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: deface
|
@@ -368,6 +368,7 @@ files:
|
|
368
368
|
- test/functional/api/v2/template_inputs_controller_test.rb
|
369
369
|
- test/functional/api/v2/template_invocations_controller_test.rb
|
370
370
|
- test/functional/job_invocations_controller_test.rb
|
371
|
+
- test/functional/job_templates_controller_test.rb
|
371
372
|
- test/test_plugin_helper.rb
|
372
373
|
- test/unit/actions/run_host_job_test.rb
|
373
374
|
- test/unit/actions/run_hosts_job_test.rb
|
@@ -416,7 +417,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
416
417
|
version: '0'
|
417
418
|
requirements: []
|
418
419
|
rubyforge_project:
|
419
|
-
rubygems_version: 2.
|
420
|
+
rubygems_version: 2.7.3
|
420
421
|
signing_key:
|
421
422
|
specification_version: 4
|
422
423
|
summary: A plugin bringing remote execution to the Foreman, completing the config
|
@@ -432,6 +433,7 @@ test_files:
|
|
432
433
|
- test/functional/api/v2/template_inputs_controller_test.rb
|
433
434
|
- test/functional/api/v2/template_invocations_controller_test.rb
|
434
435
|
- test/functional/job_invocations_controller_test.rb
|
436
|
+
- test/functional/job_templates_controller_test.rb
|
435
437
|
- test/test_plugin_helper.rb
|
436
438
|
- test/unit/actions/run_host_job_test.rb
|
437
439
|
- test/unit/actions/run_hosts_job_test.rb
|