Repository navigation
Add EKS operator for commands in existing Pods #72542
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0d64437
eca492d
90b3bfe
bd455a0
01e5862
e5ff2d3
48d851f
2ad2fc6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -47,10 +47,28 @@ | |||||||||||||||
| build_resource_in_use_retry_args, | ||||||||||||||||
| validate_execute_complete_event, | ||||||||||||||||
| ) | ||||||||||||||||
| from airflow.providers.amazon.aws.utils.mixins import aws_template_fields | ||||||||||||||||
| from airflow.providers.amazon.aws.utils.mixins import AwsHookParams, aws_template_fields | ||||||||||||||||
| from airflow.providers.amazon.aws.utils.waiter_with_logging import wait | ||||||||||||||||
| from airflow.providers.cncf.kubernetes.utils.pod_manager import OnFinishAction | ||||||||||||||||
| from airflow.providers.common.compat.sdk import AirflowException, conf | ||||||||||||||||
| from airflow.providers.common.compat.sdk import ( | ||||||||||||||||
| AirflowException, | ||||||||||||||||
| AirflowOptionalProviderFeatureException, | ||||||||||||||||
| BaseOperator, | ||||||||||||||||
| conf, | ||||||||||||||||
| ) | ||||||||||||||||
|
|
||||||||||||||||
| try: | ||||||||||||||||
| from airflow.providers.cncf.kubernetes.operators.pod_exec import KubernetesPodExecOperator | ||||||||||||||||
| except ImportError: | ||||||||||||||||
|
|
||||||||||||||||
| class KubernetesPodExecOperator(BaseOperator): # type: ignore[no-redef] | ||||||||||||||||
| """Keep existing EKS operators importable with older Kubernetes providers.""" | ||||||||||||||||
|
|
||||||||||||||||
| def __init__(self, **kwargs): | ||||||||||||||||
| raise AirflowOptionalProviderFeatureException( | ||||||||||||||||
| "EksPodExecOperator requires apache-airflow-providers-cncf-kubernetes>=10.22.0." | ||||||||||||||||
| ) | ||||||||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I based this on the existing backward-compatible import fallback in the EKS module airflow/providers/amazon/src/airflow/providers/amazon/aws/operators/eks.py Lines 55 to 61 in 503e807
The difference is that
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry. I just saw that is just a few lines down in the existing file. My mistake. |
||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| try: | ||||||||||||||||
| from airflow.providers.cncf.kubernetes.operators.pod import KubernetesPodOperator | ||||||||||||||||
|
|
@@ -1368,3 +1386,113 @@ def _refresh_cached_properties(self) -> None: | |||||||||||||||
| self.log.exception("Failed to refresh AWS credentials.") | ||||||||||||||||
| raise | ||||||||||||||||
| super()._refresh_cached_properties() | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| class EksPodExecOperator(KubernetesPodExecOperator): | ||||||||||||||||
| """ | ||||||||||||||||
| Execute a command in a running container of an existing Pod on Amazon EKS. | ||||||||||||||||
|
|
||||||||||||||||
| The operator authenticates with Amazon EKS and delegates command execution to | ||||||||||||||||
| :class:`~airflow.providers.cncf.kubernetes.operators.pod_exec.KubernetesPodExecOperator`. | ||||||||||||||||
| It does not create, restart, or delete the target Pod. | ||||||||||||||||
|
|
||||||||||||||||
| .. seealso:: | ||||||||||||||||
| For more information on how to use this operator, take a look at the guide: | ||||||||||||||||
| :ref:`howto/operator:EksPodExecOperator` | ||||||||||||||||
|
|
||||||||||||||||
| :param cluster_name: The name of the Amazon EKS Cluster containing the Pod. (templated) | ||||||||||||||||
| :param pod_name: Name of the existing Kubernetes Pod. (templated) | ||||||||||||||||
| :param command: Command and arguments to execute in the container. (templated) | ||||||||||||||||
| :param namespace: Namespace containing the Pod. Defaults to ``default``. (templated) | ||||||||||||||||
| :param container_name: Name of the container in which to execute the command. When omitted, the | ||||||||||||||||
| ``kubectl.kubernetes.io/default-container`` annotation or the first container is used. | ||||||||||||||||
| Defaults to ``None``. (templated) | ||||||||||||||||
| :param aws_conn_id: The Airflow connection used for AWS credentials. (templated) | ||||||||||||||||
| Defaults to ``aws_default``. If this is ``None`` or empty, the default boto3 credential | ||||||||||||||||
| strategy is used without an Airflow connection lookup. | ||||||||||||||||
| :param region_name: AWS region containing the Amazon EKS Cluster. (templated) | ||||||||||||||||
| Defaults to ``None``, which uses the region from the AWS connection when available and | ||||||||||||||||
| otherwise falls back to the default boto3 region strategy. | ||||||||||||||||
| :param verify: Whether to verify SSL certificates, or the path to a CA bundle. Defaults to | ||||||||||||||||
| ``None``, which uses the value from the AWS connection when available. (templated) | ||||||||||||||||
| :param botocore_config: Configuration dictionary for the botocore client. Defaults to ``None``, | ||||||||||||||||
| which uses ``config_kwargs`` from the AWS connection when available. | ||||||||||||||||
| :param kubernetes_conn_id: Kubernetes connection used for additional client configuration. | ||||||||||||||||
| Defaults to ``kubernetes_default``, as with ``EksPodOperator``. (templated) | ||||||||||||||||
| :param do_xcom_push: Return standard output through XCom when ``True``. Defaults to ``False``. | ||||||||||||||||
| :param max_xcom_output_size: Maximum UTF-8 byte size retained for XCom. Defaults to 49,344 bytes. | ||||||||||||||||
| """ | ||||||||||||||||
|
|
||||||||||||||||
| template_fields: Sequence[str] = aws_template_fields( | ||||||||||||||||
| "cluster_name", | ||||||||||||||||
| *( | ||||||||||||||||
| field | ||||||||||||||||
| for field in KubernetesPodExecOperator.template_fields | ||||||||||||||||
| if field not in {"cluster_context", "config_file"} | ||||||||||||||||
| ), | ||||||||||||||||
| ) | ||||||||||||||||
|
|
||||||||||||||||
| def __init__( | ||||||||||||||||
| self, | ||||||||||||||||
| *, | ||||||||||||||||
| cluster_name: str, | ||||||||||||||||
| pod_name: str, | ||||||||||||||||
| command: Sequence[str], | ||||||||||||||||
| namespace: str = DEFAULT_NAMESPACE_NAME, | ||||||||||||||||
| container_name: str | None = None, | ||||||||||||||||
| aws_conn_id: str | None = DEFAULT_CONN_ID, | ||||||||||||||||
| region_name: str | None = None, | ||||||||||||||||
| verify: bool | str | None = None, | ||||||||||||||||
| botocore_config: dict | None = None, | ||||||||||||||||
| **kwargs, | ||||||||||||||||
| ) -> None: | ||||||||||||||||
| hook_params = AwsHookParams.from_constructor( | ||||||||||||||||
| aws_conn_id, region_name, verify, botocore_config, additional_params=kwargs | ||||||||||||||||
| ) | ||||||||||||||||
| super().__init__( | ||||||||||||||||
| pod_name=pod_name, | ||||||||||||||||
| command=command, | ||||||||||||||||
| namespace=namespace, | ||||||||||||||||
| container_name=container_name, | ||||||||||||||||
| in_cluster=False, | ||||||||||||||||
| cluster_context=None, | ||||||||||||||||
| config_file=None, | ||||||||||||||||
| **kwargs, | ||||||||||||||||
| ) | ||||||||||||||||
| self.cluster_name = cluster_name | ||||||||||||||||
| self.aws_conn_id = hook_params.aws_conn_id | ||||||||||||||||
| self.region_name = hook_params.region_name | ||||||||||||||||
| self.verify = hook_params.verify | ||||||||||||||||
| self.botocore_config = hook_params.botocore_config | ||||||||||||||||
|
|
||||||||||||||||
| def execute(self, context: Context) -> str | None: | ||||||||||||||||
| eks_hook = EksHook( | ||||||||||||||||
| aws_conn_id=self.aws_conn_id, | ||||||||||||||||
| region_name=self.region_name, | ||||||||||||||||
| verify=self.verify, | ||||||||||||||||
| config=self.botocore_config, | ||||||||||||||||
| ) | ||||||||||||||||
| credentials = eks_hook.get_session().get_credentials() | ||||||||||||||||
| if credentials is None: | ||||||||||||||||
| raise RuntimeError( | ||||||||||||||||
| "Unable to retrieve AWS credentials. Credentials may have expired or not been configured. " | ||||||||||||||||
| "Please check your AWS connection configuration." | ||||||||||||||||
| ) | ||||||||||||||||
| frozen_credentials = credentials.get_frozen_credentials() | ||||||||||||||||
| with eks_hook._secure_credential_context( | ||||||||||||||||
| frozen_credentials.access_key, | ||||||||||||||||
| frozen_credentials.secret_key, | ||||||||||||||||
| frozen_credentials.token, | ||||||||||||||||
| ) as credentials_file: | ||||||||||||||||
| with eks_hook.generate_config_file( | ||||||||||||||||
| eks_cluster_name=self.cluster_name, | ||||||||||||||||
| pod_namespace=self.namespace, | ||||||||||||||||
| credentials_file=credentials_file, | ||||||||||||||||
| ) as config_file: | ||||||||||||||||
| self.config_file = config_file | ||||||||||||||||
| try: | ||||||||||||||||
| return super().execute(context) | ||||||||||||||||
| finally: | ||||||||||||||||
| self.config_file = None | ||||||||||||||||
|
AlejandroMorgante marked this conversation as resolved.
|
||||||||||||||||
| self.__dict__.pop("client", None) | ||||||||||||||||
| self.__dict__.pop("hook", None) | ||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.