diff --git a/flux_local/tool/visitor.py b/flux_local/tool/visitor.py index 12934337..009f1e09 100644 --- a/flux_local/tool/visitor.py +++ b/flux_local/tool/visitor.py @@ -177,6 +177,23 @@ def update_manifest(self, manifest: Manifest) -> None: helm_release.images.sort() +def strip_resource_attributes(resource: dict[str, Any], strip_attributes: list[str]) -> None: + """Strip any annotations from kustomize that contribute to diff noise when objects are re-ordered in the output.""" + strip_attrs(resource["metadata"], strip_attributes) + # Remove common noisy labels in commonly used templates + if ( + (spec := resource.get("spec")) + and (templ := spec.get("template")) + and (meta := templ.get("metadata")) + ): + strip_attrs(meta, strip_attributes) + if resource["kind"] == "List" and (items := resource.get("items")) and isinstance(items, list): + for item in items: + if not (item_meta := item.get("metadata")): + continue + strip_attrs(item_meta, strip_attributes) + + class ObjectOutput(ResourceOutput): """Resource visitor that builds outputs for objects within the kustomization.""" @@ -208,14 +225,8 @@ async def call_async( ) continue # Remove common noisy labels - strip_attrs(metadata, self.strip_attributes) - # Remove common noisy labels in commonly used templates - if ( - (spec := resource.get("spec")) - and (templ := spec.get("template")) - and (meta := templ.get("metadata")) - ): - strip_attrs(meta, self.strip_attributes) + strip_resource_attributes(resource, self.strip_attributes) + resource_key = ResourceKey( kind=kind, kustomization_path=str(kustomization_path), diff --git a/tests/tool/test_visitor.py b/tests/tool/test_visitor.py new file mode 100644 index 00000000..81cbc22a --- /dev/null +++ b/tests/tool/test_visitor.py @@ -0,0 +1,250 @@ +"""Tests for the visitor module.""" + +from typing import Any +import yaml + +import pytest + + +from flux_local.tool.visitor import strip_resource_attributes + +STRIP_ATTRIBUTES = [ + "app.kubernetes.io/version", + "chart", +] + + +@pytest.mark.parametrize( + ("metadata", "expected_metadata"), + [ + ( + { + "labels": { + "app.kubernetes.io/version": "1.0.0", + "app.kubernetes.io/managed-by": "Helm", + } + }, + { + "labels": { + "app.kubernetes.io/managed-by": "Helm", + }, + }, + ), + ( + { + "annotations": { + "app.kubernetes.io/version": "1.0.0", + "app.kubernetes.io/managed-by": "Helm", + } + }, + { + "annotations": { + "app.kubernetes.io/managed-by": "Helm", + }, + }, + ), + ( + {}, + {}, + ), + ], +) +def test_strip_resource_attributes( + metadata: dict[str, Any], expected_metadata: dict[str, Any] +) -> None: + """Test the strip_resource_attributes function.""" + resource = { + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "my-configmap", + "namespace": "default", + **metadata, + }, + "data": { + "key1": "value1", + "key2": "value2", + }, + } + strip_resource_attributes(resource, STRIP_ATTRIBUTES) + assert resource == { + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "my-configmap", + "namespace": "default", + **expected_metadata, + }, + "data": { + "key1": "value1", + "key2": "value2", + }, + } + + +def test_strip_deployment_metadata() -> None: + """Test the strip_resource_attributes function.""" + resource = yaml.load( + """apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + app.kubernetes.io/version: 1.0.0 + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 +""", + Loader=yaml.Loader, + ) + + strip_resource_attributes(resource, STRIP_ATTRIBUTES) + assert ( + yaml.dump(resource, sort_keys=False) + == """apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 +""" + ) + + +def test_strip_list_metadata() -> None: + """Test the stripping metadata from a list resource.""" + resource = yaml.load( + """apiVersion: v1 +items: +- apiVersion: stable.example.com/v1 + kind: CronTab + metadata: + annotations: + app: my-cron-tab + app.kubernetes.io/version: 1.0.0 + creationTimestamp: '2021-06-20T07:35:27Z' + generation: 1 + name: my-new-cron-object + namespace: default + resourceVersion: '1326' + uid: 9aab1d66-628e-41bb-a422-57b8b3b1f5a9 + spec: + cronSpec: '* * * * */5' + image: my-awesome-cron-image +kind: List +metadata: + resourceVersion: '' + selfLink: '' + +""", + Loader=yaml.Loader, + ) + + strip_resource_attributes(resource, STRIP_ATTRIBUTES) + assert ( + yaml.dump(resource, sort_keys=False) + == """apiVersion: v1 +items: +- apiVersion: stable.example.com/v1 + kind: CronTab + metadata: + annotations: + app: my-cron-tab + creationTimestamp: '2021-06-20T07:35:27Z' + generation: 1 + name: my-new-cron-object + namespace: default + resourceVersion: '1326' + uid: 9aab1d66-628e-41bb-a422-57b8b3b1f5a9 + spec: + cronSpec: '* * * * */5' + image: my-awesome-cron-image +kind: List +metadata: + resourceVersion: '' + selfLink: '' +""" + ) + + +def test_strip_list_null_items() -> None: + """Test corner cases of handling metadata.""" + resource = yaml.load( + """apiVersion: v1 +kind: List +metadata: + resourceVersion: '' + selfLink: '' +items: + +""", + Loader=yaml.Loader, + ) + + strip_resource_attributes(resource, STRIP_ATTRIBUTES) + assert ( + yaml.dump(resource, sort_keys=False) + == """apiVersion: v1 +kind: List +metadata: + resourceVersion: '' + selfLink: '' +items: null +""" + ) + + +def test_strip_list_item_without_metdata() -> None: + """Test corner cases of handling metadata.""" + resource = yaml.load( + """apiVersion: v1 +kind: List +metadata: + resourceVersion: '' + selfLink: '' +items: +- kind: CronTab +""", + Loader=yaml.Loader, + ) + + strip_resource_attributes(resource, STRIP_ATTRIBUTES) + assert ( + yaml.dump(resource, sort_keys=False) + == """apiVersion: v1 +kind: List +metadata: + resourceVersion: '' + selfLink: '' +items: +- kind: CronTab +""" + )