outerbounds 0.3.55rc4__py3-none-any.whl → 0.3.89__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. outerbounds/_vendor/PyYAML.LICENSE +20 -0
  2. outerbounds/_vendor/__init__.py +0 -0
  3. outerbounds/_vendor/_yaml/__init__.py +34 -0
  4. outerbounds/_vendor/click/__init__.py +73 -0
  5. outerbounds/_vendor/click/_compat.py +626 -0
  6. outerbounds/_vendor/click/_termui_impl.py +717 -0
  7. outerbounds/_vendor/click/_textwrap.py +49 -0
  8. outerbounds/_vendor/click/_winconsole.py +279 -0
  9. outerbounds/_vendor/click/core.py +2998 -0
  10. outerbounds/_vendor/click/decorators.py +497 -0
  11. outerbounds/_vendor/click/exceptions.py +287 -0
  12. outerbounds/_vendor/click/formatting.py +301 -0
  13. outerbounds/_vendor/click/globals.py +68 -0
  14. outerbounds/_vendor/click/parser.py +529 -0
  15. outerbounds/_vendor/click/py.typed +0 -0
  16. outerbounds/_vendor/click/shell_completion.py +580 -0
  17. outerbounds/_vendor/click/termui.py +787 -0
  18. outerbounds/_vendor/click/testing.py +479 -0
  19. outerbounds/_vendor/click/types.py +1073 -0
  20. outerbounds/_vendor/click/utils.py +580 -0
  21. outerbounds/_vendor/click.LICENSE +28 -0
  22. outerbounds/_vendor/vendor_any.txt +2 -0
  23. outerbounds/_vendor/yaml/__init__.py +471 -0
  24. outerbounds/_vendor/yaml/_yaml.cpython-311-darwin.so +0 -0
  25. outerbounds/_vendor/yaml/composer.py +146 -0
  26. outerbounds/_vendor/yaml/constructor.py +862 -0
  27. outerbounds/_vendor/yaml/cyaml.py +177 -0
  28. outerbounds/_vendor/yaml/dumper.py +138 -0
  29. outerbounds/_vendor/yaml/emitter.py +1239 -0
  30. outerbounds/_vendor/yaml/error.py +94 -0
  31. outerbounds/_vendor/yaml/events.py +104 -0
  32. outerbounds/_vendor/yaml/loader.py +62 -0
  33. outerbounds/_vendor/yaml/nodes.py +51 -0
  34. outerbounds/_vendor/yaml/parser.py +629 -0
  35. outerbounds/_vendor/yaml/reader.py +208 -0
  36. outerbounds/_vendor/yaml/representer.py +378 -0
  37. outerbounds/_vendor/yaml/resolver.py +245 -0
  38. outerbounds/_vendor/yaml/scanner.py +1555 -0
  39. outerbounds/_vendor/yaml/serializer.py +127 -0
  40. outerbounds/_vendor/yaml/tokens.py +129 -0
  41. outerbounds/command_groups/cli.py +1 -1
  42. outerbounds/command_groups/local_setup_cli.py +249 -33
  43. outerbounds/command_groups/perimeters_cli.py +168 -33
  44. outerbounds/command_groups/workstations_cli.py +29 -16
  45. outerbounds/utils/kubeconfig.py +2 -2
  46. outerbounds/utils/metaflowconfig.py +111 -21
  47. outerbounds/utils/schema.py +6 -0
  48. outerbounds/utils/utils.py +19 -0
  49. outerbounds/vendor.py +159 -0
  50. {outerbounds-0.3.55rc4.dist-info → outerbounds-0.3.89.dist-info}/METADATA +14 -6
  51. outerbounds-0.3.89.dist-info/RECORD +57 -0
  52. outerbounds-0.3.55rc4.dist-info/RECORD +0 -15
  53. {outerbounds-0.3.55rc4.dist-info → outerbounds-0.3.89.dist-info}/WHEEL +0 -0
  54. {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 pathlib import Path
13
- from typing import Any, Callable, Dict, List
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 kubeconfig, metaflowconfig
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
- @cli.command(help="Switch current perimeter", hidden=True)
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 switch_perimeter(config_dir=None, profile=None, output="", id=None, force=False):
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
- "OB_CURRENT_PERIMETER_URL": perimeters[id]["remote_config_url"],
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
- @cli.command(help="Show current perimeter", hidden=True)
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 show_current_perimeter(config_dir=None, profile=None, output=""):
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
- @cli.command(help="List all available perimeters", hidden=True)
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 list_perimeters(config_dir=None, profile=None, output=""):
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
- metaflow_config = metaflowconfig.init_config(config_dir, profile)
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",
@@ -1,6 +1,6 @@
1
1
  import os
2
- import yaml
3
- from yaml.scanner import ScannerError
2
+ from outerbounds._vendor import yaml
3
+ from outerbounds._vendor.yaml.scanner import ScannerError
4
4
  import subprocess
5
5
  from os import path
6
6
  import platform
@@ -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 requests
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
- def init_config(config_dir="", profile="") -> dict:
9
- profile = profile or os.environ.get("METAFLOW_PROFILE")
10
- config_dir = config_dir or os.path.expanduser(
11
- os.environ.get("METAFLOW_HOME", "~/.metaflowconfig")
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
@@ -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)