From 3898be2ef39d262a5033f43c567b34ef266998fd Mon Sep 17 00:00:00 2001 From: Shreyas Bhat Date: Wed, 3 Sep 2025 13:43:15 -0500 Subject: [PATCH 1/4] Add base_url argument to FerryCLI constructor to override base_url specified in config_path file --- ferry_cli/__main__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ferry_cli/__main__.py b/ferry_cli/__main__.py index 62e2ca3..baa8170 100755 --- a/ferry_cli/__main__.py +++ b/ferry_cli/__main__.py @@ -45,6 +45,7 @@ class FerryCLI: # pylint: disable=too-many-instance-attributes def __init__( self: "FerryCLI", + base_url: Optional[str] = None, config_path: Optional[pathlib.Path] = None, authorizer: Auth = Auth(), print_help: bool = False, @@ -53,6 +54,7 @@ def __init__( Initializes the FerryCLI instance. Args: + base_url (Optional[str]): The base URL for the Ferry API, if that should not come from the file specified by config_path. config_path (Optional[pathlib.Path]): The path to the configuration file. If None, a message will be printed indicating that a configuration file is required. authorizer (Auth): An instance of the Auth class used for authorization. Defaults to a dummy Auth instance. @@ -90,7 +92,11 @@ def __init__( self.config_path = config_path self.configs = self.__parse_config_file() self.authorizer = authorizer - self.base_url = self._sanitize_base_url(self.base_url) + self.base_url = ( + self._sanitize_base_url(self.base_url) + if base_url is None + else self._sanitize_base_url(base_url) + ) self.dev_url = self._sanitize_base_url(self.dev_url) def get_arg_parser(self: "FerryCLI") -> FerryParser: From 9047ef55c2593e173e8e469004cd0ae4b79202f5 Mon Sep 17 00:00:00 2001 From: Shreyas Bhat Date: Wed, 3 Sep 2025 13:45:25 -0500 Subject: [PATCH 2/4] Added --server flag that drives the base_url given to the FerryCLI constructor --- ferry_cli/__main__.py | 4 +++- ferry_cli/helpers/auth.py | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ferry_cli/__main__.py b/ferry_cli/__main__.py index baa8170..142fd4a 100755 --- a/ferry_cli/__main__.py +++ b/ferry_cli/__main__.py @@ -621,7 +621,9 @@ def main() -> None: try: auth_args, other_args = get_auth_args() ferry_cli = FerryCLI( - config_path=config_path, authorizer=set_auth_from_args(auth_args) + config_path=config_path, + authorizer=set_auth_from_args(auth_args), + base_url=auth_args.server, ) if auth_args.update or not os.path.exists(f"{CONFIG_DIR}/swagger.json"): if auth_args.debug_level != DebugLevel.QUIET: diff --git a/ferry_cli/helpers/auth.py b/ferry_cli/helpers/auth.py index 7fdd195..e17f741 100755 --- a/ferry_cli/helpers/auth.py +++ b/ferry_cli/helpers/auth.py @@ -213,6 +213,11 @@ def get_auth_parser() -> "FerryParser": help="Get Ferry CLI support emails", action=request_project_info("email"), ) + auth_parser.add_argument( + "-s", + "--server", + help="Server URL to use instead of configuration file api.base_url value", + ) auth_parser.add_argument( "--version", nargs=0, From 1e101b659042d0abe9ec106aff6bf422a1892282 Mon Sep 17 00:00:00 2001 From: Shreyas Bhat Date: Wed, 3 Sep 2025 13:46:34 -0500 Subject: [PATCH 3/4] Added tests for --server flag and FerryCLI behavior wrt base_url --- tests/test_main.py | 121 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 96 insertions(+), 25 deletions(-) diff --git a/tests/test_main.py b/tests/test_main.py index 90cd9d6..52e7651 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -124,39 +124,43 @@ def test_handle_show_configfile_envs_not_found( assert "Mocked write_config_file" in captured.out +@pytest.mark.parametrize( + "args, expected_out_substr", + [ + ( + ["-h"], + "--show-config-file", + ), # If we pass -h, make sure --show-config-file shows up + ( + ["-h", "--show-config-file", "-e", "getAllGroups"], + "--show-config-file", + ), # If we pass -h and --show-config-file, -h should win + ( + ["--show-config-file"], + "Configuration file", + ), # Print out config file if we only pass --show-config-file + ( + ["--show-config-file", "-e", "getAllGroups"], + "Configuration file", + ), # If we pass --show-config-file with other args, --show-config-file should print out the config file + ], +) @pytest.mark.unit def test_show_configfile_flag_with_other_args( - tmp_path, monkeypatch, write_and_set_fake_config_file + tmp_path, monkeypatch, write_and_set_fake_config_file, args, expected_out_substr ): # Since we have to handle --show-config-file outside of argparse, make sure we get the correct behavior given different combinations of args bindir = f"{os.path.dirname(os.path.dirname(os.path.abspath(__file__)))}/bin" exe = f"{bindir}/ferry-cli" - test_case = namedtuple("TestCase", ["args", "expected_out_substr"]) - - cases = ( - test_case( - [sys.executable, exe, "-h"], "--show-config-file" - ), # If we pass -h, make sure --show-config-file shows up - test_case( - [sys.executable, exe, "-h", "--show-config-file", "-e", "getAllGroups"], - "--show-config-file", - ), # If we pass -h and --show-config-file, -h should win - test_case( - [sys.executable, exe, "--show-config-file"], "Configuration file" - ), # Print out config file if we only pass --show-config-file - test_case( - [sys.executable, exe, "--show-config-file", "-e", "getAllGroups"], - "Configuration file", - ), # If we pass --show-config-file with other args, --show-config-file should print out the config file - ) + exe_args = [sys.executable, exe] + exe_args.extend(args) - for case in cases: - try: - proc = subprocess.run(case.args, capture_output=True) - except SystemExit: - pass - assert case.expected_out_substr in str(proc.stdout) + try: + proc = subprocess.run(exe_args, capture_output=True) + except SystemExit: + pass + assert expected_out_substr in str(proc.stdout) @pytest.mark.unit @@ -302,3 +306,70 @@ def test_handle_no_args_configfile_does_not_exist( assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 0 + + +@pytest.mark.parametrize( + "base_url, expected_base_url", + [ + (None, "https://example.com:12345/"), # Get base_url from config + ( + "https://override_example.com:54321/", + "https://override_example.com:54321/", + ), # Get base_url from override + ], +) +@pytest.mark.unit +def test_override_base_url_FerryCLI(tmp_path, base_url, expected_base_url): + # Set up fake config + fake_config_text = """ +[api] +base_url = https://example.com:12345/ +dev_url = https://example.com:12345/ + +""" + fake_config = tmp_path / "config.ini" + fake_config.write_text(fake_config_text) + + cli = FerryCLI(config_path=fake_config, base_url=base_url) + assert cli.base_url == expected_base_url + + +@pytest.mark.parametrize( + "args, expected_out_url", + [ + ([], "https://example.com:12345/"), # Get base_url from config + ( + ["--server", "https://override_example.com:54321/"], + "https://override_example.com:54321/", + ), # Get base_url from override + ], +) +@pytest.mark.test +def test_server_flag_main(tmp_path, monkeypatch, args, expected_out_url): + # Run ferry-cli with overridden base_url in dryrun mode to endpoint ping. Then see if we see the correct server in output + override_url = "https://override_example.com:54321/" + # Set up fake config + fake_config_text = """ +[api] +base_url = https://example.com:12345/ +dev_url = https://example.com:12345/ + +""" + # Fake config file + p = tmp_path + config_dir = p / "ferry_cli" + config_dir.mkdir() + config_file = config_dir / "config.ini" + config_file.write_text(fake_config_text) + monkeypatch.setenv("XDG_CONFIG_HOME", str(p.absolute())) + + bindir = f"{os.path.dirname(os.path.dirname(os.path.abspath(__file__)))}/bin" + exe = f"{bindir}/ferry-cli" + + exe_args = [sys.executable, exe] + exe_args.extend(args + ["--dryrun", "-e", "ping"]) + + proc = subprocess.run(exe_args, capture_output=True) + assert f"Would call endpoint: {expected_out_url}ping with params" in str( + proc.stdout + ) From fb80468caea718f08da6eca9b5eecf6b6a061b1e Mon Sep 17 00:00:00 2001 From: Shreyas Bhat Date: Wed, 3 Sep 2025 13:51:38 -0500 Subject: [PATCH 4/4] Organized .gitignore --- .gitignore | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 77d6dbd..7ccf80b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,17 @@ +# Build artifacts +build/ +.vscode/ +__pycache__/ +ferry_cli.egg-info/ + +# Development artifacts result.json .venv .git -/tmp/* -__pycache__ -.make_creds.sh swagger.json config/swagger.json + +# Testing artifacts +/tmp/* +.make_creds.sh remove