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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/template_invocation.js +48 -5
  3. data/app/controllers/api/v2/job_invocations_controller.rb +55 -10
  4. data/app/controllers/api/v2/job_templates_controller.rb +19 -4
  5. data/app/controllers/api/v2/template_inputs_controller.rb +88 -0
  6. data/app/controllers/job_invocations_controller.rb +17 -15
  7. data/app/controllers/template_invocations_controller.rb +2 -0
  8. data/app/helpers/remote_execution_helper.rb +27 -16
  9. data/app/lib/actions/middleware/bind_job_invocation.rb +7 -3
  10. data/app/lib/actions/remote_execution/run_host_job.rb +28 -17
  11. data/app/lib/actions/remote_execution/run_hosts_job.rb +9 -6
  12. data/app/models/concerns/foreman_remote_execution/foreman_tasks_task_extensions.rb +1 -1
  13. data/app/models/concerns/foreman_remote_execution/foreman_tasks_triggering_extensions.rb +9 -0
  14. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +3 -1
  15. data/app/models/job_invocation.rb +48 -41
  16. data/app/models/job_invocation_composer.rb +205 -80
  17. data/app/models/job_invocation_task_group.rb +18 -0
  18. data/app/models/job_template.rb +25 -1
  19. data/app/models/job_template_effective_user.rb +23 -0
  20. data/app/models/remote_execution_provider.rb +25 -11
  21. data/app/models/setting/remote_execution.rb +6 -0
  22. data/app/models/ssh_execution_provider.rb +37 -0
  23. data/app/models/targeting.rb +13 -0
  24. data/app/models/template_input.rb +4 -1
  25. data/app/models/template_invocation.rb +23 -0
  26. data/app/views/api/v2/job_invocations/base.json.rabl +4 -0
  27. data/app/views/api/v2/job_invocations/index.json.rabl +1 -1
  28. data/app/views/api/v2/job_invocations/main.json.rabl +19 -0
  29. data/app/views/api/v2/job_invocations/show.json.rabl +0 -15
  30. data/app/views/api/v2/job_templates/base.json.rabl +1 -1
  31. data/app/views/api/v2/job_templates/index.json.rabl +1 -1
  32. data/app/views/api/v2/job_templates/main.json.rabl +5 -1
  33. data/app/views/api/v2/job_templates/show.json.rabl +4 -0
  34. data/app/views/api/v2/job_templates/update.json.rabl +3 -0
  35. data/app/views/api/v2/template_inputs/base.json.rabl +3 -0
  36. data/app/views/api/v2/template_inputs/create.json.rabl +3 -0
  37. data/app/views/api/v2/template_inputs/index.json.rabl +3 -0
  38. data/app/views/api/v2/template_inputs/main.json.rabl +9 -0
  39. data/app/views/api/v2/template_inputs/show.json.rabl +3 -0
  40. data/app/views/job_invocation_task_groups/_job_invocation_task_group.html.erb +31 -0
  41. data/app/views/job_invocation_task_groups/_job_invocation_task_groups.html.erb +3 -0
  42. data/app/views/job_invocations/_form.html.erb +102 -71
  43. data/app/views/job_invocations/_tab_overview.html.erb +5 -2
  44. data/app/views/job_invocations/index.html.erb +4 -4
  45. data/app/views/job_invocations/refresh.js.erb +2 -1
  46. data/app/views/job_invocations/show.html.erb +13 -2
  47. data/app/views/job_invocations/show.js.erb +1 -1
  48. data/app/views/job_templates/_custom_tabs.html.erb +16 -0
  49. data/app/views/templates/package_action.erb +1 -0
  50. data/app/views/templates/puppet_run_once.erb +1 -0
  51. data/app/views/templates/run_command.erb +1 -0
  52. data/app/views/templates/service_action.erb +1 -0
  53. data/config/routes.rb +15 -2
  54. data/db/migrate/20150923125825_add_job_invocation_task_group.rb +10 -0
  55. data/db/migrate/20151022105508_rename_last_task_id_column.rb +6 -0
  56. data/db/migrate/20151116105412_add_triggering_to_job_invocation.rb +10 -0
  57. data/db/migrate/20151120171100_add_effective_user_to_template_invocation.rb +5 -0
  58. data/db/migrate/20151124162300_create_job_template_effective_users.rb +13 -0
  59. data/db/migrate/20151203100824_add_description_to_job_invocation.rb +11 -0
  60. data/db/migrate/20151215114631_add_host_id_to_template_invocation.rb +29 -0
  61. data/db/migrate/20151217092555_migrate_to_task_groups.rb +16 -0
  62. data/foreman_remote_execution.gemspec +2 -1
  63. data/lib/foreman_remote_execution/engine.rb +30 -5
  64. data/lib/foreman_remote_execution/version.rb +1 -1
  65. data/test/factories/foreman_remote_execution_factories.rb +5 -0
  66. data/test/functional/api/v2/job_invocations_controller_test.rb +3 -3
  67. data/test/functional/api/v2/template_inputs_controller_test.rb +61 -0
  68. data/test/unit/actions/run_hosts_job_test.rb +10 -3
  69. data/test/unit/concerns/host_extensions_test.rb +10 -6
  70. data/test/unit/job_invocation_composer_test.rb +229 -10
  71. data/test/unit/job_invocation_test.rb +27 -27
  72. data/test/unit/job_template_effective_user_test.rb +41 -0
  73. data/test/unit/job_template_test.rb +24 -0
  74. data/test/unit/remote_execution_provider_test.rb +39 -0
  75. metadata +42 -7
  76. data/app/models/job_invocation_api_composer.rb +0 -69
  77. 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: e5f866fe37c11fd1081d685e3715e64658354b5f
4
- data.tar.gz: c5fddb603234b2930bc94226411a982039c5996e
3
+ metadata.gz: 3de54e0a9f145a63c004e5e569305d4207167111
4
+ data.tar.gz: 9cea79ef9465ceb6b2164f3eb9b072c45753a0bb
5
5
  SHA512:
6
- metadata.gz: 64da2786680750ada9d49b6459b63bc6a2440f1c0226de78a08c50b1b632c8b7583cd1d76c569dc1e7e1836318229cfbf1811671517f8aca0be0539188403961
7
- data.tar.gz: f707c970f5b3fb9fccbe4e65d4321a449a6980b5b9f3999266ab49e4d238eb56f4644319bb12e88e691e85819570d3fc7267c62e6ec851a7f40fa58d58b6a912
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 :template_id, String, :required => false, :desc => N_("If using a specific template, the id of that template.")
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, Array, :required => false, :desc => N_("Inputs to use") do
29
- param :name, String, :required => true
30
- param :value, String, :required => true
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 template")
42
+ api :POST, "/job_invocations/", N_("Create a job invocation")
38
43
  param_group :job_invocation, :as => :create
39
44
  def create
40
- composer = JobInvocationApiComposer.new(JobInvocation.new, User.current, params[:job_invocation])
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 params[:job_invocation][:template_id]
52
- templates << JobTemplate.find(params[:job_invocation][:template_id])
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 => params[:job_invocation][: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 template_id.")
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, String, :required => true, :desc => N_("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(params[:job_template])
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(params[:job_template])
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 = params[: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.new.compose_from_params(
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.new.compose_from_invocation(job_invocation)
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.new.compose_from_params(params)
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.trigger_mode == :future
31
- ForemanTasks.delay action,
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.last_task.try(:pending?)
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.new.compose_from_params(params)
54
+ @composer = JobInvocationComposer.from_ui_params(params)
57
55
  end
58
56
 
59
57
  def preview_hosts
60
- composer = JobInvocationComposer.new.compose_from_params(params)
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'
@@ -1,4 +1,6 @@
1
1
  class TemplateInvocationsController < ApplicationController
2
+ include Foreman::Controller::AutoCompleteSearch
3
+
2
4
  def controller_permission
3
5
  'job_invocations'
4
6
  end
@@ -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.last_task)
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.last_task.blank?
33
+ if invocation.task.blank?
34
34
  _('Job not started yet 0%')
35
- elsif invocation.last_task.state == 'scheduled'
36
- _('Job set to execute at %s') % invocation.last_task.start_at
37
- elsif invocation.last_task.state == 'stopped' && invocation.last_task.result == 'error'
38
- invocation.last_task.execution_plan.errors.map(&:message).join("\n")
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.last_task.pending ? _('Running') : _('Finished')
41
- label + ' ' + (invocation.last_task.progress * 100).to_i.to_s + '%'
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') : _(RemoteExecutionProvider.provider_for(template_invocation.template.provider_type))
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 => task.locks.where(:resource_type => 'JobInvocation').first.resource),
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 => task.locks.where(:resource_type => 'JobInvocation').first.resource, :failed_only => 1),
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(_("Last Job Task"), foreman_tasks_task_path(task),
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.last_task.present? && invocation.last_task.state != 'scheduled'
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.last_task).merge(:auth_object => invocation.last_task, :permission => :view_foreman_tasks)
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.last_task.nil? || invocation.last_task.state != 'scheduled'
153
- (invocation.last_task.try(:output) || {}).fetch(options[:output_key], options[:unknown_string])
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
- _schedule_options, job_invocation = args
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.find_by_external_id!(action.execution_plan_id)
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 :last_task_id, task.id if job_invocation.last_task_id != task.id
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, connection_options = {})
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 = { :global_proxy => 'remote_execution_global_proxy',
18
- :fallback_proxy => 'remote_execution_fallback_proxy' }
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
- link!(job_invocation)
28
- link!(template_invocation)
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
- _('Run %{job_name} on %{host}') % { :job_name => input[:job_name], :host => input[:host][:name] }
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
- def provider_settings(provider, host)
74
- case provider
75
- when 'Ssh'
76
- { :ssh_user => host.params['remote_execution_ssh_user'] || Setting[:remote_execution_ssh_user] }
77
- else
78
- {}
79
- end
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