Skip to content

Commit

Permalink
Add tests for value reference expansion
Browse files Browse the repository at this point in the history
  • Loading branch information
allenporter committed Dec 24, 2023
1 parent 612ab05 commit ccc2682
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 6 deletions.
2 changes: 1 addition & 1 deletion flux_local/git_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
ConfigMap,
Secret,
SECRET_KIND,
CONFIG_MAP_KIND,
)
from .exceptions import InputException
from .context import trace_context
Expand All @@ -76,7 +77,6 @@
HELM_REPO_KIND = "HelmRepository"
HELM_RELEASE_KIND = "HelmRelease"
CONFIG_MAP_KIND = "ConfigMap"
SECRET_KIND = "Secret"
CLUSTER_POLICY_KIND = "ClusterPolicy"
GIT_REPO_KIND = "GitRepository"
OCI_REPO_KIND = "OCIRepository"
Expand Down
47 changes: 46 additions & 1 deletion flux_local/helm.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@

from . import command
from .kustomize import Kustomize
from .manifest import HelmRelease, HelmRepository, CRD_KIND, SECRET_KIND, REPO_TYPE_OCI
from .manifest import HelmRelease, HelmRepository, CRD_KIND, SECRET_KIND, REPO_TYPE_OCI, Kustomization, CONFIG_MAP_KIND, SECRET_KIND
from .exceptions import HelmException

__all__ = [
Expand Down Expand Up @@ -225,3 +225,48 @@ async def template(
if options.skip_resources:
cmd = cmd.skip_resources(options.skip_resources)
return cmd

def expand_value_references(helm_release: HelmRelease, ks: Kustomization) -> HelmRelease:
"""Expand value references in the HelmRelease."""
if not helm_release.values_from:
return helm_release

values = helm_release.values or {}
for ref in helm_release.values_from:
if ref.kind == SECRET_KIND:
found_secret = next(filter(lambda secret: secret.name == ref.name, ks.secrets), None)
if found_secret:
if found_secret.data:
values.update(found_secret.data)
if found_secret.string_data:
values.update(found_secret.string_data)
else:
if not ref.optional:
_LOGGER.warning(
"Unable to find secret %s referenced in HelmRelease %s",
ref.name,
helm_release.name,
)
continue
elif ref.kind == CONFIG_MAP_KIND:
found_configmap = next(filter(lambda configmap: configmap.name == ref.name, ks.config_maps), None)
if found_configmap:
if found_configmap.data:
values.update(found_configmap.data)
if found_configmap.binary_data:
values.update(found_configmap.binary_data)
else:
if not ref.optional:
_LOGGER.warning(
"Unable to find configmap %s referenced in HelmRelease %s",
ref.name,
helm_release.name,
)
continue
else:
_LOGGER.warning(
"Unsupported valueFrom kind %s in HelmRelease %s",
ref.kind,
helm_release.name,
)
return helm_release.model_copy(update={"values": values})
37 changes: 34 additions & 3 deletions flux_local/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
CLUSTER_POLICY_DOMAIN = "kyverno.io"
CRD_KIND = "CustomResourceDefinition"
SECRET_KIND = "Secret"
CONFIG_MAP_KIND = "ConfigMap"
DEFAULT_NAMESPACE = "flux-system"

REPO_TYPE_DEFAULT = "default"
Expand Down Expand Up @@ -137,6 +138,25 @@ def compact_exclude_fields(cls) -> dict[str, Any]:
return {"version": True}


class ValuesReference(BaseManifest):
"""A reference to a resource containing values for a HelmRelease."""

kind: str
"""The kind of resource."""

name: str
"""The name of the resource."""

values_key: str | None = Field(alias="valuesKey")
"""The key in the resource that contains the values."""

target_path: str | None = Field(alias="targetPath")
"""The path in the HelmRelease values to store the values."""

optional: bool
"""Whether the reference is optional."""


class HelmRelease(BaseManifest):
"""A representation of a Flux HelmRelease."""

Expand All @@ -152,6 +172,9 @@ class HelmRelease(BaseManifest):
values: Optional[dict[str, Any]] = None
"""The values to install in the chart."""

values_from: Optional[list[ValuesReference]]
"""A list of values to reference from an ConfigMap or Secret."""

images: list[str] = Field(default_factory=list)
"""The list of images referenced in the HelmRelease."""

Expand All @@ -166,11 +189,19 @@ def parse_doc(cls, doc: dict[str, Any]) -> "HelmRelease":
if not (namespace := metadata.get("namespace")):
raise InputException(f"Invalid {cls} missing metadata.namespace: {doc}")
chart = HelmChart.parse_doc(doc, namespace)
spec = doc["spec"]
values_from: list[ValuesReference] | None = None
if values_from_dict := spec.get("valuesFrom"):
values_from = [
ValuesReference.model_construct(**subdoc)
for subdoc in values_from_dict
]
return cls(
name=name,
namespace=namespace,
chart=chart,
values=doc["spec"].get("values"),
values=spec.get("values"),
values_from=values_from,
)

@property
Expand All @@ -191,7 +222,7 @@ def namespaced_name(self) -> str:
@classmethod
def compact_exclude_fields(cls) -> dict[str, Any]:
"""Return a dictionary of fields to exclude from compact_dict."""
return {"values": True, "chart": HelmChart.compact_exclude_fields()}
return {"values": True, "values_from": True, "chart": HelmChart.compact_exclude_fields()}


class HelmRepository(BaseManifest):
Expand Down Expand Up @@ -281,7 +312,7 @@ class ConfigMap(BaseManifest):
data: dict[str, Any] | None = None
"""The data in the ConfigMap."""

binaryData: dict[str, Any] | None = None
binary_data: dict[str, Any] | None = None
"""The binary data in the ConfigMap."""

@classmethod
Expand Down
25 changes: 25 additions & 0 deletions tests/__snapshots__/test_helm.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# serializer version: 1
# name: test_value_references
dict({
'ingress': dict({
'className': 'nginx',
'enabled': True,
'hosts': list([
dict({
'host': 'podinfo.production',
'paths': list([
dict({
'path': '/',
'pathType': 'ImplementationSpecific',
}),
]),
}),
]),
}),
'redis': dict({
'enabled': True,
'repository': 'public.ecr.aws/docker/library/redis',
'tag': '7.0.6',
}),
})
# ---
22 changes: 21 additions & 1 deletion tests/test_helm.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@

import pytest
from aiofiles.os import mkdir
from syrupy.assertion import SnapshotAssertion

from flux_local import kustomize
from flux_local.helm import Helm
from flux_local.helm import Helm, expand_value_references
from flux_local.manifest import HelmRelease, HelmRepository
from flux_local.git_repo import ResourceSelector, PathSelector, build_manifest

REPO_DIR = Path("tests/testdata/cluster/infrastructure/configs")
RELEASE_DIR = Path("tests/testdata/cluster/infrastructure/controllers")
Expand Down Expand Up @@ -62,3 +64,21 @@ async def test_template(helm: Helm, helm_releases: list[dict[str, Any]]) -> None
docs = await obj.grep("kind=ServiceAccount").objects()
names = [doc.get("metadata", {}).get("name") for doc in docs]
assert names == ["metallb-controller", "metallb-speaker"]


async def test_value_references(snapshot: SnapshotAssertion) -> None:
"""Test for expanding value references."""
path = Path("tests/testdata/cluster8")
selector = ResourceSelector(path=PathSelector(path=path))
manifest = await build_manifest(selector=selector)
assert len(manifest.clusters) == 1
assert len(manifest.clusters[0].kustomizations) == 2
ks = manifest.clusters[0].kustomizations[0]
assert ks.name == "apps"
assert len(ks.helm_releases) == 1
hr = ks.helm_releases[0]
assert hr.name == "podinfo"
assert not hr.values

updated_hr = expand_value_references(hr, ks)
assert updated_hr.values == snapshot

0 comments on commit ccc2682

Please sign in to comment.