apache-airflow-providers-amazon 9.5.0rc1__py3-none-any.whl → 9.5.0rc2__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/aws/auth_manager/avp/entities.py +2 -0
- airflow/providers/amazon/aws/auth_manager/aws_auth_manager.py +56 -5
- airflow/providers/amazon/aws/auth_manager/router/login.py +8 -4
- airflow/providers/amazon/aws/hooks/appflow.py +5 -15
- airflow/providers/amazon/aws/hooks/base_aws.py +9 -1
- airflow/providers/amazon/aws/hooks/eks.py +3 -6
- airflow/providers/amazon/aws/hooks/s3.py +3 -1
- airflow/providers/amazon/aws/links/base_aws.py +2 -1
- airflow/providers/amazon/aws/notifications/chime.py +1 -2
- airflow/providers/amazon/aws/notifications/sns.py +1 -1
- airflow/providers/amazon/aws/notifications/sqs.py +1 -1
- airflow/providers/amazon/aws/operators/ec2.py +91 -83
- airflow/providers/amazon/aws/operators/mwaa.py +73 -2
- airflow/providers/amazon/aws/operators/sagemaker.py +1 -2
- airflow/providers/amazon/aws/sensors/ec2.py +5 -12
- airflow/providers/amazon/aws/sensors/mwaa.py +58 -11
- airflow/providers/amazon/aws/transfers/redshift_to_s3.py +19 -4
- airflow/providers/amazon/aws/transfers/s3_to_redshift.py +19 -3
- airflow/providers/amazon/aws/triggers/base.py +10 -1
- airflow/providers/amazon/aws/triggers/mwaa.py +128 -0
- airflow/providers/amazon/aws/utils/waiter_with_logging.py +4 -3
- airflow/providers/amazon/aws/waiters/mwaa.json +36 -0
- airflow/providers/amazon/get_provider_info.py +9 -3
- {apache_airflow_providers_amazon-9.5.0rc1.dist-info → apache_airflow_providers_amazon-9.5.0rc2.dist-info}/METADATA +9 -7
- {apache_airflow_providers_amazon-9.5.0rc1.dist-info → apache_airflow_providers_amazon-9.5.0rc2.dist-info}/RECORD +27 -25
- {apache_airflow_providers_amazon-9.5.0rc1.dist-info → apache_airflow_providers_amazon-9.5.0rc2.dist-info}/WHEEL +0 -0
- {apache_airflow_providers_amazon-9.5.0rc1.dist-info → apache_airflow_providers_amazon-9.5.0rc2.dist-info}/entry_points.txt +0 -0
@@ -21,12 +21,15 @@ from collections import defaultdict
|
|
21
21
|
from collections.abc import Sequence
|
22
22
|
from functools import cached_property
|
23
23
|
from typing import TYPE_CHECKING, Any, cast
|
24
|
+
from urllib.parse import urljoin
|
24
25
|
|
25
26
|
from fastapi import FastAPI
|
26
27
|
|
28
|
+
from airflow.api_fastapi.app import AUTH_MANAGER_FASTAPI_APP_PREFIX
|
27
29
|
from airflow.api_fastapi.auth.managers.base_auth_manager import BaseAuthManager
|
28
30
|
from airflow.api_fastapi.auth.managers.models.resource_details import (
|
29
31
|
AccessView,
|
32
|
+
BackfillDetails,
|
30
33
|
ConnectionDetails,
|
31
34
|
DagAccessEntity,
|
32
35
|
DagDetails,
|
@@ -55,7 +58,12 @@ if TYPE_CHECKING:
|
|
55
58
|
IsAuthorizedPoolRequest,
|
56
59
|
IsAuthorizedVariableRequest,
|
57
60
|
)
|
58
|
-
from airflow.api_fastapi.auth.managers.models.resource_details import
|
61
|
+
from airflow.api_fastapi.auth.managers.models.resource_details import (
|
62
|
+
AssetAliasDetails,
|
63
|
+
AssetDetails,
|
64
|
+
ConfigurationDetails,
|
65
|
+
)
|
66
|
+
from airflow.api_fastapi.common.types import MenuItem
|
59
67
|
|
60
68
|
|
61
69
|
class AwsAuthManager(BaseAuthManager[AwsAuthManagerUser]):
|
@@ -84,11 +92,11 @@ class AwsAuthManager(BaseAuthManager[AwsAuthManagerUser]):
|
|
84
92
|
return conf.get("api", "base_url")
|
85
93
|
|
86
94
|
def deserialize_user(self, token: dict[str, Any]) -> AwsAuthManagerUser:
|
87
|
-
return AwsAuthManagerUser(**token)
|
95
|
+
return AwsAuthManagerUser(user_id=token.pop("sub"), **token)
|
88
96
|
|
89
97
|
def serialize_user(self, user: AwsAuthManagerUser) -> dict[str, Any]:
|
90
98
|
return {
|
91
|
-
"
|
99
|
+
"sub": user.get_id(),
|
92
100
|
"groups": user.get_groups(),
|
93
101
|
"username": user.username,
|
94
102
|
"email": user.email,
|
@@ -150,6 +158,14 @@ class AwsAuthManager(BaseAuthManager[AwsAuthManagerUser]):
|
|
150
158
|
context=context,
|
151
159
|
)
|
152
160
|
|
161
|
+
def is_authorized_backfill(
|
162
|
+
self, *, method: ResourceMethod, user: AwsAuthManagerUser, details: BackfillDetails | None = None
|
163
|
+
) -> bool:
|
164
|
+
backfill_id = details.id if details else None
|
165
|
+
return self.avp_facade.is_authorized(
|
166
|
+
method=method, entity_type=AvpEntities.BACKFILL, user=user, entity_id=backfill_id
|
167
|
+
)
|
168
|
+
|
153
169
|
def is_authorized_asset(
|
154
170
|
self, *, method: ResourceMethod, user: AwsAuthManagerUser, details: AssetDetails | None = None
|
155
171
|
) -> bool:
|
@@ -158,6 +174,14 @@ class AwsAuthManager(BaseAuthManager[AwsAuthManagerUser]):
|
|
158
174
|
method=method, entity_type=AvpEntities.ASSET, user=user, entity_id=asset_id
|
159
175
|
)
|
160
176
|
|
177
|
+
def is_authorized_asset_alias(
|
178
|
+
self, *, method: ResourceMethod, user: AwsAuthManagerUser, details: AssetAliasDetails | None = None
|
179
|
+
) -> bool:
|
180
|
+
asset_alias_id = details.id if details else None
|
181
|
+
return self.avp_facade.is_authorized(
|
182
|
+
method=method, entity_type=AvpEntities.ASSET_ALIAS, user=user, entity_id=asset_alias_id
|
183
|
+
)
|
184
|
+
|
161
185
|
def is_authorized_pool(
|
162
186
|
self, *, method: ResourceMethod, user: AwsAuthManagerUser, details: PoolDetails | None = None
|
163
187
|
) -> bool:
|
@@ -203,6 +227,25 @@ class AwsAuthManager(BaseAuthManager[AwsAuthManagerUser]):
|
|
203
227
|
entity_id=resource_name,
|
204
228
|
)
|
205
229
|
|
230
|
+
def filter_authorized_menu_items(
|
231
|
+
self, menu_items: list[MenuItem], *, user: AwsAuthManagerUser
|
232
|
+
) -> list[MenuItem]:
|
233
|
+
requests: dict[str, IsAuthorizedRequest] = {}
|
234
|
+
for menu_item in menu_items:
|
235
|
+
requests[menu_item.value] = self._get_menu_item_request(menu_item.value)
|
236
|
+
|
237
|
+
batch_is_authorized_results = self.avp_facade.get_batch_is_authorized_results(
|
238
|
+
requests=list(requests.values()), user=user
|
239
|
+
)
|
240
|
+
|
241
|
+
def _has_access_to_menu_item(request: IsAuthorizedRequest):
|
242
|
+
result = self.avp_facade.get_batch_is_authorized_single_result(
|
243
|
+
batch_is_authorized_results=batch_is_authorized_results, request=request, user=user
|
244
|
+
)
|
245
|
+
return result["decision"] == "ALLOW"
|
246
|
+
|
247
|
+
return [menu_item for menu_item in menu_items if _has_access_to_menu_item(requests[menu_item.value])]
|
248
|
+
|
206
249
|
def batch_is_authorized_connection(
|
207
250
|
self,
|
208
251
|
requests: Sequence[IsAuthorizedConnectionRequest],
|
@@ -278,7 +321,7 @@ class AwsAuthManager(BaseAuthManager[AwsAuthManagerUser]):
|
|
278
321
|
]
|
279
322
|
return self.avp_facade.batch_is_authorized(requests=facade_requests, user=user)
|
280
323
|
|
281
|
-
def
|
324
|
+
def filter_authorized_dag_ids(
|
282
325
|
self,
|
283
326
|
*,
|
284
327
|
dag_ids: set[str],
|
@@ -309,7 +352,7 @@ class AwsAuthManager(BaseAuthManager[AwsAuthManagerUser]):
|
|
309
352
|
return {dag_id for dag_id in dag_ids if _has_access_to_dag(requests[dag_id][method])}
|
310
353
|
|
311
354
|
def get_url_login(self, **kwargs) -> str:
|
312
|
-
return f"{
|
355
|
+
return urljoin(self.apiserver_endpoint, f"{AUTH_MANAGER_FASTAPI_APP_PREFIX}/login")
|
313
356
|
|
314
357
|
@staticmethod
|
315
358
|
def get_cli_commands() -> list[CLICommand]:
|
@@ -337,6 +380,14 @@ class AwsAuthManager(BaseAuthManager[AwsAuthManagerUser]):
|
|
337
380
|
|
338
381
|
return app
|
339
382
|
|
383
|
+
@staticmethod
|
384
|
+
def _get_menu_item_request(menu_item_text: str) -> IsAuthorizedRequest:
|
385
|
+
return {
|
386
|
+
"method": "MENU",
|
387
|
+
"entity_type": AvpEntities.MENU,
|
388
|
+
"entity_id": menu_item_text,
|
389
|
+
}
|
390
|
+
|
340
391
|
def _check_avp_schema_version(self):
|
341
392
|
if not self.avp_facade.is_policy_store_schema_up_to_date():
|
342
393
|
self.log.warning(
|
@@ -25,7 +25,8 @@ from fastapi import HTTPException, Request
|
|
25
25
|
from starlette import status
|
26
26
|
from starlette.responses import RedirectResponse
|
27
27
|
|
28
|
-
from airflow.api_fastapi.app import get_auth_manager
|
28
|
+
from airflow.api_fastapi.app import AUTH_MANAGER_FASTAPI_APP_PREFIX, get_auth_manager
|
29
|
+
from airflow.api_fastapi.auth.managers.base_auth_manager import COOKIE_NAME_JWT_TOKEN
|
29
30
|
from airflow.api_fastapi.common.router import AirflowRouter
|
30
31
|
from airflow.configuration import conf
|
31
32
|
from airflow.providers.amazon.aws.auth_manager.constants import CONF_SAML_METADATA_URL_KEY, CONF_SECTION_NAME
|
@@ -79,8 +80,11 @@ def login_callback(request: Request):
|
|
79
80
|
username=saml_auth.get_nameid(),
|
80
81
|
email=attributes["email"][0] if "email" in attributes else None,
|
81
82
|
)
|
82
|
-
url =
|
83
|
-
|
83
|
+
url = conf.get("api", "base_url")
|
84
|
+
token = get_auth_manager().generate_jwt(user)
|
85
|
+
response = RedirectResponse(url=url, status_code=303)
|
86
|
+
response.set_cookie(COOKIE_NAME_JWT_TOKEN, token, secure=True)
|
87
|
+
return response
|
84
88
|
|
85
89
|
|
86
90
|
def _init_saml_auth(request: Request) -> OneLogin_Saml2_Auth:
|
@@ -93,7 +97,7 @@ def _init_saml_auth(request: Request) -> OneLogin_Saml2_Auth:
|
|
93
97
|
"sp": {
|
94
98
|
"entityId": "aws-auth-manager-saml-client",
|
95
99
|
"assertionConsumerService": {
|
96
|
-
"url": f"{base_url}/
|
100
|
+
"url": f"{base_url}{AUTH_MANAGER_FASTAPI_APP_PREFIX}/login_callback",
|
97
101
|
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
98
102
|
},
|
99
103
|
},
|
@@ -16,15 +16,7 @@
|
|
16
16
|
# under the License.
|
17
17
|
from __future__ import annotations
|
18
18
|
|
19
|
-
from
|
20
|
-
from typing import TYPE_CHECKING, cast
|
21
|
-
|
22
|
-
from mypy_boto3_appflow.type_defs import (
|
23
|
-
DestinationFlowConfigTypeDef,
|
24
|
-
SourceFlowConfigTypeDef,
|
25
|
-
TaskTypeDef,
|
26
|
-
TriggerConfigTypeDef,
|
27
|
-
)
|
19
|
+
from typing import TYPE_CHECKING
|
28
20
|
|
29
21
|
from airflow.providers.amazon.aws.hooks.base_aws import AwsGenericHook
|
30
22
|
from airflow.providers.amazon.aws.utils.waiter_with_logging import wait
|
@@ -125,11 +117,9 @@ class AppflowHook(AwsGenericHook["AppflowClient"]):
|
|
125
117
|
|
126
118
|
self.conn.update_flow(
|
127
119
|
flowName=response["flowName"],
|
128
|
-
destinationFlowConfigList=
|
129
|
-
|
130
|
-
|
131
|
-
sourceFlowConfig=cast(SourceFlowConfigTypeDef, response["sourceFlowConfig"]),
|
132
|
-
triggerConfig=cast(TriggerConfigTypeDef, response["triggerConfig"]),
|
120
|
+
destinationFlowConfigList=response["destinationFlowConfigList"],
|
121
|
+
sourceFlowConfig=response["sourceFlowConfig"],
|
122
|
+
triggerConfig=response["triggerConfig"],
|
133
123
|
description=response.get("description", "Flow description."),
|
134
|
-
tasks=
|
124
|
+
tasks=tasks,
|
135
125
|
)
|
@@ -943,6 +943,7 @@ class AwsGenericHook(BaseHook, Generic[BaseAwsConnection]):
|
|
943
943
|
self,
|
944
944
|
waiter_name: str,
|
945
945
|
parameters: dict[str, str] | None = None,
|
946
|
+
config_overrides: dict[str, Any] | None = None,
|
946
947
|
deferrable: bool = False,
|
947
948
|
client=None,
|
948
949
|
) -> Waiter:
|
@@ -962,6 +963,9 @@ class AwsGenericHook(BaseHook, Generic[BaseAwsConnection]):
|
|
962
963
|
:param parameters: will scan the waiter config for the keys of that dict,
|
963
964
|
and replace them with the corresponding value. If a custom waiter has
|
964
965
|
such keys to be expanded, they need to be provided here.
|
966
|
+
Note: cannot be used if parameters are included in config_overrides
|
967
|
+
:param config_overrides: will update values of provided keys in the waiter's
|
968
|
+
config. Only specified keys will be updated.
|
965
969
|
:param deferrable: If True, the waiter is going to be an async custom waiter.
|
966
970
|
An async client must be provided in that case.
|
967
971
|
:param client: The client to use for the waiter's operations
|
@@ -970,14 +974,18 @@ class AwsGenericHook(BaseHook, Generic[BaseAwsConnection]):
|
|
970
974
|
|
971
975
|
if deferrable and not client:
|
972
976
|
raise ValueError("client must be provided for a deferrable waiter.")
|
977
|
+
if parameters is not None and config_overrides is not None and "acceptors" in config_overrides:
|
978
|
+
raise ValueError('parameters must be None when "acceptors" is included in config_overrides')
|
973
979
|
# Currently, the custom waiter doesn't work with resource_type, only client_type is supported.
|
974
980
|
client = client or self._client
|
975
981
|
if self.waiter_path and (waiter_name in self._list_custom_waiters()):
|
976
982
|
# Technically if waiter_name is in custom_waiters then self.waiter_path must
|
977
983
|
# exist but MyPy doesn't like the fact that self.waiter_path could be None.
|
978
984
|
with open(self.waiter_path) as config_file:
|
979
|
-
config = json.loads(config_file.read())
|
985
|
+
config: dict = json.loads(config_file.read())
|
980
986
|
|
987
|
+
if config_overrides is not None:
|
988
|
+
config["waiters"][waiter_name].update(config_overrides)
|
981
989
|
config = self._apply_parameters_value(config, waiter_name, parameters)
|
982
990
|
return BaseBotoWaiter(client=client, model_config=config, deferrable=deferrable).waiter(
|
983
991
|
waiter_name
|
@@ -35,7 +35,6 @@ from botocore.signers import RequestSigner
|
|
35
35
|
from airflow.providers.amazon.aws.hooks.base_aws import AwsBaseHook
|
36
36
|
from airflow.providers.amazon.aws.hooks.sts import StsHook
|
37
37
|
from airflow.utils import yaml
|
38
|
-
from airflow.utils.json import AirflowJsonEncoder
|
39
38
|
|
40
39
|
DEFAULT_PAGINATION_TOKEN = ""
|
41
40
|
STS_TOKEN_EXPIRES_IN = 60
|
@@ -315,7 +314,7 @@ class EksHook(AwsBaseHook):
|
|
315
314
|
)
|
316
315
|
if verbose:
|
317
316
|
cluster_data = response.get("cluster")
|
318
|
-
self.log.info("Amazon EKS cluster details: %s", json.dumps(cluster_data,
|
317
|
+
self.log.info("Amazon EKS cluster details: %s", json.dumps(cluster_data, default=repr))
|
319
318
|
return response
|
320
319
|
|
321
320
|
def describe_nodegroup(self, clusterName: str, nodegroupName: str, verbose: bool = False) -> dict:
|
@@ -343,7 +342,7 @@ class EksHook(AwsBaseHook):
|
|
343
342
|
nodegroup_data = response.get("nodegroup")
|
344
343
|
self.log.info(
|
345
344
|
"Amazon EKS managed node group details: %s",
|
346
|
-
json.dumps(nodegroup_data,
|
345
|
+
json.dumps(nodegroup_data, default=repr),
|
347
346
|
)
|
348
347
|
return response
|
349
348
|
|
@@ -374,9 +373,7 @@ class EksHook(AwsBaseHook):
|
|
374
373
|
)
|
375
374
|
if verbose:
|
376
375
|
fargate_profile_data = response.get("fargateProfile")
|
377
|
-
self.log.info(
|
378
|
-
"AWS Fargate profile details: %s", json.dumps(fargate_profile_data, cls=AirflowJsonEncoder)
|
379
|
-
)
|
376
|
+
self.log.info("AWS Fargate profile details: %s", json.dumps(fargate_profile_data, default=repr))
|
380
377
|
return response
|
381
378
|
|
382
379
|
def get_cluster_state(self, clusterName: str) -> ClusterStates:
|
@@ -1494,7 +1494,9 @@ class S3Hook(AwsBaseHook):
|
|
1494
1494
|
get_hook_lineage_collector().add_output_asset(
|
1495
1495
|
context=self,
|
1496
1496
|
scheme="file",
|
1497
|
-
asset_kwargs={
|
1497
|
+
asset_kwargs={
|
1498
|
+
"path": str(file_path) if file_path.is_absolute() else str(file_path.absolute())
|
1499
|
+
},
|
1498
1500
|
)
|
1499
1501
|
file = open(file_path, "wb")
|
1500
1502
|
else:
|
@@ -19,7 +19,6 @@ from __future__ import annotations
|
|
19
19
|
|
20
20
|
from typing import TYPE_CHECKING, ClassVar
|
21
21
|
|
22
|
-
from airflow.models import XCom
|
23
22
|
from airflow.providers.amazon.aws.utils.suppress import return_on_error
|
24
23
|
from airflow.providers.amazon.version_compat import AIRFLOW_V_3_0_PLUS
|
25
24
|
|
@@ -30,7 +29,9 @@ if TYPE_CHECKING:
|
|
30
29
|
|
31
30
|
if AIRFLOW_V_3_0_PLUS:
|
32
31
|
from airflow.sdk import BaseOperatorLink
|
32
|
+
from airflow.sdk.execution_time.xcom import XCom
|
33
33
|
else:
|
34
|
+
from airflow.models import XCom # type: ignore[no-redef]
|
34
35
|
from airflow.models.baseoperatorlink import BaseOperatorLink # type: ignore[no-redef]
|
35
36
|
|
36
37
|
|
@@ -21,12 +21,11 @@ from functools import cached_property
|
|
21
21
|
from typing import TYPE_CHECKING
|
22
22
|
|
23
23
|
from airflow.providers.amazon.aws.hooks.chime import ChimeWebhookHook
|
24
|
+
from airflow.providers.common.compat.notifier import BaseNotifier
|
24
25
|
|
25
26
|
if TYPE_CHECKING:
|
26
27
|
from airflow.utils.context import Context
|
27
28
|
|
28
|
-
from airflow.notifications.basenotifier import BaseNotifier
|
29
|
-
|
30
29
|
|
31
30
|
class ChimeNotifier(BaseNotifier):
|
32
31
|
"""
|
@@ -20,8 +20,8 @@ from __future__ import annotations
|
|
20
20
|
from collections.abc import Sequence
|
21
21
|
from functools import cached_property
|
22
22
|
|
23
|
-
from airflow.notifications.basenotifier import BaseNotifier
|
24
23
|
from airflow.providers.amazon.aws.hooks.sns import SnsHook
|
24
|
+
from airflow.providers.common.compat.notifier import BaseNotifier
|
25
25
|
|
26
26
|
|
27
27
|
class SnsNotifier(BaseNotifier):
|
@@ -20,8 +20,8 @@ from __future__ import annotations
|
|
20
20
|
from collections.abc import Sequence
|
21
21
|
from functools import cached_property
|
22
22
|
|
23
|
-
from airflow.notifications.basenotifier import BaseNotifier
|
24
23
|
from airflow.providers.amazon.aws.hooks.sqs import SqsHook
|
24
|
+
from airflow.providers.common.compat.notifier import BaseNotifier
|
25
25
|
|
26
26
|
|
27
27
|
class SqsNotifier(BaseNotifier):
|