22 Commits

Author SHA1 Message Date
Red5d
85e398193f Update README with note on how to validate output. 2022-08-20 14:46:27 -04:00
Red5d
dfc73d0bbd Update compose version 3 to 3.6.
With somewhat-recent docker features and spec changes that have been included in the compose file generation for this script, specifying compose file version 3.6 (at least) is necessary for the output compose file to pass validation using "docker-compose config".
2022-08-20 14:39:29 -04:00
Red5d
1768a65fce Fixes the --all option, volumes of type 'bind' and read only option (#46)
(from @d-EScape)

One PR that includes my suggestions for #17 and some new ones:

The -all option would not work because every iteration of container_names could set the 'networks' and 'volumes' to None. Even if a previous container had a network. Later iterations could not add a network, because it was no longer a dict, resulting in an exception.

The code might need some cleaning up. I left some comments and old pieces (commented out) to explain to @Red5d what I did and why. Since I am new to this script and the docker-compose format i might have overlooked something. Please check.

Co-authored-by: d-EScape <8693608+d-EScape@users.noreply.github.com>
2022-08-20 14:36:47 -04:00
acdoussan
357fef9782 better handling for default networks (#42)
* better handling for default networks

* fix for issues/43

* check for none explictly rather than allowing truthy/falsy conversion

* update to read volume data from mounts, rather than host config binds

Co-authored-by: Adam Doussan <acdoussan@Adams-MacBook-Pro.local>
2022-08-14 17:54:43 -04:00
acdoussan
0c4ff4fb25 Fix volumes missing in generated compose file (#41)
* fix volume export based off of https://github.com/Red5d/docker-autocompose/issues/17#issuecomment-943041549

* remove unneeded space

* export volumes in addition to networks

* fix syntax error

* actuall fix syntax errors

Co-authored-by: Adam Doussan <acdoussan@Adams-MacBook-Pro.local>
2022-08-13 16:31:05 -04:00
Red5d
e19c4654af Merge pull request #38 from ostafen/master
Add -a/--all flag to list all containers
2022-04-09 22:12:19 -04:00
Stefano
d6dddedb3d Add -a/--all flag to list all containers 2022-04-09 09:30:14 +02:00
Red5d
0dfdac353f Merge pull request #36 from ostafen/master
Format properly date/datetime labels
2022-03-16 09:24:12 -04:00
Red5d
a8f00e0deb Merge pull request #37 from alexanderpetrenz/master
Adding Name Attribute to each Network
2022-03-16 08:45:53 -04:00
alexanderpetrenz
adf98bb062 Merge branch 'Red5d:master' into master 2022-03-11 09:26:21 +01:00
Alexander Petrenz
1af6b49233 added name attribute to every retrieved network 2022-03-11 08:59:55 +01:00
Alexander Petrenz
40aaf8e82c fixed network retrieval 2022-03-11 08:59:47 +01:00
Stefano
63810906f9 Format properly date/datetime labels 2022-03-10 20:45:35 +01:00
Red5d
e32c9d4275 Merge pull request #35 from ostafen/master
Fix ERROR: network must be a mapping, not an array.
2022-03-10 14:04:38 -05:00
Stefano
caa747b605 Fix ERROR: network must be a mapping, not an array. 2022-03-10 15:32:50 +01:00
Red5d
d783902265 Merge pull request #34 from alexanderpetrenz/master
command property: replace string concatenation by taking over given list
2022-03-09 08:10:19 -05:00
Alexander Petrenz
e6badd31c3 now collecting networks not present in every container 2022-03-08 15:49:49 +01:00
Alexander Petrenz
3f756235b2 command property: replace string concatenation by taking over given list 2022-03-08 14:33:33 +01:00
Red5d
b9c096dd94 Merge pull request #33 from moschlar/patch-1
Update autocompose.py
2022-03-07 14:50:04 -05:00
Moritz Schlarb
e7dbe41f23 Update autocompose.py
Print error message to stderr so that I will be seen when redirecting stdout to `docker-compose.yml`
2022-03-07 11:56:06 +01:00
Red5d
d976d520b4 Merge pull request #32 from hgghyxo/patch-1
Update README.md
2022-02-24 11:14:25 -05:00
hgghyxo
ec211717ed Update README.md
adding a oneliner to print out all containers
2022-02-24 11:49:46 +01:00
2 changed files with 111 additions and 23 deletions

View File

@@ -43,3 +43,10 @@ Use the new image to generate a docker-compose file from a running container or
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/red5d/docker-autocompose <container-name-or-id> <additional-names-or-ids>...
To print out all containers in a docker-compose format:
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/red5d/docker-autocompose $(docker ps -aq)
## Contributing
When making changes, please validate the output from the script by writing it to a file (docker-compose.yml or docker-compose.yaml) and running "docker-compose config" in the same folder with it to ensure that the resulting compose file will be accepted by docker-compose.

View File

@@ -1,38 +1,84 @@
#! /usr/bin/env python
#! /usr/bin/env python3
import datetime
import sys, argparse, pyaml, docker
from collections import OrderedDict
def list_container_names():
c = docker.from_env()
return [container.name for container in c.containers.list(all=True)]
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('-a', '--all', action='store_true', help='Include all active containers')
parser.add_argument('-v', '--version', type=int, default=3, help='Compose file version (1 or 3)')
parser.add_argument('cnames', nargs='*', type=str, help='The name of the container to process.')
parser.add_argument('-c', '--createvolumes', action='store_true', help='Create new volumes instead of reusing existing ones')
args = parser.parse_args()
container_names = args.cnames
if args.all:
container_names.extend(list_container_names())
struct = {}
networks = []
for cname in args.cnames:
cfile, networks = generate(cname)
networks = {}
volumes = {}
containers = {}
for cname in container_names:
cfile, c_networks, c_volumes = generate(cname, createvolumes=args.createvolumes)
struct.update(cfile)
render(struct, args, networks)
if not c_networks == None:
networks.update(c_networks)
if not c_volumes == None:
volumes.update(c_volumes)
# moving the networks = None statemens outside of the for loop. Otherwise any container could reset it.
if len(networks) == 0:
networks = None
if len(volumes) == 0:
volumes = None
render(struct, args, networks, volumes)
def render(struct, args, networks):
def render(struct, args, networks, volumes):
# Render yaml file
if args.version == 1:
pyaml.p(OrderedDict(struct))
else:
pyaml.p(OrderedDict({'version': '"3"', 'services': struct, 'networks': networks}))
ans = {'version': '"3.6"', 'services': struct}
def generate(cname):
if networks is not None:
ans['networks'] = networks
if volumes is not None:
ans['volumes'] = volumes
pyaml.p(OrderedDict(ans))
def is_date_or_time(s: str):
for parse_func in [datetime.date.fromisoformat, datetime.datetime.fromisoformat]:
try:
parse_func(s.rstrip('Z'))
return True
except ValueError:
pass
return False
def fix_label(label: str):
return f"'{label}'" if is_date_or_time(label) else label
def generate(cname, createvolumes=False):
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.")
print("That container is not available.", file=sys.stderr)
sys.exit(1)
cattrs = c.containers.get(cid).attrs
@@ -44,6 +90,8 @@ def generate(cname):
cfile[cattrs['Name'][1:]] = {}
ct = cfile[cattrs['Name'][1:]]
default_networks = ['bridge', 'host', 'none']
values = {
'cap_add': cattrs['HostConfig']['CapAdd'],
'cap_drop': cattrs['HostConfig']['CapDrop'],
@@ -55,15 +103,17 @@ def generate(cname):
'environment': cattrs['Config']['Env'],
'extra_hosts': cattrs['HostConfig']['ExtraHosts'],
'image': cattrs['Config']['Image'],
'labels': cattrs['Config']['Labels'],
'labels': {label: fix_label(value) for label, value in cattrs['Config']['Labels'].items()},
'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'},
'networks': {x for x in cattrs['NetworkSettings']['Networks'].keys() if x not in default_networks},
'security_opt': cattrs['HostConfig']['SecurityOpt'],
'ulimits': cattrs['HostConfig']['Ulimits'],
'volumes': cattrs['HostConfig']['Binds'],
# the line below would not handle type bind
# 'volumes': [f'{m["Name"]}:{m["Destination"]}' for m in cattrs['Mounts'] if m['Type'] == 'volume'],
'mounts': cattrs['Mounts'], #this could be moved outside of the dict. will only use it for generate
'volume_driver': cattrs['HostConfig']['VolumeDriver'],
'volumes_from': cattrs['HostConfig']['VolumesFrom'],
'entrypoint': cattrs['Config']['Entrypoint'],
@@ -83,23 +133,55 @@ def generate(cname):
# 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']
assumed_default_network = list(cattrs['NetworkSettings']['Networks'].keys())[0]
values['network_mode'] = assumed_default_network
networks = None
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'])}
networks[network.attrs['Name']] = {'external': (not network.attrs['Internal']),
'name': network.attrs['Name']}
# volumes = {}
# if values['volumes'] is not None:
# for volume in values['volumes']:
# volume_name = volume.split(':')[0]
# volumes[volume_name] = {'external': True}
# else:
# volumes = None
# handles both the returned values['volumes'] (in c_file) and volumes for both, the bind and volume types
# also includes the read only option
volumes = {}
mountpoints = []
if values['mounts'] is not None:
for mount in values['mounts']:
destination = mount['Destination']
if not mount['RW']:
destination = destination + ':ro'
if mount['Type'] == 'volume':
mountpoints.append(mount['Name'] + ':' + destination)
if not createvolumes:
volumes[mount['Name']] = {'external': True} #to reuse an existing volume ... better to make that a choice? (cli argument)
elif mount['Type'] == 'bind':
mountpoints.append(mount['Source'] + ':' + destination)
values['volumes'] = mountpoints
if len(volumes) == 0:
volumes = None
values['mounts'] = None #remove this temporary data from the returned data
# Check for command and add it if present.
if cattrs['Config']['Cmd'] != None:
values['command'] = " ".join(cattrs['Config']['Cmd']),
if cattrs['Config']['Cmd'] is not None:
values['command'] = cattrs['Config']['Cmd']
# Check for exposed/bound ports and add them if needed.
try:
expose_value = list(cattrs['Config']['ExposedPorts'].keys())
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.
@@ -122,9 +204,8 @@ def generate(cname):
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
return cfile, networks
return cfile, networks, volumes
if __name__ == "__main__":
main()