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