"""Convert a `docker run ...` command string into a Compose YAML fragment.""" from __future__ import annotations import shlex import yaml def _service_name_from_image(image: str) -> str: name = image.split("/")[-1].split(":")[0] return name or "app" def convert_docker_run(command: str) -> str: tokens = shlex.split(command) # Drop leading "docker run" / "docker container run". while tokens and tokens[0] in ("docker", "run", "container", "sudo"): tokens.pop(0) service: dict = {} name = None image = None ports: list[str] = [] volumes: list[str] = [] environment: list[str] = [] env_file: list[str] = [] devices: list[str] = [] cap_add: list[str] = [] labels: list[str] = [] networks: list[str] = [] command_args: list[str] = [] i = 0 n = len(tokens) def take_value(idx: int, tok: str): if "=" in tok and tok.startswith("--") and not tok.endswith("="): return tok.split("=", 1)[1], idx + 1 return tokens[idx + 1], idx + 2 while i < n: tok = tokens[i] if image is not None: # Everything after the image is the container command. command_args = tokens[i:] break if not tok.startswith("-"): image = tok i += 1 continue flag = tok.split("=", 1)[0] if flag in ("-d", "--detach", "-i", "--interactive", "-t", "--tty", "--rm", "--init"): i += 1 elif flag in ("--name",): name, i = take_value(i, tok) elif flag in ("-p", "--publish"): val, i = take_value(i, tok) ports.append(val) elif flag in ("-v", "--volume", "--mount"): val, i = take_value(i, tok) volumes.append(val) elif flag in ("-e", "--env"): val, i = take_value(i, tok) environment.append(val) elif flag in ("--env-file",): val, i = take_value(i, tok) env_file.append(val) elif flag in ("--device",): val, i = take_value(i, tok) devices.append(val) elif flag in ("--cap-add",): val, i = take_value(i, tok) cap_add.append(val) elif flag in ("-l", "--label"): val, i = take_value(i, tok) labels.append(val) elif flag in ("--network", "--net"): val, i = take_value(i, tok) networks.append(val) elif flag in ("--restart",): val, i = take_value(i, tok) service["restart"] = val elif flag in ("--privileged",): service["privileged"] = True i += 1 elif flag in ("--hostname", "-h"): val, i = take_value(i, tok) service["hostname"] = val elif flag in ("-u", "--user"): val, i = take_value(i, tok) service["user"] = val elif flag in ("-w", "--workdir"): val, i = take_value(i, tok) service["working_dir"] = val else: # Unknown flag — try to consume a value if it looks like it takes one. if "=" in tok: i += 1 elif i + 1 < n and not tokens[i + 1].startswith("-"): i += 2 else: i += 1 if not image: raise ValueError("Could not find an image in the docker run command") service["image"] = image if "restart" not in service: service["restart"] = "unless-stopped" if ports: service["ports"] = ports if volumes: service["volumes"] = volumes if environment: service["environment"] = environment if env_file: service["env_file"] = env_file if devices: service["devices"] = devices if cap_add: service["cap_add"] = cap_add if labels: service["labels"] = labels if networks: service["networks"] = networks if command_args: service["command"] = command_args svc_name = name or _service_name_from_image(image) if name: service["container_name"] = name doc = {"services": {svc_name: service}} return yaml.safe_dump(doc, sort_keys=False, default_flow_style=False)