Skip to content

Commit

Permalink
Add HelmRelease images to cluster manifest
Browse files Browse the repository at this point in the history
  • Loading branch information
allenporter committed Dec 21, 2023
1 parent b77ba60 commit af813cd
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 24 deletions.
14 changes: 11 additions & 3 deletions flux_local/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class BaseManifest(BaseModel):

_COMPACT_EXCLUDE_FIELDS: dict[str, Any] = {}

def compact_dict(self, exclude: dict[str, Any] | None = None, include: dict[str, Any] | None = None) -> dict[str, Any]:
def compact_dict(self, exclude: dict[str, Any] | None = None) -> dict[str, Any]:
"""Return a compact dictionary representation of the object.
This is similar to `dict()` but with a specific implementation for serializing
Expand Down Expand Up @@ -143,6 +143,9 @@ class HelmRelease(BaseManifest):
values: Optional[dict[str, Any]] = None
"""The values to install in the chart."""

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

@classmethod
def parse_doc(cls, doc: dict[str, Any]) -> "HelmRelease":
"""Parse a HelmRelease from a kubernetes resource object."""
Expand Down Expand Up @@ -171,6 +174,11 @@ def repo_name(self) -> str:
"""Identifier for the HelmRepository identified in the HelmChart."""
return f"{self.chart.repo_namespace}-{self.chart.repo_name}"

@property
def namespaced_name(self) -> str:
"""Return the namespace and name concatenated as an id."""
return f"{self.namespace}/{self.name}"

_COMPACT_EXCLUDE_FIELDS = {
"values": True,
"chart": HelmChart._COMPACT_EXCLUDE_FIELDS,
Expand Down Expand Up @@ -329,9 +337,9 @@ def id_name(self) -> str:
return f"{self.path}"

@property
def namespaced_name(self, sep: str = "/") -> str:
def namespaced_name(self) -> str:
"""Return the namespace and name concatenated as an id."""
return f"{self.namespace}{sep}{self.name}"
return f"{self.namespace}/{self.name}"

_COMPACT_EXCLUDE_FIELDS = {
"helm_releases": {
Expand Down
32 changes: 19 additions & 13 deletions flux_local/tool/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
from argparse import ArgumentParser, BooleanOptionalAction, _SubParsersAction as SubParsersAction
from typing import cast, Any
import sys
import pathlib
import tempfile
import yaml

from flux_local import git_repo, image
from flux_local import git_repo, image, helm

from .format import PrintFormatter, YamlFormatter
from . import selector
from .visitor import HelmVisitor, ObjectOutput, ImageOutput


_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -178,6 +182,7 @@ async def run( # type: ignore[no-untyped-def]
query.helm_release.enabled = output == "yaml"

image_visitor: image.ImageVisitor | None = None
helm_content: ImageOutput | None = None
if enable_images:
if output != "yaml":
print(
Expand All @@ -188,24 +193,25 @@ async def run( # type: ignore[no-untyped-def]
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.helm_release.visitor = helm_visitor.release_visitor()

manifest = await git_repo.build_manifest(
selector=query, options=selector.options(**kwargs)
)
if output == "yaml":
include: dict[str, Any] | None = None
if image_visitor:
image_visitor.update_manifest(manifest)
include = {
"clusters": {
"__all__": {
"kustomizations": {
"__all__": True,
#"images": True,
}
}
}
}
YamlFormatter().print([manifest.compact_dict(include=include)])
if helm_content:
with tempfile.TemporaryDirectory() as helm_cache_dir:
await helm_visitor.inflate(
pathlib.Path(helm_cache_dir), helm_content.visitor(), helm.Options(),
)
helm_content.update_manifest(manifest)

YamlFormatter().print([manifest.compact_dict()])
return

cols = ["path", "kustomizations"]
Expand Down
39 changes: 38 additions & 1 deletion flux_local/tool/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
import yaml
from typing import Any

from flux_local import git_repo
from flux_local import git_repo, image
from flux_local.helm import Helm, Options
from flux_local.kustomize import Kustomize
from flux_local.manifest import (
HelmRelease,
Kustomization,
HelmRepository,
ClusterPolicy,
Manifest,
)


Expand Down Expand Up @@ -152,6 +153,42 @@ def strip_attrs(metadata: dict[str, Any], strip_attributes: list[str]) -> None:
del metadata[attr_key]
break

class ImageOutput(ResourceOutput):
"""Resource visitor that builds outputs for objects within the kustomization."""

def __init__(self) -> None:
"""Initialize ObjectOutput."""
# Map of kustomizations to the map of built objects as lines of the yaml string
self.content: dict[ResourceKey, dict[ResourceKey, list[str]]] = {}
self.image_visitor = image.ImageVisitor()
self.repo_visitor = self.image_visitor.repo_visitor()

async def call_async(
self,
cluster_path: pathlib.Path,
kustomization_path: pathlib.Path,
doc: ResourceType,
cmd: Kustomize | None,
) -> None:
"""Visitor function invoked to build and record resource objects."""
if cmd:
objects = await cmd.objects()
name = doc.namespaced_name
for obj in objects:
if obj.get("kind") in self.repo_visitor.kinds:
#_LOGGER.debug("Looking for image %s", doc)
self.repo_visitor.func(doc.namespaced_name, obj)


def update_manifest(self, manifest: Manifest) -> None:
"""Update the manifest with the images found in the repo."""
#_LOGGER.debug(self.image_visitor.images)
for cluster in manifest.clusters:
for kustomization in cluster.kustomizations:
for helm_release in kustomization.helm_releases:
if images := self.image_visitor.images.get(f"{helm_release.namespace}/{helm_release.name}"):
helm_release.images = list(images)


class ObjectOutput(ResourceOutput):
"""Resource visitor that builds outputs for objects within the kustomization."""
Expand Down
108 changes: 102 additions & 6 deletions tests/tool/__snapshots__/test_get_cluster.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -62,28 +62,79 @@

'''
# ---
# name: test_get_cluster[yaml-cluster8-images]
# name: test_get_cluster[yaml-cluster-images]
'''
---
clusters:
- path: tests/testdata/cluster8
- path: tests/testdata/cluster
kustomizations:
- name: apps
namespace: flux-system
path: tests/testdata/cluster8/apps
path: tests/testdata/cluster/apps/prod
helm_repos: []
helm_releases: []
helm_releases:
- name: podinfo
namespace: podinfo
chart:
name: podinfo
repo_name: podinfo
repo_namespace: flux-system
images:
- ghcr.io/stefanprodan/podinfo:6.3.2
- public.ecr.aws/docker/library/redis:7.0.6
cluster_policies: []
- name: flux-system
namespace: flux-system
path: tests/testdata/cluster8/cluster
path: tests/testdata/cluster/clusters/prod
helm_repos: []
helm_releases: []
cluster_policies: []
- name: infra-configs
namespace: flux-system
path: tests/testdata/cluster/infrastructure/configs
helm_repos:
- name: bitnami
namespace: flux-system
url: https://charts.bitnami.com/bitnami
repo_type: default
- name: podinfo
namespace: flux-system
url: oci://ghcr.io/stefanprodan/charts
repo_type: oci
- name: weave-charts
namespace: flux-system
url: oci://ghcr.io/weaveworks/charts
repo_type: oci
helm_releases: []
cluster_policies:
- name: test-allow-policy
- name: infra-controllers
namespace: flux-system
path: tests/testdata/cluster/infrastructure/controllers
helm_repos: []
helm_releases:
- name: metallb
namespace: metallb
chart:
name: metallb
repo_name: bitnami
repo_namespace: flux-system
images:
- docker.io/bitnami/metallb-speaker:0.13.7-debian-11-r28
- docker.io/bitnami/metallb-controller:0.13.7-debian-11-r29
- name: weave-gitops
namespace: flux-system
chart:
name: weave-gitops
repo_name: weave-charts
repo_namespace: flux-system
images:
- ghcr.io/weaveworks/wego-app:v0.24.0
cluster_policies: []

'''
# ---
# name: test_get_cluster[yaml]
# name: test_get_cluster[yaml-cluster-no-images]
'''
---
clusters:
Expand Down Expand Up @@ -147,3 +198,48 @@

'''
# ---
# name: test_get_cluster[yaml-cluster8-images]
'''
---
clusters:
- path: tests/testdata/cluster8
kustomizations:
- name: apps
namespace: flux-system
path: tests/testdata/cluster8/apps
helm_repos: []
helm_releases: []
cluster_policies: []
images:
- busybox
- alpine
- name: flux-system
namespace: flux-system
path: tests/testdata/cluster8/cluster
helm_repos: []
helm_releases: []
cluster_policies: []

'''
# ---
# name: test_get_cluster[yaml-cluster8-no-images]
'''
---
clusters:
- path: tests/testdata/cluster8
kustomizations:
- name: apps
namespace: flux-system
path: tests/testdata/cluster8/apps
helm_repos: []
helm_releases: []
cluster_policies: []
- name: flux-system
namespace: flux-system
path: tests/testdata/cluster8/cluster
helm_repos: []
helm_releases: []
cluster_policies: []

'''
# ---
6 changes: 5 additions & 1 deletion tests/tool/test_get_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
(["--path", "tests/testdata/cluster7"]),
(["--all-namespaces", "--path", "tests/testdata/cluster/"]),
(["--path", "tests/testdata/cluster", "-o", "yaml"]),
(["--path", "tests/testdata/cluster", "-o", "yaml", "--enable-images"]),
(["--path", "tests/testdata/cluster8", "-o", "yaml"]),
(["--path", "tests/testdata/cluster8", "-o", "yaml", "--enable-images"]),
],
ids=[
"cluster",
Expand All @@ -39,7 +41,9 @@
"cluster6",
"cluster7",
"all-namespaces",
"yaml",
"yaml-cluster-no-images",
"yaml-cluster-images",
"yaml-cluster8-no-images",
"yaml-cluster8-images"
],
)
Expand Down

0 comments on commit af813cd

Please sign in to comment.