Skip to content

Commit b4576b4

Browse files
proxy: fix dynamodb read-only operations (#102)
1 parent f4515a1 commit b4576b4

4 files changed

Lines changed: 89 additions & 1 deletion

File tree

aws-replicator/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ If you wish to access the deprecated instructions, they can be found [here](http
126126

127127
## Change Log
128128

129+
* `0.1.25`: Fix dynamodb proxying for read-only mode.
129130
* `0.1.24`: Fix healthcheck probe for proxy container
130131
* `0.1.23`: Fix unpinned React.js dependencies preventing webui from loading
131132
* `0.1.22`: Fix auth-related imports that prevent the AWS proxy from starting

aws-replicator/aws_replicator/server/aws_request_forwarder.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,13 @@ def _is_read_request(self, context: RequestContext) -> bool:
188188
# service-specific rules
189189
if context.service.service_name == "cognito-idp" and operation_name == "InitiateAuth":
190190
return True
191+
if context.service.service_name == "dynamodb" and operation_name in {
192+
"Scan",
193+
"Query",
194+
"BatchGetItem",
195+
"PartiQLSelect",
196+
}:
197+
return True
191198
# TODO: add more rules
192199
return False
193200

aws-replicator/setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = localstack-extension-aws-replicator
3-
version = 0.1.24
3+
version = 0.1.25
44
summary = LocalStack AWS Proxy Extension
55
description = Proxy AWS resources into your LocalStack instance
66
long_description = file: README.md

aws-replicator/tests/test_proxy_requests.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
from botocore.exceptions import ClientError
1111
from localstack.aws.connect import connect_to
1212
from localstack.utils.aws.arns import sqs_queue_arn, sqs_queue_url_for_arn
13+
from localstack.utils.aws.resources import create_dynamodb_table
1314
from localstack.utils.net import wait_for_port_open
15+
from localstack.utils.strings import short_uid
1416
from localstack.utils.sync import retry
1517

1618
from aws_replicator.client.auth_proxy import start_aws_auth_proxy
@@ -243,3 +245,81 @@ def test_sqs_requests(start_aws_proxy, cleanups):
243245
"Attributes"
244246
]["QueueArn"]
245247
assert result == queue_arn_aws
248+
249+
250+
class TestDynamoDBRequests:
251+
region_name = "us-east-1"
252+
253+
@pytest.fixture(scope="class")
254+
def dynamodb_client_aws(self):
255+
return boto3.client("dynamodb", region_name=self.region_name)
256+
257+
@pytest.fixture
258+
def create_dynamodb_table_aws(self, dynamodb_client_aws):
259+
tables = []
260+
261+
def factory(**kwargs):
262+
kwargs["client"] = dynamodb_client_aws
263+
if "table_name" not in kwargs:
264+
kwargs["table_name"] = f"test-table-{short_uid()}"
265+
if "partition_key" not in kwargs:
266+
kwargs["partition_key"] = "id"
267+
268+
tables.append(kwargs["table_name"])
269+
270+
return create_dynamodb_table(**kwargs)
271+
272+
yield factory
273+
274+
# cleanup
275+
for table in tables:
276+
try:
277+
dynamodb_client_aws.delete_table(TableName=table)
278+
except Exception as e:
279+
print(f"error cleaning up table {table}: {e}", table, e)
280+
281+
def test_dynamodb_requests_read_only(
282+
self, start_aws_proxy, create_dynamodb_table_aws, dynamodb_client_aws
283+
):
284+
# create clients
285+
dynamodb_client = connect_to(region_name=self.region_name).dynamodb
286+
287+
# start proxy - only forwarding requests for read operations
288+
config = ProxyConfig(
289+
services={"dynamodb": {"resources": ".*", "read_only": True}},
290+
bind_host=PROXY_BIND_HOST,
291+
)
292+
start_aws_proxy(config)
293+
294+
# create table in AWS
295+
table_name = f"test-table-{short_uid()}"
296+
create_dynamodb_table_aws(table_name=table_name)
297+
tables_aws = dynamodb_client_aws.list_tables()["TableNames"]
298+
assert table_name in tables_aws
299+
300+
# assert that local call for this table is proxied
301+
tables_local = dynamodb_client.list_tables()["TableNames"]
302+
assert table_name in tables_local
303+
304+
item = {"id": {"S": "123"}, "value": {"S": "foobar"}}
305+
# put item via AWS client
306+
dynamodb_client_aws.put_item(TableName=table_name, Item=item)
307+
308+
# get item via AWS client
309+
result = dynamodb_client_aws.get_item(TableName=table_name, Key={"id": {"S": "123"}})
310+
assert result["Item"] == item
311+
312+
# get item via local client
313+
result = dynamodb_client.get_item(TableName=table_name, Key={"id": {"S": "123"}})
314+
assert result["Item"] == item
315+
316+
# assert that scan operation is working
317+
result = dynamodb_client.scan(TableName=table_name)
318+
assert len(result["Items"]) == 1
319+
320+
# assert that write operation is NOT working - it's sent to localstack, which cannot find the table
321+
item3 = {"id": {"S": "789"}, "value": {"S": "foobar3"}}
322+
with pytest.raises(ClientError) as exc:
323+
dynamodb_client.put_item(TableName=table_name, Item=item3)
324+
325+
assert exc.match("ResourceNotFoundException")

0 commit comments

Comments
 (0)