PageRenderTime 39ms CodeModel.GetById 9ms app.highlight 25ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/ansible/playbook/role/requirement.py

https://github.com/debfx/ansible
Python | 191 lines | 145 code | 22 blank | 24 comment | 39 complexity | 359be95acbab8e2e06c39fa3ad8f9ca3 MD5 | raw file
  1# (c) 2014 Michael DeHaan, <michael@ansible.com>
  2#
  3# This file is part of Ansible
  4#
  5# Ansible is free software: you can redistribute it and/or modify
  6# it under the terms of the GNU General Public License as published by
  7# the Free Software Foundation, either version 3 of the License, or
  8# (at your option) any later version.
  9#
 10# Ansible is distributed in the hope that it will be useful,
 11# but WITHOUT ANY WARRANTY; without even the implied warranty of
 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13# GNU General Public License for more details.
 14#
 15# You should have received a copy of the GNU General Public License
 16# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
 17
 18# Make coding more python3-ish
 19from __future__ import (absolute_import, division, print_function)
 20__metaclass__ = type
 21
 22import os
 23import tempfile
 24import tarfile
 25
 26from subprocess import Popen, PIPE
 27
 28from ansible import constants as C
 29from ansible.errors import AnsibleError
 30from ansible.module_utils._text import to_native
 31from ansible.module_utils.common.process import get_bin_path
 32from ansible.module_utils.six import string_types
 33from ansible.playbook.role.definition import RoleDefinition
 34from ansible.utils.display import Display
 35
 36__all__ = ['RoleRequirement']
 37
 38VALID_SPEC_KEYS = [
 39    'name',
 40    'role',
 41    'scm',
 42    'src',
 43    'version',
 44]
 45
 46display = Display()
 47
 48
 49class RoleRequirement(RoleDefinition):
 50
 51    """
 52    Helper class for Galaxy, which is used to parse both dependencies
 53    specified in meta/main.yml and requirements.yml files.
 54    """
 55
 56    def __init__(self):
 57        pass
 58
 59    @staticmethod
 60    def repo_url_to_role_name(repo_url):
 61        # gets the role name out of a repo like
 62        # http://git.example.com/repos/repo.git" => "repo"
 63
 64        if '://' not in repo_url and '@' not in repo_url:
 65            return repo_url
 66        trailing_path = repo_url.split('/')[-1]
 67        if trailing_path.endswith('.git'):
 68            trailing_path = trailing_path[:-4]
 69        if trailing_path.endswith('.tar.gz'):
 70            trailing_path = trailing_path[:-7]
 71        if ',' in trailing_path:
 72            trailing_path = trailing_path.split(',')[0]
 73        return trailing_path
 74
 75    @staticmethod
 76    def role_yaml_parse(role):
 77
 78        if isinstance(role, string_types):
 79            name = None
 80            scm = None
 81            src = None
 82            version = None
 83            if ',' in role:
 84                if role.count(',') == 1:
 85                    (src, version) = role.strip().split(',', 1)
 86                elif role.count(',') == 2:
 87                    (src, version, name) = role.strip().split(',', 2)
 88                else:
 89                    raise AnsibleError("Invalid role line (%s). Proper format is 'role_name[,version[,name]]'" % role)
 90            else:
 91                src = role
 92
 93            if name is None:
 94                name = RoleRequirement.repo_url_to_role_name(src)
 95            if '+' in src:
 96                (scm, src) = src.split('+', 1)
 97
 98            return dict(name=name, src=src, scm=scm, version=version)
 99
100        if 'role' in role:
101            name = role['role']
102            if ',' in name:
103                raise AnsibleError("Invalid old style role requirement: %s" % name)
104            else:
105                del role['role']
106                role['name'] = name
107        else:
108            role = role.copy()
109
110            if 'src'in role:
111                # New style: { src: 'galaxy.role,version,name', other_vars: "here" }
112                if 'github.com' in role["src"] and 'http' in role["src"] and '+' not in role["src"] and not role["src"].endswith('.tar.gz'):
113                    role["src"] = "git+" + role["src"]
114
115                if '+' in role["src"]:
116                    (scm, src) = role["src"].split('+')
117                    role["scm"] = scm
118                    role["src"] = src
119
120                if 'name' not in role:
121                    role["name"] = RoleRequirement.repo_url_to_role_name(role["src"])
122
123            if 'version' not in role:
124                role['version'] = ''
125
126            if 'scm' not in role:
127                role['scm'] = None
128
129        for key in list(role.keys()):
130            if key not in VALID_SPEC_KEYS:
131                role.pop(key)
132
133        return role
134
135    @staticmethod
136    def scm_archive_role(src, scm='git', name=None, version='HEAD', keep_scm_meta=False):
137
138        def run_scm_cmd(cmd, tempdir):
139            try:
140                stdout = ''
141                stderr = ''
142                popen = Popen(cmd, cwd=tempdir, stdout=PIPE, stderr=PIPE)
143                stdout, stderr = popen.communicate()
144            except Exception as e:
145                ran = " ".join(cmd)
146                display.debug("ran %s:" % ran)
147                display.debug("\tstdout: " + stdout)
148                display.debug("\tstderr: " + stderr)
149                raise AnsibleError("when executing %s: %s" % (ran, to_native(e)))
150            if popen.returncode != 0:
151                raise AnsibleError("- command %s failed in directory %s (rc=%s)" % (' '.join(cmd), tempdir, popen.returncode))
152
153        if scm not in ['hg', 'git']:
154            raise AnsibleError("- scm %s is not currently supported" % scm)
155
156        try:
157            scm_path = get_bin_path(scm, required=True)
158        except (ValueError, OSError, IOError):
159            raise AnsibleError("could not find/use %s, it is required to continue with installing %s" % (scm, src))
160
161        tempdir = tempfile.mkdtemp(dir=C.DEFAULT_LOCAL_TMP)
162        clone_cmd = [scm_path, 'clone', src, name]
163        run_scm_cmd(clone_cmd, tempdir)
164
165        if scm == 'git' and version:
166            checkout_cmd = [scm_path, 'checkout', version]
167            run_scm_cmd(checkout_cmd, os.path.join(tempdir, name))
168
169        temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.tar', dir=C.DEFAULT_LOCAL_TMP)
170        archive_cmd = None
171        if keep_scm_meta:
172            display.vvv('tarring %s from %s to %s' % (name, tempdir, temp_file.name))
173            with tarfile.open(temp_file.name, "w") as tar:
174                tar.add(os.path.join(tempdir, name), arcname=name)
175        elif scm == 'hg':
176            archive_cmd = [scm_path, 'archive', '--prefix', "%s/" % name]
177            if version:
178                archive_cmd.extend(['-r', version])
179            archive_cmd.append(temp_file.name)
180        elif scm == 'git':
181            archive_cmd = [scm_path, 'archive', '--prefix=%s/' % name, '--output=%s' % temp_file.name]
182            if version:
183                archive_cmd.append(version)
184            else:
185                archive_cmd.append('HEAD')
186
187        if archive_cmd is not None:
188            display.vvv('archiving %s' % archive_cmd)
189            run_scm_cmd(archive_cmd, os.path.join(tempdir, name))
190
191        return temp_file.name