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.
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