apache-airflow-providers-amazon 8.3.1rc1__py3-none-any.whl → 8.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- airflow/providers/amazon/__init__.py +4 -2
- airflow/providers/amazon/aws/hooks/base_aws.py +29 -12
- airflow/providers/amazon/aws/hooks/emr.py +17 -9
- airflow/providers/amazon/aws/hooks/eventbridge.py +27 -0
- airflow/providers/amazon/aws/hooks/redshift_data.py +10 -0
- airflow/providers/amazon/aws/hooks/sagemaker.py +24 -14
- airflow/providers/amazon/aws/notifications/chime.py +1 -1
- airflow/providers/amazon/aws/operators/eks.py +140 -7
- airflow/providers/amazon/aws/operators/emr.py +202 -22
- airflow/providers/amazon/aws/operators/eventbridge.py +87 -0
- airflow/providers/amazon/aws/operators/rds.py +120 -48
- airflow/providers/amazon/aws/operators/redshift_data.py +7 -0
- airflow/providers/amazon/aws/operators/sagemaker.py +75 -7
- airflow/providers/amazon/aws/operators/step_function.py +34 -2
- airflow/providers/amazon/aws/transfers/s3_to_redshift.py +1 -1
- airflow/providers/amazon/aws/triggers/batch.py +1 -1
- airflow/providers/amazon/aws/triggers/ecs.py +7 -5
- airflow/providers/amazon/aws/triggers/eks.py +174 -3
- airflow/providers/amazon/aws/triggers/emr.py +215 -1
- airflow/providers/amazon/aws/triggers/rds.py +161 -5
- airflow/providers/amazon/aws/triggers/sagemaker.py +84 -1
- airflow/providers/amazon/aws/triggers/step_function.py +59 -0
- airflow/providers/amazon/aws/utils/__init__.py +16 -1
- airflow/providers/amazon/aws/utils/rds.py +2 -2
- airflow/providers/amazon/aws/waiters/sagemaker.json +46 -0
- airflow/providers/amazon/aws/waiters/stepfunctions.json +36 -0
- airflow/providers/amazon/get_provider_info.py +21 -1
- {apache_airflow_providers_amazon-8.3.1rc1.dist-info → apache_airflow_providers_amazon-8.4.0.dist-info}/METADATA +13 -13
- {apache_airflow_providers_amazon-8.3.1rc1.dist-info → apache_airflow_providers_amazon-8.4.0.dist-info}/RECORD +34 -30
- {apache_airflow_providers_amazon-8.3.1rc1.dist-info → apache_airflow_providers_amazon-8.4.0.dist-info}/WHEEL +1 -1
- {apache_airflow_providers_amazon-8.3.1rc1.dist-info → apache_airflow_providers_amazon-8.4.0.dist-info}/LICENSE +0 -0
- {apache_airflow_providers_amazon-8.3.1rc1.dist-info → apache_airflow_providers_amazon-8.4.0.dist-info}/NOTICE +0 -0
- {apache_airflow_providers_amazon-8.3.1rc1.dist-info → apache_airflow_providers_amazon-8.4.0.dist-info}/entry_points.txt +0 -0
- {apache_airflow_providers_amazon-8.3.1rc1.dist-info → apache_airflow_providers_amazon-8.4.0.dist-info}/top_level.txt +0 -0
@@ -33,6 +33,12 @@ from airflow.providers.amazon.aws.triggers.emr import (
|
|
33
33
|
EmrAddStepsTrigger,
|
34
34
|
EmrContainerTrigger,
|
35
35
|
EmrCreateJobFlowTrigger,
|
36
|
+
EmrServerlessCancelJobsTrigger,
|
37
|
+
EmrServerlessCreateApplicationTrigger,
|
38
|
+
EmrServerlessDeleteApplicationTrigger,
|
39
|
+
EmrServerlessStartApplicationTrigger,
|
40
|
+
EmrServerlessStartJobTrigger,
|
41
|
+
EmrServerlessStopApplicationTrigger,
|
36
42
|
EmrTerminateJobFlowTrigger,
|
37
43
|
)
|
38
44
|
from airflow.providers.amazon.aws.utils.waiter import waiter
|
@@ -972,7 +978,7 @@ class EmrServerlessCreateApplicationOperator(BaseOperator):
|
|
972
978
|
:param release_label: The EMR release version associated with the application.
|
973
979
|
:param job_type: The type of application you want to start, such as Spark or Hive.
|
974
980
|
:param wait_for_completion: If true, wait for the Application to start before returning. Default to True.
|
975
|
-
If set to False, ``
|
981
|
+
If set to False, ``waiter_max_attempts`` and ``waiter_delay`` will only be applied when
|
976
982
|
waiting for the application to be in the ``CREATED`` state.
|
977
983
|
:param client_request_token: The client idempotency token of the application to create.
|
978
984
|
Its value must be unique for each request.
|
@@ -985,6 +991,9 @@ class EmrServerlessCreateApplicationOperator(BaseOperator):
|
|
985
991
|
:waiter_max_attempts: Number of times the waiter should poll the application to check the state.
|
986
992
|
If not set, the waiter will use its default value.
|
987
993
|
:param waiter_delay: Number of seconds between polling the state of the application.
|
994
|
+
:param deferrable: If True, the operator will wait asynchronously for application to be created.
|
995
|
+
This implies waiting for completion. This mode requires aiobotocore module to be installed.
|
996
|
+
(default: False, but can be overridden in config file by setting default_deferrable to True)
|
988
997
|
"""
|
989
998
|
|
990
999
|
def __init__(
|
@@ -999,6 +1008,7 @@ class EmrServerlessCreateApplicationOperator(BaseOperator):
|
|
999
1008
|
waiter_check_interval_seconds: int | ArgNotSet = NOTSET,
|
1000
1009
|
waiter_max_attempts: int | ArgNotSet = NOTSET,
|
1001
1010
|
waiter_delay: int | ArgNotSet = NOTSET,
|
1011
|
+
deferrable: bool = conf.getboolean("operators", "default_deferrable", fallback=False),
|
1002
1012
|
**kwargs,
|
1003
1013
|
):
|
1004
1014
|
if waiter_check_interval_seconds is NOTSET:
|
@@ -1030,6 +1040,7 @@ class EmrServerlessCreateApplicationOperator(BaseOperator):
|
|
1030
1040
|
self.config = config or {}
|
1031
1041
|
self.waiter_max_attempts = int(waiter_max_attempts) # type: ignore[arg-type]
|
1032
1042
|
self.waiter_delay = int(waiter_delay) # type: ignore[arg-type]
|
1043
|
+
self.deferrable = deferrable
|
1033
1044
|
super().__init__(**kwargs)
|
1034
1045
|
|
1035
1046
|
self.client_request_token = client_request_token or str(uuid4())
|
@@ -1052,8 +1063,19 @@ class EmrServerlessCreateApplicationOperator(BaseOperator):
|
|
1052
1063
|
raise AirflowException(f"Application Creation failed: {response}")
|
1053
1064
|
|
1054
1065
|
self.log.info("EMR serverless application created: %s", application_id)
|
1055
|
-
|
1066
|
+
if self.deferrable:
|
1067
|
+
self.defer(
|
1068
|
+
trigger=EmrServerlessCreateApplicationTrigger(
|
1069
|
+
application_id=application_id,
|
1070
|
+
aws_conn_id=self.aws_conn_id,
|
1071
|
+
waiter_delay=self.waiter_delay,
|
1072
|
+
waiter_max_attempts=self.waiter_max_attempts,
|
1073
|
+
),
|
1074
|
+
timeout=timedelta(seconds=self.waiter_max_attempts * self.waiter_delay),
|
1075
|
+
method_name="start_application_deferred",
|
1076
|
+
)
|
1056
1077
|
|
1078
|
+
waiter = self.hook.get_waiter("serverless_app_created")
|
1057
1079
|
wait(
|
1058
1080
|
waiter=waiter,
|
1059
1081
|
waiter_delay=self.waiter_delay,
|
@@ -1079,6 +1101,32 @@ class EmrServerlessCreateApplicationOperator(BaseOperator):
|
|
1079
1101
|
)
|
1080
1102
|
return application_id
|
1081
1103
|
|
1104
|
+
def start_application_deferred(self, context: Context, event: dict[str, Any] | None = None) -> None:
|
1105
|
+
if event is None:
|
1106
|
+
self.log.error("Trigger error: event is None")
|
1107
|
+
raise AirflowException("Trigger error: event is None")
|
1108
|
+
elif event["status"] != "success":
|
1109
|
+
raise AirflowException(f"Application {event['application_id']} failed to create")
|
1110
|
+
self.log.info("Starting application %s", event["application_id"])
|
1111
|
+
self.hook.conn.start_application(applicationId=event["application_id"])
|
1112
|
+
self.defer(
|
1113
|
+
trigger=EmrServerlessStartApplicationTrigger(
|
1114
|
+
application_id=event["application_id"],
|
1115
|
+
aws_conn_id=self.aws_conn_id,
|
1116
|
+
waiter_delay=self.waiter_delay,
|
1117
|
+
waiter_max_attempts=self.waiter_max_attempts,
|
1118
|
+
),
|
1119
|
+
timeout=timedelta(seconds=self.waiter_max_attempts * self.waiter_delay),
|
1120
|
+
method_name="execute_complete",
|
1121
|
+
)
|
1122
|
+
|
1123
|
+
def execute_complete(self, context: Context, event: dict[str, Any] | None = None) -> None:
|
1124
|
+
if event is None or event["status"] != "success":
|
1125
|
+
raise AirflowException(f"Trigger error: Application failed to start, event is {event}")
|
1126
|
+
|
1127
|
+
self.log.info("Application %s started", event["application_id"])
|
1128
|
+
return event["application_id"]
|
1129
|
+
|
1082
1130
|
|
1083
1131
|
class EmrServerlessStartJobOperator(BaseOperator):
|
1084
1132
|
"""
|
@@ -1107,6 +1155,9 @@ class EmrServerlessStartJobOperator(BaseOperator):
|
|
1107
1155
|
:waiter_max_attempts: Number of times the waiter should poll the application to check the state.
|
1108
1156
|
If not set, the waiter will use its default value.
|
1109
1157
|
:param waiter_delay: Number of seconds between polling the state of the job run.
|
1158
|
+
:param deferrable: If True, the operator will wait asynchronously for the crawl to complete.
|
1159
|
+
This implies waiting for completion. This mode requires aiobotocore module to be installed.
|
1160
|
+
(default: False, but can be overridden in config file by setting default_deferrable to True)
|
1110
1161
|
"""
|
1111
1162
|
|
1112
1163
|
template_fields: Sequence[str] = (
|
@@ -1137,6 +1188,7 @@ class EmrServerlessStartJobOperator(BaseOperator):
|
|
1137
1188
|
waiter_check_interval_seconds: int | ArgNotSet = NOTSET,
|
1138
1189
|
waiter_max_attempts: int | ArgNotSet = NOTSET,
|
1139
1190
|
waiter_delay: int | ArgNotSet = NOTSET,
|
1191
|
+
deferrable: bool = conf.getboolean("operators", "default_deferrable", fallback=False),
|
1140
1192
|
**kwargs,
|
1141
1193
|
):
|
1142
1194
|
if waiter_check_interval_seconds is NOTSET:
|
@@ -1171,6 +1223,7 @@ class EmrServerlessStartJobOperator(BaseOperator):
|
|
1171
1223
|
self.waiter_max_attempts = int(waiter_max_attempts) # type: ignore[arg-type]
|
1172
1224
|
self.waiter_delay = int(waiter_delay) # type: ignore[arg-type]
|
1173
1225
|
self.job_id: str | None = None
|
1226
|
+
self.deferrable = deferrable
|
1174
1227
|
super().__init__(**kwargs)
|
1175
1228
|
|
1176
1229
|
self.client_request_token = client_request_token or str(uuid4())
|
@@ -1180,14 +1233,25 @@ class EmrServerlessStartJobOperator(BaseOperator):
|
|
1180
1233
|
"""Create and return an EmrServerlessHook."""
|
1181
1234
|
return EmrServerlessHook(aws_conn_id=self.aws_conn_id)
|
1182
1235
|
|
1183
|
-
def execute(self, context: Context) -> str | None:
|
1184
|
-
self.log.info("Starting job on Application: %s", self.application_id)
|
1236
|
+
def execute(self, context: Context, event: dict[str, Any] | None = None) -> str | None:
|
1185
1237
|
|
1186
1238
|
app_state = self.hook.conn.get_application(applicationId=self.application_id)["application"]["state"]
|
1187
1239
|
if app_state not in EmrServerlessHook.APPLICATION_SUCCESS_STATES:
|
1240
|
+
self.log.info("Application state is %s", app_state)
|
1241
|
+
self.log.info("Starting application %s", self.application_id)
|
1188
1242
|
self.hook.conn.start_application(applicationId=self.application_id)
|
1189
1243
|
waiter = self.hook.get_waiter("serverless_app_started")
|
1190
|
-
|
1244
|
+
if self.deferrable:
|
1245
|
+
self.defer(
|
1246
|
+
trigger=EmrServerlessStartApplicationTrigger(
|
1247
|
+
application_id=self.application_id,
|
1248
|
+
waiter_delay=self.waiter_delay,
|
1249
|
+
waiter_max_attempts=self.waiter_max_attempts,
|
1250
|
+
aws_conn_id=self.aws_conn_id,
|
1251
|
+
),
|
1252
|
+
method_name="execute",
|
1253
|
+
timeout=timedelta(seconds=self.waiter_max_attempts * self.waiter_delay),
|
1254
|
+
)
|
1191
1255
|
wait(
|
1192
1256
|
waiter=waiter,
|
1193
1257
|
waiter_max_attempts=self.waiter_max_attempts,
|
@@ -1197,7 +1261,7 @@ class EmrServerlessStartJobOperator(BaseOperator):
|
|
1197
1261
|
status_message="Serverless Application status is",
|
1198
1262
|
status_args=["application.state", "application.stateDetails"],
|
1199
1263
|
)
|
1200
|
-
|
1264
|
+
self.log.info("Starting job on Application: %s", self.application_id)
|
1201
1265
|
response = self.hook.conn.start_job_run(
|
1202
1266
|
clientToken=self.client_request_token,
|
1203
1267
|
applicationId=self.application_id,
|
@@ -1213,6 +1277,18 @@ class EmrServerlessStartJobOperator(BaseOperator):
|
|
1213
1277
|
|
1214
1278
|
self.job_id = response["jobRunId"]
|
1215
1279
|
self.log.info("EMR serverless job started: %s", self.job_id)
|
1280
|
+
if self.deferrable:
|
1281
|
+
self.defer(
|
1282
|
+
trigger=EmrServerlessStartJobTrigger(
|
1283
|
+
application_id=self.application_id,
|
1284
|
+
job_id=self.job_id,
|
1285
|
+
waiter_delay=self.waiter_delay,
|
1286
|
+
waiter_max_attempts=self.waiter_max_attempts,
|
1287
|
+
aws_conn_id=self.aws_conn_id,
|
1288
|
+
),
|
1289
|
+
method_name="execute_complete",
|
1290
|
+
timeout=timedelta(seconds=self.waiter_max_attempts * self.waiter_delay),
|
1291
|
+
)
|
1216
1292
|
if self.wait_for_completion:
|
1217
1293
|
waiter = self.hook.get_waiter("serverless_job_completed")
|
1218
1294
|
wait(
|
@@ -1227,8 +1303,20 @@ class EmrServerlessStartJobOperator(BaseOperator):
|
|
1227
1303
|
|
1228
1304
|
return self.job_id
|
1229
1305
|
|
1306
|
+
def execute_complete(self, context: Context, event: dict[str, Any] | None = None) -> None:
|
1307
|
+
if event is None:
|
1308
|
+
self.log.error("Trigger error: event is None")
|
1309
|
+
raise AirflowException("Trigger error: event is None")
|
1310
|
+
elif event["status"] == "success":
|
1311
|
+
self.log.info("Serverless job completed")
|
1312
|
+
return event["job_id"]
|
1313
|
+
|
1230
1314
|
def on_kill(self) -> None:
|
1231
|
-
"""
|
1315
|
+
"""
|
1316
|
+
Cancel the submitted job run.
|
1317
|
+
|
1318
|
+
Note: this method will not run in deferrable mode.
|
1319
|
+
"""
|
1232
1320
|
if self.job_id:
|
1233
1321
|
self.log.info("Stopping job run with jobId - %s", self.job_id)
|
1234
1322
|
response = self.hook.conn.cancel_job_run(applicationId=self.application_id, jobRunId=self.job_id)
|
@@ -1270,14 +1358,21 @@ class EmrServerlessStopApplicationOperator(BaseOperator):
|
|
1270
1358
|
:param application_id: ID of the EMR Serverless application to stop.
|
1271
1359
|
:param wait_for_completion: If true, wait for the Application to stop before returning. Default to True
|
1272
1360
|
:param aws_conn_id: AWS connection to use
|
1273
|
-
:param waiter_countdown: Total amount of time, in seconds, the operator will wait for
|
1361
|
+
:param waiter_countdown: (deprecated) Total amount of time, in seconds, the operator will wait for
|
1274
1362
|
the application be stopped. Defaults to 5 minutes.
|
1275
|
-
:param waiter_check_interval_seconds: Number of seconds between polling the state of the
|
1276
|
-
Defaults to
|
1363
|
+
:param waiter_check_interval_seconds: (deprecated) Number of seconds between polling the state of the
|
1364
|
+
application. Defaults to 60 seconds.
|
1277
1365
|
:param force_stop: If set to True, any job for that app that is not in a terminal state will be cancelled.
|
1278
1366
|
Otherwise, trying to stop an app with running jobs will return an error.
|
1279
1367
|
If you want to wait for the jobs to finish gracefully, use
|
1280
1368
|
:class:`airflow.providers.amazon.aws.sensors.emr.EmrServerlessJobSensor`
|
1369
|
+
:waiter_max_attempts: Number of times the waiter should poll the application to check the state.
|
1370
|
+
Default is 25.
|
1371
|
+
:param waiter_delay: Number of seconds between polling the state of the application.
|
1372
|
+
Default is 60 seconds.
|
1373
|
+
:param deferrable: If True, the operator will wait asynchronously for the application to stop.
|
1374
|
+
This implies waiting for completion. This mode requires aiobotocore module to be installed.
|
1375
|
+
(default: False, but can be overridden in config file by setting default_deferrable to True)
|
1281
1376
|
"""
|
1282
1377
|
|
1283
1378
|
template_fields: Sequence[str] = ("application_id",)
|
@@ -1292,6 +1387,7 @@ class EmrServerlessStopApplicationOperator(BaseOperator):
|
|
1292
1387
|
waiter_max_attempts: int | ArgNotSet = NOTSET,
|
1293
1388
|
waiter_delay: int | ArgNotSet = NOTSET,
|
1294
1389
|
force_stop: bool = False,
|
1390
|
+
deferrable: bool = conf.getboolean("operators", "default_deferrable", fallback=False),
|
1295
1391
|
**kwargs,
|
1296
1392
|
):
|
1297
1393
|
if waiter_check_interval_seconds is NOTSET:
|
@@ -1317,10 +1413,11 @@ class EmrServerlessStopApplicationOperator(BaseOperator):
|
|
1317
1413
|
)
|
1318
1414
|
self.aws_conn_id = aws_conn_id
|
1319
1415
|
self.application_id = application_id
|
1320
|
-
self.wait_for_completion = wait_for_completion
|
1416
|
+
self.wait_for_completion = False if deferrable else wait_for_completion
|
1321
1417
|
self.waiter_max_attempts = int(waiter_max_attempts) # type: ignore[arg-type]
|
1322
1418
|
self.waiter_delay = int(waiter_delay) # type: ignore[arg-type]
|
1323
1419
|
self.force_stop = force_stop
|
1420
|
+
self.deferrable = deferrable
|
1324
1421
|
super().__init__(**kwargs)
|
1325
1422
|
|
1326
1423
|
@cached_property
|
@@ -1332,16 +1429,46 @@ class EmrServerlessStopApplicationOperator(BaseOperator):
|
|
1332
1429
|
self.log.info("Stopping application: %s", self.application_id)
|
1333
1430
|
|
1334
1431
|
if self.force_stop:
|
1335
|
-
self.hook.cancel_running_jobs(
|
1336
|
-
self.application_id,
|
1337
|
-
|
1338
|
-
"Delay": self.waiter_delay,
|
1339
|
-
"MaxAttempts": self.waiter_max_attempts,
|
1340
|
-
},
|
1432
|
+
count = self.hook.cancel_running_jobs(
|
1433
|
+
application_id=self.application_id,
|
1434
|
+
wait_for_completion=False,
|
1341
1435
|
)
|
1436
|
+
if count > 0:
|
1437
|
+
self.log.info("now waiting for the %s cancelled job(s) to terminate", count)
|
1438
|
+
if self.deferrable:
|
1439
|
+
self.defer(
|
1440
|
+
trigger=EmrServerlessCancelJobsTrigger(
|
1441
|
+
application_id=self.application_id,
|
1442
|
+
aws_conn_id=self.aws_conn_id,
|
1443
|
+
waiter_delay=self.waiter_delay,
|
1444
|
+
waiter_max_attempts=self.waiter_max_attempts,
|
1445
|
+
),
|
1446
|
+
timeout=timedelta(seconds=self.waiter_max_attempts * self.waiter_delay),
|
1447
|
+
method_name="stop_application",
|
1448
|
+
)
|
1449
|
+
self.hook.get_waiter("no_job_running").wait(
|
1450
|
+
applicationId=self.application_id,
|
1451
|
+
states=list(self.hook.JOB_INTERMEDIATE_STATES.union({"CANCELLING"})),
|
1452
|
+
WaiterConfig={
|
1453
|
+
"Delay": self.waiter_delay,
|
1454
|
+
"MaxAttempts": self.waiter_max_attempts,
|
1455
|
+
},
|
1456
|
+
)
|
1457
|
+
else:
|
1458
|
+
self.log.info("no running jobs found with application ID %s", self.application_id)
|
1342
1459
|
|
1343
1460
|
self.hook.conn.stop_application(applicationId=self.application_id)
|
1344
|
-
|
1461
|
+
if self.deferrable:
|
1462
|
+
self.defer(
|
1463
|
+
trigger=EmrServerlessStopApplicationTrigger(
|
1464
|
+
application_id=self.application_id,
|
1465
|
+
aws_conn_id=self.aws_conn_id,
|
1466
|
+
waiter_delay=self.waiter_delay,
|
1467
|
+
waiter_max_attempts=self.waiter_max_attempts,
|
1468
|
+
),
|
1469
|
+
timeout=timedelta(seconds=self.waiter_max_attempts * self.waiter_delay),
|
1470
|
+
method_name="execute_complete",
|
1471
|
+
)
|
1345
1472
|
if self.wait_for_completion:
|
1346
1473
|
waiter = self.hook.get_waiter("serverless_app_stopped")
|
1347
1474
|
wait(
|
@@ -1355,6 +1482,30 @@ class EmrServerlessStopApplicationOperator(BaseOperator):
|
|
1355
1482
|
)
|
1356
1483
|
self.log.info("EMR serverless application %s stopped successfully", self.application_id)
|
1357
1484
|
|
1485
|
+
def stop_application(self, context: Context, event: dict[str, Any] | None = None) -> None:
|
1486
|
+
if event is None:
|
1487
|
+
self.log.error("Trigger error: event is None")
|
1488
|
+
raise AirflowException("Trigger error: event is None")
|
1489
|
+
elif event["status"] == "success":
|
1490
|
+
self.hook.conn.stop_application(applicationId=self.application_id)
|
1491
|
+
self.defer(
|
1492
|
+
trigger=EmrServerlessStopApplicationTrigger(
|
1493
|
+
application_id=self.application_id,
|
1494
|
+
aws_conn_id=self.aws_conn_id,
|
1495
|
+
waiter_delay=self.waiter_delay,
|
1496
|
+
waiter_max_attempts=self.waiter_max_attempts,
|
1497
|
+
),
|
1498
|
+
timeout=timedelta(seconds=self.waiter_max_attempts * self.waiter_delay),
|
1499
|
+
method_name="execute_complete",
|
1500
|
+
)
|
1501
|
+
|
1502
|
+
def execute_complete(self, context: Context, event: dict[str, Any] | None = None) -> None:
|
1503
|
+
if event is None:
|
1504
|
+
self.log.error("Trigger error: event is None")
|
1505
|
+
raise AirflowException("Trigger error: event is None")
|
1506
|
+
elif event["status"] == "success":
|
1507
|
+
self.log.info("EMR serverless application %s stopped successfully", self.application_id)
|
1508
|
+
|
1358
1509
|
|
1359
1510
|
class EmrServerlessDeleteApplicationOperator(EmrServerlessStopApplicationOperator):
|
1360
1511
|
"""
|
@@ -1368,10 +1519,17 @@ class EmrServerlessDeleteApplicationOperator(EmrServerlessStopApplicationOperato
|
|
1368
1519
|
:param wait_for_completion: If true, wait for the Application to be deleted before returning.
|
1369
1520
|
Defaults to True. Note that this operator will always wait for the application to be STOPPED first.
|
1370
1521
|
:param aws_conn_id: AWS connection to use
|
1371
|
-
:param waiter_countdown: Total amount of time, in seconds, the operator will wait for each
|
1372
|
-
the application to be stopped, and then deleted. Defaults to 25 minutes.
|
1373
|
-
:param waiter_check_interval_seconds: Number of seconds between polling the state
|
1522
|
+
:param waiter_countdown: (deprecated) Total amount of time, in seconds, the operator will wait for each
|
1523
|
+
step of first,the application to be stopped, and then deleted. Defaults to 25 minutes.
|
1524
|
+
:param waiter_check_interval_seconds: (deprecated) Number of seconds between polling the state
|
1525
|
+
of the application. Defaults to 60 seconds.
|
1526
|
+
:waiter_max_attempts: Number of times the waiter should poll the application to check the state.
|
1527
|
+
Defaults to 25.
|
1528
|
+
:param waiter_delay: Number of seconds between polling the state of the application.
|
1374
1529
|
Defaults to 60 seconds.
|
1530
|
+
:param deferrable: If True, the operator will wait asynchronously for application to be deleted.
|
1531
|
+
This implies waiting for completion. This mode requires aiobotocore module to be installed.
|
1532
|
+
(default: False, but can be overridden in config file by setting default_deferrable to True)
|
1375
1533
|
:param force_stop: If set to True, any job for that app that is not in a terminal state will be cancelled.
|
1376
1534
|
Otherwise, trying to delete an app with running jobs will return an error.
|
1377
1535
|
If you want to wait for the jobs to finish gracefully, use
|
@@ -1390,6 +1548,7 @@ class EmrServerlessDeleteApplicationOperator(EmrServerlessStopApplicationOperato
|
|
1390
1548
|
waiter_max_attempts: int | ArgNotSet = NOTSET,
|
1391
1549
|
waiter_delay: int | ArgNotSet = NOTSET,
|
1392
1550
|
force_stop: bool = False,
|
1551
|
+
deferrable: bool = conf.getboolean("operators", "default_deferrable", fallback=False),
|
1393
1552
|
**kwargs,
|
1394
1553
|
):
|
1395
1554
|
if waiter_check_interval_seconds is NOTSET:
|
@@ -1425,6 +1584,8 @@ class EmrServerlessDeleteApplicationOperator(EmrServerlessStopApplicationOperato
|
|
1425
1584
|
force_stop=force_stop,
|
1426
1585
|
**kwargs,
|
1427
1586
|
)
|
1587
|
+
self.deferrable = deferrable
|
1588
|
+
self.wait_for_delete_completion = False if deferrable else wait_for_completion
|
1428
1589
|
|
1429
1590
|
def execute(self, context: Context) -> None:
|
1430
1591
|
# super stops the app (or makes sure it's already stopped)
|
@@ -1436,7 +1597,19 @@ class EmrServerlessDeleteApplicationOperator(EmrServerlessStopApplicationOperato
|
|
1436
1597
|
if response["ResponseMetadata"]["HTTPStatusCode"] != 200:
|
1437
1598
|
raise AirflowException(f"Application deletion failed: {response}")
|
1438
1599
|
|
1439
|
-
if self.
|
1600
|
+
if self.deferrable:
|
1601
|
+
self.defer(
|
1602
|
+
trigger=EmrServerlessDeleteApplicationTrigger(
|
1603
|
+
application_id=self.application_id,
|
1604
|
+
aws_conn_id=self.aws_conn_id,
|
1605
|
+
waiter_delay=self.waiter_delay,
|
1606
|
+
waiter_max_attempts=self.waiter_max_attempts,
|
1607
|
+
),
|
1608
|
+
timeout=timedelta(seconds=self.waiter_max_attempts * self.waiter_delay),
|
1609
|
+
method_name="execute_complete",
|
1610
|
+
)
|
1611
|
+
|
1612
|
+
elif self.wait_for_delete_completion:
|
1440
1613
|
waiter = self.hook.get_waiter("serverless_app_terminated")
|
1441
1614
|
|
1442
1615
|
wait(
|
@@ -1450,3 +1623,10 @@ class EmrServerlessDeleteApplicationOperator(EmrServerlessStopApplicationOperato
|
|
1450
1623
|
)
|
1451
1624
|
|
1452
1625
|
self.log.info("EMR serverless application deleted")
|
1626
|
+
|
1627
|
+
def execute_complete(self, context: Context, event: dict[str, Any] | None = None) -> None:
|
1628
|
+
if event is None:
|
1629
|
+
self.log.error("Trigger error: event is None")
|
1630
|
+
raise AirflowException("Trigger error: event is None")
|
1631
|
+
elif event["status"] == "success":
|
1632
|
+
self.log.info("EMR serverless application %s deleted successfully", self.application_id)
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
2
|
+
# or more contributor license agreements. See the NOTICE file
|
3
|
+
# distributed with this work for additional information
|
4
|
+
# regarding copyright ownership. The ASF licenses this file
|
5
|
+
# to you under the Apache License, Version 2.0 (the
|
6
|
+
# "License"); you may not use this file except in compliance
|
7
|
+
# with the License. You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
from __future__ import annotations
|
18
|
+
|
19
|
+
from functools import cached_property
|
20
|
+
from typing import TYPE_CHECKING, Sequence
|
21
|
+
|
22
|
+
from airflow import AirflowException
|
23
|
+
from airflow.models import BaseOperator
|
24
|
+
from airflow.providers.amazon.aws.hooks.eventbridge import EventBridgeHook
|
25
|
+
from airflow.providers.amazon.aws.utils import trim_none_values
|
26
|
+
|
27
|
+
if TYPE_CHECKING:
|
28
|
+
from airflow.utils.context import Context
|
29
|
+
|
30
|
+
|
31
|
+
class EventBridgePutEventsOperator(BaseOperator):
|
32
|
+
"""
|
33
|
+
Put Events onto Amazon EventBridge.
|
34
|
+
|
35
|
+
:param entries: the list of events to be put onto EventBridge, each event is a dict (required)
|
36
|
+
:param endpoint_id: the URL subdomain of the endpoint
|
37
|
+
:param aws_conn_id: the AWS connection to use
|
38
|
+
:param region_name: the region where events are to be sent
|
39
|
+
|
40
|
+
"""
|
41
|
+
|
42
|
+
template_fields: Sequence[str] = ("entries", "endpoint_id", "aws_conn_id", "region_name")
|
43
|
+
|
44
|
+
def __init__(
|
45
|
+
self,
|
46
|
+
*,
|
47
|
+
entries: list[dict],
|
48
|
+
endpoint_id: str | None = None,
|
49
|
+
aws_conn_id: str = "aws_default",
|
50
|
+
region_name: str | None = None,
|
51
|
+
**kwargs,
|
52
|
+
):
|
53
|
+
super().__init__(**kwargs)
|
54
|
+
self.entries = entries
|
55
|
+
self.endpoint_id = endpoint_id
|
56
|
+
self.aws_conn_id = aws_conn_id
|
57
|
+
self.region_name = region_name
|
58
|
+
|
59
|
+
@cached_property
|
60
|
+
def hook(self) -> EventBridgeHook:
|
61
|
+
"""Create and return an EventBridgeHook."""
|
62
|
+
return EventBridgeHook(aws_conn_id=self.aws_conn_id, region_name=self.region_name)
|
63
|
+
|
64
|
+
def execute(self, context: Context):
|
65
|
+
|
66
|
+
response = self.hook.conn.put_events(
|
67
|
+
**trim_none_values(
|
68
|
+
{
|
69
|
+
"Entries": self.entries,
|
70
|
+
"EndpointId": self.endpoint_id,
|
71
|
+
}
|
72
|
+
)
|
73
|
+
)
|
74
|
+
|
75
|
+
self.log.info("Sent %d events to EventBridge.", len(self.entries))
|
76
|
+
|
77
|
+
if response.get("FailedEntryCount"):
|
78
|
+
for event in response["Entries"]:
|
79
|
+
if "ErrorCode" in event:
|
80
|
+
self.log.error(event)
|
81
|
+
|
82
|
+
raise AirflowException(
|
83
|
+
f"{response['FailedEntryCount']} entries in this request have failed to send."
|
84
|
+
)
|
85
|
+
|
86
|
+
if self.do_xcom_push:
|
87
|
+
return [e["EventId"] for e in response["Entries"]]
|