cornflow 1.2.1__py3-none-any.whl → 1.2.3__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.
- cornflow/app.py +4 -2
- cornflow/cli/__init__.py +4 -0
- cornflow/cli/actions.py +4 -0
- cornflow/cli/config.py +4 -0
- cornflow/cli/migrations.py +13 -8
- cornflow/cli/permissions.py +4 -0
- cornflow/cli/roles.py +5 -1
- cornflow/cli/schemas.py +5 -0
- cornflow/cli/service.py +263 -131
- cornflow/cli/tools/api_generator.py +13 -10
- cornflow/cli/tools/endpoint_tools.py +191 -196
- cornflow/cli/tools/models_tools.py +87 -60
- cornflow/cli/tools/schema_generator.py +161 -67
- cornflow/cli/tools/schemas_tools.py +4 -5
- cornflow/cli/users.py +8 -0
- cornflow/cli/views.py +4 -0
- cornflow/commands/access.py +14 -3
- cornflow/commands/auxiliar.py +106 -0
- cornflow/commands/dag.py +3 -2
- cornflow/commands/permissions.py +186 -81
- cornflow/commands/roles.py +15 -14
- cornflow/commands/schemas.py +6 -4
- cornflow/commands/users.py +12 -17
- cornflow/commands/views.py +171 -41
- cornflow/endpoints/dag.py +27 -25
- cornflow/endpoints/data_check.py +128 -165
- cornflow/endpoints/example_data.py +9 -3
- cornflow/endpoints/execution.py +40 -34
- cornflow/endpoints/health.py +7 -7
- cornflow/endpoints/instance.py +39 -12
- cornflow/endpoints/meta_resource.py +4 -5
- cornflow/schemas/execution.py +9 -1
- cornflow/schemas/health.py +1 -0
- cornflow/shared/authentication/auth.py +76 -45
- cornflow/shared/const.py +10 -1
- cornflow/shared/exceptions.py +3 -1
- cornflow/shared/utils_tables.py +36 -8
- cornflow/shared/validators.py +1 -1
- cornflow/tests/const.py +1 -0
- cornflow/tests/custom_test_case.py +4 -4
- cornflow/tests/unit/test_alarms.py +1 -2
- cornflow/tests/unit/test_cases.py +4 -7
- cornflow/tests/unit/test_executions.py +22 -1
- cornflow/tests/unit/test_external_role_creation.py +785 -0
- cornflow/tests/unit/test_health.py +4 -1
- cornflow/tests/unit/test_log_in.py +46 -9
- cornflow/tests/unit/test_tables.py +3 -3
- {cornflow-1.2.1.dist-info → cornflow-1.2.3.dist-info}/METADATA +2 -2
- {cornflow-1.2.1.dist-info → cornflow-1.2.3.dist-info}/RECORD +52 -50
- {cornflow-1.2.1.dist-info → cornflow-1.2.3.dist-info}/WHEEL +1 -1
- {cornflow-1.2.1.dist-info → cornflow-1.2.3.dist-info}/entry_points.txt +0 -0
- {cornflow-1.2.1.dist-info → cornflow-1.2.3.dist-info}/top_level.txt +0 -0
cornflow/commands/permissions.py
CHANGED
@@ -1,88 +1,99 @@
|
|
1
|
-
import
|
2
|
-
from
|
1
|
+
from flask import current_app
|
2
|
+
from sqlalchemy.exc import DBAPIError, IntegrityError
|
3
3
|
|
4
|
-
from cornflow.
|
5
|
-
|
6
|
-
|
4
|
+
from cornflow.commands.auxiliar import (
|
5
|
+
get_all_external,
|
6
|
+
get_all_resources,
|
7
7
|
)
|
8
8
|
from cornflow.models import ViewModel, PermissionViewRoleModel
|
9
9
|
from cornflow.shared import db
|
10
|
-
from
|
11
|
-
from
|
10
|
+
from cornflow.shared.const import ALL_DEFAULT_ROLES, GET_ACTION
|
11
|
+
from cornflow.shared.const import (
|
12
|
+
BASE_PERMISSION_ASSIGNATION,
|
13
|
+
)
|
12
14
|
|
13
15
|
|
14
16
|
def register_base_permissions_command(external_app: str = None, verbose: bool = False):
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
exit()
|
27
|
-
|
17
|
+
"""
|
18
|
+
Register base permissions for the application.
|
19
|
+
external_app: If provided, it will register the permissions for the external app.
|
20
|
+
verbose: If True, it will print the permissions that are being registered.
|
21
|
+
"""
|
22
|
+
# Get all resources, extra permissions, and custom roles actions
|
23
|
+
resources_to_register, extra_permissions, custom_roles_actions = get_all_external(
|
24
|
+
external_app
|
25
|
+
)
|
26
|
+
|
27
|
+
# Get all views in the database
|
28
28
|
views_in_db = {view.name: view.id for view in ViewModel.get_all_objects()}
|
29
|
-
permissions_in_db =
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
permissions_in_db, permissions_in_db_keys = get_db_permissions()
|
30
|
+
|
31
|
+
# Get all resources and roles with access
|
32
|
+
resources_roles_with_access = get_all_resources(resources_to_register)
|
33
|
+
|
34
|
+
# Get the new roles and base permissions assignation
|
35
|
+
base_permissions_assignation = get_base_permissions(
|
36
|
+
resources_roles_with_access, custom_roles_actions
|
37
|
+
)
|
38
|
+
# Get the permissions to register and delete
|
39
|
+
permissions_tuples = get_permissions_in_code_as_tuples(
|
40
|
+
resources_to_register,
|
41
|
+
views_in_db,
|
42
|
+
base_permissions_assignation,
|
43
|
+
extra_permissions,
|
44
|
+
)
|
45
|
+
permissions_to_register = get_permissions_to_register(
|
46
|
+
permissions_tuples, permissions_in_db_keys
|
47
|
+
)
|
48
|
+
permissions_to_delete = get_permissions_to_delete(
|
49
|
+
permissions_tuples, resources_roles_with_access.keys(), permissions_in_db
|
50
|
+
)
|
51
|
+
|
52
|
+
# Save the new permissions in the data
|
53
|
+
save_and_delete_permissions(permissions_to_register, permissions_to_delete)
|
34
54
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
"role_id": role,
|
40
|
-
"action_id": action,
|
41
|
-
"api_view_id": views_in_db[view["endpoint"]],
|
42
|
-
}
|
43
|
-
)
|
44
|
-
for role, action in BASE_PERMISSION_ASSIGNATION
|
45
|
-
for view in resources_to_register
|
46
|
-
if role in view["resource"].ROLES_WITH_ACCESS
|
47
|
-
] + [
|
48
|
-
PermissionViewRoleModel(
|
49
|
-
{
|
50
|
-
"role_id": role,
|
51
|
-
"action_id": action,
|
52
|
-
"api_view_id": views_in_db[endpoint],
|
53
|
-
}
|
54
|
-
)
|
55
|
-
for role, action, endpoint in EXTRA_PERMISSION_ASSIGNATION
|
56
|
-
]
|
55
|
+
if len(permissions_to_register) > 0:
|
56
|
+
current_app.logger.info(f"Permissions registered: {permissions_to_register}")
|
57
|
+
else:
|
58
|
+
current_app.logger.info("No new permissions to register")
|
57
59
|
|
58
|
-
|
59
|
-
|
60
|
-
|
60
|
+
if len(permissions_to_delete) > 0:
|
61
|
+
current_app.logger.info(f"Permissions deleted: {permissions_to_delete}")
|
62
|
+
else:
|
63
|
+
current_app.logger.info("No permissions to delete")
|
61
64
|
|
62
|
-
permissions_to_register = [
|
63
|
-
permission
|
64
|
-
for permission in permissions_in_app
|
65
|
-
if (permission.role_id, permission.action_id, permission.api_view_id)
|
66
|
-
not in permissions_in_db_keys
|
67
|
-
]
|
68
65
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
66
|
+
def save_new_roles(new_roles_to_add):
|
67
|
+
"""
|
68
|
+
Save the new roles in the database.
|
69
|
+
new_roles_to_add: List of new roles to add.
|
70
|
+
"""
|
71
|
+
if len(new_roles_to_add) > 0:
|
72
|
+
db.session.bulk_save_objects(new_roles_to_add)
|
73
|
+
try:
|
74
|
+
db.session.commit()
|
75
|
+
except IntegrityError as e:
|
76
|
+
db.session.rollback()
|
77
|
+
current_app.logger.error(
|
78
|
+
f"Integrity error on base permissions register: {e}"
|
79
|
+
)
|
80
|
+
except DBAPIError as e:
|
81
|
+
db.session.rollback()
|
82
|
+
current_app.logger.error(f"Unknown error on base permissions register: {e}")
|
83
|
+
|
76
84
|
|
85
|
+
def save_and_delete_permissions(permissions_to_register, permissions_to_delete):
|
86
|
+
"""
|
87
|
+
Save and delete permissions in the database.
|
88
|
+
permissions_to_register: List of permissions to register.
|
89
|
+
permissions_to_delete: List of permissions to delete.
|
90
|
+
"""
|
77
91
|
if len(permissions_to_register) > 0:
|
78
92
|
db.session.bulk_save_objects(permissions_to_register)
|
79
93
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
# for permission in permissions_to_delete:
|
84
|
-
# db.session.delete(permission)
|
85
|
-
|
94
|
+
if len(permissions_to_delete) > 0:
|
95
|
+
for permission in permissions_to_delete:
|
96
|
+
db.session.delete(permission)
|
86
97
|
try:
|
87
98
|
db.session.commit()
|
88
99
|
except IntegrityError as e:
|
@@ -105,25 +116,119 @@ def register_base_permissions_command(external_app: str = None, verbose: bool =
|
|
105
116
|
f"Unknown error on base permissions sequence updating: {e}"
|
106
117
|
)
|
107
118
|
|
108
|
-
if verbose:
|
109
|
-
if len(permissions_to_register) > 0:
|
110
|
-
current_app.logger.info(
|
111
|
-
f"Permissions registered: {permissions_to_register}"
|
112
|
-
)
|
113
|
-
else:
|
114
|
-
current_app.logger.info("No new permissions to register")
|
115
119
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
+
def get_permissions_to_delete(permissions_tuples, resources_names, permissions_in_db):
|
121
|
+
"""
|
122
|
+
Get the permissions to delete.
|
123
|
+
"""
|
124
|
+
permissions_to_delete = [
|
125
|
+
permission
|
126
|
+
for permission in permissions_in_db
|
127
|
+
if (permission.role_id, permission.action_id, permission.api_view_id)
|
128
|
+
not in permissions_tuples
|
129
|
+
]
|
130
|
+
|
131
|
+
return permissions_to_delete
|
132
|
+
|
133
|
+
|
134
|
+
def get_permissions_to_register(permissions_tuples, permissions_in_db_keys):
|
135
|
+
"""
|
136
|
+
Get the permissions to register.
|
137
|
+
"""
|
138
|
+
# Convert set of tuples to list of PermissionViewRoleModel objects
|
139
|
+
return [
|
140
|
+
PermissionViewRoleModel(
|
141
|
+
{
|
142
|
+
"role_id": role_id,
|
143
|
+
"action_id": action_id,
|
144
|
+
"api_view_id": api_view_id,
|
145
|
+
}
|
146
|
+
)
|
147
|
+
for role_id, action_id, api_view_id in permissions_tuples
|
148
|
+
if (role_id, action_id, api_view_id) not in permissions_in_db_keys
|
149
|
+
]
|
150
|
+
|
151
|
+
|
152
|
+
def get_permissions_in_code_as_tuples(
|
153
|
+
resources_to_register, views_in_db, base_permissions_assignation, extra_permissions
|
154
|
+
):
|
155
|
+
"""
|
156
|
+
Get the permissions in code as tuples.
|
157
|
+
"""
|
158
|
+
# Create base permissions using a set to avoid duplicates
|
159
|
+
permissions_tuples = set()
|
160
|
+
|
161
|
+
# Add permissions from ROLES_WITH_ACCESS
|
162
|
+
for role, action in base_permissions_assignation:
|
163
|
+
for view in resources_to_register:
|
164
|
+
if role in view["resource"].ROLES_WITH_ACCESS:
|
165
|
+
permissions_tuples.add((role, action, views_in_db[view["endpoint"]]))
|
166
|
+
|
167
|
+
# Add permissions from extra_permissions
|
168
|
+
for role, action, endpoint in extra_permissions:
|
169
|
+
if endpoint in views_in_db:
|
170
|
+
permissions_tuples.add((role, action, views_in_db[endpoint]))
|
171
|
+
|
172
|
+
return permissions_tuples
|
173
|
+
|
174
|
+
|
175
|
+
def get_base_permissions(resources_roles_with_access, custom_roles_actions):
|
176
|
+
"""
|
177
|
+
Get the new roles and base permissions assignation.
|
178
|
+
resources_roles_with_access: Dictionary of resources and roles with access.
|
179
|
+
custom_roles_actions: Dictionary mapping custom roles to their allowed actions.
|
180
|
+
"""
|
181
|
+
# Get all custom roles (both new and existing) that appear in ROLES_WITH_ACCESS
|
182
|
+
all_custom_roles_in_access = set(
|
183
|
+
[
|
184
|
+
role
|
185
|
+
for roles in resources_roles_with_access.values()
|
186
|
+
for role in roles
|
187
|
+
if role not in ALL_DEFAULT_ROLES
|
188
|
+
]
|
189
|
+
)
|
190
|
+
|
191
|
+
# Validate that all custom roles are defined in custom_roles_actions
|
192
|
+
undefined_roles = all_custom_roles_in_access - set(custom_roles_actions.keys())
|
193
|
+
if undefined_roles:
|
194
|
+
raise ValueError(
|
195
|
+
f"The following custom roles are used in code but not defined in CUSTOM_ROLES_ACTIONS: {undefined_roles}. "
|
196
|
+
f"Please define their allowed actions in the CUSTOM_ROLES_ACTIONS dictionary in shared/const.py."
|
197
|
+
)
|
198
|
+
|
199
|
+
# Create extended permission assignation including all custom roles
|
200
|
+
# For custom roles, use the actions defined in custom_roles_actions
|
201
|
+
custom_permissions = [
|
202
|
+
(custom_role, action)
|
203
|
+
for custom_role in all_custom_roles_in_access
|
204
|
+
for action in custom_roles_actions[custom_role]
|
205
|
+
]
|
206
|
+
|
207
|
+
base_permissions_assignation = BASE_PERMISSION_ASSIGNATION + custom_permissions
|
208
|
+
|
209
|
+
return base_permissions_assignation
|
210
|
+
|
211
|
+
|
212
|
+
def get_db_permissions():
|
213
|
+
"""
|
214
|
+
Get all permissions in the database.
|
215
|
+
"""
|
216
|
+
permissions_in_db = [perm for perm in PermissionViewRoleModel.get_all_objects()]
|
217
|
+
permissions_in_db_keys = [
|
218
|
+
(perm.role_id, perm.action_id, perm.api_view_id) for perm in permissions_in_db
|
219
|
+
]
|
120
220
|
|
121
|
-
return
|
221
|
+
return permissions_in_db, permissions_in_db_keys
|
122
222
|
|
123
223
|
|
124
224
|
def register_dag_permissions_command(
|
125
225
|
open_deployment: int = None, verbose: bool = False
|
126
226
|
):
|
227
|
+
"""
|
228
|
+
Register DAG permissions.
|
229
|
+
open_deployment: If 1, it will register the permissions for the open deployment.
|
230
|
+
verbose: If True, it will print the permissions that are being registered.
|
231
|
+
"""
|
127
232
|
|
128
233
|
from flask import current_app
|
129
234
|
from sqlalchemy.exc import DBAPIError, IntegrityError
|
cornflow/commands/roles.py
CHANGED
@@ -1,22 +1,23 @@
|
|
1
|
-
def register_roles_command(verbose: bool = True):
|
1
|
+
def register_roles_command(external_app: str = None, verbose: bool = True):
|
2
2
|
|
3
3
|
from sqlalchemy.exc import DBAPIError, IntegrityError
|
4
4
|
from flask import current_app
|
5
5
|
|
6
|
-
from cornflow.models import RoleModel
|
7
|
-
from cornflow.shared.const import ROLES_MAP
|
8
6
|
from cornflow.shared import db
|
7
|
+
from cornflow.commands.auxiliar import (
|
8
|
+
get_all_external,
|
9
|
+
get_all_resources,
|
10
|
+
get_new_roles_to_add,
|
11
|
+
)
|
9
12
|
|
10
|
-
|
13
|
+
resources_to_register, extra_permissions, _ = get_all_external(external_app)
|
14
|
+
resources_roles_with_access = get_all_resources(resources_to_register)
|
15
|
+
new_roles_to_add = get_new_roles_to_add(
|
16
|
+
extra_permissions, resources_roles_with_access
|
17
|
+
)
|
11
18
|
|
12
|
-
|
13
|
-
|
14
|
-
for key, value in ROLES_MAP.items()
|
15
|
-
if value not in roles_registered
|
16
|
-
]
|
17
|
-
|
18
|
-
if len(roles_to_register) > 0:
|
19
|
-
db.session.bulk_save_objects(roles_to_register)
|
19
|
+
if len(new_roles_to_add) > 0:
|
20
|
+
db.session.bulk_save_objects(new_roles_to_add)
|
20
21
|
|
21
22
|
try:
|
22
23
|
db.session.commit()
|
@@ -38,8 +39,8 @@ def register_roles_command(verbose: bool = True):
|
|
38
39
|
current_app.logger.error(f"Unknown error on roles sequence updating: {e}")
|
39
40
|
|
40
41
|
if verbose:
|
41
|
-
if len(
|
42
|
-
current_app.logger.info(f"Roles registered: {
|
42
|
+
if len(new_roles_to_add) > 0:
|
43
|
+
current_app.logger.info(f"Roles registered: {new_roles_to_add}")
|
43
44
|
else:
|
44
45
|
current_app.logger.info("No new roles to be registered")
|
45
46
|
|
cornflow/commands/schemas.py
CHANGED
@@ -3,6 +3,7 @@ def update_schemas_command(url, user, pwd, verbose: bool = False):
|
|
3
3
|
from flask import current_app
|
4
4
|
|
5
5
|
from cornflow_client.airflow.api import Airflow
|
6
|
+
from cornflow.shared.const import AIRFLOW_NOT_REACHABLE_MSG
|
6
7
|
|
7
8
|
af_client = Airflow(url, user, pwd)
|
8
9
|
max_attempts = 20
|
@@ -10,12 +11,12 @@ def update_schemas_command(url, user, pwd, verbose: bool = False):
|
|
10
11
|
while not af_client.is_alive() and attempts < max_attempts:
|
11
12
|
attempts += 1
|
12
13
|
if verbose == 1:
|
13
|
-
current_app.logger.info(f"
|
14
|
+
current_app.logger.info(f"{AIRFLOW_NOT_REACHABLE_MSG} (attempt {attempts})")
|
14
15
|
time.sleep(15)
|
15
16
|
|
16
17
|
if not af_client.is_alive():
|
17
18
|
if verbose == 1:
|
18
|
-
current_app.logger.info("
|
19
|
+
current_app.logger.info(f"{AIRFLOW_NOT_REACHABLE_MSG}")
|
19
20
|
return False
|
20
21
|
|
21
22
|
response = af_client.update_schemas()
|
@@ -34,6 +35,7 @@ def update_dag_registry_command(url, user, pwd, verbose: bool = False):
|
|
34
35
|
from flask import current_app
|
35
36
|
|
36
37
|
from cornflow_client.airflow.api import Airflow
|
38
|
+
from cornflow.shared.const import AIRFLOW_NOT_REACHABLE_MSG
|
37
39
|
|
38
40
|
af_client = Airflow(url, user, pwd)
|
39
41
|
max_attempts = 20
|
@@ -41,12 +43,12 @@ def update_dag_registry_command(url, user, pwd, verbose: bool = False):
|
|
41
43
|
while not af_client.is_alive() and attempts < max_attempts:
|
42
44
|
attempts += 1
|
43
45
|
if verbose == 1:
|
44
|
-
current_app.logger.info(f"
|
46
|
+
current_app.logger.info(f"{AIRFLOW_NOT_REACHABLE_MSG} (attempt {attempts})")
|
45
47
|
time.sleep(15)
|
46
48
|
|
47
49
|
if not af_client.is_alive():
|
48
50
|
if verbose == 1:
|
49
|
-
current_app.logger.info("
|
51
|
+
current_app.logger.info(f"{AIRFLOW_NOT_REACHABLE_MSG}")
|
50
52
|
return False
|
51
53
|
|
52
54
|
response = af_client.update_dag_registry()
|
cornflow/commands/users.py
CHANGED
@@ -16,19 +16,16 @@ def create_user_with_role(
|
|
16
16
|
current_app.logger.info(
|
17
17
|
f"User {username} is created and assigned {role_name} role"
|
18
18
|
)
|
19
|
-
return
|
19
|
+
return
|
20
20
|
|
21
21
|
user_roles = UserRoleModel.get_all_objects(user_id=user.id)
|
22
22
|
user_actual_roles = [ur.role for ur in user_roles]
|
23
|
-
if (
|
24
|
-
user_roles is not None
|
25
|
-
and RoleModel.get_one_object(role) in user_actual_roles
|
26
|
-
):
|
23
|
+
if user_roles is not None and RoleModel.get_one_object(role) in user_actual_roles:
|
27
24
|
if verbose:
|
28
25
|
current_app.logger.info(
|
29
26
|
f"User {username} exists and already has {role_name} role assigned"
|
30
27
|
)
|
31
|
-
return
|
28
|
+
return
|
32
29
|
|
33
30
|
user_role = UserRoleModel({"user_id": user.id, "role_id": role})
|
34
31
|
user_role.save()
|
@@ -36,7 +33,6 @@ def create_user_with_role(
|
|
36
33
|
current_app.logger.info(
|
37
34
|
f"User {username} already exists and is assigned a {role_name} role"
|
38
35
|
)
|
39
|
-
return True
|
40
36
|
|
41
37
|
|
42
38
|
def create_service_user_command(username, email, password, verbose: bool = True):
|
@@ -45,8 +41,9 @@ def create_service_user_command(username, email, password, verbose: bool = True)
|
|
45
41
|
|
46
42
|
if username is None or email is None or password is None:
|
47
43
|
current_app.logger.info("Missing required arguments")
|
48
|
-
return
|
49
|
-
|
44
|
+
return
|
45
|
+
|
46
|
+
create_user_with_role(
|
50
47
|
username, email, password, "serviceuser", SERVICE_ROLE, verbose
|
51
48
|
)
|
52
49
|
|
@@ -57,10 +54,9 @@ def create_admin_user_command(username, email, password, verbose: bool = True):
|
|
57
54
|
|
58
55
|
if username is None or email is None or password is None:
|
59
56
|
current_app.logger.info("Missing required arguments")
|
60
|
-
return
|
61
|
-
|
62
|
-
|
63
|
-
)
|
57
|
+
return
|
58
|
+
|
59
|
+
create_user_with_role(username, email, password, "admin", ADMIN_ROLE, verbose)
|
64
60
|
|
65
61
|
|
66
62
|
def create_planner_user_command(username, email, password, verbose: bool = True):
|
@@ -69,7 +65,6 @@ def create_planner_user_command(username, email, password, verbose: bool = True)
|
|
69
65
|
|
70
66
|
if username is None or email is None or password is None:
|
71
67
|
current_app.logger.info("Missing required arguments")
|
72
|
-
return
|
73
|
-
|
74
|
-
|
75
|
-
)
|
68
|
+
return
|
69
|
+
|
70
|
+
create_user_with_role(username, email, password, "planner", PLANNER_ROLE, verbose)
|