Feature: LakeFormation (#6172)
This commit is contained in:
parent
8a79c34674
commit
6c32c089a1
@ -4077,6 +4077,59 @@
|
||||
- [ ] verify_mac
|
||||
</details>
|
||||
|
||||
## lakeformation
|
||||
<details>
|
||||
<summary>34% implemented</summary>
|
||||
|
||||
- [ ] add_lf_tags_to_resource
|
||||
- [ ] assume_decorated_role_with_saml
|
||||
- [X] batch_grant_permissions
|
||||
- [X] batch_revoke_permissions
|
||||
- [ ] cancel_transaction
|
||||
- [ ] commit_transaction
|
||||
- [ ] create_data_cells_filter
|
||||
- [X] create_lf_tag
|
||||
- [ ] delete_data_cells_filter
|
||||
- [X] delete_lf_tag
|
||||
- [ ] delete_objects_on_cancel
|
||||
- [X] deregister_resource
|
||||
- [X] describe_resource
|
||||
- [ ] describe_transaction
|
||||
- [ ] extend_transaction
|
||||
- [ ] get_data_cells_filter
|
||||
- [X] get_data_lake_settings
|
||||
- [ ] get_effective_permissions_for_path
|
||||
- [X] get_lf_tag
|
||||
- [ ] get_query_state
|
||||
- [ ] get_query_statistics
|
||||
- [ ] get_resource_lf_tags
|
||||
- [ ] get_table_objects
|
||||
- [ ] get_temporary_glue_partition_credentials
|
||||
- [ ] get_temporary_glue_table_credentials
|
||||
- [ ] get_work_unit_results
|
||||
- [ ] get_work_units
|
||||
- [X] grant_permissions
|
||||
- [X] list_data_cells_filter
|
||||
- [X] list_lf_tags
|
||||
- [X] list_permissions
|
||||
- [X] list_resources
|
||||
- [ ] list_table_storage_optimizers
|
||||
- [ ] list_transactions
|
||||
- [X] put_data_lake_settings
|
||||
- [X] register_resource
|
||||
- [ ] remove_lf_tags_from_resource
|
||||
- [X] revoke_permissions
|
||||
- [ ] search_databases_by_lf_tags
|
||||
- [ ] search_tables_by_lf_tags
|
||||
- [ ] start_query_planning
|
||||
- [ ] start_transaction
|
||||
- [ ] update_data_cells_filter
|
||||
- [ ] update_lf_tag
|
||||
- [ ] update_resource
|
||||
- [ ] update_table_objects
|
||||
- [ ] update_table_storage_optimizer
|
||||
</details>
|
||||
|
||||
## lambda
|
||||
<details>
|
||||
<summary>53% implemented</summary>
|
||||
@ -6956,7 +7009,6 @@
|
||||
- kinesis-video-webrtc-storage
|
||||
- kinesisanalytics
|
||||
- kinesisanalyticsv2
|
||||
- lakeformation
|
||||
- lex-models
|
||||
- lex-runtime
|
||||
- lexv2-models
|
||||
|
83
docs/docs/services/lakeformation.rst
Normal file
83
docs/docs/services/lakeformation.rst
Normal file
@ -0,0 +1,83 @@
|
||||
.. _implementedservice_lakeformation:
|
||||
|
||||
.. |start-h3| raw:: html
|
||||
|
||||
<h3>
|
||||
|
||||
.. |end-h3| raw:: html
|
||||
|
||||
</h3>
|
||||
|
||||
=============
|
||||
lakeformation
|
||||
=============
|
||||
|
||||
|start-h3| Example usage |end-h3|
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
@mock_lakeformation
|
||||
def test_lakeformation_behaviour:
|
||||
boto3.client("lakeformation")
|
||||
...
|
||||
|
||||
|
||||
|
||||
|start-h3| Implemented features for this service |end-h3|
|
||||
|
||||
- [ ] add_lf_tags_to_resource
|
||||
- [ ] assume_decorated_role_with_saml
|
||||
- [X] batch_grant_permissions
|
||||
- [X] batch_revoke_permissions
|
||||
- [ ] cancel_transaction
|
||||
- [ ] commit_transaction
|
||||
- [ ] create_data_cells_filter
|
||||
- [X] create_lf_tag
|
||||
- [ ] delete_data_cells_filter
|
||||
- [X] delete_lf_tag
|
||||
- [ ] delete_objects_on_cancel
|
||||
- [X] deregister_resource
|
||||
- [X] describe_resource
|
||||
- [ ] describe_transaction
|
||||
- [ ] extend_transaction
|
||||
- [ ] get_data_cells_filter
|
||||
- [X] get_data_lake_settings
|
||||
- [ ] get_effective_permissions_for_path
|
||||
- [X] get_lf_tag
|
||||
- [ ] get_query_state
|
||||
- [ ] get_query_statistics
|
||||
- [ ] get_resource_lf_tags
|
||||
- [ ] get_table_objects
|
||||
- [ ] get_temporary_glue_partition_credentials
|
||||
- [ ] get_temporary_glue_table_credentials
|
||||
- [ ] get_work_unit_results
|
||||
- [ ] get_work_units
|
||||
- [X] grant_permissions
|
||||
- [X] list_data_cells_filter
|
||||
|
||||
This currently just returns an empty list, as the corresponding Create is not yet implemented
|
||||
|
||||
|
||||
- [X] list_lf_tags
|
||||
- [X] list_permissions
|
||||
|
||||
No parameters have been implemented yet
|
||||
|
||||
|
||||
- [X] list_resources
|
||||
- [ ] list_table_storage_optimizers
|
||||
- [ ] list_transactions
|
||||
- [X] put_data_lake_settings
|
||||
- [X] register_resource
|
||||
- [ ] remove_lf_tags_from_resource
|
||||
- [X] revoke_permissions
|
||||
- [ ] search_databases_by_lf_tags
|
||||
- [ ] search_tables_by_lf_tags
|
||||
- [ ] start_query_planning
|
||||
- [ ] start_transaction
|
||||
- [ ] update_data_cells_filter
|
||||
- [ ] update_lf_tag
|
||||
- [ ] update_resource
|
||||
- [ ] update_table_objects
|
||||
- [ ] update_table_storage_optimizer
|
||||
|
@ -102,6 +102,7 @@ mock_kinesisvideoarchivedmedia = lazy_load(
|
||||
boto3_name="kinesis-video-archived-media",
|
||||
)
|
||||
mock_kms = lazy_load(".kms", "mock_kms")
|
||||
mock_lakeformation = lazy_load(".lakeformation", "mock_lakeformation")
|
||||
mock_logs = lazy_load(".logs", "mock_logs")
|
||||
mock_managedblockchain = lazy_load(".managedblockchain", "mock_managedblockchain")
|
||||
mock_mediaconnect = lazy_load(".mediaconnect", "mock_mediaconnect")
|
||||
|
@ -92,6 +92,7 @@ backend_url_patterns = [
|
||||
re.compile("https?://.*\\.kinesisvideo\\.(.+)\\.amazonaws.com"),
|
||||
),
|
||||
("kms", re.compile("https?://kms\\.(.+)\\.amazonaws\\.com")),
|
||||
("lakeformation", re.compile("https?://lakeformation\\.(.+)\\.amazonaws\\.com")),
|
||||
("lambda", re.compile("https?://lambda\\.(.+)\\.amazonaws\\.com")),
|
||||
("logs", re.compile("https?://logs\\.(.+)\\.amazonaws\\.com")),
|
||||
(
|
||||
|
@ -1818,6 +1818,7 @@ class IAMBackend(BaseBackend):
|
||||
# Maybe we can enable this (and roles for other services) as part of a major release
|
||||
# self.create_service_linked_role(
|
||||
# service_name="opensearchservice.amazonaws.com", suffix="", description=""
|
||||
# service_name="lakeformation.amazonaws.com"
|
||||
# )
|
||||
|
||||
def attach_role_policy(self, policy_arn: str, role_name: str) -> None:
|
||||
|
5
moto/lakeformation/__init__.py
Normal file
5
moto/lakeformation/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""lakeformation module initialization; sets value for base decorator."""
|
||||
from .models import lakeformation_backends
|
||||
from ..core.models import base_decorator
|
||||
|
||||
mock_lakeformation = base_decorator(lakeformation_backends)
|
7
moto/lakeformation/exceptions.py
Normal file
7
moto/lakeformation/exceptions.py
Normal file
@ -0,0 +1,7 @@
|
||||
"""Exceptions raised by the lakeformation service."""
|
||||
from moto.core.exceptions import JsonRESTError
|
||||
|
||||
|
||||
class EntityNotFound(JsonRESTError):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("EntityNotFoundException", "Entity not found")
|
173
moto/lakeformation/models.py
Normal file
173
moto/lakeformation/models.py
Normal file
@ -0,0 +1,173 @@
|
||||
from collections import defaultdict
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from moto.core import BaseBackend, BackendDict, BaseModel
|
||||
from moto.utilities.tagging_service import TaggingService
|
||||
from .exceptions import EntityNotFound
|
||||
|
||||
|
||||
class Resource(BaseModel):
|
||||
def __init__(self, arn: str, role_arn: str):
|
||||
self.arn = arn
|
||||
self.role_arn = role_arn
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"ResourceArn": self.arn,
|
||||
"RoleArn": self.role_arn,
|
||||
}
|
||||
|
||||
|
||||
def default_settings() -> Dict[str, Any]:
|
||||
return {
|
||||
"DataLakeAdmins": [],
|
||||
"CreateDatabaseDefaultPermissions": [
|
||||
{
|
||||
"Principal": {"DataLakePrincipalIdentifier": "IAM_ALLOWED_PRINCIPALS"},
|
||||
"Permissions": ["ALL"],
|
||||
}
|
||||
],
|
||||
"CreateTableDefaultPermissions": [
|
||||
{
|
||||
"Principal": {"DataLakePrincipalIdentifier": "IAM_ALLOWED_PRINCIPALS"},
|
||||
"Permissions": ["ALL"],
|
||||
}
|
||||
],
|
||||
"TrustedResourceOwners": [],
|
||||
"AllowExternalDataFiltering": False,
|
||||
"ExternalDataFilteringAllowList": [],
|
||||
}
|
||||
|
||||
|
||||
class LakeFormationBackend(BaseBackend):
|
||||
def __init__(self, region_name: str, account_id: str):
|
||||
super().__init__(region_name, account_id)
|
||||
self.resources: Dict[str, Resource] = dict()
|
||||
self.settings: Dict[str, Dict[str, Any]] = defaultdict(default_settings)
|
||||
self.grants: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
|
||||
self.tagger = TaggingService()
|
||||
|
||||
def describe_resource(self, resource_arn: str) -> Resource:
|
||||
if resource_arn not in self.resources:
|
||||
raise EntityNotFound
|
||||
return self.resources[resource_arn]
|
||||
|
||||
def deregister_resource(self, resource_arn: str) -> None:
|
||||
del self.resources[resource_arn]
|
||||
|
||||
def register_resource(self, resource_arn: str, role_arn: str) -> None:
|
||||
self.resources[resource_arn] = Resource(resource_arn, role_arn)
|
||||
|
||||
def list_resources(self) -> List[Resource]:
|
||||
return list(self.resources.values())
|
||||
|
||||
def get_data_lake_settings(self, catalog_id: str) -> Dict[str, Any]:
|
||||
return self.settings[catalog_id]
|
||||
|
||||
def put_data_lake_settings(self, catalog_id: str, settings: Dict[str, Any]) -> None:
|
||||
self.settings[catalog_id] = settings
|
||||
|
||||
def grant_permissions(
|
||||
self,
|
||||
catalog_id: str,
|
||||
principal: Dict[str, str],
|
||||
resource: Dict[str, Any],
|
||||
permissions: List[str],
|
||||
permissions_with_grant_options: List[str],
|
||||
) -> None:
|
||||
self.grants[catalog_id].append(
|
||||
{
|
||||
"Principal": principal,
|
||||
"Resource": resource,
|
||||
"Permissions": permissions,
|
||||
"PermissionsWithGrantOption": permissions_with_grant_options,
|
||||
}
|
||||
)
|
||||
|
||||
def revoke_permissions(
|
||||
self,
|
||||
catalog_id: str,
|
||||
principal: Dict[str, str],
|
||||
resource: Dict[str, Any],
|
||||
permissions_to_revoke: List[str],
|
||||
permissions_with_grant_options_to_revoke: List[str],
|
||||
) -> None:
|
||||
for grant in self.grants[catalog_id]:
|
||||
if grant["Principal"] == principal and grant["Resource"] == resource:
|
||||
grant["Permissions"] = [
|
||||
perm
|
||||
for perm in grant["Permissions"]
|
||||
if perm not in permissions_to_revoke
|
||||
]
|
||||
if grant.get("PermissionsWithGrantOption") is not None:
|
||||
grant["PermissionsWithGrantOption"] = [
|
||||
perm
|
||||
for perm in grant["PermissionsWithGrantOption"]
|
||||
if perm not in permissions_with_grant_options_to_revoke
|
||||
]
|
||||
self.grants[catalog_id] = [
|
||||
grant for grant in self.grants[catalog_id] if grant["Permissions"] != []
|
||||
]
|
||||
|
||||
def list_permissions(self, catalog_id: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
No parameters have been implemented yet
|
||||
"""
|
||||
return self.grants[catalog_id]
|
||||
|
||||
def create_lf_tag(self, catalog_id: str, key: str, values: List[str]) -> None:
|
||||
# There is no ARN that we can use, so just create another unique identifier that's easy to recognize and reproduce
|
||||
arn = f"arn:lakeformation:{catalog_id}"
|
||||
tag_list = TaggingService.convert_dict_to_tags_input({key: values}) # type: ignore
|
||||
self.tagger.tag_resource(arn=arn, tags=tag_list)
|
||||
|
||||
def get_lf_tag(self, catalog_id: str, key: str) -> List[str]:
|
||||
# There is no ARN that we can use, so just create another unique identifier that's easy to recognize and reproduce
|
||||
arn = f"arn:lakeformation:{catalog_id}"
|
||||
all_tags = self.tagger.get_tag_dict_for_resource(arn=arn)
|
||||
return all_tags.get(key, []) # type: ignore
|
||||
|
||||
def delete_lf_tag(self, catalog_id: str, key: str) -> None:
|
||||
# There is no ARN that we can use, so just create another unique identifier that's easy to recognize and reproduce
|
||||
arn = f"arn:lakeformation:{catalog_id}"
|
||||
self.tagger.untag_resource_using_names(arn, tag_names=[key])
|
||||
|
||||
def list_lf_tags(self, catalog_id: str) -> Dict[str, str]:
|
||||
# There is no ARN that we can use, so just create another unique identifier that's easy to recognize and reproduce
|
||||
arn = f"arn:lakeformation:{catalog_id}"
|
||||
return self.tagger.get_tag_dict_for_resource(arn=arn)
|
||||
|
||||
def list_data_cells_filter(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
This currently just returns an empty list, as the corresponding Create is not yet implemented
|
||||
"""
|
||||
return []
|
||||
|
||||
def batch_grant_permissions(
|
||||
self, catalog_id: str, entries: List[Dict[str, Any]]
|
||||
) -> None:
|
||||
for entry in entries:
|
||||
self.grant_permissions(
|
||||
catalog_id=catalog_id,
|
||||
principal=entry.get("Principal"), # type: ignore[arg-type]
|
||||
resource=entry.get("Resource"), # type: ignore[arg-type]
|
||||
permissions=entry.get("Permissions"), # type: ignore[arg-type]
|
||||
permissions_with_grant_options=entry.get("PermissionsWithGrantOptions"), # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
def batch_revoke_permissions(
|
||||
self, catalog_id: str, entries: List[Dict[str, Any]]
|
||||
) -> None:
|
||||
for entry in entries:
|
||||
self.revoke_permissions(
|
||||
catalog_id=catalog_id,
|
||||
principal=entry.get("Principal"), # type: ignore[arg-type]
|
||||
resource=entry.get("Resource"), # type: ignore[arg-type]
|
||||
permissions_to_revoke=entry.get("Permissions"), # type: ignore[arg-type]
|
||||
permissions_with_grant_options_to_revoke=entry.get( # type: ignore[arg-type]
|
||||
"PermissionsWithGrantOptions"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
lakeformation_backends = BackendDict(LakeFormationBackend, "lakeformation")
|
139
moto/lakeformation/responses.py
Normal file
139
moto/lakeformation/responses.py
Normal file
@ -0,0 +1,139 @@
|
||||
"""Handles incoming lakeformation requests, invokes methods, returns responses."""
|
||||
import json
|
||||
|
||||
from moto.core.responses import BaseResponse
|
||||
from .models import lakeformation_backends, LakeFormationBackend
|
||||
|
||||
|
||||
class LakeFormationResponse(BaseResponse):
|
||||
"""Handler for LakeFormation requests and responses."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(service_name="lakeformation")
|
||||
|
||||
@property
|
||||
def lakeformation_backend(self) -> LakeFormationBackend:
|
||||
"""Return backend instance specific for this region."""
|
||||
return lakeformation_backends[self.current_account][self.region]
|
||||
|
||||
def describe_resource(self) -> str:
|
||||
resource_arn = self._get_param("ResourceArn")
|
||||
resource = self.lakeformation_backend.describe_resource(
|
||||
resource_arn=resource_arn
|
||||
)
|
||||
return json.dumps({"ResourceInfo": resource.to_dict()})
|
||||
|
||||
def deregister_resource(self) -> str:
|
||||
resource_arn = self._get_param("ResourceArn")
|
||||
self.lakeformation_backend.deregister_resource(resource_arn=resource_arn)
|
||||
return "{}"
|
||||
|
||||
def register_resource(self) -> str:
|
||||
resource_arn = self._get_param("ResourceArn")
|
||||
role_arn = self._get_param("RoleArn")
|
||||
self.lakeformation_backend.register_resource(
|
||||
resource_arn=resource_arn,
|
||||
role_arn=role_arn,
|
||||
)
|
||||
return "{}"
|
||||
|
||||
def list_resources(self) -> str:
|
||||
resources = self.lakeformation_backend.list_resources()
|
||||
return json.dumps({"ResourceInfoList": [res.to_dict() for res in resources]})
|
||||
|
||||
def get_data_lake_settings(self) -> str:
|
||||
catalog_id = self._get_param("CatalogId") or self.current_account
|
||||
settings = self.lakeformation_backend.get_data_lake_settings(catalog_id)
|
||||
return json.dumps({"DataLakeSettings": settings})
|
||||
|
||||
def put_data_lake_settings(self) -> str:
|
||||
catalog_id = self._get_param("CatalogId") or self.current_account
|
||||
settings = self._get_param("DataLakeSettings")
|
||||
self.lakeformation_backend.put_data_lake_settings(catalog_id, settings)
|
||||
return "{}"
|
||||
|
||||
def grant_permissions(self) -> str:
|
||||
catalog_id = self._get_param("CatalogId") or self.current_account
|
||||
principal = self._get_param("Principal")
|
||||
resource = self._get_param("Resource")
|
||||
permissions = self._get_param("Permissions")
|
||||
permissions_with_grant_options = self._get_param("PermissionsWithGrantOption")
|
||||
self.lakeformation_backend.grant_permissions(
|
||||
catalog_id=catalog_id,
|
||||
principal=principal,
|
||||
resource=resource,
|
||||
permissions=permissions,
|
||||
permissions_with_grant_options=permissions_with_grant_options,
|
||||
)
|
||||
return "{}"
|
||||
|
||||
def revoke_permissions(self) -> str:
|
||||
catalog_id = self._get_param("CatalogId") or self.current_account
|
||||
principal = self._get_param("Principal")
|
||||
resource = self._get_param("Resource")
|
||||
permissions = self._get_param("Permissions")
|
||||
permissions_with_grant_options = (
|
||||
self._get_param("PermissionsWithGrantOption") or []
|
||||
)
|
||||
self.lakeformation_backend.revoke_permissions(
|
||||
catalog_id=catalog_id,
|
||||
principal=principal,
|
||||
resource=resource,
|
||||
permissions_to_revoke=permissions,
|
||||
permissions_with_grant_options_to_revoke=permissions_with_grant_options,
|
||||
)
|
||||
return "{}"
|
||||
|
||||
def list_permissions(self) -> str:
|
||||
catalog_id = self._get_param("CatalogId") or self.current_account
|
||||
permissions = self.lakeformation_backend.list_permissions(catalog_id)
|
||||
return json.dumps({"PrincipalResourcePermissions": permissions})
|
||||
|
||||
def create_lf_tag(self) -> str:
|
||||
catalog_id = self._get_param("CatalogId") or self.current_account
|
||||
key = self._get_param("TagKey")
|
||||
values = self._get_param("TagValues")
|
||||
self.lakeformation_backend.create_lf_tag(catalog_id, key, values)
|
||||
return "{}"
|
||||
|
||||
def get_lf_tag(self) -> str:
|
||||
catalog_id = self._get_param("CatalogId") or self.current_account
|
||||
key = self._get_param("TagKey")
|
||||
tag_values = self.lakeformation_backend.get_lf_tag(catalog_id, key)
|
||||
return json.dumps(
|
||||
{"CatalogId": catalog_id, "TagKey": key, "TagValues": tag_values}
|
||||
)
|
||||
|
||||
def delete_lf_tag(self) -> str:
|
||||
catalog_id = self._get_param("CatalogId") or self.current_account
|
||||
key = self._get_param("TagKey")
|
||||
self.lakeformation_backend.delete_lf_tag(catalog_id, key)
|
||||
return "{}"
|
||||
|
||||
def list_lf_tags(self) -> str:
|
||||
catalog_id = self._get_param("CatalogId") or self.current_account
|
||||
tags = self.lakeformation_backend.list_lf_tags(catalog_id)
|
||||
return json.dumps(
|
||||
{
|
||||
"LFTags": [
|
||||
{"CatalogId": catalog_id, "TagKey": tag, "TagValues": value}
|
||||
for tag, value in tags.items()
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
def list_data_cells_filter(self) -> str:
|
||||
data_cells = self.lakeformation_backend.list_data_cells_filter()
|
||||
return json.dumps({"DataCellsFilters": data_cells})
|
||||
|
||||
def batch_grant_permissions(self) -> str:
|
||||
catalog_id = self._get_param("CatalogId") or self.current_account
|
||||
entries = self._get_param("Entries")
|
||||
self.lakeformation_backend.batch_grant_permissions(catalog_id, entries)
|
||||
return json.dumps({"Failures": []})
|
||||
|
||||
def batch_revoke_permissions(self) -> str:
|
||||
catalog_id = self._get_param("CatalogId") or self.current_account
|
||||
entries = self._get_param("Entries")
|
||||
self.lakeformation_backend.batch_revoke_permissions(catalog_id, entries)
|
||||
return json.dumps({"Failures": []})
|
29
moto/lakeformation/urls.py
Normal file
29
moto/lakeformation/urls.py
Normal file
@ -0,0 +1,29 @@
|
||||
"""lakeformation base URL and path."""
|
||||
from .responses import LakeFormationResponse
|
||||
|
||||
url_bases = [
|
||||
r"https?://lakeformation\.(.+)\.amazonaws\.com",
|
||||
]
|
||||
|
||||
|
||||
response = LakeFormationResponse()
|
||||
|
||||
|
||||
url_paths = {
|
||||
"{0}/DescribeResource$": response.dispatch,
|
||||
"{0}/DeregisterResource$": response.dispatch,
|
||||
"{0}/RegisterResource$": response.dispatch,
|
||||
"{0}/ListResources$": response.dispatch,
|
||||
"{0}/GetDataLakeSettings$": response.dispatch,
|
||||
"{0}/PutDataLakeSettings$": response.dispatch,
|
||||
"{0}/GrantPermissions$": response.dispatch,
|
||||
"{0}/ListPermissions$": response.dispatch,
|
||||
"{0}/RevokePermissions$": response.dispatch,
|
||||
"{0}/CreateLFTag$": response.dispatch,
|
||||
"{0}/GetLFTag$": response.dispatch,
|
||||
"{0}/DeleteLFTag$": response.dispatch,
|
||||
"{0}/ListLFTags$": response.dispatch,
|
||||
"{0}/ListDataCellsFilter$": response.dispatch,
|
||||
"{0}/BatchGrantPermissions$": response.dispatch,
|
||||
"{0}/BatchRevokePermissions$": response.dispatch,
|
||||
}
|
@ -342,6 +342,8 @@ kms:
|
||||
- TestAccKMSKey_Policy_iamServiceLinkedRole
|
||||
- TestAccKMSSecretDataSource
|
||||
- TestAccKMSSecretsDataSource
|
||||
lakeformation:
|
||||
- TestAccLakeFormationResource
|
||||
lambda:
|
||||
- TestAccLambdaAlias_
|
||||
- TestAccLambdaLayerVersion_basic
|
||||
|
0
tests/test_lakeformation/__init__.py
Normal file
0
tests/test_lakeformation/__init__.py
Normal file
256
tests/test_lakeformation/test_lakeformation.py
Normal file
256
tests/test_lakeformation/test_lakeformation.py
Normal file
@ -0,0 +1,256 @@
|
||||
"""Unit tests for lakeformation-supported APIs."""
|
||||
import boto3
|
||||
import pytest
|
||||
|
||||
from botocore.exceptions import ClientError
|
||||
from moto import mock_lakeformation
|
||||
from moto.core import DEFAULT_ACCOUNT_ID
|
||||
|
||||
# See our Development Tips on writing tests for hints on how to write good tests:
|
||||
# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html
|
||||
|
||||
|
||||
@mock_lakeformation
|
||||
def test_register_resource():
|
||||
client = boto3.client("lakeformation", region_name="us-east-2")
|
||||
resp = client.register_resource(
|
||||
ResourceArn="some arn",
|
||||
)
|
||||
|
||||
del resp["ResponseMetadata"]
|
||||
assert resp == {}
|
||||
|
||||
|
||||
@mock_lakeformation
|
||||
def test_describe_resource():
|
||||
client = boto3.client("lakeformation", region_name="us-east-2")
|
||||
client.register_resource(ResourceArn="some arn", RoleArn="role arn")
|
||||
|
||||
resp = client.describe_resource(ResourceArn="some arn")
|
||||
|
||||
assert resp["ResourceInfo"] == {"ResourceArn": "some arn", "RoleArn": "role arn"}
|
||||
|
||||
|
||||
@mock_lakeformation
|
||||
def test_deregister_resource():
|
||||
client = boto3.client("lakeformation", region_name="us-east-2")
|
||||
client.register_resource(ResourceArn="some arn")
|
||||
client.deregister_resource(ResourceArn="some arn")
|
||||
|
||||
with pytest.raises(ClientError) as exc:
|
||||
client.describe_resource(ResourceArn="some arn")
|
||||
err = exc.value.response["Error"]
|
||||
assert err["Code"] == "EntityNotFoundException"
|
||||
|
||||
|
||||
@mock_lakeformation
|
||||
def test_list_resources():
|
||||
client = boto3.client("lakeformation", region_name="us-east-2")
|
||||
|
||||
resp = client.list_resources()
|
||||
assert resp["ResourceInfoList"] == []
|
||||
|
||||
client.register_resource(ResourceArn="some arn")
|
||||
client.register_resource(ResourceArn="another arn")
|
||||
|
||||
resp = client.list_resources()
|
||||
assert len(resp["ResourceInfoList"]) == 2
|
||||
|
||||
|
||||
@mock_lakeformation
|
||||
def test_data_lake_settings():
|
||||
client = boto3.client("lakeformation", region_name="us-east-2")
|
||||
resp = client.get_data_lake_settings()
|
||||
assert resp["DataLakeSettings"] == {
|
||||
"DataLakeAdmins": [],
|
||||
"CreateDatabaseDefaultPermissions": [
|
||||
{
|
||||
"Principal": {"DataLakePrincipalIdentifier": "IAM_ALLOWED_PRINCIPALS"},
|
||||
"Permissions": ["ALL"],
|
||||
}
|
||||
],
|
||||
"CreateTableDefaultPermissions": [
|
||||
{
|
||||
"Principal": {"DataLakePrincipalIdentifier": "IAM_ALLOWED_PRINCIPALS"},
|
||||
"Permissions": ["ALL"],
|
||||
}
|
||||
],
|
||||
"TrustedResourceOwners": [],
|
||||
"AllowExternalDataFiltering": False,
|
||||
"ExternalDataFilteringAllowList": [],
|
||||
}
|
||||
|
||||
settings = {"DataLakeAdmins": [{"DataLakePrincipalIdentifier": "dlpi"}]}
|
||||
client.put_data_lake_settings(DataLakeSettings=settings)
|
||||
|
||||
resp = client.get_data_lake_settings()
|
||||
assert resp["DataLakeSettings"] == settings
|
||||
|
||||
|
||||
@mock_lakeformation
|
||||
def test_list_permissions():
|
||||
client = boto3.client("lakeformation", region_name="eu-west-2")
|
||||
|
||||
resp = client.grant_permissions(
|
||||
Principal={"DataLakePrincipalIdentifier": "asdf"},
|
||||
Resource={"Database": {"Name": "db"}},
|
||||
Permissions=["ALL"],
|
||||
PermissionsWithGrantOption=["SELECT"],
|
||||
)
|
||||
|
||||
del resp["ResponseMetadata"]
|
||||
assert resp == {}
|
||||
|
||||
# list all
|
||||
resp = client.list_permissions()
|
||||
assert resp["PrincipalResourcePermissions"] == [
|
||||
{
|
||||
"Principal": {"DataLakePrincipalIdentifier": "asdf"},
|
||||
"Resource": {"Database": {"Name": "db"}},
|
||||
"Permissions": ["ALL"],
|
||||
"PermissionsWithGrantOption": ["SELECT"],
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@mock_lakeformation
|
||||
def test_revoke_permissions():
|
||||
client = boto3.client("lakeformation", region_name="eu-west-2")
|
||||
|
||||
client.grant_permissions(
|
||||
Principal={"DataLakePrincipalIdentifier": "asdf"},
|
||||
Resource={"Database": {"Name": "db"}},
|
||||
Permissions=["SELECT", "ALTER", "DROP"],
|
||||
PermissionsWithGrantOption=["SELECT", "DROP"],
|
||||
)
|
||||
|
||||
resp = client.revoke_permissions(
|
||||
Principal={"DataLakePrincipalIdentifier": "asdf"},
|
||||
Resource={"Database": {"Name": "db"}},
|
||||
Permissions=["DROP"],
|
||||
)
|
||||
|
||||
del resp["ResponseMetadata"]
|
||||
assert resp == {}
|
||||
|
||||
# list all
|
||||
resp = client.list_permissions()
|
||||
assert resp["PrincipalResourcePermissions"] == [
|
||||
{
|
||||
"Principal": {"DataLakePrincipalIdentifier": "asdf"},
|
||||
"Resource": {"Database": {"Name": "db"}},
|
||||
"Permissions": ["SELECT", "ALTER"],
|
||||
"PermissionsWithGrantOption": ["SELECT", "DROP"],
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@mock_lakeformation
|
||||
def test_lf_tags():
|
||||
client = boto3.client("lakeformation", region_name="eu-west-2")
|
||||
|
||||
client.create_lf_tag(TagKey="tag1", TagValues=["1a", "1b"])
|
||||
client.create_lf_tag(TagKey="tag2", TagValues=["2a", "2b"])
|
||||
client.create_lf_tag(TagKey="tag3", TagValues=["3a", "3b"])
|
||||
|
||||
resp = client.get_lf_tag(TagKey="tag1")
|
||||
assert resp["CatalogId"] == DEFAULT_ACCOUNT_ID
|
||||
assert resp["TagKey"] == "tag1"
|
||||
assert resp["TagValues"] == ["1a", "1b"]
|
||||
|
||||
resp = client.list_lf_tags()
|
||||
assert len(resp["LFTags"]) == 3
|
||||
assert {
|
||||
"CatalogId": DEFAULT_ACCOUNT_ID,
|
||||
"TagKey": "tag1",
|
||||
"TagValues": ["1a", "1b"],
|
||||
} in resp["LFTags"]
|
||||
assert {
|
||||
"CatalogId": DEFAULT_ACCOUNT_ID,
|
||||
"TagKey": "tag2",
|
||||
"TagValues": ["2a", "2b"],
|
||||
} in resp["LFTags"]
|
||||
assert {
|
||||
"CatalogId": DEFAULT_ACCOUNT_ID,
|
||||
"TagKey": "tag3",
|
||||
"TagValues": ["3a", "3b"],
|
||||
} in resp["LFTags"]
|
||||
|
||||
client.delete_lf_tag(TagKey="tag2")
|
||||
|
||||
resp = client.list_lf_tags()
|
||||
assert len(resp["LFTags"]) == 2
|
||||
assert {
|
||||
"CatalogId": DEFAULT_ACCOUNT_ID,
|
||||
"TagKey": "tag1",
|
||||
"TagValues": ["1a", "1b"],
|
||||
} in resp["LFTags"]
|
||||
assert {
|
||||
"CatalogId": DEFAULT_ACCOUNT_ID,
|
||||
"TagKey": "tag3",
|
||||
"TagValues": ["3a", "3b"],
|
||||
} in resp["LFTags"]
|
||||
|
||||
|
||||
@mock_lakeformation
|
||||
def test_list_data_cells_filter():
|
||||
client = boto3.client("lakeformation", region_name="eu-west-2")
|
||||
|
||||
resp = client.list_data_cells_filter()
|
||||
assert resp["DataCellsFilters"] == []
|
||||
|
||||
|
||||
@mock_lakeformation
|
||||
def test_batch_revoke_permissions():
|
||||
client = boto3.client("lakeformation", region_name="eu-west-2")
|
||||
|
||||
client.batch_grant_permissions(
|
||||
Entries=[
|
||||
{
|
||||
"Id": "id1",
|
||||
"Principal": {"DataLakePrincipalIdentifier": "id1"},
|
||||
"Resource": {"Database": {"Name": "db"}},
|
||||
"Permissions": ["SELECT", "ALTER", "DROP"],
|
||||
"PermissionsWithGrantOption": ["SELECT", "DROP"],
|
||||
},
|
||||
{
|
||||
"Id": "id2",
|
||||
"Principal": {"DataLakePrincipalIdentifier": "id2"},
|
||||
"Resource": {"Database": {"Name": "db"}},
|
||||
"Permissions": ["SELECT", "ALTER", "DROP"],
|
||||
"PermissionsWithGrantOption": ["SELECT", "DROP"],
|
||||
},
|
||||
{
|
||||
"Id": "id3",
|
||||
"Principal": {"DataLakePrincipalIdentifier": "id3"},
|
||||
"Resource": {"Database": {"Name": "db"}},
|
||||
"Permissions": ["SELECT", "ALTER", "DROP"],
|
||||
"PermissionsWithGrantOption": ["SELECT", "DROP"],
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
resp = client.list_permissions()
|
||||
assert len(resp["PrincipalResourcePermissions"]) == 3
|
||||
|
||||
client.batch_revoke_permissions(
|
||||
Entries=[
|
||||
{
|
||||
"Id": "id1",
|
||||
"Principal": {"DataLakePrincipalIdentifier": "id2"},
|
||||
"Resource": {"Database": {"Name": "db"}},
|
||||
"Permissions": ["SELECT", "ALTER", "DROP"],
|
||||
"PermissionsWithGrantOption": ["SELECT", "DROP"],
|
||||
},
|
||||
{
|
||||
"Id": "id2",
|
||||
"Principal": {"DataLakePrincipalIdentifier": "id3"},
|
||||
"Resource": {"Database": {"Name": "db"}},
|
||||
"Permissions": ["SELECT", "ALTER", "DROP"],
|
||||
"PermissionsWithGrantOption": ["SELECT", "DROP"],
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
resp = client.list_permissions()
|
||||
assert len(resp["PrincipalResourcePermissions"]) == 1
|
Loading…
Reference in New Issue
Block a user