apache-airflow-providers-amazon 9.6.1rc1__py3-none-any.whl → 9.7.1a1__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 +3 -3
- airflow/providers/amazon/aws/auth_manager/avp/entities.py +1 -1
- airflow/providers/amazon/aws/auth_manager/avp/schema.json +33 -7
- airflow/providers/amazon/aws/auth_manager/aws_auth_manager.py +8 -5
- airflow/providers/amazon/aws/auth_manager/cli/avp_commands.py +6 -9
- airflow/providers/amazon/aws/auth_manager/cli/definition.py +2 -12
- airflow/providers/amazon/aws/auth_manager/datamodels/login.py +26 -0
- airflow/providers/amazon/aws/auth_manager/routes/__init__.py +16 -0
- airflow/providers/amazon/aws/auth_manager/{router → routes}/login.py +29 -10
- airflow/providers/amazon/aws/executors/batch/batch_executor.py +1 -5
- airflow/providers/amazon/aws/executors/ecs/ecs_executor.py +1 -6
- airflow/providers/amazon/aws/hooks/redshift_sql.py +1 -4
- airflow/providers/amazon/aws/operators/emr.py +147 -142
- airflow/providers/amazon/aws/operators/glue.py +56 -48
- airflow/providers/amazon/aws/queues/__init__.py +16 -0
- airflow/providers/amazon/aws/queues/sqs.py +52 -0
- airflow/providers/amazon/aws/sensors/emr.py +49 -52
- airflow/providers/amazon/get_provider_info.py +2 -7
- airflow/providers/amazon/version_compat.py +0 -1
- {apache_airflow_providers_amazon-9.6.1rc1.dist-info → apache_airflow_providers_amazon-9.7.1a1.dist-info}/METADATA +17 -11
- {apache_airflow_providers_amazon-9.6.1rc1.dist-info → apache_airflow_providers_amazon-9.7.1a1.dist-info}/RECORD +24 -20
- /airflow/providers/amazon/aws/auth_manager/{router → datamodels}/__init__.py +0 -0
- {apache_airflow_providers_amazon-9.6.1rc1.dist-info → apache_airflow_providers_amazon-9.7.1a1.dist-info}/WHEEL +0 -0
- {apache_airflow_providers_amazon-9.6.1rc1.dist-info → apache_airflow_providers_amazon-9.7.1a1.dist-info}/entry_points.txt +0 -0
@@ -29,11 +29,11 @@ from airflow import __version__ as airflow_version
|
|
29
29
|
|
30
30
|
__all__ = ["__version__"]
|
31
31
|
|
32
|
-
__version__ = "9.
|
32
|
+
__version__ = "9.7.0"
|
33
33
|
|
34
34
|
if packaging.version.parse(packaging.version.parse(airflow_version).base_version) < packaging.version.parse(
|
35
|
-
"2.
|
35
|
+
"2.10.0"
|
36
36
|
):
|
37
37
|
raise RuntimeError(
|
38
|
-
f"The package `apache-airflow-providers-amazon:{__version__}` needs Apache Airflow 2.
|
38
|
+
f"The package `apache-airflow-providers-amazon:{__version__}` needs Apache Airflow 2.10.0+"
|
39
39
|
)
|
@@ -1,6 +1,36 @@
|
|
1
1
|
{
|
2
2
|
"Airflow": {
|
3
3
|
"actions": {
|
4
|
+
"Asset.GET": {
|
5
|
+
"appliesTo": {
|
6
|
+
"principalTypes": ["User"],
|
7
|
+
"resourceTypes": ["Asset"]
|
8
|
+
}
|
9
|
+
},
|
10
|
+
"AssetAlias.GET": {
|
11
|
+
"appliesTo": {
|
12
|
+
"principalTypes": ["User"],
|
13
|
+
"resourceTypes": ["AssetAlias"]
|
14
|
+
}
|
15
|
+
},
|
16
|
+
"Backfill.GET": {
|
17
|
+
"appliesTo": {
|
18
|
+
"principalTypes": ["User"],
|
19
|
+
"resourceTypes": ["Backfill"]
|
20
|
+
}
|
21
|
+
},
|
22
|
+
"Backfill.POST": {
|
23
|
+
"appliesTo": {
|
24
|
+
"principalTypes": ["User"],
|
25
|
+
"resourceTypes": ["Backfill"]
|
26
|
+
}
|
27
|
+
},
|
28
|
+
"Backfill.PUT": {
|
29
|
+
"appliesTo": {
|
30
|
+
"principalTypes": ["User"],
|
31
|
+
"resourceTypes": ["Backfill"]
|
32
|
+
}
|
33
|
+
},
|
4
34
|
"Connection.DELETE": {
|
5
35
|
"appliesTo": {
|
6
36
|
"principalTypes": ["User"],
|
@@ -115,12 +145,6 @@
|
|
115
145
|
}
|
116
146
|
}
|
117
147
|
},
|
118
|
-
"Dataset.GET": {
|
119
|
-
"appliesTo": {
|
120
|
-
"principalTypes": ["User"],
|
121
|
-
"resourceTypes": ["Dataset"]
|
122
|
-
}
|
123
|
-
},
|
124
148
|
"Menu.MENU": {
|
125
149
|
"appliesTo": {
|
126
150
|
"principalTypes": ["User"],
|
@@ -183,11 +207,13 @@
|
|
183
207
|
}
|
184
208
|
},
|
185
209
|
"entityTypes": {
|
210
|
+
"Asset": {},
|
211
|
+
"AssetAlias": {},
|
212
|
+
"Backfill": {},
|
186
213
|
"Configuration": {},
|
187
214
|
"Connection": {},
|
188
215
|
"Custom": {},
|
189
216
|
"Dag": {},
|
190
|
-
"Dataset": {},
|
191
217
|
"Menu": {},
|
192
218
|
"Pool": {},
|
193
219
|
"Group": {},
|
@@ -72,13 +72,11 @@ class AwsAuthManager(BaseAuthManager[AwsAuthManagerUser]):
|
|
72
72
|
authentication and authorization in Airflow.
|
73
73
|
"""
|
74
74
|
|
75
|
-
def
|
75
|
+
def init(self) -> None:
|
76
76
|
if not AIRFLOW_V_3_0_PLUS:
|
77
77
|
raise AirflowOptionalProviderFeatureException(
|
78
78
|
"AWS auth manager is only compatible with Airflow versions >= 3.0.0"
|
79
79
|
)
|
80
|
-
|
81
|
-
super().__init__()
|
82
80
|
self._check_avp_schema_version()
|
83
81
|
|
84
82
|
@cached_property
|
@@ -90,7 +88,12 @@ class AwsAuthManager(BaseAuthManager[AwsAuthManagerUser]):
|
|
90
88
|
return conf.get("api", "base_url", fallback="/")
|
91
89
|
|
92
90
|
def deserialize_user(self, token: dict[str, Any]) -> AwsAuthManagerUser:
|
93
|
-
return AwsAuthManagerUser(
|
91
|
+
return AwsAuthManagerUser(
|
92
|
+
user_id=token.pop("sub"),
|
93
|
+
groups=token.get("groups", []),
|
94
|
+
username=token.get("username"),
|
95
|
+
email=token.get("email"),
|
96
|
+
)
|
94
97
|
|
95
98
|
def serialize_user(self, user: AwsAuthManagerUser) -> dict[str, Any]:
|
96
99
|
return {
|
@@ -364,7 +367,7 @@ class AwsAuthManager(BaseAuthManager[AwsAuthManagerUser]):
|
|
364
367
|
]
|
365
368
|
|
366
369
|
def get_fastapi_app(self) -> FastAPI | None:
|
367
|
-
from airflow.providers.amazon.aws.auth_manager.
|
370
|
+
from airflow.providers.amazon.aws.auth_manager.routes.login import login_router
|
368
371
|
|
369
372
|
app = FastAPI(
|
370
373
|
title="AWS auth manager sub application",
|
@@ -14,8 +14,6 @@
|
|
14
14
|
# KIND, either express or implied. See the License for the
|
15
15
|
# specific language governing permissions and limitations
|
16
16
|
# under the License.
|
17
|
-
"""User sub-commands."""
|
18
|
-
|
19
17
|
from __future__ import annotations
|
20
18
|
|
21
19
|
import logging
|
@@ -71,6 +69,8 @@ def init_avp(args):
|
|
71
69
|
@providers_configuration_loaded
|
72
70
|
def update_schema(args):
|
73
71
|
"""Update Amazon Verified Permissions policy store schema."""
|
72
|
+
if not args.policy_store_id:
|
73
|
+
raise ValueError("Parameter '--policy-store-id' is required.")
|
74
74
|
client = _get_client()
|
75
75
|
_set_schema(client, args.policy_store_id, args)
|
76
76
|
|
@@ -98,9 +98,8 @@ def _create_policy_store(client: BaseClient, args) -> tuple[str | None, bool]:
|
|
98
98
|
if policy_store.get("description") == args.policy_store_description
|
99
99
|
]
|
100
100
|
|
101
|
-
|
102
|
-
|
103
|
-
log.debug("Existing policy stores found: %s", existing_policy_stores)
|
101
|
+
log.debug("Policy stores found: %s", policy_stores)
|
102
|
+
log.debug("Existing policy stores found: %s", existing_policy_stores)
|
104
103
|
|
105
104
|
if len(existing_policy_stores) > 0:
|
106
105
|
print(
|
@@ -118,8 +117,7 @@ def _create_policy_store(client: BaseClient, args) -> tuple[str | None, bool]:
|
|
118
117
|
},
|
119
118
|
description=args.policy_store_description,
|
120
119
|
)
|
121
|
-
|
122
|
-
log.debug("Response from create_policy_store: %s", response)
|
120
|
+
log.debug("Response from create_policy_store: %s", response)
|
123
121
|
|
124
122
|
print(f"Policy store created: '{response['policyStoreId']}'")
|
125
123
|
|
@@ -141,7 +139,6 @@ def _set_schema(client: BaseClient, policy_store_id: str, args) -> None:
|
|
141
139
|
},
|
142
140
|
)
|
143
141
|
|
144
|
-
|
145
|
-
log.debug("Response from put_schema: %s", response)
|
142
|
+
log.debug("Response from put_schema: %s", response)
|
146
143
|
|
147
144
|
print("Policy store schema updated.")
|
@@ -27,22 +27,12 @@ from airflow.cli.cli_config import (
|
|
27
27
|
# # ARGS # #
|
28
28
|
############
|
29
29
|
|
30
|
-
ARG_VERBOSE = Arg(("-v", "--verbose"), help="Make logging output more verbose", action="store_true")
|
31
|
-
|
32
30
|
ARG_DRY_RUN = Arg(
|
33
31
|
("--dry-run",),
|
34
32
|
help="Perform a dry run",
|
35
33
|
action="store_true",
|
36
34
|
)
|
37
35
|
|
38
|
-
# AWS IAM Identity Center
|
39
|
-
ARG_INSTANCE_NAME = Arg(("--instance-name",), help="Instance name in Identity Center", default="Airflow")
|
40
|
-
|
41
|
-
ARG_APPLICATION_NAME = Arg(
|
42
|
-
("--application-name",), help="Application name in Identity Center", default="Airflow"
|
43
|
-
)
|
44
|
-
|
45
|
-
|
46
36
|
# Amazon Verified Permissions
|
47
37
|
ARG_POLICY_STORE_DESCRIPTION = Arg(
|
48
38
|
("--policy-store-description",), help="Policy store description", default="Airflow"
|
@@ -59,12 +49,12 @@ AWS_AUTH_MANAGER_COMMANDS = (
|
|
59
49
|
name="init-avp",
|
60
50
|
help="Initialize Amazon Verified resources to be used by AWS manager",
|
61
51
|
func=lazy_load_command("airflow.providers.amazon.aws.auth_manager.cli.avp_commands.init_avp"),
|
62
|
-
args=(ARG_POLICY_STORE_DESCRIPTION, ARG_DRY_RUN
|
52
|
+
args=(ARG_POLICY_STORE_DESCRIPTION, ARG_DRY_RUN),
|
63
53
|
),
|
64
54
|
ActionCommand(
|
65
55
|
name="update-avp-schema",
|
66
56
|
help="Update Amazon Verified permissions policy store schema to the latest version in 'airflow/providers/amazon/aws/auth_manager/avp/schema.json'",
|
67
57
|
func=lazy_load_command("airflow.providers.amazon.aws.auth_manager.cli.avp_commands.update_schema"),
|
68
|
-
args=(ARG_POLICY_STORE_ID, ARG_DRY_RUN
|
58
|
+
args=(ARG_POLICY_STORE_ID, ARG_DRY_RUN),
|
69
59
|
),
|
70
60
|
)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#
|
2
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
3
|
+
# or more contributor license agreements. See the NOTICE file
|
4
|
+
# distributed with this work for additional information
|
5
|
+
# regarding copyright ownership. The ASF licenses this file
|
6
|
+
# to you under the Apache License, Version 2.0 (the
|
7
|
+
# "License"); you may not use this file except in compliance
|
8
|
+
# with the License. You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing,
|
13
|
+
# software distributed under the License is distributed on an
|
14
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
15
|
+
# KIND, either express or implied. See the License for the
|
16
|
+
# specific language governing permissions and limitations
|
17
|
+
# under the License.
|
18
|
+
from __future__ import annotations
|
19
|
+
|
20
|
+
from airflow.api_fastapi.core_api.base import BaseModel
|
21
|
+
|
22
|
+
|
23
|
+
class LoginResponse(BaseModel):
|
24
|
+
"""Login serializer for responses."""
|
25
|
+
|
26
|
+
access_token: str
|
@@ -0,0 +1,16 @@
|
|
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.
|
@@ -25,11 +25,15 @@ 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
|
28
|
+
from airflow.api_fastapi.app import (
|
29
|
+
AUTH_MANAGER_FASTAPI_APP_PREFIX,
|
30
|
+
get_auth_manager,
|
31
|
+
)
|
29
32
|
from airflow.api_fastapi.auth.managers.base_auth_manager import COOKIE_NAME_JWT_TOKEN
|
30
33
|
from airflow.api_fastapi.common.router import AirflowRouter
|
31
34
|
from airflow.configuration import conf
|
32
35
|
from airflow.providers.amazon.aws.auth_manager.constants import CONF_SAML_METADATA_URL_KEY, CONF_SECTION_NAME
|
36
|
+
from airflow.providers.amazon.aws.auth_manager.datamodels.login import LoginResponse
|
33
37
|
from airflow.providers.amazon.aws.auth_manager.user import AwsAuthManagerUser
|
34
38
|
|
35
39
|
try:
|
@@ -49,9 +53,17 @@ login_router = AirflowRouter(tags=["AWSAuthManagerLogin"])
|
|
49
53
|
|
50
54
|
@login_router.get("/login")
|
51
55
|
def login(request: Request):
|
52
|
-
"""
|
56
|
+
"""Initiate the authentication."""
|
53
57
|
saml_auth = _init_saml_auth(request)
|
54
|
-
callback_url = saml_auth.login()
|
58
|
+
callback_url = saml_auth.login("login-redirect")
|
59
|
+
return RedirectResponse(url=callback_url)
|
60
|
+
|
61
|
+
|
62
|
+
@login_router.get("/login/token")
|
63
|
+
def login_token(request: Request) -> RedirectResponse:
|
64
|
+
"""Initiate the authentication to create a token."""
|
65
|
+
saml_auth = _init_saml_auth(request)
|
66
|
+
callback_url = saml_auth.login("login-token")
|
55
67
|
return RedirectResponse(url=callback_url)
|
56
68
|
|
57
69
|
|
@@ -76,22 +88,29 @@ def login_callback(request: Request):
|
|
76
88
|
attributes = saml_auth.get_attributes()
|
77
89
|
user = AwsAuthManagerUser(
|
78
90
|
user_id=attributes["id"][0],
|
79
|
-
groups=attributes["groups"],
|
91
|
+
groups=attributes["groups"] or [],
|
80
92
|
username=saml_auth.get_nameid(),
|
81
93
|
email=attributes["email"][0] if "email" in attributes else None,
|
82
94
|
)
|
83
95
|
url = conf.get("api", "base_url", fallback="/")
|
84
96
|
token = get_auth_manager().generate_jwt(user)
|
85
|
-
response = RedirectResponse(url=url, status_code=303)
|
86
97
|
|
87
|
-
|
88
|
-
|
89
|
-
|
98
|
+
form_data = anyio.from_thread.run(request.form)
|
99
|
+
relay_state = form_data["RelayState"]
|
100
|
+
|
101
|
+
if relay_state == "login-redirect":
|
102
|
+
response = RedirectResponse(url=url, status_code=303)
|
103
|
+
secure = bool(conf.get("api", "ssl_cert", fallback=""))
|
104
|
+
response.set_cookie(COOKIE_NAME_JWT_TOKEN, token, secure=secure)
|
105
|
+
return response
|
106
|
+
if relay_state == "login-token":
|
107
|
+
return LoginResponse(access_token=token)
|
108
|
+
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, f"Invalid relay state: {relay_state}")
|
90
109
|
|
91
110
|
|
92
111
|
def _init_saml_auth(request: Request) -> OneLogin_Saml2_Auth:
|
93
112
|
request_data = _prepare_request(request)
|
94
|
-
base_url = conf.get(section="api", key="base_url"
|
113
|
+
base_url = conf.get(section="api", key="base_url")
|
95
114
|
settings = {
|
96
115
|
# We want to keep this flag on in case of errors.
|
97
116
|
# It provides an error reasons, if turned off, it does not
|
@@ -99,7 +118,7 @@ def _init_saml_auth(request: Request) -> OneLogin_Saml2_Auth:
|
|
99
118
|
"sp": {
|
100
119
|
"entityId": "aws-auth-manager-saml-client",
|
101
120
|
"assertionConsumerService": {
|
102
|
-
"url": f"{base_url}{AUTH_MANAGER_FASTAPI_APP_PREFIX}/login_callback",
|
121
|
+
"url": f"{base_url.rstrip('/')}{AUTH_MANAGER_FASTAPI_APP_PREFIX}/login_callback",
|
103
122
|
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
104
123
|
},
|
105
124
|
},
|
@@ -324,11 +324,7 @@ class AwsBatchExecutor(BaseExecutor):
|
|
324
324
|
exec_config=exec_config,
|
325
325
|
attempt_number=attempt_number,
|
326
326
|
)
|
327
|
-
|
328
|
-
# TODO: Remove this when min_airflow_version is 2.10.0 or higher in Amazon provider.
|
329
|
-
# running_state is added in Airflow 2.10 and only needed to support task adoption
|
330
|
-
# (an optional executor feature).
|
331
|
-
self.running_state(key, job_id)
|
327
|
+
self.running_state(key, job_id)
|
332
328
|
|
333
329
|
def _describe_jobs(self, job_ids) -> list[BatchJob]:
|
334
330
|
all_jobs = []
|
@@ -23,7 +23,6 @@ Each Airflow task gets delegated out to an Amazon ECS Task.
|
|
23
23
|
|
24
24
|
from __future__ import annotations
|
25
25
|
|
26
|
-
import contextlib
|
27
26
|
import time
|
28
27
|
from collections import defaultdict, deque
|
29
28
|
from collections.abc import Sequence
|
@@ -450,11 +449,7 @@ class AwsEcsExecutor(BaseExecutor):
|
|
450
449
|
else:
|
451
450
|
task = run_task_response["tasks"][0]
|
452
451
|
self.active_workers.add_task(task, task_key, queue, cmd, exec_config, attempt_number)
|
453
|
-
|
454
|
-
# running_state is newly added, and only needed to support task adoption (an optional
|
455
|
-
# executor feature).
|
456
|
-
# TODO: remove when min airflow version >= 2.9.2
|
457
|
-
self.running_state(task_key, task.task_arn)
|
452
|
+
self.running_state(task_key, task.task_arn)
|
458
453
|
|
459
454
|
def _run_task(
|
460
455
|
self, task_id: TaskInstanceKey, cmd: CommandType, queue: str, exec_config: ExecutorConfigType
|
@@ -26,7 +26,6 @@ from sqlalchemy.engine.url import URL
|
|
26
26
|
|
27
27
|
from airflow.exceptions import AirflowException
|
28
28
|
from airflow.providers.amazon.aws.hooks.base_aws import AwsBaseHook
|
29
|
-
from airflow.providers.amazon.version_compat import AIRFLOW_V_2_10_PLUS
|
30
29
|
from airflow.providers.common.sql.hooks.sql import DbApiHook
|
31
30
|
|
32
31
|
if TYPE_CHECKING:
|
@@ -260,6 +259,4 @@ class RedshiftSQLHook(DbApiHook):
|
|
260
259
|
|
261
260
|
def get_openlineage_default_schema(self) -> str | None:
|
262
261
|
"""Return current schema. This is usually changed with ``SEARCH_PATH`` parameter."""
|
263
|
-
|
264
|
-
return self.get_first("SELECT CURRENT_SCHEMA();")[0]
|
265
|
-
return super().get_openlineage_default_schema()
|
262
|
+
return self.get_first("SELECT CURRENT_SCHEMA();")[0]
|