outerbounds 0.3.93__py3-none-any.whl → 0.3.95__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/command_groups/apps_cli.py +96 -45
- outerbounds/command_groups/local_setup_cli.py +3 -74
- {outerbounds-0.3.93.dist-info → outerbounds-0.3.95.dist-info}/METADATA +4 -4
- {outerbounds-0.3.93.dist-info → outerbounds-0.3.95.dist-info}/RECORD +6 -6
- {outerbounds-0.3.93.dist-info → outerbounds-0.3.95.dist-info}/WHEEL +0 -0
- {outerbounds-0.3.93.dist-info → outerbounds-0.3.95.dist-info}/entry_points.txt +0 -0
@@ -56,6 +56,21 @@ def app(**kwargs):
|
|
56
56
|
type=click.Choice(["json", ""]),
|
57
57
|
)
|
58
58
|
def start(config_dir=None, profile=None, port=-1, name="", output=""):
|
59
|
+
if len(name) == 0 or len(name) >= 20:
|
60
|
+
click.secho(
|
61
|
+
"App name should not be more than 20 characters long.",
|
62
|
+
fg="red",
|
63
|
+
err=True,
|
64
|
+
)
|
65
|
+
return
|
66
|
+
elif not name.isalnum() or not name.islower():
|
67
|
+
click.secho(
|
68
|
+
"App name can only contain lowercase alphanumeric characters.",
|
69
|
+
fg="red",
|
70
|
+
err=True,
|
71
|
+
)
|
72
|
+
return
|
73
|
+
|
59
74
|
start_app_response = OuterboundsCommandResponse()
|
60
75
|
|
61
76
|
validate_workstation_step = CommandStatus(
|
@@ -70,10 +85,10 @@ def start(config_dir=None, profile=None, port=-1, name="", output=""):
|
|
70
85
|
"List of workstations fetched.",
|
71
86
|
)
|
72
87
|
|
73
|
-
|
74
|
-
"
|
88
|
+
validate_request = CommandStatus(
|
89
|
+
"ValidateRequest",
|
75
90
|
OuterboundsCommandStatus.OK,
|
76
|
-
"
|
91
|
+
"Start app request is valid.",
|
77
92
|
)
|
78
93
|
|
79
94
|
start_app_step = CommandStatus(
|
@@ -127,12 +142,31 @@ def start(config_dir=None, profile=None, port=-1, name="", output=""):
|
|
127
142
|
for workstation in workstations_json:
|
128
143
|
if workstation["instance_id"] == os.environ["WORKSTATION_ID"]:
|
129
144
|
if "named_ports" in workstation["spec"]:
|
145
|
+
try:
|
146
|
+
ensure_app_start_request_is_valid(
|
147
|
+
workstation["spec"]["named_ports"], port, name
|
148
|
+
)
|
149
|
+
except ValueError as e:
|
150
|
+
click.secho(str(e), fg="red", err=True)
|
151
|
+
validate_request.update(
|
152
|
+
OuterboundsCommandStatus.FAIL,
|
153
|
+
str(e),
|
154
|
+
"",
|
155
|
+
)
|
156
|
+
start_app_response.add_step(validate_request)
|
157
|
+
if output == "json":
|
158
|
+
click.echo(
|
159
|
+
json.dumps(start_app_response.as_dict(), indent=4)
|
160
|
+
)
|
161
|
+
return
|
162
|
+
|
163
|
+
start_app_response.add_step(validate_request)
|
164
|
+
|
130
165
|
for named_port in workstation["spec"]["named_ports"]:
|
131
166
|
if int(named_port["port"]) == port:
|
132
|
-
start_app_response.add_step(validate_port_exists)
|
133
167
|
if named_port["enabled"] and named_port["name"] == name:
|
134
168
|
click.secho(
|
135
|
-
f"App {name}
|
169
|
+
f"App {name} already running on port {port}!",
|
136
170
|
fg="green",
|
137
171
|
err=True,
|
138
172
|
)
|
@@ -167,7 +201,12 @@ def start(config_dir=None, profile=None, port=-1, name="", output=""):
|
|
167
201
|
fg="green",
|
168
202
|
err=True,
|
169
203
|
)
|
170
|
-
|
204
|
+
click.secho(
|
205
|
+
f"App URL: {api_url.replace('api', 'ui')}/apps/{os.environ['WORKSTATION_ID']}/{name}/",
|
206
|
+
fg="green",
|
207
|
+
err=True,
|
208
|
+
)
|
209
|
+
except Exception:
|
171
210
|
click.secho(
|
172
211
|
f"Failed to start app {name} on port {port}!",
|
173
212
|
fg="red",
|
@@ -187,20 +226,6 @@ def start(config_dir=None, profile=None, port=-1, name="", output=""):
|
|
187
226
|
)
|
188
227
|
)
|
189
228
|
return
|
190
|
-
|
191
|
-
click.secho(
|
192
|
-
f"Port {port} not found on workstation {os.environ['WORKSTATION_ID']}",
|
193
|
-
fg="red",
|
194
|
-
err=True,
|
195
|
-
)
|
196
|
-
validate_port_exists.update(
|
197
|
-
OuterboundsCommandStatus.FAIL,
|
198
|
-
f"Port {port} not found on workstation {os.environ['WORKSTATION_ID']}",
|
199
|
-
"",
|
200
|
-
)
|
201
|
-
start_app_response.add_step(validate_port_exists)
|
202
|
-
if output == "json":
|
203
|
-
click.echo(json.dumps(start_app_response.as_dict(), indent=4))
|
204
229
|
except Exception as e:
|
205
230
|
click.secho(f"Failed to start app {name} on port {port}!", fg="red", err=True)
|
206
231
|
start_app_step.update(
|
@@ -256,6 +281,13 @@ def stop(config_dir=None, profile=None, port=-1, name="", output=""):
|
|
256
281
|
err=True,
|
257
282
|
)
|
258
283
|
return
|
284
|
+
elif port > 0 and name:
|
285
|
+
click.secho(
|
286
|
+
"Please provide either a port number or a name to stop the app, not both.",
|
287
|
+
fg="red",
|
288
|
+
err=True,
|
289
|
+
)
|
290
|
+
return
|
259
291
|
|
260
292
|
stop_app_response = OuterboundsCommandResponse()
|
261
293
|
|
@@ -324,6 +356,7 @@ def stop(config_dir=None, profile=None, port=-1, name="", output=""):
|
|
324
356
|
click.echo(json.dumps(stop_app_response.as_dict(), indent=4))
|
325
357
|
return
|
326
358
|
|
359
|
+
app_found = False
|
327
360
|
workstations_json = workstations_response.json()["workstations"]
|
328
361
|
for workstation in workstations_json:
|
329
362
|
if workstation["instance_id"] == os.environ["WORKSTATION_ID"]:
|
@@ -333,47 +366,34 @@ def stop(config_dir=None, profile=None, port=-1, name="", output=""):
|
|
333
366
|
int(named_port["port"]) == port
|
334
367
|
or named_port["name"] == name
|
335
368
|
):
|
369
|
+
app_found = True
|
336
370
|
stop_app_response.add_step(validate_port_exists)
|
337
|
-
if
|
338
|
-
click.secho(
|
339
|
-
f"App {named_port['name']} stopped on port {port}!",
|
340
|
-
fg="green",
|
341
|
-
err=True,
|
342
|
-
)
|
343
|
-
stop_app_response.add_step(stop_app_step)
|
344
|
-
if output == "json":
|
345
|
-
click.echo(
|
346
|
-
json.dumps(
|
347
|
-
stop_app_response.as_dict(), indent=4
|
348
|
-
)
|
349
|
-
)
|
350
|
-
return
|
351
|
-
else:
|
371
|
+
if named_port["enabled"]:
|
352
372
|
try:
|
353
373
|
response = requests.put(
|
354
374
|
f"{api_url}/v1/workstations/update/{os.environ['WORKSTATION_ID']}/namedports",
|
355
375
|
headers={"x-api-key": metaflow_token},
|
356
376
|
json={
|
357
|
-
"port": port,
|
377
|
+
"port": named_port["port"],
|
358
378
|
"name": named_port["name"],
|
359
379
|
"enabled": False,
|
360
380
|
},
|
361
381
|
)
|
362
382
|
response.raise_for_status()
|
363
383
|
click.secho(
|
364
|
-
f"App stopped on port {port}!",
|
384
|
+
f"App {named_port['name']} stopped on port {named_port['port']}!",
|
365
385
|
fg="green",
|
366
386
|
err=True,
|
367
387
|
)
|
368
|
-
except:
|
388
|
+
except Exception as e:
|
369
389
|
click.secho(
|
370
|
-
f"Failed to stop app on port {port}!",
|
390
|
+
f"Failed to stop app {named_port['name']} on port {named_port['port']}!",
|
371
391
|
fg="red",
|
372
392
|
err=True,
|
373
393
|
)
|
374
394
|
stop_app_step.update(
|
375
395
|
OuterboundsCommandStatus.FAIL,
|
376
|
-
f"Failed to stop app on port {port}!",
|
396
|
+
f"Failed to stop app {named_port['name']} on port {named_port['port']}!",
|
377
397
|
"",
|
378
398
|
)
|
379
399
|
|
@@ -386,13 +406,27 @@ def stop(config_dir=None, profile=None, port=-1, name="", output=""):
|
|
386
406
|
)
|
387
407
|
return
|
388
408
|
|
409
|
+
if app_found:
|
410
|
+
already_stopped_message = (
|
411
|
+
f"No deployed app named {name} found."
|
412
|
+
if name
|
413
|
+
else f"There is no app deployed on port {port}"
|
414
|
+
)
|
415
|
+
click.secho(
|
416
|
+
already_stopped_message,
|
417
|
+
fg="green",
|
418
|
+
err=True,
|
419
|
+
)
|
420
|
+
stop_app_response.add_step(stop_app_step)
|
421
|
+
if output == "json":
|
422
|
+
click.echo(json.dumps(stop_app_response.as_dict(), indent=4))
|
423
|
+
return
|
424
|
+
|
389
425
|
err_message = (
|
390
|
-
f"Port {port} not found on workstation {os.environ['WORKSTATION_ID']}"
|
426
|
+
(f"Port {port} not found on workstation {os.environ['WORKSTATION_ID']}")
|
427
|
+
if port != -1
|
428
|
+
else f"App {name} not found on workstation {os.environ['WORKSTATION_ID']}"
|
391
429
|
)
|
392
|
-
if port == -1:
|
393
|
-
err_message = (
|
394
|
-
f"App {name} not found on workstation {os.environ['WORKSTATION_ID']}"
|
395
|
-
)
|
396
430
|
|
397
431
|
click.secho(
|
398
432
|
err_message,
|
@@ -532,4 +566,21 @@ def list(config_dir=None, profile=None, output=""):
|
|
532
566
|
click.echo(json.dumps(list_app_response.as_dict(), indent=4))
|
533
567
|
|
534
568
|
|
569
|
+
def ensure_app_start_request_is_valid(existing_named_ports, port: int, name: str):
|
570
|
+
existing_apps_by_port = {np["port"]: np for np in existing_named_ports}
|
571
|
+
|
572
|
+
if port not in existing_apps_by_port:
|
573
|
+
raise ValueError(f"Port {port} not found on workstation")
|
574
|
+
|
575
|
+
for existing_named_port in existing_named_ports:
|
576
|
+
if (
|
577
|
+
name == existing_named_port["name"]
|
578
|
+
and existing_named_port["port"] != port
|
579
|
+
and existing_named_port["enabled"]
|
580
|
+
):
|
581
|
+
raise ValueError(
|
582
|
+
f"App with name '{name}' is already deployed on port {existing_named_port['port']}"
|
583
|
+
)
|
584
|
+
|
585
|
+
|
535
586
|
cli.add_command(app, name="app")
|
@@ -5,7 +5,6 @@ import os
|
|
5
5
|
import re
|
6
6
|
import subprocess
|
7
7
|
import sys
|
8
|
-
import time
|
9
8
|
import zlib
|
10
9
|
from base64 import b64decode, b64encode
|
11
10
|
from importlib.machinery import PathFinder
|
@@ -16,10 +15,6 @@ from outerbounds._vendor import click
|
|
16
15
|
import requests
|
17
16
|
from requests.exceptions import HTTPError
|
18
17
|
|
19
|
-
from google.oauth2 import service_account
|
20
|
-
import google.auth
|
21
|
-
import google.auth.jwt
|
22
|
-
|
23
18
|
from ..utils import kubeconfig, metaflowconfig
|
24
19
|
from ..utils.schema import (
|
25
20
|
CommandStatus,
|
@@ -767,48 +762,6 @@ def get_gha_jwt(audience: str):
|
|
767
762
|
sys.exit(1)
|
768
763
|
|
769
764
|
|
770
|
-
def get_gcp_jwt(audience: str, service_account_token_file_path: str = ""):
|
771
|
-
try:
|
772
|
-
if service_account_token_file_path != "":
|
773
|
-
credentials = service_account.Credentials.from_service_account_file(
|
774
|
-
service_account_token_file_path
|
775
|
-
)
|
776
|
-
else:
|
777
|
-
credentials, project = google.auth.default()
|
778
|
-
except Exception as e:
|
779
|
-
click.secho(
|
780
|
-
f"Failed to get Google Cloud service account credentials. Error: {str(e)}",
|
781
|
-
fg="red",
|
782
|
-
)
|
783
|
-
sys.exit(1)
|
784
|
-
|
785
|
-
# Ensure the credentials are service account credentials to sign the JWT
|
786
|
-
if isinstance(credentials, service_account.Credentials):
|
787
|
-
try:
|
788
|
-
payload = {
|
789
|
-
"iat": int(time.time()), # Issued at time
|
790
|
-
"exp": int(time.time()) + 172800, # 48 hours expiration time
|
791
|
-
"sub": credentials.service_account_email,
|
792
|
-
"aud": audience,
|
793
|
-
"sa_email": credentials.service_account_email,
|
794
|
-
}
|
795
|
-
|
796
|
-
signed_jwt = google.auth.jwt.encode(credentials.signer, payload)
|
797
|
-
return signed_jwt.decode("utf-8")
|
798
|
-
except Exception as e:
|
799
|
-
click.secho(
|
800
|
-
f"Failed to sign JWT token using Google Cloud service account credentials. Error: {str(e)}",
|
801
|
-
fg="red",
|
802
|
-
)
|
803
|
-
sys.exit(1)
|
804
|
-
|
805
|
-
click.secho(
|
806
|
-
"The provided credentials are not service account credentials. Please provide a valid service account credentials file or set valid service account credentials in the environment using GOOGLE_APPLICATION_CREDENTIALS.",
|
807
|
-
fg="red",
|
808
|
-
)
|
809
|
-
sys.exit(1)
|
810
|
-
|
811
|
-
|
812
765
|
def get_origin_token(
|
813
766
|
service_principal_name: str,
|
814
767
|
deployment: str,
|
@@ -979,17 +932,6 @@ def configure(
|
|
979
932
|
is_flag=True,
|
980
933
|
help="Set if the command is being run in a GitHub Actions environment. If both --jwt-token and --github-actions are specified the --github-actions flag will be ignored.",
|
981
934
|
)
|
982
|
-
@click.option(
|
983
|
-
"--gcp",
|
984
|
-
is_flag=True,
|
985
|
-
help="Set if the command is being run which a Google Cloud service account credential. If both --jwt-token and --gcp are specified the -gcp flag will be ignored.",
|
986
|
-
)
|
987
|
-
@click.option(
|
988
|
-
"--gcp-service-account-toke-file-path",
|
989
|
-
default="",
|
990
|
-
help="The full path to the Google Cloud service account token file. If --gcp is set and this value is not specified, the default GOOGLE_APPLICATION_CREDENTIALS environment variable will be used.",
|
991
|
-
required=True,
|
992
|
-
)
|
993
935
|
@click.option(
|
994
936
|
"-d",
|
995
937
|
"--config-dir",
|
@@ -1021,31 +963,18 @@ def service_principal_configure(
|
|
1021
963
|
perimeter: str,
|
1022
964
|
jwt_token="",
|
1023
965
|
github_actions=False,
|
1024
|
-
gcp=False,
|
1025
|
-
gcp_service_account_toke_file_path="",
|
1026
966
|
config_dir=None,
|
1027
967
|
profile=None,
|
1028
968
|
echo=None,
|
1029
969
|
force=False,
|
1030
970
|
):
|
1031
971
|
audience = f"https://{deployment_domain}"
|
1032
|
-
|
1033
|
-
|
1034
|
-
if github_actions and gcp:
|
1035
|
-
click.secho(
|
1036
|
-
"Both --github-actions and --gcp flags cannot be set at the same time.",
|
1037
|
-
fg="red",
|
1038
|
-
)
|
1039
|
-
sys.exit(1)
|
972
|
+
if jwt_token == "" and github_actions:
|
973
|
+
jwt_token = get_gha_jwt(audience)
|
1040
974
|
|
1041
975
|
if jwt_token == "":
|
1042
|
-
if github_actions:
|
1043
|
-
jwt_token = get_gha_jwt(audience)
|
1044
|
-
elif gcp:
|
1045
|
-
jwt_token = get_gcp_jwt(audience, gcp_service_account_toke_file_path)
|
1046
|
-
else:
|
1047
976
|
click.secho(
|
1048
|
-
"No JWT token provided. Please provider either a valid jwt token or set --github-actions
|
977
|
+
"No JWT token provided. Please provider either a valid jwt token or set --github-actions",
|
1049
978
|
fg="red",
|
1050
979
|
)
|
1051
980
|
sys.exit(1)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: outerbounds
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.95
|
4
4
|
Summary: More Data Science, Less Administration
|
5
5
|
License: Proprietary
|
6
6
|
Keywords: data science,machine learning,MLOps
|
@@ -24,9 +24,9 @@ Requires-Dist: google-api-core (>=2.16.1,<3.0.0) ; extra == "gcp"
|
|
24
24
|
Requires-Dist: google-auth (>=2.27.0,<3.0.0) ; extra == "gcp"
|
25
25
|
Requires-Dist: google-cloud-secret-manager (>=2.20.0,<3.0.0) ; extra == "gcp"
|
26
26
|
Requires-Dist: google-cloud-storage (>=2.14.0,<3.0.0) ; extra == "gcp"
|
27
|
-
Requires-Dist: ob-metaflow (==2.12.
|
28
|
-
Requires-Dist: ob-metaflow-extensions (==1.1.
|
29
|
-
Requires-Dist: ob-metaflow-stubs (==5.
|
27
|
+
Requires-Dist: ob-metaflow (==2.12.18.1)
|
28
|
+
Requires-Dist: ob-metaflow-extensions (==1.1.83)
|
29
|
+
Requires-Dist: ob-metaflow-stubs (==5.6)
|
30
30
|
Requires-Dist: opentelemetry-distro (==0.41b0)
|
31
31
|
Requires-Dist: opentelemetry-exporter-otlp-proto-http (==1.20.0)
|
32
32
|
Requires-Dist: opentelemetry-instrumentation-requests (==0.41b0)
|
@@ -41,9 +41,9 @@ outerbounds/_vendor/yaml/serializer.py,sha256=8wFZRy9SsQSktF_f9OOroroqsh4qVUe53r
|
|
41
41
|
outerbounds/_vendor/yaml/tokens.py,sha256=JBSu38wihGr4l73JwbfMA7Ks1-X84g8-NskTz7KwPmA,2578
|
42
42
|
outerbounds/cli_main.py,sha256=e9UMnPysmc7gbrimq2I4KfltggyU7pw59Cn9aEguVcU,74
|
43
43
|
outerbounds/command_groups/__init__.py,sha256=QPWtj5wDRTINDxVUL7XPqG3HoxHNvYOg08EnuSZB2Hc,21
|
44
|
-
outerbounds/command_groups/apps_cli.py,sha256=
|
44
|
+
outerbounds/command_groups/apps_cli.py,sha256=iXaLnO-FwU_zK2ZjE-gBu1ZQdOYDLCbT0HJXJJZckeE,21895
|
45
45
|
outerbounds/command_groups/cli.py,sha256=q0hdJO4biD3iEOdyJcxnRkeleA8AKAhx842kQ49I6kk,365
|
46
|
-
outerbounds/command_groups/local_setup_cli.py,sha256=
|
46
|
+
outerbounds/command_groups/local_setup_cli.py,sha256=tuuqJRXQ_guEwOuQSIf9wkUU0yg8yAs31myGViAK15s,36364
|
47
47
|
outerbounds/command_groups/perimeters_cli.py,sha256=mrJfFIRYFOjuiz-9h4OKg2JT8Utmbs72z6wvPzDss3s,18685
|
48
48
|
outerbounds/command_groups/tutorials_cli.py,sha256=UInFyiMqtscHFfi8YQwiY_6Sdw9quJOtRu5OukEBccw,3522
|
49
49
|
outerbounds/command_groups/workstations_cli.py,sha256=V5Jbj1cVb4IRllI7fOgNgL6OekRpuFDv6CEhDb4xC6w,22016
|
@@ -53,7 +53,7 @@ outerbounds/utils/metaflowconfig.py,sha256=l2vJbgPkLISU-XPGZFaC8ZKmYFyJemlD6bwB-
|
|
53
53
|
outerbounds/utils/schema.py,sha256=lMUr9kNgn9wy-sO_t_Tlxmbt63yLeN4b0xQXbDUDj4A,2331
|
54
54
|
outerbounds/utils/utils.py,sha256=4Z8cszNob_8kDYCLNTrP-wWads_S_MdL3Uj3ju4mEsk,501
|
55
55
|
outerbounds/vendor.py,sha256=gRLRJNXtZBeUpPEog0LOeIsl6GosaFFbCxUvR4bW6IQ,5093
|
56
|
-
outerbounds-0.3.
|
57
|
-
outerbounds-0.3.
|
58
|
-
outerbounds-0.3.
|
59
|
-
outerbounds-0.3.
|
56
|
+
outerbounds-0.3.95.dist-info/entry_points.txt,sha256=7ye0281PKlvqxu15rjw60zKg2pMsXI49_A8BmGqIqBw,47
|
57
|
+
outerbounds-0.3.95.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
|
58
|
+
outerbounds-0.3.95.dist-info/METADATA,sha256=3LLiN9Qy5q9P2ps9CMZO6C_5zi8eSTkxniYoRILxbxA,1632
|
59
|
+
outerbounds-0.3.95.dist-info/RECORD,,
|
File without changes
|
File without changes
|