brew.py 6.7 KB

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