Why compare YAML as data
YAML is read by indentation, so what a line diff shows and what the file means drift apart quickly. Reorder two keys, rewrite [80, 443] as one item per line, or reword a comment, and a line diff reports changes no program reading the file would notice. Move one line two spaces to the left, and it shows a whitespace edit where the file now says something different.
This page parses both files and compares the values they hold. It opens on two versions of a Kubernetes Deployment in which the keys were reordered, a comment was added and the two containers swapped places. It reports the swap as a move and lists the four edits that change what gets deployed, one of them a port that became a string. Replace either side with your own files: two Spring profiles, two Helm values files, or a CI workflow before and after a change.
What it reports
Containers matched by name
containers:
- name: web
image: web:1.4
- name: sidecar
image: envoy:1.29containers:
- name: sidecar
image: envoy:1.29
- name: web
image: web:1.5One edit, the web image from web:1.4 to web:1.5, and one move. The list items are matched by name, so the swap is not reported as two containers removed and two added.
Comments and list style set aside
# ports the service listens on ports: [80, 443] debug: false
debug: false # public ports ports: - 80 - 443
No changes. The comment was reworded, the keys swapped order and the list went from one line to one item per line, but every value is the same.
An indentation slip
spring:
datasource:
url: jdbc:postgresql://db:5432/app
username: appspring:
datasource:
url: jdbc:postgresql://db:5432/app
username: appTwo changes: /spring/datasource/username removed and /spring/username added. A line diff shows two missing spaces; this shows that the setting left the datasource block, where the application looks for it.
Options
- Match lists
- Automatic looks for a field that is unique on every item of a list. In Kubernetes files that is usually
name, which containers, environment variables and ports all carry. You can name another field, or match by position when the order of the items is the point, as it is for the steps of a CI job. - Ignore a path
- Compare a manifest with what
kubectl get -o yamlreturns and the fields the cluster adds get in the way. Ignore/status,/metadata/managedFieldsor/metadata/resourceVersionfrom any change row, and everything under that path stops counting. - Noise
- A
uidor acreationTimestampthat differs between two exports is labelled noise and counted apart, and so are changed hashes, IP addresses and request IDs. - Compare this block
- Select a subtree, such as
/spec/templateor/spring/datasource, and compare only that part of the two files. - Copy as JSON Patch
- The difference as RFC 6902 operations, with paths into the YAML's structure.
kubectl patch --type=jsonaccepts this format.
In a terminal
diff -u <(yq -P 'sort_keys(..)' old.yaml) <(yq -P 'sort_keys(..)' new.yaml)
Sorts the keys of every map, so reordered keys stop counting. On this page's sample the added comment still shows, and the two containers that swapped places show as one block removed and the same block added.
q='(.. | select(tag == "!!seq")) |= sort_by(.name) | sort_keys(..) | ... comments=""' diff -u <(yq -P "$q" old.yaml) <(yq -P "$q" new.yaml)
Also sorts every list by its
namefield and drops comments. On this page's sample that leaves exactly the four real edits:replicas, the image tag, the timeout and the port.
- What the page does that these do not
- Sorting lists by
namehides the swap instead of reporting it as a move, would also reorder a list whose order matters, and yq prints"8080"in quotes without saying that the port became a string. - What the terminal does better
- It runs unattended in a CI step, where
diffexiting with 1 can fail the build when a manifest drifts, and it handles large files: the first command took about 16 seconds on two 8 MB manifests.
These are the Go yq (github.com/mikefarah/yq, version 4). The Python package also called yq takes other arguments: there, yq -S -y . old.yaml sorts the keys and drops comments.
Questions
Why is version: 1.10 the same as version: 1.1?
Unquoted, both are numbers, and 1.10 and 1.1 are the same number, so the data has not changed. If the value is a version, quote it ("1.10"): it is then a string, and changing it to "1.1" is reported. The application reading the file sees the unquoted value the same way this page does.
Why is a reworded comment not reported?
Comments are not part of the data a YAML parser returns, so rewording one is not a change here. To review comment edits as well, press Back to line by line and the same two files are compared as text.
Why does enabled: yes to enabled: true show a type change?
This page reads YAML 1.2, where only true and false are booleans and yes, no, on and off are plain strings. Parsers that follow the older YAML 1.1, including PyYAML and SnakeYAML (which Spring Boot uses), read yes as true, so for them the two lines mean the same. The type change is the page telling you the two versions disagree on that point.
Can it compare a file with several documents?
Not as data yet. A file that holds more than one document separated by ---, as rendered Helm charts and multi-resource manifests often do, is compared line by line, and the page says so. Split the documents and compare them a pair at a time to get the path view.
Are anchors and aliases followed?
Yes. An alias such as *defaults is compared as the value it points to, so changing an anchored value shows up at every place that uses it. A merge key (<<) is compared as an ordinary key rather than merged into its map.