Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-flux-local for 
openSUSE:Factory checked in at 2025-04-25 22:19:57
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-flux-local (Old)
 and      /work/SRC/openSUSE:Factory/.python-flux-local.new.30101 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-flux-local"

Fri Apr 25 22:19:57 2025 rev:4 rq:1272560 version:7.4.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-flux-local/python-flux-local.changes      
2025-01-09 15:11:56.579513167 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-flux-local.new.30101/python-flux-local.changes
   2025-04-25 22:21:17.544536919 +0200
@@ -1,0 +2,74 @@
+Fri Apr 25 06:02:17 UTC 2025 - Johannes Kastl 
<opensuse_buildserv...@ojkastl.de>
+
+- update to 7.4.0:
+  * What's Changed
+    - Update install instructions by @allenporter in #858
+    - Add support for targetNamespace by @weisdd in #866
+
+-------------------------------------------------------------------
+Fri Mar 21 16:49:49 UTC 2025 - Johannes Kastl 
<opensuse_buildserv...@ojkastl.de>
+
+- update to 7.3.0:
+  * What's Changed
+    - Add support for HelmRelease disableSchemaValidation and
+      disableOpenAPIValidation by @allenporter in #856
+    - Remove unnecessary slugify dependency by @filipposc5 in #849
+  * Developer updates
+    - Update tag to avoid drifting repo by @allenporter in #852
+    - chore(deps): update dependency ruff to v0.10.0 by @renovate
+      in #850
+    - chore(deps): update pre-commit hook
+      charliermarsh/ruff-pre-commit to v0.10.0 by @renovate in #851
+    - chore(deps): update dependency yamllint to v1.36.0 by
+      @renovate in #846
+    - chore(deps): update pre-commit hook adrienverge/yamllint to
+      v1.36.0 by @renovate in #847
+    - chore(deps): update docker.io/bitnami/kubectl docker tag to
+      v1.32.3 by @renovate in #848
+
+-------------------------------------------------------------------
+Thu Mar  6 17:22:44 UTC 2025 - Johannes Kastl 
<opensuse_buildserv...@ojkastl.de>
+
+- update to 7.2.1:
+  * What's Changed
+    - Make OCI chartRef work with optional namespace by @Alexsaphir
+      in #844
+
+-------------------------------------------------------------------
+Wed Mar  5 10:49:44 UTC 2025 - Johannes Kastl 
<opensuse_buildserv...@ojkastl.de>
+
+- update to 7.2.0:
+  * What's Changed
+    - Fix OCIRepository support in get cluster by @allenporter in
+      #843
+    - Detect more images from commonly used crds by @buroa in #833
+
+-------------------------------------------------------------------
+Wed Mar  5 10:46:35 UTC 2025 - Johannes Kastl 
<opensuse_buildserv...@ojkastl.de>
+
+- update to 7.1.0:
+  * What's Changed
+    - fix: remove kubectl from Dockefile by @onedr0p in #822
+    - Add get cluster --only-images flag to limit output by
+      @allenporter in #835
+    - Add --output=json for most commands by @allenporter in #836
+    - Add label selector in internal code that walks the repo and
+      matches Kustomization and HelmReleases by @allenporter in
+      #837
+    - Add --label-selector command line flags by @allenporter in
+      #839
+    - Add a flag --skip-kinds to omit kind from the output by
+      @allenporter in #840
+    - Pass OCIRepository chart ref tag to helm --version by
+      @allenporter in #841
+  * Developer updates
+    - chore(deps): update dependency pip to v25 by @renovate in
+      #825
+    - feat(actions): bump actions/setup-python to v5 by @layertwo
+      in #827
+    - chore(deps): update pre-commit hook psf/black to v25 by
+      @renovate in #829
+    - chore(deps): update dependency black to v25 by @renovate in
+      #828
+
+-------------------------------------------------------------------

Old:
----
  flux_local-7.0.0.tar.gz

New:
----
  flux_local-7.4.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-flux-local.spec ++++++
--- /var/tmp/diff_new_pack.ylD1PJ/_old  2025-04-25 22:21:18.092559950 +0200
+++ /var/tmp/diff_new_pack.ylD1PJ/_new  2025-04-25 22:21:18.096560118 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-flux-local
 #
-# Copyright (c) 2024 SUSE LLC
+# Copyright (c) 2025 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -18,37 +18,35 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-flux-local
-Version:        7.0.0
+Version:        7.4.0
 Release:        0
 Summary:        Set of tools for managing a flux gitops repository
 License:        Apache-2.0
 URL:            https://github.com/allenporter/flux-local
 Source:         
https://files.pythonhosted.org/packages/source/f/flux-local/flux_local-%{version}.tar.gz
-BuildRequires:  python-rpm-macros
 BuildRequires:  %{python_module pip}
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  %{python_module wheel}
+BuildRequires:  python-rpm-macros
 #
 BuildRequires:  %{python_module aiofiles >= 24.1.0}
-BuildRequires:  %{python_module GitPython >= 3.1.43}
+BuildRequires:  %{python_module GitPython >= 3.1.44}
+BuildRequires:  %{python_module PyYAML >= 6.0.2}
 BuildRequires:  %{python_module mashumaro >= 3.15}
 BuildRequires:  %{python_module nest-asyncio >= 1.6.0}
-BuildRequires:  %{python_module PyYAML >= 6.0.2}
-BuildRequires:  %{python_module python-slugify >= 8.0.4}
 # SECTION test requirements
-BuildRequires:  %{python_module pytest >= 8.3.3}
-BuildRequires:  %{python_module pytest-asyncio >= 0.25.0}
+BuildRequires:  %{python_module pytest >= 8.3.5}
+BuildRequires:  %{python_module pytest-asyncio >= 0.25.3}
 # /SECTION
 BuildRequires:  fdupes
+Requires:       python-GitPython >= 3.1.44
+Requires:       python-PyYAML >= 6.0.2
 Requires:       python-aiofiles >= 24.1.0
-Requires:       python-GitPython >= 3.1.43
 Requires:       python-mashumaro >= 3.15
 Requires:       python-nest-asyncio >= 1.6.0
-Requires:       python-python-slugify >= 8.0.4
-Requires:       python-PyYAML >= 6.0.2
 # Note: flux-local provides repo testing using pytest
-Requires:       python-pytest >= 8.3.3
-Requires:       python-pytest-asyncio >= 0.25.0
+Requires:       python-pytest >= 8.3.5
+Requires:       python-pytest-asyncio >= 0.25.3
 Requires(post): update-alternatives
 Requires(postun): update-alternatives
 BuildArch:      noarch

++++++ flux_local-7.0.0.tar.gz -> flux_local-7.4.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/PKG-INFO 
new/flux_local-7.4.0/PKG-INFO
--- old/flux_local-7.0.0/PKG-INFO       2024-12-31 20:47:21.301954000 +0100
+++ new/flux_local-7.4.0/PKG-INFO       2025-04-24 06:04:31.768139100 +0200
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
 Name: flux-local
-Version: 7.0.0
+Version: 7.4.0
 Summary: flux-local is a python library and set of tools for managing a flux 
gitops repository, with validation steps to help improve quality of commits, 
PRs, and general local testing.
 Home-page: https://github.com/allenporter/flux-local
 Author: Allen Porter
@@ -12,12 +12,12 @@
 License-File: LICENSE
 Requires-Dist: aiofiles>=22.1.0
 Requires-Dist: nest_asyncio>=1.5.6
-Requires-Dist: python-slugify>=8.0.0
 Requires-Dist: GitPython>=3.1.30
 Requires-Dist: PyYAML>=6.0
 Requires-Dist: mashumaro>=3.12
 Requires-Dist: pytest>=7.2.1
 Requires-Dist: pytest-asyncio>=0.20.3
+Dynamic: license-file
 
 flux-local is a set of tools and libraries for managing a local flux gitops 
repository focused on validation steps to help improve quality of commits, PRs, 
and general local testing.
 
@@ -36,10 +36,13 @@
 
 ## flux-local CLI
 
-The CLI is written in python and packaged as part of the `flux-local` python 
library, which can be installed using pip:
+The CLI is written in python and packaged as part of the `flux-local` python 
library, which can be installed using pip and uv. If you have not yet embraced 
python virtual environments, now might be
+the right time to do so to avoid clobbering the packages in your system.
 
 ```bash
-$ pip3 install flux-local
+$ uv venv
+$ source .venv/bin/activate
+$ uv pip install flux-local
 ```
 
 ### flux-local get
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/README.md 
new/flux_local-7.4.0/README.md
--- old/flux_local-7.0.0/README.md      2024-12-31 20:47:07.000000000 +0100
+++ new/flux_local-7.4.0/README.md      2025-04-24 06:04:26.000000000 +0200
@@ -15,10 +15,13 @@
 
 ## flux-local CLI
 
-The CLI is written in python and packaged as part of the `flux-local` python 
library, which can be installed using pip:
+The CLI is written in python and packaged as part of the `flux-local` python 
library, which can be installed using pip and uv. If you have not yet embraced 
python virtual environments, now might be
+the right time to do so to avoid clobbering the packages in your system.
 
 ```bash
-$ pip3 install flux-local
+$ uv venv
+$ source .venv/bin/activate
+$ uv pip install flux-local
 ```
 
 ### flux-local get
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/flux_local/git_repo.py 
new/flux_local-7.4.0/flux_local/git_repo.py
--- old/flux_local-7.0.0/flux_local/git_repo.py 2024-12-31 20:47:07.000000000 
+0100
+++ new/flux_local-7.4.0/flux_local/git_repo.py 2025-04-24 06:04:26.000000000 
+0200
@@ -203,10 +203,7 @@
     func: Callable[
         [
             Path,
-            Kustomization
-            | HelmRelease
-            | HelmRepository
-            | OCIRepository,
+            Kustomization | HelmRelease | HelmRepository | OCIRepository,
             kustomize.Kustomize | None,
         ],
         Awaitable[None],
@@ -255,12 +252,18 @@
     namespace: str | None = None
     """Resources returned will be from this namespace."""
 
+    label_selector: dict[str, str] | None = None
+    """Resources returned must have these labels."""
+
     skip_crds: bool = True
     """If false, CRDs may be processed, depending on the resource type."""
 
     skip_secrets: bool = True
     """If false, Secrets may be processed, depending on the resource type."""
 
+    skip_kinds: list[str] | None = None
+    """A list of potential CRDs to skip when emitting objects."""
+
     visitor: ResourceVisitor | None = None
     """Visitor for the specified object type that can be used for building."""
 
@@ -274,12 +277,7 @@
         """A predicate that selects Kustomization objects."""
 
         def predicate(
-            obj: (
-                Kustomization
-                | HelmRelease
-                | HelmRepository
-                | OCIRepository
-            ),
+            obj: Kustomization | HelmRelease | HelmRepository | OCIRepository,
         ) -> bool:
             if not self.enabled:
                 return False
@@ -287,6 +285,15 @@
                 return False
             if self.namespace and obj.namespace != self.namespace:
                 return False
+            if self.label_selector and isinstance(obj, (Kustomization, 
HelmRelease)):
+                obj_labels = obj.labels or {}
+                for name, value in self.label_selector.items():
+                    _LOGGER.debug("Checking %s=%s", name, value)
+                    if (
+                        obj_value := obj_labels.get(name)
+                    ) is None or obj_value != value:
+                        _LOGGER.debug("mismatch v=%s", obj_value)
+                        return False
             return True
 
         return predicate
@@ -596,6 +603,8 @@
             skips.append(CRD_KIND)
         if kustomization_selector.skip_secrets:
             skips.append(SECRET_KIND)
+        if kustomization_selector.skip_kinds:
+            skips.extend(kustomization_selector.skip_kinds)
         cmd = cmd.skip_resources(skips)
         try:
             cmd = await cmd.stash()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/flux_local/helm.py 
new/flux_local-7.4.0/flux_local/helm.py
--- old/flux_local-7.0.0/flux_local/helm.py     2024-12-31 20:47:07.000000000 
+0100
+++ new/flux_local-7.4.0/flux_local/helm.py     2025-04-24 06:04:26.000000000 
+0200
@@ -137,6 +137,9 @@
     skip_secrets: bool = False
     """Don't emit secrets in the output."""
 
+    skip_kinds: list[str] | None = None
+    """Omit these kinds in the output."""
+
     kube_version: str | None = None
     """Value of the helm --kube-version flag."""
 
@@ -173,6 +176,8 @@
             skips.append(CRD_KIND)
         if self.skip_secrets:
             skips.append(SECRET_KIND)
+        if self.skip_kinds:
+            skips.extend(self.skip_kinds)
         return skips
 
 
@@ -257,10 +262,14 @@
             release.name,
             _chart_name(release, repo),
             "--namespace",
-            release.namespace,
+            release.release_namespace,
         ]
         args.extend(self._flags)
         args.extend(options.template_args)
+        if release.disable_openapi_validation:
+            args.append("--disable-openapi-validation")
+        if release.disable_schema_validation:
+            args.append("--skip-schema-validation")
         if release.chart.version:
             args.extend(
                 [
@@ -268,6 +277,13 @@
                     release.chart.version,
                 ]
             )
+        elif isinstance(repo, OCIRepository) and repo.ref_tag:
+            args.extend(
+                [
+                    "--version",
+                    repo.ref_tag,
+                ]
+            )
         if release.values:
             values_path = self._tmp_dir / f"{release.release_name}-values.yaml"
             async with aiofiles.open(values_path, mode="w") as values_file:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/flux_local/image.py 
new/flux_local-7.4.0/flux_local/image.py
--- old/flux_local-7.0.0/flux_local/image.py    2024-12-31 20:47:07.000000000 
+0100
+++ new/flux_local-7.4.0/flux_local/image.py    2025-04-24 06:04:26.000000000 
+0200
@@ -18,22 +18,38 @@
     "CronJob",
     "Job",
     "ReplicationController",
+    "EMQX",                  # apps.emqx.io/v2beta1
+    "Cluster",               # postgresql.cnpg.io/v1
+    "CephCluster",           # ceph.rook.io/v1
+    "Alertmanager",          # monitoring.coreos.com/v1
+    "Prometheus",            # monitoring.coreos.com/v1
+    "AutoscalingRunnerSet",  # actions.github.com/v1alpha1
 ]
+
+# Default image key for most object types.
 IMAGE_KEY = "image"
 
+# Override the default image key for some object types.
+KINDS_IMAGE_KEY = {
+    "Cluster": "imageName"
+}
+
 
-def _extract_images(doc: dict[str, Any]) -> set[str]:
+def _extract_images(kind: str, doc: dict[str, Any]) -> set[str]:
     """Extract the image from a Kubernetes object."""
     images: set[str] = set({})
+    image_key = KINDS_IMAGE_KEY.get(kind) or IMAGE_KEY
+
     for key, value in doc.items():
-        if key == IMAGE_KEY:
+        if key == image_key:
             images.add(value)
         elif isinstance(value, dict):
-            images.update(_extract_images(value))
+            images.update(_extract_images(kind, value))
         elif isinstance(value, list):
             for item in value:
                 if isinstance(item, dict):
-                    images.update(_extract_images(item))
+                    images.update(_extract_images(kind, item))
+
     return images
 
 
@@ -56,7 +72,8 @@
 
             Updates the image set with the images found in the document.
             """
-            images = _extract_images(doc)
+            kind: str = doc["kind"]
+            images = _extract_images(kind, doc)
             if not images:
                 return
             if name in self.images:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/flux_local/manifest.py 
new/flux_local-7.4.0/flux_local/manifest.py
--- old/flux_local-7.0.0/flux_local/manifest.py 2024-12-31 20:47:07.000000000 
+0100
+++ new/flux_local-7.4.0/flux_local/manifest.py 2025-04-24 06:04:26.000000000 
+0200
@@ -140,15 +140,12 @@
                 raise InputException(f"Invalid {cls} missing 
spec.chartRef.kind: {doc}")
             if not (name := chart_ref.get("name")):
                 raise InputException(f"Invalid {cls} missing 
spec.chartRef.name: {doc}")
-            if not (namespace := chart_ref.get("namespace")):
-                raise InputException(
-                    f"Invalid {cls} missing spec.chartRef.namespace: {doc}"
-                )
+
             return cls(
                 name=name,
                 version=None,
                 repo_name=name,
-                repo_namespace=namespace,
+                repo_namespace=chart_ref.get("namespace", default_namespace),
                 repo_kind=kind,
             )
         if not (chart_spec := chart.get("spec")):
@@ -218,6 +215,9 @@
     chart: HelmChart
     """A mapping to a specific helm chart for this HelmRelease."""
 
+    target_namespace: str | None = field(metadata={"serialize": "omit"}, 
default=None)
+    """The namespace to target when performing the operation."""
+
     values: Optional[dict[str, Any]] = field(
         metadata={"serialize": "omit"}, default=None
     )
@@ -231,6 +231,19 @@
     images: list[str] | None = field(default=None)
     """The list of images referenced in the HelmRelease."""
 
+    labels: dict[str, str] | None = field(metadata={"serialize": "omit"}, 
default=None)
+    """A list of labels on the HelmRelease."""
+
+    disable_schema_validation: bool = field(
+        metadata={"serialize": "omit"}, default=False
+    )
+    """Prevents Helm from validating the values against the JSON Schema."""
+
+    disable_openapi_validation: bool = field(
+        metadata={"serialize": "omit"}, default=False
+    )
+    """Prevents Helm from validating the values against the Kubernetes OpenAPI 
Schema."""
+
     @classmethod
     def parse_doc(cls, doc: dict[str, Any]) -> "HelmRelease":
         """Parse a HelmRelease from a kubernetes resource object."""
@@ -248,12 +261,26 @@
             values_from = [
                 ValuesReference.from_dict(subdoc) for subdoc in 
values_from_dict
             ]
+        disable_schema_validation = any(
+            bag.get("disableSchemaValidation")
+            for key in ("install", "upgrade")
+            if (bag := spec.get(key)) is not None
+        )
+        disable_openapi_validation = any(
+            bag.get("disableOpenAPIValidation")
+            for key in ("install", "upgrade")
+            if (bag := spec.get(key)) is not None
+        )
         return HelmRelease(
             name=name,
             namespace=namespace,
+            target_namespace=spec.get("targetNamespace"),
             chart=chart,
             values=spec.get("values"),
             values_from=values_from,
+            labels=metadata.get("labels"),
+            disable_schema_validation=disable_schema_validation,
+            disable_openapi_validation=disable_openapi_validation,
         )
 
     @property
@@ -262,6 +289,13 @@
         return f"{self.namespace}-{self.name}"
 
     @property
+    def release_namespace(self) -> str:
+        """Actual namespace where the HelmRelease will be installed to."""
+        if self.target_namespace:
+            return self.target_namespace
+        return self.namespace
+
+    @property
     def repo_name(self) -> str:
         """Identifier for the HelmRepository identified in the HelmChart."""
         return f"{self.chart.repo_namespace}-{self.chart.repo_name}"
@@ -356,6 +390,9 @@
     url: str
     """The URL to the repository."""
 
+    ref_tag: str | None = None
+    """The version tag of the repository."""
+
     @classmethod
     def parse_doc(cls, doc: dict[str, Any]) -> "OCIRepository":
         """Parse a HelmRepository from a kubernetes resource."""
@@ -370,10 +407,12 @@
             raise InputException(f"Invalid {cls} missing spec: {doc}")
         if not (url := spec.get("url")):
             raise InputException(f"Invalid {cls} missing spec.url: {doc}")
+        ref_tag = spec.get("ref", {}).get("tag")
         return cls(
             name=name,
             namespace=namespace,
             url=url,
+            ref_tag=ref_tag,
         )
 
     @property
@@ -542,6 +581,9 @@
     depends_on: list[str] | None = field(metadata={"serialize": "omit"}, 
default=None)
     """A list of namespaced names that this Kustomization depends on."""
 
+    labels: dict[str, str] | None = field(metadata={"serialize": "omit"}, 
default=None)
+    """A list of labels on the Kustomization."""
+
     @classmethod
     def parse_doc(cls, doc: dict[str, Any]) -> "Kustomization":
         """Parse a partial Kustomization from a kubernetes resource."""
@@ -582,6 +624,7 @@
             postbuild_substitute=postbuild.get("substitute"),
             postbuild_substitute_from=substitute_from,
             depends_on=depends_on,
+            labels=metadata.get("labels"),
         )
 
     @property
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/flux_local/resource_diff.py 
new/flux_local-7.4.0/flux_local/resource_diff.py
--- old/flux_local-7.0.0/flux_local/resource_diff.py    2024-12-31 
20:47:07.000000000 +0100
+++ new/flux_local-7.4.0/flux_local/resource_diff.py    2025-04-24 
06:04:26.000000000 +0200
@@ -3,7 +3,7 @@
 This is used internally, primarily by the diff tool.
 """
 
-from collections.abc import Iterable
+from collections.abc import Iterable, Callable
 from dataclasses import asdict
 import difflib
 import logging
@@ -11,6 +11,7 @@
 import tempfile
 from typing import Generator, Any, AsyncGenerator, TypeVar
 import yaml
+import json
 
 
 from . import command
@@ -118,7 +119,40 @@
 ) -> Generator[str, None, None]:
     """Generate diffs between the two output objects."""
 
-    diffs = []
+    def diff_func(diffs: list[dict[str, Any]]) -> str:
+        return yaml.dump(
+            diffs, sort_keys=False, explicit_start=True, default_style=None
+        )
+
+    for result in _perform_function_diff(a, b, n, limit_bytes, diff_func):
+        yield result
+
+
+def perform_json_diff(
+    a: ObjectOutput,
+    b: ObjectOutput,
+    n: int,
+    limit_bytes: int,
+) -> Generator[str, None, None]:
+    """Generate diffs between the two output objects."""
+
+    def diff_func(diffs: list[dict[str, Any]]) -> str:
+        return json.dumps(diffs, sort_keys=False, indent=4)
+
+    for result in _perform_function_diff(a, b, n, limit_bytes, diff_func):
+        yield result
+
+
+def _perform_function_diff(
+    a: ObjectOutput,
+    b: ObjectOutput,
+    n: int,
+    limit_bytes: int,
+    diff_func: Callable[[list[dict[str, Any]]], str],
+) -> Generator[str, None, None]:
+    """Generate diffs between the two output objects."""
+
+    diffs: list[dict[str, Any]] = []
     for kustomization_key in _unique_keys(a.content, b.content):
         _LOGGER.debug("Diffing results for %s (n=%d)", kustomization_key, n)
         a_resources = a.content.get(kustomization_key, {})
@@ -150,7 +184,7 @@
                 }
             )
     if diffs:
-        yield yaml.dump(diffs, sort_keys=False, explicit_start=True, 
default_style=None)
+        yield diff_func(diffs)
 
 
 def merge_helm_releases(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/flux_local/tool/build.py 
new/flux_local-7.4.0/flux_local/tool/build.py
--- old/flux_local-7.0.0/flux_local/tool/build.py       2024-12-31 
20:47:07.000000000 +0100
+++ new/flux_local-7.4.0/flux_local/tool/build.py       2025-04-24 
06:04:26.000000000 +0200
@@ -65,6 +65,7 @@
         enable_helm: bool,
         skip_crds: bool,
         skip_secrets: bool,
+        skip_kinds: list[str],
         output_file: str,
         **kwargs,  # pylint: disable=unused-argument
     ) -> None:
@@ -74,10 +75,11 @@
         query.kustomization.namespace = None
         query.kustomization.skip_crds = skip_crds
         query.kustomization.skip_secrets = skip_secrets
+        query.kustomization.skip_kinds = skip_kinds
         query.helm_release.enabled = enable_helm
         query.helm_release.namespace = None
         helm_options = selector.build_helm_options(
-            skip_crds=skip_crds, skip_secrets=skip_secrets, **kwargs
+            skip_crds=skip_crds, skip_secrets=skip_secrets, 
skip_kinds=skip_kinds, **kwargs
         )
 
         content = ContentOutput()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/flux_local/tool/diff.py 
new/flux_local-7.4.0/flux_local/tool/diff.py
--- old/flux_local-7.0.0/flux_local/tool/diff.py        2024-12-31 
20:47:07.000000000 +0100
+++ new/flux_local-7.4.0/flux_local/tool/diff.py        2025-04-24 
06:04:26.000000000 +0200
@@ -20,6 +20,7 @@
     perform_external_diff,
     perform_object_diff,
     build_helm_dependency_map,
+    perform_json_diff,
 )
 
 from . import selector
@@ -35,7 +36,7 @@
     args.add_argument(
         "--output",
         "-o",
-        choices=["diff", "yaml", "object"],
+        choices=["diff", "yaml", "object", "json"],
         default="diff",
         help="Output format of the command",
     )
@@ -163,6 +164,10 @@
                 result = perform_yaml_diff(orig_content, content, unified, 
limit_bytes)
                 for line in result:
                     print(line, file=file)
+            elif output == "json":
+                result = perform_json_diff(orig_content, content, unified, 
limit_bytes)
+                for line in result:
+                    print(line, file=file)
             elif external_diff := os.environ.get("DIFF"):
                 async for line in perform_external_diff(
                     shlex.split(external_diff), orig_content, content, 
limit_bytes
@@ -253,7 +258,9 @@
         # depends on in the kustomization has a diff. This avoids needing to
         # template every possible helm chart when nothing as changed.
         dependency_map = build_helm_dependency_map(orig_helm_visitor, 
helm_visitor)
-        diff_resource_keys = get_helm_release_diff_keys(orig_content, content, 
dependency_map)
+        diff_resource_keys = get_helm_release_diff_keys(
+            orig_content, content, dependency_map
+        )
         diff_names = {
             resource_key.namespaced_name for resource_key in diff_resource_keys
         }
@@ -286,6 +293,11 @@
                     orig_helm_content, helm_content, unified, limit_bytes
                 ):
                     print(line, file=file)
+            elif output == "json":
+                for line in perform_json_diff(
+                    orig_helm_content, helm_content, unified, limit_bytes
+                ):
+                    print(line, file=file)
             elif external_diff := os.environ.get("DIFF"):
                 async for line in perform_external_diff(
                     shlex.split(external_diff),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/flux_local/tool/format.py 
new/flux_local-7.4.0/flux_local/tool/format.py
--- old/flux_local-7.0.0/flux_local/tool/format.py      2024-12-31 
20:47:07.000000000 +0100
+++ new/flux_local-7.4.0/flux_local/tool/format.py      2025-04-24 
06:04:26.000000000 +0200
@@ -1,10 +1,12 @@
 """Library for formatting output."""
 
+from abc import ABC, abstractmethod
 from typing import Generator, Any
 
 import sys
 from typing import TextIO
 import yaml
+import json
 
 
 PADDING = 4
@@ -56,18 +58,57 @@
             print(result, file=file)
 
 
-class YamlFormatter:
+class StructFormatter(ABC):
+    """A formatter that prints objects."""
+
+    @abstractmethod
+    def format(self, data: Any) -> Generator[str, None, None]:
+        """Format the data objects."""
+
+    @abstractmethod
+    def print(self, data: Any, file: TextIO = sys.stdout) -> None:
+        """Print the data objects."""
+
+
+class YamlFormatter(StructFormatter):
     """A formatter that prints yaml output."""
 
-    def format(self, data: list[dict[str, Any]]) -> Generator[str, None, None]:
+    def format(self, data: Any) -> Generator[str, None, None]:
         """Format the data objects."""
         for line in yaml.dump_all(data, sort_keys=False, 
explicit_start=True).split(
             "\n"
         ):
             yield line
 
-    def print(self, data: list[dict[str, Any]], file: TextIO = sys.stdout) -> 
None:
+    def print(self, data: Any, file: TextIO = sys.stdout) -> None:
         """Format the data objects."""
         print(
             yaml.dump_all(data, sort_keys=False, explicit_start=True), end="", 
file=file
         )
+
+
+class YamlListFormatter(StructFormatter):
+    """A formatter that prints yaml output for a list instead of a document."""
+
+    def format(self, data: Any) -> Generator[str, None, None]:
+        """Format the data objects."""
+        content = yaml.dump(data, sort_keys=False, explicit_start=True)
+        for line in content.split("\n"):
+            yield line
+
+    def print(self, data: Any, file: TextIO = sys.stdout) -> None:
+        """Format the data objects."""
+        print(yaml.dump(data, sort_keys=False, explicit_start=True), end="", 
file=file)
+
+
+class JsonFormatter(StructFormatter):
+    """A formatter that prints json output."""
+
+    def format(self, data: Any) -> Generator[str, None, None]:
+        """Format the data objects."""
+        for line in json.dumps(data, indent=4, sort_keys=False).split("\n"):
+            yield line
+
+    def print(self, data: Any, file: TextIO = sys.stdout) -> None:
+        """Format the data objects."""
+        json.dump(data, sort_keys=False, indent=4, fp=file)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/flux_local/tool/get.py 
new/flux_local-7.4.0/flux_local/tool/get.py
--- old/flux_local-7.0.0/flux_local/tool/get.py 2024-12-31 20:47:07.000000000 
+0100
+++ new/flux_local-7.4.0/flux_local/tool/get.py 2025-04-24 06:04:26.000000000 
+0200
@@ -14,7 +14,13 @@
 from flux_local import git_repo, image, helm
 from flux_local.visitor import HelmVisitor, ImageOutput
 
-from .format import PrintFormatter, YamlFormatter
+from .format import (
+    PrintFormatter,
+    YamlFormatter,
+    YamlListFormatter,
+    JsonFormatter,
+    StructFormatter,
+)
 from . import selector
 
 
@@ -168,9 +174,16 @@
             help="Output container images when traversing the cluster",
         )
         args.add_argument(
+            "--only-images",
+            type=str,
+            default=False,
+            action=BooleanOptionalAction,
+            help="Output only container images when traversing the cluster",
+        )
+        args.add_argument(
             "--output",
             "-o",
-            choices=["diff", "yaml"],
+            choices=["diff", "yaml", "json"],
             default="diff",
             help="Output format of the command",
         )
@@ -188,33 +201,43 @@
         output: str,
         output_file: str,
         enable_images: bool,
+        only_images: bool,
         **kwargs,  # pylint: disable=unused-argument
     ) -> None:
         """Async Action implementation."""
+        if output not in {"yaml", "json"}:
+            if enable_images:
+                print(
+                    "Flag --enable-images only works with --output yaml or 
json",
+                    file=sys.stderr,
+                )
+                return
+        if only_images and not enable_images:
+            print(
+                "Flag --only-images only works with --enable-images",
+                file=sys.stderr,
+            )
+            return
+
         query = selector.build_cluster_selector(**kwargs)
-        query.helm_release.enabled = output == "yaml"
+        query.helm_release.enabled = output in {"yaml", "json"}
 
         image_visitor: image.ImageVisitor | None = None
         helm_content: ImageOutput | None = None
         if enable_images:
-            if output != "yaml":
-                print(
-                    "Flag --enable-images only works with --output yaml",
-                    file=sys.stderr,
-                )
-                return
             image_visitor = image.ImageVisitor()
             query.doc_visitor = image_visitor.repo_visitor()
 
             helm_content = ImageOutput()
             helm_visitor = HelmVisitor()
             query.helm_repo.visitor = helm_visitor.repo_visitor()
+            query.oci_repo.visitor = helm_visitor.repo_visitor()
             query.helm_release.visitor = helm_visitor.release_visitor()
 
         manifest = await git_repo.build_manifest(
             selector=query, options=selector.options(**kwargs)
         )
-        if output == "yaml":
+        if output == "yaml" or output == "json":
             if image_visitor:
                 image_visitor.update_manifest(manifest)
             if helm_content:
@@ -226,8 +249,35 @@
                     )
                     helm_content.update_manifest(manifest)
 
+            output_content: Any
+            formatter: StructFormatter
+            if only_images:
+                output_content = sorted(
+                    list(
+                        {
+                            *[
+                                image
+                                for cluster in manifest.clusters
+                                for hr in cluster.helm_releases
+                                for image in hr.images or ()
+                            ],
+                            *[
+                                image
+                                for cluster in manifest.clusters
+                                for ks in cluster.kustomizations
+                                for image in ks.images or ()
+                            ],
+                        }
+                    )
+                )
+                formatter = YamlListFormatter() if output == "yaml" else 
JsonFormatter()
+            else:
+                output_content = manifest.compact_dict()
+                if output == "yaml":  # Yaml is printing multiple docs
+                    output_content = [output_content]
+                formatter = YamlFormatter() if output == "yaml" else 
JsonFormatter()
             with open(output_file, "w") as file:
-                YamlFormatter().print([manifest.compact_dict()], file=file)
+                formatter.print(output_content, file=file)
             return
 
         cols = ["path", "kustomizations"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/flux_local/tool/selector.py 
new/flux_local-7.4.0/flux_local/tool/selector.py
--- old/flux_local-7.0.0/flux_local/tool/selector.py    2024-12-31 
20:47:07.000000000 +0100
+++ new/flux_local-7.4.0/flux_local/tool/selector.py    2025-04-24 
06:04:26.000000000 +0200
@@ -44,6 +44,28 @@
         setattr(namespace, self.dest, result)
 
 
+class SelectorAppendAction(Action):
+    """Append a key=value pair to the argument dict."""
+
+    def __call__(
+        self,
+        parser: ArgumentParser,
+        namespace: Namespace,
+        values: Any,
+        option_string: str | None = None,
+    ) -> None:
+        values = values.split(",")
+        if not values[0]:
+            return
+        result = getattr(namespace, self.dest) or {}
+        for value in values:
+            if "=" not in value:
+                raise ValueError(f"Expected key=value format but got 
'{value}'")
+            k, v = value.split("=")
+            result[k] = v
+        setattr(namespace, self.dest, result)
+
+
 def add_selector_flags(args: ArgumentParser) -> None:
     """Add common selector flags to the arguments object."""
     args.add_argument(
@@ -75,6 +97,12 @@
         default=DEFAULT_NAMESPACE,
         help="If present, the namespace scope for this request",
     )
+    args.add_argument(
+        "--label-selector",
+        "-l",
+        action=SelectorAppendAction,
+        help="Filter objects by label selector by name=value",
+    )
     add_common_flags(args)
 
 
@@ -95,6 +123,11 @@
         help="When true do not include Secrets to reduce output size and 
randomness",
     )
     args.add_argument(
+        "--skip-kinds",
+        type=lambda x: x.split(","),
+        help="A comma separated list of CRDs to omit from the output.",
+    )
+    args.add_argument(
         "--kustomize-build-flags",
         type=str,
         default="",
@@ -135,8 +168,10 @@
     selector.kustomization.namespace = kwargs["namespace"]
     if kwargs["all_namespaces"]:
         selector.kustomization.namespace = None
+    selector.kustomization.label_selector = kwargs["label_selector"]
     selector.kustomization.skip_crds = kwargs["skip_crds"]
     selector.kustomization.skip_secrets = kwargs["skip_secrets"]
+    selector.kustomization.skip_kinds = kwargs["skip_kinds"]
     return selector
 
 
@@ -165,8 +200,10 @@
     selector.helm_release.namespace = kwargs["namespace"]
     if kwargs["all_namespaces"]:
         selector.helm_release.namespace = None
+    selector.helm_release.label_selector = kwargs["label_selector"]
     selector.helm_release.skip_crds = kwargs["skip_crds"]
     selector.helm_release.skip_secrets = kwargs["skip_secrets"]
+    selector.helm_release.skip_kinds = kwargs["skip_kinds"]
     selector.kustomization.name = None
     selector.kustomization.namespace = None
     return selector
@@ -198,6 +235,7 @@
     return helm.Options(
         skip_crds=kwargs["skip_crds"],
         skip_secrets=kwargs["skip_secrets"],
+        skip_kinds=kwargs["skip_kinds"],
         kube_version=kwargs.get("kube_version"),
         api_versions=kwargs.get("api_versions"),
         registry_config=kwargs.get("registry_config"),
@@ -222,8 +260,10 @@
     if kwargs.get("all_namespaces"):
         selector.cluster.namespace = None
         selector.kustomization.namespace = None
+    selector.kustomization.label_selector = kwargs["label_selector"]
     selector.kustomization.skip_crds = kwargs["skip_crds"]
     selector.kustomization.skip_secrets = kwargs["skip_secrets"]
+    selector.kustomization.skip_kinds = kwargs["skip_kinds"]
     return selector
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/flux_local.egg-info/PKG-INFO 
new/flux_local-7.4.0/flux_local.egg-info/PKG-INFO
--- old/flux_local-7.0.0/flux_local.egg-info/PKG-INFO   2024-12-31 
20:47:21.000000000 +0100
+++ new/flux_local-7.4.0/flux_local.egg-info/PKG-INFO   2025-04-24 
06:04:31.000000000 +0200
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
 Name: flux-local
-Version: 7.0.0
+Version: 7.4.0
 Summary: flux-local is a python library and set of tools for managing a flux 
gitops repository, with validation steps to help improve quality of commits, 
PRs, and general local testing.
 Home-page: https://github.com/allenporter/flux-local
 Author: Allen Porter
@@ -12,12 +12,12 @@
 License-File: LICENSE
 Requires-Dist: aiofiles>=22.1.0
 Requires-Dist: nest_asyncio>=1.5.6
-Requires-Dist: python-slugify>=8.0.0
 Requires-Dist: GitPython>=3.1.30
 Requires-Dist: PyYAML>=6.0
 Requires-Dist: mashumaro>=3.12
 Requires-Dist: pytest>=7.2.1
 Requires-Dist: pytest-asyncio>=0.20.3
+Dynamic: license-file
 
 flux-local is a set of tools and libraries for managing a local flux gitops 
repository focused on validation steps to help improve quality of commits, PRs, 
and general local testing.
 
@@ -36,10 +36,13 @@
 
 ## flux-local CLI
 
-The CLI is written in python and packaged as part of the `flux-local` python 
library, which can be installed using pip:
+The CLI is written in python and packaged as part of the `flux-local` python 
library, which can be installed using pip and uv. If you have not yet embraced 
python virtual environments, now might be
+the right time to do so to avoid clobbering the packages in your system.
 
 ```bash
-$ pip3 install flux-local
+$ uv venv
+$ source .venv/bin/activate
+$ uv pip install flux-local
 ```
 
 ### flux-local get
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/flux_local.egg-info/requires.txt 
new/flux_local-7.4.0/flux_local.egg-info/requires.txt
--- old/flux_local-7.0.0/flux_local.egg-info/requires.txt       2024-12-31 
20:47:21.000000000 +0100
+++ new/flux_local-7.4.0/flux_local.egg-info/requires.txt       2025-04-24 
06:04:31.000000000 +0200
@@ -1,6 +1,5 @@
 aiofiles>=22.1.0
 nest_asyncio>=1.5.6
-python-slugify>=8.0.0
 GitPython>=3.1.30
 PyYAML>=6.0
 mashumaro>=3.12
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/setup.cfg 
new/flux_local-7.4.0/setup.cfg
--- old/flux_local-7.0.0/setup.cfg      2024-12-31 20:47:21.301954000 +0100
+++ new/flux_local-7.4.0/setup.cfg      2025-04-24 06:04:31.769139300 +0200
@@ -1,6 +1,6 @@
 [metadata]
 name = flux-local
-version = 7.0.0
+version = 7.4.0
 description = flux-local is a python library and set of tools for managing a 
flux gitops repository, with validation steps to help improve quality of 
commits, PRs, and general local testing.
 long_description = file: README.md
 long_description_content_type = text/markdown
@@ -19,7 +19,6 @@
 install_requires = 
        aiofiles>=22.1.0
        nest_asyncio>=1.5.6
-       python-slugify>=8.0.0
        GitPython>=3.1.30
        PyYAML>=6.0
        mashumaro>=3.12
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/tests/test_git_repo.py 
new/flux_local-7.4.0/tests/test_git_repo.py
--- old/flux_local-7.0.0/tests/test_git_repo.py 2024-12-31 20:47:07.000000000 
+0100
+++ new/flux_local-7.4.0/tests/test_git_repo.py 2025-04-24 06:04:26.000000000 
+0200
@@ -282,3 +282,80 @@
     )
     selector = PathSelector(TESTDATA_FULL_PATH)
     assert adjust_ks_path(ks, selector) == Path(expected_path)
+
+
+async def test_kustomization_label_selector() -> None:
+    """Tests for building the manifest."""
+
+    query = ResourceSelector()
+    query.path.path = TESTDATA
+
+    async def get_ks() -> list[str]:
+        manifest = await build_manifest(selector=query)
+        return [
+            ks.namespaced_name
+            for cluster in manifest.clusters
+            for ks in cluster.kustomizations
+        ]
+
+    assert await get_ks() == [
+        "flux-system/apps",
+        "flux-system/flux-system",
+        "flux-system/infra-configs",
+        "flux-system/infra-controllers",
+    ]
+
+    query.kustomization.label_selector = {"app.kubernetes.io/name": "apps"}
+    assert await get_ks() == [
+        "flux-system/apps",
+    ]
+
+    query.kustomization.label_selector = {"app.kubernetes.io/name": "podinfo"}
+    assert await get_ks() == []
+
+    # Match on multiple fields
+    query.kustomization.label_selector = {
+        "app.kubernetes.io/name": "apps",
+        "app.kubernetes.io/instance": "apps",
+    }
+    assert await get_ks() == [
+        "flux-system/apps",
+    ]
+
+    # Mismatch on one field
+    query.kustomization.label_selector = {
+        "app.kubernetes.io/name": "apps",
+        "app.kubernetes.io/instance": "flux-system",
+    }
+    assert await get_ks() == []
+
+
+async def test_helmrelease_label_selector() -> None:
+    """Tests for building the manifest."""
+
+    query = ResourceSelector()
+    query.path.path = TESTDATA
+
+    async def get_hr() -> list[str]:
+        manifest = await build_manifest(selector=query)
+        return [
+            hr.namespaced_name
+            for cluster in manifest.clusters
+            for hr in cluster.helm_releases
+        ]
+
+    assert await get_hr() == [
+        "podinfo/podinfo",
+        "metallb/metallb",
+        "flux-system/weave-gitops",
+    ]
+
+    query.helm_release.label_selector = {"app.kubernetes.io/name": "podinfo"}
+    assert await get_hr() == [
+        "podinfo/podinfo",
+    ]
+
+    query.helm_release.label_selector = {
+        "app.kubernetes.io/name": "kubernetes-dashboard"
+    }
+    assert await get_hr() == []
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/tests/test_helm.py 
new/flux_local-7.4.0/tests/test_helm.py
--- old/flux_local-7.0.0/tests/test_helm.py     2024-12-31 20:47:07.000000000 
+0100
+++ new/flux_local-7.4.0/tests/test_helm.py     2025-04-24 06:04:26.000000000 
+0200
@@ -93,11 +93,24 @@
     await helm.update()
 
     assert len(helm_releases) == 2
+
+    # metallb, no targetNamespace overrides
     release = helm_releases[0]
     obj = await helm.template(HelmRelease.parse_doc(release))
     docs = await obj.grep("kind=ServiceAccount").objects()
     names = [doc.get("metadata", {}).get("name") for doc in docs]
+    namespaces = [doc.get("metadata", {}).get("namespace") for doc in docs]
     assert names == ["metallb-controller", "metallb-speaker"]
+    assert namespaces == ["metallb", "metallb"]
+
+    # weave-gitops, with targetNamespace overrides
+    release = helm_releases[1]
+    obj = await helm.template(HelmRelease.parse_doc(release))
+    docs = await obj.grep("kind=ServiceAccount").objects()
+    names = [doc.get("metadata", {}).get("name") for doc in docs]
+    namespaces = [doc.get("metadata", {}).get("namespace") for doc in docs]
+    assert names == ["weave-gitops"]
+    assert namespaces == ["weave"]
 
 
 @pytest.mark.parametrize(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/tests/test_image.py 
new/flux_local-7.4.0/tests/test_image.py
--- old/flux_local-7.0.0/tests/test_image.py    2024-12-31 20:47:07.000000000 
+0100
+++ new/flux_local-7.4.0/tests/test_image.py    2025-04-24 06:04:26.000000000 
+0200
@@ -24,6 +24,15 @@
             CWD / "tests/testdata/cluster7",
             {},
         ),
+        (
+            CWD / "tests/testdata/cluster",
+            {
+                "flux-system/infra-configs": {
+                    "ceph/ceph:v16.2.6",
+                    "ghcr.io/cloudnative-pg/postgis:17-3.4",
+                },
+            },
+        )
     ],
 )
 async def test_image_visitor(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/tests/test_resource_diff.py 
new/flux_local-7.4.0/tests/test_resource_diff.py
--- old/flux_local-7.0.0/tests/test_resource_diff.py    2024-12-31 
20:47:07.000000000 +0100
+++ new/flux_local-7.4.0/tests/test_resource_diff.py    2025-04-24 
06:04:26.000000000 +0200
@@ -7,6 +7,7 @@
 from collections.abc import Generator
 
 import yaml
+from syrupy.assertion import SnapshotAssertion
 
 from flux_local import git_repo
 from flux_local.resource_diff import (
@@ -14,6 +15,8 @@
     merge_helm_releases,
     merge_named_resources,
     build_helm_dependency_map,
+    perform_json_diff,
+    perform_yaml_diff,
 )
 from flux_local.visitor import ObjectOutput, HelmVisitor, ResourceKey
 from flux_local.manifest import HelmRelease, HelmChart, NamedResource
@@ -198,3 +201,49 @@
         NamedResource(name="secret", namespace="podinfo", kind="Secret"),
         NamedResource(name="podinfo", namespace="flux-system", 
kind="HelmRepository"),
     ]
+
+
+async def test_perform_yaml_diff(snapshot: SnapshotAssertion) -> None:
+    """Test perform_yaml_diff where an input HelmRepository change causes a 
diff."""
+
+    content, helm_visitor = await visit_helm_content(TESTDATA_PATH)
+    with mirror_worktree(TESTDATA_PATH) as new_cluster_path:
+
+        # Generate a diff by changing a value in the HelmRelease
+        podinfo_path = new_cluster_path / "apps/podinfo.yaml"
+        podinfo_doc = list(
+            yaml.load_all(podinfo_path.read_text(), Loader=yaml.SafeLoader)
+        )
+        assert len(podinfo_doc) == 2
+        assert podinfo_doc[0]["kind"] == "HelmRepository"
+        assert podinfo_doc[0]["spec"]["url"] == 
"oci://ghcr.io/stefanprodan/charts"
+        podinfo_doc[0]["spec"]["url"] = "oci://ghcr.io/stefanprodan/charts2"
+        podinfo_path.write_text(yaml.dump_all(podinfo_doc))
+
+        content_new, helm_visitor_new = await 
visit_helm_content(new_cluster_path)
+
+    diff = "\n".join(list(perform_yaml_diff(content, content_new, 5, 50000)))
+    assert diff == snapshot
+
+
+async def test_perform_json_diff(snapshot: SnapshotAssertion) -> None:
+    """Test perform_yaml_diff where an input HelmRepository change causes a 
diff."""
+
+    content, helm_visitor = await visit_helm_content(TESTDATA_PATH)
+    with mirror_worktree(TESTDATA_PATH) as new_cluster_path:
+
+        # Generate a diff by changing a value in the HelmRelease
+        podinfo_path = new_cluster_path / "apps/podinfo.yaml"
+        podinfo_doc = list(
+            yaml.load_all(podinfo_path.read_text(), Loader=yaml.SafeLoader)
+        )
+        assert len(podinfo_doc) == 2
+        assert podinfo_doc[0]["kind"] == "HelmRepository"
+        assert podinfo_doc[0]["spec"]["url"] == 
"oci://ghcr.io/stefanprodan/charts"
+        podinfo_doc[0]["spec"]["url"] = "oci://ghcr.io/stefanprodan/charts2"
+        podinfo_path.write_text(yaml.dump_all(podinfo_doc))
+
+        content_new, helm_visitor_new = await 
visit_helm_content(new_cluster_path)
+
+    diff = "\n".join(list(perform_json_diff(content, content_new, 5, 50000)))
+    assert diff == snapshot
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/tests/tool/test_get_cluster.py 
new/flux_local-7.4.0/tests/tool/test_get_cluster.py
--- old/flux_local-7.0.0/tests/tool/test_get_cluster.py 2024-12-31 
20:47:07.000000000 +0100
+++ new/flux_local-7.4.0/tests/tool/test_get_cluster.py 2025-04-24 
06:04:26.000000000 +0200
@@ -30,7 +30,47 @@
         (["--path", "tests/testdata/cluster", "-o", "yaml", 
"--enable-images"]),
         (["--path", "tests/testdata/cluster8", "-o", "yaml"]),
         (["--path", "tests/testdata/cluster8", "-o", "yaml", 
"--enable-images"]),
-        (["--path", "tests/testdata/cluster8", "-o", "yaml", 
"--enable-images", "--no-skip-secrets"]),
+        (
+            [
+                "--path",
+                "tests/testdata/cluster8",
+                "-o",
+                "yaml",
+                "--enable-images",
+                "--no-skip-secrets",
+            ]
+        ),
+        (
+            [
+                "--path",
+                "tests/testdata/cluster8",
+                "-o",
+                "yaml",
+                "--enable-images",
+                "--only-images",
+            ]
+        ),
+        (
+            [
+                "--path",
+                "tests/testdata/cluster9/clusters/dev",
+                "--output",
+                "yaml",
+                "--enable-images",
+            ]
+        ),
+        (["--path", "tests/testdata/cluster", "-o", "json"]),
+        (["--path", "tests/testdata/cluster", "-o", "json", 
"--enable-images"]),
+        (
+            [
+                "--path",
+                "tests/testdata/cluster8",
+                "-o",
+                "json",
+                "--enable-images",
+                "--only-images",
+            ]
+        ),
     ],
     ids=[
         "cluster",
@@ -47,6 +87,11 @@
         "yaml-cluster8-no-images",
         "yaml-cluster8-images",
         "yaml-cluster8-images-allow-secrets",
+        "yaml-cluster8-only-images",
+        "yaml-cluster9-only-images-with-oci",
+        "json-cluster",
+        "json-cluster-images",
+        "json-cluster-only-images",
     ],
 )
 async def test_get_cluster(args: list[str], snapshot: SnapshotAssertion) -> 
None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flux_local-7.0.0/tests/tool/test_get_hr.py 
new/flux_local-7.4.0/tests/tool/test_get_hr.py
--- old/flux_local-7.0.0/tests/tool/test_get_hr.py      2024-12-31 
20:47:07.000000000 +0100
+++ new/flux_local-7.4.0/tests/tool/test_get_hr.py      2025-04-24 
06:04:26.000000000 +0200
@@ -29,6 +29,24 @@
         (["metallb", "-A", "--path", "tests/testdata/cluster"]),
         (["-n", "metallb", "--path", "tests/testdata/cluster"]),
         (["-A", "--path", "tests/testdata/cluster9/clusters/dev"]),
+        (
+            [
+                "-A",
+                "--path",
+                "tests/testdata/cluster",
+                "-l",
+                "app.kubernetes.io/name=podinfo",
+            ]
+        ),
+        (
+            [
+                "-A",
+                "--path",
+                "tests/testdata/cluster",
+                "-l",
+                "app.kubernetes.io/name=kubernetes-dashboard",
+            ]
+        ),
     ],
     ids=[
         "cluster",
@@ -42,6 +60,8 @@
         "all_namespace",
         "name",
         "cluster9",
+        "label-selector-match",
+        "label-selector-no-match",
     ],
 )
 async def test_get_hr(args: list[str], snapshot: SnapshotAssertion) -> None:

Reply via email to