foreman_remote_execution 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/javascripts/template_invocation.js +48 -5
- data/app/controllers/api/v2/job_invocations_controller.rb +55 -10
- data/app/controllers/api/v2/job_templates_controller.rb +19 -4
- data/app/controllers/api/v2/template_inputs_controller.rb +88 -0
- data/app/controllers/job_invocations_controller.rb +17 -15
- data/app/controllers/template_invocations_controller.rb +2 -0
- data/app/helpers/remote_execution_helper.rb +27 -16
- data/app/lib/actions/middleware/bind_job_invocation.rb +7 -3
- data/app/lib/actions/remote_execution/run_host_job.rb +28 -17
- data/app/lib/actions/remote_execution/run_hosts_job.rb +9 -6
- data/app/models/concerns/foreman_remote_execution/foreman_tasks_task_extensions.rb +1 -1
- data/app/models/concerns/foreman_remote_execution/foreman_tasks_triggering_extensions.rb +9 -0
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +3 -1
- data/app/models/job_invocation.rb +48 -41
- data/app/models/job_invocation_composer.rb +205 -80
- data/app/models/job_invocation_task_group.rb +18 -0
- data/app/models/job_template.rb +25 -1
- data/app/models/job_template_effective_user.rb +23 -0
- data/app/models/remote_execution_provider.rb +25 -11
- data/app/models/setting/remote_execution.rb +6 -0
- data/app/models/ssh_execution_provider.rb +37 -0
- data/app/models/targeting.rb +13 -0
- data/app/models/template_input.rb +4 -1
- data/app/models/template_invocation.rb +23 -0
- data/app/views/api/v2/job_invocations/base.json.rabl +4 -0
- data/app/views/api/v2/job_invocations/index.json.rabl +1 -1
- data/app/views/api/v2/job_invocations/main.json.rabl +19 -0
- data/app/views/api/v2/job_invocations/show.json.rabl +0 -15
- data/app/views/api/v2/job_templates/base.json.rabl +1 -1
- data/app/views/api/v2/job_templates/index.json.rabl +1 -1
- data/app/views/api/v2/job_templates/main.json.rabl +5 -1
- data/app/views/api/v2/job_templates/show.json.rabl +4 -0
- data/app/views/api/v2/job_templates/update.json.rabl +3 -0
- data/app/views/api/v2/template_inputs/base.json.rabl +3 -0
- data/app/views/api/v2/template_inputs/create.json.rabl +3 -0
- data/app/views/api/v2/template_inputs/index.json.rabl +3 -0
- data/app/views/api/v2/template_inputs/main.json.rabl +9 -0
- data/app/views/api/v2/template_inputs/show.json.rabl +3 -0
- data/app/views/job_invocation_task_groups/_job_invocation_task_group.html.erb +31 -0
- data/app/views/job_invocation_task_groups/_job_invocation_task_groups.html.erb +3 -0
- data/app/views/job_invocations/_form.html.erb +102 -71
- data/app/views/job_invocations/_tab_overview.html.erb +5 -2
- data/app/views/job_invocations/index.html.erb +4 -4
- data/app/views/job_invocations/refresh.js.erb +2 -1
- data/app/views/job_invocations/show.html.erb +13 -2
- data/app/views/job_invocations/show.js.erb +1 -1
- data/app/views/job_templates/_custom_tabs.html.erb +16 -0
- data/app/views/templates/package_action.erb +1 -0
- data/app/views/templates/puppet_run_once.erb +1 -0
- data/app/views/templates/run_command.erb +1 -0
- data/app/views/templates/service_action.erb +1 -0
- data/config/routes.rb +15 -2
- data/db/migrate/20150923125825_add_job_invocation_task_group.rb +10 -0
- data/db/migrate/20151022105508_rename_last_task_id_column.rb +6 -0
- data/db/migrate/20151116105412_add_triggering_to_job_invocation.rb +10 -0
- data/db/migrate/20151120171100_add_effective_user_to_template_invocation.rb +5 -0
- data/db/migrate/20151124162300_create_job_template_effective_users.rb +13 -0
- data/db/migrate/20151203100824_add_description_to_job_invocation.rb +11 -0
- data/db/migrate/20151215114631_add_host_id_to_template_invocation.rb +29 -0
- data/db/migrate/20151217092555_migrate_to_task_groups.rb +16 -0
- data/foreman_remote_execution.gemspec +2 -1
- data/lib/foreman_remote_execution/engine.rb +30 -5
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/factories/foreman_remote_execution_factories.rb +5 -0
- data/test/functional/api/v2/job_invocations_controller_test.rb +3 -3
- data/test/functional/api/v2/template_inputs_controller_test.rb +61 -0
- data/test/unit/actions/run_hosts_job_test.rb +10 -3
- data/test/unit/concerns/host_extensions_test.rb +10 -6
- data/test/unit/job_invocation_composer_test.rb +229 -10
- data/test/unit/job_invocation_test.rb +27 -27
- data/test/unit/job_template_effective_user_test.rb +41 -0
- data/test/unit/job_template_test.rb +24 -0
- data/test/unit/remote_execution_provider_test.rb +39 -0
- metadata +42 -7
- data/app/models/job_invocation_api_composer.rb +0 -69
- data/test/unit/job_invocation_api_composer_test.rb +0 -143
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3de54e0a9f145a63c004e5e569305d4207167111
|
4
|
+
data.tar.gz: 9cea79ef9465ceb6b2164f3eb9b072c45753a0bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e0dde3c38bf68c68c6cadeb4b5c78d1d916be96b92e0dcd313bb1fb0f2113c8003b2e652c95c3dba8138afb680bb5c40fa631131aa5ff4bb589373f209107993
|
7
|
+
data.tar.gz: 9057f5eaad349a39352a6ecaeeee828978695e58c8f9b20fe10e4395c1ea60066b469bd776f7470cf69dee44a463407a1abca512c584108760143cc5f35afbbc
|
@@ -58,11 +58,6 @@ function job_invocation_form_binds() {
|
|
58
58
|
$('#job_template_' + $(this).val()).show();
|
59
59
|
});
|
60
60
|
|
61
|
-
$('input.trigger_mode_selector').on('click', function () {
|
62
|
-
$("#trigger_mode_future").hide();
|
63
|
-
$('#trigger_mode_' + $(this).val()).show();
|
64
|
-
});
|
65
|
-
|
66
61
|
$('select#job_invocation_job_name').on('change', refresh_execution_form);
|
67
62
|
|
68
63
|
$('button#refresh_execution_form').on('click', refresh_execution_form);
|
@@ -99,3 +94,51 @@ function job_invocation_refresh_data(){
|
|
99
94
|
function fetch_ids_of_hosts(attribute){
|
100
95
|
return $('div#hosts td.host_' + attribute + '[data-refresh_required="true"]').map(function() { return $(this).data('id') }).get();
|
101
96
|
}
|
97
|
+
|
98
|
+
function regenerate_description(thing) {
|
99
|
+
var fieldset = $(thing).closest('fieldset');
|
100
|
+
var dict = load_keys(fieldset);
|
101
|
+
var format = fieldset.find('#description_format').val();
|
102
|
+
fieldset.find('#description').val(String.format(format, dict));
|
103
|
+
}
|
104
|
+
|
105
|
+
function load_keys(parent) {
|
106
|
+
var dict = {};
|
107
|
+
var pattern = $(parent).find("#description_format").val();
|
108
|
+
var re = new RegExp("%\\{([^\\}]+)\\}", "gm");
|
109
|
+
var match = re.exec(pattern);
|
110
|
+
while(match != null) {
|
111
|
+
dict[match[1]] = $(parent).find("#" + match[1]).val();
|
112
|
+
match = re.exec(pattern);
|
113
|
+
}
|
114
|
+
dict['job_name'] = $('#job_invocation_job_name').val();
|
115
|
+
return dict;
|
116
|
+
}
|
117
|
+
|
118
|
+
function description_override(source) {
|
119
|
+
var description_format_container = $(source).closest('fieldset').find('#description_format_container');
|
120
|
+
var description_format = $(description_format_container).find('#description_format');
|
121
|
+
var old_value = $(source).val();
|
122
|
+
if($(source).is(':checked')) {
|
123
|
+
$(description_format_container).hide();
|
124
|
+
} else {
|
125
|
+
$(description_format_container).show();
|
126
|
+
}
|
127
|
+
$(source).val($(description_format).val());
|
128
|
+
$(description_format).val(old_value);
|
129
|
+
regenerate_description(description_format);
|
130
|
+
}
|
131
|
+
|
132
|
+
String.format = function (pattern, dict) {
|
133
|
+
if(pattern == undefined) {
|
134
|
+
return "";
|
135
|
+
}
|
136
|
+
for (var key in dict) {
|
137
|
+
var regEx = new RegExp("%\\{" + key + "\\}", "gm");
|
138
|
+
if(dict[key] == undefined) {
|
139
|
+
dict[key] = "%{" + key + "}"
|
140
|
+
}
|
141
|
+
pattern = pattern.replace(regEx, dict[key]);
|
142
|
+
}
|
143
|
+
return pattern;
|
144
|
+
};
|
@@ -6,9 +6,12 @@ module Api
|
|
6
6
|
include ::Foreman::Renderer
|
7
7
|
|
8
8
|
before_filter :find_optional_nested_object
|
9
|
+
before_filter :find_host, :only => %w{output}
|
9
10
|
before_filter :find_resource, :only => %w{show update destroy clone}
|
10
11
|
before_filter :validate_templates, :only => :create
|
11
12
|
|
13
|
+
wrap_parameters JobInvocation, :include => (JobInvocation.attribute_names + [:ssh])
|
14
|
+
|
12
15
|
api :GET, "/job_invocations/", N_("List job invocations")
|
13
16
|
param_group :search_and_pagination, ::Api::V2::BaseController
|
14
17
|
def index
|
@@ -23,42 +26,84 @@ module Api
|
|
23
26
|
def_param_group :job_invocation do
|
24
27
|
param :job_invocation, Hash, :required => true, :action_aware => true do
|
25
28
|
param :job_name, String, :required => true, :desc => N_("Job name")
|
26
|
-
param :
|
29
|
+
param :job_template_id, String, :required => false, :desc => N_("If using a specific template, the id of that template.")
|
27
30
|
param :targeting_type, String, :required => true, :desc => N_("Invocation type, one of %s") % Targeting::TYPES
|
28
|
-
param :inputs,
|
29
|
-
|
30
|
-
param :
|
31
|
+
param :inputs, Hash, :required => false, :desc => N_("Inputs to use")
|
32
|
+
param :ssh, Hash, :desc => N_("Ssh provider specific options") do
|
33
|
+
param :effective_user, String,
|
34
|
+
:required => false,
|
35
|
+
:desc => N_("What user should be used to run the script (using sudo-like mechanisms). Defaults to a template parameter or global setting.")
|
31
36
|
end
|
32
37
|
param :bookmark_id, Integer, :required => false
|
33
38
|
param :search_query, Integer, :required => false
|
34
39
|
end
|
35
40
|
end
|
36
41
|
|
37
|
-
api :POST, "/job_invocations/", N_("Create a job
|
42
|
+
api :POST, "/job_invocations/", N_("Create a job invocation")
|
38
43
|
param_group :job_invocation, :as => :create
|
39
44
|
def create
|
40
|
-
composer =
|
45
|
+
composer = JobInvocationComposer.from_api_params(job_invocation_params)
|
41
46
|
composer.save!
|
42
47
|
ForemanTasks.async_task(::Actions::RemoteExecution::RunHostsJob, composer.job_invocation)
|
43
48
|
@job_invocation = composer.job_invocation
|
44
49
|
process_response @job_invocation
|
45
50
|
end
|
46
51
|
|
52
|
+
api :GET, "/job_invocations/:id/hosts/:host_id", N_("Get output for a host")
|
53
|
+
param :id, :identifier, :required => true
|
54
|
+
param :host_id, :identifier, :required => true
|
55
|
+
param :since, String, :required => false
|
56
|
+
def output
|
57
|
+
task = @nested_obj.sub_task_for_host(@host)
|
58
|
+
refresh = task.pending?
|
59
|
+
since = params[:since].to_f if params[:since].present?
|
60
|
+
|
61
|
+
line_sets = task.main_action.live_output
|
62
|
+
line_sets = line_sets.drop_while { |o| o['timestamp'].to_f <= since } if since
|
63
|
+
|
64
|
+
if line_sets.blank?
|
65
|
+
render :json => {:refresh => refresh, :output => []}
|
66
|
+
else
|
67
|
+
render :json => {:refresh => refresh, :output => line_sets }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
47
71
|
private
|
48
72
|
|
73
|
+
def action_permission
|
74
|
+
case params[:action]
|
75
|
+
when 'output'
|
76
|
+
:view
|
77
|
+
else
|
78
|
+
super
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def find_host
|
83
|
+
@host = Host::Base.authorized(:view_hosts).find(params['host_id'])
|
84
|
+
rescue ActiveRecord::RecordNotFound
|
85
|
+
not_found({ :error => { :message => (_("Host with id '%{id}' was not found") % { :id => params['host_id'] }) } })
|
86
|
+
end
|
87
|
+
|
49
88
|
def validate_templates
|
50
89
|
templates = []
|
51
|
-
if
|
52
|
-
templates << JobTemplate.find(
|
90
|
+
if job_invocation_params[:job_template_id]
|
91
|
+
templates << JobTemplate.find(job_invocation_params[:job_template_id])
|
53
92
|
else
|
54
|
-
templates = JobTemplate.where(:job_name =>
|
93
|
+
templates = JobTemplate.where(:job_name => job_invocation_params[:job_name])
|
55
94
|
if templates.pluck(:provider_type).uniq.length != templates.length
|
56
|
-
raise Foreman::Exception, _("Duplicate remote execution providers found for specified Job, please specify a single
|
95
|
+
raise Foreman::Exception, _("Duplicate remote execution providers found for specified Job, please specify a single job_template_id.")
|
57
96
|
end
|
58
97
|
end
|
59
98
|
|
60
99
|
raise Foreman::Exception, _("No templates associated with specified Job Name") if templates.empty?
|
61
100
|
end
|
101
|
+
|
102
|
+
def job_invocation_params
|
103
|
+
job_invocation_params = params.fetch(:job_invocation, {}).dup
|
104
|
+
job_invocation_params.merge!(job_invocation_params.delete(:ssh)) if job_invocation_params.key?(:ssh)
|
105
|
+
job_invocation_params
|
106
|
+
end
|
62
107
|
end
|
63
108
|
end
|
64
109
|
end
|
@@ -11,6 +11,8 @@ module Api
|
|
11
11
|
|
12
12
|
before_filter :handle_template_upload, :only => [:create, :update]
|
13
13
|
|
14
|
+
wrap_parameters JobTemplate, :include => (JobTemplate.attribute_names + [:ssh])
|
15
|
+
|
14
16
|
api :GET, "/job_templates/", N_("List job templates")
|
15
17
|
api :GET, "/locations/:location_id/job_templates/", N_("List job templates per location")
|
16
18
|
api :GET, "/organizations/:organization_id/job_templates/", N_("List job templates per organization")
|
@@ -30,10 +32,17 @@ module Api
|
|
30
32
|
param :name, String, :required => true, :desc => N_("Template name")
|
31
33
|
param :job_name, String, :required => true, :desc => N_("Job name")
|
32
34
|
param :template, String, :required => true
|
33
|
-
param :provider_type,
|
35
|
+
param :provider_type, RemoteExecutionProvider.provider_names, :required => true, :desc => N_("Provider type")
|
34
36
|
param :snippet, :bool, :allow_nil => true
|
35
37
|
param :audit_comment, String, :allow_nil => true
|
36
38
|
param :locked, :bool, :desc => N_("Whether or not the template is locked for editing")
|
39
|
+
param :ssh, Hash, :desc => N_("Ssh provider specific options") do
|
40
|
+
param :effective_user, Hash, :desc => N_("Effective user options") do
|
41
|
+
param :value, String, :desc => N_("What user should be used to run the script (using sudo-like mechanisms)"), :allowed_nil => true
|
42
|
+
param :overridable, :bool, :desc => N_("Whether it should be allowed to override the effective user from the invocation form.")
|
43
|
+
param :current_user, :bool, :desc => N_("Whether the current user login should be used as the effective user")
|
44
|
+
end
|
45
|
+
end
|
37
46
|
param_group :taxonomies, ::Api::V2::BaseController
|
38
47
|
end
|
39
48
|
end
|
@@ -41,7 +50,7 @@ module Api
|
|
41
50
|
api :POST, "/job_templates/", N_("Create a job template")
|
42
51
|
param_group :job_template, :as => :create
|
43
52
|
def create
|
44
|
-
@job_template = JobTemplate.new(
|
53
|
+
@job_template = JobTemplate.new(job_template_params)
|
45
54
|
process_response @job_template.save
|
46
55
|
end
|
47
56
|
|
@@ -49,7 +58,7 @@ module Api
|
|
49
58
|
param :id, :identifier, :required => true
|
50
59
|
param_group :job_template
|
51
60
|
def update
|
52
|
-
process_response @job_template.update_attributes(
|
61
|
+
process_response @job_template.update_attributes(job_template_params)
|
53
62
|
end
|
54
63
|
|
55
64
|
api :GET, "/job_templates/revision"
|
@@ -77,7 +86,7 @@ module Api
|
|
77
86
|
def clone
|
78
87
|
@job_template = @job_template.clone
|
79
88
|
load_vars_from_template
|
80
|
-
@job_template.name =
|
89
|
+
@job_template.name = job_template_params[:name]
|
81
90
|
process_response @job_template.save
|
82
91
|
end
|
83
92
|
|
@@ -87,6 +96,12 @@ module Api
|
|
87
96
|
|
88
97
|
private
|
89
98
|
|
99
|
+
def job_template_params
|
100
|
+
job_template_params = params[:job_template].dup
|
101
|
+
effective_user_attributes = (job_template_params.delete(:ssh) || {}).fetch(:effective_user, {})
|
102
|
+
job_template_params.merge(:effective_user_attributes => effective_user_attributes)
|
103
|
+
end
|
104
|
+
|
90
105
|
def action_permission
|
91
106
|
case params[:action]
|
92
107
|
when 'clone'
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Api
|
2
|
+
module V2
|
3
|
+
class TemplateInputsController < ::Api::V2::BaseController
|
4
|
+
include ::Api::Version2
|
5
|
+
include ::Foreman::Renderer
|
6
|
+
|
7
|
+
before_filter :find_required_nested_object
|
8
|
+
before_filter :find_resource, :only => %w{show update destroy clone}
|
9
|
+
before_filter :normalize_options, :only => %w{create update}
|
10
|
+
|
11
|
+
api :GET, "/templates/:template_id/template_inputs", N_("List template inputs")
|
12
|
+
param :template_id, :identifier, :required => true
|
13
|
+
param_group :search_and_pagination, ::Api::V2::BaseController
|
14
|
+
def index
|
15
|
+
@template_inputs = nested_obj.template_inputs.search_for(*search_options).paginate(paginate_options)
|
16
|
+
end
|
17
|
+
|
18
|
+
api :GET, "/templates/:template_id/template_inputs/:id", N_("Show template input details")
|
19
|
+
param :template_id, :identifier, :required => true
|
20
|
+
param :id, :identifier, :required => true
|
21
|
+
def show
|
22
|
+
end
|
23
|
+
|
24
|
+
def_param_group :template_input do
|
25
|
+
param :template_input, Hash, :required => true, :action_aware => true do
|
26
|
+
param :name, String, :required => true, :desc => N_('Input name')
|
27
|
+
param :input_type, TemplateInput::TYPES.keys.map(&:to_s), :required => true, :desc => N_('Input type')
|
28
|
+
param :fact_name, String, :required => false, :desc => N_('Fact name, used when input type is fact')
|
29
|
+
param :variable_name, String, :required => false, :desc => N_('Variable name, used when input type is variable')
|
30
|
+
param :puppet_parameter_name, String, :required => false, :desc => N_('Puppet parameter name, used when input type is puppet_parameter')
|
31
|
+
param :options, Array, :required => false, :desc => N_('Selectable values for user inputs')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
api :POST, "/templates/:template_id/template_inputs/", N_("Create a template input")
|
36
|
+
param :template_id, :identifier, :required => true
|
37
|
+
param_group :template_input, :as => :create
|
38
|
+
def create
|
39
|
+
@template_input = TemplateInput.new(params[:template_input].merge(:template_id => @nested_obj.id))
|
40
|
+
process_response @template_input.save
|
41
|
+
end
|
42
|
+
|
43
|
+
api :DELETE, "/templates/:template_id/template_inputs/:id", N_("Delete a template input")
|
44
|
+
param :template_id, :identifier, :required => true
|
45
|
+
param :id, :identifier, :required => true
|
46
|
+
def destroy
|
47
|
+
process_response @template_input.destroy
|
48
|
+
end
|
49
|
+
|
50
|
+
api :PUT, "/templates/:template_id/template_inputs/:id", N_("Update a template input")
|
51
|
+
param :template_id, :identifier, :required => true
|
52
|
+
param :id, :identifier, :required => true
|
53
|
+
param_group :template_input
|
54
|
+
def update
|
55
|
+
process_response @template_input.update_attributes(params[:template_input])
|
56
|
+
end
|
57
|
+
|
58
|
+
def resource_name(nested_resource = nil)
|
59
|
+
nested_resource || 'template_input'
|
60
|
+
end
|
61
|
+
|
62
|
+
def controller_permission
|
63
|
+
'templates'
|
64
|
+
end
|
65
|
+
|
66
|
+
def action_permission
|
67
|
+
case params[:action]
|
68
|
+
when :create, :edit, :destroy
|
69
|
+
'edit'
|
70
|
+
else
|
71
|
+
super
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def normalize_options
|
78
|
+
if params[:template_input][:options].is_a?(Array)
|
79
|
+
params[:template_input][:options] = params[:template_input][:options].join("\n")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def resource_class
|
84
|
+
TemplateInput
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -1,9 +1,13 @@
|
|
1
1
|
class JobInvocationsController < ApplicationController
|
2
2
|
include Foreman::Controller::AutoCompleteSearch
|
3
3
|
|
4
|
+
before_filter :find_or_create_triggering, :only => [:create, :refresh]
|
5
|
+
|
4
6
|
def new
|
5
|
-
@composer = JobInvocationComposer.
|
7
|
+
@composer = JobInvocationComposer.from_ui_params(
|
6
8
|
:host_ids => params[:host_ids],
|
9
|
+
:job_invocation => {
|
10
|
+
},
|
7
11
|
:targeting => {
|
8
12
|
:targeting_type => Targeting::STATIC_TYPE,
|
9
13
|
:bookmark_id => params[:bookmark_id]
|
@@ -12,7 +16,7 @@ class JobInvocationsController < ApplicationController
|
|
12
16
|
|
13
17
|
def rerun
|
14
18
|
job_invocation = resource_base.find(params[:id])
|
15
|
-
@composer = JobInvocationComposer.
|
19
|
+
@composer = JobInvocationComposer.from_job_invocation(job_invocation)
|
16
20
|
|
17
21
|
if params[:failed_only]
|
18
22
|
host_ids = job_invocation.failed_host_ids
|
@@ -23,17 +27,11 @@ class JobInvocationsController < ApplicationController
|
|
23
27
|
end
|
24
28
|
|
25
29
|
def create
|
26
|
-
@composer = JobInvocationComposer.
|
27
|
-
action = ::Actions::RemoteExecution::RunHostsJob
|
30
|
+
@composer = JobInvocationComposer.from_ui_params(params)
|
28
31
|
if @composer.save
|
29
32
|
job_invocation = @composer.job_invocation
|
30
|
-
if job_invocation.
|
31
|
-
|
32
|
-
job_invocation.delay_options,
|
33
|
-
job_invocation
|
34
|
-
else
|
35
|
-
ForemanTasks.async_task(action, job_invocation)
|
36
|
-
end
|
33
|
+
job_invocation.generate_description! if job_invocation.description.blank?
|
34
|
+
@composer.triggering.trigger(::Actions::RemoteExecution::RunHostsJob, job_invocation)
|
37
35
|
redirect_to job_invocation_path(job_invocation)
|
38
36
|
else
|
39
37
|
render :action => 'new'
|
@@ -42,22 +40,22 @@ class JobInvocationsController < ApplicationController
|
|
42
40
|
|
43
41
|
def show
|
44
42
|
@job_invocation = resource_base.find(params[:id])
|
45
|
-
@auto_refresh = @job_invocation.
|
43
|
+
@auto_refresh = @job_invocation.task.try(:pending?)
|
46
44
|
hosts_base = @job_invocation.targeting.hosts.authorized(:view_hosts, Host)
|
47
45
|
@hosts = hosts_base.search_for(params[:search], :order => params[:order] || 'name ASC').paginate(:page => params[:page])
|
48
46
|
end
|
49
47
|
|
50
48
|
def index
|
51
|
-
@job_invocations = resource_base.search_for(params[:search]).paginate(:page => params[:page]).order('id DESC')
|
49
|
+
@job_invocations = resource_base.search_for(params[:search]).paginate(:page => params[:page]).with_task.order(params[:order] || 'job_invocations.id DESC')
|
52
50
|
end
|
53
51
|
|
54
52
|
# refreshes the form
|
55
53
|
def refresh
|
56
|
-
@composer = JobInvocationComposer.
|
54
|
+
@composer = JobInvocationComposer.from_ui_params(params)
|
57
55
|
end
|
58
56
|
|
59
57
|
def preview_hosts
|
60
|
-
composer = JobInvocationComposer.
|
58
|
+
composer = JobInvocationComposer.from_ui_params(params)
|
61
59
|
|
62
60
|
@hosts = composer.targeted_hosts.limit(Setting[:entries_per_page])
|
63
61
|
@additional = composer.targeted_hosts.count - Setting[:entries_per_page]
|
@@ -69,6 +67,10 @@ class JobInvocationsController < ApplicationController
|
|
69
67
|
|
70
68
|
private
|
71
69
|
|
70
|
+
def find_or_create_triggering
|
71
|
+
@triggering ||= ::ForemanTasks::Triggering.new_from_params(params[:triggering])
|
72
|
+
end
|
73
|
+
|
72
74
|
def action_permission
|
73
75
|
case params[:action]
|
74
76
|
when 'rerun'
|
@@ -10,7 +10,7 @@ module RemoteExecutionHelper
|
|
10
10
|
def job_invocation_chart(invocation)
|
11
11
|
options = { :class => 'statistics-pie small', :expandable => true, :border => 0, :show_title => true }
|
12
12
|
|
13
|
-
if (bulk_task = invocation.
|
13
|
+
if (bulk_task = invocation.task)
|
14
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
|
@@ -30,15 +30,15 @@ module RemoteExecutionHelper
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def job_invocation_status(invocation)
|
33
|
-
if invocation.
|
33
|
+
if invocation.task.blank?
|
34
34
|
_('Job not started yet 0%')
|
35
|
-
elsif invocation.
|
36
|
-
_('Job set to execute at %s') % invocation.
|
37
|
-
elsif invocation.
|
38
|
-
invocation.
|
35
|
+
elsif invocation.task.state == 'scheduled'
|
36
|
+
_('Job set to execute at %s') % invocation.task.start_at
|
37
|
+
elsif invocation.task.state == 'stopped' && invocation.task.result == 'error'
|
38
|
+
invocation.task.execution_plan.errors.map(&:message).join("\n")
|
39
39
|
else
|
40
|
-
label = invocation.
|
41
|
-
label + ' ' + (invocation.
|
40
|
+
label = invocation.task.pending ? _('Running') : _('Finished')
|
41
|
+
label + ' ' + (invocation.task.progress * 100).to_i.to_s + '%'
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
@@ -87,26 +87,27 @@ module RemoteExecutionHelper
|
|
87
87
|
|
88
88
|
def remote_execution_provider_for(task)
|
89
89
|
template_invocation = task.locks.where(:resource_type => 'TemplateInvocation').first.try(:resource) unless task.nil?
|
90
|
-
template_invocation.nil? ? _('N/A') :
|
90
|
+
template_invocation.nil? ? _('N/A') : template_invocation.template.provider.humanized_name
|
91
91
|
end
|
92
92
|
|
93
93
|
# rubocop:disable Metrics/AbcSize
|
94
94
|
def job_invocation_task_buttons(task)
|
95
|
+
job_invocation = task.task_groups.find { |group| group.class == JobInvocationTaskGroup }.job_invocation
|
95
96
|
buttons = []
|
96
97
|
buttons << link_to(_('Refresh'), {}, :class => 'btn btn-default', :title => _('Refresh this page'))
|
97
98
|
if authorized_for(hash_for_new_job_invocation_path)
|
98
|
-
buttons << link_to(_("Rerun"), rerun_job_invocation_path(:id =>
|
99
|
+
buttons << link_to(_("Rerun"), rerun_job_invocation_path(:id => job_invocation.id),
|
99
100
|
:class => "btn btn-default",
|
100
101
|
:title => _('Rerun the job'))
|
101
102
|
end
|
102
103
|
if authorized_for(hash_for_new_job_invocation_path)
|
103
|
-
buttons << link_to(_("Rerun failed"), rerun_job_invocation_path(:id =>
|
104
|
+
buttons << link_to(_("Rerun failed"), rerun_job_invocation_path(:id => job_invocation.id, :failed_only => 1),
|
104
105
|
:class => "btn btn-default",
|
105
106
|
:disabled => !task.sub_tasks.any? { |sub_task| task_failed?(sub_task) },
|
106
107
|
:title => _('Rerun on failed hosts'))
|
107
108
|
end
|
108
109
|
if authorized_for(:permission => :view_foreman_tasks, :auth_object => task)
|
109
|
-
buttons << link_to(_("
|
110
|
+
buttons << link_to(_("Job Task"), foreman_tasks_task_path(task),
|
110
111
|
:class => "btn btn-default",
|
111
112
|
:title => _('See the last task details'))
|
112
113
|
end
|
@@ -139,9 +140,9 @@ module RemoteExecutionHelper
|
|
139
140
|
end
|
140
141
|
|
141
142
|
def link_to_invocation_task_if_authorized(invocation)
|
142
|
-
if invocation.
|
143
|
+
if invocation.task.present? && invocation.task.state != 'scheduled'
|
143
144
|
link_to_if_authorized job_invocation_status(invocation),
|
144
|
-
hash_for_foreman_tasks_task_path(invocation.
|
145
|
+
hash_for_foreman_tasks_task_path(invocation.task).merge(:auth_object => invocation.task, :permission => :view_foreman_tasks)
|
145
146
|
else
|
146
147
|
job_invocation_status(invocation)
|
147
148
|
end
|
@@ -149,8 +150,8 @@ module RemoteExecutionHelper
|
|
149
150
|
|
150
151
|
def invocation_count(invocation, options = {})
|
151
152
|
options = { :unknown_string => 'N/A' }.merge(options)
|
152
|
-
if invocation.
|
153
|
-
(invocation.
|
153
|
+
if invocation.task.nil? || invocation.task.state != 'scheduled'
|
154
|
+
(invocation.task.try(:output) || {}).fetch(options[:output_key], options[:unknown_string])
|
154
155
|
else
|
155
156
|
options[:unknown_string]
|
156
157
|
end
|
@@ -176,4 +177,14 @@ module RemoteExecutionHelper
|
|
176
177
|
tab == :overview ? active : inactive
|
177
178
|
end
|
178
179
|
end
|
180
|
+
|
181
|
+
def time_ago(time)
|
182
|
+
if time.nil?
|
183
|
+
_('-')
|
184
|
+
else
|
185
|
+
content_tag :span, _("%s ago") % time_ago_in_words(time),
|
186
|
+
{ :'data-original-title' => time.try(:in_time_zone), :rel => 'twipsy' }
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
179
190
|
end
|
@@ -4,7 +4,11 @@ module Actions
|
|
4
4
|
class BindJobInvocation < ::Dynflow::Middleware
|
5
5
|
|
6
6
|
def delay(*args)
|
7
|
-
|
7
|
+
schedule_options, job_invocation = args
|
8
|
+
if !job_invocation.task_id.nil? && job_invocation.task_id != task.id
|
9
|
+
job_invocation = job_invocation.deep_clone
|
10
|
+
args = [schedule_options, job_invocation]
|
11
|
+
end
|
8
12
|
pass(*args).tap { bind(job_invocation) }
|
9
13
|
end
|
10
14
|
|
@@ -16,11 +20,11 @@ module Actions
|
|
16
20
|
private
|
17
21
|
|
18
22
|
def task
|
19
|
-
@task ||= ForemanTasks::Task::DynflowTask.
|
23
|
+
@task ||= ForemanTasks::Task::DynflowTask.where(:external_id => action.execution_plan_id).first!
|
20
24
|
end
|
21
25
|
|
22
26
|
def bind(job_invocation)
|
23
|
-
job_invocation.update_attribute :
|
27
|
+
job_invocation.update_attribute :task_id, task.id if job_invocation.task_id != task.id
|
24
28
|
end
|
25
29
|
|
26
30
|
end
|
@@ -2,20 +2,27 @@ module Actions
|
|
2
2
|
module RemoteExecution
|
3
3
|
class RunHostJob < Actions::EntryAction
|
4
4
|
|
5
|
+
middleware.do_not_use Dynflow::Middleware::Common::Transaction
|
5
6
|
include Actions::RemoteExecution::Helpers::LiveOutput
|
6
7
|
|
7
8
|
def resource_locks
|
8
9
|
:link
|
9
10
|
end
|
10
11
|
|
11
|
-
def plan(job_invocation, host, template_invocation, proxy
|
12
|
-
action_subject(host, :job_name => job_invocation.job_name)
|
12
|
+
def plan(job_invocation, host, template_invocation, proxy)
|
13
|
+
action_subject(host, :job_name => job_invocation.job_name, :description => job_invocation.description)
|
14
|
+
|
15
|
+
template_invocation.update_attribute :host_id, host.id
|
16
|
+
link!(job_invocation)
|
17
|
+
link!(template_invocation)
|
18
|
+
|
19
|
+
verify_permissions(host, template_invocation)
|
13
20
|
hostname = find_ip_or_hostname(host)
|
14
21
|
|
15
22
|
raise _("Could not use any template used in the job invocation") if template_invocation.blank?
|
16
23
|
|
17
|
-
settings =
|
18
|
-
|
24
|
+
settings = { :global_proxy => 'remote_execution_global_proxy',
|
25
|
+
:fallback_proxy => 'remote_execution_fallback_proxy' }
|
19
26
|
|
20
27
|
raise _("Could not use any proxy. Consider configuring %{global_proxy} " +
|
21
28
|
"or %{fallback_proxy} in settings") % settings if proxy.blank?
|
@@ -24,11 +31,8 @@ module Actions
|
|
24
31
|
script = renderer.render
|
25
32
|
raise _("Failed rendering template: %s") % renderer.error_message unless script
|
26
33
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
provider = template_invocation.template.provider_type.to_s
|
31
|
-
plan_action(RunProxyCommand, proxy, hostname, script, { :connection_options => connection_options }.merge(provider_settings(provider, host)))
|
34
|
+
provider = template_invocation.template.provider
|
35
|
+
plan_action(RunProxyCommand, proxy, hostname, script, provider.proxy_command_options(template_invocation, host))
|
32
36
|
plan_self
|
33
37
|
end
|
34
38
|
|
@@ -53,7 +57,9 @@ module Actions
|
|
53
57
|
end
|
54
58
|
|
55
59
|
def humanized_name
|
56
|
-
_('
|
60
|
+
_('%{description} on %{host}') % { :job_name => input[:job_name],
|
61
|
+
:host => input[:host][:name],
|
62
|
+
:description => input[:description].try(:capitalize) || input[:job_name] }
|
57
63
|
end
|
58
64
|
|
59
65
|
def find_ip_or_hostname(host)
|
@@ -70,13 +76,18 @@ module Actions
|
|
70
76
|
return host.fqdn
|
71
77
|
end
|
72
78
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
79
|
+
private
|
80
|
+
|
81
|
+
def verify_permissions(host, template_invocation)
|
82
|
+
raise _('User can not execute job on host %s') % host.name unless User.current.can?(:view_hosts, host)
|
83
|
+
raise _('User can not execute this job template') unless User.current.can?(:view_job_templates, template_invocation.template)
|
84
|
+
|
85
|
+
# we don't want to load all template_invocations to verify so we construct Authorizer object manually and set
|
86
|
+
# the base collection to current template
|
87
|
+
authorizer = Authorizer.new(User.current, :collection => [ template_invocation.id ])
|
88
|
+
raise _('User can not execute this job template on %s') % host.name unless authorizer.can?(:execute_template_invocation, template_invocation)
|
89
|
+
|
90
|
+
true
|
80
91
|
end
|
81
92
|
end
|
82
93
|
end
|