From 3486944401234548ba3195cd57e6156f7318f8ec Mon Sep 17 00:00:00 2001 From: Eirini Koutsaniti Date: Thu, 29 Feb 2024 09:03:59 +0100 Subject: [PATCH] Add the groups endpoint --- firecrest/AsyncClient.py | 16 ++++++++++++ firecrest/BasicClient.py | 16 ++++++++++++ firecrest/cli/__init__.py | 28 ++++++++++++++++++++ firecrest/types.py | 13 ++++++++++ tests/test_utilities.py | 49 ++++++++++++++++++++++++++++++++++- tests/test_utilities_async.py | 22 ++++++++++++++++ 6 files changed, 143 insertions(+), 1 deletion(-) diff --git a/firecrest/AsyncClient.py b/firecrest/AsyncClient.py index 540d7cf..1e961f4 100644 --- a/firecrest/AsyncClient.py +++ b/firecrest/AsyncClient.py @@ -973,6 +973,22 @@ async def whoami(self, machine=None) -> Optional[str]: # Invalid token, cannot retrieve username return None + async def groups(self, machine) -> t.UserId: + """Returns the output of the `id` command, user and group ids. + + :calls: GET `/utilities/whoami` + + .. warning:: This is available only for FirecREST>=1.15.0 + """ + resp = await self._get_request( + endpoint="/utilities/whoami", + additional_headers={"X-Machine-Name": machine}, + params={ + "groups": True + } + ) + return self._json_response([resp], 200)["output"] + # Compute async def submit( self, diff --git a/firecrest/BasicClient.py b/firecrest/BasicClient.py index 1b0ba18..70566aa 100644 --- a/firecrest/BasicClient.py +++ b/firecrest/BasicClient.py @@ -799,6 +799,22 @@ def whoami(self, machine=None) -> Optional[str]: # Invalid token, cannot retrieve username return None + def groups(self, machine) -> t.UserId: + """Returns the output of the `id` command, user and group ids. + + :calls: GET `/utilities/whoami` + + .. warning:: This is available only for FirecREST>=1.15.0 + """ + resp = self._get_request( + endpoint="/utilities/whoami", + additional_headers={"X-Machine-Name": machine}, + params={ + "groups": True + } + ) + return self._json_response([resp], 200)["output"] + # Compute def _submit_request(self, machine: str, job_script, local_file, account=None, env_vars=None): data = {} diff --git a/firecrest/cli/__init__.py b/firecrest/cli/__init__.py index 159bbe2..bc06775 100644 --- a/firecrest/cli/__init__.py +++ b/firecrest/cli/__init__.py @@ -769,6 +769,34 @@ def whoami( raise typer.Exit(code=1) +@app.command(rich_help_panel="Utilities commands") +def id( + config_from_parent: str = typer.Option(None, + callback=config_parent_load_callback, + is_eager=True, + hidden=True + ), + system: str = typer.Option( + None, + "-s", + "--system", + help="The name of the system where the `id` command will run.", + envvar="FIRECREST_SYSTEM", + ), +): + """Return the identity of the user in the remote system. + """ + try: + result = client.groups(system) + user = f"{result['user']['id']}({result['user']['name']})" + group = f"{result['group']['id']}({result['group']['name']})" + all_groups = ",".join([f"{g['id']}({g['name']})" for g in result["groups"]]) + console.print(f"uid={user} gid={group} groups={all_groups}") + except Exception as e: + examine_exeption(e) + raise typer.Exit(code=1) + + class TransferType(str, Enum): direct = "direct" external = "external" diff --git a/firecrest/types.py b/firecrest/types.py index e066927..29381ec 100644 --- a/firecrest/types.py +++ b/firecrest/types.py @@ -140,3 +140,16 @@ class JobSubmit(TypedDict): job_file_out: str jobid: int result: str + + +class Id(TypedDict): + name: str + id: str + + +class UserId(TypedDict): + """A record from the `id` command""" + + user: Id + group: Id + groups: list[Id] diff --git a/tests/test_utilities.py b/tests/test_utilities.py index d038011..71dafde 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -730,8 +730,21 @@ def whoami_handler(request: Request): content_type="application/json", ) + groups = request.args.get("groups", False) + if groups: + ret = { + "description": "User information", + "output": { + "group": {"id": "1000", "name": "group1"}, + "groups": [{"id": "1000", "name": "group1"}, {"id": "1001", "name": "group2"}], + "user": {"id": "10000", "name": "test_user"}, + } + } + else: + ret = {"description": "Success on whoami operation.", "output": "username"} + return Response( - json.dumps({"description": "Success on whoami operation.", "output": "username"}), + json.dumps(ret), status=200, content_type="application/json", ) @@ -1510,3 +1523,37 @@ def test_whoami_invalid_machine(valid_client): def test_whoami_invalid_client(invalid_client): with pytest.raises(firecrest.UnauthorizedException): invalid_client.whoami("cluster1") + + +def test_cli_whoami(valid_credentials): + args = valid_credentials + ["whoami", "--system", "cluster1"] + result = runner.invoke(cli.app, args=args) + stdout = common.clean_stdout(result.stdout) + assert result.exit_code == 0 + assert "username" in stdout + + +def test_groups(valid_client): + assert valid_client.groups("cluster1") == { + "group": {"id": "1000", "name": "group1"}, + "groups": [{"id": "1000", "name": "group1"}, {"id": "1001", "name": "group2"}], + "user": {"id": "10000", "name": "test_user"}, + } + + +def test_groups_invalid_machine(valid_client): + with pytest.raises(firecrest.HeaderException): + valid_client.groups("cluster2") + + +def test_groups_invalid_client(invalid_client): + with pytest.raises(firecrest.UnauthorizedException): + invalid_client.groups("cluster1") + + +def test_cli_id(valid_credentials): + args = valid_credentials + ["id", "--system", "cluster1"] + result = runner.invoke(cli.app, args=args) + stdout = common.clean_stdout(result.stdout) + assert result.exit_code == 0 + assert "uid=10000(test_user) gid=1000(group1) groups=1000(group1),1001(group2)" in stdout diff --git a/tests/test_utilities_async.py b/tests/test_utilities_async.py index 6606d2f..04404ea 100644 --- a/tests/test_utilities_async.py +++ b/tests/test_utilities_async.py @@ -651,3 +651,25 @@ async def test_whoami_invalid_machine(valid_client): async def test_whoami_invalid_client(invalid_client): with pytest.raises(firecrest.UnauthorizedException): await invalid_client.whoami("cluster1") + + +@pytest.mark.asyncio +async def test_groups(valid_client): + assert await valid_client.groups("cluster1") == { + "group": {"id": "1000", "name": "group1"}, + "groups": [{"id": "1000", "name": "group1"}, {"id": "1001", "name": "group2"}], + "user": {"id": "10000", "name": "test_user"}, + } + + +@pytest.mark.asyncio +async def test_groups_invalid_machine(valid_client): + with pytest.raises(firecrest.HeaderException): + await valid_client.groups("cluster2") + + +@pytest.mark.asyncio +async def test_groups_invalid_client(invalid_client): + with pytest.raises(firecrest.UnauthorizedException): + await invalid_client.groups("cluster1") +