mage-ai 0.8.98__py3-none-any.whl → 0.8.99__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.

Potentially problematic release.


This version of mage-ai might be problematic. Click here for more details.

Files changed (83) hide show
  1. mage_ai/api/policies/OauthPolicy.py +15 -0
  2. mage_ai/api/policies/SessionPolicy.py +2 -0
  3. mage_ai/api/presenters/PipelinePresenter.py +1 -0
  4. mage_ai/api/presenters/SyncPresenter.py +1 -0
  5. mage_ai/api/resources/OauthResource.py +28 -2
  6. mage_ai/api/resources/PipelineResource.py +5 -1
  7. mage_ai/api/resources/SessionResource.py +18 -0
  8. mage_ai/api/resources/WorkspaceResource.py +3 -2
  9. mage_ai/authentication/oauth/active_directory.py +17 -0
  10. mage_ai/authentication/oauth/constants.py +9 -0
  11. mage_ai/cli/main.py +56 -49
  12. mage_ai/cluster_manager/kubernetes/workload_manager.py +46 -9
  13. mage_ai/data_preparation/git/__init__.py +1 -1
  14. mage_ai/data_preparation/models/block/dbt/utils/__init__.py +32 -0
  15. mage_ai/data_preparation/models/block/sql/__init__.py +3 -2
  16. mage_ai/data_preparation/models/pipeline.py +4 -1
  17. mage_ai/data_preparation/preferences.py +4 -2
  18. mage_ai/data_preparation/repo_manager.py +5 -1
  19. mage_ai/data_preparation/sync/__init__.py +1 -0
  20. mage_ai/data_preparation/sync/git_sync.py +2 -5
  21. mage_ai/data_preparation/templates/utils.py +2 -0
  22. mage_ai/orchestration/pipeline_scheduler.py +19 -8
  23. mage_ai/orchestration/queue/process_queue.py +15 -12
  24. mage_ai/server/api/clusters.py +1 -1
  25. mage_ai/server/constants.py +1 -1
  26. mage_ai/server/frontend_dist/404.html +2 -2
  27. mage_ai/server/frontend_dist/404.html.html +2 -2
  28. mage_ai/server/frontend_dist/_next/static/{-1TwyIVv18EeGuJPahFzG → WRxCTOtmZhTqQws_7OJZD}/_buildManifest.js +1 -1
  29. mage_ai/server/frontend_dist/_next/static/chunks/{9350-1ff50f1d7b9ee754.js → 9350-5191c83a8d0cf454.js} +1 -1
  30. mage_ai/server/frontend_dist/_next/static/chunks/pages/{_app-1a7dd14f59bb82dc.js → _app-171846e16d26855a.js} +1 -1
  31. mage_ai/server/frontend_dist/_next/static/chunks/pages/{manage-3f5032353f15dd3c.js → manage-af11f9cf94024ac0.js} +1 -1
  32. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/{sync-data-513e80a872b783a7.js → sync-data-90f8830890036eb2.js} +1 -1
  33. mage_ai/server/frontend_dist/_next/static/chunks/pages/sign-in-a1871b8a537d823c.js +1 -0
  34. mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-48859b4e9c846212.js +1 -0
  35. mage_ai/server/frontend_dist/files.html +2 -2
  36. mage_ai/server/frontend_dist/index.html +2 -2
  37. mage_ai/server/frontend_dist/manage/settings.html +2 -2
  38. mage_ai/server/frontend_dist/manage/users/[user].html +2 -2
  39. mage_ai/server/frontend_dist/manage/users/new.html +2 -2
  40. mage_ai/server/frontend_dist/manage/users.html +2 -2
  41. mage_ai/server/frontend_dist/manage.html +2 -2
  42. mage_ai/server/frontend_dist/pipeline-runs.html +2 -2
  43. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills/[...slug].html +2 -2
  44. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills.html +2 -2
  45. mage_ai/server/frontend_dist/pipelines/[pipeline]/edit.html +2 -2
  46. mage_ai/server/frontend_dist/pipelines/[pipeline]/logs.html +2 -2
  47. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runs.html +2 -2
  48. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
  49. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors.html +2 -2
  50. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs/[run].html +2 -2
  51. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs.html +2 -2
  52. mage_ai/server/frontend_dist/pipelines/[pipeline]/settings.html +2 -2
  53. mage_ai/server/frontend_dist/pipelines/[pipeline]/syncs.html +2 -2
  54. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers/[...slug].html +2 -2
  55. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers.html +2 -2
  56. mage_ai/server/frontend_dist/pipelines/[pipeline].html +2 -2
  57. mage_ai/server/frontend_dist/pipelines.html +2 -2
  58. mage_ai/server/frontend_dist/settings/account/profile.html +2 -2
  59. mage_ai/server/frontend_dist/settings/workspace/preferences.html +2 -2
  60. mage_ai/server/frontend_dist/settings/workspace/sync-data.html +2 -2
  61. mage_ai/server/frontend_dist/settings/workspace/users.html +2 -2
  62. mage_ai/server/frontend_dist/settings.html +2 -2
  63. mage_ai/server/frontend_dist/sign-in.html +2 -2
  64. mage_ai/server/frontend_dist/terminal.html +2 -2
  65. mage_ai/server/frontend_dist/test.html +2 -2
  66. mage_ai/server/frontend_dist/triggers.html +2 -2
  67. mage_ai/server/frontend_dist/version-control.html +2 -2
  68. mage_ai/server/scheduler_manager.py +7 -2
  69. mage_ai/server/server.py +21 -0
  70. mage_ai/services/newrelic/__init__.py +21 -0
  71. mage_ai/settings/__init__.py +10 -1
  72. mage_ai/shared/hash.py +2 -0
  73. mage_ai/tests/data_preparation/models/test_pipeline.py +5 -0
  74. {mage_ai-0.8.98.dist-info → mage_ai-0.8.99.dist-info}/METADATA +4 -1
  75. {mage_ai-0.8.98.dist-info → mage_ai-0.8.99.dist-info}/RECORD +81 -79
  76. mage_ai/server/frontend_dist/_next/static/chunks/pages/sign-in-2925c2c1b0c5559a.js +0 -1
  77. mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-63fbd3334e4509d7.js +0 -1
  78. /mage_ai/server/frontend_dist/_next/static/{-1TwyIVv18EeGuJPahFzG → WRxCTOtmZhTqQws_7OJZD}/_middlewareManifest.js +0 -0
  79. /mage_ai/server/frontend_dist/_next/static/{-1TwyIVv18EeGuJPahFzG → WRxCTOtmZhTqQws_7OJZD}/_ssgManifest.js +0 -0
  80. {mage_ai-0.8.98.dist-info → mage_ai-0.8.99.dist-info}/LICENSE +0 -0
  81. {mage_ai-0.8.98.dist-info → mage_ai-0.8.99.dist-info}/WHEEL +0 -0
  82. {mage_ai-0.8.98.dist-info → mage_ai-0.8.99.dist-info}/entry_points.txt +0 -0
  83. {mage_ai-0.8.98.dist-info → mage_ai-0.8.99.dist-info}/top_level.txt +0 -0
@@ -10,9 +10,16 @@ class OauthPolicy(BasePolicy):
10
10
 
11
11
  OauthPolicy.allow_actions([
12
12
  constants.CREATE,
13
+ ], scopes=[
14
+ OauthScope.CLIENT_PRIVATE,
15
+ ], condition=lambda policy: policy.has_at_least_viewer_role())
16
+
17
+
18
+ OauthPolicy.allow_actions([
13
19
  constants.DETAIL,
14
20
  ], scopes=[
15
21
  OauthScope.CLIENT_PRIVATE,
22
+ OauthScope.CLIENT_PUBLIC,
16
23
  ], condition=lambda policy: policy.has_at_least_viewer_role())
17
24
 
18
25
 
@@ -20,6 +27,13 @@ OauthPolicy.allow_read(OauthPresenter.default_attributes, scopes=[
20
27
  OauthScope.CLIENT_PRIVATE,
21
28
  ], on_action=[
22
29
  constants.CREATE,
30
+ ], condition=lambda policy: policy.has_at_least_viewer_role())
31
+
32
+
33
+ OauthPolicy.allow_read(OauthPresenter.default_attributes, scopes=[
34
+ OauthScope.CLIENT_PRIVATE,
35
+ OauthScope.CLIENT_PUBLIC,
36
+ ], on_action=[
23
37
  constants.DETAIL,
24
38
  ], condition=lambda policy: policy.has_at_least_viewer_role())
25
39
 
@@ -40,4 +54,5 @@ OauthPolicy.allow_query([
40
54
  constants.DETAIL,
41
55
  ], scopes=[
42
56
  OauthScope.CLIENT_PRIVATE,
57
+ OauthScope.CLIENT_PUBLIC,
43
58
  ], condition=lambda policy: policy.has_at_least_viewer_role())
@@ -46,6 +46,8 @@ SessionPolicy.allow_write([
46
46
  'email',
47
47
  'password',
48
48
  'username',
49
+ 'token',
50
+ 'provider',
49
51
  ], scopes=[
50
52
  OauthScope.CLIENT_PUBLIC,
51
53
  ], on_action=[
@@ -12,6 +12,7 @@ class PipelinePresenter(BasePresenter):
12
12
  'executor_count',
13
13
  'executor_type',
14
14
  'name',
15
+ 'notification_config',
15
16
  'spark_config',
16
17
  'type',
17
18
  'updated_at',
@@ -12,6 +12,7 @@ class SyncPresenter(BasePresenter):
12
12
  'ssh_private_key',
13
13
  'ssh_public_key',
14
14
  'sync_on_pipeline_run',
15
+ 'sync_on_start',
15
16
  'type',
16
17
  'user_git_settings',
17
18
  'username',
@@ -1,3 +1,4 @@
1
+ import json
1
2
  import urllib.parse
2
3
  from datetime import datetime, timedelta
3
4
 
@@ -5,13 +6,17 @@ from mage_ai.api.errors import ApiError
5
6
  from mage_ai.api.resources.GenericResource import GenericResource
6
7
  from mage_ai.authentication.oauth2 import generate_access_token
7
8
  from mage_ai.authentication.oauth.constants import (
9
+ ACTIVE_DIRECTORY_CLIENT_ID,
8
10
  GITHUB_CLIENT_ID,
9
11
  GITHUB_STATE,
12
+ OAUTH_PROVIDER_ACTIVE_DIRECTORY,
10
13
  OAUTH_PROVIDER_GITHUB,
14
+ VALID_OAUTH_PROVIDERS,
11
15
  )
12
16
  from mage_ai.authentication.oauth.utils import access_tokens_for_provider
13
17
  from mage_ai.orchestration.db import safe_db_query
14
18
  from mage_ai.orchestration.db.models.oauth import Oauth2AccessToken, Oauth2Application
19
+ from mage_ai.settings import ACTIVE_DIRECTORY_DIRECTORY_ID
15
20
 
16
21
 
17
22
  class OauthResource(GenericResource):
@@ -23,7 +28,7 @@ class OauthResource(GenericResource):
23
28
  provider = payload.get('provider')
24
29
  token = payload.get('token')
25
30
 
26
- if not provider or provider not in [OAUTH_PROVIDER_GITHUB]:
31
+ if not provider or provider not in VALID_OAUTH_PROVIDERS:
27
32
  error.update(dict(message='Invalid provider.'))
28
33
  raise ApiError(error)
29
34
 
@@ -66,7 +71,7 @@ class OauthResource(GenericResource):
66
71
  model = dict(provider=pk)
67
72
 
68
73
  error = ApiError.RESOURCE_INVALID.copy()
69
- if pk not in [OAUTH_PROVIDER_GITHUB]:
74
+ if pk not in VALID_OAUTH_PROVIDERS:
70
75
  error.update(dict(message='Invalid provider.'))
71
76
  raise ApiError(error)
72
77
 
@@ -95,5 +100,26 @@ class OauthResource(GenericResource):
95
100
  query_strings.append(f'{k}={v}')
96
101
 
97
102
  model['url'] = f"https://github.com/login/oauth/authorize?{'&'.join(query_strings)}"
103
+ elif OAUTH_PROVIDER_ACTIVE_DIRECTORY == pk:
104
+ ad_directory_id = ACTIVE_DIRECTORY_DIRECTORY_ID
105
+ if ad_directory_id:
106
+ mage_redirect_uri = '?'.join([
107
+ redirect_uri,
108
+ f'provider={pk}',
109
+ ])
110
+ query = dict(
111
+ client_id=ACTIVE_DIRECTORY_CLIENT_ID,
112
+ redirect_uri=f'https://api.mage.ai/v1/oauth/{pk}',
113
+ response_type='code',
114
+ scope='User.Read',
115
+ state=urllib.parse.quote_plus(json.dumps(dict(
116
+ redirect_uri=mage_redirect_uri,
117
+ tenant_id=ad_directory_id,
118
+ ))),
119
+ )
120
+ query_strings = []
121
+ for k, v in query.items():
122
+ query_strings.append(f'{k}={v}')
123
+ model['url'] = f"https://login.microsoftonline.com/{ad_directory_id}/oauth2/v2.0/authorize?{'&'.join(query_strings)}" # noqa: E501
98
124
 
99
125
  return self(model, user, **kwargs)
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
 
3
+ from sqlalchemy import or_
3
4
  from sqlalchemy.orm import aliased
4
5
 
5
6
  from mage_ai.api.operations.constants import DELETE
@@ -81,7 +82,10 @@ class PipelineResource(BaseResource):
81
82
  ]).
82
83
  filter(
83
84
  a.pipeline_uuid.in_(pipeline_uuids),
84
- a.repo_path == get_repo_path(),
85
+ or_(
86
+ a.repo_path == get_repo_path(),
87
+ a.repo_path.is_(None),
88
+ )
85
89
  )
86
90
  ).all()
87
91
  return group_by(lambda x: x.pipeline_uuid, result)
@@ -4,6 +4,8 @@ from mage_ai.api.errors import ApiError
4
4
  from mage_ai.api.resources.BaseResource import BaseResource
5
5
  from mage_ai.authentication.ldap import new_ldap_connection
6
6
  from mage_ai.authentication.oauth2 import encode_token, generate_access_token
7
+ from mage_ai.authentication.oauth.active_directory import get_user_info
8
+ from mage_ai.authentication.oauth.constants import OAUTH_PROVIDER_ACTIVE_DIRECTORY
7
9
  from mage_ai.authentication.passwords import verify_password
8
10
  from mage_ai.orchestration.db import safe_db_query
9
11
  from mage_ai.orchestration.db.models.oauth import Role, User
@@ -18,6 +20,22 @@ class SessionResource(BaseResource):
18
20
  email = payload.get('email')
19
21
  password = payload.get('password')
20
22
  username = payload.get('username')
23
+ token = payload.get('token')
24
+ provider = payload.get('provider')
25
+
26
+ if token and provider:
27
+ if provider == OAUTH_PROVIDER_ACTIVE_DIRECTORY:
28
+ user_info = get_user_info(token)
29
+ principal_name = user_info.get('userPrincipalName')
30
+ user = User.query.filter(User.email == principal_name).first()
31
+ if not user: # noqa: E712
32
+ print('first user login, creating user.')
33
+ user = User.create(
34
+ username=principal_name,
35
+ email=principal_name,
36
+ )
37
+ oauth_token = generate_access_token(user, kwargs['oauth_client'])
38
+ return self(oauth_token, user, **kwargs)
21
39
 
22
40
  error = ApiError.RESOURCE_NOT_FOUND
23
41
  error.update({'message': 'Email/username and/or password invalid.'})
@@ -46,7 +46,8 @@ class WorkspaceResource(GenericResource):
46
46
  query_user = None
47
47
  if user_id:
48
48
  user_id = user_id[0]
49
- query_user = User.query.get(user_id)
49
+ if user_id:
50
+ query_user = User.query.get(user_id)
50
51
 
51
52
  instances = self.get_instances(cluster_type)
52
53
  instance_map = {
@@ -313,7 +314,7 @@ class WorkspaceResource(GenericResource):
313
314
  namespace = os.getenv(KUBE_NAMESPACE)
314
315
  workload_manager = WorkloadManager(namespace)
315
316
 
316
- instances = workload_manager.list_services()
317
+ instances = workload_manager.list_workloads()
317
318
  elif cluster_type == ClusterType.ECS:
318
319
  from mage_ai.cluster_manager.aws.ecs_task_manager import EcsTaskManager
319
320
 
@@ -0,0 +1,17 @@
1
+ from typing import Dict
2
+
3
+ import requests
4
+
5
+
6
+ def get_user_info(token: str) -> Dict:
7
+ url = 'https://graph.microsoft.com/v1.0/me'
8
+
9
+ headers = {
10
+ 'Content-Type': 'application\\json',
11
+ 'Authorization': 'Bearer {}'.format(token)
12
+ }
13
+
14
+ resp = requests.get(url, headers=headers)
15
+ result = resp.json()
16
+
17
+ return result
@@ -1,3 +1,12 @@
1
+ ACTIVE_DIRECTORY_CLIENT_ID = '51aec820-9d49-40a9-b046-17c1f28f620d'
2
+
1
3
  GITHUB_CLIENT_ID = '8577f13ddc81e2848b07'
2
4
  GITHUB_STATE = '1337'
5
+
6
+ OAUTH_PROVIDER_ACTIVE_DIRECTORY = 'active_directory'
3
7
  OAUTH_PROVIDER_GITHUB = 'github'
8
+
9
+ VALID_OAUTH_PROVIDERS = [
10
+ OAUTH_PROVIDER_ACTIVE_DIRECTORY,
11
+ OAUTH_PROVIDER_GITHUB,
12
+ ]
mage_ai/cli/main.py CHANGED
@@ -10,6 +10,7 @@ from typer.core import TyperGroup
10
10
 
11
11
  from mage_ai.cli.utils import parse_runtime_variables
12
12
  from mage_ai.data_preparation.repo_manager import ProjectType
13
+ from mage_ai.services.newrelic import initialize_new_relic
13
14
  from mage_ai.shared.constants import InstanceType
14
15
 
15
16
 
@@ -191,6 +192,9 @@ def run(
191
192
  project_path = os.path.abspath(project_path)
192
193
  set_repo_path(project_path)
193
194
 
195
+ from contextlib import nullcontext
196
+
197
+ import newrelic.agent
194
198
  import sentry_sdk
195
199
 
196
200
  from mage_ai.data_preparation.executors.executor_factory import ExecutorFactory
@@ -207,55 +211,58 @@ def run(
207
211
  sentry_dsn,
208
212
  traces_sample_rate=SENTRY_TRACES_SAMPLE_RATE,
209
213
  )
210
-
211
- runtime_variables = dict()
212
- if runtime_vars is not None:
213
- runtime_variables = parse_runtime_variables(runtime_vars)
214
-
215
- sys.path.append(os.path.dirname(project_path))
216
- pipeline = Pipeline.get(pipeline_uuid, repo_path=project_path)
217
-
218
- db_connection.start_session()
219
-
220
- if pipeline_run_id is None:
221
- default_variables = get_global_variables(pipeline_uuid)
222
- global_vars = merge_dict(default_variables, runtime_variables)
223
- else:
224
- pipeline_run = PipelineRun.query.get(pipeline_run_id)
225
- global_vars = pipeline_run.get_variables(extra_variables=runtime_variables)
226
-
227
- if template_runtime_configuration is not None:
228
- template_runtime_configuration = json.loads(template_runtime_configuration)
229
-
230
- if block_uuid is None:
231
- ExecutorFactory.get_pipeline_executor(
232
- pipeline,
233
- execution_partition=execution_partition,
234
- executor_type=executor_type,
235
- ).execute(
236
- analyze_outputs=False,
237
- global_vars=global_vars,
238
- pipeline_run_id=pipeline_run_id,
239
- run_sensors=not skip_sensors,
240
- run_tests=test,
241
- update_status=False,
242
- )
243
- else:
244
- ExecutorFactory.get_block_executor(
245
- pipeline,
246
- block_uuid,
247
- execution_partition=execution_partition,
248
- executor_type=executor_type,
249
- ).execute(
250
- analyze_outputs=False,
251
- block_run_id=block_run_id,
252
- callback_url=callback_url,
253
- global_vars=global_vars,
254
- pipeline_run_id=pipeline_run_id,
255
- template_runtime_configuration=template_runtime_configuration,
256
- update_status=False,
257
- )
258
- print('Pipeline run completed.')
214
+ (enable_new_relic, application) = initialize_new_relic()
215
+
216
+ with newrelic.agent.BackgroundTask(application, name="mage-run", group='Task') \
217
+ if enable_new_relic else nullcontext():
218
+ runtime_variables = dict()
219
+ if runtime_vars is not None:
220
+ runtime_variables = parse_runtime_variables(runtime_vars)
221
+
222
+ sys.path.append(os.path.dirname(project_path))
223
+ pipeline = Pipeline.get(pipeline_uuid, repo_path=project_path)
224
+
225
+ db_connection.start_session()
226
+
227
+ if pipeline_run_id is None:
228
+ default_variables = get_global_variables(pipeline_uuid)
229
+ global_vars = merge_dict(default_variables, runtime_variables)
230
+ else:
231
+ pipeline_run = PipelineRun.query.get(pipeline_run_id)
232
+ global_vars = pipeline_run.get_variables(extra_variables=runtime_variables)
233
+
234
+ if template_runtime_configuration is not None:
235
+ template_runtime_configuration = json.loads(template_runtime_configuration)
236
+
237
+ if block_uuid is None:
238
+ ExecutorFactory.get_pipeline_executor(
239
+ pipeline,
240
+ execution_partition=execution_partition,
241
+ executor_type=executor_type,
242
+ ).execute(
243
+ analyze_outputs=False,
244
+ global_vars=global_vars,
245
+ pipeline_run_id=pipeline_run_id,
246
+ run_sensors=not skip_sensors,
247
+ run_tests=test,
248
+ update_status=False,
249
+ )
250
+ else:
251
+ ExecutorFactory.get_block_executor(
252
+ pipeline,
253
+ block_uuid,
254
+ execution_partition=execution_partition,
255
+ executor_type=executor_type,
256
+ ).execute(
257
+ analyze_outputs=False,
258
+ block_run_id=block_run_id,
259
+ callback_url=callback_url,
260
+ global_vars=global_vars,
261
+ pipeline_run_id=pipeline_run_id,
262
+ template_runtime_configuration=template_runtime_configuration,
263
+ update_status=False,
264
+ )
265
+ print('Pipeline run completed.')
259
266
 
260
267
 
261
268
  @app.command()
@@ -26,6 +26,7 @@ from mage_ai.orchestration.constants import (
26
26
  DB_USER,
27
27
  )
28
28
  from mage_ai.settings import MAGE_SETTINGS_ENVIRONMENT_VARIABLES
29
+ from mage_ai.shared.array import find
29
30
 
30
31
 
31
32
  class WorkloadManager:
@@ -53,24 +54,57 @@ class WorkloadManager:
53
54
 
54
55
  return False
55
56
 
56
- def list_services(self):
57
+ def list_workloads(self):
57
58
  services = self.core_client.list_namespaced_service(self.namespace).items
58
- services_list = []
59
+ workloads_list = []
60
+
61
+ pods = self.core_client.list_namespaced_pod(self.namespace).items
62
+ pod_map = dict()
63
+ for pod in pods:
64
+ try:
65
+ name = pod.metadata.labels.get('app')
66
+ pod_map[name] = pod
67
+ except Exception:
68
+ pass
59
69
  for service in services:
60
70
  try:
61
71
  labels = service.metadata.labels
62
72
  if not labels.get('dev-instance'):
63
73
  continue
64
- conditions = service.status.conditions or list()
65
- services_list.append(dict(
74
+ name = labels.get('app')
75
+ pod = pod_map[name]
76
+ service_type = service.spec.type
77
+ workload = dict(
66
78
  name=labels.get('app'),
67
- status='RUNNING' if len(conditions) == 0 else conditions[0].status,
68
- type='kubernetes',
69
- ))
79
+ type=service_type,
80
+ )
81
+ if pod:
82
+ status = pod.status.phase
83
+ workload['status'] = status.upper()
84
+
85
+ node_name = pod.spec.node_name
86
+ ip = None
87
+ if service_type == 'NodePort':
88
+ try:
89
+ if node_name:
90
+ items = self.core_client.list_node(
91
+ field_selector=f'metadata.name={node_name}').items
92
+ node = items[0]
93
+ ip = find(
94
+ lambda a: a.type == 'ExternalIP',
95
+ node.status.addresses
96
+ ).address
97
+ if ip:
98
+ node_port = service.spec.ports[0].node_port
99
+ workload['ip'] = f'{ip}:{node_port}'
100
+ except Exception:
101
+ pass
102
+
103
+ workloads_list.append(workload)
70
104
  except Exception:
71
105
  pass
72
106
 
73
- return services_list
107
+ return workloads_list
74
108
 
75
109
  def create_workload(
76
110
  self,
@@ -88,7 +122,10 @@ class WorkloadManager:
88
122
  'service_account_name',
89
123
  os.getenv(KUBE_SERVICE_ACCOUNT_NAME),
90
124
  )
91
- storage_class_name = kwargs.get('storage_class_name', os.getenv(KUBE_STORAGE_CLASS_NAME))
125
+ storage_class_name = kwargs.get(
126
+ 'storage_class_name',
127
+ os.getenv(KUBE_STORAGE_CLASS_NAME, 'default'),
128
+ )
92
129
  storage_access_mode = kwargs.get('storage_access_mode', 'ReadWriteOnce')
93
130
  storage_request_size = kwargs.get('storage_request_size', 2)
94
131
 
@@ -486,7 +486,7 @@ class Git:
486
486
  self._run_command(cmd)
487
487
  print(f'Installing {requirements_file} completed successfully.')
488
488
  except Exception as err:
489
- print(f'Skip installing {requirements_file} due to error: {err}')
489
+ print(f'Skip installing {requirements_file} due to error: {str(err)}')
490
490
  pass
491
491
 
492
492
  def __create_ssh_keys(self) -> str:
@@ -28,6 +28,7 @@ from mage_ai.data_preparation.models.block.sql import (
28
28
  snowflake,
29
29
  spark,
30
30
  trino,
31
+ clickhouse,
31
32
  )
32
33
  from mage_ai.data_preparation.models.constants import BlockLanguage, BlockType
33
34
  from mage_ai.data_preparation.repo_manager import get_repo_path
@@ -597,6 +598,23 @@ def config_file_loader_and_configuration(block, profile_target: str) -> Dict:
597
598
  data_provider_schema=schema,
598
599
  export_write_policy=ExportWritePolicy.REPLACE,
599
600
  )
601
+ elif DataSource.CLICKHOUSE == profile_type:
602
+ database = profile.get('schema')
603
+ interface = profile.get('driver')
604
+
605
+ config_file_loader = ConfigFileLoader(config=dict(
606
+ CLICKHOUSE_DATABASE=database,
607
+ CLICKHOUSE_HOST=profile.get('host'),
608
+ CLICKHOUSE_INTERFACE=interface,
609
+ CLICKHOUSE_PASSWORD=profile.get('password'),
610
+ CLICKHOUSE_PORT=profile.get('port'),
611
+ CLICKHOUSE_USERNAME=profile.get('user'),
612
+ ))
613
+ configuration = dict(
614
+ data_provider=profile_type,
615
+ data_provider_database=database,
616
+ export_write_policy=ExportWritePolicy.REPLACE,
617
+ )
600
618
 
601
619
  if not config_file_loader or not configuration:
602
620
  attr = parse_attributes(block)
@@ -733,6 +751,15 @@ def create_upstream_tables(
733
751
  block,
734
752
  **kwargs_shared,
735
753
  )
754
+ elif DataSource.CLICKHOUSE == data_provider:
755
+ from mage_ai.io.clickhouse import ClickHouse
756
+
757
+ loader = ClickHouse.with_config(config_file_loader)
758
+ clickhouse.create_upstream_block_tables(
759
+ loader,
760
+ block,
761
+ **kwargs_shared,
762
+ )
736
763
 
737
764
  block.upstream_blocks = upstream_blocks_init
738
765
 
@@ -930,6 +957,11 @@ def execute_query(
930
957
 
931
958
  with Trino.with_config(config_file_loader) as loader:
932
959
  return loader.load(query_string, **shared_kwargs)
960
+ elif DataSource.CLICKHOUSE == data_provider:
961
+ from mage_ai.io.clickhouse import ClickHouse
962
+
963
+ loader = ClickHouse.with_config(config_file_loader)
964
+ return loader.load(query_string, **shared_kwargs)
933
965
 
934
966
 
935
967
  def query_from_compiled_sql(block, profile_target: str, limit: int = None) -> DataFrame:
@@ -137,8 +137,9 @@ def execute_sql_code(
137
137
  NotFound: 404 Not found: Table database:schema.table_name
138
138
  was not found in location XX
139
139
  """
140
+ total_retries = 5
140
141
  tries = 0
141
- while tries < 10:
142
+ while tries < total_retries:
142
143
  sleep(tries)
143
144
  tries += 1
144
145
  try:
@@ -149,7 +150,7 @@ def execute_sql_code(
149
150
  )
150
151
  return [result]
151
152
  except Exception as err:
152
- if '404' not in str(err):
153
+ if '404' not in str(err) or tries == total_retries:
153
154
  raise err
154
155
  elif DataSource.CLICKHOUSE.value == data_provider:
155
156
  from mage_ai.io.clickhouse import ClickHouse
@@ -53,6 +53,7 @@ class Pipeline:
53
53
  self.executor_type = None
54
54
  self.executor_config = dict()
55
55
  self.name = None
56
+ self.notification_config = dict()
56
57
  self.repo_path = repo_path or get_repo_path()
57
58
  self.schedules = []
58
59
  self.uuid = uuid
@@ -425,7 +426,8 @@ class Pipeline:
425
426
  self.callback_configs = config.get('callbacks') or []
426
427
  self.conditional_configs = config.get('conditionals') or []
427
428
  self.executor_type = config.get('executor_type')
428
- self.executor_config = config.get('executor_confid') or dict()
429
+ self.executor_config = config.get('executor_config') or dict()
430
+ self.notification_config = config.get('notification_config') or dict()
429
431
  self.spark_config = config.get('spark_config') or dict()
430
432
  self.widget_configs = config.get('widgets') or []
431
433
 
@@ -543,6 +545,7 @@ class Pipeline:
543
545
  executor_count=self.executor_count,
544
546
  executor_type=self.executor_type,
545
547
  name=self.name,
548
+ notification_config=self.notification_config,
546
549
  type=self.type.value if type(self.type) is not str else self.type,
547
550
  updated_at=self.updated_at,
548
551
  uuid=self.uuid,
@@ -16,7 +16,8 @@ GIT_USERNAME_VAR = 'GIT_USERNAME'
16
16
  GIT_EMAIL_VAR = 'GIT_EMAIL'
17
17
  GIT_AUTH_TYPE_VAR = 'GIT_AUTH_TYPE'
18
18
  GIT_BRANCH_VAR = 'GIT_BRANCH'
19
- GIT_SYNC_ON_PIPELINE_RUN_TYPE = 'GIT_SYNC_ON_PIPELINE_RUN'
19
+ GIT_SYNC_ON_PIPELINE_RUN_VAR = 'GIT_SYNC_ON_PIPELINE_RUN'
20
+ GIT_SYNC_ON_START_VAR = 'GIT_SYNC_ON_PIPELINE_RUN'
20
21
 
21
22
 
22
23
  class Preferences:
@@ -52,7 +53,8 @@ class Preferences:
52
53
  username=os.getenv(GIT_USERNAME_VAR),
53
54
  email=os.getenv(GIT_EMAIL_VAR),
54
55
  branch=os.getenv(GIT_BRANCH_VAR),
55
- sync_on_pipeline_run=bool(int(os.getenv(GIT_SYNC_ON_PIPELINE_RUN_TYPE) or 0)),
56
+ sync_on_pipeline_run=bool(int(os.getenv(GIT_SYNC_ON_PIPELINE_RUN_VAR) or 0)),
57
+ sync_on_start=bool(int(os.getenv(GIT_SYNC_ON_START_VAR) or 0)),
56
58
  )
57
59
  else:
58
60
  project_sync_config = project_preferences.get('sync_config', dict())
@@ -213,7 +213,11 @@ def get_repo_config(repo_path=None) -> RepoConfig:
213
213
 
214
214
 
215
215
  def get_project_type(repo_path=None) -> ProjectType:
216
- return get_repo_config(repo_path=repo_path).project_type
216
+ try:
217
+ return get_repo_config(repo_path=repo_path).project_type
218
+ except Exception:
219
+ # default to standalone project type
220
+ return ProjectType.STANDALONE
217
221
 
218
222
 
219
223
  def set_repo_path(repo_path: str) -> None:
@@ -21,6 +21,7 @@ class GitConfig(BaseConfig):
21
21
  repo_path: str = os.getcwd()
22
22
  branch: str = 'main'
23
23
  sync_on_pipeline_run: bool = False
24
+ sync_on_start: bool = False
24
25
  auth_type: AuthType = AuthType.SSH
25
26
  # User settings moved to UserGitConfig, these will be used for Git syncs
26
27
  username: str = ''
@@ -11,12 +11,9 @@ class GitSync(BaseSync):
11
11
  self.git_manager = Git(sync_config)
12
12
 
13
13
  def sync_data(self):
14
- with VerboseFunctionExec(
15
- f'Syncing data with remote repo {self.remote_repo_link}',
16
- verbose=True,
17
- ):
18
- self.git_manager.reset(self.branch)
14
+ self.git_manager.reset(self.branch)
19
15
 
16
+ # Reset git sync by cloning the remote repo
20
17
  def reset(self):
21
18
  with VerboseFunctionExec(
22
19
  f'Attempting to clone from remote repo {self.remote_repo_link}',
@@ -51,6 +51,8 @@ def write_template(template_source: str, dest_path: str) -> None:
51
51
  template_source (str): Template source code to write to file
52
52
  dest_path (str): Destination file to write template source code to.
53
53
  """
54
+ os.makedirs(os.path.dirname(dest_path), exist_ok=True)
55
+
54
56
  with open(dest_path, 'w') as foutput:
55
57
  foutput.write(template_source)
56
58