outerbounds 0.3.55rc4__py3-none-any.whl → 0.3.89__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/_vendor/PyYAML.LICENSE +20 -0
- outerbounds/_vendor/__init__.py +0 -0
- outerbounds/_vendor/_yaml/__init__.py +34 -0
- outerbounds/_vendor/click/__init__.py +73 -0
- outerbounds/_vendor/click/_compat.py +626 -0
- outerbounds/_vendor/click/_termui_impl.py +717 -0
- outerbounds/_vendor/click/_textwrap.py +49 -0
- outerbounds/_vendor/click/_winconsole.py +279 -0
- outerbounds/_vendor/click/core.py +2998 -0
- outerbounds/_vendor/click/decorators.py +497 -0
- outerbounds/_vendor/click/exceptions.py +287 -0
- outerbounds/_vendor/click/formatting.py +301 -0
- outerbounds/_vendor/click/globals.py +68 -0
- outerbounds/_vendor/click/parser.py +529 -0
- outerbounds/_vendor/click/py.typed +0 -0
- outerbounds/_vendor/click/shell_completion.py +580 -0
- outerbounds/_vendor/click/termui.py +787 -0
- outerbounds/_vendor/click/testing.py +479 -0
- outerbounds/_vendor/click/types.py +1073 -0
- outerbounds/_vendor/click/utils.py +580 -0
- outerbounds/_vendor/click.LICENSE +28 -0
- outerbounds/_vendor/vendor_any.txt +2 -0
- outerbounds/_vendor/yaml/__init__.py +471 -0
- outerbounds/_vendor/yaml/_yaml.cpython-311-darwin.so +0 -0
- outerbounds/_vendor/yaml/composer.py +146 -0
- outerbounds/_vendor/yaml/constructor.py +862 -0
- outerbounds/_vendor/yaml/cyaml.py +177 -0
- outerbounds/_vendor/yaml/dumper.py +138 -0
- outerbounds/_vendor/yaml/emitter.py +1239 -0
- outerbounds/_vendor/yaml/error.py +94 -0
- outerbounds/_vendor/yaml/events.py +104 -0
- outerbounds/_vendor/yaml/loader.py +62 -0
- outerbounds/_vendor/yaml/nodes.py +51 -0
- outerbounds/_vendor/yaml/parser.py +629 -0
- outerbounds/_vendor/yaml/reader.py +208 -0
- outerbounds/_vendor/yaml/representer.py +378 -0
- outerbounds/_vendor/yaml/resolver.py +245 -0
- outerbounds/_vendor/yaml/scanner.py +1555 -0
- outerbounds/_vendor/yaml/serializer.py +127 -0
- outerbounds/_vendor/yaml/tokens.py +129 -0
- outerbounds/command_groups/cli.py +1 -1
- outerbounds/command_groups/local_setup_cli.py +249 -33
- outerbounds/command_groups/perimeters_cli.py +168 -33
- outerbounds/command_groups/workstations_cli.py +29 -16
- outerbounds/utils/kubeconfig.py +2 -2
- outerbounds/utils/metaflowconfig.py +111 -21
- outerbounds/utils/schema.py +6 -0
- outerbounds/utils/utils.py +19 -0
- outerbounds/vendor.py +159 -0
- {outerbounds-0.3.55rc4.dist-info → outerbounds-0.3.89.dist-info}/METADATA +14 -6
- outerbounds-0.3.89.dist-info/RECORD +57 -0
- outerbounds-0.3.55rc4.dist-info/RECORD +0 -15
- {outerbounds-0.3.55rc4.dist-info → outerbounds-0.3.89.dist-info}/WHEEL +0 -0
- {outerbounds-0.3.55rc4.dist-info → outerbounds-0.3.89.dist-info}/entry_points.txt +0 -0
@@ -1,36 +1,33 @@
|
|
1
|
-
import base64
|
2
|
-
import hashlib
|
3
1
|
import json
|
4
2
|
import os
|
5
|
-
import re
|
6
|
-
import subprocess
|
7
3
|
import sys
|
8
|
-
import zlib
|
9
|
-
from base64 import b64decode, b64encode
|
10
|
-
from importlib.machinery import PathFinder
|
11
4
|
from os import path
|
12
|
-
from
|
13
|
-
from
|
14
|
-
|
15
|
-
import boto3
|
16
|
-
import click
|
5
|
+
from typing import Any, Dict
|
6
|
+
from outerbounds._vendor import click
|
17
7
|
import requests
|
18
|
-
from requests.exceptions import HTTPError
|
19
8
|
|
20
|
-
from ..utils import
|
9
|
+
from ..utils import metaflowconfig
|
10
|
+
from ..utils.utils import safe_write_to_disk
|
21
11
|
from ..utils.schema import (
|
22
12
|
CommandStatus,
|
23
13
|
OuterboundsCommandResponse,
|
24
14
|
OuterboundsCommandStatus,
|
25
15
|
)
|
26
16
|
|
17
|
+
from .local_setup_cli import PERIMETER_CONFIG_URL_KEY
|
18
|
+
|
27
19
|
|
28
20
|
@click.group()
|
29
21
|
def cli(**kwargs):
|
30
22
|
pass
|
31
23
|
|
32
24
|
|
33
|
-
@
|
25
|
+
@click.group(help="Manage perimeters")
|
26
|
+
def perimeter(**kwargs):
|
27
|
+
pass
|
28
|
+
|
29
|
+
|
30
|
+
@perimeter.command(help="Switch current perimeter")
|
34
31
|
@click.option(
|
35
32
|
"-d",
|
36
33
|
"--config-dir",
|
@@ -59,7 +56,7 @@ def cli(**kwargs):
|
|
59
56
|
help="Force change the existing perimeter",
|
60
57
|
default=False,
|
61
58
|
)
|
62
|
-
def
|
59
|
+
def switch(config_dir=None, profile=None, output="", id=None, force=False):
|
63
60
|
switch_perimeter_response = OuterboundsCommandResponse()
|
64
61
|
|
65
62
|
switch_perimeter_step = CommandStatus(
|
@@ -75,7 +72,7 @@ def switch_perimeter(config_dir=None, profile=None, output="", id=None, force=Fa
|
|
75
72
|
id, perimeters, output, switch_perimeter_response, switch_perimeter_step
|
76
73
|
)
|
77
74
|
|
78
|
-
path_to_config = get_ob_config_file_path(config_dir, profile)
|
75
|
+
path_to_config = metaflowconfig.get_ob_config_file_path(config_dir, profile)
|
79
76
|
|
80
77
|
import fcntl
|
81
78
|
|
@@ -94,7 +91,7 @@ def switch_perimeter(config_dir=None, profile=None, output="", id=None, force=Fa
|
|
94
91
|
|
95
92
|
ob_config_dict = {
|
96
93
|
"OB_CURRENT_PERIMETER": str(id),
|
97
|
-
|
94
|
+
PERIMETER_CONFIG_URL_KEY: perimeters[id]["remote_config_url"],
|
98
95
|
}
|
99
96
|
|
100
97
|
# Now that we have the lock, we can safely write to the file
|
@@ -119,12 +116,34 @@ def switch_perimeter(config_dir=None, profile=None, output="", id=None, force=Fa
|
|
119
116
|
)
|
120
117
|
|
121
118
|
switch_perimeter_response.add_step(switch_perimeter_step)
|
119
|
+
|
120
|
+
ensure_cloud_creds_step = CommandStatus(
|
121
|
+
"EnsureCloudCredentials",
|
122
|
+
OuterboundsCommandStatus.OK,
|
123
|
+
"Cloud credentials were successfully updated.",
|
124
|
+
)
|
125
|
+
|
126
|
+
try:
|
127
|
+
ensure_cloud_credentials_for_shell(config_dir, profile)
|
128
|
+
except:
|
129
|
+
click.secho(
|
130
|
+
"Failed to update cloud credentials.",
|
131
|
+
fg="red",
|
132
|
+
err=True,
|
133
|
+
)
|
134
|
+
ensure_cloud_creds_step.update(
|
135
|
+
status=OuterboundsCommandStatus.FAIL,
|
136
|
+
reason="Failed to update cloud credentials.",
|
137
|
+
mitigation="",
|
138
|
+
)
|
139
|
+
|
140
|
+
switch_perimeter_response.add_step(ensure_cloud_creds_step)
|
141
|
+
|
122
142
|
if output == "json":
|
123
143
|
click.echo(json.dumps(switch_perimeter_response.as_dict(), indent=4))
|
124
|
-
return
|
125
144
|
|
126
145
|
|
127
|
-
@
|
146
|
+
@perimeter.command(help="Show current perimeter")
|
128
147
|
@click.option(
|
129
148
|
"-d",
|
130
149
|
"--config-dir",
|
@@ -146,7 +165,7 @@ def switch_perimeter(config_dir=None, profile=None, output="", id=None, force=Fa
|
|
146
165
|
help="Show output in the specified format.",
|
147
166
|
type=click.Choice(["json", ""]),
|
148
167
|
)
|
149
|
-
def
|
168
|
+
def show_current(config_dir=None, profile=None, output=""):
|
150
169
|
show_current_perimeter_response = OuterboundsCommandResponse()
|
151
170
|
|
152
171
|
show_current_perimeter_step = CommandStatus(
|
@@ -192,7 +211,7 @@ def show_current_perimeter(config_dir=None, profile=None, output=""):
|
|
192
211
|
click.echo(json.dumps(show_current_perimeter_response.as_dict(), indent=4))
|
193
212
|
|
194
213
|
|
195
|
-
@
|
214
|
+
@perimeter.command(help="List all available perimeters")
|
196
215
|
@click.option(
|
197
216
|
"-d",
|
198
217
|
"--config-dir",
|
@@ -213,13 +232,29 @@ def show_current_perimeter(config_dir=None, profile=None, output=""):
|
|
213
232
|
help="Show output in the specified format.",
|
214
233
|
type=click.Choice(["json", ""]),
|
215
234
|
)
|
216
|
-
def
|
235
|
+
def list(config_dir=None, profile=None, output=""):
|
217
236
|
list_perimeters_response = OuterboundsCommandResponse()
|
218
237
|
|
219
238
|
list_perimeters_step = CommandStatus(
|
220
239
|
"ListPerimeters", OuterboundsCommandStatus.OK, "Perimeter Fetch Successful."
|
221
240
|
)
|
222
241
|
|
242
|
+
if "WORKSTATION_ID" in os.environ and (
|
243
|
+
"OBP_DEFAULT_PERIMETER" not in os.environ
|
244
|
+
or "OBP_DEFAULT_PERIMETER_URL" not in os.environ
|
245
|
+
):
|
246
|
+
list_perimeters_response.update(
|
247
|
+
OuterboundsCommandStatus.NOT_SUPPORTED,
|
248
|
+
500,
|
249
|
+
"Perimeters are not supported on old workstations.",
|
250
|
+
)
|
251
|
+
click.secho(
|
252
|
+
"Perimeters are not supported on old workstations.", err=True, fg="red"
|
253
|
+
)
|
254
|
+
if output == "json":
|
255
|
+
click.echo(json.dumps(list_perimeters_response.as_dict(), indent=4))
|
256
|
+
return
|
257
|
+
|
223
258
|
ob_config_dict = get_ob_config_or_fail_command(
|
224
259
|
config_dir, profile, output, list_perimeters_response, list_perimeters_step
|
225
260
|
)
|
@@ -254,6 +289,58 @@ def list_perimeters(config_dir=None, profile=None, output=""):
|
|
254
289
|
click.echo(json.dumps(list_perimeters_response.as_dict(), indent=4))
|
255
290
|
|
256
291
|
|
292
|
+
@perimeter.command(
|
293
|
+
help="Ensure credentials for cloud are synced with perimeter", hidden=True
|
294
|
+
)
|
295
|
+
@click.option(
|
296
|
+
"-d",
|
297
|
+
"--config-dir",
|
298
|
+
default=path.expanduser(os.environ.get("METAFLOW_HOME", "~/.metaflowconfig")),
|
299
|
+
help="Path to Metaflow configuration directory",
|
300
|
+
show_default=True,
|
301
|
+
)
|
302
|
+
@click.option(
|
303
|
+
"-p",
|
304
|
+
"--profile",
|
305
|
+
default=os.environ.get("METAFLOW_PROFILE", ""),
|
306
|
+
help="The named metaflow profile in which your workstation exists",
|
307
|
+
)
|
308
|
+
@click.option(
|
309
|
+
"-o",
|
310
|
+
"--output",
|
311
|
+
default="",
|
312
|
+
help="Show output in the specified format.",
|
313
|
+
type=click.Choice(["json", ""]),
|
314
|
+
)
|
315
|
+
def ensure_cloud_creds(config_dir=None, profile=None, output=""):
|
316
|
+
ensure_cloud_creds_step = CommandStatus(
|
317
|
+
"EnsureCloudCredentials",
|
318
|
+
OuterboundsCommandStatus.OK,
|
319
|
+
"Cloud credentials were successfully updated.",
|
320
|
+
)
|
321
|
+
|
322
|
+
ensure_cloud_creds_response = OuterboundsCommandResponse()
|
323
|
+
|
324
|
+
try:
|
325
|
+
ensure_cloud_credentials_for_shell(config_dir, profile)
|
326
|
+
click.secho("Cloud credentials updated successfully.", fg="green", err=True)
|
327
|
+
except:
|
328
|
+
click.secho(
|
329
|
+
"Failed to update cloud credentials.",
|
330
|
+
fg="red",
|
331
|
+
err=True,
|
332
|
+
)
|
333
|
+
ensure_cloud_creds_step.update(
|
334
|
+
status=OuterboundsCommandStatus.FAIL,
|
335
|
+
reason="Failed to update cloud credentials.",
|
336
|
+
mitigation="",
|
337
|
+
)
|
338
|
+
|
339
|
+
ensure_cloud_creds_response.add_step(ensure_cloud_creds_step)
|
340
|
+
if output == "json":
|
341
|
+
click.echo(json.dumps(ensure_cloud_creds_response.as_dict(), indent=4))
|
342
|
+
|
343
|
+
|
257
344
|
def get_list_perimeters_api_response(config_dir, profile):
|
258
345
|
metaflow_token = metaflowconfig.get_metaflow_token_from_config(config_dir, profile)
|
259
346
|
api_url = metaflowconfig.get_sanitized_url_from_config(
|
@@ -267,15 +354,6 @@ def get_list_perimeters_api_response(config_dir, profile):
|
|
267
354
|
return perimeters_response.json()["perimeters"]
|
268
355
|
|
269
356
|
|
270
|
-
def get_ob_config_file_path(config_dir: str, profile: str) -> str:
|
271
|
-
# If OBP_CONFIG_DIR is set, use that, otherwise use METAFLOW_HOME
|
272
|
-
# If neither are set, use ~/.metaflowconfig
|
273
|
-
obp_config_dir = path.expanduser(os.environ.get("OBP_CONFIG_DIR", config_dir))
|
274
|
-
|
275
|
-
ob_config_filename = f"ob_config_{profile}.json" if profile else "ob_config.json"
|
276
|
-
return os.path.expanduser(os.path.join(obp_config_dir, ob_config_filename))
|
277
|
-
|
278
|
-
|
279
357
|
def get_perimeters_from_api_or_fail_command(
|
280
358
|
config_dir: str,
|
281
359
|
profile: str,
|
@@ -310,7 +388,7 @@ def get_ob_config_or_fail_command(
|
|
310
388
|
command_response: OuterboundsCommandResponse,
|
311
389
|
command_step: CommandStatus,
|
312
390
|
) -> Dict[str, str]:
|
313
|
-
path_to_config = get_ob_config_file_path(config_dir, profile)
|
391
|
+
path_to_config = metaflowconfig.get_ob_config_file_path(config_dir, profile)
|
314
392
|
|
315
393
|
if not os.path.exists(path_to_config):
|
316
394
|
click.secho(
|
@@ -350,6 +428,20 @@ def get_ob_config_or_fail_command(
|
|
350
428
|
return ob_config_dict
|
351
429
|
|
352
430
|
|
431
|
+
def ensure_cloud_credentials_for_shell(config_dir, profile):
|
432
|
+
if "WORKSTATION_ID" not in os.environ:
|
433
|
+
# Naive check to see if we're running in workstation. No need to ensure anything
|
434
|
+
# if this is not a workstation.
|
435
|
+
return
|
436
|
+
|
437
|
+
mf_config = metaflowconfig.init_config(config_dir, profile)
|
438
|
+
|
439
|
+
# Currently we only support GCP. TODO: utkarsh to add support for AWS and Azure
|
440
|
+
if "METAFLOW_DEFAULT_GCP_CLIENT_PROVIDER" in mf_config:
|
441
|
+
# This is a GCP deployment.
|
442
|
+
ensure_gcp_cloud_creds(config_dir, profile)
|
443
|
+
|
444
|
+
|
353
445
|
def confirm_user_has_access_to_perimeter_or_fail(
|
354
446
|
perimeter_id: str,
|
355
447
|
perimeters: Dict[str, Any],
|
@@ -372,3 +464,46 @@ def confirm_user_has_access_to_perimeter_or_fail(
|
|
372
464
|
if output == "json":
|
373
465
|
click.echo(json.dumps(command_response.as_dict(), indent=4))
|
374
466
|
sys.exit(1)
|
467
|
+
|
468
|
+
|
469
|
+
def ensure_gcp_cloud_creds(config_dir, profile):
|
470
|
+
token_info = get_gcp_auth_credentials(config_dir, profile)
|
471
|
+
auth_url = metaflowconfig.get_sanitized_url_from_config(
|
472
|
+
config_dir, profile, "OBP_AUTH_SERVER"
|
473
|
+
)
|
474
|
+
metaflow_token = metaflowconfig.get_metaflow_token_from_config(config_dir, profile)
|
475
|
+
|
476
|
+
# GOOGLE_APPLICATION_CREDENTIALS is a well known gcloud environment variable
|
477
|
+
credentials_file_loc = os.environ["GOOGLE_APPLICATION_CREDENTIALS"]
|
478
|
+
|
479
|
+
credentials_json = {
|
480
|
+
"type": "external_account",
|
481
|
+
"audience": f"//iam.googleapis.com/projects/{token_info['gcpProjectNumber']}/locations/global/workloadIdentityPools/{token_info['gcpWorkloadIdentityPool']}/providers/{token_info['gcpWorkloadIdentityPoolProvider']}",
|
482
|
+
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
|
483
|
+
"token_url": "https://sts.googleapis.com/v1/token",
|
484
|
+
"service_account_impersonation_url": f"https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/{token_info['gcpServiceAccountEmail']}:generateAccessToken",
|
485
|
+
"credential_source": {
|
486
|
+
"url": f"{auth_url}/generate/gcp",
|
487
|
+
"headers": {"x-api-key": metaflow_token},
|
488
|
+
"format": {"type": "json", "subject_token_field_name": "token"},
|
489
|
+
},
|
490
|
+
}
|
491
|
+
|
492
|
+
safe_write_to_disk(credentials_file_loc, json.dumps(credentials_json))
|
493
|
+
|
494
|
+
|
495
|
+
def get_gcp_auth_credentials(config_dir, profile):
|
496
|
+
token = metaflowconfig.get_metaflow_token_from_config(config_dir, profile)
|
497
|
+
auth_server_url = metaflowconfig.get_sanitized_url_from_config(
|
498
|
+
config_dir, profile, "OBP_AUTH_SERVER"
|
499
|
+
)
|
500
|
+
|
501
|
+
response = requests.get(
|
502
|
+
"{}/generate/gcp".format(auth_server_url), headers={"x-api-key": token}
|
503
|
+
)
|
504
|
+
response.raise_for_status()
|
505
|
+
|
506
|
+
return response.json()
|
507
|
+
|
508
|
+
|
509
|
+
cli.add_command(perimeter, name="perimeter")
|
@@ -1,5 +1,5 @@
|
|
1
|
-
import click
|
2
|
-
import yaml
|
1
|
+
from outerbounds._vendor import click
|
2
|
+
from outerbounds._vendor import yaml
|
3
3
|
import requests
|
4
4
|
import base64
|
5
5
|
import datetime
|
@@ -19,6 +19,10 @@ from ..utils.schema import (
|
|
19
19
|
OuterboundsCommandStatus,
|
20
20
|
)
|
21
21
|
from tempfile import NamedTemporaryFile
|
22
|
+
from .perimeters_cli import (
|
23
|
+
get_perimeters_from_api_or_fail_command,
|
24
|
+
confirm_user_has_access_to_perimeter_or_fail,
|
25
|
+
)
|
22
26
|
|
23
27
|
KUBECTL_INSTALL_MITIGATION = "Please install kubectl manually from https://kubernetes.io/docs/tasks/tools/#kubectl"
|
24
28
|
|
@@ -89,7 +93,7 @@ def generate_workstation_token(config_dir=None, profile=None):
|
|
89
93
|
@click.option(
|
90
94
|
"-p",
|
91
95
|
"--profile",
|
92
|
-
default="",
|
96
|
+
default=os.environ.get("METAFLOW_PROFILE", ""),
|
93
97
|
help="The named metaflow profile in which your workstation exists",
|
94
98
|
)
|
95
99
|
@click.option(
|
@@ -111,9 +115,6 @@ def configure_cloud_workstation(config_dir=None, profile=None, binary=None, outp
|
|
111
115
|
"ConfigureKubeConfig", OuterboundsCommandStatus.OK, "Kubeconfig is configured"
|
112
116
|
)
|
113
117
|
try:
|
114
|
-
if not profile:
|
115
|
-
profile = metaflowconfig.get_metaflow_profile()
|
116
|
-
|
117
118
|
metaflow_token = metaflowconfig.get_metaflow_token_from_config(
|
118
119
|
config_dir, profile
|
119
120
|
)
|
@@ -193,7 +194,7 @@ def configure_cloud_workstation(config_dir=None, profile=None, binary=None, outp
|
|
193
194
|
@click.option(
|
194
195
|
"-p",
|
195
196
|
"--profile",
|
196
|
-
default="",
|
197
|
+
default=os.environ.get("METAFLOW_PROFILE", ""),
|
197
198
|
help="The named metaflow profile in which your workstation exists",
|
198
199
|
)
|
199
200
|
@click.option(
|
@@ -213,8 +214,6 @@ def list_workstations(config_dir=None, profile=None, output="json"):
|
|
213
214
|
list_response.add_or_update_data("workstations", [])
|
214
215
|
|
215
216
|
try:
|
216
|
-
if not profile:
|
217
|
-
profile = metaflowconfig.get_metaflow_profile()
|
218
217
|
metaflow_token = metaflowconfig.get_metaflow_token_from_config(
|
219
218
|
config_dir, profile
|
220
219
|
)
|
@@ -260,7 +259,7 @@ def list_workstations(config_dir=None, profile=None, output="json"):
|
|
260
259
|
@click.option(
|
261
260
|
"-w",
|
262
261
|
"--workstation",
|
263
|
-
default="",
|
262
|
+
default=os.environ.get("METAFLOW_PROFILE", ""),
|
264
263
|
help="The ID of the workstation to hibernate",
|
265
264
|
)
|
266
265
|
def hibernate_workstation(config_dir=None, profile=None, workstation=None):
|
@@ -308,7 +307,7 @@ def hibernate_workstation(config_dir=None, profile=None, workstation=None):
|
|
308
307
|
@click.option(
|
309
308
|
"-p",
|
310
309
|
"--profile",
|
311
|
-
default="",
|
310
|
+
default=os.environ.get("METAFLOW_PROFILE", ""),
|
312
311
|
help="The named metaflow profile in which your workstation exists",
|
313
312
|
)
|
314
313
|
@click.option(
|
@@ -322,9 +321,6 @@ def restart_workstation(config_dir=None, profile=None, workstation=None):
|
|
322
321
|
click.secho("Please specify a workstation ID", fg="red")
|
323
322
|
return
|
324
323
|
try:
|
325
|
-
if not profile:
|
326
|
-
profile = metaflowconfig.get_metaflow_profile()
|
327
|
-
|
328
324
|
metaflow_token = metaflowconfig.get_metaflow_token_from_config(
|
329
325
|
config_dir, profile
|
330
326
|
)
|
@@ -516,7 +512,7 @@ def add_to_path(program_path, platform):
|
|
516
512
|
with open(path_to_rc_file, "a+") as f: # Open bashrc file
|
517
513
|
if program_path not in f.read():
|
518
514
|
f.write("\n# Added by Outerbounds\n")
|
519
|
-
f.write(program_path)
|
515
|
+
f.write(f"export PATH=$PATH:{program_path}")
|
520
516
|
|
521
517
|
|
522
518
|
def to_windows_path(path):
|
@@ -559,7 +555,24 @@ def show_relevant_links(config_dir=None, profile=None, perimeter_id="", output="
|
|
559
555
|
show_links_response.add_or_update_data("links", [])
|
560
556
|
links = []
|
561
557
|
try:
|
562
|
-
|
558
|
+
if not perimeter_id:
|
559
|
+
metaflow_config = metaflowconfig.init_config(config_dir, profile)
|
560
|
+
else:
|
561
|
+
perimeters_dict = get_perimeters_from_api_or_fail_command(
|
562
|
+
config_dir, profile, output, show_links_response, show_links_step
|
563
|
+
)
|
564
|
+
confirm_user_has_access_to_perimeter_or_fail(
|
565
|
+
perimeter_id,
|
566
|
+
perimeters_dict,
|
567
|
+
output,
|
568
|
+
show_links_response,
|
569
|
+
show_links_step,
|
570
|
+
)
|
571
|
+
|
572
|
+
metaflow_config = metaflowconfig.init_config_from_url(
|
573
|
+
config_dir, profile, perimeters_dict[perimeter_id]["remote_config_url"]
|
574
|
+
)
|
575
|
+
|
563
576
|
links.append(
|
564
577
|
{
|
565
578
|
"id": "metaflow-ui-url",
|
outerbounds/utils/kubeconfig.py
CHANGED
@@ -1,17 +1,66 @@
|
|
1
|
+
from outerbounds._vendor import click
|
1
2
|
import json
|
2
3
|
import os
|
3
4
|
import requests
|
4
5
|
from os import path
|
5
|
-
import
|
6
|
+
from typing import Dict, Union
|
7
|
+
import sys
|
8
|
+
|
9
|
+
"""
|
10
|
+
key: perimeter specific URL to fetch the remote metaflow config from
|
11
|
+
value: the remote metaflow config
|
12
|
+
"""
|
13
|
+
CACHED_REMOTE_METAFLOW_CONFIG: Dict[str, Dict[str, str]] = {}
|
14
|
+
|
15
|
+
|
16
|
+
CURRENT_PERIMETER_KEY = "OB_CURRENT_PERIMETER"
|
17
|
+
CURRENT_PERIMETER_URL = "OB_CURRENT_PERIMETER_MF_CONFIG_URL"
|
18
|
+
CURRENT_PERIMETER_URL_LEGACY_KEY = (
|
19
|
+
"OB_CURRENT_PERIMETER_URL" # For backwards compatibility with workstations.
|
20
|
+
)
|
21
|
+
|
22
|
+
|
23
|
+
def init_config(config_dir, profile) -> Dict[str, str]:
|
24
|
+
global CACHED_REMOTE_METAFLOW_CONFIG
|
25
|
+
config = read_metaflow_config_from_filesystem(config_dir, profile)
|
26
|
+
|
27
|
+
# Either user has an ob_config.json file with the perimeter URL
|
28
|
+
# or the default config on the filesystem has the config URL in it.
|
29
|
+
perimeter_specifc_url = get_perimeter_config_url_if_set_in_ob_config(
|
30
|
+
config_dir, profile
|
31
|
+
) or config.get("OBP_METAFLOW_CONFIG_URL", "")
|
32
|
+
|
33
|
+
if perimeter_specifc_url != "":
|
34
|
+
if perimeter_specifc_url in CACHED_REMOTE_METAFLOW_CONFIG:
|
35
|
+
return CACHED_REMOTE_METAFLOW_CONFIG[perimeter_specifc_url]
|
36
|
+
|
37
|
+
remote_config = init_config_from_url(config_dir, profile, perimeter_specifc_url)
|
38
|
+
remote_config["OBP_METAFLOW_CONFIG_URL"] = perimeter_specifc_url
|
6
39
|
|
40
|
+
CACHED_REMOTE_METAFLOW_CONFIG[perimeter_specifc_url] = remote_config
|
41
|
+
return remote_config
|
42
|
+
|
43
|
+
return config
|
44
|
+
|
45
|
+
|
46
|
+
def init_config_from_url(config_dir, profile, url) -> Dict[str, str]:
|
47
|
+
config = read_metaflow_config_from_filesystem(config_dir, profile)
|
7
48
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
49
|
+
if config is None or "METAFLOW_SERVICE_AUTH_KEY" not in config:
|
50
|
+
raise Exception("METAFLOW_SERVICE_AUTH_KEY not found in config file")
|
51
|
+
|
52
|
+
config_response = requests.get(
|
53
|
+
url,
|
54
|
+
headers={"x-api-key": f'{config["METAFLOW_SERVICE_AUTH_KEY"]}'},
|
12
55
|
)
|
56
|
+
config_response.raise_for_status()
|
57
|
+
remote_config = config_response.json()["config"]
|
58
|
+
return remote_config
|
59
|
+
|
13
60
|
|
61
|
+
def read_metaflow_config_from_filesystem(config_dir, profile) -> Dict[str, str]:
|
14
62
|
config_filename = f"config_{profile}.json" if profile else "config.json"
|
63
|
+
|
15
64
|
path_to_config = os.path.join(config_dir, config_filename)
|
16
65
|
|
17
66
|
if os.path.exists(path_to_config):
|
@@ -19,22 +68,6 @@ def init_config(config_dir="", profile="") -> dict:
|
|
19
68
|
config = json.load(json_file)
|
20
69
|
else:
|
21
70
|
raise Exception("Unable to locate metaflow config at '%s')" % (path_to_config))
|
22
|
-
|
23
|
-
# This is new remote-metaflow config; fetch it from the URL
|
24
|
-
if "OBP_METAFLOW_CONFIG_URL" in config:
|
25
|
-
if config is None or "METAFLOW_SERVICE_AUTH_KEY" not in config:
|
26
|
-
raise Exception("METAFLOW_SERVICE_AUTH_KEY not found in config file")
|
27
|
-
|
28
|
-
config_response = requests.get(
|
29
|
-
config["OBP_METAFLOW_CONFIG_URL"],
|
30
|
-
headers={"x-api-key": f'{config["METAFLOW_SERVICE_AUTH_KEY"]}'},
|
31
|
-
)
|
32
|
-
config_response.raise_for_status()
|
33
|
-
remote_config = config_response.json()["config"]
|
34
|
-
remote_config["METAFLOW_SERVICE_AUTH_KEY"] = config["METAFLOW_SERVICE_AUTH_KEY"]
|
35
|
-
return remote_config
|
36
|
-
|
37
|
-
# Legacy config, use from filesystem
|
38
71
|
return config
|
39
72
|
|
40
73
|
|
@@ -70,3 +103,60 @@ def get_sanitized_url_from_config(config_dir: str, profile: str, key: str) -> st
|
|
70
103
|
|
71
104
|
url_in_config = url_in_config.rstrip("/")
|
72
105
|
return url_in_config
|
106
|
+
|
107
|
+
|
108
|
+
def get_remote_metaflow_config_for_perimeter(
|
109
|
+
origin_token: str, perimeter: str, api_server: str
|
110
|
+
):
|
111
|
+
try:
|
112
|
+
response = requests.get(
|
113
|
+
f"{api_server}/v1/perimeters/{perimeter}/metaflowconfigs/default",
|
114
|
+
headers={"x-api-key": origin_token},
|
115
|
+
)
|
116
|
+
response.raise_for_status()
|
117
|
+
config = response.json()["config"]
|
118
|
+
config["METAFLOW_SERVICE_AUTH_KEY"] = origin_token
|
119
|
+
return config
|
120
|
+
except Exception as e:
|
121
|
+
click.secho(
|
122
|
+
f"Failed to get metaflow config from {api_server}. Error: {str(e)}",
|
123
|
+
fg="red",
|
124
|
+
)
|
125
|
+
sys.exit(1)
|
126
|
+
|
127
|
+
|
128
|
+
def get_ob_config_file_path(config_dir: str, profile: str) -> str:
|
129
|
+
# If OBP_CONFIG_DIR is set, use that, otherwise use METAFLOW_HOME
|
130
|
+
# If neither are set, use ~/.metaflowconfig
|
131
|
+
obp_config_dir = path.expanduser(os.environ.get("OBP_CONFIG_DIR", config_dir))
|
132
|
+
|
133
|
+
ob_config_filename = f"ob_config_{profile}.json" if profile else "ob_config.json"
|
134
|
+
return os.path.expanduser(os.path.join(obp_config_dir, ob_config_filename))
|
135
|
+
|
136
|
+
|
137
|
+
def get_perimeter_config_url_if_set_in_ob_config(
|
138
|
+
config_dir: str, profile: str
|
139
|
+
) -> Union[str, None]:
|
140
|
+
file_path = get_ob_config_file_path(config_dir, profile)
|
141
|
+
|
142
|
+
if os.path.exists(file_path):
|
143
|
+
with open(file_path, "r") as f:
|
144
|
+
ob_config = json.loads(f.read())
|
145
|
+
|
146
|
+
if CURRENT_PERIMETER_URL in ob_config:
|
147
|
+
return ob_config[CURRENT_PERIMETER_URL]
|
148
|
+
elif CURRENT_PERIMETER_URL_LEGACY_KEY in ob_config:
|
149
|
+
return ob_config[CURRENT_PERIMETER_URL_LEGACY_KEY]
|
150
|
+
else:
|
151
|
+
raise ValueError(
|
152
|
+
"{} does not contain the key {}".format(
|
153
|
+
file_path, CURRENT_PERIMETER_KEY
|
154
|
+
)
|
155
|
+
)
|
156
|
+
elif "OBP_CONFIG_DIR" in os.environ:
|
157
|
+
raise FileNotFoundError(
|
158
|
+
"Environment variable OBP_CONFIG_DIR is set to {} but this directory does not contain an ob_config.json file.".format(
|
159
|
+
os.environ["OBP_CONFIG_DIR"]
|
160
|
+
)
|
161
|
+
)
|
162
|
+
return None
|
outerbounds/utils/schema.py
CHANGED
@@ -5,6 +5,7 @@ class OuterboundsCommandStatus(Enum):
|
|
5
5
|
OK = "OK"
|
6
6
|
FAIL = "FAIL"
|
7
7
|
WARN = "WARN"
|
8
|
+
NOT_SUPPORTED = "NOT_SUPPORTED"
|
8
9
|
|
9
10
|
|
10
11
|
class CommandStatus:
|
@@ -39,6 +40,11 @@ class OuterboundsCommandResponse:
|
|
39
40
|
self.metadata = {}
|
40
41
|
self._data = {}
|
41
42
|
|
43
|
+
def update(self, status, code, message):
|
44
|
+
self.status = status
|
45
|
+
self._code = code
|
46
|
+
self._message = message
|
47
|
+
|
42
48
|
def add_or_update_metadata(self, key, value):
|
43
49
|
self.metadata[key] = value
|
44
50
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import tempfile
|
2
|
+
import os
|
3
|
+
|
4
|
+
"""
|
5
|
+
Writes the given data to file_loc. Ensures that the directory exists
|
6
|
+
and uses a temporary file to ensure that the file is written atomically.
|
7
|
+
"""
|
8
|
+
|
9
|
+
|
10
|
+
def safe_write_to_disk(file_loc, data):
|
11
|
+
# Ensure the directory exists
|
12
|
+
os.makedirs(os.path.dirname(file_loc), exist_ok=True)
|
13
|
+
|
14
|
+
with tempfile.NamedTemporaryFile(
|
15
|
+
"w", dir=os.path.dirname(file_loc), delete=False
|
16
|
+
) as f:
|
17
|
+
f.write(data)
|
18
|
+
tmp_file = f.name
|
19
|
+
os.rename(tmp_file, file_loc)
|