foreman_remote_execution 0.1.2 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/.rubocop_todo.yml +0 -6
  4. data/app/assets/javascripts/template_input.js +5 -0
  5. data/app/assets/javascripts/template_invocation.js +44 -12
  6. data/app/controllers/api/v2/foreign_input_sets_controller.rb +80 -0
  7. data/app/controllers/api/v2/job_invocations_controller.rb +28 -14
  8. data/app/controllers/api/v2/job_templates_controller.rb +24 -20
  9. data/app/controllers/api/v2/template_inputs_controller.rb +10 -7
  10. data/app/controllers/job_invocations_controller.rb +18 -6
  11. data/app/controllers/job_templates_controller.rb +14 -4
  12. data/app/controllers/template_invocations_controller.rb +5 -3
  13. data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +1 -1
  14. data/app/helpers/concerns/foreman_remote_execution/job_templates_extensions.rb +19 -0
  15. data/app/helpers/remote_execution_helper.rb +88 -39
  16. data/app/lib/actions/remote_execution/run_host_job.rb +11 -8
  17. data/app/lib/actions/remote_execution/run_hosts_job.rb +5 -2
  18. data/app/lib/actions/remote_execution/run_proxy_command.rb +9 -4
  19. data/app/models/concerns/foreman_remote_execution/errors_flattener.rb +2 -2
  20. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +2 -2
  21. data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +1 -1
  22. data/app/models/concerns/foreman_remote_execution/template_extensions.rb +9 -1
  23. data/app/models/foreign_input_set.rb +49 -0
  24. data/app/models/host_status/execution_status.rb +50 -5
  25. data/app/models/input_template_renderer.rb +52 -7
  26. data/app/models/job_invocation.rb +89 -32
  27. data/app/models/job_invocation_composer.rb +71 -55
  28. data/app/models/job_template.rb +43 -7
  29. data/app/models/remote_execution_provider.rb +1 -1
  30. data/app/models/setting/remote_execution.rb +7 -7
  31. data/app/models/ssh_execution_provider.rb +1 -1
  32. data/app/models/targeting.rb +1 -1
  33. data/app/models/template_invocation.rb +9 -4
  34. data/app/views/api/v2/foreign_input_sets/base.json.rabl +3 -0
  35. data/app/views/api/v2/foreign_input_sets/create.json.rabl +3 -0
  36. data/app/views/api/v2/foreign_input_sets/index.json.rabl +3 -0
  37. data/app/views/api/v2/foreign_input_sets/main.json.rabl +5 -0
  38. data/app/views/api/v2/foreign_input_sets/show.json.rabl +3 -0
  39. data/app/views/api/v2/job_invocations/base.json.rabl +10 -1
  40. data/app/views/api/v2/job_invocations/main.json.rabl +14 -1
  41. data/app/views/api/v2/job_templates/base.json.rabl +1 -1
  42. data/app/views/api/v2/job_templates/main.json.rabl +9 -1
  43. data/app/views/api/v2/job_templates/show.json.rabl +0 -10
  44. data/app/views/api/v2/template_inputs/main.json.rabl +2 -1
  45. data/app/views/job_invocation_task_groups/_job_invocation_task_group.html.erb +1 -1
  46. data/app/views/job_invocations/_description_fields.html.erb +4 -0
  47. data/app/views/job_invocations/_form.html.erb +73 -91
  48. data/app/views/job_invocations/_host_actions_td.html.erb +2 -2
  49. data/app/views/job_invocations/_host_name_td.html.erb +7 -0
  50. data/app/views/job_invocations/_tab_hosts.html.erb +6 -6
  51. data/app/views/job_invocations/_tab_overview.html.erb +3 -3
  52. data/app/views/job_invocations/index.html.erb +6 -4
  53. data/app/views/job_invocations/refresh.js.erb +1 -0
  54. data/app/views/job_invocations/show.html.erb +1 -1
  55. data/app/views/job_invocations/show.js.erb +6 -3
  56. data/app/views/job_templates/_custom_tabs.html.erb +24 -14
  57. data/app/views/job_templates/index.html.erb +3 -1
  58. data/app/views/template_inputs/_foreign_input_set_form.html.erb +12 -0
  59. data/app/views/template_inputs/_form.html.erb +11 -12
  60. data/app/views/template_invocations/show.html.erb +2 -2
  61. data/app/views/templates/package_action.erb +2 -2
  62. data/app/views/templates/puppet_run_once.erb +3 -3
  63. data/app/views/templates/run_command.erb +3 -3
  64. data/app/views/templates/service_action.erb +2 -2
  65. data/app/views/unattended/snippets/_remote_execution_ssh_keys.erb +1 -1
  66. data/config/routes.rb +5 -3
  67. data/db/migrate/20150616080015_create_template_input.rb +1 -1
  68. data/db/migrate/20150708133241_add_targeting.rb +7 -7
  69. data/db/migrate/20150708133242_add_invocation.rb +2 -2
  70. data/db/migrate/20150708133305_add_template_invocation.rb +6 -6
  71. data/db/migrate/20151215114631_add_host_id_to_template_invocation.rb +3 -3
  72. data/db/migrate/20151217092555_migrate_to_task_groups.rb +1 -1
  73. data/db/migrate/20160108134600_create_template_input_sets.rb +16 -0
  74. data/db/migrate/20160108141144_make_job_name_default_to_something.rb +9 -0
  75. data/db/migrate/20160111113032_upcase_ssh_feature.rb +19 -0
  76. data/db/migrate/20160113161916_add_run_host_job_task_id_to_template_invocation.rb +6 -0
  77. data/db/migrate/20160113162007_expand_all_template_invocations.rb +45 -0
  78. data/db/migrate/20160114120200_rename_job_categories.rb +20 -0
  79. data/db/migrate/20160114125628_rename_job_name_to_job_category.rb +19 -0
  80. data/db/seeds.d/60-ssh_proxy_feature.rb +1 -1
  81. data/db/seeds.d/80-provision_templates.rb +2 -2
  82. data/db/seeds.d/90-bookmarks.rb +19 -0
  83. data/doc/plugins/graphviz.rb +5 -5
  84. data/doc/plugins/plantuml.rb +6 -6
  85. data/doc/plugins/tags.rb +4 -4
  86. data/foreman_remote_execution.gemspec +3 -4
  87. data/lib/foreman_remote_execution/engine.rb +12 -9
  88. data/lib/foreman_remote_execution/version.rb +1 -1
  89. data/test/factories/foreman_remote_execution_factories.rb +11 -9
  90. data/test/functional/api/v2/foreign_input_sets_controller_test.rb +63 -0
  91. data/test/functional/api/v2/job_invocations_controller_test.rb +45 -13
  92. data/test/functional/api/v2/job_templates_controller_test.rb +6 -6
  93. data/test/functional/api/v2/template_inputs_controller_test.rb +3 -3
  94. data/test/unit/actions/run_hosts_job_test.rb +3 -4
  95. data/test/unit/actions/run_proxy_command_test.rb +7 -7
  96. data/test/unit/concerns/host_extensions_test.rb +1 -1
  97. data/test/unit/execution_task_status_mapper_test.rb +93 -0
  98. data/test/unit/input_template_renderer_test.rb +182 -9
  99. data/test/unit/job_invocation_composer_test.rb +144 -168
  100. data/test/unit/job_invocation_test.rb +67 -15
  101. data/test/unit/job_template_effective_user_test.rb +2 -2
  102. data/test/unit/job_template_test.rb +36 -12
  103. data/test/unit/remote_execution_provider_test.rb +6 -6
  104. data/test/unit/targeting_test.rb +2 -2
  105. metadata +27 -21
  106. data/app/models/concerns/foreman_remote_execution/template_relations.rb +0 -10
  107. data/app/views/job_invocations/_host_provider_td.html.erb +0 -3
  108. data/app/views/job_templates/auto_complete_job_name.json.erb +0 -3
@@ -1,7 +1,7 @@
1
1
  module ForemanRemoteExecution
2
2
  module ErrorsFlattener
3
3
  def flattened_validation_exception
4
- ActiveRecord::RecordNotSaved.new(I18n.t("activerecord.errors.messages.record_invalid", :errors => flattened_errors.join(', ')))
4
+ ActiveRecord::RecordNotSaved.new(I18n.t('activerecord.errors.messages.record_invalid', :errors => flattened_errors.join(', ')))
5
5
  end
6
6
 
7
7
  def flattened_errors
@@ -25,7 +25,7 @@ module ForemanRemoteExecution
25
25
  def invalid_objects_for_attribute(attribute)
26
26
  if self.respond_to?(attribute)
27
27
  invalid_object = self.public_send(attribute)
28
- if invalid_object.is_a? Enumerable
28
+ if invalid_object.respond_to? :each_with_index
29
29
  invalid_object.select { |o| o.respond_to?(:invalid?) && o.invalid? }
30
30
  else
31
31
  [invalid_object]
@@ -9,7 +9,7 @@ module ForemanRemoteExecution
9
9
  alias_method_chain :params, :remote_execution
10
10
 
11
11
  has_many :targeting_hosts, :dependent => :destroy, :foreign_key => 'host_id'
12
-
12
+ has_many :template_invocations, :dependent => :destroy, :foreign_key => 'host_id'
13
13
  has_one :execution_status_object, :class_name => 'HostStatus::ExecutionStatus', :foreign_key => 'host_id'
14
14
 
15
15
  scoped_search :in => :execution_status_object, :on => :status, :rename => :'execution_status',
@@ -57,7 +57,7 @@ module ForemanRemoteExecution
57
57
  end
58
58
 
59
59
  def remote_execution_ssh_keys
60
- remote_execution_proxies('Ssh').values.flatten.uniq.map { |proxy| proxy.pubkey }.compact.uniq
60
+ remote_execution_proxies('SSH').values.flatten.uniq.map { |proxy| proxy.pubkey }.compact.uniq
61
61
  end
62
62
 
63
63
  def drop_execution_interface_cache
@@ -11,7 +11,7 @@ module ForemanRemoteExecution
11
11
  end
12
12
 
13
13
  def update_pubkey
14
- return unless has_feature?('Ssh')
14
+ return unless has_feature?('SSH')
15
15
  key = ::ProxyAPI::RemoteExecutionSSH.new(:url => url).pubkey
16
16
  self.update_attribute(:pubkey, key) if key
17
17
  key
@@ -3,8 +3,16 @@ module ForemanRemoteExecution
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
+ # autosave => true is required so the changes of inputs are saved even if template was not changed
7
+ has_many :template_inputs, :dependent => :destroy, :foreign_key => 'template_id', :autosave => true
8
+ has_many :foreign_input_sets, :dependent => :destroy, :foreign_key => 'template_id', :autosave => true
9
+
10
+ def template_inputs_with_foreign(templates_stack = [])
11
+ self.template_inputs.to_a + foreign_input_sets.map { |set| set.inputs(templates_stack) }.flatten
12
+ end
6
13
  accepts_nested_attributes_for :template_inputs, :allow_destroy => true
7
- attr_accessible :template_inputs_attributes
14
+ accepts_nested_attributes_for :foreign_input_sets, :allow_destroy => true
15
+ attr_accessible :template_inputs_attributes, :foreign_input_sets_attributes
8
16
  end
9
17
 
10
18
  # create or overwrite instance methods...
@@ -0,0 +1,49 @@
1
+ class ForeignInputSet < ActiveRecord::Base
2
+ class CircularDependencyError < Foreman::Exception
3
+ end
4
+
5
+ attr_accessible :template_id, :target_template_id, :include_all, :include, :exclude
6
+
7
+ belongs_to :template
8
+ belongs_to :target_template, :class_name => 'Template'
9
+
10
+ scoped_search :on => :target_template_id, :complete_value => true
11
+
12
+ validates :target_template, :presence => true
13
+ validate :check_circular_dependency
14
+
15
+ def inputs(templates_stack = [])
16
+ return [] unless target_template
17
+ if templates_stack.include?(target_template)
18
+ raise CircularDependencyError.new(N_("Circular dependency detected in foreign input set '%{template}' -> '%{target_template}'. Templates stack: %{templates_stack}"),
19
+ :template => template.name, :target_template => target_template.name, :templates_stack => templates_stack.map(&:name).inspect)
20
+ end
21
+ inputs = target_template.template_inputs_with_foreign(templates_stack + [target_template])
22
+ unless self.include_all?
23
+ inputs = inputs.select { |input| included_names.include?(input.name) }
24
+ end
25
+ inputs = inputs.reject { |input| excluded_names.include?(input.name) }
26
+ return inputs
27
+ end
28
+
29
+ def included_names
30
+ comma_separated_names(self.include)
31
+ end
32
+
33
+ def excluded_names
34
+ comma_separated_names(self.exclude)
35
+ end
36
+
37
+ private
38
+
39
+ def comma_separated_names(value)
40
+ value.to_s.split(',').map(&:strip)
41
+ end
42
+
43
+ def check_circular_dependency
44
+ self.inputs
45
+ true
46
+ rescue CircularDependencyError => e
47
+ self.errors.add :base, e.message
48
+ end
49
+ end
@@ -1,17 +1,21 @@
1
1
  class HostStatus::ExecutionStatus < HostStatus::Status
2
+ # execution finished successfully
2
3
  OK = 0
4
+ # execution finished with error
3
5
  ERROR = 1
6
+ # execution hasn't started yet, either scheduled to future or executor didn't create task yet
7
+ QUEUED = 2
8
+ # execution is in progress, dynflow task was created
9
+ RUNNING = 3
10
+ # mapping to string representation
11
+ STATUS_NAMES = { OK => 'succeeded', ERROR => 'failed', QUEUED => 'queued', RUNNING => 'running' }
4
12
 
5
13
  def relevant?
6
14
  execution_tasks.present?
7
15
  end
8
16
 
9
17
  def to_status(options = {})
10
- if last_stopped_task.nil? || last_stopped_task.result == 'success'
11
- OK
12
- else
13
- ERROR
14
- end
18
+ ExecutionTaskStatusMapper.new(last_stopped_task).status
15
19
  end
16
20
 
17
21
  def to_global(options = {})
@@ -37,6 +41,47 @@ class HostStatus::ExecutionStatus < HostStatus::Status
37
41
  end
38
42
  end
39
43
 
44
+ class ExecutionTaskStatusMapper
45
+ attr_accessor :task
46
+
47
+ def self.sql_conditions_for(status)
48
+ status = STATUS_NAMES.key(status) if status.is_a?(String)
49
+
50
+ case status
51
+ when OK
52
+ [ "state = 'stopped' AND result = 'success'" ]
53
+ when ERROR
54
+ [ "state = 'stopped' AND (result = 'error' OR result = 'warning')" ]
55
+ when QUEUED
56
+ [ "state = 'scheduled' OR state IS NULL" ]
57
+ when RUNNING
58
+ [ "state <> 'stopped'" ]
59
+ else
60
+ [ '1 = 0' ]
61
+ end
62
+ end
63
+
64
+ def initialize(task)
65
+ self.task = task
66
+ end
67
+
68
+ def status
69
+ if task.nil? || task.state == 'scheduled'
70
+ QUEUED
71
+ elsif task.state == 'stopped' && 'success' == task.result
72
+ OK
73
+ elsif task.pending?
74
+ RUNNING
75
+ else
76
+ ERROR
77
+ end
78
+ end
79
+
80
+ def status_label
81
+ STATUS_NAMES[status]
82
+ end
83
+ end
84
+
40
85
  private
41
86
 
42
87
  def last_stopped_task
@@ -4,19 +4,25 @@ class InputTemplateRenderer
4
4
 
5
5
  include UnattendedHelper
6
6
 
7
- attr_accessor :template, :host, :invocation, :error_message
7
+ attr_accessor :template, :host, :invocation, :input_values, :error_message
8
8
 
9
9
  # takes template object that should be rendered
10
10
  # host and template invocation arguments are optional
11
11
  # so we can render values based on parameters, facts or user inputs
12
- def initialize(template, host = nil, invocation = nil)
12
+ def initialize(template, host = nil, invocation = nil, input_values = nil, preview = false, templates_stack = [])
13
+ raise Foreman::Exception, N_('Recursive rendering of templates detected') if templates_stack.include?(template)
14
+
13
15
  @host = host
14
16
  @invocation = invocation
15
17
  @template = template
18
+ @input_values = input_values
19
+ @preview = preview
20
+ @templates_stack = templates_stack + [template]
16
21
  end
17
22
 
18
23
  def render
19
- render_safe(@template.template, ::Foreman::Renderer::ALLOWED_HELPERS + [ :input ], :host => @host)
24
+ @template.validate_unique_inputs!
25
+ render_safe(@template.template, ::Foreman::Renderer::ALLOWED_HELPERS + [ :input, :render_template ], :host => @host)
20
26
  rescue => e
21
27
  self.error_message ||= _('error during rendering: %s') % e.message
22
28
  Rails.logger.debug e.to_s + "\n" + e.backtrace.join("\n")
@@ -24,14 +30,16 @@ class InputTemplateRenderer
24
30
  end
25
31
 
26
32
  def preview
33
+ old_preview = @preview
27
34
  @preview = true
28
- output = render
29
- @preview = false
30
- output
35
+ render
36
+ ensure
37
+ @preview = old_preview
31
38
  end
32
39
 
33
40
  def input(name)
34
- input = @template.template_inputs.where(:name => name.to_s).first || @template.template_inputs.detect { |i| i.name == name.to_s }
41
+ return @input_values[name.to_s] if @input_values
42
+ input = find_by_name(@template.template_inputs_with_foreign, name)
35
43
  if input
36
44
  @preview ? input.preview(self) : input.value(self)
37
45
  else
@@ -40,7 +48,44 @@ class InputTemplateRenderer
40
48
  end
41
49
  end
42
50
 
51
+ def render_template(template_name, input_values = {}, options = {})
52
+ options.assert_valid_keys(:with_foreign_input_set)
53
+ with_foreign_input_set = options.fetch(:with_foreign_input_set, true)
54
+ template = JobTemplate.authorized(:view_job_templates).where(:name => template_name).first
55
+ unless template
56
+ self.error_message = _('included template \'%s\' not found') % template_name
57
+ raise error_message
58
+ end
59
+ if with_foreign_input_set
60
+ input_values = foreign_input_set_values(template, input_values)
61
+ end
62
+ included_renderer = self.class.new(template, host, invocation, input_values.with_indifferent_access, @preview, @templates_stack)
63
+ out = included_renderer.render
64
+ if included_renderer.error_message
65
+ self.error_message = included_renderer.error_message
66
+ raise error_message
67
+ else
68
+ out
69
+ end
70
+ end
71
+
72
+ def foreign_input_set_values(target_template, overrides = {})
73
+ input_set = @template.foreign_input_sets.where(:target_template_id => target_template).first
74
+ return overrides if input_set.nil?
75
+
76
+ inputs_to_generate = input_set.inputs.map(&:name) - overrides.keys.map(&:to_s)
77
+ included_renderer = self.class.new(input_set.target_template, host, invocation, nil, @preview, @templates_stack)
78
+ input_values = inputs_to_generate.inject(HashWithIndifferentAccess.new) do |hash, input_name|
79
+ hash.merge(input_name => included_renderer.input(input_name))
80
+ end
81
+ input_values.merge(overrides)
82
+ end
83
+
43
84
  def logger
44
85
  Rails.logger
45
86
  end
87
+
88
+ def find_by_name(collection, name)
89
+ collection.detect { |i| i.name == name.to_s }
90
+ end
46
91
  end
@@ -2,20 +2,28 @@ class JobInvocation < ActiveRecord::Base
2
2
  include Authorizable
3
3
 
4
4
  include ForemanRemoteExecution::ErrorsFlattener
5
- FLATTENED_ERRORS_MAPPING = { :template_invocations => lambda do |template_invocation|
6
- _("template") + " #{template_invocation.template.name}"
7
- end }
5
+ FLATTENED_ERRORS_MAPPING = {
6
+ :pattern_template_invocations => lambda do |template_invocation|
7
+ _('template') + " #{template_invocation.template.name}"
8
+ end
9
+ }
8
10
 
9
11
  belongs_to :targeting, :dependent => :destroy
10
- has_many :template_invocations, :inverse_of => :job_invocation, :dependent => :destroy
12
+ has_many :all_template_invocations, :inverse_of => :job_invocation, :dependent => :destroy, :class_name => 'TemplateInvocation'
13
+ if Rails::VERSION::MAJOR >= 4
14
+ has_many :template_invocations, -> { where('host_id IS NOT NULL') }, :inverse_of => :job_invocation
15
+ has_many :pattern_template_invocations, -> { where('host_id IS NULL') }, :inverse_of => :job_invocation, :class_name => 'TemplateInvocation'
16
+ else
17
+ has_many :template_invocations, :conditions => 'host_id IS NOT NULL', :inverse_of => :job_invocation
18
+ has_many :pattern_template_invocations, :conditions => 'host_id IS NULL', :inverse_of => :job_invocation, :class_name => 'TemplateInvocation'
19
+ end
11
20
 
12
21
  validates :targeting, :presence => true
13
- validates :job_name, :presence => true
14
- validates_associated :targeting, :template_invocations
22
+ validates :job_category, :presence => true
23
+ validates_associated :targeting, :all_template_invocations
15
24
 
16
- scoped_search :on => :job_name
17
-
18
- scoped_search :in => :recurring_logic, :on => 'id', :rename => 'recurring_logic.id', :auto_complete => true
25
+ scoped_search :on => :job_category, :complete_value => true
26
+ scoped_search :on => :description # FIXME No auto complete because of https://github.com/wvanbergen/scoped_search/issues/138
19
27
 
20
28
  delegate :bookmark, :resolved?, :to => :targeting, :allow_nil => true
21
29
 
@@ -27,55 +35,96 @@ class JobInvocation < ActiveRecord::Base
27
35
  belongs_to :task_group, :class_name => 'JobInvocationTaskGroup'
28
36
 
29
37
  has_many :tasks, :through => :task_group, :class_name => 'ForemanTasks::Task'
30
-
31
- scoped_search :on => [:job_name], :complete_value => true
38
+ has_many :template_invocation_tasks, :through => :template_invocations,
39
+ :class_name => 'ForemanTasks::Task',
40
+ :source => 'run_host_job_task'
32
41
 
33
42
  scoped_search :in => :task, :on => :started_at, :rename => 'started_at', :complete_value => true
43
+ scoped_search :in => :task, :on => :start_at, :rename => 'start_at', :complete_value => true
34
44
  scoped_search :in => :task, :on => :ended_at, :rename => 'ended_at', :complete_value => true
45
+ scoped_search :in => :task, :on => :state, :rename => 'status', :ext_method => :search_by_status,
46
+ :only_explicit => true, :complete_value => Hash[HostStatus::ExecutionStatus::STATUS_NAMES.values.map { |v| [v, v] }]
35
47
 
36
48
  belongs_to :triggering, :class_name => 'ForemanTasks::Triggering'
37
49
  has_one :recurring_logic, :through => :triggering, :class_name => 'ForemanTasks::RecurringLogic'
38
50
 
39
- scope :with_task, -> { joins('LEFT JOIN foreman_tasks_tasks ON foreman_tasks_tasks.id = job_invocations.task_id') }
51
+ if Rails::VERSION::MAJOR >= 4
52
+ scope :with_task, -> { references(:task) }
53
+ else
54
+ scope :with_task, -> { joins('LEFT JOIN foreman_tasks_tasks ON foreman_tasks_tasks.id = job_invocations.task_id') }
55
+ end
56
+
57
+ scoped_search :in => :recurring_logic, :on => 'id', :rename => 'recurring_logic.id', :auto_complete => true
58
+
59
+ scoped_search :in => :recurring_logic, :on => 'id', :rename => 'recurring',
60
+ :ext_method => :search_by_recurring_logic, :only_explicit => true,
61
+ :complete_value => { :true => true, :false => false }
40
62
 
41
63
  default_scope -> { order('job_invocations.id DESC') }
42
64
 
65
+ validates_lengths_from_database :only => [:description]
66
+
43
67
  attr_accessor :start_before, :description_format
44
68
  attr_writer :start_at
45
69
 
70
+ delegate :start_at, :to => :task, :allow_nil => true
71
+
72
+ def self.search_by_status(key, operator, value)
73
+ conditions = HostStatus::ExecutionStatus::ExecutionTaskStatusMapper.sql_conditions_for(value)
74
+ conditions[0] = "NOT (#{conditions[0]})" if operator == '<>'
75
+ { :conditions => sanitize_sql_for_conditions(conditions), :include => :task }
76
+ end
77
+
78
+ def self.search_by_recurring_logic(key, operator, value)
79
+ reucurring = value == 'true'
80
+ reucurring = !reucurring if operator == '<>'
81
+ not_operator = reucurring ? 'NOT' : ''
82
+
83
+ { :conditions => sanitize_sql_for_conditions(["foreman_tasks_recurring_logics.id IS #{not_operator} NULL"]), :joins => :recurring_logic }
84
+ end
85
+
86
+ def status
87
+ HostStatus::ExecutionStatus::ExecutionTaskStatusMapper.new(task).status
88
+ end
89
+
90
+ def status_label
91
+ HostStatus::ExecutionStatus::ExecutionTaskStatusMapper.new(task).status_label
92
+ end
93
+
94
+ # returns progress in percents
95
+ def progress
96
+ queued? ? 0 : (task.progress * 100).to_i
97
+ end
98
+
99
+ def queued?
100
+ status == HostStatus::ExecutionStatus::QUEUED
101
+ end
102
+
46
103
  def deep_clone
47
104
  JobInvocationComposer.from_job_invocation(self).job_invocation.tap do |invocation|
48
105
  invocation.task_group = JobInvocationTaskGroup.new.tap(&:save!)
49
106
  invocation.triggering = self.triggering
50
107
  invocation.description_format = self.description_format
51
108
  invocation.description = self.description
52
- invocation.template_invocations = self.template_invocations.map(&:deep_clone)
109
+ invocation.pattern_template_invocations = self.pattern_template_invocations.map(&:deep_clone)
53
110
  invocation.save!
54
111
  end
55
112
  end
56
113
 
57
114
  def to_action_input
58
- { :id => id, :name => job_name }
59
- end
60
-
61
- def template_invocation_tasks
62
- sub_tasks.for_action_types('Actions::RemoteExecution::RunHostJob')
115
+ { :id => id, :name => job_category }
63
116
  end
64
117
 
65
118
  def failed_template_invocation_tasks
66
- template_invocation_tasks.where(:result => 'warning')
119
+ template_invocation_tasks.where(:result => [ 'error', 'warning' ])
67
120
  end
68
121
 
69
122
  def failed_host_ids
70
- locks_for_resource(failed_template_invocation_tasks, 'Host::Managed').map(&:resource_id)
123
+ failed_template_invocations.map(&:host_id)
71
124
  end
72
125
 
73
126
  def failed_hosts
74
- locks_for_resource(failed_template_invocation_tasks, 'Host::Managed').map(&:resource)
75
- end
76
-
77
- def locks_for_resource(tasks, resource_type)
78
- tasks.map { |task| task.locks.where(:resource_type => resource_type).first }.compact
127
+ failed_template_invocations.includes(:host).map(&:host)
79
128
  end
80
129
 
81
130
  def total_hosts_count
@@ -86,10 +135,10 @@ class JobInvocation < ActiveRecord::Base
86
135
  end
87
136
  end
88
137
 
89
- def template_invocation_for_host(host)
138
+ def pattern_template_invocation_for_host(host)
90
139
  providers = available_providers(host)
91
140
  providers.each do |provider|
92
- template_invocations.each do |template_invocation|
141
+ pattern_template_invocations.each do |template_invocation|
93
142
  if template_invocation.template.provider_type == provider
94
143
  return template_invocation
95
144
  end
@@ -103,7 +152,7 @@ class JobInvocation < ActiveRecord::Base
103
152
  end
104
153
 
105
154
  def sub_task_for_host(host)
106
- sub_tasks.joins(:locks).where("#{ForemanTasks::Lock.table_name}.resource_type" => 'Host::Managed', "#{ForemanTasks::Lock.table_name}.resource_id" => host.id).first
155
+ template_invocations.where(:host => host.id).first.try(:run_host_job_task)
107
156
  end
108
157
 
109
158
  def output(host)
@@ -113,16 +162,24 @@ class JobInvocation < ActiveRecord::Base
113
162
 
114
163
  def generate_description!
115
164
  key_re = /%\{([^\}]+)\}/
116
- template_invocation = template_invocations.first
117
- input_names = template_invocation.template.template_input_names
165
+ template_invocation = pattern_template_invocations.first
166
+ input_names = template_invocation.template.template_inputs_with_foreign(&:name)
118
167
  hash_base = Hash.new { |hash, key| hash[key] = "%{#{key}}" }
119
168
  input_hash = hash_base.merge Hash[input_names.zip(template_invocation.input_values.pluck(:value))]
120
- input_hash.update(:job_name => job_name)
169
+ input_hash.update(:job_category => job_category)
170
+ input_hash.update(:template_name => template_invocation.template.name)
121
171
  description_format.scan(key_re) { |key| input_hash[key.first] }
122
172
  self.description = description_format
123
173
  input_hash.each do |k, v|
124
- self.description.gsub!(Regexp.new("%\{#{k}\}"), v)
174
+ self.description.gsub!(Regexp.new("%\{#{k}\}"), v || '')
125
175
  end
176
+ self.description = self.description[0..(JobInvocation.columns_hash['description'].limit - 1)]
126
177
  save!
127
178
  end
179
+
180
+ private
181
+
182
+ def failed_template_invocations
183
+ template_invocations.joins(:run_host_job_task).where("#{ForemanTasks::Task.table_name}.result" => ['error', 'warning'])
184
+ end
128
185
  end