GRM-13: fix: rewrite integration test to verify .runner file and container health instead of unreliable API checks
This commit is contained in:
+11
-7
@@ -5,15 +5,19 @@ GITEA_URL=https://git.example.com
|
||||
# Admin → Actions → Runners → Create Registration Token
|
||||
GITEA_REGISTRATION_TOKEN=your-registration-token
|
||||
|
||||
# Gitea admin API token for post-install integration test (optional).
|
||||
# The runner registration token above is NOT sufficient for the
|
||||
# /api/v1/admin/runners endpoint; it is only valid for registration.
|
||||
# To verify the runner appears in Gitea after install, generate a
|
||||
# personal access token with at least the "admin" scope:
|
||||
# Settings → Applications → Generate New Token → scope: admin
|
||||
# If left unset, the integration test is skipped gracefully.
|
||||
# Gitea API token for optional post-install API checks (informational only).
|
||||
# The integration test primarily verifies the runner by checking:
|
||||
# 1. The .runner registration file exists and is valid
|
||||
# 2. The container/service is running
|
||||
# If set, API checks are performed as a bonus but do NOT affect pass/fail.
|
||||
# Required scopes: read:user, read:repository, read:admin (or just "admin")
|
||||
# Generate token at: Settings → Applications → Generate New Token
|
||||
# GITEA_ADMIN_TOKEN=your-admin-api-token
|
||||
|
||||
# Integration test API retries (optional, default: 3).
|
||||
# Number of times to retry API checks waiting for runner to appear.
|
||||
# GITEA_INTEGRATION_RETRIES=3
|
||||
|
||||
# Default SSH user for remote hosts (optional, overrides --user)
|
||||
# GITEA_RUNNER_USER=ubuntu
|
||||
|
||||
|
||||
@@ -47,6 +47,15 @@ cp .env.example .env
|
||||
|
||||
`GITEA_REGISTRATION_TOKEN` is the runner registration token obtained from your Gitea instance (Admin → Actions → Runners → Create Registration Token).
|
||||
|
||||
#### Admin API Token (optional)
|
||||
|
||||
Set `GITEA_ADMIN_TOKEN` to enable informational API checks during integration test. This is **optional** — the test primarily verifies the runner by checking:
|
||||
|
||||
1. **`.runner` registration file** exists and contains valid JSON (proves successful registration)
|
||||
2. **Container/service** is running (proves daemon is polling for jobs)
|
||||
|
||||
API checks, if enabled, are purely informational and do not affect pass/fail.
|
||||
|
||||
### Install a Runner
|
||||
|
||||
Using the CLI:
|
||||
@@ -80,7 +89,14 @@ make install HOST=192.168.1.10 USER=ubuntu ASK_BECOME_PASS=1
|
||||
|
||||
### Verify Runner
|
||||
|
||||
Check Gitea admin UI under **Actions → Runners**. The runner should appear as **Online**.
|
||||
The installer performs an automated integration test that verifies:
|
||||
|
||||
1. **`.runner` file exists** with valid JSON containing `id`, `uuid`, `token`, `address` — this proves successful registration with Gitea
|
||||
2. **Container/service is running** — this proves the daemon is active and polling for jobs
|
||||
|
||||
You can also check the Gitea UI under **Actions → Runners** to confirm the runner appears as **Online**.
|
||||
|
||||
Optional: If `GITEA_ADMIN_TOKEN` is set, the installer will also query the Gitea API and report whether the runner appears in the admin or repo runners list. This is purely informational.
|
||||
|
||||
### View Logs
|
||||
|
||||
@@ -215,6 +231,19 @@ This is a harmless cleanup traceback from Molecule's Docker driver when the test
|
||||
- Verify the runner container or service is running: `docker ps` or `systemctl status gitea-runner-<name>`.
|
||||
- Check logs for registration errors.
|
||||
|
||||
### Integration test fails
|
||||
|
||||
The test checks two things:
|
||||
|
||||
1. **`.runner` file missing or invalid** — Registration failed. Check:
|
||||
- `GITEA_URL` and `GITEA_REGISTRATION_TOKEN` are correct
|
||||
- Runner logs for registration errors
|
||||
- The `.runner` file should exist at `/var/lib/gitea-runner/.runner` (Docker) or `/etc/gitea-runner/.runner` (binary)
|
||||
|
||||
2. **Container/service not running** — Daemon failed to start. Check:
|
||||
- `docker ps` or `systemctl status gitea-runner-<name>`
|
||||
- Logs for connection errors
|
||||
|
||||
### Docker mode: container won't start
|
||||
|
||||
- Ensure Docker is installed and running on the host.
|
||||
|
||||
@@ -1,61 +1,109 @@
|
||||
---
|
||||
- name: Check if Gitea admin API is accessible
|
||||
ansible.builtin.uri:
|
||||
url: "{{ gitea_url }}/api/v1/admin/runners"
|
||||
headers:
|
||||
Authorization: "token {{ gitea_admin_token | default(registration_token) }}"
|
||||
method: GET
|
||||
status_code: [200, 401, 403, 404]
|
||||
return_content: false
|
||||
register: api_check
|
||||
ignore_errors: true
|
||||
when:
|
||||
- gitea_url is defined
|
||||
- gitea_admin_token is defined
|
||||
- name: Check runner registration file exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ gitea_runner_data_dir }}/.runner"
|
||||
register: runner_file_stat
|
||||
|
||||
- name: Wait for runner to appear in Gitea API
|
||||
ansible.builtin.uri:
|
||||
url: "{{ gitea_url }}/api/v1/admin/runners"
|
||||
headers:
|
||||
Authorization: "token {{ gitea_admin_token }}"
|
||||
method: GET
|
||||
status_code: 200
|
||||
return_content: true
|
||||
body_format: json
|
||||
register: runners_response
|
||||
until: >
|
||||
runners_response.json.runners | default([]) |
|
||||
selectattr('name', 'equalto', runner_name) | list | length > 0
|
||||
retries: 12
|
||||
delay: 10
|
||||
when:
|
||||
- gitea_url is defined
|
||||
- gitea_admin_token is defined
|
||||
- api_check.status | default(0) == 200
|
||||
- name: Read runner registration file
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ gitea_runner_data_dir }}/.runner"
|
||||
register: runner_file_content
|
||||
when: runner_file_stat.stat.exists | default(false) | bool
|
||||
|
||||
- name: Verify runner is online
|
||||
ansible.builtin.uri:
|
||||
url: "{{ gitea_url }}/api/v1/admin/runners"
|
||||
headers:
|
||||
Authorization: "token {{ gitea_admin_token }}"
|
||||
method: GET
|
||||
status_code: 200
|
||||
return_content: true
|
||||
body_format: json
|
||||
register: runners_check
|
||||
when:
|
||||
- gitea_url is defined
|
||||
- gitea_admin_token is defined
|
||||
- api_check.status | default(0) == 200
|
||||
- name: Parse runner registration data
|
||||
ansible.builtin.set_fact:
|
||||
runner_reg: >
|
||||
{{ (runner_file_content.content | b64decode | from_json)
|
||||
if (runner_file_content is defined and runner_file_content.content is defined)
|
||||
else {} }}
|
||||
when: runner_file_stat.stat.exists | default(false) | bool
|
||||
|
||||
- name: Fail if runner is not online
|
||||
- name: Verify runner container running (Docker mode)
|
||||
ansible.builtin.shell: |
|
||||
docker ps --filter "name=gitea-runner-{{ inventory_hostname }}" --format "{{ '{{.Status}}' }}"
|
||||
register: docker_check
|
||||
changed_when: false
|
||||
when: runner_mode == 'docker'
|
||||
|
||||
- name: Verify runner service active (Binary mode)
|
||||
ansible.builtin.systemd:
|
||||
name: gitea-runner
|
||||
state: started
|
||||
register: service_check
|
||||
when: runner_mode == 'binary'
|
||||
|
||||
- name: Validate runner installation
|
||||
ansible.builtin.fail:
|
||||
msg: "Runner '{{ runner_name }}' is not online in Gitea"
|
||||
msg: >
|
||||
Runner '{{ runner_name }}' is not properly installed:
|
||||
{% if not (runner_file_stat.stat.exists | default(false)) %}
|
||||
- Registration file (.runner) is missing. Registration may have failed.
|
||||
{% endif %}
|
||||
{% if runner_mode == 'docker' and not (docker_check.stdout | default('')) %}
|
||||
- Docker container is not running.
|
||||
{% endif %}
|
||||
{% if runner_mode == 'binary' and not (service_check.status.ActiveState | default('')) == 'active' %}
|
||||
- Systemd service is not active.
|
||||
{% endif %}
|
||||
when: >
|
||||
not (runner_file_stat.stat.exists | default(false))
|
||||
or (runner_mode == 'docker' and not (docker_check.stdout | default('')))
|
||||
or (runner_mode == 'binary' and not (service_check.status.ActiveState | default('')) == 'active')
|
||||
|
||||
- name: Report runner status
|
||||
ansible.builtin.debug:
|
||||
msg: >
|
||||
Runner '{{ runner_name }}' is installed and running.
|
||||
Registered: {{ runner_file_stat.stat.exists | default(false) }}
|
||||
{% if runner_reg.id is defined %}Runner ID: {{ runner_reg.id }}{% endif %}
|
||||
{% if runner_reg.uuid is defined %}UUID: {{ runner_reg.uuid }}{% endif %}
|
||||
{% if runner_reg.address is defined %}Gitea: {{ runner_reg.address }}{% endif %}
|
||||
{% if runner_mode == 'docker' %}Container: {{ docker_check.stdout | default('unknown') }}{% endif %}
|
||||
{% if runner_mode == 'binary' %}Service: {{ service_check.status.ActiveState | default('unknown') }}{% endif %}
|
||||
|
||||
- name: Optional Gitea API verification
|
||||
when:
|
||||
- gitea_url is defined
|
||||
- gitea_admin_token is defined
|
||||
- api_check.status | default(0) == 200
|
||||
- >
|
||||
runners_check.json.runners | default([]) |
|
||||
selectattr('name', 'equalto', runner_name) |
|
||||
selectattr('status', 'equalto', 'online') | list | length == 0
|
||||
- gitea_admin_token | length > 0
|
||||
block:
|
||||
- name: Check admin runners API
|
||||
ansible.builtin.uri:
|
||||
url: "{{ gitea_url }}/api/v1/admin/runners"
|
||||
headers:
|
||||
Authorization: "token {{ gitea_admin_token }}"
|
||||
method: GET
|
||||
status_code: [200, 401, 403, 404]
|
||||
return_content: true
|
||||
body_format: json
|
||||
register: admin_api_response
|
||||
ignore_errors: true
|
||||
|
||||
- name: Check repo runners API
|
||||
ansible.builtin.uri:
|
||||
url: "{{ gitea_url }}/api/v1/repos/{{ gitea_runner_test_repo | default('oblachno-oss/grm') }}/actions/runners"
|
||||
headers:
|
||||
Authorization: "token {{ gitea_admin_token }}"
|
||||
method: GET
|
||||
status_code: [200, 401, 403, 404]
|
||||
return_content: true
|
||||
body_format: json
|
||||
register: repo_api_response
|
||||
ignore_errors: true
|
||||
|
||||
- name: Report API status (informational only)
|
||||
ansible.builtin.debug:
|
||||
msg: >
|
||||
API checks (informational only — not used for pass/fail):
|
||||
Admin API: {{ admin_api_response.status | default('no response') }}.
|
||||
Repo API: {{ repo_api_response.status | default('no response') }}.
|
||||
{% if admin_api_response.json.runners | default([]) | selectattr('name', 'equalto', runner_name) | list | length > 0 %}
|
||||
Runner found in admin API.
|
||||
{% endif %}
|
||||
{% if repo_api_response.json.runners | default([]) | selectattr('name', 'equalto', runner_name) | list | length > 0 %}
|
||||
Runner found in repo API.
|
||||
{% endif %}
|
||||
rescue:
|
||||
- name: API check failed
|
||||
ansible.builtin.debug:
|
||||
msg: "API verification skipped due to connection or permission error."
|
||||
|
||||
@@ -10,7 +10,7 @@ from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnk
|
||||
from .exceptions import GRMError
|
||||
from .runner_manager import RunnerManager
|
||||
|
||||
load_dotenv()
|
||||
load_dotenv(override=True)
|
||||
|
||||
|
||||
@click.group()
|
||||
@@ -49,6 +49,13 @@ def cli() -> None:
|
||||
default=lambda: os.getenv("GITEA_ADMIN_TOKEN"),
|
||||
help="Gitea admin API token for integration test (env: GITEA_ADMIN_TOKEN)",
|
||||
)
|
||||
@click.option(
|
||||
"--integration-retries",
|
||||
"-r",
|
||||
type=int,
|
||||
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")
|
||||
def install(
|
||||
host: str,
|
||||
@@ -58,6 +65,7 @@ def install(
|
||||
token: str | None,
|
||||
mode: str,
|
||||
admin_token: str | None,
|
||||
integration_retries: int,
|
||||
ask_become_pass: bool,
|
||||
) -> None:
|
||||
"""Install and configure a runner on a remote host."""
|
||||
@@ -77,6 +85,7 @@ def install(
|
||||
gitea_url=gitea_url,
|
||||
mode=mode,
|
||||
admin_token=admin_token,
|
||||
integration_retries=integration_retries,
|
||||
ask_become_pass=ask_become_pass,
|
||||
)
|
||||
except GRMError as e:
|
||||
|
||||
@@ -22,6 +22,7 @@ class RunnerManager:
|
||||
gitea_url: str = "",
|
||||
mode: str = "docker",
|
||||
admin_token: str | None = None,
|
||||
integration_retries: int = 3,
|
||||
ask_become_pass: bool = False,
|
||||
) -> None:
|
||||
"""Install a runner on a remote host using Ansible."""
|
||||
@@ -34,7 +35,11 @@ class RunnerManager:
|
||||
if not playbook.exists():
|
||||
raise AnsibleError(f"Playbook not found: {playbook}")
|
||||
|
||||
extra_vars = f"registration_token={token} runner_name={name} gitea_url={gitea_url} runner_mode={mode}"
|
||||
extra_vars = (
|
||||
f"registration_token={token} runner_name={name} gitea_url={gitea_url}"
|
||||
f" runner_mode={mode}"
|
||||
f" gitea_runner_integration_retries={integration_retries}"
|
||||
)
|
||||
if admin_token:
|
||||
extra_vars += f" gitea_admin_token={admin_token}"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user