Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> 

# 

# This file is part of Ansible 

# 

# Ansible is free software: you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 3 of the License, or 

# (at your option) any later version. 

# 

# Ansible is distributed in the hope that it will be useful, 

# but WITHOUT ANY WARRANTY; without even the implied warranty of 

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

# GNU General Public License for more details. 

# 

# You should have received a copy of the GNU General Public License 

# along with Ansible. If not, see <http://www.gnu.org/licenses/>. 

 

# Make coding more python3-ish 

from __future__ import (absolute_import, division, print_function) 

__metaclass__ = type 

 

import os 

 

from ansible import constants as C 

from ansible.executor.task_queue_manager import TaskQueueManager 

from ansible.module_utils._text import to_native, to_text 

from ansible.playbook import Playbook 

from ansible.template import Templar 

from ansible.utils.helpers import pct_to_int 

from ansible.module_utils.parsing.convert_bool import boolean 

from ansible.utils.path import makedirs_safe 

from ansible.utils.ssh_functions import check_for_controlpersist 

 

try: 

from __main__ import display 

except ImportError: 

from ansible.utils.display import Display 

display = Display() 

 

 

class PlaybookExecutor: 

 

''' 

This is the primary class for executing playbooks, and thus the 

basis for bin/ansible-playbook operation. 

''' 

 

def __init__(self, playbooks, inventory, variable_manager, loader, options, passwords): 

self._playbooks = playbooks 

self._inventory = inventory 

self._variable_manager = variable_manager 

self._loader = loader 

self._options = options 

self.passwords = passwords 

self._unreachable_hosts = dict() 

 

57 ↛ 58line 57 didn't jump to line 58, because the condition on line 57 was never true if options.listhosts or options.listtasks or options.listtags or options.syntax: 

self._tqm = None 

else: 

self._tqm = TaskQueueManager(inventory=inventory, variable_manager=variable_manager, loader=loader, options=options, passwords=self.passwords) 

 

# Note: We run this here to cache whether the default ansible ssh 

# executable supports control persist. Sometime in the future we may 

# need to enhance this to check that ansible_ssh_executable specified 

# in inventory is also cached. We can't do this caching at the point 

# where it is used (in task_executor) because that is post-fork and 

# therefore would be discarded after every task. 

check_for_controlpersist(C.ANSIBLE_SSH_EXECUTABLE) 

 

def run(self): 

''' 

Run the given playbook, based on the settings in the play which 

may limit the runs to serialized groups, etc. 

''' 

 

result = 0 

entrylist = [] 

entry = {} 

try: 

for playbook_path in self._playbooks: 

pb = Playbook.load(playbook_path, variable_manager=self._variable_manager, loader=self._loader) 

# FIXME: move out of inventory self._inventory.set_playbook_basedir(os.path.realpath(os.path.dirname(playbook_path))) 

 

84 ↛ 85line 84 didn't jump to line 85, because the condition on line 84 was never true if self._tqm is None: # we are doing a listing 

entry = {'playbook': playbook_path} 

entry['plays'] = [] 

else: 

# make sure the tqm has callbacks loaded 

self._tqm.load_callbacks() 

self._tqm.send_callback('v2_playbook_on_start', pb) 

 

i = 1 

plays = pb.get_plays() 

display.vv(u'%d plays in %s' % (len(plays), to_text(playbook_path))) 

 

for play in plays: 

97 ↛ 98line 97 didn't jump to line 98, because the condition on line 97 was never true if play._included_path is not None: 

self._loader.set_basedir(play._included_path) 

else: 

self._loader.set_basedir(pb._basedir) 

 

# clear any filters which may have been applied to the inventory 

self._inventory.remove_restriction() 

 

# Create a temporary copy of the play here, so we can run post_validate 

# on it without the templating changes affecting the original object. 

# Doing this before vars_prompt to allow for using variables in prompt. 

all_vars = self._variable_manager.get_vars(play=play) 

templar = Templar(loader=self._loader, variables=all_vars) 

new_play = play.copy() 

new_play.post_validate(templar) 

 

113 ↛ 114line 113 didn't jump to line 114, because the condition on line 113 was never true if play.vars_prompt: 

for var in new_play.vars_prompt: 

vname = var['name'] 

prompt = var.get("prompt", vname) 

default = var.get("default", None) 

private = boolean(var.get("private", True)) 

confirm = boolean(var.get("confirm", False)) 

encrypt = var.get("encrypt", None) 

salt_size = var.get("salt_size", None) 

salt = var.get("salt", None) 

 

if vname not in self._variable_manager.extra_vars: 

if self._tqm: 

self._tqm.send_callback('v2_playbook_on_vars_prompt', vname, private, prompt, encrypt, confirm, salt_size, salt, default) 

play.vars[vname] = display.do_var_prompt(vname, private, prompt, encrypt, confirm, salt_size, salt, default) 

else: # we are either in --list-<option> or syntax check 

play.vars[vname] = default 

 

# Post validating again in case variables were entered in the prompt. 

all_vars = self._variable_manager.get_vars(play=play) 

templar = Templar(loader=self._loader, variables=all_vars) 

new_play.post_validate(templar) 

 

136 ↛ 137line 136 didn't jump to line 137, because the condition on line 136 was never true if self._options.syntax: 

continue 

 

139 ↛ 141line 139 didn't jump to line 141, because the condition on line 139 was never true if self._tqm is None: 

# we are just doing a listing 

entry['plays'].append(new_play) 

 

else: 

self._tqm._unreachable_hosts.update(self._unreachable_hosts) 

 

previously_failed = len(self._tqm._failed_hosts) 

previously_unreachable = len(self._tqm._unreachable_hosts) 

 

break_play = False 

# we are actually running plays 

batches = self._get_serialized_batches(new_play) 

152 ↛ 153line 152 didn't jump to line 153, because the condition on line 152 was never true if len(batches) == 0: 

self._tqm.send_callback('v2_playbook_on_play_start', new_play) 

self._tqm.send_callback('v2_playbook_on_no_hosts_matched') 

for batch in batches: 

# restrict the inventory to the hosts in the serialized batch 

self._inventory.restrict_to_hosts(batch) 

# and run it... 

result = self._tqm.run(play=play) 

 

# break the play if the result equals the special return code 

162 ↛ 163line 162 didn't jump to line 163, because the condition on line 162 was never true if result & self._tqm.RUN_FAILED_BREAK_PLAY != 0: 

result = self._tqm.RUN_FAILED_HOSTS 

break_play = True 

 

# check the number of failures here, to see if they're above the maximum 

# failure percentage allowed, or if any errors are fatal. If either of those 

# conditions are met, we break out, otherwise we only break out if the entire 

# batch failed 

failed_hosts_count = len(self._tqm._failed_hosts) + len(self._tqm._unreachable_hosts) - \ 

(previously_failed + previously_unreachable) 

 

173 ↛ 174line 173 didn't jump to line 174, because the condition on line 173 was never true if len(batch) == failed_hosts_count: 

break_play = True 

break 

 

# update the previous counts so they don't accumulate incorrectly 

# over multiple serial batches 

previously_failed += len(self._tqm._failed_hosts) - previously_failed 

previously_unreachable += len(self._tqm._unreachable_hosts) - previously_unreachable 

 

# save the unreachable hosts from this batch 

self._unreachable_hosts.update(self._tqm._unreachable_hosts) 

 

185 ↛ 186line 185 didn't jump to line 186, because the condition on line 185 was never true if break_play: 

break 

 

i = i + 1 # per play 

 

190 ↛ 191line 190 didn't jump to line 191, because the condition on line 190 was never true if entry: 

entrylist.append(entry) # per playbook 

 

# send the stats callback for this playbook 

194 ↛ 215line 194 didn't jump to line 215, because the condition on line 194 was never false if self._tqm is not None: 

195 ↛ 212line 195 didn't jump to line 212, because the condition on line 195 was never false if C.RETRY_FILES_ENABLED: 

retries = set(self._tqm._failed_hosts.keys()) 

retries.update(self._tqm._unreachable_hosts.keys()) 

retries = sorted(retries) 

199 ↛ 200line 199 didn't jump to line 200, because the condition on line 199 was never true if len(retries) > 0: 

if C.RETRY_FILES_SAVE_PATH: 

basedir = C.RETRY_FILES_SAVE_PATH 

elif playbook_path: 

basedir = os.path.dirname(os.path.abspath(playbook_path)) 

else: 

basedir = '~/' 

 

(retry_name, _) = os.path.splitext(os.path.basename(playbook_path)) 

filename = os.path.join(basedir, "%s.retry" % retry_name) 

if self._generate_retry_inventory(filename, retries): 

display.display("\tto retry, use: --limit @%s\n" % filename) 

 

self._tqm.send_callback('v2_playbook_on_stats', self._tqm._stats) 

 

# if the last result wasn't zero, break out of the playbook file name loop 

215 ↛ 216line 215 didn't jump to line 216, because the condition on line 215 was never true if result != 0: 

break 

 

218 ↛ 219line 218 didn't jump to line 219, because the condition on line 218 was never true if entrylist: 

return entrylist 

 

finally: 

222 ↛ 224line 222 didn't jump to line 224, because the condition on line 222 was never false if self._tqm is not None: 

self._tqm.cleanup() 

224 ↛ exit,   224 ↛ 2272 missed branches: 1) line 224 didn't return from function 'run', because the return on line 219 wasn't executed, 2) line 224 didn't jump to line 227, because the condition on line 224 was never false if self._loader: 

225 ↛ exitline 225 didn't return from function 'run', because the return on line 219 wasn't executed self._loader.cleanup_all_tmp_files() 

 

227 ↛ 228line 227 didn't jump to line 228, because the condition on line 227 was never true if self._options.syntax: 

display.display("No issues encountered") 

return result 

 

return result 

 

def _get_serialized_batches(self, play): 

''' 

Returns a list of hosts, subdivided into batches based on 

the serial size specified in the play. 

''' 

 

# make sure we have a unique list of hosts 

all_hosts = self._inventory.get_hosts(play.hosts) 

all_hosts_len = len(all_hosts) 

 

# the serial value can be listed as a scalar or a list of 

# scalars, so we make sure it's a list here 

serial_batch_list = play.serial 

246 ↛ 249line 246 didn't jump to line 249, because the condition on line 246 was never false if len(serial_batch_list) == 0: 

serial_batch_list = [-1] 

 

cur_item = 0 

serialized_batches = [] 

 

252 ↛ 277line 252 didn't jump to line 277, because the condition on line 252 was never false while len(all_hosts) > 0: 

# get the serial value from current item in the list 

serial = pct_to_int(serial_batch_list[cur_item], all_hosts_len) 

 

# if the serial count was not specified or is invalid, default to 

# a list of all hosts, otherwise grab a chunk of the hosts equal 

# to the current serial item size 

259 ↛ 263line 259 didn't jump to line 263, because the condition on line 259 was never false if serial <= 0: 

serialized_batches.append(all_hosts) 

break 

else: 

play_hosts = [] 

for x in range(serial): 

if len(all_hosts) > 0: 

play_hosts.append(all_hosts.pop(0)) 

 

serialized_batches.append(play_hosts) 

 

# increment the current batch list item number, and if we've hit 

# the end keep using the last element until we've consumed all of 

# the hosts in the inventory 

cur_item += 1 

if cur_item > len(serial_batch_list) - 1: 

cur_item = len(serial_batch_list) - 1 

 

return serialized_batches 

 

def _generate_retry_inventory(self, retry_path, replay_hosts): 

''' 

Called when a playbook run fails. It generates an inventory which allows 

re-running on ONLY the failed hosts. This may duplicate some variable 

information in group_vars/host_vars but that is ok, and expected. 

''' 

try: 

makedirs_safe(os.path.dirname(retry_path)) 

with open(retry_path, 'w') as fd: 

for x in replay_hosts: 

fd.write("%s\n" % x) 

except Exception as e: 

display.warning("Could not create retry file '%s'.\n\t%s" % (retry_path, to_native(e))) 

return False 

 

return True