mage-ai 0.9.50__py3-none-any.whl → 0.9.51__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/operations/base.py +11 -2
- mage_ai/api/presenters/WorkspacePresenter.py +1 -0
- mage_ai/cluster_manager/kubernetes/workload_manager.py +184 -147
- mage_ai/cluster_manager/workspace/kubernetes.py +25 -3
- mage_ai/data_preparation/executors/block_executor.py +10 -4
- mage_ai/data_preparation/models/global_hooks/models.py +9 -3
- mage_ai/data_preparation/models/pipeline.py +1 -47
- mage_ai/data_preparation/preferences.py +71 -27
- mage_ai/data_preparation/templates/custom/python/default.jinja +3 -5
- mage_ai/io/postgres.py +1 -0
- mage_ai/server/constants.py +1 -1
- mage_ai/server/frontend_dist/404.html +2 -2
- mage_ai/server/frontend_dist/_next/static/chunks/4138-1ffb2d1ab4fc61f2.js +1 -0
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/manage-221f6ba4b04367c6.js → frontend_dist/_next/static/chunks/pages/manage-41c56c0ae1c15cb6.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/{gxu21gnSyZ-GtqcWCfQmv → z6eg1yN60N6gosbEr2pGr}/_buildManifest.js +1 -1
- mage_ai/server/frontend_dist/block-layout.html +2 -2
- mage_ai/server/frontend_dist/compute.html +5 -5
- mage_ai/server/frontend_dist/files.html +5 -5
- mage_ai/server/frontend_dist/global-data-products/[...slug].html +5 -5
- mage_ai/server/frontend_dist/global-data-products.html +5 -5
- mage_ai/server/frontend_dist/global-hooks/[...slug].html +5 -5
- mage_ai/server/frontend_dist/global-hooks.html +5 -5
- mage_ai/server/frontend_dist/index.html +2 -2
- mage_ai/server/frontend_dist/manage/files.html +5 -5
- mage_ai/server/frontend_dist/manage/settings.html +5 -5
- mage_ai/server/frontend_dist/manage/users/[user].html +5 -5
- mage_ai/server/frontend_dist/manage/users/new.html +5 -5
- mage_ai/server/frontend_dist/manage/users.html +5 -5
- mage_ai/server/frontend_dist/manage.html +5 -5
- mage_ai/server/frontend_dist/oauth.html +4 -4
- mage_ai/server/frontend_dist/overview.html +5 -5
- mage_ai/server/frontend_dist/pipeline-runs.html +5 -5
- mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills/[...slug].html +5 -5
- mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills.html +5 -5
- mage_ai/server/frontend_dist/pipelines/[pipeline]/dashboard.html +5 -5
- mage_ai/server/frontend_dist/pipelines/[pipeline]/edit.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/logs.html +5 -5
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runs.html +5 -5
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runtime.html +5 -5
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors.html +5 -5
- mage_ai/server/frontend_dist/pipelines/[pipeline]/runs/[run].html +5 -5
- mage_ai/server/frontend_dist/pipelines/[pipeline]/runs.html +5 -5
- mage_ai/server/frontend_dist/pipelines/[pipeline]/settings.html +5 -5
- mage_ai/server/frontend_dist/pipelines/[pipeline]/syncs.html +5 -5
- mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers/[...slug].html +5 -5
- mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers.html +5 -5
- mage_ai/server/frontend_dist/pipelines/[pipeline].html +2 -2
- mage_ai/server/frontend_dist/pipelines.html +5 -5
- mage_ai/server/frontend_dist/settings/account/profile.html +5 -5
- mage_ai/server/frontend_dist/settings/workspace/permissions/[...slug].html +5 -5
- mage_ai/server/frontend_dist/settings/workspace/permissions.html +5 -5
- mage_ai/server/frontend_dist/settings/workspace/preferences.html +5 -5
- mage_ai/server/frontend_dist/settings/workspace/roles/[...slug].html +5 -5
- mage_ai/server/frontend_dist/settings/workspace/roles.html +5 -5
- mage_ai/server/frontend_dist/settings/workspace/sync-data.html +5 -5
- mage_ai/server/frontend_dist/settings/workspace/users/[...slug].html +5 -5
- mage_ai/server/frontend_dist/settings/workspace/users.html +5 -5
- mage_ai/server/frontend_dist/settings.html +2 -2
- mage_ai/server/frontend_dist/sign-in.html +12 -12
- mage_ai/server/frontend_dist/templates/[...slug].html +5 -5
- mage_ai/server/frontend_dist/templates.html +5 -5
- mage_ai/server/frontend_dist/terminal.html +5 -5
- mage_ai/server/frontend_dist/test.html +5 -5
- mage_ai/server/frontend_dist/triggers.html +5 -5
- mage_ai/server/frontend_dist/version-control.html +5 -5
- mage_ai/server/frontend_dist_base_path_template/404.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/_next/static/{nkbwVNyttvbUEHU4g3aOV → GkYZIZv7ClhebnBGgTrwO}/_buildManifest.js +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{1952-0f9a12782f0aaae6.js → 1952-0c8a3cb84da67f53.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{2714-68fef54789d7eaeb.js → 2714-1e79e9f2e998b544.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{2717-82a714ddff3edf43.js → 2717-e599ab448e3c1b7f.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{3437-ed09bb896e50e022.js → 3437-0712d7142aed2c46.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/4138-1ffb2d1ab4fc61f2.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{4783-422429203610c318.js → 4783-1a21d9be47574bba.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{5896-7b8e36634d7d94eb.js → 5896-14e5a23b1c6a0769.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{6285-648f9a732e100b2f.js → 6285-e9b45335bfb9ccaf.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{6798-b904395b0c18647b.js → 6798-1ef0247e65215a0f.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{976-0a8c2c4d7acd957b.js → 976-18c98af60b76f1a7.js} +1 -1
- mage_ai/server/{frontend_dist/_next/static/chunks/pages/manage-221f6ba4b04367c6.js → frontend_dist_base_path_template/_next/static/chunks/pages/manage-41c56c0ae1c15cb6.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/{edit-0cd2a275eb8b6345.js → edit-285ea9b8083b5f22.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs/{[run]-e9c1506c0a1f87b6.js → [run]-15deea898d2fa18b.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/{settings-495e877aa7ed709e.js → settings-6f5a7e367ec63c43.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/{preferences-f3c29ec53ee35795.js → preferences-b058d0ff37970cd9.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/block-layout.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/compute.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/files.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/global-data-products/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/global-data-products.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/global-hooks/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/global-hooks.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/index.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/files.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/settings.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/users/[user].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/users/new.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/users.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/oauth.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/overview.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipeline-runs.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/dashboard.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/edit.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/logs.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runs.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs/[run].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/settings.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/syncs.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/account/profile.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/preferences.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/sync-data.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/users/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/users.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/sign-in.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/templates/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/templates.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/terminal.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/test.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/triggers.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/version-control.html +2 -2
- mage_ai/settings/__init__.py +32 -13
- mage_ai/tests/cluster_manager/kubernetes/test_workload_manager.py +76 -12
- {mage_ai-0.9.50.dist-info → mage_ai-0.9.51.dist-info}/METADATA +1 -1
- {mage_ai-0.9.50.dist-info → mage_ai-0.9.51.dist-info}/RECORD +142 -142
- mage_ai/server/frontend_dist/_next/static/chunks/4138-a2c37d37dba2f44c.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/4138-a2c37d37dba2f44c.js +0 -1
- /mage_ai/server/frontend_dist/_next/static/{gxu21gnSyZ-GtqcWCfQmv → z6eg1yN60N6gosbEr2pGr}/_ssgManifest.js +0 -0
- /mage_ai/server/frontend_dist_base_path_template/_next/static/{nkbwVNyttvbUEHU4g3aOV → GkYZIZv7ClhebnBGgTrwO}/_ssgManifest.js +0 -0
- {mage_ai-0.9.50.dist-info → mage_ai-0.9.51.dist-info}/LICENSE +0 -0
- {mage_ai-0.9.50.dist-info → mage_ai-0.9.51.dist-info}/WHEEL +0 -0
- {mage_ai-0.9.50.dist-info → mage_ai-0.9.51.dist-info}/entry_points.txt +0 -0
- {mage_ai-0.9.50.dist-info → mage_ai-0.9.51.dist-info}/top_level.txt +0 -0
mage_ai/api/operations/base.py
CHANGED
|
@@ -42,6 +42,7 @@ from mage_ai.orchestration.db import db_connection
|
|
|
42
42
|
from mage_ai.orchestration.db.errors import DoesNotExistError
|
|
43
43
|
from mage_ai.settings import REQUIRE_USER_PERMISSIONS
|
|
44
44
|
from mage_ai.shared.array import flatten
|
|
45
|
+
from mage_ai.shared.environments import is_debug
|
|
45
46
|
from mage_ai.shared.hash import ignore_keys, merge_dict
|
|
46
47
|
from mage_ai.shared.strings import classify
|
|
47
48
|
|
|
@@ -325,9 +326,17 @@ class BaseOperation():
|
|
|
325
326
|
resource_parent_id=self.resource_parent_id,
|
|
326
327
|
resource_parent_type=self.__resource_parent_entity_name(),
|
|
327
328
|
resources=resources,
|
|
328
|
-
user=dict(
|
|
329
|
+
user=dict(
|
|
330
|
+
avatar=self.user.avatar,
|
|
331
|
+
first_name=self.user.first_name,
|
|
332
|
+
id=self.user.id,
|
|
333
|
+
last_name=self.user.last_name,
|
|
334
|
+
username=self.user.username,
|
|
335
|
+
) if self.user else None,
|
|
329
336
|
)
|
|
330
|
-
except Exception:
|
|
337
|
+
except Exception as err:
|
|
338
|
+
if is_debug():
|
|
339
|
+
raise err
|
|
331
340
|
hooks = []
|
|
332
341
|
|
|
333
342
|
try:
|
|
@@ -83,7 +83,9 @@ class WorkloadManager:
|
|
|
83
83
|
services = self.core_client.list_namespaced_service(self.namespace).items
|
|
84
84
|
workloads_list = []
|
|
85
85
|
|
|
86
|
-
stateful_sets = self.apps_client.list_namespaced_stateful_set(
|
|
86
|
+
stateful_sets = self.apps_client.list_namespaced_stateful_set(
|
|
87
|
+
self.namespace
|
|
88
|
+
).items
|
|
87
89
|
stateful_set_mapping = dict()
|
|
88
90
|
for ss in stateful_sets:
|
|
89
91
|
try:
|
|
@@ -123,11 +125,12 @@ class WorkloadManager:
|
|
|
123
125
|
try:
|
|
124
126
|
if node_name:
|
|
125
127
|
items = self.core_client.list_node(
|
|
126
|
-
field_selector=f'metadata.name={node_name}'
|
|
128
|
+
field_selector=f'metadata.name={node_name}'
|
|
129
|
+
).items
|
|
127
130
|
node = items[0]
|
|
128
131
|
ip = find(
|
|
129
132
|
lambda a: a.type == 'ExternalIP',
|
|
130
|
-
node.status.addresses
|
|
133
|
+
node.status.addresses,
|
|
131
134
|
).address
|
|
132
135
|
if ip:
|
|
133
136
|
node_port = service.spec.ports[0].node_port
|
|
@@ -188,12 +191,7 @@ class WorkloadManager:
|
|
|
188
191
|
ingress_name = workspace_config.ingress_name
|
|
189
192
|
|
|
190
193
|
volumes = []
|
|
191
|
-
volume_mounts = [
|
|
192
|
-
{
|
|
193
|
-
'name': 'mage-data',
|
|
194
|
-
'mountPath': '/home/src'
|
|
195
|
-
}
|
|
196
|
-
]
|
|
194
|
+
volume_mounts = [{'name': 'mage-data', 'mountPath': '/home/src'}]
|
|
197
195
|
|
|
198
196
|
env_vars = self.__populate_env_vars(
|
|
199
197
|
name,
|
|
@@ -207,7 +205,9 @@ class WorkloadManager:
|
|
|
207
205
|
lifecycle_config = workspace_config.lifecycle_config or LifecycleConfig()
|
|
208
206
|
if lifecycle_config.post_start:
|
|
209
207
|
if lifecycle_config.post_start.hook_path:
|
|
210
|
-
post_start_file_name = os.path.basename(
|
|
208
|
+
post_start_file_name = os.path.basename(
|
|
209
|
+
lifecycle_config.post_start.hook_path
|
|
210
|
+
)
|
|
211
211
|
volume_mounts.append(
|
|
212
212
|
{
|
|
213
213
|
'name': 'lifecycle-hooks',
|
|
@@ -230,18 +230,13 @@ class WorkloadManager:
|
|
|
230
230
|
'exec': {
|
|
231
231
|
'command': post_start_command,
|
|
232
232
|
}
|
|
233
|
-
}
|
|
233
|
+
},
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
mage_container_config = {
|
|
237
237
|
'name': f'{name}-container',
|
|
238
238
|
'image': 'mageai/mageai:latest',
|
|
239
|
-
'ports': [
|
|
240
|
-
{
|
|
241
|
-
'containerPort': 6789,
|
|
242
|
-
'name': 'web'
|
|
243
|
-
}
|
|
244
|
-
],
|
|
239
|
+
'ports': [{'containerPort': 6789, 'name': 'web'}],
|
|
245
240
|
'volumeMounts': volume_mounts,
|
|
246
241
|
**container_config,
|
|
247
242
|
}
|
|
@@ -266,7 +261,7 @@ class WorkloadManager:
|
|
|
266
261
|
'name': 'lifecycle-hooks',
|
|
267
262
|
'mountPath': '/app/initial-config.json',
|
|
268
263
|
'subPath': 'initial-config.json',
|
|
269
|
-
}
|
|
264
|
+
},
|
|
270
265
|
],
|
|
271
266
|
'env': [
|
|
272
267
|
{
|
|
@@ -276,7 +271,7 @@ class WorkloadManager:
|
|
|
276
271
|
{
|
|
277
272
|
'name': KUBE_NAMESPACE,
|
|
278
273
|
'value': os.getenv(KUBE_NAMESPACE, 'default'),
|
|
279
|
-
}
|
|
274
|
+
},
|
|
280
275
|
],
|
|
281
276
|
}
|
|
282
277
|
)
|
|
@@ -294,32 +289,23 @@ class WorkloadManager:
|
|
|
294
289
|
'/cloud_sql_proxy',
|
|
295
290
|
'-log_debug_stdout',
|
|
296
291
|
f'-instances={os.getenv(CLOUD_SQL_CONNECTION_NAME)}=tcp:5432',
|
|
297
|
-
f'-credential_file=/secrets/{credential_file_path}'
|
|
292
|
+
f'-credential_file=/secrets/{credential_file_path}',
|
|
298
293
|
],
|
|
299
|
-
'securityContext': {
|
|
300
|
-
|
|
301
|
-
},
|
|
302
|
-
'resources': {
|
|
303
|
-
'requests': {
|
|
304
|
-
'memory': '1Gi',
|
|
305
|
-
'cpu': '1'
|
|
306
|
-
}
|
|
307
|
-
},
|
|
294
|
+
'securityContext': {'runAsNonRoot': True},
|
|
295
|
+
'resources': {'requests': {'memory': '1Gi', 'cpu': '1'}},
|
|
308
296
|
'volumeMounts': [
|
|
309
297
|
{
|
|
310
298
|
'name': 'service-account-volume',
|
|
311
299
|
'mountPath': '/secrets/',
|
|
312
|
-
'readOnly': True
|
|
300
|
+
'readOnly': True,
|
|
313
301
|
}
|
|
314
|
-
]
|
|
302
|
+
],
|
|
315
303
|
}
|
|
316
304
|
)
|
|
317
305
|
volumes.append(
|
|
318
306
|
{
|
|
319
307
|
'name': 'service-account-volume',
|
|
320
|
-
'secret': {
|
|
321
|
-
'secretName': os.getenv(SERVICE_ACCOUNT_SECRETS_NAME)
|
|
322
|
-
}
|
|
308
|
+
'secret': {'secretName': os.getenv(SERVICE_ACCOUNT_SECRETS_NAME)},
|
|
323
309
|
}
|
|
324
310
|
)
|
|
325
311
|
|
|
@@ -343,8 +329,8 @@ class WorkloadManager:
|
|
|
343
329
|
**({'mode': 0o0755} if key.endswith('.sh') else {}),
|
|
344
330
|
}
|
|
345
331
|
for key in config_map
|
|
346
|
-
]
|
|
347
|
-
}
|
|
332
|
+
],
|
|
333
|
+
},
|
|
348
334
|
}
|
|
349
335
|
)
|
|
350
336
|
|
|
@@ -362,45 +348,28 @@ class WorkloadManager:
|
|
|
362
348
|
stateful_set = {
|
|
363
349
|
'apiVersion': 'apps/v1',
|
|
364
350
|
'kind': 'StatefulSet',
|
|
365
|
-
'metadata': {
|
|
366
|
-
'name': name,
|
|
367
|
-
'labels': {
|
|
368
|
-
'app': name
|
|
369
|
-
}
|
|
370
|
-
},
|
|
351
|
+
'metadata': {'name': name, 'labels': {'app': name}},
|
|
371
352
|
'spec': {
|
|
372
|
-
'selector': {
|
|
373
|
-
'matchLabels': {
|
|
374
|
-
'app': name
|
|
375
|
-
}
|
|
376
|
-
},
|
|
353
|
+
'selector': {'matchLabels': {'app': name}},
|
|
377
354
|
'replicas': 1,
|
|
378
355
|
'minReadySeconds': 10,
|
|
379
356
|
'template': {
|
|
380
|
-
'metadata': {
|
|
381
|
-
|
|
382
|
-
'app': name
|
|
383
|
-
}
|
|
384
|
-
},
|
|
385
|
-
'spec': stateful_set_template_spec
|
|
357
|
+
'metadata': {'labels': {'app': name}},
|
|
358
|
+
'spec': stateful_set_template_spec,
|
|
386
359
|
},
|
|
387
360
|
'volumeClaimTemplates': [
|
|
388
361
|
{
|
|
389
|
-
'metadata': {
|
|
390
|
-
'name': 'mage-data'
|
|
391
|
-
},
|
|
362
|
+
'metadata': {'name': 'mage-data'},
|
|
392
363
|
'spec': {
|
|
393
364
|
'accessModes': [storage_access_mode],
|
|
394
365
|
'storageClassName': storage_class_name,
|
|
395
366
|
'resources': {
|
|
396
|
-
'requests': {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
}
|
|
367
|
+
'requests': {'storage': storage_request_size}
|
|
368
|
+
},
|
|
369
|
+
},
|
|
401
370
|
}
|
|
402
|
-
]
|
|
403
|
-
}
|
|
371
|
+
],
|
|
372
|
+
},
|
|
404
373
|
}
|
|
405
374
|
|
|
406
375
|
self.apps_client.create_namespaced_stateful_set(self.namespace, stateful_set)
|
|
@@ -409,8 +378,9 @@ class WorkloadManager:
|
|
|
409
378
|
|
|
410
379
|
annotations = {}
|
|
411
380
|
if os.getenv(KUBE_SERVICE_GCP_BACKEND_CONFIG):
|
|
412
|
-
annotations[GCP_BACKEND_CONFIG_ANNOTATION] =
|
|
413
|
-
|
|
381
|
+
annotations[GCP_BACKEND_CONFIG_ANNOTATION] = os.getenv(
|
|
382
|
+
KUBE_SERVICE_GCP_BACKEND_CONFIG
|
|
383
|
+
)
|
|
414
384
|
|
|
415
385
|
service = {
|
|
416
386
|
'apiVersion': 'v1',
|
|
@@ -421,7 +391,7 @@ class WorkloadManager:
|
|
|
421
391
|
'app': name,
|
|
422
392
|
'dev-instance': '1',
|
|
423
393
|
},
|
|
424
|
-
'annotations': annotations
|
|
394
|
+
'annotations': annotations,
|
|
425
395
|
},
|
|
426
396
|
'spec': {
|
|
427
397
|
'ports': [
|
|
@@ -430,14 +400,14 @@ class WorkloadManager:
|
|
|
430
400
|
'port': 6789,
|
|
431
401
|
}
|
|
432
402
|
],
|
|
433
|
-
'selector': {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
'type': os.getenv(KUBE_SERVICE_TYPE, NODE_PORT_SERVICE_TYPE)
|
|
437
|
-
}
|
|
403
|
+
'selector': {'app': name},
|
|
404
|
+
'type': os.getenv(KUBE_SERVICE_TYPE, NODE_PORT_SERVICE_TYPE),
|
|
405
|
+
},
|
|
438
406
|
}
|
|
439
407
|
|
|
440
|
-
k8s_service = self.core_client.create_namespaced_service(
|
|
408
|
+
k8s_service = self.core_client.create_namespaced_service(
|
|
409
|
+
self.namespace, service
|
|
410
|
+
)
|
|
441
411
|
|
|
442
412
|
try:
|
|
443
413
|
if ingress_name:
|
|
@@ -452,9 +422,12 @@ class WorkloadManager:
|
|
|
452
422
|
self,
|
|
453
423
|
ingress_name: str,
|
|
454
424
|
service_name: str,
|
|
455
|
-
|
|
425
|
+
workload_name: str,
|
|
456
426
|
) -> None:
|
|
457
|
-
ingress = self.networking_client.read_namespaced_ingress(
|
|
427
|
+
ingress = self.networking_client.read_namespaced_ingress(
|
|
428
|
+
ingress_name,
|
|
429
|
+
self.namespace,
|
|
430
|
+
)
|
|
458
431
|
rule = ingress.spec.rules[0]
|
|
459
432
|
paths = rule.http.paths
|
|
460
433
|
paths.insert(
|
|
@@ -462,32 +435,84 @@ class WorkloadManager:
|
|
|
462
435
|
client.V1HTTPIngressPath(
|
|
463
436
|
backend=client.V1IngressBackend(
|
|
464
437
|
service=client.V1IngressServiceBackend(
|
|
465
|
-
name=service_name,
|
|
466
|
-
port=client.V1ServiceBackendPort(
|
|
467
|
-
number=6789
|
|
468
|
-
)
|
|
438
|
+
name=service_name, port=client.V1ServiceBackendPort(number=6789)
|
|
469
439
|
)
|
|
470
440
|
),
|
|
471
|
-
path=f'/{
|
|
441
|
+
path=f'/{workload_name}',
|
|
472
442
|
path_type='Prefix',
|
|
473
|
-
)
|
|
443
|
+
),
|
|
474
444
|
)
|
|
475
445
|
ingress.spec.rules[0] = client.V1IngressRule(
|
|
476
446
|
host=rule.host,
|
|
477
447
|
http=client.V1HTTPIngressRuleValue(paths=paths),
|
|
478
448
|
)
|
|
479
|
-
self.networking_client.patch_namespaced_ingress(
|
|
449
|
+
self.networking_client.patch_namespaced_ingress(
|
|
450
|
+
ingress_name, self.namespace, ingress
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
def get_url_from_ingress(self, ingress_name: str, workload_name: str) -> str:
|
|
454
|
+
ingress = self.networking_client.read_namespaced_ingress(
|
|
455
|
+
ingress_name,
|
|
456
|
+
self.namespace,
|
|
457
|
+
)
|
|
458
|
+
rule = ingress.spec.rules[0]
|
|
459
|
+
host = rule.host
|
|
460
|
+
|
|
461
|
+
tls_enabled = False
|
|
462
|
+
try:
|
|
463
|
+
tls = ingress.spec.tls[0]
|
|
464
|
+
tls_enabled = host in tls.hosts
|
|
465
|
+
except Exception:
|
|
466
|
+
pass
|
|
480
467
|
|
|
481
|
-
|
|
468
|
+
paths = rule.http.paths
|
|
469
|
+
for path in paths:
|
|
470
|
+
if path.backend.service.name == f'{workload_name}-service':
|
|
471
|
+
prefix = 'https' if tls_enabled else 'http'
|
|
472
|
+
return f'{prefix}://{host}{path.path}'
|
|
473
|
+
|
|
474
|
+
def remove_service_from_ingress_paths(
|
|
475
|
+
self,
|
|
476
|
+
ingress_name: str,
|
|
477
|
+
workload_name: str,
|
|
478
|
+
) -> None:
|
|
479
|
+
ingress = self.networking_client.read_namespaced_ingress(
|
|
480
|
+
ingress_name, self.namespace
|
|
481
|
+
)
|
|
482
|
+
rule = ingress.spec.rules[0]
|
|
483
|
+
paths = rule.http.paths
|
|
484
|
+
for path in paths:
|
|
485
|
+
if path.backend.service.name == f'{workload_name}-service':
|
|
486
|
+
paths.remove(path)
|
|
487
|
+
break
|
|
488
|
+
ingress.spec.rules[0] = client.V1IngressRule(
|
|
489
|
+
host=rule.host,
|
|
490
|
+
http=client.V1HTTPIngressRuleValue(paths=paths),
|
|
491
|
+
)
|
|
492
|
+
self.networking_client.patch_namespaced_ingress(
|
|
493
|
+
ingress_name, self.namespace, ingress
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
def delete_workload(self, name: str, ingress_name: str = None):
|
|
482
497
|
self.apps_client.delete_namespaced_stateful_set(name, self.namespace)
|
|
483
498
|
self.core_client.delete_namespaced_service(f'{name}-service', self.namespace)
|
|
484
499
|
try:
|
|
485
|
-
self.core_client.delete_namespaced_config_map(
|
|
500
|
+
self.core_client.delete_namespaced_config_map(
|
|
501
|
+
f'{name}-hooks', self.namespace
|
|
502
|
+
)
|
|
486
503
|
except ApiException as ex:
|
|
487
504
|
# The delete operation will return a 404 response if the config map does not exist
|
|
488
505
|
if ex.status != 404:
|
|
489
506
|
raise
|
|
490
507
|
|
|
508
|
+
try:
|
|
509
|
+
if ingress_name:
|
|
510
|
+
self.remove_service_from_ingress_paths(ingress_name, name)
|
|
511
|
+
except Exception as ex:
|
|
512
|
+
raise Exception(
|
|
513
|
+
'Failed to delete workspace path from ingress, you may need to manually delete it'
|
|
514
|
+
) from ex
|
|
515
|
+
|
|
491
516
|
def get_workload_activity(self, name: str) -> Dict:
|
|
492
517
|
pods = self.core_client.list_namespaced_pod(self.namespace).items
|
|
493
518
|
pod_name = None
|
|
@@ -527,12 +552,16 @@ class WorkloadManager:
|
|
|
527
552
|
|
|
528
553
|
def scale_down_workload(self, name: str) -> None:
|
|
529
554
|
self.apps_client.patch_namespaced_stateful_set(
|
|
530
|
-
name,
|
|
555
|
+
name,
|
|
556
|
+
namespace=self.namespace,
|
|
557
|
+
body={'spec': {'replicas': 0}},
|
|
531
558
|
)
|
|
532
559
|
|
|
533
560
|
def restart_workload(self, name: str) -> None:
|
|
534
561
|
self.apps_client.patch_namespaced_stateful_set(
|
|
535
|
-
name,
|
|
562
|
+
name,
|
|
563
|
+
namespace=self.namespace,
|
|
564
|
+
body={'spec': {'replicas': 1}},
|
|
536
565
|
)
|
|
537
566
|
|
|
538
567
|
def create_hooks_config_map(
|
|
@@ -546,7 +575,9 @@ class WorkloadManager:
|
|
|
546
575
|
if pre_start_script_path:
|
|
547
576
|
if not mage_container_config:
|
|
548
577
|
raise ConfigurationError('The container config can not be empty')
|
|
549
|
-
self.__validate_pre_start_script(
|
|
578
|
+
self.__validate_pre_start_script(
|
|
579
|
+
pre_start_script_path, mage_container_config
|
|
580
|
+
)
|
|
550
581
|
|
|
551
582
|
with open(pre_start_script_path, 'r', encoding='utf-8') as f:
|
|
552
583
|
pre_start_script = f.read()
|
|
@@ -567,11 +598,10 @@ class WorkloadManager:
|
|
|
567
598
|
'data': config_map_data,
|
|
568
599
|
'metadata': {
|
|
569
600
|
'name': f'{name}-hooks',
|
|
570
|
-
}
|
|
601
|
+
},
|
|
571
602
|
}
|
|
572
603
|
self.core_client.create_namespaced_config_map(
|
|
573
|
-
namespace=self.namespace,
|
|
574
|
-
body=config_map
|
|
604
|
+
namespace=self.namespace, body=config_map
|
|
575
605
|
)
|
|
576
606
|
|
|
577
607
|
return config_map_data
|
|
@@ -588,7 +618,9 @@ class WorkloadManager:
|
|
|
588
618
|
except Exception as ex:
|
|
589
619
|
raise Exception(f'Pre-start script is invalid: {str(ex)}')
|
|
590
620
|
|
|
591
|
-
spec = importlib.util.spec_from_file_location(
|
|
621
|
+
spec = importlib.util.spec_from_file_location(
|
|
622
|
+
'pre_start', pre_start_script_path
|
|
623
|
+
)
|
|
592
624
|
module = importlib.util.module_from_spec(spec)
|
|
593
625
|
|
|
594
626
|
try:
|
|
@@ -602,7 +634,9 @@ class WorkloadManager:
|
|
|
602
634
|
f', error: {str(ex)}'
|
|
603
635
|
)
|
|
604
636
|
except Exception as ex:
|
|
605
|
-
raise ConfigurationError(
|
|
637
|
+
raise ConfigurationError(
|
|
638
|
+
f'Pre-start script validation failed with error: {str(ex)}'
|
|
639
|
+
)
|
|
606
640
|
|
|
607
641
|
def __populate_env_vars(
|
|
608
642
|
self,
|
|
@@ -619,20 +653,26 @@ class WorkloadManager:
|
|
|
619
653
|
}
|
|
620
654
|
]
|
|
621
655
|
if set_base_path:
|
|
622
|
-
env_vars.append(
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
656
|
+
env_vars.append(
|
|
657
|
+
{
|
|
658
|
+
'name': 'MAGE_BASE_PATH',
|
|
659
|
+
'value': name,
|
|
660
|
+
}
|
|
661
|
+
)
|
|
626
662
|
if project_type:
|
|
627
|
-
env_vars.append(
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
663
|
+
env_vars.append(
|
|
664
|
+
{
|
|
665
|
+
'name': 'PROJECT_TYPE',
|
|
666
|
+
'value': project_type,
|
|
667
|
+
}
|
|
668
|
+
)
|
|
631
669
|
if project_uuid:
|
|
632
|
-
env_vars.append(
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
670
|
+
env_vars.append(
|
|
671
|
+
{
|
|
672
|
+
'name': 'PROJECT_UUID',
|
|
673
|
+
'value': project_uuid,
|
|
674
|
+
}
|
|
675
|
+
)
|
|
636
676
|
|
|
637
677
|
connection_url_secrets_name = os.getenv(CONNECTION_URL_SECRETS_NAME)
|
|
638
678
|
if connection_url_secrets_name:
|
|
@@ -642,9 +682,9 @@ class WorkloadManager:
|
|
|
642
682
|
'valueFrom': {
|
|
643
683
|
'secretKeyRef': {
|
|
644
684
|
'name': connection_url_secrets_name,
|
|
645
|
-
'key': 'connection_url'
|
|
685
|
+
'key': 'connection_url',
|
|
646
686
|
}
|
|
647
|
-
}
|
|
687
|
+
},
|
|
648
688
|
}
|
|
649
689
|
)
|
|
650
690
|
|
|
@@ -653,50 +693,47 @@ class WorkloadManager:
|
|
|
653
693
|
KUBE_NAMESPACE,
|
|
654
694
|
]:
|
|
655
695
|
if os.getenv(var) is not None:
|
|
656
|
-
env_vars.append(
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
696
|
+
env_vars.append(
|
|
697
|
+
{
|
|
698
|
+
'name': var,
|
|
699
|
+
'value': str(os.getenv(var)),
|
|
700
|
+
}
|
|
701
|
+
)
|
|
660
702
|
|
|
661
703
|
# For connecting to CloudSQL PostgreSQL database.
|
|
662
704
|
db_secrets_name = os.getenv(DB_SECRETS_NAME)
|
|
663
705
|
if db_secrets_name:
|
|
664
|
-
env_vars.extend(
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
'
|
|
669
|
-
'name': db_secrets_name,
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
'secretKeyRef': {
|
|
687
|
-
'name': db_secrets_name,
|
|
688
|
-
'key': 'database'
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
])
|
|
706
|
+
env_vars.extend(
|
|
707
|
+
[
|
|
708
|
+
{
|
|
709
|
+
'name': PG_DB_USER,
|
|
710
|
+
'valueFrom': {
|
|
711
|
+
'secretKeyRef': {'name': db_secrets_name, 'key': 'username'}
|
|
712
|
+
},
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
'name': PG_DB_PASS,
|
|
716
|
+
'valueFrom': {
|
|
717
|
+
'secretKeyRef': {'name': db_secrets_name, 'key': 'password'}
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
'name': PG_DB_NAME,
|
|
722
|
+
'valueFrom': {
|
|
723
|
+
'secretKeyRef': {'name': db_secrets_name, 'key': 'database'}
|
|
724
|
+
},
|
|
725
|
+
},
|
|
726
|
+
]
|
|
727
|
+
)
|
|
693
728
|
|
|
694
729
|
if container_config and 'env' in container_config:
|
|
695
730
|
env_vars += container_config['env']
|
|
696
731
|
|
|
697
732
|
return env_vars
|
|
698
733
|
|
|
699
|
-
def __get_configurable_parameters(
|
|
734
|
+
def __get_configurable_parameters(
|
|
735
|
+
self, workspace_config: KubernetesWorkspaceConfig
|
|
736
|
+
) -> Dict:
|
|
700
737
|
service_account_name_default = None
|
|
701
738
|
storage_class_name_default = None
|
|
702
739
|
storage_access_mode_default = None
|
|
@@ -729,8 +766,10 @@ class WorkloadManager:
|
|
|
729
766
|
return dict(
|
|
730
767
|
service_account_name=workspace_config.service_account_name
|
|
731
768
|
or service_account_name_default,
|
|
732
|
-
storage_class_name=workspace_config.storage_class_name
|
|
733
|
-
|
|
769
|
+
storage_class_name=workspace_config.storage_class_name
|
|
770
|
+
or storage_class_name_default,
|
|
771
|
+
storage_access_mode=workspace_config.storage_access_mode
|
|
772
|
+
or storage_access_mode_default,
|
|
734
773
|
storage_request_size=storage_request_size,
|
|
735
774
|
)
|
|
736
775
|
|
|
@@ -747,9 +786,7 @@ class WorkloadManager:
|
|
|
747
786
|
pv = {
|
|
748
787
|
'apiVersion': 'v1',
|
|
749
788
|
'kind': 'PersistentVolume',
|
|
750
|
-
'metadata': {
|
|
751
|
-
'name': f'{name}-pv'
|
|
752
|
-
},
|
|
789
|
+
'metadata': {'name': f'{name}-pv'},
|
|
753
790
|
'spec': {
|
|
754
791
|
'capacity': {
|
|
755
792
|
'storage': storage_request_size,
|
|
@@ -769,14 +806,14 @@ class WorkloadManager:
|
|
|
769
806
|
{
|
|
770
807
|
'key': 'kubernetes.io/hostname',
|
|
771
808
|
'operator': 'In',
|
|
772
|
-
'values': hostnames
|
|
809
|
+
'values': hostnames,
|
|
773
810
|
}
|
|
774
811
|
]
|
|
775
812
|
}
|
|
776
813
|
]
|
|
777
814
|
}
|
|
778
|
-
}
|
|
779
|
-
}
|
|
815
|
+
},
|
|
816
|
+
},
|
|
780
817
|
}
|
|
781
818
|
persistent_volumes = self.core_client.list_persistent_volume().items
|
|
782
819
|
for volume in persistent_volumes:
|
|
@@ -80,12 +80,34 @@ class KubernetesWorkspace(Workspace):
|
|
|
80
80
|
return cls(name)
|
|
81
81
|
|
|
82
82
|
def delete(self, **kwargs):
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
try:
|
|
84
|
+
self.workload_manager.delete_workload(
|
|
85
|
+
self.name, ingress_name=self.config.ingress_name
|
|
86
|
+
)
|
|
87
|
+
finally:
|
|
88
|
+
super().delete(**kwargs)
|
|
86
89
|
|
|
87
90
|
def stop(self, **kwargs):
|
|
88
91
|
self.workload_manager.scale_down_workload(self.name)
|
|
89
92
|
|
|
90
93
|
def resume(self, **kwargs):
|
|
91
94
|
self.workload_manager.restart_workload(self.name)
|
|
95
|
+
|
|
96
|
+
def to_dict(self):
|
|
97
|
+
config = dict(
|
|
98
|
+
name=self.name,
|
|
99
|
+
**self.config.to_dict(),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
ingress_name = config.get('ingress_name')
|
|
103
|
+
try:
|
|
104
|
+
if ingress_name:
|
|
105
|
+
url = self.workload_manager.get_url_from_ingress(
|
|
106
|
+
ingress_name,
|
|
107
|
+
self.name,
|
|
108
|
+
)
|
|
109
|
+
config['url'] = url
|
|
110
|
+
except Exception:
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
return config
|
|
@@ -1255,10 +1255,16 @@ class BlockExecutor:
|
|
|
1255
1255
|
if status == BlockRun.BlockRunStatus.COMPLETED:
|
|
1256
1256
|
update_kwargs['completed_at'] = datetime.now(tz=pytz.UTC)
|
|
1257
1257
|
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1258
|
+
# Cannot save raw value in DB; it breaks:
|
|
1259
|
+
# sqlalchemy.exc.StatementError:
|
|
1260
|
+
# (builtins.TypeError) Object of type Py4JJavaError is not JSON serializable
|
|
1261
|
+
# [SQL: UPDATE block_run SET updated_at=CURRENT_TIMESTAMP, status=?, metrics=?
|
|
1262
|
+
# WHERE block_run.id = ?]
|
|
1263
|
+
|
|
1264
|
+
# if BlockRun.BlockRunStatus.FAILED == status and error_details:
|
|
1265
|
+
# update_kwargs['metrics'] = merge_dict(block_run.metrics or {}, dict(
|
|
1266
|
+
# __error_details=error_details,
|
|
1267
|
+
# ))
|
|
1262
1268
|
|
|
1263
1269
|
block_run.update(**update_kwargs)
|
|
1264
1270
|
return
|