diff --git a/pyhelm3/client.py b/pyhelm3/client.py index 7039e52..adc20a9 100644 --- a/pyhelm3/client.py +++ b/pyhelm3/client.py @@ -12,12 +12,12 @@ def mergeconcat( - defaults: t.Dict[t.Any, t.Any], - *overrides: t.Dict[t.Any, t.Any] + defaults: t.Dict[t.Any, t.Any], *overrides: t.Dict[t.Any, t.Any] ) -> t.Dict[t.Any, t.Any]: """ Deep-merge two or more dictionaries together. Lists are concatenated. """ + def mergeconcat2(defaults, overrides): if isinstance(defaults, dict) and isinstance(overrides, dict): merged = dict(defaults) @@ -27,23 +27,27 @@ def mergeconcat2(defaults, overrides): else: merged[key] = value return merged - elif isinstance(defaults, (list, tuple)) and isinstance(overrides, (list, tuple)): + elif isinstance(defaults, (list, tuple)) and isinstance( + overrides, (list, tuple) + ): merged = list(defaults) merged.extend(overrides) return merged else: return overrides if overrides is not None else defaults + return functools.reduce(mergeconcat2, overrides, defaults) #: Bound type var for forward references -ClientType = t.TypeVar("ClientType", bound = "Client") +ClientType = t.TypeVar("ClientType", bound="Client") class Client: """ Entrypoint for interactions with Helm. """ + def __init__( self, command: t.Optional[Command] = None, @@ -56,18 +60,18 @@ def __init__( kubecontext: t.Optional[str] = None, kubeapiserver: t.Optional[str] = None, kubetoken: t.Optional[str] = None, - unpack_directory: t.Optional[str] = None + unpack_directory: t.Optional[str] = None, ): self._command = command or Command( - default_timeout = default_timeout, - executable = executable, - history_max_revisions = history_max_revisions, - insecure_skip_tls_verify = insecure_skip_tls_verify, - kubeconfig = kubeconfig, - kubecontext = kubecontext, - kubeapiserver = kubeapiserver, - kubetoken = kubetoken, - unpack_directory = unpack_directory + default_timeout=default_timeout, + executable=executable, + history_max_revisions=history_max_revisions, + insecure_skip_tls_verify=insecure_skip_tls_verify, + kubeconfig=kubeconfig, + kubecontext=kubecontext, + kubeapiserver=kubeapiserver, + kubetoken=kubetoken, + unpack_directory=unpack_directory, ) async def get_chart( @@ -76,22 +80,26 @@ async def get_chart( *, devel: bool = False, repo: t.Optional[str] = None, - version: t.Optional[str] = None + version: t.Optional[str] = None, + username: t.Optional[str] = None, + password: t.Optional[str] = None, ) -> Chart: """ Returns the resolved chart for the given ref, repo and version. """ return Chart( self._command, - ref = chart_ref, - repo = repo, + ref=chart_ref, + repo=repo, # Load the metadata for the specified args - metadata = await self._command.show_chart( + metadata=await self._command.show_chart( chart_ref, - devel = devel, - repo = repo, - version = version - ) + devel=devel, + repo=repo, + version=version, + username=username, + password=password, + ), ) @contextlib.asynccontextmanager @@ -101,7 +109,7 @@ async def pull_chart( *, devel: bool = False, repo: t.Optional[str] = None, - version: t.Optional[str] = None + version: t.Optional[str] = None, ) -> t.AsyncIterator[pathlib.Path]: """ Context manager that pulls the specified chart and yields a chart object @@ -110,10 +118,7 @@ async def pull_chart( Ensures that the directory is cleaned up when the context manager exits. """ path = await self._command.pull( - chart_ref, - devel = devel, - repo = repo, - version = version + chart_ref, devel=devel, repo=repo, version=version ) try: # The path from pull is the managed directory containing the archive and unpacked chart @@ -122,9 +127,9 @@ async def pull_chart( chart_directory = chart_yaml.parent # To save the overhead of another Helm command invocation, just read the Chart.yaml with chart_yaml.open() as fh: - metadata = yaml.load(fh, Loader = SafeLoader) + metadata = yaml.load(fh, Loader=SafeLoader) # Yield the chart object - yield Chart(self._command, ref = chart_directory, metadata = metadata) + yield Chart(self._command, ref=chart_directory, metadata=metadata) finally: if path.is_dir(): shutil.rmtree(path) @@ -138,7 +143,7 @@ async def template_resources( is_upgrade: bool = False, namespace: t.Optional[str] = None, no_hooks: bool = False, - ) -> t.Iterable[t.Dict[str, t.Any]]: + ) -> t.Iterable[t.Dict[str, t.Any]]: """ Renders the templates from the given chart with the given values and returns the resources that would be produced. @@ -147,12 +152,12 @@ async def template_resources( release_name, chart.ref, mergeconcat(*values) if values else None, - include_crds = include_crds, - is_upgrade = is_upgrade, - namespace = namespace, - no_hooks = no_hooks, - repo = chart.repo, - version = chart.metadata.version + include_crds=include_crds, + is_upgrade=is_upgrade, + namespace=namespace, + no_hooks=no_hooks, + repo=chart.repo, + version=chart.metadata.version, ) async def list_releases( @@ -169,7 +174,7 @@ async def list_releases( max_releases: int = 256, namespace: t.Optional[str] = None, sort_by_date: bool = False, - sort_reversed: bool = False + sort_reversed: bool = False, ) -> t.Iterable[Release]: """ Returns an iterable of the deployed releases. @@ -177,40 +182,33 @@ async def list_releases( return ( Release( self._command, - name = release["name"], - namespace = release["namespace"], + name=release["name"], + namespace=release["namespace"], ) for release in await self._command.list( - all = all, - all_namespaces = all_namespaces, - include_deployed = include_deployed, - include_failed = include_failed, - include_pending = include_pending, - include_superseded = include_superseded, - include_uninstalled = include_uninstalled, - include_uninstalling = include_uninstalling, - max_releases = max_releases, - namespace = namespace, - sort_by_date = sort_by_date, - sort_reversed = sort_reversed + all=all, + all_namespaces=all_namespaces, + include_deployed=include_deployed, + include_failed=include_failed, + include_pending=include_pending, + include_superseded=include_superseded, + include_uninstalled=include_uninstalled, + include_uninstalling=include_uninstalling, + max_releases=max_releases, + namespace=namespace, + sort_by_date=sort_by_date, + sort_reversed=sort_reversed, ) ) async def get_current_revision( - self, - release_name: str, - *, - namespace: t.Optional[str] = None + self, release_name: str, *, namespace: t.Optional[str] = None ) -> ReleaseRevision: """ Returns the current revision of the named release. """ return ReleaseRevision._from_status( - await self._command.status( - release_name, - namespace = namespace - ), - self._command + await self._command.status(release_name, namespace=namespace), self._command ) async def install_or_upgrade_release( @@ -232,6 +230,8 @@ async def install_or_upgrade_release( timeout: t.Union[int, str, None] = None, wait: bool = False, disable_validation: bool = False, + username: t.Optional[str] = None, + password: t.Optional[str] = None, ) -> ReleaseRevision: """ Install or upgrade the named release using the given chart and values and return @@ -242,24 +242,26 @@ async def install_or_upgrade_release( release_name, chart.ref, mergeconcat(*values) if values else None, - atomic = atomic, - cleanup_on_fail = cleanup_on_fail, - create_namespace = create_namespace, - description = description, - dry_run = dry_run, - force = force, - namespace = namespace, - no_hooks = no_hooks, - repo = chart.repo, - reset_values = reset_values, - reuse_values = reuse_values, - skip_crds = skip_crds, - timeout = timeout, - version = chart.metadata.version, - wait = wait, - disable_validation = disable_validation, + atomic=atomic, + cleanup_on_fail=cleanup_on_fail, + create_namespace=create_namespace, + description=description, + dry_run=dry_run, + force=force, + namespace=namespace, + no_hooks=no_hooks, + repo=chart.repo, + reset_values=reset_values, + reuse_values=reuse_values, + skip_crds=skip_crds, + timeout=timeout, + version=chart.metadata.version, + wait=wait, + disable_validation=disable_validation, + username=username, + password=password, ), - self._command + self._command, ) async def get_proceedable_revision( @@ -267,7 +269,7 @@ async def get_proceedable_revision( release_name: str, *, namespace: t.Optional[str] = None, - timeout: t.Union[int, str, None] = None + timeout: t.Union[int, str, None] = None, ) -> ReleaseRevision: """ Returns a proceedable revision for the named release by rolling back or deleting @@ -275,8 +277,7 @@ async def get_proceedable_revision( """ try: current_revision = await self.get_current_revision( - release_name, - namespace = namespace + release_name, namespace=namespace ) except ReleaseNotFoundError: # This condition is an easy one ;-) @@ -289,7 +290,7 @@ async def get_proceedable_revision( # If the release is stuck in uninstalling, we need to complete the uninstall ReleaseRevisionStatus.UNINSTALLING, }: - await current_revision.release.uninstall(timeout = timeout, wait = True) + await current_revision.release.uninstall(timeout=timeout, wait=True) return None elif current_revision.status in { # If the release is stuck in pending-upgrade, we need to rollback to the previous @@ -299,9 +300,7 @@ async def get_proceedable_revision( ReleaseRevisionStatus.PENDING_ROLLBACK, }: return await current_revision.release.rollback( - cleanup_on_fail = True, - timeout = timeout, - wait = True + cleanup_on_fail=True, timeout=timeout, wait=True ) else: # All other statuses are proceedable @@ -311,7 +310,7 @@ async def should_install_or_upgrade_release( self, current_revision: t.Optional[ReleaseRevision], chart: Chart, - *values: t.Dict[str, t.Any] + *values: t.Dict[str, t.Any], ) -> bool: """ Returns True if an install or upgrade is required based on the given revision, @@ -354,7 +353,7 @@ async def ensure_release( reuse_values: bool = False, skip_crds: bool = False, timeout: t.Union[int, str, None] = None, - wait: bool = False + wait: bool = False, ) -> ReleaseRevision: """ Ensures the named release matches the given chart and values and return the current @@ -366,32 +365,28 @@ async def ensure_release( """ values = mergeconcat(*values) if values else {} current_revision = await self.get_proceedable_revision( - release_name, - namespace = namespace, - timeout = timeout + release_name, namespace=namespace, timeout=timeout ) should_install_or_upgrade = await self.should_install_or_upgrade_release( - current_revision, - chart, - values + current_revision, chart, values ) if should_install_or_upgrade: return await self.install_or_upgrade_release( release_name, chart, values, - atomic = atomic, - cleanup_on_fail = cleanup_on_fail, - create_namespace = create_namespace, - description = description, - force = force, - namespace = namespace, - no_hooks = no_hooks, - reset_values = reset_values, - reuse_values = reuse_values, - skip_crds = skip_crds, - timeout = timeout, - wait = wait + atomic=atomic, + cleanup_on_fail=cleanup_on_fail, + create_namespace=create_namespace, + description=description, + force=force, + namespace=namespace, + no_hooks=no_hooks, + reset_values=reset_values, + reuse_values=reuse_values, + skip_crds=skip_crds, + timeout=timeout, + wait=wait, ) else: return current_revision @@ -405,7 +400,7 @@ async def uninstall_release( namespace: t.Optional[str] = None, no_hooks: bool = False, timeout: t.Union[int, str, None] = None, - wait: bool = False + wait: bool = False, ): """ Uninstall the named release. @@ -413,12 +408,12 @@ async def uninstall_release( try: await self._command.uninstall( release_name, - dry_run = dry_run, - keep_history = keep_history, - namespace = namespace, - no_hooks = no_hooks, - timeout = timeout, - wait = wait + dry_run=dry_run, + keep_history=keep_history, + namespace=namespace, + no_hooks=no_hooks, + timeout=timeout, + wait=wait, ) except ReleaseNotFoundError: # If the release does not exist, it is deleted :-) diff --git a/pyhelm3/command.py b/pyhelm3/command.py index 23b852d..380a058 100644 --- a/pyhelm3/command.py +++ b/pyhelm3/command.py @@ -22,10 +22,12 @@ class SafeLoader(yaml.SafeLoader): https://github.com/yaml/pyyaml/issues/89 https://yaml.org/type/value.html """ + @staticmethod def construct_value(loader, node): return loader.construct_scalar(node) + SafeLoader.add_constructor("tag:yaml.org,2002:value", SafeLoader.construct_value) @@ -125,17 +127,20 @@ def construct_value(loader, node): #: Bound type var for forward references -CommandType = t.TypeVar("CommandType", bound = "Command") +CommandType = t.TypeVar("CommandType", bound="Command") CHART_NOT_FOUND = re.compile(r"chart \"[^\"]+\" (version \"[^\"]+\" )?not found") -CONNECTION_ERROR = re.compile(r"(read: operation timed out|connect: network is unreachable)") +CONNECTION_ERROR = re.compile( + r"(read: operation timed out|connect: network is unreachable)" +) class Command: """ Class presenting an async interface around the Helm CLI. """ + def __init__( self, *, @@ -147,7 +152,7 @@ def __init__( kubecontext: t.Optional[str] = None, kubeapiserver: t.Optional[str] = None, kubetoken: t.Optional[str] = None, - unpack_directory: t.Optional[str] = None + unpack_directory: t.Optional[str] = None, ): self._logger = logging.getLogger(__name__) self._default_timeout = default_timeout @@ -186,23 +191,22 @@ async def run(self, command: t.List[str], input: t.Optional[bytes] = None) -> by command.append("--kube-insecure-skip-tls-verify") # The command must be made up of str and bytes, so convert anything that isn't shell_formatted_command = shlex.join( - part if isinstance(part, (str, bytes)) else str(part) - for part in command + part if isinstance(part, (str, bytes)) else str(part) for part in command ) log_formatted_command = shlex.join(self._log_format(part) for part in command) self._logger.info("running command: %s", log_formatted_command) proc = await asyncio.create_subprocess_shell( shell_formatted_command, # Only make stdin a pipe if we have input to feed it - stdin = asyncio.subprocess.PIPE if input is not None else None, - stdout = asyncio.subprocess.PIPE, - stderr = asyncio.subprocess.PIPE + stdin=asyncio.subprocess.PIPE if input is not None else None, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, ) try: stdout, stderr = await proc.communicate(input) except asyncio.CancelledError: # If the asyncio task is cancelled, terminate the Helm process but let the - # process handle the termination and exit + # process handle the termination and exit # We occassionally see a ProcessLookupError here if the process finished between # us being cancelled and terminating the process, which we ignore as that is our # target state anyway @@ -232,7 +236,10 @@ async def run(self, command: t.List[str], input: t.Optional[bytes] = None) -> by error_cls = errors.FailedToRenderChartError elif "execution error" in stderr_str: error_cls = errors.FailedToRenderChartError - elif "rendered manifests contain a resource that already exists" in stderr_str: + elif ( + "rendered manifests contain a resource that already exists" + in stderr_str + ): error_cls = errors.ResourceAlreadyExistsError elif "is invalid" in stderr_str: error_cls = errors.InvalidResourceError @@ -253,7 +260,7 @@ async def diff_release( context_lines: t.Optional[int] = None, namespace: t.Optional[str] = None, # Indicates whether to show secret values in the diff - show_secrets: bool = True + show_secrets: bool = True, ) -> str: """ Returns the diff between two releases created from the same chart. @@ -285,7 +292,7 @@ async def diff_revision( context_lines: t.Optional[int] = None, namespace: t.Optional[str] = None, # Indicates whether to show secret values in the diff - show_secrets: bool = True + show_secrets: bool = True, ) -> str: """ Returns the diff between two revisions of the specified release. @@ -319,7 +326,7 @@ async def diff_rollback( context_lines: t.Optional[int] = None, namespace: t.Optional[str] = None, # Indicates whether to show secret values in the diff - show_secrets: bool = True + show_secrets: bool = True, ) -> str: """ Returns the diff that would result from rolling back the given release @@ -359,7 +366,7 @@ async def diff_upgrade( reuse_values: bool = False, # Indicates whether to show secret values in the diff show_secrets: bool = True, - version: t.Optional[str] = None + version: t.Optional[str] = None, ) -> str: """ Returns the diff that would result from rolling back the given release @@ -376,7 +383,8 @@ async def diff_upgrade( # Disable OpenAPI validation as we still want the diff to work when CRDs change "--disable-openapi-validation", # We pass the values using stdin - "--values", "-", + "--values", + "-", ] if context_lines is not None: command.extend(["--context", context_lines]) @@ -413,7 +421,7 @@ async def get_chart_metadata( release_name: str, *, namespace: t.Optional[str] = None, - revision: t.Optional[int] = None + revision: t.Optional[int] = None, ): """ Returns metadata for the chart that was used to deploy the release. @@ -425,21 +433,22 @@ async def get_chart_metadata( "all", release_name, # Use the chart metadata template - "--template", CHART_METADATA_TEMPLATE + "--template", + CHART_METADATA_TEMPLATE, ] if namespace: command.extend(["--namespace", namespace]) if revision is not None: command.extend(["--revision", revision]) - return yaml.load(await self.run(command), Loader = SafeLoader) + return yaml.load(await self.run(command), Loader=SafeLoader) async def get_hooks( self, release_name: str, *, namespace: t.Optional[str] = None, - revision: t.Optional[int] = None - ) -> t.Iterable[t.Dict[str, t.Any]]: + revision: t.Optional[int] = None, + ) -> t.Iterable[t.Dict[str, t.Any]]: """ Returns the hooks for the specified release. """ @@ -448,15 +457,15 @@ async def get_hooks( command.extend(["--revision", revision]) if namespace: command.extend(["--namespace", namespace]) - return yaml.load_all(await self.run(command), Loader = SafeLoader) + return yaml.load_all(await self.run(command), Loader=SafeLoader) async def get_resources( self, release_name: str, *, namespace: t.Optional[str] = None, - revision: t.Optional[int] = None - ) -> t.Iterable[t.Dict[str, t.Any]]: + revision: t.Optional[int] = None, + ) -> t.Iterable[t.Dict[str, t.Any]]: """ Returns the resources for the specified release. """ @@ -465,7 +474,7 @@ async def get_resources( command.extend(["--revision", revision]) if namespace: command.extend(["--namespace", namespace]) - return yaml.load_all(await self.run(command), Loader = SafeLoader) + return yaml.load_all(await self.run(command), Loader=SafeLoader) async def get_values( self, @@ -473,8 +482,8 @@ async def get_values( *, computed: bool = False, namespace: t.Optional[str] = None, - revision: t.Optional[int] = None - ) -> t.Dict[str, t.Any]: + revision: t.Optional[int] = None, + ) -> t.Dict[str, t.Any]: """ Returns the values for the specified release. @@ -494,8 +503,8 @@ async def history( release_name: str, *, max_revisions: int = 256, - namespace: t.Optional[str] = None - ) -> t.Iterable[t.Dict[str, t.Any]]: + namespace: t.Optional[str] = None, + ) -> t.Iterable[t.Dict[str, t.Any]]: """ Returns the historical revisions for the specified release. @@ -530,7 +539,9 @@ async def install_or_upgrade( version: t.Optional[str] = None, wait: bool = False, disable_validation: bool = False, - ) -> t.Iterable[t.Dict[str, t.Any]]: + username: t.Optional[str] = None, + password: t.Optional[str] = None, + ) -> t.Iterable[t.Dict[str, t.Any]]: """ Installs or upgrades the specified release using the given chart and values. """ @@ -538,14 +549,20 @@ async def install_or_upgrade( "upgrade", release_name, chart_ref, - "--history-max", self._history_max_revisions, + "--history-max", + self._history_max_revisions, "--install", - "--output", "json", + "--output", + "json", # Use the default timeout unless an override is specified - "--timeout", timeout if timeout is not None else self._default_timeout, + "--timeout", + timeout if timeout is not None else self._default_timeout, # We send the values in on stdin - "--values", "-", + "--values", + "-", ] + if username and password: + command.extend(["--username", username, "--password", password]) if atomic: command.append("--atomic") if cleanup_on_fail: @@ -596,7 +613,7 @@ async def list( max_releases: int = 256, namespace: t.Optional[str] = None, sort_by_date: bool = False, - sort_reversed: bool = False + sort_reversed: bool = False, ) -> t.Iterable[t.Dict[str, t.Any]]: """ Returns the list of releases that match the given options. @@ -633,7 +650,9 @@ async def pull( devel: bool = False, debug: bool = False, repo: t.Optional[str] = None, - version: t.Optional[str] = None + version: t.Optional[str] = None, + username: t.Optional[str] = None, + password: t.Optional[str] = None, ) -> pathlib.Path: """ Fetch a chart from a remote location and unpack it locally. @@ -641,8 +660,10 @@ async def pull( Returns the path of the directory into which the chart was downloaded and unpacked. """ # Make a directory to unpack into - destination = tempfile.mkdtemp(prefix = "helm.", dir = self._unpack_directory) + destination = tempfile.mkdtemp(prefix="helm.", dir=self._unpack_directory) command = ["pull", chart_ref, "--destination", destination, "--untar"] + if username and password: + command.extend(["--username", username, "--password", password]) if devel: command.append("--devel") if debug: @@ -660,13 +681,21 @@ async def repo_list(self) -> t.Iterable[t.Dict[str, t.Any]]: """ return json.loads(await self.run(["repo", "list", "--output", "json"])) - async def repo_add(self, name: str, url: str): + async def repo_add( + self, + name: str, + url: str, + username: t.Optional[str] = None, + password: t.Optional[str] = None, + ): """ Adds a repository to the available Helm repositories. Returns the new repo list on success. """ command = ["repo", "add", name, url, "--force-update"] + if username and password: + command.extend(["--username", username, "--password", password]) await self.run(command) async def repo_update(self, *names: str): @@ -704,7 +733,7 @@ async def rollback( no_hooks: bool = False, recreate_pods: bool = False, timeout: t.Union[int, str, None] = None, - wait: bool = False + wait: bool = False, ): """ Rollback the specified release to the specified revision. @@ -715,11 +744,15 @@ async def rollback( ] if revision is not None: command.append(revision) - command.extend([ - "--history-max", self._history_max_revisions, - # Use the default timeout unless an override is specified - "--timeout", timeout if timeout is not None else self._default_timeout, - ]) + command.extend( + [ + "--history-max", + self._history_max_revisions, + # Use the default timeout unless an override is specified + "--timeout", + timeout if timeout is not None else self._default_timeout, + ] + ) if cleanup_on_fail: command.append("--cleanup-on-fail") if debug: @@ -745,7 +778,7 @@ async def search( all_versions: bool = False, devel: bool = False, debug: bool = False, - version_constraints: t.Optional[str] = None + version_constraints: t.Optional[str] = None, ) -> t.Iterable[t.Dict[str, t.Any]]: """ Search the available Helm repositories for charts matching the specified constraints. @@ -770,7 +803,9 @@ async def show_chart( devel: bool = False, debug: bool = False, repo: t.Optional[str] = None, - version: t.Optional[str] = None + version: t.Optional[str] = None, + username: t.Optional[str] = None, + password: t.Optional[str] = None, ) -> t.Dict[str, t.Any]: """ Returns the contents of Chart.yaml for the specified chart. @@ -784,7 +819,9 @@ async def show_chart( command.extend(["--repo", repo]) if version: command.extend(["--version", version]) - return yaml.load(await self.run(command), Loader = SafeLoader) + if username and password: + command.extend(["--username", username, "--password", password]) + return yaml.load(await self.run(command), Loader=SafeLoader) async def show_crds( self, @@ -792,13 +829,13 @@ async def show_crds( *, devel: bool = False, repo: t.Optional[str] = None, - version: t.Optional[str] = None + version: t.Optional[str] = None, ) -> t.Iterable[t.Dict[str, t.Any]]: """ Returns the CRDs for the specified chart. """ # Until https://github.com/helm/helm/issues/11261 is fixed, we must manually - # unpack the chart and parse the files in the ./crds directory ourselves + # unpack the chart and parse the files in the ./crds directory ourselves # This is what the implementation should be # command = ["show", "crds", chart_ref] # if devel: @@ -815,16 +852,13 @@ async def show_crds( if repo: # If a repo is given, assume that the chart ref is a chart name in that repo ephemeral_path = await self.pull( - chart_ref, - devel = devel, - repo = repo, - version = version + chart_ref, devel=devel, repo=repo, version=version ) chart_directory = next(ephemeral_path.glob("**/Chart.yaml")).parent else: # If not, we have either a path (directory or archive) or a URL to a chart try: - chart_path = pathlib.Path(chart_ref).resolve(strict = True) + chart_path = pathlib.Path(chart_ref).resolve(strict=True) except (TypeError, ValueError, FileNotFoundError): # Assume we have a URL that needs pulling ephemeral_path = await self.pull(chart_ref) @@ -834,10 +868,14 @@ async def show_crds( # Just make sure that the directory is a chart chart_directory = next(chart_path.glob("**/Chart.yaml")).parent else: - raise RuntimeError("local archive files are not currently supported") + raise RuntimeError( + "local archive files are not currently supported" + ) + def yaml_load_all(file): with file.open() as fh: - yield from yaml.load_all(fh, Loader = SafeLoader) + yield from yaml.load_all(fh, Loader=SafeLoader) + return [ crd for crd_file in chart_directory.glob("crds/**/*.yaml") @@ -853,12 +891,16 @@ async def show_readme( *, devel: bool = False, repo: t.Optional[str] = None, - version: t.Optional[str] = None + version: t.Optional[str] = None, + username: t.Optional[str] = None, + password: t.Optional[str] = None, ) -> str: """ Returns the README for the specified chart. """ command = ["show", "readme", chart_ref] + if username and password: + command.extend(["--username", username, "--password", password]) if devel: command.append("--devel") if repo: @@ -873,19 +915,23 @@ async def show_values( *, devel: bool = False, repo: t.Optional[str] = None, - version: t.Optional[str] = None + version: t.Optional[str] = None, + username: t.Optional[str] = None, + password: t.Optional[str] = None, ) -> t.Dict[str, t.Any]: """ Returns the default values for the specified chart. """ command = ["show", "values", chart_ref] + if username and password: + command.extend(["--username", username, "--password", password]) if devel: command.append("--devel") if repo: command.extend(["--repo", repo]) if version: command.extend(["--version", version]) - return yaml.load(await self.run(command), Loader = SafeLoader) + return yaml.load(await self.run(command), Loader=SafeLoader) async def status( self, @@ -918,7 +964,9 @@ async def template( no_hooks: bool = False, repo: t.Optional[str] = None, version: t.Optional[str] = None, - ) -> t.Iterable[t.Dict[str, t.Any]]: + username: t.Optional[str] = None, + password: t.Optional[str] = None, + ) -> t.Iterable[t.Dict[str, t.Any]]: """ Renders the chart templates and returns the resources. """ @@ -928,7 +976,8 @@ async def template( chart_ref, "--include-crds" if include_crds else "--skip-crds", # We send the values in on stdin - "--values", "-", + "--values", + "-", ] if devel: command.append("--devel") @@ -944,9 +993,11 @@ async def template( command.extend(["--repo", repo]) if version: command.extend(["--version", version]) + if username and password: + command.extend(["--username", username, "--password", password]) return yaml.load_all( await self.run(command, json.dumps(values or {}).encode()), - Loader = SafeLoader + Loader=SafeLoader, ) async def uninstall( @@ -959,7 +1010,7 @@ async def uninstall( namespace: t.Optional[str] = None, no_hooks: bool = False, timeout: t.Union[int, str, None] = None, - wait: bool = False + wait: bool = False, ): """ Uninstall the specified release. @@ -968,7 +1019,8 @@ async def uninstall( "uninstall", release_name, # Use the default timeout unless an override is specified - "--timeout", timeout if timeout is not None else self._default_timeout, + "--timeout", + timeout if timeout is not None else self._default_timeout, ] if dry_run: command.append("--dry-run")