GRM-18: feat: make --ask-become-pass the default behavior
- Change --ask-become-pass from opt-in to opt-out across all commands (install, update, start, stop, enable, disable, status, remove) - Use Click toggle pattern: --ask-become-pass/--no-ask-become-pass with default=True so users are always prompted for sudo unless they explicitly opt out - Update i18n translations for both help texts - Update all CLI tests to expect ask_become_pass=True as default and add test for --no-ask-become-pass - Update README: remove --ask-become-pass from examples, document --no-ask-become-pass for passwordless-sudo setups - 120 tests, 100% coverage, pyright clean, ruff clean
This commit is contained in:
@@ -26,7 +26,7 @@ By default, runners are deployed as **Docker containers** using the official `gi
|
||||
## Prerequisites
|
||||
|
||||
- **SSH key authentication** — The remote host must be reachable via SSH using the user specified with `--user` and the private key specified with `--key`. GRM uses Ansible under the hood, which connects to the target host over SSH to execute all installation and configuration tasks. Without valid SSH credentials, Ansible cannot establish a connection and the deployment will fail.
|
||||
- **Sudo access** — GRM requires root privileges on the remote host to install packages, create systemd services, and manage Docker. You will be prompted interactively for the sudo password. For automation or uninterrupted workflows, configure passwordless sudo on the remote host.
|
||||
- **Sudo access** — GRM requires root privileges on the remote host to install packages, create systemd services, and manage Docker. By default, you will be prompted interactively for the sudo password. For automation or uninterrupted workflows, configure passwordless sudo on the remote host and pass `--no-ask-become-pass`.
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -62,26 +62,26 @@ API checks, if enabled, are purely informational and do not affect pass/fail.
|
||||
|
||||
### Install a Runner
|
||||
|
||||
Using the CLI (you will be prompted for the sudo password interactively):
|
||||
Using the CLI (you will be prompted for the sudo password by default):
|
||||
|
||||
```bash
|
||||
# Docker mode (default) — deploys gitea_runner as a container
|
||||
grm install 192.168.1.10 --user ubuntu --key ~/.ssh/id_ed25519 --name prod-runner --ask-become-pass
|
||||
grm install 192.168.1.10 --user ubuntu --key ~/.ssh/id_ed25519 --name prod-runner
|
||||
|
||||
# Binary mode — downloads and installs the gitea_runner binary with systemd
|
||||
grm install 192.168.1.10 --user ubuntu --key ~/.ssh/id_ed25519 --name prod-runner --mode binary --ask-become-pass
|
||||
grm install 192.168.1.10 --user ubuntu --key ~/.ssh/id_ed25519 --name prod-runner --mode binary
|
||||
```
|
||||
|
||||
> **Automation tip:** Configure passwordless sudo on the remote host to skip the password prompt. This is recommended for CI/CD pipelines.
|
||||
> **Automation tip:** Configure passwordless sudo on the remote host and pass `--no-ask-become-pass` to skip the password prompt. This is recommended for CI/CD pipelines.
|
||||
|
||||
Using Make:
|
||||
|
||||
```bash
|
||||
# Docker mode (default)
|
||||
make install HOST=192.168.1.10 USER=ubuntu KEY=~/.ssh/id_ed25519 NAME=prod-runner ASK_BECOME_PASS=1
|
||||
make install HOST=192.168.1.10 USER=ubuntu KEY=~/.ssh/id_ed25519 NAME=prod-runner
|
||||
|
||||
# Binary mode
|
||||
make install HOST=192.168.1.10 USER=ubuntu KEY=~/.ssh/id_ed25519 NAME=prod-runner MODE=binary ASK_BECOME_PASS=1
|
||||
make install HOST=192.168.1.10 USER=ubuntu KEY=~/.ssh/id_ed25519 NAME=prod-runner MODE=binary
|
||||
```
|
||||
|
||||
### Runner Registry
|
||||
@@ -123,7 +123,7 @@ You can override any stored value by passing the corresponding flag:
|
||||
grm start prod-runner --host 192.168.1.11 --user root --mode binary
|
||||
```
|
||||
|
||||
> **Automation tip:** If the remote host has passwordless sudo configured, omit `--ask-become-pass`.
|
||||
> **Automation tip:** If the remote host has passwordless sudo configured, pass `--no-ask-become-pass`.
|
||||
|
||||
### Multiple Instances on the Same Host
|
||||
|
||||
@@ -131,8 +131,8 @@ Each runner instance is fully isolated with its own data directory and systemd s
|
||||
|
||||
```bash
|
||||
# Install two runners on the same host
|
||||
grm install 192.168.1.10 --user ubuntu --name workflow-runner --ask-become-pass
|
||||
grm install 192.168.1.10 --user ubuntu --name build-runner --mode binary --ask-become-pass
|
||||
grm install 192.168.1.10 --user ubuntu --name workflow-runner
|
||||
grm install 192.168.1.10 --user ubuntu --name build-runner --mode binary
|
||||
|
||||
# Manage them independently by name
|
||||
grm stop workflow-runner
|
||||
|
||||
@@ -20,7 +20,11 @@ load_dotenv(override=True)
|
||||
|
||||
def _runner_options(func: Callable[..., Any]) -> Callable[..., Any]:
|
||||
"""Apply common override options for registry-based lifecycle commands."""
|
||||
func = click.option("--ask-become-pass", is_flag=True, help=_("Prompt for sudo password"))(func)
|
||||
func = click.option(
|
||||
"--ask-become-pass/--no-ask-become-pass",
|
||||
default=True,
|
||||
help=_("Prompt for sudo password (default)"),
|
||||
)(func)
|
||||
func = click.option("--key", "-k", help=_("Override SSH key from registry"))(func)
|
||||
func = click.option("--user", "-u", help=_("Override user from registry"))(func)
|
||||
func = click.option("--host", help=_("Override host from registry"))(func)
|
||||
@@ -85,7 +89,11 @@ def cli() -> None:
|
||||
default=lambda: int(os.getenv("GITEA_INTEGRATION_RETRIES", "3")),
|
||||
help=_("Integration test API retries (default: 3, env: GITEA_INTEGRATION_RETRIES)"),
|
||||
)
|
||||
@click.option("--ask-become-pass", is_flag=True, help=_("Prompt for sudo password"))
|
||||
@click.option(
|
||||
"--ask-become-pass/--no-ask-become-pass",
|
||||
default=True,
|
||||
help=_("Prompt for sudo password (default)"),
|
||||
)
|
||||
def install(
|
||||
host: str,
|
||||
user: str,
|
||||
@@ -132,7 +140,11 @@ def install(
|
||||
default="docker",
|
||||
help=_("Gitea Runner deployment mode (default: docker)"),
|
||||
)
|
||||
@click.option("--ask-become-pass", is_flag=True, help=_("Prompt for sudo password"))
|
||||
@click.option(
|
||||
"--ask-become-pass/--no-ask-become-pass",
|
||||
default=True,
|
||||
help=_("Prompt for sudo password (default)"),
|
||||
)
|
||||
@_handle_errors("Update failed: {error}")
|
||||
def update(
|
||||
host: str,
|
||||
|
||||
@@ -135,12 +135,19 @@ TRANSLATIONS: dict[str, dict[str, str]] = {
|
||||
"ru": "Повторы API интеграционного теста (по умолчанию: 3, env: GITEA_INTEGRATION_RETRIES)",
|
||||
"zh": "集成测试 API 重试次数(默认: 3,环境变量: GITEA_INTEGRATION_RETRIES)",
|
||||
},
|
||||
"Prompt for sudo password": {
|
||||
"en": "Prompt for sudo password",
|
||||
"bg": "Подканване за sudo парола",
|
||||
"de": "Nach sudo-Passwort fragen",
|
||||
"ru": "Запросить пароль sudo",
|
||||
"zh": "提示输入 sudo 密码",
|
||||
"Prompt for sudo password (default)": {
|
||||
"en": "Prompt for sudo password (default)",
|
||||
"bg": "Подканване за sudo парола (по подразбиране)",
|
||||
"de": "Nach sudo-Passwort fragen (Standard)",
|
||||
"ru": "Запросить пароль sudo (по умолчанию)",
|
||||
"zh": "提示输入 sudo 密码(默认)",
|
||||
},
|
||||
"Do not prompt for sudo password": {
|
||||
"en": "Do not prompt for sudo password",
|
||||
"bg": "Без подканване за sudo парола",
|
||||
"de": "Nicht nach sudo-Passwort fragen",
|
||||
"ru": "Не запрашивать пароль sudo",
|
||||
"zh": "不提示输入 sudo 密码",
|
||||
},
|
||||
"Specific Gitea Runner version": {
|
||||
"en": "Specific Gitea Runner version",
|
||||
|
||||
+33
-12
@@ -22,6 +22,27 @@ class TestCLI:
|
||||
runner = CliRunner(env={"GITEA_URL": "https://git.example.com", "GITEA_ADMIN_TOKEN": ""})
|
||||
result = runner.invoke(cli, ["install", "host1", "--user", "ubuntu", "--token", "tok"])
|
||||
assert result.exit_code == 0
|
||||
mock_manager.install.assert_called_once_with(
|
||||
host="host1",
|
||||
user="ubuntu",
|
||||
key=None,
|
||||
name=None,
|
||||
token="tok",
|
||||
gitea_url="https://git.example.com",
|
||||
mode="docker",
|
||||
admin_token="",
|
||||
integration_retries=3,
|
||||
ask_become_pass=True,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
def test_install_no_ask_become_pass(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", "GITEA_ADMIN_TOKEN": ""})
|
||||
result = runner.invoke(cli, ["install", "host1", "--user", "ubuntu", "--token", "tok", "--no-ask-become-pass"])
|
||||
assert result.exit_code == 0
|
||||
mock_manager.install.assert_called_once_with(
|
||||
host="host1",
|
||||
user="ubuntu",
|
||||
@@ -95,7 +116,7 @@ class TestCLI:
|
||||
mode="docker",
|
||||
admin_token="",
|
||||
integration_retries=3,
|
||||
ask_become_pass=False,
|
||||
ask_become_pass=True,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -137,7 +158,7 @@ class TestCLI:
|
||||
mode="binary",
|
||||
admin_token="",
|
||||
integration_retries=3,
|
||||
ask_become_pass=False,
|
||||
ask_become_pass=True,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -179,7 +200,7 @@ class TestCLI:
|
||||
key="/key",
|
||||
version="v0.2.0",
|
||||
mode="docker",
|
||||
ask_become_pass=False,
|
||||
ask_become_pass=True,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -213,7 +234,7 @@ class TestCLI:
|
||||
key=None,
|
||||
version=None,
|
||||
mode="binary",
|
||||
ask_become_pass=False,
|
||||
ask_become_pass=True,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -243,7 +264,7 @@ class TestCLI:
|
||||
user=None,
|
||||
key=None,
|
||||
mode=None,
|
||||
ask_become_pass=False,
|
||||
ask_become_pass=True,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -260,7 +281,7 @@ class TestCLI:
|
||||
user="newuser",
|
||||
key=None,
|
||||
mode="binary",
|
||||
ask_become_pass=False,
|
||||
ask_become_pass=True,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -276,7 +297,7 @@ class TestCLI:
|
||||
host=None,
|
||||
user=None,
|
||||
key=None,
|
||||
ask_become_pass=False,
|
||||
ask_become_pass=True,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -292,7 +313,7 @@ class TestCLI:
|
||||
host=None,
|
||||
user=None,
|
||||
key=None,
|
||||
ask_become_pass=False,
|
||||
ask_become_pass=True,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -311,7 +332,7 @@ class TestCLI:
|
||||
token="tok",
|
||||
gitea_url="https://git.example.com",
|
||||
mode=None,
|
||||
ask_become_pass=False,
|
||||
ask_become_pass=True,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -355,7 +376,7 @@ class TestCLI:
|
||||
user=None,
|
||||
key=None,
|
||||
mode=None,
|
||||
ask_become_pass=False,
|
||||
ask_become_pass=True,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -374,7 +395,7 @@ class TestCLI:
|
||||
token="tok",
|
||||
gitea_url="https://git.example.com",
|
||||
mode=None,
|
||||
ask_become_pass=False,
|
||||
ask_become_pass=True,
|
||||
force=False,
|
||||
)
|
||||
|
||||
@@ -394,7 +415,7 @@ class TestCLI:
|
||||
token="tok",
|
||||
gitea_url="https://git.example.com",
|
||||
mode=None,
|
||||
ask_become_pass=False,
|
||||
ask_become_pass=True,
|
||||
force=True,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user