Update only the repo that was added instead of all repos
[osm/N2VC.git] / n2vc / k8s_juju_conn.py
1 # Copyright 2019 Canonical Ltd.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import asyncio
16 from typing import Union
17 import os
18 import uuid
19 import yaml
20 import tempfile
21 import binascii
22
23 from n2vc.config import EnvironConfig
24 from n2vc.definitions import RelationEndpoint
25 from n2vc.exceptions import K8sException
26 from n2vc.k8s_conn import K8sConnector
27 from n2vc.kubectl import Kubectl
28 from .exceptions import MethodNotImplemented
29 from n2vc.libjuju import Libjuju
30 from n2vc.utils import obj_to_dict, obj_to_yaml
31 from n2vc.store import MotorStore
32 from n2vc.vca.cloud import Cloud
33 from n2vc.vca.connection import get_connection
34
35
36 RBAC_LABEL_KEY_NAME = "rbac-id"
37 RBAC_STACK_PREFIX = "juju-credential"
38
39
40 def generate_rbac_id():
41 return binascii.hexlify(os.urandom(4)).decode()
42
43
44 class K8sJujuConnector(K8sConnector):
45 libjuju = None
46
47 def __init__(
48 self,
49 fs: object,
50 db: object,
51 kubectl_command: str = "/usr/bin/kubectl",
52 juju_command: str = "/usr/bin/juju",
53 log: object = None,
54 loop: object = None,
55 on_update_db=None,
56 ):
57 """
58 :param fs: file system for kubernetes and helm configuration
59 :param db: Database object
60 :param kubectl_command: path to kubectl executable
61 :param helm_command: path to helm executable
62 :param log: logger
63 :param: loop: Asyncio loop
64 """
65
66 # parent class
67 K8sConnector.__init__(
68 self,
69 db,
70 log=log,
71 on_update_db=on_update_db,
72 )
73
74 self.fs = fs
75 self.loop = loop or asyncio.get_event_loop()
76 self.log.debug("Initializing K8S Juju connector")
77
78 db_uri = EnvironConfig(prefixes=["OSMLCM_", "OSMMON_"]).get("database_uri")
79 self._store = MotorStore(db_uri)
80 self.loading_libjuju = asyncio.Lock(loop=self.loop)
81 self.uninstall_locks = {}
82
83 self.log.debug("K8S Juju connector initialized")
84 # TODO: Remove these commented lines:
85 # self.authenticated = False
86 # self.models = {}
87 # self.juju_secret = ""
88
89 """Initialization"""
90
91 async def init_env(
92 self,
93 k8s_creds: str,
94 namespace: str = "kube-system",
95 reuse_cluster_uuid: str = None,
96 **kwargs,
97 ) -> (str, bool):
98 """
99 It prepares a given K8s cluster environment to run Juju bundles.
100
101 :param k8s_creds: credentials to access a given K8s cluster, i.e. a valid
102 '.kube/config'
103 :param namespace: optional namespace to be used for juju. By default,
104 'kube-system' will be used
105 :param reuse_cluster_uuid: existing cluster uuid for reuse
106 :param: kwargs: Additional parameters
107 vca_id (str): VCA ID
108
109 :return: uuid of the K8s cluster and True if connector has installed some
110 software in the cluster
111 (on error, an exception will be raised)
112 """
113 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
114
115 cluster_uuid = reuse_cluster_uuid or str(uuid.uuid4())
116 kubectl = self._get_kubectl(k8s_creds)
117
118 # CREATING RESOURCES IN K8S
119 rbac_id = generate_rbac_id()
120 metadata_name = "{}-{}".format(RBAC_STACK_PREFIX, rbac_id)
121 labels = {RBAC_STACK_PREFIX: rbac_id}
122
123 # Create cleanup dictionary to clean up created resources
124 # if it fails in the middle of the process
125 cleanup_data = []
126 try:
127 kubectl.create_cluster_role(
128 name=metadata_name,
129 labels=labels,
130 )
131 cleanup_data.append(
132 {
133 "delete": kubectl.delete_cluster_role,
134 "args": (metadata_name,),
135 }
136 )
137
138 kubectl.create_service_account(
139 name=metadata_name,
140 labels=labels,
141 )
142 cleanup_data.append(
143 {
144 "delete": kubectl.delete_service_account,
145 "args": (metadata_name,),
146 }
147 )
148
149 kubectl.create_cluster_role_binding(
150 name=metadata_name,
151 labels=labels,
152 )
153 cleanup_data.append(
154 {
155 "delete": kubectl.delete_service_account,
156 "args": (metadata_name,),
157 }
158 )
159 token, client_cert_data = await kubectl.get_secret_data(
160 metadata_name,
161 )
162
163 default_storage_class = kubectl.get_default_storage_class()
164 await libjuju.add_k8s(
165 name=cluster_uuid,
166 rbac_id=rbac_id,
167 token=token,
168 client_cert_data=client_cert_data,
169 configuration=kubectl.configuration,
170 storage_class=default_storage_class,
171 credential_name=self._get_credential_name(cluster_uuid),
172 )
173 return cluster_uuid, True
174 except Exception as e:
175 self.log.error("Error initializing k8scluster: {}".format(e))
176 if len(cleanup_data) > 0:
177 self.log.debug("Cleaning up created resources in k8s cluster...")
178 for item in cleanup_data:
179 delete_function = item["delete"]
180 delete_args = item["args"]
181 delete_function(*delete_args)
182 self.log.debug("Cleanup finished")
183 raise e
184
185 """Repo Management"""
186
187 async def repo_add(
188 self,
189 name: str,
190 url: str,
191 _type: str = "charm",
192 ):
193 raise MethodNotImplemented()
194
195 async def repo_list(self):
196 raise MethodNotImplemented()
197
198 async def repo_remove(
199 self,
200 name: str,
201 ):
202 raise MethodNotImplemented()
203
204 async def synchronize_repos(self, cluster_uuid: str, name: str):
205 """
206 Returns None as currently add_repo is not implemented
207 """
208 return None
209
210 """Reset"""
211
212 async def reset(
213 self,
214 cluster_uuid: str,
215 force: bool = False,
216 uninstall_sw: bool = False,
217 **kwargs,
218 ) -> bool:
219 """Reset a cluster
220
221 Resets the Kubernetes cluster by removing the model that represents it.
222
223 :param cluster_uuid str: The UUID of the cluster to reset
224 :param force: Force reset
225 :param uninstall_sw: Boolean to uninstall sw
226 :param: kwargs: Additional parameters
227 vca_id (str): VCA ID
228
229 :return: Returns True if successful or raises an exception.
230 """
231
232 try:
233 self.log.debug("[reset] Removing k8s cloud")
234 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
235
236 cloud = Cloud(cluster_uuid, self._get_credential_name(cluster_uuid))
237
238 cloud_creds = await libjuju.get_cloud_credentials(cloud)
239
240 await libjuju.remove_cloud(cluster_uuid)
241
242 credentials = self.get_credentials(cluster_uuid=cluster_uuid)
243
244 kubectl = self._get_kubectl(credentials)
245
246 delete_functions = [
247 kubectl.delete_cluster_role_binding,
248 kubectl.delete_service_account,
249 kubectl.delete_cluster_role,
250 ]
251
252 credential_attrs = cloud_creds[0].result["attrs"]
253 if RBAC_LABEL_KEY_NAME in credential_attrs:
254 rbac_id = credential_attrs[RBAC_LABEL_KEY_NAME]
255 metadata_name = "{}-{}".format(RBAC_STACK_PREFIX, rbac_id)
256 for delete_func in delete_functions:
257 try:
258 delete_func(metadata_name)
259 except Exception as e:
260 self.log.warning("Cannot remove resource in K8s {}".format(e))
261
262 except Exception as e:
263 self.log.debug("Caught exception during reset: {}".format(e))
264 raise e
265 return True
266
267 """Deployment"""
268
269 async def install(
270 self,
271 cluster_uuid: str,
272 kdu_model: str,
273 kdu_instance: str,
274 atomic: bool = True,
275 timeout: float = 1800,
276 params: dict = None,
277 db_dict: dict = None,
278 kdu_name: str = None,
279 namespace: str = None,
280 **kwargs,
281 ) -> bool:
282 """Install a bundle
283
284 :param cluster_uuid str: The UUID of the cluster to install to
285 :param kdu_model str: The name or path of a bundle to install
286 :param kdu_instance: Kdu instance name
287 :param atomic bool: If set, waits until the model is active and resets
288 the cluster on failure.
289 :param timeout int: The time, in seconds, to wait for the install
290 to finish
291 :param params dict: Key-value pairs of instantiation parameters
292 :param kdu_name: Name of the KDU instance to be installed
293 :param namespace: K8s namespace to use for the KDU instance
294 :param kwargs: Additional parameters
295 vca_id (str): VCA ID
296
297 :return: If successful, returns ?
298 """
299 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
300 bundle = kdu_model
301
302 if not db_dict:
303 raise K8sException("db_dict must be set")
304 if not bundle:
305 raise K8sException("bundle must be set")
306
307 if bundle.startswith("cs:"):
308 # For Juju Bundles provided by the Charm Store
309 pass
310 elif bundle.startswith("ch:"):
311 # For Juju Bundles provided by the Charm Hub (this only works for juju version >= 2.9)
312 pass
313 elif bundle.startswith("http"):
314 # Download the file
315 pass
316 else:
317 new_workdir = kdu_model.strip(kdu_model.split("/")[-1])
318 os.chdir(new_workdir)
319 bundle = "local:{}".format(kdu_model)
320
321 self.log.debug("Checking for model named {}".format(kdu_instance))
322
323 # Create the new model
324 self.log.debug("Adding model: {}".format(kdu_instance))
325 cloud = Cloud(cluster_uuid, self._get_credential_name(cluster_uuid))
326 await libjuju.add_model(kdu_instance, cloud)
327
328 # if model:
329 # TODO: Instantiation parameters
330
331 """
332 "Juju bundle that models the KDU, in any of the following ways:
333 - <juju-repo>/<juju-bundle>
334 - <juju-bundle folder under k8s_models folder in the package>
335 - <juju-bundle tgz file (w/ or w/o extension) under k8s_models folder
336 in the package>
337 - <URL_where_to_fetch_juju_bundle>
338 """
339 try:
340 previous_workdir = os.getcwd()
341 except FileNotFoundError:
342 previous_workdir = "/app/storage"
343
344 self.log.debug("[install] deploying {}".format(bundle))
345 await libjuju.deploy(
346 bundle, model_name=kdu_instance, wait=atomic, timeout=timeout
347 )
348 os.chdir(previous_workdir)
349 if self.on_update_db:
350 await self.on_update_db(
351 cluster_uuid,
352 kdu_instance,
353 filter=db_dict["filter"],
354 vca_id=kwargs.get("vca_id"),
355 )
356 return True
357
358 async def scale(
359 self,
360 kdu_instance: str,
361 scale: int,
362 resource_name: str,
363 total_timeout: float = 1800,
364 **kwargs,
365 ) -> bool:
366 """Scale an application in a model
367
368 :param: kdu_instance str: KDU instance name
369 :param: scale int: Scale to which to set this application
370 :param: resource_name str: Resource name (Application name)
371 :param: timeout float: The time, in seconds, to wait for the install
372 to finish
373 :param kwargs: Additional parameters
374 vca_id (str): VCA ID
375
376 :return: If successful, returns True
377 """
378
379 try:
380 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
381 await libjuju.scale_application(
382 model_name=kdu_instance,
383 application_name=resource_name,
384 scale=scale,
385 total_timeout=total_timeout,
386 )
387 except Exception as e:
388 error_msg = "Error scaling application {} in kdu instance {}: {}".format(
389 resource_name, kdu_instance, e
390 )
391 self.log.error(error_msg)
392 raise K8sException(message=error_msg)
393 return True
394
395 async def get_scale_count(
396 self,
397 resource_name: str,
398 kdu_instance: str,
399 **kwargs,
400 ) -> int:
401 """Get an application scale count
402
403 :param: resource_name str: Resource name (Application name)
404 :param: kdu_instance str: KDU instance name
405 :param kwargs: Additional parameters
406 vca_id (str): VCA ID
407 :return: Return application instance count
408 """
409 try:
410 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
411 status = await libjuju.get_model_status(kdu_instance)
412 return len(status.applications[resource_name].units)
413 except Exception as e:
414 error_msg = "Error getting scale count from application {} in kdu instance {}: {}".format(
415 resource_name, kdu_instance, e
416 )
417 self.log.error(error_msg)
418 raise K8sException(message=error_msg)
419
420 async def instances_list(self, cluster_uuid: str) -> list:
421 """
422 returns a list of deployed releases in a cluster
423
424 :param cluster_uuid: the cluster
425 :return:
426 """
427 return []
428
429 async def upgrade(
430 self,
431 cluster_uuid: str,
432 kdu_instance: str,
433 kdu_model: str = None,
434 params: dict = None,
435 ) -> str:
436 """Upgrade a model
437
438 :param cluster_uuid str: The UUID of the cluster to upgrade
439 :param kdu_instance str: The unique name of the KDU instance
440 :param kdu_model str: The name or path of the bundle to upgrade to
441 :param params dict: Key-value pairs of instantiation parameters
442
443 :return: If successful, reference to the new revision number of the
444 KDU instance.
445 """
446
447 # TODO: Loop through the bundle and upgrade each charm individually
448
449 """
450 The API doesn't have a concept of bundle upgrades, because there are
451 many possible changes: charm revision, disk, number of units, etc.
452
453 As such, we are only supporting a limited subset of upgrades. We'll
454 upgrade the charm revision but leave storage and scale untouched.
455
456 Scale changes should happen through OSM constructs, and changes to
457 storage would require a redeployment of the service, at least in this
458 initial release.
459 """
460 raise MethodNotImplemented()
461
462 """Rollback"""
463
464 async def rollback(
465 self,
466 cluster_uuid: str,
467 kdu_instance: str,
468 revision: int = 0,
469 ) -> str:
470 """Rollback a model
471
472 :param cluster_uuid str: The UUID of the cluster to rollback
473 :param kdu_instance str: The unique name of the KDU instance
474 :param revision int: The revision to revert to. If omitted, rolls back
475 the previous upgrade.
476
477 :return: If successful, returns the revision of active KDU instance,
478 or raises an exception
479 """
480 raise MethodNotImplemented()
481
482 """Deletion"""
483
484 async def uninstall(
485 self,
486 cluster_uuid: str,
487 kdu_instance: str,
488 **kwargs,
489 ) -> bool:
490 """Uninstall a KDU instance
491
492 :param cluster_uuid str: The UUID of the cluster
493 :param kdu_instance str: The unique name of the KDU instance
494 :param kwargs: Additional parameters
495 vca_id (str): VCA ID
496
497 :return: Returns True if successful, or raises an exception
498 """
499
500 self.log.debug("[uninstall] Destroying model")
501
502 will_not_delete = False
503 if kdu_instance not in self.uninstall_locks:
504 self.uninstall_locks[kdu_instance] = asyncio.Lock(loop=self.loop)
505 delete_lock = self.uninstall_locks[kdu_instance]
506
507 while delete_lock.locked():
508 will_not_delete = True
509 await asyncio.sleep(0.1)
510
511 if will_not_delete:
512 self.log.info("Model {} deleted by another worker.".format(kdu_instance))
513 return True
514
515 try:
516 async with delete_lock:
517 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
518
519 await libjuju.destroy_model(kdu_instance, total_timeout=3600)
520 finally:
521 self.uninstall_locks.pop(kdu_instance)
522
523 self.log.debug(f"[uninstall] Model {kdu_instance} destroyed")
524 return True
525
526 async def exec_primitive(
527 self,
528 cluster_uuid: str = None,
529 kdu_instance: str = None,
530 primitive_name: str = None,
531 timeout: float = 300,
532 params: dict = None,
533 db_dict: dict = None,
534 **kwargs,
535 ) -> str:
536 """Exec primitive (Juju action)
537
538 :param cluster_uuid str: The UUID of the cluster
539 :param kdu_instance str: The unique name of the KDU instance
540 :param primitive_name: Name of action that will be executed
541 :param timeout: Timeout for action execution
542 :param params: Dictionary of all the parameters needed for the action
543 :param db_dict: Dictionary for any additional data
544 :param kwargs: Additional parameters
545 vca_id (str): VCA ID
546
547 :return: Returns the output of the action
548 """
549 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
550
551 if not params or "application-name" not in params:
552 raise K8sException(
553 "Missing application-name argument, \
554 argument needed for K8s actions"
555 )
556 try:
557 self.log.debug(
558 "[exec_primitive] Getting model "
559 "kdu_instance: {}".format(kdu_instance)
560 )
561 application_name = params["application-name"]
562 actions = await libjuju.get_actions(application_name, kdu_instance)
563 if primitive_name not in actions:
564 raise K8sException("Primitive {} not found".format(primitive_name))
565 output, status = await libjuju.execute_action(
566 application_name, kdu_instance, primitive_name, **params
567 )
568
569 if status != "completed":
570 raise K8sException(
571 "status is not completed: {} output: {}".format(status, output)
572 )
573 if self.on_update_db:
574 await self.on_update_db(
575 cluster_uuid, kdu_instance, filter=db_dict["filter"]
576 )
577
578 return output
579
580 except Exception as e:
581 error_msg = "Error executing primitive {}: {}".format(primitive_name, e)
582 self.log.error(error_msg)
583 raise K8sException(message=error_msg)
584
585 """Introspection"""
586
587 async def inspect_kdu(
588 self,
589 kdu_model: str,
590 ) -> dict:
591 """Inspect a KDU
592
593 Inspects a bundle and returns a dictionary of config parameters and
594 their default values.
595
596 :param kdu_model str: The name or path of the bundle to inspect.
597
598 :return: If successful, returns a dictionary of available parameters
599 and their default values.
600 """
601
602 kdu = {}
603 if not os.path.exists(kdu_model):
604 raise K8sException("file {} not found".format(kdu_model))
605
606 with open(kdu_model, "r") as f:
607 bundle = yaml.safe_load(f.read())
608
609 """
610 {
611 'description': 'Test bundle',
612 'bundle': 'kubernetes',
613 'applications': {
614 'mariadb-k8s': {
615 'charm': 'cs:~charmed-osm/mariadb-k8s-20',
616 'scale': 1,
617 'options': {
618 'password': 'manopw',
619 'root_password': 'osm4u',
620 'user': 'mano'
621 },
622 'series': 'kubernetes'
623 }
624 }
625 }
626 """
627 # TODO: This should be returned in an agreed-upon format
628 kdu = bundle["applications"]
629
630 return kdu
631
632 async def help_kdu(
633 self,
634 kdu_model: str,
635 ) -> str:
636 """View the README
637
638 If available, returns the README of the bundle.
639
640 :param kdu_model str: The name or path of a bundle
641
642 :return: If found, returns the contents of the README.
643 """
644 readme = None
645
646 files = ["README", "README.txt", "README.md"]
647 path = os.path.dirname(kdu_model)
648 for file in os.listdir(path):
649 if file in files:
650 with open(file, "r") as f:
651 readme = f.read()
652 break
653
654 return readme
655
656 async def status_kdu(
657 self,
658 cluster_uuid: str,
659 kdu_instance: str,
660 complete_status: bool = False,
661 yaml_format: bool = False,
662 **kwargs,
663 ) -> Union[str, dict]:
664 """Get the status of the KDU
665
666 Get the current status of the KDU instance.
667
668 :param cluster_uuid str: The UUID of the cluster
669 :param kdu_instance str: The unique id of the KDU instance
670 :param complete_status: To get the complete_status of the KDU
671 :param yaml_format: To get the status in proper format for NSR record
672 :param: kwargs: Additional parameters
673 vca_id (str): VCA ID
674
675 :return: Returns a dictionary containing namespace, state, resources,
676 and deployment_time and returns complete_status if complete_status is True
677 """
678 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
679 status = {}
680
681 model_status = await libjuju.get_model_status(kdu_instance)
682
683 if not complete_status:
684 for name in model_status.applications:
685 application = model_status.applications[name]
686 status[name] = {"status": application["status"]["status"]}
687 else:
688 if yaml_format:
689 return obj_to_yaml(model_status)
690 else:
691 return obj_to_dict(model_status)
692
693 return status
694
695 async def add_relation(
696 self,
697 provider: RelationEndpoint,
698 requirer: RelationEndpoint,
699 ):
700 """
701 Add relation between two charmed endpoints
702
703 :param: provider: Provider relation endpoint
704 :param: requirer: Requirer relation endpoint
705 """
706 self.log.debug(f"adding new relation between {provider} and {requirer}")
707 cross_model_relation = (
708 provider.model_name != requirer.model_name
709 or requirer.vca_id != requirer.vca_id
710 )
711 try:
712 if cross_model_relation:
713 # Cross-model relation
714 provider_libjuju = await self._get_libjuju(provider.vca_id)
715 requirer_libjuju = await self._get_libjuju(requirer.vca_id)
716 offer = await provider_libjuju.offer(provider)
717 if offer:
718 saas_name = await requirer_libjuju.consume(
719 requirer.model_name, offer, provider_libjuju
720 )
721 await requirer_libjuju.add_relation(
722 requirer.model_name,
723 requirer.endpoint,
724 saas_name,
725 )
726 else:
727 # Standard relation
728 vca_id = provider.vca_id
729 model = provider.model_name
730 libjuju = await self._get_libjuju(vca_id)
731 # add juju relations between two applications
732 await libjuju.add_relation(
733 model_name=model,
734 endpoint_1=provider.endpoint,
735 endpoint_2=requirer.endpoint,
736 )
737 except Exception as e:
738 message = f"Error adding relation between {provider} and {requirer}: {e}"
739 self.log.error(message)
740 raise Exception(message=message)
741
742 async def update_vca_status(self, vcastatus: dict, kdu_instance: str, **kwargs):
743 """
744 Add all configs, actions, executed actions of all applications in a model to vcastatus dict
745
746 :param vcastatus dict: dict containing vcastatus
747 :param kdu_instance str: The unique id of the KDU instance
748 :param: kwargs: Additional parameters
749 vca_id (str): VCA ID
750
751 :return: None
752 """
753 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
754 try:
755 for model_name in vcastatus:
756 # Adding executed actions
757 vcastatus[model_name][
758 "executedActions"
759 ] = await libjuju.get_executed_actions(kdu_instance)
760
761 for application in vcastatus[model_name]["applications"]:
762 # Adding application actions
763 vcastatus[model_name]["applications"][application][
764 "actions"
765 ] = await libjuju.get_actions(application, kdu_instance)
766 # Adding application configs
767 vcastatus[model_name]["applications"][application][
768 "configs"
769 ] = await libjuju.get_application_configs(kdu_instance, application)
770
771 except Exception as e:
772 self.log.debug("Error in updating vca status: {}".format(str(e)))
773
774 async def get_services(
775 self, cluster_uuid: str, kdu_instance: str, namespace: str
776 ) -> list:
777 """Return a list of services of a kdu_instance"""
778
779 credentials = self.get_credentials(cluster_uuid=cluster_uuid)
780 kubectl = self._get_kubectl(credentials)
781 return kubectl.get_services(
782 field_selector="metadata.namespace={}".format(kdu_instance)
783 )
784
785 async def get_service(
786 self, cluster_uuid: str, service_name: str, namespace: str
787 ) -> object:
788 """Return data for a specific service inside a namespace"""
789
790 credentials = self.get_credentials(cluster_uuid=cluster_uuid)
791 kubectl = self._get_kubectl(credentials)
792 return kubectl.get_services(
793 field_selector="metadata.name={},metadata.namespace={}".format(
794 service_name, namespace
795 )
796 )[0]
797
798 def get_credentials(self, cluster_uuid: str) -> str:
799 """
800 Get Cluster Kubeconfig
801 """
802 k8scluster = self.db.get_one(
803 "k8sclusters", q_filter={"_id": cluster_uuid}, fail_on_empty=False
804 )
805
806 self.db.encrypt_decrypt_fields(
807 k8scluster.get("credentials"),
808 "decrypt",
809 ["password", "secret"],
810 schema_version=k8scluster["schema_version"],
811 salt=k8scluster["_id"],
812 )
813
814 return yaml.safe_dump(k8scluster.get("credentials"))
815
816 def _get_credential_name(self, cluster_uuid: str) -> str:
817 """
818 Get credential name for a k8s cloud
819
820 We cannot use the cluster_uuid for the credential name directly,
821 because it cannot start with a number, it must start with a letter.
822 Therefore, the k8s cloud credential name will be "cred-" followed
823 by the cluster uuid.
824
825 :param: cluster_uuid: Cluster UUID of the kubernetes cloud (=cloud_name)
826
827 :return: Name to use for the credential name.
828 """
829 return "cred-{}".format(cluster_uuid)
830
831 def get_namespace(
832 self,
833 cluster_uuid: str,
834 ) -> str:
835 """Get the namespace UUID
836 Gets the namespace's unique name
837
838 :param cluster_uuid str: The UUID of the cluster
839 :returns: The namespace UUID, or raises an exception
840 """
841 pass
842
843 @staticmethod
844 def generate_kdu_instance_name(**kwargs):
845 db_dict = kwargs.get("db_dict")
846 kdu_name = kwargs.get("kdu_name", None)
847 if kdu_name:
848 kdu_instance = "{}-{}".format(kdu_name, db_dict["filter"]["_id"])
849 else:
850 kdu_instance = db_dict["filter"]["_id"]
851 return kdu_instance
852
853 async def _get_libjuju(self, vca_id: str = None) -> Libjuju:
854 """
855 Get libjuju object
856
857 :param: vca_id: VCA ID
858 If None, get a libjuju object with a Connection to the default VCA
859 Else, geta libjuju object with a Connection to the specified VCA
860 """
861 if not vca_id:
862 while self.loading_libjuju.locked():
863 await asyncio.sleep(0.1)
864 if not self.libjuju:
865 async with self.loading_libjuju:
866 vca_connection = await get_connection(self._store)
867 self.libjuju = Libjuju(vca_connection, loop=self.loop, log=self.log)
868 return self.libjuju
869 else:
870 vca_connection = await get_connection(self._store, vca_id)
871 return Libjuju(
872 vca_connection,
873 loop=self.loop,
874 log=self.log,
875 n2vc=self,
876 )
877
878 def _get_kubectl(self, credentials: str) -> Kubectl:
879 """
880 Get Kubectl object
881
882 :param: kubeconfig_credentials: Kubeconfig credentials
883 """
884 kubecfg = tempfile.NamedTemporaryFile()
885 with open(kubecfg.name, "w") as kubecfg_file:
886 kubecfg_file.write(credentials)
887 return Kubectl(config_file=kubecfg.name)