-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmoleculeenv.py
265 lines (218 loc) · 9.29 KB
/
moleculeenv.py
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
import json
from pathlib import Path
from ruamel.yaml import YAML
from ruamel.yaml.scanner import ScannerError
import subprocess
import yaml
class MoleculeEnv(object):
def __init__(
self,
molecule_ephemeral_directory,
molecule_scenario_directory,
gather_roles,
testvars_roles_blocklist,
testvars_roles_exclusivelist,
testvars_roles_includelist,
testvars_roles_playbooks):
self._molecule_ephemeral_directory = molecule_ephemeral_directory
self._molecule_scenario_directory = molecule_scenario_directory
self._gather_roles = gather_roles
self._testvars_roles_blocklist = testvars_roles_blocklist
self._testvars_roles_exclusivelist = testvars_roles_exclusivelist
self._testvars_roles_includelist = testvars_roles_includelist
self._testvars_roles_playbooks = testvars_roles_playbooks
self._configure_roles_()
def get_molecule_ephemeral_directory(self):
return self._molecule_ephemeral_directory
def get_molecule_scenario_directory(self):
return self._molecule_scenario_directory
def get_project_dir(self):
'''Return ansible project dir.'''
# start the ansible scenario directory
path = self.get_molecule_scenario_directory()
# go up until you find the roles dir
while path != Path('/'):
path = path.parent
if path / 'roles' in [d for d in path.iterdir() if d.is_dir()]:
return path
# else return None
return None
def get_roles(self):
'''Return roles as list of pathlib.Path objects'''
roles_dir = self.get_molecule_ephemeral_directory() / 'roles'
if not roles_dir.exists():
return list()
roles = sorted([d.name for d in roles_dir.iterdir() if d.is_dir()])
return roles
def write_playbook(
self,
playbook):
'''Write playbook to disk'''
project_dir = self._molecule_ephemeral_directory / 'project'
project_dir.mkdir(exist_ok=True)
playbook_file = \
(self._molecule_ephemeral_directory / 'project/site.json')
playbook_file.write_text(json.dumps(playbook))
def write_extravars(self, vars):
envdir = self._molecule_ephemeral_directory / 'env'
envdir.mkdir(exist_ok=True)
extravars = self._molecule_ephemeral_directory / 'env/extravars'
extravars.write_text("---\n" + yaml.dump(vars))
def remove_extravars(self):
extravars = self._molecule_ephemeral_directory / 'env/extravars'
try:
extravars.unlink()
except FileNotFoundError:
pass
def _configure_roles_(self):
'''Create symlinks to roles'''
if self._gather_roles is False:
return
roles = None
# exclusivelist selects roles directly
if self._get_testvars_roles_exclusivelist_():
roles = self._get_testvars_roles_exclusivelist_()
# try to read roles from custom playbooks
if roles is None:
playbook_files = self._get_testvars_roles_playbooks_()
if playbook_files is not None:
for playbook_file in playbook_files:
if roles is None:
roles = []
roles += self._read_roles_from_playbook_(playbook_file)
# try to read roles from custom molecule converge playbook
if roles is None:
playbook_file = self._read_playbook_file_from_molecule_yml_()
if playbook_file is not None:
roles = self._read_roles_from_playbook_(playbook_file)
# try to read roles from default molecule 3 converge playbook
if roles is None:
roles = self._read_roles_from_playbook_('converge.yml')
# try to read roles from default molecule 2 converge playbook
if roles is None:
roles = self._read_roles_from_playbook_('playbook.yml')
# fallback: select all roles
if roles is None:
msd = self.get_molecule_scenario_directory()
# roles dir viewed from a project molecule scenario
roles_dir = msd / '../../roles'
if roles_dir.is_dir():
roles = sorted([d.name for d in roles_dir.iterdir()
if d.is_dir()])
else:
# roles dir viewed from a role molecule scenario
roles_dir = msd / '../../../../roles'
if roles_dir.is_dir():
roles = sorted([d.name for d in roles_dir.iterdir()
if d.is_dir()])
# apply include
# by now, roles is a list
roles = self._roles_apply_includelist_(roles)
# if roles have been selected
# then apply blocklist
# then create symlinks
# then return
if roles is not None:
roles = self._roles_apply_blocklist_(roles)
self._create_roles_symlinks_(roles)
return
def _create_roles_symlinks_(self, roles):
(self.get_molecule_ephemeral_directory() / 'roles').mkdir(
exist_ok=True)
for role in roles:
self._create_symlink_('roles/' + role)
(self.get_molecule_ephemeral_directory() / 'project').mkdir(
exist_ok=True)
source = self.get_molecule_ephemeral_directory() / 'roles'
target = self.get_molecule_ephemeral_directory() / 'project/roles'
try:
target.symlink_to(source)
except FileExistsError:
pass
def _create_symlink_(self, path):
'''Create symlink from molecule ephemeral dir to project dir.'''
project_dir = self.get_project_dir()
if project_dir is None:
return
source = project_dir / path
target = self.get_molecule_ephemeral_directory() / path
try:
target.symlink_to(source)
except FileExistsError:
pass
def _get_molecule_vars_config_(self):
molecule_vars_config = ''
vars_config = list()
group_vars = \
self._molecule_ephemeral_directory / 'inventory/group_vars'
host_vars = \
self._molecule_ephemeral_directory / 'inventory/host_vars'
if group_vars.is_symlink():
target = self._molecule_scenario_directory / 'group_vars'
vars_config.append('Using group_vars symlink to ' + str(target))
if host_vars.is_symlink():
target = self._molecule_scenario_directory / 'host_vars'
vars_config.append('Using host_vars symlink to ' + str(target))
if vars_config:
molecule_vars_config = '\n'.join(vars_config)
return molecule_vars_config
# else no vars_config so we use the variables defined in molecule.yml
molecule_yml = self._molecule_scenario_directory / 'molecule.yml'
molecule_vars_config += \
'Using variables defined in ' + str(molecule_yml)
return molecule_vars_config
def _get_testvars_roles_blocklist_(self):
return self._testvars_roles_blocklist
def _get_testvars_roles_exclusivelist_(self):
return self._testvars_roles_exclusivelist
def _get_testvars_roles_includelist_(self):
return self._testvars_roles_includelist
def _get_testvars_roles_playbooks_(self):
return self._testvars_roles_playbooks
def _read_playbook_file_from_molecule_yml_(self):
molecule_yml_path = self.get_molecule_scenario_directory() / \
'molecule.yml'
yaml = YAML(typ='safe')
try:
molecule_yml = yaml.load(molecule_yml_path)
playbook_file = \
molecule_yml['provisioner']['playbooks']['converge']
command = [
'bash',
'-c',
f"echo {playbook_file}"
]
result = subprocess.run(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE).stdout.decode('UTF-8').strip()
playbook_file = Path(result)
return playbook_file
except (FileNotFoundError, ScannerError, KeyError):
return None
def _read_roles_from_playbook_(self, playbook_file):
playbook_path = self.get_molecule_scenario_directory() / playbook_file
if not playbook_path.is_file():
return None
yaml = YAML(typ='safe')
try:
playbook = yaml.load(playbook_path)
roles = playbook[0]['roles']
except (FileNotFoundError, ScannerError, KeyError):
return None
# Roles are either a dict with string val, or string
# this normalises them to list of strings
roles = [r['role'] if isinstance(r, dict) else r for r in roles]
return roles
def _roles_apply_blocklist_(self, roles):
roles_not_blocklisted = list()
for role in roles:
if role not in self._get_testvars_roles_blocklist_():
roles_not_blocklisted.append(role)
return roles_not_blocklisted
def _roles_apply_includelist_(self, roles):
roles_include = self._get_testvars_roles_includelist_()
for role in roles_include:
if role not in roles:
roles.append(role)
return roles