blob: 187ded6a3535de1fe4271faaa825922ca49995fe [file] [log] [blame]
David Garcia5d799392020-07-02 13:56:58 +02001# Copyright 2020 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
David Garciad8d4b6e2021-06-24 18:47:22 +020015import base64
David Garciaf6e9b002020-11-27 15:32:02 +010016import logging
David Garciad8d4b6e2021-06-24 18:47:22 +020017from typing import Dict
18import typing
Patricia Reinoso6343d432022-08-23 06:22:01 +000019import uuid
Gabriel Cubafb03e902022-10-07 11:40:03 -050020import json
David Garciad8d4b6e2021-06-24 18:47:22 +020021
Patricia Reinoso6343d432022-08-23 06:22:01 +000022from distutils.version import LooseVersion
David Garciaf6e9b002020-11-27 15:32:02 +010023
David Garcia5d799392020-07-02 13:56:58 +020024from kubernetes import client, config
Patricia Reinoso6343d432022-08-23 06:22:01 +000025from kubernetes.client.api import VersionApi
David Garciad8d4b6e2021-06-24 18:47:22 +020026from kubernetes.client.models import (
27 V1ClusterRole,
Gabriel Cuba5f069332023-04-25 19:26:19 -050028 V1Role,
David Garciad8d4b6e2021-06-24 18:47:22 +020029 V1ObjectMeta,
30 V1PolicyRule,
31 V1ServiceAccount,
32 V1ClusterRoleBinding,
Gabriel Cuba5f069332023-04-25 19:26:19 -050033 V1RoleBinding,
David Garciad8d4b6e2021-06-24 18:47:22 +020034 V1RoleRef,
garciadeblasfc12ea62024-08-07 02:35:10 +020035 RbacV1Subject,
Patricia Reinoso6343d432022-08-23 06:22:01 +000036 V1Secret,
37 V1SecretReference,
Gabriel Cuba5f069332023-04-25 19:26:19 -050038 V1Namespace,
David Garciad8d4b6e2021-06-24 18:47:22 +020039)
David Garcia5d799392020-07-02 13:56:58 +020040from kubernetes.client.rest import ApiException
Mark Beierlfb797862023-05-18 22:21:06 -040041from n2vc.libjuju import retry_callback
David Garciad8d4b6e2021-06-24 18:47:22 +020042from retrying_async import retry
David Garciaf6e9b002020-11-27 15:32:02 +010043
44
David Garciad8d4b6e2021-06-24 18:47:22 +020045SERVICE_ACCOUNT_TOKEN_KEY = "token"
46SERVICE_ACCOUNT_ROOT_CA_KEY = "ca.crt"
47# clients
David Garciaf6e9b002020-11-27 15:32:02 +010048CORE_CLIENT = "core_v1"
David Garciaf6e9b002020-11-27 15:32:02 +010049RBAC_CLIENT = "rbac_v1"
David Garciad8d4b6e2021-06-24 18:47:22 +020050STORAGE_CLIENT = "storage_v1"
Gabriel Cubafb03e902022-10-07 11:40:03 -050051CUSTOM_OBJECT_CLIENT = "custom_object"
David Garcia5d799392020-07-02 13:56:58 +020052
53
54class Kubectl:
55 def __init__(self, config_file=None):
56 config.load_kube_config(config_file=config_file)
David Garciaf6e9b002020-11-27 15:32:02 +010057 self._clients = {
David Garciad8d4b6e2021-06-24 18:47:22 +020058 CORE_CLIENT: client.CoreV1Api(),
59 RBAC_CLIENT: client.RbacAuthorizationV1Api(),
60 STORAGE_CLIENT: client.StorageV1Api(),
Gabriel Cubafb03e902022-10-07 11:40:03 -050061 CUSTOM_OBJECT_CLIENT: client.CustomObjectsApi(),
David Garciaf6e9b002020-11-27 15:32:02 +010062 }
David Garciad8d4b6e2021-06-24 18:47:22 +020063 self._configuration = config.kube_config.Configuration.get_default_copy()
David Garcia5d799392020-07-02 13:56:58 +020064 self.logger = logging.getLogger("Kubectl")
65
David Garciaf6e9b002020-11-27 15:32:02 +010066 @property
67 def configuration(self):
68 return self._configuration
69
70 @property
71 def clients(self):
72 return self._clients
David Garcia475a7222020-09-21 16:19:15 +020073
David Garciad8d4b6e2021-06-24 18:47:22 +020074 def get_services(
75 self,
76 field_selector: str = None,
77 label_selector: str = None,
78 ) -> typing.List[typing.Dict]:
79 """
80 Get Service list from a namespace
81
82 :param: field_selector: Kubernetes field selector for the namespace
83 :param: label_selector: Kubernetes label selector for the namespace
84
85 :return: List of the services matching the selectors specified
86 """
David Garcia5d799392020-07-02 13:56:58 +020087 kwargs = {}
88 if field_selector:
89 kwargs["field_selector"] = field_selector
90 if label_selector:
91 kwargs["label_selector"] = label_selector
David Garcia5d799392020-07-02 13:56:58 +020092 try:
David Garciaf6e9b002020-11-27 15:32:02 +010093 result = self.clients[CORE_CLIENT].list_service_for_all_namespaces(**kwargs)
David Garcia5d799392020-07-02 13:56:58 +020094 return [
95 {
96 "name": i.metadata.name,
97 "cluster_ip": i.spec.cluster_ip,
98 "type": i.spec.type,
David Garcia37004982020-07-16 17:53:20 +020099 "ports": [
100 {
101 "name": p.name,
102 "node_port": p.node_port,
103 "port": p.port,
104 "protocol": p.protocol,
105 "target_port": p.target_port,
106 }
107 for p in i.spec.ports
David Garcia84ebb752020-07-22 13:17:56 +0200108 ]
109 if i.spec.ports
110 else [],
David Garcia5d799392020-07-02 13:56:58 +0200111 "external_ip": [i.ip for i in i.status.load_balancer.ingress]
112 if i.status.load_balancer.ingress
113 else None,
114 }
115 for i in result.items
116 ]
117 except ApiException as e:
118 self.logger.error("Error calling get services: {}".format(e))
119 raise e
David Garcia475a7222020-09-21 16:19:15 +0200120
121 def get_default_storage_class(self) -> str:
122 """
123 Default storage class
124
125 :return: Returns the default storage class name, if exists.
126 If not, it returns the first storage class.
127 If there are not storage classes, returns None
128 """
David Garciaf6e9b002020-11-27 15:32:02 +0100129 storage_classes = self.clients[STORAGE_CLIENT].list_storage_class()
David Garcia475a7222020-09-21 16:19:15 +0200130 selected_sc = None
131 default_sc_annotations = {
132 "storageclass.kubernetes.io/is-default-class": "true",
133 # Older clusters still use the beta annotation.
134 "storageclass.beta.kubernetes.io/is-default-class": "true",
135 }
136 for sc in storage_classes.items:
137 if not selected_sc:
138 # Select the first storage class in case there is no a default-class
139 selected_sc = sc.metadata.name
garciadeblas979c54e2021-05-28 14:10:59 +0200140 annotations = sc.metadata.annotations or {}
David Garcia475a7222020-09-21 16:19:15 +0200141 if any(
142 k in annotations and annotations[k] == v
143 for k, v in default_sc_annotations.items()
144 ):
145 # Default storage
146 selected_sc = sc.metadata.name
147 break
148 return selected_sc
David Garciad8d4b6e2021-06-24 18:47:22 +0200149
150 def create_cluster_role(
151 self,
152 name: str,
153 labels: Dict[str, str],
154 namespace: str = "kube-system",
155 ):
156 """
157 Create a cluster role
158
159 :param: name: Name of the cluster role
160 :param: labels: Labels for cluster role metadata
161 :param: namespace: Kubernetes namespace for cluster role metadata
162 Default: kube-system
163 """
164 cluster_roles = self.clients[RBAC_CLIENT].list_cluster_role(
165 field_selector="metadata.name={}".format(name)
166 )
167
168 if len(cluster_roles.items) > 0:
Gabriel Cuba5f069332023-04-25 19:26:19 -0500169 raise Exception("Role with metadata.name={} already exists".format(name))
David Garciad8d4b6e2021-06-24 18:47:22 +0200170
171 metadata = V1ObjectMeta(name=name, labels=labels, namespace=namespace)
172 # Cluster role
173 cluster_role = V1ClusterRole(
174 metadata=metadata,
175 rules=[
176 V1PolicyRule(api_groups=["*"], resources=["*"], verbs=["*"]),
177 V1PolicyRule(non_resource_ur_ls=["*"], verbs=["*"]),
178 ],
179 )
180
181 self.clients[RBAC_CLIENT].create_cluster_role(cluster_role)
182
Gabriel Cuba5f069332023-04-25 19:26:19 -0500183 async def create_role(
184 self,
185 name: str,
186 labels: Dict[str, str],
187 api_groups: list,
188 resources: list,
189 verbs: list,
190 namespace: str,
191 ):
192 """
193 Create a role with one PolicyRule
194
195 :param: name: Name of the namespaced Role
196 :param: labels: Labels for namespaced Role metadata
197 :param: api_groups: List with api-groups allowed in the policy rule
198 :param: resources: List with resources allowed in the policy rule
199 :param: verbs: List with verbs allowed in the policy rule
200 :param: namespace: Kubernetes namespace for Role metadata
201
202 :return: None
203 """
204
205 roles = self.clients[RBAC_CLIENT].list_namespaced_role(
206 namespace, field_selector="metadata.name={}".format(name)
207 )
208
209 if len(roles.items) > 0:
210 raise Exception("Role with metadata.name={} already exists".format(name))
211
212 metadata = V1ObjectMeta(name=name, labels=labels, namespace=namespace)
213
214 role = V1Role(
215 metadata=metadata,
216 rules=[
217 V1PolicyRule(api_groups=api_groups, resources=resources, verbs=verbs),
218 ],
219 )
220
221 self.clients[RBAC_CLIENT].create_namespaced_role(namespace, role)
222
David Garciad8d4b6e2021-06-24 18:47:22 +0200223 def delete_cluster_role(self, name: str):
224 """
225 Delete a cluster role
226
227 :param: name: Name of the cluster role
228 """
229 self.clients[RBAC_CLIENT].delete_cluster_role(name)
230
Patricia Reinoso6343d432022-08-23 06:22:01 +0000231 def _get_kubectl_version(self):
232 version = VersionApi().get_code()
233 return "{}.{}".format(version.major, version.minor)
234
235 def _need_to_create_new_secret(self):
236 min_k8s_version = "1.24"
237 current_k8s_version = self._get_kubectl_version()
238 return LooseVersion(min_k8s_version) <= LooseVersion(current_k8s_version)
239
240 def _get_secret_name(self, service_account_name: str):
241 random_alphanum = str(uuid.uuid4())[:5]
242 return "{}-token-{}".format(service_account_name, random_alphanum)
243
244 def _create_service_account_secret(
245 self, service_account_name: str, namespace: str, secret_name: str
246 ):
247 """
248 Create a secret for the service account. K8s version >= 1.24
249
250 :param: service_account_name: Name of the service account
251 :param: namespace: Kubernetes namespace for service account metadata
252 :param: secret_name: Name of the secret
253 """
254 v1_core = self.clients[CORE_CLIENT]
255 secrets = v1_core.list_namespaced_secret(
256 namespace, field_selector="metadata.name={}".format(secret_name)
257 ).items
258
259 if len(secrets) > 0:
260 raise Exception(
261 "Secret with metadata.name={} already exists".format(secret_name)
262 )
263
264 annotations = {"kubernetes.io/service-account.name": service_account_name}
265 metadata = V1ObjectMeta(
266 name=secret_name, namespace=namespace, annotations=annotations
267 )
268 type = "kubernetes.io/service-account-token"
269 secret = V1Secret(metadata=metadata, type=type)
270 v1_core.create_namespaced_secret(namespace, secret)
271
272 def _get_secret_reference_list(self, namespace: str, secret_name: str):
273 """
274 Return a secret reference list with one secret.
275 K8s version >= 1.24
276
277 :param: namespace: Kubernetes namespace for service account metadata
278 :param: secret_name: Name of the secret
279 :rtype: list[V1SecretReference]
280 """
281 return [V1SecretReference(name=secret_name, namespace=namespace)]
282
David Garciad8d4b6e2021-06-24 18:47:22 +0200283 def create_service_account(
284 self,
285 name: str,
286 labels: Dict[str, str],
287 namespace: str = "kube-system",
288 ):
289 """
290 Create a service account
291
292 :param: name: Name of the service account
293 :param: labels: Labels for service account metadata
294 :param: namespace: Kubernetes namespace for service account metadata
295 Default: kube-system
296 """
Patricia Reinoso6343d432022-08-23 06:22:01 +0000297 v1_core = self.clients[CORE_CLIENT]
298 service_accounts = v1_core.list_namespaced_service_account(
David Garciad8d4b6e2021-06-24 18:47:22 +0200299 namespace, field_selector="metadata.name={}".format(name)
300 )
301 if len(service_accounts.items) > 0:
302 raise Exception(
303 "Service account with metadata.name={} already exists".format(name)
304 )
305
306 metadata = V1ObjectMeta(name=name, labels=labels, namespace=namespace)
David Garciad8d4b6e2021-06-24 18:47:22 +0200307
Patricia Reinoso6343d432022-08-23 06:22:01 +0000308 if self._need_to_create_new_secret():
309 secret_name = self._get_secret_name(name)
310 secrets = self._get_secret_reference_list(namespace, secret_name)
311 service_account = V1ServiceAccount(metadata=metadata, secrets=secrets)
312 v1_core.create_namespaced_service_account(namespace, service_account)
313 self._create_service_account_secret(name, namespace, secret_name)
314 else:
315 service_account = V1ServiceAccount(metadata=metadata)
316 v1_core.create_namespaced_service_account(namespace, service_account)
David Garciad8d4b6e2021-06-24 18:47:22 +0200317
318 def delete_service_account(self, name: str, namespace: str = "kube-system"):
319 """
320 Delete a service account
321
322 :param: name: Name of the service account
323 :param: namespace: Kubernetes namespace for service account metadata
324 Default: kube-system
325 """
326 self.clients[CORE_CLIENT].delete_namespaced_service_account(name, namespace)
327
328 def create_cluster_role_binding(
329 self, name: str, labels: Dict[str, str], namespace: str = "kube-system"
330 ):
331 """
332 Create a cluster role binding
333
334 :param: name: Name of the cluster role
335 :param: labels: Labels for cluster role binding metadata
336 :param: namespace: Kubernetes namespace for cluster role binding metadata
337 Default: kube-system
338 """
339 role_bindings = self.clients[RBAC_CLIENT].list_cluster_role_binding(
340 field_selector="metadata.name={}".format(name)
341 )
342 if len(role_bindings.items) > 0:
343 raise Exception("Generated rbac id already exists")
344
345 role_binding = V1ClusterRoleBinding(
346 metadata=V1ObjectMeta(name=name, labels=labels),
347 role_ref=V1RoleRef(kind="ClusterRole", name=name, api_group=""),
garciadeblasfc12ea62024-08-07 02:35:10 +0200348 subjects=[
349 RbacV1Subject(kind="ServiceAccount", name=name, namespace=namespace)
350 ],
David Garciad8d4b6e2021-06-24 18:47:22 +0200351 )
352 self.clients[RBAC_CLIENT].create_cluster_role_binding(role_binding)
353
Gabriel Cuba5f069332023-04-25 19:26:19 -0500354 async def create_role_binding(
355 self,
356 name: str,
357 role_name: str,
358 sa_name: str,
359 labels: Dict[str, str],
360 namespace: str,
361 ):
362 """
363 Create a cluster role binding
364
365 :param: name: Name of the namespaced Role Binding
366 :param: role_name: Name of the namespaced Role to be bound
367 :param: sa_name: Name of the Service Account to be bound
368 :param: labels: Labels for Role Binding metadata
369 :param: namespace: Kubernetes namespace for Role Binding metadata
370
371 :return: None
372 """
373 role_bindings = self.clients[RBAC_CLIENT].list_namespaced_role_binding(
374 namespace, field_selector="metadata.name={}".format(name)
375 )
376 if len(role_bindings.items) > 0:
377 raise Exception(
378 "Role Binding with metadata.name={} already exists".format(name)
379 )
380
381 role_binding = V1RoleBinding(
382 metadata=V1ObjectMeta(name=name, labels=labels),
383 role_ref=V1RoleRef(kind="Role", name=role_name, api_group=""),
384 subjects=[
garciadeblasfc12ea62024-08-07 02:35:10 +0200385 RbacV1Subject(kind="ServiceAccount", name=sa_name, namespace=namespace)
Gabriel Cuba5f069332023-04-25 19:26:19 -0500386 ],
387 )
388 self.clients[RBAC_CLIENT].create_namespaced_role_binding(
389 namespace, role_binding
390 )
391
David Garciad8d4b6e2021-06-24 18:47:22 +0200392 def delete_cluster_role_binding(self, name: str):
393 """
394 Delete a cluster role binding
395
396 :param: name: Name of the cluster role binding
397 """
398 self.clients[RBAC_CLIENT].delete_cluster_role_binding(name)
399
400 @retry(
401 attempts=10,
402 delay=1,
403 fallback=Exception("Failed getting the secret from service account"),
Mark Beierlfb797862023-05-18 22:21:06 -0400404 callback=retry_callback,
David Garciad8d4b6e2021-06-24 18:47:22 +0200405 )
David Garcia4ae527e2021-07-26 16:04:59 +0200406 async def get_secret_data(
407 self, name: str, namespace: str = "kube-system"
408 ) -> (str, str):
David Garciad8d4b6e2021-06-24 18:47:22 +0200409 """
410 Get secret data
411
David Garcia4ae527e2021-07-26 16:04:59 +0200412 :param: name: Name of the secret data
413 :param: namespace: Name of the namespace where the secret is stored
414
David Garciad8d4b6e2021-06-24 18:47:22 +0200415 :return: Tuple with the token and client certificate
416 """
417 v1_core = self.clients[CORE_CLIENT]
418
419 secret_name = None
420
421 service_accounts = v1_core.list_namespaced_service_account(
422 namespace, field_selector="metadata.name={}".format(name)
423 )
424 if len(service_accounts.items) == 0:
425 raise Exception(
426 "Service account not found with metadata.name={}".format(name)
427 )
428 service_account = service_accounts.items[0]
429 if service_account.secrets and len(service_account.secrets) > 0:
430 secret_name = service_account.secrets[0].name
431 if not secret_name:
432 raise Exception(
433 "Failed getting the secret from service account {}".format(name)
434 )
Gabriel Cuba5f069332023-04-25 19:26:19 -0500435 # TODO: refactor to use get_secret_content
David Garciad8d4b6e2021-06-24 18:47:22 +0200436 secret = v1_core.list_namespaced_secret(
437 namespace, field_selector="metadata.name={}".format(secret_name)
438 ).items[0]
439
440 token = secret.data[SERVICE_ACCOUNT_TOKEN_KEY]
441 client_certificate_data = secret.data[SERVICE_ACCOUNT_ROOT_CA_KEY]
442
443 return (
444 base64.b64decode(token).decode("utf-8"),
445 base64.b64decode(client_certificate_data).decode("utf-8"),
446 )
Gabriel Cubafb03e902022-10-07 11:40:03 -0500447
Gabriel Cuba5f069332023-04-25 19:26:19 -0500448 @retry(
449 attempts=10,
450 delay=1,
451 fallback=Exception("Failed getting data from the secret"),
452 )
453 async def get_secret_content(
454 self,
455 name: str,
456 namespace: str,
457 ) -> dict:
458 """
459 Get secret data
460
461 :param: name: Name of the secret
462 :param: namespace: Name of the namespace where the secret is stored
463
464 :return: Dictionary with secret's data
465 """
466 v1_core = self.clients[CORE_CLIENT]
467
468 secret = v1_core.read_namespaced_secret(name, namespace)
469
470 return secret.data
471
472 @retry(
473 attempts=10,
474 delay=1,
475 fallback=Exception("Failed creating the secret"),
476 )
477 async def create_secret(
478 self, name: str, data: dict, namespace: str, secret_type: str
479 ):
480 """
481 Get secret data
482
483 :param: name: Name of the secret
484 :param: data: Dict with data content. Values must be already base64 encoded
485 :param: namespace: Name of the namespace where the secret will be stored
486 :param: secret_type: Type of the secret, e.g., Opaque, kubernetes.io/service-account-token, kubernetes.io/tls
487
488 :return: None
489 """
490 v1_core = self.clients[CORE_CLIENT]
491 metadata = V1ObjectMeta(name=name, namespace=namespace)
492 secret = V1Secret(metadata=metadata, data=data, type=secret_type)
493 v1_core.create_namespaced_secret(namespace, secret)
494
Gabriel Cubafb03e902022-10-07 11:40:03 -0500495 async def create_certificate(
496 self,
497 namespace: str,
498 name: str,
499 dns_prefix: str,
500 secret_name: str,
501 usages: list,
502 issuer_name: str,
503 ):
504 """
505 Creates cert-manager certificate object
506
507 :param: namespace: Name of the namespace where the certificate and secret is stored
508 :param: name: Name of the certificate object
509 :param: dns_prefix: Prefix for the dnsNames. They will be prefixed to the common k8s svc suffixes
510 :param: secret_name: Name of the secret created by cert-manager
511 :param: usages: List of X.509 key usages
512 :param: issuer_name: Name of the cert-manager's Issuer or ClusterIssuer object
513
514 """
515 certificate_body = {
516 "apiVersion": "cert-manager.io/v1",
517 "kind": "Certificate",
518 "metadata": {"name": name, "namespace": namespace},
519 "spec": {
520 "secretName": secret_name,
521 "privateKey": {
522 "rotationPolicy": "Always",
523 "algorithm": "ECDSA",
524 "size": 256,
525 },
526 "duration": "8760h", # 1 Year
527 "renewBefore": "2208h", # 9 months
528 "subject": {"organizations": ["osm"]},
529 "commonName": "osm",
530 "isCA": False,
531 "usages": usages,
532 "dnsNames": [
533 "{}.{}".format(dns_prefix, namespace),
534 "{}.{}.svc".format(dns_prefix, namespace),
535 "{}.{}.svc.cluster".format(dns_prefix, namespace),
536 "{}.{}.svc.cluster.local".format(dns_prefix, namespace),
537 ],
538 "issuerRef": {"name": issuer_name, "kind": "ClusterIssuer"},
539 },
540 }
541 client = self.clients[CUSTOM_OBJECT_CLIENT]
542 try:
543 client.create_namespaced_custom_object(
544 group="cert-manager.io",
545 plural="certificates",
546 version="v1",
547 body=certificate_body,
548 namespace=namespace,
549 )
550 except ApiException as e:
551 info = json.loads(e.body)
552 if info.get("reason").lower() == "alreadyexists":
553 self.logger.warning("Certificate already exists: {}".format(e))
554 else:
555 raise e
556
557 async def delete_certificate(self, namespace, object_name):
558 client = self.clients[CUSTOM_OBJECT_CLIENT]
559 try:
560 client.delete_namespaced_custom_object(
561 group="cert-manager.io",
562 plural="certificates",
563 version="v1",
564 name=object_name,
565 namespace=namespace,
566 )
567 except ApiException as e:
568 info = json.loads(e.body)
569 if info.get("reason").lower() == "notfound":
570 self.logger.warning("Certificate already deleted: {}".format(e))
571 else:
572 raise e
Gabriel Cuba5f069332023-04-25 19:26:19 -0500573
574 @retry(
575 attempts=10,
576 delay=1,
577 fallback=Exception("Failed creating the namespace"),
578 )
Gabriel Cubad21509c2023-05-17 01:30:15 -0500579 async def create_namespace(self, name: str, labels: dict = None):
Gabriel Cuba5f069332023-04-25 19:26:19 -0500580 """
581 Create a namespace
582
583 :param: name: Name of the namespace to be created
Gabriel Cubad21509c2023-05-17 01:30:15 -0500584 :param: labels: Dictionary with labels for the new namespace
Gabriel Cuba5f069332023-04-25 19:26:19 -0500585
586 """
587 v1_core = self.clients[CORE_CLIENT]
Gabriel Cubad21509c2023-05-17 01:30:15 -0500588 metadata = V1ObjectMeta(name=name, labels=labels)
Gabriel Cuba5f069332023-04-25 19:26:19 -0500589 namespace = V1Namespace(
590 metadata=metadata,
591 )
592
593 try:
594 v1_core.create_namespace(namespace)
595 self.logger.debug("Namespace created: {}".format(name))
596 except ApiException as e:
597 info = json.loads(e.body)
598 if info.get("reason").lower() == "alreadyexists":
599 self.logger.warning("Namespace already exists: {}".format(e))
600 else:
601 raise e
602
603 @retry(
604 attempts=10,
605 delay=1,
606 fallback=Exception("Failed deleting the namespace"),
607 )
608 async def delete_namespace(self, name: str):
609 """
610 Delete a namespace
611
612 :param: name: Name of the namespace to be deleted
613
614 """
615 try:
616 self.clients[CORE_CLIENT].delete_namespace(name)
617 except ApiException as e:
618 if e.reason == "Not Found":
619 self.logger.warning("Namespace already deleted: {}".format(e))