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.
- mage_ai/api/policies/OauthPolicy.py +15 -0
- mage_ai/api/policies/SessionPolicy.py +2 -0
- mage_ai/api/presenters/PipelinePresenter.py +1 -0
- mage_ai/api/presenters/SyncPresenter.py +1 -0
- mage_ai/api/resources/OauthResource.py +28 -2
- mage_ai/api/resources/PipelineResource.py +5 -1
- mage_ai/api/resources/SessionResource.py +18 -0
- mage_ai/api/resources/WorkspaceResource.py +3 -2
- mage_ai/authentication/oauth/active_directory.py +17 -0
- mage_ai/authentication/oauth/constants.py +9 -0
- mage_ai/cli/main.py +56 -49
- mage_ai/cluster_manager/kubernetes/workload_manager.py +46 -9
- mage_ai/data_preparation/git/__init__.py +1 -1
- mage_ai/data_preparation/models/block/dbt/utils/__init__.py +32 -0
- mage_ai/data_preparation/models/block/sql/__init__.py +3 -2
- mage_ai/data_preparation/models/pipeline.py +4 -1
- mage_ai/data_preparation/preferences.py +4 -2
- mage_ai/data_preparation/repo_manager.py +5 -1
- mage_ai/data_preparation/sync/__init__.py +1 -0
- mage_ai/data_preparation/sync/git_sync.py +2 -5
- mage_ai/data_preparation/templates/utils.py +2 -0
- mage_ai/orchestration/pipeline_scheduler.py +19 -8
- mage_ai/orchestration/queue/process_queue.py +15 -12
- mage_ai/server/api/clusters.py +1 -1
- 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/{-1TwyIVv18EeGuJPahFzG → WRxCTOtmZhTqQws_7OJZD}/_buildManifest.js +1 -1
- 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-1a7dd14f59bb82dc.js → _app-171846e16d26855a.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/{manage-3f5032353f15dd3c.js → manage-af11f9cf94024ac0.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/{sync-data-513e80a872b783a7.js → sync-data-90f8830890036eb2.js} +1 -1
- 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 +2 -2
- mage_ai/server/frontend_dist/manage/users/[user].html +2 -2
- mage_ai/server/frontend_dist/manage/users/new.html +2 -2
- 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 +21 -0
- mage_ai/services/newrelic/__init__.py +21 -0
- mage_ai/settings/__init__.py +10 -1
- mage_ai/shared/hash.py +2 -0
- mage_ai/tests/data_preparation/models/test_pipeline.py +5 -0
- {mage_ai-0.8.98.dist-info → mage_ai-0.8.99.dist-info}/METADATA +4 -1
- {mage_ai-0.8.98.dist-info → mage_ai-0.8.99.dist-info}/RECORD +81 -79
- 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-63fbd3334e4509d7.js +0 -1
- /mage_ai/server/frontend_dist/_next/static/{-1TwyIVv18EeGuJPahFzG → WRxCTOtmZhTqQws_7OJZD}/_middlewareManifest.js +0 -0
- /mage_ai/server/frontend_dist/_next/static/{-1TwyIVv18EeGuJPahFzG → WRxCTOtmZhTqQws_7OJZD}/_ssgManifest.js +0 -0
- {mage_ai-0.8.98.dist-info → mage_ai-0.8.99.dist-info}/LICENSE +0 -0
- {mage_ai-0.8.98.dist-info → mage_ai-0.8.99.dist-info}/WHEEL +0 -0
- {mage_ai-0.8.98.dist-info → mage_ai-0.8.99.dist-info}/entry_points.txt +0 -0
- {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())
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
|
57
|
+
def list_workloads(self):
|
|
57
58
|
services = self.core_client.list_namespaced_service(self.namespace).items
|
|
58
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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
|
|
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(
|
|
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 <
|
|
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('
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|