220 lines
6.6 KiB
Python
220 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
|
import argparse
|
|
import subprocess
|
|
from copy import deepcopy
|
|
from pathlib import Path
|
|
from collections import defaultdict
|
|
|
|
from ruamel.yaml import YAML
|
|
from ruamel.yaml.comments import CommentedMap
|
|
|
|
|
|
# -----------------------------
|
|
# YAML 설정
|
|
# -----------------------------
|
|
yaml = YAML()
|
|
yaml.preserve_quotes = True
|
|
yaml.indent(mapping=2, sequence=4, offset=2)
|
|
|
|
|
|
# -----------------------------
|
|
# kubectl ingress 조회
|
|
# -----------------------------
|
|
def kubectl_ingress():
|
|
out = subprocess.check_output(
|
|
["kubectl", "get", "ingress", "--all-namespaces", "-o", "yaml"]
|
|
)
|
|
return yaml.load(out)
|
|
|
|
|
|
# -----------------------------
|
|
# mapping.yaml 로드
|
|
# -----------------------------
|
|
def load_mapping(path):
|
|
with open(path) as f:
|
|
return yaml.load(f)
|
|
|
|
|
|
# -----------------------------
|
|
# ingress 변환 로직
|
|
# -----------------------------
|
|
def migrate_ingress(data, mapping, ingress_class=None):
|
|
report = {
|
|
"converted": [],
|
|
"partial": [],
|
|
"unsupported": [],
|
|
"detail": defaultdict(lambda: {"partial": [], "unsupported": []}),
|
|
}
|
|
|
|
new = deepcopy(data)
|
|
|
|
for item in new.get("items", []):
|
|
meta = item.setdefault("metadata", {})
|
|
spec = item.setdefault("spec", {})
|
|
|
|
name = meta.get("name")
|
|
namespace = meta.get("namespace", "default")
|
|
ingress_id = f"{namespace}/{name}"
|
|
|
|
if ingress_class:
|
|
spec["ingressClassName"] = ingress_class
|
|
|
|
anns = meta.get("annotations", {}) or {}
|
|
new_anns = CommentedMap()
|
|
|
|
for k, v in anns.items():
|
|
if k in mapping:
|
|
rule = mapping[k]
|
|
support = rule.get("support", "unsupported")
|
|
haproxy_key = rule.get("haproxy")
|
|
|
|
if support == "full":
|
|
new_anns[haproxy_key] = v
|
|
report["converted"].append(k)
|
|
|
|
elif support == "partial":
|
|
new_anns[haproxy_key] = v
|
|
note = rule.get("note", "")
|
|
new_anns.yaml_set_comment_before_after_key(
|
|
haproxy_key,
|
|
before=f"PARTIAL SUPPORT: {note}",
|
|
)
|
|
report["partial"].append(k)
|
|
report["detail"][ingress_id]["partial"].append(
|
|
{
|
|
"nginx": k,
|
|
"haproxy": haproxy_key,
|
|
"note": note,
|
|
}
|
|
)
|
|
|
|
else:
|
|
lines = [f"UNSUPPORTED {k}:"]
|
|
for line in str(v).splitlines():
|
|
lines.append(f" {line}")
|
|
new_anns.yaml_set_end_comment("\n".join(lines))
|
|
|
|
report["unsupported"].append(k)
|
|
report["detail"][ingress_id]["unsupported"].append(
|
|
{
|
|
"nginx": k,
|
|
"value": v,
|
|
}
|
|
)
|
|
else:
|
|
new_anns[k] = v
|
|
|
|
meta["annotations"] = new_anns
|
|
|
|
return new, report
|
|
|
|
|
|
# -----------------------------
|
|
# 리포트 생성 (Markdown)
|
|
# -----------------------------
|
|
def write_report(report, path):
|
|
with open(path, "w") as f:
|
|
f.write("# Ingress Migration Report\n\n")
|
|
f.write("## Summary\n")
|
|
f.write(f"- Converted : {len(report['converted'])}\n")
|
|
f.write(f"- Partial : {len(report['partial'])}\n")
|
|
f.write(f"- Unsupported: {len(report['unsupported'])}\n\n")
|
|
f.write("---\n\n")
|
|
|
|
if any(v["partial"] for v in report["detail"].values()):
|
|
f.write("## ⚠️ Partial Support\n\n")
|
|
for ingress, items in report["detail"].items():
|
|
if not items["partial"]:
|
|
continue
|
|
f.write(f"### {ingress}\n")
|
|
for p in items["partial"]:
|
|
f.write(
|
|
f"- {p['nginx']}\n"
|
|
f" - mapped to: {p['haproxy']}\n"
|
|
f" - note: {p['note']}\n"
|
|
)
|
|
f.write("\n")
|
|
|
|
if any(v["unsupported"] for v in report["detail"].values()):
|
|
f.write("## ❌ Unsupported\n\n")
|
|
for ingress, items in report["detail"].items():
|
|
if not items["unsupported"]:
|
|
continue
|
|
f.write(f"### {ingress}\n")
|
|
for u in items["unsupported"]:
|
|
f.write(f"- {u['nginx']}\n")
|
|
f.write(" ```nginx\n")
|
|
f.write(f"{u['value']}\n")
|
|
f.write(" ```\n")
|
|
f.write("\n")
|
|
|
|
|
|
# -----------------------------
|
|
# 파일 저장
|
|
# -----------------------------
|
|
def save_single(data, out_path):
|
|
with open(out_path, "w") as f:
|
|
yaml.dump(data, f)
|
|
|
|
|
|
def save_split(data, out_dir):
|
|
Path(out_dir).mkdir(parents=True, exist_ok=True)
|
|
for item in data.get("items", []):
|
|
name = item["metadata"]["name"]
|
|
namespace = item["metadata"].get("namespace", "default")
|
|
path = Path(out_dir) / f"{namespace}__{name}.yaml"
|
|
with open(path, "w") as f:
|
|
yaml.dump(item, f)
|
|
|
|
|
|
# -----------------------------
|
|
# main
|
|
# -----------------------------
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="nginx ingress → HAProxy ingress migration utility"
|
|
)
|
|
parser.add_argument("--mapping", default="mapping.yaml")
|
|
parser.add_argument("--single", action="store_true")
|
|
parser.add_argument("--split", action="store_true")
|
|
parser.add_argument("--out", default="output")
|
|
parser.add_argument("--ingress-class")
|
|
parser.add_argument(
|
|
"--report",
|
|
default="migration-report.md",
|
|
help="migration summary report file",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
if not args.single and not args.split:
|
|
parser.error("one of --single or --split must be specified")
|
|
|
|
ingress = kubectl_ingress()
|
|
mapping = load_mapping(args.mapping)
|
|
|
|
converted, report = migrate_ingress(
|
|
ingress,
|
|
mapping,
|
|
ingress_class=args.ingress_class,
|
|
)
|
|
|
|
if args.single:
|
|
out_file = args.out if args.out.endswith(".yaml") else f"{args.out}.yaml"
|
|
save_single(converted, out_file)
|
|
|
|
if args.split:
|
|
save_split(converted, args.out)
|
|
|
|
write_report(report, args.report)
|
|
|
|
print("\n=== Migration Summary ===")
|
|
print(f"Converted : {len(report['converted'])}")
|
|
print(f"Partial : {len(report['partial'])}")
|
|
print(f"Unsupported: {len(report['unsupported'])}")
|
|
print(f"Report : {args.report}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|