-
Notifications
You must be signed in to change notification settings - Fork 900
/
Copy pathplaybook_runner.rb
213 lines (179 loc) · 6.84 KB
/
playbook_runner.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
class ManageIQ::Providers::EmbeddedAnsible::AutomationManager::PlaybookRunner < ::Job
DEFAULT_EXECUTION_TTL = 10 # minutes
# options are job table columns, including options column which is the playbook context info
def self.create_job(options)
super(options.with_indifferent_access)
end
def minimize_indirect
@minimize_indirect = true if @minimize_indirect.nil?
@minimize_indirect
end
def current_job_timeout(_timeout_adjustment = 1)
@execution_ttl ||=
(options[:execution_ttl].present? ? options[:execution_ttl].try(:to_i) : DEFAULT_EXECUTION_TTL) * 60
end
def start
time = Time.zone.now
update_attributes(:started_on => time)
miq_task.update_attributes(:started_on => time)
my_signal(false, :create_job_template)
end
def create_job_template
set_status('creating job template')
raw_job_template = playbook.raw_create_job_template(options)
options[:job_template_ref] = raw_job_template.id
save!
my_signal(minimize_indirect, :launch_ansible_tower_job)
rescue => err
_log.log_backtrace(err)
my_signal(minimize_indirect, :post_ansible_run, err.message, 'error')
end
def translate_credentials!(launch_options)
%i[credential vault_credential cloud_credential network_credential].each do |cred_type|
credential_id = launch_options.delete("#{cred_type}_id".to_sym)
next if credential_id.blank?
launch_options[cred_type] = Authentication.find(credential_id).native_ref
end
end
LAUNCH_OPTIONS_KEYS = %i[
become_enabled
cloud_credential_id
credential_id
extra_vars
limit
network_credential_id
vault_credential_id
verbosity
].freeze
def launch_ansible_tower_job
set_status('launching tower job')
launch_options = options.slice(*LAUNCH_OPTIONS_KEYS)
launch_options[:hosts] = hosts_array(options[:hosts])
translate_credentials!(launch_options)
tower_job = ManageIQ::Providers::EmbeddedAnsible::AutomationManager::Job.create_job(temp_configuration_script, launch_options)
options[:tower_job_id] = tower_job.id
self.name = "#{name}, Job ID: #{tower_job.id}"
miq_task.update_attributes(:name => name)
save!
my_signal(false, :poll_ansible_tower_job_status, 10)
rescue => err
_log.log_backtrace(err)
my_signal(minimize_indirect, :post_ansible_run, err.message, 'error')
end
def poll_ansible_tower_job_status(interval)
set_status('waiting for tower job to complete')
tower_job_status = ansible_job.raw_status
if tower_job_status.completed?
ansible_job.refresh_ems
log_stdout(tower_job_status)
if tower_job_status.succeeded?
my_signal(minimize_indirect, :post_ansible_run, 'Playbook ran successfully', 'ok')
else
my_signal(minimize_indirect, :post_ansible_run, 'Ansible engine returned an error for the job', 'error')
end
else
interval = 60 if interval > 60
my_signal(false, :poll_ansible_tower_job_status, interval * 2, :deliver_on => Time.now.utc + interval)
end
rescue => err
_log.log_backtrace(err)
my_signal(minimize_indirect, :post_ansible_run, err.message, 'error')
end
def post_ansible_run(message, status)
save_playbook_set_stats
jt_not_deleted = !delete_job_template
message = "#{message}; Cleanup encountered error" if jt_not_deleted
my_signal(true, :finish, message, status)
end
def playbook
ManageIQ::Providers::EmbeddedAnsible::AutomationManager::Playbook.find(options[:playbook_id])
end
def ansible_job
ManageIQ::Providers::EmbeddedAnsible::AutomationManager::Job.find(options[:tower_job_id])
end
def set_status(message, status = "ok")
_log.info(message)
super
end
alias_method :initializing, :dispatch_start
alias_method :finish, :process_finished
alias_method :abort_job, :process_abort
alias_method :cancel, :process_cancel
alias_method :error, :process_error
private
attr_writer :minimize_indirect
def load_transitions
self.state ||= 'initialize'
{
:initializing => {'initialize' => 'waiting_to_start'},
:start => {'waiting_to_start' => 'running'},
:create_job_template => {'running' => 'job_template'},
:launch_ansible_tower_job => {'job_template' => 'ansible_job'},
:poll_ansible_tower_job_status => {'ansible_job' => 'ansible_job'},
:post_ansible_run => {'job_template' => 'ansible_done', 'ansible_job' => 'ansible_done'},
:finish => {'*' => 'finished'},
:abort_job => {'*' => 'aborting'},
:cancel => {'*' => 'canceling'},
:error => {'*' => '*'}
}
end
def my_signal(no_queue, action, *args, deliver_on: nil)
if no_queue
signal(action, *args)
else
queue_signal(action, *args, :deliver_on => deliver_on)
end
end
def queue_signal(*args, deliver_on: nil)
priority = options[:priority] || MiqQueue::NORMAL_PRIORITY
MiqQueue.put(
:class_name => self.class.name,
:method_name => "signal",
:instance_id => id,
:priority => priority,
:role => 'embedded_ansible',
:args => args,
:deliver_on => deliver_on
)
end
def temp_configuration_script
ManageIQ::Providers::EmbeddedAnsible::AutomationManager::ConfigurationScript.new(
:name => playbook.name,
:manager => playbook.manager,
:manager_ref => options[:job_template_ref],
:parent_id => playbook.id,
:variables => {}
)
end
def delete_job_template
return true unless options[:job_template_ref]
temp_configuration_script.raw_delete_in_provider
rescue => err
# log the error but do not treat the playbook running as failure
_log.log_backtrace(err)
false
end
def log_stdout(tower_job_status)
return unless %(on_error always).include?(options[:log_output])
return if options[:log_output] == 'on_error' && tower_job_status.succeeded?
_log.info("Stdout from playbook #{playbook.name}: #{ansible_job.raw_stdout('txt_download')}")
rescue => err
_log.error("Failed to get stdout from playbook #{playbook.name}")
_log.log_backtrace(err)
end
def save_playbook_set_stats
#
# save playbook set_stats data into MiqTask#task_results for future usage
#
miq_task.update(:task_results => {'ansible_stats' => ansible_job.raw_stdout('json').dig(-1, 'event_data', 'artifact_data')})
end
# Duplicated from ServiceAnsiblePlaybook
# TODO: Deduplicate all of this logic
def use_default_inventory?(hosts)
hosts.blank? || hosts == 'localhost'
end
def hosts_array(hosts_string)
return ["localhost"] if use_default_inventory?(hosts_string)
hosts_string.split(',').map(&:strip).delete_blanks
end
end