foreman_remote_execution 0.1.2 → 0.2.1

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