기능 추가
This commit is contained in:
@@ -47,6 +47,8 @@
|
|||||||
--ingress-class : ingressClassName 설정
|
--ingress-class : ingressClassName 설정
|
||||||
--report : report 파일 저장 경로
|
--report : report 파일 저장 경로
|
||||||
--mapping : 맵핑 파일 지정 (default : mapping.yaml)
|
--mapping : 맵핑 파일 지정 (default : mapping.yaml)
|
||||||
|
--from-ingress-class : 기존 ingressclassname 지정 (지정된 대상만 변환)
|
||||||
|
--namespace : 지정시 해당 namespace 내 ingress만 변환
|
||||||
```
|
```
|
||||||
|
|
||||||
# 구성 (Kubernetes 실행 기준)
|
# 구성 (Kubernetes 실행 기준)
|
||||||
|
|||||||
164
migrate.py
164
migrate.py
@@ -20,10 +20,17 @@ yaml.width = 4096
|
|||||||
# -----------------------------
|
# -----------------------------
|
||||||
# kubectl ingress 조회
|
# kubectl ingress 조회
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
def kubectl_ingress():
|
def kubectl_ingress(namespace=None):
|
||||||
out = subprocess.check_output(
|
cmd = ["kubectl", "get", "ingress"]
|
||||||
["kubectl", "get", "ingress", "--all-namespaces", "-o", "yaml"]
|
|
||||||
)
|
if namespace:
|
||||||
|
cmd += ["-n", namespace]
|
||||||
|
else:
|
||||||
|
cmd.append("--all-namespaces")
|
||||||
|
|
||||||
|
cmd += ["-o", "yaml"]
|
||||||
|
|
||||||
|
out = subprocess.check_output(cmd)
|
||||||
return yaml.load(out)
|
return yaml.load(out)
|
||||||
|
|
||||||
|
|
||||||
@@ -37,7 +44,6 @@ def load_mapping(path):
|
|||||||
|
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
# annotation 렌더링
|
# annotation 렌더링
|
||||||
# (모든 value를 문자열로 강제)
|
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
def render_annotations_block(unsupported, partial, converted):
|
def render_annotations_block(unsupported, partial, converted):
|
||||||
lines = []
|
lines = []
|
||||||
@@ -53,7 +59,7 @@ def render_annotations_block(unsupported, partial, converted):
|
|||||||
for p in partial:
|
for p in partial:
|
||||||
lines.append(f" # PARTIAL SUPPORT: {p['note']}")
|
lines.append(f" # PARTIAL SUPPORT: {p['note']}")
|
||||||
lines.append(f" {p['haproxy']}: \"{str(p['value'])}\"")
|
lines.append(f" {p['haproxy']}: \"{str(p['value'])}\"")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
for c in converted:
|
for c in converted:
|
||||||
lines.append(f" {c['haproxy']}: \"{str(c['value'])}\"")
|
lines.append(f" {c['haproxy']}: \"{str(c['value'])}\"")
|
||||||
@@ -62,7 +68,7 @@ def render_annotations_block(unsupported, partial, converted):
|
|||||||
|
|
||||||
|
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
# 불필요한 metadata 제거
|
# metadata 정리
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
def cleanup_metadata(meta):
|
def cleanup_metadata(meta):
|
||||||
for k in [
|
for k in [
|
||||||
@@ -75,10 +81,46 @@ def cleanup_metadata(meta):
|
|||||||
meta.pop(k, None)
|
meta.pop(k, None)
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# ingressClassName 기준 필터링
|
||||||
|
# -----------------------------
|
||||||
|
def filter_by_ingress_class(data, from_ingress_class):
|
||||||
|
filtered = deepcopy(data)
|
||||||
|
filtered["items"] = []
|
||||||
|
|
||||||
|
for item in data.get("items", []):
|
||||||
|
spec = item.get("spec", {})
|
||||||
|
current_class = spec.get("ingressClassName", "nginx")
|
||||||
|
|
||||||
|
if current_class == from_ingress_class:
|
||||||
|
filtered["items"].append(item)
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# 변환 전 원본 백업 (backup/ns/ingress.yaml)
|
||||||
|
# -----------------------------
|
||||||
|
def backup_original_ingress(data, backup_dir="./backup"):
|
||||||
|
base = Path(backup_dir)
|
||||||
|
base.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
for item in data.get("items", []):
|
||||||
|
meta = item.get("metadata", {})
|
||||||
|
name = meta.get("name")
|
||||||
|
namespace = meta.get("namespace", "default")
|
||||||
|
|
||||||
|
ns_dir = base / namespace
|
||||||
|
ns_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
with (ns_dir / f"{name}.yaml").open("w") as f:
|
||||||
|
yaml.dump(item, f)
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
# ingress 변환 로직
|
# ingress 변환 로직
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
def migrate_ingress(data, mapping, ingress_class=None):
|
def migrate_ingress(data, mapping, to_ingress_class=None):
|
||||||
report = {
|
report = {
|
||||||
"converted": [],
|
"converted": [],
|
||||||
"partial": [],
|
"partial": [],
|
||||||
@@ -98,8 +140,8 @@ def migrate_ingress(data, mapping, ingress_class=None):
|
|||||||
namespace = meta.get("namespace", "default")
|
namespace = meta.get("namespace", "default")
|
||||||
ingress_id = f"{namespace}/{name}"
|
ingress_id = f"{namespace}/{name}"
|
||||||
|
|
||||||
if ingress_class:
|
if to_ingress_class:
|
||||||
spec["ingressClassName"] = ingress_class
|
spec["ingressClassName"] = to_ingress_class
|
||||||
|
|
||||||
anns = meta.get("annotations", {}) or {}
|
anns = meta.get("annotations", {}) or {}
|
||||||
|
|
||||||
@@ -123,18 +165,13 @@ def migrate_ingress(data, mapping, ingress_class=None):
|
|||||||
report["converted"].append(k)
|
report["converted"].append(k)
|
||||||
|
|
||||||
elif support == "partial" and haproxy_key:
|
elif support == "partial" and haproxy_key:
|
||||||
partial.append(
|
partial.append({"haproxy": haproxy_key, "value": v, "note": note})
|
||||||
{"haproxy": haproxy_key, "value": v, "note": note}
|
|
||||||
)
|
|
||||||
report["partial"].append(k)
|
report["partial"].append(k)
|
||||||
report["detail"][ingress_id]["partial"].append(
|
report["detail"][ingress_id]["partial"].append(
|
||||||
{"nginx": k, "haproxy": haproxy_key, "note": note}
|
{"nginx": k, "haproxy": haproxy_key, "note": note}
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
unsupported.append(
|
unsupported.append({"key": k, "value": v, "note": note})
|
||||||
{"key": k, "value": v, "note": note}
|
|
||||||
)
|
|
||||||
report["unsupported"].append(k)
|
report["unsupported"].append(k)
|
||||||
report["detail"][ingress_id]["unsupported"].append(
|
report["detail"][ingress_id]["unsupported"].append(
|
||||||
{"nginx": k, "value": v, "note": note}
|
{"nginx": k, "value": v, "note": note}
|
||||||
@@ -182,18 +219,24 @@ def write_report(report, path):
|
|||||||
|
|
||||||
|
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
# 파일 저장 (split)
|
# 파일 저장 (yaml/ns/ingress.yaml)
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
def save_split(data, out_dir):
|
def save_split(data, out_dir="./yaml"):
|
||||||
Path(out_dir).mkdir(parents=True, exist_ok=True)
|
base = Path(out_dir)
|
||||||
|
base.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
for item in data.get("items", []):
|
for item in data.get("items", []):
|
||||||
rendered = item.pop("_rendered_annotations", "")
|
rendered = item.pop("_rendered_annotations", "")
|
||||||
name = item["metadata"]["name"]
|
meta = item["metadata"]
|
||||||
namespace = item["metadata"].get("namespace", "default")
|
name = meta["name"]
|
||||||
path = Path(out_dir) / f"{namespace}__{name}.yaml"
|
namespace = meta.get("namespace", "default")
|
||||||
|
|
||||||
with open(path, "w") as f:
|
ns_dir = base / namespace
|
||||||
|
ns_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
path = ns_dir / f"{name}.yaml"
|
||||||
|
|
||||||
|
with path.open("w") as f:
|
||||||
yaml.dump(item, f)
|
yaml.dump(item, f)
|
||||||
|
|
||||||
if rendered:
|
if rendered:
|
||||||
@@ -206,33 +249,6 @@ def save_split(data, out_dir):
|
|||||||
path.write_text(content)
|
path.write_text(content)
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
# 파일 저장 (single)
|
|
||||||
# -----------------------------
|
|
||||||
def save_single(data, out_path):
|
|
||||||
rendered_map = {}
|
|
||||||
|
|
||||||
for item in data.get("items", []):
|
|
||||||
rendered_map[id(item)] = item.pop("_rendered_annotations", "")
|
|
||||||
|
|
||||||
path = Path(out_path)
|
|
||||||
with path.open("w") as f:
|
|
||||||
yaml.dump(data, f)
|
|
||||||
|
|
||||||
content = path.read_text()
|
|
||||||
|
|
||||||
for item in data.get("items", []):
|
|
||||||
rendered = rendered_map.get(id(item))
|
|
||||||
if rendered:
|
|
||||||
content = content.replace(
|
|
||||||
"metadata:\n",
|
|
||||||
"metadata:\n annotations:\n" + rendered + "\n",
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
|
|
||||||
path.write_text(content)
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
# main
|
# main
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
@@ -241,37 +257,55 @@ def main():
|
|||||||
description="nginx ingress → HAProxy ingress migration utility"
|
description="nginx ingress → HAProxy ingress migration utility"
|
||||||
)
|
)
|
||||||
parser.add_argument("--mapping", default="mapping.yaml")
|
parser.add_argument("--mapping", default="mapping.yaml")
|
||||||
parser.add_argument("--single", action="store_true")
|
|
||||||
parser.add_argument("--split", action="store_true")
|
parser.add_argument("--split", action="store_true")
|
||||||
parser.add_argument("--out", default="output")
|
parser.add_argument("--out", default="./yaml")
|
||||||
parser.add_argument("--ingress-class")
|
parser.add_argument("--ingress-class", help="target ingressClassName")
|
||||||
|
parser.add_argument(
|
||||||
|
"--from-ingress-class",
|
||||||
|
default="nginx",
|
||||||
|
help="source ingressClassName (default: nginx)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--namespace",
|
||||||
|
help="target namespace (default: all namespaces)",
|
||||||
|
)
|
||||||
parser.add_argument("--report", default="migration-report.md")
|
parser.add_argument("--report", default="migration-report.md")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if not args.single and not args.split:
|
if not args.split:
|
||||||
parser.error("one of --single or --split must be specified")
|
parser.error("--split must be specified")
|
||||||
|
|
||||||
|
# 🔹 기본 디렉토리 보장
|
||||||
|
Path("./backup").mkdir(parents=True, exist_ok=True)
|
||||||
|
Path(args.out).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
ingress = kubectl_ingress(args.namespace)
|
||||||
|
|
||||||
|
ingress = filter_by_ingress_class(
|
||||||
|
ingress,
|
||||||
|
args.from_ingress_class,
|
||||||
|
)
|
||||||
|
|
||||||
|
backup_original_ingress(ingress)
|
||||||
|
|
||||||
ingress = kubectl_ingress()
|
|
||||||
mapping = load_mapping(args.mapping)
|
mapping = load_mapping(args.mapping)
|
||||||
|
|
||||||
converted, report = migrate_ingress(
|
converted, report = migrate_ingress(
|
||||||
ingress, mapping, ingress_class=args.ingress_class
|
ingress,
|
||||||
|
mapping,
|
||||||
|
to_ingress_class=args.ingress_class,
|
||||||
)
|
)
|
||||||
|
|
||||||
if args.single:
|
save_split(converted, args.out)
|
||||||
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)
|
write_report(report, args.report)
|
||||||
|
|
||||||
print("\n=== Migration Summary ===")
|
print("\n=== Migration Summary ===")
|
||||||
print(f"Converted : {len(report['converted'])}")
|
print(f"Converted : {len(report['converted'])}")
|
||||||
print(f"Partial : {len(report['partial'])}")
|
print(f"Partial : {len(report['partial'])}")
|
||||||
print(f"Unsupported: {len(report['unsupported'])}")
|
print(f"Unsupported: {len(report['unsupported'])}")
|
||||||
|
print(f"Backup : ./backup/<namespace>/<ingress>.yaml")
|
||||||
|
print(f"Output : {args.out}/<namespace>/<ingress>.yaml")
|
||||||
print(f"Report : {args.report}")
|
print(f"Report : {args.report}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user