Skip to content

Commit 68cee79

Browse files
authored
Merge pull request #23 from IanMeyers/main
Version 1.0.17
2 parents c3f030e + b68006e commit 68cee79

File tree

10 files changed

+89
-41
lines changed

10 files changed

+89
-41
lines changed

setup.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
from setuptools import find_packages, setup
44

55
setup(
6-
install_requires=['boto3~=1.26.46',
6+
install_requires=['boto3~=1.26.121',
77
'pystache~=0.6.0',
8-
'setuptools~=65.6.3',
8+
'setuptools~=67.7.2',
99
'shortuuid~=1.0.11',
10-
'botocore~=1.29.46'],
10+
'botocore~=1.29.121'],
1111
include_package_data=True
1212
)

src/cli-examples.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export DATA_MESH_ACCOUNT=<my account id>
2727
./data-mesh-cli deny-subscription --credentials_file $CREDS_FILE --request-id 4GaXSebUtSvzXQXv7mZt3M --decision_notes "No Way Dude!"
2828

2929
# change your mind and approve it now
30-
./data-mesh-cli approve-subscription --data_mesh_account_id $DATA_MESH_ACCOUNT --region_name $AWS_REGION --use_credentials "{\"AccessKeyId\":\"my access key\",\"SecretAccessKey\":\"my secret key\"}" --request-id <request> --grant-permissions <permissions allowed to the consumer> --grantable-permissions <permissions the consumer can pass on. remove parameter for no grantable permissions> --decision_notes "Notes attached to the approval"
30+
./data-mesh-cli approve-subscription --data_mesh_account_id $DATA_MESH_ACCOUNT --region_name $AWS_REGION --use_credentials "{\"AccessKeyId\":\"my access key\",\"SecretAccessKey\":\"my secret key\"}" --request-id <request> --grant-permissions <permissions allowed to the consumer> --grantable-permissions <permissions the consumer can pass on. remove parameter for no grantable permissions> --decision_notes "Notes attached to the approval"
3131
./data-mesh-cli approve-subscription --credentials_file $CREDS_FILE --request-id 4GaXSebUtSvzXQXv7mZt3M --grant-permissions SELECT --decision_notes "Changed my mind"
3232

3333
# modify the subscription to allow an additional grant

src/data_mesh_util/DataMeshAdmin.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ def __init__(self, data_mesh_account_id: str, region_name: str = 'us-east-1', lo
6363

6464
self._logger.debug(f"Running as {self._current_identity.get('Arn')}")
6565

66+
if self._log_level == 'DEBUG':
67+
utils.log_instance_signature(self, self._logger)
68+
6669
def _create_template_config(self, config: dict):
6770
if config is None:
6871
config = {}

src/data_mesh_util/DataMeshConsumer.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ def __init__(self, data_mesh_account_id: str, region_name: str = 'us-east-1', lo
8787
self._mesh_automator = ApiAutomator(target_account=self._data_mesh_account_id,
8888
session=self._ro_session, log_level=self._log_level)
8989

90+
if self._log_level == 'DEBUG':
91+
utils.log_instance_signature(self, self._logger)
92+
9093
def request_access_to_product(self, owner_account_id: str, database_name: str,
9194
tables: list, request_permissions: list) -> dict:
9295
'''

src/data_mesh_util/DataMeshMacros.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from data_mesh_util.lib.constants import *
22
from data_mesh_util import DataMeshAdmin as data_mesh_admin
3+
import data_mesh_util.lib.utils as utils
34

45

56
class DataMeshMacros:
@@ -13,6 +14,9 @@ def __init__(self, data_mesh_account_id: str, region_name: str = 'us-east-1', lo
1314
self._region = region_name
1415
self._log_level = log_level
1516

17+
if self._log_level == 'DEBUG':
18+
utils.log_instance_signature(self, self._logger)
19+
1620
def bootstrap_account(self, account_type: str, mesh_credentials, account_credentials, crawler_role_arn: str = None):
1721
# create a data mesh admin for the mesh account
1822
mesh_admin = data_mesh_admin.DataMeshAdmin(

src/data_mesh_util/DataMeshProducer.py

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
class DataMeshProducer:
1515
_data_mesh_account_id = None
1616
_data_producer_account_id = None
17-
_data_mesh_manager_role_arn = None
1817
_session = None
1918
_iam_client = None
2019
_sts_client = None
@@ -58,13 +57,16 @@ def __init__(self, data_mesh_account_id: str, region_name: str = 'us-east-1', lo
5857

5958
self._data_producer_identity = self._sts_client.get_caller_identity()
6059
self._data_producer_account_id = self._data_producer_identity.get('Account')
60+
producer_role_name = utils.get_central_role_name(self._data_producer_account_id, PRODUCER)
61+
self._data_producer_role_arn = utils.get_role_arn(account_id=self._data_mesh_account_id,
62+
role_name=producer_role_name)
6163

6264
self._producer_automator = ApiAutomator(target_account=self._data_producer_account_id,
6365
session=self._session, log_level=self._log_level)
6466

6567
# now assume the DataMeshProducer-<account-id> Role in the Mesh Account
6668
self._data_mesh_session, self._data_mesh_credentials, self._data_mesh_arn = utils.assume_iam_role(
67-
role_name=utils.get_central_role_name(self._data_producer_account_id, PRODUCER),
69+
role_name=producer_role_name,
6870
region_name=self._current_region,
6971
use_credentials=_producer_credentials,
7072
target_account=self._data_mesh_account_id
@@ -85,11 +87,15 @@ def __init__(self, data_mesh_account_id: str, region_name: str = 'us-east-1', lo
8587
region_name=self._current_region,
8688
log_level=log_level)
8789

90+
if self._log_level == 'DEBUG':
91+
utils.log_instance_signature(self, self._logger)
92+
8893
def _create_mesh_table(self, table_def: dict, data_mesh_glue_client, source_database_name: str,
8994
data_mesh_database_name: str,
9095
producer_account_id: str,
9196
data_mesh_account_id: str, create_public_metadata: bool = True,
92-
expose_table_references_with_suffix: str = "_link", use_original_table_name: bool = False):
97+
expose_table_references_with_suffix: str = "_link",
98+
use_original_table_name: bool = False) -> tuple:
9399
'''
94100
API to create a table as a data product in the data mesh
95101
:param table_def:
@@ -106,7 +112,7 @@ def _create_mesh_table(self, table_def: dict, data_mesh_glue_client, source_data
106112
# remove properties from a TableInfo object returned from get_table to be compatible with put_table
107113
keys = [
108114
'DatabaseName', 'CreateTime', 'UpdateTime', 'CreatedBy', 'IsRegisteredWithLakeFormation', 'CatalogId',
109-
'Tags'
115+
'Tags', 'VersionId'
110116
]
111117
t = utils.remove_dict_keys(input_dict=table_def, remove_keys=keys)
112118
t['Owner'] = producer_account_id
@@ -138,8 +144,8 @@ def _create_mesh_table(self, table_def: dict, data_mesh_glue_client, source_data
138144
partition_input_list=table_partitions
139145
)
140146

141-
# grant access to the producer account
142-
perms = ['INSERT', 'SELECT', 'ALTER', 'DELETE', 'DESCRIBE']
147+
# grant full access to the producer account
148+
perms = ['INSERT', 'SELECT', 'ALTER', 'DELETE', 'DESCRIBE', 'DROP']
143149
permissions_granted = self._mesh_automator.lf_grant_permissions(
144150
data_mesh_account_id=self._data_mesh_account_id,
145151
principal=producer_account_id,
@@ -153,37 +159,37 @@ def _create_mesh_table(self, table_def: dict, data_mesh_glue_client, source_data
153159
if create_public_metadata is True:
154160
self._mesh_automator.lf_grant_permissions(
155161
data_mesh_account_id=self._data_mesh_account_id,
156-
principal=utils.get_role_arn(self._data_mesh_account_id, DATA_MESH_READONLY_ROLENAME),
162+
principal=utils.get_role_arn(account_id=self._data_mesh_account_id,
163+
role_name=DATA_MESH_READONLY_ROLENAME),
157164
database_name=data_mesh_database_name,
158165
table_name=table_name,
159166
permissions=['DESCRIBE'],
160167
grantable_permissions=None
161168
)
162-
self._logger.info(f"Granted Describe on {table_name} to {DATA_MESH_READONLY_ROLENAME}")
169+
self._logger.info(f"Granted Describe on Table {table_name} to {DATA_MESH_READONLY_ROLENAME}")
163170

164171
# in the producer account, accept the RAM share after 1 second - seems to be an async delay
165-
if permissions_granted > 0:
166-
time.sleep(1)
167-
self._producer_automator.accept_pending_lf_resource_shares(
168-
sender_account=data_mesh_account_id
169-
)
172+
time.sleep(1)
173+
self._producer_automator.accept_pending_lf_resource_shares(
174+
sender_account=data_mesh_account_id
175+
)
170176

171-
# create a resource link for the data mesh table in producer account
172-
if use_original_table_name is True:
173-
link_table_name = table_name
174-
else:
175-
link_table_name = f"{table_name}_link"
176-
if expose_table_references_with_suffix is not None:
177-
link_table_name = f"{table_name}{expose_table_references_with_suffix}"
177+
# create a resource link for the data mesh table in producer account
178+
if use_original_table_name is True:
179+
link_table_name = table_name
180+
else:
181+
link_table_name = f"{table_name}_link"
182+
if expose_table_references_with_suffix is not None:
183+
link_table_name = f"{table_name}{expose_table_references_with_suffix}"
178184

179-
self._producer_automator.create_remote_table(
180-
data_mesh_account_id=self._data_mesh_account_id,
181-
database_name=data_mesh_database_name,
182-
local_table_name=link_table_name,
183-
remote_table_name=table_name
184-
)
185+
self._producer_automator.create_remote_table(
186+
data_mesh_account_id=self._data_mesh_account_id,
187+
database_name=data_mesh_database_name,
188+
local_table_name=link_table_name,
189+
remote_table_name=table_name
190+
)
185191

186-
return table_name, link_table_name
192+
return table_name, link_table_name
187193

188194
def _make_database_name(self, database_name: str):
189195
return "%s-%s" % (database_name, self._data_producer_identity.get('Account'))
@@ -227,6 +233,9 @@ def create_data_products(self, source_database_name: str,
227233
expose_data_mesh_db_name: str = None,
228234
expose_table_references_with_suffix: str = "_link",
229235
use_original_table_name: bool = False):
236+
if self._log_level == 'DEBUG':
237+
self._logger.debug(locals())
238+
230239
if create_public_metadata is None:
231240
create_public_metadata = True
232241

@@ -272,7 +281,7 @@ def create_data_products(self, source_database_name: str,
272281
# grant the mesh permissions to administer the database
273282
self._mesh_automator.lf_grant_permissions(
274283
data_mesh_account_id=self._data_mesh_account_id,
275-
principal=self._data_mesh_arn,
284+
principal=self._data_producer_role_arn,
276285
database_name=data_mesh_database_name,
277286
permissions=['ALL'],
278287
grantable_permissions=None
@@ -334,19 +343,24 @@ def create_data_products(self, source_database_name: str,
334343
)
335344

336345
# grant the mesh permissions to describe and select from the table
346+
manager_perms = ['DESCRIBE', 'SELECT', 'DROP']
337347
self._mesh_automator.lf_grant_permissions(
338348
data_mesh_account_id=self._data_mesh_account_id,
339-
principal=self._data_mesh_arn,
349+
principal=utils.get_role_arn(account_id=self._data_mesh_account_id,
350+
role_name=DATA_MESH_MANAGER_ROLENAME),
340351
database_name=data_mesh_database_name,
341352
table_name=table.get('Name'),
342-
permissions=['DESCRIBE', 'SELECT'],
353+
permissions=manager_perms,
343354
grantable_permissions=None
344355
)
345356
self._logger.info(
346-
f"Granted describe access on Table {table.get('Name')} to Data Mesh {self._data_mesh_account_id}")
357+
f"Granted {manager_perms} access on Table {table.get('Name')} to {DATA_MESH_MANAGER_ROLENAME}")
347358

348359
shared_objects.get('Tables').append({
349-
'SourceTable': created_table[0],
360+
'SourceTable': table.get('Name'),
361+
'TargetDatabase': data_mesh_database_name,
362+
'TargetTable': created_table[0],
363+
'LinkDatabase': data_mesh_database_name,
350364
'LinkTable': created_table[1]
351365
})
352366

src/data_mesh_util/lib/ApiAutomator.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ def __init__(self, target_account: str, session: boto3.session.Session, log_leve
2525
self._logger.setLevel(log_level)
2626
self._clients = {}
2727

28+
if log_level == 'DEBUG':
29+
utils.log_instance_signature(self, self._logger)
30+
2831
def _get_client(self, client_name):
2932
client = self._clients.get(client_name)
3033

@@ -822,9 +825,16 @@ def create_lf_permissions_entry(self,
822825
entry["Id"] = shortuuid.uuid()
823826

824827
log_message = f"{target_account_id} Database {database_name} Permissions:{permissions}"
828+
825829
if grantable_permissions is not None:
826-
entry["PermissionsWithGrantOption"] = grantable_permissions
827-
log_message = f"{log_message}, {grantable_permissions} WITH GRANT OPTION"
830+
# make sure that grantable permissions can't include anything that isn't in permissions
831+
_set_grantable_permissions = []
832+
for p in permissions:
833+
if p in grantable_permissions:
834+
_set_grantable_permissions.append(p)
835+
836+
entry["PermissionsWithGrantOption"] = _set_grantable_permissions
837+
log_message = f"{log_message}, {_set_grantable_permissions} WITH GRANT OPTION"
828838

829839
self._logger.info(log_message)
830840
db_entries.append(entry)
@@ -910,7 +920,8 @@ def lf_batch_grant_permissions(self,
910920
entries.extend(self.create_lf_permissions_entry(
911921
data_mesh_account_id=data_mesh_account_id,
912922
target_account_id=target_account_id,
913-
database_name=database_name, table_name=t,
923+
database_name=database_name,
924+
table_name=t,
914925
permissions=permissions,
915926
grantable_permissions=grantable_permissions,
916927
target_batch=True)
@@ -927,7 +938,13 @@ def lf_batch_grant_permissions(self,
927938
if 'Failures' in response:
928939
perms_added -= len(response.get('Failures'))
929940

930-
return perms_added
941+
if perms_added == 0:
942+
self._logger.error(
943+
f"Exceptions raised while granting Batch Permissions from {data_mesh_account_id} to {target_account_id}")
944+
self._logger.error(response.get('Failures'))
945+
raise Exception(f"Failed to grant permissions on Account {data_mesh_account_id}")
946+
else:
947+
return perms_added
931948

932949
def lf_grant_permissions(self, data_mesh_account_id: str, principal: str, database_name: str,
933950
table_name: str = None,

src/data_mesh_util/lib/utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,3 +344,8 @@ def generate_resource(service: str, region: str, credentials):
344344
if 'SessionToken' in use_creds:
345345
args['aws_session_token'] = use_creds.get('SessionToken')
346346
return boto3.resource(**args)
347+
348+
def log_instance_signature(obj, logger) -> None:
349+
logger.debug(f"Instance Signature for {obj.__class__.__name__}")
350+
for attr in vars(obj):
351+
logger.debug(f"{attr}: {obj.__dict__.get(attr)}")

src/data_mesh_util/resource/producer_account_policy.pystache

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"Sid": "ProducerAccess4",
4242
"Effect": "Allow",
4343
"Action": [
44+
"lakeformation:BatchGrantPermissions",
4445
"lakeformation:GrantPermissions",
4546
"lakeformation:GetResourceLFTags",
4647
"lakeformation:GetLFTag"

src/data_mesh_util/resource/producer_mesh_policy.pystache

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@
7373
"Sid": "ProducerPolicy2",
7474
"Effect": "Allow",
7575
"Action": [
76-
"ram:CreateResourceShare"
76+
"ram:CreateResourceShare",
77+
"ram:GetResourceShares"
7778
],
7879
"Resource": "*"
7980
},

0 commit comments

Comments
 (0)