diff --git a/lib/ansible/cli/galaxy.py b/lib/ansible/cli/galaxy.py index 6f9bf612ff0..84a0bce2a19 100644 --- a/lib/ansible/cli/galaxy.py +++ b/lib/ansible/cli/galaxy.py @@ -352,6 +352,7 @@ class GalaxyCLI(CLI): raise AnsibleOptionsError("- please specify a user/role name, or a roles file, but not both") no_deps = self.get_opt("no_deps", False) + force = self.get_opt('force', False) roles_path = self.get_opt("roles_path") roles_done = [] @@ -389,6 +390,9 @@ class GalaxyCLI(CLI): role = roles_left.pop(0) role_path = role.path + if role.install_info is not None and not force: + self.display.display('- %s is already installed, skipping.' % role.name) + continue if role_path: self.options.roles_path = role_path @@ -443,25 +447,20 @@ class GalaxyCLI(CLI): os.unlink(tmp_file) # install dependencies, if we want them if not no_deps and installed: - if not role_data: - role_data = gr.get_metadata(role.get("name"), options) - role_dependencies = role_data['dependencies'] - else: - role_dependencies = role_data['summary_fields']['dependencies'] # api_fetch_role_related(api_server, 'dependencies', role_data['id']) + role_dependencies = role.metadata.get('dependencies', []) for dep in role_dependencies: self.display.debug('Installing dep %s' % dep) - if isinstance(dep, basestring): - dep = ansible.utils.role_spec_parse(dep) - else: - dep = ansible.utils.role_yaml_parse(dep) - if not get_role_metadata(dep["name"], options): - if dep not in roles_left: - self.display.display('- adding dependency: %s' % dep["name"]) - roles_left.append(dep) + dep_req = RoleRequirement() + __, dep_name, __ = dep_req.parse(dep) + dep_role = GalaxyRole(self.galaxy, name=dep_name) + if dep_role.install_info is None or force: + if dep_role not in roles_left: + self.display.display('- adding dependency: %s' % dep_name) + roles_left.append(GalaxyRole(self.galaxy, name=dep_name)) else: - self.display.display('- dependency %s already pending installation.' % dep["name"]) + self.display.display('- dependency %s already pending installation.' % dep_name) 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: self.display.warning("- %s was NOT installed successfully." % role.name) diff --git a/lib/ansible/galaxy/__init__.py b/lib/ansible/galaxy/__init__.py index 3b89dac8472..90caed6e22d 100644 --- a/lib/ansible/galaxy/__init__.py +++ b/lib/ansible/galaxy/__init__.py @@ -22,6 +22,8 @@ import os +from six import string_types + from ansible.errors import AnsibleError from ansible.utils.display import Display @@ -40,9 +42,9 @@ class Galaxy(object): self.display = display self.options = options - self.roles_path = getattr(self.options, 'roles_path', None) - if self.roles_path: - self.roles_path = os.path.expanduser(self.roles_path) + roles_paths = getattr(self.options, 'roles_path', []) + if isinstance(roles_paths, string_types): + self.roles_paths = [os.path.expanduser(roles_path) for roles_path in roles_paths.split(os.pathsep)] self.roles = {} diff --git a/lib/ansible/galaxy/role.py b/lib/ansible/galaxy/role.py index 439ba665b7a..dbe979a5834 100644 --- a/lib/ansible/galaxy/role.py +++ b/lib/ansible/galaxy/role.py @@ -39,7 +39,7 @@ class GalaxyRole(object): ROLE_DIRS = ('defaults','files','handlers','meta','tasks','templates','vars') - def __init__(self, galaxy, name, src=None, version=None, scm=None): + def __init__(self, galaxy, name, src=None, version=None, scm=None, role_path=None): self._metadata = None self._install_info = None @@ -52,7 +52,20 @@ class GalaxyRole(object): self.src = src or name self.scm = scm - self.path = (os.path.join(galaxy.roles_path, self.name)) + if role_path is not None: + self.path = role_path + else: + for path in galaxy.roles_paths: + role_path = os.path.join(path, self.name) + if os.path.exists(role_path): + self.path = role_path + break + else: + # use the first path by default + self.path = os.path.join(galaxy.roles_paths[0], self.name) + + def __eq__(self, other): + return self.name == other.name def fetch_from_scm_archive(self): diff --git a/lib/ansible/playbook/role/requirement.py b/lib/ansible/playbook/role/requirement.py index 863f393409e..d7ae9a626ae 100644 --- a/lib/ansible/playbook/role/requirement.py +++ b/lib/ansible/playbook/role/requirement.py @@ -54,26 +54,26 @@ class RoleRequirement(RoleDefinition): assert type(ds) == dict or isinstance(ds, string_types) - role_name = '' + role_name = None role_params = dict() new_ds = dict() if isinstance(ds, string_types): role_name = ds else: - ds = self._preprocess_role_spec(ds) - (new_ds, role_params) = self._split_role_params(ds) + (new_ds, role_params) = self._split_role_params(self._preprocess_role_spec(ds)) # pull the role name out of the ds - role_name = new_ds.get('role_name') - del ds['role_name'] + role_name = new_ds.pop('role_name', new_ds.pop('role', None)) + if role_name is None: + raise AnsibleError("Role requirement did not contain a role name!", obj=ds) return (new_ds, role_name, role_params) def _preprocess_role_spec(self, ds): if 'role' in ds: # Old style: {role: "galaxy.role,version,name", other_vars: "here" } - role_info = self._role_spec_parse(ds['role']) + role_info = role_spec_parse(ds['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. @@ -96,7 +96,7 @@ class RoleRequirement(RoleDefinition): ds["role"] = ds["name"] del ds["name"] else: - ds["role"] = self._repo_url_to_role_name(ds["src"]) + ds["role"] = repo_url_to_role_name(ds["src"]) # set some values to a default value, if none were specified ds.setdefault('version', '') @@ -104,96 +104,102 @@ class RoleRequirement(RoleDefinition): return ds - def _repo_url_to_role_name(self, repo_url): - # gets the role name out of a repo like - # http://git.example.com/repos/repo.git" => "repo" +def repo_url_to_role_name(repo_url): + # 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 - trailing_path = repo_url.split('/')[-1] - if trailing_path.endswith('.git'): - trailing_path = trailing_path[:-4] - if trailing_path.endswith('.tar.gz'): - trailing_path = trailing_path[:-7] - if ',' in trailing_path: - trailing_path = trailing_path.split(',')[0] - return trailing_path + if '://' not in repo_url and '@' not in repo_url: + return repo_url + trailing_path = repo_url.split('/')[-1] + if trailing_path.endswith('.git'): + trailing_path = trailing_path[:-4] + if trailing_path.endswith('.tar.gz'): + trailing_path = trailing_path[:-7] + if ',' in trailing_path: + trailing_path = trailing_path.split(',')[0] + return trailing_path - def _role_spec_parse(self, role_spec): - # takes a repo and a version like - # git+http://git.example.com/repos/repo.git,v1.0 - # and returns a list of properties such as: - # { - # 'scm': 'git', - # 'src': 'http://git.example.com/repos/repo.git', - # 'version': 'v1.0', - # 'name': 'repo' - # } +def role_spec_parse(role_spec): + # takes a repo and a version like + # git+http://git.example.com/repos/repo.git,v1.0 + # and returns a list of properties such as: + # { + # 'scm': 'git', + # 'src': 'http://git.example.com/repos/repo.git', + # 'version': 'v1.0', + # 'name': 'repo' + # } - default_role_versions = dict(git='master', hg='tip') + default_role_versions = dict(git='master', hg='tip') - role_spec = role_spec.strip() - role_version = '' - if role_spec == "" or role_spec.startswith("#"): - return (None, None, None, None) + role_spec = role_spec.strip() + role_version = '' + if role_spec == "" or role_spec.startswith("#"): + return (None, None, None, None) - tokens = [s.strip() for s in role_spec.split(',')] + tokens = [s.strip() for s in role_spec.split(',')] - # assume https://github.com URLs are git+https:// URLs and not - # 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'): - tokens[0] = 'git+' + tokens[0] + # assume https://github.com URLs are git+https:// URLs and not + # 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'): + tokens[0] = 'git+' + tokens[0] - if '+' in tokens[0]: - (scm, role_url) = tokens[0].split('+') - else: - scm = None - role_url = tokens[0] - - if len(tokens) >= 2: - role_version = tokens[1] - - if len(tokens) == 3: - role_name = tokens[2] - else: - role_name = self._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) - - -def role_yaml_parse(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) + if '+' in tokens[0]: + (scm, role_url) = tokens[0].split('+') 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"] + scm = None + role_url = tokens[0] - if '+' in role["src"]: - (scm, src) = role["src"].split('+') - role["scm"] = scm - role["src"] = src + if len(tokens) >= 2: + role_version = tokens[1] - if 'name' not in role: - role["name"] = repo_url_to_role_name(role["src"]) + if len(tokens) == 3: + role_name = tokens[2] + else: + role_name = repo_url_to_role_name(tokens[0]) - if 'version' not in role: - role['version'] = '' + if scm and not role_version: + role_version = default_role_versions.get(scm, '') - if 'scm' not in role: - role['scm'] = None + return dict(scm=scm, src=role_url, version=role_version, role_name=role_name) - return role +# 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): + """ + Returns the metadata as YAML, if the file 'meta/main.yml' + exists in the specified role_path + """ + 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: + return None + except: + return None