brew.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. import os
  2. import logging
  3. from shutil import rmtree
  4. import docker
  5. import git
  6. DEFAULT_REPOSITORY = 'git://github.com/dotcloud/docker'
  7. DEFAULT_BRANCH = 'master'
  8. logger = logging.getLogger(__name__)
  9. logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
  10. level='INFO')
  11. client = docker.Client()
  12. processed = {}
  13. processed_folders = []
  14. def build_library(repository=None, branch=None, namespace=None, push=False,
  15. debug=False, prefill=True, registry=None):
  16. dst_folder = None
  17. summary = Summary()
  18. if repository is None:
  19. repository = DEFAULT_REPOSITORY
  20. if branch is None:
  21. branch = DEFAULT_BRANCH
  22. if debug:
  23. logger.setLevel('DEBUG')
  24. if not (repository.startswith('https://') or repository.startswith('git://')):
  25. logger.info('Repository provided assumed to be a local path')
  26. dst_folder = repository
  27. try:
  28. client.version()
  29. except Exception as e:
  30. logger.error('Could not reach the docker daemon. Please make sure it '
  31. 'is running.')
  32. logger.warning('Also make sure you have access to the docker UNIX '
  33. 'socket (use sudo)')
  34. return
  35. #FIXME: set destination folder and only pull latest changes instead of
  36. # cloning the whole repo everytime
  37. if not dst_folder:
  38. logger.info('Cloning docker repo from {0}, branch: {1}'.format(
  39. repository, branch))
  40. try:
  41. rep, dst_folder = git.clone_branch(repository, branch)
  42. except Exception as e:
  43. logger.exception(e)
  44. logger.error('Source repository could not be fetched. Check '
  45. 'that the address is correct and the branch exists.')
  46. return
  47. try:
  48. dirlist = os.listdir(os.path.join(dst_folder, 'library'))
  49. except OSError as e:
  50. logger.error('The path provided ({0}) could not be found or didn\'t'
  51. 'contain a library/ folder.'.format(dst_folder))
  52. return
  53. for buildfile in dirlist:
  54. if buildfile == 'MAINTAINERS':
  55. continue
  56. f = open(os.path.join(dst_folder, 'library', buildfile))
  57. linecnt = 0
  58. for line in f:
  59. linecnt = linecnt + 1
  60. logger.debug('{0} ---> {1}'.format(buildfile, line))
  61. args = line.split()
  62. try:
  63. if len(args) > 3:
  64. raise RuntimeError('Incorrect line format, '
  65. 'please refer to the docs')
  66. url = None
  67. ref = 'refs/heads/master'
  68. tag = None
  69. if len(args) == 1: # Just a URL, simple mode
  70. url = args[0]
  71. elif len(args) == 2 or len(args) == 3: # docker-tag url
  72. url = args[1]
  73. tag = args[0]
  74. if len(args) == 3: # docker-tag url B:branch or T:tag
  75. ref = None
  76. if args[2].startswith('B:'):
  77. ref = 'refs/heads/' + args[2][2:]
  78. elif args[2].startswith('T:'):
  79. ref = 'refs/tags/' + args[2][2:]
  80. elif args[2].startswith('C:'):
  81. ref = args[2][2:]
  82. else:
  83. raise RuntimeError('Incorrect line format, '
  84. 'please refer to the docs')
  85. if prefill:
  86. logger.debug('Pulling {0} from official repository (cache '
  87. 'fill)'.format(buildfile))
  88. try:
  89. client.pull('stackbrew/' + buildfile)
  90. except:
  91. # Image is not on official repository, ignore prefill
  92. pass
  93. img = build_repo(url, ref, buildfile, tag, namespace, push,
  94. registry)
  95. summary.add_success(buildfile, (linecnt, line), img)
  96. processed['{0}@{1}'.format(url, ref)] = img
  97. except Exception as e:
  98. logger.exception(e)
  99. summary.add_exception(buildfile, (linecnt, line), e)
  100. f.close()
  101. if dst_folder != repository:
  102. rmtree(dst_folder, True)
  103. for d in processed_folders:
  104. rmtree(d, True)
  105. summary.print_summary(logger)
  106. def build_repo(repository, ref, docker_repo, docker_tag, namespace, push, registry):
  107. docker_repo = '{0}/{1}'.format(namespace or 'library', docker_repo)
  108. img_id = None
  109. dst_folder = None
  110. if '{0}@{1}'.format(repository, ref) not in processed.keys():
  111. logger.info('Cloning {0} (ref: {1})'.format(repository, ref))
  112. if repository not in processed:
  113. rep, dst_folder = git.clone(repository, ref)
  114. processed[repository] = rep
  115. processed_folders.append(dst_folder)
  116. else:
  117. dst_folder = git.checkout(processed[repository], ref)
  118. if not 'Dockerfile' in os.listdir(dst_folder):
  119. raise RuntimeError('Dockerfile not found in cloned repository')
  120. logger.info('Building using dockerfile...')
  121. img_id, logs = client.build(path=dst_folder, quiet=True)
  122. else:
  123. img_id = processed['{0}@{1}'.format(repository, ref)]
  124. logger.info('Committing to {0}:{1}'.format(docker_repo,
  125. docker_tag or 'latest'))
  126. client.tag(img_id, docker_repo, docker_tag)
  127. if push:
  128. logger.info('Pushing result to registry {0}'.format(
  129. registry or "default"))
  130. if registry is not None:
  131. docker_repo = '{0}/{1}'.format(registry, docker_repo)
  132. logger.info('Also tagging {0}'.format(docker_repo))
  133. client.tag(img_id, docker_repo, docker_tag)
  134. client.push(docker_repo)
  135. return img_id
  136. class Summary(object):
  137. def __init__(self):
  138. self._summary = {}
  139. self._has_exc = False
  140. def _add_data(self, image, linestr, data):
  141. if image not in self._summary:
  142. self._summary[image] = { linestr: data }
  143. else:
  144. self._summary[image][linestr] = data
  145. def add_exception(self, image, line, exc):
  146. lineno, linestr = line
  147. self._add_data(image, linestr, { 'line': lineno, 'exc': str(exc) })
  148. self._has_exc = True
  149. def add_success(self, image, line, img_id):
  150. lineno, linestr = line
  151. self._add_data(image, linestr, { 'line': lineno, 'id': img_id })
  152. def print_summary(self, logger=None):
  153. linesep = ''.center(61, '-') + '\n'
  154. s = 'BREW BUILD SUMMARY\n' + linesep
  155. success = 'OVERALL SUCCESS: {}\n'.format(not self._has_exc)
  156. details = linesep
  157. for image, lines in self._summary.iteritems():
  158. details = details + '{}\n{}'.format(image, linesep)
  159. for linestr, data in lines.iteritems():
  160. details = details + '{0:2} | {1} | {2:50}\n'.format(
  161. data['line'],
  162. 'KO' if 'exc' in data else 'OK',
  163. data['exc'] if 'exc' in data else data['id']
  164. )
  165. details = details + linesep
  166. if logger:
  167. logger.info(s + success + details)
  168. else:
  169. print s, success, details