outerbounds 0.3.180rc5__py3-none-any.whl → 0.3.181__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.
- outerbounds/__init__.py +1 -3
- outerbounds/command_groups/apps_cli.py +6 -2
- outerbounds/command_groups/cli.py +2 -0
- outerbounds/command_groups/kubernetes_cli.py +479 -0
- {outerbounds-0.3.180rc5.dist-info → outerbounds-0.3.181.dist-info}/METADATA +3 -3
- {outerbounds-0.3.180rc5.dist-info → outerbounds-0.3.181.dist-info}/RECORD +8 -28
- outerbounds-0.3.181.dist-info/entry_points.txt +3 -0
- outerbounds/_vendor/spinner/__init__.py +0 -4
- outerbounds/_vendor/spinner/spinners.py +0 -478
- outerbounds/_vendor/spinner.LICENSE +0 -21
- outerbounds/apps/__init__.py +0 -0
- outerbounds/apps/_state_machine.py +0 -358
- outerbounds/apps/app_cli.py +0 -1312
- outerbounds/apps/app_config.py +0 -293
- outerbounds/apps/artifacts.py +0 -0
- outerbounds/apps/capsule.py +0 -798
- outerbounds/apps/cli_to_config.py +0 -99
- outerbounds/apps/code_package/__init__.py +0 -3
- outerbounds/apps/code_package/code_packager.py +0 -610
- outerbounds/apps/code_package/examples.py +0 -125
- outerbounds/apps/config_schema.yaml +0 -264
- outerbounds/apps/dependencies.py +0 -115
- outerbounds/apps/deployer.py +0 -0
- outerbounds/apps/experimental/__init__.py +0 -103
- outerbounds/apps/perimeters.py +0 -51
- outerbounds/apps/secrets.py +0 -164
- outerbounds/apps/utils.py +0 -386
- outerbounds/apps/validations.py +0 -22
- outerbounds-0.3.180rc5.dist-info/entry_points.txt +0 -3
- {outerbounds-0.3.180rc5.dist-info → outerbounds-0.3.181.dist-info}/WHEEL +0 -0
outerbounds/__init__.py
CHANGED
@@ -8,7 +8,6 @@ import shutil
|
|
8
8
|
import subprocess
|
9
9
|
|
10
10
|
from ..utils import metaflowconfig
|
11
|
-
from ..apps.app_cli import app
|
12
11
|
|
13
12
|
APP_READY_POLL_TIMEOUT_SECONDS = 300
|
14
13
|
# Even after our backend validates that the app routes are ready, it takes a few seconds for
|
@@ -21,6 +20,11 @@ def cli(**kwargs):
|
|
21
20
|
pass
|
22
21
|
|
23
22
|
|
23
|
+
@click.group(help="Manage apps")
|
24
|
+
def app(**kwargs):
|
25
|
+
pass
|
26
|
+
|
27
|
+
|
24
28
|
@app.command(help="Start an app using a port and a name")
|
25
29
|
@click.option(
|
26
30
|
"-d",
|
@@ -410,7 +414,7 @@ def kill_process(config_dir=None, profile=None, port=-1, name=""):
|
|
410
414
|
default=os.environ.get("METAFLOW_PROFILE", ""),
|
411
415
|
help="The named metaflow profile in which your workstation exists",
|
412
416
|
)
|
413
|
-
def
|
417
|
+
def list(config_dir=None, profile=None):
|
414
418
|
if "WORKSTATION_ID" not in os.environ:
|
415
419
|
click.secho(
|
416
420
|
"All outerbounds app commands can only be run from a workstation.",
|
@@ -7,6 +7,7 @@ from . import (
|
|
7
7
|
tutorials_cli,
|
8
8
|
fast_bakery_cli,
|
9
9
|
secrets_cli,
|
10
|
+
kubernetes_cli,
|
10
11
|
)
|
11
12
|
|
12
13
|
|
@@ -20,6 +21,7 @@ from . import (
|
|
20
21
|
tutorials_cli.cli,
|
21
22
|
fast_bakery_cli.cli,
|
22
23
|
secrets_cli.cli,
|
24
|
+
kubernetes_cli.cli,
|
23
25
|
],
|
24
26
|
)
|
25
27
|
def cli(**kwargs):
|
@@ -0,0 +1,479 @@
|
|
1
|
+
import json
|
2
|
+
import base64
|
3
|
+
import boto3
|
4
|
+
import requests
|
5
|
+
import os
|
6
|
+
import time
|
7
|
+
from datetime import datetime
|
8
|
+
from functools import partial
|
9
|
+
|
10
|
+
from os import path, environ
|
11
|
+
from sys import exit
|
12
|
+
from outerbounds._vendor import click
|
13
|
+
|
14
|
+
from ..utils import metaflowconfig
|
15
|
+
|
16
|
+
|
17
|
+
def _logger(
|
18
|
+
body="", system_msg=False, head="", bad=False, timestamp=True, nl=True, color=None
|
19
|
+
):
|
20
|
+
if timestamp:
|
21
|
+
if timestamp is True:
|
22
|
+
dt = datetime.now()
|
23
|
+
else:
|
24
|
+
dt = timestamp
|
25
|
+
tstamp = dt.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
26
|
+
click.secho(tstamp + " ", fg=ColorTheme.TIMESTAMP, nl=False)
|
27
|
+
if head:
|
28
|
+
click.secho(head, fg=ColorTheme.INFO_COLOR, nl=False)
|
29
|
+
click.secho(
|
30
|
+
body,
|
31
|
+
bold=system_msg,
|
32
|
+
fg=ColorTheme.BAD_COLOR if bad else color if color is not None else None,
|
33
|
+
nl=nl,
|
34
|
+
)
|
35
|
+
|
36
|
+
|
37
|
+
class ColorTheme:
|
38
|
+
TIMESTAMP = "magenta"
|
39
|
+
LOADING_COLOR = "cyan"
|
40
|
+
BAD_COLOR = "red"
|
41
|
+
INFO_COLOR = "green"
|
42
|
+
|
43
|
+
TL_HEADER_COLOR = "magenta"
|
44
|
+
ROW_COLOR = "bright_white"
|
45
|
+
|
46
|
+
INFO_KEY_COLOR = "green"
|
47
|
+
INFO_VALUE_COLOR = "bright_white"
|
48
|
+
|
49
|
+
|
50
|
+
def print_table(data, headers):
|
51
|
+
"""Print data in a formatted table."""
|
52
|
+
|
53
|
+
if not data:
|
54
|
+
return
|
55
|
+
|
56
|
+
# Calculate column widths
|
57
|
+
col_widths = [len(h) for h in headers]
|
58
|
+
|
59
|
+
# Calculate actual widths based on data
|
60
|
+
for row in data:
|
61
|
+
for i, cell in enumerate(row):
|
62
|
+
col_widths[i] = max(col_widths[i], len(str(cell)))
|
63
|
+
|
64
|
+
# Print header
|
65
|
+
header_row = " | ".join(
|
66
|
+
[headers[i].ljust(col_widths[i]) for i in range(len(headers))]
|
67
|
+
)
|
68
|
+
click.secho("-" * len(header_row), fg=ColorTheme.TL_HEADER_COLOR)
|
69
|
+
click.secho(header_row, fg=ColorTheme.TL_HEADER_COLOR, bold=True)
|
70
|
+
click.secho("-" * len(header_row), fg=ColorTheme.TL_HEADER_COLOR)
|
71
|
+
|
72
|
+
# Print data rows
|
73
|
+
for row in data:
|
74
|
+
formatted_row = " | ".join(
|
75
|
+
[str(row[i]).ljust(col_widths[i]) for i in range(len(row))]
|
76
|
+
)
|
77
|
+
click.secho(formatted_row, fg=ColorTheme.ROW_COLOR, bold=True)
|
78
|
+
click.secho("-" * len(header_row), fg=ColorTheme.TL_HEADER_COLOR)
|
79
|
+
|
80
|
+
|
81
|
+
def _get_kubernetes_client():
|
82
|
+
"""Get kubernetes client from metaflow configuration."""
|
83
|
+
from metaflow.plugins.kubernetes.kubernetes_client import KubernetesClient
|
84
|
+
|
85
|
+
return KubernetesClient()
|
86
|
+
|
87
|
+
|
88
|
+
def _get_current_user():
|
89
|
+
"""Get current user from environment or metaflow config."""
|
90
|
+
# Try to get user from metaflow config first
|
91
|
+
try:
|
92
|
+
from metaflow.util import get_username
|
93
|
+
|
94
|
+
user = get_username()
|
95
|
+
if user:
|
96
|
+
return user
|
97
|
+
except:
|
98
|
+
pass
|
99
|
+
|
100
|
+
# Fallback to environment variables
|
101
|
+
raise click.ClickException("Failed to get current user")
|
102
|
+
|
103
|
+
|
104
|
+
def _format_jobs_and_jobsets_table(
|
105
|
+
jobs_with_outcomes, jobsets_with_outcomes, filter_unchanged=True
|
106
|
+
):
|
107
|
+
"""Format jobs and jobsets into a table for display."""
|
108
|
+
headers = [
|
109
|
+
"Type",
|
110
|
+
"Name",
|
111
|
+
"Namespace",
|
112
|
+
"Status",
|
113
|
+
"Outcome",
|
114
|
+
"Created",
|
115
|
+
"Flow",
|
116
|
+
"Run ID",
|
117
|
+
"User",
|
118
|
+
]
|
119
|
+
table_data = []
|
120
|
+
|
121
|
+
# Add jobs to table
|
122
|
+
for job, outcome in jobs_with_outcomes:
|
123
|
+
# Filter out unchanged resources if requested
|
124
|
+
if filter_unchanged and outcome == "leave_unchanged":
|
125
|
+
continue
|
126
|
+
|
127
|
+
annotations = job.metadata.annotations or {}
|
128
|
+
|
129
|
+
# Format creation timestamp
|
130
|
+
created_time = "N/A"
|
131
|
+
if job.metadata.creation_timestamp:
|
132
|
+
created_time = job.metadata.creation_timestamp.strftime("%Y-%m-%d %H:%M:%S")
|
133
|
+
|
134
|
+
table_data.append(
|
135
|
+
[
|
136
|
+
"Job",
|
137
|
+
job.metadata.name,
|
138
|
+
job.metadata.namespace,
|
139
|
+
str(job.status.active or 0) + " active"
|
140
|
+
if job.status.active
|
141
|
+
else "inactive",
|
142
|
+
outcome,
|
143
|
+
created_time,
|
144
|
+
annotations.get("metaflow/flow_name", "N/A"),
|
145
|
+
annotations.get("metaflow/run_id", "N/A"),
|
146
|
+
annotations.get("metaflow/user", "N/A"),
|
147
|
+
]
|
148
|
+
)
|
149
|
+
|
150
|
+
# Add jobsets to table
|
151
|
+
for jobset, outcome in jobsets_with_outcomes:
|
152
|
+
# Filter out unchanged resources if requested
|
153
|
+
if filter_unchanged and outcome == "leave_unchanged":
|
154
|
+
continue
|
155
|
+
|
156
|
+
metadata = jobset.get("metadata", {})
|
157
|
+
annotations = metadata.get("annotations", {})
|
158
|
+
status = jobset.get("status", {})
|
159
|
+
|
160
|
+
# Format creation timestamp
|
161
|
+
created_time = "N/A"
|
162
|
+
creation_timestamp = metadata.get("creationTimestamp")
|
163
|
+
if creation_timestamp:
|
164
|
+
try:
|
165
|
+
from datetime import datetime
|
166
|
+
|
167
|
+
# Parse ISO timestamp
|
168
|
+
dt = datetime.fromisoformat(creation_timestamp.replace("Z", "+00:00"))
|
169
|
+
created_time = dt.strftime("%Y-%m-%d %H:%M:%S")
|
170
|
+
except:
|
171
|
+
created_time = (
|
172
|
+
creation_timestamp[:19]
|
173
|
+
if len(creation_timestamp) >= 19
|
174
|
+
else creation_timestamp
|
175
|
+
)
|
176
|
+
|
177
|
+
table_data.append(
|
178
|
+
[
|
179
|
+
"JobSet",
|
180
|
+
metadata.get("name", "N/A"),
|
181
|
+
metadata.get("namespace", "N/A"),
|
182
|
+
"terminal" if status.get("terminalState") else "running",
|
183
|
+
outcome,
|
184
|
+
created_time,
|
185
|
+
annotations.get("metaflow/flow_name", "N/A"),
|
186
|
+
annotations.get("metaflow/run_id", "N/A"),
|
187
|
+
annotations.get("metaflow/user", "N/A"),
|
188
|
+
]
|
189
|
+
)
|
190
|
+
|
191
|
+
return headers, table_data
|
192
|
+
|
193
|
+
|
194
|
+
@click.group()
|
195
|
+
def cli(**kwargs):
|
196
|
+
pass
|
197
|
+
|
198
|
+
|
199
|
+
@click.group(help="Commands for interacting with Kubernetes.")
|
200
|
+
def kubernetes(**kwargs):
|
201
|
+
pass
|
202
|
+
|
203
|
+
|
204
|
+
@kubernetes.command(help="Kill pods/jobs/jobsets for a specific flow.")
|
205
|
+
@click.option("--flow-name", required=True, help="Flow name to kill pods for")
|
206
|
+
@click.option("--run-id", help="Specific run ID to kill pods for")
|
207
|
+
@click.option("--my-runs", is_flag=True, help="Only kill runs by current user")
|
208
|
+
@click.option(
|
209
|
+
"--dry-run", is_flag=True, help="Show what would be killed without actually killing"
|
210
|
+
)
|
211
|
+
@click.option("--auto-approve", is_flag=True, help="Skip confirmation prompt")
|
212
|
+
@click.option(
|
213
|
+
"--clear-everything",
|
214
|
+
is_flag=True,
|
215
|
+
help="Force delete ALL matching resources regardless of their status (including terminal/completed ones)",
|
216
|
+
)
|
217
|
+
def kill(flow_name, run_id, my_runs, dry_run, auto_approve, clear_everything):
|
218
|
+
"""Kill pods/jobs/jobsets for a specific flow."""
|
219
|
+
import warnings
|
220
|
+
from metaflow.ob_internal import PodKiller # type: ignore
|
221
|
+
|
222
|
+
warnings.filterwarnings("ignore")
|
223
|
+
|
224
|
+
logger = partial(_logger, timestamp=True)
|
225
|
+
|
226
|
+
# Get kubernetes client
|
227
|
+
kubernetes_client = _get_kubernetes_client()
|
228
|
+
|
229
|
+
# Determine user filter
|
230
|
+
user = None
|
231
|
+
if my_runs:
|
232
|
+
user = _get_current_user()
|
233
|
+
logger(f"🔍 Filtering for runs by user: {user}", color=ColorTheme.INFO_COLOR)
|
234
|
+
|
235
|
+
pod_killer = PodKiller(
|
236
|
+
kubernetes_client=kubernetes_client.get(),
|
237
|
+
echo_func=lambda x: None,
|
238
|
+
namespace=kubernetes_client._namespace,
|
239
|
+
)
|
240
|
+
|
241
|
+
# Find matching jobs and jobsets
|
242
|
+
logger(
|
243
|
+
f"🔍 Searching for jobs and jobsets matching flow: {flow_name}",
|
244
|
+
color=ColorTheme.INFO_COLOR,
|
245
|
+
)
|
246
|
+
if run_id:
|
247
|
+
logger(f"🔍 Filtering by run ID: {run_id}", color=ColorTheme.INFO_COLOR)
|
248
|
+
|
249
|
+
try:
|
250
|
+
(
|
251
|
+
jobs_with_outcomes,
|
252
|
+
jobsets_with_outcomes,
|
253
|
+
) = pod_killer.extract_matching_jobs_and_jobsets(
|
254
|
+
flow_name=flow_name, run_id=run_id, user=user
|
255
|
+
)
|
256
|
+
except Exception as e:
|
257
|
+
logger(f"Error finding matching resources: {e}", bad=True, system_msg=True)
|
258
|
+
exit(1)
|
259
|
+
|
260
|
+
# Check if anything was found
|
261
|
+
total_resources_found = len(jobs_with_outcomes) + len(jobsets_with_outcomes)
|
262
|
+
if total_resources_found == 0:
|
263
|
+
logger("✅ No matching jobs or jobsets found.", color=ColorTheme.INFO_COLOR)
|
264
|
+
return
|
265
|
+
|
266
|
+
# Calculate resources that will be processed
|
267
|
+
if clear_everything:
|
268
|
+
# Process ALL resources regardless of status
|
269
|
+
jobs_to_process = len(jobs_with_outcomes)
|
270
|
+
jobsets_to_process = len(jobsets_with_outcomes)
|
271
|
+
total_to_process = jobs_to_process + jobsets_to_process
|
272
|
+
filter_table = False
|
273
|
+
|
274
|
+
# Show warning for clear-everything mode
|
275
|
+
logger(
|
276
|
+
"⚠️ CLEAR EVERYTHING MODE: All matching resources will be force deleted regardless of status!",
|
277
|
+
color=ColorTheme.BAD_COLOR,
|
278
|
+
system_msg=True,
|
279
|
+
)
|
280
|
+
else:
|
281
|
+
# Normal mode: only process resources not in terminal state
|
282
|
+
jobs_to_process = len(
|
283
|
+
[j for j, outcome in jobs_with_outcomes if outcome != "leave_unchanged"]
|
284
|
+
)
|
285
|
+
jobsets_to_process = len(
|
286
|
+
[j for j, outcome in jobsets_with_outcomes if outcome != "leave_unchanged"]
|
287
|
+
)
|
288
|
+
total_to_process = jobs_to_process + jobsets_to_process
|
289
|
+
filter_table = True
|
290
|
+
|
291
|
+
# Display what will be affected
|
292
|
+
headers, table_data = _format_jobs_and_jobsets_table(
|
293
|
+
jobs_with_outcomes, jobsets_with_outcomes, filter_unchanged=filter_table
|
294
|
+
)
|
295
|
+
|
296
|
+
if total_to_process == 0:
|
297
|
+
logger(
|
298
|
+
"✅ All matching resources are already in terminal state. Nothing to do.",
|
299
|
+
color=ColorTheme.INFO_COLOR,
|
300
|
+
)
|
301
|
+
return
|
302
|
+
|
303
|
+
if dry_run:
|
304
|
+
logger(
|
305
|
+
"=== DRY RUN - The following resources would be affected ===",
|
306
|
+
color=ColorTheme.INFO_COLOR,
|
307
|
+
system_msg=True,
|
308
|
+
timestamp=False,
|
309
|
+
)
|
310
|
+
else:
|
311
|
+
logger(
|
312
|
+
"=== The following resources will be killed/deleted ===",
|
313
|
+
color=ColorTheme.BAD_COLOR,
|
314
|
+
system_msg=True,
|
315
|
+
timestamp=False,
|
316
|
+
)
|
317
|
+
|
318
|
+
print_table(table_data, headers)
|
319
|
+
|
320
|
+
# Show summary
|
321
|
+
logger(
|
322
|
+
"📊 Summary:",
|
323
|
+
)
|
324
|
+
logger(
|
325
|
+
f" • Total resources found: {total_resources_found}",
|
326
|
+
)
|
327
|
+
logger(
|
328
|
+
f" • Jobs to process: {jobs_to_process}",
|
329
|
+
)
|
330
|
+
logger(
|
331
|
+
f" • JobSets to process: {jobsets_to_process}",
|
332
|
+
)
|
333
|
+
logger(
|
334
|
+
f" • Resources to process: {total_to_process}",
|
335
|
+
)
|
336
|
+
|
337
|
+
if clear_everything:
|
338
|
+
logger(
|
339
|
+
" • Mode: CLEAR EVERYTHING (forcing deletion of ALL resources)",
|
340
|
+
color=ColorTheme.BAD_COLOR,
|
341
|
+
)
|
342
|
+
else:
|
343
|
+
# Show how many are being skipped in normal mode
|
344
|
+
skipped_resources = total_resources_found - total_to_process
|
345
|
+
if skipped_resources > 0:
|
346
|
+
logger(
|
347
|
+
f" • Resources already in terminal state (skipped): {skipped_resources}",
|
348
|
+
color=ColorTheme.INFO_COLOR,
|
349
|
+
)
|
350
|
+
|
351
|
+
if dry_run:
|
352
|
+
logger(
|
353
|
+
"🔍 Dry run completed. No resources were actually killed.",
|
354
|
+
color=ColorTheme.INFO_COLOR,
|
355
|
+
system_msg=True,
|
356
|
+
)
|
357
|
+
return
|
358
|
+
|
359
|
+
# Confirm before proceeding (unless auto-approve is set)
|
360
|
+
if not auto_approve:
|
361
|
+
confirm = click.prompt(
|
362
|
+
click.style(
|
363
|
+
f"⚠️ Are you sure you want to kill/delete {total_to_process} resources?",
|
364
|
+
fg=ColorTheme.BAD_COLOR,
|
365
|
+
bold=True,
|
366
|
+
),
|
367
|
+
default="no",
|
368
|
+
type=click.Choice(["yes", "no"]),
|
369
|
+
)
|
370
|
+
if confirm == "no":
|
371
|
+
logger("❌ Operation cancelled.", color=ColorTheme.BAD_COLOR)
|
372
|
+
exit(1)
|
373
|
+
|
374
|
+
# Execute the kills/deletions
|
375
|
+
logger(
|
376
|
+
f"🚀 Processing {total_to_process} resources...",
|
377
|
+
color=ColorTheme.INFO_COLOR,
|
378
|
+
system_msg=True,
|
379
|
+
)
|
380
|
+
|
381
|
+
try:
|
382
|
+
progress_label = (
|
383
|
+
f"⚰️ Coffin: Deleting jobs and jobsets matching flow: {flow_name}"
|
384
|
+
)
|
385
|
+
if clear_everything:
|
386
|
+
progress_label = (
|
387
|
+
f"🔥 CLEAR ALL: Force deleting ALL resources for flow: {flow_name}"
|
388
|
+
)
|
389
|
+
|
390
|
+
__progress_bar = click.progressbar(
|
391
|
+
length=total_to_process,
|
392
|
+
label=click.style(
|
393
|
+
progress_label,
|
394
|
+
fg=ColorTheme.BAD_COLOR if clear_everything else ColorTheme.INFO_COLOR,
|
395
|
+
bold=True,
|
396
|
+
),
|
397
|
+
fill_char=click.style(
|
398
|
+
"█",
|
399
|
+
fg=ColorTheme.BAD_COLOR if clear_everything else ColorTheme.INFO_COLOR,
|
400
|
+
bold=True,
|
401
|
+
),
|
402
|
+
empty_char=click.style(
|
403
|
+
"░",
|
404
|
+
fg=ColorTheme.BAD_COLOR if clear_everything else ColorTheme.INFO_COLOR,
|
405
|
+
bold=True,
|
406
|
+
),
|
407
|
+
item_show_func=lambda x: click.style(
|
408
|
+
x,
|
409
|
+
fg=ColorTheme.BAD_COLOR,
|
410
|
+
bold=True,
|
411
|
+
),
|
412
|
+
)
|
413
|
+
|
414
|
+
pod_killer = PodKiller(
|
415
|
+
kubernetes_client=kubernetes_client.get(),
|
416
|
+
echo_func=lambda x: None,
|
417
|
+
namespace=kubernetes_client._namespace,
|
418
|
+
progress_bar=__progress_bar,
|
419
|
+
)
|
420
|
+
|
421
|
+
if clear_everything:
|
422
|
+
# Force delete everything mode
|
423
|
+
(
|
424
|
+
results,
|
425
|
+
jobs_processed,
|
426
|
+
jobsets_processed,
|
427
|
+
) = pod_killer.process_matching_jobs_and_jobsets_force_all(
|
428
|
+
flow_name=flow_name, run_id=run_id, user=user
|
429
|
+
)
|
430
|
+
else:
|
431
|
+
# Normal mode
|
432
|
+
(
|
433
|
+
results,
|
434
|
+
jobs_processed,
|
435
|
+
jobsets_processed,
|
436
|
+
) = pod_killer.process_matching_jobs_and_jobsets(
|
437
|
+
flow_name=flow_name, run_id=run_id, user=user
|
438
|
+
)
|
439
|
+
|
440
|
+
# Report results
|
441
|
+
successful_operations = sum(1 for r in results if r is True)
|
442
|
+
failed_operations = sum(1 for r in results if r is False)
|
443
|
+
|
444
|
+
logger(
|
445
|
+
"📊 Operation completed:",
|
446
|
+
)
|
447
|
+
logger(
|
448
|
+
f" • Jobs processed: {jobs_processed}",
|
449
|
+
)
|
450
|
+
logger(
|
451
|
+
f" • JobSets processed: {jobsets_processed}",
|
452
|
+
)
|
453
|
+
logger(
|
454
|
+
f" • Successful operations: {successful_operations}",
|
455
|
+
)
|
456
|
+
|
457
|
+
if failed_operations > 0:
|
458
|
+
logger(
|
459
|
+
f" • Failed operations: {failed_operations}",
|
460
|
+
color=ColorTheme.BAD_COLOR,
|
461
|
+
)
|
462
|
+
logger(
|
463
|
+
"⚠️ Some operations failed. Check the logs above for details.",
|
464
|
+
color=ColorTheme.BAD_COLOR,
|
465
|
+
system_msg=True,
|
466
|
+
)
|
467
|
+
else:
|
468
|
+
logger(
|
469
|
+
"✅ All operations completed successfully!",
|
470
|
+
color=ColorTheme.INFO_COLOR,
|
471
|
+
system_msg=True,
|
472
|
+
)
|
473
|
+
|
474
|
+
except Exception as e:
|
475
|
+
logger(f"Error during kill operation: {e}", bad=True, system_msg=True)
|
476
|
+
raise e
|
477
|
+
|
478
|
+
|
479
|
+
cli.add_command(kubernetes, name="kubernetes")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: outerbounds
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.181
|
4
4
|
Summary: More Data Science, Less Administration
|
5
5
|
License: Proprietary
|
6
6
|
Keywords: data science,machine learning,MLOps
|
@@ -29,8 +29,8 @@ Requires-Dist: google-cloud-secret-manager (>=2.20.0,<3.0.0) ; extra == "gcp"
|
|
29
29
|
Requires-Dist: google-cloud-storage (>=2.14.0,<3.0.0) ; extra == "gcp"
|
30
30
|
Requires-Dist: metaflow-checkpoint (==0.2.1)
|
31
31
|
Requires-Dist: ob-metaflow (==2.15.17.1)
|
32
|
-
Requires-Dist: ob-metaflow-extensions (==1.1.
|
33
|
-
Requires-Dist: ob-metaflow-stubs (==6.0.3.
|
32
|
+
Requires-Dist: ob-metaflow-extensions (==1.1.169)
|
33
|
+
Requires-Dist: ob-metaflow-stubs (==6.0.3.181)
|
34
34
|
Requires-Dist: opentelemetry-distro (>=0.41b0) ; extra == "otel"
|
35
35
|
Requires-Dist: opentelemetry-exporter-otlp-proto-http (>=1.20.0) ; extra == "otel"
|
36
36
|
Requires-Dist: opentelemetry-instrumentation-requests (>=0.41b0) ; extra == "otel"
|
@@ -1,4 +1,4 @@
|
|
1
|
-
outerbounds/__init__.py,sha256=
|
1
|
+
outerbounds/__init__.py,sha256=GPdaubvAYF8pOFWJ3b-sPMKCpyfpteWVMZWkmaYhxRw,32
|
2
2
|
outerbounds/_vendor/PyYAML.LICENSE,sha256=jTko-dxEkP1jVwfLiOsmvXZBAqcoKVQwfT5RZ6V36KQ,1101
|
3
3
|
outerbounds/_vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
4
|
outerbounds/_vendor/_yaml/__init__.py,sha256=nD985-g4Mrx97PhtSzI2L53o8sCHUJ4ZoBWcUd7o0PQ,1449
|
@@ -20,9 +20,6 @@ outerbounds/_vendor/click/testing.py,sha256=ptpMYgRY7dVfE3UDgkgwayu9ePw98sQI3D7z
|
|
20
20
|
outerbounds/_vendor/click/types.py,sha256=u8LK2CRcVw3jWDutzP_wgFV478TXhsjL8gYSFPjGKkE,35865
|
21
21
|
outerbounds/_vendor/click/utils.py,sha256=33D6E7poH_nrKB-xr-UyDEXnxOcCiQqxuRLtrqeVv6o,18682
|
22
22
|
outerbounds/_vendor/click.LICENSE,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475
|
23
|
-
outerbounds/_vendor/spinner/__init__.py,sha256=gTm0NdQGTRxmghye1CYkhRtodji0MezsqAWs9OrLLRc,102
|
24
|
-
outerbounds/_vendor/spinner/spinners.py,sha256=0Hl1dskTzfibHPM5dfHV3no1maFduOCJ48r8mr3tgr0,15786
|
25
|
-
outerbounds/_vendor/spinner.LICENSE,sha256=R2t7cIDZ6-VroHO5M_FWET8adqmdI-PGv0AnhyvCo4A,1069
|
26
23
|
outerbounds/_vendor/vendor_any.txt,sha256=9vi_h2zSBIx0sFM9i69xNEwe-HyeX0_sdtMjImmvgXo,27
|
27
24
|
outerbounds/_vendor/yaml/__init__.py,sha256=lPcXUknB0EUNfCL8MJOgq26y70nOQR_Eajqzycmtnhg,12569
|
28
25
|
outerbounds/_vendor/yaml/_yaml.cpython-311-darwin.so,sha256=YiF55JiadfOvw_mUH-lONNnsiMHj6C6o1SBfTCvvW54,362008
|
@@ -42,29 +39,12 @@ outerbounds/_vendor/yaml/resolver.py,sha256=dPhU1d7G1JCMktPFvNhyqwj2oNvx1yf_Jfa3
|
|
42
39
|
outerbounds/_vendor/yaml/scanner.py,sha256=ZcI8IngR56PaQ0m27WU2vxCqmDCuRjz-hr7pirbMPuw,52982
|
43
40
|
outerbounds/_vendor/yaml/serializer.py,sha256=8wFZRy9SsQSktF_f9OOroroqsh4qVUe53ry07P9UgCc,4368
|
44
41
|
outerbounds/_vendor/yaml/tokens.py,sha256=JBSu38wihGr4l73JwbfMA7Ks1-X84g8-NskTz7KwPmA,2578
|
45
|
-
outerbounds/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
46
|
-
outerbounds/apps/_state_machine.py,sha256=3hQF5O2zJdtQWdy9e5w393O85u6UjGApqTMlRU3UhFk,12964
|
47
|
-
outerbounds/apps/app_cli.py,sha256=j_0vEQlsKGgvlPidog4bSZrY-_D1ne3JmHVafijL-iI,45354
|
48
|
-
outerbounds/apps/app_config.py,sha256=KBmW9grhiuG9XZG-R0GZkM-024cjj6ztGzOX_2wZW34,11291
|
49
|
-
outerbounds/apps/artifacts.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
50
|
-
outerbounds/apps/capsule.py,sha256=YoOkUYGPrbXPaSUwS3teui1XPJDZCIxn-9tPpm1X0GM,30927
|
51
|
-
outerbounds/apps/cli_to_config.py,sha256=Thc5jXRxoU6Pr8kAVVOX-5Es5ha6y6Vh_GBzL__oI7Q,3299
|
52
|
-
outerbounds/apps/code_package/__init__.py,sha256=8McF7pgx8ghvjRnazp2Qktlxi9yYwNiwESSQrk-2oW8,68
|
53
|
-
outerbounds/apps/code_package/code_packager.py,sha256=RWvM5BKjgLhu7icsO_n5SSYC57dwyST0dWpoWF88ovU,22881
|
54
|
-
outerbounds/apps/code_package/examples.py,sha256=aF8qKIJxCVv_ugcShQjqUsXKKKMsm1oMkQIl8w3QKuw,4016
|
55
|
-
outerbounds/apps/config_schema.yaml,sha256=j_mysTAPkIMSocItTg3aduMDfBs2teIhAErvpF0Elus,8826
|
56
|
-
outerbounds/apps/dependencies.py,sha256=UucyQYZ5VjUPBb3XzAARa4fuiLHXuV7iMZ8OZ_nAuE8,3949
|
57
|
-
outerbounds/apps/deployer.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
58
|
-
outerbounds/apps/experimental/__init__.py,sha256=ExPFIJSF8FcE1pKVyiNQnX8aBi3Rz8YFQ2_s5NVeU7I,3056
|
59
|
-
outerbounds/apps/perimeters.py,sha256=DKWKMbkSx8WObf4yA74UolleT417B9c_6POioztMHxY,1766
|
60
|
-
outerbounds/apps/secrets.py,sha256=sBMJzwjsARhtcuxgn18i66lagllpYOlh2oXR_hVAb1Y,6110
|
61
|
-
outerbounds/apps/utils.py,sha256=NbEpSrzk-l95EBFoWuxil_0xjpIuFt0jjIXD2r1w71M,12118
|
62
|
-
outerbounds/apps/validations.py,sha256=kR2eXckx0XJ4kUOOLkMRepbTh0INtL1Z8aV4-fZpfc8,678
|
63
42
|
outerbounds/cli_main.py,sha256=e9UMnPysmc7gbrimq2I4KfltggyU7pw59Cn9aEguVcU,74
|
64
43
|
outerbounds/command_groups/__init__.py,sha256=QPWtj5wDRTINDxVUL7XPqG3HoxHNvYOg08EnuSZB2Hc,21
|
65
|
-
outerbounds/command_groups/apps_cli.py,sha256=
|
66
|
-
outerbounds/command_groups/cli.py,sha256=
|
44
|
+
outerbounds/command_groups/apps_cli.py,sha256=v9OlQ1b4BGB-cBZiHB6W5gDocDoMmrQ7zdK11QiJ-B8,20847
|
45
|
+
outerbounds/command_groups/cli.py,sha256=FTeeDrvyBb-qcs2xklTiCyVTN5I0tBPyBReqDIE4oWU,530
|
67
46
|
outerbounds/command_groups/fast_bakery_cli.py,sha256=5kja7v6C651XAY6dsP_IkBPJQgfU4hA4S9yTOiVPhW0,6213
|
47
|
+
outerbounds/command_groups/kubernetes_cli.py,sha256=2bxPKUp5g_gdwVo4lT-IeWvHxz6Jmj1KxG70nXNgX_M,14758
|
68
48
|
outerbounds/command_groups/local_setup_cli.py,sha256=tuuqJRXQ_guEwOuQSIf9wkUU0yg8yAs31myGViAK15s,36364
|
69
49
|
outerbounds/command_groups/perimeters_cli.py,sha256=iF_Uw7ROiSctf6FgoJEy30iDBLVE1j9FKuR3shgJRmc,19050
|
70
50
|
outerbounds/command_groups/secrets_cli.py,sha256=Vgn_aiTo76a0s5hCJhNWEOrCVhyYeivD08ooQxz0y7c,2952
|
@@ -76,7 +56,7 @@ outerbounds/utils/metaflowconfig.py,sha256=l2vJbgPkLISU-XPGZFaC8ZKmYFyJemlD6bwB-
|
|
76
56
|
outerbounds/utils/schema.py,sha256=lMUr9kNgn9wy-sO_t_Tlxmbt63yLeN4b0xQXbDUDj4A,2331
|
77
57
|
outerbounds/utils/utils.py,sha256=4Z8cszNob_8kDYCLNTrP-wWads_S_MdL3Uj3ju4mEsk,501
|
78
58
|
outerbounds/vendor.py,sha256=gRLRJNXtZBeUpPEog0LOeIsl6GosaFFbCxUvR4bW6IQ,5093
|
79
|
-
outerbounds-0.3.
|
80
|
-
outerbounds-0.3.
|
81
|
-
outerbounds-0.3.
|
82
|
-
outerbounds-0.3.
|
59
|
+
outerbounds-0.3.181.dist-info/METADATA,sha256=XyEanDRdzyNArTKojXDq1HKZtEC6iPvJwv_7VrPFb6I,1837
|
60
|
+
outerbounds-0.3.181.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
61
|
+
outerbounds-0.3.181.dist-info/entry_points.txt,sha256=7ye0281PKlvqxu15rjw60zKg2pMsXI49_A8BmGqIqBw,47
|
62
|
+
outerbounds-0.3.181.dist-info/RECORD,,
|