cornflow 1.2.3a3__py3-none-any.whl → 1.2.3a4__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 CHANGED
@@ -164,7 +164,7 @@ def create_base_user(username, email, password, verbose):
164
164
  @click.option("-v", "--verbose", is_flag=True, default=False)
165
165
  @with_appcontext
166
166
  def register_roles(verbose):
167
- register_roles_command(verbose)
167
+ register_roles_command(verbose=verbose)
168
168
 
169
169
 
170
170
  @click.command("register_actions")
cornflow/cli/roles.py CHANGED
@@ -18,4 +18,4 @@ def roles():
18
18
  def init(verbose):
19
19
  app = get_app()
20
20
  with app.app_context():
21
- register_roles_command(verbose)
21
+ register_roles_command(verbose=verbose)
cornflow/cli/service.py CHANGED
@@ -2,8 +2,20 @@ import os
2
2
  import subprocess
3
3
  import sys
4
4
  import time
5
+ import logging
5
6
  from logging import error
6
7
 
8
+ # Configure a logger for the service module
9
+ logger = logging.getLogger("cornflow.service")
10
+ logger.setLevel(logging.INFO)
11
+
12
+ # Add console handler if not already present
13
+ if not logger.handlers:
14
+ handler = logging.StreamHandler(sys.stdout)
15
+ formatter = logging.Formatter("%(asctime)s [%(name)s] [%(levelname)s] %(message)s")
16
+ handler.setFormatter(formatter)
17
+ logger.addHandler(handler)
18
+ logger.propagate = False
7
19
 
8
20
  import click
9
21
  from .utils import get_db_conn
@@ -89,9 +101,10 @@ def init_cornflow_service():
89
101
 
90
102
  external_app_lib = import_module(external_app_module)
91
103
  app = external_app_lib.create_wsgi_app(environment, cornflow_db_conn)
92
-
93
104
  with app.app_context():
105
+
94
106
  _initialize_database(app, external_app_module)
107
+
95
108
  _create_initial_users(
96
109
  config["auth"],
97
110
  config["cornflow_admin_user"],
@@ -101,6 +114,7 @@ def init_cornflow_service():
101
114
  config["cornflow_service_email"],
102
115
  config["cornflow_service_pwd"],
103
116
  )
117
+
104
118
  _sync_with_airflow(
105
119
  config["airflow_url"],
106
120
  config["airflow_user"],
@@ -117,6 +131,7 @@ def init_cornflow_service():
117
131
 
118
132
  def _setup_environment_variables():
119
133
  """Reads environment variables, sets defaults, and returns config values."""
134
+
120
135
  environment = os.getenv("FLASK_ENV", "development")
121
136
  os.environ["FLASK_ENV"] = environment
122
137
 
@@ -218,7 +233,7 @@ def _configure_logging(cornflow_logging):
218
233
  if logrotate.returncode != 0:
219
234
  error(f"Error configuring logrotate: {logrotate.stderr}")
220
235
  else:
221
- print(logrotate.stdout)
236
+ logger.info(logrotate.stdout)
222
237
  except Exception as e:
223
238
  error(f"Exception during logrotate configuration: {e}")
224
239
 
@@ -236,6 +251,7 @@ def _initialize_database(app, external_app_module=None):
236
251
 
237
252
  Migrate(app=app, db=db, directory=migrations_path)
238
253
  upgrade()
254
+ logger.info("----------------Migrations applied----------------")
239
255
  access_init_command(verbose=False)
240
256
 
241
257
 
@@ -285,14 +301,21 @@ def _sync_with_airflow(
285
301
 
286
302
  def _setup_external_app():
287
303
  """Performs setup steps specific to external applications."""
304
+
288
305
  os.chdir(MAIN_WD)
306
+
289
307
  if _register_key():
308
+
290
309
  prefix = "CUSTOM_SSH_"
291
310
  env_variables = {
292
311
  key: value for key, value in os.environ.items() if key.startswith(prefix)
293
312
  }
294
313
  for _, value in env_variables.items():
295
314
  _register_ssh_host(value)
315
+ else:
316
+ logger.info(
317
+ "************************ NO SSH KEY TO REGISTER ************************"
318
+ )
296
319
 
297
320
  # Install requirements for the external app
298
321
  pip_install_cmd = "$(command -v pip) install --user -r requirements.txt"
@@ -301,10 +324,15 @@ def _setup_external_app():
301
324
  if result.returncode != 0:
302
325
  error(f"Error installing requirements: {result.stderr}")
303
326
  else:
304
- print(result.stdout)
327
+ logger.info(result.stdout)
305
328
  time.sleep(5) # Consider if this sleep is truly necessary
306
329
  sys.path.append(MAIN_WD)
307
330
 
331
+ # Add .local path to sys.path so pip --user packages can be found
332
+ local_lib_path = os.path.expanduser("~/.local/lib/python3.12/site-packages")
333
+ if local_lib_path not in sys.path:
334
+ sys.path.insert(0, local_lib_path)
335
+
308
336
 
309
337
  def _start_application(external_application, environment, external_app_module=None):
310
338
  """Starts the Gunicorn server."""
@@ -349,4 +377,4 @@ def _register_key():
349
377
  os.system(add_key)
350
378
  return True
351
379
  else:
352
- return False
380
+ return False
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  import os
2
3
 
3
4
  from .actions import register_actions_command
@@ -5,18 +6,28 @@ from .permissions import register_base_permissions_command
5
6
  from .roles import register_roles_command
6
7
  from .views import register_views_command
7
8
 
9
+ # Configure logger for access
10
+ logger = logging.getLogger("cornflow.access")
11
+
8
12
 
9
13
  def access_init_command(verbose: bool = False):
14
+ """
15
+ Initialize the access to the system.
16
+ """
17
+
10
18
  external = int(os.getenv("EXTERNAL_APP", 0))
11
19
  external_app = os.getenv("EXTERNAL_APP_MODULE", "external_app")
12
20
 
13
21
  register_actions_command(verbose)
14
- register_roles_command(verbose)
15
22
 
16
- register_views_command(verbose=verbose)
17
23
  if external != 0:
24
+ register_roles_command(external_app=external_app, verbose=verbose)
18
25
  register_views_command(external_app=external_app, verbose=verbose)
26
+ else:
27
+ register_roles_command(verbose=verbose)
28
+ register_views_command(verbose=verbose)
19
29
 
20
- register_base_permissions_command(verbose=verbose)
21
30
  if external != 0:
22
31
  register_base_permissions_command(external_app=external_app, verbose=verbose)
32
+ else:
33
+ register_base_permissions_command(verbose=verbose)
@@ -0,0 +1,100 @@
1
+ import sys
2
+ from importlib import import_module
3
+
4
+ from flask import current_app
5
+
6
+ from cornflow.endpoints import resources, alarms_resources
7
+ from cornflow.models import RoleModel
8
+ from cornflow.shared.const import (
9
+ EXTRA_PERMISSION_ASSIGNATION,
10
+ ALL_DEFAULT_ROLES,
11
+ )
12
+ from cornflow.shared.const import ROLES_MAP
13
+
14
+
15
+ def get_all_external(external_app):
16
+ """
17
+ Get all resources and extra permissions.
18
+ external_app: If provided, it will get the resources and extra permissions for the external app.
19
+ """
20
+ if external_app is None:
21
+ resources_to_register = resources
22
+ extra_permissions = EXTRA_PERMISSION_ASSIGNATION
23
+ if current_app.config["ALARMS_ENDPOINTS"]:
24
+ resources_to_register = resources + alarms_resources
25
+ else:
26
+ sys.path.append("./")
27
+ external_module = import_module(external_app)
28
+ try:
29
+ extra_permissions = (
30
+ EXTRA_PERMISSION_ASSIGNATION
31
+ + external_module.shared.const.EXTRA_PERMISSION_ASSIGNATION
32
+ )
33
+ except AttributeError:
34
+ extra_permissions = EXTRA_PERMISSION_ASSIGNATION
35
+
36
+ if current_app.config["ALARMS_ENDPOINTS"]:
37
+ resources_to_register = (
38
+ external_module.endpoints.resources + resources + alarms_resources
39
+ )
40
+ else:
41
+ resources_to_register = external_module.endpoints.resources + resources
42
+ return resources_to_register, extra_permissions
43
+
44
+
45
+ def get_all_resources(resources_to_register):
46
+ """
47
+ Get all resources and roles with access.
48
+ resources_to_register: List of resources to register.
49
+ """
50
+
51
+ resources_roles_with_access = {
52
+ resource["endpoint"]: resource["resource"].ROLES_WITH_ACCESS
53
+ for resource in resources_to_register
54
+ }
55
+
56
+ return resources_roles_with_access
57
+
58
+
59
+ def get_new_roles_to_add(extra_permissions, resources_roles_with_access):
60
+ """
61
+ Get the new roles to add.
62
+ extra_permissions: List of extra permissions.
63
+ resources_roles_with_access: Dictionary of resources and roles with access.
64
+ """
65
+
66
+ roles_with_access = list(
67
+ set([role for roles in resources_roles_with_access.values() for role in roles])
68
+ )
69
+ roles_in_extra_permissions = [role for role, _, _ in extra_permissions]
70
+ roles_with_access = list(set(roles_with_access + roles_in_extra_permissions))
71
+
72
+ # Add all default roles that are referenced in BASE_PERMISSION_ASSIGNATION
73
+ roles_with_access = list(set(roles_with_access + ALL_DEFAULT_ROLES))
74
+
75
+ # We extract the existing roles in the database
76
+ existing_roles = [role.id for role in RoleModel.get_all_objects()]
77
+ new_roles_to_add = []
78
+
79
+ for role_id in roles_with_access:
80
+ if role_id not in existing_roles:
81
+ if role_id in ALL_DEFAULT_ROLES:
82
+ # Create standard role with predefined name
83
+ role_name = ROLES_MAP[role_id]
84
+ new_role = RoleModel(
85
+ {
86
+ "id": role_id,
87
+ "name": role_name,
88
+ }
89
+ )
90
+ else:
91
+ # Create custom role with custom_role_<id> name
92
+ new_role = RoleModel(
93
+ {
94
+ "id": role_id,
95
+ "name": f"custom_role_{role_id}",
96
+ }
97
+ )
98
+ new_roles_to_add.append(new_role)
99
+
100
+ return new_roles_to_add
@@ -1,112 +1,92 @@
1
- import sys
2
- from importlib import import_module
1
+ from flask import current_app
2
+ from sqlalchemy.exc import DBAPIError, IntegrityError
3
3
 
4
+ from cornflow.commands.auxiliar import (
5
+ get_all_external,
6
+ get_all_resources,
7
+ )
8
+ from cornflow.models import ViewModel, PermissionViewRoleModel
9
+ from cornflow.shared import db
10
+ from cornflow.shared.const import ALL_DEFAULT_ROLES, GET_ACTION
4
11
  from cornflow.shared.const import (
5
12
  BASE_PERMISSION_ASSIGNATION,
6
- EXTRA_PERMISSION_ASSIGNATION,
7
- ROLES_MAP,
8
- GET_ACTION,
9
- PATCH_ACTION,
10
- POST_ACTION,
11
- PUT_ACTION,
12
- DELETE_ACTION,
13
13
  )
14
- from cornflow.models import ViewModel, PermissionViewRoleModel, RoleModel
15
- from cornflow.shared import db
16
- from flask import current_app
17
- from sqlalchemy.exc import DBAPIError, IntegrityError
18
14
 
19
15
 
20
16
  def register_base_permissions_command(external_app: str = None, verbose: bool = False):
21
- if external_app is None:
22
- from cornflow.endpoints import resources, alarms_resources
23
-
24
- resources_to_register = resources
25
- if current_app.config["ALARMS_ENDPOINTS"]:
26
- resources_to_register = resources + alarms_resources
27
- elif external_app is not None:
28
- sys.path.append("./")
29
- external_module = import_module(external_app)
30
- resources_to_register = external_module.endpoints.resources
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 and extra permissions
23
+ resources_to_register, extra_permissions = get_all_external(external_app)
24
+ # Get all views in the database
25
+ views_in_db = {view.name: view.id for view in ViewModel.get_all_objects()}
26
+ permissions_in_db, permissions_in_db_keys = get_db_permissions()
27
+ # Get all resources and roles with access
28
+ resources_roles_with_access = get_all_resources(resources_to_register)
29
+ # Get the new roles and base permissions assignation
30
+ base_permissions_assignation = get_base_permissions(resources_roles_with_access)
31
+ # Get the permissions to register and delete
32
+ permissions_tuples = get_permissions_in_code_as_tuples(
33
+ resources_to_register,
34
+ views_in_db,
35
+ base_permissions_assignation,
36
+ extra_permissions,
37
+ )
38
+ permissions_to_register = get_permissions_to_register(
39
+ permissions_tuples, permissions_in_db_keys
40
+ )
41
+ permissions_to_delete = get_permissions_to_delete(
42
+ permissions_tuples, resources_roles_with_access.keys(), permissions_in_db
43
+ )
44
+
45
+ # Save the new permissions in the data
46
+ save_and_delete_permissions(permissions_to_register, permissions_to_delete)
47
+
48
+ if len(permissions_to_register) > 0:
49
+ current_app.logger.info(f"Permissions registered: {permissions_to_register}")
31
50
  else:
32
- resources_to_register = []
33
- exit()
51
+ current_app.logger.info("No new permissions to register")
34
52
 
35
- views_in_db = {view.name: view.id for view in ViewModel.get_all_objects()}
36
- permissions_in_db = [perm for perm in PermissionViewRoleModel.get_all_objects()]
37
- permissions_in_db_keys = [
38
- (perm.role_id, perm.action_id, perm.api_view_id) for perm in permissions_in_db
39
- ]
40
- resources_names = [resource["endpoint"] for resource in resources_to_register]
41
- roles_in_db = [role.id for role in RoleModel.get_all_objects()]
42
- # Check which roles are not in ROLES_MAP
43
- roles_not_in_map = [
44
- role_id for role_id in roles_in_db if role_id not in ROLES_MAP.keys()
45
- ]
46
- complete_base_assignation = BASE_PERMISSION_ASSIGNATION.copy()
47
- if len(roles_not_in_map) > 0:
48
- # We add to the complete_base_assignation the roles that are not in ROLES_MAP
49
- for role_id in roles_not_in_map:
50
- for action in [
51
- GET_ACTION,
52
- PATCH_ACTION,
53
- POST_ACTION,
54
- PUT_ACTION,
55
- DELETE_ACTION,
56
- ]:
57
- complete_base_assignation.append((role_id, action))
58
-
59
- # Create base permissions
60
- permissions_in_app = [
61
- PermissionViewRoleModel(
62
- {
63
- "role_id": role,
64
- "action_id": action,
65
- "api_view_id": views_in_db[view["endpoint"]],
66
- }
67
- )
68
- for role, action in complete_base_assignation
69
- for view in resources_to_register
70
- if role in view["resource"].ROLES_WITH_ACCESS
71
- ] + [
72
- PermissionViewRoleModel(
73
- {
74
- "role_id": role,
75
- "action_id": action,
76
- "api_view_id": views_in_db[endpoint],
77
- }
78
- )
79
- for role, action, endpoint in EXTRA_PERMISSION_ASSIGNATION
80
- ]
53
+ if len(permissions_to_delete) > 0:
54
+ current_app.logger.info(f"Permissions deleted: {permissions_to_delete}")
55
+ else:
56
+ current_app.logger.info("No permissions to delete")
81
57
 
82
- permissions_in_app_keys = [
83
- (perm.role_id, perm.action_id, perm.api_view_id) for perm in permissions_in_app
84
- ]
85
58
 
86
- permissions_to_register = [
87
- permission
88
- for permission in permissions_in_app
89
- if (permission.role_id, permission.action_id, permission.api_view_id)
90
- not in permissions_in_db_keys
91
- ]
59
+ def save_new_roles(new_roles_to_add):
60
+ """
61
+ Save the new roles in the database.
62
+ new_roles_to_add: List of new roles to add.
63
+ """
64
+ if len(new_roles_to_add) > 0:
65
+ db.session.bulk_save_objects(new_roles_to_add)
66
+ try:
67
+ db.session.commit()
68
+ except IntegrityError as e:
69
+ db.session.rollback()
70
+ current_app.logger.error(
71
+ f"Integrity error on base permissions register: {e}"
72
+ )
73
+ except DBAPIError as e:
74
+ db.session.rollback()
75
+ current_app.logger.error(f"Unknown error on base permissions register: {e}")
92
76
 
93
- permissions_to_delete = [
94
- permission
95
- for permission in permissions_in_db
96
- if (permission.role_id, permission.action_id, permission.api_view_id)
97
- not in permissions_in_app_keys
98
- and permission.api_view.name in resources_names
99
- ]
100
77
 
78
+ def save_and_delete_permissions(permissions_to_register, permissions_to_delete):
79
+ """
80
+ Save and delete permissions in the database.
81
+ permissions_to_register: List of permissions to register.
82
+ permissions_to_delete: List of permissions to delete.
83
+ """
101
84
  if len(permissions_to_register) > 0:
102
85
  db.session.bulk_save_objects(permissions_to_register)
103
86
 
104
- # TODO: for now the permission are not going to get deleted just in case.
105
- # We are just going to register new permissions
106
87
  if len(permissions_to_delete) > 0:
107
88
  for permission in permissions_to_delete:
108
89
  db.session.delete(permission)
109
-
110
90
  try:
111
91
  db.session.commit()
112
92
  except IntegrityError as e:
@@ -129,25 +109,107 @@ def register_base_permissions_command(external_app: str = None, verbose: bool =
129
109
  f"Unknown error on base permissions sequence updating: {e}"
130
110
  )
131
111
 
132
- if verbose:
133
- if len(permissions_to_register) > 0:
134
- current_app.logger.info(
135
- f"Permissions registered: {permissions_to_register}"
136
- )
137
- else:
138
- current_app.logger.info("No new permissions to register")
139
112
 
140
- if len(permissions_to_delete) > 0:
141
- current_app.logger.info(f"Permissions deleted: {permissions_to_delete}")
142
- else:
143
- current_app.logger.info("No permissions to delete")
113
+ def get_permissions_to_delete(permissions_tuples, resources_names, permissions_in_db):
114
+ """
115
+ Get the permissions to delete.
116
+ """
117
+ permissions_to_delete = [
118
+ permission
119
+ for permission in permissions_in_db
120
+ if (permission.role_id, permission.action_id, permission.api_view_id)
121
+ not in permissions_tuples
122
+ ]
123
+
124
+ return permissions_to_delete
125
+
126
+
127
+ def get_permissions_to_register(permissions_tuples, permissions_in_db_keys):
128
+ """
129
+ Get the permissions to register.
130
+ """
131
+ # Convert set of tuples to list of PermissionViewRoleModel objects
132
+ return [
133
+ PermissionViewRoleModel(
134
+ {
135
+ "role_id": role_id,
136
+ "action_id": action_id,
137
+ "api_view_id": api_view_id,
138
+ }
139
+ )
140
+ for role_id, action_id, api_view_id in permissions_tuples
141
+ if (role_id, action_id, api_view_id) not in permissions_in_db_keys
142
+ ]
143
+
144
+
145
+ def get_permissions_in_code_as_tuples(
146
+ resources_to_register, views_in_db, base_permissions_assignation, extra_permissions
147
+ ):
148
+ """
149
+ Get the permissions in code as tuples.
150
+ """
151
+ # Create base permissions using a set to avoid duplicates
152
+ permissions_tuples = set()
153
+
154
+ # Add permissions from ROLES_WITH_ACCESS
155
+ for role, action in base_permissions_assignation:
156
+ for view in resources_to_register:
157
+ if role in view["resource"].ROLES_WITH_ACCESS:
158
+ permissions_tuples.add((role, action, views_in_db[view["endpoint"]]))
159
+
160
+ # Add permissions from extra_permissions
161
+ for role, action, endpoint in extra_permissions:
162
+ if endpoint in views_in_db:
163
+ permissions_tuples.add((role, action, views_in_db[endpoint]))
164
+
165
+ return permissions_tuples
166
+
167
+
168
+ def get_base_permissions(resources_roles_with_access):
169
+ """
170
+ Get the new roles and base permissions assignation.
171
+ new_roles_to_add: List of new roles to add.
172
+ resources_roles_with_access: Dictionary of resources and roles with access.
173
+ """
174
+ # Get all custom roles (both new and existing) that appear in ROLES_WITH_ACCESS
175
+ all_custom_roles_in_access = set(
176
+ [
177
+ role
178
+ for roles in resources_roles_with_access.values()
179
+ for role in roles
180
+ if role not in ALL_DEFAULT_ROLES
181
+ ]
182
+ )
183
+
184
+ # Create extended permission assignation including all custom roles
185
+ # For custom roles (not in ALL_DEFAULT_ROLES), only grant GET access
186
+ base_permissions_assignation = BASE_PERMISSION_ASSIGNATION + [
187
+ (custom_role, GET_ACTION) for custom_role in all_custom_roles_in_access
188
+ ]
189
+
190
+ return base_permissions_assignation
191
+
192
+
193
+ def get_db_permissions():
194
+ """
195
+ Get all permissions in the database.
196
+ """
197
+ permissions_in_db = [perm for perm in PermissionViewRoleModel.get_all_objects()]
198
+ permissions_in_db_keys = [
199
+ (perm.role_id, perm.action_id, perm.api_view_id) for perm in permissions_in_db
200
+ ]
144
201
 
145
- return True
202
+ return permissions_in_db, permissions_in_db_keys
146
203
 
147
204
 
148
205
  def register_dag_permissions_command(
149
206
  open_deployment: int = None, verbose: bool = False
150
207
  ):
208
+ """
209
+ Register DAG permissions.
210
+ open_deployment: If 1, it will register the permissions for the open deployment.
211
+ verbose: If True, it will print the permissions that are being registered.
212
+ """
151
213
 
152
214
  from flask import current_app
153
215
  from sqlalchemy.exc import DBAPIError, IntegrityError
@@ -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
- roles_registered = [role.name for role in RoleModel.get_all_objects()]
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
- roles_to_register = [
13
- RoleModel({"id": key, "name": value})
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(roles_to_register) > 0:
42
- current_app.logger.info(f"Roles registered: {roles_to_register}")
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