Repository: getmoto/moto
Pull request #8233 (2024-10-16 05:17:28)

Pinpoint: Fix region in application ARN
This PR fixes the hardcoded region in Pinpoint Application resource.

This region is now correctly picked up from the request context.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/8233?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 94.54%. Comparing base [(`191ebcc`)](https://app.codecov.io/gh/getmoto/moto/commit/191ebcc02f7d63db3efdc7ea40f37c10a86bd7c7?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) to head [(`3933b2a`)](https://app.codecov.io/gh/getmoto/moto/commit/3933b2aa2e8d63370228355faf855bcdaa7d35c8?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).
> Report is 132 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #8233      +/-   ##
==========================================
+ Coverage   94.43%   94.54%   +0.11%     
==========================================
  Files        1139     1158      +19     
  Lines       97355   100101    +2746     
==========================================
+ Hits        91935    94644    +2709     
- Misses       5420     5457      +37     
```

| [Flag](https://app.codecov.io/gh/getmoto/moto/pull/8233/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | Coverage Δ | |
|---|---|---|
| [servertests](https://app.codecov.io/gh/getmoto/moto/pull/8233/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `28.88% <0.00%> (-0.04%)` | :arrow_down: |
| [unittests](https://app.codecov.io/gh/getmoto/moto/pull/8233/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `94.52% <100.00%> (+0.11%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/getmoto/moto/pull/8233?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).


Patch:
diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py
index fad7ca7e1..43b71cfc6 100644
--- a/moto/apigateway/models.py
+++ b/moto/apigateway/models.py
@@ -1658,7 +1658,7 @@ class APIGatewayBackend(BaseBackend):
     ) -> RestAPI:
         api_id = ApigwRestApiIdentifier(
             self.account_id, self.region_name, name
-        ).generate()
+        ).generate(tags=tags)
         rest_api = RestAPI(
             api_id,
             self.account_id,
diff --git a/moto/ecs/models.py b/moto/ecs/models.py
index 302fa2efd..fec148d86 100644
--- a/moto/ecs/models.py
+++ b/moto/ecs/models.py
@@ -1692,6 +1692,7 @@ class EC2ContainerServiceBackend(BaseBackend):
         task_definition_str = service_properties.pop("task_definition", None)
         cluster = self._get_cluster(cluster_str)
         service_name = service_properties.pop("service").split("/")[-1]
+        force_new_deployment = service_properties.pop("force_new_deployment", False)
         cluster_service_pair = f"{cluster.name}:{service_name}"
 
         if cluster_service_pair in self.services:
@@ -1705,6 +1706,12 @@ class EC2ContainerServiceBackend(BaseBackend):
             if task_definition_str:
                 self.describe_task_definition(task_definition_str)
                 current_service.task_definition = task_definition_str
+            if force_new_deployment and current_service.deployments:
+                deployment = current_service.deployments[0]
+                deployment["id"] = f"ecs-svc/{mock_random.randint(0, 32**12)}"
+                now = datetime.now(timezone.utc)
+                deployment["createdAt"] = now
+                deployment["updatedAt"] = now
             return current_service
         else:
             raise ServiceNotFoundException
diff --git a/moto/pinpoint/models.py b/moto/pinpoint/models.py
index 69053ce47..a482a783a 100644
--- a/moto/pinpoint/models.py
+++ b/moto/pinpoint/models.py
@@ -14,7 +14,7 @@ from .exceptions import ApplicationNotFound, EventStreamNotFound
 class App(BaseModel):
     def __init__(self, account_id: str, region_name: str, name: str):
         self.application_id = str(mock_random.uuid4()).replace("-", "")
-        self.arn = f"arn:{get_partition(region_name)}:mobiletargeting:us-east-1:{account_id}:apps/{self.application_id}"
+        self.arn = f"arn:{get_partition(region_name)}:mobiletargeting:{region_name}:{account_id}:apps/{self.application_id}"
         self.name = name
         self.created = unix_time()
         self.settings = AppSettings()
diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py
index 8d3d1d5a2..b2f185fb0 100644
--- a/moto/secretsmanager/models.py
+++ b/moto/secretsmanager/models.py
@@ -100,7 +100,7 @@ class FakeSecret:
         self.name = secret_id
         self.arn = SecretsManagerSecretIdentifier(
             account_id, region_name, secret_id
-        ).generate()
+        ).generate(tags=tags)
         self.account_id = account_id
         self.region = region_name
         self.secret_string = secret_string
diff --git a/moto/utilities/id_generator.py b/moto/utilities/id_generator.py
index c5f0a2c93..a26464a43 100644
--- a/moto/utilities/id_generator.py
+++ b/moto/utilities/id_generator.py
@@ -8,7 +8,7 @@ from moto.moto_api._internal import mock_random
 log = logging.getLogger(__name__)
 
 ExistingIds = Union[List[str], None]
-Tags = Union[Dict[str, str], None]
+Tags = Union[Dict[str, str], List[Dict[str, str]], None]
 
 # Custom resource tag to override the generated resource ID.
 TAG_KEY_CUSTOM_ID = "_custom_id_"
@@ -71,7 +71,8 @@ class MotoIdManager:
     def get_custom_id(
         self, resource_identifier: ResourceIdentifier
     ) -> Union[str, None]:
-        # retrieves a custom_id for a resource. Returns None
+        # retrieves a custom_id for a resource. Returns None if no id were registered
+        # that matches the `resource_identifier`
         return self._custom_ids.get(resource_identifier.unique_identifier)
 
     def set_custom_id(
@@ -95,10 +96,21 @@ class MotoIdManager:
 
     @staticmethod
     def get_id_from_tags(id_source_context: IdSourceContext) -> Union[str, None]:
-        if tags := id_source_context.get("tags"):
+        if not (tags := id_source_context.get("tags")):
+            return None
+
+        if isinstance(tags, dict):
             return tags.get(TAG_KEY_CUSTOM_ID)
 
-        return None
+        if isinstance(tags, list):
+            return next(
+                (
+                    tag.get("Value")
+                    for tag in tags
+                    if tag.get("Key") == TAG_KEY_CUSTOM_ID
+                ),
+                None,
+            )
 
     def get_custom_id_from_context(
         self, id_source_context: IdSourceContext


Test patch:
diff --git a/tests/test_apigateway/test_apigateway_custom_ids.py b/tests/test_apigateway/test_apigateway_custom_ids.py
index 1d2debbba..3bfdc544c 100644
--- a/tests/test_apigateway/test_apigateway_custom_ids.py
+++ b/tests/test_apigateway/test_apigateway_custom_ids.py
@@ -11,6 +11,8 @@ from moto.apigateway.utils import (
     ApigwRestApiIdentifier,
     ApigwUsagePlanIdentifier,
 )
+from moto.utilities.id_generator import TAG_KEY_CUSTOM_ID
+from tests import DEFAULT_ACCOUNT_ID
 
 API_ID = "ApiId"
 API_KEY_ID = "ApiKeyId"
@@ -27,7 +29,7 @@ USAGE_PLAN_ID = "UPlanId"
 @pytest.mark.skipif(
     not settings.TEST_DECORATOR_MODE, reason="Can't access the id manager in proxy mode"
 )
-def test_custom_id_rest_api(set_custom_id, account_id):
+def test_custom_id_rest_api(set_custom_id):
     region_name = "us-west-2"
     rest_api_name = "my-api"
     model_name = "modelName"
@@ -37,33 +39,40 @@ def test_custom_id_rest_api(set_custom_id, account_id):
     client = boto3.client("apigateway", region_name=region_name)
 
     set_custom_id(
-        ApigwRestApiIdentifier(account_id, region_name, rest_api_name), API_ID
+        ApigwRestApiIdentifier(DEFAULT_ACCOUNT_ID, region_name, rest_api_name), API_ID
     )
     set_custom_id(
-        ApigwResourceIdentifier(account_id, region_name, path_name="/"),
+        ApigwResourceIdentifier(DEFAULT_ACCOUNT_ID, region_name, path_name="/"),
         ROOT_RESOURCE_ID,
     )
     set_custom_id(
         ApigwResourceIdentifier(
-            account_id, region_name, parent_id=ROOT_RESOURCE_ID, path_name="pet"
+            DEFAULT_ACCOUNT_ID, region_name, parent_id=ROOT_RESOURCE_ID, path_name="pet"
         ),
         PET_1_RESOURCE_ID,
     )
     set_custom_id(
         ApigwResourceIdentifier(
-            account_id, region_name, parent_id=PET_1_RESOURCE_ID, path_name="pet"
+            DEFAULT_ACCOUNT_ID,
+            region_name,
+            parent_id=PET_1_RESOURCE_ID,
+            path_name="pet",
         ),
         PET_2_RESOURCE_ID,
     )
-    set_custom_id(ApigwModelIdentifier(account_id, region_name, model_name), MODEL_ID)
+    set_custom_id(
+        ApigwModelIdentifier(DEFAULT_ACCOUNT_ID, region_name, model_name), MODEL_ID
+    )
     set_custom_id(
         ApigwRequestValidatorIdentifier(
-            account_id, region_name, request_validator_name
+            DEFAULT_ACCOUNT_ID, region_name, request_validator_name
         ),
         REQUEST_VALIDATOR_ID,
     )
     set_custom_id(
-        ApigwDeploymentIdentifier(account_id, region_name, stage_name=stage_name),
+        ApigwDeploymentIdentifier(
+            DEFAULT_ACCOUNT_ID, region_name, stage_name=stage_name
+        ),
         DEPLOYMENT_ID,
     )
 
@@ -113,7 +122,7 @@ def test_custom_id_rest_api(set_custom_id, account_id):
 @pytest.mark.skipif(
     not settings.TEST_DECORATOR_MODE, reason="Can't access the id manager in proxy mode"
 )
-def test_custom_id_api_key(account_id, set_custom_id):
+def test_custom_id_api_key(set_custom_id):
     region_name = "us-west-2"
     api_key_value = "01234567890123456789"
     usage_plan_name = "usage-plan"
@@ -121,10 +130,11 @@ def test_custom_id_api_key(account_id, set_custom_id):
     client = boto3.client("apigateway", region_name=region_name)
 
     set_custom_id(
-        ApigwApiKeyIdentifier(account_id, region_name, value=api_key_value), API_KEY_ID
+        ApigwApiKeyIdentifier(DEFAULT_ACCOUNT_ID, region_name, value=api_key_value),
+        API_KEY_ID,
     )
     set_custom_id(
-        ApigwUsagePlanIdentifier(account_id, region_name, usage_plan_name),
+        ApigwUsagePlanIdentifier(DEFAULT_ACCOUNT_ID, region_name, usage_plan_name),
         USAGE_PLAN_ID,
     )
 
@@ -138,3 +148,15 @@ def test_custom_id_api_key(account_id, set_custom_id):
 
     assert api_key["id"] == API_KEY_ID
     assert usage_plan["id"] == USAGE_PLAN_ID
+
+
+@mock_aws
+def test_create_rest_api_with_custom_id_tag():
+    rest_api_name = "rest_api"
+    api_id = "testTagId"
+    client = boto3.client("apigateway", region_name="us-west-2")
+    rest_api = client.create_rest_api(
+        name=rest_api_name, tags={TAG_KEY_CUSTOM_ID: api_id}
+    )
+
+    assert rest_api["id"] == api_id
diff --git a/tests/test_ecs/test_ecs_boto3.py b/tests/test_ecs/test_ecs_boto3.py
index 0aaec121d..5f6baba84 100644
--- a/tests/test_ecs/test_ecs_boto3.py
+++ b/tests/test_ecs/test_ecs_boto3.py
@@ -1225,6 +1225,8 @@ def test_update_service():
         desiredCount=2,
     )
     assert response["service"]["desiredCount"] == 2
+    id = response["service"]["deployments"][0]["id"]
+    created_at = response["service"]["deployments"][0]["createdAt"]
 
     response = client.update_service(
         cluster="test_ecs_cluster",
@@ -1248,6 +1250,20 @@ def test_update_service():
     assert response["service"]["runningCount"] == 1
     assert response["service"]["pendingCount"] == 0
 
+    response = client.update_service(
+        cluster="test_ecs_cluster",
+        service="test_ecs_service",
+        taskDefinition="test_ecs_task",
+        desiredCount=1,
+        forceNewDeployment=True,
+    )
+    assert response["service"]["deployments"][0]["id"] != id
+    assert response["service"]["deployments"][0]["createdAt"] != created_at
+    assert (
+        response["service"]["deployments"][0]["createdAt"]
+        == response["service"]["deployments"][0]["updatedAt"]
+    )
+
 
 @mock_aws
 def test_update_missing_service():
diff --git a/tests/test_pinpoint/test_pinpoint.py b/tests/test_pinpoint/test_pinpoint.py
index bdf4cc042..adea2e753 100644
--- a/tests/test_pinpoint/test_pinpoint.py
+++ b/tests/test_pinpoint/test_pinpoint.py
@@ -17,6 +17,7 @@ def test_create_app():
 
     assert "ApplicationResponse" in resp
     assert "Arn" in resp["ApplicationResponse"]
+    assert "us-east-1" in resp["ApplicationResponse"]["Arn"]
     assert "Id" in resp["ApplicationResponse"]
     assert resp["ApplicationResponse"]["Name"] == "myfirstapp"
     assert "CreationDate" in resp["ApplicationResponse"]
@@ -50,6 +51,7 @@ def test_get_app():
 
     assert "ApplicationResponse" in resp
     assert "Arn" in resp["ApplicationResponse"]
+    assert "eu-west-1" in resp["ApplicationResponse"]["Arn"]
     assert "Id" in resp["ApplicationResponse"]
     assert resp["ApplicationResponse"]["Name"] == "myfirstapp"
     assert "CreationDate" in resp["ApplicationResponse"]
diff --git a/tests/test_secretsmanager/test_secretsmanager.py b/tests/test_secretsmanager/test_secretsmanager.py
index 734fcf384..b98349b4c 100644
--- a/tests/test_secretsmanager/test_secretsmanager.py
+++ b/tests/test_secretsmanager/test_secretsmanager.py
@@ -14,7 +14,9 @@ from freezegun import freeze_time
 from moto import mock_aws, settings
 from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
 from moto.secretsmanager.utils import SecretsManagerSecretIdentifier
+from moto.utilities.id_generator import TAG_KEY_CUSTOM_ID
 
+from .. import DEFAULT_ACCOUNT_ID
 from . import secretsmanager_aws_verified
 
 DEFAULT_SECRET_NAME = "test-secret7"
@@ -1968,7 +1970,7 @@ def test_update_secret_version_stage_dont_specify_current_stage(secret_arn=None)
 @pytest.mark.skipif(
     not settings.TEST_DECORATOR_MODE, reason="Can't access the id manager in proxy mode"
 )
-def test_create_secret_custom_id(account_id, set_custom_id):
+def test_create_secret_custom_id(set_custom_id):
     secret_suffix = "randomSuffix"
     secret_name = "secret-name"
     region_name = "us-east-1"
@@ -1976,9 +1978,25 @@ def test_create_secret_custom_id(account_id, set_custom_id):
     client = boto3.client("secretsmanager", region_name=region_name)
 
     set_custom_id(
-        SecretsManagerSecretIdentifier(account_id, region_name, secret_name),
+        SecretsManagerSecretIdentifier(DEFAULT_ACCOUNT_ID, region_name, secret_name),
         secret_suffix,
     )
     secret = client.create_secret(Name=secret_name, SecretString="my secret")
 
     assert secret["ARN"].split(":")[-1] == f"{secret_name}-{secret_suffix}"
+
+
+@mock_aws
+def test_create_secret_with_tag_custom_id(set_custom_id):
+    secret_suffix = "randomSuffix"
+    secret_name = "secret-name"
+
+    client = boto3.client("secretsmanager")
+
+    secret = client.create_secret(
+        Name=secret_name,
+        SecretString="my secret",
+        Tags=[{"Key": TAG_KEY_CUSTOM_ID, "Value": secret_suffix}],
+    )
+
+    assert secret["ARN"].split(":")[-1] == f"{secret_name}-{secret_suffix}"
diff --git a/tests/test_utilities/test_id_generator.py b/tests/test_utilities/test_id_generator.py
index 7ee85fd32..45ff2a043 100644
--- a/tests/test_utilities/test_id_generator.py
+++ b/tests/test_utilities/test_id_generator.py
@@ -12,7 +12,7 @@ REGION = "us-east-1"
 RESOURCE_NAME = "my-resource"
 
 CUSTOM_ID = "custom"
-GENERATED_ID = "generated"
+GENERIC_ID = "generated"
 TAG_ID = "fromTag"
 SERVICE = "test-service"
 RESOURCE = "test-resource"
@@ -24,7 +24,7 @@ def generate_test_id(
     existing_ids: ExistingIds = None,
     tags: Tags = None,
 ):
-    return GENERATED_ID
+    return GENERIC_ID
 
 
 class TestResourceIdentifier(ResourceIdentifier):
@@ -39,7 +39,7 @@ class TestResourceIdentifier(ResourceIdentifier):
 
 def test_generate_with_no_resource_identifier():
     generated_id = generate_test_id(None)
-    assert generated_id == GENERATED_ID
+    assert generated_id == GENERIC_ID
 
 
 def test_generate_with_matching_resource_identifier(set_custom_id):
@@ -58,7 +58,7 @@ def test_generate_with_non_matching_resource_identifier(set_custom_id):
     set_custom_id(resource_identifier, CUSTOM_ID)
 
     generated_id = generate_test_id(resource_identifier=resource_identifier_2)
-    assert generated_id == GENERATED_ID
+    assert generated_id == GENERIC_ID
 
 
 def test_generate_with_custom_id_tag():
@@ -87,7 +87,7 @@ def test_generate_with_existing_id(set_custom_id):
     generated_id = generate_test_id(
         resource_identifier=resource_identifier, existing_ids=[CUSTOM_ID]
     )
-    assert generated_id == GENERATED_ID
+    assert generated_id == GENERIC_ID
 
 
 def test_generate_with_tags_and_existing_id(set_custom_id):
@@ -98,7 +98,7 @@ def test_generate_with_tags_and_existing_id(set_custom_id):
         existing_ids=[TAG_ID],
         tags={TAG_KEY_CUSTOM_ID: TAG_ID},
     )
-    assert generated_id == GENERATED_ID
+    assert generated_id == GENERIC_ID
 
 
 def test_generate_with_tags_fallback(set_custom_id):