mage-ai 0.8.97__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 (152) hide show
  1. mage_ai/api/policies/AutocompleteItemPolicy.py +2 -1
  2. mage_ai/api/policies/BasePolicy.py +2 -2
  3. mage_ai/api/policies/BlockTemplatePolicy.py +2 -1
  4. mage_ai/api/policies/ClusterPolicy.py +2 -1
  5. mage_ai/api/policies/DataProviderPolicy.py +2 -1
  6. mage_ai/api/policies/EventRulePolicy.py +2 -1
  7. mage_ai/api/policies/ExtensionOptionPolicy.py +2 -1
  8. mage_ai/api/policies/FileVersionPolicy.py +2 -1
  9. mage_ai/api/policies/GitBranchPolicy.py +9 -0
  10. mage_ai/api/policies/KernelPolicy.py +2 -1
  11. mage_ai/api/policies/LogPolicy.py +2 -2
  12. mage_ai/api/policies/OauthPolicy.py +15 -0
  13. mage_ai/api/policies/OutputPolicy.py +2 -2
  14. mage_ai/api/policies/PipelinePolicy.py +2 -2
  15. mage_ai/api/policies/PipelineRunPolicy.py +2 -2
  16. mage_ai/api/policies/PipelineSchedulePolicy.py +2 -2
  17. mage_ai/api/policies/PullRequestPolicy.py +64 -0
  18. mage_ai/api/policies/SessionPolicy.py +4 -1
  19. mage_ai/api/policies/VariablePolicy.py +2 -2
  20. mage_ai/api/policies/WidgetPolicy.py +2 -2
  21. mage_ai/api/policies/WorkspacePolicy.py +3 -3
  22. mage_ai/api/presenters/PipelinePresenter.py +1 -0
  23. mage_ai/api/presenters/PullRequestPresenter.py +16 -0
  24. mage_ai/api/presenters/StatusPresenter.py +2 -0
  25. mage_ai/api/presenters/SyncPresenter.py +1 -0
  26. mage_ai/api/presenters/WorkspacePresenter.py +2 -0
  27. mage_ai/api/resources/GitBranchResource.py +81 -26
  28. mage_ai/api/resources/OauthResource.py +31 -4
  29. mage_ai/api/resources/PipelineResource.py +8 -1
  30. mage_ai/api/resources/PullRequestResource.py +87 -0
  31. mage_ai/api/resources/RoleResource.py +6 -3
  32. mage_ai/api/resources/SecretResource.py +2 -5
  33. mage_ai/api/resources/SessionResource.py +18 -0
  34. mage_ai/api/resources/StatusResource.py +7 -3
  35. mage_ai/api/resources/UserResource.py +11 -16
  36. mage_ai/api/resources/WorkspaceResource.py +83 -53
  37. mage_ai/authentication/oauth/active_directory.py +17 -0
  38. mage_ai/authentication/oauth/constants.py +9 -0
  39. mage_ai/authentication/oauth/utils.py +2 -1
  40. mage_ai/authentication/oauth2.py +9 -3
  41. mage_ai/cli/main.py +94 -51
  42. mage_ai/cluster_manager/kubernetes/workload_manager.py +141 -45
  43. mage_ai/data_preparation/git/__init__.py +86 -16
  44. mage_ai/data_preparation/git/api.py +175 -0
  45. mage_ai/data_preparation/models/block/dbt/utils/__init__.py +49 -14
  46. mage_ai/data_preparation/models/block/sql/__init__.py +3 -2
  47. mage_ai/data_preparation/models/pipeline.py +4 -1
  48. mage_ai/data_preparation/models/pipelines/integration_pipeline.py +7 -3
  49. mage_ai/data_preparation/preferences.py +4 -2
  50. mage_ai/data_preparation/repo_manager.py +41 -10
  51. mage_ai/data_preparation/shared/secrets.py +5 -6
  52. mage_ai/data_preparation/sync/__init__.py +2 -1
  53. mage_ai/data_preparation/sync/git_sync.py +2 -5
  54. mage_ai/data_preparation/templates/utils.py +2 -0
  55. mage_ai/orchestration/db/models/oauth.py +22 -4
  56. mage_ai/orchestration/pipeline_scheduler.py +19 -8
  57. mage_ai/orchestration/queue/process_queue.py +15 -12
  58. mage_ai/server/api/clusters.py +21 -11
  59. mage_ai/server/constants.py +1 -1
  60. mage_ai/server/frontend_dist/404.html +2 -2
  61. mage_ai/server/frontend_dist/404.html.html +2 -2
  62. mage_ai/server/frontend_dist/_next/static/WRxCTOtmZhTqQws_7OJZD/_buildManifest.js +1 -0
  63. mage_ai/server/frontend_dist/_next/static/chunks/{1286-993725c925c56a98.js → 1286-b90bd4b7f8abfc3a.js} +1 -1
  64. mage_ai/server/frontend_dist/_next/static/chunks/{1424-f475cae42f8a7fca.js → 1424-90c0f66ba2f86b88.js} +1 -1
  65. mage_ai/server/frontend_dist/_next/static/chunks/3883-c95563b9f60ae526.js +1 -0
  66. mage_ai/server/frontend_dist/_next/static/chunks/6694-c8f2a68074420906.js +1 -0
  67. mage_ai/server/frontend_dist/_next/static/chunks/{9350-1ff50f1d7b9ee754.js → 9350-5191c83a8d0cf454.js} +1 -1
  68. mage_ai/server/frontend_dist/_next/static/chunks/pages/{_app-3527178abd99bc87.js → _app-171846e16d26855a.js} +1 -1
  69. mage_ai/server/frontend_dist/_next/static/chunks/pages/files-e4e778f8f5e1bf2e.js +1 -0
  70. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/settings-c788c1b127999825.js +1 -0
  71. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users/[user]-b4650224a19e8fe6.js +1 -0
  72. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users/new-931eb719e3fae29c.js +1 -0
  73. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users-d3724bde0b186dd9.js +1 -0
  74. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-af11f9cf94024ac0.js +1 -0
  75. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/{[...slug]-3ec5eb9562e4bff4.js → [...slug]-34326db259f922d1.js} +1 -1
  76. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-503ecb7a72257b79.js +1 -0
  77. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs/{[run]-7667080098731e30.js → [run]-2994b8ab7862c07b.js} +1 -1
  78. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs-7b31b851e2544b42.js +1 -0
  79. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers/{[...slug]-e18058e13882b20d.js → [...slug]-4445619d4eabe065.js} +1 -1
  80. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{triggers-6854c10d5589d394.js → triggers-b7db0b682fadb840.js} +1 -1
  81. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/account/profile-ee0931af3abb55b3.js +1 -0
  82. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-f8a59d718751be9a.js +1 -0
  83. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/sync-data-90f8830890036eb2.js +1 -0
  84. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/users-9f82673fc438ea83.js +1 -0
  85. mage_ai/server/frontend_dist/_next/static/chunks/pages/sign-in-a1871b8a537d823c.js +1 -0
  86. mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-48859b4e9c846212.js +1 -0
  87. mage_ai/server/frontend_dist/files.html +2 -2
  88. mage_ai/server/frontend_dist/index.html +2 -2
  89. mage_ai/server/frontend_dist/manage/settings.html +24 -0
  90. mage_ai/server/frontend_dist/manage/users/[user].html +2 -2
  91. mage_ai/server/frontend_dist/manage/users/new.html +24 -0
  92. mage_ai/server/frontend_dist/manage/users.html +2 -2
  93. mage_ai/server/frontend_dist/manage.html +2 -2
  94. mage_ai/server/frontend_dist/pipeline-runs.html +2 -2
  95. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills/[...slug].html +2 -2
  96. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills.html +2 -2
  97. mage_ai/server/frontend_dist/pipelines/[pipeline]/edit.html +2 -2
  98. mage_ai/server/frontend_dist/pipelines/[pipeline]/logs.html +2 -2
  99. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runs.html +2 -2
  100. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
  101. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors.html +2 -2
  102. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs/[run].html +2 -2
  103. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs.html +2 -2
  104. mage_ai/server/frontend_dist/pipelines/[pipeline]/settings.html +2 -2
  105. mage_ai/server/frontend_dist/pipelines/[pipeline]/syncs.html +2 -2
  106. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers/[...slug].html +2 -2
  107. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers.html +2 -2
  108. mage_ai/server/frontend_dist/pipelines/[pipeline].html +2 -2
  109. mage_ai/server/frontend_dist/pipelines.html +2 -2
  110. mage_ai/server/frontend_dist/settings/account/profile.html +2 -2
  111. mage_ai/server/frontend_dist/settings/workspace/preferences.html +2 -2
  112. mage_ai/server/frontend_dist/settings/workspace/sync-data.html +2 -2
  113. mage_ai/server/frontend_dist/settings/workspace/users.html +2 -2
  114. mage_ai/server/frontend_dist/settings.html +2 -2
  115. mage_ai/server/frontend_dist/sign-in.html +2 -2
  116. mage_ai/server/frontend_dist/terminal.html +2 -2
  117. mage_ai/server/frontend_dist/test.html +2 -2
  118. mage_ai/server/frontend_dist/triggers.html +2 -2
  119. mage_ai/server/frontend_dist/version-control.html +2 -2
  120. mage_ai/server/scheduler_manager.py +7 -2
  121. mage_ai/server/server.py +37 -3
  122. mage_ai/server/terminal_server.py +2 -2
  123. mage_ai/server/websocket_server.py +6 -2
  124. mage_ai/services/newrelic/__init__.py +21 -0
  125. mage_ai/settings/__init__.py +32 -0
  126. mage_ai/shared/hash.py +2 -0
  127. mage_ai/tests/api/test_utils.py +29 -2
  128. mage_ai/tests/data_preparation/models/test_pipeline.py +5 -0
  129. {mage_ai-0.8.97.dist-info → mage_ai-0.8.99.dist-info}/METADATA +8 -3
  130. {mage_ai-0.8.97.dist-info → mage_ai-0.8.99.dist-info}/RECORD +136 -127
  131. mage_ai/data_preparation/templates/main/projects/__init__.py +0 -0
  132. mage_ai/server/frontend_dist/_next/static/YLZRSrQ0aqtl-GGePfsMB/_buildManifest.js +0 -1
  133. mage_ai/server/frontend_dist/_next/static/chunks/3077-d58f18ed770e5137.js +0 -1
  134. mage_ai/server/frontend_dist/_next/static/chunks/3714-b676173cd4d8d86c.js +0 -1
  135. mage_ai/server/frontend_dist/_next/static/chunks/pages/files-82b5409dac9564f4.js +0 -1
  136. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users/[user]-bb6aaa23e92a5add.js +0 -1
  137. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users-c91ee702a4cd7a6f.js +0 -1
  138. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-7961010cb0fb9abd.js +0 -1
  139. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-7b8ce89f0d717465.js +0 -1
  140. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs-5bd17a8f3f3d57ef.js +0 -1
  141. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/account/profile-7d75e42d5f4936bb.js +0 -1
  142. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-8220c1200472bf70.js +0 -1
  143. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/sync-data-b602fa9b6ffabd12.js +0 -1
  144. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/users-3f9d5800f268a263.js +0 -1
  145. mage_ai/server/frontend_dist/_next/static/chunks/pages/sign-in-2925c2c1b0c5559a.js +0 -1
  146. mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-5ffc663cfb0ec81e.js +0 -1
  147. /mage_ai/server/frontend_dist/_next/static/{YLZRSrQ0aqtl-GGePfsMB → WRxCTOtmZhTqQws_7OJZD}/_middlewareManifest.js +0 -0
  148. /mage_ai/server/frontend_dist/_next/static/{YLZRSrQ0aqtl-GGePfsMB → WRxCTOtmZhTqQws_7OJZD}/_ssgManifest.js +0 -0
  149. {mage_ai-0.8.97.dist-info → mage_ai-0.8.99.dist-info}/LICENSE +0 -0
  150. {mage_ai-0.8.97.dist-info → mage_ai-0.8.99.dist-info}/WHEEL +0 -0
  151. {mage_ai-0.8.97.dist-info → mage_ai-0.8.99.dist-info}/entry_points.txt +0 -0
  152. {mage_ai-0.8.97.dist-info → mage_ai-0.8.99.dist-info}/top_level.txt +0 -0
mage_ai/cli/main.py CHANGED
@@ -9,6 +9,8 @@ from rich import print
9
9
  from typer.core import TyperGroup
10
10
 
11
11
  from mage_ai.cli.utils import parse_runtime_variables
12
+ from mage_ai.data_preparation.repo_manager import ProjectType
13
+ from mage_ai.services.newrelic import initialize_new_relic
12
14
  from mage_ai.shared.constants import InstanceType
13
15
 
14
16
 
@@ -26,7 +28,17 @@ app = typer.Typer(
26
28
 
27
29
  INIT_PROJECT_PATH_DEFAULT = typer.Argument(..., help='path of the Mage project to be created.')
28
30
  INIT_PROJECT_TYPE_DEFAULT = typer.Option(
29
- 'standalone', help='type of project to create, options are main, sub, or standalone')
31
+ ProjectType.STANDALONE.value,
32
+ help='type of project to create, options are main, sub, or standalone'
33
+ )
34
+ INIT_CLUSTER_TYPE_DEFAULT = typer.Option(
35
+ None,
36
+ help='type of instance to create for workspace management',
37
+ )
38
+ INIT_PROJECT_UUID_DEFAULT = typer.Option(
39
+ None,
40
+ help='project uuid for the new project',
41
+ )
30
42
 
31
43
  START_PROJECT_PATH_DEFAULT = typer.Argument(
32
44
  os.getcwd(), help='path of the Mage project to be loaded.')
@@ -36,6 +48,18 @@ START_MANAGE_INSTANCE_DEFAULT = typer.Option('0', help='')
36
48
  START_DBT_DOCS_INSTANCE_DEFAULT = typer.Option('0', help='')
37
49
  START_INSTANCE_TYPE_DEFAULT = typer.Option(
38
50
  InstanceType.SERVER_AND_SCHEDULER.value, help='specify the instance type.')
51
+ START_PROJECT_TYPE_DEFAULT = typer.Option(
52
+ ProjectType.STANDALONE.value,
53
+ help='create project of this type if does not exist, options are main, sub, or standalone',
54
+ )
55
+ START_CLUSTER_TYPE_DEFAULT = typer.Option(
56
+ None,
57
+ help='type of instance to create for workspace management',
58
+ )
59
+ START_PROJECT_UUID_DEFAULT = typer.Option(
60
+ None,
61
+ help='set project uuid if it has not been set for the project already',
62
+ )
39
63
 
40
64
  RUN_PROJECT_PATH_DEFAULT = typer.Argument(
41
65
  ..., help='path of the Mage project that contains the pipeline.'
@@ -90,6 +114,8 @@ CREATE_SPARK_CLUSTER_PROJECT_PATH_DEFAULT = typer.Argument(
90
114
  def init(
91
115
  project_path: str = INIT_PROJECT_PATH_DEFAULT,
92
116
  project_type: Union[str, None] = INIT_PROJECT_TYPE_DEFAULT,
117
+ cluster_type: str = INIT_CLUSTER_TYPE_DEFAULT,
118
+ project_uuid: str = INIT_PROJECT_UUID_DEFAULT,
93
119
  ):
94
120
  """
95
121
  Initialize Mage project.
@@ -97,7 +123,12 @@ def init(
97
123
  from mage_ai.data_preparation.repo_manager import init_repo
98
124
 
99
125
  repo_path = os.path.join(os.getcwd(), project_path)
100
- init_repo(repo_path, project_type=project_type)
126
+ init_repo(
127
+ repo_path,
128
+ project_type=project_type,
129
+ cluster_type=cluster_type,
130
+ project_uuid=project_uuid,
131
+ )
101
132
  print(f'Initialized Mage project at {repo_path}')
102
133
 
103
134
 
@@ -109,6 +140,9 @@ def start(
109
140
  manage_instance: str = START_MANAGE_INSTANCE_DEFAULT,
110
141
  dbt_docs_instance: str = START_DBT_DOCS_INSTANCE_DEFAULT,
111
142
  instance_type: str = START_INSTANCE_TYPE_DEFAULT,
143
+ project_type: str = START_PROJECT_TYPE_DEFAULT,
144
+ cluster_type: str = START_CLUSTER_TYPE_DEFAULT,
145
+ project_uuid: str = START_PROJECT_UUID_DEFAULT,
112
146
  ):
113
147
  """
114
148
  Start Mage server and UI.
@@ -128,6 +162,9 @@ def start(
128
162
  manage=manage_instance == "1",
129
163
  dbt_docs=dbt_docs_instance == "1",
130
164
  instance_type=instance_type,
165
+ project_type=project_type,
166
+ cluster_type=cluster_type,
167
+ project_uuid=project_uuid,
131
168
  )
132
169
 
133
170
 
@@ -155,6 +192,9 @@ def run(
155
192
  project_path = os.path.abspath(project_path)
156
193
  set_repo_path(project_path)
157
194
 
195
+ from contextlib import nullcontext
196
+
197
+ import newrelic.agent
158
198
  import sentry_sdk
159
199
 
160
200
  from mage_ai.data_preparation.executors.executor_factory import ExecutorFactory
@@ -171,55 +211,58 @@ def run(
171
211
  sentry_dsn,
172
212
  traces_sample_rate=SENTRY_TRACES_SAMPLE_RATE,
173
213
  )
174
-
175
- runtime_variables = dict()
176
- if runtime_vars is not None:
177
- runtime_variables = parse_runtime_variables(runtime_vars)
178
-
179
- sys.path.append(os.path.dirname(project_path))
180
- pipeline = Pipeline.get(pipeline_uuid, repo_path=project_path)
181
-
182
- db_connection.start_session()
183
-
184
- if pipeline_run_id is None:
185
- default_variables = get_global_variables(pipeline_uuid)
186
- global_vars = merge_dict(default_variables, runtime_variables)
187
- else:
188
- pipeline_run = PipelineRun.query.get(pipeline_run_id)
189
- global_vars = pipeline_run.get_variables(extra_variables=runtime_variables)
190
-
191
- if template_runtime_configuration is not None:
192
- template_runtime_configuration = json.loads(template_runtime_configuration)
193
-
194
- if block_uuid is None:
195
- ExecutorFactory.get_pipeline_executor(
196
- pipeline,
197
- execution_partition=execution_partition,
198
- executor_type=executor_type,
199
- ).execute(
200
- analyze_outputs=False,
201
- global_vars=global_vars,
202
- pipeline_run_id=pipeline_run_id,
203
- run_sensors=not skip_sensors,
204
- run_tests=test,
205
- update_status=False,
206
- )
207
- else:
208
- ExecutorFactory.get_block_executor(
209
- pipeline,
210
- block_uuid,
211
- execution_partition=execution_partition,
212
- executor_type=executor_type,
213
- ).execute(
214
- analyze_outputs=False,
215
- block_run_id=block_run_id,
216
- callback_url=callback_url,
217
- global_vars=global_vars,
218
- pipeline_run_id=pipeline_run_id,
219
- template_runtime_configuration=template_runtime_configuration,
220
- update_status=False,
221
- )
222
- 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.')
223
266
 
224
267
 
225
268
  @app.command()
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  from typing import Dict, List
3
3
 
4
+ import yaml
4
5
  from kubernetes import client, config
5
6
 
6
7
  from mage_ai.cluster_manager.constants import (
@@ -8,18 +9,24 @@ from mage_ai.cluster_manager.constants import (
8
9
  CONNECTION_URL_SECRETS_NAME,
9
10
  DB_SECRETS_NAME,
10
11
  GCP_BACKEND_CONFIG_ANNOTATION,
12
+ KUBE_NAMESPACE,
13
+ KUBE_SERVICE_ACCOUNT_NAME,
11
14
  KUBE_SERVICE_GCP_BACKEND_CONFIG,
12
15
  KUBE_SERVICE_TYPE,
16
+ KUBE_STORAGE_CLASS_NAME,
13
17
  NODE_PORT_SERVICE_TYPE,
14
18
  SERVICE_ACCOUNT_CREDENTIAL_FILE_PATH,
15
19
  SERVICE_ACCOUNT_SECRETS_NAME,
16
20
  )
21
+ from mage_ai.data_preparation.repo_manager import ProjectType
17
22
  from mage_ai.orchestration.constants import (
18
23
  DATABASE_CONNECTION_URL_ENV_VAR,
19
24
  DB_NAME,
20
25
  DB_PASS,
21
26
  DB_USER,
22
27
  )
28
+ from mage_ai.settings import MAGE_SETTINGS_ENVIRONMENT_VARIABLES
29
+ from mage_ai.shared.array import find
23
30
 
24
31
 
25
32
  class WorkloadManager:
@@ -47,46 +54,93 @@ class WorkloadManager:
47
54
 
48
55
  return False
49
56
 
50
- def list_services(self):
57
+ def list_workloads(self):
51
58
  services = self.core_client.list_namespaced_service(self.namespace).items
52
- 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
53
69
  for service in services:
54
70
  try:
55
71
  labels = service.metadata.labels
56
72
  if not labels.get('dev-instance'):
57
73
  continue
58
- ip_address = service.status.load_balancer.ingress[0].ip
59
- conditions = service.status.conditions or list()
60
- services_list.append(dict(
61
- ip=ip_address,
74
+ name = labels.get('app')
75
+ pod = pod_map[name]
76
+ service_type = service.spec.type
77
+ workload = dict(
62
78
  name=labels.get('app'),
63
- status='RUNNING' if len(conditions) == 0 else conditions[0].status,
64
- type='kubernetes',
65
- ))
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)
66
104
  except Exception:
67
105
  pass
68
106
 
69
- return services_list
107
+ return workloads_list
70
108
 
71
- def create_deployment(
109
+ def create_workload(
72
110
  self,
73
- deployment_name,
74
- container_config: Dict = None,
75
- service_account_name: str = None,
76
- storage_class_name: str = None,
77
- volume_host_path: str = None,
111
+ name: str,
112
+ project_type: str = ProjectType.STANDALONE,
113
+ project_uuid: str = None,
114
+ **kwargs,
78
115
  ):
79
- if container_config is None:
80
- container_config = dict()
116
+ container_config_yaml = kwargs.get('container_config')
117
+ container_config = dict()
118
+ if container_config_yaml:
119
+ container_config = yaml.full_load(container_config_yaml)
81
120
 
82
- env_vars = self.__populate_env_vars(container_config)
121
+ service_account_name = kwargs.get(
122
+ 'service_account_name',
123
+ os.getenv(KUBE_SERVICE_ACCOUNT_NAME),
124
+ )
125
+ storage_class_name = kwargs.get(
126
+ 'storage_class_name',
127
+ os.getenv(KUBE_STORAGE_CLASS_NAME, 'default'),
128
+ )
129
+ storage_access_mode = kwargs.get('storage_access_mode', 'ReadWriteOnce')
130
+ storage_request_size = kwargs.get('storage_request_size', 2)
131
+
132
+ env_vars = self.__populate_env_vars(
133
+ name,
134
+ project_type=project_type,
135
+ project_uuid=project_uuid,
136
+ container_config=container_config,
137
+ )
83
138
  container_config['env'] = env_vars
84
139
 
85
140
  containers = [
86
141
  {
87
- 'name': f'{deployment_name}-container',
142
+ 'name': f'{name}-container',
88
143
  'image': 'mageai/mageai:latest',
89
- 'command': ['mage', 'start', deployment_name],
90
144
  'ports': [
91
145
  {
92
146
  'containerPort': 6789,
@@ -146,32 +200,23 @@ class WorkloadManager:
146
200
  }
147
201
  )
148
202
 
149
- if volume_host_path:
150
- volumes.append(
151
- {
152
- 'name': 'mage-data',
153
- 'hostPath': {
154
- 'path': volume_host_path
155
- },
156
- }
157
- )
158
- deployment_template_spec = dict()
203
+ stateful_set_template_spec = dict()
159
204
  if service_account_name:
160
- deployment_template_spec['serviceAccountName'] = service_account_name
205
+ stateful_set_template_spec['serviceAccountName'] = service_account_name
161
206
 
162
- deployment = {
207
+ stateful_set = {
163
208
  'apiVersion': 'apps/v1',
164
- 'kind': 'Deployment',
209
+ 'kind': 'StatefulSet',
165
210
  'metadata': {
166
- 'name': deployment_name,
211
+ 'name': name,
167
212
  'labels': {
168
- 'app': deployment_name
213
+ 'app': name
169
214
  }
170
215
  },
171
216
  'spec': {
172
217
  'selector': {
173
218
  'matchLabels': {
174
- 'app': deployment_name
219
+ 'app': name
175
220
  }
176
221
  },
177
222
  'replicas': 1,
@@ -179,22 +224,38 @@ class WorkloadManager:
179
224
  'template': {
180
225
  'metadata': {
181
226
  'labels': {
182
- 'app': deployment_name
227
+ 'app': name
183
228
  }
184
229
  },
185
230
  'spec': {
186
231
  'terminationGracePeriodSeconds': 10,
187
232
  'containers': containers,
188
233
  'volumes': volumes,
189
- **deployment_template_spec
234
+ **stateful_set_template_spec
190
235
  }
191
236
  },
237
+ 'volumeClaimTemplates': [
238
+ {
239
+ 'metadata': {
240
+ 'name': 'mage-data'
241
+ },
242
+ 'spec': {
243
+ 'accessModes': [storage_access_mode],
244
+ 'storageClassName': storage_class_name,
245
+ 'resources': {
246
+ 'requests': {
247
+ 'storage': f'{storage_request_size}Gi'
248
+ }
249
+ }
250
+ }
251
+ }
252
+ ]
192
253
  }
193
254
  }
194
255
 
195
- self.apps_client.create_namespaced_deployment(self.namespace, deployment)
256
+ self.apps_client.create_namespaced_stateful_set(self.namespace, stateful_set)
196
257
 
197
- service_name = f'{deployment_name}-service'
258
+ service_name = f'{name}-service'
198
259
 
199
260
  annotations = {}
200
261
  if os.getenv(KUBE_SERVICE_GCP_BACKEND_CONFIG):
@@ -207,7 +268,7 @@ class WorkloadManager:
207
268
  'metadata': {
208
269
  'name': service_name,
209
270
  'labels': {
210
- 'app': deployment_name,
271
+ 'app': name,
211
272
  'dev-instance': '1',
212
273
  },
213
274
  'annotations': annotations
@@ -220,7 +281,7 @@ class WorkloadManager:
220
281
  }
221
282
  ],
222
283
  'selector': {
223
- 'app': deployment_name
284
+ 'app': name
224
285
  },
225
286
  'type': os.getenv(KUBE_SERVICE_TYPE, NODE_PORT_SERVICE_TYPE)
226
287
  }
@@ -228,8 +289,33 @@ class WorkloadManager:
228
289
 
229
290
  return self.core_client.create_namespaced_service(self.namespace, service)
230
291
 
231
- def __populate_env_vars(self, container_config) -> List:
232
- env_vars = []
292
+ def delete_workload(self, name: str):
293
+ self.apps_client.delete_namespaced_stateful_set(name, self.namespace)
294
+ self.core_client.delete_namespaced_service(f'{name}-service', self.namespace)
295
+
296
+ def __populate_env_vars(
297
+ self,
298
+ name,
299
+ project_type: str = 'standalone',
300
+ project_uuid: str = None,
301
+ container_config: Dict = None
302
+ ) -> List:
303
+ env_vars = [
304
+ {
305
+ 'name': 'USER_CODE_PATH',
306
+ 'value': name,
307
+ }
308
+ ]
309
+ if project_type:
310
+ env_vars.append({
311
+ 'name': 'PROJECT_TYPE',
312
+ 'value': project_type,
313
+ })
314
+ if project_uuid:
315
+ env_vars.append({
316
+ 'name': 'PROJECT_UUID',
317
+ 'value': project_uuid,
318
+ })
233
319
 
234
320
  connection_url_secrets_name = os.getenv(CONNECTION_URL_SECRETS_NAME)
235
321
  if connection_url_secrets_name:
@@ -245,6 +331,16 @@ class WorkloadManager:
245
331
  }
246
332
  )
247
333
 
334
+ for var in MAGE_SETTINGS_ENVIRONMENT_VARIABLES + [
335
+ DATABASE_CONNECTION_URL_ENV_VAR,
336
+ KUBE_NAMESPACE,
337
+ ]:
338
+ if os.getenv(var) is not None:
339
+ env_vars.append({
340
+ 'name': var,
341
+ 'value': str(os.getenv(var)),
342
+ })
343
+
248
344
  # For connecting to CloudSQL PostgreSQL database.
249
345
  db_secrets_name = os.getenv(DB_SECRETS_NAME)
250
346
  if db_secrets_name:
@@ -102,7 +102,7 @@ class Git:
102
102
  self.repo.create_remote(name, url)
103
103
 
104
104
  def remove_remote(self, name: str) -> None:
105
- self.repo.remotes[name].remove(self.repo, name)
105
+ self.repo.git.remote('remove', name)
106
106
 
107
107
  def staged_files(self) -> List[str]:
108
108
  files_string = self.repo.git.diff('--name-only', '--cached')
@@ -308,31 +308,101 @@ class Git:
308
308
  if message:
309
309
  self.repo.index.commit(message)
310
310
 
311
- def remotes(self, limit: int = 40) -> List[Dict]:
311
+ def remotes(self, limit: int = 40, user: User = None) -> List[Dict]:
312
312
  arr = []
313
313
 
314
314
  for idx, remote in enumerate(self.repo.remotes):
315
+ from git.exc import GitCommandError
316
+
315
317
  if idx >= limit:
316
318
  break
317
319
 
318
- refs = []
319
- for ref in remote.refs:
320
- refs.append(dict(
321
- name=ref.name,
322
- commit=dict(
323
- author=dict(
324
- email=ref.commit.author.email,
325
- name=ref.commit.author.name,
320
+ error = None
321
+ remote_url = None
322
+ remote_exists = False
323
+ try:
324
+ remote_url = [url for url in remote.urls][0]
325
+ remote_exists = True
326
+ except GitCommandError as err:
327
+ print('WARNING (mage_ai.data_preparation.git.remotes):')
328
+ print(err)
329
+
330
+ try:
331
+ remote_refs = remote.refs
332
+ if len(remote_refs) == 0 and user:
333
+ from mage_ai.data_preparation.git import api
334
+
335
+ access_token = api.get_access_token_for_user(user)
336
+ if access_token:
337
+
338
+ if remote_exists:
339
+ token = access_token.token
340
+ username = api.get_username(token)
341
+ url = api.build_authenticated_remote_url(
342
+ remote_url,
343
+ username,
344
+ token,
345
+ )
346
+
347
+ remote.set_url(url)
348
+
349
+ authenticated = False
350
+ try:
351
+ api.check_connection(self.repo, url)
352
+ authenticated = True
353
+ except Exception as err:
354
+ print('WARNING (mage_ai.data_preparation.git.remotes):')
355
+ print(err)
356
+
357
+ if authenticated:
358
+ try:
359
+ remote.fetch()
360
+ remote_refs = remote.refs
361
+ except Exception as err:
362
+ print('WARNING (mage_ai.data_preparation.git.remotes):')
363
+ print(err)
364
+
365
+ refs = []
366
+ for ref in remote_refs:
367
+ refs.append(dict(
368
+ name=ref.name,
369
+ commit=dict(
370
+ author=dict(
371
+ email=ref.commit.author.email,
372
+ name=ref.commit.author.name,
373
+ ),
374
+ date=datetime.fromtimestamp(ref.commit.authored_date).isoformat(),
375
+ message=ref.commit.message,
326
376
  ),
327
- date=datetime.fromtimestamp(ref.commit.authored_date).isoformat(),
328
- message=ref.commit.message,
329
- ),
330
- ))
377
+ ))
378
+ except Exception as err:
379
+ error = err
380
+
381
+ try:
382
+ remote.set_url(remote_url)
383
+ except GitCommandError as err:
384
+ print('WARNING (mage_ai.data_preparation.git.remotes):')
385
+ print(err)
386
+
387
+ if error:
388
+ raise error
389
+
390
+ repository_names = []
391
+ urls = []
392
+ try:
393
+ for url in remote.urls:
394
+ if url.lower().startswith('https'):
395
+ repository_names.append('/'.join(url.split('/')[-2:]).replace('.git', ''))
396
+ urls.append(url)
397
+ except GitCommandError as err:
398
+ print('WARNING (mage_ai.data_preparation.git.remotes):')
399
+ print(err)
331
400
 
332
401
  arr.append(dict(
333
402
  name=remote.name,
334
403
  refs=refs,
335
- urls=[url for url in remote.urls],
404
+ repository_names=repository_names,
405
+ urls=urls,
336
406
  ))
337
407
 
338
408
  return arr
@@ -416,7 +486,7 @@ class Git:
416
486
  self._run_command(cmd)
417
487
  print(f'Installing {requirements_file} completed successfully.')
418
488
  except Exception as err:
419
- print(f'Skip installing {requirements_file} due to error: {err}')
489
+ print(f'Skip installing {requirements_file} due to error: {str(err)}')
420
490
  pass
421
491
 
422
492
  def __create_ssh_keys(self) -> str: