Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion cylc/flow/network/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
abstractmethod,
)
import asyncio
import getpass
import os
from shutil import which
import socket
Expand Down Expand Up @@ -366,6 +367,16 @@

if cmd.startswith(cylc_bin_dir):
cmd = cmd.replace(cylc_bin_dir, '')
try:
behalf_of = f" (possibly on behalf of {os.getlogin()})"
except OSError:
behalf_of = None
if not behalf_of:
try:
behalf_of = f" (possibly on behalf of {getpass.getuser()})"
except OSError:
behalf_of = ''

Check warning on line 378 in cylc/flow/network/client.py

View check run for this annotation

Codecov / codecov/patch

cylc/flow/network/client.py#L377-L378

Added lines #L377 - L378 were not covered by tests

return {
'meta': {
'prog': cmd,
Expand All @@ -374,6 +385,7 @@
os.getenv(
"CLIENT_COMMS_METH",
default=CommsMeth.ZMQ.value
)
),
'behalf_of': behalf_of
Copy link
Member

@oliver-sanders oliver-sanders Jun 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Send the full message through using the existing auth_user field rather than creating a new behalf_of one.

auth_user = '<actor> (possibly on behalf of <user>)'

That way no changes will be required in cylc/flow/network/resolvers.py to log this and the feature will be fully backwards compatible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oliver-sanders
I've been poking at this but need help from someone with more py-zmq knowledge.

I was expecting self.socket.get(zmq.PLAIN_USERNAME) in async_request() to achieve this. I've not found a way to get the username on the client side.

Ideas on how I might approach this?

Copy link
Member

@oliver-sanders oliver-sanders Jun 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@psa, ZMQ doesn't come into it, just replace behalf_of with auth_user and supply it with the full text:

Suggested change
'behalf_of': behalf_of
'auth_user': f'{actor} (possibly on behalf of {user})'

Where actor is the actual user account (i.e. $USER) and user is the name of the person who is acting via sudo (i.e. $LOGNAME).

}
}
5 changes: 3 additions & 2 deletions cylc/flow/network/resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -723,13 +723,14 @@ async def _mutation_mapper(
Others go to the scheduler command queue.

"""
behalf_of = meta.get('behalf_of', '')
user = meta.get('auth_user', self.schd.owner)
if user == self.schd.owner:
log_user = "" # don't log user name if owner
log_user = f" from {self.schd.owner}"
else:
log_user = f" from {user}"

log1 = f'Command "{command}" received{log_user}.'
log1 = f'Command "{command}" received{log_user}{behalf_of}.'
log2 = (
f"{command}("
+ ", ".join(
Expand Down
9 changes: 8 additions & 1 deletion tests/integration/network/test_resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,12 +244,19 @@ async def test_command_logging(mock_flow, caplog, log_filter):
}
meta["auth_user"] = mock_flow.owner
await mock_flow.resolvers._mutation_mapper("put_messages", kwargs, meta)
assert not log_filter(contains='Command "put_messages" received:')
assert not log_filter(
contains=f'Command "put_messages" received from {mock_flow.owner}')

meta["auth_user"] = "Dr Spock"
await mock_flow.resolvers._mutation_mapper("put_messages", kwargs, meta)
assert log_filter(contains='Command "put_messages" received from Dr Spock')

meta["auth_user"] = "Dr Spock"
meta["behalf_of"] = " (possibly on behalf of Mr Nimoy)"
await mock_flow.resolvers._mutation_mapper("put_messages", kwargs, meta)
assert log_filter(
contains='received from Dr Spock (possibly on behalf of Mr Nimoy)')


async def test_command_validation_failure(
mock_flow,
Expand Down
Loading