mirror of
https://github.com/Red5d/docker-autocompose
synced 2026-01-07 00:58:02 +00:00
Compare commits
37 Commits
v1.0.1
...
d976d520b4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d976d520b4 | ||
|
|
ec211717ed | ||
|
|
4e8ff192dc | ||
|
|
8ada367b9e | ||
|
|
94ca597f3d | ||
|
|
0ed2f306cc | ||
|
|
6415678751 | ||
|
|
4b5e27cf29 | ||
|
|
d90e2d5389 | ||
|
|
881b7979d5 | ||
|
|
a1f2aabdee | ||
|
|
d8e5aacf20 | ||
|
|
0556a35376 | ||
|
|
046f0e9da7 | ||
|
|
9c3e01d167 | ||
|
|
9f3960defd | ||
|
|
98704a81b6 | ||
|
|
a3ff6534ab | ||
|
|
555f1f90b3 | ||
|
|
09f14bceca | ||
|
|
e0f6c83bc4 | ||
|
|
7fe7a07fb5 | ||
|
|
4eb8d97536 | ||
|
|
b78da97768 | ||
|
|
e5ac520ff6 | ||
|
|
9827c4488d | ||
|
|
800b088cea | ||
|
|
9e0fa327ee | ||
|
|
f22f154fe9 | ||
|
|
bdb38eecac | ||
|
|
f3f2eca906 | ||
|
|
05bab6fda0 | ||
|
|
2a1b25e4f4 | ||
|
|
40ec3f9b42 | ||
|
|
858b187e75 | ||
|
|
b71078f68b | ||
|
|
83bc0afad6 |
77
.github/workflows/docker.yml
vendored
Normal file
77
.github/workflows/docker.yml
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
name: Build Multi Stage Docker Image
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'dependabot/**'
|
||||
schedule:
|
||||
- cron: '0 5 * * *'
|
||||
|
||||
jobs:
|
||||
GHRC:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
|
||||
- uses: FranzDiebold/github-env-vars-action@v2.3.0
|
||||
- name: Print environment variables
|
||||
run: |
|
||||
echo "CI_REPOSITORY_NAME=$CI_REPOSITORY_NAME"
|
||||
|
||||
- name: Prepare GHRC.io
|
||||
id: prep
|
||||
run: |
|
||||
REPO=$CI_REPOSITORY_NAME
|
||||
OWNER="$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')"
|
||||
DOCKER_IMAGE=${OWNER}/${REPO}
|
||||
if [ "$CI_REF_NAME" == "master" ];then VERSION=latest;fi
|
||||
if [ "$CI_REF_NAME" == "dev" ];then VERSION=mightly;fi
|
||||
if [ "$CI_REF_NAME" == "dockserver" ];then VERSION=dockserver;fi
|
||||
TAGS="${DOCKER_IMAGE}:${VERSION}"
|
||||
echo ::set-output name=tags::${TAGS}
|
||||
echo ::set-output name=title::${GITHUB_REPOSITORY}
|
||||
echo ::set-output name=version::${VERSION}
|
||||
echo ::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ')
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
platforms: linux/amd64,linux/armhf,linux/arm64
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1.3.0
|
||||
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1.9.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.CR_PAT }}
|
||||
|
||||
- name: Build and push GHRC.io
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2.5.0
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/armhf,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ghcr.io/${{ steps.prep.outputs.tags }}
|
||||
labels: |
|
||||
org.opencontainers.image.title=${{ steps.prep.outputs.title }}
|
||||
org.opencontainers.image.version=${{ steps.prep.outputs.version }}
|
||||
org.opencontainers.image.created=${{ steps.prep.outputs.created }}
|
||||
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
venv
|
||||
.vscode
|
||||
.DS_Store
|
||||
10
Dockerfile
Normal file
10
Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
||||
FROM python:3-alpine
|
||||
LABEL org.opencontainers.image.source https://github.com/Red5d/docker-autocompose
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN python ./setup.py install
|
||||
|
||||
ENTRYPOINT [ "python", "./autocompose.py" ]
|
||||
40
README.md
40
README.md
@@ -1,18 +1,48 @@
|
||||
# docker-autocompose
|
||||
Generates a docker-compose yaml definition from a running container.
|
||||
Generates a docker-compose yaml definition from a docker container.
|
||||
|
||||
Required Modules:
|
||||
* [pyaml](https://pypi.python.org/pypi/pyaml/)
|
||||
* [docker-py](https://pypi.python.org/pypi/docker-py)
|
||||
* [pyaml](https://pypi.python.org/project/pyaml/)
|
||||
* [docker](https://pypi.python.org/project/docker)
|
||||
* [six](https://pypi.python.org/project/six)
|
||||
|
||||
Example Usage:
|
||||
|
||||
sudo python autocompose.py container-name
|
||||
sudo python autocompose.py <container-name-or-id>
|
||||
|
||||
|
||||
Generate a compose file for multiple containers together:
|
||||
|
||||
sudo python autocompose.py apache-test mysql-test
|
||||
|
||||
|
||||
The script defaults to outputting to compose file version 3, but use "-v 1" to output to version 1:
|
||||
|
||||
sudo python autocompose.py -v 1 apache-test
|
||||
|
||||
|
||||
Outputs a docker-compose compatible yaml structure:
|
||||
|
||||
Outputs a docker-compose compatible yaml structure.
|
||||
[docker-compose reference](https://docs.docker.com/compose/)
|
||||
|
||||
[docker-compose yaml file specification](https://docs.docker.com/compose/compose-file/)
|
||||
|
||||
While experimenting with various docker containers from the Hub, I realized that I'd started several containers with complex options for volumes, ports, environment variables, etc. and there was no way I could remember all those commands without referencing the Hub page for each image if I needed to delete and re-create the container (for updates, or if something broke).
|
||||
|
||||
With this tool, I can easily generate docker-compose files for managing the containers that I've set up manually.
|
||||
|
||||
## Docker Usage
|
||||
|
||||
You can use this tool from a docker container by either cloning this repo and building the image or using the [automatically generated image on GitHub](https://github.com/Red5d/docker-autocompose/pkgs/container/docker-autocompose)
|
||||
|
||||
Pull the image from GitHub (supports both x86 and ARM)
|
||||
|
||||
docker pull ghcr.io/red5d/docker-autocompose:latest
|
||||
|
||||
Use the new image to generate a docker-compose file from a running container or a list of space-separated container names or ids:
|
||||
|
||||
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)
|
||||
|
||||
133
autocompose.py
133
autocompose.py
@@ -1,81 +1,106 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
import pyaml, argparse, sys
|
||||
from docker import Client
|
||||
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('cname', type=str, help='The name of the container to process.')
|
||||
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.')
|
||||
args = parser.parse_args()
|
||||
|
||||
generate(args)
|
||||
struct = {}
|
||||
networks = []
|
||||
for cname in args.cnames:
|
||||
cfile, networks = generate(cname)
|
||||
struct.update(cfile)
|
||||
|
||||
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(args):
|
||||
c = Client(base_url='unix://var/run/docker.sock')
|
||||
def generate(cname):
|
||||
c = docker.from_env()
|
||||
|
||||
try:
|
||||
cid = [x['Id'] for x in c.containers() if args.cname in x['Names'][0]][0]
|
||||
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 running.")
|
||||
print("That container is not available.")
|
||||
sys.exit(1)
|
||||
|
||||
cinspect = c.inspect_container(cid)
|
||||
cattrs = c.containers.get(cid).attrs
|
||||
|
||||
|
||||
# Build yaml dict structure
|
||||
|
||||
cfile = {}
|
||||
cfile[args.cname] = {}
|
||||
ct = cfile[args.cname]
|
||||
cfile[cattrs['Name'][1:]] = {}
|
||||
ct = cfile[cattrs['Name'][1:]]
|
||||
|
||||
values = {
|
||||
'cap_add': cinspect['HostConfig']['CapAdd'],
|
||||
'cap_drop': cinspect['HostConfig']['CapDrop'],
|
||||
'cgroup_parent': cinspect['HostConfig']['CgroupParent'],
|
||||
'container_name': args.cname,
|
||||
'devices': cinspect['HostConfig']['Devices'],
|
||||
'dns': cinspect['HostConfig']['Dns'],
|
||||
'dns_search': cinspect['HostConfig']['DnsSearch'],
|
||||
'environment': cinspect['Config']['Env'],
|
||||
'extra_hosts': cinspect['HostConfig']['ExtraHosts'],
|
||||
'image': cinspect['Config']['Image'],
|
||||
'labels': cinspect['Config']['Labels'],
|
||||
'links': cinspect['HostConfig']['Links'],
|
||||
'log_driver': cinspect['HostConfig']['LogConfig']['Type'],
|
||||
'log_opt': cinspect['HostConfig']['LogConfig']['Config'],
|
||||
'net': cinspect['HostConfig']['NetworkMode'],
|
||||
'security_opt': cinspect['HostConfig']['SecurityOpt'],
|
||||
'ulimits': cinspect['HostConfig']['Ulimits'],
|
||||
'volumes': cinspect['HostConfig']['Binds'],
|
||||
'volume_driver': cinspect['HostConfig']['VolumeDriver'],
|
||||
'volumes_from': cinspect['HostConfig']['VolumesFrom'],
|
||||
'cpu_shares': cinspect['HostConfig']['CpuShares'],
|
||||
'cpuset': cinspect['HostConfig']['CpusetCpus']+','+cinspect['HostConfig']['CpusetMems'],
|
||||
'entrypoint': cinspect['Config']['Entrypoint'],
|
||||
'user': cinspect['Config']['User'],
|
||||
'working_dir': cinspect['Config']['WorkingDir'],
|
||||
'domainname': cinspect['Config']['Domainname'],
|
||||
'hostname': cinspect['Config']['Hostname'],
|
||||
'ipc': cinspect['HostConfig']['IpcMode'],
|
||||
'mac_address': cinspect['NetworkSettings']['MacAddress'],
|
||||
'mem_limit': cinspect['HostConfig']['Memory'],
|
||||
'memswap_limit': cinspect['HostConfig']['MemorySwap'],
|
||||
'privileged': cinspect['HostConfig']['Privileged'],
|
||||
'restart': cinspect['HostConfig']['RestartPolicy']['Name'],
|
||||
'read_only': cinspect['HostConfig']['ReadonlyRootfs'],
|
||||
'stdin_open': cinspect['Config']['OpenStdin'],
|
||||
'tty': cinspect['Config']['Tty']
|
||||
'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']
|
||||
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 cinspect['Config']['Cmd'] != None:
|
||||
values['command'] = " ".join(cinspect['Config']['Cmd']),
|
||||
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(cinspect['Config']['ExposedPorts'].keys())
|
||||
ports_value = [cinspect['HostConfig']['PortBindings'][key][0]['HostIp']+':'+cinspect['HostConfig']['PortBindings'][key][0]['HostPort']+':'+key for key in cinspect['HostConfig']['PortBindings']]
|
||||
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 != ",") and (ports_value != "no"):
|
||||
@@ -87,7 +112,7 @@ def generate(args):
|
||||
else:
|
||||
values['expose'] = expose_value
|
||||
|
||||
except KeyError:
|
||||
except (KeyError, TypeError):
|
||||
# No ports exposed/bound. Continue without them.
|
||||
ports = None
|
||||
|
||||
@@ -97,8 +122,8 @@ def generate(args):
|
||||
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
|
||||
|
||||
# Render yaml file
|
||||
pyaml.p(cfile)
|
||||
return cfile, networks
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
pyaml==20.4.0
|
||||
docker==4.4.4
|
||||
six==1.16.0
|
||||
4
setup.py
4
setup.py
@@ -1,14 +1,14 @@
|
||||
from setuptools import setup, find_packages
|
||||
setup(
|
||||
name = "docker-autocompose",
|
||||
version = "1.0.1",
|
||||
version = "1.2.0",
|
||||
description = "Generate a docker-compose yaml definition from a running container",
|
||||
url = "https://github.com/Red5d/docker-autocompose",
|
||||
author = "Red5d",
|
||||
license = "GPLv2",
|
||||
keywords = "docker yaml container",
|
||||
packages = find_packages(),
|
||||
install_requires = ['pyaml>=15.8.2', 'docker-py>=1.6.0'],
|
||||
install_requires = ['pyaml>=17.12.1', 'docker>=3.4.1','six>=1.16.0'],
|
||||
scripts = ['autocompose.py'],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
|
||||
Reference in New Issue
Block a user