|
15 | 15 |
|
16 | 16 | from azure.cli.core.azclierror import (ValidationError, ResourceNotFoundError, CLIError, InvalidArgumentValueError) |
17 | 17 | from ._constants import (CUSTOM_CORE_DNS_VOLUME_NAME, CUSTOM_CORE_DNS_VOLUME_MOUNT_PATH, |
18 | | - CUSTOM_CORE_DNS, CORE_DNS, KUBE_SYSTEM, EMPTY_CUSTOM_CORE_DNS) |
| 18 | + CUSTOM_CORE_DNS, CORE_DNS, KUBE_SYSTEM, EMPTY_CUSTOM_CORE_DNS, OPENSHIFT_DNS) |
19 | 19 |
|
20 | 20 | logger = get_logger(__name__) |
21 | 21 |
|
@@ -278,6 +278,21 @@ def update_deployment(resource_name, resource_namespace, kube_client, deployment |
278 | 278 | apps_v1_api.patch_namespaced_deployment(name=resource_name, namespace=resource_namespace, body=deployment) |
279 | 279 | except Exception as e: |
280 | 280 | raise ValidationError(f"other errors while patching deployment coredns in kube-system {str(e)}") |
| 281 | + |
| 282 | +def create_or_update_deployment(name, namespace, kube_client, deployment): |
| 283 | + validate_resource_name_and_resource_namespace_not_empty(name, namespace) |
| 284 | + |
| 285 | + try: |
| 286 | + logger.info(f"Start to create deployment {name} in namespace {namespace}") |
| 287 | + apps_v1_api = client.AppsV1Api(kube_client) |
| 288 | + apps_v1_api.create_namespaced_deployment(namespace=namespace, body=deployment) |
| 289 | + except client.exceptions.ApiException as e: |
| 290 | + if e.status == 409: |
| 291 | + logger.warning(f"Deployment '{name}' already exists, replacing it") |
| 292 | + apps_v1_api.replace_namespaced_deployment(name=name,namespace=namespace, body=deployment) |
| 293 | + else: |
| 294 | + raise CLIError(f"Failed to create or replace Deployment'{name}': {str(e)}") |
| 295 | + |
281 | 296 |
|
282 | 297 |
|
283 | 298 | def replace_deployment(resource_name, resource_namespace, kube_client, deployment): |
@@ -319,6 +334,20 @@ def update_configmap(resource_name, resource_namespace, kube_client, config_map) |
319 | 334 |
|
320 | 335 | except Exception as e: |
321 | 336 | raise CLIError(f"other errors while patching config map coredns in kube-system {str(e)}") |
| 337 | + |
| 338 | +def create_or_update_configmap(name, namespace, kube_client, configmap): |
| 339 | + validate_resource_name_and_resource_namespace_not_empty(name, namespace) |
| 340 | + |
| 341 | + try: |
| 342 | + logger.info(f"Start to create configmap {name} in namespace {namespace}") |
| 343 | + core_v1_api = client.CoreV1Api(kube_client) |
| 344 | + core_v1_api.create_namespaced_config_map(namespace=namespace, body=configmap) |
| 345 | + except client.exceptions.ApiException as e: |
| 346 | + if e.status == 409: |
| 347 | + logger.warning(f"Configmap '{name}' already exists, replacing it") |
| 348 | + core_v1_api.replace_namespaced_config_map(name=name,namespace=namespace, body=configmap) |
| 349 | + else: |
| 350 | + raise CLIError(f"Failed to create or replace Deployment'{name}': {str(e)}") |
322 | 351 |
|
323 | 352 |
|
324 | 353 | def replace_configmap(resource_name, resource_namespace, kube_client, config_map): |
@@ -356,3 +385,264 @@ def validate_resource_name_and_resource_namespace_not_empty(resource_name, resou |
356 | 385 | raise InvalidArgumentValueError("Arg resource_name should not be None or Empty") |
357 | 386 | if resource_namespace is None or len(resource_namespace) == 0: |
358 | 387 | raise InvalidArgumentValueError("Arg resource_namespace should not be None or Empty") |
| 388 | + |
| 389 | +def get_dns_operator_config(kube_client, folder=None): |
| 390 | + try: |
| 391 | + logger.info("Fetching DNS operator configuration from OpenShift cluster") |
| 392 | + custom_objects_api = client.CustomObjectsApi(kube_client) |
| 393 | + dns_operator_config = custom_objects_api.get_cluster_custom_object( |
| 394 | + group="operator.openshift.io", |
| 395 | + version="v1", |
| 396 | + plural="dnses", |
| 397 | + name="default" |
| 398 | + ) |
| 399 | + |
| 400 | + # Save the DNS operator configuration to the folder if provided |
| 401 | + if folder is not None: |
| 402 | + filepath = os.path.join(folder, "dns-operator-config.json") |
| 403 | + try: |
| 404 | + logger.info(f"Saving DNS operator configuration to {filepath}") |
| 405 | + with open(filepath, "w") as f: |
| 406 | + f.write(json.dumps(dns_operator_config, indent=2)) |
| 407 | + except Exception as e: |
| 408 | + raise ValidationError(f"Failed to save DNS operator configuration to {filepath}: {str(e)}") |
| 409 | + |
| 410 | + return dns_operator_config |
| 411 | + except client.exceptions.ApiException as e: |
| 412 | + if e.status == 404: |
| 413 | + raise ResourceNotFoundError("DNS operator configuration not found in the OpenShift cluster.") |
| 414 | + else: |
| 415 | + raise CLIError(f"Failed to fetch DNS operator configuration: {str(e)}") |
| 416 | + except Exception as e: |
| 417 | + raise CLIError(f"An error occurred while fetching DNS operator configuration: {str(e)}") |
| 418 | + |
| 419 | +def create_or_replace_cluster_role(rbac_api, role_name, role): |
| 420 | + try: |
| 421 | + logger.info(f"Creating new ClusterRole '{role_name}'") |
| 422 | + rbac_api.create_cluster_role(body=role) |
| 423 | + except client.exceptions.ApiException as e: |
| 424 | + if e.status == 409: |
| 425 | + logger.info(f"ClusterRole '{role_name}' already exists, replacing it") |
| 426 | + rbac_api.replace_cluster_role(name=role_name, body=role) |
| 427 | + else: |
| 428 | + raise CLIError(f"Failed to create or replace ClusterRole '{role_name}': {str(e)}") |
| 429 | + |
| 430 | +def create_or_replace_cluster_rolebinding(rbac_api, rolebinding_name, rolebinding): |
| 431 | + try: |
| 432 | + logger.info(f"Creating new ClusterRolebinding '{rolebinding_name}'") |
| 433 | + rbac_api.create_cluster_role_binding(body=rolebinding) |
| 434 | + except client.exceptions.ApiException as e: |
| 435 | + if e.status == 409: |
| 436 | + logger.info(f"ClusterRole '{rolebinding_name}' already exists, replacing it") |
| 437 | + rbac_api.replace_cluster_role_binding(name=rolebinding_name, body=rolebinding) |
| 438 | + else: |
| 439 | + raise CLIError(f"Failed to create or replace ClusterRole '{rolebinding_name}': {str(e)}") |
| 440 | + |
| 441 | + |
| 442 | + |
| 443 | +def create_openshift_custom_coredns_resources(kube_client, namespace=OPENSHIFT_DNS): |
| 444 | + try: |
| 445 | + logger.info("Creating custom CoreDNS resources in OpenShift") |
| 446 | + core_v1_api = client.CoreV1Api(kube_client) |
| 447 | + rbac_api = client.RbacAuthorizationV1Api(kube_client) |
| 448 | + apps_v1_api = client.AppsV1Api(kube_client) |
| 449 | + |
| 450 | + # 1. Create ClusterRole |
| 451 | + cluster_role = client.V1ClusterRole( |
| 452 | + metadata=client.V1ObjectMeta( |
| 453 | + name=CUSTOM_CORE_DNS |
| 454 | + ), |
| 455 | + rules=[ |
| 456 | + client.V1PolicyRule( |
| 457 | + api_groups=[""], |
| 458 | + resources=["services", "endpoints", "pods", "namespaces"], |
| 459 | + verbs=["list", "watch"] |
| 460 | + ), |
| 461 | + client.V1PolicyRule( |
| 462 | + api_groups=["discovery.k8s.io"], |
| 463 | + resources=["endpointslices"], |
| 464 | + verbs=["list", "watch"] |
| 465 | + ) |
| 466 | + ] |
| 467 | + ) |
| 468 | + create_or_replace_cluster_role(rbac_api, CUSTOM_CORE_DNS, cluster_role) |
| 469 | + |
| 470 | + # 2. Create ClusterRoleBinding |
| 471 | + cluster_role_binding = client.V1ClusterRoleBinding( |
| 472 | + metadata=client.V1ObjectMeta( |
| 473 | + name=CUSTOM_CORE_DNS |
| 474 | + ), |
| 475 | + role_ref=client.V1RoleRef( |
| 476 | + api_group="rbac.authorization.k8s.io", |
| 477 | + kind="ClusterRole", |
| 478 | + name=CUSTOM_CORE_DNS |
| 479 | + ), |
| 480 | + subjects=[ |
| 481 | + client.V1Subject( |
| 482 | + kind="ServiceAccount", |
| 483 | + name="default", |
| 484 | + namespace=namespace |
| 485 | + ) |
| 486 | + ] |
| 487 | + ) |
| 488 | + create_or_replace_cluster_rolebinding(rbac_api,CUSTOM_CORE_DNS, cluster_role_binding) |
| 489 | + |
| 490 | + # 3. Create ConfigMap |
| 491 | + existing_config_map = core_v1_api.read_namespaced_config_map(name=CUSTOM_CORE_DNS, namespace=KUBE_SYSTEM) |
| 492 | + corefile_data = existing_config_map.data.get("k4apps-default.io.server") or existing_config_map.data.get("Corefile") |
| 493 | + if not corefile_data: |
| 494 | + raise ValidationError(F"Neither 'k4apps-default.io.server' nor 'Corefile' key found in the {CUSTOM_CORE_DNS} ConfigMap in {KUBE_SYSTEM} namespace.") |
| 495 | + |
| 496 | + config_map = client.V1ConfigMap( |
| 497 | + metadata=client.V1ObjectMeta( |
| 498 | + name=CUSTOM_CORE_DNS, |
| 499 | + namespace=namespace |
| 500 | + ), |
| 501 | + data={"Corefile": corefile_data} |
| 502 | + ) |
| 503 | + |
| 504 | + create_or_update_configmap(name=CUSTOM_CORE_DNS,namespace=namespace,kube_client=kube_client, configmap=config_map) |
| 505 | + logger.info("Custom CoreDNS ConfigMap created successfully") |
| 506 | + |
| 507 | + # 4. Create Deployment |
| 508 | + deployment = client.V1Deployment( |
| 509 | + metadata=client.V1ObjectMeta( |
| 510 | + name=CUSTOM_CORE_DNS, |
| 511 | + namespace=namespace |
| 512 | + ), |
| 513 | + spec=client.V1DeploymentSpec( |
| 514 | + replicas=1, |
| 515 | + selector=client.V1LabelSelector( |
| 516 | + match_labels={"app": CUSTOM_CORE_DNS} |
| 517 | + ), |
| 518 | + template=client.V1PodTemplateSpec( |
| 519 | + metadata=client.V1ObjectMeta( |
| 520 | + labels={"app": CUSTOM_CORE_DNS} |
| 521 | + ), |
| 522 | + spec=client.V1PodSpec( |
| 523 | + containers=[ |
| 524 | + client.V1Container( |
| 525 | + name="coredns", |
| 526 | + image="coredns/coredns:latest", |
| 527 | + args=["-conf", "/etc/coredns/Corefile"], |
| 528 | + volume_mounts=[ |
| 529 | + client.V1VolumeMount( |
| 530 | + name="config-volume", |
| 531 | + mount_path="/etc/coredns" |
| 532 | + ) |
| 533 | + ] |
| 534 | + ) |
| 535 | + ], |
| 536 | + volumes=[ |
| 537 | + client.V1Volume( |
| 538 | + name="config-volume", |
| 539 | + config_map=client.V1ConfigMapVolumeSource( |
| 540 | + name=CUSTOM_CORE_DNS |
| 541 | + ) |
| 542 | + ) |
| 543 | + ] |
| 544 | + ) |
| 545 | + ) |
| 546 | + ) |
| 547 | + ) |
| 548 | + create_or_update_deployment(name=CUSTOM_CORE_DNS,namespace=namespace,kube_client=kube_client, deployment=deployment) |
| 549 | + logger.info("Custom CoreDNS Deployment created successfully") |
| 550 | + |
| 551 | + # 5 Create Service |
| 552 | + service = client.V1Service( |
| 553 | + metadata=client.V1ObjectMeta( |
| 554 | + name=CUSTOM_CORE_DNS, |
| 555 | + namespace=namespace |
| 556 | + ), |
| 557 | + spec=client.V1ServiceSpec( |
| 558 | + selector={"app": CUSTOM_CORE_DNS}, |
| 559 | + ports=[ |
| 560 | + client.V1ServicePort( |
| 561 | + protocol="UDP", |
| 562 | + port=53, |
| 563 | + target_port=53 |
| 564 | + ) |
| 565 | + ] |
| 566 | + ) |
| 567 | + ) |
| 568 | + core_v1_api.create_namespaced_service(namespace=namespace, body=service) |
| 569 | + logger.info("Custom CoreDNS Service created successfully") |
| 570 | + |
| 571 | + except client.exceptions.ApiException as e: |
| 572 | + if e.status == 409: |
| 573 | + logger.warning("Custom CoreDNS resources already exist") |
| 574 | + else: |
| 575 | + raise CLIError(f"Failed to create custom CoreDNS resources: {str(e)}") |
| 576 | + except Exception as e: |
| 577 | + raise CLIError(f"An error occurred while creating custom CoreDNS resources: {str(e)}") |
| 578 | + |
| 579 | +def patch_openshift_dns_operator(kube_client, domain): |
| 580 | + try: |
| 581 | + logger.info("Patching OpenShift DNS operator to add custom resolver") |
| 582 | + |
| 583 | + # Fetch the existing DNS operator configuration |
| 584 | + custom_objects_api = client.CustomObjectsApi(kube_client) |
| 585 | + |
| 586 | + dns_operator_config = custom_objects_api.get_cluster_custom_object( |
| 587 | + group="operator.openshift.io", |
| 588 | + version="v1", |
| 589 | + plural="dnses", |
| 590 | + name="default" |
| 591 | + ) |
| 592 | + |
| 593 | + # Add the custom resolver to the DNS operator configuration |
| 594 | + servers = dns_operator_config.get("spec", {}).get("servers", []) |
| 595 | + custom_resolver = { |
| 596 | + "name": CUSTOM_CORE_DNS, |
| 597 | + "zones": [domain], |
| 598 | + "forwardPlugin": { |
| 599 | + "upstreams": [f"{CUSTOM_CORE_DNS}.svc.cluster.local"] |
| 600 | + } |
| 601 | + } |
| 602 | + |
| 603 | + # Check if the custom resolver already exists |
| 604 | + if not any(server.get("name") == CUSTOM_CORE_DNS for server in servers): |
| 605 | + servers.append(custom_resolver) |
| 606 | + dns_operator_config["spec"]["servers"] = servers |
| 607 | + |
| 608 | + # Update the DNS operator configuration |
| 609 | + custom_objects_api.patch_cluster_custom_object( |
| 610 | + group="operator.openshift.io", |
| 611 | + version="v1", |
| 612 | + plural="dnses", |
| 613 | + name="default", |
| 614 | + body=dns_operator_config |
| 615 | + ) |
| 616 | + logger.info("Successfully patched OpenShift DNS operator with custom resolver") |
| 617 | + else: |
| 618 | + logger.info("Custom resolver already exists in the DNS operator configuration") |
| 619 | + |
| 620 | + except client.exceptions.ApiException as e: |
| 621 | + raise CLIError(f"Failed to patch DNS operator: {str(e)}") |
| 622 | + except Exception as e: |
| 623 | + raise CLIError(f"An error occurred while patching DNS operator: {str(e)}") |
| 624 | + |
| 625 | + |
| 626 | +def extract_domain_from_configmap(kube_client, resource_name=CUSTOM_CORE_DNS, namespace=KUBE_SYSTEM): |
| 627 | + import re |
| 628 | + |
| 629 | + try: |
| 630 | + core_v1_api = client.CoreV1Api(kube_client) |
| 631 | + configmap = core_v1_api.read_namespaced_config_map(name=CUSTOM_CORE_DNS, namespace=KUBE_SYSTEM) |
| 632 | + if configmap is None: |
| 633 | + raise ResourceNotFoundError(f"ConfigMap '{resource_name}' not found in namespace '{namespace}'.") |
| 634 | + |
| 635 | + corefile = configmap.data.get("k4apps-default.io.server") |
| 636 | + if not corefile: |
| 637 | + raise ValidationError("'k4apps-default.io.server' key found in the coredns-custom ConfigMap in kube-system namespace.") |
| 638 | + |
| 639 | + # Extract the domain (excluding 'dapr') |
| 640 | + for line in corefile.splitlines(): |
| 641 | + match = re.match(r'^\s*([a-zA-Z0-9\-\.]+):53\s*{', line) |
| 642 | + if match and match.group(1) != "dapr": |
| 643 | + return match.group(1) |
| 644 | + |
| 645 | + raise ValidationError("No valid domain found in CoreDNS configmap data.") |
| 646 | + except Exception as e: |
| 647 | + logger.error(f"Failed to extract domain from configmap: {str(e)}") |
| 648 | + return None |
0 commit comments