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

shahar1 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 0b64e4eae4d Add Amazon ECR repository operators (#69886)
0b64e4eae4d is described below

commit 0b64e4eae4d570fec15392b74a04a5c2c7f60603
Author: Morgan <[email protected]>
AuthorDate: Sat Jul 18 14:59:46 2026 -0300

    Add Amazon ECR repository operators (#69886)
---
 providers/amazon/docs/operators/ecr.rst            |  90 ++++++++
 providers/amazon/provider.yaml                     |   5 +
 .../airflow/providers/amazon/aws/operators/ecr.py  | 231 +++++++++++++++++++++
 .../airflow/providers/amazon/get_provider_info.py  |   5 +
 .../amazon/tests/system/amazon/aws/example_ecr.py  |  99 +++++++++
 .../tests/unit/amazon/aws/operators/test_ecr.py    | 208 +++++++++++++++++++
 6 files changed, 638 insertions(+)

diff --git a/providers/amazon/docs/operators/ecr.rst 
b/providers/amazon/docs/operators/ecr.rst
new file mode 100644
index 00000000000..466e3c72d57
--- /dev/null
+++ b/providers/amazon/docs/operators/ecr.rst
@@ -0,0 +1,90 @@
+ .. Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+ ..   http://www.apache.org/licenses/LICENSE-2.0
+
+ .. Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+=======================================
+Amazon Elastic Container Registry (ECR)
+=======================================
+
+`Amazon Elastic Container Registry (ECR) <https://aws.amazon.com/ecr/>`__ is a 
managed container registry
+for storing, sharing, and deploying container images and artifacts.
+
+Prerequisite Tasks
+------------------
+
+.. include:: ../_partials/prerequisite_tasks.rst
+
+Generic Parameters
+------------------
+
+.. include:: ../_partials/generic_parameters.rst
+
+Operators
+---------
+
+.. _howto/operator:EcrCreateRepositoryOperator:
+
+Create an ECR repository
+========================
+
+To create an ECR repository, use
+:class:`~airflow.providers.amazon.aws.operators.ecr.EcrCreateRepositoryOperator`.
+This operator can provision a repository before a workflow builds or pushes 
container images.
+The operator returns the complete response from the Boto3 
``create_repository`` API operation.
+
+.. exampleinclude:: /../../amazon/tests/system/amazon/aws/example_ecr.py
+    :language: python
+    :dedent: 4
+    :start-after: [START howto_operator_ecr_create_repository]
+    :end-before: [END howto_operator_ecr_create_repository]
+
+.. _howto/operator:EcrSetRepositoryPolicyOperator:
+
+Set an ECR repository policy
+============================
+
+To set the repository policy for an ECR repository, use
+:class:`~airflow.providers.amazon.aws.operators.ecr.EcrSetRepositoryPolicyOperator`.
+This operator can configure permissions such as cross-account access to images 
stored in the repository.
+The operator returns the complete response from the Boto3 
``set_repository_policy`` API operation.
+
+.. exampleinclude:: /../../amazon/tests/system/amazon/aws/example_ecr.py
+    :language: python
+    :dedent: 4
+    :start-after: [START howto_operator_ecr_set_repository_policy]
+    :end-before: [END howto_operator_ecr_set_repository_policy]
+
+.. _howto/operator:EcrDeleteRepositoryOperator:
+
+Delete an ECR repository
+========================
+
+To delete an ECR repository, use
+:class:`~airflow.providers.amazon.aws.operators.ecr.EcrDeleteRepositoryOperator`.
+This operator can clean up temporary repositories or repositories that are no 
longer needed.
+Set ``force=True`` to delete a repository that contains images. The operator 
returns the complete response
+from the Boto3 ``delete_repository`` API operation.
+
+.. exampleinclude:: /../../amazon/tests/system/amazon/aws/example_ecr.py
+    :language: python
+    :dedent: 4
+    :start-after: [START howto_operator_ecr_delete_repository]
+    :end-before: [END howto_operator_ecr_delete_repository]
+
+Reference
+---------
+
+* `AWS Boto3 library documentation for ECR 
<https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ecr.html>`__
diff --git a/providers/amazon/provider.yaml b/providers/amazon/provider.yaml
index 4fa06f21121..afc76681482 100644
--- a/providers/amazon/provider.yaml
+++ b/providers/amazon/provider.yaml
@@ -182,6 +182,8 @@ integrations:
   - integration-name: Amazon Elastic Container Registry (ECR)
     external-doc-url: https://aws.amazon.com/ecr/
     logo: 
/docs/integration-logos/[email protected]
+    how-to-guide:
+      - /docs/apache-airflow-providers-amazon/operators/ecr.rst
     tags: [aws]
   - integration-name: Amazon ECS
     external-doc-url: https://aws.amazon.com/ecs/
@@ -447,6 +449,9 @@ operators:
   - integration-name: Amazon EC2
     python-modules:
       - airflow.providers.amazon.aws.operators.ec2
+  - integration-name: Amazon Elastic Container Registry (ECR)
+    python-modules:
+      - airflow.providers.amazon.aws.operators.ecr
   - integration-name: Amazon ECS
     python-modules:
       - airflow.providers.amazon.aws.operators.ecs
diff --git a/providers/amazon/src/airflow/providers/amazon/aws/operators/ecr.py 
b/providers/amazon/src/airflow/providers/amazon/aws/operators/ecr.py
new file mode 100644
index 00000000000..dec75ede9b5
--- /dev/null
+++ b/providers/amazon/src/airflow/providers/amazon/aws/operators/ecr.py
@@ -0,0 +1,231 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from collections.abc import Sequence
+from typing import TYPE_CHECKING, Any, ClassVar
+
+from airflow.providers.amazon.aws.hooks.ecr import EcrHook
+from airflow.providers.amazon.aws.operators.base_aws import AwsBaseOperator
+from airflow.providers.amazon.aws.utils.mixins import aws_template_fields
+from airflow.utils.helpers import prune_dict
+
+if TYPE_CHECKING:
+    from airflow.sdk import Context
+
+
+class EcrCreateRepositoryOperator(AwsBaseOperator[EcrHook]):
+    """
+    Create an Amazon ECR repository.
+
+    .. seealso::
+        For more information on how to use this operator, take a look at the 
guide:
+        :ref:`howto/operator:EcrCreateRepositoryOperator`
+
+    :param repository_name: The name of the repository to create. (templated)
+    :param registry_id: The AWS account ID associated with the registry. 
(templated)
+    :param tags: Metadata to apply to the repository. (templated)
+    :param image_tag_mutability: The tag mutability setting for the 
repository. (templated)
+    :param image_tag_mutability_exclusion_filters: Filters that override the 
repository's
+        image tag mutability setting. (templated)
+    :param image_scanning_configuration: The image scanning configuration for 
the repository. (templated)
+    :param encryption_configuration: The encryption configuration for the 
repository. (templated)
+    :param aws_conn_id: The Airflow connection used for AWS credentials.
+        If this is ``None`` or empty then the default boto3 behaviour is used. 
If
+        running Airflow in a distributed manner and aws_conn_id is None or
+        empty, then default boto3 configuration would be used (and must be
+        maintained on each worker node).
+    :param region_name: AWS region_name. If not specified then the default 
boto3 behaviour is used.
+    :param verify: Whether or not to verify SSL certificates. See:
+        
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/core/session.html
+    :param botocore_config: Configuration dictionary (key-values) for botocore 
client. See:
+        
https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html
+    """
+
+    aws_hook_class = EcrHook
+    template_fields: Sequence[str] = aws_template_fields(
+        "repository_name",
+        "registry_id",
+        "tags",
+        "image_tag_mutability",
+        "image_tag_mutability_exclusion_filters",
+        "image_scanning_configuration",
+        "encryption_configuration",
+    )
+    template_fields_renderers: ClassVar[dict[str, str]] = {
+        "tags": "json",
+        "image_tag_mutability_exclusion_filters": "json",
+        "image_scanning_configuration": "json",
+        "encryption_configuration": "json",
+    }
+
+    def __init__(
+        self,
+        *,
+        repository_name: str,
+        registry_id: str | None = None,
+        tags: list[dict[str, str]] | None = None,
+        image_tag_mutability: str | None = None,
+        image_tag_mutability_exclusion_filters: list[dict[str, str]] | None = 
None,
+        image_scanning_configuration: dict[str, bool] | None = None,
+        encryption_configuration: dict[str, str] | None = None,
+        aws_conn_id: str | None = "aws_default",
+        **kwargs,
+    ):
+        super().__init__(aws_conn_id=aws_conn_id, **kwargs)
+        self.repository_name = repository_name
+        self.registry_id = registry_id
+        self.tags = tags
+        self.image_tag_mutability = image_tag_mutability
+        self.image_tag_mutability_exclusion_filters = 
image_tag_mutability_exclusion_filters
+        self.image_scanning_configuration = image_scanning_configuration
+        self.encryption_configuration = encryption_configuration
+
+    def execute(self, context: Context) -> dict[str, Any]:
+        self.log.info("Creating Amazon ECR repository %s", 
self.repository_name)
+        response = self.hook.conn.create_repository(
+            **prune_dict(
+                {
+                    "registryId": self.registry_id,
+                    "repositoryName": self.repository_name,
+                    "tags": self.tags,
+                    "imageTagMutability": self.image_tag_mutability,
+                    "imageTagMutabilityExclusionFilters": 
self.image_tag_mutability_exclusion_filters,
+                    "imageScanningConfiguration": 
self.image_scanning_configuration,
+                    "encryptionConfiguration": self.encryption_configuration,
+                }
+            )
+        )
+        self.log.info("Created Amazon ECR repository %s", self.repository_name)
+        return response
+
+
+class EcrSetRepositoryPolicyOperator(AwsBaseOperator[EcrHook]):
+    """
+    Set the repository policy for an Amazon ECR repository.
+
+    .. seealso::
+        For more information on how to use this operator, take a look at the 
guide:
+        :ref:`howto/operator:EcrSetRepositoryPolicyOperator`
+
+    :param repository_name: The name of the repository to receive the policy. 
(templated)
+    :param policy_text: The JSON repository policy text to apply. (templated)
+    :param registry_id: The AWS account ID associated with the registry. 
(templated)
+    :param force: Whether to replace an existing policy that prevents setting 
a new policy. (templated)
+    :param aws_conn_id: The Airflow connection used for AWS credentials.
+        If this is ``None`` or empty then the default boto3 behaviour is used. 
If
+        running Airflow in a distributed manner and aws_conn_id is None or
+        empty, then default boto3 configuration would be used (and must be
+        maintained on each worker node).
+    :param region_name: AWS region_name. If not specified then the default 
boto3 behaviour is used.
+    :param verify: Whether or not to verify SSL certificates. See:
+        
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/core/session.html
+    :param botocore_config: Configuration dictionary (key-values) for botocore 
client. See:
+        
https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html
+    """
+
+    aws_hook_class = EcrHook
+    template_fields: Sequence[str] = aws_template_fields(
+        "repository_name", "policy_text", "registry_id", "force"
+    )
+    template_fields_renderers: ClassVar[dict[str, str]] = {"policy_text": 
"json"}
+
+    def __init__(
+        self,
+        *,
+        repository_name: str,
+        policy_text: str,
+        registry_id: str | None = None,
+        force: bool = False,
+        aws_conn_id: str | None = "aws_default",
+        **kwargs,
+    ):
+        super().__init__(aws_conn_id=aws_conn_id, **kwargs)
+        self.repository_name = repository_name
+        self.policy_text = policy_text
+        self.registry_id = registry_id
+        self.force = force
+
+    def execute(self, context: Context) -> dict[str, Any]:
+        self.log.info("Setting repository policy for Amazon ECR repository 
%s", self.repository_name)
+        response = self.hook.conn.set_repository_policy(
+            **prune_dict(
+                {
+                    "registryId": self.registry_id,
+                    "repositoryName": self.repository_name,
+                    "policyText": self.policy_text,
+                    "force": self.force,
+                }
+            )
+        )
+        self.log.info("Set repository policy for Amazon ECR repository %s", 
self.repository_name)
+        return response
+
+
+class EcrDeleteRepositoryOperator(AwsBaseOperator[EcrHook]):
+    """
+    Delete an Amazon ECR repository.
+
+    .. seealso::
+        For more information on how to use this operator, take a look at the 
guide:
+        :ref:`howto/operator:EcrDeleteRepositoryOperator`
+
+    :param repository_name: The name of the repository to delete. (templated)
+    :param registry_id: The AWS account ID associated with the registry. 
(templated)
+    :param force: Whether to delete the repository when it contains images. 
(templated)
+    :param aws_conn_id: The Airflow connection used for AWS credentials.
+        If this is ``None`` or empty then the default boto3 behaviour is used. 
If
+        running Airflow in a distributed manner and aws_conn_id is None or
+        empty, then default boto3 configuration would be used (and must be
+        maintained on each worker node).
+    :param region_name: AWS region_name. If not specified then the default 
boto3 behaviour is used.
+    :param verify: Whether or not to verify SSL certificates. See:
+        
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/core/session.html
+    :param botocore_config: Configuration dictionary (key-values) for botocore 
client. See:
+        
https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html
+    """
+
+    aws_hook_class = EcrHook
+    template_fields: Sequence[str] = aws_template_fields("repository_name", 
"registry_id", "force")
+
+    def __init__(
+        self,
+        *,
+        repository_name: str,
+        registry_id: str | None = None,
+        force: bool = False,
+        aws_conn_id: str | None = "aws_default",
+        **kwargs,
+    ):
+        super().__init__(aws_conn_id=aws_conn_id, **kwargs)
+        self.repository_name = repository_name
+        self.registry_id = registry_id
+        self.force = force
+
+    def execute(self, context: Context) -> dict[str, Any]:
+        self.log.info("Deleting Amazon ECR repository %s", 
self.repository_name)
+        response = self.hook.conn.delete_repository(
+            **prune_dict(
+                {
+                    "registryId": self.registry_id,
+                    "repositoryName": self.repository_name,
+                    "force": self.force,
+                }
+            )
+        )
+        self.log.info("Deleted Amazon ECR repository %s", self.repository_name)
+        return response
diff --git a/providers/amazon/src/airflow/providers/amazon/get_provider_info.py 
b/providers/amazon/src/airflow/providers/amazon/get_provider_info.py
index a5a5c532e7e..35fdd5756a6 100644
--- a/providers/amazon/src/airflow/providers/amazon/get_provider_info.py
+++ b/providers/amazon/src/airflow/providers/amazon/get_provider_info.py
@@ -94,6 +94,7 @@ def get_provider_info():
                 "integration-name": "Amazon Elastic Container Registry (ECR)",
                 "external-doc-url": "https://aws.amazon.com/ecr/";,
                 "logo": 
"/docs/integration-logos/[email protected]",
+                "how-to-guide": 
["/docs/apache-airflow-providers-amazon/operators/ecr.rst"],
                 "tags": ["aws"],
             },
             {
@@ -424,6 +425,10 @@ def get_provider_info():
                 "integration-name": "Amazon EC2",
                 "python-modules": 
["airflow.providers.amazon.aws.operators.ec2"],
             },
+            {
+                "integration-name": "Amazon Elastic Container Registry (ECR)",
+                "python-modules": 
["airflow.providers.amazon.aws.operators.ecr"],
+            },
             {
                 "integration-name": "Amazon ECS",
                 "python-modules": 
["airflow.providers.amazon.aws.operators.ecs"],
diff --git a/providers/amazon/tests/system/amazon/aws/example_ecr.py 
b/providers/amazon/tests/system/amazon/aws/example_ecr.py
new file mode 100644
index 00000000000..64230657cb0
--- /dev/null
+++ b/providers/amazon/tests/system/amazon/aws/example_ecr.py
@@ -0,0 +1,99 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from datetime import datetime
+
+from airflow.providers.amazon.aws.operators.ecr import (
+    EcrCreateRepositoryOperator,
+    EcrDeleteRepositoryOperator,
+    EcrSetRepositoryPolicyOperator,
+)
+from airflow.providers.common.compat.sdk import DAG, chain
+
+from system.amazon.aws.utils import ENV_ID_KEY, SystemTestContextBuilder
+from tests_common.test_utils.version_compat import AIRFLOW_V_3_0_PLUS
+
+if AIRFLOW_V_3_0_PLUS:
+    from airflow.sdk import TriggerRule
+else:
+    from airflow.utils.trigger_rule import TriggerRule  # type: 
ignore[no-redef,attr-defined]
+
+DAG_ID = "example_ecr"
+
+sys_test_context_task = SystemTestContextBuilder().build()
+
+
+with DAG(
+    dag_id=DAG_ID,
+    schedule=None,
+    start_date=datetime(2024, 1, 1),
+    catchup=False,
+) as dag:
+    test_context = sys_test_context_task()
+    repository_name = f"{test_context[ENV_ID_KEY]}-test-repository"
+
+    # [START howto_operator_ecr_create_repository]
+    create_repository = EcrCreateRepositoryOperator(
+        task_id="create_repository",
+        repository_name=repository_name,
+    )
+    # [END howto_operator_ecr_create_repository]
+
+    # [START howto_operator_ecr_set_repository_policy]
+    set_repository_policy = EcrSetRepositoryPolicyOperator(
+        task_id="set_repository_policy",
+        repository_name=repository_name,
+        policy_text="""
+        {
+          "Version": "2012-10-17",
+          "Statement": [
+            {
+              "Sid": "AllowAccountPull",
+              "Effect": "Allow",
+              "Principal": {
+                "AWS": "arn:aws:iam::{{ 
task_instance.xcom_pull(task_ids='create_repository')['repository']['registryId']
 }}:root"
+              },
+              "Action": [
+                "ecr:BatchGetImage",
+                "ecr:GetDownloadUrlForLayer"
+              ]
+            }
+          ]
+        }
+        """,
+    )
+    # [END howto_operator_ecr_set_repository_policy]
+
+    # [START howto_operator_ecr_delete_repository]
+    delete_repository = EcrDeleteRepositoryOperator(
+        task_id="delete_repository",
+        repository_name=repository_name,
+        force=True,
+        trigger_rule=TriggerRule.ALL_DONE,
+    )
+    # [END howto_operator_ecr_delete_repository]
+
+    chain(test_context, create_repository, set_repository_policy, 
delete_repository)
+
+    from tests_common.test_utils.watcher import watcher
+
+    list(dag.tasks) >> watcher()
+
+from tests_common.test_utils.system_tests import get_test_run  # noqa: E402
+
+test_run = get_test_run(dag)
diff --git a/providers/amazon/tests/unit/amazon/aws/operators/test_ecr.py 
b/providers/amazon/tests/unit/amazon/aws/operators/test_ecr.py
new file mode 100644
index 00000000000..ba485ba4016
--- /dev/null
+++ b/providers/amazon/tests/unit/amazon/aws/operators/test_ecr.py
@@ -0,0 +1,208 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from unittest import mock
+
+import pytest
+
+from airflow.providers.amazon.aws.hooks.ecr import EcrHook
+from airflow.providers.amazon.aws.operators.ecr import (
+    EcrCreateRepositoryOperator,
+    EcrDeleteRepositoryOperator,
+    EcrSetRepositoryPolicyOperator,
+)
+
+from unit.amazon.aws.utils.test_template_fields import validate_template_fields
+
+REPOSITORY_NAME = "test-repository"
+REGISTRY_ID = "123456789012"
+REPOSITORY_RESPONSE = {
+    "repository": {
+        "repositoryArn": 
f"arn:aws:ecr:us-east-1:{REGISTRY_ID}:repository/{REPOSITORY_NAME}",
+        "registryId": REGISTRY_ID,
+        "repositoryName": REPOSITORY_NAME,
+        "repositoryUri": 
f"{REGISTRY_ID}.dkr.ecr.us-east-1.amazonaws.com/{REPOSITORY_NAME}",
+    },
+    "ResponseMetadata": {"HTTPStatusCode": 200},
+}
+POLICY_TEXT = '{"Version":"2012-10-17","Statement":[]}'
+POLICY_RESPONSE = {
+    "registryId": REGISTRY_ID,
+    "repositoryName": REPOSITORY_NAME,
+    "policyText": POLICY_TEXT,
+    "ResponseMetadata": {"HTTPStatusCode": 200},
+}
+
+
+class TestEcrCreateRepositoryOperator:
+    @pytest.mark.parametrize(
+        ("operator_parameters", "boto3_parameters"),
+        [
+            pytest.param(
+                {},
+                {
+                    "repositoryName": REPOSITORY_NAME,
+                },
+                id="required-parameters",
+            ),
+            pytest.param(
+                {
+                    "registry_id": REGISTRY_ID,
+                    "tags": [{"Key": "environment", "Value": "test"}],
+                    "image_tag_mutability": "IMMUTABLE_WITH_EXCLUSION",
+                    "image_tag_mutability_exclusion_filters": [
+                        {"filterType": "WILDCARD", "filter": "latest"}
+                    ],
+                    "image_scanning_configuration": {"scanOnPush": True},
+                    "encryption_configuration": {
+                        "encryptionType": "KMS",
+                        "kmsKey": 
"arn:aws:kms:us-east-1:123456789012:key/test-key",
+                    },
+                },
+                {
+                    "repositoryName": REPOSITORY_NAME,
+                    "registryId": REGISTRY_ID,
+                    "tags": [{"Key": "environment", "Value": "test"}],
+                    "imageTagMutability": "IMMUTABLE_WITH_EXCLUSION",
+                    "imageTagMutabilityExclusionFilters": [{"filterType": 
"WILDCARD", "filter": "latest"}],
+                    "imageScanningConfiguration": {"scanOnPush": True},
+                    "encryptionConfiguration": {
+                        "encryptionType": "KMS",
+                        "kmsKey": 
"arn:aws:kms:us-east-1:123456789012:key/test-key",
+                    },
+                },
+                id="all-parameters",
+            ),
+        ],
+    )
+    @mock.patch.object(EcrHook, "conn", new_callable=mock.PropertyMock)
+    def test_execute(self, mock_conn, operator_parameters, boto3_parameters):
+        mock_client = mock.MagicMock(spec=["create_repository"])
+        mock_client.create_repository.return_value = REPOSITORY_RESPONSE
+        mock_conn.return_value = mock_client
+        operator = EcrCreateRepositoryOperator(
+            task_id="create_repository",
+            repository_name=REPOSITORY_NAME,
+            **operator_parameters,
+        )
+
+        result = operator.execute({})
+
+        
mock_client.create_repository.assert_called_once_with(**boto3_parameters)
+        assert result == REPOSITORY_RESPONSE
+
+    def test_template_fields(self):
+        operator = EcrCreateRepositoryOperator(
+            task_id="create_repository",
+            repository_name=REPOSITORY_NAME,
+        )
+
+        validate_template_fields(operator)
+
+
+class TestEcrSetRepositoryPolicyOperator:
+    @pytest.mark.parametrize(
+        ("operator_parameters", "boto3_parameters"),
+        [
+            pytest.param(
+                {},
+                {
+                    "repositoryName": REPOSITORY_NAME,
+                    "policyText": POLICY_TEXT,
+                    "force": False,
+                },
+                id="defaults",
+            ),
+            pytest.param(
+                {"registry_id": REGISTRY_ID, "force": True},
+                {
+                    "registryId": REGISTRY_ID,
+                    "repositoryName": REPOSITORY_NAME,
+                    "policyText": POLICY_TEXT,
+                    "force": True,
+                },
+                id="registry-and-force",
+            ),
+        ],
+    )
+    @mock.patch.object(EcrHook, "conn", new_callable=mock.PropertyMock)
+    def test_execute(self, mock_conn, operator_parameters, boto3_parameters):
+        mock_client = mock.MagicMock(spec=["set_repository_policy"])
+        mock_client.set_repository_policy.return_value = POLICY_RESPONSE
+        mock_conn.return_value = mock_client
+        operator = EcrSetRepositoryPolicyOperator(
+            task_id="set_repository_policy",
+            repository_name=REPOSITORY_NAME,
+            policy_text=POLICY_TEXT,
+            **operator_parameters,
+        )
+
+        result = operator.execute({})
+
+        
mock_client.set_repository_policy.assert_called_once_with(**boto3_parameters)
+        assert result == POLICY_RESPONSE
+
+    def test_template_fields(self):
+        operator = EcrSetRepositoryPolicyOperator(
+            task_id="set_repository_policy",
+            repository_name=REPOSITORY_NAME,
+            policy_text=POLICY_TEXT,
+        )
+
+        validate_template_fields(operator)
+
+
+class TestEcrDeleteRepositoryOperator:
+    @pytest.mark.parametrize(
+        ("operator_parameters", "boto3_parameters"),
+        [
+            pytest.param(
+                {},
+                {"repositoryName": REPOSITORY_NAME, "force": False},
+                id="defaults",
+            ),
+            pytest.param(
+                {"registry_id": REGISTRY_ID, "force": True},
+                {"repositoryName": REPOSITORY_NAME, "registryId": REGISTRY_ID, 
"force": True},
+                id="registry-and-force",
+            ),
+        ],
+    )
+    @mock.patch.object(EcrHook, "conn", new_callable=mock.PropertyMock)
+    def test_execute(self, mock_conn, operator_parameters, boto3_parameters):
+        mock_client = mock.MagicMock(spec=["delete_repository"])
+        mock_client.delete_repository.return_value = REPOSITORY_RESPONSE
+        mock_conn.return_value = mock_client
+        operator = EcrDeleteRepositoryOperator(
+            task_id="delete_repository",
+            repository_name=REPOSITORY_NAME,
+            **operator_parameters,
+        )
+
+        result = operator.execute({})
+
+        
mock_client.delete_repository.assert_called_once_with(**boto3_parameters)
+        assert result == REPOSITORY_RESPONSE
+
+    def test_template_fields(self):
+        operator = EcrDeleteRepositoryOperator(
+            task_id="delete_repository",
+            repository_name=REPOSITORY_NAME,
+        )
+
+        validate_template_fields(operator)

Reply via email to