Skip to content

Commit

Permalink
Core: Simplify routing for tags in URLs (#7634)
Browse files Browse the repository at this point in the history
  • Loading branch information
bblommers committed Apr 27, 2024
1 parent 0152ef4 commit 636d508
Show file tree
Hide file tree
Showing 8 changed files with 35 additions and 68 deletions.
8 changes: 2 additions & 6 deletions moto/amp/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@
"{0}/workspaces/(?P<workspace_id>[^/]+)/rulegroupsnamespaces$": PrometheusServiceResponse.dispatch,
"{0}/workspaces/(?P<workspace_id>[^/]+)/rulegroupsnamespaces/(?P<name>[^/]+)$": PrometheusServiceResponse.dispatch,
"{0}/tags/(?P<resource_arn>[^/]+)$": PrometheusServiceResponse.dispatch,
"{0}/tags/(?P<arn_prefix>[^/]+)/(?P<workspace_id>[^/]+)$": PrometheusServiceResponse.method_dispatch(
PrometheusServiceResponse.tags # type: ignore
),
"{0}/tags/(?P<arn_prefix>[^/]+)/(?P<workspace_id>[^/]+)/(?P<ns_name>[^/]+)$": PrometheusServiceResponse.method_dispatch(
PrometheusServiceResponse.tags # type: ignore
),
"{0}/tags/(?P<arn_prefix>[^/]+)/(?P<workspace_id>[^/]+)$": PrometheusServiceResponse.dispatch,
"{0}/tags/(?P<arn_prefix>[^/]+)/(?P<workspace_id>[^/]+)/(?P<ns_name>[^/]+)$": PrometheusServiceResponse.dispatch,
}
32 changes: 16 additions & 16 deletions moto/apigateway/responses.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import json
from typing import Any, Dict, List
from typing import Dict, List
from urllib.parse import unquote

from moto.core.responses import TYPE_RESPONSE, BaseResponse
Expand Down Expand Up @@ -413,25 +413,25 @@ def update_stage(self) -> str:
)
return json.dumps(stage_response.to_json())

def restapis_stages_tags( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
def tag_resource(self) -> str:
url_path_parts = unquote(self.path.split("/tags/")[1]).split("/")
function_id = url_path_parts[-3]
stage_name = url_path_parts[-1]
if self.method == "PUT":
tags = self._get_param("tags")
if tags:
stage = self.backend.get_stage(function_id, stage_name)
stage.tags = merge_multiple_dicts(stage.tags or {}, tags)
return 200, {}, json.dumps({"item": tags})
if self.method == "DELETE":
tags = self._get_param("tags")
if tags:
stage = self.backend.get_stage(function_id, stage_name)
for tag in (stage.tags or {}).copy():
if tag in (self.querystring.get("tagKeys") or {}):
stage.tags.pop(tag, None) # type: ignore[union-attr]
return 200, {}, json.dumps({"item": ""})
stage.tags = merge_multiple_dicts(stage.tags or {}, tags)
return json.dumps({"item": tags})

def untag_resource(self) -> str:
url_path_parts = unquote(self.path.split("/tags/")[1]).split("/")
function_id = url_path_parts[-3]
stage_name = url_path_parts[-1]
stage = self.backend.get_stage(function_id, stage_name)
for tag in (stage.tags or {}).copy():
if tag in (self.querystring.get("tagKeys") or {}):
stage.tags.pop(tag, None) # type: ignore[union-attr]
return json.dumps({"item": ""})

def get_export(self) -> TYPE_RESPONSE:
url_path_parts = self.path.split("/")
Expand Down
8 changes: 2 additions & 6 deletions moto/apigateway/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,8 @@
"{0}/restapis/(?P<function_id>[^/]+)/authorizers$": APIGatewayResponse.dispatch,
"{0}/restapis/(?P<function_id>[^/]+)/authorizers/(?P<authorizer_id>[^/]+)/?$": APIGatewayResponse.dispatch,
"{0}/restapis/(?P<function_id>[^/]+)/stages$": APIGatewayResponse.dispatch,
"{0}/tags/(?P<resourceArn>[^/]+)$": APIGatewayResponse.method_dispatch(
APIGatewayResponse.restapis_stages_tags
),
"{0}/tags/arn:aws:apigateway:(?P<region_name>[^/]+)::/restapis/(?P<function_id>[^/]+)/stages/(?P<stage_name>[^/]+)$": APIGatewayResponse.method_dispatch(
APIGatewayResponse.restapis_stages_tags
),
"{0}/tags/(?P<resourceArn>[^/]+)$": APIGatewayResponse.dispatch,
"{0}/tags/arn:aws:apigateway:(?P<region_name>[^/]+)::/restapis/(?P<function_id>[^/]+)/stages/(?P<stage_name>[^/]+)$": APIGatewayResponse.dispatch,
"{0}/restapis/(?P<function_id>[^/]+)/stages/(?P<stage_name>[^/]+)/?$": APIGatewayResponse.dispatch,
"{0}/restapis/(?P<function_id>[^/]+)/stages/(?P<stage_name>[^/]+)/exports/(?P<export_type>[^/]+)/?$": APIGatewayResponse.dispatch,
"{0}/restapis/(?P<function_id>[^/]+)/deployments$": APIGatewayResponse.dispatch,
Expand Down
11 changes: 0 additions & 11 deletions moto/apigatewayv2/responses.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Handles incoming apigatewayv2 requests, invokes methods, returns responses."""

import json
from typing import Any
from urllib.parse import unquote

from moto.core.responses import TYPE_RESPONSE, BaseResponse
Expand All @@ -21,16 +20,6 @@ def apigatewayv2_backend(self) -> ApiGatewayV2Backend:
"""Return backend instance specific for this region."""
return apigatewayv2_backends[self.current_account][self.region]

def tags(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE: # type: ignore[return]
self.setup_class(request, full_url, headers)

if self.method == "POST":
return self.tag_resource()
if self.method == "GET":
return self.get_tags()
if self.method == "DELETE":
return self.untag_resource()

def create_api(self) -> TYPE_RESPONSE:
params = json.loads(self.body)

Expand Down
12 changes: 3 additions & 9 deletions moto/apigatewayv2/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,9 @@
"{0}/v2/apis/(?P<api_id>[^/]+)/routes/(?P<route_id>[^/]+)/requestparameters/(?P<request_parameter>[^/]+)$": ApiGatewayV2Response.dispatch,
"{0}/v2/apis/(?P<api_id>[^/]+)/stages$": ApiGatewayV2Response.dispatch,
"{0}/v2/apis/(?P<api_id>[^/]+)/stages/(?P<stage_name>[^/]+)$": ApiGatewayV2Response.dispatch,
"{0}/v2/tags/(?P<resource_arn>[^/]+)$": ApiGatewayV2Response.method_dispatch(
ApiGatewayV2Response.tags
),
"{0}/v2/tags/(?P<resource_arn_pt1>[^/]+)/apis/(?P<resource_arn_pt2>[^/]+)$": ApiGatewayV2Response.method_dispatch(
ApiGatewayV2Response.tags
),
"{0}/v2/tags/(?P<resource_arn_pt1>[^/]+)/vpclinks/(?P<resource_arn_pt2>[^/]+)$": ApiGatewayV2Response.method_dispatch(
ApiGatewayV2Response.tags
),
"{0}/v2/tags/(?P<resource_arn>[^/]+)$": ApiGatewayV2Response.dispatch,
"{0}/v2/tags/(?P<resource_arn_pt1>[^/]+)/apis/(?P<resource_arn_pt2>[^/]+)$": ApiGatewayV2Response.dispatch,
"{0}/v2/tags/(?P<resource_arn_pt1>[^/]+)/vpclinks/(?P<resource_arn_pt2>[^/]+)$": ApiGatewayV2Response.dispatch,
"{0}/v2/vpclinks$": ApiGatewayV2Response.dispatch,
"{0}/v2/vpclinks/(?P<vpc_link_id>[^/]+)$": ApiGatewayV2Response.dispatch,
"{0}/v2/domainnames$": ApiGatewayV2Response.dispatch,
Expand Down
21 changes: 5 additions & 16 deletions moto/awslambda/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,6 @@ def json_body(self) -> Dict[str, Any]: # type: ignore[misc]
def backend(self) -> LambdaBackend:
return get_backend(self.current_account, self.region)

def tag(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if request.method == "GET":
return self._list_tags()
elif request.method == "POST":
return self._tag_resource()
elif request.method == "DELETE":
return self._untag_resource()
else:
raise ValueError(f"Cannot handle {request.method} request")

def add_permission(self) -> str:
function_name = unquote(self.path.split("/")[-2])
qualifier = self.querystring.get("Qualifier", [None])[0]
Expand Down Expand Up @@ -280,19 +269,19 @@ def _get_aws_region(self, full_url: str) -> str:
else:
return self.default_region

def _list_tags(self) -> TYPE_RESPONSE:
def list_tags(self) -> str:
function_arn = unquote(self.path.rsplit("/", 1)[-1])

tags = self.backend.list_tags(function_arn)
return 200, {}, json.dumps({"Tags": tags})
return json.dumps({"Tags": tags})

def _tag_resource(self) -> TYPE_RESPONSE:
def tag_resource(self) -> str:
function_arn = unquote(self.path.rsplit("/", 1)[-1])

self.backend.tag_resource(function_arn, self.json_body["Tags"])
return 200, {}, "{}"
return "{}"

def _untag_resource(self) -> TYPE_RESPONSE:
def untag_resource(self) -> TYPE_RESPONSE:
function_arn = unquote(self.path.rsplit("/", 1)[-1])
tag_keys = self.querystring["tagKeys"]

Expand Down
4 changes: 1 addition & 3 deletions moto/awslambda/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@
r"{0}/(?P<api_version>[^/]+)/functions/(?P<resource_arn>.+)/invocations/?$": LambdaResponse.dispatch,
r"{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_:%-]+)/invoke-async$": LambdaResponse.dispatch,
r"{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_:%-]+)/invoke-async/$": LambdaResponse.dispatch,
r"{0}/(?P<api_version>[^/]+)/tags/(?P<resource_arn>.+)": LambdaResponse.method_dispatch(
LambdaResponse.tag
),
r"{0}/(?P<api_version>[^/]+)/tags/(?P<resource_arn>.+)": LambdaResponse.dispatch,
r"{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_:%-]+)/policy/(?P<statement_id>[\w_-]+)$": LambdaResponse.dispatch,
r"{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_:%-]+)/policy/?$": LambdaResponse.dispatch,
r"{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_:%-]+)/configuration/?$": LambdaResponse.dispatch,
Expand Down
7 changes: 6 additions & 1 deletion moto/core/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,8 @@ def setup_class(
self.path = self.parsed_url.path
if self.is_werkzeug_request and "RAW_URI" in request.environ:
self.raw_path = urlparse(request.environ.get("RAW_URI")).path
if self.raw_path and not self.raw_path.startswith("/"):
self.raw_path = f"/{self.raw_path}"
else:
self.raw_path = self.path

Expand Down Expand Up @@ -491,6 +493,9 @@ def uri_to_regexp(self, uri: str) -> str:

def _convert(elem: str, is_last: bool) -> str:
if not re.match("^{.*}$", elem):
# URL-parts sometimes contain a $
# Like Greengrass: /../deployments/$reset
# We don't want to our regex to think this marks an end-of-line, so let's escape it
return elem.replace("$", r"\$")
name = (
elem.replace("{", "")
Expand Down Expand Up @@ -549,7 +554,7 @@ def _get_action(self) -> str:
if match:
return match.split(".")[-1]
# get action from method and uri
return self._get_action_from_method_and_request_uri(self.method, self.path)
return self._get_action_from_method_and_request_uri(self.method, self.raw_path)

def call_action(self) -> TYPE_RESPONSE:
headers = self.response_headers
Expand Down

0 comments on commit 636d508

Please sign in to comment.