4
4
import logging
5
5
import re
6
6
from copy import deepcopy
7
- from datetime import UTC , datetime
7
+ from datetime import datetime
8
8
from typing import IO , Any
9
9
10
10
from moto .apigateway import models as apigw_models
@@ -360,7 +360,7 @@ def update_rest_api(
360
360
361
361
fixed_patch_ops .append (patch_op )
362
362
363
- _patch_api_gateway_entity (rest_api , fixed_patch_ops )
363
+ patch_api_gateway_entity (rest_api , fixed_patch_ops )
364
364
365
365
# fix data types after patches have been applied
366
366
endpoint_configs = rest_api .endpoint_configuration or {}
@@ -684,7 +684,7 @@ def update_resource(
684
684
)
685
685
686
686
# TODO: test with multiple patch operations which would not be compatible between each other
687
- _patch_api_gateway_entity (moto_resource , patch_operations )
687
+ patch_api_gateway_entity (moto_resource , patch_operations )
688
688
689
689
# after setting it, mutate the store
690
690
if moto_resource .parent_id != current_parent_id :
@@ -914,7 +914,7 @@ def update_method(
914
914
]
915
915
916
916
# TODO: test with multiple patch operations which would not be compatible between each other
917
- _patch_api_gateway_entity (moto_method , applicable_patch_operations )
917
+ patch_api_gateway_entity (moto_method , applicable_patch_operations )
918
918
919
919
# if we removed all values of those fields, set them to None so that they're not returned anymore
920
920
if had_req_params and len (moto_method .request_parameters ) == 0 :
@@ -985,8 +985,6 @@ def update_method_response(
985
985
# TODO: add createdDate / lastUpdatedDate in Stage operations below!
986
986
@handler ("CreateStage" , expand = False )
987
987
def create_stage (self , context : RequestContext , request : CreateStageRequest ) -> Stage :
988
- # TODO: we need to internalize Stages and Deployments in LocalStack, we have a lot of split logic
989
-
990
988
call_moto (context )
991
989
moto_api = get_moto_rest_api (context , rest_api_id = request ["restApiId" ])
992
990
stage = moto_api .stages .get (request ["stageName" ])
@@ -996,26 +994,6 @@ def create_stage(self, context: RequestContext, request: CreateStageRequest) ->
996
994
if not hasattr (stage , "documentation_version" ):
997
995
stage .documentation_version = request .get ("documentationVersion" )
998
996
999
- if not hasattr (stage , "canary_settings" ):
1000
- # TODO: validate canarySettings
1001
- if canary_settings := request .get ("canarySettings" ):
1002
- if (
1003
- deployment_id := canary_settings .get ("deploymentId" )
1004
- ) and deployment_id not in moto_api .deployments :
1005
- raise BadRequestException ("Deployment id does not exist" )
1006
-
1007
- default_settings = {
1008
- "deploymentId" : stage .deployment_id ,
1009
- "percentTraffic" : 0.0 ,
1010
- "useStageCache" : False ,
1011
- }
1012
- default_settings .update (canary_settings )
1013
- stage .canary_settings = default_settings
1014
- else :
1015
- stage .canary_settings = None
1016
-
1017
- # TODO: add createdData, lastUpdatedData
1018
-
1019
997
# make sure we update the stage_name on the deployment entity in moto
1020
998
deployment = moto_api .deployments .get (request ["deploymentId" ])
1021
999
deployment .stage_name = stage .name
@@ -1050,7 +1028,10 @@ def update_stage(
1050
1028
patch_operations : ListOfPatchOperation = None ,
1051
1029
** kwargs ,
1052
1030
) -> Stage :
1053
- moto_rest_api = get_moto_rest_api (context , rest_api_id )
1031
+ call_moto (context )
1032
+
1033
+ moto_backend = get_moto_backend (context .account_id , context .region )
1034
+ moto_rest_api : MotoRestAPI = moto_backend .apis .get (rest_api_id )
1054
1035
if not (moto_stage := moto_rest_api .stages .get (stage_name )):
1055
1036
raise NotFoundException ("Invalid Stage identifier specified" )
1056
1037
@@ -1059,17 +1040,12 @@ def update_stage(
1059
1040
1060
1041
# copy the patch operations to not mutate them, so that we're logging the correct input
1061
1042
patch_operations = copy .deepcopy (patch_operations ) or []
1062
- # we are only passing a subset of operations to Moto as it does not handle properly all of them
1063
- moto_patch_operations = []
1064
- moto_stage_copy = copy .deepcopy (moto_stage )
1065
1043
for patch_operation in patch_operations :
1066
- skip_moto_apply = False
1067
1044
patch_path = patch_operation ["path" ]
1068
- patch_op = patch_operation ["op" ]
1069
1045
1070
1046
# special case: handle updates (op=remove) for wildcard method settings
1071
1047
patch_path_stripped = patch_path .strip ("/" )
1072
- if patch_path_stripped == "*/*" and patch_op == "remove" :
1048
+ if patch_path_stripped == "*/*" and patch_operation [ "op" ] == "remove" :
1073
1049
if not moto_stage .method_settings .pop (patch_path_stripped , None ):
1074
1050
raise BadRequestException (
1075
1051
"Cannot remove method setting */* because there is no method setting for this method "
@@ -1081,39 +1057,6 @@ def update_stage(
1081
1057
path_valid = patch_path in STAGE_UPDATE_PATHS or any (
1082
1058
re .match (regex , patch_path ) for regex in path_regexes
1083
1059
)
1084
- if is_canary := patch_path .startswith ("/canarySettings" ):
1085
- skip_moto_apply = True
1086
- path_valid = is_canary_settings_update_patch_valid (op = patch_op , path = patch_path )
1087
- # it seems our JSON Patch utility does not handle replace properly if the value does not exists before
1088
- # it seems to maybe be a Stage-only thing, so replacing it here
1089
- if patch_op == "replace" :
1090
- patch_operation ["op" ] = "add"
1091
-
1092
- if patch_op == "copy" :
1093
- copy_from = patch_operation .get ("from" )
1094
- if patch_path not in ("/deploymentId" , "/variables" ) or copy_from not in (
1095
- "/canarySettings/deploymentId" ,
1096
- "/canarySettings/stageVariableOverrides" ,
1097
- ):
1098
- raise BadRequestException (
1099
- "Invalid copy operation with path: /canarySettings/stageVariableOverrides and from /variables. Valid copy:path are [/deploymentId, /variables] and valid copy:from are [/canarySettings/deploymentId, /canarySettings/stageVariableOverrides]"
1100
- )
1101
-
1102
- if copy_from .startswith ("/canarySettings" ) and not getattr (
1103
- moto_stage_copy , "canary_settings" , None
1104
- ):
1105
- raise BadRequestException ("Promotion not available. Canary does not exist." )
1106
-
1107
- if patch_path == "/variables" :
1108
- moto_stage_copy .variables .update (
1109
- moto_stage_copy .canary_settings .get ("stageVariableOverrides" , {})
1110
- )
1111
- elif patch_path == "/deploymentId" :
1112
- moto_stage_copy .deployment_id = moto_stage_copy .canary_settings ["deploymentId" ]
1113
-
1114
- # we manually assign `copy` ops, no need to apply them
1115
- continue
1116
-
1117
1060
if not path_valid :
1118
1061
valid_paths = f"[{ ', ' .join (STAGE_UPDATE_PATHS )} ]"
1119
1062
# note: weird formatting in AWS - required for snapshot testing
@@ -1131,33 +1074,10 @@ def update_stage(
1131
1074
if patch_path == "/tracingEnabled" and (value := patch_operation .get ("value" )):
1132
1075
patch_operation ["value" ] = value and value .lower () == "true" or False
1133
1076
1134
- elif patch_path in ("/canarySettings/deploymentId" , "/deploymentId" ):
1135
- if patch_op != "copy" and not moto_rest_api .deployments .get (
1136
- patch_operation .get ("value" )
1137
- ):
1138
- raise BadRequestException ("Deployment id does not exist" )
1139
-
1140
- if not skip_moto_apply :
1141
- # we need to copy the patch operation because `_patch_api_gateway_entity` is mutating it in place
1142
- moto_patch_operations .append (dict (patch_operation ))
1143
-
1144
- # we need to apply patch operation individually to be able to validate the logic
1145
- _patch_api_gateway_entity (moto_stage_copy , [patch_operation ])
1146
- if is_canary and (canary_settings := getattr (moto_stage_copy , "canary_settings" , None )):
1147
- default_canary_settings = {
1148
- "deploymentId" : moto_stage_copy .deployment_id ,
1149
- "percentTraffic" : 0.0 ,
1150
- "useStageCache" : False ,
1151
- }
1152
- default_canary_settings .update (canary_settings )
1153
- moto_stage_copy .canary_settings = default_canary_settings
1154
-
1155
- moto_rest_api .stages [stage_name ] = moto_stage_copy
1156
- moto_stage_copy .apply_operations (moto_patch_operations )
1157
-
1158
- moto_stage_copy .last_updated_date = datetime .now (tz = UTC )
1159
-
1160
- response = moto_stage_copy .to_json ()
1077
+ patch_api_gateway_entity (moto_stage , patch_operations )
1078
+ moto_stage .apply_operations (patch_operations )
1079
+
1080
+ response = moto_stage .to_json ()
1161
1081
self ._patch_stage_response (response )
1162
1082
return response
1163
1083
@@ -1544,7 +1464,7 @@ def update_documentation_version(
1544
1464
if not result :
1545
1465
raise NotFoundException (f"Documentation version not found: { documentation_version } " )
1546
1466
1547
- _patch_api_gateway_entity (result , patch_operations )
1467
+ patch_api_gateway_entity (result , patch_operations )
1548
1468
1549
1469
return result
1550
1470
@@ -2091,7 +2011,7 @@ def update_integration(
2091
2011
raise NotFoundException ("Invalid Integration identifier specified" )
2092
2012
2093
2013
integration = method .method_integration
2094
- _patch_api_gateway_entity (integration , patch_operations )
2014
+ patch_api_gateway_entity (integration , patch_operations )
2095
2015
2096
2016
# fix data types
2097
2017
if integration .timeout_in_millis :
@@ -2697,7 +2617,7 @@ def update_gateway_response(
2697
2617
f"Invalid null or empty value in { param_type } "
2698
2618
)
2699
2619
2700
- _patch_api_gateway_entity (patched_entity , patch_operations )
2620
+ patch_api_gateway_entity (patched_entity , patch_operations )
2701
2621
2702
2622
return patched_entity
2703
2623
@@ -2819,7 +2739,7 @@ def create_custom_context(
2819
2739
return ctx
2820
2740
2821
2741
2822
- def _patch_api_gateway_entity (entity : Any , patch_operations : ListOfPatchOperation ):
2742
+ def patch_api_gateway_entity (entity : Any , patch_operations : ListOfPatchOperation ):
2823
2743
patch_operations = patch_operations or []
2824
2744
2825
2745
if isinstance (entity , dict ):
@@ -2916,33 +2836,6 @@ def to_response_json(model_type, data, api_id=None, self_link=None, id_attr=None
2916
2836
return result
2917
2837
2918
2838
2919
- def is_canary_settings_update_patch_valid (op : str , path : str ) -> bool :
2920
- path_regexes = (
2921
- r"\/canarySettings\/percentTraffic" ,
2922
- r"\/canarySettings\/deploymentId" ,
2923
- r"\/canarySettings\/stageVariableOverrides\/.+" ,
2924
- r"\/canarySettings\/useStageCache" ,
2925
- )
2926
- if path == "/canarySettings" and op == "remove" :
2927
- return True
2928
-
2929
- matches_path = any (re .match (regex , path ) for regex in path_regexes )
2930
-
2931
- if op not in ("replace" , "copy" ):
2932
- if matches_path :
2933
- raise BadRequestException (f"Invalid { op } operation with path: { path } " )
2934
-
2935
- raise BadRequestException (
2936
- f"Cannot { op } method setting { path .lstrip ('/' )} because there is no method setting for this method "
2937
- )
2938
-
2939
- # stageVariableOverrides is a bit special as it's nested, it doesn't return the same error message
2940
- if not matches_path and path != "/canarySettings/stageVariableOverrides" :
2941
- return False
2942
-
2943
- return True
2944
-
2945
-
2946
2839
DEFAULT_EMPTY_MODEL = Model (
2947
2840
id = short_uid ()[:6 ],
2948
2841
name = EMPTY_MODEL ,
0 commit comments