Files
docker-compose/yaml_convert2.py
2025-11-30 18:01:08 +01:00

172 lines
5.7 KiB
Python

import yaml
import sys
import re
import os
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():
if os.path.exists(f"__swarm/{stack_name}/stack.env"):
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()