혹시 GitHub에서 Folder 구조가 바뀌거나 diff_yaml.py 도구가 제거될 것을 우려해서 여기 blog에 흔적을 남긴다.
diff_yaml.py (source code 열람)
#!/usr/bin/env python
#
# Copyright 2018 Istio Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Compare 2 multi document kubernetes yaml files
# It ensures that order does not matter
#
from __future__ import print_function
import argparse
import datadiff
import sys
import yaml # pyyaml
# returns fully qualified resource name of the k8s resource
def by_resource_name(res):
if res is None:
return ""
return "{}::{}::{}".format(res['apiVersion'],
res['kind'],
res['metadata']['name'])
def keydiff(k0, k1):
k0s = set(k0)
k1s = set(k1)
added = k1s - k0s
removed = k0s - k1s
common = k0s.intersection(k1s)
return added, removed, common
def drop_keys(res, k1, k2):
if k2 in res[k1]:
del res[k1][k2]
def normalize_configmap(res):
try:
if res['kind'] != "ConfigMap":
return res
data = res['data']
# some times keys are yamls...
# so parse them
for k in data:
try:
op = yaml.safe_load_all(data[k])
data[k] = list(op)
except yaml.YAMLError as ex:
print(ex)
return res
except KeyError as ke:
if 'kind' in str(ke) or 'data' in str(ke):
return res
raise
def normalize_ports(res):
try:
spec = res["spec"]
if spec is None:
return res
ports = sorted(spec['ports'], key=lambda x: x["port"])
spec['ports'] = ports
return res
except KeyError as ke:
if 'spec' in str(ke) or 'ports' in str(ke) or 'port' in str(ke):
return res
raise
def normalize_res(res, args):
if not res:
return res
if args.ignore_labels:
drop_keys(res, "metadata", "labels")
if args.ignore_namespace:
drop_keys(res, "metadata", "namespace")
res = normalize_ports(res)
res = normalize_configmap(res)
return res
def normalize(rl, args):
for i in range(len(rl)):
rl[i] = normalize_res(rl[i], args)
return rl
def compare(args):
j0 = normalize(list(yaml.safe_load_all(open(args.orig))), args)
j1 = normalize(list(yaml.safe_load_all(open(args.new))), args)
q0 = {by_resource_name(res): res for res in j0 if res is not None}
q1 = {by_resource_name(res): res for res in j1 if res is not None}
added, removed, common = keydiff(q0.keys(), q1.keys())
changed = 0
for k in sorted(common):
if q0[k] != q1[k]:
changed += 1
print("## +++ ", args.new)
print("## --- ", args.orig)
print("## Added:", len(added))
print("## Removed:", len(removed))
print("## Updated:", changed)
print("## Unchanged:", len(common) - changed)
for k in sorted(added):
print("+", k)
for k in sorted(removed):
print("-", k)
print("##", "*" * 25)
for k in sorted(common):
if q0[k] != q1[k]:
print("## ", k)
s0 = yaml.safe_dump(q0[k], default_flow_style=False, indent=2)
s1 = yaml.safe_dump(q1[k], default_flow_style=False, indent=2)
print(datadiff.diff(s0, s1, fromfile=args.orig, tofile=args.new))
return changed + len(added) + len(removed)
def main(args):
return compare(args)
def get_parser():
parser = argparse.ArgumentParser(
description="Compare kubernetes yaml files")
parser.add_argument("orig")
parser.add_argument("new")
parser.add_argument("--ignore-namespace", action="store_true", default=False,
help="Ignore namespace during comparison")
parser.add_argument("--ignore-labels", action="store_true", default=False,
help="Ignore resource labels during comparison")
parser.add_argument("--ignore-annotations", action="store_true", default=False,
help="Ignore annotations during comparison")
return parser
if __name__ == "__main__":
parser = get_parser()
args = parser.parse_args()
sys.exit(main(args))
diff_yaml.py 명령 따라하기
위 python code를 diff_yaml.py 파일명으로 저장하고, 아래와 같이 필요한 Python package를 설치하고, Run 한다.
$ pip install --upgrade pip
$ python -m pip install datadiff
$ ./diff_yaml.py /tmp/metallb-1.yaml /tmp/metallb-2.yaml
## +++ /tmp/metallb-2.yaml
## --- /tmp/metallb-1.yaml
## Added: 1
## Removed: 1
## Updated: 0
## Unchanged: 14
+ apps/v1::Deployment_Test::controller ## 변경된 내용을 보여준다.
- apps/v1::Deployment::controller ## 변경된 내용을 보여준다.
## *************************
$
## 이것은 Linux diff 명령으로 확인한 내용이다.
## 실제로 yaml 내용은 변경된 것이 1개 이지만, 단지 행을 이동했다는 이유만으로
## 여러 곳이 다르다고 표기하고 있다. (즉, YAML을 사용하는 User 관점에서 보면 다 쓸데 없는 정보이다.)
$ diff /tmp/metallb-1.yaml /tmp/metallb-2.yaml
3d2
< kind: Namespace ## <-- 실제로 이 부분은 내용이 바뀐 것이 아니라, 라인만 이동한 것이다.
5d3
< name: metallb-system ## <-- 실제로 이 부분은 내용이 바뀐 것이 아니라, 라인만 이동한 것이다.
8a7,8
> name: metallb-system ## <-- 실제로 이 부분은 내용이 바뀐 것이 아니라, 라인만 이동한 것이다.
> kind: Namespace ## <-- 실제로 이 부분은 내용이 바뀐 것이 아니라, 라인만 이동한 것이다.
352d351
< kind: Deployment
400a400
> kind: Deployment_Test ## <-- 이 부분만 내용이 변경된 것이다.
위 결과를 보면, 일반적인 diff와 내용이 다르다는 것을 알 수 있다.
가장 큰 차이점은 diff_yaml.py는 수정된 Line과 Column을 출력하지 않는다. 왜냐하면, 내용 자체의 변경 여부가 관심사이지 어떤 내용이 다른 행으로 이동했는지 또는 삭제, 추가되었는지 중요하지 않기 때문이다.