1- import json
21import os
3- from typing import Dict
2+ from typing import Dict , Optional , Tuple
43from urllib .parse import urlparse
54
65# note: leave this import here for now, as some upstream code is depending on it (TODO needs to be updated)
76from localstack_client .patch import patch_expand_host_prefix # noqa
87
98# central entrypoint port for all LocalStack API endpoints
10- EDGE_PORT = int ( os . environ . get ( "EDGE_PORT" ) or 4566 )
9+ DEFAULT_EDGE_PORT = 4566
1110
12- # NOTE: The endpoints below will soon become deprecated/removed, as the default in the
11+ # NOTE: The ports listed below will soon become deprecated/removed, as the default in the
1312# latest version is to access all services via a single "edge service" (port 4566 by default)
14- _service_endpoints_template = {
15- "edge" : "{proto}://{host}: 4566" ,
16- "apigateway" : "{proto}://{host}: 4567" ,
17- "apigatewayv2" : "{proto}://{host}: 4567" ,
18- "kinesis" : "{proto}://{host}: 4568" ,
19- "dynamodb" : "{proto}://{host}: 4569" ,
20- "dynamodbstreams" : "{proto}://{host}: 4570" ,
21- "elasticsearch" : "{proto}://{host}: 4571" ,
22- "s3" : "{proto}://{host}: 4572" ,
23- "firehose" : "{proto}://{host}: 4573" ,
24- "lambda" : "{proto}://{host}: 4574" ,
25- "sns" : "{proto}://{host}: 4575" ,
26- "sqs" : "{proto}://{host}: 4576" ,
27- "redshift" : "{proto}://{host}: 4577" ,
28- "redshift-data" : "{proto}://{host}: 4577" ,
29- "es" : "{proto}://{host}: 4578" ,
30- "opensearch" : "{proto}://{host}: 4578" ,
31- "ses" : "{proto}://{host}: 4579" ,
32- "sesv2" : "{proto}://{host}: 4579" ,
33- "route53" : "{proto}://{host}: 4580" ,
34- "route53resolver" : "{proto}://{host}: 4580" ,
35- "cloudformation" : "{proto}://{host}: 4581" ,
36- "cloudwatch" : "{proto}://{host}: 4582" ,
37- "ssm" : "{proto}://{host}: 4583" ,
38- "secretsmanager" : "{proto}://{host}: 4584" ,
39- "stepfunctions" : "{proto}://{host}: 4585" ,
40- "logs" : "{proto}://{host}: 4586" ,
41- "events" : "{proto}://{host}: 4587" ,
42- "elb" : "{proto}://{host}: 4588" ,
43- "iot" : "{proto}://{host}: 4589" ,
44- "iotanalytics" : "{proto}://{host}: 4589" ,
45- "iotevents" : "{proto}://{host}: 4589" ,
46- "iotevents-data" : "{proto}://{host}: 4589" ,
47- "iotwireless" : "{proto}://{host}: 4589" ,
48- "iot-data" : "{proto}://{host}: 4589" ,
49- "iot-jobs-data" : "{proto}://{host}: 4589" ,
50- "cognito-idp" : "{proto}://{host}: 4590" ,
51- "cognito-identity" : "{proto}://{host}: 4591" ,
52- "sts" : "{proto}://{host}: 4592" ,
53- "iam" : "{proto}://{host}: 4593" ,
54- "rds" : "{proto}://{host}: 4594" ,
55- "rds-data" : "{proto}://{host}: 4594" ,
56- "cloudsearch" : "{proto}://{host}: 4595" ,
57- "swf" : "{proto}://{host}: 4596" ,
58- "ec2" : "{proto}://{host}: 4597" ,
59- "elasticache" : "{proto}://{host}: 4598" ,
60- "kms" : "{proto}://{host}: 4599" ,
61- "emr" : "{proto}://{host}: 4600" ,
62- "ecs" : "{proto}://{host}: 4601" ,
63- "eks" : "{proto}://{host}: 4602" ,
64- "xray" : "{proto}://{host}: 4603" ,
65- "elasticbeanstalk" : "{proto}://{host}: 4604" ,
66- "appsync" : "{proto}://{host}: 4605" ,
67- "cloudfront" : "{proto}://{host}: 4606" ,
68- "athena" : "{proto}://{host}: 4607" ,
69- "glue" : "{proto}://{host}: 4608" ,
70- "sagemaker" : "{proto}://{host}: 4609" ,
71- "sagemaker-runtime" : "{proto}://{host}: 4609" ,
72- "ecr" : "{proto}://{host}: 4610" ,
73- "qldb" : "{proto}://{host}: 4611" ,
74- "qldb-session" : "{proto}://{host}: 4611" ,
75- "cloudtrail" : "{proto}://{host}: 4612" ,
76- "glacier" : "{proto}://{host}: 4613" ,
77- "batch" : "{proto}://{host}: 4614" ,
78- "organizations" : "{proto}://{host}: 4615" ,
79- "autoscaling" : "{proto}://{host}: 4616" ,
80- "mediastore" : "{proto}://{host}: 4617" ,
81- "mediastore-data" : "{proto}://{host}: 4617" ,
82- "transfer" : "{proto}://{host}: 4618" ,
83- "acm" : "{proto}://{host}: 4619" ,
84- "codecommit" : "{proto}://{host}: 4620" ,
85- "kinesisanalytics" : "{proto}://{host}: 4621" ,
86- "kinesisanalyticsv2" : "{proto}://{host}: 4621" ,
87- "amplify" : "{proto}://{host}: 4622" ,
88- "application-autoscaling" : "{proto}://{host}: 4623" ,
89- "kafka" : "{proto}://{host}: 4624" ,
90- "apigatewaymanagementapi" : "{proto}://{host}: 4625" ,
91- "timestream" : "{proto}://{host}: 4626" ,
92- "timestream-query" : "{proto}://{host}: 4626" ,
93- "timestream-write" : "{proto}://{host}: 4626" ,
94- "s3control" : "{proto}://{host}: 4627" ,
95- "elbv2" : "{proto}://{host}: 4628" ,
96- "support" : "{proto}://{host}: 4629" ,
97- "neptune" : "{proto}://{host}: 4594" ,
98- "docdb" : "{proto}://{host}: 4594" ,
99- "servicediscovery" : "{proto}://{host}: 4630" ,
100- "serverlessrepo" : "{proto}://{host}: 4631" ,
101- "appconfig" : "{proto}://{host}: 4632" ,
102- "ce" : "{proto}://{host}: 4633" ,
103- "mediaconvert" : "{proto}://{host}: 4634" ,
104- "resourcegroupstaggingapi" : "{proto}://{host}: 4635" ,
105- "resource-groups" : "{proto}://{host}: 4636" ,
106- "efs" : "{proto}://{host}: 4637" ,
107- "backup" : "{proto}://{host}: 4638" ,
108- "lakeformation" : "{proto}://{host}: 4639" ,
109- "waf" : "{proto}://{host}: 4640" ,
110- "wafv2" : "{proto}://{host}: 4640" ,
111- "config" : "{proto}://{host}: 4641" ,
112- "configservice" : "{proto}://{host}: 4641" ,
113- "mwaa" : "{proto}://{host}: 4642" ,
114- "fis" : "{proto}://{host}: 4643" ,
115- "meteringmarketplace" : "{proto}://{host}: 4644" ,
116- "transcribe" : "{proto}://{host}: 4566" ,
117- "mq" : "{proto}://{host}: 4566"
13+ _service_ports : Dict [ str , int ] = {
14+ "edge" : 4566 ,
15+ "apigateway" : 4567 ,
16+ "apigatewayv2" : 4567 ,
17+ "kinesis" : 4568 ,
18+ "dynamodb" : 4569 ,
19+ "dynamodbstreams" : 4570 ,
20+ "elasticsearch" : 4571 ,
21+ "s3" : 4572 ,
22+ "firehose" : 4573 ,
23+ "lambda" : 4574 ,
24+ "sns" : 4575 ,
25+ "sqs" : 4576 ,
26+ "redshift" : 4577 ,
27+ "redshift-data" : 4577 ,
28+ "es" : 4578 ,
29+ "opensearch" : 4578 ,
30+ "ses" : 4579 ,
31+ "sesv2" : 4579 ,
32+ "route53" : 4580 ,
33+ "route53resolver" : 4580 ,
34+ "cloudformation" : 4581 ,
35+ "cloudwatch" : 4582 ,
36+ "ssm" : 4583 ,
37+ "secretsmanager" : 4584 ,
38+ "stepfunctions" : 4585 ,
39+ "logs" : 4586 ,
40+ "events" : 4587 ,
41+ "elb" : 4588 ,
42+ "iot" : 4589 ,
43+ "iotanalytics" : 4589 ,
44+ "iotevents" : 4589 ,
45+ "iotevents-data" : 4589 ,
46+ "iotwireless" : 4589 ,
47+ "iot-data" : 4589 ,
48+ "iot-jobs-data" : 4589 ,
49+ "cognito-idp" : 4590 ,
50+ "cognito-identity" : 4591 ,
51+ "sts" : 4592 ,
52+ "iam" : 4593 ,
53+ "rds" : 4594 ,
54+ "rds-data" : 4594 ,
55+ "cloudsearch" : 4595 ,
56+ "swf" : 4596 ,
57+ "ec2" : 4597 ,
58+ "elasticache" : 4598 ,
59+ "kms" : 4599 ,
60+ "emr" : 4600 ,
61+ "ecs" : 4601 ,
62+ "eks" : 4602 ,
63+ "xray" : 4603 ,
64+ "elasticbeanstalk" : 4604 ,
65+ "appsync" : 4605 ,
66+ "cloudfront" : 4606 ,
67+ "athena" : 4607 ,
68+ "glue" : 4608 ,
69+ "sagemaker" : 4609 ,
70+ "sagemaker-runtime" : 4609 ,
71+ "ecr" : 4610 ,
72+ "qldb" : 4611 ,
73+ "qldb-session" : 4611 ,
74+ "cloudtrail" : 4612 ,
75+ "glacier" : 4613 ,
76+ "batch" : 4614 ,
77+ "organizations" : 4615 ,
78+ "autoscaling" : 4616 ,
79+ "mediastore" : 4617 ,
80+ "mediastore-data" : 4617 ,
81+ "transfer" : 4618 ,
82+ "acm" : 4619 ,
83+ "codecommit" : 4620 ,
84+ "kinesisanalytics" : 4621 ,
85+ "kinesisanalyticsv2" : 4621 ,
86+ "amplify" : 4622 ,
87+ "application-autoscaling" : 4623 ,
88+ "kafka" : 4624 ,
89+ "apigatewaymanagementapi" : 4625 ,
90+ "timestream" : 4626 ,
91+ "timestream-query" : 4626 ,
92+ "timestream-write" : 4626 ,
93+ "s3control" : 4627 ,
94+ "elbv2" : 4628 ,
95+ "support" : 4629 ,
96+ "neptune" : 4594 ,
97+ "docdb" : 4594 ,
98+ "servicediscovery" : 4630 ,
99+ "serverlessrepo" : 4631 ,
100+ "appconfig" : 4632 ,
101+ "ce" : 4633 ,
102+ "mediaconvert" : 4634 ,
103+ "resourcegroupstaggingapi" : 4635 ,
104+ "resource-groups" : 4636 ,
105+ "efs" : 4637 ,
106+ "backup" : 4638 ,
107+ "lakeformation" : 4639 ,
108+ "waf" : 4640 ,
109+ "wafv2" : 4640 ,
110+ "config" : 4641 ,
111+ "configservice" : 4641 ,
112+ "mwaa" : 4642 ,
113+ "fis" : 4643 ,
114+ "meteringmarketplace" : 4644 ,
115+ "transcribe" : 4566 ,
116+ "mq" : 4566 ,
118117}
119118
120119# TODO remove service port mapping above entirely
121120if os .environ .get ("USE_LEGACY_PORTS" ) not in ["1" , "true" ]:
122- for key , value in _service_endpoints_template .items ():
121+ for key , value in _service_ports .items ():
123122 if key not in ["dashboard" , "elasticsearch" ]:
124- _service_endpoints_template [key ] = f" { value . rpartition ( ':' )[ 0 ] } : { EDGE_PORT } "
123+ _service_ports [key ] = DEFAULT_EDGE_PORT
125124
126125
127- def get_service_endpoint (service : str , localstack_host : str = None ) -> str :
126+ def get_service_endpoint (
127+ service : str , localstack_host : Optional [str ] = None
128+ ) -> Optional [str ]:
128129 """
129130 Return the local endpoint URL for the given boto3 service (e.g., "s3").
130131 If $AWS_ENDPOINT_URL is configured in the environment, it is returned directly.
@@ -137,19 +138,37 @@ def get_service_endpoint(service: str, localstack_host: str = None) -> str:
137138 return endpoints .get (service )
138139
139140
140- def get_service_endpoints (localstack_host : str = None ) -> Dict [str , str ]:
141+ def parse_localstack_host (given : str ) -> Tuple [str , int ]:
142+ parts = given .split (":" , 1 )
143+ if len (parts ) == 1 :
144+ # just hostname
145+ return parts [0 ].strip () or "localhost" , DEFAULT_EDGE_PORT
146+ elif len (parts ) == 2 :
147+ hostname = parts [0 ].strip () or "localhost"
148+ port_s = parts [1 ]
149+ try :
150+ port = int (port_s )
151+ return (hostname , port )
152+ except Exception :
153+ raise RuntimeError (f"could not parse { given } into <hostname>:<port>" )
154+ else :
155+ raise RuntimeError (f"could not parse { given } into <hostname>:<port>" )
156+
157+
158+ def get_service_endpoints (localstack_host : Optional [str ] = None ) -> Dict [str , str ]:
141159 if localstack_host is None :
142- localstack_host = os .environ .get ("LOCALSTACK_HOST" , "localhost" )
160+ localstack_host = os .environ .get (
161+ "LOCALSTACK_HOST" , f"localhost:{ DEFAULT_EDGE_PORT } "
162+ )
163+
164+ hostname , port = parse_localstack_host (localstack_host )
165+
143166 protocol = "https" if os .environ .get ("USE_SSL" ) in ("1" , "true" ) else "http"
144167
145- return json .loads (
146- json .dumps (_service_endpoints_template )
147- .replace ("{proto}" , protocol )
148- .replace ("{host}" , localstack_host )
149- )
168+ return {key : f"{ protocol } ://{ hostname } :{ port } " for key in _service_ports .keys ()}
150169
151170
152- def get_service_port (service : str ) -> int :
171+ def get_service_port (service : str ) -> Optional [ int ] :
153172 ports = get_service_ports ()
154173 return ports .get (service )
155174
0 commit comments