ob-metaflow-extensions 1.1.175rc1__py2.py3-none-any.whl → 1.1.175rc2__py2.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 ob-metaflow-extensions might be problematic. Click here for more details.
- metaflow_extensions/outerbounds/plugins/apps/core/app_cli.py +42 -374
- metaflow_extensions/outerbounds/plugins/apps/core/app_config.py +45 -225
- metaflow_extensions/outerbounds/plugins/apps/core/code_package/code_packager.py +16 -15
- metaflow_extensions/outerbounds/plugins/apps/core/config/__init__.py +11 -0
- metaflow_extensions/outerbounds/plugins/apps/core/config/cli_generator.py +161 -0
- metaflow_extensions/outerbounds/plugins/apps/core/config/config_utils.py +768 -0
- metaflow_extensions/outerbounds/plugins/apps/core/config/schema_export.py +285 -0
- metaflow_extensions/outerbounds/plugins/apps/core/config/unified_config.py +870 -0
- metaflow_extensions/outerbounds/plugins/apps/core/config_schema.yaml +217 -211
- metaflow_extensions/outerbounds/plugins/apps/core/dependencies.py +3 -3
- metaflow_extensions/outerbounds/plugins/apps/core/experimental/__init__.py +4 -36
- metaflow_extensions/outerbounds/plugins/apps/core/validations.py +4 -9
- {ob_metaflow_extensions-1.1.175rc1.dist-info → ob_metaflow_extensions-1.1.175rc2.dist-info}/METADATA +1 -1
- {ob_metaflow_extensions-1.1.175rc1.dist-info → ob_metaflow_extensions-1.1.175rc2.dist-info}/RECORD +16 -13
- metaflow_extensions/outerbounds/plugins/apps/core/cli_to_config.py +0 -99
- metaflow_extensions/outerbounds/plugins/apps/core/config_schema_autogen.json +0 -336
- {ob_metaflow_extensions-1.1.175rc1.dist-info → ob_metaflow_extensions-1.1.175rc2.dist-info}/WHEEL +0 -0
- {ob_metaflow_extensions-1.1.175rc1.dist-info → ob_metaflow_extensions-1.1.175rc2.dist-info}/top_level.txt +0 -0
|
@@ -28,13 +28,11 @@ from .app_config import (
|
|
|
28
28
|
CAPSULE_DEBUG,
|
|
29
29
|
AuthType,
|
|
30
30
|
)
|
|
31
|
+
from .config import auto_cli_options, CoreConfig
|
|
31
32
|
from .perimeters import PerimeterExtractor
|
|
32
|
-
from .cli_to_config import build_config_from_options
|
|
33
33
|
from .utils import (
|
|
34
34
|
MultiStepSpinner,
|
|
35
35
|
)
|
|
36
|
-
from . import experimental
|
|
37
|
-
from .validations import deploy_validations
|
|
38
36
|
from .code_package import CodePackager
|
|
39
37
|
from .capsule import (
|
|
40
38
|
CapsuleDeployer,
|
|
@@ -101,101 +99,6 @@ class KeyValuePair(click.ParamType):
|
|
|
101
99
|
return "KV-PAIR"
|
|
102
100
|
|
|
103
101
|
|
|
104
|
-
class MountMetaflowArtifact(click.ParamType):
|
|
105
|
-
name = "MOUNT-METAFLOW-ARTIFACT" # type: ignore
|
|
106
|
-
|
|
107
|
-
def convert(self, value, param, ctx):
|
|
108
|
-
"""
|
|
109
|
-
Convert a string like "flow=MyFlow,artifact=my_model,path=/tmp/abc" or
|
|
110
|
-
"pathspec=MyFlow/123/foo/345/my_model,path=/tmp/abc" to a dict.
|
|
111
|
-
"""
|
|
112
|
-
artifact_dict = {}
|
|
113
|
-
parts = value.split(",")
|
|
114
|
-
|
|
115
|
-
for part in parts:
|
|
116
|
-
if "=" not in part:
|
|
117
|
-
self.fail(
|
|
118
|
-
f"Invalid format in part '{part}'. Expected 'key=value'", param, ctx
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
key, val = part.split("=", 1)
|
|
122
|
-
artifact_dict[key.strip()] = val.strip()
|
|
123
|
-
|
|
124
|
-
# Validate required fields
|
|
125
|
-
if "pathspec" in artifact_dict:
|
|
126
|
-
if "path" not in artifact_dict:
|
|
127
|
-
self.fail(
|
|
128
|
-
"When using 'pathspec', you must also specify 'path'", param, ctx
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
# Return as pathspec format
|
|
132
|
-
return {
|
|
133
|
-
"pathspec": artifact_dict["pathspec"],
|
|
134
|
-
"path": artifact_dict["path"],
|
|
135
|
-
}
|
|
136
|
-
elif (
|
|
137
|
-
"flow" in artifact_dict
|
|
138
|
-
and "artifact" in artifact_dict
|
|
139
|
-
and "path" in artifact_dict
|
|
140
|
-
):
|
|
141
|
-
# Return as flow/artifact format
|
|
142
|
-
result = {
|
|
143
|
-
"flow": artifact_dict["flow"],
|
|
144
|
-
"artifact": artifact_dict["artifact"],
|
|
145
|
-
"path": artifact_dict["path"],
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
# Add optional namespace if provided
|
|
149
|
-
if "namespace" in artifact_dict:
|
|
150
|
-
result["namespace"] = artifact_dict["namespace"]
|
|
151
|
-
|
|
152
|
-
return result
|
|
153
|
-
else:
|
|
154
|
-
self.fail(
|
|
155
|
-
"Invalid format. Must be either 'flow=X,artifact=Y,path=Z' or 'pathspec=X,path=Z'",
|
|
156
|
-
param,
|
|
157
|
-
ctx,
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
def __str__(self):
|
|
161
|
-
return repr(self)
|
|
162
|
-
|
|
163
|
-
def __repr__(self):
|
|
164
|
-
return "MOUNT-METAFLOW-ARTIFACT"
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
class MountSecret(click.ParamType):
|
|
168
|
-
name = "MOUNT-SECRET" # type: ignore
|
|
169
|
-
|
|
170
|
-
def convert(self, value, param, ctx):
|
|
171
|
-
"""
|
|
172
|
-
Convert a string like "id=my_secret,path=/tmp/secret" to a dict.
|
|
173
|
-
"""
|
|
174
|
-
secret_dict = {}
|
|
175
|
-
parts = value.split(",")
|
|
176
|
-
|
|
177
|
-
for part in parts:
|
|
178
|
-
if "=" not in part:
|
|
179
|
-
self.fail(
|
|
180
|
-
f"Invalid format in part '{part}'. Expected 'key=value'", param, ctx
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
key, val = part.split("=", 1)
|
|
184
|
-
secret_dict[key.strip()] = val.strip()
|
|
185
|
-
|
|
186
|
-
# Validate required fields
|
|
187
|
-
if "id" in secret_dict and "path" in secret_dict:
|
|
188
|
-
return {"id": secret_dict["id"], "path": secret_dict["path"]}
|
|
189
|
-
else:
|
|
190
|
-
self.fail("Invalid format. Must be 'key=X,path=Y'", param, ctx)
|
|
191
|
-
|
|
192
|
-
def __str__(self):
|
|
193
|
-
return repr(self)
|
|
194
|
-
|
|
195
|
-
def __repr__(self):
|
|
196
|
-
return "MOUNT-SECRET"
|
|
197
|
-
|
|
198
|
-
|
|
199
102
|
class CommaSeparatedList(click.ParamType):
|
|
200
103
|
name = "COMMA-SEPARATED-LIST" # type: ignore
|
|
201
104
|
|
|
@@ -209,11 +112,9 @@ class CommaSeparatedList(click.ParamType):
|
|
|
209
112
|
return "COMMA-SEPARATED-LIST"
|
|
210
113
|
|
|
211
114
|
|
|
212
|
-
KVPairType = KeyValuePair()
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
CommaSeparatedListType = CommaSeparatedList()
|
|
216
|
-
KVDictType = KeyValueDictPair()
|
|
115
|
+
KVPairType = KeyValuePair() # used for --tag and --env
|
|
116
|
+
CommaSeparatedListType = CommaSeparatedList() # used for --compute-pools
|
|
117
|
+
KVDictType = KeyValueDictPair() # only Used for the list/delete commands for tags
|
|
217
118
|
|
|
218
119
|
|
|
219
120
|
class ColorTheme:
|
|
@@ -224,10 +125,13 @@ class ColorTheme:
|
|
|
224
125
|
DEBUG_COLOR = "yellow"
|
|
225
126
|
|
|
226
127
|
TL_HEADER_COLOR = "magenta"
|
|
227
|
-
|
|
128
|
+
# Use a color that is readable in both light and dark mode.
|
|
129
|
+
# "white" can be hard to see in light mode, so use "black" for rows.
|
|
130
|
+
# Alternatively, "reset" (default terminal color) is safest.
|
|
131
|
+
ROW_COLOR = "reset"
|
|
228
132
|
|
|
229
133
|
INFO_KEY_COLOR = "green"
|
|
230
|
-
INFO_VALUE_COLOR = "
|
|
134
|
+
INFO_VALUE_COLOR = "reset"
|
|
231
135
|
|
|
232
136
|
|
|
233
137
|
NativeList = list
|
|
@@ -293,7 +197,6 @@ def _pre_create_debug(
|
|
|
293
197
|
capsule: CapsuleDeployer,
|
|
294
198
|
state_dir: str,
|
|
295
199
|
options: Dict[str, Any],
|
|
296
|
-
cli_parsed_config: Dict[str, Any],
|
|
297
200
|
):
|
|
298
201
|
if CAPSULE_DEBUG:
|
|
299
202
|
os.makedirs(state_dir, exist_ok=True)
|
|
@@ -309,7 +212,6 @@ def _pre_create_debug(
|
|
|
309
212
|
"capsule_input": capsule.create_input(), # This is the input that is passed to the capsule deploy API
|
|
310
213
|
"deploy_response": capsule._capsule_deploy_response, # This is the response from the capsule deploy API
|
|
311
214
|
"cli_options": options, # These are the actual options passing down to the CLI
|
|
312
|
-
"cli_parsed_config": cli_parsed_config, # This is the config object that is created after parsing options from the CLI
|
|
313
215
|
},
|
|
314
216
|
indent=2,
|
|
315
217
|
default=str,
|
|
@@ -404,25 +306,18 @@ def app(ctx):
|
|
|
404
306
|
os.makedirs(ctx.obj.app_state_dir, exist_ok=True)
|
|
405
307
|
|
|
406
308
|
|
|
407
|
-
def
|
|
309
|
+
def parse_cli_commands(cli_command_input):
|
|
408
310
|
# There can be two modes:
|
|
409
311
|
# 1. User passes command via `--` in the CLI
|
|
410
312
|
# 2. User passes the `commands` key in the config.
|
|
313
|
+
# This function parses the command for mode 1.
|
|
411
314
|
base_commands = []
|
|
412
315
|
if len(cli_command_input) > 0:
|
|
413
|
-
# TODO: we can be a little more fancy here by allowing the user to just call
|
|
414
|
-
# `outerbounds app deploy -- foo.py` and figure out if we need to stuff python
|
|
415
|
-
# in front of the command or not. But for sake of dumb simplicity, we can just
|
|
416
|
-
# assume what ever the user called on local needs to be called remotely, we can
|
|
417
|
-
# just ask them to add the outerbounds command in front of it.
|
|
418
|
-
# So the dev ex would be :
|
|
419
|
-
# `python foo.py` -> `outerbounds app deploy -- python foo.py`
|
|
420
316
|
if type(cli_command_input) == str:
|
|
421
317
|
base_commands.append(cli_command_input)
|
|
422
318
|
else:
|
|
423
319
|
base_commands.append(shlex.join(cli_command_input))
|
|
424
|
-
|
|
425
|
-
base_commands.extend(app_config.get("commands"))
|
|
320
|
+
|
|
426
321
|
return base_commands
|
|
427
322
|
|
|
428
323
|
|
|
@@ -466,194 +361,6 @@ def deployment_instance_options(func):
|
|
|
466
361
|
return wrapper
|
|
467
362
|
|
|
468
363
|
|
|
469
|
-
def common_deploy_options(func):
|
|
470
|
-
@click.option(
|
|
471
|
-
"--name",
|
|
472
|
-
type=str,
|
|
473
|
-
help="The name of the app to deploy.",
|
|
474
|
-
)
|
|
475
|
-
@click.option("--port", type=int, help="Port where the app is hosted.")
|
|
476
|
-
@click.option(
|
|
477
|
-
"--tag",
|
|
478
|
-
"tags",
|
|
479
|
-
multiple=True,
|
|
480
|
-
type=KVPairType,
|
|
481
|
-
help="The tags of the app to deploy. Format KEY=VALUE. Example --tag foo=bar --tag x=y",
|
|
482
|
-
default=None,
|
|
483
|
-
)
|
|
484
|
-
@click.option(
|
|
485
|
-
"--image",
|
|
486
|
-
type=str,
|
|
487
|
-
help="The Docker image to deploy with the App",
|
|
488
|
-
default=None,
|
|
489
|
-
)
|
|
490
|
-
@click.option(
|
|
491
|
-
"--cpu",
|
|
492
|
-
type=str,
|
|
493
|
-
help="CPU resource request and limit",
|
|
494
|
-
default=None,
|
|
495
|
-
)
|
|
496
|
-
@click.option(
|
|
497
|
-
"--memory",
|
|
498
|
-
type=str,
|
|
499
|
-
help="Memory resource request and limit",
|
|
500
|
-
default=None,
|
|
501
|
-
)
|
|
502
|
-
@click.option(
|
|
503
|
-
"--gpu",
|
|
504
|
-
type=str,
|
|
505
|
-
help="GPU resource request and limit",
|
|
506
|
-
default=None,
|
|
507
|
-
)
|
|
508
|
-
@click.option(
|
|
509
|
-
"--disk",
|
|
510
|
-
type=str,
|
|
511
|
-
help="Storage resource request and limit",
|
|
512
|
-
default=None,
|
|
513
|
-
)
|
|
514
|
-
@click.option(
|
|
515
|
-
"--health-check-enabled",
|
|
516
|
-
type=bool,
|
|
517
|
-
help="Enable health checks",
|
|
518
|
-
default=None,
|
|
519
|
-
)
|
|
520
|
-
@click.option(
|
|
521
|
-
"--health-check-path",
|
|
522
|
-
type=str,
|
|
523
|
-
help="Health check path",
|
|
524
|
-
default=None,
|
|
525
|
-
)
|
|
526
|
-
@click.option(
|
|
527
|
-
"--health-check-initial-delay",
|
|
528
|
-
type=int,
|
|
529
|
-
help="Initial delay seconds for health check",
|
|
530
|
-
default=None,
|
|
531
|
-
)
|
|
532
|
-
@click.option(
|
|
533
|
-
"--health-check-period",
|
|
534
|
-
type=int,
|
|
535
|
-
help="Period seconds for health check",
|
|
536
|
-
default=None,
|
|
537
|
-
)
|
|
538
|
-
@click.option(
|
|
539
|
-
"--compute-pools",
|
|
540
|
-
type=CommaSeparatedListType,
|
|
541
|
-
help="The compute pools to deploy the app to. Example: --compute-pools default,large",
|
|
542
|
-
default=None,
|
|
543
|
-
)
|
|
544
|
-
@click.option(
|
|
545
|
-
"--auth-type",
|
|
546
|
-
type=click.Choice(AuthType.enums()),
|
|
547
|
-
help="The type of authentication to use for the app.",
|
|
548
|
-
default=None,
|
|
549
|
-
)
|
|
550
|
-
@click.option(
|
|
551
|
-
"--public-access/--private-access",
|
|
552
|
-
"auth_public",
|
|
553
|
-
type=bool,
|
|
554
|
-
help="Whether the app is public or not.",
|
|
555
|
-
default=None,
|
|
556
|
-
)
|
|
557
|
-
@click.option(
|
|
558
|
-
"--no-deps",
|
|
559
|
-
is_flag=True,
|
|
560
|
-
help="Do not any dependencies. Directly used the image provided",
|
|
561
|
-
default=False,
|
|
562
|
-
)
|
|
563
|
-
@click.option(
|
|
564
|
-
"--min-replicas",
|
|
565
|
-
type=int,
|
|
566
|
-
help="Minimum number of replicas to deploy",
|
|
567
|
-
default=None,
|
|
568
|
-
)
|
|
569
|
-
@click.option(
|
|
570
|
-
"--max-replicas",
|
|
571
|
-
type=int,
|
|
572
|
-
help="Maximum number of replicas to deploy",
|
|
573
|
-
default=None,
|
|
574
|
-
)
|
|
575
|
-
@click.option(
|
|
576
|
-
"--description",
|
|
577
|
-
type=str,
|
|
578
|
-
help="The description of the app to deploy.",
|
|
579
|
-
default=None,
|
|
580
|
-
)
|
|
581
|
-
@click.option(
|
|
582
|
-
"--app-type",
|
|
583
|
-
type=str,
|
|
584
|
-
help="The type of app to deploy.",
|
|
585
|
-
default=None,
|
|
586
|
-
)
|
|
587
|
-
@click.option(
|
|
588
|
-
"--force-upgrade",
|
|
589
|
-
is_flag=True,
|
|
590
|
-
help="Force upgrade the app even if it is currently being upgraded.",
|
|
591
|
-
default=False,
|
|
592
|
-
)
|
|
593
|
-
@wraps(func)
|
|
594
|
-
def wrapper(*args, **kwargs):
|
|
595
|
-
return func(*args, **kwargs)
|
|
596
|
-
|
|
597
|
-
return wrapper
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
def common_run_options(func):
|
|
601
|
-
"""Common options for running and deploying apps."""
|
|
602
|
-
|
|
603
|
-
@click.option(
|
|
604
|
-
"--config-file",
|
|
605
|
-
type=str,
|
|
606
|
-
help="The config file to use for the App (YAML or JSON)",
|
|
607
|
-
default=None,
|
|
608
|
-
)
|
|
609
|
-
@click.option(
|
|
610
|
-
"--secret",
|
|
611
|
-
"secrets",
|
|
612
|
-
multiple=True,
|
|
613
|
-
type=str,
|
|
614
|
-
help="Secrets to deploy with the App",
|
|
615
|
-
default=None,
|
|
616
|
-
)
|
|
617
|
-
@click.option(
|
|
618
|
-
"--env",
|
|
619
|
-
"envs",
|
|
620
|
-
multiple=True,
|
|
621
|
-
type=KVPairType,
|
|
622
|
-
help="Environment variables to deploy with the App. Use format KEY=VALUE",
|
|
623
|
-
default=None,
|
|
624
|
-
)
|
|
625
|
-
@click.option(
|
|
626
|
-
"--package-src-path",
|
|
627
|
-
type=str,
|
|
628
|
-
help="The path to the source code to deploy with the App.",
|
|
629
|
-
default=None,
|
|
630
|
-
)
|
|
631
|
-
@click.option(
|
|
632
|
-
"--package-suffixes",
|
|
633
|
-
type=CommaSeparatedListType,
|
|
634
|
-
help="The suffixes of the source code to deploy with the App.",
|
|
635
|
-
default=None,
|
|
636
|
-
)
|
|
637
|
-
@click.option(
|
|
638
|
-
"--dep-from-requirements",
|
|
639
|
-
type=str,
|
|
640
|
-
help="Path to requirements.txt file for dependencies",
|
|
641
|
-
default=None,
|
|
642
|
-
)
|
|
643
|
-
@click.option(
|
|
644
|
-
"--dep-from-pyproject",
|
|
645
|
-
type=str,
|
|
646
|
-
help="Path to pyproject.toml file for dependencies",
|
|
647
|
-
default=None,
|
|
648
|
-
)
|
|
649
|
-
# TODO: [FIX ME]: Get better CLI abstraction for pypi/conda dependencies
|
|
650
|
-
@wraps(func)
|
|
651
|
-
def wrapper(*args, **kwargs):
|
|
652
|
-
return func(*args, **kwargs)
|
|
653
|
-
|
|
654
|
-
return wrapper
|
|
655
|
-
|
|
656
|
-
|
|
657
364
|
def _package_necessary_things(app_config: AppConfig, logger):
|
|
658
365
|
# Packaging has a few things to be thought through:
|
|
659
366
|
# 1. if `entrypoint_path` exists then should we package the directory
|
|
@@ -692,10 +399,14 @@ def _package_necessary_things(app_config: AppConfig, logger):
|
|
|
692
399
|
|
|
693
400
|
|
|
694
401
|
@app.command(help="Deploy an app to the Outerbounds Platform.")
|
|
695
|
-
@
|
|
696
|
-
|
|
402
|
+
@click.option(
|
|
403
|
+
"--config-file",
|
|
404
|
+
type=str,
|
|
405
|
+
help="The config file to use for the App (YAML or JSON)",
|
|
406
|
+
default=None,
|
|
407
|
+
)
|
|
697
408
|
@deployment_instance_options
|
|
698
|
-
@
|
|
409
|
+
@auto_cli_options()
|
|
699
410
|
@click.pass_context
|
|
700
411
|
@click.argument("command", nargs=-1, type=click.UNPROCESSED, required=False)
|
|
701
412
|
def deploy(
|
|
@@ -715,8 +426,10 @@ def deploy(
|
|
|
715
426
|
raise AppConfigError("OB_CURRENT_PERIMETER is not set")
|
|
716
427
|
_current_instance_debug_dir = None
|
|
717
428
|
logger = partial(_logger, timestamp=True)
|
|
429
|
+
|
|
430
|
+
base_commands = parse_cli_commands(command)
|
|
431
|
+
options["commands"] = base_commands
|
|
718
432
|
try:
|
|
719
|
-
_cli_parsed_config = build_config_from_options(options)
|
|
720
433
|
# Create configuration
|
|
721
434
|
if options["config_file"]:
|
|
722
435
|
# Load from file
|
|
@@ -726,10 +439,10 @@ def deploy(
|
|
|
726
439
|
app_config.update_from_cli_options(options)
|
|
727
440
|
else:
|
|
728
441
|
# Create from CLI options
|
|
729
|
-
app_config = AppConfig(
|
|
442
|
+
app_config = AppConfig.from_cli(options)
|
|
730
443
|
|
|
731
444
|
# Validate the configuration
|
|
732
|
-
app_config.
|
|
445
|
+
app_config.commit()
|
|
733
446
|
logger(
|
|
734
447
|
f"🚀 Deploying {app_config.get('name')} to the Outerbounds platform...",
|
|
735
448
|
color=ColorTheme.INFO_COLOR,
|
|
@@ -754,23 +467,23 @@ def deploy(
|
|
|
754
467
|
logger(
|
|
755
468
|
"📦 Packaging Directory : %s" % app_config.get_state("packaging_directory"),
|
|
756
469
|
)
|
|
757
|
-
# TODO: Construct the command needed to run the app
|
|
758
|
-
# If we are constructing the directory with the src_path
|
|
759
|
-
# then we need to add the command from the option otherwise
|
|
760
|
-
# we use the command from the entrypoint path and whatever follows `--`
|
|
761
|
-
# is the command to run.
|
|
762
|
-
|
|
763
|
-
# Set some defaults for the deploy command
|
|
764
|
-
app_config.set_deploy_defaults(packaging_directory)
|
|
765
470
|
|
|
766
|
-
if
|
|
471
|
+
if app_config.get("no_deps", False):
|
|
767
472
|
# Setting this in the state will make it skip the fast-bakery step
|
|
768
473
|
# of building an image.
|
|
769
474
|
app_config.set_state("skip_dependencies", True)
|
|
770
475
|
else:
|
|
771
476
|
# Check if the user has set the dependencies in the app config
|
|
772
477
|
dependencies = app_config.get("dependencies", {})
|
|
773
|
-
|
|
478
|
+
|
|
479
|
+
if all(
|
|
480
|
+
[
|
|
481
|
+
dependencies.get("from_pyproject_toml", None) is None,
|
|
482
|
+
dependencies.get("from_requirements_file", None) is None,
|
|
483
|
+
dependencies.get("pypi", None) is None,
|
|
484
|
+
dependencies.get("conda", None) is None,
|
|
485
|
+
]
|
|
486
|
+
):
|
|
774
487
|
# The user has not set any dependencies, so we can sniff the packaging directory
|
|
775
488
|
# for a dependencies file.
|
|
776
489
|
requirements_file = os.path.join(
|
|
@@ -798,8 +511,6 @@ def deploy(
|
|
|
798
511
|
# 2. TODO: validate that the compute pool specified in the app exists.
|
|
799
512
|
# 3. Building Docker image if necessary (based on parameters)
|
|
800
513
|
# - We will bake images with fastbakery and pass it to the deploy command
|
|
801
|
-
# TODO: validation logic can be wrapped in try catch so that we can provide
|
|
802
|
-
# better error messages.
|
|
803
514
|
cache_dir = os.path.join(
|
|
804
515
|
ctx.obj.app_state_dir, app_config.get("name", "default")
|
|
805
516
|
)
|
|
@@ -808,11 +519,6 @@ def deploy(
|
|
|
808
519
|
for m in msg:
|
|
809
520
|
logger(m, **kwargs)
|
|
810
521
|
|
|
811
|
-
deploy_validations(
|
|
812
|
-
app_config,
|
|
813
|
-
cache_dir=cache_dir,
|
|
814
|
-
logger=logger,
|
|
815
|
-
)
|
|
816
522
|
image_spinner = None
|
|
817
523
|
img_logger = _non_spinner_logger
|
|
818
524
|
if not no_loader:
|
|
@@ -830,10 +536,6 @@ def deploy(
|
|
|
830
536
|
if image_spinner:
|
|
831
537
|
image_spinner.stop()
|
|
832
538
|
|
|
833
|
-
base_commands = parse_commands(app_config, command)
|
|
834
|
-
|
|
835
|
-
app_config.set_state("commands", base_commands)
|
|
836
|
-
|
|
837
539
|
# TODO: Handle the case where packaging_directory is None
|
|
838
540
|
# This would involve:
|
|
839
541
|
# 1. Packaging the code:
|
|
@@ -889,7 +591,6 @@ def deploy(
|
|
|
889
591
|
capsule,
|
|
890
592
|
_current_instance_debug_dir,
|
|
891
593
|
options,
|
|
892
|
-
_cli_parsed_config,
|
|
893
594
|
)
|
|
894
595
|
|
|
895
596
|
if len(currently_present_capsules) > 0:
|
|
@@ -1043,6 +744,14 @@ def list(ctx, project, branch, name, tags, format, auth_type):
|
|
|
1043
744
|
print_table(table_data, headers)
|
|
1044
745
|
|
|
1045
746
|
|
|
747
|
+
@app.command()
|
|
748
|
+
@auto_cli_options()
|
|
749
|
+
@click.pass_context
|
|
750
|
+
@click.argument("command", nargs=-1, type=click.UNPROCESSED, required=False)
|
|
751
|
+
def deploy2(ctx, **kwargs):
|
|
752
|
+
pass
|
|
753
|
+
|
|
754
|
+
|
|
1046
755
|
@app.command(help="Delete an app/apps from the Outerbounds Platform.")
|
|
1047
756
|
@click.option("--name", type=str, help="Filter app to delete by name")
|
|
1048
757
|
@click.option("--id", "cap_id", type=str, help="Filter app to delete by id")
|
|
@@ -1118,47 +827,6 @@ def delete(ctx, name, cap_id, project, branch, tags, auto_approve):
|
|
|
1118
827
|
time.sleep(0.5 + random.random() * 2) # delay to avoid rate limiting
|
|
1119
828
|
|
|
1120
829
|
|
|
1121
|
-
@app.command(help="Run an app locally (for testing).")
|
|
1122
|
-
@common_run_options
|
|
1123
|
-
@click.pass_context
|
|
1124
|
-
def run(ctx, **options):
|
|
1125
|
-
"""Run an app locally for testing."""
|
|
1126
|
-
try:
|
|
1127
|
-
# Create configuration
|
|
1128
|
-
if options["config_file"]:
|
|
1129
|
-
# Load from file
|
|
1130
|
-
app_config = AppConfig.from_file(options["config_file"])
|
|
1131
|
-
|
|
1132
|
-
# Update with any CLI options using the unified method
|
|
1133
|
-
app_config.update_from_cli_options(options)
|
|
1134
|
-
else:
|
|
1135
|
-
# Create from CLI options
|
|
1136
|
-
config_dict = build_config_from_options(options)
|
|
1137
|
-
app_config = AppConfig(config_dict)
|
|
1138
|
-
|
|
1139
|
-
# Validate the configuration
|
|
1140
|
-
app_config.validate()
|
|
1141
|
-
|
|
1142
|
-
# Print the configuration
|
|
1143
|
-
click.echo("Running App with configuration:")
|
|
1144
|
-
click.echo(app_config.to_yaml())
|
|
1145
|
-
|
|
1146
|
-
# TODO: Implement local run logic
|
|
1147
|
-
# This would involve:
|
|
1148
|
-
# 1. Setting up the environment
|
|
1149
|
-
# 2. Running the app locally
|
|
1150
|
-
# 3. Reporting status
|
|
1151
|
-
|
|
1152
|
-
click.echo(f"App '{app_config.config['name']}' running locally!")
|
|
1153
|
-
|
|
1154
|
-
except AppConfigError as e:
|
|
1155
|
-
click.echo(f"Error in app configuration: {e}", err=True)
|
|
1156
|
-
ctx.exit(1)
|
|
1157
|
-
except Exception as e:
|
|
1158
|
-
click.echo(f"Error running app: {e}", err=True)
|
|
1159
|
-
ctx.exit(1)
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
830
|
@app.command(
|
|
1163
831
|
help="Get detailed information about an app from the Outerbounds Platform."
|
|
1164
832
|
)
|