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
|
# Admin → Actions → Runners → Create Registration Token
|
||||||
GITEA_REGISTRATION_TOKEN=your-registration-token
|
GITEA_REGISTRATION_TOKEN=your-registration-token
|
||||||
|
|
||||||
# Gitea admin API token for post-install integration test (optional).
|
# Gitea API token for optional post-install API checks (informational only).
|
||||||
# The runner registration token above is NOT sufficient for the
|
# The integration test primarily verifies the runner by checking:
|
||||||
# /api/v1/admin/runners endpoint; it is only valid for registration.
|
# 1. The .runner registration file exists and is valid
|
||||||
# To verify the runner appears in Gitea after install, generate a
|
# 2. The container/service is running
|
||||||
# personal access token with at least the "admin" scope:
|
# If set, API checks are performed as a bonus but do NOT affect pass/fail.
|
||||||
# Settings → Applications → Generate New Token → scope: admin
|
# Required scopes: read:user, read:repository, read:admin (or just "admin")
|
||||||
# If left unset, the integration test is skipped gracefully.
|
# Generate token at: Settings → Applications → Generate New Token
|
||||||
# GITEA_ADMIN_TOKEN=your-admin-api-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)
|
# Default SSH user for remote hosts (optional, overrides --user)
|
||||||
# GITEA_RUNNER_USER=ubuntu
|
# 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).
|
`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
|
### Install a Runner
|
||||||
|
|
||||||
Using the CLI:
|
Using the CLI:
|
||||||
@@ -80,7 +89,14 @@ make install HOST=192.168.1.10 USER=ubuntu ASK_BECOME_PASS=1
|
|||||||
|
|
||||||
### Verify Runner
|
### 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
|
### 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>`.
|
- 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
|
||||||
|
|
||||||
|
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
|
### Docker mode: container won't start
|
||||||
|
|
||||||
- Ensure Docker is installed and running on the host.
|
- Ensure Docker is installed and running on the host.
|
||||||
|
|||||||
@@ -1,61 +1,109 @@
|
|||||||
---
|
---
|
||||||
- name: Check if Gitea admin API is accessible
|
- name: Check runner registration file exists
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ gitea_runner_data_dir }}/.runner"
|
||||||
|
register: runner_file_stat
|
||||||
|
|
||||||
|
- 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: 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: 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 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
|
||||||
|
- gitea_admin_token | length > 0
|
||||||
|
block:
|
||||||
|
- name: Check admin runners API
|
||||||
ansible.builtin.uri:
|
ansible.builtin.uri:
|
||||||
url: "{{ gitea_url }}/api/v1/admin/runners"
|
url: "{{ gitea_url }}/api/v1/admin/runners"
|
||||||
headers:
|
headers:
|
||||||
Authorization: "token {{ gitea_admin_token | default(registration_token) }}"
|
Authorization: "token {{ gitea_admin_token }}"
|
||||||
method: GET
|
method: GET
|
||||||
status_code: [200, 401, 403, 404]
|
status_code: [200, 401, 403, 404]
|
||||||
return_content: false
|
return_content: true
|
||||||
register: api_check
|
body_format: json
|
||||||
|
register: admin_api_response
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
when:
|
|
||||||
- gitea_url is defined
|
|
||||||
- gitea_admin_token is defined
|
|
||||||
|
|
||||||
- name: Wait for runner to appear in Gitea API
|
- name: Check repo runners API
|
||||||
ansible.builtin.uri:
|
ansible.builtin.uri:
|
||||||
url: "{{ gitea_url }}/api/v1/admin/runners"
|
url: "{{ gitea_url }}/api/v1/repos/{{ gitea_runner_test_repo | default('oblachno-oss/grm') }}/actions/runners"
|
||||||
headers:
|
headers:
|
||||||
Authorization: "token {{ gitea_admin_token }}"
|
Authorization: "token {{ gitea_admin_token }}"
|
||||||
method: GET
|
method: GET
|
||||||
status_code: 200
|
status_code: [200, 401, 403, 404]
|
||||||
return_content: true
|
return_content: true
|
||||||
body_format: json
|
body_format: json
|
||||||
register: runners_response
|
register: repo_api_response
|
||||||
until: >
|
ignore_errors: true
|
||||||
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: Verify runner is online
|
- name: Report API status (informational only)
|
||||||
ansible.builtin.uri:
|
ansible.builtin.debug:
|
||||||
url: "{{ gitea_url }}/api/v1/admin/runners"
|
msg: >
|
||||||
headers:
|
API checks (informational only — not used for pass/fail):
|
||||||
Authorization: "token {{ gitea_admin_token }}"
|
Admin API: {{ admin_api_response.status | default('no response') }}.
|
||||||
method: GET
|
Repo API: {{ repo_api_response.status | default('no response') }}.
|
||||||
status_code: 200
|
{% if admin_api_response.json.runners | default([]) | selectattr('name', 'equalto', runner_name) | list | length > 0 %}
|
||||||
return_content: true
|
Runner found in admin API.
|
||||||
body_format: json
|
{% endif %}
|
||||||
register: runners_check
|
{% if repo_api_response.json.runners | default([]) | selectattr('name', 'equalto', runner_name) | list | length > 0 %}
|
||||||
when:
|
Runner found in repo API.
|
||||||
- gitea_url is defined
|
{% endif %}
|
||||||
- gitea_admin_token is defined
|
rescue:
|
||||||
- api_check.status | default(0) == 200
|
- name: API check failed
|
||||||
|
ansible.builtin.debug:
|
||||||
- name: Fail if runner is not online
|
msg: "API verification skipped due to connection or permission error."
|
||||||
ansible.builtin.fail:
|
|
||||||
msg: "Runner '{{ runner_name }}' is not online in Gitea"
|
|
||||||
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
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnk
|
|||||||
from .exceptions import GRMError
|
from .exceptions import GRMError
|
||||||
from .runner_manager import RunnerManager
|
from .runner_manager import RunnerManager
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
@@ -49,6 +49,13 @@ def cli() -> None:
|
|||||||
default=lambda: os.getenv("GITEA_ADMIN_TOKEN"),
|
default=lambda: os.getenv("GITEA_ADMIN_TOKEN"),
|
||||||
help="Gitea admin API token for integration test (env: 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")
|
@click.option("--ask-become-pass", is_flag=True, help="Prompt for sudo password")
|
||||||
def install(
|
def install(
|
||||||
host: str,
|
host: str,
|
||||||
@@ -58,6 +65,7 @@ def install(
|
|||||||
token: str | None,
|
token: str | None,
|
||||||
mode: str,
|
mode: str,
|
||||||
admin_token: str | None,
|
admin_token: str | None,
|
||||||
|
integration_retries: int,
|
||||||
ask_become_pass: bool,
|
ask_become_pass: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Install and configure a runner on a remote host."""
|
"""Install and configure a runner on a remote host."""
|
||||||
@@ -77,6 +85,7 @@ def install(
|
|||||||
gitea_url=gitea_url,
|
gitea_url=gitea_url,
|
||||||
mode=mode,
|
mode=mode,
|
||||||
admin_token=admin_token,
|
admin_token=admin_token,
|
||||||
|
integration_retries=integration_retries,
|
||||||
ask_become_pass=ask_become_pass,
|
ask_become_pass=ask_become_pass,
|
||||||
)
|
)
|
||||||
except GRMError as e:
|
except GRMError as e:
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class RunnerManager:
|
|||||||
gitea_url: str = "",
|
gitea_url: str = "",
|
||||||
mode: str = "docker",
|
mode: str = "docker",
|
||||||
admin_token: str | None = None,
|
admin_token: str | None = None,
|
||||||
|
integration_retries: int = 3,
|
||||||
ask_become_pass: bool = False,
|
ask_become_pass: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Install a runner on a remote host using Ansible."""
|
"""Install a runner on a remote host using Ansible."""
|
||||||
@@ -34,7 +35,11 @@ class RunnerManager:
|
|||||||
if not playbook.exists():
|
if not playbook.exists():
|
||||||
raise AnsibleError(f"Playbook not found: {playbook}")
|
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:
|
if admin_token:
|
||||||
extra_vars += f" gitea_admin_token={admin_token}"
|
extra_vars += f" gitea_admin_token={admin_token}"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user