Skip to content

Commit 952ef90

Browse files
author
Łukasz Wyszomirski
authored
Support impersonation_chain parameter in the GKEStartPodOperator (#19518)
1 parent 2976070 commit 952ef90

3 files changed

Lines changed: 71 additions & 1 deletion

File tree

airflow/providers/google/cloud/operators/kubernetes_engine.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,15 @@ class GKEStartPodOperator(KubernetesPodOperator):
285285
:param gcp_conn_id: The google cloud connection id to use. This allows for
286286
users to specify a service account.
287287
:type gcp_conn_id: str
288+
:param impersonation_chain: Optional service account to impersonate using short-term
289+
credentials, or list of accounts required to get the access_token
290+
of the last account in the list, which will be impersonated in the request.
291+
If set as a string, the account must grant the originating account
292+
the Service Account Token Creator IAM role.
293+
If set as a sequence, the identities from the list must grant
294+
Service Account Token Creator IAM role to the directly preceding identity, with first
295+
account from the list granting this role to the originating account (templated).
296+
:type impersonation_chain: Union[str, Sequence[str]]
288297
"""
289298

290299
template_fields = {'project_id', 'location', 'cluster_name'} | set(KubernetesPodOperator.template_fields)
@@ -297,6 +306,7 @@ def __init__(
297306
use_internal_ip: bool = False,
298307
project_id: Optional[str] = None,
299308
gcp_conn_id: str = 'google_cloud_default',
309+
impersonation_chain: Optional[Union[str, Sequence[str]]] = None,
300310
**kwargs,
301311
) -> None:
302312
super().__init__(**kwargs)
@@ -305,6 +315,7 @@ def __init__(
305315
self.cluster_name = cluster_name
306316
self.gcp_conn_id = gcp_conn_id
307317
self.use_internal_ip = use_internal_ip
318+
self.impersonation_chain = impersonation_chain
308319

309320
if self.gcp_conn_id is None:
310321
raise AirflowException(
@@ -350,6 +361,22 @@ def execute(self, context) -> Optional[str]:
350361
"--project",
351362
self.project_id,
352363
]
364+
if self.impersonation_chain:
365+
if isinstance(self.impersonation_chain, str):
366+
impersonation_account = self.impersonation_chain
367+
elif len(self.impersonation_chain) == 1:
368+
impersonation_account = self.impersonation_chain[:-1]
369+
else:
370+
raise AirflowException(
371+
"Chained list of accounts is not supported, please specify only one service account"
372+
)
373+
374+
cmd.extend(
375+
[
376+
'--impersonate-service-account',
377+
impersonation_account,
378+
]
379+
)
353380
if self.use_internal_ip:
354381
cmd.append('--internal-ip')
355382
execute_in_subprocess(cmd)

docs/apache-airflow-providers-google/connections/gcp.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,6 @@ access token, which will allow to act on its behalf using its permissions. ``imp
176176
does not even need to have a generated key.
177177

178178
.. warning::
179-
:class:`~airflow.providers.google.cloud.operators.kubernetes_engine.GKEStartPodOperator`,
180179
:class:`~airflow.providers.google.cloud.operators.dataflow.DataflowCreateJavaJobOperator` and
181180
:class:`~airflow.providers.google.cloud.operators.dataflow.DataflowCreatePythonJobOperator`
182181
do not support direct impersonation as of now.

tests/providers/google/cloud/operators/test_kubernetes_engine.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,3 +291,47 @@ def test_execute_with_internal_ip(
291291
)
292292

293293
assert self.gke_op.config_file == FILE_NAME
294+
295+
@mock.patch.dict(os.environ, {})
296+
@mock.patch(
297+
"airflow.hooks.base.BaseHook.get_connections",
298+
return_value=[
299+
Connection(
300+
extra=json.dumps(
301+
{"extra__google_cloud_platform__keyfile_dict": '{"private_key": "r4nd0m_k3y"}'}
302+
)
303+
)
304+
],
305+
)
306+
@mock.patch('airflow.providers.cncf.kubernetes.operators.kubernetes_pod.KubernetesPodOperator.execute')
307+
@mock.patch('airflow.providers.google.cloud.operators.kubernetes_engine.GoogleBaseHook')
308+
@mock.patch('airflow.providers.google.cloud.operators.kubernetes_engine.execute_in_subprocess')
309+
@mock.patch('tempfile.NamedTemporaryFile')
310+
def test_execute_with_impersonation_service_account(
311+
self, file_mock, mock_execute_in_subprocess, mock_gcp_hook, exec_mock, get_con_mock
312+
):
313+
type(file_mock.return_value.__enter__.return_value).name = PropertyMock(
314+
side_effect=[FILE_NAME, '/path/to/new-file']
315+
)
316+
self.gke_op.impersonation_service_account = "test_account@example.com"
317+
self.gke_op.execute(None)
318+
319+
mock_gcp_hook.return_value.provide_authorized_gcloud.assert_called_once()
320+
321+
mock_execute_in_subprocess.assert_called_once_with(
322+
[
323+
'gcloud',
324+
'container',
325+
'clusters',
326+
'get-credentials',
327+
CLUSTER_NAME,
328+
'--zone',
329+
PROJECT_LOCATION,
330+
'--project',
331+
TEST_GCP_PROJECT_ID,
332+
'--impersonate-service-account',
333+
'test_account@example.com',
334+
]
335+
)
336+
337+
assert self.gke_op.config_file == FILE_NAME

0 commit comments

Comments
 (0)