Skip to content

Commit

Permalink
Ensure that the cached globalconfig object
Browse files Browse the repository at this point in the history
is reloaded after the export of `CYLC_SYMLINKS` variable.
This means that using the `CYLC_SYMLINKS` variable allows
users to specify installation symlink locations in the
`rose-suite.conf`.
  • Loading branch information
wxtim committed Jan 2, 2024
1 parent e20ec22 commit fa3611b
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 7 deletions.
8 changes: 8 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ creating a new release entry be sure to copy & paste the span tag with the
updated. Only the first match gets replaced, so it's fine to leave the old
ones in. -->


## __cylc-rose-1.4.0 (<span actions:bind='release-date'>Upcoming</span>)__

### Features

[#269](https://github.com/cylc/cylc-rose/pull/269) - Allow environment variables
set in ``rose-suite.conf`` to be used when parsing ``global.cylc``.

## __cylc-rose-1.3.1 (<span actions:bind='release-date'>Released 2023-10-24</span>)__

### Fixes
Expand Down
29 changes: 28 additions & 1 deletion cylc/rose/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,37 @@
for ease of porting Cylc 7 workflows.
The ``global.cylc`` file
^^^^^^^^^^^^^^^^^^^^^^^^
The Cylc Rose Plugin forces the reloading of the ``global.cylc`` file
to allow environment variables set by Rose to change the global configuration.
For example you could use ``CYLC_SYMLINKS`` as a variable to control
the behaviour of ``cylc install``:
.. code-block:: cylc
#!jinja2
# part of a global.cylc file
[install]
[[symlink dirs]]
[[[hpc]]]
{% if environ["CYLC_SYMLINKS"] | default("x") == "A" %}
run = $LOCATION_A
{% elif environ["CYLC_SYMLINKS"] | default("x") == "B" %}
run = $LOCATION_B
{% else %}
run = $LOCATION_C
{% endif %}
Special Variables
-----------------
The Cylc Rose plugin provides two environment/template variables
to the Cylc scheduler:
to the Cylc scheduler.
``ROSE_ORIG_HOST``
Cylc commands (such as ``cylc install``, ``cylc validate`` and
Expand Down Expand Up @@ -111,6 +137,7 @@
``CYLC_VERSION`` will be removed from your configuration by the
Cylc-Rose plugin, as it is now set by Cylc.
Additional CLI options
----------------------
You can use command line options to set or override
Expand Down
8 changes: 8 additions & 0 deletions cylc/rose/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from cylc.flow import LOG
from cylc.flow.exceptions import CylcError
from cylc.flow.flags import cylc7_back_compat
from cylc.flow.cfgspec.glbl_cfg import glbl_cfg
from cylc.flow.hostuserutil import get_host
from metomi.isodatetime.datetimeoper import DateTimeOperator
from metomi.rose import __version__ as ROSE_VERSION
Expand Down Expand Up @@ -869,6 +870,13 @@ def export_environment(environment: Dict[str, str]) -> None:
for key, val in environment.items():
os.environ[key] = val

# If env vars have been set we want to force reload
# the global config so that the value of this vars
# can be used by Jinja2 in the global config.
# https://github.com/cylc/cylc-rose/issues/237
if environment:
glbl_cfg().load()


def record_cylc_install_options(
srcdir: Path,
Expand Down
24 changes: 18 additions & 6 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from types import SimpleNamespace
from uuid import uuid4

from cylc.flow import __version__ as CYLC_VERSION
from cylc.flow.option_parsers import Options
from cylc.flow.pathutil import get_workflow_run_dir
from cylc.flow.scripts.install import get_option_parser as install_gop
from cylc.flow.scripts.install import install_cli as cylc_install
from cylc.flow.scripts.reinstall import get_option_parser as reinstall_gop
Expand All @@ -27,6 +29,11 @@
import pytest


@pytest.fixture
def generate_workflow_name():
return 'cylc-rose-test-' + str(uuid4())[:8]


@pytest.fixture(scope='module')
def mod_capsys(request):
from _pytest.capture import SysCapture
Expand Down Expand Up @@ -108,7 +115,7 @@ def _inner(srcpath, args=None):
return _inner


def _cylc_install_cli(capsys, caplog):
def _cylc_install_cli(capsys, caplog, generate_workflow_name):
"""Access the install CLI"""
def _inner(srcpath, args=None):
"""Install a workflow.
Expand All @@ -119,16 +126,21 @@ def _inner(srcpath, args=None):
"""
options = Options(install_gop(), args)()
output = SimpleNamespace()
if not options.workflow_name:
options.workflow_name = generate_workflow_name
if not args or args and not args.get('no_run_name', ''):
options.no_run_name = True

try:
cylc_install(options, str(srcpath))
output.name, output.id = cylc_install(options, str(srcpath))
output.ret = 0
output.exc = ''
except Exception as exc:
output.ret = 1
output.exc = exc
output.logging = '\n'.join([i.message for i in caplog.records])
output.out, output.err = capsys.readouterr()
output.run_dir = get_workflow_run_dir(output.id)
return output
return _inner

Expand Down Expand Up @@ -159,13 +171,13 @@ def _inner(workflow_id, opts=None):


@pytest.fixture
def cylc_install_cli(capsys, caplog):
return _cylc_install_cli(capsys, caplog)
def cylc_install_cli(capsys, caplog, generate_workflow_name):
return _cylc_install_cli(capsys, caplog, generate_workflow_name)


@pytest.fixture(scope='module')
def mod_cylc_install_cli(mod_capsys, mod_caplog):
return _cylc_install_cli(mod_capsys, mod_caplog)
def mod_cylc_install_cli(mod_capsys, mod_caplog, generate_workflow_name):
return _cylc_install_cli(mod_capsys, mod_caplog, generate_workflow_name)


@pytest.fixture
Expand Down
81 changes: 81 additions & 0 deletions tests/functional/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,84 @@ def test_basic(tmp_path):
assert Path(tmp_path / 'src/rose-suite.conf').read_text() == (
Path(tmp_path / 'dest/rose-suite.conf').read_text()
)


def test_CYLC_SYMLINKS_validate(monkeypatch, tmp_path, cylc_validate_cli):
"""We reload the global config after exporting env variables."""
# Setup global config:
global_conf = """#!jinja2
{% from "cylc.flow" import LOG %}
{% set cylc_symlinks = environ.get('CYLC_SYMLINKS', None) %}
{% do LOG.critical(cylc_symlinks) %}
"""
conf_path = tmp_path / 'conf'
conf_path.mkdir()
monkeypatch.setenv('CYLC_CONF_PATH', conf_path)

# Setup workflow config:
(conf_path / 'global.cylc').write_text(global_conf)
(tmp_path / 'rose-suite.conf').write_text(
'[env]\nCYLC_SYMLINKS="Foo"\n')
(tmp_path / 'flow.cylc').write_text("""
[scheduling]
initial cycle point = now
[[graph]]
R1 = x
[runtime]
[[x]]
""")

# Validate the config:
output = cylc_validate_cli(tmp_path)
assert output.ret == 0

# CYLC_SYMLINKS == None the first time the global.cylc
# is loaded and "Foo" the second time.
assert output.logging == 'None\n"Foo"'


def test_CYLC_SYMLINKS_install(monkeypatch, tmp_path, cylc_install_cli):
"""We reload the global config after exporting env variables."""
# Setup global config:
global_conf = (
'#!jinja2\n'
'[install]\n'
' [[symlink dirs]]\n'
' [[[localhost]]]\n'
'{% set cylc_symlinks = environ.get(\'CYLC_SYMLINKS\', None) %}\n'
'{% if cylc_symlinks == "foo" %}\n'
f'log = {str(tmp_path)}/foo\n'
'{% else %}\n'
f'log = {str(tmp_path)}/bar\n'
'{% endif %}\n'
)
glbl_conf_path = tmp_path / 'conf'
glbl_conf_path.mkdir()
(glbl_conf_path / 'global.cylc').write_text(global_conf)
monkeypatch.setenv('CYLC_CONF_PATH', glbl_conf_path)

# Setup workflow config:
(tmp_path / 'rose-suite.conf').write_text(
'[env]\nCYLC_SYMLINKS=foo\n')
(tmp_path / 'flow.cylc').write_text("""
[scheduling]
initial cycle point = now
[[graph]]
R1 = x
[runtime]
[[x]]
""")

# Install the config:
output = cylc_install_cli(tmp_path)
import sys
for i in output.logging.split('\n'):
print(i, file=sys.stderr)
assert output.ret == 0

# Assert symlink created back to test_path/foo:
expected_msg = (
f'Symlink created: {output.run_dir}/log -> '
f'{tmp_path}/foo/cylc-run/{output.id}/log'
)
assert expected_msg in output.logging.split('\n')[0]

0 comments on commit fa3611b

Please sign in to comment.