This is an automated email from the ASF dual-hosted git repository.

eladkal pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 4bbcc163321 Standardize ECS TaskDoneTrigger on region_name and AWS 
hook parameters (#68923)
4bbcc163321 is described below

commit 4bbcc16332119867611b55bbca93f5a88c3dea1d
Author: Niko Oliveira <[email protected]>
AuthorDate: Wed Jun 24 21:58:21 2026 -0700

    Standardize ECS TaskDoneTrigger on region_name and AWS hook parameters 
(#68923)
    
    * Standardize ECS TaskDoneTrigger on region_name and AWS hook parameters
    
    TaskDoneTrigger used a non-standard 'region' argument and ignored SSL
    verification and botocore configuration. It now uses region_name and accepts
    verify and botocore_config like the other Amazon provider triggers, and
    EcsRunTaskOperator forwards those values when deferring, completing the 
trigger
    portion of the ECS migration in apache/airflow#35278.
    
    * Keep region as a deprecated TaskDoneTrigger alias
    
    Preserve backward compatibility for ECS TaskDoneTrigger after standardizing 
on
    region_name: the old region argument is still accepted as a deprecated 
alias,
    emitting AirflowProviderDeprecationWarning. This keeps existing keyword 
callers
    working and lets deferred-task triggers serialized by older versions 
deserialize
    after upgrade.
    
    * Exclude TaskDoneTrigger from trigger serialize/init sync check
    
    The deprecated region alias is folded into region_name at construction 
time, so
    it intentionally does not round-trip through serialize() -- the same 
by-design
    pattern already used for GKEJobTrigger's pod_name alias.
---
 .../airflow/providers/amazon/aws/operators/ecs.py  |  4 +-
 .../airflow/providers/amazon/aws/triggers/ecs.py   | 38 ++++++++--
 .../tests/unit/amazon/aws/triggers/test_ecs.py     | 82 ++++++++++++++++++++++
 scripts/ci/prek/check_trigger_serialize_init.py    |  3 +
 4 files changed, 120 insertions(+), 7 deletions(-)

diff --git a/providers/amazon/src/airflow/providers/amazon/aws/operators/ecs.py 
b/providers/amazon/src/airflow/providers/amazon/aws/operators/ecs.py
index 05f0102152d..4b71424e42d 100644
--- a/providers/amazon/src/airflow/providers/amazon/aws/operators/ecs.py
+++ b/providers/amazon/src/airflow/providers/amazon/aws/operators/ecs.py
@@ -556,9 +556,11 @@ class EcsRunTaskOperator(EcsBaseOperator):
                         waiter_delay=self.waiter_delay,
                         waiter_max_attempts=self.waiter_max_attempts,
                         aws_conn_id=self.aws_conn_id,
-                        region=self.region_name,
+                        region_name=self.region_name,
                         log_group=self.awslogs_group,
                         log_stream=self._get_logs_stream_name(),
+                        verify=self.verify,
+                        botocore_config=self.botocore_config,
                     ),
                     method_name="execute_complete",
                     # timeout is set to ensure that if a trigger dies, the 
timeout does not restart
diff --git a/providers/amazon/src/airflow/providers/amazon/aws/triggers/ecs.py 
b/providers/amazon/src/airflow/providers/amazon/aws/triggers/ecs.py
index 3768af58a12..39896960cfa 100644
--- a/providers/amazon/src/airflow/providers/amazon/aws/triggers/ecs.py
+++ b/providers/amazon/src/airflow/providers/amazon/aws/triggers/ecs.py
@@ -18,11 +18,13 @@
 from __future__ import annotations
 
 import asyncio
+import warnings
 from collections.abc import AsyncIterator
 from typing import TYPE_CHECKING, Any
 
 from botocore.exceptions import ClientError, WaiterError
 
+from airflow.exceptions import AirflowProviderDeprecationWarning
 from airflow.providers.amazon.aws.hooks.ecs import EcsHook
 from airflow.providers.amazon.aws.hooks.logs import AwsLogsHook
 from airflow.providers.amazon.aws.triggers.base import AwsBaseWaiterTrigger
@@ -125,7 +127,10 @@ class TaskDoneTrigger(BaseTrigger):
     :param waiter_max_attempts: The number of times to ping for status.
         Will fail after that many unsuccessful attempts.
     :param aws_conn_id: The Airflow connection used for AWS credentials.
-    :param region: The AWS region where the cluster is located.
+    :param region_name: The AWS region where the cluster is located. Used to 
build the hook.
+    :param verify: Whether or not to verify SSL certificates. Used to build 
the hook.
+    :param botocore_config: Configuration dictionary for the botocore client. 
Used to build the hook.
+    :param region: (deprecated) use ``region_name`` instead.
     """
 
     def __init__(
@@ -135,17 +140,30 @@ class TaskDoneTrigger(BaseTrigger):
         waiter_delay: int,
         waiter_max_attempts: int,
         aws_conn_id: str | None,
-        region: str | None,
+        region_name: str | None = None,
         log_group: str | None = None,
         log_stream: str | None = None,
+        verify: bool | str | None = None,
+        botocore_config: dict | None = None,
+        region: str | None = None,
     ):
+        if region is not None:
+            warnings.warn(
+                "`region` is deprecated and will be removed in a future 
release. "
+                "Please use `region_name` instead.",
+                AirflowProviderDeprecationWarning,
+                stacklevel=2,
+            )
+            region_name = region
         self.cluster = cluster
         self.task_arn = task_arn
 
         self.waiter_delay = waiter_delay
         self.waiter_max_attempts = waiter_max_attempts
         self.aws_conn_id = aws_conn_id
-        self.region = region
+        self.region_name = region_name
+        self.verify = verify
+        self.botocore_config = botocore_config
 
         self.log_group = log_group
         self.log_stream = log_stream
@@ -159,19 +177,27 @@ class TaskDoneTrigger(BaseTrigger):
                 "waiter_delay": self.waiter_delay,
                 "waiter_max_attempts": self.waiter_max_attempts,
                 "aws_conn_id": self.aws_conn_id,
-                "region": self.region,
+                "region_name": self.region_name,
                 "log_group": self.log_group,
                 "log_stream": self.log_stream,
+                "verify": self.verify,
+                "botocore_config": self.botocore_config,
             },
         )
 
     async def run(self) -> AsyncIterator[TriggerEvent]:
         async with (
             await EcsHook(
-                aws_conn_id=self.aws_conn_id, region_name=self.region
+                aws_conn_id=self.aws_conn_id,
+                region_name=self.region_name,
+                verify=self.verify,
+                config=self.botocore_config,
             ).get_async_conn() as ecs_client,
             await AwsLogsHook(
-                aws_conn_id=self.aws_conn_id, region_name=self.region
+                aws_conn_id=self.aws_conn_id,
+                region_name=self.region_name,
+                verify=self.verify,
+                config=self.botocore_config,
             ).get_async_conn() as logs_client,
         ):
             waiter = ecs_client.get_waiter("tasks_stopped")
diff --git a/providers/amazon/tests/unit/amazon/aws/triggers/test_ecs.py 
b/providers/amazon/tests/unit/amazon/aws/triggers/test_ecs.py
index 5a4ba278cfd..a46fc053c38 100644
--- a/providers/amazon/tests/unit/amazon/aws/triggers/test_ecs.py
+++ b/providers/amazon/tests/unit/amazon/aws/triggers/test_ecs.py
@@ -23,6 +23,7 @@ from unittest.mock import AsyncMock
 import pytest
 from botocore.exceptions import WaiterError
 
+from airflow.exceptions import AirflowProviderDeprecationWarning
 from airflow.providers.amazon.aws.hooks.ecs import EcsHook
 from airflow.providers.amazon.aws.hooks.logs import AwsLogsHook
 from airflow.providers.amazon.aws.triggers.ecs import (
@@ -35,6 +36,87 @@ if TYPE_CHECKING:
 
 
 class TestTaskDoneTrigger:
+    def test_deprecated_region_alias(self):
+        with pytest.warns(AirflowProviderDeprecationWarning, match="region"):
+            trigger = TaskDoneTrigger(
+                cluster="cluster",
+                task_arn="task_arn",
+                waiter_delay=5,
+                waiter_max_attempts=10,
+                aws_conn_id="my_conn",
+                region="eu-west-1",
+            )
+        assert trigger.region_name == "eu-west-1"
+        _, kwargs = trigger.serialize()
+        assert kwargs["region_name"] == "eu-west-1"
+        assert "region" not in kwargs
+
+    def test_serialize_includes_generic_hook_params(self):
+        trigger = TaskDoneTrigger(
+            cluster="cluster",
+            task_arn="task_arn",
+            waiter_delay=5,
+            waiter_max_attempts=10,
+            aws_conn_id="my_conn",
+            region_name="eu-west-1",
+            log_group="lg",
+            log_stream="ls",
+            verify=False,
+            botocore_config={"read_timeout": 7},
+        )
+        classpath, kwargs = trigger.serialize()
+        assert classpath == 
"airflow.providers.amazon.aws.triggers.ecs.TaskDoneTrigger"
+        assert kwargs == {
+            "cluster": "cluster",
+            "task_arn": "task_arn",
+            "waiter_delay": 5,
+            "waiter_max_attempts": 10,
+            "aws_conn_id": "my_conn",
+            "region_name": "eu-west-1",
+            "log_group": "lg",
+            "log_stream": "ls",
+            "verify": False,
+            "botocore_config": {"read_timeout": 7},
+        }
+
+    @pytest.mark.asyncio
+    @mock.patch("airflow.providers.amazon.aws.triggers.ecs.AwsLogsHook")
+    @mock.patch("airflow.providers.amazon.aws.triggers.ecs.EcsHook")
+    async def test_run_builds_hooks_with_generic_params(self, ecs_hook_cls, 
logs_hook_cls):
+        def make_hook(client):
+            ctx = mock.MagicMock()
+            ctx.__aenter__ = AsyncMock(return_value=client)
+            ctx.__aexit__ = AsyncMock(return_value=False)
+            instance = mock.MagicMock()
+            instance.get_async_conn = AsyncMock(return_value=ctx)
+            return instance
+
+        ecs_client = mock.MagicMock()
+        ecs_client.get_waiter().wait = AsyncMock()
+        ecs_hook_cls.return_value = make_hook(ecs_client)
+        logs_hook_cls.return_value = make_hook(mock.MagicMock())
+
+        trigger = TaskDoneTrigger(
+            cluster="cluster",
+            task_arn="task_arn",
+            waiter_delay=0,
+            waiter_max_attempts=10,
+            aws_conn_id="my_conn",
+            region_name="eu-west-1",
+            verify=False,
+            botocore_config={"read_timeout": 7},
+        )
+        await trigger.run().asend(None)
+
+        expected = {
+            "aws_conn_id": "my_conn",
+            "region_name": "eu-west-1",
+            "verify": False,
+            "config": {"read_timeout": 7},
+        }
+        ecs_hook_cls.assert_called_once_with(**expected)
+        logs_hook_cls.assert_called_once_with(**expected)
+
     @pytest.mark.asyncio
     @mock.patch.object(EcsHook, "get_async_conn")
     # this mock is only necessary to avoid a "No module named 'aiobotocore'" 
error in the LatestBoto CI step
diff --git a/scripts/ci/prek/check_trigger_serialize_init.py 
b/scripts/ci/prek/check_trigger_serialize_init.py
index f1050a9340e..b87eddc67fc 100755
--- a/scripts/ci/prek/check_trigger_serialize_init.py
+++ b/scripts/ci/prek/check_trigger_serialize_init.py
@@ -67,6 +67,9 @@ BY_DESIGN_EXCLUSIONS: set[str] = {
     # `pod_names` preserves the value and avoids re-triggering the deprecation 
path on restart.
     
"google/src/airflow/providers/google/cloud/triggers/kubernetes_engine.py::GKEJobTrigger",
     
"cncf/kubernetes/src/airflow/providers/cncf/kubernetes/triggers/job.py::KubernetesJobTrigger",
+    # `region` is a deprecated alias folded into `region_name` in __init__; 
serializing only
+    # `region_name` preserves the value and avoids re-triggering the 
deprecation path on restart.
+    "amazon/src/airflow/providers/amazon/aws/triggers/ecs.py::TaskDoneTrigger",
 }
 
 

Reply via email to