Origin of the issue
I recently renamed one of my runners, and that is when I started seeing this error.
Problem description
With gitlab_runner_config_update_mode: by_config_toml, the role produces an invalid TOML configuration when a runner already contains an unmarked [runners.feature_flags] section.
The Set feature flag options task uses ansible.builtin.blockinfile with the # BEGIN/END runners.feature_flags markers. Since blockinfile does not detect the existing TOML section, it adds a second [runners.feature_flags] section at the end of the runner configuration.
GitLab Runner then rejects the candidate file during validation in the Assemble new config.toml task.
Versions
- Tag: v2.1.2
- GitLab Runner:
19.2.4
- OS: Linux
gitlab_runner_config_update_mode: by_config_toml (default)
Minimal Ansible configuration
gitlab_runner_runners:
- name: "shared-default-name"
executor: docker
feature_flags:
- FF_USE_ADAPTIVE_REQUEST_CONCURRENCY
Existing configuration
Before the update, the runner already contains an unmarked section:
[[runners]]
name = "shared-default-name"
executor = "docker"
[runners.feature_flags]
FF_USE_ADAPTIVE_REQUEST_CONCURRENCY = true
[runners.docker]
image = "docker:29.0.4"
Observed result
The task adds a second block:
[runners.feature_flags]
FF_USE_ADAPTIVE_REQUEST_CONCURRENCY = true
# BEGIN runners.feature_flags
[runners.feature_flags]
FF_USE_ADAPTIVE_REQUEST_CONCURRENCY = true
# END runners.feature_flags
Validation fails with:
FATAL: decoding configuration file: toml: line 394
(last key "runners"): Key 'runners.feature_flags' has already been defined.
Identified cause
The relevant task is:
|
#### [runners.feature_flags] section ##### |
It systematically appends the marked block at EOF. It does not migrate or replace an existing [runners.feature_flags] table that does not have the role's markers.
The temporary file already contains the first section because tasks/config-runners.yml splits the existing config.toml, then tasks/config-runner.yml copies each fragment as-is before running update-config-runner.yml.
Expected behavior
The role should produce exactly one [runners.feature_flags] table per runner, including when the existing config.toml contains a section that is not managed by blockinfile.
Suggested fix
Before the blockinfile task, remove or migrate only [runners.feature_flags] tables that are not surrounded by the role's markers, then let blockinfile manage the section idempotently.
A by_config_toml integration test should cover:
- an initial
config.toml containing an unmarked [runners.feature_flags] table;
- successful convergence without a parsing error;
- exactly one table in the resulting file;
- a second idempotent convergence.
Origin of the issue
I recently renamed one of my runners, and that is when I started seeing this error.
Problem description
With
gitlab_runner_config_update_mode: by_config_toml, the role produces an invalid TOML configuration when a runner already contains an unmarked[runners.feature_flags]section.The
Set feature flag optionstask usesansible.builtin.blockinfilewith the# BEGIN/END runners.feature_flagsmarkers. Sinceblockinfiledoes not detect the existing TOML section, it adds a second[runners.feature_flags]section at the end of the runner configuration.GitLab Runner then rejects the candidate file during validation in the
Assemble new config.tomltask.Versions
19.2.4gitlab_runner_config_update_mode:by_config_toml(default)Minimal Ansible configuration
Existing configuration
Before the update, the runner already contains an unmarked section:
Observed result
The task adds a second block:
Validation fails with:
Identified cause
The relevant task is:
ansible-gitlab-runner/tasks/update-config-runner.yml
Line 931 in 81361de
It systematically appends the marked block at EOF. It does not migrate or replace an existing
[runners.feature_flags]table that does not have the role's markers.The temporary file already contains the first section because
tasks/config-runners.ymlsplits the existingconfig.toml, thentasks/config-runner.ymlcopies each fragment as-is before runningupdate-config-runner.yml.Expected behavior
The role should produce exactly one
[runners.feature_flags]table per runner, including when the existingconfig.tomlcontains a section that is not managed byblockinfile.Suggested fix
Before the
blockinfiletask, remove or migrate only[runners.feature_flags]tables that are not surrounded by the role's markers, then letblockinfilemanage the section idempotently.A
by_config_tomlintegration test should cover:config.tomlcontaining an unmarked[runners.feature_flags]table;