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