Skip to content

Commit

Permalink
Allow change of templating language when reinstalling
Browse files Browse the repository at this point in the history
- Move checks for multiple templating sections
  to before old and new configs are merged
- Algorithm merging previous and current config
  renames old template variables section if
  new install has changes it.
- Update tests for merge algorithm.
- Remove test that it's impossible to change
  templating languages on reinstall - it
  should not be.
  • Loading branch information
wxtim committed Oct 18, 2022
1 parent d909df7 commit 111dfa2
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 46 deletions.
7 changes: 7 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ creating a new release entry be sure to copy & paste the span tag with the
`actions:bind` attribute, which is used by a regex to find the text to be
updated. Only the first match gets replaced, so it's fine to leave the old
ones in. -->
## __cylc-rose-1.1.2 (<span actions:bind='release-date'>Upcoming</span>)__

### Fixes

[#192](https://github.com/cylc/cylc-rose/pull/192) - Fix bug where Cylc Rose would prevent change to template language on reinstall.


## __cylc-rose-1.1.1 (<span actions:bind='release-date'>Released 2022-09-14</span>)__

### Fixes
Expand Down
6 changes: 4 additions & 2 deletions cylc/rose/entry_points.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ def record_cylc_install_options(
# Leave now if there is nothing to do:
if not cli_config:
return False
# raise error if CLI config has multiple templating sections
identify_templating_section(cli_config)

# Construct path objects representing our target files.
(Path(rundir) / 'opt').mkdir(exist_ok=True)
Expand All @@ -195,6 +197,8 @@ def record_cylc_install_options(
conf_filepath.unlink()
else:
oldconfig = loader.load(str(conf_filepath))
# Check old config for clashing template variables sections.
identify_templating_section(oldconfig)
cli_config = merge_rose_cylc_suite_install_conf(
oldconfig, cli_config
)
Expand All @@ -211,7 +215,6 @@ def record_cylc_install_options(
]

cli_config.comments = [' This file records CLI Options.']
identify_templating_section(cli_config)
dumper.dump(cli_config, str(conf_filepath))

# Merge the opts section of the rose-suite.conf with those set by CLI:
Expand All @@ -221,7 +224,6 @@ def record_cylc_install_options(
rose_suite_conf = add_cylc_install_to_rose_conf_node_opts(
rose_suite_conf, cli_config
)
identify_templating_section(rose_suite_conf)
dumper(rose_suite_conf, rose_conf_filepath)

return cli_config, rose_suite_conf
Expand Down
20 changes: 19 additions & 1 deletion cylc/rose/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"""Cylc support for reading and interpreting ``rose-suite.conf`` workflow
configuration files.
"""

import itertools
import os
from pathlib import Path
import re
Expand All @@ -42,6 +42,9 @@
ROSE_ORIG_HOST_INSTALLED_OVERRIDE_STRING = (
' ROSE_ORIG_HOST set by cylc install.'
)
TEMPLATING_SECTIONS = [
'jinja2:suite.rc', 'empy:suite.rc', 'template variables']
JINJA2_SECTION, EMPY_SECTION, TV_SECTION = TEMPLATING_SECTIONS


class MultipleTemplatingEnginesError(Exception):
Expand Down Expand Up @@ -179,6 +182,10 @@ def get_rose_vars_from_config_node(config, config_node, environ):


def identify_templating_section(config_node):
"""Get the name of the templating section.
Can be used as a check for multiple templating sections.
"""
defined_sections = SECTIONS.intersection(set(config_node.value.keys()))
if len(defined_sections) > 1:
raise MultipleTemplatingEnginesError(
Expand Down Expand Up @@ -280,6 +287,8 @@ def merge_rose_cylc_suite_install_conf(old, new):
Opts are merged separately to allow special behaviour.
The rest is merged using ConfigNodeDiff.
If the template language has changed, use the new templating language.
Args:
old, new (ConfigNode):
Old and new nodes.
Expand All @@ -294,6 +303,14 @@ def merge_rose_cylc_suite_install_conf(old, new):
>>> merge_rose_cylc_suite_install_conf(old, new)['opts']
{'value': 'a b c d e', 'state': '', 'comments': []}
"""
# remove jinja2/empy:suite.rc from old if template variables in new
for before, after in itertools.permutations(TEMPLATING_SECTIONS, 2):
if new.value.get(after, '') and old.value.get(before, ''):
# Choosing not to warn if user downgrades here because
# other checks warn of old sections.
old.value[after] = old.value[before]
old.value.pop(before)

# Special treatement of opts key:
if 'opts' in old and 'opts' in new:
new_opts_str = f'{old["opts"].value} {new["opts"].value}'
Expand All @@ -306,6 +323,7 @@ def merge_rose_cylc_suite_install_conf(old, new):
diff.set_from_configs(old, new)
diff.delete_removed()
old.add(diff)

return old


Expand Down
35 changes: 0 additions & 35 deletions tests/functional/test_reinstall.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,38 +250,3 @@ def test_cylc_reinstall_run2(fixture_reinstall_flow2):
def test_cylc_reinstall_files2(fixture_reinstall_flow2, file_, expect):
fpath = fixture_reinstall_flow2['fixture_provide_flow']['flowpath']
assert (fpath / file_).read_text() == expect


def test_cylc_reinstall_fail_on_clashing_template_vars(tmp_path, request):
"""If you re-install with a different templating engine in suite.rc
reinstall should fail.
"""
(tmp_path / 'rose-suite.conf').write_text(
'[jinja2:suite.rc]\n'
'Primrose=\'Primula Vulgaris\'\n'
)
(tmp_path / 'flow.cylc').touch()
test_flow_name = f'cylc-rose-test-{str(uuid4())[:8]}'
install = subprocess.run(
[
'cylc', 'install', str(tmp_path), '--workflow-name',
test_flow_name, '--no-run-name'
]
)
assert install.returncode == 0
(tmp_path / 'rose-suite.conf').write_text(
'[empy:suite.rc]\n'
'Primrose=\'Primula Vulgaris\'\n'
)
reinstall = subprocess.run(
['cylc', 'reinstall', test_flow_name],
capture_output=True
)
assert reinstall.returncode != 0
assert (
'You should not define more than one templating section'
in reinstall.stderr.decode()
)
# Clean up run dir:
if not request.session.testsfailed:
shutil.rmtree(get_workflow_run_dir(test_flow_name))
29 changes: 21 additions & 8 deletions tests/unit/test_config_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import os
import pytest
from pytest import param

from types import SimpleNamespace
from io import StringIO
Expand All @@ -41,7 +42,6 @@
from metomi.rose.config import ConfigLoader


param = pytest.param
HOST = get_host()


Expand Down Expand Up @@ -534,24 +534,37 @@ def test_get_cli_opts_node(opt_confs, defines, rose_template_vars, expect):
'old, new, expect',
[
# An example with only opts:
('opts=a b c', 'opts=c d e', '\nopts=a b c d e\n'),
param(
'opts=a b c', 'opts=c d e', '\nopts=a b c d e\n',
id='only opts'
),
# An example with lots of options:
(
param(
'opts=A B\n[env]\nFOO=Whipton\n[jinja2:suite.rc]\nBAR=Heavitree',
'opts=B C\n[env]\nFOO=Pinhoe\n[jinja2:suite.rc]\nBAR=Broadclyst',
'opts=A B C\n[env]\nFOO=Pinhoe\n[jinja2:suite.rc]\nBAR=Broadclyst'
'opts=A B C\n[env]\nFOO=Pinhoe\n[jinja2:suite.rc]\nBAR=Broadclyst',
id='lots of options'
),
# An example with updated template variables:
param(
'opts=A B\n[env]\nFOO=Whipton\n[jinja2:suite.rc]\nBAR=Ottery',
'opts=B C\n[env]\nFOO=Pinhoe\n[template variables]\nBAR=Whipton',
'opts=A B C\n[env]\nFOO=Pinhoe\n[template variables]\nBAR=Whipton',
id='changed template vars'
),
# An example with no old opts:
(
param(
'',
'opts=A B\n[env]\nFOO=Whipton\n[jinja2:suite.rc]\nBAR=Heavitree',
'opts=A B\n[env]\nFOO=Whipton\n[jinja2:suite.rc]\nBAR=Heavitree'
'opts=A B\n[env]\nFOO=Whipton\n[jinja2:suite.rc]\nBAR=Heavitree',
id='no old options'
),
# An example with no new opts:
(
param(
'opts=A B\n[env]\nFOO=Whipton\n[jinja2:suite.rc]\nBAR=Heavitree',
'',
'opts=A B\n[env]\nFOO=Whipton\n[jinja2:suite.rc]\nBAR=Heavitree'
'opts=A B\n[env]\nFOO=Whipton\n[jinja2:suite.rc]\nBAR=Heavitree',
id='no new opts'
)
]
)
Expand Down

0 comments on commit 111dfa2

Please sign in to comment.