main.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import logging
  2. import sys
  3. import re
  4. from inspect import getdoc
  5. from .. import __version__
  6. from ..project import NoSuchService
  7. from .command import Command
  8. from .formatter import Formatter
  9. from .log_printer import LogPrinter
  10. from .utils import yesno
  11. from docker.client import APIError
  12. from .errors import UserError
  13. from .docopt_command import NoSuchCommand
  14. from .socketclient import SocketClient
  15. log = logging.getLogger(__name__)
  16. def main():
  17. console_handler = logging.StreamHandler()
  18. console_handler.setFormatter(logging.Formatter())
  19. console_handler.setLevel(logging.INFO)
  20. root_logger = logging.getLogger()
  21. root_logger.addHandler(console_handler)
  22. root_logger.setLevel(logging.DEBUG)
  23. # Disable requests logging
  24. logging.getLogger("requests").propagate = False
  25. try:
  26. command = TopLevelCommand()
  27. command.sys_dispatch()
  28. except KeyboardInterrupt:
  29. log.error("\nAborting.")
  30. exit(1)
  31. except UserError, e:
  32. log.error(e.msg)
  33. exit(1)
  34. except NoSuchService, e:
  35. log.error(e.msg)
  36. exit(1)
  37. except NoSuchCommand, e:
  38. log.error("No such command: %s", e.command)
  39. log.error("")
  40. log.error("\n".join(parse_doc_section("commands:", getdoc(e.supercommand))))
  41. exit(1)
  42. except APIError, e:
  43. log.error(e.explanation)
  44. exit(1)
  45. # stolen from docopt master
  46. def parse_doc_section(name, source):
  47. pattern = re.compile('^([^\n]*' + name + '[^\n]*\n?(?:[ \t].*?(?:\n|$))*)',
  48. re.IGNORECASE | re.MULTILINE)
  49. return [s.strip() for s in pattern.findall(source)]
  50. class TopLevelCommand(Command):
  51. """Punctual, lightweight development environments using Docker.
  52. Usage:
  53. fig [options] [COMMAND] [ARGS...]
  54. fig -h|--help
  55. Options:
  56. --verbose Show more output
  57. --version Print version and exit
  58. Commands:
  59. build Build or rebuild services
  60. kill Kill containers
  61. logs View output from containers
  62. ps List containers
  63. rm Remove stopped containers
  64. run Run a one-off command
  65. start Start services
  66. stop Stop services
  67. up Create and start containers
  68. """
  69. def docopt_options(self):
  70. options = super(TopLevelCommand, self).docopt_options()
  71. options['version'] = "fig %s" % __version__
  72. return options
  73. def build(self, options):
  74. """
  75. Build or rebuild services.
  76. Usage: build [SERVICE...]
  77. """
  78. self.project.build(service_names=options['SERVICE'])
  79. def kill(self, options):
  80. """
  81. Kill containers.
  82. Usage: kill [SERVICE...]
  83. """
  84. self.project.kill(service_names=options['SERVICE'])
  85. def logs(self, options):
  86. """
  87. View output from containers.
  88. Usage: logs [SERVICE...]
  89. """
  90. containers = self.project.containers(service_names=options['SERVICE'], stopped=False)
  91. print "Attaching to", list_containers(containers)
  92. LogPrinter(containers, attach_params={'logs': True}).run()
  93. def ps(self, options):
  94. """
  95. List containers.
  96. Usage: ps [options] [SERVICE...]
  97. Options:
  98. -q Only display IDs
  99. """
  100. containers = self.project.containers(service_names=options['SERVICE'], stopped=True) + self.project.containers(service_names=options['SERVICE'], one_off=True)
  101. if options['-q']:
  102. for container in containers:
  103. print container.id
  104. else:
  105. headers = [
  106. 'Name',
  107. 'Command',
  108. 'State',
  109. 'Ports',
  110. ]
  111. rows = []
  112. for container in containers:
  113. rows.append([
  114. container.name,
  115. container.human_readable_command,
  116. container.human_readable_state,
  117. container.human_readable_ports,
  118. ])
  119. print Formatter().table(headers, rows)
  120. def rm(self, options):
  121. """
  122. Remove stopped containers
  123. Usage: rm [SERVICE...]
  124. """
  125. all_containers = self.project.containers(service_names=options['SERVICE'], stopped=True)
  126. stopped_containers = [c for c in all_containers if not c.is_running]
  127. if len(stopped_containers) > 0:
  128. print "Going to remove", list_containers(stopped_containers)
  129. if yesno("Are you sure? [yN] ", default=False):
  130. self.project.remove_stopped(service_names=options['SERVICE'])
  131. else:
  132. print "No stopped containers"
  133. def run(self, options):
  134. """
  135. Run a one-off command.
  136. Usage: run [options] SERVICE COMMAND [ARGS...]
  137. Options:
  138. -d Detached mode: Run container in the background, print new container name
  139. """
  140. service = self.project.get_service(options['SERVICE'])
  141. container_options = {
  142. 'command': [options['COMMAND']] + options['ARGS'],
  143. 'tty': not options['-d'],
  144. 'stdin_open': not options['-d'],
  145. }
  146. container = service.create_container(one_off=True, **container_options)
  147. if options['-d']:
  148. service.start_container(container, ports=None)
  149. print container.name
  150. else:
  151. with self._attach_to_container(
  152. container.id,
  153. interactive=True,
  154. logs=True,
  155. raw=True
  156. ) as c:
  157. service.start_container(container, ports=None)
  158. c.run()
  159. def start(self, options):
  160. """
  161. Start existing containers.
  162. Usage: start [SERVICE...]
  163. """
  164. self.project.start(service_names=options['SERVICE'])
  165. def stop(self, options):
  166. """
  167. Stop running containers.
  168. Usage: stop [SERVICE...]
  169. """
  170. self.project.stop(service_names=options['SERVICE'])
  171. def up(self, options):
  172. """
  173. Create and start containers.
  174. Usage: up [options] [SERVICE...]
  175. Options:
  176. -d Detached mode: Run containers in the background, print new container names
  177. """
  178. detached = options['-d']
  179. self.project.create_containers(service_names=options['SERVICE'])
  180. containers = self.project.containers(service_names=options['SERVICE'], stopped=True)
  181. if not detached:
  182. print "Attaching to", list_containers(containers)
  183. log_printer = LogPrinter(containers)
  184. self.project.start(service_names=options['SERVICE'])
  185. if not detached:
  186. try:
  187. log_printer.run()
  188. finally:
  189. self.project.kill(service_names=options['SERVICE'])
  190. def _attach_to_container(self, container_id, interactive, logs=False, stream=True, raw=False):
  191. stdio = self.client.attach_socket(
  192. container_id,
  193. params={
  194. 'stdin': 1 if interactive else 0,
  195. 'stdout': 1,
  196. 'stderr': 0,
  197. 'logs': 1 if logs else 0,
  198. 'stream': 1 if stream else 0
  199. },
  200. ws=True,
  201. )
  202. stderr = self.client.attach_socket(
  203. container_id,
  204. params={
  205. 'stdin': 0,
  206. 'stdout': 0,
  207. 'stderr': 1,
  208. 'logs': 1 if logs else 0,
  209. 'stream': 1 if stream else 0
  210. },
  211. ws=True,
  212. )
  213. return SocketClient(
  214. socket_in=stdio,
  215. socket_out=stdio,
  216. socket_err=stderr,
  217. raw=raw,
  218. )
  219. def list_containers(containers):
  220. return ", ".join(c.name for c in containers)