Utility to re-run a docker container with slightly different arguments
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

docker-rerun 6.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. #!/usr/bin/python3
  2. """Re-runs a docker container using the same arguments as before.
  3. Given the name of a container, the previous arguments are determined
  4. and reconstructed by looking at the `docker inspect` output.
  5. Each function named `handle_*` handles one configuration option,
  6. reading the relevant information from the inspect output and adding
  7. the relevant command line flags to the config.
  8. """
  9. import argparse
  10. import inspect
  11. import json
  12. import subprocess
  13. import sys
  14. class Container(object):
  15. """Encapsulates information about a container."""
  16. def __init__(self, name, container_info, image_info):
  17. """Creates a new Container.
  18. Args:
  19. name (str): The name of the container.
  20. container_info (dict): Dictionary describing the container state.
  21. image_info (dict): Dictionary describing the image state.
  22. """
  23. self.args = ['--name=%s' % name]
  24. """The arguments passed to docker to create the container."""
  25. self.image = ''
  26. """The image that the container uses."""
  27. self.cmd = []
  28. """The command executed within the container."""
  29. self.info = container_info
  30. """The container information as returned by `docker inspect`"""
  31. self.image_info = image_info
  32. """The image information as returned by `docker inspect`"""
  33. def command_line(self):
  34. """Gets the full command-line used to run this container."""
  35. return ['docker', 'run'] + sorted(self.args) + [self.image] + self.cmd
  36. def add_args_from_list(self, template, selector):
  37. """Adds an argument for each item in a list.
  38. Args:
  39. template (str): Template to use for the argument. Use %s for value.
  40. selector (func): Function to extract the list from our info.
  41. """
  42. target = selector(self.info)
  43. if target:
  44. self.args.extend([template % entry for entry in target])
  45. def docker_inspect(target, what):
  46. """Uses `docker inspect` to get details about the given container or image.
  47. Args:
  48. target (str): The name of the container or image to inspect.
  49. what (str): The type of object to inspect ('container' or 'image').
  50. Returns:
  51. dict: Detailed information about the target.
  52. Raises:
  53. CalledProcessError: An error occurred talking to Docker.
  54. """
  55. output = subprocess.check_output(['docker', 'inspect',
  56. '--type=%s' % what, target])
  57. return json.loads(output.decode('utf-8'))[0]
  58. def handle_binds(container, config):
  59. """Copies the volume bind (--volume/-v) arguments."""
  60. if container['HostConfig']['Binds']:
  61. config['args'].extend(['--volume=%s' % bind
  62. for bind in container['HostConfig']['Binds']])
  63. def handle_image(container, config):
  64. """Copies the image argument."""
  65. config['image'] = container['Config']['Image']
  66. def handle_name(container, config):
  67. """Copies the name (--name) argument."""
  68. # Trim the leading / off the name. They're equivalent from docker's point
  69. # of view, but having the plain name looks nicer from a human point of view.
  70. config['args'].append('--name=%s' % container['Name'][1:])
  71. def handle_network_mode(container, config):
  72. """Copies the network mode (--net) argument."""
  73. network = container['HostConfig']['NetworkMode']
  74. if network != 'default':
  75. config['args'].append('--net=%s' % network)
  76. def handle_ports(container, config):
  77. """Copies the port publication (-p) arguments."""
  78. ports = container['HostConfig']['PortBindings']
  79. if ports:
  80. for port, bindings in ports.items():
  81. for binding in bindings:
  82. if binding['HostIp']:
  83. config['args'].append('-p=%s:%s:%s' % (binding['HostIp'],
  84. binding['HostPort'],
  85. port))
  86. elif binding['HostPort']:
  87. config['args'].append('-p=%s:%s' % (binding['HostPort'],
  88. port))
  89. else:
  90. config['args'].append('-p=%s' % port)
  91. def handle_restart(container, config):
  92. """Copies the restart policy (--restart) argument."""
  93. policy = container['HostConfig']['RestartPolicy']
  94. if policy and policy['Name'] != 'no':
  95. arg = '--restart=%s' % policy['Name']
  96. if policy['MaximumRetryCount'] > 0:
  97. arg += ':%s' % policy['MaximumRetryCount']
  98. config['args'].append(arg)
  99. def handle_volumes_from(container, config):
  100. """Copies the volumes from (--volumes-from) argument."""
  101. if container['HostConfig']['VolumesFrom']:
  102. config['args'].extend(['--volumes-from=%s' % cont for
  103. cont in container['HostConfig']['VolumesFrom']])
  104. def functions():
  105. """Lists all functions defined in this module.
  106. Returns:
  107. list of (str,function): List of (name, function) pairs for each
  108. function defined in this module.
  109. """
  110. return [m for m
  111. in inspect.getmembers(sys.modules[__name__])
  112. if inspect.isfunction(m[1])]
  113. def handlers():
  114. """Lists all handlers defined in this module.
  115. Returns:
  116. list of function: All handlers (handle_* funcs) defined in this module.
  117. """
  118. return [func for (name, func) in functions() if name.startswith('handle_')]
  119. def main():
  120. """Script entry point."""
  121. parser = argparse.ArgumentParser(description='Reruns docker containers ' \
  122. 'with different parameters.')
  123. parser.add_argument('container', type=str, help='The container to rerun')
  124. parser.add_argument('-d', '--dry-run', action='store_true',
  125. help='Don\'t actually re-run the container, just ' \
  126. 'print what would happen.')
  127. args = parser.parse_args()
  128. container = docker_inspect(args.container, 'container')
  129. docker_config = {'args': ['-d'], 'image': ''}
  130. for handler in handlers():
  131. handler(container, docker_config)
  132. docker_config['args'].sort()
  133. commands = [
  134. ['docker', 'stop', args.container],
  135. ['docker', 'rm', args.container],
  136. ['docker', 'run'] + docker_config['args'] + [docker_config['image']],
  137. ]
  138. if args.dry_run:
  139. print('Performing dry run for container %s. The following would be ' \
  140. 'executed:' % args.container)
  141. for command in commands:
  142. print(' '.join(command))
  143. else:
  144. print('Re-running container %s...' % args.container)
  145. for command in commands:
  146. subprocess.check_call(command)
  147. if __name__ == "__main__":
  148. main()