From 5c21426fe818b7d4131ae0c85c91b415e0131ef4 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 5 Feb 2023 20:05:16 -0800 Subject: [PATCH] Allow building resources without a Kustomization (#20) Allow building with `kustomize cfg grep` without needing to first build from a `Kustomization`. Includes a breaking change to move `build` out of `Kustomization` into the module level. --- flux_local/kustomize.py | 41 ++++++++++++------- setup.cfg | 2 +- tests/test_helm.py | 14 +++---- tests/test_kustomize.py | 31 ++++++++------ ...onfigmap.golden => configmap.build.golden} | 0 tests/testdata/repo/configmap.grep.golden | 13 ++++++ 6 files changed, 67 insertions(+), 34 deletions(-) rename tests/testdata/repo/{configmap.golden => configmap.build.golden} (100%) create mode 100644 tests/testdata/repo/configmap.grep.golden diff --git a/flux_local/kustomize.py b/flux_local/kustomize.py index 08fb4a0e..00fd3420 100644 --- a/flux_local/kustomize.py +++ b/flux_local/kustomize.py @@ -6,18 +6,27 @@ This example returns the objects inside a Kustomization using `kustomize build`: ```python -from flux_local.kustomize import Kustomize +from flux_local import kustomize -objects = await Kustomize.build('/path/to/objects').objects() +objects = await kustomize.build('/path/to/objects').objects() for object in objects: print(f"Found object {object['apiVersion']} {object['kind']}") ``` You can also filter documents to specific resource types or other fields: ```python -from flux_local.kustomize import Kustomize +from flux_local import kustomize -objects = await Kustomize.build('/path/to/objects').grep('kind=ConfigMap').objects() +objects = await kustomize.build('/path/to/objects').grep('kind=ConfigMap').objects() +for object in objects: + print(f"Found ConfigMap: {object['metadata']['name']}") +``` + +It is also possible to find bare objects without a Kustomization: +```python +from flux_local import kustomize + +objects = await kustomize.grep('kind=ConfigMap', '/path/to/objects').objects() for object in objects: print(f"Found ConfigMap: {object['metadata']['name']}") ``` @@ -43,14 +52,7 @@ def __init__(self, cmds: list[list[str]]) -> None: """Initialize Kustomize.""" self._cmds = cmds - @classmethod - def build(cls, path: Path) -> "Kustomize": - """Build cluster artifacts from the specified path.""" - return Kustomize(cmds=[[KUSTOMIZE_BIN, "build", str(path)]]) - - def grep( - self, expr: str, path: Path | None = None, invert: bool = False - ) -> "Kustomize": + def grep(self, expr: str, invert: bool = False) -> "Kustomize": """Filter resources based on an expression. Example expressions: @@ -60,8 +62,6 @@ def grep( out = [KUSTOMIZE_BIN, "cfg", "grep", expr] if invert: out.append("--invert-match") - if path: - out.append(str(path)) return Kustomize(self._cmds + [out]) async def run(self) -> str: @@ -95,3 +95,16 @@ async def validate(self, policy_path: Path) -> None: ], ] await command.run_piped(cmds) + + +def build(path: Path) -> Kustomize: + """Build cluster artifacts from the specified path.""" + return Kustomize(cmds=[[KUSTOMIZE_BIN, "build", str(path)]]) + + +def grep(expr: str, path: Path, invert: bool = False) -> Kustomize: + """Filter resources in the specified path based on an expression.""" + out = [KUSTOMIZE_BIN, "cfg", "grep", expr, str(path)] + if invert: + out.append("--invert-match") + return Kustomize([out]) diff --git a/setup.cfg b/setup.cfg index 5aa12b59..fb50fca0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = flux-local -version = 0.0.1 +version = 0.0.2 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 diff --git a/tests/test_helm.py b/tests/test_helm.py index cf323e3f..da7dcfe8 100644 --- a/tests/test_helm.py +++ b/tests/test_helm.py @@ -6,8 +6,8 @@ import pytest from aiofiles.os import mkdir +from flux_local import kustomize from flux_local.helm import Helm -from flux_local.kustomize import Kustomize from flux_local.manifest import HelmRelease, HelmRepository TESTDATA_DIR = Path("tests/testdata/") / "helm-repo" @@ -22,8 +22,8 @@ def tmp_config_path_fixture(tmp_path_factory: Any) -> Generator[Path, None, None @pytest.fixture(name="helm_repos") async def helm_repos_fixture() -> list[dict[str, Any]]: """Fixture for creating the HelmRepository objects""" - kustomize = Kustomize.build(TESTDATA_DIR).grep("kind=^HelmRepository$") - return await kustomize.objects() + cmd = kustomize.grep("kind=^HelmRepository$", TESTDATA_DIR) + return await cmd.objects() @pytest.fixture(name="helm") @@ -43,8 +43,8 @@ async def helm_fixture(tmp_config_path: Path, helm_repos: list[dict[str, Any]]) @pytest.fixture(name="helm_releases") async def helm_releases_fixture() -> list[dict[str, Any]]: """Fixture for creating the HelmRelease objects.""" - kustomize = Kustomize.build(TESTDATA_DIR).grep("kind=^HelmRelease$") - return await kustomize.objects() + cmd = kustomize.grep("kind=^HelmRelease$", TESTDATA_DIR) + return await cmd.objects() async def test_update(helm: Helm) -> None: @@ -58,9 +58,9 @@ async def test_template(helm: Helm, helm_releases: list[dict[str, Any]]) -> None assert len(helm_releases) == 1 release = helm_releases[0] - kustomize = await helm.template( + obj = await helm.template( HelmRelease.from_doc(release), release["spec"].get("values") ) - docs = await kustomize.grep("kind=ServiceAccount").objects() + docs = await obj.grep("kind=ServiceAccount").objects() names = [doc.get("metadata", {}).get("name") for doc in docs] assert names == ["metallb-controller", "metallb-speaker"] diff --git a/tests/test_kustomize.py b/tests/test_kustomize.py index a3ab317f..6d8c4b1b 100644 --- a/tests/test_kustomize.py +++ b/tests/test_kustomize.py @@ -4,32 +4,39 @@ import pytest -from flux_local import command -from flux_local.kustomize import Kustomize +from flux_local import command, kustomize TESTDATA_DIR = Path("tests/testdata") async def test_build() -> None: """Test a kustomize build command.""" - result = await Kustomize.build(TESTDATA_DIR / "repo").run() + result = await kustomize.build(TESTDATA_DIR / "repo").run() assert "Secret" in result assert "ConfigMap" in result assert result == (TESTDATA_DIR / "repo/all.golden").read_text() +async def test_build_grep() -> None: + """Test a kustomize build and grep command chained.""" + result = await kustomize.build(TESTDATA_DIR / "repo").grep("kind=ConfigMap").run() + assert "Secret" not in result + assert "ConfigMap" in result + assert result == (TESTDATA_DIR / "repo/configmap.build.golden").read_text() + + async def test_grep() -> None: - """Test a kustomize build command.""" - result = await Kustomize.build(TESTDATA_DIR / "repo").grep("kind=ConfigMap").run() + """Test a kustomize grep command.""" + result = await kustomize.grep("kind=ConfigMap", TESTDATA_DIR / "repo").run() assert "Secret" not in result assert "ConfigMap" in result - assert result == (TESTDATA_DIR / "repo/configmap.golden").read_text() + assert result == (TESTDATA_DIR / "repo/configmap.grep.golden").read_text() async def test_objects() -> None: """Test loading yaml documents.""" - kustomize = Kustomize.build(TESTDATA_DIR / "repo").grep("kind=ConfigMap") - result = await kustomize.objects() + cmd = kustomize.build(TESTDATA_DIR / "repo").grep("kind=ConfigMap") + result = await cmd.objects() assert len(result) == 1 assert result[0].get("kind") == "ConfigMap" assert result[0].get("apiVersion") == "v1" @@ -37,14 +44,14 @@ async def test_objects() -> None: async def test_validate_pass() -> None: """Test applying policies to validate resources.""" - kustomize = Kustomize.build(TESTDATA_DIR / "repo") - await kustomize.validate(TESTDATA_DIR / "policies/pass.yaml") + cmd = kustomize.build(TESTDATA_DIR / "repo") + await cmd.validate(TESTDATA_DIR / "policies/pass.yaml") async def test_validate_fail() -> None: """Test applying policies to validate resources.""" - kustomize = Kustomize.build(TESTDATA_DIR / "repo") + cmd = kustomize.build(TESTDATA_DIR / "repo") with pytest.raises( command.CommandException, match="require-test-annotation: validation error" ): - await kustomize.validate(TESTDATA_DIR / "policies/fail.yaml") + await cmd.validate(TESTDATA_DIR / "policies/fail.yaml") diff --git a/tests/testdata/repo/configmap.golden b/tests/testdata/repo/configmap.build.golden similarity index 100% rename from tests/testdata/repo/configmap.golden rename to tests/testdata/repo/configmap.build.golden diff --git a/tests/testdata/repo/configmap.grep.golden b/tests/testdata/repo/configmap.grep.golden new file mode 100644 index 00000000..0bee8ad7 --- /dev/null +++ b/tests/testdata/repo/configmap.grep.golden @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + namespace: flux-system + name: cluster-settings + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'cluster-settings.yaml' + internal.config.kubernetes.io/index: '0' + internal.config.kubernetes.io/path: 'cluster-settings.yaml' +data: + CLUSTER: dev + DOMAIN: example.org