
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
136 lines
4.1 KiB
Python
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
|