sd109 57a9d43d6f Fix cluster deletion on trust creation failure
This change fixes an issue where clusters which fail early in the creation process (e.g. if the user autheticates with a restricted app cred then the trust creation fails) would then also fail to delete correctly. The issue is caused by attempting to delete the Helm release instance even when it was not created in the first place. In such cases the user would receive an error such as 'Error: uninstall: Release name is invalid: None'.

Change-Id: I0fe188f284f94f069b5116b3be882eebf1b80706
2024-05-14 15:23:23 +01:00

136 lines
4.1 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import functools
import json
import pathlib
import typing as t
from magnum.common import utils
from oslo_concurrency import processutils
from oslo_log import log as logging
from magnum_capi_helm import conf
LOG = logging.getLogger(__name__)
CONF = conf.CONF
# This code is loosely based on:
# https://github.com/stackhpc/pyhelm3
# Ideally we can share this code in the future.
def mergeconcat(defaults, *overrides):
"""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)
for key, value in overrides.items():
if key in defaults:
merged[key] = mergeconcat2(defaults[key], value)
else:
merged[key] = value
return merged
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)
class Client:
"""Client for interacting with Helm CLI."""
def __init__(self):
self._default_timeout = "5m"
self._executable = "helm"
self._history_max_revisions = 10
self._kubeconfig = CONF.capi_helm.kubeconfig_file
def _run(self, command, **kwargs) -> bytes:
command = [self._executable] + command
if self._kubeconfig:
command.extend(["--kubeconfig", self._kubeconfig])
stdout, stderr = utils.execute(*command, **kwargs)
LOG.debug(f"Ran helm {command} got out:{stdout} err:{stderr}")
return stdout
def install_or_upgrade(
self,
release_name: str,
chart_ref: t.Union[pathlib.Path, str],
*values: t.Dict[str, t.Any],
namespace: str,
repo: t.Optional[str] = None,
version: t.Optional[str] = None,
) -> t.Iterable[t.Dict[str, t.Any]]:
"""Install or upgrade specified release using chart and values."""
assert release_name is not None
command = [
"upgrade",
release_name,
chart_ref,
"--history-max",
self._history_max_revisions,
"--install",
"--output",
"json",
"--timeout",
self._default_timeout,
# We send the values in on stdin
"--values",
"-",
"--namespace",
namespace,
]
if repo:
command += ["--repo", repo]
if version:
command += [
"--version",
version,
]
process_input = json.dumps(mergeconcat({}, *values))
return json.loads(self._run(command, process_input=process_input))
def uninstall_release(
self,
release_name: str,
namespace: str,
):
"""Uninstall the named release."""
assert release_name is not None
command = [
"uninstall",
release_name,
"--timeout",
self._default_timeout,
"--namespace",
namespace,
]
try:
self._run(command)
except processutils.ProcessExecutionError as exc:
# Swallow release not found errors, as that is our desired state
if not exc.stderr or "release: not found" not in exc.stderr:
raise