forked from extern/podman-compose
add support for volumes
This commit is contained in:
parent
882cef73b7
commit
5606c69f9d
@ -14,6 +14,7 @@ import argparse
|
||||
import subprocess
|
||||
import time
|
||||
import re
|
||||
import hashlib
|
||||
|
||||
# import fnmatch
|
||||
# fnmatch.fnmatchcase(env, "*_HOST")
|
||||
@ -40,6 +41,59 @@ def try_int(i, fallback=None):
|
||||
pass
|
||||
return fallback
|
||||
|
||||
dir_re = re.compile("^[~/\.]")
|
||||
propagation_re=re.compile("^(?:z|Z|r?shared|r?slave|r?private)$")
|
||||
|
||||
def parse_short_mount(mount_str, basedir):
|
||||
mount_a = mount_str.split(':')
|
||||
mount_opt_dict = {}
|
||||
mount_opt = None
|
||||
if len(mount_a)==1:
|
||||
# Just specify a path and let the Engine create a volume
|
||||
# - /var/lib/mysql
|
||||
mount_src, mount_dst=None, mount_str
|
||||
elif len(mount_a)==2:
|
||||
mount_src, mount_dst = mount_a
|
||||
if not mount_dst.startswith('/'):
|
||||
mount_dst, mount_opt = mount_a
|
||||
mount_src = None
|
||||
elif len(mount_a)==3:
|
||||
mount_src, mount_dst, mount_opt = mount_a
|
||||
else:
|
||||
raise ValueError("could not parse mount "+mount_str)
|
||||
if mount_src and dir_re.match(mount_src):
|
||||
# Specify an absolute path mapping
|
||||
# - /opt/data:/var/lib/mysql
|
||||
# Path on the host, relative to the Compose file
|
||||
# - ./cache:/tmp/cache
|
||||
# User-relative path
|
||||
# - ~/configs:/etc/configs/:ro
|
||||
mount_type = "bind"
|
||||
# TODO: should we use os.path.realpath(basedir)?
|
||||
mount_src = os.path.join(basedir, os.path.expanduser(mount_src))
|
||||
else:
|
||||
# Named volume
|
||||
# - datavolume:/var/lib/mysql
|
||||
mount_type = "volume"
|
||||
for opt in mount_opt.split(','):
|
||||
if opt=='ro': mount_opt_dict["read_only"]=True
|
||||
elif opt=='rw': mount_opt_dict["read_only"]=False
|
||||
elif propagation_re.match(opt): mount_opt_dict["bind"]=dict(propagation=opt)
|
||||
else:
|
||||
# TODO: ignore
|
||||
raise ValueError("unknown mount option "+opt)
|
||||
return dict(type=mount_type, source=mount_src, target=mount_dst, **mount_opt_dict)
|
||||
|
||||
def fix_mount_dict(mount_dict, srv_name, cnt_name):
|
||||
"""
|
||||
in-place fix mount dictionary to add missing source
|
||||
"""
|
||||
if mount_dict["type"]=="volume" and not mount_dict.get("source"):
|
||||
mount_dict["source"] = "_".join([
|
||||
srv_name, cnt_name,
|
||||
hashlib.md5(mount_dict["target"]).hexdigest(),
|
||||
])
|
||||
return mount_dict
|
||||
|
||||
# docker and docker-compose support subset of bash variable substitution
|
||||
# https://docs.docker.com/compose/compose-file/#variable-substitution
|
||||
@ -277,6 +331,67 @@ def run_podman(dry_run, podman_path, podman_args, wait=True, sleep=1):
|
||||
time.sleep(sleep)
|
||||
return p
|
||||
|
||||
def mount_dict_vol_to_bind(mount_dict, podman_path, proj_name, shared_vols):
|
||||
"""
|
||||
inspect volume to get directory
|
||||
create volume if needed
|
||||
and return mount_dict as bind of that directory
|
||||
"""
|
||||
if mount_dict["type"]!="volume": return
|
||||
vol_name = mount_dict["source"]
|
||||
print("podman volume inspect {vol_name} || podman volume create {vol_name}".format(vol_name=vol_name))
|
||||
try: out = subprocess.check_output([podman_path, "volume", "inspect", vol_name])
|
||||
except subprocess.CalledProcessError:
|
||||
subprocess.check_output([podman_path, "volume", "create", "-l", "io.podman.compose.project={}".format(proj_name), vol_name])
|
||||
out = subprocess.check_output([podman_path, "volume", "inspect", vol_name])
|
||||
src = json.loads(out)[0]["mountPoint"]
|
||||
ret=dict(mount_dict, type="bind", source=src, _vol=vol_name)
|
||||
bind_prop=ret.get("bind", {}).get("propagation")
|
||||
if not bind_prop:
|
||||
if "bind" not in ret:
|
||||
ret["bind"]={}
|
||||
# if in top level volumes then it's shared bind-propagation=z
|
||||
if vol_name in shared_vols:
|
||||
ret["bind"]["propagation"]="z"
|
||||
else:
|
||||
ret["bind"]["propagation"]="Z"
|
||||
del ret["volume"]
|
||||
return ret
|
||||
|
||||
def mount_desc_to_args(mount_desc, podman_path, basedir, proj_name, srv_name, cnt_name, shared_vols):
|
||||
if is_str(mount_desc): mount_desc=parse_short_mount(mount_desc, basedir)
|
||||
mount_desc = mount_dict_vol_to_bind(fix_mount_dict(mount_desc, srv_name, cnt_name), podman_path, proj_name, shared_vols)
|
||||
mount_type = mount_desc.get("type")
|
||||
source = mount_desc.get("source")
|
||||
target = mount_desc["target"]
|
||||
opts=[]
|
||||
if mount_desc.get("bind"):
|
||||
bind_prop=mount_desc.["bind"].get("propagation")
|
||||
if bind_prop: opts.append("bind-propagation={}".format(bind_prop))
|
||||
if mount_desc.get("read_only", False): opts.append("ro")
|
||||
if mount_type=='tmpfs':
|
||||
tmpfs_opts = mount_desc.get("tmpfs", {})
|
||||
tmpfs_size = tmpfs_opts.get("size")
|
||||
if tmpfs_size:
|
||||
opts.append("tmpfs-size={}".format(tmpfs_size))
|
||||
tmpfs_mode = tmpfs_opts.get("mode")
|
||||
if tmpfs_mode:
|
||||
opts.append("tmpfs-mode={}".format(tmpfs_mode))
|
||||
opts=",".join(opts)
|
||||
if mount_type=='bind':
|
||||
return "type=bind,source={source},destination={target},{opts}".format(
|
||||
source=source,
|
||||
target=target,
|
||||
opts=opts
|
||||
).rstrip(",")
|
||||
elif mount_type=='tmpfs':
|
||||
return "type=tmpfs,destination={target},{opts}".format(
|
||||
target=target,
|
||||
opts=opts
|
||||
).rstrip(",")
|
||||
else:
|
||||
raise ValueError("unknown mount type:"+mount_type)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def down(project_name, dirname, pods, containers, dry_run, podman_path):
|
||||
for cnt in containers:
|
||||
@ -288,7 +403,7 @@ def down(project_name, dirname, pods, containers, dry_run, podman_path):
|
||||
run_podman(dry_run, podman_path, ["pod", "rm", pod["name"]], sleep=0)
|
||||
|
||||
|
||||
def container_to_args(cnt, dirname):
|
||||
def container_to_args(cnt, dirname, podman_path, shared_vols):
|
||||
pod = cnt.get('pod') or ''
|
||||
args = [
|
||||
'run',
|
||||
@ -314,8 +429,13 @@ def container_to_args(cnt, dirname):
|
||||
for i in cnt.get('tmpfs', []):
|
||||
args.extend(['--tmpfs', i])
|
||||
for i in cnt.get('volumes', []):
|
||||
# TODO: should we make it os.path.realpath(os.path.join(cnt['_dirname'], i))?
|
||||
args.extend(['-v', os.path.realpath(i)])
|
||||
# TODO: should we make it os.path.realpath(os.path.join(, i))?
|
||||
mount_args = mount_desc_to_args(
|
||||
i, podman_path, cnt['_dirname'],
|
||||
cnt['_project'], cnt['_service'], cnt['name'],
|
||||
shared_vols
|
||||
)
|
||||
args.extend(['--mount', mount_args])
|
||||
for i in cnt.get('extra_hosts', []):
|
||||
args.extend(['--add-host', i])
|
||||
for i in cnt.get('expose', []):
|
||||
@ -409,7 +529,7 @@ def build(project_name, dirname, pods, containers, dry_run, podman_path):
|
||||
build_args.append(ctx)
|
||||
run_podman(dry_run, podman_path, build_args, sleep=0)
|
||||
|
||||
def up(project_name, dirname, pods, containers, no_cleanup, dry_run, podman_path):
|
||||
def up(project_name, dirname, pods, containers, no_cleanup, dry_run, podman_path, shared_vols):
|
||||
os.chdir(dirname)
|
||||
|
||||
# NOTE: podman does not cache, so don't always build
|
||||
@ -432,7 +552,7 @@ def up(project_name, dirname, pods, containers, no_cleanup, dry_run, podman_path
|
||||
|
||||
for cnt in containers:
|
||||
# TODO: -e , --add-host, -v, --read-only
|
||||
args = container_to_args(cnt, dirname)
|
||||
args = container_to_args(cnt, dirname, podman_path, shared_vols)
|
||||
run_podman(dry_run, podman_path, args)
|
||||
|
||||
|
||||
@ -481,13 +601,14 @@ def run_compose(
|
||||
|
||||
ver = compose.get('version')
|
||||
services = compose.get('services')
|
||||
# volumes: [...]
|
||||
shared_vols = compose.get('volumes', [])
|
||||
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: {...}
|
||||
@ -523,6 +644,7 @@ def run_compose(
|
||||
])
|
||||
cnt['labels'] = labels
|
||||
cnt['_service'] = service_name
|
||||
cnt['_project'] = project_name
|
||||
given_containers.append(cnt)
|
||||
container_by_name = dict([(c["name"], c) for c in given_containers])
|
||||
flat_deps(container_names_by_service, container_by_name)
|
||||
@ -540,7 +662,7 @@ def run_compose(
|
||||
build(project_name, dirname, pods, containers, dry_run, podman_path)
|
||||
elif cmd == "up":
|
||||
up(project_name, dirname, pods, containers,
|
||||
no_cleanup, dry_run, podman_path)
|
||||
no_cleanup, dry_run, podman_path, shared_vols)
|
||||
elif cmd == "down":
|
||||
down(project_name, dirname, pods, containers, dry_run, podman_path)
|
||||
else:
|
||||
|
Loading…
Reference in New Issue
Block a user