... | ... |
@@ -512,3 +512,16 @@ class CLI(object): |
512 | 512 |
|
513 | 513 |
return vault_pass |
514 | 514 |
|
515 |
+ def get_opt(self, k, defval=""): |
|
516 |
+ """ |
|
517 |
+ Returns an option from an Optparse values instance. |
|
518 |
+ """ |
|
519 |
+ try: |
|
520 |
+ data = getattr(self.options, k) |
|
521 |
+ except: |
|
522 |
+ return defval |
|
523 |
+ if k == "roles_path": |
|
524 |
+ if os.pathsep in data: |
|
525 |
+ data = data.split(os.pathsep)[0] |
|
526 |
+ return data |
|
527 |
+ |
... | ... |
@@ -29,8 +29,6 @@ from distutils.version import LooseVersion |
29 | 29 |
from jinja2 import Environment |
30 | 30 |
|
31 | 31 |
import ansible.constants as C |
32 |
-import ansible.utils |
|
33 |
-import ansible.galaxy |
|
34 | 32 |
from ansible.cli import CLI |
35 | 33 |
from ansible.errors import AnsibleError, AnsibleOptionsError |
36 | 34 |
from ansible.galaxy import Galaxy |
... | ... |
@@ -126,19 +124,6 @@ class GalaxyCLI(CLI): |
126 | 126 |
|
127 | 127 |
self.execute() |
128 | 128 |
|
129 |
- def get_opt(self, k, defval=""): |
|
130 |
- """ |
|
131 |
- Returns an option from an Optparse values instance. |
|
132 |
- """ |
|
133 |
- try: |
|
134 |
- data = getattr(self.options, k) |
|
135 |
- except: |
|
136 |
- return defval |
|
137 |
- if k == "roles_path": |
|
138 |
- if os.pathsep in data: |
|
139 |
- data = data.split(os.pathsep)[0] |
|
140 |
- return data |
|
141 |
- |
|
142 | 129 |
def exit_without_ignore(self, rc=1): |
143 | 130 |
""" |
144 | 131 |
Exits with the specified return code unless the |
... | ... |
@@ -147,40 +132,6 @@ class GalaxyCLI(CLI): |
147 | 147 |
if not self.get_opt("ignore_errors", False): |
148 | 148 |
raise AnsibleError('- you can use --ignore-errors to skip failed roles and finish processing the list.') |
149 | 149 |
|
150 |
- def parse_requirements_files(self, role): |
|
151 |
- if 'role' in role: |
|
152 |
- # Old style: {role: "galaxy.role,version,name", other_vars: "here" } |
|
153 |
- role_info = role_spec_parse(role['role']) |
|
154 |
- if isinstance(role_info, dict): |
|
155 |
- # Warning: Slight change in behaviour here. name may be being |
|
156 |
- # overloaded. Previously, name was only a parameter to the role. |
|
157 |
- # Now it is both a parameter to the role and the name that |
|
158 |
- # ansible-galaxy will install under on the local system. |
|
159 |
- if 'name' in role and 'name' in role_info: |
|
160 |
- del role_info['name'] |
|
161 |
- role.update(role_info) |
|
162 |
- else: |
|
163 |
- # New style: { src: 'galaxy.role,version,name', other_vars: "here" } |
|
164 |
- if 'github.com' in role["src"] and 'http' in role["src"] and '+' not in role["src"] and not role["src"].endswith('.tar.gz'): |
|
165 |
- role["src"] = "git+" + role["src"] |
|
166 |
- |
|
167 |
- if '+' in role["src"]: |
|
168 |
- (scm, src) = role["src"].split('+') |
|
169 |
- role["scm"] = scm |
|
170 |
- role["src"] = src |
|
171 |
- |
|
172 |
- if 'name' not in role: |
|
173 |
- role["name"] = GalaxyRole.url_to_spec(role["src"]) |
|
174 |
- |
|
175 |
- if 'version' not in role: |
|
176 |
- role['version'] = '' |
|
177 |
- |
|
178 |
- if 'scm' not in role: |
|
179 |
- role['scm'] = None |
|
180 |
- |
|
181 |
- return role |
|
182 |
- |
|
183 |
- |
|
184 | 150 |
def _display_role_info(self, role_info): |
185 | 151 |
|
186 | 152 |
text = "\nRole: %s \n" % role_info['name'] |
... | ... |
@@ -298,9 +249,8 @@ class GalaxyCLI(CLI): |
298 | 298 |
data = '' |
299 | 299 |
for role in self.args: |
300 | 300 |
|
301 |
- role_info = {} |
|
301 |
+ role_info = {'role_path': roles_path} |
|
302 | 302 |
gr = GalaxyRole(self.galaxy, role) |
303 |
- #self.galaxy.add_role(gr) |
|
304 | 303 |
|
305 | 304 |
install_info = gr.install_info |
306 | 305 |
if install_info: |
... | ... |
@@ -351,54 +301,45 @@ class GalaxyCLI(CLI): |
351 | 351 |
|
352 | 352 |
no_deps = self.get_opt("no_deps", False) |
353 | 353 |
force = self.get_opt('force', False) |
354 |
- roles_path = self.get_opt("roles_path") |
|
355 | 354 |
|
356 |
- roles_done = [] |
|
357 | 355 |
roles_left = [] |
358 | 356 |
if role_file: |
359 |
- self.display.debug('Getting roles from %s' % role_file) |
|
360 | 357 |
try: |
361 |
- self.display.debug('Processing role file: %s' % role_file) |
|
362 | 358 |
f = open(role_file, 'r') |
363 | 359 |
if role_file.endswith('.yaml') or role_file.endswith('.yml'): |
364 |
- try: |
|
365 |
- rolesparsed = map(self.parse_requirements_files, yaml.safe_load(f)) |
|
366 |
- except Exception as e: |
|
367 |
- raise AnsibleError("%s does not seem like a valid yaml file: %s" % (role_file, str(e))) |
|
368 |
- roles_left = [GalaxyRole(self.galaxy, **r) for r in rolesparsed] |
|
360 |
+ for role in yaml.safe_load(f.read()): |
|
361 |
+ self.display.debug('found role %s in yaml file' % str(role)) |
|
362 |
+ if 'name' not in role: |
|
363 |
+ if 'src' in role: |
|
364 |
+ role['name'] = RoleRequirement.repo_url_to_role_name(role['src']) |
|
365 |
+ else: |
|
366 |
+ raise AnsibleError("Must specify name or src for role") |
|
367 |
+ roles_left.append(GalaxyRole(self.galaxy, **role)) |
|
369 | 368 |
else: |
369 |
+ self.display.deprecated("going forward only the yaml format will be supported") |
|
370 | 370 |
# roles listed in a file, one per line |
371 |
- self.display.deprecated("Non yaml files for role requirements") |
|
372 |
- for rname in f.readlines(): |
|
373 |
- if rname.startswith("#") or rname.strip() == '': |
|
374 |
- continue |
|
375 |
- roles_left.append(GalaxyRole(self.galaxy, rname.strip())) |
|
371 |
+ for rline in f.readlines(): |
|
372 |
+ self.display.debug('found role %s in text file' % str(rline)) |
|
373 |
+ roles_left.append(GalaxyRole(self.galaxy, **RoleRequirement.role_spec_parse(rline))) |
|
376 | 374 |
f.close() |
377 |
- except (IOError,OSError) as e: |
|
378 |
- raise AnsibleError("Unable to read requirements file (%s): %s" % (role_file, str(e))) |
|
375 |
+ except (IOError, OSError) as e: |
|
376 |
+ self.display.error('Unable to open %s: %s' % (role_file, str(e))) |
|
379 | 377 |
else: |
380 | 378 |
# roles were specified directly, so we'll just go out grab them |
381 | 379 |
# (and their dependencies, unless the user doesn't want us to). |
382 | 380 |
for rname in self.args: |
383 | 381 |
roles_left.append(GalaxyRole(self.galaxy, rname.strip())) |
384 | 382 |
|
385 |
- while len(roles_left) > 0: |
|
383 |
+ for role in roles_left: |
|
384 |
+ self.display.debug('Installing role %s ' % role.name) |
|
386 | 385 |
# query the galaxy API for the role data |
387 | 386 |
role_data = None |
388 | 387 |
role = roles_left.pop(0) |
389 |
- role_path = role.path |
|
390 | 388 |
|
391 | 389 |
if role.install_info is not None and not force: |
392 | 390 |
self.display.display('- %s is already installed, skipping.' % role.name) |
393 | 391 |
continue |
394 | 392 |
|
395 |
- if role_path: |
|
396 |
- self.options.roles_path = role_path |
|
397 |
- else: |
|
398 |
- self.options.roles_path = roles_path |
|
399 |
- |
|
400 |
- self.display.debug('Installing role %s from %s' % (role.name, self.options.roles_path)) |
|
401 |
- |
|
402 | 393 |
tmp_file = None |
403 | 394 |
installed = False |
404 | 395 |
if role.src and os.path.isfile(role.src): |
... | ... |
@@ -407,7 +348,7 @@ class GalaxyCLI(CLI): |
407 | 407 |
else: |
408 | 408 |
if role.scm: |
409 | 409 |
# create tar file from scm url |
410 |
- tmp_file = GalaxyRole.scm_archive_role(role.scm, role.src, role.version, role.name) |
|
410 |
+ tmp_file = RoleRequirement.scm_archive_role(role.scm, role.src, role.version, role.name) |
|
411 | 411 |
if role.src: |
412 | 412 |
if '://' not in role.src: |
413 | 413 |
role_data = self.api.lookup_role_by_name(role.src) |
... | ... |
@@ -438,11 +379,14 @@ class GalaxyCLI(CLI): |
438 | 438 |
# download the role. if --no-deps was specified, we stop here, |
439 | 439 |
# otherwise we recursively grab roles and all of their deps. |
440 | 440 |
tmp_file = role.fetch(role_data) |
441 |
+ |
|
441 | 442 |
if tmp_file: |
443 |
+ self.display.debug('using %s' % tmp_file) |
|
442 | 444 |
installed = role.install(tmp_file) |
443 |
- # we're done with the temp file, clean it up |
|
445 |
+ # we're done with the temp file, clean it up if we created it |
|
444 | 446 |
if tmp_file != role.src: |
445 | 447 |
os.unlink(tmp_file) |
448 |
+ |
|
446 | 449 |
# install dependencies, if we want them |
447 | 450 |
if not no_deps and installed: |
448 | 451 |
role_dependencies = role.metadata.get('dependencies', []) |
... | ... |
@@ -460,9 +404,10 @@ class GalaxyCLI(CLI): |
460 | 460 |
else: |
461 | 461 |
self.display.display('- dependency %s is already installed, skipping.' % dep_name) |
462 | 462 |
|
463 |
- if not tmp_file or not installed: |
|
463 |
+ if not installed: |
|
464 | 464 |
self.display.warning("- %s was NOT installed successfully." % role.name) |
465 | 465 |
self.exit_without_ignore() |
466 |
+ |
|
466 | 467 |
return 0 |
467 | 468 |
|
468 | 469 |
def execute_remove(self): |
... | ... |
@@ -21,15 +21,16 @@ |
21 | 21 |
|
22 | 22 |
import datetime |
23 | 23 |
import os |
24 |
-import subprocess |
|
25 | 24 |
import tarfile |
26 | 25 |
import tempfile |
27 | 26 |
import yaml |
27 |
+from distutils.version import LooseVersion |
|
28 | 28 |
from shutil import rmtree |
29 | 29 |
|
30 |
-from ansible import constants as C |
|
31 | 30 |
from ansible.errors import AnsibleError |
32 | 31 |
from ansible.module_utils.urls import open_url |
32 |
+from ansible.playbook.role.requirement import RoleRequirement |
|
33 |
+from ansible.galaxy.api import GalaxyAPI |
|
33 | 34 |
|
34 | 35 |
try: |
35 | 36 |
from __main__ import display |
... | ... |
@@ -51,6 +52,7 @@ class GalaxyRole(object): |
51 | 51 |
self._install_info = None |
52 | 52 |
|
53 | 53 |
self.options = galaxy.options |
54 |
+ self.galaxy = galaxy |
|
54 | 55 |
|
55 | 56 |
self.name = name |
56 | 57 |
self.version = version |
... | ... |
@@ -135,9 +137,9 @@ class GalaxyRole(object): |
135 | 135 |
|
136 | 136 |
def remove(self): |
137 | 137 |
""" |
138 |
- Removes the specified role from the roles path. There is a |
|
139 |
- sanity check to make sure there's a meta/main.yml file at this |
|
140 |
- path so the user doesn't blow away random directories |
|
138 |
+ Removes the specified role from the roles path. |
|
139 |
+ There is a sanity check to make sure there's a meta/main.yml file at this |
|
140 |
+ path so the user doesn't blow away random directories. |
|
141 | 141 |
""" |
142 | 142 |
if self.metadata: |
143 | 143 |
try: |
... | ... |
@@ -159,6 +161,7 @@ class GalaxyRole(object): |
159 | 159 |
archive_url = 'https://github.com/%s/%s/archive/%s.tar.gz' % (role_data["github_user"], role_data["github_repo"], self.version) |
160 | 160 |
else: |
161 | 161 |
archive_url = self.src |
162 |
+ |
|
162 | 163 |
display.display("- downloading role from %s" % archive_url) |
163 | 164 |
|
164 | 165 |
try: |
... | ... |
@@ -170,87 +173,125 @@ class GalaxyRole(object): |
170 | 170 |
data = url_file.read() |
171 | 171 |
temp_file.close() |
172 | 172 |
return temp_file.name |
173 |
- except: |
|
174 |
- # TODO: better urllib2 error handling for error |
|
175 |
- # messages that are more exact |
|
176 |
- display.error("failed to download the file.") |
|
173 |
+ except Exception as e: |
|
174 |
+ display.error("failed to download the file: %s" % str(e)) |
|
177 | 175 |
|
178 | 176 |
return False |
179 | 177 |
|
180 |
- def install(self, role_filename): |
|
178 |
+ def install(self): |
|
181 | 179 |
# the file is a tar, so open it that way and extract it |
182 | 180 |
# to the specified (or default) roles directory |
183 | 181 |
|
184 |
- if not tarfile.is_tarfile(role_filename): |
|
185 |
- display.error("the file downloaded was not a tar.gz") |
|
186 |
- return False |
|
187 |
- else: |
|
188 |
- if role_filename.endswith('.gz'): |
|
189 |
- role_tar_file = tarfile.open(role_filename, "r:gz") |
|
182 |
+ if self.scm: |
|
183 |
+ # create tar file from scm url |
|
184 |
+ tmp_file = RoleRequirement.scm_archive_role(**self.spec) |
|
185 |
+ elif self.src: |
|
186 |
+ if os.path.isfile(self.src): |
|
187 |
+ # installing a local tar.gz |
|
188 |
+ tmp_file = self.src |
|
189 |
+ elif '://' in self.src: |
|
190 |
+ role_data = self.src |
|
191 |
+ tmp_file = self.fetch(role_data) |
|
190 | 192 |
else: |
191 |
- role_tar_file = tarfile.open(role_filename, "r") |
|
192 |
- # verify the role's meta file |
|
193 |
- meta_file = None |
|
194 |
- members = role_tar_file.getmembers() |
|
195 |
- # next find the metadata file |
|
196 |
- for member in members: |
|
197 |
- if self.META_MAIN in member.name: |
|
198 |
- meta_file = member |
|
199 |
- break |
|
200 |
- if not meta_file: |
|
201 |
- display.error("this role does not appear to have a meta/main.yml file.") |
|
202 |
- return False |
|
193 |
+ api = GalaxyAPI(self.galaxy, self.options.api_server) |
|
194 |
+ role_data = api.lookup_role_by_name(self.src) |
|
195 |
+ if not role_data: |
|
196 |
+ raise AnsibleError("- sorry, %s was not found on %s." % (self.src, self.options.api_server)) |
|
197 |
+ |
|
198 |
+ role_versions = api.fetch_role_related('versions', role_data['id']) |
|
199 |
+ if not self.version: |
|
200 |
+ # convert the version names to LooseVersion objects |
|
201 |
+ # and sort them to get the latest version. If there |
|
202 |
+ # are no versions in the list, we'll grab the head |
|
203 |
+ # of the master branch |
|
204 |
+ if len(role_versions) > 0: |
|
205 |
+ loose_versions = [LooseVersion(a.get('name',None)) for a in role_versions] |
|
206 |
+ loose_versions.sort() |
|
207 |
+ self.version = str(loose_versions[-1]) |
|
208 |
+ else: |
|
209 |
+ self.version = 'master' |
|
210 |
+ elif self.version != 'master': |
|
211 |
+ if role_versions and self.version not in [a.get('name', None) for a in role_versions]: |
|
212 |
+ raise AnsibleError("- the specified version (%s) of %s was not found in the list of available versions (%s)." % (self.version, self.name, role_versions)) |
|
213 |
+ |
|
214 |
+ tmp_file = self.fetch(role_data) |
|
215 |
+ |
|
216 |
+ else: |
|
217 |
+ raise AnsibleError("No valid role data found") |
|
218 |
+ |
|
219 |
+ |
|
220 |
+ if tmp_file: |
|
221 |
+ |
|
222 |
+ display.display("installing from %s" % tmp_file) |
|
223 |
+ |
|
224 |
+ if not tarfile.is_tarfile(tmp_file): |
|
225 |
+ raise AnsibleError("the file downloaded was not a tar.gz") |
|
203 | 226 |
else: |
227 |
+ if tmp_file.endswith('.gz'): |
|
228 |
+ role_tar_file = tarfile.open(tmp_file, "r:gz") |
|
229 |
+ else: |
|
230 |
+ role_tar_file = tarfile.open(tmp_file, "r") |
|
231 |
+ # verify the role's meta file |
|
232 |
+ meta_file = None |
|
233 |
+ members = role_tar_file.getmembers() |
|
234 |
+ # next find the metadata file |
|
235 |
+ for member in members: |
|
236 |
+ if self.META_MAIN in member.name: |
|
237 |
+ meta_file = member |
|
238 |
+ break |
|
239 |
+ if not meta_file: |
|
240 |
+ raise AnsibleError("this role does not appear to have a meta/main.yml file.") |
|
241 |
+ else: |
|
242 |
+ try: |
|
243 |
+ self._metadata = yaml.safe_load(role_tar_file.extractfile(meta_file)) |
|
244 |
+ except: |
|
245 |
+ raise AnsibleError("this role does not appear to have a valid meta/main.yml file.") |
|
246 |
+ |
|
247 |
+ # we strip off the top-level directory for all of the files contained within |
|
248 |
+ # the tar file here, since the default is 'github_repo-target', and change it |
|
249 |
+ # to the specified role's name |
|
250 |
+ display.display("- extracting %s to %s" % (self.name, self.path)) |
|
204 | 251 |
try: |
205 |
- self._metadata = yaml.safe_load(role_tar_file.extractfile(meta_file)) |
|
206 |
- except: |
|
207 |
- display.error("this role does not appear to have a valid meta/main.yml file.") |
|
208 |
- return False |
|
209 |
- |
|
210 |
- # we strip off the top-level directory for all of the files contained within |
|
211 |
- # the tar file here, since the default is 'github_repo-target', and change it |
|
212 |
- # to the specified role's name |
|
213 |
- display.display("- extracting %s to %s" % (self.name, self.path)) |
|
214 |
- try: |
|
215 |
- if os.path.exists(self.path): |
|
216 |
- if not os.path.isdir(self.path): |
|
217 |
- display.error("the specified roles path exists and is not a directory.") |
|
218 |
- return False |
|
219 |
- elif not getattr(self.options, "force", False): |
|
220 |
- display.error("the specified role %s appears to already exist. Use --force to replace it." % self.name) |
|
221 |
- return False |
|
252 |
+ if os.path.exists(self.path): |
|
253 |
+ if not os.path.isdir(self.path): |
|
254 |
+ raise AnsibleError("the specified roles path exists and is not a directory.") |
|
255 |
+ elif not getattr(self.options, "force", False): |
|
256 |
+ raise AnsibleError("the specified role %s appears to already exist. Use --force to replace it." % self.name) |
|
257 |
+ else: |
|
258 |
+ # using --force, remove the old path |
|
259 |
+ if not self.remove(): |
|
260 |
+ raise AnsibleError("%s doesn't appear to contain a role.\n please remove this directory manually if you really want to put the role here." % self.path) |
|
222 | 261 |
else: |
223 |
- # using --force, remove the old path |
|
224 |
- if not self.remove(): |
|
225 |
- display.error("%s doesn't appear to contain a role." % self.path) |
|
226 |
- display.error(" please remove this directory manually if you really want to put the role here.") |
|
227 |
- return False |
|
228 |
- else: |
|
229 |
- os.makedirs(self.path) |
|
262 |
+ os.makedirs(self.path) |
|
263 |
+ |
|
264 |
+ # now we do the actual extraction to the path |
|
265 |
+ for member in members: |
|
266 |
+ # we only extract files, and remove any relative path |
|
267 |
+ # bits that might be in the file for security purposes |
|
268 |
+ # and drop the leading directory, as mentioned above |
|
269 |
+ if member.isreg() or member.issym(): |
|
270 |
+ parts = member.name.split(os.sep)[1:] |
|
271 |
+ final_parts = [] |
|
272 |
+ for part in parts: |
|
273 |
+ if part != '..' and '~' not in part and '$' not in part: |
|
274 |
+ final_parts.append(part) |
|
275 |
+ member.name = os.path.join(*final_parts) |
|
276 |
+ role_tar_file.extract(member, self.path) |
|
277 |
+ |
|
278 |
+ # write out the install info file for later use |
|
279 |
+ self._write_galaxy_install_info() |
|
280 |
+ except OSError as e: |
|
281 |
+ raise AnsibleError("Could not update files in %s: %s" % (self.path, str(e))) |
|
282 |
+ |
|
283 |
+ # return the parsed yaml metadata |
|
284 |
+ display.display("- %s was installed successfully" % self.name) |
|
285 |
+ try: |
|
286 |
+ os.unlink(tmp_file) |
|
287 |
+ except (OSError,IOError) as e: |
|
288 |
+ display.warning("Unable to remove tmp file (%s): %s" % (tmp_file, str(e))) |
|
289 |
+ return True |
|
230 | 290 |
|
231 |
- # now we do the actual extraction to the path |
|
232 |
- for member in members: |
|
233 |
- # we only extract files, and remove any relative path |
|
234 |
- # bits that might be in the file for security purposes |
|
235 |
- # and drop the leading directory, as mentioned above |
|
236 |
- if member.isreg() or member.issym(): |
|
237 |
- parts = member.name.split(os.sep)[1:] |
|
238 |
- final_parts = [] |
|
239 |
- for part in parts: |
|
240 |
- if part != '..' and '~' not in part and '$' not in part: |
|
241 |
- final_parts.append(part) |
|
242 |
- member.name = os.path.join(*final_parts) |
|
243 |
- role_tar_file.extract(member, self.path) |
|
244 |
- |
|
245 |
- # write out the install info file for later use |
|
246 |
- self._write_galaxy_install_info() |
|
247 |
- except OSError as e: |
|
248 |
- display.error("Could not update files in %s: %s" % (self.path, str(e))) |
|
249 |
- return False |
|
250 |
- |
|
251 |
- # return the parsed yaml metadata |
|
252 |
- display.display("- %s was installed successfully" % self.name) |
|
253 |
- return True |
|
291 |
+ return False |
|
254 | 292 |
|
255 | 293 |
@property |
256 | 294 |
def spec(self): |
... | ... |
@@ -266,65 +307,3 @@ class GalaxyRole(object): |
266 | 266 |
return dict(scm=self.scm, src=self.src, version=self.version, name=self.name) |
267 | 267 |
|
268 | 268 |
|
269 |
- @staticmethod |
|
270 |
- def url_to_spec(roleurl): |
|
271 |
- # gets the role name out of a repo like |
|
272 |
- # http://git.example.com/repos/repo.git" => "repo" |
|
273 |
- |
|
274 |
- if '://' not in roleurl and '@' not in roleurl: |
|
275 |
- return roleurl |
|
276 |
- trailing_path = roleurl.split('/')[-1] |
|
277 |
- if trailing_path.endswith('.git'): |
|
278 |
- trailing_path = trailing_path[:-4] |
|
279 |
- if trailing_path.endswith('.tar.gz'): |
|
280 |
- trailing_path = trailing_path[:-7] |
|
281 |
- if ',' in trailing_path: |
|
282 |
- trailing_path = trailing_path.split(',')[0] |
|
283 |
- return trailing_path |
|
284 |
- |
|
285 |
- @staticmethod |
|
286 |
- def scm_archive_role(scm, role_url, role_version, role_name): |
|
287 |
- if scm not in ['hg', 'git']: |
|
288 |
- display.display("- scm %s is not currently supported" % scm) |
|
289 |
- return False |
|
290 |
- tempdir = tempfile.mkdtemp() |
|
291 |
- clone_cmd = [scm, 'clone', role_url, role_name] |
|
292 |
- with open('/dev/null', 'w') as devnull: |
|
293 |
- try: |
|
294 |
- display.display("- executing: %s" % " ".join(clone_cmd)) |
|
295 |
- popen = subprocess.Popen(clone_cmd, cwd=tempdir, stdout=devnull, stderr=devnull) |
|
296 |
- except: |
|
297 |
- raise AnsibleError("error executing: %s" % " ".join(clone_cmd)) |
|
298 |
- rc = popen.wait() |
|
299 |
- if rc != 0: |
|
300 |
- display.display("- command %s failed" % ' '.join(clone_cmd)) |
|
301 |
- display.display(" in directory %s" % tempdir) |
|
302 |
- return False |
|
303 |
- |
|
304 |
- temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.tar') |
|
305 |
- if scm == 'hg': |
|
306 |
- archive_cmd = ['hg', 'archive', '--prefix', "%s/" % role_name] |
|
307 |
- if role_version: |
|
308 |
- archive_cmd.extend(['-r', role_version]) |
|
309 |
- archive_cmd.append(temp_file.name) |
|
310 |
- if scm == 'git': |
|
311 |
- archive_cmd = ['git', 'archive', '--prefix=%s/' % role_name, '--output=%s' % temp_file.name] |
|
312 |
- if role_version: |
|
313 |
- archive_cmd.append(role_version) |
|
314 |
- else: |
|
315 |
- archive_cmd.append('HEAD') |
|
316 |
- |
|
317 |
- with open('/dev/null', 'w') as devnull: |
|
318 |
- display.display("- executing: %s" % " ".join(archive_cmd)) |
|
319 |
- popen = subprocess.Popen(archive_cmd, cwd=os.path.join(tempdir, role_name), |
|
320 |
- stderr=devnull, stdout=devnull) |
|
321 |
- rc = popen.wait() |
|
322 |
- if rc != 0: |
|
323 |
- display.display("- command %s failed" % ' '.join(archive_cmd)) |
|
324 |
- display.display(" in directory %s" % tempdir) |
|
325 |
- return False |
|
326 |
- |
|
327 |
- rmtree(tempdir, ignore_errors=True) |
|
328 |
- |
|
329 |
- return temp_file.name |
|
330 |
- |
... | ... |
@@ -21,19 +21,14 @@ __metaclass__ = type |
21 | 21 |
|
22 | 22 |
from six import iteritems, string_types |
23 | 23 |
|
24 |
-import inspect |
|
25 | 24 |
import os |
26 | 25 |
|
27 |
-from hashlib import sha1 |
|
28 |
- |
|
29 | 26 |
from ansible.errors import AnsibleError, AnsibleParserError |
30 |
-from ansible.parsing import DataLoader |
|
31 | 27 |
from ansible.playbook.attribute import FieldAttribute |
32 | 28 |
from ansible.playbook.base import Base |
33 | 29 |
from ansible.playbook.become import Become |
34 | 30 |
from ansible.playbook.conditional import Conditional |
35 | 31 |
from ansible.playbook.helpers import load_list_of_blocks |
36 |
-from ansible.playbook.role.include import RoleInclude |
|
37 | 32 |
from ansible.playbook.role.metadata import RoleMetadata |
38 | 33 |
from ansible.playbook.taggable import Taggable |
39 | 34 |
from ansible.plugins import get_all_plugin_loaders |
... | ... |
@@ -19,11 +19,14 @@ |
19 | 19 |
from __future__ import (absolute_import, division, print_function) |
20 | 20 |
__metaclass__ = type |
21 | 21 |
|
22 |
-from six import iteritems, string_types |
|
22 |
+from six import string_types |
|
23 | 23 |
|
24 | 24 |
import os |
25 |
+import shutil |
|
26 |
+import subprocess |
|
27 |
+import tempfile |
|
25 | 28 |
|
26 |
-from ansible.errors import AnsibleError, AnsibleParserError |
|
29 |
+from ansible.errors import AnsibleError |
|
27 | 30 |
from ansible.playbook.role.definition import RoleDefinition |
28 | 31 |
|
29 | 32 |
__all__ = ['RoleRequirement'] |
... | ... |
@@ -73,7 +76,7 @@ class RoleRequirement(RoleDefinition): |
73 | 73 |
def _preprocess_role_spec(self, ds): |
74 | 74 |
if 'role' in ds: |
75 | 75 |
# Old style: {role: "galaxy.role,version,name", other_vars: "here" } |
76 |
- role_info = role_spec_parse(ds['role']) |
|
76 |
+ role_info = RoleRequirement.role_spec_parse(ds['role']) |
|
77 | 77 |
if isinstance(role_info, dict): |
78 | 78 |
# Warning: Slight change in behaviour here. name may be being |
79 | 79 |
# overloaded. Previously, name was only a parameter to the role. |
... | ... |
@@ -96,7 +99,7 @@ class RoleRequirement(RoleDefinition): |
96 | 96 |
ds["role"] = ds["name"] |
97 | 97 |
del ds["name"] |
98 | 98 |
else: |
99 |
- ds["role"] = repo_url_to_role_name(ds["src"]) |
|
99 |
+ ds["role"] = RoleRequirement.repo_url_to_role_name(ds["src"]) |
|
100 | 100 |
|
101 | 101 |
# set some values to a default value, if none were specified |
102 | 102 |
ds.setdefault('version', '') |
... | ... |
@@ -104,102 +107,137 @@ class RoleRequirement(RoleDefinition): |
104 | 104 |
|
105 | 105 |
return ds |
106 | 106 |
|
107 |
-def repo_url_to_role_name(repo_url): |
|
108 |
- # gets the role name out of a repo like |
|
109 |
- # http://git.example.com/repos/repo.git" => "repo" |
|
110 |
- |
|
111 |
- if '://' not in repo_url and '@' not in repo_url: |
|
112 |
- return repo_url |
|
113 |
- trailing_path = repo_url.split('/')[-1] |
|
114 |
- if trailing_path.endswith('.git'): |
|
115 |
- trailing_path = trailing_path[:-4] |
|
116 |
- if trailing_path.endswith('.tar.gz'): |
|
117 |
- trailing_path = trailing_path[:-7] |
|
118 |
- if ',' in trailing_path: |
|
119 |
- trailing_path = trailing_path.split(',')[0] |
|
120 |
- return trailing_path |
|
121 |
- |
|
122 |
-def role_spec_parse(role_spec): |
|
123 |
- # takes a repo and a version like |
|
124 |
- # git+http://git.example.com/repos/repo.git,v1.0 |
|
125 |
- # and returns a list of properties such as: |
|
126 |
- # { |
|
127 |
- # 'scm': 'git', |
|
128 |
- # 'src': 'http://git.example.com/repos/repo.git', |
|
129 |
- # 'version': 'v1.0', |
|
130 |
- # 'name': 'repo' |
|
131 |
- # } |
|
132 |
- |
|
133 |
- default_role_versions = dict(git='master', hg='tip') |
|
134 |
- |
|
135 |
- role_spec = role_spec.strip() |
|
136 |
- role_version = '' |
|
137 |
- if role_spec == "" or role_spec.startswith("#"): |
|
138 |
- return (None, None, None, None) |
|
139 |
- |
|
140 |
- tokens = [s.strip() for s in role_spec.split(',')] |
|
141 |
- |
|
142 |
- # assume https://github.com URLs are git+https:// URLs and not |
|
143 |
- # tarballs unless they end in '.zip' |
|
144 |
- if 'github.com/' in tokens[0] and not tokens[0].startswith("git+") and not tokens[0].endswith('.tar.gz'): |
|
145 |
- tokens[0] = 'git+' + tokens[0] |
|
146 |
- |
|
147 |
- if '+' in tokens[0]: |
|
148 |
- (scm, role_url) = tokens[0].split('+') |
|
149 |
- else: |
|
150 |
- scm = None |
|
151 |
- role_url = tokens[0] |
|
152 |
- |
|
153 |
- if len(tokens) >= 2: |
|
154 |
- role_version = tokens[1] |
|
155 |
- |
|
156 |
- if len(tokens) == 3: |
|
157 |
- role_name = tokens[2] |
|
158 |
- else: |
|
159 |
- role_name = repo_url_to_role_name(tokens[0]) |
|
160 |
- |
|
161 |
- if scm and not role_version: |
|
162 |
- role_version = default_role_versions.get(scm, '') |
|
163 |
- |
|
164 |
- return dict(scm=scm, src=role_url, version=role_version, role_name=role_name) |
|
165 |
- |
|
166 |
-# FIXME: all of these methods need to be cleaned up/reorganized below this |
|
167 |
-def get_opt(options, k, defval=""): |
|
168 |
- """ |
|
169 |
- Returns an option from an Optparse values instance. |
|
170 |
- """ |
|
171 |
- try: |
|
172 |
- data = getattr(options, k) |
|
173 |
- except: |
|
174 |
- return defval |
|
175 |
- if k == "roles_path": |
|
176 |
- if os.pathsep in data: |
|
177 |
- data = data.split(os.pathsep)[0] |
|
178 |
- return data |
|
179 |
- |
|
180 |
-def get_role_path(role_name, options): |
|
181 |
- """ |
|
182 |
- Returns the role path based on the roles_path option |
|
183 |
- and the role name. |
|
184 |
- """ |
|
185 |
- roles_path = get_opt(options,'roles_path') |
|
186 |
- roles_path = os.path.join(roles_path, role_name) |
|
187 |
- roles_path = os.path.expanduser(roles_path) |
|
188 |
- return roles_path |
|
107 |
+ @staticmethod |
|
108 |
+ def repo_url_to_role_name(repo_url): |
|
109 |
+ # gets the role name out of a repo like |
|
110 |
+ # http://git.example.com/repos/repo.git" => "repo" |
|
111 |
+ |
|
112 |
+ if '://' not in repo_url and '@' not in repo_url: |
|
113 |
+ return repo_url |
|
114 |
+ trailing_path = repo_url.split('/')[-1] |
|
115 |
+ if trailing_path.endswith('.git'): |
|
116 |
+ trailing_path = trailing_path[:-4] |
|
117 |
+ if trailing_path.endswith('.tar.gz'): |
|
118 |
+ trailing_path = trailing_path[:-7] |
|
119 |
+ if ',' in trailing_path: |
|
120 |
+ trailing_path = trailing_path.split(',')[0] |
|
121 |
+ return trailing_path |
|
122 |
+ |
|
123 |
+ @staticmethod |
|
124 |
+ def role_spec_parse(role_spec): |
|
125 |
+ # takes a repo and a version like |
|
126 |
+ # git+http://git.example.com/repos/repo.git,v1.0 |
|
127 |
+ # and returns a list of properties such as: |
|
128 |
+ # { |
|
129 |
+ # 'scm': 'git', |
|
130 |
+ # 'src': 'http://git.example.com/repos/repo.git', |
|
131 |
+ # 'version': 'v1.0', |
|
132 |
+ # 'name': 'repo' |
|
133 |
+ # } |
|
134 |
+ |
|
135 |
+ default_role_versions = dict(git='master', hg='tip') |
|
136 |
+ |
|
137 |
+ role_spec = role_spec.strip() |
|
138 |
+ role_version = '' |
|
139 |
+ if role_spec == "" or role_spec.startswith("#"): |
|
140 |
+ return (None, None, None, None) |
|
141 |
+ |
|
142 |
+ tokens = [s.strip() for s in role_spec.split(',')] |
|
143 |
+ |
|
144 |
+ # assume https://github.com URLs are git+https:// URLs and not |
|
145 |
+ # tarballs unless they end in '.zip' |
|
146 |
+ if 'github.com/' in tokens[0] and not tokens[0].startswith("git+") and not tokens[0].endswith('.tar.gz'): |
|
147 |
+ tokens[0] = 'git+' + tokens[0] |
|
148 |
+ |
|
149 |
+ if '+' in tokens[0]: |
|
150 |
+ (scm, role_url) = tokens[0].split('+') |
|
151 |
+ else: |
|
152 |
+ scm = None |
|
153 |
+ role_url = tokens[0] |
|
189 | 154 |
|
190 |
-def get_role_metadata(role_name, options): |
|
191 |
- """ |
|
192 |
- Returns the metadata as YAML, if the file 'meta/main.yml' |
|
193 |
- exists in the specified role_path |
|
194 |
- """ |
|
195 |
- role_path = os.path.join(get_role_path(role_name, options), 'meta/main.yml') |
|
196 |
- try: |
|
197 |
- if os.path.isfile(role_path): |
|
198 |
- f = open(role_path, 'r') |
|
199 |
- meta_data = yaml.safe_load(f) |
|
200 |
- f.close() |
|
201 |
- return meta_data |
|
155 |
+ if len(tokens) >= 2: |
|
156 |
+ role_version = tokens[1] |
|
157 |
+ |
|
158 |
+ if len(tokens) == 3: |
|
159 |
+ role_name = tokens[2] |
|
202 | 160 |
else: |
203 |
- return None |
|
204 |
- except: |
|
205 |
- return None |
|
161 |
+ role_name = RoleRequirement.repo_url_to_role_name(tokens[0]) |
|
162 |
+ |
|
163 |
+ if scm and not role_version: |
|
164 |
+ role_version = default_role_versions.get(scm, '') |
|
165 |
+ |
|
166 |
+ return dict(scm=scm, src=role_url, version=role_version, name=role_name) |
|
167 |
+ |
|
168 |
+ @staticmethod |
|
169 |
+ def role_yaml_parse(role): |
|
170 |
+ |
|
171 |
+ if 'role' in role: |
|
172 |
+ # Old style: {role: "galaxy.role,version,name", other_vars: "here" } |
|
173 |
+ role_info = RoleRequirement.role_spec_parse(role['role']) |
|
174 |
+ if isinstance(role_info, dict): |
|
175 |
+ # Warning: Slight change in behaviour here. name may be being |
|
176 |
+ # overloaded. Previously, name was only a parameter to the role. |
|
177 |
+ # Now it is both a parameter to the role and the name that |
|
178 |
+ # ansible-galaxy will install under on the local system. |
|
179 |
+ if 'name' in role and 'name' in role_info: |
|
180 |
+ del role_info['name'] |
|
181 |
+ role.update(role_info) |
|
182 |
+ else: |
|
183 |
+ # New style: { src: 'galaxy.role,version,name', other_vars: "here" } |
|
184 |
+ if 'github.com' in role["src"] and 'http' in role["src"] and '+' not in role["src"] and not role["src"].endswith('.tar.gz'): |
|
185 |
+ role["src"] = "git+" + role["src"] |
|
186 |
+ |
|
187 |
+ if '+' in role["src"]: |
|
188 |
+ (scm, src) = role["src"].split('+') |
|
189 |
+ role["scm"] = scm |
|
190 |
+ role["src"] = src |
|
191 |
+ |
|
192 |
+ if 'name' not in role: |
|
193 |
+ role["name"] = RoleRequirement.repo_url_to_role_name(role["src"]) |
|
194 |
+ |
|
195 |
+ if 'version' not in role: |
|
196 |
+ role['version'] = '' |
|
197 |
+ |
|
198 |
+ if 'scm' not in role: |
|
199 |
+ role['scm'] = None |
|
200 |
+ |
|
201 |
+ return role |
|
202 |
+ |
|
203 |
+ @staticmethod |
|
204 |
+ def scm_archive_role(src, scm='git', name=None, version='HEAD'): |
|
205 |
+ if scm not in ['hg', 'git']: |
|
206 |
+ raise AnsibleError("- scm %s is not currently supported" % scm) |
|
207 |
+ tempdir = tempfile.mkdtemp() |
|
208 |
+ clone_cmd = [scm, 'clone', src, name] |
|
209 |
+ with open('/dev/null', 'w') as devnull: |
|
210 |
+ try: |
|
211 |
+ popen = subprocess.Popen(clone_cmd, cwd=tempdir, stdout=devnull, stderr=devnull) |
|
212 |
+ except: |
|
213 |
+ raise AnsibleError("error executing: %s" % " ".join(clone_cmd)) |
|
214 |
+ rc = popen.wait() |
|
215 |
+ if rc != 0: |
|
216 |
+ raise AnsibleError ("- command %s failed in directory %s" % (' '.join(clone_cmd), tempdir)) |
|
217 |
+ |
|
218 |
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.tar') |
|
219 |
+ if scm == 'hg': |
|
220 |
+ archive_cmd = ['hg', 'archive', '--prefix', "%s/" % name] |
|
221 |
+ if version: |
|
222 |
+ archive_cmd.extend(['-r', version]) |
|
223 |
+ archive_cmd.append(temp_file.name) |
|
224 |
+ if scm == 'git': |
|
225 |
+ archive_cmd = ['git', 'archive', '--prefix=%s/' % name, '--output=%s' % temp_file.name] |
|
226 |
+ if version: |
|
227 |
+ archive_cmd.append(version) |
|
228 |
+ else: |
|
229 |
+ archive_cmd.append('HEAD') |
|
230 |
+ |
|
231 |
+ with open('/dev/null', 'w') as devnull: |
|
232 |
+ popen = subprocess.Popen(archive_cmd, cwd=os.path.join(tempdir, name), |
|
233 |
+ stderr=devnull, stdout=devnull) |
|
234 |
+ rc = popen.wait() |
|
235 |
+ if rc != 0: |
|
236 |
+ raise AnsibleError("- command %s failed in directory %s" % (' '.join(archive_cmd), tempdir)) |
|
237 |
+ |
|
238 |
+ shutil.rmtree(tempdir, ignore_errors=True) |
|
239 |
+ return temp_file.name |
|
240 |
+ |
... | ... |
@@ -63,6 +63,7 @@ def get_docstring(filename, verbose=False): |
63 | 63 |
theid = t.id |
64 | 64 |
except AttributeError as e: |
65 | 65 |
# skip errors can happen when trying to use the normal code |
66 |
+ display.warning("Failed to assign id for %t on %s, skipping" % (t, filename)) |
|
66 | 67 |
continue |
67 | 68 |
|
68 | 69 |
if 'DOCUMENTATION' in theid: |
... | ... |
@@ -119,6 +120,7 @@ def get_docstring(filename, verbose=False): |
119 | 119 |
except: |
120 | 120 |
display.error("unable to parse %s" % filename) |
121 | 121 |
if verbose == True: |
122 |
+ display.display("unable to parse %s" % filename) |
|
122 | 123 |
raise |
123 | 124 |
return doc, plainexamples, returndocs |
124 | 125 |
|
... | ... |
@@ -172,7 +172,7 @@ test_galaxy: test_galaxy_spec test_galaxy_yaml |
172 | 172 |
|
173 | 173 |
test_galaxy_spec: |
174 | 174 |
mytmpdir=$(MYTMPDIR) ; \ |
175 |
- ansible-galaxy install -r galaxy_rolesfile -p $$mytmpdir/roles ; \ |
|
175 |
+ ansible-galaxy install -r galaxy_rolesfile -p $$mytmpdir/roles -vvvv ; \ |
|
176 | 176 |
cp galaxy_playbook.yml $$mytmpdir ; \ |
177 | 177 |
ansible-playbook -i $(INVENTORY) $$mytmpdir/galaxy_playbook.yml -v $(TEST_FLAGS) ; \ |
178 | 178 |
RC=$$? ; \ |
... | ... |
@@ -181,7 +181,7 @@ test_galaxy_spec: |
181 | 181 |
|
182 | 182 |
test_galaxy_yaml: |
183 | 183 |
mytmpdir=$(MYTMPDIR) ; \ |
184 |
- ansible-galaxy install -r galaxy_roles.yml -p $$mytmpdir/roles ; \ |
|
184 |
+ ansible-galaxy install -r galaxy_roles.yml -p $$mytmpdir/roles -vvvv; \ |
|
185 | 185 |
cp galaxy_playbook.yml $$mytmpdir ; \ |
186 | 186 |
ansible-playbook -i $(INVENTORY) $$mytmpdir/galaxy_playbook.yml -v $(TEST_FLAGS) ; \ |
187 | 187 |
RC=$$? ; \ |