outerbounds 0.3.52rc6__py3-none-any.whl → 0.3.53rc1__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.
@@ -1,10 +1,12 @@
1
1
  import click
2
2
  from . import local_setup_cli
3
3
  from . import workstations_cli
4
+ from . import perimeters_cli
4
5
 
5
6
 
6
7
  @click.command(
7
- cls=click.CommandCollection, sources=[local_setup_cli.cli, workstations_cli.cli]
8
+ cls=click.CommandCollection,
9
+ sources=[local_setup_cli.cli, workstations_cli.cli, perimeters_cli.cli],
8
10
  )
9
11
  def cli(**kwargs):
10
12
  pass
@@ -189,7 +189,7 @@ def check_ob_extension(narrator: Narrator) -> CommandStatus:
189
189
  if d.is_dir() and not d.name.startswith("__")
190
190
  ]
191
191
  subdirs.sort()
192
- if subdirs != ["config", "plugins", "toplevel"]:
192
+ if subdirs != ["config", "plugins", "profilers", "toplevel"]:
193
193
  check_status.update(
194
194
  status=OuterboundsCommandStatus.FAIL,
195
195
  reason=BAD_EXTENSION_MESSAGE,
@@ -586,6 +586,12 @@ class ConfigurationWriter:
586
586
  self.out_dir = out_dir
587
587
  self.profile = profile
588
588
 
589
+ ob_config_dir = path.expanduser(os.getenv("OBP_CONFIG_DIR", out_dir))
590
+ self.ob_config_path = path.join(
591
+ ob_config_dir,
592
+ "ob_config_{}.json".format(profile) if profile else "ob_config.json",
593
+ )
594
+
589
595
  def decode(self):
590
596
  self.decoded_config = deserialize(self.encoded_config)
591
597
 
@@ -648,6 +654,15 @@ class ConfigurationWriter:
648
654
  with open(config_path, "w") as fd:
649
655
  json.dump(self.existing, fd, indent=4)
650
656
 
657
+ # Every time a config is initialized, we should also reset the corresponding ob_config[_profile].json
658
+ remote_config = metaflowconfig.init_config(self.out_dir, self.profile)
659
+ with open(self.ob_config_path, "w") as fd:
660
+ ob_config_dict = {
661
+ "OB_CURRENT_PERIMETER": remote_config["OBP_PERIMETER"],
662
+ "OB_CURRENT_PERIMETER_URL": remote_config["OBP_PERIMETER_URL"],
663
+ }
664
+ json.dump(ob_config_dict, fd, indent=4)
665
+
651
666
  def confirm_overwrite_config(self, config_path):
652
667
  if os.path.exists(config_path):
653
668
  if not click.confirm(
@@ -778,337 +793,3 @@ def configure(
778
793
  except Exception as e:
779
794
  click.secho("Writing the configuration file '{}' failed.".format(writer.path()))
780
795
  click.secho("Error: {}".format(str(e)))
781
-
782
-
783
- @cli.command(help="Switch current perimeter")
784
- @click.option(
785
- "-o",
786
- "--output",
787
- default="",
788
- help="Show output in the specified format.",
789
- type=click.Choice(["json", ""]),
790
- )
791
- @click.option("--id", default="", type=str, help="Perimeter name to switch to")
792
- @click.option(
793
- "-f",
794
- "--force",
795
- is_flag=True,
796
- help="Force change the existing perimeter",
797
- default=False,
798
- )
799
- def switch_perimeter(output="", id=None, force=False):
800
- switch_perimeter_response = OuterboundsCommandResponse()
801
-
802
- switch_perimeter_step = CommandStatus(
803
- "SwitchPerimeter",
804
- OuterboundsCommandStatus.OK,
805
- "Perimeter was successfully switched!",
806
- )
807
-
808
- metaflow_home_dir = path.expanduser(
809
- os.environ.get("METAFLOW_HOME", "~/.metaflowconfig")
810
- )
811
-
812
- if not os.path.exists("{}/config_{}.json".format(metaflow_home_dir, id)):
813
- click.secho(
814
- "Perimeter {} does not exist or you don't have access to it".format(id),
815
- fg="red",
816
- err=True,
817
- )
818
- switch_perimeter_step.update(
819
- status=OuterboundsCommandStatus.FAIL,
820
- reason="Perimeter {} does not exist or you don't have access to it".format(
821
- id
822
- ),
823
- mitigation="Please contact your admin or the Outerbounds team for assistance.",
824
- )
825
- switch_perimeter_response.add_step(switch_perimeter_step)
826
- if output == "json":
827
- click.echo(json.dumps(switch_perimeter_response.as_dict(), indent=4))
828
- return
829
-
830
- # If OBP_CONFIG_DIR is set, use that, otherwise use METAFLOW_HOME
831
- # If neither are set, use ~/.metaflowconfig
832
- obp_config_dir = path.expanduser(
833
- os.environ.get(
834
- "OBP_CONFIG_DIR", os.environ.get("METAFLOW_HOME", "~/.metaflowconfig")
835
- )
836
- )
837
-
838
- path_to_config = os.path.expanduser(os.path.join(obp_config_dir, "ob_config.json"))
839
-
840
- if not os.path.exists(path_to_config):
841
- click.secho(
842
- "Config file not found at {}".format(path_to_config), fg="red", err=True
843
- )
844
- switch_perimeter_step.update(
845
- status=OuterboundsCommandStatus.FAIL,
846
- reason="Config file not found",
847
- mitigation="Please make sure the config file exists at {}".format(
848
- path_to_config
849
- ),
850
- )
851
- switch_perimeter_response.add_step(switch_perimeter_step)
852
- if output == "json":
853
- click.echo(json.dumps(switch_perimeter_response.as_dict(), indent=4))
854
- return
855
-
856
- import fcntl
857
-
858
- fd = os.open(path_to_config, os.O_WRONLY)
859
-
860
- try:
861
- if not force:
862
- # Try to acquire an exclusive lock
863
- fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
864
- else:
865
- click.secho(
866
- "Force flag is set. Perimeter will be switched, but can have unintended consequences on other running processes.",
867
- fg="yellow",
868
- err=True,
869
- )
870
-
871
- ob_config_dict = {"OB_CURRENT_PERIMETER": str(id)}
872
-
873
- # Now that we have the lock, we can safely write to the file
874
- with open(path_to_config, "w") as file:
875
- json.dump(ob_config_dict, file, indent=4)
876
-
877
- click.secho("Perimeter switched to {}".format(id), fg="green", err=True)
878
- except BlockingIOError:
879
- # This exception is raised if the file is already locked (non-blocking mode)
880
- # Note that its the metaflow package (the extension actually) that acquires a shared read lock
881
- # on the file whenever a process imports metaflow.
882
- # In the future we might want to get smarter about it and show which process is holding the lock.
883
- click.secho(
884
- "Can't switch perimeter while Metaflow is in use. Please make sure there are no running python processes or notebooks using metaflow.",
885
- fg="red",
886
- err=True,
887
- )
888
- switch_perimeter_step.update(
889
- status=OuterboundsCommandStatus.FAIL,
890
- reason="Can't switch perimeter while Metaflow is in use.",
891
- mitigation="Please make sure there are no running python processes or notebooks using metaflow.",
892
- )
893
-
894
- switch_perimeter_response.add_step(switch_perimeter_step)
895
- if output == "json":
896
- click.echo(json.dumps(switch_perimeter_response.as_dict(), indent=4))
897
- return
898
-
899
-
900
- @cli.command(help="Show current perimeter")
901
- @click.option(
902
- "-o",
903
- "--output",
904
- default="",
905
- help="Show output in the specified format.",
906
- type=click.Choice(["json", ""]),
907
- )
908
- def show_current_perimeter(output=""):
909
- show_current_perimeter_response = OuterboundsCommandResponse()
910
-
911
- show_current_perimeter_step = CommandStatus(
912
- "ShowCurrentPerimeter",
913
- OuterboundsCommandStatus.OK,
914
- "Current Perimeter Fetch Successful.",
915
- )
916
-
917
- metaflow_home_dir = path.expanduser(
918
- os.environ.get("METAFLOW_HOME", "~/.metaflowconfig")
919
- )
920
-
921
- # If OBP_CONFIG_DIR is set, use that, otherwise use METAFLOW_HOME
922
- # If neither are set, use ~/.metaflowconfig
923
- obp_config_dir = path.expanduser(
924
- os.environ.get(
925
- "OBP_CONFIG_DIR", os.environ.get("METAFLOW_HOME", "~/.metaflowconfig")
926
- )
927
- )
928
-
929
- path_to_config = os.path.expanduser(os.path.join(obp_config_dir, "ob_config.json"))
930
-
931
- if not os.path.exists(path_to_config):
932
- click.secho(
933
- "Config file not found at {}".format(path_to_config), fg="red", err=True
934
- )
935
- show_current_perimeter_step.update(
936
- status=OuterboundsCommandStatus.FAIL,
937
- reason="Config file not found",
938
- mitigation="Please make sure the config file exists at {}".format(
939
- path_to_config
940
- ),
941
- )
942
- show_current_perimeter_response.add_step(show_current_perimeter_step)
943
- if output == "json":
944
- click.echo(json.dumps(show_current_perimeter_response.as_dict(), indent=4))
945
- return
946
-
947
- with open(path_to_config, "r") as file:
948
- ob_config_dict = json.load(file)
949
-
950
- if "OB_CURRENT_PERIMETER" not in ob_config_dict:
951
- click.secho(
952
- "OB_CURRENT_PERIMETER not found in Config file: {}".format(path_to_config),
953
- fg="red",
954
- err=True,
955
- )
956
- show_current_perimeter_step.update(
957
- status=OuterboundsCommandStatus.FAIL,
958
- reason="OB_CURRENT_PERIMETER not found in Config file: {}",
959
- mitigation="",
960
- )
961
- show_current_perimeter_response.add_step(show_current_perimeter_step)
962
- if output == "json":
963
- click.echo(json.dumps(show_current_perimeter_response.as_dict(), indent=4))
964
- return
965
-
966
- if os.path.exists(
967
- os.path.join(
968
- metaflow_home_dir,
969
- "config_{}.json".format(ob_config_dict["OB_CURRENT_PERIMETER"]),
970
- )
971
- ):
972
- click.secho(
973
- "Current Perimeter: {}".format(ob_config_dict["OB_CURRENT_PERIMETER"]),
974
- fg="green",
975
- err=True,
976
- )
977
- show_current_perimeter_response.add_or_update_data(
978
- "current_perimeter", ob_config_dict["OB_CURRENT_PERIMETER"]
979
- )
980
- else:
981
- click.secho(
982
- "Perimeter config file not found at {}".format(
983
- os.path.join(
984
- metaflow_home_dir,
985
- "config_{}.json".format(ob_config_dict["OB_CURRENT_PERIMETER"]),
986
- )
987
- ),
988
- fg="red",
989
- err=True,
990
- )
991
- show_current_perimeter_step.update(
992
- status=OuterboundsCommandStatus.FAIL,
993
- reason="Perimeter config file not found",
994
- mitigation="Please make sure the perimeter config file exists at {}/config_{}.json".format(
995
- metaflow_home_dir, ob_config_dict["OB_CURRENT_PERIMETER"]
996
- ),
997
- )
998
- show_current_perimeter_response.add_step(show_current_perimeter_step)
999
-
1000
- if output == "json":
1001
- click.echo(json.dumps(show_current_perimeter_response.as_dict(), indent=4))
1002
-
1003
-
1004
- @cli.command(help="List all available perimeters")
1005
- @click.option(
1006
- "-o",
1007
- "--output",
1008
- default="",
1009
- help="Show output in the specified format.",
1010
- type=click.Choice(["json", ""]),
1011
- )
1012
- def list_perimeters(output=""):
1013
- list_perimeters_response = OuterboundsCommandResponse()
1014
-
1015
- list_perimeters_step = CommandStatus(
1016
- "ListPerimeters", OuterboundsCommandStatus.OK, "Perimeter Fetch Successful."
1017
- )
1018
-
1019
- metaflow_home_dir = path.expanduser(
1020
- os.environ.get("METAFLOW_HOME", "~/.metaflowconfig")
1021
- )
1022
-
1023
- # If OBP_CONFIG_DIR is set, use that, otherwise use METAFLOW_HOME
1024
- # If neither are set, use ~/.metaflowconfig
1025
- obp_config_dir = path.expanduser(
1026
- os.environ.get(
1027
- "OBP_CONFIG_DIR", os.environ.get("METAFLOW_HOME", "~/.metaflowconfig")
1028
- )
1029
- )
1030
-
1031
- path_to_config = os.path.expanduser(os.path.join(obp_config_dir, "ob_config.json"))
1032
-
1033
- if not os.path.exists(path_to_config):
1034
- click.secho(
1035
- "Config file not found at {}".format(path_to_config), fg="red", err=True
1036
- )
1037
- list_perimeters_step.update(
1038
- status=OuterboundsCommandStatus.FAIL,
1039
- reason="Config file not found",
1040
- mitigation="Please make sure the config file exists at {}".format(
1041
- path_to_config
1042
- ),
1043
- )
1044
- list_perimeters_response.add_step(list_perimeters_step)
1045
- if output == "json":
1046
- click.echo(json.dumps(list_perimeters_response.as_dict(), indent=4))
1047
- return
1048
-
1049
- with open(path_to_config, "r") as file:
1050
- ob_config_dict = json.load(file)
1051
-
1052
- if "OB_CURRENT_PERIMETER" not in ob_config_dict:
1053
- click.secho(
1054
- "OB_CURRENT_PERIMETER not found in Config file: {}".format(path_to_config),
1055
- fg="red",
1056
- err=True,
1057
- )
1058
- list_perimeters_step.update(
1059
- status=OuterboundsCommandStatus.FAIL,
1060
- reason="OB_CURRENT_PERIMETER not found in Config file: {}",
1061
- mitigation="",
1062
- )
1063
- list_perimeters_response.add_step(list_perimeters_step)
1064
- if output == "json":
1065
- click.echo(json.dumps(list_perimeters_response.as_dict(), indent=4))
1066
- return
1067
-
1068
- if os.path.exists(
1069
- os.path.join(
1070
- metaflow_home_dir,
1071
- "config_{}.json".format(ob_config_dict["OB_CURRENT_PERIMETER"]),
1072
- )
1073
- ):
1074
- active_perimeter = ob_config_dict["OB_CURRENT_PERIMETER"]
1075
- else:
1076
- click.secho(
1077
- "Perimeter config file not found at {}".format(
1078
- os.path.join(
1079
- metaflow_home_dir,
1080
- "config_{}.json".format(ob_config_dict["OB_CURRENT_PERIMETER"]),
1081
- )
1082
- ),
1083
- fg="red",
1084
- err=True,
1085
- )
1086
- list_perimeters_step.update(
1087
- status=OuterboundsCommandStatus.FAIL,
1088
- reason="Perimeter config file not found",
1089
- mitigation="Please make sure the perimeter config file exists at {}/config_{}.json".format(
1090
- metaflow_home_dir, ob_config_dict["OB_CURRENT_PERIMETER"]
1091
- ),
1092
- )
1093
- list_perimeters_response.add_step(list_perimeters_step)
1094
- if output == "json":
1095
- click.echo(json.dumps(list_perimeters_response.as_dict(), indent=4))
1096
- return
1097
-
1098
- click.secho("Perimeter: {} (active)".format(active_perimeter), fg="green", err=True)
1099
-
1100
- perimeter_list = []
1101
- for file in os.listdir(metaflow_home_dir):
1102
- if file.startswith("config_") and file.endswith(".json"):
1103
- perimeter_id = file.split("_")[1].split(".")[0]
1104
- perimeter_list.append(
1105
- {"id": perimeter_id, "active": perimeter_id == active_perimeter}
1106
- )
1107
-
1108
- if perimeter_id != active_perimeter:
1109
- click.secho("Perimeter: {}".format(perimeter_id), err=True)
1110
-
1111
- list_perimeters_response.add_or_update_data("perimeters", perimeter_list)
1112
-
1113
- if output == "json":
1114
- click.echo(json.dumps(list_perimeters_response.as_dict(), indent=4))
@@ -0,0 +1,374 @@
1
+ import base64
2
+ import hashlib
3
+ import json
4
+ import os
5
+ import re
6
+ import subprocess
7
+ import sys
8
+ import zlib
9
+ from base64 import b64decode, b64encode
10
+ from importlib.machinery import PathFinder
11
+ from os import path
12
+ from pathlib import Path
13
+ from typing import Any, Callable, Dict, List
14
+
15
+ import boto3
16
+ import click
17
+ import requests
18
+ from requests.exceptions import HTTPError
19
+
20
+ from ..utils import kubeconfig, metaflowconfig
21
+ from ..utils.schema import (
22
+ CommandStatus,
23
+ OuterboundsCommandResponse,
24
+ OuterboundsCommandStatus,
25
+ )
26
+
27
+
28
+ @click.group()
29
+ def cli(**kwargs):
30
+ pass
31
+
32
+
33
+ @cli.command(help="Switch current perimeter", hidden=True)
34
+ @click.option(
35
+ "-d",
36
+ "--config-dir",
37
+ default=path.expanduser(os.environ.get("METAFLOW_HOME", "~/.metaflowconfig")),
38
+ help="Path to Metaflow configuration directory",
39
+ show_default=True,
40
+ )
41
+ @click.option(
42
+ "-p",
43
+ "--profile",
44
+ default=os.environ.get("METAFLOW_PROFILE", ""),
45
+ help="The named metaflow profile in which your workstation exists",
46
+ )
47
+ @click.option(
48
+ "-o",
49
+ "--output",
50
+ default="",
51
+ help="Show output in the specified format.",
52
+ type=click.Choice(["json", ""]),
53
+ )
54
+ @click.option("--id", default="", type=str, help="Perimeter name to switch to")
55
+ @click.option(
56
+ "-f",
57
+ "--force",
58
+ is_flag=True,
59
+ help="Force change the existing perimeter",
60
+ default=False,
61
+ )
62
+ def switch_perimeter(config_dir=None, profile=None, output="", id=None, force=False):
63
+ switch_perimeter_response = OuterboundsCommandResponse()
64
+
65
+ switch_perimeter_step = CommandStatus(
66
+ "SwitchPerimeter",
67
+ OuterboundsCommandStatus.OK,
68
+ "Perimeter was successfully switched!",
69
+ )
70
+
71
+ perimeters = get_perimeters_from_api_or_fail_command(
72
+ config_dir, profile, output, switch_perimeter_response, switch_perimeter_step
73
+ )
74
+ confirm_user_has_access_to_perimeter_or_fail(
75
+ id, perimeters, output, switch_perimeter_response, switch_perimeter_step
76
+ )
77
+
78
+ path_to_config = get_ob_config_file_path(config_dir, profile)
79
+
80
+ import fcntl
81
+
82
+ try:
83
+ if os.path.exists(path_to_config):
84
+ if not force:
85
+ fd = os.open(path_to_config, os.O_WRONLY)
86
+ # Try to acquire an exclusive lock
87
+ fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
88
+ else:
89
+ click.secho(
90
+ "Force flag is set. Perimeter will be switched, but can have unintended consequences on other running processes.",
91
+ fg="yellow",
92
+ err=True,
93
+ )
94
+
95
+ ob_config_dict = {
96
+ "OB_CURRENT_PERIMETER": str(id),
97
+ "OB_CURRENT_PERIMETER_URL": perimeters[id]["remote_config_url"],
98
+ }
99
+
100
+ # Now that we have the lock, we can safely write to the file
101
+ with open(path_to_config, "w") as file:
102
+ json.dump(ob_config_dict, file, indent=4)
103
+
104
+ click.secho("Perimeter switched to {}".format(id), fg="green", err=True)
105
+ except BlockingIOError:
106
+ # This exception is raised if the file is already locked (non-blocking mode)
107
+ # Note that its the metaflow package (the extension actually) that acquires a shared read lock
108
+ # on the file whenever a process imports metaflow.
109
+ # In the future we might want to get smarter about it and show which process is holding the lock.
110
+ click.secho(
111
+ "Can't switch perimeter while Metaflow is in use. Please make sure there are no running python processes or notebooks using metaflow.",
112
+ fg="red",
113
+ err=True,
114
+ )
115
+ switch_perimeter_step.update(
116
+ status=OuterboundsCommandStatus.FAIL,
117
+ reason="Can't switch perimeter while Metaflow is in use.",
118
+ mitigation="Please make sure there are no running python processes or notebooks using metaflow.",
119
+ )
120
+
121
+ switch_perimeter_response.add_step(switch_perimeter_step)
122
+ if output == "json":
123
+ click.echo(json.dumps(switch_perimeter_response.as_dict(), indent=4))
124
+ return
125
+
126
+
127
+ @cli.command(help="Show current perimeter", hidden=True)
128
+ @click.option(
129
+ "-d",
130
+ "--config-dir",
131
+ default=path.expanduser(os.environ.get("METAFLOW_HOME", "~/.metaflowconfig")),
132
+ help="Path to Metaflow configuration directory",
133
+ show_default=True,
134
+ )
135
+ @click.option(
136
+ "-p",
137
+ "--profile",
138
+ default=os.environ.get("METAFLOW_PROFILE", ""),
139
+ help="Configure a named profile. Activate the profile by setting "
140
+ "`METAFLOW_PROFILE` environment variable.",
141
+ )
142
+ @click.option(
143
+ "-o",
144
+ "--output",
145
+ default="",
146
+ help="Show output in the specified format.",
147
+ type=click.Choice(["json", ""]),
148
+ )
149
+ def show_current_perimeter(config_dir=None, profile=None, output=""):
150
+ show_current_perimeter_response = OuterboundsCommandResponse()
151
+
152
+ show_current_perimeter_step = CommandStatus(
153
+ "ShowCurrentPerimeter",
154
+ OuterboundsCommandStatus.OK,
155
+ "Current Perimeter Fetch Successful.",
156
+ )
157
+
158
+ ob_config_dict = get_ob_config_or_fail_command(
159
+ config_dir,
160
+ profile,
161
+ output,
162
+ show_current_perimeter_response,
163
+ show_current_perimeter_step,
164
+ )
165
+
166
+ perimeters = get_perimeters_from_api_or_fail_command(
167
+ config_dir,
168
+ profile,
169
+ output,
170
+ show_current_perimeter_response,
171
+ show_current_perimeter_step,
172
+ )
173
+ confirm_user_has_access_to_perimeter_or_fail(
174
+ ob_config_dict["OB_CURRENT_PERIMETER"],
175
+ perimeters,
176
+ output,
177
+ show_current_perimeter_response,
178
+ show_current_perimeter_step,
179
+ )
180
+
181
+ click.secho(
182
+ "Current Perimeter: {}".format(ob_config_dict["OB_CURRENT_PERIMETER"]),
183
+ fg="green",
184
+ err=True,
185
+ )
186
+
187
+ show_current_perimeter_response.add_or_update_data(
188
+ "current_perimeter", ob_config_dict["OB_CURRENT_PERIMETER"]
189
+ )
190
+
191
+ if output == "json":
192
+ click.echo(json.dumps(show_current_perimeter_response.as_dict(), indent=4))
193
+
194
+
195
+ @cli.command(help="List all available perimeters", hidden=True)
196
+ @click.option(
197
+ "-d",
198
+ "--config-dir",
199
+ default=path.expanduser(os.environ.get("METAFLOW_HOME", "~/.metaflowconfig")),
200
+ help="Path to Metaflow configuration directory",
201
+ show_default=True,
202
+ )
203
+ @click.option(
204
+ "-p",
205
+ "--profile",
206
+ default=os.environ.get("METAFLOW_PROFILE", ""),
207
+ help="The named metaflow profile in which your workstation exists",
208
+ )
209
+ @click.option(
210
+ "-o",
211
+ "--output",
212
+ default="",
213
+ help="Show output in the specified format.",
214
+ type=click.Choice(["json", ""]),
215
+ )
216
+ def list_perimeters(config_dir=None, profile=None, output=""):
217
+ list_perimeters_response = OuterboundsCommandResponse()
218
+
219
+ list_perimeters_step = CommandStatus(
220
+ "ListPerimeters", OuterboundsCommandStatus.OK, "Perimeter Fetch Successful."
221
+ )
222
+
223
+ ob_config_dict = get_ob_config_or_fail_command(
224
+ config_dir, profile, output, list_perimeters_response, list_perimeters_step
225
+ )
226
+ active_perimeter = ob_config_dict["OB_CURRENT_PERIMETER"]
227
+
228
+ perimeters = get_perimeters_from_api_or_fail_command(
229
+ config_dir, profile, output, list_perimeters_response, list_perimeters_step
230
+ )
231
+
232
+ perimeter_list = []
233
+ for perimeter in perimeters.values():
234
+ status = "OK"
235
+ perimeter_list.append(
236
+ {
237
+ "id": perimeter["perimeter"],
238
+ "active": perimeter["perimeter"] == active_perimeter,
239
+ "status": status,
240
+ }
241
+ )
242
+ if perimeter["perimeter"] != active_perimeter:
243
+ click.secho("Perimeter: {}".format(perimeter["perimeter"]), err=True)
244
+ else:
245
+ click.secho(
246
+ "Perimeter: {} (active)".format(perimeter["perimeter"]),
247
+ fg="green",
248
+ err=True,
249
+ )
250
+
251
+ list_perimeters_response.add_or_update_data("perimeters", perimeter_list)
252
+
253
+ if output == "json":
254
+ click.echo(json.dumps(list_perimeters_response.as_dict(), indent=4))
255
+
256
+
257
+ def get_list_perimeters_api_response(config_dir, profile):
258
+ metaflow_token = metaflowconfig.get_metaflow_token_from_config(config_dir, profile)
259
+ api_url = metaflowconfig.get_sanitized_url_from_config(
260
+ config_dir, profile, "OBP_API_SERVER"
261
+ )
262
+ perimeters_response = requests.get(
263
+ f"{api_url}/v1/me/perimeters?privilege=Execute",
264
+ headers={"x-api-key": metaflow_token},
265
+ )
266
+ perimeters_response.raise_for_status()
267
+ return perimeters_response.json()["perimeters"]
268
+
269
+
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
+ def get_perimeters_from_api_or_fail_command(
280
+ config_dir: str,
281
+ profile: str,
282
+ output: str,
283
+ command_response: OuterboundsCommandResponse,
284
+ command_step: CommandStatus,
285
+ ) -> Dict[str, Dict[str, str]]:
286
+ try:
287
+ perimeters = get_list_perimeters_api_response(config_dir, profile)
288
+ except:
289
+ click.secho(
290
+ "Failed to fetch perimeters from API.",
291
+ fg="red",
292
+ err=True,
293
+ )
294
+ command_step.update(
295
+ status=OuterboundsCommandStatus.FAIL,
296
+ reason="Failed to fetch perimeters from API",
297
+ mitigation="",
298
+ )
299
+ command_response.add_step(command_step)
300
+ if output == "json":
301
+ click.echo(json.dumps(command_response.as_dict(), indent=4))
302
+ sys.exit(1)
303
+ return {p["perimeter"]: p for p in perimeters}
304
+
305
+
306
+ def get_ob_config_or_fail_command(
307
+ config_dir: str,
308
+ profile: str,
309
+ output: str,
310
+ command_response: OuterboundsCommandResponse,
311
+ command_step: CommandStatus,
312
+ ) -> Dict[str, str]:
313
+ path_to_config = get_ob_config_file_path(config_dir, profile)
314
+
315
+ if not os.path.exists(path_to_config):
316
+ click.secho(
317
+ "Config file not found at {}".format(path_to_config), fg="red", err=True
318
+ )
319
+ command_step.update(
320
+ status=OuterboundsCommandStatus.FAIL,
321
+ reason="Config file not found",
322
+ mitigation="Please make sure the config file exists at {}".format(
323
+ path_to_config
324
+ ),
325
+ )
326
+ command_response.add_step(command_step)
327
+ if output == "json":
328
+ click.echo(json.dumps(command_response.as_dict(), indent=4))
329
+ sys.exit(1)
330
+
331
+ with open(path_to_config, "r") as file:
332
+ ob_config_dict = json.load(file)
333
+
334
+ if "OB_CURRENT_PERIMETER" not in ob_config_dict:
335
+ click.secho(
336
+ "OB_CURRENT_PERIMETER not found in Config file: {}".format(path_to_config),
337
+ fg="red",
338
+ err=True,
339
+ )
340
+ command_step.update(
341
+ status=OuterboundsCommandStatus.FAIL,
342
+ reason="OB_CURRENT_PERIMETER not found in Config file: {}",
343
+ mitigation="",
344
+ )
345
+ command_response.add_step(command_step)
346
+ if output == "json":
347
+ click.echo(json.dumps(command_response.as_dict(), indent=4))
348
+ sys.exit(1)
349
+
350
+ return ob_config_dict
351
+
352
+
353
+ def confirm_user_has_access_to_perimeter_or_fail(
354
+ perimeter_id: str,
355
+ perimeters: Dict[str, Any],
356
+ output: str,
357
+ command_response: OuterboundsCommandResponse,
358
+ command_step: CommandStatus,
359
+ ):
360
+ if perimeter_id not in perimeters:
361
+ click.secho(
362
+ f"You do not have access to perimeter {perimeter_id} or it does not exist.",
363
+ fg="red",
364
+ err=True,
365
+ )
366
+ command_step.update(
367
+ status=OuterboundsCommandStatus.FAIL,
368
+ reason=f"You do not have access to perimeter {perimeter_id} or it does not exist.",
369
+ mitigation="",
370
+ )
371
+ command_response.add_step(command_step)
372
+ if output == "json":
373
+ click.echo(json.dumps(command_response.as_dict(), indent=4))
374
+ sys.exit(1)
@@ -1,54 +1,41 @@
1
1
  import json
2
2
  import os
3
+ import requests
3
4
  from os import path
5
+ import requests
4
6
 
5
7
 
6
- def init_config():
7
- # Read configuration from $METAFLOW_HOME/config_<profile>.json.
8
- home = os.environ.get("METAFLOW_HOME", "~/.metaflowconfig")
9
- profile = get_metaflow_profile()
10
- path_to_config = os.path.join(home, "config.json")
11
- if profile:
12
- path_to_config = os.path.join(home, "config_%s.json" % profile)
13
- path_to_config = os.path.expanduser(path_to_config)
14
- config = {}
15
- if os.path.exists(path_to_config):
16
- with open(path_to_config, encoding="utf-8") as f:
17
- return json.load(f)
18
- elif profile:
19
- raise Exception(
20
- "Unable to locate METAFLOW_PROFILE '%s' in '%s')" % (profile, home)
21
- )
22
- return config
23
-
24
-
25
- def get_metaflow_profile():
26
- # If OBP_CONFIG_DIR is set, use that, otherwise use METAFLOW_HOME
27
- # If neither are set, use ~/.metaflowconfig
28
- obp_config_dir = path.expanduser(
29
- os.environ.get(
30
- "OBP_CONFIG_DIR", os.environ.get("METAFLOW_HOME", "~/.metaflowconfig")
31
- )
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")
32
12
  )
33
13
 
34
- file_path = os.path.join(obp_config_dir, "ob_config.json")
14
+ config_filename = f"config_{profile}.json" if profile else "config.json"
15
+ path_to_config = os.path.join(config_dir, config_filename)
16
+
17
+ if os.path.exists(path_to_config):
18
+ with open(path_to_config, encoding="utf-8") as json_file:
19
+ config = json.load(json_file)
20
+ else:
21
+ raise Exception("Unable to locate metaflow config at '%s')" % (path_to_config))
35
22
 
36
- if os.path.exists(file_path):
37
- with open(file_path, "r") as f:
38
- ob_config = json.loads(f.read())
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")
39
27
 
40
- if "OB_CURRENT_PERIMETER" in ob_config:
41
- return ob_config["OB_CURRENT_PERIMETER"]
42
- else:
43
- raise Exception(f"OB_CURRENT_PERIMETER not found in {file_path}")
44
- elif "OBP_CONFIG_DIR" in os.environ:
45
- raise Exception(
46
- "OBP_CONFIG_DIR is set, but no ob_config.json found in that directory"
28
+ config_response = requests.get(
29
+ config["OBP_METAFLOW_CONFIG_URL"],
30
+ headers={"x-api-key": f'{config["METAFLOW_SERVICE_AUTH_KEY"]}'},
47
31
  )
48
- elif "METAFLOW_PROFILE" in os.environ:
49
- return os.environ["METAFLOW_PROFILE"]
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
50
36
 
51
- return ""
37
+ # Legacy config, use from filesystem
38
+ return config
52
39
 
53
40
 
54
41
  def get_metaflow_token_from_config(config_dir: str, profile: str) -> str:
@@ -59,13 +46,10 @@ def get_metaflow_token_from_config(config_dir: str, profile: str) -> str:
59
46
  config_dir (str): Path to the config directory
60
47
  profile (str): The named metaflow profile
61
48
  """
62
- config_filename = f"config_{profile}.json" if profile else "config.json"
63
- config_path = os.path.join(config_dir, config_filename)
64
- with open(config_path) as json_file:
65
- config = json.load(json_file)
66
- if config is None or "METAFLOW_SERVICE_AUTH_KEY" not in config:
67
- raise Exception("METAFLOW_SERVICE_AUTH_KEY not found in config file")
68
- return config["METAFLOW_SERVICE_AUTH_KEY"]
49
+ config = init_config(config_dir, profile)
50
+ if config is None or "METAFLOW_SERVICE_AUTH_KEY" not in config:
51
+ raise Exception("METAFLOW_SERVICE_AUTH_KEY not found in config file")
52
+ return config["METAFLOW_SERVICE_AUTH_KEY"]
69
53
 
70
54
 
71
55
  def get_sanitized_url_from_config(config_dir: str, profile: str, key: str) -> str:
@@ -77,16 +61,12 @@ def get_sanitized_url_from_config(config_dir: str, profile: str, key: str) -> st
77
61
  profile (str): The named metaflow profile
78
62
  key (str): The key to look up in the config file
79
63
  """
80
- config_filename = f"config_{profile}.json" if profile else "config.json"
81
- config_path = os.path.join(config_dir, config_filename)
82
-
83
- with open(config_path) as json_file:
84
- config = json.load(json_file)
85
- if key not in config:
86
- raise Exception(f"Key {key} not found in config file {config_path}")
87
- url_in_config = config[key]
88
- if not url_in_config.startswith("https://"):
89
- url_in_config = f"https://{url_in_config}"
90
-
91
- url_in_config = url_in_config.rstrip("/")
92
- return url_in_config
64
+ config = init_config(config_dir, profile)
65
+ if key not in config:
66
+ raise Exception(f"Key {key} not found in config")
67
+ url_in_config = config[key]
68
+ if not url_in_config.startswith("https://"):
69
+ url_in_config = f"https://{url_in_config}"
70
+
71
+ url_in_config = url_in_config.rstrip("/")
72
+ return url_in_config
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: outerbounds
3
- Version: 0.3.52rc6
3
+ Version: 0.3.53rc1
4
4
  Summary: More Data Science, Less Administration
5
5
  License: Proprietary
6
6
  Keywords: data science,machine learning,MLOps
@@ -24,7 +24,7 @@ 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-storage (>=2.14.0,<3.0.0) ; extra == "gcp"
26
26
  Requires-Dist: ob-metaflow (==2.11.0.4)
27
- Requires-Dist: ob-metaflow-extensions (==1.1.42rc1)
27
+ Requires-Dist: ob-metaflow-extensions (==1.1.43)
28
28
  Requires-Dist: opentelemetry-distro (==0.41b0)
29
29
  Requires-Dist: opentelemetry-exporter-otlp-proto-http (==1.20.0)
30
30
  Requires-Dist: opentelemetry-instrumentation-requests (==0.41b0)
@@ -1,14 +1,15 @@
1
1
  outerbounds/__init__.py,sha256=GPdaubvAYF8pOFWJ3b-sPMKCpyfpteWVMZWkmaYhxRw,32
2
2
  outerbounds/cli_main.py,sha256=e9UMnPysmc7gbrimq2I4KfltggyU7pw59Cn9aEguVcU,74
3
3
  outerbounds/command_groups/__init__.py,sha256=QPWtj5wDRTINDxVUL7XPqG3HoxHNvYOg08EnuSZB2Hc,21
4
- outerbounds/command_groups/cli.py,sha256=61VsBlPG2ykP_786eCyllqeM8DMhPAOfj2FhktrSd7k,207
5
- outerbounds/command_groups/local_setup_cli.py,sha256=7U4mZbAXBDsY-ghKAzcfgMkuwnTExDW0nyJ52nVCg-g,40694
4
+ outerbounds/command_groups/cli.py,sha256=H4LxcYTmsY9DQUrReSRLjvbg9s9Ro7s-eUrcMqEJ_9A,261
5
+ outerbounds/command_groups/local_setup_cli.py,sha256=DwQYfTnK_kb63oQM0A8cAJeLRCTLOcyEwjkBL42O1fQ,29414
6
+ outerbounds/command_groups/perimeters_cli.py,sha256=9tOql42d00KfHpZYkLLGEAOiy8iRbIzsknldCyICwU0,12063
6
7
  outerbounds/command_groups/workstations_cli.py,sha256=f3gwHMZPHzeOcGj5VfC5tZZA18JQhFzy2LRGzqAosOk,19286
7
8
  outerbounds/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
9
  outerbounds/utils/kubeconfig.py,sha256=l1mUP1j9VIq3fsffi5bJ1Nk-hYlwd1dIqkpj7DvVS1E,7936
9
- outerbounds/utils/metaflowconfig.py,sha256=_Khqzu0KE6X05KkVbb39GXN6CFpG2WRr6Xs35pT2l7A,3299
10
+ outerbounds/utils/metaflowconfig.py,sha256=HgaDmK3F97rppfGUdysS1Zppe28ERTLV_HcB5IuPpV4,2631
10
11
  outerbounds/utils/schema.py,sha256=Ht_Yf5uoKO0m36WXHZLSPmWPH6EFWXfZDQsiAUquc5k,2160
11
- outerbounds-0.3.52rc6.dist-info/METADATA,sha256=RZn_J3Jy1JYymPhAygV-QcheRb-GEVN_VPx8jzFzRSw,1367
12
- outerbounds-0.3.52rc6.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
13
- outerbounds-0.3.52rc6.dist-info/entry_points.txt,sha256=7ye0281PKlvqxu15rjw60zKg2pMsXI49_A8BmGqIqBw,47
14
- outerbounds-0.3.52rc6.dist-info/RECORD,,
12
+ outerbounds-0.3.53rc1.dist-info/METADATA,sha256=uUkhPz_gBBdZGk4mA3B5PrwediF87wcbiMRxb9bPe_w,1364
13
+ outerbounds-0.3.53rc1.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
14
+ outerbounds-0.3.53rc1.dist-info/entry_points.txt,sha256=7ye0281PKlvqxu15rjw60zKg2pMsXI49_A8BmGqIqBw,47
15
+ outerbounds-0.3.53rc1.dist-info/RECORD,,