podman-compose/podman-compose.py
2019-03-04 11:30:14 +02:00

243 lines
8.6 KiB
Python
Executable File

#! /usr/bin/env python
from __future__ import print_function
import os
import argparse
import subprocess
import time
import json
import yaml
def try_int(i, fallback=None):
try: return int(i)
except ValueError: pass
except TypeError: pass
return fallback
# https://docs.docker.com/compose/compose-file/#service-configuration-reference
# https://docs.docker.com/samples/
# https://docs.docker.com/compose/gettingstarted/
# https://docs.docker.com/compose/django/
# https://docs.docker.com/compose/wordpress/
def tr_identity(project_name, services, given_containers):
containers=[]
for cnt in given_containers:
containers.append()
return [], containers
def tr_1pod(project_name, services, given_containers):
"""
project_name:
services: {service_name: ["container_name1", "..."]}, currently only one is supported
given_containers: [{}, ...]
"""
pod=dict(name=project_name)
containers=[]
common_extra_hosts = []
for srv, cnts in services.items():
common_extra_hosts.append("{}:127.0.0.1".format(srv))
for cnt in cnts:
common_extra_hosts.append("{}:127.0.0.1".format(cnt))
for cnt0 in given_containers:
cnt = dict(cnt0, pod=project_name)
# services can be accessed as localhost because they are on one pod
extra_hosts = list(cnt.get("extra_hosts", []))
extra_hosts.extend(common_extra_hosts)
# link aliases
for link in cnt.get("links", []):
a = link.strip().split(':', 1)
if len(a)==2:
alias=a[1].strip()
extra_hosts.append("{}:127.0.0.1".format(alias))
cnt["extra_hosts"] = extra_hosts
containers.append(cnt)
return [pod], containers
def tr_1podfw(project_name, services, given_containers):
pods, containers = tr_1pod(project_name, services, given_containers)
pod=pods[0]
ports=[]
for cnt in containers:
cnt_ports = cnt.get('ports')
if cnt_ports:
ports.extend(cnt_ports)
del cnt['ports']
if ports: pod["ports"]=ports
return pods, containers
def down(project_name, dirname, pods, containers):
for cnt in containers:
print("""podman stop -t=1 '{name}'""".format(**cnt))
for cnt in containers:
print("""podman rm '{name}'""".format(**cnt))
for pod in pods:
print("""podman pod rm '{name}'""".format(**pod))
def container_to_args(cnt, dirname):
pod=cnt.get('pod') or ''
args=[
'podman', 'run', '--pod={}'.format(pod),
'--name={}'.format(cnt.get('name')),
]
args.extend(['-d'])
if cnt.get('read_only'):
args.append('--read-only')
for i in cnt.get('labels', []):
args.extend(['--label', i])
env=cnt.get('environment', {})
if isinstance(env, dict):
env=[("{}={}".format(k, v) if v else k) for k,v in env.items()]
for e in env:
args.extend(['-e', e])
for i in cnt.get('env_file', []):
i=os.path.realpath(os.path.join(dirname, i))
args.extend(['--env-file', i])
for i in cnt.get('tmpfs', []):
args.extend(['--tmpfs', i])
for i in cnt.get('volumes', []):
# TODO: make it absolute using os.path.realpath(i)
args.extend(['-v', i])
for i in cnt.get('extra_hosts', []):
args.extend(['--add-host', i])
for i in cnt.get('expose', []):
args.extend(['--expose', i])
for i in cnt.get('ports', []):
args.extend(['-p', i])
user=cnt.get('user')
if user is not None:
args.extend(['-u', user])
if cnt.get('working_dir') is not None:
args.extend(['-w', cnt.get('working_dir')])
if cnt.get('hostname'):
args.extend(['--hostname', cnt.get('hostname')])
if cnt.get('shm_size'):
args.extend(['--shm_size', '{}'.format(cnt.get('shm_size'))])
if cnt.get('stdin_open'):
args.append('-i')
if cnt.get('tty'):
args.append('--tty')
# currently podman shipped by fedora does not package this
#if cnt.get('init'):
# args.append('--init')
entrypoint = cnt.get('entrypoint')
if entrypoint is not None:
if isinstance(entrypoint, list):
args.extend(['--entrypoint', json.dumps(entrypoint)])
else:
args.extend(['--entrypoint', entrypoint])
args.append(cnt.get('image')) # command, ..etc.
command=cnt.get('command')
if command is not None:
# TODO: handle if command is string
args.extend(command)
return args
def up(project_name, dirname, pods, containers):
os.chdir(dirname)
# no need remove them if they have same hash label
down(project_name, dirname, pods, containers)
for pod in pods:
args=[
"podman", "pod", "create",
"--name={}".format(pod["name"]),
"--share", "cgroup,ipc",
]
ports = pod.get("ports") or []
for i in ports:
args.extend(['-p', i])
print(" ".join(args))
p=subprocess.Popen(args)
print(p.wait())
#print(opts)
for cnt in containers:
# TODO: -e , --add-host, -v, --read-only
args = container_to_args(cnt, dirname)
print(" ".join(args))
p=subprocess.Popen(args)
print(p.wait())
#print("""podman run -d --pod='{pod}' --name='{name}' '{image}'""".format(**cnt))
# subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)
time.sleep(3600)
def main(filename, project_name, no_ansi, transform_policy):
filename= os.path.realpath(filename)
dirname = os.path.dirname(filename)
dir_basename = os.path.basename(dirname)
if not project_name: project_name = dir_basename
with open(filename, 'r') as f:
compose=yaml.safe_load(f)
print(json.dumps(compose, indent=2))
ver=compose.get('version')
services=compose.get('services')
podman_compose_labels=[
"io.podman.compose.config-hash=123",
"io.podman.compose.project="+project_name,
"io.podman.compose.version=0.0.1",
]
# other top-levels:
# volumes: {...}
# networks: {driver: ...}
# configs: {...}
# secrets: {...}
given_containers = []
container_names_by_service = {}
for service_name, service_desc in services.items():
replicas = try_int(service_desc.get('deploy', {}).get('replicas', '1'))
container_names_by_service[service_name]=[]
for num in range(1, replicas+1):
name = "{project_name}_{service_name}_{num}".format(
project_name=project_name,
service_name=service_name,
num=num,
)
container_names_by_service[service_name].append(name)
#print(service_name,service_desc)
cnt=dict(name=name, num=num, service_name=service_name, **service_desc)
labels = cnt.get('labels') or []
if isinstance(labels, dict):
labels=[("{}={}".format(k, v) if v else k) for k,v in labels.items()]
labels.extend(podman_compose_labels)
labels.extend([
"com.docker.compose.container-number={}".format(num),
"com.docker.compose.service="+service_name,
])
cnt['labels'] = labels
given_containers.append(cnt)
#pods, containers = tr_1pod(project_name, container_names_by_service, given_containers)
pods, containers = tr_1podfw(project_name, container_names_by_service, given_containers)
#pods, containers = tr_identity(project_name, container_names_by_service, given_containers)
up(project_name, dirname, pods, containers)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('command', metavar='command',
help='command to run',
choices=['up', 'down'], nargs=1, default="up")
parser.add_argument("-f", "--file",
help="Specify an alternate compose file (default: docker-compose.yml)",
type=str, default='docker-compose.yml')
parser.add_argument("-p", "--project-name",
help="Specify an alternate project name (default: directory name)",
type=str, default=None)
parser.add_argument("--no-ansi",
help="Do not print ANSI control characters", action='store_true')
parser.add_argument("-t", "--transform_policy",
help="how to translate docker compose to podman [1pod|hostnet|accurate]",
choices=['1pod', '1podfw', 'hostnet', 'identity'], default='1podfw')
args = parser.parse_args()
main(
filename=args.file,
project_name=args.project_name,
no_ansi=args.no_ansi,
transform_policy=args.transform_policy)