foreman_remote_execution 0.1.1 → 0.1.2
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 +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
|