outerbounds 0.3.52rc6__py3-none-any.whl → 0.3.53rc1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,,