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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/.rubocop_todo.yml +0 -6
- data/app/assets/javascripts/template_input.js +5 -0
- data/app/assets/javascripts/template_invocation.js +44 -12
- data/app/controllers/api/v2/foreign_input_sets_controller.rb +80 -0
- data/app/controllers/api/v2/job_invocations_controller.rb +28 -14
- data/app/controllers/api/v2/job_templates_controller.rb +24 -20
- data/app/controllers/api/v2/template_inputs_controller.rb +10 -7
- data/app/controllers/job_invocations_controller.rb +18 -6
- data/app/controllers/job_templates_controller.rb +14 -4
- data/app/controllers/template_invocations_controller.rb +5 -3
- data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +1 -1
- data/app/helpers/concerns/foreman_remote_execution/job_templates_extensions.rb +19 -0
- data/app/helpers/remote_execution_helper.rb +88 -39
- data/app/lib/actions/remote_execution/run_host_job.rb +11 -8
- data/app/lib/actions/remote_execution/run_hosts_job.rb +5 -2
- data/app/lib/actions/remote_execution/run_proxy_command.rb +9 -4
- data/app/models/concerns/foreman_remote_execution/errors_flattener.rb +2 -2
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +2 -2
- data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +1 -1
- data/app/models/concerns/foreman_remote_execution/template_extensions.rb +9 -1
- data/app/models/foreign_input_set.rb +49 -0
- data/app/models/host_status/execution_status.rb +50 -5
- data/app/models/input_template_renderer.rb +52 -7
- data/app/models/job_invocation.rb +89 -32
- data/app/models/job_invocation_composer.rb +71 -55
- data/app/models/job_template.rb +43 -7
- data/app/models/remote_execution_provider.rb +1 -1
- data/app/models/setting/remote_execution.rb +7 -7
- data/app/models/ssh_execution_provider.rb +1 -1
- data/app/models/targeting.rb +1 -1
- data/app/models/template_invocation.rb +9 -4
- data/app/views/api/v2/foreign_input_sets/base.json.rabl +3 -0
- data/app/views/api/v2/foreign_input_sets/create.json.rabl +3 -0
- data/app/views/api/v2/foreign_input_sets/index.json.rabl +3 -0
- data/app/views/api/v2/foreign_input_sets/main.json.rabl +5 -0
- data/app/views/api/v2/foreign_input_sets/show.json.rabl +3 -0
- data/app/views/api/v2/job_invocations/base.json.rabl +10 -1
- data/app/views/api/v2/job_invocations/main.json.rabl +14 -1
- data/app/views/api/v2/job_templates/base.json.rabl +1 -1
- data/app/views/api/v2/job_templates/main.json.rabl +9 -1
- data/app/views/api/v2/job_templates/show.json.rabl +0 -10
- data/app/views/api/v2/template_inputs/main.json.rabl +2 -1
- data/app/views/job_invocation_task_groups/_job_invocation_task_group.html.erb +1 -1
- data/app/views/job_invocations/_description_fields.html.erb +4 -0
- data/app/views/job_invocations/_form.html.erb +73 -91
- data/app/views/job_invocations/_host_actions_td.html.erb +2 -2
- data/app/views/job_invocations/_host_name_td.html.erb +7 -0
- data/app/views/job_invocations/_tab_hosts.html.erb +6 -6
- data/app/views/job_invocations/_tab_overview.html.erb +3 -3
- data/app/views/job_invocations/index.html.erb +6 -4
- data/app/views/job_invocations/refresh.js.erb +1 -0
- data/app/views/job_invocations/show.html.erb +1 -1
- data/app/views/job_invocations/show.js.erb +6 -3
- data/app/views/job_templates/_custom_tabs.html.erb +24 -14
- data/app/views/job_templates/index.html.erb +3 -1
- data/app/views/template_inputs/_foreign_input_set_form.html.erb +12 -0
- data/app/views/template_inputs/_form.html.erb +11 -12
- data/app/views/template_invocations/show.html.erb +2 -2
- data/app/views/templates/package_action.erb +2 -2
- data/app/views/templates/puppet_run_once.erb +3 -3
- data/app/views/templates/run_command.erb +3 -3
- data/app/views/templates/service_action.erb +2 -2
- data/app/views/unattended/snippets/_remote_execution_ssh_keys.erb +1 -1
- data/config/routes.rb +5 -3
- data/db/migrate/20150616080015_create_template_input.rb +1 -1
- data/db/migrate/20150708133241_add_targeting.rb +7 -7
- data/db/migrate/20150708133242_add_invocation.rb +2 -2
- data/db/migrate/20150708133305_add_template_invocation.rb +6 -6
- data/db/migrate/20151215114631_add_host_id_to_template_invocation.rb +3 -3
- data/db/migrate/20151217092555_migrate_to_task_groups.rb +1 -1
- data/db/migrate/20160108134600_create_template_input_sets.rb +16 -0
- data/db/migrate/20160108141144_make_job_name_default_to_something.rb +9 -0
- data/db/migrate/20160111113032_upcase_ssh_feature.rb +19 -0
- data/db/migrate/20160113161916_add_run_host_job_task_id_to_template_invocation.rb +6 -0
- data/db/migrate/20160113162007_expand_all_template_invocations.rb +45 -0
- data/db/migrate/20160114120200_rename_job_categories.rb +20 -0
- data/db/migrate/20160114125628_rename_job_name_to_job_category.rb +19 -0
- data/db/seeds.d/60-ssh_proxy_feature.rb +1 -1
- data/db/seeds.d/80-provision_templates.rb +2 -2
- data/db/seeds.d/90-bookmarks.rb +19 -0
- data/doc/plugins/graphviz.rb +5 -5
- data/doc/plugins/plantuml.rb +6 -6
- data/doc/plugins/tags.rb +4 -4
- data/foreman_remote_execution.gemspec +3 -4
- data/lib/foreman_remote_execution/engine.rb +12 -9
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/factories/foreman_remote_execution_factories.rb +11 -9
- data/test/functional/api/v2/foreign_input_sets_controller_test.rb +63 -0
- data/test/functional/api/v2/job_invocations_controller_test.rb +45 -13
- data/test/functional/api/v2/job_templates_controller_test.rb +6 -6
- data/test/functional/api/v2/template_inputs_controller_test.rb +3 -3
- data/test/unit/actions/run_hosts_job_test.rb +3 -4
- data/test/unit/actions/run_proxy_command_test.rb +7 -7
- data/test/unit/concerns/host_extensions_test.rb +1 -1
- data/test/unit/execution_task_status_mapper_test.rb +93 -0
- data/test/unit/input_template_renderer_test.rb +182 -9
- data/test/unit/job_invocation_composer_test.rb +144 -168
- data/test/unit/job_invocation_test.rb +67 -15
- data/test/unit/job_template_effective_user_test.rb +2 -2
- data/test/unit/job_template_test.rb +36 -12
- data/test/unit/remote_execution_provider_test.rb +6 -6
- data/test/unit/targeting_test.rb +2 -2
- metadata +27 -21
- data/app/models/concerns/foreman_remote_execution/template_relations.rb +0 -10
- data/app/views/job_invocations/_host_provider_td.html.erb +0 -3
- 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(
|
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.
|
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('
|
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
|
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
35
|
+
render
|
36
|
+
ensure
|
37
|
+
@preview = old_preview
|
31
38
|
end
|
32
39
|
|
33
40
|
def input(name)
|
34
|
-
|
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 = {
|
6
|
-
|
7
|
-
|
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 :
|
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 :
|
14
|
-
validates_associated :targeting, :
|
22
|
+
validates :job_category, :presence => true
|
23
|
+
validates_associated :targeting, :all_template_invocations
|
15
24
|
|
16
|
-
scoped_search :on => :
|
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
|
-
|
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
|
-
|
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.
|
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 =>
|
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
|
-
|
123
|
+
failed_template_invocations.map(&:host_id)
|
71
124
|
end
|
72
125
|
|
73
126
|
def failed_hosts
|
74
|
-
|
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
|
138
|
+
def pattern_template_invocation_for_host(host)
|
90
139
|
providers = available_providers(host)
|
91
140
|
providers.each do |provider|
|
92
|
-
|
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
|
-
|
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 =
|
117
|
-
input_names = template_invocation.template.
|
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(:
|
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
|