blob: ed0bdaa4aa8eaf65b7b288bfd0afa02f4ec73c3e [file] [log] [blame]
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001##
2# Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U.
3# This file is part of OSM
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
15# implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18#
19# For those usages not covered by the Apache License, Version 2.0 please
20# contact with: nfvlabs@tid.es
21##
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +010022from typing import Union
Daniel Arndtde6984b2023-06-27 16:42:41 -030023from shlex import quote
lloretgalleg1c83f2e2020-10-22 09:12:35 +000024import os
25import yaml
26
27from n2vc.k8s_helm_base_conn import K8sHelmBaseConnector
28from n2vc.exceptions import K8sException
29
30
31class K8sHelm3Connector(K8sHelmBaseConnector):
32
33 """
34 ####################################################################################
35 ################################### P U B L I C ####################################
36 ####################################################################################
37 """
38
39 def __init__(
garciadeblas82b591c2021-03-24 09:22:13 +010040 self,
41 fs: object,
42 db: object,
43 kubectl_command: str = "/usr/bin/kubectl",
44 helm_command: str = "/usr/bin/helm3",
45 log: object = None,
46 on_update_db=None,
lloretgalleg1c83f2e2020-10-22 09:12:35 +000047 ):
48 """
49 Initializes helm connector for helm v3
50
51 :param fs: file system for kubernetes and helm configuration
52 :param db: database object to write current operation status
53 :param kubectl_command: path to kubectl executable
54 :param helm_command: path to helm executable
55 :param log: logger
56 :param on_update_db: callback called when k8s connector updates database
57 """
58
59 # parent class
garciadeblas82b591c2021-03-24 09:22:13 +010060 K8sHelmBaseConnector.__init__(
61 self,
62 db=db,
63 log=log,
64 fs=fs,
65 kubectl_command=kubectl_command,
66 helm_command=helm_command,
67 on_update_db=on_update_db,
garciadeblas82b591c2021-03-24 09:22:13 +010068 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +000069
70 self.log.info("K8S Helm3 connector initialized")
71
lloretgalleg095392b2020-11-20 11:28:08 +000072 async def install(
garciadeblas82b591c2021-03-24 09:22:13 +010073 self,
74 cluster_uuid: str,
75 kdu_model: str,
76 kdu_instance: str,
77 atomic: bool = True,
78 timeout: float = 300,
79 params: dict = None,
80 db_dict: dict = None,
81 kdu_name: str = None,
82 namespace: str = None,
83 **kwargs,
lloretgalleg095392b2020-11-20 11:28:08 +000084 ):
David Garciaeb8943a2021-04-12 12:07:37 +020085 """Install a helm chart
86
87 :param cluster_uuid str: The UUID of the cluster to install to
garciadeblas04393192022-06-08 15:39:24 +020088 :param kdu_model str: chart/reference (string), which can be either
89 of these options:
90 - a name of chart available via the repos known by OSM
91 (e.g. stable/openldap, stable/openldap:1.2.4)
92 - a path to a packaged chart (e.g. mychart.tgz)
93 - a path to an unpacked chart directory or a URL (e.g. mychart)
David Garciaeb8943a2021-04-12 12:07:37 +020094 :param kdu_instance: Kdu instance name
95 :param atomic bool: If set, waits until the model is active and resets
96 the cluster on failure.
97 :param timeout int: The time, in seconds, to wait for the install
98 to finish
99 :param params dict: Key-value pairs of instantiation parameters
100 :param kdu_name: Name of the KDU instance to be installed
101 :param namespace: K8s namespace to use for the KDU instance
102
103 :param kwargs: Additional parameters (None yet)
104
105 :return: True if successful
106 """
Pedro Escaleirab41de172022-04-02 00:44:08 +0100107
108 self.log.debug("installing {} in cluster {}".format(kdu_model, cluster_uuid))
lloretgalleg095392b2020-11-20 11:28:08 +0000109
Pedro Pereirabe693152024-03-12 14:58:13 +0000110 labels_dict = None
111 if db_dict:
112 labels_dict = await self._labels_dict(db_dict, kdu_instance)
113
lloretgalleg095392b2020-11-20 11:28:08 +0000114 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100115 self.fs.sync(from_path=cluster_uuid)
lloretgalleg095392b2020-11-20 11:28:08 +0000116
117 # init env, paths
118 paths, env = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100119 cluster_name=cluster_uuid, create_if_not_exist=True
lloretgalleg095392b2020-11-20 11:28:08 +0000120 )
121
122 # for helm3 if namespace does not exist must create it
123 if namespace and namespace != "kube-system":
Pedro Escaleirab41de172022-04-02 00:44:08 +0100124 if not await self._namespace_exists(cluster_uuid, namespace):
aktas2a3ffde2021-06-24 11:37:11 +0300125 try:
Gabriel Cuba5f069332023-04-25 19:26:19 -0500126 # TODO: refactor to use kubernetes API client
Pedro Escaleirab41de172022-04-02 00:44:08 +0100127 await self._create_namespace(cluster_uuid, namespace)
aktas2a3ffde2021-06-24 11:37:11 +0300128 except Exception as e:
Pedro Escaleirab41de172022-04-02 00:44:08 +0100129 if not await self._namespace_exists(cluster_uuid, namespace):
aktas2a3ffde2021-06-24 11:37:11 +0300130 err_msg = (
131 "namespace {} does not exist in cluster_id {} "
David Garcia4ae527e2021-07-26 16:04:59 +0200132 "error message: ".format(namespace, e)
aktas2a3ffde2021-06-24 11:37:11 +0300133 )
134 self.log.error(err_msg)
135 raise K8sException(err_msg)
lloretgalleg095392b2020-11-20 11:28:08 +0000136
David Garciac4da25c2021-02-23 11:47:29 +0100137 await self._install_impl(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100138 cluster_uuid,
David Garciac4da25c2021-02-23 11:47:29 +0100139 kdu_model,
140 paths,
141 env,
142 kdu_instance,
143 atomic=atomic,
144 timeout=timeout,
145 params=params,
146 db_dict=db_dict,
Pedro Pereirabe693152024-03-12 14:58:13 +0000147 labels=labels_dict,
David Garciac4da25c2021-02-23 11:47:29 +0100148 kdu_name=kdu_name,
149 namespace=namespace,
150 )
lloretgalleg095392b2020-11-20 11:28:08 +0000151
152 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +0100153 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg095392b2020-11-20 11:28:08 +0000154
155 self.log.debug("Returning kdu_instance {}".format(kdu_instance))
David Garciac4da25c2021-02-23 11:47:29 +0100156 return True
lloretgalleg095392b2020-11-20 11:28:08 +0000157
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000158 async def inspect_kdu(self, kdu_model: str, repo_url: str = None) -> str:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000159 self.log.debug(
160 "inspect kdu_model {} from (optional) repo: {}".format(kdu_model, repo_url)
161 )
162
aktas867418c2021-10-19 18:26:13 +0300163 return await self._exec_inspect_command(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000164 inspect_command="all", kdu_model=kdu_model, repo_url=repo_url
165 )
166
167 """
168 ####################################################################################
169 ################################### P R I V A T E ##################################
170 ####################################################################################
171 """
172
173 def _init_paths_env(self, cluster_name: str, create_if_not_exist: bool = True):
174 """
175 Creates and returns base cluster and kube dirs and returns them.
176 Also created helm3 dirs according to new directory specification, paths are
177 returned and also environment variables that must be provided to execute commands
178
179 Helm 3 directory specification uses XDG categories for variable support:
180 - Cache: $XDG_CACHE_HOME, for example, ${HOME}/.cache/helm/
181 - Configuration: $XDG_CONFIG_HOME, for example, ${HOME}/.config/helm/
182 - Data: $XDG_DATA_HOME, for example ${HOME}/.local/share/helm
183
184 The variables assigned for this paths are:
185 (In the documentation the variables names are $HELM_PATH_CACHE, $HELM_PATH_CONFIG,
186 $HELM_PATH_DATA but looking and helm env the variable names are different)
187 - Cache: $HELM_CACHE_HOME
188 - Config: $HELM_CONFIG_HOME
189 - Data: $HELM_DATA_HOME
190 - helm kubeconfig: $KUBECONFIG
191
192 :param cluster_name: cluster_name
193 :return: Dictionary with config_paths and dictionary with helm environment variables
194 """
195
196 base = self.fs.path
197 if base.endswith("/") or base.endswith("\\"):
198 base = base[:-1]
199
200 # base dir for cluster
201 cluster_dir = base + "/" + cluster_name
202
203 # kube dir
204 kube_dir = cluster_dir + "/" + ".kube"
205 if create_if_not_exist and not os.path.exists(kube_dir):
206 self.log.debug("Creating dir {}".format(kube_dir))
207 os.makedirs(kube_dir)
208
209 helm_path_cache = cluster_dir + "/.cache/helm"
210 if create_if_not_exist and not os.path.exists(helm_path_cache):
211 self.log.debug("Creating dir {}".format(helm_path_cache))
212 os.makedirs(helm_path_cache)
213
214 helm_path_config = cluster_dir + "/.config/helm"
215 if create_if_not_exist and not os.path.exists(helm_path_config):
216 self.log.debug("Creating dir {}".format(helm_path_config))
217 os.makedirs(helm_path_config)
218
219 helm_path_data = cluster_dir + "/.local/share/helm"
220 if create_if_not_exist and not os.path.exists(helm_path_data):
221 self.log.debug("Creating dir {}".format(helm_path_data))
222 os.makedirs(helm_path_data)
223
224 config_filename = kube_dir + "/config"
225
226 # 2 - Prepare dictionary with paths
227 paths = {
228 "kube_dir": kube_dir,
229 "kube_config": config_filename,
garciadeblas82b591c2021-03-24 09:22:13 +0100230 "cluster_dir": cluster_dir,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000231 }
232
233 # 3 - Prepare environment variables
234 env = {
235 "HELM_CACHE_HOME": helm_path_cache,
236 "HELM_CONFIG_HOME": helm_path_config,
237 "HELM_DATA_HOME": helm_path_data,
garciadeblas82b591c2021-03-24 09:22:13 +0100238 "KUBECONFIG": config_filename,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000239 }
240
241 for file_name, file in paths.items():
242 if "dir" in file_name and not os.path.exists(file):
243 err_msg = "{} dir does not exist".format(file)
244 self.log.error(err_msg)
245 raise K8sException(err_msg)
246
247 return paths, env
248
aktas2a3ffde2021-06-24 11:37:11 +0300249 async def _namespace_exists(self, cluster_id, namespace) -> bool:
250 self.log.debug(
251 "checking if namespace {} exists cluster_id {}".format(
252 namespace, cluster_id
253 )
254 )
255 namespaces = await self._get_namespaces(cluster_id)
256 return namespace in namespaces if namespaces else False
257
garciadeblas82b591c2021-03-24 09:22:13 +0100258 async def _get_namespaces(self, cluster_id: str):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000259 self.log.debug("get namespaces cluster_id {}".format(cluster_id))
260
261 # init config, env
262 paths, env = self._init_paths_env(
263 cluster_name=cluster_id, create_if_not_exist=True
264 )
265
266 command = "{} --kubeconfig={} get namespaces -o=yaml".format(
Daniel Arndtde6984b2023-06-27 16:42:41 -0300267 self.kubectl_command, quote(paths["kube_config"])
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000268 )
269 output, _rc = await self._local_async_exec(
270 command=command, raise_exception_on_error=True, env=env
271 )
272
273 data = yaml.load(output, Loader=yaml.SafeLoader)
274 namespaces = [item["metadata"]["name"] for item in data["items"]]
275 self.log.debug(f"namespaces {namespaces}")
276
277 return namespaces
278
garciadeblas82b591c2021-03-24 09:22:13 +0100279 async def _create_namespace(self, cluster_id: str, namespace: str):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000280 self.log.debug(f"create namespace: {cluster_id} for cluster_id: {namespace}")
281
282 # init config, env
283 paths, env = self._init_paths_env(
284 cluster_name=cluster_id, create_if_not_exist=True
285 )
286
287 command = "{} --kubeconfig={} create namespace {}".format(
Daniel Arndtde6984b2023-06-27 16:42:41 -0300288 self.kubectl_command, quote(paths["kube_config"]), quote(namespace)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000289 )
290 _, _rc = await self._local_async_exec(
291 command=command, raise_exception_on_error=True, env=env
292 )
293 self.log.debug(f"namespace {namespace} created")
294
295 return _rc
296
bravof7bd5c6a2021-11-17 11:14:57 -0300297 async def _get_services(
298 self, cluster_id: str, kdu_instance: str, namespace: str, kubeconfig: str
299 ):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000300 # init config, env
301 paths, env = self._init_paths_env(
302 cluster_name=cluster_id, create_if_not_exist=True
303 )
304
bravof7bd5c6a2021-11-17 11:14:57 -0300305 command1 = "env KUBECONFIG={} {} get manifest {} --namespace={}".format(
Daniel Arndtde6984b2023-06-27 16:42:41 -0300306 kubeconfig, self._helm_command, quote(kdu_instance), quote(namespace)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000307 )
Daniel Arndtde6984b2023-06-27 16:42:41 -0300308 command2 = "{} get --namespace={} -f -".format(
309 self.kubectl_command, quote(namespace)
310 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000311 output, _rc = await self._local_async_exec_pipe(
312 command1, command2, env=env, raise_exception_on_error=True
313 )
314 services = self._parse_services(output)
315
316 return services
317
318 async def _cluster_init(self, cluster_id, namespace, paths, env):
319 """
320 Implements the helm version dependent cluster initialization:
321 For helm3 it creates the namespace if it is not created
322 """
323 if namespace != "kube-system":
324 namespaces = await self._get_namespaces(cluster_id)
325 if namespace not in namespaces:
Gabriel Cuba5f069332023-04-25 19:26:19 -0500326 # TODO: refactor to use kubernetes API client
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000327 await self._create_namespace(cluster_id, namespace)
328
Pedro Escaleirab41de172022-04-02 00:44:08 +0100329 repo_list = await self.repo_list(cluster_id)
David Garcia4395cfa2021-05-28 16:21:51 +0200330 stable_repo = [repo for repo in repo_list if repo["name"] == "stable"]
331 if not stable_repo and self._stable_repo_url:
Pedro Escaleirab41de172022-04-02 00:44:08 +0100332 await self.repo_add(cluster_id, "stable", self._stable_repo_url)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000333
334 # Returns False as no software needs to be uninstalled
335 return False
336
337 async def _uninstall_sw(self, cluster_id: str, namespace: str):
338 # nothing to do to uninstall sw
339 pass
340
341 async def _instances_list(self, cluster_id: str):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000342 # init paths, env
343 paths, env = self._init_paths_env(
344 cluster_name=cluster_id, create_if_not_exist=True
345 )
346
garciadeblas82b591c2021-03-24 09:22:13 +0100347 command = "{} list --all-namespaces --output yaml".format(self._helm_command)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000348 output, _rc = await self._local_async_exec(
349 command=command, raise_exception_on_error=True, env=env
350 )
351
352 if output and len(output) > 0:
353 self.log.debug("instances list output: {}".format(output))
354 return yaml.load(output, Loader=yaml.SafeLoader)
355 else:
356 return []
357
garciadeblas82b591c2021-03-24 09:22:13 +0100358 def _get_inspect_command(
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +0100359 self, show_command: str, kdu_model: str, repo_str: str, version: str
garciadeblas82b591c2021-03-24 09:22:13 +0100360 ):
Pedro Escaleira547f8232022-06-03 19:48:46 +0100361 """Generates the command to obtain the information about an Helm Chart package
362 (´helm show ...´ command)
363
364 Args:
365 show_command: the second part of the command (`helm show <show_command>`)
Gabriel Cuba1c1a2562023-11-20 01:08:39 -0500366 kdu_model: The name or path of a Helm Chart
367 repo_str: Helm Chart repository url
Pedro Escaleira547f8232022-06-03 19:48:46 +0100368 version: constraint with specific version of the Chart to use
369
370 Returns:
371 str: the generated Helm Chart command
372 """
373
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000374 inspect_command = "{} show {} {}{} {}".format(
Daniel Arndtde6984b2023-06-27 16:42:41 -0300375 self._helm_command, show_command, quote(kdu_model), repo_str, version
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000376 )
377 return inspect_command
378
aktas867418c2021-10-19 18:26:13 +0300379 def _get_get_command(
380 self, get_command: str, kdu_instance: str, namespace: str, kubeconfig: str
381 ):
382 get_command = (
383 "env KUBECONFIG={} {} get {} {} --namespace={} --output yaml".format(
Daniel Arndtde6984b2023-06-27 16:42:41 -0300384 kubeconfig,
385 self._helm_command,
386 get_command,
387 quote(kdu_instance),
388 quote(namespace),
aktas867418c2021-10-19 18:26:13 +0300389 )
390 )
391 return get_command
392
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000393 async def _status_kdu(
394 self,
395 cluster_id: str,
396 kdu_instance: str,
397 namespace: str = None,
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +0100398 yaml_format: bool = False,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000399 show_error_log: bool = False,
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +0100400 ) -> Union[str, dict]:
garciadeblas82b591c2021-03-24 09:22:13 +0100401 self.log.debug(
402 "status of kdu_instance: {}, namespace: {} ".format(kdu_instance, namespace)
403 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000404
405 if not namespace:
406 namespace = "kube-system"
407
408 # init config, env
409 paths, env = self._init_paths_env(
410 cluster_name=cluster_id, create_if_not_exist=True
411 )
bravof7bd5c6a2021-11-17 11:14:57 -0300412 command = "env KUBECONFIG={} {} status {} --namespace={} --output yaml".format(
Daniel Arndtde6984b2023-06-27 16:42:41 -0300413 paths["kube_config"],
414 self._helm_command,
415 quote(kdu_instance),
416 quote(namespace),
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000417 )
418
419 output, rc = await self._local_async_exec(
420 command=command,
421 raise_exception_on_error=True,
422 show_error_log=show_error_log,
garciadeblas82b591c2021-03-24 09:22:13 +0100423 env=env,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000424 )
425
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +0100426 if yaml_format:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000427 return str(output)
428
429 if rc != 0:
430 return None
431
432 data = yaml.load(output, Loader=yaml.SafeLoader)
433
434 # remove field 'notes' and manifest
435 try:
436 del data.get("info")["notes"]
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000437 except KeyError:
438 pass
439
Pedro Escaleiraed0ff052022-04-03 13:51:46 +0100440 # parse the manifest to a list of dictionaries
441 if "manifest" in data:
442 manifest_str = data.get("manifest")
443 manifest_docs = yaml.load_all(manifest_str, Loader=yaml.SafeLoader)
444
445 data["manifest"] = []
446 for doc in manifest_docs:
447 data["manifest"].append(doc)
448
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000449 return data
450
garciadeblas82b591c2021-03-24 09:22:13 +0100451 def _get_install_command(
452 self,
453 kdu_model: str,
454 kdu_instance: str,
455 namespace: str,
Pedro Pereirabe693152024-03-12 14:58:13 +0000456 labels: dict,
garciadeblas82b591c2021-03-24 09:22:13 +0100457 params_str: str,
458 version: str,
459 atomic: bool,
460 timeout: float,
bravof7bd5c6a2021-11-17 11:14:57 -0300461 kubeconfig: str,
garciadeblas82b591c2021-03-24 09:22:13 +0100462 ) -> str:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000463 timeout_str = ""
464 if timeout:
465 timeout_str = "--timeout {}s".format(timeout)
466
467 # atomic
468 atomic_str = ""
469 if atomic:
470 atomic_str = "--atomic"
471 # namespace
472 namespace_str = ""
473 if namespace:
Daniel Arndtde6984b2023-06-27 16:42:41 -0300474 namespace_str = "--namespace {}".format(quote(namespace))
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000475
476 # version
477 version_str = ""
478 if version:
479 version_str = "--version {}".format(version)
480
Pedro Pereirabe693152024-03-12 14:58:13 +0000481 # labels
482 post_renderer_args = []
483 post_renderer_str = post_renderer_args_str = ""
484 if labels and self.podLabels_post_renderer_path:
485 post_renderer_args.append(
486 "{}={}".format(
487 self.podLabels_post_renderer_path,
488 " ".join(
489 ["{}:{}".format(key, value) for key, value in labels.items()]
490 ),
491 )
492 )
493
494 if len(post_renderer_args) > 0 and self.main_post_renderer_path:
495 post_renderer_str = "--post-renderer {}".format(
496 self.main_post_renderer_path,
497 )
498 post_renderer_args_str += (
499 "--post-renderer-args '" + ",".join(post_renderer_args) + "'"
500 )
501
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000502 command = (
bravof7bd5c6a2021-11-17 11:14:57 -0300503 "env KUBECONFIG={kubeconfig} {helm} install {name} {atomic} --output yaml "
Pedro Pereirabe693152024-03-12 14:58:13 +0000504 "{params} {timeout} {ns} {post_renderer} {post_renderer_args} {model} {ver}".format(
bravof7bd5c6a2021-11-17 11:14:57 -0300505 kubeconfig=kubeconfig,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000506 helm=self._helm_command,
Daniel Arndtde6984b2023-06-27 16:42:41 -0300507 name=quote(kdu_instance),
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000508 atomic=atomic_str,
509 params=params_str,
510 timeout=timeout_str,
511 ns=namespace_str,
Pedro Pereirabe693152024-03-12 14:58:13 +0000512 post_renderer=post_renderer_str,
513 post_renderer_args=post_renderer_args_str,
Daniel Arndtde6984b2023-06-27 16:42:41 -0300514 model=quote(kdu_model),
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000515 ver=version_str,
516 )
517 )
518 return command
519
aktas867418c2021-10-19 18:26:13 +0300520 def _get_upgrade_scale_command(
521 self,
522 kdu_model: str,
523 kdu_instance: str,
524 namespace: str,
525 scale: int,
Pedro Pereirabe693152024-03-12 14:58:13 +0000526 labels: dict,
aktas867418c2021-10-19 18:26:13 +0300527 version: str,
528 atomic: bool,
529 replica_str: str,
530 timeout: float,
531 resource_name: str,
532 kubeconfig: str,
533 ) -> str:
Pedro Escaleira0a2060c2022-07-07 22:18:35 +0100534 """Generates the command to scale a Helm Chart release
aktas867418c2021-10-19 18:26:13 +0300535
Pedro Escaleira0a2060c2022-07-07 22:18:35 +0100536 Args:
537 kdu_model (str): Kdu model name, corresponding to the Helm local location or repository
538 kdu_instance (str): KDU instance, corresponding to the Helm Chart release in question
539 namespace (str): Namespace where this KDU instance is deployed
540 scale (int): Scale count
541 version (str): Constraint with specific version of the Chart to use
542 atomic (bool): If set, upgrade process rolls back changes made in case of failed upgrade.
543 The --wait flag will be set automatically if --atomic is used
544 replica_str (str): The key under resource_name key where the scale count is stored
545 timeout (float): The time, in seconds, to wait
546 resource_name (str): The KDU's resource to scale
547 kubeconfig (str): Kubeconfig file path
aktas867418c2021-10-19 18:26:13 +0300548
Pedro Escaleira0a2060c2022-07-07 22:18:35 +0100549 Returns:
550 str: command to scale a Helm Chart release
551 """
aktas867418c2021-10-19 18:26:13 +0300552
553 # scale
554 if resource_name:
555 scale_dict = {"{}.{}".format(resource_name, replica_str): scale}
556 else:
557 scale_dict = {replica_str: scale}
558
559 scale_str = self._params_to_set_option(scale_dict)
560
Pedro Escaleira0a2060c2022-07-07 22:18:35 +0100561 return self._get_upgrade_command(
562 kdu_model=kdu_model,
563 kdu_instance=kdu_instance,
564 namespace=namespace,
565 params_str=scale_str,
Pedro Pereirabe693152024-03-12 14:58:13 +0000566 labels=labels,
Pedro Escaleira0a2060c2022-07-07 22:18:35 +0100567 version=version,
568 atomic=atomic,
569 timeout=timeout,
aktas867418c2021-10-19 18:26:13 +0300570 kubeconfig=kubeconfig,
571 )
aktas867418c2021-10-19 18:26:13 +0300572
garciadeblas82b591c2021-03-24 09:22:13 +0100573 def _get_upgrade_command(
574 self,
575 kdu_model: str,
576 kdu_instance: str,
577 namespace: str,
578 params_str: str,
Pedro Pereirabe693152024-03-12 14:58:13 +0000579 labels: dict,
garciadeblas82b591c2021-03-24 09:22:13 +0100580 version: str,
581 atomic: bool,
582 timeout: float,
bravof7bd5c6a2021-11-17 11:14:57 -0300583 kubeconfig: str,
garciadeblas1db89e82024-02-08 13:05:27 +0100584 reset_values: bool = False,
585 reuse_values: bool = True,
586 reset_then_reuse_values: bool = False,
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500587 force: bool = False,
garciadeblas82b591c2021-03-24 09:22:13 +0100588 ) -> str:
Pedro Escaleira0a2060c2022-07-07 22:18:35 +0100589 """Generates the command to upgrade a Helm Chart release
590
591 Args:
592 kdu_model (str): Kdu model name, corresponding to the Helm local location or repository
593 kdu_instance (str): KDU instance, corresponding to the Helm Chart release in question
594 namespace (str): Namespace where this KDU instance is deployed
595 params_str (str): Params used to upgrade the Helm Chart release
596 version (str): Constraint with specific version of the Chart to use
597 atomic (bool): If set, upgrade process rolls back changes made in case of failed upgrade.
598 The --wait flag will be set automatically if --atomic is used
599 timeout (float): The time, in seconds, to wait
600 kubeconfig (str): Kubeconfig file path
garciadeblas1db89e82024-02-08 13:05:27 +0100601 reset_values(bool): If set, helm resets values instead of reusing previous values.
602 reuse_values(bool): If set, helm reuses previous values.
603 reset_then_reuse_values(bool): If set, helm resets values, then apply the last release's values
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500604 force (bool): If set, helm forces resource updates through a replacement strategy. This may recreate pods.
Pedro Escaleira0a2060c2022-07-07 22:18:35 +0100605 Returns:
606 str: command to upgrade a Helm Chart release
607 """
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000608
609 timeout_str = ""
610 if timeout:
611 timeout_str = "--timeout {}s".format(timeout)
612
613 # atomic
614 atomic_str = ""
615 if atomic:
616 atomic_str = "--atomic"
617
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500618 # force
619 force_str = ""
620 if force:
621 force_str = "--force "
622
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000623 # version
624 version_str = ""
625 if version:
Daniel Arndtde6984b2023-06-27 16:42:41 -0300626 version_str = "--version {}".format(quote(version))
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000627
628 # namespace
629 namespace_str = ""
630 if namespace:
Daniel Arndtde6984b2023-06-27 16:42:41 -0300631 namespace_str = "--namespace {}".format(quote(namespace))
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000632
garciadeblas1db89e82024-02-08 13:05:27 +0100633 # reset, reuse or reset_then_reuse values
634 on_values_str = "--reuse-values"
635 if reset_values:
636 on_values_str = "--reset-values"
637 elif reuse_values:
638 on_values_str = "--reuse-values"
639 elif reset_then_reuse_values:
640 on_values_str = "--reset-then-reuse-values"
Pedro Pereirabe693152024-03-12 14:58:13 +0000641
642 # labels
643 post_renderer_args = []
644 post_renderer_str = post_renderer_args_str = ""
645 if labels and self.podLabels_post_renderer_path:
646 post_renderer_args.append(
647 "{}={}".format(
648 self.podLabels_post_renderer_path,
649 " ".join(
650 ["{}:{}".format(key, value) for key, value in labels.items()]
651 ),
652 )
653 )
654
655 if len(post_renderer_args) > 0 and self.main_post_renderer_path:
656 post_renderer_str = "--post-renderer {}".format(
657 self.main_post_renderer_path,
658 )
659 post_renderer_args_str += (
660 "--post-renderer-args '" + ",".join(post_renderer_args) + "'"
661 )
662
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000663 command = (
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500664 "env KUBECONFIG={kubeconfig} {helm} upgrade {name} {model} {namespace} {atomic} {force}"
Pedro Pereirabe693152024-03-12 14:58:13 +0000665 "--output yaml {params} {timeout} {post_renderer} {post_renderer_args} {on_values} {ver}"
bravof7bd5c6a2021-11-17 11:14:57 -0300666 ).format(
667 kubeconfig=kubeconfig,
668 helm=self._helm_command,
Daniel Arndtde6984b2023-06-27 16:42:41 -0300669 name=quote(kdu_instance),
bravof7bd5c6a2021-11-17 11:14:57 -0300670 namespace=namespace_str,
671 atomic=atomic_str,
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500672 force=force_str,
bravof7bd5c6a2021-11-17 11:14:57 -0300673 params=params_str,
674 timeout=timeout_str,
Pedro Pereirabe693152024-03-12 14:58:13 +0000675 post_renderer=post_renderer_str,
676 post_renderer_args=post_renderer_args_str,
Daniel Arndtde6984b2023-06-27 16:42:41 -0300677 model=quote(kdu_model),
garciadeblas1db89e82024-02-08 13:05:27 +0100678 on_values=on_values_str,
bravof7bd5c6a2021-11-17 11:14:57 -0300679 ver=version_str,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000680 )
681 return command
682
garciadeblas82b591c2021-03-24 09:22:13 +0100683 def _get_rollback_command(
bravof7bd5c6a2021-11-17 11:14:57 -0300684 self, kdu_instance: str, namespace: str, revision: float, kubeconfig: str
garciadeblas82b591c2021-03-24 09:22:13 +0100685 ) -> str:
bravof7bd5c6a2021-11-17 11:14:57 -0300686 return "env KUBECONFIG={} {} rollback {} {} --namespace={} --wait".format(
Daniel Arndtde6984b2023-06-27 16:42:41 -0300687 kubeconfig,
688 self._helm_command,
689 quote(kdu_instance),
690 revision,
691 quote(namespace),
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000692 )
693
bravof7bd5c6a2021-11-17 11:14:57 -0300694 def _get_uninstall_command(
695 self, kdu_instance: str, namespace: str, kubeconfig: str
696 ) -> str:
bravof7bd5c6a2021-11-17 11:14:57 -0300697 return "env KUBECONFIG={} {} uninstall {} --namespace={}".format(
Daniel Arndtde6984b2023-06-27 16:42:41 -0300698 kubeconfig, self._helm_command, quote(kdu_instance), quote(namespace)
garciadeblas82b591c2021-03-24 09:22:13 +0100699 )
lloretgalleg095392b2020-11-20 11:28:08 +0000700
701 def _get_helm_chart_repos_ids(self, cluster_uuid) -> list:
702 repo_ids = []
703 cluster_filter = {"_admin.helm-chart-v3.id": cluster_uuid}
704 cluster = self.db.get_one("k8sclusters", cluster_filter)
705 if cluster:
706 repo_ids = cluster.get("_admin").get("helm_chart_repos") or []
707 return repo_ids
708 else:
709 raise K8sException(
710 "k8cluster with helm-id : {} not found".format(cluster_uuid)
711 )