blob: f0cbb46bc0ce99adecf03f997b558ff6dd062e61 [file] [log] [blame]
quilesj26c78a42019-10-28 18:10:42 +01001##
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##
quilesj26c78a42019-10-28 18:10:42 +010022import asyncio
Pedro Escaleirad901a802022-04-05 17:32:13 +010023from typing import Union
beierlmf52cb7c2020-04-21 16:36:35 -040024import os
beierlmf52cb7c2020-04-21 16:36:35 -040025import yaml
quilesj26c78a42019-10-28 18:10:42 +010026
lloretgalleg1c83f2e2020-10-22 09:12:35 +000027from n2vc.k8s_helm_base_conn import K8sHelmBaseConnector
28from n2vc.exceptions import K8sException
quilesj26c78a42019-10-28 18:10:42 +010029
lloretgalleg1c83f2e2020-10-22 09:12:35 +000030
31class K8sHelmConnector(K8sHelmBaseConnector):
quilesj26c78a42019-10-28 18:10:42 +010032
33 """
beierlmf52cb7c2020-04-21 16:36:35 -040034 ####################################################################################
35 ################################### P U B L I C ####################################
36 ####################################################################################
quilesj26c78a42019-10-28 18:10:42 +010037 """
38
39 def __init__(
beierlmf52cb7c2020-04-21 16:36:35 -040040 self,
41 fs: object,
42 db: object,
43 kubectl_command: str = "/usr/bin/kubectl",
44 helm_command: str = "/usr/bin/helm",
45 log: object = None,
46 on_update_db=None,
quilesj26c78a42019-10-28 18:10:42 +010047 ):
48 """
lloretgalleg1c83f2e2020-10-22 09:12:35 +000049 Initializes helm connector for helm v2
quilesj26c78a42019-10-28 18:10:42 +010050
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
lloretgalleg1c83f2e2020-10-22 09:12:35 +000060 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,
68 )
quilesj26c78a42019-10-28 18:10:42 +010069
lloretgalleg1c83f2e2020-10-22 09:12:35 +000070 self.log.info("Initializing K8S Helm2 connector")
quilesj26c78a42019-10-28 18:10:42 +010071
quilesj1be06302019-11-29 11:17:11 +000072 # initialize helm client-only
beierlmf52cb7c2020-04-21 16:36:35 -040073 self.log.debug("Initializing helm client-only...")
David Garciadd322062021-05-28 16:21:51 +020074 command = "{} init --client-only {} ".format(
75 self._helm_command,
76 "--stable-repo-url {}".format(self._stable_repo_url)
77 if self._stable_repo_url
78 else "--skip-repos",
garciadeblas82b591c2021-03-24 09:22:13 +010079 )
quilesj1be06302019-11-29 11:17:11 +000080 try:
beierlmf52cb7c2020-04-21 16:36:35 -040081 asyncio.ensure_future(
82 self._local_async_exec(command=command, raise_exception_on_error=False)
83 )
quilesj1be06302019-11-29 11:17:11 +000084 # loop = asyncio.get_event_loop()
beierlmf52cb7c2020-04-21 16:36:35 -040085 # loop.run_until_complete(self._local_async_exec(command=command,
86 # raise_exception_on_error=False))
quilesj1be06302019-11-29 11:17:11 +000087 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -040088 self.warning(
89 msg="helm init failed (it was already initialized): {}".format(e)
90 )
quilesj1be06302019-11-29 11:17:11 +000091
lloretgalleg1c83f2e2020-10-22 09:12:35 +000092 self.log.info("K8S Helm2 connector initialized")
quilesj26c78a42019-10-28 18:10:42 +010093
lloretgalleg095392b2020-11-20 11:28:08 +000094 async def install(
garciadeblas82b591c2021-03-24 09:22:13 +010095 self,
96 cluster_uuid: str,
97 kdu_model: str,
98 kdu_instance: str,
99 atomic: bool = True,
100 timeout: float = 300,
101 params: dict = None,
102 db_dict: dict = None,
103 kdu_name: str = None,
104 namespace: str = None,
105 **kwargs,
lloretgalleg095392b2020-11-20 11:28:08 +0000106 ):
David Garciaeb8943a2021-04-12 12:07:37 +0200107 """
108 Deploys of a new KDU instance. It would implicitly rely on the `install` call
109 to deploy the Chart/Bundle properly parametrized (in practice, this call would
110 happen before any _initial-config-primitive_of the VNF is called).
111
112 :param cluster_uuid: UUID of a K8s cluster known by OSM
113 :param kdu_model: chart/ reference (string), which can be either
114 of these options:
115 - a name of chart available via the repos known by OSM
116 - a path to a packaged chart
117 - a path to an unpacked chart directory or a URL
118 :param kdu_instance: Kdu instance name
119 :param atomic: If set, installation process purges chart/bundle on fail, also
120 will wait until all the K8s objects are active
121 :param timeout: Time in seconds to wait for the install of the chart/bundle
122 (defaults to Helm default timeout: 300s)
123 :param params: dictionary of key-value pairs for instantiation parameters
124 (overriding default values)
125 :param dict db_dict: where to write into database when the status changes.
126 It contains a dict with {collection: <str>, filter: {},
127 path: <str>},
128 e.g. {collection: "nsrs", filter:
129 {_id: <nsd-id>, path: "_admin.deployed.K8S.3"}
130 :param kdu_name: Name of the KDU instance to be installed
131 :param namespace: K8s namespace to use for the KDU instance
132 :param kwargs: Additional parameters (None yet)
133 :return: True if successful
134 """
David Garcia2a10e432022-06-17 14:27:54 +0200135 _, cluster_id = self._get_namespace_cluster_id(cluster_uuid)
136 self.log.debug("installing {} in cluster {}".format(kdu_model, cluster_id))
lloretgalleg095392b2020-11-20 11:28:08 +0000137
138 # sync local dir
David Garcia2a10e432022-06-17 14:27:54 +0200139 self.fs.sync(from_path=cluster_id)
lloretgalleg095392b2020-11-20 11:28:08 +0000140
141 # init env, paths
142 paths, env = self._init_paths_env(
David Garcia2a10e432022-06-17 14:27:54 +0200143 cluster_name=cluster_id, create_if_not_exist=True
lloretgalleg095392b2020-11-20 11:28:08 +0000144 )
145
David Garciac4da25c2021-02-23 11:47:29 +0100146 await self._install_impl(
David Garcia2a10e432022-06-17 14:27:54 +0200147 cluster_id,
David Garciac4da25c2021-02-23 11:47:29 +0100148 kdu_model,
149 paths,
150 env,
151 kdu_instance,
152 atomic=atomic,
153 timeout=timeout,
154 params=params,
155 db_dict=db_dict,
156 kdu_name=kdu_name,
157 namespace=namespace,
158 )
lloretgalleg095392b2020-11-20 11:28:08 +0000159
160 # sync fs
David Garcia2a10e432022-06-17 14:27:54 +0200161 self.fs.reverse_sync(from_path=cluster_id)
lloretgalleg095392b2020-11-20 11:28:08 +0000162
163 self.log.debug("Returning kdu_instance {}".format(kdu_instance))
David Garciac4da25c2021-02-23 11:47:29 +0100164 return True
lloretgalleg095392b2020-11-20 11:28:08 +0000165
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000166 async def inspect_kdu(self, kdu_model: str, repo_url: str = None) -> str:
167
168 self.log.debug(
169 "inspect kdu_model {} from (optional) repo: {}".format(kdu_model, repo_url)
170 )
171
172 return await self._exec_inspect_comand(
173 inspect_command="", kdu_model=kdu_model, repo_url=repo_url
174 )
175
176 """
177 ####################################################################################
178 ################################### P R I V A T E ##################################
179 ####################################################################################
180 """
181
182 def _init_paths_env(self, cluster_name: str, create_if_not_exist: bool = True):
tiernoa5728bf2020-06-25 15:48:52 +0000183 """
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000184 Creates and returns base cluster and kube dirs and returns them.
185 Also created helm3 dirs according to new directory specification, paths are
186 returned and also environment variables that must be provided to execute commands
187
188 Helm 2 directory specification uses helm_home dir:
189
190 The variables assigned for this paths are:
191 - Helm hone: $HELM_HOME
192 - helm kubeconfig: $KUBECONFIG
193
194 :param cluster_name: cluster_name
195 :return: Dictionary with config_paths and dictionary with helm environment variables
tiernoa5728bf2020-06-25 15:48:52 +0000196 """
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000197 base = self.fs.path
198 if base.endswith("/") or base.endswith("\\"):
199 base = base[:-1]
tiernoa5728bf2020-06-25 15:48:52 +0000200
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000201 # base dir for cluster
202 cluster_dir = base + "/" + cluster_name
garciadeblas54771fa2019-12-13 13:39:03 +0100203
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000204 # kube dir
205 kube_dir = cluster_dir + "/" + ".kube"
206 if create_if_not_exist and not os.path.exists(kube_dir):
207 self.log.debug("Creating dir {}".format(kube_dir))
208 os.makedirs(kube_dir)
quilesj26c78a42019-10-28 18:10:42 +0100209
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000210 # helm home dir
211 helm_dir = cluster_dir + "/" + ".helm"
212 if create_if_not_exist and not os.path.exists(helm_dir):
213 self.log.debug("Creating dir {}".format(helm_dir))
214 os.makedirs(helm_dir)
quilesj26c78a42019-10-28 18:10:42 +0100215
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000216 config_filename = kube_dir + "/config"
quilesj26c78a42019-10-28 18:10:42 +0100217
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000218 # 2 - Prepare dictionary with paths
219 paths = {
220 "kube_dir": kube_dir,
221 "kube_config": config_filename,
222 "cluster_dir": cluster_dir,
223 "helm_dir": helm_dir,
224 }
225
226 for file_name, file in paths.items():
227 if "dir" in file_name and not os.path.exists(file):
228 err_msg = "{} dir does not exist".format(file)
229 self.log.error(err_msg)
230 raise K8sException(err_msg)
231
232 # 3 - Prepare environment variables
233 env = {"HELM_HOME": helm_dir, "KUBECONFIG": config_filename}
234
235 return paths, env
236
David Garcia05bccf72022-02-02 11:35:20 +0100237 async def _get_services(self, cluster_id, kdu_instance, namespace, kubeconfig):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000238
239 # init config, env
240 paths, env = self._init_paths_env(
tiernoa5728bf2020-06-25 15:48:52 +0000241 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400242 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000243
David Garcia05bccf72022-02-02 11:35:20 +0100244 command1 = "env KUBECONFIG={} {} get manifest {} ".format(
245 kubeconfig, self._helm_command, kdu_instance
246 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000247 command2 = "{} get --namespace={} -f -".format(self.kubectl_command, namespace)
248 output, _rc = await self._local_async_exec_pipe(
249 command1, command2, env=env, raise_exception_on_error=True
250 )
251 services = self._parse_services(output)
252
253 return services
254
garciadeblas82b591c2021-03-24 09:22:13 +0100255 async def _cluster_init(
256 self, cluster_id: str, namespace: str, paths: dict, env: dict
257 ):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000258 """
259 Implements the helm version dependent cluster initialization:
260 For helm2 it initialized tiller environment if needed
261 """
quilesj26c78a42019-10-28 18:10:42 +0100262
263 # check if tiller pod is up in cluster
beierlmf52cb7c2020-04-21 16:36:35 -0400264 command = "{} --kubeconfig={} --namespace={} get deployments".format(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000265 self.kubectl_command, paths["kube_config"], namespace
beierlmf52cb7c2020-04-21 16:36:35 -0400266 )
267 output, _rc = await self._local_async_exec(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000268 command=command, raise_exception_on_error=True, env=env
beierlmf52cb7c2020-04-21 16:36:35 -0400269 )
quilesj26c78a42019-10-28 18:10:42 +0100270
tierno119f7232020-04-21 13:22:26 +0000271 output_table = self._output_to_table(output=output)
quilesj26c78a42019-10-28 18:10:42 +0100272
273 # find 'tiller' pod in all pods
274 already_initialized = False
275 try:
276 for row in output_table:
beierlmf52cb7c2020-04-21 16:36:35 -0400277 if row[0].startswith("tiller-deploy"):
quilesj26c78a42019-10-28 18:10:42 +0100278 already_initialized = True
279 break
beierlmf52cb7c2020-04-21 16:36:35 -0400280 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100281 pass
282
283 # helm init
284 n2vc_installed_sw = False
285 if not already_initialized:
beierlmf52cb7c2020-04-21 16:36:35 -0400286 self.log.info(
tiernoa5728bf2020-06-25 15:48:52 +0000287 "Initializing helm in client and server: {}".format(cluster_id)
beierlmf52cb7c2020-04-21 16:36:35 -0400288 )
tiernoa5728bf2020-06-25 15:48:52 +0000289 command = "{} --kubeconfig={} --namespace kube-system create serviceaccount {}".format(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000290 self.kubectl_command, paths["kube_config"], self.service_account
291 )
292 _, _rc = await self._local_async_exec(
293 command=command, raise_exception_on_error=False, env=env
294 )
tiernoa5728bf2020-06-25 15:48:52 +0000295
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000296 command = (
297 "{} --kubeconfig={} create clusterrolebinding osm-tiller-cluster-rule "
298 "--clusterrole=cluster-admin --serviceaccount=kube-system:{}"
299 ).format(self.kubectl_command, paths["kube_config"], self.service_account)
300 _, _rc = await self._local_async_exec(
301 command=command, raise_exception_on_error=False, env=env
302 )
tiernoa5728bf2020-06-25 15:48:52 +0000303
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000304 command = (
305 "{} --kubeconfig={} --tiller-namespace={} --home={} --service-account {} "
David Garciadd322062021-05-28 16:21:51 +0200306 " {} init"
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000307 ).format(
308 self._helm_command,
309 paths["kube_config"],
310 namespace,
311 paths["helm_dir"],
312 self.service_account,
David Garciadd322062021-05-28 16:21:51 +0200313 "--stable-repo-url {}".format(self._stable_repo_url)
314 if self._stable_repo_url
315 else "--skip-repos",
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000316 )
317 _, _rc = await self._local_async_exec(
318 command=command, raise_exception_on_error=True, env=env
319 )
quilesj26c78a42019-10-28 18:10:42 +0100320 n2vc_installed_sw = True
321 else:
322 # check client helm installation
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000323 check_file = paths["helm_dir"] + "/repository/repositories.yaml"
324 if not self._check_file_exists(
325 filename=check_file, exception_if_not_exists=False
326 ):
tiernoa5728bf2020-06-25 15:48:52 +0000327 self.log.info("Initializing helm in client: {}".format(cluster_id))
beierlmf52cb7c2020-04-21 16:36:35 -0400328 command = (
329 "{} --kubeconfig={} --tiller-namespace={} "
David Garciadd322062021-05-28 16:21:51 +0200330 "--home={} init --client-only {} "
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000331 ).format(
332 self._helm_command,
333 paths["kube_config"],
334 namespace,
335 paths["helm_dir"],
David Garciadd322062021-05-28 16:21:51 +0200336 "--stable-repo-url {}".format(self._stable_repo_url)
337 if self._stable_repo_url
338 else "--skip-repos",
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000339 )
beierlmf52cb7c2020-04-21 16:36:35 -0400340 output, _rc = await self._local_async_exec(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000341 command=command, raise_exception_on_error=True, env=env
beierlmf52cb7c2020-04-21 16:36:35 -0400342 )
quilesj26c78a42019-10-28 18:10:42 +0100343 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400344 self.log.info("Helm client already initialized")
quilesj26c78a42019-10-28 18:10:42 +0100345
David Garcia2a10e432022-06-17 14:27:54 +0200346 # remove old stable repo and add new one
347 cluster_uuid = "{}:{}".format(namespace, cluster_id)
348 repo_list = await self.repo_list(cluster_uuid)
lloretgalleg83e55892020-12-17 12:42:11 +0000349 for repo in repo_list:
350 if repo["name"] == "stable" and repo["url"] != self._stable_repo_url:
351 self.log.debug("Add new stable repo url: {}")
David Garcia2a10e432022-06-17 14:27:54 +0200352 await self.repo_remove(cluster_uuid, "stable")
David Garciadd322062021-05-28 16:21:51 +0200353 if self._stable_repo_url:
David Garcia2a10e432022-06-17 14:27:54 +0200354 await self.repo_add(cluster_uuid, "stable", self._stable_repo_url)
lloretgalleg83e55892020-12-17 12:42:11 +0000355 break
356
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000357 return n2vc_installed_sw
lloretgallege308c712020-09-02 09:40:38 +0000358
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000359 async def _uninstall_sw(self, cluster_id: str, namespace: str):
360 # uninstall Tiller if necessary
quilesj26c78a42019-10-28 18:10:42 +0100361
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000362 self.log.debug("Uninstalling tiller from cluster {}".format(cluster_id))
quilesj26c78a42019-10-28 18:10:42 +0100363
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000364 # init paths, env
365 paths, env = self._init_paths_env(
tiernoa5728bf2020-06-25 15:48:52 +0000366 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400367 )
quilesj26c78a42019-10-28 18:10:42 +0100368
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000369 if not namespace:
370 # find namespace for tiller pod
371 command = "{} --kubeconfig={} get deployments --all-namespaces".format(
372 self.kubectl_command, paths["kube_config"]
373 )
374 output, _rc = await self._local_async_exec(
375 command=command, raise_exception_on_error=False, env=env
376 )
377 output_table = self._output_to_table(output=output)
378 namespace = None
379 for r in output_table:
380 try:
381 if "tiller-deploy" in r[1]:
382 namespace = r[0]
383 break
384 except Exception:
385 pass
quilesj26c78a42019-10-28 18:10:42 +0100386 else:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000387 msg = "Tiller deployment not found in cluster {}".format(cluster_id)
388 self.log.error(msg)
quilesj26c78a42019-10-28 18:10:42 +0100389
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000390 self.log.debug("namespace for tiller: {}".format(namespace))
quilesj26c78a42019-10-28 18:10:42 +0100391
tierno53555f62020-04-07 11:08:16 +0000392 if namespace:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000393 # uninstall tiller from cluster
394 self.log.debug("Uninstalling tiller from cluster {}".format(cluster_id))
395 command = "{} --kubeconfig={} --home={} reset".format(
396 self._helm_command, paths["kube_config"], paths["helm_dir"]
beierlmf52cb7c2020-04-21 16:36:35 -0400397 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000398 self.log.debug("resetting: {}".format(command))
399 output, _rc = await self._local_async_exec(
400 command=command, raise_exception_on_error=True, env=env
quilesj26c78a42019-10-28 18:10:42 +0100401 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000402 # Delete clusterrolebinding and serviceaccount.
403 # Ignore if errors for backward compatibility
404 command = (
405 "{} --kubeconfig={} delete clusterrolebinding.rbac.authorization.k8s."
406 "io/osm-tiller-cluster-rule"
407 ).format(self.kubectl_command, paths["kube_config"])
408 output, _rc = await self._local_async_exec(
409 command=command, raise_exception_on_error=False, env=env
quilesj26c78a42019-10-28 18:10:42 +0100410 )
David Garcia2a10e432022-06-17 14:27:54 +0200411 command = "{} --kubeconfig={} --namespace kube-system delete serviceaccount/{}".format(
412 self.kubectl_command, paths["kube_config"], self.service_account
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000413 )
414 output, _rc = await self._local_async_exec(
415 command=command, raise_exception_on_error=False, env=env
416 )
quilesj26c78a42019-10-28 18:10:42 +0100417
418 else:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000419 self.log.debug("namespace not found")
quilesj26c78a42019-10-28 18:10:42 +0100420
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000421 async def _instances_list(self, cluster_id):
quilesj26c78a42019-10-28 18:10:42 +0100422
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000423 # init paths, env
424 paths, env = self._init_paths_env(
tiernoa5728bf2020-06-25 15:48:52 +0000425 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400426 )
quilesj26c78a42019-10-28 18:10:42 +0100427
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000428 command = "{} list --output yaml".format(self._helm_command)
quilesj26c78a42019-10-28 18:10:42 +0100429
beierlmf52cb7c2020-04-21 16:36:35 -0400430 output, _rc = await self._local_async_exec(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000431 command=command, raise_exception_on_error=True, env=env
beierlmf52cb7c2020-04-21 16:36:35 -0400432 )
quilesj26c78a42019-10-28 18:10:42 +0100433
434 if output and len(output) > 0:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000435 # parse yaml and update keys to lower case to unify with helm3
436 instances = yaml.load(output, Loader=yaml.SafeLoader).get("Releases")
437 new_instances = []
438 for instance in instances:
439 new_instance = dict((k.lower(), v) for k, v in instance.items())
440 new_instances.append(new_instance)
441 return new_instances
quilesj26c78a42019-10-28 18:10:42 +0100442 else:
443 return []
444
garciadeblas82b591c2021-03-24 09:22:13 +0100445 def _get_inspect_command(
446 self, show_command: str, kdu_model: str, repo_str: str, version: str
447 ):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000448 inspect_command = "{} inspect {} {}{} {}".format(
449 self._helm_command, show_command, kdu_model, repo_str, version
beierlmf52cb7c2020-04-21 16:36:35 -0400450 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000451 return inspect_command
quilesj1be06302019-11-29 11:17:11 +0000452
quilesj26c78a42019-10-28 18:10:42 +0100453 async def _status_kdu(
beierlmf52cb7c2020-04-21 16:36:35 -0400454 self,
tiernoa5728bf2020-06-25 15:48:52 +0000455 cluster_id: str,
beierlmf52cb7c2020-04-21 16:36:35 -0400456 kdu_instance: str,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000457 namespace: str = None,
Pedro Escaleirad901a802022-04-05 17:32:13 +0100458 yaml_format: bool = False,
beierlmf52cb7c2020-04-21 16:36:35 -0400459 show_error_log: bool = False,
Pedro Escaleirad901a802022-04-05 17:32:13 +0100460 ) -> Union[str, dict]:
quilesj26c78a42019-10-28 18:10:42 +0100461
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000462 self.log.debug(
463 "status of kdu_instance: {}, namespace: {} ".format(kdu_instance, namespace)
464 )
quilesj26c78a42019-10-28 18:10:42 +0100465
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000466 # init config, env
467 paths, env = self._init_paths_env(
tiernoa5728bf2020-06-25 15:48:52 +0000468 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400469 )
David Garcia05bccf72022-02-02 11:35:20 +0100470 command = ("env KUBECONFIG={} {} status {} --output yaml").format(
471 paths["kube_config"], self._helm_command, kdu_instance
472 )
quilesj26c78a42019-10-28 18:10:42 +0100473 output, rc = await self._local_async_exec(
474 command=command,
475 raise_exception_on_error=True,
beierlmf52cb7c2020-04-21 16:36:35 -0400476 show_error_log=show_error_log,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000477 env=env,
quilesj26c78a42019-10-28 18:10:42 +0100478 )
479
Pedro Escaleirad901a802022-04-05 17:32:13 +0100480 if yaml_format:
quilesj1be06302019-11-29 11:17:11 +0000481 return str(output)
482
quilesj26c78a42019-10-28 18:10:42 +0100483 if rc != 0:
484 return None
485
486 data = yaml.load(output, Loader=yaml.SafeLoader)
487
488 # remove field 'notes'
489 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400490 del data.get("info").get("status")["notes"]
quilesj26c78a42019-10-28 18:10:42 +0100491 except KeyError:
492 pass
493
Pedro Escaleirabeaac1e2022-04-03 13:51:46 +0100494 # parse the manifest to a list of dictionaries
495 if "manifest" in data:
496 manifest_str = data.get("manifest")
497 manifest_docs = yaml.load_all(manifest_str, Loader=yaml.SafeLoader)
498
499 data["manifest"] = []
500 for doc in manifest_docs:
501 data["manifest"].append(doc)
502
quilesj26c78a42019-10-28 18:10:42 +0100503 # parse field 'resources'
504 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400505 resources = str(data.get("info").get("status").get("resources"))
quilesj26c78a42019-10-28 18:10:42 +0100506 resource_table = self._output_to_table(resources)
beierlmf52cb7c2020-04-21 16:36:35 -0400507 data.get("info").get("status")["resources"] = resource_table
508 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100509 pass
510
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000511 # set description to lowercase (unify with helm3)
512 try:
513 data.get("info")["description"] = data.get("info").pop("Description")
514 except KeyError:
515 pass
516
quilesj26c78a42019-10-28 18:10:42 +0100517 return data
518
lloretgalleg095392b2020-11-20 11:28:08 +0000519 def _get_helm_chart_repos_ids(self, cluster_uuid) -> list:
520 repo_ids = []
521 cluster_filter = {"_admin.helm-chart.id": cluster_uuid}
522 cluster = self.db.get_one("k8sclusters", cluster_filter)
523 if cluster:
524 repo_ids = cluster.get("_admin").get("helm_chart_repos") or []
525 return repo_ids
526 else:
527 raise K8sException(
528 "k8cluster with helm-id : {} not found".format(cluster_uuid)
529 )
530
tiernoa5728bf2020-06-25 15:48:52 +0000531 async def _is_install_completed(self, cluster_id: str, kdu_instance: str) -> bool:
David Garcia05bccf72022-02-02 11:35:20 +0100532 # init config, env
533 paths, env = self._init_paths_env(
534 cluster_name=cluster_id, create_if_not_exist=True
535 )
quilesj26c78a42019-10-28 18:10:42 +0100536
beierlmf52cb7c2020-04-21 16:36:35 -0400537 status = await self._status_kdu(
Pedro Escaleirad901a802022-04-05 17:32:13 +0100538 cluster_id=cluster_id, kdu_instance=kdu_instance, yaml_format=False
beierlmf52cb7c2020-04-21 16:36:35 -0400539 )
quilesj26c78a42019-10-28 18:10:42 +0100540
541 # extract info.status.resources-> str
542 # format:
543 # ==> v1/Deployment
544 # NAME READY UP-TO-DATE AVAILABLE AGE
545 # halting-horse-mongodb 0/1 1 0 0s
546 # halting-petit-mongodb 1/1 1 0 0s
547 # blank line
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000548 resources = K8sHelmBaseConnector._get_deep(
549 status, ("info", "status", "resources")
550 )
quilesj26c78a42019-10-28 18:10:42 +0100551
552 # convert to table
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000553 resources = K8sHelmBaseConnector._output_to_table(resources)
quilesj26c78a42019-10-28 18:10:42 +0100554
555 num_lines = len(resources)
556 index = 0
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000557 ready = True
quilesj26c78a42019-10-28 18:10:42 +0100558 while index < num_lines:
559 try:
560 line1 = resources[index]
561 index += 1
562 # find '==>' in column 0
beierlmf52cb7c2020-04-21 16:36:35 -0400563 if line1[0] == "==>":
quilesj26c78a42019-10-28 18:10:42 +0100564 line2 = resources[index]
565 index += 1
566 # find READY in column 1
beierlmf52cb7c2020-04-21 16:36:35 -0400567 if line2[1] == "READY":
quilesj26c78a42019-10-28 18:10:42 +0100568 # read next lines
569 line3 = resources[index]
570 index += 1
571 while len(line3) > 1 and index < num_lines:
572 ready_value = line3[1]
beierlmf52cb7c2020-04-21 16:36:35 -0400573 parts = ready_value.split(sep="/")
quilesj26c78a42019-10-28 18:10:42 +0100574 current = int(parts[0])
575 total = int(parts[1])
576 if current < total:
beierlmf52cb7c2020-04-21 16:36:35 -0400577 self.log.debug("NOT READY:\n {}".format(line3))
quilesj26c78a42019-10-28 18:10:42 +0100578 ready = False
579 line3 = resources[index]
580 index += 1
581
beierlmf52cb7c2020-04-21 16:36:35 -0400582 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100583 pass
584
585 return ready
586
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000587 def _get_install_command(
David Garcia05bccf72022-02-02 11:35:20 +0100588 self,
589 kdu_model,
590 kdu_instance,
591 namespace,
592 params_str,
593 version,
594 atomic,
595 timeout,
596 kubeconfig,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000597 ) -> str:
lloretgallegd99f3f22020-06-29 14:18:30 +0000598
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000599 timeout_str = ""
600 if timeout:
601 timeout_str = "--timeout {}".format(timeout)
lloretgallegd99f3f22020-06-29 14:18:30 +0000602
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000603 # atomic
604 atomic_str = ""
605 if atomic:
606 atomic_str = "--atomic"
607 # namespace
608 namespace_str = ""
609 if namespace:
610 namespace_str = "--namespace {}".format(namespace)
lloretgallegd99f3f22020-06-29 14:18:30 +0000611
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000612 # version
613 version_str = ""
614 if version:
615 version_str = version_str = "--version {}".format(version)
lloretgallegd99f3f22020-06-29 14:18:30 +0000616
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000617 command = (
David Garcia05bccf72022-02-02 11:35:20 +0100618 "env KUBECONFIG={kubeconfig} {helm} install {atomic} --output yaml "
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000619 "{params} {timeout} --name={name} {ns} {model} {ver}".format(
David Garcia05bccf72022-02-02 11:35:20 +0100620 kubeconfig=kubeconfig,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000621 helm=self._helm_command,
622 atomic=atomic_str,
623 params=params_str,
624 timeout=timeout_str,
625 name=kdu_instance,
626 ns=namespace_str,
627 model=kdu_model,
628 ver=version_str,
beierlmf52cb7c2020-04-21 16:36:35 -0400629 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000630 )
631 return command
quilesj26c78a42019-10-28 18:10:42 +0100632
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000633 def _get_upgrade_command(
David Garcia05bccf72022-02-02 11:35:20 +0100634 self,
635 kdu_model,
636 kdu_instance,
637 namespace,
638 params_str,
639 version,
640 atomic,
641 timeout,
642 kubeconfig,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000643 ) -> str:
quilesj26c78a42019-10-28 18:10:42 +0100644
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000645 timeout_str = ""
646 if timeout:
647 timeout_str = "--timeout {}".format(timeout)
quilesj26c78a42019-10-28 18:10:42 +0100648
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000649 # atomic
650 atomic_str = ""
651 if atomic:
652 atomic_str = "--atomic"
quilesj26c78a42019-10-28 18:10:42 +0100653
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000654 # version
655 version_str = ""
656 if version:
657 version_str = "--version {}".format(version)
quilesj26c78a42019-10-28 18:10:42 +0100658
David Garcia05bccf72022-02-02 11:35:20 +0100659 command = (
660 "env KUBECONFIG={kubeconfig} {helm} upgrade {atomic} --output yaml {params} {timeout} {name} {model} {ver}"
661 ).format(
662 kubeconfig=kubeconfig,
garciadeblas82b591c2021-03-24 09:22:13 +0100663 helm=self._helm_command,
664 atomic=atomic_str,
665 params=params_str,
666 timeout=timeout_str,
667 name=kdu_instance,
668 model=kdu_model,
669 ver=version_str,
670 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000671 return command
quilesj26c78a42019-10-28 18:10:42 +0100672
David Garcia05bccf72022-02-02 11:35:20 +0100673 def _get_rollback_command(
674 self, kdu_instance, namespace, revision, kubeconfig
675 ) -> str:
676 return "env KUBECONFIG={} {} rollback {} {} --wait".format(
677 kubeconfig, self._helm_command, kdu_instance, revision
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000678 )
quilesj26c78a42019-10-28 18:10:42 +0100679
David Garcia05bccf72022-02-02 11:35:20 +0100680 def _get_uninstall_command(
681 self, kdu_instance: str, namespace: str, kubeconfig: str
682 ) -> str:
683 return "env KUBECONFIG={} {} delete --purge {}".format(
684 kubeconfig, self._helm_command, kdu_instance
685 )