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.
- mage_ai/api/policies/AutocompleteItemPolicy.py +2 -1
- mage_ai/api/policies/BasePolicy.py +2 -2
- mage_ai/api/policies/BlockTemplatePolicy.py +2 -1
- mage_ai/api/policies/ClusterPolicy.py +2 -1
- mage_ai/api/policies/DataProviderPolicy.py +2 -1
- mage_ai/api/policies/EventRulePolicy.py +2 -1
- mage_ai/api/policies/ExtensionOptionPolicy.py +2 -1
- mage_ai/api/policies/FileVersionPolicy.py +2 -1
- mage_ai/api/policies/GitBranchPolicy.py +9 -0
- mage_ai/api/policies/KernelPolicy.py +2 -1
- mage_ai/api/policies/LogPolicy.py +2 -2
- mage_ai/api/policies/OauthPolicy.py +15 -0
- mage_ai/api/policies/OutputPolicy.py +2 -2
- mage_ai/api/policies/PipelinePolicy.py +2 -2
- mage_ai/api/policies/PipelineRunPolicy.py +2 -2
- mage_ai/api/policies/PipelineSchedulePolicy.py +2 -2
- mage_ai/api/policies/PullRequestPolicy.py +64 -0
- mage_ai/api/policies/SessionPolicy.py +4 -1
- mage_ai/api/policies/VariablePolicy.py +2 -2
- mage_ai/api/policies/WidgetPolicy.py +2 -2
- mage_ai/api/policies/WorkspacePolicy.py +3 -3
- mage_ai/api/presenters/PipelinePresenter.py +1 -0
- mage_ai/api/presenters/PullRequestPresenter.py +16 -0
- mage_ai/api/presenters/StatusPresenter.py +2 -0
- mage_ai/api/presenters/SyncPresenter.py +1 -0
- mage_ai/api/presenters/WorkspacePresenter.py +2 -0
- mage_ai/api/resources/GitBranchResource.py +81 -26
- mage_ai/api/resources/OauthResource.py +31 -4
- mage_ai/api/resources/PipelineResource.py +8 -1
- mage_ai/api/resources/PullRequestResource.py +87 -0
- mage_ai/api/resources/RoleResource.py +6 -3
- mage_ai/api/resources/SecretResource.py +2 -5
- mage_ai/api/resources/SessionResource.py +18 -0
- mage_ai/api/resources/StatusResource.py +7 -3
- mage_ai/api/resources/UserResource.py +11 -16
- mage_ai/api/resources/WorkspaceResource.py +83 -53
- mage_ai/authentication/oauth/active_directory.py +17 -0
- mage_ai/authentication/oauth/constants.py +9 -0
- mage_ai/authentication/oauth/utils.py +2 -1
- mage_ai/authentication/oauth2.py +9 -3
- mage_ai/cli/main.py +94 -51
- mage_ai/cluster_manager/kubernetes/workload_manager.py +141 -45
- mage_ai/data_preparation/git/__init__.py +86 -16
- mage_ai/data_preparation/git/api.py +175 -0
- mage_ai/data_preparation/models/block/dbt/utils/__init__.py +49 -14
- mage_ai/data_preparation/models/block/sql/__init__.py +3 -2
- mage_ai/data_preparation/models/pipeline.py +4 -1
- mage_ai/data_preparation/models/pipelines/integration_pipeline.py +7 -3
- mage_ai/data_preparation/preferences.py +4 -2
- mage_ai/data_preparation/repo_manager.py +41 -10
- mage_ai/data_preparation/shared/secrets.py +5 -6
- mage_ai/data_preparation/sync/__init__.py +2 -1
- mage_ai/data_preparation/sync/git_sync.py +2 -5
- mage_ai/data_preparation/templates/utils.py +2 -0
- mage_ai/orchestration/db/models/oauth.py +22 -4
- mage_ai/orchestration/pipeline_scheduler.py +19 -8
- mage_ai/orchestration/queue/process_queue.py +15 -12
- mage_ai/server/api/clusters.py +21 -11
- mage_ai/server/constants.py +1 -1
- mage_ai/server/frontend_dist/404.html +2 -2
- mage_ai/server/frontend_dist/404.html.html +2 -2
- mage_ai/server/frontend_dist/_next/static/WRxCTOtmZhTqQws_7OJZD/_buildManifest.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/{1286-993725c925c56a98.js → 1286-b90bd4b7f8abfc3a.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/{1424-f475cae42f8a7fca.js → 1424-90c0f66ba2f86b88.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/3883-c95563b9f60ae526.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/6694-c8f2a68074420906.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/{9350-1ff50f1d7b9ee754.js → 9350-5191c83a8d0cf454.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/{_app-3527178abd99bc87.js → _app-171846e16d26855a.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/files-e4e778f8f5e1bf2e.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/settings-c788c1b127999825.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users/[user]-b4650224a19e8fe6.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users/new-931eb719e3fae29c.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users-d3724bde0b186dd9.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-af11f9cf94024ac0.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/{[...slug]-3ec5eb9562e4bff4.js → [...slug]-34326db259f922d1.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-503ecb7a72257b79.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs/{[run]-7667080098731e30.js → [run]-2994b8ab7862c07b.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs-7b31b851e2544b42.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers/{[...slug]-e18058e13882b20d.js → [...slug]-4445619d4eabe065.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{triggers-6854c10d5589d394.js → triggers-b7db0b682fadb840.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/account/profile-ee0931af3abb55b3.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-f8a59d718751be9a.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/sync-data-90f8830890036eb2.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/users-9f82673fc438ea83.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/sign-in-a1871b8a537d823c.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-48859b4e9c846212.js +1 -0
- mage_ai/server/frontend_dist/files.html +2 -2
- mage_ai/server/frontend_dist/index.html +2 -2
- mage_ai/server/frontend_dist/manage/settings.html +24 -0
- mage_ai/server/frontend_dist/manage/users/[user].html +2 -2
- mage_ai/server/frontend_dist/manage/users/new.html +24 -0
- mage_ai/server/frontend_dist/manage/users.html +2 -2
- mage_ai/server/frontend_dist/manage.html +2 -2
- mage_ai/server/frontend_dist/pipeline-runs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills/[...slug].html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/edit.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/logs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/runs/[run].html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/runs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/settings.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/syncs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers/[...slug].html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline].html +2 -2
- mage_ai/server/frontend_dist/pipelines.html +2 -2
- mage_ai/server/frontend_dist/settings/account/profile.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/preferences.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/sync-data.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/users.html +2 -2
- mage_ai/server/frontend_dist/settings.html +2 -2
- mage_ai/server/frontend_dist/sign-in.html +2 -2
- mage_ai/server/frontend_dist/terminal.html +2 -2
- mage_ai/server/frontend_dist/test.html +2 -2
- mage_ai/server/frontend_dist/triggers.html +2 -2
- mage_ai/server/frontend_dist/version-control.html +2 -2
- mage_ai/server/scheduler_manager.py +7 -2
- mage_ai/server/server.py +37 -3
- mage_ai/server/terminal_server.py +2 -2
- mage_ai/server/websocket_server.py +6 -2
- mage_ai/services/newrelic/__init__.py +21 -0
- mage_ai/settings/__init__.py +32 -0
- mage_ai/shared/hash.py +2 -0
- mage_ai/tests/api/test_utils.py +29 -2
- mage_ai/tests/data_preparation/models/test_pipeline.py +5 -0
- {mage_ai-0.8.97.dist-info → mage_ai-0.8.99.dist-info}/METADATA +8 -3
- {mage_ai-0.8.97.dist-info → mage_ai-0.8.99.dist-info}/RECORD +136 -127
- mage_ai/data_preparation/templates/main/projects/__init__.py +0 -0
- mage_ai/server/frontend_dist/_next/static/YLZRSrQ0aqtl-GGePfsMB/_buildManifest.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/3077-d58f18ed770e5137.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/3714-b676173cd4d8d86c.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/files-82b5409dac9564f4.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users/[user]-bb6aaa23e92a5add.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users-c91ee702a4cd7a6f.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-7961010cb0fb9abd.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-7b8ce89f0d717465.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs-5bd17a8f3f3d57ef.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/account/profile-7d75e42d5f4936bb.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-8220c1200472bf70.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/sync-data-b602fa9b6ffabd12.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/users-3f9d5800f268a263.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/sign-in-2925c2c1b0c5559a.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-5ffc663cfb0ec81e.js +0 -1
- /mage_ai/server/frontend_dist/_next/static/{YLZRSrQ0aqtl-GGePfsMB → WRxCTOtmZhTqQws_7OJZD}/_middlewareManifest.js +0 -0
- /mage_ai/server/frontend_dist/_next/static/{YLZRSrQ0aqtl-GGePfsMB → WRxCTOtmZhTqQws_7OJZD}/_ssgManifest.js +0 -0
- {mage_ai-0.8.97.dist-info → mage_ai-0.8.99.dist-info}/LICENSE +0 -0
- {mage_ai-0.8.97.dist-info → mage_ai-0.8.99.dist-info}/WHEEL +0 -0
- {mage_ai-0.8.97.dist-info → mage_ai-0.8.99.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
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(
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
|
57
|
+
def list_workloads(self):
|
|
51
58
|
services = self.core_client.list_namespaced_service(self.namespace).items
|
|
52
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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
|
|
107
|
+
return workloads_list
|
|
70
108
|
|
|
71
|
-
def
|
|
109
|
+
def create_workload(
|
|
72
110
|
self,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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'{
|
|
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
|
-
|
|
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
|
-
|
|
205
|
+
stateful_set_template_spec['serviceAccountName'] = service_account_name
|
|
161
206
|
|
|
162
|
-
|
|
207
|
+
stateful_set = {
|
|
163
208
|
'apiVersion': 'apps/v1',
|
|
164
|
-
'kind': '
|
|
209
|
+
'kind': 'StatefulSet',
|
|
165
210
|
'metadata': {
|
|
166
|
-
'name':
|
|
211
|
+
'name': name,
|
|
167
212
|
'labels': {
|
|
168
|
-
'app':
|
|
213
|
+
'app': name
|
|
169
214
|
}
|
|
170
215
|
},
|
|
171
216
|
'spec': {
|
|
172
217
|
'selector': {
|
|
173
218
|
'matchLabels': {
|
|
174
|
-
'app':
|
|
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':
|
|
227
|
+
'app': name
|
|
183
228
|
}
|
|
184
229
|
},
|
|
185
230
|
'spec': {
|
|
186
231
|
'terminationGracePeriodSeconds': 10,
|
|
187
232
|
'containers': containers,
|
|
188
233
|
'volumes': volumes,
|
|
189
|
-
**
|
|
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.
|
|
256
|
+
self.apps_client.create_namespaced_stateful_set(self.namespace, stateful_set)
|
|
196
257
|
|
|
197
|
-
service_name = f'{
|
|
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':
|
|
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':
|
|
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
|
|
232
|
-
|
|
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.
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
328
|
-
|
|
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
|
-
|
|
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:
|