cornflow 2.0.0a13__py3-none-any.whl → 2.0.0a15__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.
Files changed (46) hide show
  1. cornflow/app.py +3 -1
  2. cornflow/cli/__init__.py +4 -0
  3. cornflow/cli/actions.py +4 -0
  4. cornflow/cli/config.py +4 -0
  5. cornflow/cli/migrations.py +13 -8
  6. cornflow/cli/permissions.py +4 -0
  7. cornflow/cli/roles.py +4 -0
  8. cornflow/cli/schemas.py +5 -0
  9. cornflow/cli/service.py +262 -147
  10. cornflow/cli/tools/api_generator.py +13 -10
  11. cornflow/cli/tools/endpoint_tools.py +191 -196
  12. cornflow/cli/tools/models_tools.py +87 -60
  13. cornflow/cli/tools/schema_generator.py +161 -67
  14. cornflow/cli/tools/schemas_tools.py +4 -5
  15. cornflow/cli/users.py +8 -0
  16. cornflow/cli/views.py +4 -0
  17. cornflow/commands/dag.py +3 -2
  18. cornflow/commands/schemas.py +6 -4
  19. cornflow/commands/users.py +12 -17
  20. cornflow/config.py +3 -2
  21. cornflow/endpoints/dag.py +27 -25
  22. cornflow/endpoints/data_check.py +102 -164
  23. cornflow/endpoints/example_data.py +9 -3
  24. cornflow/endpoints/execution.py +27 -23
  25. cornflow/endpoints/health.py +4 -5
  26. cornflow/endpoints/instance.py +39 -12
  27. cornflow/endpoints/meta_resource.py +4 -5
  28. cornflow/shared/airflow.py +157 -0
  29. cornflow/shared/authentication/auth.py +73 -42
  30. cornflow/shared/const.py +9 -0
  31. cornflow/shared/databricks.py +10 -10
  32. cornflow/shared/exceptions.py +3 -1
  33. cornflow/shared/utils_tables.py +36 -8
  34. cornflow/shared/validators.py +1 -1
  35. cornflow/tests/custom_test_case.py +4 -4
  36. cornflow/tests/unit/test_alarms.py +1 -2
  37. cornflow/tests/unit/test_cases.py +4 -7
  38. cornflow/tests/unit/test_executions.py +29 -20
  39. cornflow/tests/unit/test_log_in.py +46 -9
  40. cornflow/tests/unit/test_tables.py +3 -3
  41. cornflow/tests/unit/tools.py +31 -13
  42. {cornflow-2.0.0a13.dist-info → cornflow-2.0.0a15.dist-info}/METADATA +2 -2
  43. {cornflow-2.0.0a13.dist-info → cornflow-2.0.0a15.dist-info}/RECORD +46 -45
  44. {cornflow-2.0.0a13.dist-info → cornflow-2.0.0a15.dist-info}/WHEEL +1 -1
  45. {cornflow-2.0.0a13.dist-info → cornflow-2.0.0a15.dist-info}/entry_points.txt +0 -0
  46. {cornflow-2.0.0a13.dist-info → cornflow-2.0.0a15.dist-info}/top_level.txt +0 -0
cornflow/app.py CHANGED
@@ -51,6 +51,8 @@ def create_app(env_name="development", dataconn=None):
51
51
  """
52
52
  dictConfig(log_config(app_config[env_name].LOG_LEVEL))
53
53
 
54
+ # Note: Explicit CSRF protection is not configured as the application uses
55
+ # JWT for authentication via headers, mitigating standard CSRF vulnerabilities.
54
56
  app = Flask(__name__)
55
57
  app.json.sort_keys = False
56
58
  app.logger.setLevel(app_config[env_name].LOG_LEVEL)
@@ -103,7 +105,7 @@ def create_app(env_name="development", dataconn=None):
103
105
  else:
104
106
  raise ConfigurationError(
105
107
  error="Invalid authentication type",
106
- log_txt="Error while configuring authentication. The authentication type is not valid."
108
+ log_txt="Error while configuring authentication. The authentication type is not valid.",
107
109
  )
108
110
 
109
111
  initialize_errorhandlers(app)
cornflow/cli/__init__.py CHANGED
@@ -17,6 +17,10 @@ from cornflow.cli.views import views
17
17
 
18
18
  @click.group(name="cornflow", help="Commands in the cornflow cli")
19
19
  def cli():
20
+ """
21
+ This method is empty but it serves as the building block
22
+ for the rest of the commands
23
+ """
20
24
  pass
21
25
 
22
26
 
cornflow/cli/actions.py CHANGED
@@ -7,6 +7,10 @@ from .arguments import verbose
7
7
 
8
8
  @click.group(name="actions", help="Commands to manage the actions")
9
9
  def actions():
10
+ """
11
+ This method is empty but it serves as the building block
12
+ for the rest of the commands
13
+ """
10
14
  pass
11
15
 
12
16
 
cornflow/cli/config.py CHANGED
@@ -7,6 +7,10 @@ from .arguments import path
7
7
 
8
8
  @click.group(name="config", help="Commands to manage the configuration variables")
9
9
  def config():
10
+ """
11
+ This method is empty but it serves as the building block
12
+ for the rest of the commands
13
+ """
10
14
  pass
11
15
 
12
16
 
@@ -3,6 +3,7 @@ import os.path
3
3
 
4
4
  import click
5
5
  from cornflow.shared import db
6
+ from cornflow.shared.const import MIGRATIONS_DEFAULT_PATH
6
7
  from flask_migrate import Migrate, migrate, upgrade, downgrade, init
7
8
 
8
9
  from .utils import get_app
@@ -10,6 +11,10 @@ from .utils import get_app
10
11
 
11
12
  @click.group(name="migrations", help="Commands to manage the migrations")
12
13
  def migrations():
14
+ """
15
+ This method is empty but it serves as the building block
16
+ for the rest of the commands
17
+ """
13
18
  pass
14
19
 
15
20
 
@@ -18,12 +23,12 @@ def migrate_migrations():
18
23
  app = get_app()
19
24
  external = int(os.getenv("EXTERNAL_APP", 0))
20
25
  if external == 0:
21
- path = "./cornflow/migrations"
26
+ path = MIGRATIONS_DEFAULT_PATH
22
27
  else:
23
28
  path = f"./{os.getenv('EXTERNAL_APP_MODULE', 'external_app')}/migrations"
24
29
 
25
30
  with app.app_context():
26
- migration_client = Migrate(app=app, db=db, directory=path)
31
+ Migrate(app=app, db=db, directory=path)
27
32
  migrate()
28
33
 
29
34
 
@@ -35,12 +40,12 @@ def upgrade_migrations(revision="head"):
35
40
  app = get_app()
36
41
  external = int(os.getenv("EXTERNAL_APP", 0))
37
42
  if external == 0:
38
- path = "./cornflow/migrations"
43
+ path = MIGRATIONS_DEFAULT_PATH
39
44
  else:
40
45
  path = f"./{os.getenv('EXTERNAL_APP_MODULE', 'external_app')}/migrations"
41
46
 
42
47
  with app.app_context():
43
- migration_client = Migrate(app=app, db=db, directory=path)
48
+ Migrate(app=app, db=db, directory=path)
44
49
  upgrade(revision=revision)
45
50
 
46
51
 
@@ -52,12 +57,12 @@ def downgrade_migrations(revision="-1"):
52
57
  app = get_app()
53
58
  external = int(os.getenv("EXTERNAL_APP", 0))
54
59
  if external == 0:
55
- path = "./cornflow/migrations"
60
+ path = MIGRATIONS_DEFAULT_PATH
56
61
  else:
57
62
  path = f"./{os.getenv('EXTERNAL_APP_MODULE', 'external_app')}/migrations"
58
63
 
59
64
  with app.app_context():
60
- migration_client = Migrate(app=app, db=db, directory=path)
65
+ Migrate(app=app, db=db, directory=path)
61
66
  downgrade(revision=revision)
62
67
 
63
68
 
@@ -69,10 +74,10 @@ def init_migrations():
69
74
  app = get_app()
70
75
  external = int(os.getenv("EXTERNAL_APP", 0))
71
76
  if external == 0:
72
- path = "./cornflow/migrations"
77
+ path = MIGRATIONS_DEFAULT_PATH
73
78
  else:
74
79
  path = f"./{os.getenv('EXTERNAL_APP_MODULE', 'external_app')}/migrations"
75
80
 
76
81
  with app.app_context():
77
- migration_client = Migrate(app=app, db=db, directory=path)
82
+ Migrate(app=app, db=db, directory=path)
78
83
  init()
@@ -11,6 +11,10 @@ from .utils import get_app
11
11
 
12
12
  @click.group(name="permissions", help="Commands to manage the permissions")
13
13
  def permissions():
14
+ """
15
+ This method is empty but it serves as the building block
16
+ for the rest of the commands
17
+ """
14
18
  pass
15
19
 
16
20
 
cornflow/cli/roles.py CHANGED
@@ -6,6 +6,10 @@ from .utils import get_app
6
6
 
7
7
  @click.group(name="roles", help="Commands to manage the roles")
8
8
  def roles():
9
+ """
10
+ This method is empty but it serves as the building block
11
+ for the rest of the commands
12
+ """
9
13
  pass
10
14
 
11
15
 
cornflow/cli/schemas.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """
2
2
  File that implements the generate from schema cli command
3
3
  """
4
+
4
5
  import click
5
6
  from .tools.api_generator import APIGenerator
6
7
  from .tools.schema_generator import SchemaGenerator
@@ -20,6 +21,10 @@ METHOD_OPTIONS = [
20
21
 
21
22
  @click.group(name="schemas", help="Commands to manage the schemas")
22
23
  def schemas():
24
+ """
25
+ This method is empty but it serves as the building block
26
+ for the rest of the commands
27
+ """
23
28
  pass
24
29
 
25
30
 
cornflow/cli/service.py CHANGED
@@ -31,16 +31,96 @@ from cornflow.shared import db
31
31
  from cryptography.fernet import Fernet
32
32
  from flask_migrate import Migrate, upgrade
33
33
 
34
+ MAIN_WD = "/usr/src/app"
35
+
34
36
 
35
37
  @click.group(name="service", help="Commands to run the cornflow service")
36
38
  def service():
39
+ """
40
+ This method is empty but it serves as the building block
41
+ for the rest of the commands
42
+ """
37
43
  pass
38
44
 
39
45
 
40
46
  @service.command(name="init", help="Initialize the service")
41
47
  def init_cornflow_service():
42
48
  click.echo("Starting the service")
43
- os.chdir("/usr/src/app")
49
+ os.chdir(MAIN_WD)
50
+
51
+ config = _setup_environment_variables()
52
+ _configure_logging(config["cornflow_logging"])
53
+
54
+ external_application = config["external_application"]
55
+ environment = config["environment"]
56
+ cornflow_db_conn = config["cornflow_db_conn"]
57
+ external_app_module = config["external_app_module"]
58
+
59
+ app = None # Initialize app to None
60
+
61
+ if external_application == 0:
62
+ click.echo("Initializing standard Cornflow application")
63
+ app = create_app(environment, cornflow_db_conn)
64
+ with app.app_context():
65
+ _initialize_database(app)
66
+ _create_initial_users(
67
+ config["auth"],
68
+ config["cornflow_admin_user"],
69
+ config["cornflow_admin_email"],
70
+ config["cornflow_admin_pwd"],
71
+ config["cornflow_service_user"],
72
+ config["cornflow_service_email"],
73
+ config["cornflow_service_pwd"],
74
+ )
75
+ if config["cornflow_backend"] == AIRFLOW_BACKEND:
76
+ _sync_with_airflow(
77
+ config["airflow_url"],
78
+ config["airflow_user"],
79
+ config["airflow_pwd"],
80
+ config["open_deployment"],
81
+ external_app=False,
82
+ )
83
+ _start_application(external_application, environment)
84
+
85
+ elif external_application == 1:
86
+ click.echo(f"Initializing Cornflow with external app: {external_app_module}")
87
+ if not external_app_module:
88
+ sys.exit("FATAL: EXTERNAL_APP is 1 but EXTERNAL_APP_MODULE is not set.")
89
+
90
+ _setup_external_app()
91
+ from importlib import import_module
92
+
93
+ external_app_lib = import_module(external_app_module)
94
+ app = external_app_lib.create_wsgi_app(environment, cornflow_db_conn)
95
+
96
+ with app.app_context():
97
+ _initialize_database(app, external_app_module)
98
+ _create_initial_users(
99
+ config["auth"],
100
+ config["cornflow_admin_user"],
101
+ config["cornflow_admin_email"],
102
+ config["cornflow_admin_pwd"],
103
+ config["cornflow_service_user"],
104
+ config["cornflow_service_email"],
105
+ config["cornflow_service_pwd"],
106
+ )
107
+ if config["cornflow_backend"] == AIRFLOW_BACKEND:
108
+ _sync_with_airflow(
109
+ config["airflow_url"],
110
+ config["airflow_user"],
111
+ config["airflow_pwd"],
112
+ config["open_deployment"],
113
+ external_app=True,
114
+ )
115
+ _start_application(external_application, environment, external_app_module)
116
+
117
+ else:
118
+ # This case should ideally be caught earlier or handled differently
119
+ sys.exit(f"FATAL: Invalid EXTERNAL_APP value: {external_application}")
120
+
121
+
122
+ def _setup_environment_variables():
123
+ """Reads environment variables, sets defaults, and returns config values."""
44
124
  environment = os.getenv("FLASK_ENV", "development")
45
125
  os.environ["FLASK_ENV"] = environment
46
126
 
@@ -72,7 +152,8 @@ def init_cornflow_service():
72
152
  os.environ["DATABRICKS_CLIENT_ID"] = databricks_client_id
73
153
  else:
74
154
  raise Exception("Selected backend not among valid options")
75
-
155
+ # Cornflow app config
156
+ os.environ.setdefault("cornflow_url", "http://cornflow:5000")
76
157
  os.environ["FLASK_APP"] = "cornflow.app"
77
158
  os.environ["SECRET_KEY"] = os.getenv("FERNET_KEY", Fernet.generate_key().decode())
78
159
 
@@ -94,10 +175,9 @@ def init_cornflow_service():
94
175
  )
95
176
  cornflow_service_pwd = os.getenv("CORNFLOW_SERVICE_PWD", "Service_user1234")
96
177
 
97
- # Cornflow logging and storage config
178
+ # Cornflow logging and deployment config
98
179
  cornflow_logging = os.getenv("CORNFLOW_LOGGING", "console")
99
180
  os.environ["CORNFLOW_LOGGING"] = cornflow_logging
100
-
101
181
  open_deployment = os.getenv("OPEN_DEPLOYMENT", 1)
102
182
  os.environ["OPEN_DEPLOYMENT"] = str(open_deployment)
103
183
  signup_activated = os.getenv("SIGNUP_ACTIVATED", 1)
@@ -108,176 +188,211 @@ def init_cornflow_service():
108
188
  os.environ["DEFAULT_ROLE"] = str(default_role)
109
189
 
110
190
  # Check LDAP parameters for active directory and show message
111
- if os.getenv("AUTH_TYPE") == AUTH_LDAP:
112
- print(
113
- "WARNING: Cornflow will be deployed with LDAP Authorization. Please review your ldap auth configuration."
191
+ if auth == AUTH_LDAP:
192
+ click.echo(
193
+ "WARNING: Cornflow will be deployed with LDAP Authorization. "
194
+ "Please review your ldap auth configuration."
114
195
  )
115
196
 
116
197
  # check database param from docker env
117
- if os.getenv("DATABASE_URL") is None:
198
+ if cornflow_db_conn is None:
118
199
  sys.exit("FATAL: you need to provide a postgres database for Cornflow")
119
200
 
120
- # set logrotate config file
121
- if cornflow_logging == "file":
122
- try:
123
- conf = "/usr/src/app/log/*.log {\n\
124
- rotate 30\n \
125
- daily\n\
126
- compress\n\
127
- size 20M\n\
128
- postrotate\n\
129
- kill -HUP \$(cat /usr/src/app/gunicorn.pid)\n \
130
- endscript}"
131
- logrotate = subprocess.run(
132
- f"cat > /etc/logrotate.d/cornflow <<EOF\n {conf} \nEOF", shell=True
133
- )
134
- out_logrotate = logrotate.stdout
135
- click.echo(out_logrotate)
136
-
137
- except error:
138
- click.echo(error)
139
-
140
201
  external_application = int(os.getenv("EXTERNAL_APP", 0))
141
- if external_application == 0:
142
- os.environ["GUNICORN_WORKING_DIR"] = "/usr/src/app"
143
- elif external_application == 1:
144
- os.environ["GUNICORN_WORKING_DIR"] = "/usr/src/app"
202
+ external_app_module = os.getenv("EXTERNAL_APP_MODULE")
203
+ if cornflow_backend == AIRFLOW_BACKEND:
204
+ return {
205
+ "environment": environment,
206
+ "auth": auth,
207
+ "airflow_user": airflow_user,
208
+ "airflow_pwd": airflow_pwd,
209
+ "airflow_url": airflow_url,
210
+ "cornflow_db_conn": cornflow_db_conn,
211
+ "cornflow_admin_user": cornflow_admin_user,
212
+ "cornflow_admin_email": cornflow_admin_email,
213
+ "cornflow_admin_pwd": cornflow_admin_pwd,
214
+ "cornflow_service_user": cornflow_service_user,
215
+ "cornflow_service_email": cornflow_service_email,
216
+ "cornflow_service_pwd": cornflow_service_pwd,
217
+ "cornflow_logging": cornflow_logging,
218
+ "open_deployment": open_deployment,
219
+ "external_application": external_application,
220
+ "external_app_module": external_app_module,
221
+ }
222
+ elif cornflow_backend == DATABRICKS_BACKEND:
223
+ return {
224
+ "environment": environment,
225
+ "auth": auth,
226
+ "databricks_url": databricks_url,
227
+ "databricks_auth_secret": databricks_auth_secret,
228
+ "databricks_token_endpoint": databricks_token_endpoint,
229
+ "databricks_ep_clusters": databricks_ep_clusters,
230
+ "databricks_client_id": databricks_client_id,
231
+ "cornflow_db_conn": cornflow_db_conn,
232
+ "cornflow_admin_user": cornflow_admin_user,
233
+ "cornflow_admin_email": cornflow_admin_email,
234
+ "cornflow_admin_pwd": cornflow_admin_pwd,
235
+ "cornflow_service_user": cornflow_service_user,
236
+ "cornflow_service_email": cornflow_service_email,
237
+ "cornflow_service_pwd": cornflow_service_pwd,
238
+ "cornflow_logging": cornflow_logging,
239
+ "open_deployment": open_deployment,
240
+ "external_application": external_application,
241
+ "external_app_module": external_app_module,
242
+ }
145
243
  else:
146
- raise Exception("No external application found")
244
+ raise Exception("Selected backend not among valid options")
147
245
 
148
- if external_application == 0:
149
- click.echo("Starting cornflow")
150
- app = create_app(environment, cornflow_db_conn)
151
- with app.app_context():
152
- path = f"{os.path.dirname(cornflow.__file__)}/migrations"
153
- Migrate(app=app, db=db, directory=path)
154
- upgrade()
155
- access_init_command(verbose=False)
156
- if auth == AUTH_DB or auth == AUTH_OID:
157
- # create cornflow admin user
158
- create_user_with_role(
159
- cornflow_admin_user,
160
- cornflow_admin_email,
161
- cornflow_admin_pwd,
162
- "admin",
163
- ADMIN_ROLE,
164
- verbose=True,
165
- )
166
- # create cornflow service user
167
- create_user_with_role(
168
- cornflow_service_user,
169
- cornflow_service_email,
170
- cornflow_service_pwd,
171
- "serviceuser",
172
- SERVICE_ROLE,
173
- verbose=True,
174
- )
175
246
 
176
- if cornflow_backend == AIRFLOW_BACKEND:
177
- register_deployed_dags_command(
178
- airflow_url, airflow_user, airflow_pwd, verbose=True
179
- )
180
- register_dag_permissions_command(open_deployment, verbose=True)
181
- update_schemas_command(
182
- airflow_url, airflow_user, airflow_pwd, verbose=True
183
- )
247
+ def _configure_logging(cornflow_logging):
248
+ """Configures log rotation if logging to file."""
249
+ if cornflow_logging == "file":
250
+ try:
251
+ conf = f"""/usr/src/app/log/*.log {{
252
+ rotate 30
253
+ daily
254
+ compress
255
+ size 20M
256
+ postrotate
257
+ kill -HUP $(cat {MAIN_WD}/gunicorn.pid)
258
+ endscript}}"""
259
+ logrotate = subprocess.run(
260
+ f"cat > /etc/logrotate.d/cornflow <<EOF\n {conf} \nEOF",
261
+ shell=True,
262
+ capture_output=True,
263
+ text=True,
264
+ )
265
+ if logrotate.returncode != 0:
266
+ error(f"Error configuring logrotate: {logrotate.stderr}")
184
267
  else:
185
- register_dag_permissions_command(open_deployment, verbose=True)
186
-
187
- # execute gunicorn application
188
- os.system(
189
- "/usr/local/bin/gunicorn -c python:cornflow.gunicorn \"cornflow.app:create_app('$FLASK_ENV')\""
268
+ print(logrotate.stdout)
269
+ except Exception as e:
270
+ error(f"Exception during logrotate configuration: {e}")
271
+
272
+
273
+ def _initialize_database(app, external_app_module=None):
274
+ """Initializes the database and runs migrations."""
275
+ with app.app_context():
276
+ if external_app_module:
277
+ from importlib import import_module
278
+
279
+ external_app_lib = import_module(external_app_module)
280
+ migrations_path = f"{os.path.dirname(external_app_lib.__file__)}/migrations"
281
+ else:
282
+ migrations_path = f"{os.path.dirname(cornflow.__file__)}/migrations"
283
+
284
+ Migrate(app=app, db=db, directory=migrations_path)
285
+ upgrade()
286
+ access_init_command(verbose=False)
287
+
288
+
289
+ def _create_initial_users(
290
+ auth,
291
+ admin_user,
292
+ admin_email,
293
+ admin_pwd,
294
+ service_user,
295
+ service_email,
296
+ service_pwd,
297
+ ):
298
+ """Creates the initial admin and service users if using DB or OID auth."""
299
+ if auth == AUTH_DB or auth == AUTH_OID:
300
+ # create cornflow admin user
301
+ create_user_with_role(
302
+ admin_user,
303
+ admin_email,
304
+ admin_pwd,
305
+ "admin",
306
+ ADMIN_ROLE,
307
+ verbose=True,
308
+ )
309
+ # create cornflow service user
310
+ create_user_with_role(
311
+ service_user,
312
+ service_email,
313
+ service_pwd,
314
+ "serviceuser",
315
+ SERVICE_ROLE,
316
+ verbose=True,
190
317
  )
191
318
 
192
- elif external_application == 1:
193
- click.echo(f"Starting cornflow + {os.getenv('EXTERNAL_APP_MODULE')}")
194
- os.chdir("/usr/src/app")
195
-
196
- if register_key():
197
- prefix = "CUSTOM_SSH_"
198
- env_variables = {}
199
- for key, value in os.environ.items():
200
- if key.startswith(prefix):
201
- env_variables[key] = value
202
-
203
- for _, value in env_variables.items():
204
- register_ssh_host(value)
205
-
206
- os.system("$(command -v pip) install --user -r requirements.txt")
207
- time.sleep(5)
208
- sys.path.append("/usr/src/app")
209
319
 
210
- from importlib import import_module
320
+ def _sync_with_airflow(
321
+ airflow_url, airflow_user, airflow_pwd, open_deployment, external_app=False
322
+ ):
323
+ """Syncs DAGs, permissions, and schemas with Airflow."""
324
+ register_deployed_dags_command(airflow_url, airflow_user, airflow_pwd, verbose=True)
325
+ register_dag_permissions_command(open_deployment, verbose=True)
326
+ update_schemas_command(airflow_url, airflow_user, airflow_pwd, verbose=True)
327
+ if external_app:
328
+ update_dag_registry_command(
329
+ airflow_url, airflow_user, airflow_pwd, verbose=True
330
+ )
211
331
 
212
- external_app = import_module(os.getenv("EXTERNAL_APP_MODULE"))
213
- app = external_app.create_wsgi_app(environment, cornflow_db_conn)
214
- with app.app_context():
215
- path = f"{os.path.dirname(external_app.__file__)}/migrations"
216
- migrate = Migrate(app=app, db=db, directory=path)
217
- upgrade()
218
- access_init_command(verbose=False)
219
- if auth == AUTH_DB or auth == AUTH_OID:
220
- # create cornflow admin user
221
- create_user_with_role(
222
- cornflow_admin_user,
223
- cornflow_admin_email,
224
- cornflow_admin_pwd,
225
- "admin",
226
- ADMIN_ROLE,
227
- verbose=True,
228
- )
229
- # create cornflow service user
230
- create_user_with_role(
231
- cornflow_service_user,
232
- cornflow_service_email,
233
- cornflow_service_pwd,
234
- "serviceuser",
235
- SERVICE_ROLE,
236
- verbose=True,
237
- )
238
332
 
239
- click.echo(f"Selected backend is: {cornflow_backend}")
240
- if cornflow_backend == AIRFLOW_BACKEND:
241
- register_deployed_dags_command(
242
- airflow_url, airflow_user, airflow_pwd, verbose=True
243
- )
333
+ def _setup_external_app():
334
+ """Performs setup steps specific to external applications."""
335
+ os.chdir(MAIN_WD)
336
+ if _register_key():
337
+ prefix = "CUSTOM_SSH_"
338
+ env_variables = {
339
+ key: value for key, value in os.environ.items() if key.startswith(prefix)
340
+ }
341
+ for _, value in env_variables.items():
342
+ _register_ssh_host(value)
343
+
344
+ # Install requirements for the external app
345
+ pip_install_cmd = "$(command -v pip) install --user -r requirements.txt"
346
+ click.echo(f"Running: {pip_install_cmd}")
347
+ result = subprocess.run(pip_install_cmd, shell=True, capture_output=True, text=True)
348
+ if result.returncode != 0:
349
+ error(f"Error installing requirements: {result.stderr}")
350
+ else:
351
+ print(result.stdout)
352
+ time.sleep(5) # Consider if this sleep is truly necessary
353
+ sys.path.append(MAIN_WD)
244
354
 
245
- register_dag_permissions_command(open_deployment, verbose=True)
246
- update_schemas_command(
247
- airflow_url, airflow_user, airflow_pwd, verbose=True
248
- )
249
- update_dag_registry_command(
250
- airflow_url, airflow_user, airflow_pwd, verbose=True
251
- )
252
- elif cornflow_backend == DATABRICKS_BACKEND:
253
- register_dag_permissions_command(open_deployment, verbose=True)
254
- else:
255
- raise Exception("Selected backend not among valid options")
256
355
 
257
- os.system(
258
- f"/usr/local/bin/gunicorn -c python:cornflow.gunicorn "
259
- f"\"$EXTERNAL_APP_MODULE:create_wsgi_app('$FLASK_ENV')\""
356
+ def _start_application(external_application, environment, external_app_module=None):
357
+ """Starts the Gunicorn server."""
358
+ if external_application == 0:
359
+ os.environ["GUNICORN_WORKING_DIR"] = MAIN_WD
360
+ gunicorn_cmd = (
361
+ "/usr/local/bin/gunicorn -c python:cornflow.gunicorn "
362
+ f"\"cornflow.app:create_app('{environment}')\""
363
+ )
364
+ elif external_application == 1:
365
+ os.environ["GUNICORN_WORKING_DIR"] = MAIN_WD
366
+ if not external_app_module:
367
+ raise ValueError(
368
+ "EXTERNAL_APP_MODULE must be set for external applications"
369
+ )
370
+ gunicorn_cmd = (
371
+ "/usr/local/bin/gunicorn -c python:cornflow.gunicorn "
372
+ f"\"{external_app_module}:create_wsgi_app('{environment}')\""
260
373
  )
261
-
262
374
  else:
263
- raise Exception("No external application found")
375
+ raise ValueError(f"Invalid EXTERNAL_APP value: {external_application}")
376
+
377
+ click.echo(f"Starting application with Gunicorn: {gunicorn_cmd}")
378
+ os.system(gunicorn_cmd)
264
379
 
265
380
 
266
- def register_ssh_host(host):
381
+ def _register_ssh_host(host):
267
382
  if host is not None:
268
- add_host = f"ssh-keyscan {host} >> /usr/src/app/.ssh/known_hosts"
269
- config_ssh_host = f"echo Host {host} >> /usr/src/app/.ssh/config"
270
- config_ssh_key = 'echo " IdentityFile /usr/src/app/.ssh/id_rsa" >> /usr/src/app/.ssh/config'
383
+ add_host = f"ssh-keyscan {host} >> {MAIN_WD}/.ssh/known_hosts"
384
+ config_ssh_host = f"echo Host {host} >> {MAIN_WD}/.ssh/config"
385
+ config_ssh_key = (
386
+ 'echo " IdentityFile {MAIN_WD}/.ssh/id_rsa" >> {MAIN_WD}/.ssh/config'
387
+ )
271
388
  os.system(add_host)
272
389
  os.system(config_ssh_host)
273
390
  os.system(config_ssh_key)
274
391
 
275
392
 
276
- def register_key():
277
- if os.path.isfile("/usr/src/app/.ssh/id_rsa"):
278
- add_key = (
279
- "chmod 0600 /usr/src/app/.ssh/id_rsa && ssh-add /usr/src/app/.ssh/id_rsa"
280
- )
393
+ def _register_key():
394
+ if os.path.isfile(f"{MAIN_WD}/.ssh/id_rsa"):
395
+ add_key = f"chmod 0600 {MAIN_WD}/.ssh/id_rsa && ssh-add {MAIN_WD}/.ssh/id_rsa"
281
396
  os.system(add_key)
282
397
  return True
283
398
  else: