blob: 68cf067ba865b92fefccec1771fce4e15aa232eb [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
Pedro Escaleirafebbaa02023-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
110 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100111 self.fs.sync(from_path=cluster_uuid)
lloretgalleg095392b2020-11-20 11:28:08 +0000112
113 # init env, paths
114 paths, env = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100115 cluster_name=cluster_uuid, create_if_not_exist=True
lloretgalleg095392b2020-11-20 11:28:08 +0000116 )
117
118 # for helm3 if namespace does not exist must create it
119 if namespace and namespace != "kube-system":
Pedro Escaleirab41de172022-04-02 00:44:08 +0100120 if not await self._namespace_exists(cluster_uuid, namespace):
aktas2a3ffde2021-06-24 11:37:11 +0300121 try:
Gabriel Cuba5f069332023-04-25 19:26:19 -0500122 # TODO: refactor to use kubernetes API client
Pedro Escaleirab41de172022-04-02 00:44:08 +0100123 await self._create_namespace(cluster_uuid, namespace)
aktas2a3ffde2021-06-24 11:37:11 +0300124 except Exception as e:
Pedro Escaleirab41de172022-04-02 00:44:08 +0100125 if not await self._namespace_exists(cluster_uuid, namespace):
aktas2a3ffde2021-06-24 11:37:11 +0300126 err_msg = (
127 "namespace {} does not exist in cluster_id {} "
David Garcia4ae527e2021-07-26 16:04:59 +0200128 "error message: ".format(namespace, e)
aktas2a3ffde2021-06-24 11:37:11 +0300129 )
130 self.log.error(err_msg)
131 raise K8sException(err_msg)
lloretgalleg095392b2020-11-20 11:28:08 +0000132
David Garciac4da25c2021-02-23 11:47:29 +0100133 await self._install_impl(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100134 cluster_uuid,
David Garciac4da25c2021-02-23 11:47:29 +0100135 kdu_model,
136 paths,
137 env,
138 kdu_instance,
139 atomic=atomic,
140 timeout=timeout,
141 params=params,
142 db_dict=db_dict,
143 kdu_name=kdu_name,
144 namespace=namespace,
145 )
lloretgalleg095392b2020-11-20 11:28:08 +0000146
147 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +0100148 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg095392b2020-11-20 11:28:08 +0000149
150 self.log.debug("Returning kdu_instance {}".format(kdu_instance))
David Garciac4da25c2021-02-23 11:47:29 +0100151 return True
lloretgalleg095392b2020-11-20 11:28:08 +0000152
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000153 async def inspect_kdu(self, kdu_model: str, repo_url: str = None) -> str:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000154 self.log.debug(
155 "inspect kdu_model {} from (optional) repo: {}".format(kdu_model, repo_url)
156 )
157
aktas867418c2021-10-19 18:26:13 +0300158 return await self._exec_inspect_command(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000159 inspect_command="all", kdu_model=kdu_model, repo_url=repo_url
160 )
161
162 """
163 ####################################################################################
164 ################################### P R I V A T E ##################################
165 ####################################################################################
166 """
167
168 def _init_paths_env(self, cluster_name: str, create_if_not_exist: bool = True):
169 """
170 Creates and returns base cluster and kube dirs and returns them.
171 Also created helm3 dirs according to new directory specification, paths are
172 returned and also environment variables that must be provided to execute commands
173
174 Helm 3 directory specification uses XDG categories for variable support:
175 - Cache: $XDG_CACHE_HOME, for example, ${HOME}/.cache/helm/
176 - Configuration: $XDG_CONFIG_HOME, for example, ${HOME}/.config/helm/
177 - Data: $XDG_DATA_HOME, for example ${HOME}/.local/share/helm
178
179 The variables assigned for this paths are:
180 (In the documentation the variables names are $HELM_PATH_CACHE, $HELM_PATH_CONFIG,
181 $HELM_PATH_DATA but looking and helm env the variable names are different)
182 - Cache: $HELM_CACHE_HOME
183 - Config: $HELM_CONFIG_HOME
184 - Data: $HELM_DATA_HOME
185 - helm kubeconfig: $KUBECONFIG
186
187 :param cluster_name: cluster_name
188 :return: Dictionary with config_paths and dictionary with helm environment variables
189 """
190
191 base = self.fs.path
192 if base.endswith("/") or base.endswith("\\"):
193 base = base[:-1]
194
195 # base dir for cluster
196 cluster_dir = base + "/" + cluster_name
197
198 # kube dir
199 kube_dir = cluster_dir + "/" + ".kube"
200 if create_if_not_exist and not os.path.exists(kube_dir):
201 self.log.debug("Creating dir {}".format(kube_dir))
202 os.makedirs(kube_dir)
203
204 helm_path_cache = cluster_dir + "/.cache/helm"
205 if create_if_not_exist and not os.path.exists(helm_path_cache):
206 self.log.debug("Creating dir {}".format(helm_path_cache))
207 os.makedirs(helm_path_cache)
208
209 helm_path_config = cluster_dir + "/.config/helm"
210 if create_if_not_exist and not os.path.exists(helm_path_config):
211 self.log.debug("Creating dir {}".format(helm_path_config))
212 os.makedirs(helm_path_config)
213
214 helm_path_data = cluster_dir + "/.local/share/helm"
215 if create_if_not_exist and not os.path.exists(helm_path_data):
216 self.log.debug("Creating dir {}".format(helm_path_data))
217 os.makedirs(helm_path_data)
218
219 config_filename = kube_dir + "/config"
220
221 # 2 - Prepare dictionary with paths
222 paths = {
223 "kube_dir": kube_dir,
224 "kube_config": config_filename,
garciadeblas82b591c2021-03-24 09:22:13 +0100225 "cluster_dir": cluster_dir,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000226 }
227
228 # 3 - Prepare environment variables
229 env = {
230 "HELM_CACHE_HOME": helm_path_cache,
231 "HELM_CONFIG_HOME": helm_path_config,
232 "HELM_DATA_HOME": helm_path_data,
garciadeblas82b591c2021-03-24 09:22:13 +0100233 "KUBECONFIG": config_filename,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000234 }
235
236 for file_name, file in paths.items():
237 if "dir" in file_name and not os.path.exists(file):
238 err_msg = "{} dir does not exist".format(file)
239 self.log.error(err_msg)
240 raise K8sException(err_msg)
241
242 return paths, env
243
aktas2a3ffde2021-06-24 11:37:11 +0300244 async def _namespace_exists(self, cluster_id, namespace) -> bool:
245 self.log.debug(
246 "checking if namespace {} exists cluster_id {}".format(
247 namespace, cluster_id
248 )
249 )
250 namespaces = await self._get_namespaces(cluster_id)
251 return namespace in namespaces if namespaces else False
252
garciadeblas82b591c2021-03-24 09:22:13 +0100253 async def _get_namespaces(self, cluster_id: str):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000254 self.log.debug("get namespaces cluster_id {}".format(cluster_id))
255
256 # init config, env
257 paths, env = self._init_paths_env(
258 cluster_name=cluster_id, create_if_not_exist=True
259 )
260
261 command = "{} --kubeconfig={} get namespaces -o=yaml".format(
Pedro Escaleirafebbaa02023-06-27 16:42:41 -0300262 self.kubectl_command, quote(paths["kube_config"])
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000263 )
264 output, _rc = await self._local_async_exec(
265 command=command, raise_exception_on_error=True, env=env
266 )
267
268 data = yaml.load(output, Loader=yaml.SafeLoader)
269 namespaces = [item["metadata"]["name"] for item in data["items"]]
270 self.log.debug(f"namespaces {namespaces}")
271
272 return namespaces
273
garciadeblas82b591c2021-03-24 09:22:13 +0100274 async def _create_namespace(self, cluster_id: str, namespace: str):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000275 self.log.debug(f"create namespace: {cluster_id} for cluster_id: {namespace}")
276
277 # init config, env
278 paths, env = self._init_paths_env(
279 cluster_name=cluster_id, create_if_not_exist=True
280 )
281
282 command = "{} --kubeconfig={} create namespace {}".format(
Pedro Escaleirafebbaa02023-06-27 16:42:41 -0300283 self.kubectl_command, quote(paths["kube_config"]), quote(namespace)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000284 )
285 _, _rc = await self._local_async_exec(
286 command=command, raise_exception_on_error=True, env=env
287 )
288 self.log.debug(f"namespace {namespace} created")
289
290 return _rc
291
bravof7bd5c6a2021-11-17 11:14:57 -0300292 async def _get_services(
293 self, cluster_id: str, kdu_instance: str, namespace: str, kubeconfig: str
294 ):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000295 # init config, env
296 paths, env = self._init_paths_env(
297 cluster_name=cluster_id, create_if_not_exist=True
298 )
299
garciadeblasae1d9492025-07-17 18:26:11 +0200300 command = "env KUBECONFIG={} {} get manifest {} --namespace={}".format(
Pedro Escaleirafebbaa02023-06-27 16:42:41 -0300301 kubeconfig, self._helm_command, quote(kdu_instance), quote(namespace)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000302 )
garciadeblasae1d9492025-07-17 18:26:11 +0200303 output, _rc = await self._local_async_exec(
304 command, env=env, raise_exception_on_error=True
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000305 )
306 services = self._parse_services(output)
307
308 return services
309
310 async def _cluster_init(self, cluster_id, namespace, paths, env):
311 """
312 Implements the helm version dependent cluster initialization:
313 For helm3 it creates the namespace if it is not created
314 """
315 if namespace != "kube-system":
316 namespaces = await self._get_namespaces(cluster_id)
317 if namespace not in namespaces:
Gabriel Cuba5f069332023-04-25 19:26:19 -0500318 # TODO: refactor to use kubernetes API client
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000319 await self._create_namespace(cluster_id, namespace)
320
Pedro Escaleirab41de172022-04-02 00:44:08 +0100321 repo_list = await self.repo_list(cluster_id)
David Garcia4395cfa2021-05-28 16:21:51 +0200322 stable_repo = [repo for repo in repo_list if repo["name"] == "stable"]
323 if not stable_repo and self._stable_repo_url:
Pedro Escaleirab41de172022-04-02 00:44:08 +0100324 await self.repo_add(cluster_id, "stable", self._stable_repo_url)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000325
326 # Returns False as no software needs to be uninstalled
327 return False
328
329 async def _uninstall_sw(self, cluster_id: str, namespace: str):
330 # nothing to do to uninstall sw
331 pass
332
333 async def _instances_list(self, cluster_id: str):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000334 # init paths, env
335 paths, env = self._init_paths_env(
336 cluster_name=cluster_id, create_if_not_exist=True
337 )
338
garciadeblas82b591c2021-03-24 09:22:13 +0100339 command = "{} list --all-namespaces --output yaml".format(self._helm_command)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000340 output, _rc = await self._local_async_exec(
341 command=command, raise_exception_on_error=True, env=env
342 )
343
344 if output and len(output) > 0:
345 self.log.debug("instances list output: {}".format(output))
346 return yaml.load(output, Loader=yaml.SafeLoader)
347 else:
348 return []
349
garciadeblas82b591c2021-03-24 09:22:13 +0100350 def _get_inspect_command(
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +0100351 self, show_command: str, kdu_model: str, repo_str: str, version: str
garciadeblas82b591c2021-03-24 09:22:13 +0100352 ):
Pedro Escaleira547f8232022-06-03 19:48:46 +0100353 """Generates the command to obtain the information about an Helm Chart package
354 (´helm show ...´ command)
355
356 Args:
357 show_command: the second part of the command (`helm show <show_command>`)
358 kdu_model: The name or path of an Helm Chart
359 repo_url: Helm Chart repository url
360 version: constraint with specific version of the Chart to use
361
362 Returns:
363 str: the generated Helm Chart command
364 """
365
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000366 inspect_command = "{} show {} {}{} {}".format(
Pedro Escaleirafebbaa02023-06-27 16:42:41 -0300367 self._helm_command, show_command, quote(kdu_model), repo_str, version
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000368 )
369 return inspect_command
370
aktas867418c2021-10-19 18:26:13 +0300371 def _get_get_command(
372 self, get_command: str, kdu_instance: str, namespace: str, kubeconfig: str
373 ):
374 get_command = (
375 "env KUBECONFIG={} {} get {} {} --namespace={} --output yaml".format(
Pedro Escaleirafebbaa02023-06-27 16:42:41 -0300376 kubeconfig,
377 self._helm_command,
378 get_command,
379 quote(kdu_instance),
380 quote(namespace),
aktas867418c2021-10-19 18:26:13 +0300381 )
382 )
383 return get_command
384
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000385 async def _status_kdu(
386 self,
387 cluster_id: str,
388 kdu_instance: str,
389 namespace: str = None,
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +0100390 yaml_format: bool = False,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000391 show_error_log: bool = False,
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +0100392 ) -> Union[str, dict]:
garciadeblas82b591c2021-03-24 09:22:13 +0100393 self.log.debug(
394 "status of kdu_instance: {}, namespace: {} ".format(kdu_instance, namespace)
395 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000396
397 if not namespace:
398 namespace = "kube-system"
399
400 # init config, env
401 paths, env = self._init_paths_env(
402 cluster_name=cluster_id, create_if_not_exist=True
403 )
bravof7bd5c6a2021-11-17 11:14:57 -0300404 command = "env KUBECONFIG={} {} status {} --namespace={} --output yaml".format(
Pedro Escaleirafebbaa02023-06-27 16:42:41 -0300405 paths["kube_config"],
406 self._helm_command,
407 quote(kdu_instance),
408 quote(namespace),
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000409 )
410
411 output, rc = await self._local_async_exec(
412 command=command,
413 raise_exception_on_error=True,
414 show_error_log=show_error_log,
garciadeblas82b591c2021-03-24 09:22:13 +0100415 env=env,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000416 )
417
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +0100418 if yaml_format:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000419 return str(output)
420
421 if rc != 0:
422 return None
423
424 data = yaml.load(output, Loader=yaml.SafeLoader)
425
426 # remove field 'notes' and manifest
427 try:
428 del data.get("info")["notes"]
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000429 except KeyError:
430 pass
431
Pedro Escaleiraed0ff052022-04-03 13:51:46 +0100432 # parse the manifest to a list of dictionaries
433 if "manifest" in data:
434 manifest_str = data.get("manifest")
435 manifest_docs = yaml.load_all(manifest_str, Loader=yaml.SafeLoader)
436
437 data["manifest"] = []
438 for doc in manifest_docs:
439 data["manifest"].append(doc)
440
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000441 return data
442
garciadeblas82b591c2021-03-24 09:22:13 +0100443 def _get_install_command(
444 self,
445 kdu_model: str,
446 kdu_instance: str,
447 namespace: str,
448 params_str: str,
449 version: str,
450 atomic: bool,
451 timeout: float,
bravof7bd5c6a2021-11-17 11:14:57 -0300452 kubeconfig: str,
garciadeblas82b591c2021-03-24 09:22:13 +0100453 ) -> str:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000454 timeout_str = ""
455 if timeout:
456 timeout_str = "--timeout {}s".format(timeout)
457
458 # atomic
459 atomic_str = ""
460 if atomic:
461 atomic_str = "--atomic"
462 # namespace
463 namespace_str = ""
464 if namespace:
Pedro Escaleirafebbaa02023-06-27 16:42:41 -0300465 namespace_str = "--namespace {}".format(quote(namespace))
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000466
467 # version
468 version_str = ""
469 if version:
470 version_str = "--version {}".format(version)
471
472 command = (
bravof7bd5c6a2021-11-17 11:14:57 -0300473 "env KUBECONFIG={kubeconfig} {helm} install {name} {atomic} --output yaml "
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000474 "{params} {timeout} {ns} {model} {ver}".format(
bravof7bd5c6a2021-11-17 11:14:57 -0300475 kubeconfig=kubeconfig,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000476 helm=self._helm_command,
Pedro Escaleirafebbaa02023-06-27 16:42:41 -0300477 name=quote(kdu_instance),
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000478 atomic=atomic_str,
479 params=params_str,
480 timeout=timeout_str,
481 ns=namespace_str,
Pedro Escaleirafebbaa02023-06-27 16:42:41 -0300482 model=quote(kdu_model),
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000483 ver=version_str,
484 )
485 )
486 return command
487
aktas867418c2021-10-19 18:26:13 +0300488 def _get_upgrade_scale_command(
489 self,
490 kdu_model: str,
491 kdu_instance: str,
492 namespace: str,
493 scale: int,
494 version: str,
495 atomic: bool,
496 replica_str: str,
497 timeout: float,
498 resource_name: str,
499 kubeconfig: str,
500 ) -> str:
Pedro Escaleira0a2060c2022-07-07 22:18:35 +0100501 """Generates the command to scale a Helm Chart release
aktas867418c2021-10-19 18:26:13 +0300502
Pedro Escaleira0a2060c2022-07-07 22:18:35 +0100503 Args:
504 kdu_model (str): Kdu model name, corresponding to the Helm local location or repository
505 kdu_instance (str): KDU instance, corresponding to the Helm Chart release in question
506 namespace (str): Namespace where this KDU instance is deployed
507 scale (int): Scale count
508 version (str): Constraint with specific version of the Chart to use
509 atomic (bool): If set, upgrade process rolls back changes made in case of failed upgrade.
510 The --wait flag will be set automatically if --atomic is used
511 replica_str (str): The key under resource_name key where the scale count is stored
512 timeout (float): The time, in seconds, to wait
513 resource_name (str): The KDU's resource to scale
514 kubeconfig (str): Kubeconfig file path
aktas867418c2021-10-19 18:26:13 +0300515
Pedro Escaleira0a2060c2022-07-07 22:18:35 +0100516 Returns:
517 str: command to scale a Helm Chart release
518 """
aktas867418c2021-10-19 18:26:13 +0300519
520 # scale
521 if resource_name:
522 scale_dict = {"{}.{}".format(resource_name, replica_str): scale}
523 else:
524 scale_dict = {replica_str: scale}
525
526 scale_str = self._params_to_set_option(scale_dict)
527
Pedro Escaleira0a2060c2022-07-07 22:18:35 +0100528 return self._get_upgrade_command(
529 kdu_model=kdu_model,
530 kdu_instance=kdu_instance,
531 namespace=namespace,
532 params_str=scale_str,
533 version=version,
534 atomic=atomic,
535 timeout=timeout,
aktas867418c2021-10-19 18:26:13 +0300536 kubeconfig=kubeconfig,
537 )
aktas867418c2021-10-19 18:26:13 +0300538
garciadeblas82b591c2021-03-24 09:22:13 +0100539 def _get_upgrade_command(
540 self,
541 kdu_model: str,
542 kdu_instance: str,
543 namespace: str,
544 params_str: str,
545 version: str,
546 atomic: bool,
547 timeout: float,
bravof7bd5c6a2021-11-17 11:14:57 -0300548 kubeconfig: str,
garciadeblasb2ac7622024-02-08 13:05:27 +0100549 reset_values: bool = False,
550 reuse_values: bool = True,
551 reset_then_reuse_values: bool = False,
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500552 force: bool = False,
garciadeblas82b591c2021-03-24 09:22:13 +0100553 ) -> str:
Pedro Escaleira0a2060c2022-07-07 22:18:35 +0100554 """Generates the command to upgrade a Helm Chart release
555
556 Args:
557 kdu_model (str): Kdu model name, corresponding to the Helm local location or repository
558 kdu_instance (str): KDU instance, corresponding to the Helm Chart release in question
559 namespace (str): Namespace where this KDU instance is deployed
560 params_str (str): Params used to upgrade the Helm Chart release
561 version (str): Constraint with specific version of the Chart to use
562 atomic (bool): If set, upgrade process rolls back changes made in case of failed upgrade.
563 The --wait flag will be set automatically if --atomic is used
564 timeout (float): The time, in seconds, to wait
565 kubeconfig (str): Kubeconfig file path
garciadeblasb2ac7622024-02-08 13:05:27 +0100566 reset_values(bool): If set, helm resets values instead of reusing previous values.
567 reuse_values(bool): If set, helm reuses previous values.
568 reset_then_reuse_values(bool): If set, helm resets values, then apply the last release's values
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500569 force (bool): If set, helm forces resource updates through a replacement strategy. This may recreate pods.
Pedro Escaleira0a2060c2022-07-07 22:18:35 +0100570 Returns:
571 str: command to upgrade a Helm Chart release
572 """
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000573
574 timeout_str = ""
575 if timeout:
576 timeout_str = "--timeout {}s".format(timeout)
577
578 # atomic
579 atomic_str = ""
580 if atomic:
581 atomic_str = "--atomic"
582
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500583 # force
584 force_str = ""
585 if force:
586 force_str = "--force "
587
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000588 # version
589 version_str = ""
590 if version:
Pedro Escaleirafebbaa02023-06-27 16:42:41 -0300591 version_str = "--version {}".format(quote(version))
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000592
593 # namespace
594 namespace_str = ""
595 if namespace:
Pedro Escaleirafebbaa02023-06-27 16:42:41 -0300596 namespace_str = "--namespace {}".format(quote(namespace))
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000597
garciadeblasb2ac7622024-02-08 13:05:27 +0100598 # reset, reuse or reset_then_reuse values
599 on_values_str = "--reuse-values"
600 if reset_values:
601 on_values_str = "--reset-values"
602 elif reuse_values:
603 on_values_str = "--reuse-values"
604 elif reset_then_reuse_values:
605 on_values_str = "--reset-then-reuse-values"
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000606 command = (
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500607 "env KUBECONFIG={kubeconfig} {helm} upgrade {name} {model} {namespace} {atomic} {force}"
garciadeblasb2ac7622024-02-08 13:05:27 +0100608 "--output yaml {params} {timeout} {on_values} {ver}"
bravof7bd5c6a2021-11-17 11:14:57 -0300609 ).format(
610 kubeconfig=kubeconfig,
611 helm=self._helm_command,
Pedro Escaleirafebbaa02023-06-27 16:42:41 -0300612 name=quote(kdu_instance),
bravof7bd5c6a2021-11-17 11:14:57 -0300613 namespace=namespace_str,
614 atomic=atomic_str,
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500615 force=force_str,
bravof7bd5c6a2021-11-17 11:14:57 -0300616 params=params_str,
617 timeout=timeout_str,
Pedro Escaleirafebbaa02023-06-27 16:42:41 -0300618 model=quote(kdu_model),
garciadeblasb2ac7622024-02-08 13:05:27 +0100619 on_values=on_values_str,
bravof7bd5c6a2021-11-17 11:14:57 -0300620 ver=version_str,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000621 )
622 return command
623
garciadeblas82b591c2021-03-24 09:22:13 +0100624 def _get_rollback_command(
bravof7bd5c6a2021-11-17 11:14:57 -0300625 self, kdu_instance: str, namespace: str, revision: float, kubeconfig: str
garciadeblas82b591c2021-03-24 09:22:13 +0100626 ) -> str:
bravof7bd5c6a2021-11-17 11:14:57 -0300627 return "env KUBECONFIG={} {} rollback {} {} --namespace={} --wait".format(
Pedro Escaleirafebbaa02023-06-27 16:42:41 -0300628 kubeconfig,
629 self._helm_command,
630 quote(kdu_instance),
631 revision,
632 quote(namespace),
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000633 )
634
bravof7bd5c6a2021-11-17 11:14:57 -0300635 def _get_uninstall_command(
636 self, kdu_instance: str, namespace: str, kubeconfig: str
637 ) -> str:
bravof7bd5c6a2021-11-17 11:14:57 -0300638 return "env KUBECONFIG={} {} uninstall {} --namespace={}".format(
Pedro Escaleirafebbaa02023-06-27 16:42:41 -0300639 kubeconfig, self._helm_command, quote(kdu_instance), quote(namespace)
garciadeblas82b591c2021-03-24 09:22:13 +0100640 )
lloretgalleg095392b2020-11-20 11:28:08 +0000641
642 def _get_helm_chart_repos_ids(self, cluster_uuid) -> list:
643 repo_ids = []
644 cluster_filter = {"_admin.helm-chart-v3.id": cluster_uuid}
645 cluster = self.db.get_one("k8sclusters", cluster_filter)
646 if cluster:
647 repo_ids = cluster.get("_admin").get("helm_chart_repos") or []
648 return repo_ids
649 else:
650 raise K8sException(
651 "k8cluster with helm-id : {} not found".format(cluster_uuid)
652 )