Skip to content

Commit 329de3a

Browse files
authored
Merge pull request #45 from datamasque/DM-4094-tagging-role
feat: DM-4094: Add iam_role_arn to DatabaseConnectionConfig
2 parents dd622e7 + aa31bf0 commit 329de3a

5 files changed

Lines changed: 117 additions & 2 deletions

File tree

HISTORY.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22
History
33
=======
44

5+
1.2.5 (2026-08-17)
6+
------------------
7+
8+
* Added ``iam_role_arn`` to ``DatabaseConnectionConfig``: the IAM role DataMasque assumes to tag
9+
the connection's AWS resource, for resources in another AWS account. Sent only for the engines
10+
that can be an Amazon RDS instance, Aurora cluster or Redshift cluster.
11+
12+
Requires server version 3.26.16
13+
514
1.2.4 (2026-08-12)
615
------------------
716

datamasque/client/models/connection.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,22 @@ def _strip_encrypted_password(cls, data: dict) -> dict:
289289
return data
290290

291291

292+
# Engines that can sit behind an Amazon RDS, Aurora or Redshift endpoint, and so can carry a role
293+
# for DataMasque to assume when tagging the resource. Aurora needs no entry of its own: it uses
294+
# these same engines with a cluster endpoint.
295+
_AWS_TAGGABLE_DATABASE_TYPES = frozenset(
296+
{
297+
DatabaseType.postgres,
298+
DatabaseType.mysql,
299+
DatabaseType.mariadb,
300+
DatabaseType.oracle,
301+
DatabaseType.mssql,
302+
DatabaseType.db2_luw,
303+
DatabaseType.redshift,
304+
}
305+
)
306+
307+
292308
class DatabaseConnectionConfig(ConnectionConfig):
293309
"""
294310
Connection configuration for a SQL database.
@@ -309,6 +325,11 @@ class DatabaseConnectionConfig(ConnectionConfig):
309325
is_read_only: bool = False
310326
s3_bucket_name: Optional[str] = None
311327
s3_redshift_iam_role: Optional[str] = None
328+
# An IAM role for DataMasque to assume when tagging this database's AWS resource after a run.
329+
# Only the engines in `_AWS_TAGGABLE_DATABASE_TYPES` can be an RDS instance, Aurora cluster or
330+
# Redshift cluster, so it is pruned for the rest. Distinct from `s3_redshift_iam_role`, which is
331+
# the role the Redshift cluster itself uses to reach S3.
332+
iam_role_arn: Optional[str] = None
312333

313334
@model_validator(mode="after")
314335
def _reject_special_engines(self) -> "DatabaseConnectionConfig":
@@ -348,6 +369,8 @@ def _serialize(self, handler: Callable) -> dict:
348369
if db_type is not DatabaseType.redshift:
349370
d.pop("s3_bucket_name", None)
350371
d.pop("s3_redshift_iam_role", None)
372+
if db_type not in _AWS_TAGGABLE_DATABASE_TYPES:
373+
d.pop("iam_role_arn", None)
351374
if not d.get("engine_options"):
352375
d.pop("engine_options", None)
353376
return d

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "datamasque-python"
3-
version = "1.2.4"
3+
version = "1.2.5"
44
description = "Official Python client for the DataMasque data-masking API."
55
authors = [
66
{ name = "DataMasque Ltd" },

tests/test_connections.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1400,3 +1400,86 @@ def test_create_or_update_mongo_connection(client):
14001400
assert sent["db_type"] == "mongodb"
14011401
assert sent["dbpassword"] == "hunter2"
14021402
assert sent["replica_set"] == "rs0"
1403+
1404+
1405+
def _database_connection(database_type: DatabaseType, **extra) -> DatabaseConnectionConfig:
1406+
return DatabaseConnectionConfig(
1407+
name="conn",
1408+
host="mydb.abc123.ap-southeast-2.rds.amazonaws.com",
1409+
port=5432,
1410+
database="db",
1411+
user="user",
1412+
password="secret",
1413+
database_type=database_type,
1414+
**extra,
1415+
)
1416+
1417+
1418+
@pytest.mark.parametrize(
1419+
"database_type",
1420+
[
1421+
DatabaseType.postgres,
1422+
DatabaseType.mysql,
1423+
DatabaseType.mariadb,
1424+
DatabaseType.oracle,
1425+
DatabaseType.mssql,
1426+
DatabaseType.db2_luw,
1427+
DatabaseType.redshift,
1428+
],
1429+
)
1430+
def test_database_connection_sends_tagging_iam_role_for_aws_hosted_engines(database_type):
1431+
"""The engines that can sit behind an RDS, Aurora or Redshift endpoint can carry a tagging role."""
1432+
extra = (
1433+
{"s3_bucket_name": "bucket", "s3_redshift_iam_role": "arn:aws:iam::123456789012:role/redshift-s3"}
1434+
if database_type is DatabaseType.redshift
1435+
else {}
1436+
)
1437+
conn = _database_connection(database_type, iam_role_arn="arn:aws:iam::119836602066:role/tagger", **extra)
1438+
1439+
api_dict = conn.model_dump(exclude_none=True, by_alias=True, mode="json")
1440+
1441+
assert api_dict["iam_role_arn"] == "arn:aws:iam::119836602066:role/tagger"
1442+
1443+
1444+
@pytest.mark.parametrize(
1445+
"database_type",
1446+
[
1447+
DatabaseType.db2i,
1448+
DatabaseType.informix,
1449+
DatabaseType.saphana,
1450+
# The two worth documenting rather than merely covering. `mssql_linked` inherits the field
1451+
# through `MssqlLinkedServerConnectionConfig` and is excluded only by its own enum value;
1452+
# `databricks_lakebase` is AWS-hosted, so it reads like an omission from the taggable set
1453+
# until you know it presents no endpoint tagging can act on.
1454+
DatabaseType.mssql_linked,
1455+
DatabaseType.databricks_lakebase,
1456+
],
1457+
)
1458+
def test_database_connection_omits_tagging_iam_role_for_engines_that_cannot_be_aws_resources(database_type):
1459+
"""Mirrors how `s3_redshift_iam_role` is pruned: the server has no such field on these engines."""
1460+
conn = _database_connection(database_type, iam_role_arn="arn:aws:iam::119836602066:role/tagger")
1461+
1462+
api_dict = conn.model_dump(exclude_none=True, by_alias=True, mode="json")
1463+
1464+
assert "iam_role_arn" not in api_dict
1465+
1466+
1467+
def test_database_connection_omits_tagging_iam_role_when_unset():
1468+
conn = _database_connection(DatabaseType.postgres)
1469+
1470+
assert "iam_role_arn" not in conn.model_dump(exclude_none=True, by_alias=True, mode="json")
1471+
1472+
1473+
def test_redshift_connection_keeps_its_cluster_s3_role_separate_from_its_tagging_role():
1474+
"""Two ARNs with different meanings: the cluster's own S3 access, and the role DataMasque assumes to tag."""
1475+
conn = _database_connection(
1476+
DatabaseType.redshift,
1477+
s3_bucket_name="bucket",
1478+
s3_redshift_iam_role="arn:aws:iam::123456789012:role/redshift-s3",
1479+
iam_role_arn="arn:aws:iam::119836602066:role/tagger",
1480+
)
1481+
1482+
api_dict = conn.model_dump(exclude_none=True, by_alias=True, mode="json")
1483+
1484+
assert api_dict["s3_redshift_iam_role"] == "arn:aws:iam::123456789012:role/redshift-s3"
1485+
assert api_dict["iam_role_arn"] == "arn:aws:iam::119836602066:role/tagger"

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)