Feature: LakeFormation (#6172)

This commit is contained in:
Bert Blommers 2023-04-04 10:36:48 +01:00 committed by GitHub
parent 8a79c34674
commit 6c32c089a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 750 additions and 1 deletions

View File

@ -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

View 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

View File

@ -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")

View File

@ -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")),
(

View File

@ -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:

View 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)

View 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")

View 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")

View 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": []})

View 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,
}

View File

@ -342,6 +342,8 @@ kms:
- TestAccKMSKey_Policy_iamServiceLinkedRole
- TestAccKMSSecretDataSource
- TestAccKMSSecretsDataSource
lakeformation:
- TestAccLakeFormationResource
lambda:
- TestAccLambdaAlias_
- TestAccLambdaLayerVersion_basic

View File

View 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