flux_local.helm

Library for running helm template to produce local items in the cluster.

You can instantiate a helm template with the following:

  • A HelmRepository which is a url that contains charts
  • A HelmRelease which is an instance of a HelmChart in a HelmRepository

This is an example that prepares the helm repository:

from flux_local.kustomize import Kustomize
from flux_local.helm import Helm
from flux_local.manifest import HelmRepository

kustomize = Kustomize.build(TESTDATA_DIR)
repos = await kustomize.grep("kind=^HelmRepository$").objects()
helm = Helm("/tmp/path/helm", "/tmp/path/cache")
for repo in repos:
  helm.add_repo(HelmRepository.parse_doc(repo))
await helm.update()

Then to actually instantiate a template from a HelmRelease:

from flux_local.manifest import HelmRelease

releases = await kustomize.grep("kind=^HelmRelease$").objects()
if not len(releases) == 1:
    raise ValueError("Expected only one HelmRelease")
tmpl = helm.template(HelmRelease.parse_doc(releases[0]))
objects = await tmpl.objects()
for object in objects:
    print(f"Found object {object['apiVersion']} {object['kind']}")
  1"""Library for running `helm template` to produce local items in the cluster.
  2
  3You can instantiate a helm template with the following:
  4- A HelmRepository which is a url that contains charts
  5- A HelmRelease which is an instance of a HelmChart in a HelmRepository
  6
  7This is an example that prepares the helm repository:
  8```python
  9from flux_local.kustomize import Kustomize
 10from flux_local.helm import Helm
 11from flux_local.manifest import HelmRepository
 12
 13kustomize = Kustomize.build(TESTDATA_DIR)
 14repos = await kustomize.grep("kind=^HelmRepository$").objects()
 15helm = Helm("/tmp/path/helm", "/tmp/path/cache")
 16for repo in repos:
 17  helm.add_repo(HelmRepository.parse_doc(repo))
 18await helm.update()
 19```
 20
 21Then to actually instantiate a template from a HelmRelease:
 22```python
 23from flux_local.manifest import HelmRelease
 24
 25releases = await kustomize.grep("kind=^HelmRelease$").objects()
 26if not len(releases) == 1:
 27    raise ValueError("Expected only one HelmRelease")
 28tmpl = helm.template(HelmRelease.parse_doc(releases[0]))
 29objects = await tmpl.objects()
 30for object in objects:
 31    print(f"Found object {object['apiVersion']} {object['kind']}")
 32```
 33"""
 34
 35from collections.abc import Generator
 36from contextlib import contextmanager
 37import contextvars
 38import datetime
 39from dataclasses import dataclass
 40import logging
 41from pathlib import Path
 42import tempfile
 43from typing import Any
 44
 45import aiofiles
 46from aiofiles.ospath import exists
 47import yaml
 48
 49from . import command
 50from .kustomize import Kustomize
 51from .manifest import (
 52    HelmRelease,
 53    HelmRepository,
 54    OCIRepository,
 55    CRD_KIND,
 56    SECRET_KIND,
 57    REPO_TYPE_OCI,
 58    HELM_REPO_KIND,
 59    GIT_REPOSITORY,
 60    OCI_REPOSITORY,
 61    GitRepository,
 62)
 63from .source_controller.artifact import GitArtifact
 64from .exceptions import HelmException
 65
 66__all__ = [
 67    "Helm",
 68    "Options",
 69]
 70
 71_LOGGER = logging.getLogger(__name__)
 72
 73
 74HELM_BIN = "helm"
 75
 76_config_context = contextvars.ContextVar[str | None]("config_context", default=None)
 77
 78
 79@dataclass(kw_only=True, frozen=True)
 80class LocalGitRepository:
 81    """A GitRepository resolved to a local path.."""
 82
 83    repo: GitRepository
 84    """The GitRepository object."""
 85
 86    artifact: GitArtifact
 87    """The GitArtifact object."""
 88
 89    @property
 90    def repo_name(self) -> str:
 91        """Return the name of the repository."""
 92        return self.repo.repo_name
 93
 94
 95@contextmanager
 96def empty_registry_config_file() -> Generator[Path]:
 97    """Context manager a temporary JSON configuration file.
 98
 99    This is needed because some versions of helm can't handle reading /dev/null.
100    It is preferred to call this once at the start of the program to create the
101    empty json file. It may be called multiple times and it will reuse the
102    existing file.
103    """
104    if (existing_path := _config_context.get()) is not None:
105        # Reuse existing config file already created
106        yield Path(existing_path)
107        return
108
109    with tempfile.NamedTemporaryFile(
110        mode="w+",
111        suffix=".json",
112    ) as temp_file:
113        temp_file_path = Path(temp_file.name)
114        temp_file_path.write_text("{}")
115        token = _config_context.set(str(temp_file_path))
116        try:
117            yield temp_file_path
118        finally:
119            _config_context.reset(token)
120
121
122def _get_registry_config_file() -> str:
123    """Get the current registry config file."""
124    if (filename := _config_context.get()) is None:
125        raise ValueError(
126            "No registry config file found, call with empty_registry_config_file() first"
127        )
128    return filename
129
130
131def _chart_name(
132    release: HelmRelease,
133    repo: HelmRepository | OCIRepository | LocalGitRepository | None,
134) -> str:
135    """Return the helm chart name used for the helm template command."""
136    if release.chart.repo_kind == OCI_REPOSITORY:
137        if not repo:
138            raise HelmException(
139                f"Unable to find OCIRepository for {release.chart.chart_name} for "
140                f"HelmRelease {release.name}"
141            )
142        if isinstance(repo, OCIRepository):
143            return repo.url
144        raise HelmException(
145            f"HelmRelease {release.name} expected OCIRepository but got HelmRepository {repo.repo_name}"
146        )
147    if release.chart.repo_kind == HELM_REPO_KIND:
148        if not repo:
149            raise HelmException(
150                f"Unable to find HelmRepository for {release.chart.chart_name} for "
151                f"HelmRelease {release.name}"
152            )
153        if not isinstance(repo, HelmRepository):
154            raise HelmException(
155                f"HelmRelease {release.name} expected HelmRepository but got OCIRepository {repo.repo_name}"
156            )
157        return repo.helm_chart_name(release.chart)
158    if release.chart.repo_kind == GIT_REPOSITORY:
159        if isinstance(repo, LocalGitRepository):
160            return str(Path(repo.artifact.local_path) / release.chart.name)
161        _LOGGER.warning(
162            "Unable to find chart %s for HelmRelease %s, using %s",
163            release.chart.chart_name,
164            release.name,
165            release.chart.name,
166        )
167        return release.chart.name
168    raise HelmException(
169        f"Unable to find chart source for chart {release.chart.chart_name} "
170        f"kind {release.chart.repo_kind} for HelmRelease {release.name}"
171    )
172
173
174class RepositoryConfig:
175    """Generates a helm repository configuration from flux HelmRepository objects."""
176
177    def __init__(self, repos: list[HelmRepository]) -> None:
178        """Initialize RepositoryConfig."""
179        self._repos = repos
180
181    @property
182    def config(self) -> dict[str, Any]:
183        """Return a synthetic repository config object."""
184        now = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0)
185        return {
186            "apiVersion": "",
187            "generated": now.isoformat(),
188            "repositories": [
189                {
190                    "name": f"{repo.namespace}-{repo.name}",
191                    "url": repo.url,
192                }
193                for repo in self._repos
194            ],
195        }
196
197
198@dataclass
199class Options:
200    """Options to use when inflating a Helm chart.
201
202    Internally, these translate into either command line flags or
203    resource kinds that will be filtered form the output.
204    """
205
206    skip_crds: bool = True
207    """Skip CRDs when building the output."""
208
209    skip_tests: bool = True
210    """Don't run helm tests on the output."""
211
212    skip_secrets: bool = False
213    """Don't emit secrets in the output."""
214
215    skip_kinds: list[str] | None = None
216    """Omit these kinds in the output."""
217
218    kube_version: str | None = None
219    """Value of the helm --kube-version flag."""
220
221    api_versions: str | None = None
222    """Value of the helm --api-versions flag."""
223
224    registry_config: str | None = None
225    """Value of the helm --registry-config flag."""
226
227    skip_invalid_paths: bool = False
228    """Skip HelmReleases with invalid local paths."""
229
230    @property
231    def base_args(self) -> list[str]:
232        """Helm template CLI arguments built from the options."""
233        return [
234            "--registry-config",
235            self.registry_config or _get_registry_config_file(),
236        ]
237
238    @property
239    def template_args(self) -> list[str]:
240        """Helm template CLI arguments built from the options."""
241        args = self.base_args
242        if self.skip_crds:
243            args.append("--skip-crds")
244        if self.skip_tests:
245            args.append("--skip-tests")
246        if self.kube_version:
247            args.extend(["--kube-version", self.kube_version])
248        if self.api_versions:
249            args.extend(["--api-versions", self.api_versions])
250        return args
251
252    @property
253    def skip_resources(self) -> list[str]:
254        """A list of CRD resources to filter from the output."""
255        skips = []
256        if self.skip_crds:
257            skips.append(CRD_KIND)
258        if self.skip_secrets:
259            skips.append(SECRET_KIND)
260        if self.skip_kinds:
261            skips.extend(self.skip_kinds)
262        return skips
263
264
265class Helm:
266    """Manages local HelmRepository state."""
267
268    def __init__(self, tmp_dir: Path, cache_dir: Path) -> None:
269        """Initialize Helm."""
270        self._tmp_dir = tmp_dir
271        self._repo_config_file = self._tmp_dir / "repository-config.yaml"
272        self._flags = [
273            "--repository-cache",
274            str(cache_dir),
275            "--repository-config",
276            str(self._repo_config_file),
277        ]
278        self._repos: list[HelmRepository | OCIRepository | LocalGitRepository] = []
279
280    def add_repo(
281        self, repo: HelmRepository | OCIRepository | LocalGitRepository
282    ) -> None:
283        """Add the specified HelmRepository to the local config."""
284        self._repos.append(repo)
285
286    def add_repos(
287        self,
288        repos: (
289            list[HelmRepository]
290            | list[OCIRepository]
291            | list[HelmRepository | OCIRepository]
292            | list[HelmRepository | OCIRepository | LocalGitRepository]
293        ),
294    ) -> None:
295        """Add the specified HelmRepository to the local config."""
296        for repo in repos:
297            self._repos.append(repo)
298
299    async def update(self) -> None:
300        """Run the update command to update the local repo.
301
302        Typically the repository must be updated before doing any chart templating.
303        """
304        _LOGGER.debug("Updating %d repositories", len(self._repos))
305        helm_repos = [
306            repo
307            for repo in self._repos
308            if isinstance(repo, HelmRepository) and repo.repo_type != REPO_TYPE_OCI
309        ]
310        if not helm_repos:
311            return
312        content = yaml.dump(RepositoryConfig(helm_repos).config, sort_keys=False)
313        async with aiofiles.open(str(self._repo_config_file), mode="w") as config_file:
314            await config_file.write(content)
315        with empty_registry_config_file():
316            args = [HELM_BIN, "repo", "update"]
317            args.extend(Options().base_args)
318            args.extend(self._flags)
319            await command.run(command.Command(args, exc=HelmException))
320
321    async def is_invalid_local_path(
322        self,
323        release: HelmRelease,
324    ) -> bool:
325        """Check if the HelmRelease has an invalid GitRepository path."""
326        repo = next(
327            iter([repo for repo in self._repos if repo.repo_name == release.repo_name]),
328            None,
329        )
330        chart_name = _chart_name(release, repo)
331        if release.chart.repo_kind == GIT_REPOSITORY:
332            if not await exists(chart_name):
333                return True
334        return False
335
336    async def template(
337        self,
338        release: HelmRelease,
339        options: Options | None = None,
340    ) -> Kustomize:
341        """Return command to evaluate the template of the specified chart.
342
343        The values will come from the `HelmRelease` object.
344        """
345        if options is None:
346            options = Options()
347        repo = next(
348            iter([repo for repo in self._repos if repo.repo_name == release.repo_name]),
349            None,
350        )
351        with empty_registry_config_file():
352            args: list[str] = [
353                HELM_BIN,
354                "template",
355                release.name,
356                _chart_name(release, repo),
357                "--namespace",
358                release.release_namespace,
359            ]
360            args.extend(self._flags)
361            args.extend(options.template_args)
362            if release.disable_openapi_validation:
363                args.append("--disable-openapi-validation")
364            if release.disable_schema_validation:
365                args.append("--skip-schema-validation")
366            if release.chart.version:
367                args.extend(
368                    [
369                        "--version",
370                        release.chart.version,
371                    ]
372                )
373            elif isinstance(repo, OCIRepository) and (oci_version := repo.version()):
374                args.extend(
375                    [
376                        "--version",
377                        oci_version,
378                    ]
379                )
380            if release.values:
381                values_path = self._tmp_dir / f"{release.release_name}-values.yaml"
382                async with aiofiles.open(values_path, mode="w") as values_file:
383                    await values_file.write(yaml.dump(release.values, sort_keys=False))
384                args.extend(["--values", str(values_path)])
385            cmd = Kustomize([command.Command(args, exc=HelmException)])
386            if options.skip_resources:
387                cmd = cmd.skip_resources(options.skip_resources)
388            return cmd
class Helm:
266class Helm:
267    """Manages local HelmRepository state."""
268
269    def __init__(self, tmp_dir: Path, cache_dir: Path) -> None:
270        """Initialize Helm."""
271        self._tmp_dir = tmp_dir
272        self._repo_config_file = self._tmp_dir / "repository-config.yaml"
273        self._flags = [
274            "--repository-cache",
275            str(cache_dir),
276            "--repository-config",
277            str(self._repo_config_file),
278        ]
279        self._repos: list[HelmRepository | OCIRepository | LocalGitRepository] = []
280
281    def add_repo(
282        self, repo: HelmRepository | OCIRepository | LocalGitRepository
283    ) -> None:
284        """Add the specified HelmRepository to the local config."""
285        self._repos.append(repo)
286
287    def add_repos(
288        self,
289        repos: (
290            list[HelmRepository]
291            | list[OCIRepository]
292            | list[HelmRepository | OCIRepository]
293            | list[HelmRepository | OCIRepository | LocalGitRepository]
294        ),
295    ) -> None:
296        """Add the specified HelmRepository to the local config."""
297        for repo in repos:
298            self._repos.append(repo)
299
300    async def update(self) -> None:
301        """Run the update command to update the local repo.
302
303        Typically the repository must be updated before doing any chart templating.
304        """
305        _LOGGER.debug("Updating %d repositories", len(self._repos))
306        helm_repos = [
307            repo
308            for repo in self._repos
309            if isinstance(repo, HelmRepository) and repo.repo_type != REPO_TYPE_OCI
310        ]
311        if not helm_repos:
312            return
313        content = yaml.dump(RepositoryConfig(helm_repos).config, sort_keys=False)
314        async with aiofiles.open(str(self._repo_config_file), mode="w") as config_file:
315            await config_file.write(content)
316        with empty_registry_config_file():
317            args = [HELM_BIN, "repo", "update"]
318            args.extend(Options().base_args)
319            args.extend(self._flags)
320            await command.run(command.Command(args, exc=HelmException))
321
322    async def is_invalid_local_path(
323        self,
324        release: HelmRelease,
325    ) -> bool:
326        """Check if the HelmRelease has an invalid GitRepository path."""
327        repo = next(
328            iter([repo for repo in self._repos if repo.repo_name == release.repo_name]),
329            None,
330        )
331        chart_name = _chart_name(release, repo)
332        if release.chart.repo_kind == GIT_REPOSITORY:
333            if not await exists(chart_name):
334                return True
335        return False
336
337    async def template(
338        self,
339        release: HelmRelease,
340        options: Options | None = None,
341    ) -> Kustomize:
342        """Return command to evaluate the template of the specified chart.
343
344        The values will come from the `HelmRelease` object.
345        """
346        if options is None:
347            options = Options()
348        repo = next(
349            iter([repo for repo in self._repos if repo.repo_name == release.repo_name]),
350            None,
351        )
352        with empty_registry_config_file():
353            args: list[str] = [
354                HELM_BIN,
355                "template",
356                release.name,
357                _chart_name(release, repo),
358                "--namespace",
359                release.release_namespace,
360            ]
361            args.extend(self._flags)
362            args.extend(options.template_args)
363            if release.disable_openapi_validation:
364                args.append("--disable-openapi-validation")
365            if release.disable_schema_validation:
366                args.append("--skip-schema-validation")
367            if release.chart.version:
368                args.extend(
369                    [
370                        "--version",
371                        release.chart.version,
372                    ]
373                )
374            elif isinstance(repo, OCIRepository) and (oci_version := repo.version()):
375                args.extend(
376                    [
377                        "--version",
378                        oci_version,
379                    ]
380                )
381            if release.values:
382                values_path = self._tmp_dir / f"{release.release_name}-values.yaml"
383                async with aiofiles.open(values_path, mode="w") as values_file:
384                    await values_file.write(yaml.dump(release.values, sort_keys=False))
385                args.extend(["--values", str(values_path)])
386            cmd = Kustomize([command.Command(args, exc=HelmException)])
387            if options.skip_resources:
388                cmd = cmd.skip_resources(options.skip_resources)
389            return cmd

Manages local HelmRepository state.

Helm(tmp_dir: pathlib._local.Path, cache_dir: pathlib._local.Path)
269    def __init__(self, tmp_dir: Path, cache_dir: Path) -> None:
270        """Initialize Helm."""
271        self._tmp_dir = tmp_dir
272        self._repo_config_file = self._tmp_dir / "repository-config.yaml"
273        self._flags = [
274            "--repository-cache",
275            str(cache_dir),
276            "--repository-config",
277            str(self._repo_config_file),
278        ]
279        self._repos: list[HelmRepository | OCIRepository | LocalGitRepository] = []

Initialize Helm.

def add_repo( self, repo: flux_local.manifest.HelmRepository | flux_local.manifest.OCIRepository | flux_local.helm.LocalGitRepository) -> None:
281    def add_repo(
282        self, repo: HelmRepository | OCIRepository | LocalGitRepository
283    ) -> None:
284        """Add the specified HelmRepository to the local config."""
285        self._repos.append(repo)

Add the specified HelmRepository to the local config.

def add_repos( self, repos: list[flux_local.manifest.HelmRepository] | list[flux_local.manifest.OCIRepository] | list[flux_local.manifest.HelmRepository | flux_local.manifest.OCIRepository] | list[flux_local.manifest.HelmRepository | flux_local.manifest.OCIRepository | flux_local.helm.LocalGitRepository]) -> None:
287    def add_repos(
288        self,
289        repos: (
290            list[HelmRepository]
291            | list[OCIRepository]
292            | list[HelmRepository | OCIRepository]
293            | list[HelmRepository | OCIRepository | LocalGitRepository]
294        ),
295    ) -> None:
296        """Add the specified HelmRepository to the local config."""
297        for repo in repos:
298            self._repos.append(repo)

Add the specified HelmRepository to the local config.

async def update(self) -> None:
300    async def update(self) -> None:
301        """Run the update command to update the local repo.
302
303        Typically the repository must be updated before doing any chart templating.
304        """
305        _LOGGER.debug("Updating %d repositories", len(self._repos))
306        helm_repos = [
307            repo
308            for repo in self._repos
309            if isinstance(repo, HelmRepository) and repo.repo_type != REPO_TYPE_OCI
310        ]
311        if not helm_repos:
312            return
313        content = yaml.dump(RepositoryConfig(helm_repos).config, sort_keys=False)
314        async with aiofiles.open(str(self._repo_config_file), mode="w") as config_file:
315            await config_file.write(content)
316        with empty_registry_config_file():
317            args = [HELM_BIN, "repo", "update"]
318            args.extend(Options().base_args)
319            args.extend(self._flags)
320            await command.run(command.Command(args, exc=HelmException))

Run the update command to update the local repo.

Typically the repository must be updated before doing any chart templating.

async def is_invalid_local_path(self, release: flux_local.manifest.HelmRelease) -> bool:
322    async def is_invalid_local_path(
323        self,
324        release: HelmRelease,
325    ) -> bool:
326        """Check if the HelmRelease has an invalid GitRepository path."""
327        repo = next(
328            iter([repo for repo in self._repos if repo.repo_name == release.repo_name]),
329            None,
330        )
331        chart_name = _chart_name(release, repo)
332        if release.chart.repo_kind == GIT_REPOSITORY:
333            if not await exists(chart_name):
334                return True
335        return False

Check if the HelmRelease has an invalid GitRepository path.

async def template( self, release: flux_local.manifest.HelmRelease, options: Options | None = None) -> flux_local.kustomize.Kustomize:
337    async def template(
338        self,
339        release: HelmRelease,
340        options: Options | None = None,
341    ) -> Kustomize:
342        """Return command to evaluate the template of the specified chart.
343
344        The values will come from the `HelmRelease` object.
345        """
346        if options is None:
347            options = Options()
348        repo = next(
349            iter([repo for repo in self._repos if repo.repo_name == release.repo_name]),
350            None,
351        )
352        with empty_registry_config_file():
353            args: list[str] = [
354                HELM_BIN,
355                "template",
356                release.name,
357                _chart_name(release, repo),
358                "--namespace",
359                release.release_namespace,
360            ]
361            args.extend(self._flags)
362            args.extend(options.template_args)
363            if release.disable_openapi_validation:
364                args.append("--disable-openapi-validation")
365            if release.disable_schema_validation:
366                args.append("--skip-schema-validation")
367            if release.chart.version:
368                args.extend(
369                    [
370                        "--version",
371                        release.chart.version,
372                    ]
373                )
374            elif isinstance(repo, OCIRepository) and (oci_version := repo.version()):
375                args.extend(
376                    [
377                        "--version",
378                        oci_version,
379                    ]
380                )
381            if release.values:
382                values_path = self._tmp_dir / f"{release.release_name}-values.yaml"
383                async with aiofiles.open(values_path, mode="w") as values_file:
384                    await values_file.write(yaml.dump(release.values, sort_keys=False))
385                args.extend(["--values", str(values_path)])
386            cmd = Kustomize([command.Command(args, exc=HelmException)])
387            if options.skip_resources:
388                cmd = cmd.skip_resources(options.skip_resources)
389            return cmd

Return command to evaluate the template of the specified chart.

The values will come from the HelmRelease object.

@dataclass
class Options:
199@dataclass
200class Options:
201    """Options to use when inflating a Helm chart.
202
203    Internally, these translate into either command line flags or
204    resource kinds that will be filtered form the output.
205    """
206
207    skip_crds: bool = True
208    """Skip CRDs when building the output."""
209
210    skip_tests: bool = True
211    """Don't run helm tests on the output."""
212
213    skip_secrets: bool = False
214    """Don't emit secrets in the output."""
215
216    skip_kinds: list[str] | None = None
217    """Omit these kinds in the output."""
218
219    kube_version: str | None = None
220    """Value of the helm --kube-version flag."""
221
222    api_versions: str | None = None
223    """Value of the helm --api-versions flag."""
224
225    registry_config: str | None = None
226    """Value of the helm --registry-config flag."""
227
228    skip_invalid_paths: bool = False
229    """Skip HelmReleases with invalid local paths."""
230
231    @property
232    def base_args(self) -> list[str]:
233        """Helm template CLI arguments built from the options."""
234        return [
235            "--registry-config",
236            self.registry_config or _get_registry_config_file(),
237        ]
238
239    @property
240    def template_args(self) -> list[str]:
241        """Helm template CLI arguments built from the options."""
242        args = self.base_args
243        if self.skip_crds:
244            args.append("--skip-crds")
245        if self.skip_tests:
246            args.append("--skip-tests")
247        if self.kube_version:
248            args.extend(["--kube-version", self.kube_version])
249        if self.api_versions:
250            args.extend(["--api-versions", self.api_versions])
251        return args
252
253    @property
254    def skip_resources(self) -> list[str]:
255        """A list of CRD resources to filter from the output."""
256        skips = []
257        if self.skip_crds:
258            skips.append(CRD_KIND)
259        if self.skip_secrets:
260            skips.append(SECRET_KIND)
261        if self.skip_kinds:
262            skips.extend(self.skip_kinds)
263        return skips

Options to use when inflating a Helm chart.

Internally, these translate into either command line flags or resource kinds that will be filtered form the output.

Options( skip_crds: bool = True, skip_tests: bool = True, skip_secrets: bool = False, skip_kinds: list[str] | None = None, kube_version: str | None = None, api_versions: str | None = None, registry_config: str | None = None, skip_invalid_paths: bool = False)
skip_crds: bool = True

Skip CRDs when building the output.

skip_tests: bool = True

Don't run helm tests on the output.

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.

api_versions: str | None = None

Value of the helm --api-versions flag.

registry_config: str | None = None

Value of the helm --registry-config flag.

skip_invalid_paths: bool = False

Skip HelmReleases with invalid local paths.

base_args: list[str]
231    @property
232    def base_args(self) -> list[str]:
233        """Helm template CLI arguments built from the options."""
234        return [
235            "--registry-config",
236            self.registry_config or _get_registry_config_file(),
237        ]

Helm template CLI arguments built from the options.

template_args: list[str]
239    @property
240    def template_args(self) -> list[str]:
241        """Helm template CLI arguments built from the options."""
242        args = self.base_args
243        if self.skip_crds:
244            args.append("--skip-crds")
245        if self.skip_tests:
246            args.append("--skip-tests")
247        if self.kube_version:
248            args.extend(["--kube-version", self.kube_version])
249        if self.api_versions:
250            args.extend(["--api-versions", self.api_versions])
251        return args

Helm template CLI arguments built from the options.

skip_resources: list[str]
253    @property
254    def skip_resources(self) -> list[str]:
255        """A list of CRD resources to filter from the output."""
256        skips = []
257        if self.skip_crds:
258            skips.append(CRD_KIND)
259        if self.skip_secrets:
260            skips.append(SECRET_KIND)
261        if self.skip_kinds:
262            skips.extend(self.skip_kinds)
263        return skips

A list of CRD resources to filter from the output.