Skip to content

Commit 9cbfb13

Browse files
authored
Merge pull request #87 from fermitools/output_logic
Issue # 83 (output files)
2 parents b440942 + a0b38c9 commit 9cbfb13

File tree

4 files changed

+91
-40
lines changed

4 files changed

+91
-40
lines changed

ferry_cli/__main__.py

Lines changed: 63 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import pathlib
66
import sys
77
import textwrap
8-
from typing import Any, Dict, Optional, List
8+
from typing import Any, Dict, Optional, List, Type
99
from urllib.parse import urlsplit, urlunsplit, SplitResult
1010

1111
import validators # pylint: disable=import-error
@@ -49,7 +49,6 @@ def __init__(self: "FerryCLI", config_path: Optional[pathlib.Path]) -> None:
4949
self.authorizer: Optional["Auth"] = None
5050
self.ferry_api: Optional["FerryAPI"] = None
5151
self.parser: Optional["FerryParser"] = None
52-
5352
if config_path is None:
5453
print(
5554
'A configuration file is required to run the Ferry CLI. Please run "ferry-cli" to generate one interactively if one does not already exist.'
@@ -66,10 +65,9 @@ def get_arg_parser(self: "FerryCLI") -> FerryParser:
6665
description="CLI for Ferry API endpoints", parents=[get_auth_parser()]
6766
)
6867
parser.add_argument(
69-
"--dryrun",
70-
action="store_true",
71-
default=False,
72-
help="Populate the API call(s) but don't actually run them",
68+
"--output",
69+
default=None,
70+
help="(string) Specifies the path to a file where the output will be stored in JSON format. If a file already exists in the specified path, it will be overritten.",
7371
)
7472
parser.add_argument(
7573
"--filter",
@@ -290,7 +288,9 @@ def parse_description(
290288
endpoint_description += f"{'':<50} | {line}\n"
291289
return endpoint_description
292290

293-
def run(self: "FerryCLI", debug: bool, quiet: bool, extra_args: Any) -> None:
291+
def run(
292+
self: "FerryCLI", debug: bool, quiet: bool, dryrun: bool, extra_args: Any
293+
) -> None:
294294
self.parser = self.get_arg_parser()
295295
args, endpoint_args = self.parser.parse_known_args(extra_args)
296296

@@ -300,7 +300,7 @@ def run(self: "FerryCLI", debug: bool, quiet: bool, extra_args: Any) -> None:
300300

301301
if not self.ferry_api:
302302
self.ferry_api = FerryAPI(
303-
base_url=self.base_url, authorizer=self.authorizer, quiet=quiet, dryrun=args.dryrun # type: ignore
303+
base_url=self.base_url, authorizer=self.authorizer, quiet=quiet, dryrun=dryrun # type: ignore
304304
)
305305

306306
if args.endpoint:
@@ -310,8 +310,10 @@ def run(self: "FerryCLI", debug: bool, quiet: bool, extra_args: Any) -> None:
310310
json_result = self.execute_endpoint(args.endpoint, endpoint_args)
311311
except Exception as e:
312312
raise Exception(f"{e}")
313-
if (not quiet) and (not args.dryrun):
314-
self.handle_output(json.dumps(json_result, indent=4))
313+
if not dryrun:
314+
self.handle_output(
315+
json.dumps(json_result, indent=4), args.output, quiet
316+
)
315317

316318
elif args.workflow:
317319
try:
@@ -320,25 +322,59 @@ def run(self: "FerryCLI", debug: bool, quiet: bool, extra_args: Any) -> None:
320322
workflow.init_parser()
321323
workflow_params, _ = workflow.parser.parse_known_args(endpoint_args)
322324
json_result = workflow.run(self.ferry_api, vars(workflow_params)) # type: ignore
323-
if (not quiet) and (not args.dryrun):
324-
self.handle_output(json.dumps(json_result, indent=4))
325+
if not dryrun:
326+
self.handle_output(
327+
json.dumps(json_result, indent=4), args.output, quiet
328+
)
325329
except KeyError:
326330
raise KeyError(f"Error: '{args.workflow}' is not a supported workflow.")
327331

328332
else:
329333
self.parser.print_help()
330334

331-
# TBD if we will use this at all
332-
def handle_output(self: "FerryCLI", output: str) -> None:
333-
# Don't print excessively long responses - just store them in the result.json file and point to it.
334-
if len(output) < 1000:
335-
print(f"Response: {output}")
336-
else:
337-
with open("result.json", "w") as file:
335+
def handle_output(
336+
self: "FerryCLI", output: str, output_file: str = "", quiet: bool = False
337+
) -> None:
338+
def error_raised(
339+
exception_type: Type[BaseException],
340+
message: str,
341+
) -> None:
342+
message = f"{exception_type.__name__}\n" f"{message}"
343+
if not quiet:
344+
message += f"\nPrinting response instead: {output}"
345+
raise exception_type(message)
346+
347+
if not output_file:
348+
if not quiet:
349+
print(f"Response: {output}")
350+
return
351+
352+
directory = os.path.dirname(output_file)
353+
if directory:
354+
try:
355+
os.makedirs(directory, exist_ok=True)
356+
except PermissionError:
357+
error_raised(
358+
PermissionError,
359+
f"Permission denied: Unable to create directory: {directory}",
360+
)
361+
except OSError as e:
362+
error_raised(OSError, f"Error creating directory: {e}")
363+
try:
364+
with open(output_file, "w") as file:
338365
file.write(output)
339-
print(
340-
f"\nResponse in file: {os.path.abspath(os.environ.get('PWD', ''))}/result.json"
366+
if not quiet:
367+
print(f"Output file: {output_file}")
368+
return
369+
except PermissionError:
370+
error_raised(
371+
PermissionError,
372+
f"Permission denied: Unable to write to file: {output_file}",
341373
)
374+
except IOError as e:
375+
error_raised(IOError, f"Error writing to file: {e}")
376+
except Exception as e: # pylint: disable=broad-except
377+
error_raised(Exception, f"Error: {e}")
342378

343379
@staticmethod
344380
def _sanitize_base_url(raw_base_url: str) -> str:
@@ -529,19 +565,21 @@ def main() -> None:
529565
sys.exit(0)
530566
ferry_cli.authorizer = set_auth_from_args(auth_args)
531567
if auth_args.update or not os.path.exists(f"{CONFIG_DIR}/swagger.json"):
532-
print("Fetching latest swagger file...")
568+
if not auth_args.quiet:
569+
print("Fetching latest swagger file...")
533570
ferry_cli.ferry_api = FerryAPI(
534571
ferry_cli.base_url, ferry_cli.authorizer, auth_args.quiet
535572
)
536573
ferry_cli.ferry_api.get_latest_swagger_file()
537-
print("Successfully stored latest swagger file.\n")
574+
if not auth_args.quiet:
575+
print("Successfully stored latest swagger file.\n")
538576

539577
ferry_cli.endpoints = ferry_cli.generate_endpoints()
540-
ferry_cli.run(auth_args.debug, auth_args.quiet, other_args)
578+
ferry_cli.run(auth_args.debug, auth_args.quiet, auth_args.dryrun, other_args)
541579
except (
542580
Exception # pylint: disable=broad-except
543581
) as e: # TODO Eventually we want to handle a certain set of exceptions, but this will do for now # pylint: disable=fixme
544-
print(f"There was an error querying the FERRY API: {e}")
582+
print(f"An error occurred while using the FERRY CLI: {e}")
545583
sys.exit(1)
546584

547585

ferry_cli/helpers/auth.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,14 @@ def get_auth_parser() -> "FerryParser":
179179
default=False,
180180
help="Turn on debugging",
181181
)
182-
auth_parser.add_argument(
182+
output_group = auth_parser.add_mutually_exclusive_group(required=False)
183+
output_group.add_argument(
184+
"--dryrun",
185+
action="store_true",
186+
default=False,
187+
help="Populate the API call(s) but don't actually run them",
188+
)
189+
output_group.add_argument(
183190
"-q", "--quiet", action="store_true", default=False, help="Hide output"
184191
)
185192
auth_parser.add_argument(
@@ -207,10 +214,12 @@ def get_auth_parser() -> "FerryParser":
207214
def set_auth_from_args(args: Namespace) -> Auth:
208215
"""Set the auth class based on the given arguments"""
209216
if args.auth_method == "token":
210-
print("\nUsing token auth")
217+
if not args.quiet:
218+
print("\nUsing token auth")
211219
return AuthToken(args.token_path, args.debug)
212220
elif args.auth_method in ["cert", "certificate"]:
213-
print("\nUsing cert auth")
221+
if not args.quiet:
222+
print("\nUsing cert auth")
214223
return AuthCert(args.cert_path, args.ca_path, args.debug)
215224
else:
216225
raise ValueError(

ferry_cli/helpers/supported_workflows/CloneResource.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,19 +103,22 @@ def run(self: "CloneResource", api: "FerryAPI", args: Any) -> Any: # type: igno
103103
"Dryrun: Since no API was actually run, we cannot simulate adding users from the cloned resource to the new resource"
104104
)
105105
else:
106-
print(f"Received response, searching for resource: {args['clone']}")
106+
if not api.quiet:
107+
print(f"Received response, searching for resource: {args['clone']}")
107108
resources = [resource for resource in group_json if resource.get("resourcename", "") == args["clone"]] # type: ignore
108109
if resources:
109110
# Add each user from the original resource into the new resource with the same configurations
110111
for resource in resources:
111-
print("Found Resources")
112-
print(resource)
112+
if not api.quiet:
113+
print("Found Resources")
114+
print(resource)
113115
for user in resource.get("users", []):
114116
user_access_data = dict(user)
115117
user_access_data["resourcename"] = args["new_resource"]
116-
print(
117-
f"Updating user access to {args['new_resource']} for {user['username']}"
118-
)
118+
if not api.quiet:
119+
print(
120+
f"Updating user access to {args['new_resource']} for {user['username']}"
121+
)
119122
if "status" in user_access_data:
120123
del user_access_data["status"]
121124
self.verify_output(
@@ -126,10 +129,10 @@ def run(self: "CloneResource", api: "FerryAPI", args: Any) -> Any: # type: igno
126129
params=user_access_data,
127130
),
128131
)
129-
130-
print(
131-
f"Resource '{args['clone']}' has been successful cloned as '{args['new_resource']}'"
132-
)
132+
if not api.quiet:
133+
print(
134+
f"Resource '{args['clone']}' has been successful cloned as '{args['new_resource']}'"
135+
)
133136
sys.exit(0)
134137
except Exception as e:
135138
raise e

ferry_cli/helpers/supported_workflows/GetFilteredGroupInfo.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ def run(self, api, args): # type: ignore # pylint: disable=arguments-differ
2626
group_json = self.verify_output(api, api.call_endpoint("getAllGroups"))
2727
if api.dryrun:
2828
return []
29-
print("Received successful response")
30-
print(f"Filtering by groupname: '{args['groupname']}'")
29+
if not api.quiet:
30+
print("Received successful response")
31+
print(f"Filtering by groupname: '{args['groupname']}'")
3132
group_info = [
3233
entry for entry in group_json if entry["groupname"] == args["groupname"]
3334
]

0 commit comments

Comments
 (0)