outerbounds 0.3.93__py3-none-any.whl → 0.3.94__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 +89 -32
- outerbounds/command_groups/local_setup_cli.py +3 -74
- {outerbounds-0.3.93.dist-info → outerbounds-0.3.94.dist-info}/METADATA +1 -1
- {outerbounds-0.3.93.dist-info → outerbounds-0.3.94.dist-info}/RECORD +6 -6
- {outerbounds-0.3.93.dist-info → outerbounds-0.3.94.dist-info}/WHEEL +0 -0
- {outerbounds-0.3.93.dist-info → outerbounds-0.3.94.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,36 @@ 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
|
-
|
133
|
-
|
167
|
+
if named_port["enabled"]:
|
168
|
+
already_running_message = (
|
169
|
+
f"App {name} already running on port {port}!"
|
170
|
+
if named_port["name"] == name
|
171
|
+
else f"App {named_port['name']} already running on port {port} is now renamed to {name}!"
|
172
|
+
)
|
134
173
|
click.secho(
|
135
|
-
|
174
|
+
already_running_message,
|
136
175
|
fg="green",
|
137
176
|
err=True,
|
138
177
|
)
|
@@ -167,7 +206,12 @@ def start(config_dir=None, profile=None, port=-1, name="", output=""):
|
|
167
206
|
fg="green",
|
168
207
|
err=True,
|
169
208
|
)
|
170
|
-
|
209
|
+
click.secho(
|
210
|
+
f"App URL: {api_url.replace('api', 'ui')}/apps/{os.environ['WORKSTATION_ID']}/{name}/",
|
211
|
+
fg="green",
|
212
|
+
err=True,
|
213
|
+
)
|
214
|
+
except Exception:
|
171
215
|
click.secho(
|
172
216
|
f"Failed to start app {name} on port {port}!",
|
173
217
|
fg="red",
|
@@ -187,20 +231,6 @@ def start(config_dir=None, profile=None, port=-1, name="", output=""):
|
|
187
231
|
)
|
188
232
|
)
|
189
233
|
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
234
|
except Exception as e:
|
205
235
|
click.secho(f"Failed to start app {name} on port {port}!", fg="red", err=True)
|
206
236
|
start_app_step.update(
|
@@ -256,6 +286,13 @@ def stop(config_dir=None, profile=None, port=-1, name="", output=""):
|
|
256
286
|
err=True,
|
257
287
|
)
|
258
288
|
return
|
289
|
+
elif port > 0 and name:
|
290
|
+
click.secho(
|
291
|
+
"Please provide either a port number or a name to stop the app, not both.",
|
292
|
+
fg="red",
|
293
|
+
err=True,
|
294
|
+
)
|
295
|
+
return
|
259
296
|
|
260
297
|
stop_app_response = OuterboundsCommandResponse()
|
261
298
|
|
@@ -335,8 +372,13 @@ def stop(config_dir=None, profile=None, port=-1, name="", output=""):
|
|
335
372
|
):
|
336
373
|
stop_app_response.add_step(validate_port_exists)
|
337
374
|
if not named_port["enabled"]:
|
375
|
+
already_stopped_message = (
|
376
|
+
f"No deployed app named {named_port['name']} found."
|
377
|
+
if name
|
378
|
+
else f"There is no app deployed on port {port}"
|
379
|
+
)
|
338
380
|
click.secho(
|
339
|
-
|
381
|
+
already_stopped_message,
|
340
382
|
fg="green",
|
341
383
|
err=True,
|
342
384
|
)
|
@@ -354,26 +396,26 @@ def stop(config_dir=None, profile=None, port=-1, name="", output=""):
|
|
354
396
|
f"{api_url}/v1/workstations/update/{os.environ['WORKSTATION_ID']}/namedports",
|
355
397
|
headers={"x-api-key": metaflow_token},
|
356
398
|
json={
|
357
|
-
"port": port,
|
399
|
+
"port": named_port["port"],
|
358
400
|
"name": named_port["name"],
|
359
401
|
"enabled": False,
|
360
402
|
},
|
361
403
|
)
|
362
404
|
response.raise_for_status()
|
363
405
|
click.secho(
|
364
|
-
f"App stopped on port {port}!",
|
406
|
+
f"App {named_port['name']} stopped on port {named_port['port']}!",
|
365
407
|
fg="green",
|
366
408
|
err=True,
|
367
409
|
)
|
368
|
-
except:
|
410
|
+
except Exception as e:
|
369
411
|
click.secho(
|
370
|
-
f"Failed to stop app on port {port}!",
|
412
|
+
f"Failed to stop app {named_port['name']} on port {named_port['port']}!",
|
371
413
|
fg="red",
|
372
414
|
err=True,
|
373
415
|
)
|
374
416
|
stop_app_step.update(
|
375
417
|
OuterboundsCommandStatus.FAIL,
|
376
|
-
f"Failed to stop app on port {port}!",
|
418
|
+
f"Failed to stop app {named_port['name']} on port {named_port['port']}!",
|
377
419
|
"",
|
378
420
|
)
|
379
421
|
|
@@ -387,12 +429,10 @@ def stop(config_dir=None, profile=None, port=-1, name="", output=""):
|
|
387
429
|
return
|
388
430
|
|
389
431
|
err_message = (
|
390
|
-
f"Port {port} not found on workstation {os.environ['WORKSTATION_ID']}"
|
432
|
+
(f"Port {port} not found on workstation {os.environ['WORKSTATION_ID']}")
|
433
|
+
if port != -1
|
434
|
+
else f"App {name} not found on workstation {os.environ['WORKSTATION_ID']}"
|
391
435
|
)
|
392
|
-
if port == -1:
|
393
|
-
err_message = (
|
394
|
-
f"App {name} not found on workstation {os.environ['WORKSTATION_ID']}"
|
395
|
-
)
|
396
436
|
|
397
437
|
click.secho(
|
398
438
|
err_message,
|
@@ -532,4 +572,21 @@ def list(config_dir=None, profile=None, output=""):
|
|
532
572
|
click.echo(json.dumps(list_app_response.as_dict(), indent=4))
|
533
573
|
|
534
574
|
|
575
|
+
def ensure_app_start_request_is_valid(existing_named_ports, port: int, name: str):
|
576
|
+
existing_apps_by_port = {np["port"]: np for np in existing_named_ports}
|
577
|
+
|
578
|
+
if port not in existing_apps_by_port:
|
579
|
+
raise ValueError(f"Port {port} not found on workstation")
|
580
|
+
|
581
|
+
for existing_named_port in existing_named_ports:
|
582
|
+
if (
|
583
|
+
name == existing_named_port["name"]
|
584
|
+
and existing_named_port["port"] != port
|
585
|
+
and existing_named_port["enabled"]
|
586
|
+
):
|
587
|
+
raise ValueError(
|
588
|
+
f"App with name '{name}' is already deployed on port {existing_named_port['port']}"
|
589
|
+
)
|
590
|
+
|
591
|
+
|
535
592
|
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)
|
@@ -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=sgRPYMdL_c7UUcFqy1pTQno0Q2aT1LHT7POB_VUuKoE,22612
|
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.94.dist-info/entry_points.txt,sha256=7ye0281PKlvqxu15rjw60zKg2pMsXI49_A8BmGqIqBw,47
|
57
|
+
outerbounds-0.3.94.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
|
58
|
+
outerbounds-0.3.94.dist-info/METADATA,sha256=ipH_wW6z5ITcDg1vBorq7clnY8ajMt-WMBgOcfNbxyA,1632
|
59
|
+
outerbounds-0.3.94.dist-info/RECORD,,
|
File without changes
|
File without changes
|