FIXES #35: now support multiple composer files

This commit is contained in:
Muayyad Alsadi 2019-09-08 02:20:48 +03:00
parent a512c0cb82
commit 2a8d430c92
3 changed files with 86 additions and 19 deletions

View File

@ -202,6 +202,9 @@ def norm_as_dict(src):
elif is_list(src): elif is_list(src):
dst = [i.split("=", 1) for i in src if i] dst = [i.split("=", 1) for i in src if i]
dst = dict([(a if len(a) == 2 else (a[0], None)) for a in dst]) dst = dict([(a if len(a) == 2 else (a[0], None)) for a in dst])
elif is_str(src):
key, value = src.split("=", 1) if "=" in src else (src, None)
dst = {key: value}
else: else:
raise ValueError("dictionary or iterable is expected") raise ValueError("dictionary or iterable is expected")
return dst return dst
@ -359,10 +362,10 @@ def mount_dict_vol_to_bind(compose, mount_dict):
vol_name = mount_dict["source"] vol_name = mount_dict["source"]
print("podman volume inspect {vol_name} || podman volume create {vol_name}".format(vol_name=vol_name)) print("podman volume inspect {vol_name} || podman volume create {vol_name}".format(vol_name=vol_name))
# podman volume list --format '{{.Name}}\t{{.MountPoint}}' -f 'label=io.podman.compose.project=HERE' # podman volume list --format '{{.Name}}\t{{.MountPoint}}' -f 'label=io.podman.compose.project=HERE'
try: out = compose.podman.output(["volume", "inspect", vol_name]) try: out = compose.podman.output(["volume", "inspect", vol_name]).decode('utf-8')
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
compose.podman.output(["volume", "create", "-l", "io.podman.compose.project={}".format(proj_name), vol_name]) compose.podman.output(["volume", "create", "-l", "io.podman.compose.project={}".format(proj_name), vol_name])
out = compose.podman.output(["volume", "inspect", vol_name]) out = compose.podman.output(["volume", "inspect", vol_name]).decode('utf-8')
src = json.loads(out)[0]["mountPoint"] src = json.loads(out)[0]["mountPoint"]
ret=dict(mount_dict, type="bind", source=src, _vol=vol_name) ret=dict(mount_dict, type="bind", source=src, _vol=vol_name)
bind_prop=ret.get("bind", {}).get("propagation") bind_prop=ret.get("bind", {}).get("propagation")
@ -594,6 +597,45 @@ class Podman:
time.sleep(sleep) time.sleep(sleep)
return p return p
def normalize(compose):
"""
convert compose dict of some keys from string or dicts into arrays
"""
services = compose.get("services", None) or {}
for service_name, service in services.items():
for key in ("env_file", "security_opt"):
if key not in service: continue
if is_str(service[key]): service[key]=[service[key]]
for key in ("environment", "labels"):
if key not in service: continue
service[key] = norm_as_dict(service[key])
return compose
def rec_merge(target, source):
"""
update content of compose with keys from content recursively
"""
done = set()
for key, value in source.items():
if key in target: continue
target[key]=value
done.add(key)
for key, value in target.items():
if key in done: continue
if key not in source: continue
value2 = source[key]
if type(value2)!=type(value):
raise ValueError("can't merge value of {} of type {} and {}".format(key, type(value), type(value2)))
if is_str(value2):
target[key]=value2
elif is_list(value2):
value.extend(value2)
elif is_dict(value2):
rec_merge(value, value2)
else:
raise ValueError("unexpected type of {}".format(key))
return target
class PodmanCompose: class PodmanCompose:
def __init__(self): def __init__(self):
self.commands = {} self.commands = {}
@ -627,21 +669,30 @@ class PodmanCompose:
def _parse_compose_file(self): def _parse_compose_file(self):
args = self.global_args args = self.global_args
cmd = args.command cmd = args.command
filename = args.file if not args.file:
args.file = list(filter(os.path.exists, [
"docker-compose.yml",
"docker-compose.yaml",
"docker-compose.override.yml",
"docker-compose.override.yaml"
]))
files = args.file
if not files:
print("no docker-compose.yml found, pass files with -f")
ex = map(os.path.exists, files)
missing = [ fn0 for ex0, fn0 in zip(ex, files) if not ex0 ]
if missing:
print("missing files: ", missing)
exit(1)
# make absolute
files = list(map(os.path.realpath, files))
filename = files[0]
project_name = args.project_name project_name = args.project_name
no_ansi = args.no_ansi no_ansi = args.no_ansi
no_cleanup = args.no_cleanup no_cleanup = args.no_cleanup
dry_run = args.dry_run dry_run = args.dry_run
transform_policy = args.transform_policy transform_policy = args.transform_policy
host_env = None host_env = None
if not os.path.exists(filename):
alt_path = filename.replace('.yml', '.yaml')
if os.path.exists(alt_path):
filename = alt_path
else:
print("file [{}] not found".format(filename))
exit(-1)
filename = os.path.realpath(filename)
dirname = os.path.dirname(filename) dirname = os.path.dirname(filename)
dir_basename = os.path.basename(dirname) dir_basename = os.path.basename(dirname)
self.dirname = dirname self.dirname = dirname
@ -660,13 +711,18 @@ class PodmanCompose:
dotenv_dict = dict([l.split("=", 1) for l in dotenv_ls if "=" in l]) dotenv_dict = dict([l.split("=", 1) for l in dotenv_ls if "=" in l])
else: else:
dotenv_dict = {} dotenv_dict = {}
compose={'_dirname': dirname}
with open(filename, 'r') as f: for filename in files:
compose = rec_subs(yaml.safe_load(f), [os.environ, dotenv_dict]) with open(filename, 'r') as f:
compose['_dirname']=dirname content = yaml.safe_load(f)
#print(filename, json.dumps(content, indent = 2))
content = normalize(content)
#print(filename, json.dumps(content, indent = 2))
content = rec_subs(content, [os.environ, dotenv_dict])
rec_merge(compose, content)
# debug mode # debug mode
#print(json.dumps(compose, indent = 2)) if len(files)>1:
print(" ** merged:\n", json.dumps(compose, indent = 2))
ver = compose.get('version') ver = compose.get('version')
services = compose.get('services') services = compose.get('services')
# volumes: [...] # volumes: [...]
@ -736,12 +792,13 @@ class PodmanCompose:
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
self._init_global_parser(parser) self._init_global_parser(parser)
subparsers = parser.add_subparsers(title='command', dest='command') subparsers = parser.add_subparsers(title='command', dest='command')
subparser = subparsers.add_parser('help', help='show help')
for cmd_name, cmd in self.commands.items(): for cmd_name, cmd in self.commands.items():
subparser = subparsers.add_parser(cmd_name, help=cmd._cmd_desc) subparser = subparsers.add_parser(cmd_name, help=cmd._cmd_desc)
for cmd_parser in cmd._parse_args: for cmd_parser in cmd._parse_args:
cmd_parser(subparser) cmd_parser(subparser)
self.global_args = parser.parse_args() self.global_args = parser.parse_args()
if not self.global_args.command: if not self.global_args.command or self.global_args.command=='help':
parser.print_help() parser.print_help()
exit(-1) exit(-1)
return self.global_args return self.global_args
@ -749,7 +806,7 @@ class PodmanCompose:
def _init_global_parser(self, parser): def _init_global_parser(self, parser):
parser.add_argument("-f", "--file", parser.add_argument("-f", "--file",
help="Specify an alternate compose file (default: docker-compose.yml)", help="Specify an alternate compose file (default: docker-compose.yml)",
type=str, default="docker-compose.yml") metavar='file', action='append', default=[])
parser.add_argument("-p", "--project-name", parser.add_argument("-p", "--project-name",
help="Specify an alternate project name (default: directory name)", help="Specify an alternate project name (default: directory name)",
type=str, default=None) type=str, default=None)

View File

@ -6,4 +6,8 @@ services:
volumes: volumes:
- ./1.env:/var/www/html/index.txt - ./1.env:/var/www/html/index.txt
env_file: ./1.env env_file: ./1.env
labels:
l1: v1
environment:
- mykey1=myval1

View File

@ -2,6 +2,12 @@ version: '3'
services: services:
web1: web1:
env_file: ./12.env env_file: ./12.env
labels:
- l1=v2
- l2=v2
environment:
mykey1: myval2
mykey2: myval2
web2: web2:
image: busybox image: busybox