outerbounds 0.3.93__py3-none-any.whl → 0.3.95__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|