GRM-16: feat: add --force flag to grm remove for unreachable runners
- Add force parameter to RunnerManager.remove() — skips remote Ansible playbook and only removes the local registry entry - Add --force/-f CLI flag to grm remove command - Add translations for --force help text across all 5 languages - Add unit tests for force skip and CLI flag propagation - 118 tests, 100% coverage, pyright clean, ruff clean
This commit is contained in:
@@ -306,6 +306,12 @@ def status(
|
||||
type=click.Choice(["docker", "binary"]),
|
||||
help=_("Override deployment mode from registry"),
|
||||
)
|
||||
@click.option(
|
||||
"--force",
|
||||
"-f",
|
||||
is_flag=True,
|
||||
help=_("Skip remote cleanup and only remove the local registry entry"),
|
||||
)
|
||||
@_handle_errors("Remove failed: {error}")
|
||||
def remove(
|
||||
runner_name: str,
|
||||
@@ -314,6 +320,7 @@ def remove(
|
||||
key: str | None,
|
||||
token: str | None,
|
||||
mode: str | None,
|
||||
force: bool,
|
||||
ask_become_pass: bool,
|
||||
) -> None:
|
||||
manager = RunnerManager()
|
||||
@@ -326,6 +333,7 @@ def remove(
|
||||
gitea_url=os.getenv("GITEA_URL", ""),
|
||||
mode=mode,
|
||||
ask_become_pass=ask_become_pass,
|
||||
force=force,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -467,6 +467,13 @@ TRANSLATIONS: dict[str, dict[str, str]] = {
|
||||
"ru": "Удалить runner '{name}' из локального реестра",
|
||||
"zh": "从本地注册表移除 runner '{name}'",
|
||||
},
|
||||
"Skip remote cleanup and only remove the local registry entry": {
|
||||
"en": "Skip remote cleanup and only remove the local registry entry",
|
||||
"bg": "Пропуснете отдалеченото почистване и премахнете само локалния запис",
|
||||
"de": "Remote-Bereinigung überspringen und nur den lokalen Registrierungseintrag entfernen",
|
||||
"ru": "Пропустить удаленную очистку и удалить только локальную запись реестра",
|
||||
"zh": "跳过远程清理,仅删除本地注册表条目",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -240,8 +240,14 @@ class RunnerManager:
|
||||
gitea_url: str = "",
|
||||
mode: str | None = None,
|
||||
ask_become_pass: bool = False,
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
"""Remove a runner instance completely."""
|
||||
"""Remove a runner instance completely.
|
||||
|
||||
When *force* is ``True``, skip the remote Ansible playbook and
|
||||
only remove the local registry entry. Use this when the remote
|
||||
host is already gone or unreachable.
|
||||
"""
|
||||
if not gitea_url:
|
||||
raise AnsibleError(_("GITEA_URL must be set (or pass --url)"))
|
||||
actual_host, actual_user, actual_key, actual_mode = self._resolve_runner(name, host, user, key, mode)
|
||||
@@ -249,6 +255,7 @@ class RunnerManager:
|
||||
raise AnsibleError(_("GITEA_REGISTRATION_TOKEN must be set (or pass --token)"))
|
||||
with track_steps() as tracker:
|
||||
tracker.begin(_("Removing Gitea Runner {name} from {host}", name=name, host=actual_host))
|
||||
if not force:
|
||||
extra_vars = (
|
||||
f"runner_name={name} registration_token={token} gitea_url={gitea_url} runner_mode={actual_mode}"
|
||||
)
|
||||
|
||||
@@ -375,6 +375,27 @@ class TestCLI:
|
||||
gitea_url="https://git.example.com",
|
||||
mode=None,
|
||||
ask_become_pass=False,
|
||||
force=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
def test_remove_force_flag(self, mock_manager_class: MagicMock) -> None:
|
||||
mock_manager = MagicMock()
|
||||
mock_manager_class.return_value = mock_manager
|
||||
|
||||
runner = CliRunner(env={"GITEA_URL": "https://git.example.com"})
|
||||
result = runner.invoke(cli, ["remove", "r1", "--token", "tok", "--force"])
|
||||
assert result.exit_code == 0
|
||||
mock_manager.remove.assert_called_once_with(
|
||||
name="r1",
|
||||
host=None,
|
||||
user=None,
|
||||
key=None,
|
||||
token="tok",
|
||||
gitea_url="https://git.example.com",
|
||||
mode=None,
|
||||
ask_become_pass=False,
|
||||
force=True,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
|
||||
@@ -381,6 +381,17 @@ class TestRunnerManager:
|
||||
manager.remove("r1", token="tok")
|
||||
mock_registry.remove.assert_not_called()
|
||||
|
||||
def test_remove_force_skips_playbook(self) -> None:
|
||||
mock_registry = MagicMock()
|
||||
mock_registry.get.return_value = {"host": "host", "user": "user", "key": None, "mode": "docker"}
|
||||
manager = RunnerManager(registry=mock_registry)
|
||||
mock_executor = MagicMock()
|
||||
manager._executor = mock_executor
|
||||
|
||||
manager.remove("r1", token="tok", gitea_url="https://git.example.com", force=True)
|
||||
mock_executor.run.assert_not_called()
|
||||
mock_registry.remove.assert_called_once_with("r1")
|
||||
|
||||
def test_list_runners(self) -> None:
|
||||
mock_registry = MagicMock()
|
||||
mock_registry.list.return_value = {
|
||||
|
||||
Reference in New Issue
Block a user