-
Notifications
You must be signed in to change notification settings - Fork 96
Fix reinstall not detecting changed permissions by itself #6658
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
03e8bdb
9db9521
50421d7
144600b
3f37872
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Fixed `cylc reinstall` not picking up file permissions changes. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,33 +68,43 @@ | |
| "cylc install" even if present in the source directory. | ||
| """ | ||
|
|
||
| from functools import partial | ||
| from pathlib import Path | ||
| import re | ||
| import sys | ||
| from typing import Optional, TYPE_CHECKING, List, Callable | ||
| from functools import partial | ||
| from typing import ( | ||
| TYPE_CHECKING, | ||
| Callable, | ||
| List, | ||
| Optional, | ||
| ) | ||
|
|
||
| from ansimarkup import parse as cparse | ||
|
|
||
| from cylc.flow.exceptions import ( | ||
| ServiceFileError, | ||
| WorkflowFilesError, | ||
| ) | ||
| from cylc.flow.install import ( | ||
| reinstall_workflow, | ||
| ) | ||
| import cylc.flow.flags | ||
| from cylc.flow.install import reinstall_workflow | ||
| from cylc.flow.network.multi import call_multi | ||
| from cylc.flow.option_parsers import ( | ||
| ID_MULTI_ARG_DOC, | ||
| CylcOptionParser as COP, | ||
| OptionSettings, | ||
| ID_MULTI_ARG_DOC | ||
| ) | ||
| from cylc.flow.pathutil import get_workflow_run_dir | ||
| from cylc.flow.plugins import run_plugins_async | ||
| from cylc.flow.terminal import ( | ||
| DIM, | ||
| cli_function, | ||
| is_terminal, | ||
| ) | ||
| from cylc.flow.workflow_files import ( | ||
| get_workflow_source_dir, | ||
| load_contact_file, | ||
| ) | ||
| from cylc.flow.terminal import cli_function, DIM, is_terminal | ||
|
|
||
|
|
||
| if TYPE_CHECKING: | ||
| from optparse import Values | ||
|
|
@@ -269,9 +279,8 @@ async def reinstall( | |
| update anything, else returns True. | ||
|
|
||
| """ | ||
| # run pre_configure plugins | ||
| if not dry_run: | ||
| # don't run plugins in dry-mode | ||
| # run pre_configure plugins | ||
| async for _entry_point, _plugin_result in run_plugins_async( | ||
| 'cylc.pre_configure', | ||
| srcdir=src_dir, | ||
|
|
@@ -287,18 +296,17 @@ async def reinstall( | |
| dry_run=dry_run, | ||
| ) | ||
|
|
||
| # display changes | ||
| if dry_run: | ||
| if not stdout or stdout == 'send ./': | ||
| # no rsync output == no changes => exit | ||
| # display changes | ||
| changes = format_reinstall_output(stdout) | ||
| if not changes: | ||
| return False | ||
|
|
||
| # display rsync output | ||
| write('\n'.join(format_rsync_out(stdout)), file=sys.stderr) | ||
| write('\n'.join(changes), file=sys.stdout) | ||
|
|
||
| # run post_install plugins | ||
| if not dry_run: | ||
| # don't run plugins in dry-mode | ||
| # run post_install plugins | ||
| async for _entry_point, _plugin_result in run_plugins_async( | ||
| 'cylc.post_install', | ||
| srcdir=src_dir, | ||
|
|
@@ -311,15 +319,15 @@ async def reinstall( | |
| return True | ||
|
|
||
|
|
||
| def format_rsync_out(out: str) -> List[str]: | ||
| def format_reinstall_output(out: str) -> List[str]: | ||
| r"""Format rsync stdout for presenting to users. | ||
|
|
||
| Note: Output formats of different rsync implementations may differ so keep | ||
| this code simple and robust. | ||
|
|
||
| Example: | ||
| >>> format_rsync_out( | ||
| ... 'send foo\ndel. bar\nbaz' | ||
| >>> format_reinstall_output( | ||
| ... '>f+++++++++ send foo\n*deleting del. bar\nbaz' | ||
| ... '\ncannot delete non-empty directory: opt' | ||
| ... ) == [ | ||
| ... cparse('<green>send foo</green>'), | ||
|
|
@@ -331,20 +339,23 @@ def format_rsync_out(out: str) -> List[str]: | |
| """ | ||
| lines = [] | ||
| for line in out.splitlines(): | ||
| if line[0:4] == 'send': | ||
| # file added or updated | ||
| lines.append(cparse(f'<green>{line}</green>')) | ||
| elif line[0:4] == 'del.': | ||
| # file deleted | ||
| lines.append(cparse(f'<red>{line}</red>')) | ||
| elif line == 'cannot delete non-empty directory: opt': | ||
| # These "cannot delete non-empty directory" messages can arise | ||
| # as a result of excluding files within sub-directories. | ||
| # This opt dir message is likely to occur when a rose-suit.conf | ||
| if not line or line.startswith('cannot delete non-empty directory:'): | ||
| # This can arise as a result of deleting a subdir when we are | ||
| # excluding files within the subdir. | ||
| # This is likely to occur for the opt dir when a rose-suite.conf | ||
| # file is present. | ||
| # Skip this line as nothing will happen to this dir. | ||
| continue | ||
| match = re.match(r'^(.{11}) (send|del\.) (.*)$', line) | ||
| if match: | ||
| summary, operation, file = match.groups() | ||
wxtim marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| color = 'green' if operation == 'send' else 'red' | ||
| formatted_line = f"<{color}>{operation} {file}</{color}>" | ||
| if cylc.flow.flags.verbosity > 0: | ||
| formatted_line = f"<{DIM}>{summary}</{DIM}> {formatted_line}" | ||
| lines.append(cparse(formatted_line)) | ||
| else: | ||
| # other uncategorised log line | ||
| # shouldn't happen; tolerate unknown rsync implementation? | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you don't think it should happen, then perhaps a message warning that this is an unexpected rsync implementation, please contact the dev team might be appropriate.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are multiple potential causes of this:
We just need to keep our scripts as robust to output changes as possible. No need to bother the user about it. |
||
| lines.append(line) | ||
| return lines | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # THIS FILE IS PART OF THE CYLC WORKFLOW ENGINE. | ||
| # Copyright (C) NIWA & British Crown (Met Office) & Contributors. | ||
| # | ||
| # This program is free software: you can redistribute it and/or modify | ||
| # it under the terms of the GNU General Public License as published by | ||
| # the Free Software Foundation, either version 3 of the License, or | ||
| # (at your option) any later version. | ||
| # | ||
| # This program is distributed in the hope that it will be useful, | ||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| # GNU General Public License for more details. | ||
| # | ||
| # You should have received a copy of the GNU General Public License | ||
| # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
|
||
| from textwrap import dedent | ||
|
|
||
| from ansimarkup import parse as cparse | ||
| import pytest | ||
|
|
||
| from cylc.flow.scripts.reinstall import format_reinstall_output | ||
| from cylc.flow.terminal import DIM | ||
|
|
||
|
|
||
| @pytest.mark.parametrize('verbose', [True, False]) | ||
| def test_format_reinstall_output(verbose, monkeypatch: pytest.MonkeyPatch): | ||
| """It should: | ||
| - colorize the output | ||
| - remove the itemized changes summary if not in verbose mode | ||
| - remove the "cannot delete non-empty directory" message | ||
| """ | ||
| output = dedent(""" | ||
| *deleting del. Cloud.jpg | ||
| >f+++++++++ send cloud.jpg | ||
| .f...p..... send foo | ||
| >fcsTp..... send bar | ||
| cannot delete non-empty directory: scarf | ||
| >f+++++++++ send meow.txt | ||
| cL+++++++++ send garage -> foo | ||
| """).strip() | ||
| expected = [ | ||
| f"<{DIM}>*deleting </{DIM}> <red>del. Cloud.jpg</red>", | ||
| f"<{DIM}>>f+++++++++</{DIM}> <green>send cloud.jpg</green>", | ||
| f"<{DIM}>.f...p.....</{DIM}> <green>send foo</green>", | ||
| f"<{DIM}>>fcsTp.....</{DIM}> <green>send bar</green>", | ||
| f"<{DIM}>>f+++++++++</{DIM}> <green>send meow.txt</green>", | ||
| f"<{DIM}>cL+++++++++</{DIM}> <green>send garage -> foo</green>", | ||
| ] | ||
| if verbose: | ||
| monkeypatch.setattr('cylc.flow.flags.verbosity', 1) | ||
| else: | ||
| # itemized changes summary should not be in output | ||
| shift = len(f'<{DIM}></{DIM}> ') + 11 | ||
| expected = [i[shift:] for i in expected] | ||
| assert format_reinstall_output(output) == [cparse(i) for i in expected] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seemed to be duplicating the work of
format_rsync_output(), but removing allcannot delete non-empty directory:lines, not just theoptdirUh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The point of the
+++prefix/suffix will have been to separate intendedrsyncoutput from any unexpected "junk" that might be output by the command / shell, ensuring that we only display what we need, even if the output differs from expectation in other ways.Why did you remove this? Just didn't like the look of it?
Remember that this code is used for more than just the dry-run check.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the context I could gather in #5125, the only unwanted output that was being targeted was
cannot delete non-empty directory:. This is implemented informat_reinstall_output()There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This
rsynccommand doesn't just apply to the dry-run check (which is passed throughformat_rsync_output). And this prefix stripped out other potential output.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cylc-flow/cylc/flow/install.py
Lines 226 to 227 in 48d3d38
is at odds with
cylc-flow/cylc/flow/scripts/reinstall.py
Lines 346 to 348 in 48d3d38
The former seems to have been implemented to cover
cannot delete non-empty directory:, there was no mention of any other potential rsync output in #5125. And the latter seems to have been implemented to deliberately include other potential rsync output in case the rsync implementation results in non-standard output format.Hence I am not sure we should be stripping other potential rsync output.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not at odds.
+++prefix ensures that only the intended output lines get through.elsehandles intended output lines that don't matchsendordel..Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cannot delete non-empty directory:does not have+++pre/suffixed to it (it is orthogonal to the%o %n%Loutput format). It is always being stripped. Only lines that matchsendordel.have+++pre/suffixed anyway.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only case I can think of where some other potential output would make it through both bits of code is if the
%ooutput format resulted in something other thansendordel.. But that seems unlikelyThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#6954