diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c6d27175ff9..a1bf42e6215 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,7 +64,7 @@ requests_). - Prasanna Challuri - David Matthews - Tim Whitcomb - - (Scott Wales) + - Scott Wales - Tomek Trzeciak - Thomas Coleman - Bruno Kinoshita diff --git a/changes.d/5709.feat.md b/changes.d/5709.feat.md new file mode 100644 index 00000000000..11aeabcf81d --- /dev/null +++ b/changes.d/5709.feat.md @@ -0,0 +1 @@ +Forward arbitrary environment variables over SSH connections diff --git a/cylc/flow/cfgspec/globalcfg.py b/cylc/flow/cfgspec/globalcfg.py index 86c639af113..5251c0ffad3 100644 --- a/cylc/flow/cfgspec/globalcfg.py +++ b/cylc/flow/cfgspec/globalcfg.py @@ -1652,6 +1652,14 @@ def default_for( .. versionadded:: 8.0.0 ''') + Conf('ssh forward environment variables', VDR.V_STRING_LIST, '', + desc=''' + A list containing the names of the environment variables to + forward with SSH connections to the workflow host from + the host running 'cylc play' + + .. versionadded:: 8.3.0 + ''') with Conf('selection', desc=''' How to select a host from the list of platform hosts. diff --git a/cylc/flow/remote.py b/cylc/flow/remote.py index fd51934af11..e0ab7544a39 100644 --- a/cylc/flow/remote.py +++ b/cylc/flow/remote.py @@ -298,7 +298,8 @@ def construct_ssh_cmd( 'CYLC_CONF_PATH', 'CYLC_COVERAGE', 'CLIENT_COMMS_METH', - 'CYLC_ENV_NAME' + 'CYLC_ENV_NAME', + *platform['ssh forward environment variables'], ]: if envvar in os.environ: command.append( diff --git a/tests/unit/cfgspec/test_globalcfg.py b/tests/unit/cfgspec/test_globalcfg.py index 2becda1caad..6db8d76dcf8 100644 --- a/tests/unit/cfgspec/test_globalcfg.py +++ b/tests/unit/cfgspec/test_globalcfg.py @@ -148,3 +148,13 @@ def test_source_dir_validation( assert "must be an absolute path" in str(excinfo.value) else: glblcfg.load() + +def test_platform_ssh_forward_variables(mock_global_config): + + glblcfg: GlobalConfig = mock_global_config(''' + [platforms] + [[foo]] + ssh forward environment variables = "FOO", "BAR" + ''') + + assert glblcfg.get(['platforms','foo','ssh forward environment variables']) == ["FOO", "BAR"] diff --git a/tests/unit/test_remote.py b/tests/unit/test_remote.py index 7be01de2e20..8d24fc0bb3a 100644 --- a/tests/unit/test_remote.py +++ b/tests/unit/test_remote.py @@ -15,7 +15,9 @@ # along with this program. If not, see . """Test the cylc.flow.remote module.""" -from cylc.flow.remote import run_cmd, construct_rsync_over_ssh_cmd +from cylc.flow.remote import run_cmd, construct_rsync_over_ssh_cmd, construct_ssh_cmd +from unittest import mock +import cylc.flow def test_run_cmd_stdin_str(): @@ -86,3 +88,28 @@ def test_construct_rsync_over_ssh_cmd(): '/foo/', 'miklegard:/bar/', ] + + +def test_construct_ssh_cmd_forward_env(): + """ Test for 'ssh forward environment variables' + """ + import os + + host = 'example.com' + config = { + 'ssh command': 'ssh', + 'use login shell': None, + 'cylc path': None, + 'ssh forward environment variables': ['FOO', 'BAZ'], + } + + # Variable isn't set, no change to command + expect = ['ssh', host, 'env', f'CYLC_VERSION={cylc.flow.__version__}', 'cylc', 'play'] + cmd = construct_ssh_cmd(['play'], config, host) + assert cmd == expect + + # Variable is set, appears in `env` list + with mock.patch.dict(os.environ, {'FOO': 'BAR'}): + expect = ['ssh', host, 'env', f'CYLC_VERSION={cylc.flow.__version__}', 'FOO=BAR', 'cylc', 'play'] + cmd = construct_ssh_cmd(['play'], config, host) + assert cmd == expect