import yaml import sys import re stack_name = sys.argv[1] INPUT_FILE = f"{stack_name}/docker-compose.yml" OUTPUT_FILE = f"__swarm/{stack_name}/{stack_name}-swarm.yml" def fix_env_file(filepath): """Convert YAML-style env (KEY: value) → Docker env (KEY=value).""" fixed_lines = [] changed = False with open(filepath, "r") as f: for raw_line in f: line = raw_line.rstrip("\n") stripped = line.strip() # Preserve comments and blank lines if not stripped or stripped.startswith("#"): fixed_lines.append(raw_line) continue # Detect YAML-style: KEY: value # MUST convert if ":" in stripped and "=" not in stripped.split(":")[0]: key, value = stripped.split(":", 1) key = key.strip() value = value.strip() # Validate env key if not re.match(r"^[A-Za-z0-9_]+$", key): raise ValueError(f"Invalid variable name: {key}") fixed_lines.append(f"{key}={value}\n") changed = True continue # Detect valid Docker-style: KEY=value if "=" in stripped: key, value = stripped.split("=", 1) # Validate key if not re.match(r"^[A-Za-z0-9_]+$", key): raise ValueError(f"Invalid environment variable name: {key}") # Value may contain anything fixed_lines.append(raw_line) continue # Anything else is invalid raise ValueError(f"Invalid env line: {stripped}") # Write file if modified if changed: with open(filepath, "w") as f: f.writelines(fixed_lines) print(f"[FIXED] Converted YAML → Docker env format in {filepath}") else: print(f"[OK] .env file valid: {filepath}") def convert_ports(ports): """Convert short port syntax to Swarm long syntax.""" result = [] print(f"Converting ports: {ports}") for p in ports: print(f"Port entry: {p}") if isinstance(p, str): # format: "8080:80" pub, tgt = p.split(":") result.append({ "target": int(tgt), "published": int(pub), "protocol": "tcp", "mode": "ingress" }) else: result.append(p) return result def to_str_lower(value): """Convert value to string. Booleans become lowercase 'true'/'false'.""" if isinstance(value, bool): return "true" if value else "false" return str(value) def env_list_to_dict(env_list): """Convert environment from list ['KEY=VAL'] to dict {KEY: VAL} as strings.""" env_dict = {} for item in env_list: key, value = item.split("=", 1) # convert 'true'/'false' strings to lowercase if value.lower() in ["true", "false"]: env_dict[key] = value.lower() else: env_dict[key] = str(value) return env_dict def ensure_labels_as_string(labels): """Ensure all label values are strings, lowercase for booleans.""" return {k: to_str_lower(v) for k, v in labels.items()} def convert_compose_to_swarm(data): services = data.get("services", {}) #input(services) for name, svc in services.items(): print(f"Converting service: {name} , svc: {svc}") if name in ["container_name", "restart", "depends_on"]: continue svc.pop('restart', None) svc.pop('depends_on', None) svc.pop('container_name', None) # 1) Convert environment list → dict (strings) if "environment" in svc and isinstance(svc["environment"], list): svc["environment"] = env_list_to_dict(svc["environment"]) # 2) Ensure deploy exists deploy = svc.setdefault("deploy", {}) # 3) Move labels into deploy.labels, all as strings (lowercase booleans) if "labels" in svc: deploy.setdefault("labels", {}) if isinstance(svc["labels"], dict): deploy["labels"].update(ensure_labels_as_string(svc["labels"])) elif isinstance(svc["labels"], list): for label in svc["labels"]: key, value = label.split("=", 1) deploy["labels"][key] = value.lower() if value.lower() in ["true", "false"] else str(value) del svc["labels"] labels = deploy.get("labels", {}) if "homepage.server" in labels and labels["homepage.server"] == "my-docker": labels["homepage.server"] = "my-docker-swarm" # 4) Default replicas deploy.setdefault("replicas", 1) # 5) Add placement constraint deploy.setdefault("placement", {}) deploy["placement"].setdefault("constraints", []) if "node.role == manager" not in deploy["placement"]["constraints"]: deploy["placement"]["constraints"].append("node.role == manager") # 6) Convert ports to long format if "ports" in svc: #input(svc) svc["ports"] = convert_ports(svc["ports"]) # 7) Remove container_name (not allowed in Swarm) svc.pop("container_name", None) return data def main(): fix_env_file(f"__swarm/{stack_name}/stack.env") # NEW FIX STEP with open(INPUT_FILE, "r") as f: compose = yaml.safe_load(f) #input(compose) swarm = convert_compose_to_swarm(compose) with open(OUTPUT_FILE, "w") as f: yaml.dump(swarm, f, sort_keys=False) print(f"Swarm stack file written to {OUTPUT_FILE}") if __name__ == "__main__": main()