GRM-16: test: add molecule lifecycle scenarios, integration tests, and docs
- Add multi-instance molecule scenario verifying isolated data/config dirs - Add lifecycle molecule scenario testing stop/disable/enable/start sequence - Update default and binary verify playbooks for template unit assertions - Add integration tests for full CLI lifecycle and multi-instance support - Add pytest integration marker and --no-cov Makefile target - Update README with lifecycle commands, multi-instance examples, and architecture - Update Makefile with start/stop/enable/disable/status/remove targets - Update .env.example with GRM_LANG documentation
This commit is contained in:
@@ -23,3 +23,7 @@ GITEA_REGISTRATION_TOKEN=your-registration-token
|
|||||||
|
|
||||||
# Default SSH private key path (optional, overrides --key)
|
# Default SSH private key path (optional, overrides --key)
|
||||||
# GITEA_RUNNER_KEY=~/.ssh/id_ed25519
|
# GITEA_RUNNER_KEY=~/.ssh/id_ed25519
|
||||||
|
|
||||||
|
# UI language for GRM console messages (optional, default: en)
|
||||||
|
# Supported: en, bg, de, ru, zh
|
||||||
|
# GRM_LANG=en
|
||||||
|
|||||||
@@ -39,6 +39,30 @@ update:
|
|||||||
@if [ -z "$(HOST)" ]; then echo "HOST is required. Example: make update HOST=192.168.1.10"; exit 1; fi
|
@if [ -z "$(HOST)" ]; then echo "HOST is required. Example: make update HOST=192.168.1.10"; exit 1; fi
|
||||||
$(BIN)/grm update $(HOST) $(if $(USER),--user $(USER),) $(if $(KEY),--key $(KEY),) $(if $(VERSION),--version $(VERSION),) $(if $(MODE),--mode $(MODE),) $(if $(ASK_BECOME_PASS),--ask-become-pass,)
|
$(BIN)/grm update $(HOST) $(if $(USER),--user $(USER),) $(if $(KEY),--key $(KEY),) $(if $(VERSION),--version $(VERSION),) $(if $(MODE),--mode $(MODE),) $(if $(ASK_BECOME_PASS),--ask-become-pass,)
|
||||||
|
|
||||||
|
start:
|
||||||
|
@if [ -z "$(HOST)" ]; then echo "HOST is required. Example: make start HOST=192.168.1.10"; exit 1; fi
|
||||||
|
$(BIN)/grm start $(HOST) $(if $(USER),--user $(USER),) $(if $(NAME),--name $(NAME),) $(if $(MODE),--mode $(MODE),) $(if $(ASK_BECOME_PASS),--ask-become-pass,)
|
||||||
|
|
||||||
|
stop:
|
||||||
|
@if [ -z "$(HOST)" ]; then echo "HOST is required. Example: make stop HOST=192.168.1.10"; exit 1; fi
|
||||||
|
$(BIN)/grm stop $(HOST) $(if $(USER),--user $(USER),) $(if $(NAME),--name $(NAME),) $(if $(ASK_BECOME_PASS),--ask-become-pass,)
|
||||||
|
|
||||||
|
enable:
|
||||||
|
@if [ -z "$(HOST)" ]; then echo "HOST is required. Example: make enable HOST=192.168.1.10"; exit 1; fi
|
||||||
|
$(BIN)/grm enable $(HOST) $(if $(USER),--user $(USER),) $(if $(NAME),--name $(NAME),) $(if $(ASK_BECOME_PASS),--ask-become-pass,)
|
||||||
|
|
||||||
|
disable:
|
||||||
|
@if [ -z "$(HOST)" ]; then echo "HOST is required. Example: make disable HOST=192.168.1.10"; exit 1; fi
|
||||||
|
$(BIN)/grm disable $(HOST) $(if $(USER),--user $(USER),) $(if $(NAME),--name $(NAME),) $(if $(TOKEN),--token $(TOKEN),) $(if $(MODE),--mode $(MODE),) $(if $(ASK_BECOME_PASS),--ask-become-pass,)
|
||||||
|
|
||||||
|
status:
|
||||||
|
@if [ -z "$(HOST)" ]; then echo "HOST is required. Example: make status HOST=192.168.1.10"; exit 1; fi
|
||||||
|
$(BIN)/grm status $(HOST) $(if $(USER),--user $(USER),) $(if $(NAME),--name $(NAME),) $(if $(MODE),--mode $(MODE),) $(if $(ASK_BECOME_PASS),--ask-become-pass,)
|
||||||
|
|
||||||
|
remove:
|
||||||
|
@if [ -z "$(HOST)" ]; then echo "HOST is required. Example: make remove HOST=192.168.1.10"; exit 1; fi
|
||||||
|
$(BIN)/grm remove $(HOST) $(if $(USER),--user $(USER),) $(if $(NAME),--name $(NAME),) $(if $(TOKEN),--token $(TOKEN),) $(if $(MODE),--mode $(MODE),) $(if $(ASK_BECOME_PASS),--ask-become-pass,)
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
$(BIN)/ruff check src/ tests/
|
$(BIN)/ruff check src/ tests/
|
||||||
$(BIN)/ruff format --check src/ tests/
|
$(BIN)/ruff format --check src/ tests/
|
||||||
@@ -55,6 +79,9 @@ lint-all: lint ansible-lint makefile-lint
|
|||||||
test-unit:
|
test-unit:
|
||||||
$(BIN)/pytest tests/unit/ -v
|
$(BIN)/pytest tests/unit/ -v
|
||||||
|
|
||||||
|
test-integration:
|
||||||
|
$(BIN)/pytest tests/integration/ -v --no-cov
|
||||||
|
|
||||||
pytest-cov:
|
pytest-cov:
|
||||||
$(BIN)/pytest tests/unit/ -v --cov=src/gitea_runner_manager --cov-report=term-missing --cov-fail-under=100
|
$(BIN)/pytest tests/unit/ -v --cov=src/gitea_runner_manager --cov-report=term-missing --cov-fail-under=100
|
||||||
|
|
||||||
@@ -65,6 +92,8 @@ MOLECULE_BASE := cd ansible/roles/gitea-runner && ANSIBLE_ALLOW_BROKEN_CONDITION
|
|||||||
molecule:
|
molecule:
|
||||||
$(MOLECULE_BASE) test
|
$(MOLECULE_BASE) test
|
||||||
$(MOLECULE_BASE) test -s binary
|
$(MOLECULE_BASE) test -s binary
|
||||||
|
$(MOLECULE_BASE) test -s multi-instance
|
||||||
|
$(MOLECULE_BASE) test -s lifecycle
|
||||||
|
|
||||||
# Docker mode tests for all platforms (sequential; override env vars for CI matrix parallelisation)
|
# Docker mode tests for all platforms (sequential; override env vars for CI matrix parallelisation)
|
||||||
molecule-docker:
|
molecule-docker:
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ By default, runners are deployed as **Docker containers** using the official `gi
|
|||||||
- **Secure** — no hardcoded secrets, uses scoped tokens.
|
- **Secure** — no hardcoded secrets, uses scoped tokens.
|
||||||
- **Idempotent** — can be run multiple times safely.
|
- **Idempotent** — can be run multiple times safely.
|
||||||
- **Flexible** — accepts a plain IP address or hostname, and allows specifying the SSH user and private key.
|
- **Flexible** — accepts a plain IP address or hostname, and allows specifying the SSH user and private key.
|
||||||
|
- **Lifecycle management** — start, stop, enable, disable, status, and remove runners via CLI.
|
||||||
|
- **Multi-instance** — run multiple isolated runners on the same host, each with its own data directory and systemd service.
|
||||||
|
- **Systemd-managed** — both Docker and binary modes run under systemd template units (`gitea-runner@<name>.service`).
|
||||||
|
|
||||||
## Supported Operating Systems
|
## Supported Operating Systems
|
||||||
|
|
||||||
@@ -22,7 +25,7 @@ By default, runners are deployed as **Docker containers** using the official `gi
|
|||||||
## Prerequisites
|
## 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.
|
- **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 privileges** — The specified user must have passwordless or password-prompted (`--ask-become-pass`) sudo access on the remote host. Runner installation requires root privileges for tasks such as installing packages, creating systemd services, and managing Docker.
|
- **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.
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
@@ -58,33 +61,66 @@ API checks, if enabled, are purely informational and do not affect pass/fail.
|
|||||||
|
|
||||||
### Install a Runner
|
### Install a Runner
|
||||||
|
|
||||||
Using the CLI:
|
Using the CLI (you will be prompted for the sudo password interactively):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Docker mode (default) — deploys gitea_runner as a container
|
# Docker mode (default) — deploys gitea_runner as a container
|
||||||
grm install 192.168.1.10 --user ubuntu --key ~/.ssh/id_ed25519 --name prod-runner
|
grm install 192.168.1.10 --user ubuntu --key ~/.ssh/id_ed25519 --name prod-runner --ask-become-pass
|
||||||
|
|
||||||
# Binary mode — downloads and installs the gitea_runner binary with systemd
|
# 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
|
grm install 192.168.1.10 --user ubuntu --key ~/.ssh/id_ed25519 --name prod-runner --mode binary --ask-become-pass
|
||||||
```
|
```
|
||||||
|
|
||||||
If the remote user requires a sudo password:
|
> **Automation tip:** Configure passwordless sudo on the remote host to skip the password prompt. This is recommended for CI/CD pipelines.
|
||||||
|
|
||||||
```bash
|
|
||||||
grm install 192.168.1.10 --user ubuntu --ask-become-pass
|
|
||||||
```
|
|
||||||
|
|
||||||
Using Make:
|
Using Make:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Docker mode (default)
|
# Docker mode (default)
|
||||||
make install HOST=192.168.1.10 USER=ubuntu KEY=~/.ssh/id_ed25519 NAME=prod-runner
|
make install HOST=192.168.1.10 USER=ubuntu KEY=~/.ssh/id_ed25519 NAME=prod-runner ASK_BECOME_PASS=1
|
||||||
|
|
||||||
# Binary mode
|
# Binary mode
|
||||||
make install HOST=192.168.1.10 USER=ubuntu KEY=~/.ssh/id_ed25519 NAME=prod-runner MODE=binary
|
make install HOST=192.168.1.10 USER=ubuntu KEY=~/.ssh/id_ed25519 NAME=prod-runner MODE=binary ASK_BECOME_PASS=1
|
||||||
|
```
|
||||||
|
|
||||||
# With sudo password prompt:
|
### Manage Runner Lifecycle
|
||||||
make install HOST=192.168.1.10 USER=ubuntu ASK_BECOME_PASS=1
|
|
||||||
|
GRM provides commands to manage runners after installation. All commands below prompt for the sudo password interactively:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start a runner (registers if .runner file is missing)
|
||||||
|
grm start 192.168.1.10 --user ubuntu --name prod-runner --ask-become-pass
|
||||||
|
|
||||||
|
# Stop a runner (graceful stop, no deregistration)
|
||||||
|
grm stop 192.168.1.10 --user ubuntu --name prod-runner --ask-become-pass
|
||||||
|
|
||||||
|
# Enable a runner to start on boot
|
||||||
|
grm enable 192.168.1.10 --user ubuntu --name prod-runner --ask-become-pass
|
||||||
|
|
||||||
|
# Disable a runner (stops, deregisters from Gitea, and disables systemd)
|
||||||
|
grm disable 192.168.1.10 --user ubuntu --name prod-runner --token <token> --ask-become-pass
|
||||||
|
|
||||||
|
# Check runner status
|
||||||
|
grm status 192.168.1.10 --user ubuntu --name prod-runner --ask-become-pass
|
||||||
|
|
||||||
|
# Remove a runner completely (stops, disables, deregisters, removes files)
|
||||||
|
grm remove 192.168.1.10 --user ubuntu --name prod-runner --token <token> --ask-become-pass
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Automation tip:** If the remote host has passwordless sudo configured, omit `--ask-become-pass`.
|
||||||
|
|
||||||
|
### Multiple Instances on the Same Host
|
||||||
|
|
||||||
|
Each runner instance is fully isolated with its own data directory and systemd service:
|
||||||
|
|
||||||
|
```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
|
||||||
|
|
||||||
|
# Manage them independently
|
||||||
|
grm stop 192.168.1.10 --user ubuntu --name workflow-runner --ask-become-pass
|
||||||
|
grm status 192.168.1.10 --user ubuntu --name build-runner --ask-become-pass
|
||||||
```
|
```
|
||||||
|
|
||||||
### Verify Runner
|
### Verify Runner
|
||||||
@@ -101,8 +137,8 @@ Optional: If `GITEA_ADMIN_TOKEN` is set, the installer will also query the Gitea
|
|||||||
### View Logs
|
### View Logs
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Binary mode logs
|
# Binary mode logs (via systemd template unit)
|
||||||
sudo journalctl -u gitea-runner-<name> -f
|
sudo journalctl -u gitea-runner@<name> -f
|
||||||
|
|
||||||
# Docker mode logs
|
# Docker mode logs
|
||||||
docker logs gitea-runner-<name> -f
|
docker logs gitea-runner-<name> -f
|
||||||
@@ -112,8 +148,8 @@ docker logs gitea-runner-<name> -f
|
|||||||
|
|
||||||
GRM consists of two layers:
|
GRM consists of two layers:
|
||||||
|
|
||||||
1. **Python CLI** (`src/gitea_runner_manager/`) — built with Click, handles argument parsing, environment loading, and delegates to Ansible via the `ansible-playbook` subprocess.
|
1. **Python CLI** (`src/gitea_runner_manager/`) — built with Click, handles argument parsing, environment loading, i18n translations, and delegates to Ansible via the `ansible-playbook` subprocess.
|
||||||
2. **Ansible Role** (`ansible/roles/gitea-runner/`) — idempotent role that installs Docker, configures the runner (binary or container), creates systemd services, and registers the runner with Gitea.
|
2. **Ansible Role** (`ansible/roles/gitea-runner/`) — idempotent role that installs Docker, configures the runner (binary or container), creates systemd template units, and registers the runner with Gitea.
|
||||||
|
|
||||||
```
|
```
|
||||||
grm install <host>
|
grm install <host>
|
||||||
@@ -123,10 +159,18 @@ grm install <host>
|
|||||||
├── docker.yml (Docker installation)
|
├── docker.yml (Docker installation)
|
||||||
├── docker_mode.yml (container deployment)
|
├── docker_mode.yml (container deployment)
|
||||||
├── binary_mode.yml (binary + systemd deployment)
|
├── binary_mode.yml (binary + systemd deployment)
|
||||||
|
├── service.yml (systemd template unit)
|
||||||
|
├── deregister.yml (deregistration from Gitea)
|
||||||
├── prune.yml (Docker prune timer)
|
├── prune.yml (Docker prune timer)
|
||||||
└── integration_test.yml (validate online status)
|
└── integration_test.yml (validate online status)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Both Docker and binary modes run under a single systemd template unit (`gitea-runner@.service`), instantiated per runner name (e.g., `gitea-runner@prod-runner.service`). Each instance has fully isolated directories:
|
||||||
|
|
||||||
|
- **Data**: `/var/lib/gitea-runner/<name>/`
|
||||||
|
- **Config**: `/etc/gitea-runner/<name>/` (binary mode)
|
||||||
|
- **Service**: `gitea-runner@<name>.service`
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
All tunable values are exposed as Ansible variables in `ansible/roles/gitea-runner/defaults/main.yml`:
|
All tunable values are exposed as Ansible variables in `ansible/roles/gitea-runner/defaults/main.yml`:
|
||||||
@@ -138,8 +182,10 @@ All tunable values are exposed as Ansible variables in `ansible/roles/gitea-runn
|
|||||||
| `gitea_runner_docker_image` | `gitea/runner` | Docker image name |
|
| `gitea_runner_docker_image` | `gitea/runner` | Docker image name |
|
||||||
| `runner_labels` | `ubuntu-latest:docker://runner-images:ubuntu-22.04` | Runner labels |
|
| `runner_labels` | `ubuntu-latest:docker://runner-images:ubuntu-22.04` | Runner labels |
|
||||||
| `skip_runner_registration` | `false` | Skip API registration (useful for tests) |
|
| `skip_runner_registration` | `false` | Skip API registration (useful for tests) |
|
||||||
| `gitea_runner_data_dir` | `/var/lib/gitea-runner` | Runtime data directory |
|
| `gitea_runner_base_data_dir` | `/var/lib/gitea-runner` | Base data directory (instance-scoped) |
|
||||||
| `gitea_runner_config_dir` | `/etc/gitea-runner` | Config directory (binary mode) |
|
| `gitea_runner_base_config_dir` | `/etc/gitea-runner` | Base config directory (instance-scoped) |
|
||||||
|
| `gitea_runner_data_dir` | `{{ base }}/{{ runner_name }}` | Runtime data directory per instance |
|
||||||
|
| `gitea_runner_config_dir` | `{{ base }}/{{ runner_name }}` | Config directory per instance |
|
||||||
| `gitea_runner_binary_path` | `/usr/local/bin/gitea_runner` | Binary install path |
|
| `gitea_runner_binary_path` | `/usr/local/bin/gitea_runner` | Binary install path |
|
||||||
| `gitea_runner_prune_until` | `24h` | Prune resources older than this |
|
| `gitea_runner_prune_until` | `24h` | Prune resources older than this |
|
||||||
| `gitea_runner_prune_schedule` | `daily` | systemd timer schedule |
|
| `gitea_runner_prune_schedule` | `daily` | systemd timer schedule |
|
||||||
@@ -149,6 +195,7 @@ All tunable values are exposed as Ansible variables in `ansible/roles/gitea-runn
|
|||||||
| `gitea_runner_log_level` | `info` | Runner log level |
|
| `gitea_runner_log_level` | `info` | Runner log level |
|
||||||
| `gitea_runner_container_label` | `gitea-runner=true` | Container label |
|
| `gitea_runner_container_label` | `gitea-runner=true` | Container label |
|
||||||
| `docker_gpg_key_path` | `/etc/apt/keyrings/docker.asc` | Docker GPG key path |
|
| `docker_gpg_key_path` | `/etc/apt/keyrings/docker.asc` | Docker GPG key path |
|
||||||
|
| `GRM_LANG` | `en` | CLI language: `en`, `bg`, `de`, `ru`, `zh` |
|
||||||
|
|
||||||
Override any variable by passing it to the CLI with `--extra-vars` or by setting it in your Ansible inventory.
|
Override any variable by passing it to the CLI with `--extra-vars` or by setting it in your Ansible inventory.
|
||||||
|
|
||||||
@@ -161,6 +208,8 @@ Override any variable by passing it to the CLI with `--extra-vars` or by setting
|
|||||||
├── src/gitea_runner_manager/ # Python CLI source
|
├── src/gitea_runner_manager/ # Python CLI source
|
||||||
│ ├── cli.py # Click commands
|
│ ├── cli.py # Click commands
|
||||||
│ ├── runner_manager.py # Ansible orchestration
|
│ ├── runner_manager.py # Ansible orchestration
|
||||||
|
│ ├── executor.py # Ansible subprocess execution
|
||||||
|
│ ├── i18n.py # Translations (en, bg, de, ru, zh)
|
||||||
│ └── exceptions.py # Custom exceptions
|
│ └── exceptions.py # Custom exceptions
|
||||||
├── ansible/
|
├── ansible/
|
||||||
│ ├── roles/gitea-runner/ # Main Ansible role
|
│ ├── roles/gitea-runner/ # Main Ansible role
|
||||||
@@ -169,8 +218,16 @@ Override any variable by passing it to the CLI with `--extra-vars` or by setting
|
|||||||
│ │ ├── templates/ # Jinja2 templates
|
│ │ ├── templates/ # Jinja2 templates
|
||||||
│ │ └── molecule/ # Test scenarios
|
│ │ └── molecule/ # Test scenarios
|
||||||
│ ├── install-runner.yml # Install playbook
|
│ ├── install-runner.yml # Install playbook
|
||||||
│ └── update-runner.yml # Update playbook
|
│ ├── update-runner.yml # Update playbook
|
||||||
├── tests/ # Unit tests
|
│ ├── start-runner.yml # Start playbook
|
||||||
|
│ ├── stop-runner.yml # Stop playbook
|
||||||
|
│ ├── enable-runner.yml # Enable playbook
|
||||||
|
│ ├── disable-runner.yml # Disable playbook
|
||||||
|
│ ├── status-runner.yml # Status playbook
|
||||||
|
│ └── remove-runner.yml # Remove playbook
|
||||||
|
├── tests/
|
||||||
|
│ ├── unit/ # Unit tests
|
||||||
|
│ └── integration/ # Integration tests
|
||||||
├── Makefile # Build & test automation
|
├── Makefile # Build & test automation
|
||||||
└── pyproject.toml # Python project metadata
|
└── pyproject.toml # Python project metadata
|
||||||
```
|
```
|
||||||
@@ -206,12 +263,22 @@ Runs pytest with 100% coverage requirement.
|
|||||||
make molecule
|
make molecule
|
||||||
```
|
```
|
||||||
|
|
||||||
Runs two scenarios:
|
Runs four scenarios:
|
||||||
|
|
||||||
- **default** — Docker mode installation in an Ubuntu 22.04 container
|
- **default** — Docker mode installation in an Ubuntu 22.04 container
|
||||||
- **binary** — Binary mode installation in an Ubuntu 22.04 container
|
- **binary** — Binary mode installation in an Ubuntu 22.04 container
|
||||||
|
- **multi-instance** — Two isolated runner instances on the same host
|
||||||
|
- **lifecycle** — Stop, disable, re-enable, and start sequence
|
||||||
|
|
||||||
Both scenarios test idempotence (second run produces zero changes).
|
All scenarios test idempotence (second run produces zero changes).
|
||||||
|
|
||||||
|
### Integration Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make test-integration
|
||||||
|
```
|
||||||
|
|
||||||
|
Tests the full CLI lifecycle commands end-to-end ( mocked executor boundary).
|
||||||
|
|
||||||
### Full Test Suite
|
### Full Test Suite
|
||||||
|
|
||||||
@@ -228,7 +295,7 @@ This is a harmless cleanup traceback from Molecule's Docker driver when the test
|
|||||||
### Runner appears offline after installation
|
### Runner appears offline after installation
|
||||||
|
|
||||||
- Check that the `GITEA_URL` and `GITEA_REGISTRATION_TOKEN` environment variables are correct.
|
- Check that the `GITEA_URL` and `GITEA_REGISTRATION_TOKEN` environment variables are correct.
|
||||||
- Verify the runner container or service is running: `docker ps` or `systemctl status gitea-runner-<name>`.
|
- Verify the runner container or service is running: `docker ps` or `systemctl status gitea-runner@<name>`.
|
||||||
- Check logs for registration errors.
|
- Check logs for registration errors.
|
||||||
|
|
||||||
### Integration test fails
|
### Integration test fails
|
||||||
@@ -238,33 +305,42 @@ The test checks two things:
|
|||||||
1. **`.runner` file missing or invalid** — Registration failed. Check:
|
1. **`.runner` file missing or invalid** — Registration failed. Check:
|
||||||
- `GITEA_URL` and `GITEA_REGISTRATION_TOKEN` are correct
|
- `GITEA_URL` and `GITEA_REGISTRATION_TOKEN` are correct
|
||||||
- Runner logs for registration errors
|
- Runner logs for registration errors
|
||||||
- The `.runner` file should exist at `/var/lib/gitea-runner/.runner` (Docker) or `/etc/gitea-runner/.runner` (binary)
|
- The `.runner` file should exist at `/var/lib/gitea-runner/<name>/.runner`
|
||||||
|
|
||||||
2. **Container/service not running** — Daemon failed to start. Check:
|
2. **Container/service not running** — Daemon failed to start. Check:
|
||||||
- `docker ps` or `systemctl status gitea-runner-<name>`
|
- `docker ps` or `systemctl status gitea-runner@<name>`
|
||||||
- Logs for connection errors
|
- Logs for connection errors
|
||||||
|
|
||||||
### Docker mode: container won't start
|
### Docker mode: container won't start
|
||||||
|
|
||||||
- Ensure Docker is installed and running on the host.
|
- Ensure Docker is installed and running on the host.
|
||||||
- Verify the Docker socket is accessible: `docker version`.
|
- Verify the Docker socket is accessible: `docker version`.
|
||||||
|
- Check systemd status: `systemctl status gitea-runner@<name>`.
|
||||||
|
|
||||||
### Binary mode: systemd service fails
|
### Binary mode: systemd service fails
|
||||||
|
|
||||||
- Check the service status: `systemctl status gitea-runner-<name>`.
|
- Check the service status: `systemctl status gitea-runner@<name>`.
|
||||||
- Verify the binary exists at `gitea_runner_binary_path`.
|
- Verify the binary exists at `gitea_runner_binary_path`.
|
||||||
- Ensure the service user is in the `docker` group.
|
- Ensure the service user is in the `docker` group.
|
||||||
|
- Check logs: `journalctl -u gitea-runner@<name> -f`.
|
||||||
|
|
||||||
## Makefile Targets
|
## Makefile Targets
|
||||||
|
|
||||||
| Target | Description |
|
| Target | Description |
|
||||||
|--------|-------------|
|
|--------|-------------|
|
||||||
| `setup` | Full environment setup |
|
| `setup` | Full environment setup |
|
||||||
| `install` | Installs a runner on a host (`MODE=docker` or `binary`) |
|
| `install` | Installs a runner on a host |
|
||||||
| `update` | Updates a runner on a host (`MODE=docker` or `binary`) |
|
| `update` | Updates a runner on a host |
|
||||||
|
| `start` | Starts a runner instance |
|
||||||
|
| `stop` | Stops a runner instance |
|
||||||
|
| `enable` | Enables a runner to start on boot |
|
||||||
|
| `disable` | Disables and deregisters a runner |
|
||||||
|
| `status` | Checks runner status |
|
||||||
|
| `remove` | Removes a runner completely |
|
||||||
| `lint` | Runs Python linters |
|
| `lint` | Runs Python linters |
|
||||||
| `ansible-lint` | Runs `ansible-lint` |
|
| `ansible-lint` | Runs `ansible-lint` |
|
||||||
| `test-unit` | Runs unit tests with coverage |
|
| `test-unit` | Runs unit tests with coverage |
|
||||||
|
| `test-integration` | Runs integration tests |
|
||||||
| `molecule` | Runs Ansible Molecule tests |
|
| `molecule` | Runs Ansible Molecule tests |
|
||||||
| `test-all` | Runs all tests |
|
| `test-all` | Runs all tests |
|
||||||
|
|
||||||
|
|||||||
@@ -22,16 +22,27 @@
|
|||||||
ansible.builtin.command: docker --version
|
ansible.builtin.command: docker --version
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
|
||||||
- name: Check systemd service file exists
|
- name: Check systemd template unit exists
|
||||||
ansible.builtin.stat:
|
ansible.builtin.stat:
|
||||||
path: "/etc/systemd/system/gitea-runner-molecule-test-runner.service"
|
path: "/etc/systemd/system/gitea-runner@.service"
|
||||||
register: service_stat
|
register: service_stat
|
||||||
|
|
||||||
- name: Assert service file exists
|
- name: Assert template unit exists
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- service_stat.stat.exists
|
- service_stat.stat.exists
|
||||||
fail_msg: "Systemd service file is missing"
|
fail_msg: "Systemd template unit is missing"
|
||||||
|
|
||||||
|
- name: Check instance data directory exists
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ gitea_runner_data_dir }}"
|
||||||
|
register: data_dir_stat
|
||||||
|
|
||||||
|
- name: Assert instance data directory exists
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- data_dir_stat.stat.exists
|
||||||
|
fail_msg: "Instance data directory is missing"
|
||||||
|
|
||||||
- name: Check prune timer exists
|
- name: Check prune timer exists
|
||||||
ansible.builtin.stat:
|
ansible.builtin.stat:
|
||||||
|
|||||||
@@ -33,6 +33,28 @@
|
|||||||
- timer_stat.stat.exists
|
- timer_stat.stat.exists
|
||||||
fail_msg: "Docker prune timer is missing"
|
fail_msg: "Docker prune timer is missing"
|
||||||
|
|
||||||
|
- name: Check systemd template unit exists
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "/etc/systemd/system/gitea-runner@.service"
|
||||||
|
register: service_stat
|
||||||
|
|
||||||
|
- name: Assert template unit exists
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- service_stat.stat.exists
|
||||||
|
fail_msg: "Systemd template unit is missing"
|
||||||
|
|
||||||
|
- name: Check instance data directory exists
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ gitea_runner_data_dir }}"
|
||||||
|
register: data_dir_stat
|
||||||
|
|
||||||
|
- name: Assert instance data directory exists
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- data_dir_stat.stat.exists
|
||||||
|
fail_msg: "Instance data directory is missing"
|
||||||
|
|
||||||
- name: Check config file exists in data directory
|
- name: Check config file exists in data directory
|
||||||
ansible.builtin.stat:
|
ansible.builtin.stat:
|
||||||
path: "{{ gitea_runner_data_dir }}/config.yaml"
|
path: "{{ gitea_runner_data_dir }}/config.yaml"
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
- name: Converge
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
vars:
|
||||||
|
gitea_url: "http://localhost:3000"
|
||||||
|
registration_token: "fake-token-for-testing"
|
||||||
|
runner_name: "lifecycle-test-runner"
|
||||||
|
skip_runner_registration: true
|
||||||
|
roles:
|
||||||
|
- role: gitea-runner
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
driver:
|
||||||
|
name: docker
|
||||||
|
|
||||||
|
platforms:
|
||||||
|
- name: ${MOLECULE_PLATFORM_NAME:-ubuntu-2204}
|
||||||
|
image: ${MOLECULE_PLATFORM_IMAGE:-geerlingguy/docker-ubuntu2204-ansible:latest}
|
||||||
|
command: ${MOLECULE_PLATFORM_COMMAND:-sleep infinity}
|
||||||
|
volumes:
|
||||||
|
- /sys/fs/cgroup:/sys/fs/cgroup:rw
|
||||||
|
cgroupns_mode: host
|
||||||
|
privileged: true
|
||||||
|
pre_build_image: true
|
||||||
|
|
||||||
|
provisioner:
|
||||||
|
name: ansible
|
||||||
|
playbooks:
|
||||||
|
converge: converge.yml
|
||||||
|
prepare: ../common/prepare.yml
|
||||||
|
side_effect: side_effect.yml
|
||||||
|
env:
|
||||||
|
ANSIBLE_ROLES_PATH: "../../.."
|
||||||
|
|
||||||
|
scenario:
|
||||||
|
test_sequence:
|
||||||
|
- dependency
|
||||||
|
- cleanup
|
||||||
|
- destroy
|
||||||
|
- syntax
|
||||||
|
- create
|
||||||
|
- prepare
|
||||||
|
- converge
|
||||||
|
- idempotence
|
||||||
|
- side_effect
|
||||||
|
- verify
|
||||||
|
- cleanup
|
||||||
|
- destroy
|
||||||
|
|
||||||
|
verifier:
|
||||||
|
name: ansible
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
- name: Stop runner instance
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
vars:
|
||||||
|
runner_name: "lifecycle-test-runner"
|
||||||
|
tasks:
|
||||||
|
- name: Stop gitea-runner systemd instance
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: "gitea-runner@{{ runner_name }}"
|
||||||
|
state: stopped
|
||||||
|
daemon_reload: true
|
||||||
|
|
||||||
|
- name: Disable gitea-runner systemd instance
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: "gitea-runner@{{ runner_name }}"
|
||||||
|
enabled: false
|
||||||
|
daemon_reload: true
|
||||||
|
|
||||||
|
- name: Re-enable and start runner
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
vars:
|
||||||
|
runner_name: "lifecycle-test-runner"
|
||||||
|
tasks:
|
||||||
|
- name: Enable gitea-runner systemd instance
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: "gitea-runner@{{ runner_name }}"
|
||||||
|
enabled: true
|
||||||
|
daemon_reload: true
|
||||||
|
|
||||||
|
- name: Start gitea-runner systemd instance
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: "gitea-runner@{{ runner_name }}"
|
||||||
|
state: started
|
||||||
|
daemon_reload: true
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
- name: Verify
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
pre_tasks:
|
||||||
|
- name: Load role defaults
|
||||||
|
ansible.builtin.include_vars:
|
||||||
|
dir: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults"
|
||||||
|
tasks:
|
||||||
|
- name: Check systemd template unit exists
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "/etc/systemd/system/gitea-runner@.service"
|
||||||
|
register: template_stat
|
||||||
|
|
||||||
|
- name: Assert template unit exists
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- template_stat.stat.exists
|
||||||
|
fail_msg: "Systemd template unit is missing"
|
||||||
|
|
||||||
|
- name: Check instance is enabled
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: "gitea-runner@lifecycle-test-runner"
|
||||||
|
register: service_status
|
||||||
|
|
||||||
|
- name: Assert instance is enabled
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- service_status.status.LoadState == 'loaded'
|
||||||
|
fail_msg: "Systemd instance is not loaded"
|
||||||
|
|
||||||
|
- name: Check instance data directory exists after lifecycle
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "/var/lib/gitea-runner/lifecycle-test-runner"
|
||||||
|
register: data_dir_stat
|
||||||
|
|
||||||
|
- name: Assert instance data directory exists
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- data_dir_stat.stat.exists
|
||||||
|
fail_msg: "Instance data directory is missing after lifecycle"
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
- name: Converge first runner instance
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
vars:
|
||||||
|
gitea_url: "http://localhost:3000"
|
||||||
|
registration_token: "fake-token-for-testing"
|
||||||
|
runner_name: "molecule-runner-a"
|
||||||
|
skip_runner_registration: true
|
||||||
|
roles:
|
||||||
|
- role: gitea-runner
|
||||||
|
|
||||||
|
- name: Converge second runner instance
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
vars:
|
||||||
|
gitea_url: "http://localhost:3000"
|
||||||
|
registration_token: "fake-token-for-testing"
|
||||||
|
runner_name: "molecule-runner-b"
|
||||||
|
skip_runner_registration: true
|
||||||
|
roles:
|
||||||
|
- role: gitea-runner
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
driver:
|
||||||
|
name: docker
|
||||||
|
|
||||||
|
platforms:
|
||||||
|
- name: ${MOLECULE_PLATFORM_NAME:-ubuntu-2204}
|
||||||
|
image: ${MOLECULE_PLATFORM_IMAGE:-geerlingguy/docker-ubuntu2204-ansible:latest}
|
||||||
|
command: ${MOLECULE_PLATFORM_COMMAND:-sleep infinity}
|
||||||
|
volumes:
|
||||||
|
- /sys/fs/cgroup:/sys/fs/cgroup:rw
|
||||||
|
cgroupns_mode: host
|
||||||
|
privileged: true
|
||||||
|
pre_build_image: true
|
||||||
|
|
||||||
|
provisioner:
|
||||||
|
name: ansible
|
||||||
|
playbooks:
|
||||||
|
converge: converge.yml
|
||||||
|
prepare: ../common/prepare.yml
|
||||||
|
env:
|
||||||
|
ANSIBLE_ROLES_PATH: "../../.."
|
||||||
|
|
||||||
|
scenario:
|
||||||
|
test_sequence:
|
||||||
|
- dependency
|
||||||
|
- cleanup
|
||||||
|
- destroy
|
||||||
|
- syntax
|
||||||
|
- create
|
||||||
|
- prepare
|
||||||
|
- converge
|
||||||
|
- idempotence
|
||||||
|
- side_effect
|
||||||
|
- verify
|
||||||
|
- cleanup
|
||||||
|
- destroy
|
||||||
|
|
||||||
|
verifier:
|
||||||
|
name: ansible
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
- name: Verify
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
pre_tasks:
|
||||||
|
- name: Load role defaults
|
||||||
|
ansible.builtin.include_vars:
|
||||||
|
dir: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults"
|
||||||
|
tasks:
|
||||||
|
- name: Check systemd template unit exists
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "/etc/systemd/system/gitea-runner@.service"
|
||||||
|
register: template_stat
|
||||||
|
|
||||||
|
- name: Assert template unit exists
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- template_stat.stat.exists
|
||||||
|
fail_msg: "Systemd template unit is missing"
|
||||||
|
|
||||||
|
- name: Check first instance data directory exists
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "/var/lib/gitea-runner/molecule-runner-a"
|
||||||
|
register: data_a_stat
|
||||||
|
|
||||||
|
- name: Assert first instance data directory exists
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- data_a_stat.stat.exists
|
||||||
|
fail_msg: "First instance data directory is missing"
|
||||||
|
|
||||||
|
- name: Check second instance data directory exists
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "/var/lib/gitea-runner/molecule-runner-b"
|
||||||
|
register: data_b_stat
|
||||||
|
|
||||||
|
- name: Assert second instance data directory exists
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- data_b_stat.stat.exists
|
||||||
|
fail_msg: "Second instance data directory is missing"
|
||||||
|
|
||||||
|
- name: Check first instance config exists
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "/etc/gitea-runner/molecule-runner-a/config.yaml"
|
||||||
|
register: config_a_stat
|
||||||
|
|
||||||
|
- name: Assert first instance config exists
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- config_a_stat.stat.exists
|
||||||
|
fail_msg: "First instance config file is missing"
|
||||||
|
|
||||||
|
- name: Check second instance config exists
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "/etc/gitea-runner/molecule-runner-b/config.yaml"
|
||||||
|
register: config_b_stat
|
||||||
|
|
||||||
|
- name: Assert second instance config exists
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- config_b_stat.stat.exists
|
||||||
|
fail_msg: "Second instance config file is missing"
|
||||||
@@ -44,6 +44,9 @@ where = ["src"]
|
|||||||
testpaths = ["tests"]
|
testpaths = ["tests"]
|
||||||
pythonpath = ["src"]
|
pythonpath = ["src"]
|
||||||
addopts = "--cov=src/gitea_runner_manager --cov-report=term-missing --cov-fail-under=100"
|
addopts = "--cov=src/gitea_runner_manager --cov-report=term-missing --cov-fail-under=100"
|
||||||
|
markers = [
|
||||||
|
"integration: marks tests as integration tests (not counted in coverage)",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
target-version = "py311"
|
target-version = "py311"
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
"""Integration tests for Gitea Runner lifecycle commands."""
|
||||||
|
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from click.testing import CliRunner
|
||||||
|
|
||||||
|
from gitea_runner_manager.cli import cli
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.integration
|
||||||
|
class TestLifecycleCLI:
|
||||||
|
"""Test the full lifecycle CLI commands end-to-end."""
|
||||||
|
|
||||||
|
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||||
|
def test_install_start_status_stop_disable_remove(self, mock_manager_class: MagicMock) -> None:
|
||||||
|
"""Exercise the full lifecycle via CLI."""
|
||||||
|
mock_manager = MagicMock()
|
||||||
|
mock_manager_class.return_value = mock_manager
|
||||||
|
|
||||||
|
env = {"GITEA_URL": "https://git.example.com", "GITEA_REGISTRATION_TOKEN": "tok"}
|
||||||
|
runner = CliRunner(env=env)
|
||||||
|
|
||||||
|
# Install
|
||||||
|
result = runner.invoke(cli, ["install", "host1", "--user", "ubuntu", "--name", "r1"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
mock_manager.install.assert_called_once()
|
||||||
|
|
||||||
|
# Start
|
||||||
|
result = runner.invoke(cli, ["start", "host1", "--user", "ubuntu", "--name", "r1"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
mock_manager.start.assert_called_once_with(
|
||||||
|
host="host1", user="ubuntu", name="r1", mode="docker", ask_become_pass=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Status
|
||||||
|
result = runner.invoke(cli, ["status", "host1", "--user", "ubuntu", "--name", "r1"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
mock_manager.status.assert_called_once_with(
|
||||||
|
host="host1", user="ubuntu", name="r1", mode="docker", ask_become_pass=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Stop
|
||||||
|
result = runner.invoke(cli, ["stop", "host1", "--user", "ubuntu", "--name", "r1"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
mock_manager.stop.assert_called_once_with(host="host1", user="ubuntu", name="r1", ask_become_pass=False)
|
||||||
|
|
||||||
|
# Disable
|
||||||
|
result = runner.invoke(cli, ["disable", "host1", "--user", "ubuntu", "--name", "r1", "--token", "tok"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
mock_manager.disable.assert_called_once_with(
|
||||||
|
host="host1",
|
||||||
|
user="ubuntu",
|
||||||
|
name="r1",
|
||||||
|
token="tok",
|
||||||
|
gitea_url="https://git.example.com",
|
||||||
|
mode="docker",
|
||||||
|
ask_become_pass=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove
|
||||||
|
result = runner.invoke(cli, ["remove", "host1", "--user", "ubuntu", "--name", "r1", "--token", "tok"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
mock_manager.remove.assert_called_once_with(
|
||||||
|
host="host1",
|
||||||
|
user="ubuntu",
|
||||||
|
name="r1",
|
||||||
|
token="tok",
|
||||||
|
gitea_url="https://git.example.com",
|
||||||
|
mode="docker",
|
||||||
|
ask_become_pass=False,
|
||||||
|
)
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
"""Integration tests for multi-instance support."""
|
||||||
|
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from click.testing import CliRunner
|
||||||
|
|
||||||
|
from gitea_runner_manager.cli import cli
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.integration
|
||||||
|
class TestMultiInstanceCLI:
|
||||||
|
"""Test that multiple runner instances can be managed independently."""
|
||||||
|
|
||||||
|
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||||
|
def test_install_two_instances(self, mock_manager_class: MagicMock) -> None:
|
||||||
|
"""Install two named instances on the same host."""
|
||||||
|
mock_manager = MagicMock()
|
||||||
|
mock_manager_class.return_value = mock_manager
|
||||||
|
|
||||||
|
env = {"GITEA_URL": "https://git.example.com", "GITEA_REGISTRATION_TOKEN": "tok"}
|
||||||
|
runner = CliRunner(env=env)
|
||||||
|
|
||||||
|
# Install instance A
|
||||||
|
result = runner.invoke(cli, ["install", "host1", "--user", "ubuntu", "--name", "runner-a"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
# Install instance B
|
||||||
|
result = runner.invoke(cli, ["install", "host1", "--user", "ubuntu", "--name", "runner-b", "--mode", "binary"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
assert mock_manager.install.call_count == 2
|
||||||
|
calls = mock_manager.install.call_args_list
|
||||||
|
assert calls[0].kwargs["name"] == "runner-a"
|
||||||
|
assert calls[0].kwargs["mode"] == "docker"
|
||||||
|
assert calls[1].kwargs["name"] == "runner-b"
|
||||||
|
assert calls[1].kwargs["mode"] == "binary"
|
||||||
|
|
||||||
|
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||||
|
def test_start_stop_one_instance(self, mock_manager_class: MagicMock) -> None:
|
||||||
|
"""Start one instance and stop another independently."""
|
||||||
|
mock_manager = MagicMock()
|
||||||
|
mock_manager_class.return_value = mock_manager
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
|
||||||
|
result = runner.invoke(cli, ["start", "host1", "--user", "ubuntu", "--name", "runner-a"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
mock_manager.start.assert_called_once_with(
|
||||||
|
host="host1", user="ubuntu", name="runner-a", mode="docker", ask_become_pass=False
|
||||||
|
)
|
||||||
|
|
||||||
|
result = runner.invoke(cli, ["stop", "host1", "--user", "ubuntu", "--name", "runner-b"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
mock_manager.stop.assert_called_once_with(host="host1", user="ubuntu", name="runner-b", ask_become_pass=False)
|
||||||
Reference in New Issue
Block a user