outerbounds 0.3.53__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
@@ -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(
@@ -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)
@@ -110,8 +110,10 @@ def configure_cloud_workstation(config_dir=None, profile=None, binary=None, outp
110
110
  kubeconfig_configure_step = CommandStatus(
111
111
  "ConfigureKubeConfig", OuterboundsCommandStatus.OK, "Kubeconfig is configured"
112
112
  )
113
-
114
113
  try:
114
+ if not profile:
115
+ profile = metaflowconfig.get_metaflow_profile()
116
+
115
117
  metaflow_token = metaflowconfig.get_metaflow_token_from_config(
116
118
  config_dir, profile
117
119
  )
@@ -194,8 +196,25 @@ def configure_cloud_workstation(config_dir=None, profile=None, binary=None, outp
194
196
  default="",
195
197
  help="The named metaflow profile in which your workstation exists",
196
198
  )
197
- def list_workstations(config_dir=None, profile=None):
199
+ @click.option(
200
+ "-o",
201
+ "--output",
202
+ default="json",
203
+ help="Show output in the specified format.",
204
+ type=click.Choice(["json"]),
205
+ )
206
+ def list_workstations(config_dir=None, profile=None, output="json"):
207
+ list_response = OuterboundsCommandResponse()
208
+ list_step = CommandStatus(
209
+ "listWorkstations",
210
+ OuterboundsCommandStatus.OK,
211
+ "Workstation list successfully fetched!",
212
+ )
213
+ list_response.add_or_update_data("workstations", [])
214
+
198
215
  try:
216
+ if not profile:
217
+ profile = metaflowconfig.get_metaflow_profile()
199
218
  metaflow_token = metaflowconfig.get_metaflow_token_from_config(
200
219
  config_dir, profile
201
220
  )
@@ -205,17 +224,23 @@ def list_workstations(config_dir=None, profile=None):
205
224
  workstations_response = requests.get(
206
225
  f"{api_url}/v1/workstations", headers={"x-api-key": metaflow_token}
207
226
  )
208
- try:
209
- workstations_response.raise_for_status()
210
- click.echo(json.dumps(workstations_response.json(), indent=4))
211
- except HTTPError:
212
- click.secho("Failed to generate workstation token.", fg="red")
213
- click.secho(
214
- "Error: {}".format(json.dumps(workstations_response.json(), indent=4))
215
- )
227
+ workstations_response.raise_for_status()
228
+ list_response.add_or_update_data(
229
+ "workstations", workstations_response.json()["workstations"]
230
+ )
231
+ if output == "json":
232
+ click.echo(json.dumps(list_response.as_dict(), indent=4))
216
233
  except Exception as e:
217
- click.secho("Failed to list workstations", fg="red")
218
- click.secho("Error: {}".format(str(e)))
234
+ list_step.update(
235
+ OuterboundsCommandStatus.FAIL, "Failed to list workstations", ""
236
+ )
237
+ list_response.add_step(list_step)
238
+ if output == "json":
239
+ list_response.add_or_update_data("error", str(e))
240
+ click.echo(json.dumps(list_response.as_dict(), indent=4))
241
+ else:
242
+ click.secho("Failed to list workstations", fg="red", err=True)
243
+ click.secho("Error: {}".format(str(e)), fg="red", err=True)
219
244
 
220
245
 
221
246
  @cli.command(help="Hibernate workstation", hidden=True)
@@ -243,6 +268,8 @@ def hibernate_workstation(config_dir=None, profile=None, workstation=None):
243
268
  click.secho("Please specify a workstation ID", fg="red")
244
269
  return
245
270
  try:
271
+ if not profile:
272
+ profile = metaflowconfig.get_metaflow_profile()
246
273
  metaflow_token = metaflowconfig.get_metaflow_token_from_config(
247
274
  config_dir, profile
248
275
  )
@@ -267,7 +294,7 @@ def hibernate_workstation(config_dir=None, profile=None, workstation=None):
267
294
  )
268
295
  except Exception as e:
269
296
  click.secho("Failed to hibernate workstation", fg="red")
270
- click.secho("Error: {}".format(str(e)))
297
+ click.secho("Error: {}".format(str(e)), fg="red")
271
298
 
272
299
 
273
300
  @cli.command(help="Restart workstation to the int", hidden=True)
@@ -295,6 +322,9 @@ def restart_workstation(config_dir=None, profile=None, workstation=None):
295
322
  click.secho("Please specify a workstation ID", fg="red")
296
323
  return
297
324
  try:
325
+ if not profile:
326
+ profile = metaflowconfig.get_metaflow_profile()
327
+
298
328
  metaflow_token = metaflowconfig.get_metaflow_token_from_config(
299
329
  config_dir, profile
300
330
  )
@@ -319,7 +349,7 @@ def restart_workstation(config_dir=None, profile=None, workstation=None):
319
349
  )
320
350
  except Exception as e:
321
351
  click.secho("Failed to restart workstation", fg="red")
322
- click.secho("Error: {}".format(str(e)))
352
+ click.secho("Error: {}".format(str(e)), fg="red")
323
353
 
324
354
 
325
355
  @cli.command(help="Install dependencies needed by workstations", hidden=True)
@@ -1,11 +1,13 @@
1
1
  import json
2
2
  import os
3
3
  import requests
4
+ from os import path
5
+ import requests
4
6
 
5
7
 
6
- def init_config() -> dict:
7
- profile = os.environ.get("METAFLOW_PROFILE")
8
- config_dir = os.path.expanduser(
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(
9
11
  os.environ.get("METAFLOW_HOME", "~/.metaflowconfig")
10
12
  )
11
13
 
@@ -44,13 +46,10 @@ def get_metaflow_token_from_config(config_dir: str, profile: str) -> str:
44
46
  config_dir (str): Path to the config directory
45
47
  profile (str): The named metaflow profile
46
48
  """
47
- config_filename = f"config_{profile}.json" if profile else "config.json"
48
- config_path = os.path.join(config_dir, config_filename)
49
- with open(config_path) as json_file:
50
- config = json.load(json_file)
51
- if config is None or "METAFLOW_SERVICE_AUTH_KEY" not in config:
52
- raise Exception("METAFLOW_SERVICE_AUTH_KEY not found in config file")
53
- 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"]
54
53
 
55
54
 
56
55
  def get_sanitized_url_from_config(config_dir: str, profile: str, key: str) -> str:
@@ -62,16 +61,12 @@ def get_sanitized_url_from_config(config_dir: str, profile: str, key: str) -> st
62
61
  profile (str): The named metaflow profile
63
62
  key (str): The key to look up in the config file
64
63
  """
65
- config_filename = f"config_{profile}.json" if profile else "config.json"
66
- config_path = os.path.join(config_dir, config_filename)
67
-
68
- with open(config_path) as json_file:
69
- config = json.load(json_file)
70
- if key not in config:
71
- raise Exception(f"Key {key} not found in config file {config_path}")
72
- url_in_config = config[key]
73
- if not url_in_config.startswith("https://"):
74
- url_in_config = f"https://{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}"
75
70
 
76
- url_in_config = url_in_config.rstrip("/")
77
- return url_in_config
71
+ url_in_config = url_in_config.rstrip("/")
72
+ return url_in_config
@@ -37,10 +37,14 @@ class OuterboundsCommandResponse:
37
37
  self._message = ""
38
38
  self._steps = []
39
39
  self.metadata = {}
40
+ self._data = {}
40
41
 
41
42
  def add_or_update_metadata(self, key, value):
42
43
  self.metadata[key] = value
43
44
 
45
+ def add_or_update_data(self, key, value):
46
+ self._data[key] = value
47
+
44
48
  def add_step(self, step: CommandStatus):
45
49
  self._steps.append(step)
46
50
  self._process_step_status(step)
@@ -59,10 +63,11 @@ class OuterboundsCommandResponse:
59
63
  self._message = "We found one or more warnings with your installation."
60
64
 
61
65
  def as_dict(self):
66
+ self._data["steps"] = [step.as_dict() for step in self._steps]
62
67
  return {
63
68
  "status": self.status.value,
64
69
  "code": self._code,
65
70
  "message": self._message,
66
- "steps": [step.as_dict() for step in self._steps],
67
71
  "metadata": self.metadata,
72
+ "data": self._data,
68
73
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: outerbounds
3
- Version: 0.3.53
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
@@ -0,0 +1,15 @@
1
+ outerbounds/__init__.py,sha256=GPdaubvAYF8pOFWJ3b-sPMKCpyfpteWVMZWkmaYhxRw,32
2
+ outerbounds/cli_main.py,sha256=e9UMnPysmc7gbrimq2I4KfltggyU7pw59Cn9aEguVcU,74
3
+ outerbounds/command_groups/__init__.py,sha256=QPWtj5wDRTINDxVUL7XPqG3HoxHNvYOg08EnuSZB2Hc,21
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
7
+ outerbounds/command_groups/workstations_cli.py,sha256=f3gwHMZPHzeOcGj5VfC5tZZA18JQhFzy2LRGzqAosOk,19286
8
+ outerbounds/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ outerbounds/utils/kubeconfig.py,sha256=l1mUP1j9VIq3fsffi5bJ1Nk-hYlwd1dIqkpj7DvVS1E,7936
10
+ outerbounds/utils/metaflowconfig.py,sha256=HgaDmK3F97rppfGUdysS1Zppe28ERTLV_HcB5IuPpV4,2631
11
+ outerbounds/utils/schema.py,sha256=Ht_Yf5uoKO0m36WXHZLSPmWPH6EFWXfZDQsiAUquc5k,2160
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,,
@@ -1,14 +0,0 @@
1
- outerbounds/__init__.py,sha256=GPdaubvAYF8pOFWJ3b-sPMKCpyfpteWVMZWkmaYhxRw,32
2
- outerbounds/cli_main.py,sha256=e9UMnPysmc7gbrimq2I4KfltggyU7pw59Cn9aEguVcU,74
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=g_kkrlDGzYvZTm184pW6QwotpkcqBamB14kH_Kv8TbM,28685
6
- outerbounds/command_groups/workstations_cli.py,sha256=VgydQzCas3mlAFyzZuanjl1E8Zh7pBrbKbbP6t6N2WU,18237
7
- outerbounds/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- outerbounds/utils/kubeconfig.py,sha256=l1mUP1j9VIq3fsffi5bJ1Nk-hYlwd1dIqkpj7DvVS1E,7936
9
- outerbounds/utils/metaflowconfig.py,sha256=6u9D4x-pQVCPKnmGkTg9uSSHrq4mGnWQl7TurwyV2e8,2945
10
- outerbounds/utils/schema.py,sha256=nBuarFbdZu0LGhG0YkJ6pEIvdglfM_TO_W_Db2vksb0,2017
11
- outerbounds-0.3.53.dist-info/METADATA,sha256=Q0iMl6HsByY3wbjR129VFxJJz7WpETZ2oAI2vACaYtw,1361
12
- outerbounds-0.3.53.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
13
- outerbounds-0.3.53.dist-info/entry_points.txt,sha256=7ye0281PKlvqxu15rjw60zKg2pMsXI49_A8BmGqIqBw,47
14
- outerbounds-0.3.53.dist-info/RECORD,,