Commit f7332940 by Brian Coca

galaxy updates

better error reporting on fetching errors
use scm if it exists over src
unified functions in requirements
simplified logic
added verbose to tests
cleanup code refs, unused options and dead code
moved get_opt to base class
fixes #11920
fixes #12612
fixes #10454
parent a3ed9fc1
...@@ -512,3 +512,16 @@ class CLI(object): ...@@ -512,3 +512,16 @@ class CLI(object):
return vault_pass return vault_pass
def get_opt(self, k, defval=""):
"""
Returns an option from an Optparse values instance.
"""
try:
data = getattr(self.options, k)
except:
return defval
if k == "roles_path":
if os.pathsep in data:
data = data.split(os.pathsep)[0]
return data
...@@ -29,8 +29,6 @@ from distutils.version import LooseVersion ...@@ -29,8 +29,6 @@ from distutils.version import LooseVersion
from jinja2 import Environment from jinja2 import Environment
import ansible.constants as C import ansible.constants as C
import ansible.utils
import ansible.galaxy
from ansible.cli import CLI from ansible.cli import CLI
from ansible.errors import AnsibleError, AnsibleOptionsError from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.galaxy import Galaxy from ansible.galaxy import Galaxy
...@@ -126,19 +124,6 @@ class GalaxyCLI(CLI): ...@@ -126,19 +124,6 @@ class GalaxyCLI(CLI):
self.execute() self.execute()
def get_opt(self, k, defval=""):
"""
Returns an option from an Optparse values instance.
"""
try:
data = getattr(self.options, k)
except:
return defval
if k == "roles_path":
if os.pathsep in data:
data = data.split(os.pathsep)[0]
return data
def exit_without_ignore(self, rc=1): def exit_without_ignore(self, rc=1):
""" """
Exits with the specified return code unless the Exits with the specified return code unless the
...@@ -147,40 +132,6 @@ class GalaxyCLI(CLI): ...@@ -147,40 +132,6 @@ class GalaxyCLI(CLI):
if not self.get_opt("ignore_errors", False): if not self.get_opt("ignore_errors", False):
raise AnsibleError('- you can use --ignore-errors to skip failed roles and finish processing the list.') raise AnsibleError('- you can use --ignore-errors to skip failed roles and finish processing the list.')
def parse_requirements_files(self, role):
if 'role' in role:
# Old style: {role: "galaxy.role,version,name", other_vars: "here" }
role_info = role_spec_parse(role['role'])
if isinstance(role_info, dict):
# Warning: Slight change in behaviour here. name may be being
# overloaded. Previously, name was only a parameter to the role.
# Now it is both a parameter to the role and the name that
# ansible-galaxy will install under on the local system.
if 'name' in role and 'name' in role_info:
del role_info['name']
role.update(role_info)
else:
# New style: { src: 'galaxy.role,version,name', other_vars: "here" }
if 'github.com' in role["src"] and 'http' in role["src"] and '+' not in role["src"] and not role["src"].endswith('.tar.gz'):
role["src"] = "git+" + role["src"]
if '+' in role["src"]:
(scm, src) = role["src"].split('+')
role["scm"] = scm
role["src"] = src
if 'name' not in role:
role["name"] = GalaxyRole.url_to_spec(role["src"])
if 'version' not in role:
role['version'] = ''
if 'scm' not in role:
role['scm'] = None
return role
def _display_role_info(self, role_info): def _display_role_info(self, role_info):
text = "\nRole: %s \n" % role_info['name'] text = "\nRole: %s \n" % role_info['name']
...@@ -298,9 +249,8 @@ class GalaxyCLI(CLI): ...@@ -298,9 +249,8 @@ class GalaxyCLI(CLI):
data = '' data = ''
for role in self.args: for role in self.args:
role_info = {} role_info = {'role_path': roles_path}
gr = GalaxyRole(self.galaxy, role) gr = GalaxyRole(self.galaxy, role)
#self.galaxy.add_role(gr)
install_info = gr.install_info install_info = gr.install_info
if install_info: if install_info:
...@@ -351,54 +301,45 @@ class GalaxyCLI(CLI): ...@@ -351,54 +301,45 @@ class GalaxyCLI(CLI):
no_deps = self.get_opt("no_deps", False) no_deps = self.get_opt("no_deps", False)
force = self.get_opt('force', False) force = self.get_opt('force', False)
roles_path = self.get_opt("roles_path")
roles_done = []
roles_left = [] roles_left = []
if role_file: if role_file:
self.display.debug('Getting roles from %s' % role_file)
try: try:
self.display.debug('Processing role file: %s' % role_file)
f = open(role_file, 'r') f = open(role_file, 'r')
if role_file.endswith('.yaml') or role_file.endswith('.yml'): if role_file.endswith('.yaml') or role_file.endswith('.yml'):
try: for role in yaml.safe_load(f.read()):
rolesparsed = map(self.parse_requirements_files, yaml.safe_load(f)) self.display.debug('found role %s in yaml file' % str(role))
except Exception as e: if 'name' not in role:
raise AnsibleError("%s does not seem like a valid yaml file: %s" % (role_file, str(e))) if 'src' in role:
roles_left = [GalaxyRole(self.galaxy, **r) for r in rolesparsed] role['name'] = RoleRequirement.repo_url_to_role_name(role['src'])
else:
raise AnsibleError("Must specify name or src for role")
roles_left.append(GalaxyRole(self.galaxy, **role))
else: else:
self.display.deprecated("going forward only the yaml format will be supported")
# roles listed in a file, one per line # roles listed in a file, one per line
self.display.deprecated("Non yaml files for role requirements") for rline in f.readlines():
for rname in f.readlines(): self.display.debug('found role %s in text file' % str(rline))
if rname.startswith("#") or rname.strip() == '': roles_left.append(GalaxyRole(self.galaxy, **RoleRequirement.role_spec_parse(rline)))
continue
roles_left.append(GalaxyRole(self.galaxy, rname.strip()))
f.close() f.close()
except (IOError,OSError) as e: except (IOError, OSError) as e:
raise AnsibleError("Unable to read requirements file (%s): %s" % (role_file, str(e))) self.display.error('Unable to open %s: %s' % (role_file, str(e)))
else: else:
# roles were specified directly, so we'll just go out grab them # roles were specified directly, so we'll just go out grab them
# (and their dependencies, unless the user doesn't want us to). # (and their dependencies, unless the user doesn't want us to).
for rname in self.args: for rname in self.args:
roles_left.append(GalaxyRole(self.galaxy, rname.strip())) roles_left.append(GalaxyRole(self.galaxy, rname.strip()))
while len(roles_left) > 0: for role in roles_left:
self.display.debug('Installing role %s ' % role.name)
# query the galaxy API for the role data # query the galaxy API for the role data
role_data = None role_data = None
role = roles_left.pop(0) role = roles_left.pop(0)
role_path = role.path
if role.install_info is not None and not force: if role.install_info is not None and not force:
self.display.display('- %s is already installed, skipping.' % role.name) self.display.display('- %s is already installed, skipping.' % role.name)
continue continue
if role_path:
self.options.roles_path = role_path
else:
self.options.roles_path = roles_path
self.display.debug('Installing role %s from %s' % (role.name, self.options.roles_path))
tmp_file = None tmp_file = None
installed = False installed = False
if role.src and os.path.isfile(role.src): if role.src and os.path.isfile(role.src):
...@@ -407,7 +348,7 @@ class GalaxyCLI(CLI): ...@@ -407,7 +348,7 @@ class GalaxyCLI(CLI):
else: else:
if role.scm: if role.scm:
# create tar file from scm url # create tar file from scm url
tmp_file = GalaxyRole.scm_archive_role(role.scm, role.src, role.version, role.name) tmp_file = RoleRequirement.scm_archive_role(role.scm, role.src, role.version, role.name)
if role.src: if role.src:
if '://' not in role.src: if '://' not in role.src:
role_data = self.api.lookup_role_by_name(role.src) role_data = self.api.lookup_role_by_name(role.src)
...@@ -438,11 +379,14 @@ class GalaxyCLI(CLI): ...@@ -438,11 +379,14 @@ class GalaxyCLI(CLI):
# download the role. if --no-deps was specified, we stop here, # download the role. if --no-deps was specified, we stop here,
# otherwise we recursively grab roles and all of their deps. # otherwise we recursively grab roles and all of their deps.
tmp_file = role.fetch(role_data) tmp_file = role.fetch(role_data)
if tmp_file: if tmp_file:
self.display.debug('using %s' % tmp_file)
installed = role.install(tmp_file) installed = role.install(tmp_file)
# we're done with the temp file, clean it up # we're done with the temp file, clean it up if we created it
if tmp_file != role.src: if tmp_file != role.src:
os.unlink(tmp_file) os.unlink(tmp_file)
# install dependencies, if we want them # install dependencies, if we want them
if not no_deps and installed: if not no_deps and installed:
role_dependencies = role.metadata.get('dependencies', []) role_dependencies = role.metadata.get('dependencies', [])
...@@ -460,9 +404,10 @@ class GalaxyCLI(CLI): ...@@ -460,9 +404,10 @@ class GalaxyCLI(CLI):
else: else:
self.display.display('- dependency %s is already installed, skipping.' % dep_name) self.display.display('- dependency %s is already installed, skipping.' % dep_name)
if not tmp_file or not installed: if not installed:
self.display.warning("- %s was NOT installed successfully." % role.name) self.display.warning("- %s was NOT installed successfully." % role.name)
self.exit_without_ignore() self.exit_without_ignore()
return 0 return 0
def execute_remove(self): def execute_remove(self):
......
...@@ -21,19 +21,14 @@ __metaclass__ = type ...@@ -21,19 +21,14 @@ __metaclass__ = type
from six import iteritems, string_types from six import iteritems, string_types
import inspect
import os import os
from hashlib import sha1
from ansible.errors import AnsibleError, AnsibleParserError from ansible.errors import AnsibleError, AnsibleParserError
from ansible.parsing import DataLoader
from ansible.playbook.attribute import FieldAttribute from ansible.playbook.attribute import FieldAttribute
from ansible.playbook.base import Base from ansible.playbook.base import Base
from ansible.playbook.become import Become from ansible.playbook.become import Become
from ansible.playbook.conditional import Conditional from ansible.playbook.conditional import Conditional
from ansible.playbook.helpers import load_list_of_blocks from ansible.playbook.helpers import load_list_of_blocks
from ansible.playbook.role.include import RoleInclude
from ansible.playbook.role.metadata import RoleMetadata from ansible.playbook.role.metadata import RoleMetadata
from ansible.playbook.taggable import Taggable from ansible.playbook.taggable import Taggable
from ansible.plugins import get_all_plugin_loaders from ansible.plugins import get_all_plugin_loaders
......
...@@ -19,11 +19,14 @@ ...@@ -19,11 +19,14 @@
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
from six import iteritems, string_types from six import string_types
import os import os
import shutil
import subprocess
import tempfile
from ansible.errors import AnsibleError, AnsibleParserError from ansible.errors import AnsibleError
from ansible.playbook.role.definition import RoleDefinition from ansible.playbook.role.definition import RoleDefinition
__all__ = ['RoleRequirement'] __all__ = ['RoleRequirement']
...@@ -73,7 +76,7 @@ class RoleRequirement(RoleDefinition): ...@@ -73,7 +76,7 @@ class RoleRequirement(RoleDefinition):
def _preprocess_role_spec(self, ds): def _preprocess_role_spec(self, ds):
if 'role' in ds: if 'role' in ds:
# Old style: {role: "galaxy.role,version,name", other_vars: "here" } # Old style: {role: "galaxy.role,version,name", other_vars: "here" }
role_info = role_spec_parse(ds['role']) role_info = RoleRequirement.role_spec_parse(ds['role'])
if isinstance(role_info, dict): if isinstance(role_info, dict):
# Warning: Slight change in behaviour here. name may be being # Warning: Slight change in behaviour here. name may be being
# overloaded. Previously, name was only a parameter to the role. # overloaded. Previously, name was only a parameter to the role.
...@@ -96,7 +99,7 @@ class RoleRequirement(RoleDefinition): ...@@ -96,7 +99,7 @@ class RoleRequirement(RoleDefinition):
ds["role"] = ds["name"] ds["role"] = ds["name"]
del ds["name"] del ds["name"]
else: else:
ds["role"] = repo_url_to_role_name(ds["src"]) ds["role"] = RoleRequirement.repo_url_to_role_name(ds["src"])
# set some values to a default value, if none were specified # set some values to a default value, if none were specified
ds.setdefault('version', '') ds.setdefault('version', '')
...@@ -104,102 +107,137 @@ class RoleRequirement(RoleDefinition): ...@@ -104,102 +107,137 @@ class RoleRequirement(RoleDefinition):
return ds return ds
def repo_url_to_role_name(repo_url): @staticmethod
# gets the role name out of a repo like def repo_url_to_role_name(repo_url):
# http://git.example.com/repos/repo.git" => "repo" # gets the role name out of a repo like
# http://git.example.com/repos/repo.git" => "repo"
if '://' not in repo_url and '@' not in repo_url:
return repo_url if '://' not in repo_url and '@' not in repo_url:
trailing_path = repo_url.split('/')[-1] return repo_url
if trailing_path.endswith('.git'): trailing_path = repo_url.split('/')[-1]
trailing_path = trailing_path[:-4] if trailing_path.endswith('.git'):
if trailing_path.endswith('.tar.gz'): trailing_path = trailing_path[:-4]
trailing_path = trailing_path[:-7] if trailing_path.endswith('.tar.gz'):
if ',' in trailing_path: trailing_path = trailing_path[:-7]
trailing_path = trailing_path.split(',')[0] if ',' in trailing_path:
return trailing_path trailing_path = trailing_path.split(',')[0]
return trailing_path
def role_spec_parse(role_spec):
# takes a repo and a version like @staticmethod
# git+http://git.example.com/repos/repo.git,v1.0 def role_spec_parse(role_spec):
# and returns a list of properties such as: # takes a repo and a version like
# { # git+http://git.example.com/repos/repo.git,v1.0
# 'scm': 'git', # and returns a list of properties such as:
# 'src': 'http://git.example.com/repos/repo.git', # {
# 'version': 'v1.0', # 'scm': 'git',
# 'name': 'repo' # 'src': 'http://git.example.com/repos/repo.git',
# } # 'version': 'v1.0',
# 'name': 'repo'
default_role_versions = dict(git='master', hg='tip') # }
role_spec = role_spec.strip() default_role_versions = dict(git='master', hg='tip')
role_version = ''
if role_spec == "" or role_spec.startswith("#"): role_spec = role_spec.strip()
return (None, None, None, None) role_version = ''
if role_spec == "" or role_spec.startswith("#"):
tokens = [s.strip() for s in role_spec.split(',')] return (None, None, None, None)
# assume https://github.com URLs are git+https:// URLs and not tokens = [s.strip() for s in role_spec.split(',')]
# tarballs unless they end in '.zip'
if 'github.com/' in tokens[0] and not tokens[0].startswith("git+") and not tokens[0].endswith('.tar.gz'): # assume https://github.com URLs are git+https:// URLs and not
tokens[0] = 'git+' + tokens[0] # tarballs unless they end in '.zip'
if 'github.com/' in tokens[0] and not tokens[0].startswith("git+") and not tokens[0].endswith('.tar.gz'):
if '+' in tokens[0]: tokens[0] = 'git+' + tokens[0]
(scm, role_url) = tokens[0].split('+')
else: if '+' in tokens[0]:
scm = None (scm, role_url) = tokens[0].split('+')
role_url = tokens[0] else:
scm = None
if len(tokens) >= 2: role_url = tokens[0]
role_version = tokens[1]
if len(tokens) == 3:
role_name = tokens[2]
else:
role_name = repo_url_to_role_name(tokens[0])
if scm and not role_version:
role_version = default_role_versions.get(scm, '')
return dict(scm=scm, src=role_url, version=role_version, role_name=role_name)
# FIXME: all of these methods need to be cleaned up/reorganized below this
def get_opt(options, k, defval=""):
"""
Returns an option from an Optparse values instance.
"""
try:
data = getattr(options, k)
except:
return defval
if k == "roles_path":
if os.pathsep in data:
data = data.split(os.pathsep)[0]
return data
def get_role_path(role_name, options):
"""
Returns the role path based on the roles_path option
and the role name.
"""
roles_path = get_opt(options,'roles_path')
roles_path = os.path.join(roles_path, role_name)
roles_path = os.path.expanduser(roles_path)
return roles_path
def get_role_metadata(role_name, options): if len(tokens) >= 2:
""" role_version = tokens[1]
Returns the metadata as YAML, if the file 'meta/main.yml'
exists in the specified role_path if len(tokens) == 3:
""" role_name = tokens[2]
role_path = os.path.join(get_role_path(role_name, options), 'meta/main.yml')
try:
if os.path.isfile(role_path):
f = open(role_path, 'r')
meta_data = yaml.safe_load(f)
f.close()
return meta_data
else: else:
return None role_name = RoleRequirement.repo_url_to_role_name(tokens[0])
except:
return None if scm and not role_version:
role_version = default_role_versions.get(scm, '')
return dict(scm=scm, src=role_url, version=role_version, name=role_name)
@staticmethod
def role_yaml_parse(role):
if 'role' in role:
# Old style: {role: "galaxy.role,version,name", other_vars: "here" }
role_info = RoleRequirement.role_spec_parse(role['role'])
if isinstance(role_info, dict):
# Warning: Slight change in behaviour here. name may be being
# overloaded. Previously, name was only a parameter to the role.
# Now it is both a parameter to the role and the name that
# ansible-galaxy will install under on the local system.
if 'name' in role and 'name' in role_info:
del role_info['name']
role.update(role_info)
else:
# New style: { src: 'galaxy.role,version,name', other_vars: "here" }
if 'github.com' in role["src"] and 'http' in role["src"] and '+' not in role["src"] and not role["src"].endswith('.tar.gz'):
role["src"] = "git+" + role["src"]
if '+' in role["src"]:
(scm, src) = role["src"].split('+')
role["scm"] = scm
role["src"] = src
if 'name' not in role:
role["name"] = RoleRequirement.repo_url_to_role_name(role["src"])
if 'version' not in role:
role['version'] = ''
if 'scm' not in role:
role['scm'] = None
return role
@staticmethod
def scm_archive_role(src, scm='git', name=None, version='HEAD'):
if scm not in ['hg', 'git']:
raise AnsibleError("- scm %s is not currently supported" % scm)
tempdir = tempfile.mkdtemp()
clone_cmd = [scm, 'clone', src, name]
with open('/dev/null', 'w') as devnull:
try:
popen = subprocess.Popen(clone_cmd, cwd=tempdir, stdout=devnull, stderr=devnull)
except:
raise AnsibleError("error executing: %s" % " ".join(clone_cmd))
rc = popen.wait()
if rc != 0:
raise AnsibleError ("- command %s failed in directory %s" % (' '.join(clone_cmd), tempdir))
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.tar')
if scm == 'hg':
archive_cmd = ['hg', 'archive', '--prefix', "%s/" % name]
if version:
archive_cmd.extend(['-r', version])
archive_cmd.append(temp_file.name)
if scm == 'git':
archive_cmd = ['git', 'archive', '--prefix=%s/' % name, '--output=%s' % temp_file.name]
if version:
archive_cmd.append(version)
else:
archive_cmd.append('HEAD')
with open('/dev/null', 'w') as devnull:
popen = subprocess.Popen(archive_cmd, cwd=os.path.join(tempdir, name),
stderr=devnull, stdout=devnull)
rc = popen.wait()
if rc != 0:
raise AnsibleError("- command %s failed in directory %s" % (' '.join(archive_cmd), tempdir))
shutil.rmtree(tempdir, ignore_errors=True)
return temp_file.name
...@@ -63,6 +63,7 @@ def get_docstring(filename, verbose=False): ...@@ -63,6 +63,7 @@ def get_docstring(filename, verbose=False):
theid = t.id theid = t.id
except AttributeError as e: except AttributeError as e:
# skip errors can happen when trying to use the normal code # skip errors can happen when trying to use the normal code
display.warning("Failed to assign id for %t on %s, skipping" % (t, filename))
continue continue
if 'DOCUMENTATION' in theid: if 'DOCUMENTATION' in theid:
...@@ -119,6 +120,7 @@ def get_docstring(filename, verbose=False): ...@@ -119,6 +120,7 @@ def get_docstring(filename, verbose=False):
except: except:
display.error("unable to parse %s" % filename) display.error("unable to parse %s" % filename)
if verbose == True: if verbose == True:
display.display("unable to parse %s" % filename)
raise raise
return doc, plainexamples, returndocs return doc, plainexamples, returndocs
...@@ -172,7 +172,7 @@ test_galaxy: test_galaxy_spec test_galaxy_yaml ...@@ -172,7 +172,7 @@ test_galaxy: test_galaxy_spec test_galaxy_yaml
test_galaxy_spec: test_galaxy_spec:
mytmpdir=$(MYTMPDIR) ; \ mytmpdir=$(MYTMPDIR) ; \
ansible-galaxy install -r galaxy_rolesfile -p $$mytmpdir/roles ; \ ansible-galaxy install -r galaxy_rolesfile -p $$mytmpdir/roles -vvvv ; \
cp galaxy_playbook.yml $$mytmpdir ; \ cp galaxy_playbook.yml $$mytmpdir ; \
ansible-playbook -i $(INVENTORY) $$mytmpdir/galaxy_playbook.yml -v $(TEST_FLAGS) ; \ ansible-playbook -i $(INVENTORY) $$mytmpdir/galaxy_playbook.yml -v $(TEST_FLAGS) ; \
RC=$$? ; \ RC=$$? ; \
...@@ -181,7 +181,7 @@ test_galaxy_spec: ...@@ -181,7 +181,7 @@ test_galaxy_spec:
test_galaxy_yaml: test_galaxy_yaml:
mytmpdir=$(MYTMPDIR) ; \ mytmpdir=$(MYTMPDIR) ; \
ansible-galaxy install -r galaxy_roles.yml -p $$mytmpdir/roles ; \ ansible-galaxy install -r galaxy_roles.yml -p $$mytmpdir/roles -vvvv; \
cp galaxy_playbook.yml $$mytmpdir ; \ cp galaxy_playbook.yml $$mytmpdir ; \
ansible-playbook -i $(INVENTORY) $$mytmpdir/galaxy_playbook.yml -v $(TEST_FLAGS) ; \ ansible-playbook -i $(INVENTORY) $$mytmpdir/galaxy_playbook.yml -v $(TEST_FLAGS) ; \
RC=$$? ; \ RC=$$? ; \
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment