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

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

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

import os 

 

from ansible.errors import AnsibleError, AnsibleParserError, AnsibleAssertionError 

from ansible.module_utils.six import iteritems, binary_type, text_type 

from ansible.playbook.attribute import FieldAttribute 

from ansible.playbook.base import Base 

from ansible.playbook.become import Become 

from ansible.playbook.conditional import Conditional 

from ansible.playbook.helpers import load_list_of_blocks 

from ansible.playbook.role.metadata import RoleMetadata 

from ansible.playbook.taggable import Taggable 

from ansible.plugins.loader import get_all_plugin_loaders 

from ansible.utils.vars import combine_vars 

 

 

__all__ = ['Role', 'hash_params'] 

 

# TODO: this should be a utility function, but can't be a member of 

# the role due to the fact that it would require the use of self 

# in a static method. This is also used in the base class for 

# strategies (ansible/plugins/strategy/__init__.py) 

 

 

def hash_params(params): 

""" 

Construct a data structure of parameters that is hashable. 

 

This requires changing any mutable data structures into immutable ones. 

We chose a frozenset because role parameters have to be unique. 

 

.. warning:: this does not handle unhashable scalars. Two things 

mitigate that limitation: 

 

1) There shouldn't be any unhashable scalars specified in the yaml 

2) Our only choice would be to return an error anyway. 

""" 

# Any container is unhashable if it contains unhashable items (for 

# instance, tuple() is a Hashable subclass but if it contains a dict, it 

# cannot be hashed) 

62 ↛ 91line 62 didn't jump to line 91, because the condition on line 62 was never false if isinstance(params, collections.Container) and not isinstance(params, (text_type, binary_type)): 

if isinstance(params, collections.Mapping): 

try: 

# Optimistically hope the contents are all hashable 

new_params = frozenset(params.items()) 

except TypeError: 

new_params = set() 

for k, v in params.items(): 

# Hash each entry individually 

new_params.update((k, hash_params(v))) 

new_params = frozenset(new_params) 

 

74 ↛ 86line 74 didn't jump to line 86, because the condition on line 74 was never false elif isinstance(params, (collections.Set, collections.Sequence)): 

try: 

# Optimistically hope the contents are all hashable 

new_params = frozenset(params) 

except TypeError: 

new_params = set() 

for v in params: 

# Hash each entry individually 

new_params.update(hash_params(v)) 

new_params = frozenset(new_params) 

else: 

# This is just a guess. 

new_params = frozenset(params) 

return new_params 

 

# Note: We do not handle unhashable scalars but our only choice would be 

# to raise an error there anyway. 

return frozenset((params,)) 

 

 

class Role(Base, Become, Conditional, Taggable): 

 

_delegate_to = FieldAttribute(isa='string') 

_delegate_facts = FieldAttribute(isa='bool', default=False) 

 

def __init__(self, play=None, from_files=None): 

self._role_name = None 

self._role_path = None 

self._role_params = dict() 

self._loader = None 

 

self._metadata = None 

self._play = play 

self._parents = [] 

self._dependencies = [] 

self._task_blocks = [] 

self._handler_blocks = [] 

self._default_vars = dict() 

self._role_vars = dict() 

self._had_task_run = dict() 

self._completed = dict() 

 

if from_files is None: 

from_files = {} 

self._from_files = from_files 

 

super(Role, self).__init__() 

 

def __repr__(self): 

return self.get_name() 

 

def get_name(self): 

return self._role_name 

 

@staticmethod 

def load(role_include, play, parent_role=None, from_files=None): 

 

131 ↛ 133line 131 didn't jump to line 133, because the condition on line 131 was never false if from_files is None: 

from_files = {} 

try: 

# The ROLE_CACHE is a dictionary of role names, with each entry 

# containing another dictionary corresponding to a set of parameters 

# specified for a role as the key and the Role() object itself. 

# We use frozenset to make the dictionary hashable. 

 

params = role_include.get_role_params() 

140 ↛ 142line 140 didn't jump to line 142, because the condition on line 140 was never false if role_include.when is not None: 

params['when'] = role_include.when 

142 ↛ 144line 142 didn't jump to line 144, because the condition on line 142 was never false if role_include.tags is not None: 

params['tags'] = role_include.tags 

144 ↛ 146line 144 didn't jump to line 146, because the condition on line 144 was never false if from_files is not None: 

params['from_files'] = from_files 

146 ↛ 147line 146 didn't jump to line 147, because the condition on line 146 was never true if role_include.vars: 

params['vars'] = role_include.vars 

hashed_params = hash_params(params) 

149 ↛ 150line 149 didn't jump to line 150, because the condition on line 149 was never true if role_include.role in play.ROLE_CACHE: 

for (entry, role_obj) in iteritems(play.ROLE_CACHE[role_include.role]): 

if hashed_params == entry: 

if parent_role: 

role_obj.add_parent(parent_role) 

return role_obj 

 

r = Role(play=play, from_files=from_files) 

r._load_role_data(role_include, parent_role=parent_role) 

 

159 ↛ 162line 159 didn't jump to line 162, because the condition on line 159 was never false if role_include.role not in play.ROLE_CACHE: 

play.ROLE_CACHE[role_include.role] = dict() 

 

play.ROLE_CACHE[role_include.role][hashed_params] = r 

return r 

 

except RuntimeError: 

raise AnsibleError("A recursion loop was detected with the roles specified. Make sure child roles do not have dependencies on parent roles", 

obj=role_include._ds) 

 

def _load_role_data(self, role_include, parent_role=None): 

self._role_name = role_include.role 

self._role_path = role_include.get_role_path() 

self._role_params = role_include.get_role_params() 

self._variable_manager = role_include.get_variable_manager() 

self._loader = role_include.get_loader() 

 

if parent_role: 

self.add_parent(parent_role) 

 

# copy over all field attributes, except for when and tags, which 

# are special cases and need to preserve pre-existing values 

for (attr_name, _) in iteritems(self._valid_attrs): 

if attr_name not in ('when', 'tags'): 

setattr(self, attr_name, getattr(role_include, attr_name)) 

 

current_when = getattr(self, 'when')[:] 

current_when.extend(role_include.when) 

setattr(self, 'when', current_when) 

 

current_tags = getattr(self, 'tags')[:] 

current_tags.extend(role_include.tags) 

setattr(self, 'tags', current_tags) 

 

# dynamically load any plugins from the role directory 

for name, obj in get_all_plugin_loaders(): 

if obj.subdir: 

plugin_path = os.path.join(self._role_path, obj.subdir) 

197 ↛ 198line 197 didn't jump to line 198, because the condition on line 197 was never true if os.path.isdir(plugin_path): 

obj.add_directory(plugin_path) 

 

# load the role's other files, if they exist 

metadata = self._load_role_yaml('meta') 

if metadata: 

self._metadata = RoleMetadata.load(metadata, owner=self, variable_manager=self._variable_manager, loader=self._loader) 

self._dependencies = self._load_dependencies() 

else: 

self._metadata = RoleMetadata() 

 

task_data = self._load_role_yaml('tasks', main=self._from_files.get('tasks')) 

209 ↛ 216line 209 didn't jump to line 216, because the condition on line 209 was never false if task_data: 

try: 

self._task_blocks = load_list_of_blocks(task_data, play=self._play, role=self, loader=self._loader, variable_manager=self._variable_manager) 

except AssertionError as e: 

raise AnsibleParserError("The tasks/main.yml file for role '%s' must contain a list of tasks" % self._role_name, 

obj=task_data, orig_exc=e) 

 

handler_data = self._load_role_yaml('handlers') 

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

try: 

self._handler_blocks = load_list_of_blocks(handler_data, play=self._play, role=self, use_handlers=True, loader=self._loader, 

variable_manager=self._variable_manager) 

except AssertionError as e: 

raise AnsibleParserError("The handlers/main.yml file for role '%s' must contain a list of tasks" % self._role_name, 

obj=handler_data, orig_exc=e) 

 

# vars and default vars are regular dictionaries 

self._role_vars = self._load_role_yaml('vars', main=self._from_files.get('vars')) 

227 ↛ 229line 227 didn't jump to line 229, because the condition on line 227 was never false if self._role_vars is None: 

self._role_vars = dict() 

elif not isinstance(self._role_vars, dict): 

raise AnsibleParserError("The vars/main.yml file for role '%s' must contain a dictionary of variables" % self._role_name) 

 

self._default_vars = self._load_role_yaml('defaults', main=self._from_files.get('defaults')) 

if self._default_vars is None: 

self._default_vars = dict() 

235 ↛ 236line 235 didn't jump to line 236, because the condition on line 235 was never true elif not isinstance(self._default_vars, dict): 

raise AnsibleParserError("The defaults/main.yml file for role '%s' must contain a dictionary of variables" % self._role_name) 

 

def _load_role_yaml(self, subdir, main=None): 

file_path = os.path.join(self._role_path, subdir) 

if self._loader.path_exists(file_path) and self._loader.is_directory(file_path): 

main_file = self._resolve_main(file_path, main) 

242 ↛ 244line 242 didn't jump to line 244, because the condition on line 242 was never false if self._loader.path_exists(main_file): 

return self._loader.load_from_file(main_file) 

elif main is not None: 

raise AnsibleParserError("Could not find specified file in role: %s/%s" % (subdir, main)) 

return None 

 

def _resolve_main(self, basepath, main=None): 

''' flexibly handle variations in main filenames ''' 

 

post = False 

# allow override if set, otherwise use default 

253 ↛ 257line 253 didn't jump to line 257, because the condition on line 253 was never false if main is None: 

main = 'main' 

post = True 

 

bare_main = os.path.join(basepath, main) 

 

possible_mains = ( 

os.path.join(basepath, '%s.yml' % main), 

os.path.join(basepath, '%s.yaml' % main), 

os.path.join(basepath, '%s.json' % main), 

) 

 

265 ↛ 268line 265 didn't jump to line 268, because the condition on line 265 was never false if post: 

possible_mains = possible_mains + (bare_main,) 

else: 

possible_mains = (bare_main,) + possible_mains 

 

270 ↛ 271line 270 didn't jump to line 271, because the condition on line 270 was never true if sum([self._loader.is_file(x) for x in possible_mains]) > 1: 

raise AnsibleError("found multiple main files at %s, only one allowed" % (basepath)) 

else: 

273 ↛ 276line 273 didn't jump to line 276, because the loop on line 273 didn't complete for m in possible_mains: 

if self._loader.is_file(m): 

return m # exactly one main file 

return possible_mains[0] # zero mains (we still need to return something) 

 

def _load_dependencies(self): 

''' 

Recursively loads role dependencies from the metadata list of 

dependencies, if it exists 

''' 

 

deps = [] 

285 ↛ 290line 285 didn't jump to line 290, because the condition on line 285 was never false if self._metadata: 

for role_include in self._metadata.dependencies: 

r = Role.load(role_include, play=self._play, parent_role=self) 

deps.append(r) 

 

return deps 

 

# other functions 

 

def add_parent(self, parent_role): 

''' adds a role to the list of this roles parents ''' 

296 ↛ 297line 296 didn't jump to line 297, because the condition on line 296 was never true if not isinstance(parent_role, Role): 

raise AnsibleAssertionError() 

 

299 ↛ exitline 299 didn't return from function 'add_parent', because the condition on line 299 was never false if parent_role not in self._parents: 

self._parents.append(parent_role) 

 

def get_parents(self): 

return self._parents 

 

def get_default_vars(self, dep_chain=None): 

dep_chain = [] if dep_chain is None else dep_chain 

 

default_vars = dict() 

for dep in self.get_all_dependencies(): 

default_vars = combine_vars(default_vars, dep.get_default_vars()) 

if dep_chain: 

for parent in dep_chain: 

default_vars = combine_vars(default_vars, parent._default_vars) 

default_vars = combine_vars(default_vars, self._default_vars) 

return default_vars 

 

def get_inherited_vars(self, dep_chain=None): 

dep_chain = [] if dep_chain is None else dep_chain 

 

inherited_vars = dict() 

 

if dep_chain: 

for parent in dep_chain: 

inherited_vars = combine_vars(inherited_vars, parent._role_vars) 

return inherited_vars 

 

def get_role_params(self, dep_chain=None): 

dep_chain = [] if dep_chain is None else dep_chain 

 

params = {} 

if dep_chain: 

for parent in dep_chain: 

params = combine_vars(params, parent._role_params) 

params = combine_vars(params, self._role_params) 

return params 

 

def get_vars(self, dep_chain=None, include_params=True): 

dep_chain = [] if dep_chain is None else dep_chain 

 

all_vars = self.get_inherited_vars(dep_chain) 

 

for dep in self.get_all_dependencies(): 

all_vars = combine_vars(all_vars, dep.get_vars(include_params=include_params)) 

 

all_vars = combine_vars(all_vars, self.vars) 

all_vars = combine_vars(all_vars, self._role_vars) 

347 ↛ 348line 347 didn't jump to line 348, because the condition on line 347 was never true if include_params: 

all_vars = combine_vars(all_vars, self.get_role_params(dep_chain=dep_chain)) 

 

return all_vars 

 

def get_direct_dependencies(self): 

return self._dependencies[:] 

 

def get_all_dependencies(self): 

''' 

Returns a list of all deps, built recursively from all child dependencies, 

in the proper order in which they should be executed or evaluated. 

''' 

 

child_deps = [] 

 

for dep in self.get_direct_dependencies(): 

364 ↛ 365line 364 didn't jump to line 365, because the loop on line 364 never started for child_dep in dep.get_all_dependencies(): 

child_deps.append(child_dep) 

child_deps.append(dep) 

 

return child_deps 

 

def get_task_blocks(self): 

return self._task_blocks[:] 

 

def get_handler_blocks(self, play, dep_chain=None): 

block_list = [] 

 

# update the dependency chain here 

if dep_chain is None: 

dep_chain = [] 

new_dep_chain = dep_chain + [self] 

 

for dep in self.get_direct_dependencies(): 

dep_blocks = dep.get_handler_blocks(play=play, dep_chain=new_dep_chain) 

block_list.extend(dep_blocks) 

 

385 ↛ 386line 385 didn't jump to line 386, because the loop on line 385 never started for task_block in self._handler_blocks: 

new_task_block = task_block.copy() 

new_task_block._dep_chain = new_dep_chain 

new_task_block._play = play 

block_list.append(new_task_block) 

 

return block_list 

 

def has_run(self, host): 

''' 

Returns true if this role has been iterated over completely and 

at least one task was run 

''' 

 

return host.name in self._completed and not self._metadata.allow_duplicates 

 

def compile(self, play, dep_chain=None): 

''' 

Returns the task list for this role, which is created by first 

recursively compiling the tasks for all direct dependencies, and 

then adding on the tasks for this role. 

 

The role compile() also remembers and saves the dependency chain 

with each task, so tasks know by which route they were found, and 

can correctly take their parent's tags/conditionals into account. 

''' 

 

block_list = [] 

 

# update the dependency chain here 

if dep_chain is None: 

dep_chain = [] 

new_dep_chain = dep_chain + [self] 

 

deps = self.get_direct_dependencies() 

for dep in deps: 

dep_blocks = dep.compile(play=play, dep_chain=new_dep_chain) 

block_list.extend(dep_blocks) 

 

for idx, task_block in enumerate(self._task_blocks): 

new_task_block = task_block.copy(exclude_parent=True) 

426 ↛ 427line 426 didn't jump to line 427, because the condition on line 426 was never true if task_block._parent: 

new_task_block._parent = task_block._parent.copy() 

new_task_block._dep_chain = new_dep_chain 

new_task_block._play = play 

430 ↛ 432line 430 didn't jump to line 432, because the condition on line 430 was never false if idx == len(self._task_blocks) - 1: 

new_task_block._eor = True 

block_list.append(new_task_block) 

 

return block_list 

 

def serialize(self, include_deps=True): 

res = super(Role, self).serialize() 

 

res['_role_name'] = self._role_name 

res['_role_path'] = self._role_path 

res['_role_vars'] = self._role_vars 

res['_role_params'] = self._role_params 

res['_default_vars'] = self._default_vars 

res['_had_task_run'] = self._had_task_run.copy() 

res['_completed'] = self._completed.copy() 

 

if self._metadata: 

res['_metadata'] = self._metadata.serialize() 

 

if include_deps: 

deps = [] 

for role in self.get_direct_dependencies(): 

deps.append(role.serialize()) 

res['_dependencies'] = deps 

 

parents = [] 

for parent in self._parents: 

parents.append(parent.serialize(include_deps=False)) 

res['_parents'] = parents 

 

return res 

 

def deserialize(self, data, include_deps=True): 

self._role_name = data.get('_role_name', '') 

self._role_path = data.get('_role_path', '') 

self._role_vars = data.get('_role_vars', dict()) 

self._role_params = data.get('_role_params', dict()) 

self._default_vars = data.get('_default_vars', dict()) 

self._had_task_run = data.get('_had_task_run', dict()) 

self._completed = data.get('_completed', dict()) 

 

if include_deps: 

deps = [] 

for dep in data.get('_dependencies', []): 

r = Role() 

r.deserialize(dep) 

deps.append(r) 

setattr(self, '_dependencies', deps) 

 

parent_data = data.get('_parents', []) 

parents = [] 

for parent in parent_data: 

r = Role() 

r.deserialize(parent, include_deps=False) 

parents.append(r) 

setattr(self, '_parents', parents) 

 

metadata_data = data.get('_metadata') 

if metadata_data: 

m = RoleMetadata() 

m.deserialize(metadata_data) 

self._metadata = m 

 

super(Role, self).deserialize(data) 

 

def set_loader(self, loader): 

self._loader = loader 

for parent in self._parents: 

parent.set_loader(loader) 

for dep in self.get_direct_dependencies(): 

dep.set_loader(loader)