Public Task Interface¶
The companion primetheus.gitlab_settings collection provides four ordinary
Ansible actions:
primetheus.gitlab_settings.gitlab_settings_discoverlists normalized semantic resources.primetheus.gitlab_settings.gitlab_settingsreconciles one semantic resource.primetheus.gitlab_settings.gitlab_credential_checkverifies a credential once, before the tasks that depend on it.primetheus.gitlab_settings.gitlab_oauth_sessionmaintains a managed OAuth session deliberately.
Credential declaration, verification verdicts, managed OAuth sessions, and every state those two credential tasks can report are documented in Credential operations.
Use these actions when the resource set is discovered at runtime and an
ordinary Ansible loop is the clearest composition. Use the standalone role's
tasks_from: apply entry point for efficient bulk convergence of static or
preassembled desired-state documents.
Install from this source tree¶
Build and install the supported companion artifact with standard collection tooling:
COLLECTION_ARCHIVE="$(python3 tools/build_public_collection.py --output-dir dist)"
ansible-galaxy collection install "$COLLECTION_ARCHIVE"
The installed FQCNs need no ANSIBLE_LIBRARY, role pre-inclusion, copied
plugins, or symlinks. The packaging decision is recorded in
ADR-0010.
Discover, register, and loop¶
Discovery records intentionally carry the exact identifier accepted by the
reconciliation action:
- name: Discover all groups
primetheus.gitlab_settings.gitlab_settings_discover:
target: "{{ gitlab_settings_target }}"
resource: group
scope: all
register: discovered_groups
- name: Enable Duo for every discovered group
primetheus.gitlab_settings.gitlab_settings:
target: "{{ gitlab_settings_target }}"
resource: group
identifier: "{{ item['identifier'] }}"
settings:
duo:
duo_features_enabled: true
duo_availability: default_on
loop: "{{ discovered_groups['resources'] }}"
loop_control:
label: "{{ item['identifier'] }}"
No fact accumulator or result-shaping filter connects the tasks. A looped
reconciliation task produces Ansible's normal registered results list.
when, failed_when, changed_when, task tags, delegation, check mode, and
diff mode retain their ordinary Ansible meanings.
Nested resources¶
Nested objects take a semantic parent. The module owns identifier encoding and transport paths:
- name: Discover unprotected branches
primetheus.gitlab_settings.gitlab_settings_discover:
target: "{{ gitlab_settings_target }}"
resource: branch
parent:
resource: project
identifier: platform/service
filters:
protected: false
register: discovered_branches
Scalar identities are passed directly. Composite identities use a mapping:
- name: Remove one freeze period
primetheus.gitlab_settings.gitlab_settings:
target: "{{ gitlab_settings_target }}"
resource: freeze_period
parent:
resource: project
identifier: platform/service
identifier:
freeze_start: "0 0 * * 5"
freeze_end: "0 0 * * 1"
state: absent
One desired-state schema¶
settings is exactly the equivalent bulk entry after its path identity and
lifecycle state are lifted into module arguments:
# Bulk role
gitlab_settings_groups:
- path: platform
duo:
duo_features_enabled: true
duo_availability: default_on
# Public task
- primetheus.gitlab_settings.gitlab_settings:
target: "{{ gitlab_settings_target }}"
resource: group
identifier: platform
settings:
duo:
duo_features_enabled: true
duo_availability: default_on
Both forms pass through the same normalizer, planner, policy metadata, secret separation, and transport router.
Discovery behavior¶
Singular resource names are required. Semantic scopes, filters, parent rules, normalized attributes, and transport capabilities come from the generated registry reference.
Full pagination is the default. limit returns at most that many matching
records and sets truncated: true only after the module proves another
matching record exists. Discovery is always read-only and reports
changed: false, including in check mode.
Successful records contain:
resource: group
identifier: platform
id: 42
name: Platform
parent: null
attributes:
full_path: platform
The enclosing result always includes resources, count, truncated,
pages_read, transport, skipped, skip_reason, notices,
credential-free authentication, and the live compatibility decision.
When a safe retry occurs it also includes attempts, retries used, the safe
retry reason, and any rate-limit wait.
Authentication, authorization, malformed input, and unsafe response ambiguity
fail; they never become an empty successful discovery. compatible mode
turns only an explicitly unavailable registry/transport capability into a
structured skip. Failures name the semantic resource and parent while keeping
endpoint paths, GraphQL documents, headers, and raw response bodies internal.
Reconciliation behavior¶
The module reads current state and writes only on observed drift. It reports
semantic actions, safe diff, found, resource_id, the chosen transport,
credential-free authentication/compatibility state, retry diagnostics when
used, and structured compatibility skips. Check mode performs validation and
reads, reports the planned change and diff, and suppresses mutation.
Sensitive fields remain in their established semantic locations. Module
argument leaves are no_log; planner secrets are held separately from
display operations; results and diffs never contain their values.
backend uses the same ordered preference contract as the role: api,
all, one transport, or a list. REST and GraphQL are selected internally from
resource capabilities. Public per-item Rails execution is intentionally
unavailable until a controller action can preserve normal delegation and the
role's single-boot Rails safety contract; selecting Rails alone therefore
strict-fails or compatible-skips rather than silently using another
transport.
Target and authentication¶
Every invocation takes an explicit target; public tasks do not read ambient
role variables. Structured token and OAuth access-token forms build headers
internally:
gitlab_settings_target:
url: https://gitlab.example.com
auth:
type: oauth2
access_token: "{{ vault_gitlab_oauth_access_token }}"
Project and group access tokens additionally require a caller-declared
bound_to maximum authority. For example:
gitlab_settings_target:
url: https://gitlab.example.com
auth:
type: group_access_token
token: "{{ vault_gitlab_group_access_token }}"
bound_to:
resource: group
identifier: platform
The registry records accepted credential types, scope alternatives, request
forms, resource-boundary rules, and sudo support for every operation/backend.
Each record is built from the concrete (path, method) REST endpoints, or the
committed GraphQL operation documents, that the operation actually performs,
and is the conjunction over them — so a record is never broader than the
narrowest endpoint involved. Known incompatible or out-of-boundary uses fail
before an API client builds credential headers, and an unregistered resource
fails closed rather than inheriting a general capability.
CI/CD job tokens and deploy tokens are declarable and fully implemented, but
are currently refused for every selectable operation: GitLab opts routes in to
them individually and the role holds no pinned allowlist for its v19.2.0-ee
contract. Refusing is the safe direction, because an unopted route treats such
a credential as absent rather than rejecting it. Each refusal names the
evidence artifact that would settle the decision.
GitLab requires a credential to read instance metadata, and the registry
decides which credentials it accepts. A target whose credential cannot perform
that read — currently anonymous — must
declare an exact supported target.version, and compatibility is validated
from that declaration instead of a live /metadata read. Every other
credential discovers version and edition live.
TLS validation defaults on and ca_bundle is local to the module execution
host (the controller when the task is delegated there). The public actions
discover /metadata internally after semantic validation, require the same
committed compatibility profile as the bulk role, and treat target
version/edition fields as expected constraints rather than overrides.
OAuth access tokens are supported consistently by REST, GraphQL, discovery,
and compatibility reads. Managed refresh is opt-in through
auth.refresh.enabled and is performed by each action's associated action
plugin on the Ansible controller, so a rotated pair is committed durably
before the module runs and is shared by later loop items, later tasks, and
later plays. Refresh and application material never leave the controller.
An access token with no refresh configuration still fails with a specific
recovery message once it is known to be expired. Discovery and reconciliation
never initialize a credential store or rotate a token in check mode; use
gitlab_oauth_session with check_mode: false when you intend to permit
maintenance during a --check run.
Anonymous and deploy-token targets are accepted by the same contract. Which credential an operation accepts is decided by the shared semantic resource registry before any request is built, so an unsupported combination fails with an authentication message rather than a transport one.
Snapshot discovery is separate¶
The role's existing tasks_from: discover entry point captures GitLab version
and application-setting snapshots and may persist
gitlab_settings_snapshot_file. It has not been repurposed as arbitrary
resource enumeration. The public gitlab_settings_discover action lists one
semantic resource and never writes a snapshot.
Internal actions such as gitlab_settings_rest_resource,
gitlab_settings_graphql_resource, and compatibility/planner modules are role
implementation details. Public playbooks should call only the FQCNs above or
the documented bulk role entry points.