|
4 | 4 | import functools |
5 | 5 | from typing import Any, Literal |
6 | 6 |
|
| 7 | +import requests as _requests |
7 | 8 | from gooddata_api_client.exceptions import NotFoundException |
8 | 9 | from gooddata_api_client.model.declarative_export_templates import DeclarativeExportTemplates |
9 | 10 | from gooddata_api_client.model.declarative_notification_channels import DeclarativeNotificationChannels |
|
22 | 23 | from gooddata_sdk.catalog.catalog_service_base import CatalogServiceBase |
23 | 24 | from gooddata_sdk.catalog.organization.entity_model.directive import CatalogCspDirective |
24 | 25 | from gooddata_sdk.catalog.organization.entity_model.identity_provider import CatalogIdentityProvider |
| 26 | +from gooddata_sdk.catalog.organization.entity_model.ip_allowlist_policy import CatalogIpAllowlistPolicy |
25 | 27 | from gooddata_sdk.catalog.organization.entity_model.jwk import CatalogJwk, CatalogJwkDocument |
26 | 28 | from gooddata_sdk.catalog.organization.entity_model.llm_provider import ( |
27 | 29 | CatalogLlmProvider, |
|
35 | 37 | from gooddata_sdk.client import GoodDataApiClient |
36 | 38 | from gooddata_sdk.utils import load_all_entities, load_all_entities_dict |
37 | 39 |
|
| 40 | +_IP_ALLOWLIST_BASE_PATH = "/api/v1/entities/ipAllowlistPolicies" |
| 41 | +_IP_ALLOWLIST_ACTIONS_PATH = "/api/v1/actions/ipAllowlistPolicies" |
| 42 | + |
38 | 43 | # Org-level setting controlling which HLL function family calcique uses when |
39 | 44 | # generating SQL over HLL synopses. `Native` (default) emits StarRocks-native |
40 | 45 | # `HLL_*` functions; `Presto` emits the Presto-compatible HLL function family |
@@ -628,6 +633,172 @@ def delete_llm_provider(self, id: str) -> None: |
628 | 633 | """ |
629 | 634 | self._entities_api.delete_entity_llm_providers(id, _check_return_type=False) |
630 | 635 |
|
| 636 | + # IP Allowlist Policy CRUD (entity endpoints not yet in generated client) |
| 637 | + |
| 638 | + def _ip_api_call( |
| 639 | + self, |
| 640 | + method: str, |
| 641 | + path: str, |
| 642 | + body: dict[str, Any] | None = None, |
| 643 | + ) -> dict[str, Any]: |
| 644 | + """Make an authenticated HTTP request to an IP allowlist policy endpoint. |
| 645 | +
|
| 646 | + Uses requests directly (mirroring ``GoodDataApiClient._do_post_request``) |
| 647 | + so that we are not dependent on the generated client having typed wrappers |
| 648 | + for these endpoints. Returns the parsed JSON body, or an empty dict for |
| 649 | + responses without a body (e.g. 204 No Content on DELETE). |
| 650 | + """ |
| 651 | + hostname: str = self._client._hostname # type: ignore[attr-defined] |
| 652 | + token: str = self._client._token # type: ignore[attr-defined] |
| 653 | + |
| 654 | + prefix = "" if hostname.endswith("/") else "/" |
| 655 | + url = f"{hostname}{prefix}{path.lstrip('/')}" |
| 656 | + |
| 657 | + headers: dict[str, str] = { |
| 658 | + "Content-Type": "application/vnd.gooddata.api+json", |
| 659 | + "Authorization": f"Bearer {token}", |
| 660 | + "Accept": "application/vnd.gooddata.api+json", |
| 661 | + } |
| 662 | + kwargs: dict[str, Any] = {"headers": headers} |
| 663 | + if body is not None: |
| 664 | + kwargs["json"] = body |
| 665 | + |
| 666 | + response = _requests.request(method, url, **kwargs) |
| 667 | + response.raise_for_status() |
| 668 | + if response.content: |
| 669 | + return response.json() # type: ignore[no-any-return] |
| 670 | + return {} |
| 671 | + |
| 672 | + def list_ip_allowlist_policies(self) -> list[CatalogIpAllowlistPolicy]: |
| 673 | + """Return all IP allowlist policies in the organization. |
| 674 | +
|
| 675 | + Returns: |
| 676 | + list[CatalogIpAllowlistPolicy]: |
| 677 | + List of IP allowlist policies. |
| 678 | + """ |
| 679 | + all_items: list[CatalogIpAllowlistPolicy] = [] |
| 680 | + page = 0 |
| 681 | + page_size = 500 |
| 682 | + while True: |
| 683 | + raw = self._ip_api_call( |
| 684 | + "GET", |
| 685 | + f"{_IP_ALLOWLIST_BASE_PATH}?page={page}&size={page_size}", |
| 686 | + ) |
| 687 | + data = raw.get("data") or [] |
| 688 | + all_items.extend(CatalogIpAllowlistPolicy.from_api(item) for item in data) |
| 689 | + if len(data) < page_size: |
| 690 | + break |
| 691 | + page += 1 |
| 692 | + return all_items |
| 693 | + |
| 694 | + def get_ip_allowlist_policy(self, policy_id: str) -> CatalogIpAllowlistPolicy: |
| 695 | + """Get an individual IP allowlist policy. |
| 696 | +
|
| 697 | + Args: |
| 698 | + policy_id (str): |
| 699 | + IP allowlist policy identifier. |
| 700 | +
|
| 701 | + Returns: |
| 702 | + CatalogIpAllowlistPolicy: |
| 703 | + The requested IP allowlist policy. |
| 704 | + """ |
| 705 | + raw = self._ip_api_call("GET", f"{_IP_ALLOWLIST_BASE_PATH}/{policy_id}") |
| 706 | + return CatalogIpAllowlistPolicy.from_api(raw["data"]) |
| 707 | + |
| 708 | + def create_ip_allowlist_policy(self, policy: CatalogIpAllowlistPolicy) -> CatalogIpAllowlistPolicy: |
| 709 | + """Create a new IP allowlist policy. |
| 710 | +
|
| 711 | + Args: |
| 712 | + policy (CatalogIpAllowlistPolicy): |
| 713 | + IP allowlist policy to create. |
| 714 | +
|
| 715 | + Returns: |
| 716 | + CatalogIpAllowlistPolicy: |
| 717 | + Created IP allowlist policy. |
| 718 | + """ |
| 719 | + raw = self._ip_api_call("POST", _IP_ALLOWLIST_BASE_PATH, body=policy.to_api_dict()) |
| 720 | + return CatalogIpAllowlistPolicy.from_api(raw["data"]) |
| 721 | + |
| 722 | + def update_ip_allowlist_policy(self, policy: CatalogIpAllowlistPolicy) -> CatalogIpAllowlistPolicy: |
| 723 | + """Update an existing IP allowlist policy. |
| 724 | +
|
| 725 | + Args: |
| 726 | + policy (CatalogIpAllowlistPolicy): |
| 727 | + IP allowlist policy with updated fields. |
| 728 | +
|
| 729 | + Returns: |
| 730 | + CatalogIpAllowlistPolicy: |
| 731 | + Updated IP allowlist policy. |
| 732 | +
|
| 733 | + Raises: |
| 734 | + ValueError: |
| 735 | + IP allowlist policy does not exist. |
| 736 | + """ |
| 737 | + raw = self._ip_api_call( |
| 738 | + "PUT", |
| 739 | + f"{_IP_ALLOWLIST_BASE_PATH}/{policy.id}", |
| 740 | + body=policy.to_api_dict(), |
| 741 | + ) |
| 742 | + return CatalogIpAllowlistPolicy.from_api(raw["data"]) |
| 743 | + |
| 744 | + def delete_ip_allowlist_policy(self, policy_id: str) -> None: |
| 745 | + """Delete an IP allowlist policy. |
| 746 | +
|
| 747 | + Args: |
| 748 | + policy_id (str): |
| 749 | + IP allowlist policy identifier. |
| 750 | +
|
| 751 | + Returns: |
| 752 | + None |
| 753 | + """ |
| 754 | + self._ip_api_call("DELETE", f"{_IP_ALLOWLIST_BASE_PATH}/{policy_id}") |
| 755 | + |
| 756 | + def add_targets_to_ip_allowlist_policy( |
| 757 | + self, |
| 758 | + policy_id: str, |
| 759 | + targets: list[dict[str, str]], |
| 760 | + ) -> None: |
| 761 | + """Add targets to an IP allowlist policy. |
| 762 | +
|
| 763 | + Args: |
| 764 | + policy_id (str): |
| 765 | + IP allowlist policy identifier. |
| 766 | + targets (list[dict[str, str]]): |
| 767 | + List of targets to add. Each target is a dict with ``id`` and |
| 768 | + ``type`` keys (e.g. ``{"id": "user1", "type": "user"}``). |
| 769 | +
|
| 770 | + Returns: |
| 771 | + None |
| 772 | + """ |
| 773 | + self._ip_api_call( |
| 774 | + "POST", |
| 775 | + f"{_IP_ALLOWLIST_ACTIONS_PATH}/{policy_id}/addTargets", |
| 776 | + body={"targets": targets}, |
| 777 | + ) |
| 778 | + |
| 779 | + def remove_targets_from_ip_allowlist_policy( |
| 780 | + self, |
| 781 | + policy_id: str, |
| 782 | + targets: list[dict[str, str]], |
| 783 | + ) -> None: |
| 784 | + """Remove targets from an IP allowlist policy. |
| 785 | +
|
| 786 | + Args: |
| 787 | + policy_id (str): |
| 788 | + IP allowlist policy identifier. |
| 789 | + targets (list[dict[str, str]]): |
| 790 | + List of targets to remove. Each target is a dict with ``id`` |
| 791 | + and ``type`` keys (e.g. ``{"id": "user1", "type": "user"}``). |
| 792 | +
|
| 793 | + Returns: |
| 794 | + None |
| 795 | + """ |
| 796 | + self._ip_api_call( |
| 797 | + "POST", |
| 798 | + f"{_IP_ALLOWLIST_ACTIONS_PATH}/{policy_id}/removeTargets", |
| 799 | + body={"targets": targets}, |
| 800 | + ) |
| 801 | + |
631 | 802 | # Layout APIs |
632 | 803 |
|
633 | 804 | def get_declarative_notification_channels(self) -> list[CatalogDeclarativeNotificationChannel]: |
|
0 commit comments