|
#! /usr/bin/env python |
|
|
|
# Copy of https://github.com/Red5d/docker-autocompose dirty hacked with some fixes for dates and |
|
# possiblity to create docker command lines from the existing containers by using the -o2 parameter |
|
|
|
import sys, argparse, pyaml, docker |
|
from collections import OrderedDict |
|
|
|
def main(): |
|
parser = argparse.ArgumentParser(description='Generate docker-compose yaml definition from running container.') |
|
parser.add_argument('-v', '--version', type=int, default=3, help='Compose file version (1 or 3)') |
|
parser.add_argument('-o', '--output', type=int, default=0, help='Type of output (0 or 1,2)') |
|
parser.add_argument('cnames', nargs='*', type=str, help='The name of the container to process.') |
|
args = parser.parse_args() |
|
|
|
struct = {} |
|
networks = [] |
|
for cname in args.cnames: |
|
cfile, networks,cmdline = generate(cname) |
|
struct.update(cfile) |
|
|
|
if args.output >0: |
|
print ("\r\n"+ cmdline + "\r\n") |
|
if args.output <2: |
|
render(struct, args, networks) |
|
|
|
|
|
def render(struct, args, networks): |
|
# Render yaml file |
|
if args.version == 1: |
|
pyaml.p(OrderedDict(struct)) |
|
else: |
|
pyaml.p(OrderedDict({'version': '"3"', 'services': struct, 'networks': networks})) |
|
|
|
def generate(cname): |
|
c = docker.from_env() |
|
|
|
try: |
|
cid = [x.short_id for x in c.containers.list(all=True) if cname == x.name or x.short_id in cname][0] |
|
except IndexError: |
|
print("That container is not available.") |
|
sys.exit(1) |
|
|
|
cattrs = c.containers.get(cid).attrs |
|
|
|
|
|
# Build yaml dict structure |
|
|
|
cfile = {} |
|
cfile[cattrs['Name'][1:]] = {} |
|
ct = cfile[cattrs['Name'][1:]] |
|
cmdLine ="docker run --name " + str( cattrs['Name'][1:]) |
|
|
|
values = { |
|
'cap_add': cattrs['HostConfig']['CapAdd'], |
|
'cap_drop': cattrs['HostConfig']['CapDrop'], |
|
'cgroup_parent': cattrs['HostConfig']['CgroupParent'], |
|
'container_name': cattrs['Name'][1:], |
|
'devices': [], |
|
'dns': cattrs['HostConfig']['Dns'], |
|
'dns_search': cattrs['HostConfig']['DnsSearch'], |
|
'environment': cattrs['Config']['Env'], |
|
'extra_hosts': cattrs['HostConfig']['ExtraHosts'], |
|
'image': cattrs['Config']['Image'], |
|
'labels': cattrs['Config']['Labels'], |
|
'links': cattrs['HostConfig']['Links'], |
|
#'log_driver': cattrs['HostConfig']['LogConfig']['Type'], |
|
#'log_opt': cattrs['HostConfig']['LogConfig']['Config'], |
|
#'logging': {'driver': cattrs['HostConfig']['LogConfig']['Type'], 'options': cattrs['HostConfig']['LogConfig']['Config']}, |
|
'networks': {x for x in cattrs['NetworkSettings']['Networks'].keys() if x != 'bridge'}, |
|
'security_opt': cattrs['HostConfig']['SecurityOpt'], |
|
'ulimits': cattrs['HostConfig']['Ulimits'], |
|
'volumes': cattrs['HostConfig']['Binds'], |
|
'volume_driver': cattrs['HostConfig']['VolumeDriver'], |
|
'volumes_from': cattrs['HostConfig']['VolumesFrom'], |
|
'entrypoint': cattrs['Config']['Entrypoint'], |
|
'user': cattrs['Config']['User'], |
|
'working_dir': cattrs['Config']['WorkingDir'], |
|
'domainname': cattrs['Config']['Domainname'], |
|
'hostname': cattrs['Config']['Hostname'], |
|
'ipc': cattrs['HostConfig']['IpcMode'], |
|
'mac_address': cattrs['NetworkSettings']['MacAddress'], |
|
'privileged': cattrs['HostConfig']['Privileged'], |
|
'restart': cattrs['HostConfig']['RestartPolicy']['Name'], |
|
'read_only': cattrs['HostConfig']['ReadonlyRootfs'], |
|
'stdin_open': cattrs['Config']['OpenStdin'], |
|
'tty': cattrs['Config']['Tty'] |
|
} |
|
|
|
# Populate devices key if device values are present |
|
if cattrs['HostConfig']['Devices']: |
|
values['devices'] = [x['PathOnHost']+':'+x['PathInContainer'] for x in cattrs['HostConfig']['Devices']] |
|
|
|
networks = {} |
|
if values['networks'] == set(): |
|
del values['networks'] |
|
values['network_mode'] = 'bridge' |
|
else: |
|
networklist = c.networks.list() |
|
for network in networklist: |
|
if network.attrs['Name'] in values['networks']: |
|
networks[network.attrs['Name']] = {'external': (not network.attrs['Internal'])} |
|
|
|
# Check for command and add it if present. |
|
if cattrs['Config']['Cmd'] != None: |
|
values['command'] = " ".join(cattrs['Config']['Cmd']), |
|
|
|
# Check for exposed/bound ports and add them if needed. |
|
try: |
|
expose_value = list(cattrs['Config']['ExposedPorts'].keys()) |
|
ports_value = [cattrs['HostConfig']['PortBindings'][key][0]['HostIp']+':'+cattrs['HostConfig']['PortBindings'][key][0]['HostPort']+':'+key for key in cattrs['HostConfig']['PortBindings']] |
|
|
|
# If bound ports found, don't use the 'expose' value. |
|
if (ports_value != None) and (ports_value != "") and (ports_value != []) and (ports_value != 'null') and (ports_value != {}) and (ports_value != "default") and (ports_value != 0) and (ports_value !=$ |
|
for index, port in enumerate(ports_value): |
|
if port[0] == ':': |
|
ports_value[index] = port[1:] |
|
# print ( "-p " , str (ports_value[index])) |
|
cmdLine = cmdLine + " -p " + str (ports_value[index]) |
|
values['ports'] = ports_value |
|
else: |
|
values['expose'] = expose_value |
|
|
|
except (KeyError, TypeError): |
|
# No ports exposed/bound. Continue without them. |
|
ports = None |
|
|
|
# fix some dates from causing errors. |
|
try: |
|
if values['labels']['org.label-schema.build-date'] != None: |
|
d = values['labels']['org.label-schema.build-date'] |
|
values['labels']['org.label-schema.build-date'] = "'" + str( d ) + "'" |
|
except KeyError as ke: |
|
pass |
|
|
|
# Iterate through values to finish building yaml dict. |
|
for key in values: |
|
value = values[key] |
|
if (value != None) and (value != "") and (value != []) and (value != 'null') and (value != {}) and (value != "default") and (value != 0) and (value != ",") and (value != "no"): |
|
ct[key] = value |
|
|
|
if cattrs['HostConfig']['Binds']: |
|
for key in cattrs['HostConfig']['Binds']: |
|
cmdLine = cmdLine + ' -v ' + str(key) |
|
|
|
if cattrs['Config']['Env']: |
|
for key in values['environment']: |
|
cmdLine = cmdLine + ' -e ' + str(key.split('=')[0]) |
|
if len(key.split('='))>1: |
|
cmdLine = cmdLine + '="' + str(key.split('=')[1]) + '"' |
|
cmdLine = cmdLine + " " + str ( cattrs['Config']['Image']) |
|
return cfile, networks, cmdLine |
|
|
|
|
|
if __name__ == "__main__": |
|
main() |
|
|
|
|
|
|
|
|
Great work, but there are some issues:
Line 114 of the above
autocompose.py
ends with:That's a syntax error. I have changed that to
to match the original
IGNORE_VALUES
.Also need to pin package
requests<2.29
. See docker/docker-py#3113Whenever a named volume was used, there was an error in the generated
docker-compose.yml
files:Besides of that, instead of using sed and awk to tweak docker's output I would suggest letting docker do the work itself by creating the list of container IDs with
Similarly, you can obtain the list of image IDs directly with
However, there can be duplicates, if the same image exists with several tags (for example latest and an explicit version tag). In that case, the duplicates should be eliminated by piping the output through
That applies to the original version as well.
Instead of IDs, the
autocompose.py
script also accepts a container name. A list of container names can be obtained withThis allows to organize the generated docker-compose files in folders named after the container name. Whether or not this is better understandable than the container ids depends on the names given when running the container. If one has mostly the autogenerated names à la affectionate_newton then may be the ID is better. YMMV.