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

https://github.com/debfx/ansible · Python · 191 lines · 133 code · 34 blank · 24 comment · 44 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. # Make coding more python3-ish
  18. from __future__ import (absolute_import, division, print_function)
  19. __metaclass__ = type
  20. import os
  21. import tempfile
  22. import tarfile
  23. from subprocess import Popen, PIPE
  24. from ansible import constants as C
  25. from ansible.errors import AnsibleError
  26. from ansible.module_utils._text import to_native
  27. from ansible.module_utils.common.process import get_bin_path
  28. from ansible.module_utils.six import string_types
  29. from ansible.playbook.role.definition import RoleDefinition
  30. from ansible.utils.display import Display
  31. __all__ = ['RoleRequirement']
  32. VALID_SPEC_KEYS = [
  33. 'name',
  34. 'role',
  35. 'scm',
  36. 'src',
  37. 'version',
  38. ]
  39. display = Display()
  40. class RoleRequirement(RoleDefinition):
  41. """
  42. Helper class for Galaxy, which is used to parse both dependencies
  43. specified in meta/main.yml and requirements.yml files.
  44. """
  45. def __init__(self):
  46. pass
  47. @staticmethod
  48. def repo_url_to_role_name(repo_url):
  49. # gets the role name out of a repo like
  50. # http://git.example.com/repos/repo.git" => "repo"
  51. if '://' not in repo_url and '@' not in repo_url:
  52. return repo_url
  53. trailing_path = repo_url.split('/')[-1]
  54. if trailing_path.endswith('.git'):
  55. trailing_path = trailing_path[:-4]
  56. if trailing_path.endswith('.tar.gz'):
  57. trailing_path = trailing_path[:-7]
  58. if ',' in trailing_path:
  59. trailing_path = trailing_path.split(',')[0]
  60. return trailing_path
  61. @staticmethod
  62. def role_yaml_parse(role):
  63. if isinstance(role, string_types):
  64. name = None
  65. scm = None
  66. src = None
  67. version = None
  68. if ',' in role:
  69. if role.count(',') == 1:
  70. (src, version) = role.strip().split(',', 1)
  71. elif role.count(',') == 2:
  72. (src, version, name) = role.strip().split(',', 2)
  73. else:
  74. raise AnsibleError("Invalid role line (%s). Proper format is 'role_name[,version[,name]]'" % role)
  75. else:
  76. src = role
  77. if name is None:
  78. name = RoleRequirement.repo_url_to_role_name(src)
  79. if '+' in src:
  80. (scm, src) = src.split('+', 1)
  81. return dict(name=name, src=src, scm=scm, version=version)
  82. if 'role' in role:
  83. name = role['role']
  84. if ',' in name:
  85. raise AnsibleError("Invalid old style role requirement: %s" % name)
  86. else:
  87. del role['role']
  88. role['name'] = name
  89. else:
  90. role = role.copy()
  91. if 'src'in role:
  92. # New style: { src: 'galaxy.role,version,name', other_vars: "here" }
  93. if 'github.com' in role["src"] and 'http' in role["src"] and '+' not in role["src"] and not role["src"].endswith('.tar.gz'):
  94. role["src"] = "git+" + role["src"]
  95. if '+' in role["src"]:
  96. (scm, src) = role["src"].split('+')
  97. role["scm"] = scm
  98. role["src"] = src
  99. if 'name' not in role:
  100. role["name"] = RoleRequirement.repo_url_to_role_name(role["src"])
  101. if 'version' not in role:
  102. role['version'] = ''
  103. if 'scm' not in role:
  104. role['scm'] = None
  105. for key in list(role.keys()):
  106. if key not in VALID_SPEC_KEYS:
  107. role.pop(key)
  108. return role
  109. @staticmethod
  110. def scm_archive_role(src, scm='git', name=None, version='HEAD', keep_scm_meta=False):
  111. def run_scm_cmd(cmd, tempdir):
  112. try:
  113. stdout = ''
  114. stderr = ''
  115. popen = Popen(cmd, cwd=tempdir, stdout=PIPE, stderr=PIPE)
  116. stdout, stderr = popen.communicate()
  117. except Exception as e:
  118. ran = " ".join(cmd)
  119. display.debug("ran %s:" % ran)
  120. display.debug("\tstdout: " + stdout)
  121. display.debug("\tstderr: " + stderr)
  122. raise AnsibleError("when executing %s: %s" % (ran, to_native(e)))
  123. if popen.returncode != 0:
  124. raise AnsibleError("- command %s failed in directory %s (rc=%s)" % (' '.join(cmd), tempdir, popen.returncode))
  125. if scm not in ['hg', 'git']:
  126. raise AnsibleError("- scm %s is not currently supported" % scm)
  127. try:
  128. scm_path = get_bin_path(scm, required=True)
  129. except (ValueError, OSError, IOError):
  130. raise AnsibleError("could not find/use %s, it is required to continue with installing %s" % (scm, src))
  131. tempdir = tempfile.mkdtemp(dir=C.DEFAULT_LOCAL_TMP)
  132. clone_cmd = [scm_path, 'clone', src, name]
  133. run_scm_cmd(clone_cmd, tempdir)
  134. if scm == 'git' and version:
  135. checkout_cmd = [scm_path, 'checkout', version]
  136. run_scm_cmd(checkout_cmd, os.path.join(tempdir, name))
  137. temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.tar', dir=C.DEFAULT_LOCAL_TMP)
  138. archive_cmd = None
  139. if keep_scm_meta:
  140. display.vvv('tarring %s from %s to %s' % (name, tempdir, temp_file.name))
  141. with tarfile.open(temp_file.name, "w") as tar:
  142. tar.add(os.path.join(tempdir, name), arcname=name)
  143. elif scm == 'hg':
  144. archive_cmd = [scm_path, 'archive', '--prefix', "%s/" % name]
  145. if version:
  146. archive_cmd.extend(['-r', version])
  147. archive_cmd.append(temp_file.name)
  148. elif scm == 'git':
  149. archive_cmd = [scm_path, 'archive', '--prefix=%s/' % name, '--output=%s' % temp_file.name]
  150. if version:
  151. archive_cmd.append(version)
  152. else:
  153. archive_cmd.append('HEAD')
  154. if archive_cmd is not None:
  155. display.vvv('archiving %s' % archive_cmd)
  156. run_scm_cmd(archive_cmd, os.path.join(tempdir, name))
  157. return temp_file.name