GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — release v0.10.4; fix attributes for SQS queues addressed via URL (#1626) · nodeplusplus/localstack@6e9d3cf

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • Dockerfile
  • README.md
  • localstack
    • config.py
    • constants.py
    • ext/java/src/test/java/cloud/localstack
      • BasicFunctionalityTest.java
    • services
      • generic_proxy.py
      • infra.py
      • sqs
        • sqs_listener.py
    • utils
      • common.py
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎Dockerfile‎

Copy file name to clipboardExpand all lines: Dockerfile
+7-7Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ ENV MAVEN_CONFIG=/opt/code/localstack \
4747
USER=localstack \
4848
PYTHONUNBUFFERED=1
4949

50+
# clean up and prepare for squashing the image
51+
RUN apk del --purge git
52+
RUN pip uninstall -y awscli boto3 botocore localstack_client idna s3transfer
53+
RUN rm -rf /tmp/* /root/.cache /opt/yarn-v1.15.2; mkdir -p /tmp/localstack
54+
RUN ln -s /opt/code/localstack/.venv/bin/aws /usr/bin/aws
55+
ENV PYTHONPATH=/opt/code/localstack/.venv/lib/python3.6/site-packages
56+
5057
# add rest of the code
5158
ADD localstack/ localstack/
5259
ADD bin/localstack bin/localstack
@@ -66,13 +73,6 @@ RUN mkdir -p /.npm && \
6673
chown -R localstack:localstack . /tmp/localstack && \
6774
ln -s `pwd` /tmp/localstack_install_dir
6875

69-
# clean up and prepare for squashing the image
70-
RUN apk del --purge git
71-
RUN pip uninstall -y awscli boto3 botocore localstack_client idna s3transfer
72-
RUN rm -rf /tmp/* /root/.cache /opt/yarn-v1.15.2
73-
RUN ln -s /opt/code/localstack/.venv/bin/aws /usr/bin/aws
74-
ENV PYTHONPATH=/opt/code/localstack/.venv/lib/python3.6/site-packages
75-
7676
# run tests (to verify the build before pushing the image)
7777
ADD tests/ tests/
7878
RUN LAMBDA_EXECUTOR=local make test
Collapse file

‎README.md‎

Copy file name to clipboardExpand all lines: README.md
+1Lines changed: 1 addition & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,7 @@ localstack web
527527

528528
## Change Log
529529

530+
* v0.10.4: Add checks for open UDP ports; fix S3 chunked encoding uploads; fix LatestStreamLabel; fix CORS headers for SQS/SNS; set Java lambda debug port only when needed; expose default region in a util function; fix MacOS tmp folder; clear tmp supervisord logs at container startup; fix signed header requests for S3; expose Web UI via HTTPS; add Timestamp to SNS messages; fix attributes for SQS queues addressed via URL
530531
* v0.10.3: Allow specifying data types for CF attributes; add API for service status and starting services at runtime; support NextShardIterator in DDB streams; add mock responses for S3 encryption and replication; fix rendering of resources in web UI; custom SQS queue attributes; fix Lambda docker command and imports; fix SQS queue physical ID in CF; allow proxy listener to define custom backend per request; support Lambda event body over stdin; exclude `ingest-geoip` ES module to optimize image size; skip checking MD5 on S3 copy; fix DynamoDB table ARN for CF; fix CF deployment of StepFunction activities; fix uploading of Java Lambda as JAR in ZIP; fix installing libs for plugins; added `LAMBDA_JAVA_OPTS` for Java Lambda debugging; bump Maven dependency versions; refactor Lambda API; fix boolean strings in CF templates; allow overriding AWS account id with `TEST_AWS_ACCOUNT_ID`; fix incorrect region for API GW resources created via CF; fix permissions for cache files in `/tmp`
531532
* v0.10.2: Fix logging issue with async Lambdas; fix kinesis records processing; add basic support for `Ref` in CloudFormation; fix ddb streams uuid generation; upgrade travis CI setup; fix DynamoDB error messages; cache server processes
532533
* v0.10.0: Lazy loading of libraries; fix handling of regions; add API multiserver; improve CPU profiling; fix ES xpack installation; add basic EventBridge support; refactor Lambda API and executor; add MessageAttributes on SNS payloads; tagging for SNS; ability to customize docker command
Collapse file

‎localstack/config.py‎

Copy file name to clipboardExpand all lines: localstack/config.py
-6Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -194,12 +194,6 @@ def in_docker():
194194
if is_in_docker and not os.environ.get('LAMBDA_REMOTE_DOCKER', '').strip():
195195
LAMBDA_REMOTE_DOCKER = True
196196

197-
# print a warning if we're not running in Docker but using Docker based LAMBDA_EXECUTOR
198-
if not is_in_docker and 'docker' in LAMBDA_EXECUTOR and not is_linux():
199-
print(('!WARNING! - Running outside of Docker with LAMBDA_EXECUTOR=%s can lead to '
200-
'problems on your OS. The environment variable $LOCALSTACK_HOSTNAME may not '
201-
'be properly set in your Lambdas.') % LAMBDA_EXECUTOR)
202-
203197
# local config file path in home directory
204198
CONFIG_FILE_PATH = os.path.join(expanduser('~'), '.localstack')
205199

Collapse file

‎localstack/constants.py‎

Copy file name to clipboardExpand all lines: localstack/constants.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import localstack_client.config
33

44
# LocalStack version
5-
VERSION = '0.10.3'
5+
VERSION = '0.10.4'
66

77
# constant to represent the "local" region, i.e., local machine
88
REGION_LOCAL = 'local'
Collapse file

‎localstack/ext/java/src/test/java/cloud/localstack/BasicFunctionalityTest.java‎

Copy file name to clipboardExpand all lines: localstack/ext/java/src/test/java/cloud/localstack/BasicFunctionalityTest.java
+48-3Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
import cloud.localstack.sample.S3Sample;
55
import cloud.localstack.sample.SQSLambdaHandler;
66
import cloud.localstack.sample.SQSLambdaHandlerSSL;
7+
import com.amazonaws.ClientConfiguration;
8+
import com.amazonaws.Protocol;
9+
import com.amazonaws.auth.AWSStaticCredentialsProvider;
10+
import com.amazonaws.auth.BasicAWSCredentials;
11+
import com.amazonaws.client.builder.AwsClientBuilder;
712
import com.amazonaws.services.kinesis.AmazonKinesis;
813
import com.amazonaws.services.kinesis.model.ListStreamsResult;
914
import com.amazonaws.services.kinesis.model.PutRecordRequest;
@@ -16,6 +21,8 @@
1621
import com.amazonaws.services.s3.model.Bucket;
1722
import com.amazonaws.services.s3.model.ObjectListing;
1823
import com.amazonaws.services.sqs.AmazonSQS;
24+
import com.amazonaws.services.sqs.AmazonSQSAsync;
25+
import com.amazonaws.services.sqs.AmazonSQSAsyncClientBuilder;
1926
import com.amazonaws.services.sqs.model.CreateQueueRequest;
2027
import com.amazonaws.services.sqs.model.CreateQueueResult;
2128
import com.amazonaws.services.sqs.model.DeleteQueueRequest;
@@ -28,16 +35,15 @@
2835
import com.amazonaws.services.sqs.model.SendMessageRequest;
2936
import com.amazonaws.services.sqs.model.SendMessageResult;
3037
import org.assertj.core.api.Assertions;
38+
import org.junit.Assert;
3139
import org.junit.jupiter.api.extension.ExtendWith;
3240
import org.junit.runner.RunWith;
3341

3442
import java.io.File;
3543
import java.io.FileOutputStream;
3644
import java.nio.ByteBuffer;
3745
import java.nio.file.Files;
38-
import java.util.Arrays;
39-
import java.util.List;
40-
import java.util.UUID;
46+
import java.util.*;
4147
import java.util.zip.ZipEntry;
4248
import java.util.zip.ZipOutputStream;
4349

@@ -181,6 +187,45 @@ public void testSQSLambdaIntegration() throws Exception {
181187
Assertions.assertThat(message).isEqualTo(SQSLambdaHandler.DID_YOU_GET_THE_MESSAGE);
182188
}
183189

190+
@org.junit.Test
191+
@org.junit.jupiter.api.Test
192+
public void testSQSQueueAttributes() {
193+
// Based on https://github.com/localstack/localstack/issues/1551
194+
195+
AwsClientBuilder.EndpointConfiguration endpoint = TestUtils.getEndpointConfigurationSQS();
196+
197+
ClientConfiguration cc = new ClientConfiguration();
198+
cc.setProtocol(Protocol.HTTP);
199+
200+
AmazonSQSAsync sqsAsync = AmazonSQSAsyncClientBuilder.standard()
201+
.withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials("foo", "foo")))
202+
.withEndpointConfiguration(endpoint)
203+
.withClientConfiguration(cc)
204+
.build();
205+
206+
CreateQueueResult result1 = sqsAsync.createQueue("1551-test");
207+
CreateQueueResult result2 = sqsAsync.createQueue("1551-test-dlq");
208+
209+
final String queueArn = "QueueArn";
210+
GetQueueAttributesResult dlqQueueAttributes = sqsAsync.getQueueAttributes(result2.getQueueUrl(),
211+
Collections.singletonList(queueArn));
212+
dlqQueueAttributes.getAttributes().get(queueArn);
213+
214+
// set queue attributes
215+
final Map<String, String> attributes = new HashMap<>();
216+
attributes.put("VisibilityTimeout", "60");
217+
attributes.put("MessageRetentionPeriod", "345600");
218+
attributes.put("RedrivePolicy", "{\"foo\":1}");
219+
final String queueUrl = result1.getQueueUrl();
220+
sqsAsync.setQueueAttributes(queueUrl, attributes);
221+
222+
// get and assert queue attributes
223+
Map<String, String> result = sqsAsync.getQueueAttributes(queueUrl, Arrays.asList("All")).getAttributes();
224+
Assert.assertEquals(result.get("MessageRetentionPeriod"), "345600");
225+
Assert.assertEquals(result.get("VisibilityTimeout"), "60");
226+
Assert.assertEquals(result.get("RedrivePolicy"), "{\"foo\":1}");
227+
}
228+
184229
@org.junit.Test
185230
@org.junit.jupiter.api.Test
186231
public void testLocalS3API() throws Exception {
Collapse file

‎localstack/services/generic_proxy.py‎

Copy file name to clipboardExpand all lines: localstack/services/generic_proxy.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ def _send_cors_headers(self, response=None):
345345
if 'Access-Control-Allow-Headers' not in headers:
346346
requested_headers = self.headers.get('Access-Control-Request-Headers', '')
347347
requested_headers = re.split(r'[,\s]+', requested_headers) + CORS_ALLOWED_HEADERS
348-
self.send_header('Access-Control-Allow-Headers', ','.join(requested_headers))
348+
self.send_header('Access-Control-Allow-Headers', ','.join([h for h in requested_headers if h]))
349349
if 'Access-Control-Expose-Headers' not in headers:
350350
self.send_header('Access-Control-Expose-Headers', ','.join(CORS_EXPOSE_HEADERS))
351351

Collapse file

‎localstack/services/infra.py‎

Copy file name to clipboardExpand all lines: localstack/services/infra.py
+9-2Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
DEFAULT_PORT_APIGATEWAY_BACKEND, DEFAULT_PORT_SNS_BACKEND,
1616
DEFAULT_PORT_IAM_BACKEND, DEFAULT_PORT_EC2_BACKEND, DEFAULT_SERVICE_PORTS)
1717
from localstack.utils import common, persistence
18-
from localstack.utils.common import (TMP_THREADS, run, get_free_tcp_port,
18+
from localstack.utils.common import (TMP_THREADS, run, get_free_tcp_port, is_linux,
1919
FuncThread, ShellCommandThread, get_service_protocol, in_docker, is_port_open)
2020
from localstack.utils.server import multiserver
2121
from localstack.utils.bootstrap import (
@@ -400,11 +400,18 @@ def check_infra(retries=10, expect_shutdown=False, apis=None, additional_checks=
400400

401401
def start_infra(asynchronous=False, apis=None):
402402
try:
403+
is_in_docker = in_docker()
404+
# print a warning if we're not running in Docker but using Docker based LAMBDA_EXECUTOR
405+
if not is_in_docker and 'docker' in config.LAMBDA_EXECUTOR and not is_linux():
406+
print(('!WARNING! - Running outside of Docker with LAMBDA_EXECUTOR=%s can lead to '
407+
'problems on your OS. The environment variable $LOCALSTACK_HOSTNAME may not '
408+
'be properly set in your Lambdas.') % config.LAMBDA_EXECUTOR)
409+
403410
# load plugins
404411
load_plugins()
405412

406413
event_publisher.fire_event(event_publisher.EVENT_START_INFRA,
407-
{'d': in_docker() and 1 or 0, 'c': in_ci() and 1 or 0})
414+
{'d': is_in_docker and 1 or 0, 'c': in_ci() and 1 or 0})
408415

409416
# set up logging
410417
setup_logging()
Collapse file

‎localstack/services/sqs/sqs_listener.py‎

Copy file name to clipboardExpand all lines: localstack/services/sqs/sqs_listener.py
+79-67Lines changed: 79 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def forward_request(self, method, path, data, headers):
5353
if new_response:
5454
return new_response
5555
elif action == 'SetQueueAttributes':
56-
self._set_queue_attributes(req_data)
56+
self._set_queue_attributes(path, req_data, headers)
5757

5858
if 'QueueName' in req_data:
5959
encoded_data = urlencode(req_data, doseq=True) if method == 'POST' else ''
@@ -80,67 +80,69 @@ def return_response(self, method, path, data, headers, response, request_handler
8080
# Allow CORS preflight requests to succeed.
8181
return 200
8282

83-
if method == 'POST' and path == '/':
84-
region_name = extract_region_from_auth_header(headers)
85-
req_data = urlparse.parse_qs(to_str(data))
86-
action = req_data.get('Action', [None])[0]
87-
content_str = content_str_original = to_str(response.content)
88-
89-
self._fire_event(req_data, response)
90-
91-
# patch the response and add missing attributes
92-
if action == 'GetQueueAttributes':
93-
content_str = self._add_queue_attributes(req_data, content_str)
94-
95-
# patch the response and return the correct endpoint URLs / ARNs
96-
if action in ('CreateQueue', 'GetQueueUrl', 'ListQueues', 'GetQueueAttributes'):
97-
if config.USE_SSL and '<QueueUrl>http://' in content_str:
98-
# return https://... if we're supposed to use SSL
99-
content_str = re.sub(r'<QueueUrl>\s*http://', r'<QueueUrl>https://', content_str)
100-
# expose external hostname:port
101-
external_port = SQS_PORT_EXTERNAL or get_external_port(headers, request_handler)
102-
content_str = re.sub(r'<QueueUrl>\s*([a-z]+)://[^<]*:([0-9]+)/([^<]*)\s*</QueueUrl>',
103-
r'<QueueUrl>\1://%s:%s/\3</QueueUrl>' % (HOSTNAME_EXTERNAL, external_port), content_str)
104-
# fix queue ARN
105-
content_str = re.sub(r'<([a-zA-Z0-9]+)>\s*arn:aws:sqs:elasticmq:([^<]+)</([a-zA-Z0-9]+)>',
106-
r'<\1>arn:aws:sqs:%s:\2</\3>' % (region_name), content_str)
107-
108-
if content_str_original != content_str:
109-
# if changes have been made, return patched response
110-
new_response = Response()
111-
new_response.status_code = response.status_code
112-
new_response.headers = response.headers
113-
new_response._content = content_str
114-
new_response.headers['content-length'] = len(new_response._content)
115-
return new_response
116-
117-
# Since the following 2 API calls are not implemented in ElasticMQ, we're mocking them
118-
# and letting them to return an empty response
119-
if action == 'TagQueue':
120-
new_response = Response()
121-
new_response.status_code = 200
122-
new_response._content = ("""
123-
<?xml version="1.0"?>
124-
<TagQueueResponse>
125-
<ResponseMetadata>
126-
<RequestId>{}</RequestId>
127-
</ResponseMetadata>
128-
</TagQueueResponse>
129-
""").strip().format(uuid.uuid4())
130-
return new_response
131-
elif action == 'ListQueueTags':
132-
new_response = Response()
133-
new_response.status_code = 200
134-
new_response._content = ("""
135-
<?xml version="1.0"?>
136-
<ListQueueTagsResponse xmlns="{}">
137-
<ListQueueTagsResult/>
138-
<ResponseMetadata>
139-
<RequestId>{}</RequestId>
140-
</ResponseMetadata>
141-
</ListQueueTagsResponse>
142-
""").strip().format(XMLNS_SQS, uuid.uuid4())
143-
return new_response
83+
if method != 'POST':
84+
return
85+
86+
region_name = extract_region_from_auth_header(headers)
87+
req_data = urlparse.parse_qs(to_str(data))
88+
action = req_data.get('Action', [None])[0]
89+
content_str = content_str_original = to_str(response.content)
90+
91+
self._fire_event(req_data, response)
92+
93+
# patch the response and add missing attributes
94+
if action == 'GetQueueAttributes':
95+
content_str = self._add_queue_attributes(path, req_data, content_str, headers)
96+
97+
# patch the response and return the correct endpoint URLs / ARNs
98+
if action in ('CreateQueue', 'GetQueueUrl', 'ListQueues', 'GetQueueAttributes'):
99+
if config.USE_SSL and '<QueueUrl>http://' in content_str:
100+
# return https://... if we're supposed to use SSL
101+
content_str = re.sub(r'<QueueUrl>\s*http://', r'<QueueUrl>https://', content_str)
102+
# expose external hostname:port
103+
external_port = SQS_PORT_EXTERNAL or get_external_port(headers, request_handler)
104+
content_str = re.sub(r'<QueueUrl>\s*([a-z]+)://[^<]*:([0-9]+)/([^<]*)\s*</QueueUrl>',
105+
r'<QueueUrl>\1://%s:%s/\3</QueueUrl>' % (HOSTNAME_EXTERNAL, external_port), content_str)
106+
# fix queue ARN
107+
content_str = re.sub(r'<([a-zA-Z0-9]+)>\s*arn:aws:sqs:elasticmq:([^<]+)</([a-zA-Z0-9]+)>',
108+
r'<\1>arn:aws:sqs:%s:\2</\3>' % (region_name), content_str)
109+
110+
if content_str_original != content_str:
111+
# if changes have been made, return patched response
112+
new_response = Response()
113+
new_response.status_code = response.status_code
114+
new_response.headers = response.headers
115+
new_response._content = content_str
116+
new_response.headers['content-length'] = len(new_response._content)
117+
return new_response
118+
119+
# Since the following 2 API calls are not implemented in ElasticMQ, we're mocking them
120+
# and letting them to return an empty response
121+
if action == 'TagQueue':
122+
new_response = Response()
123+
new_response.status_code = 200
124+
new_response._content = ("""
125+
<?xml version="1.0"?>
126+
<TagQueueResponse>
127+
<ResponseMetadata>
128+
<RequestId>{}</RequestId>
129+
</ResponseMetadata>
130+
</TagQueueResponse>
131+
""").strip().format(uuid.uuid4())
132+
return new_response
133+
elif action == 'ListQueueTags':
134+
new_response = Response()
135+
new_response.status_code = 200
136+
new_response._content = ("""
137+
<?xml version="1.0"?>
138+
<ListQueueTagsResponse xmlns="{}">
139+
<ListQueueTagsResult/>
140+
<ResponseMetadata>
141+
<RequestId>{}</RequestId>
142+
</ResponseMetadata>
143+
</ListQueueTagsResponse>
144+
""").strip().format(XMLNS_SQS, uuid.uuid4())
145+
return new_response
144146

145147
# Format of the message Name attribute is MessageAttribute.<int id>.<field>
146148
# Format of the Value attributes is MessageAttribute.<int id>.Value.DataType
@@ -225,7 +227,7 @@ def _format_attributes(self, req_data):
225227
return result
226228

227229
def _send_message(self, path, data, req_data, headers):
228-
queue_url = req_data.get('QueueUrl', [path.partition('?')[0]])[0]
230+
queue_url = self._queue_url(path, req_data, headers)
229231
queue_name = queue_url[queue_url.rindex('/') + 1:]
230232
message_body = req_data.get('MessageBody', [None])[0]
231233
message_attributes = self.format_message_attributes(req_data)
@@ -244,17 +246,17 @@ def _send_message(self, path, data, req_data, headers):
244246
new_response.status_code = 200
245247
return new_response
246248

247-
def _set_queue_attributes(self, req_data):
248-
queue_url = req_data['QueueUrl'][0]
249+
def _set_queue_attributes(self, path, req_data, headers):
250+
queue_url = self._queue_url(path, req_data, headers)
249251
attrs = self._format_attributes(req_data)
250252
# select only the attributes in UNSUPPORTED_ATTRIBUTE_NAMES
251253
attrs = dict([(k, v) for k, v in attrs.items() if k in UNSUPPORTED_ATTRIBUTE_NAMES])
252254
QUEUE_ATTRIBUTES[queue_url] = QUEUE_ATTRIBUTES.get(queue_url) or {}
253255
QUEUE_ATTRIBUTES[queue_url].update(attrs)
254256

255-
def _add_queue_attributes(self, req_data, content_str):
257+
def _add_queue_attributes(self, path, req_data, content_str, headers):
256258
flags = re.MULTILINE | re.DOTALL
257-
queue_url = req_data['QueueUrl'][0]
259+
queue_url = self._queue_url(path, req_data, headers)
258260
regex = r'(.*<GetQueueAttributesResult>)(.*)(</GetQueueAttributesResult>.*)'
259261
attrs = re.sub(regex, r'\2', content_str, flags=flags)
260262
for key, value in QUEUE_ATTRIBUTES.get(queue_url, {}).items():
@@ -280,6 +282,16 @@ def _fire_event(self, req_data, response):
280282
if event_type and queue_url:
281283
event_publisher.fire_event(event_type, payload={'u': event_publisher.get_hash(queue_url)})
282284

285+
def _queue_url(self, path, req_data, headers):
286+
queue_url = req_data.get('QueueUrl')
287+
if queue_url:
288+
return queue_url[0]
289+
url = config.TEST_SQS_URL
290+
if headers.get('Host'):
291+
url = 'http%s://%s' % ('s' if config.USE_SSL else '', headers['Host'])
292+
queue_url = '%s%s' % (url, path.partition('?')[0])
293+
return queue_url
294+
283295

284296
# extract the external port used by the client to make the request
285297
def get_external_port(headers, request_handler):

0 commit comments

Comments
0 (0)
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.