From 9cc76ed5fc54a4c51cc569be87ba70c14a33e142 Mon Sep 17 00:00:00 2001 From: ntkathole Date: Sun, 12 Oct 2025 17:29:47 +0530 Subject: [PATCH] fix: Check if DynamoDB table exists before create Signed-off-by: ntkathole --- .../feast/infra/online_stores/dynamodb.py | 66 +++++++++++++------ 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/sdk/python/feast/infra/online_stores/dynamodb.py b/sdk/python/feast/infra/online_stores/dynamodb.py index e4ff4fbe513..e60796b2963 100644 --- a/sdk/python/feast/infra/online_stores/dynamodb.py +++ b/sdk/python/feast/infra/online_stores/dynamodb.py @@ -203,26 +203,45 @@ def update( kwargs = {"Tags": table_tags} if table_tags else {} table_name = _get_table_name(online_config, config, table_instance) + # Check if table already exists before attempting to create + # This is required for environments where IAM roles don't have + # dynamodb:CreateTable permissions (e.g., Terraform-managed tables) + table_exists = False try: - dynamodb_resource.create_table( - TableName=table_name, - KeySchema=[{"AttributeName": "entity_id", "KeyType": "HASH"}], - AttributeDefinitions=[ - {"AttributeName": "entity_id", "AttributeType": "S"} - ], - BillingMode="PAY_PER_REQUEST", - **kwargs, + dynamodb_client.describe_table(TableName=table_name) + table_exists = True + do_tag_updates[table_name] = True + logger.info( + f"DynamoDB table {table_name} already exists, skipping creation" ) - except ClientError as ce: - do_tag_updates[table_name] = True - - # If the table creation fails with ResourceInUseException, - # it means the table already exists or is being created. - # Otherwise, re-raise the exception - if ce.response["Error"]["Code"] != "ResourceInUseException": + if ce.response["Error"]["Code"] != "ResourceNotFoundException": + # If it's not a "table not found" error, re-raise raise + # Only attempt to create table if it doesn't exist + if not table_exists: + try: + dynamodb_resource.create_table( + TableName=table_name, + KeySchema=[{"AttributeName": "entity_id", "KeyType": "HASH"}], + AttributeDefinitions=[ + {"AttributeName": "entity_id", "AttributeType": "S"} + ], + BillingMode="PAY_PER_REQUEST", + **kwargs, + ) + logger.info(f"Created DynamoDB table {table_name}") + + except ClientError as ce: + do_tag_updates[table_name] = True + + # If the table creation fails with ResourceInUseException, + # it means the table already exists or is being created. + # Otherwise, re-raise the exception + if ce.response["Error"]["Code"] != "ResourceInUseException": + raise + for table_instance in tables_to_keep: table_name = _get_table_name(online_config, config, table_instance) dynamodb_client.get_waiter("table_exists").wait(TableName=table_name) @@ -712,13 +731,22 @@ def _delete_table_idempotent( table.delete() logger.info(f"Dynamo table {table_name} was deleted") except ClientError as ce: + error_code = ce.response["Error"]["Code"] + # If the table deletion fails with ResourceNotFoundException, # it means the table has already been deleted. - # Otherwise, re-raise the exception - if ce.response["Error"]["Code"] != "ResourceNotFoundException": - raise - else: + if error_code == "ResourceNotFoundException": logger.warning(f"Trying to delete table that doesn't exist: {table_name}") + # If it fails with AccessDeniedException, the IAM role doesn't have + # dynamodb:DeleteTable permission (e.g., Terraform-managed tables) + elif error_code == "AccessDeniedException": + logger.warning( + f"Unable to delete table {table_name} due to insufficient permissions. " + f"The table may need to be deleted manually or via your infrastructure management tool (e.g., Terraform)." + ) + else: + # Some other error, re-raise + raise def _to_resource_write_item(config, entity_key, features, timestamp):