This commit is contained in:
Muayyad alsadi 2022-02-26 02:49:34 +03:00
parent 5c33e4efbb
commit 44508352e8

View File

@ -60,7 +60,7 @@ def try_float(i, fallback=None):
return fallback return fallback
def log(*msgs, sep=" ", end="\n"): def log(*msgs, sep=" ", end="\n"):
line = (sep.join(["{}".format(msg) for msg in msgs]))+end line = (sep.join([str(msg) for msg in msgs]))+end
sys.stderr.write(line) sys.stderr.write(line)
sys.stderr.flush() sys.stderr.flush()
@ -203,7 +203,7 @@ def rec_subs(value, subs_dict):
if value == "" and m.group('empty'): if value == "" and m.group('empty'):
value = None value = None
if value is not None: if value is not None:
return "%s" % value return str(value)
if m.group("err") is not None: if m.group("err") is not None:
raise RuntimeError(m.group("err")) raise RuntimeError(m.group("err"))
return m.group("default") or "" return m.group("default") or ""
@ -220,7 +220,7 @@ def norm_as_list(src):
if src is None: if src is None:
dst = [] dst = []
elif is_dict(src): elif is_dict(src):
dst = [("{}={}".format(k, v) if v is not None else k) for k, v in src.items()] dst = [(f"{k}={v}" if v is not None else k) for k, v in src.items()]
elif is_list(src): elif is_list(src):
dst = list(src) dst = list(src)
else: else:
@ -254,7 +254,7 @@ def norm_ulimit(inner_value):
raise ValueError("expected at least one soft or hard limit") raise ValueError("expected at least one soft or hard limit")
soft = inner_value.get("soft", inner_value.get("hard", None)) soft = inner_value.get("soft", inner_value.get("hard", None))
hard = inner_value.get("hard", inner_value.get("soft", None)) hard = inner_value.get("hard", inner_value.get("soft", None))
return "{}:{}".format(soft, hard) return f"{soft}:{hard}"
if is_list(inner_value): return norm_ulimit(norm_as_dict(inner_value)) if is_list(inner_value): return norm_ulimit(norm_as_dict(inner_value))
# if int or string return as is # if int or string return as is
return inner_value return inner_value
@ -295,7 +295,7 @@ def assert_volume(compose, mount_dict):
if mount_dict["type"] != "volume" or not vol or vol.get("external", None) or not vol.get("name", None): return if mount_dict["type"] != "volume" or not vol or vol.get("external", None) or not vol.get("name", None): return
proj_name = compose.project_name proj_name = compose.project_name
vol_name = vol["name"] vol_name = vol["name"]
log("podman volume inspect {vol_name} || podman volume create {vol_name}".format(vol_name=vol_name)) log(f"podman volume inspect {vol_name} || podman volume create {vol_name}")
# TODO: might move to using "volume list" # TODO: might move to using "volume list"
# 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: _ = compose.podman.output([], "volume", ["inspect", vol_name]).decode('utf-8') try: _ = compose.podman.output([], "volume", ["inspect", vol_name]).decode('utf-8')
@ -303,8 +303,8 @@ def assert_volume(compose, mount_dict):
labels = vol.get("labels", None) or [] labels = vol.get("labels", None) or []
args = [ args = [
"create", "create",
"--label", "io.podman.compose.project={}".format(proj_name), "--label", f"io.podman.compose.project={proj_name}",
"--label", "com.docker.compose.project={}".format(proj_name), "--label", f"com.docker.compose.project={proj_name}",
] ]
for item in norm_as_list(labels): for item in norm_as_list(labels):
args.extend(["--label", item]) args.extend(["--label", item])
@ -313,7 +313,7 @@ def assert_volume(compose, mount_dict):
args.extend(["--driver", driver]) args.extend(["--driver", driver])
driver_opts = vol.get("driver_opts", None) or {} driver_opts = vol.get("driver_opts", None) or {}
for opt, value in driver_opts.items(): for opt, value in driver_opts.items():
args.extend(["--opt", "{opt}={value}".format(opt=opt, value=value)]) args.extend(["--opt", f"{opt}={value}"])
args.append(vol_name) args.append(vol_name)
compose.podman.output([], "volume", args) compose.podman.output([], "volume", args)
_ = compose.podman.output([], "volume", ["inspect", vol_name]).decode('utf-8') _ = compose.podman.output([], "volume", ["inspect", vol_name]).decode('utf-8')
@ -327,34 +327,23 @@ def mount_desc_to_mount_args(compose, mount_desc, srv_name, cnt_name): # pylint:
if mount_desc.get(mount_type, None): if mount_desc.get(mount_type, None):
# TODO: we might need to add mount_dict[mount_type]["propagation"] = "z" # TODO: we might need to add mount_dict[mount_type]["propagation"] = "z"
mount_prop = mount_desc.get(mount_type, {}).get("propagation", None) mount_prop = mount_desc.get(mount_type, {}).get("propagation", None)
if mount_prop: opts.append("{}-propagation={}".format(mount_type, mount_prop)) if mount_prop: opts.append(f"{mount_type}-propagation={mount_prop}")
if mount_desc.get("read_only", False): opts.append("ro") if mount_desc.get("read_only", False): opts.append("ro")
if mount_type == 'tmpfs': if mount_type == 'tmpfs':
tmpfs_opts = mount_desc.get("tmpfs", {}) tmpfs_opts = mount_desc.get("tmpfs", {})
tmpfs_size = tmpfs_opts.get("size", None) tmpfs_size = tmpfs_opts.get("size", None)
if tmpfs_size: if tmpfs_size:
opts.append("tmpfs-size={}".format(tmpfs_size)) opts.append(f"tmpfs-size={tmpfs_size}")
tmpfs_mode = tmpfs_opts.get("mode", None) tmpfs_mode = tmpfs_opts.get("mode", None)
if tmpfs_mode: if tmpfs_mode:
opts.append("tmpfs-mode={}".format(tmpfs_mode)) opts.append(f"tmpfs-mode={tmpfs_mode}")
opts = ",".join(opts) opts = ",".join(opts)
if mount_type == 'bind': if mount_type == 'bind':
return "type=bind,source={source},destination={target},{opts}".format( return f"type=bind,source={source},destination={target},{opts}".rstrip(",")
source=source,
target=target,
opts=opts
).rstrip(",")
if mount_type == 'volume': if mount_type == 'volume':
return "type=volume,source={source},destination={target},{opts}".format( return f"type=volume,source={source},destination={target},{opts}".rstrip(",")
source=source,
target=target,
opts=opts
).rstrip(",")
if mount_type == 'tmpfs': if mount_type == 'tmpfs':
return "type=tmpfs,destination={target},{opts}".format( return f"type=tmpfs,destination={target},{opts}".rstrip(",")
target=target,
opts=opts
).rstrip(",")
raise ValueError("unknown mount type:"+mount_type) raise ValueError("unknown mount type:"+mount_type)
def container_to_ulimit_args(cnt, podman_args): def container_to_ulimit_args(cnt, podman_args):
@ -423,9 +412,9 @@ def get_mount_args(compose, cnt, volume):
tmpfs_opts = volume.get("tmpfs", {}) tmpfs_opts = volume.get("tmpfs", {})
opts = [] opts = []
size = tmpfs_opts.get("size", None) size = tmpfs_opts.get("size", None)
if size: opts.append('size={}'.format(size)) if size: opts.append(f'size={size}')
mode = tmpfs_opts.get("mode", None) mode = tmpfs_opts.get("mode", None)
if mode: opts.append('mode={}'.format(mode)) if mode: opts.append(f'mode={mode}')
if opts: args += ':' + ','.join(opts) if opts: args += ':' + ','.join(opts)
return ['--tmpfs', args] return ['--tmpfs', args]
args = mount_desc_to_volume_args(compose, volume, srv_name, cnt['name']) args = mount_desc_to_volume_args(compose, volume, srv_name, cnt['name'])
@ -438,8 +427,7 @@ def get_secret_args(compose, cnt, secret):
secret_name = secret if is_str(secret) else secret.get('source', None) secret_name = secret if is_str(secret) else secret.get('source', None)
if not secret_name or secret_name not in compose.declared_secrets.keys(): if not secret_name or secret_name not in compose.declared_secrets.keys():
raise ValueError( raise ValueError(
'ERROR: undeclared secret: "{}", service: "{}"' f'ERROR: undeclared secret: "{secret}", service: {cnt["_service"]}'
.format(secret, cnt['_service'])
) )
declared_secret = compose.declared_secrets[secret_name] declared_secret = compose.declared_secrets[secret_name]
@ -454,18 +442,19 @@ def get_secret_args(compose, cnt, secret):
if source_file: if source_file:
if not target: if not target:
dest_file = '/run/secrets/{}'.format(secret_name) dest_file = f'/run/secrets/{secret_name}'
elif not target.startswith("/"): elif not target.startswith("/"):
dest_file = '/run/secrets/{}'.format(target if target else secret_name) sec = target if target else secret_name
dest_file = f's/run/secrets/{sec}'
else: else:
dest_file = target dest_file = target
volume_ref = [ volume_ref = [
'--volume', '{}:{}:ro,rprivate,rbind'.format(source_file, dest_file) '--volume', f'{source_file}:{dest_file}:ro,rprivate,rbind'
] ]
if uid or gid or mode: if uid or gid or mode:
sec = target if target else secret_name
log( log(
'WARNING: Service "{}" uses secret "{}" with uid, gid, or mode.' f'WARNING: Service {cnt["_service"]} uses secret "{sec}" with uid, gid, or mode.'
.format(cnt['_service'], target if target else secret_name)
+ ' These fields are not supported by this implementation of the Compose file' + ' These fields are not supported by this implementation of the Compose file'
) )
return volume_ref return volume_ref
@ -477,9 +466,9 @@ def get_secret_args(compose, cnt, secret):
# podman-create commands, albiet we can only support a 1:1 mapping # podman-create commands, albiet we can only support a 1:1 mapping
# at the moment # at the moment
if declared_secret.get('external', False) or declared_secret.get('name', None): if declared_secret.get('external', False) or declared_secret.get('name', None):
secret_opts += ',uid={}'.format(uid) if uid else '' secret_opts += f',uid={uid}' if uid else ''
secret_opts += ',gid={}'.format(gid) if gid else '' secret_opts += f',gid={gid}' if gid else ''
secret_opts += ',mode={}'.format(mode) if mode else '' secret_opts += f',mode={mode}' if mode else ''
# The target option is only valid for type=env, # The target option is only valid for type=env,
# which in an ideal world would work # which in an ideal world would work
# for type=mount as well. # for type=mount as well.
@ -588,8 +577,8 @@ def assert_cnt_nets(compose, cnt):
raise RuntimeError(f"External network [{net_name}] does not exists") from e raise RuntimeError(f"External network [{net_name}] does not exists") from e
args = [ args = [
"create", "create",
"--label", "io.podman.compose.project={}".format(proj_name), "--label", f"io.podman.compose.project={proj_name}",
"--label", "com.docker.compose.project={}".format(proj_name), "--label", f"com.docker.compose.project={proj_name}",
] ]
# TODO: add more options here, like driver, internal, ..etc # TODO: add more options here, like driver, internal, ..etc
labels = net_desc.get("labels", None) or [] labels = net_desc.get("labels", None) or []
@ -664,20 +653,20 @@ def container_to_args(compose, cnt, detached=True):
# TODO: double check -e , --add-host, -v, --read-only # TODO: double check -e , --add-host, -v, --read-only
dirname = compose.dirname dirname = compose.dirname
pod = cnt.get('pod', None) or '' pod = cnt.get('pod', None) or ''
podman_args = [ name = cnt['name']
'--name={}'.format(cnt.get('name', None)), podman_args = [f'--name={name}']
]
if detached: if detached:
podman_args.append("-d") podman_args.append("-d")
if pod: if pod:
podman_args.append('--pod={}'.format(pod)) podman_args.append(f'--pod={pod}')
deps = [] deps = []
for dep_srv in (cnt.get("_deps", None) or []): for dep_srv in (cnt.get("_deps", None) or []):
deps.extend(compose.container_names_by_service.get(dep_srv, None) or []) deps.extend(compose.container_names_by_service.get(dep_srv, None) or [])
if deps: if deps:
podman_args.append('--requires={}'.format(",".join(deps))) deps_csv = ",".join(deps)
podman_args.append(f'--requires={deps_csv}')
sec = norm_as_list(cnt.get("security_opt", None)) sec = norm_as_list(cnt.get("security_opt", None))
for sec_item in sec: for sec_item in sec:
podman_args.extend(['--security-opt', sec_item]) podman_args.extend(['--security-opt', sec_item])
@ -749,7 +738,7 @@ def container_to_args(compose, cnt, detached=True):
if cnt.get('hostname', None): if cnt.get('hostname', None):
podman_args.extend(['--hostname', cnt['hostname']]) podman_args.extend(['--hostname', cnt['hostname']])
if cnt.get('shm_size', None): if cnt.get('shm_size', None):
podman_args.extend(['--shm-size', '{}'.format(cnt['shm_size'])]) podman_args.extend(['--shm-size', str(cnt['shm_size'])])
if cnt.get('stdin_open', None): if cnt.get('stdin_open', None):
podman_args.append('-i') podman_args.append('-i')
if cnt.get('stop_signal', None): if cnt.get('stop_signal', None):
@ -787,7 +776,7 @@ def container_to_args(compose, cnt, detached=True):
# If it's a string, it's equivalent to specifying CMD-SHELL # If it's a string, it's equivalent to specifying CMD-SHELL
if is_str(healthcheck_test): if is_str(healthcheck_test):
# podman does not add shell to handle command with whitespace # podman does not add shell to handle command with whitespace
podman_args.extend(['--healthcheck-command', '/bin/sh -c {}'.format(cmd_quote(healthcheck_test))]) podman_args.extend(['--healthcheck-command', '/bin/sh -c '+cmd_quote(healthcheck_test)])
elif is_list(healthcheck_test): elif is_list(healthcheck_test):
healthcheck_test = healthcheck_test.copy() healthcheck_test = healthcheck_test.copy()
# If it's a list, first item is either NONE, CMD or CMD-SHELL. # If it's a list, first item is either NONE, CMD or CMD-SHELL.
@ -795,18 +784,17 @@ def container_to_args(compose, cnt, detached=True):
if healthcheck_type == 'NONE': if healthcheck_type == 'NONE':
podman_args.append("--no-healthcheck") podman_args.append("--no-healthcheck")
elif healthcheck_type == 'CMD': elif healthcheck_type == 'CMD':
podman_args.extend(['--healthcheck-command', '/bin/sh -c {}'.format( cmd_q = "' '".join([cmd_quote(i) for i in healthcheck_test])
"' '".join([cmd_quote(i) for i in healthcheck_test]) podman_args.extend(['--healthcheck-command', '/bin/sh -c '+cmd_q])
)])
elif healthcheck_type == 'CMD-SHELL': elif healthcheck_type == 'CMD-SHELL':
if len(healthcheck_test)!=1: if len(healthcheck_test)!=1:
raise ValueError("'CMD_SHELL' takes a single string after it") raise ValueError("'CMD_SHELL' takes a single string after it")
podman_args.extend(['--healthcheck-command', '/bin/sh -c {}'.format(cmd_quote(healthcheck_test[0]))]) cmd_q = cmd_quote(healthcheck_test[0])
podman_args.extend(['--healthcheck-command', '/bin/sh -c '+cmd_q])
else: else:
raise ValueError( raise ValueError(
"unknown healthcheck test type [{}],\ f"unknown healthcheck test type [{healthcheck_type}],\
expecting NONE, CMD or CMD-SHELL." expecting NONE, CMD or CMD-SHELL."
.format(healthcheck_type)
) )
else: else:
raise ValueError("'healthcheck.test' either a string or a list") raise ValueError("'healthcheck.test' either a string or a list")
@ -821,7 +809,7 @@ def container_to_args(compose, cnt, detached=True):
# convert other parameters to string # convert other parameters to string
if 'retries' in healthcheck: if 'retries' in healthcheck:
podman_args.extend(['--healthcheck-retries', '{}'.format(healthcheck['retries'])]) podman_args.extend(['--healthcheck-retries', str(healthcheck['retries'])])
podman_args.append(cnt['image']) # command, ..etc. podman_args.append(cnt['image']) # command, ..etc.
command = cnt.get('command', None) command = cnt.get('command', None)
@ -990,7 +978,9 @@ def rec_merge_one(target, source):
if key not in source: continue if key not in source: continue
value2 = source[key] value2 = source[key]
if isinstance(value2, type(value)): if isinstance(value2, type(value)):
raise ValueError("can't merge value of {} of type {} and {}".format(key, type(value), type(value2))) value_type = type(value)
value2_type = type(value2)
raise ValueError(f"can't merge value of {key} of type {value_type} and {value2_type}")
if is_list(value2): if is_list(value2):
if key == 'volumes': if key == 'volumes':
# clean duplicate mount targets # clean duplicate mount targets
@ -1026,7 +1016,7 @@ def resolve_extends(services, service_names, environ):
if filename: if filename:
if filename.startswith('./'): if filename.startswith('./'):
filename = filename[2:] filename = filename[2:]
with open(filename, 'r') as f: with open(filename, 'r', encoding="utf-8") as f:
content = yaml.safe_load(f) or {} content = yaml.safe_load(f) or {}
if "services" in content: if "services" in content:
content = content["services"] content = content["services"]
@ -1121,7 +1111,7 @@ class PodmanCompose:
else: else:
# this also works if podman hasn't been installed now # this also works if podman hasn't been installed now
if args.dry_run is False: if args.dry_run is False:
sys.stderr.write("Binary {} has not been found.\n".format(podman_path)) log(f"Binary {podman_path} has not been found.")
sys.exit(1) sys.exit(1)
self.podman = Podman(self, podman_path, args.dry_run) self.podman = Podman(self, podman_path, args.dry_run)
if not args.dry_run: if not args.dry_run:
@ -1132,7 +1122,7 @@ class PodmanCompose:
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
self.podman_version = None self.podman_version = None
if not self.podman_version: if not self.podman_version:
sys.stderr.write("it seems that you do not have `podman` installed\n") log("it seems that you do not have `podman` installed")
sys.exit(1) sys.exit(1)
log("using podman version: "+self.podman_version) log("using podman version: "+self.podman_version)
cmd_name = args.command cmd_name = args.command
@ -1181,7 +1171,7 @@ class PodmanCompose:
project_name = os.environ.get("COMPOSE_PROJECT_NAME", None) or dir_basename.lower() project_name = os.environ.get("COMPOSE_PROJECT_NAME", None) or dir_basename.lower()
project_name = norm_re.sub('', project_name) project_name = norm_re.sub('', project_name)
if not project_name: if not project_name:
raise RuntimeError("Project name [{}] normalized to empty".format(dir_basename)) raise RuntimeError(f"Project name [{dir_basename}] normalized to empty")
self.project_name = project_name self.project_name = project_name
@ -1200,7 +1190,7 @@ class PodmanCompose:
}) })
compose = {} compose = {}
for filename in files: for filename in files:
with open(filename, 'r') as f: with open(filename, 'r', encoding="utf-8") as f:
content = yaml.safe_load(f) content = yaml.safe_load(f)
#log(filename, json.dumps(content, indent = 2)) #log(filename, json.dumps(content, indent = 2))
if not isinstance(content, dict): if not isinstance(content, dict):
@ -1273,11 +1263,7 @@ class PodmanCompose:
replicas = try_int(service_desc.get('deploy', {}).get('replicas', '1')) replicas = try_int(service_desc.get('deploy', {}).get('replicas', '1'))
container_names_by_service[service_name] = [] container_names_by_service[service_name] = []
for num in range(1, replicas+1): for num in range(1, replicas+1):
name0 = "{project_name}_{service_name}_{num}".format( name0 = f"{project_name}_{service_name}_{num}"
project_name=project_name,
service_name=service_name,
num=num,
)
if num == 1: if num == 1:
name = service_desc.get("container_name", name0) name = service_desc.get("container_name", name0)
else: else:
@ -1287,15 +1273,12 @@ class PodmanCompose:
cnt = dict(name=name, num=num, cnt = dict(name=name, num=num,
service_name=service_name, **service_desc) service_name=service_name, **service_desc)
if 'image' not in cnt: if 'image' not in cnt:
cnt['image'] = "{project_name}_{service_name}".format( cnt['image'] = f"{project_name}_{service_name}"
project_name=project_name,
service_name=service_name,
)
labels = norm_as_list(cnt.get('labels', None)) labels = norm_as_list(cnt.get('labels', None))
cnt["ports"] = norm_ports(cnt.get("ports", None)) cnt["ports"] = norm_ports(cnt.get("ports", None))
labels.extend(podman_compose_labels) labels.extend(podman_compose_labels)
labels.extend([ labels.extend([
"com.docker.compose.container-number={}".format(num), f"com.docker.compose.container-number={num}",
"com.docker.compose.service=" + service_name, "com.docker.compose.service=" + service_name,
]) ])
cnt['labels'] = labels cnt['labels'] = labels
@ -1497,7 +1480,7 @@ def create_pods(compose, args): # pylint: disable=unused-argument
for pod in compose.pods: for pod in compose.pods:
podman_args = [ podman_args = [
"create", "create",
"--name={}".format(pod["name"]), "--name=" + pod['name'],
"--infra=false", "--infra=false",
"--share=", "--share=",
] ]
@ -1611,7 +1594,7 @@ def compose_down(compose, args):
timeout=getattr(args, 'timeout', None) timeout=getattr(args, 'timeout', None)
if timeout is None: if timeout is None:
timeout = 1 timeout = 1
podman_args.extend(['-t', "{}".format(timeout)]) podman_args.extend(['-t', str(timeout)])
containers = list(reversed(compose.containers)) containers = list(reversed(compose.containers))
for cnt in containers: for cnt in containers:
@ -1732,7 +1715,7 @@ def transfer_service_status(compose, args, action):
podman_args=[] podman_args=[]
timeout=getattr(args, 'timeout', None) timeout=getattr(args, 'timeout', None)
if timeout is not None: if timeout is not None:
podman_args.extend(['-t', "{}".format(timeout)]) podman_args.extend(['-t', str(timeout)])
for target in targets: for target in targets:
compose.podman.run([], action, podman_args+[target], sleep=0) compose.podman.run([], action, podman_args+[target], sleep=0)