Managed OAuth session¶
Use case: an automation identity authenticates with OAuth instead of a
long-lived personal token. A caller-managed access token needs only three
fields; opting into managed refresh hands the role a durable
controller-side store that survives token rotation — a real credential
mutation, since GitLab invalidates the previous pair on every exchange.
The example also shows the focused gitlab_credential_check and
gitlab_oauth_session tasks, and an explicitly anonymous read of a
public resource.
Source: examples/oauth-managed-session.yml
---
- name: Reconcile GitLab settings with a managed OAuth session
hosts: localhost
gather_facts: false
connection: local
vars:
# A caller-managed access token needs only these three fields. Managed
# refresh is opt-in because rotating a refresh token is a real credential
# mutation: GitLab invalidates the previous pair on every exchange.
gitlab_settings_target:
url: https://gitlab.example.com
auth:
type: oauth2
access_token: "{{ vault_gitlab_oauth_access_token }}"
refresh_token: "{{ vault_gitlab_oauth_refresh_token }}"
client_id: "{{ vault_gitlab_oauth_client_id }}"
# Omit client_secret for a public PKCE client. An empty value is a
# missing-secret error, never an implicit downgrade.
client_secret: "{{ vault_gitlab_oauth_client_secret }}"
redirect_uri: https://automation.example.com/oauth/callback
refresh:
enabled: true
before_expiry: 300
store:
type: controller_file
path: /secure/ansible/oauth/gitlab-primary.json
tasks:
# Optional. One request verifies the credential can do the work below:
# that it is active, is the kind you declared, was issued by the expected
# application, has not expired, and carries scopes the operations accept.
# List every resource the play touches: their requirements are resolved
# locally, so the whole set is compared against a single response. Running
# it here keeps that cost at one request no matter how many groups the loop
# then reconciles.
- name: Verify the credential before converging anything
primetheus.gitlab_settings.gitlab_credential_check:
target: "{{ gitlab_settings_target }}"
resources: [group]
write: true
# Optional. Ordinary tasks refresh transparently in a normal run; this is
# only needed to establish or repair the session deliberately, or to make
# a session usable by a later --check run.
- name: Ensure the OAuth session is valid
primetheus.gitlab_settings.gitlab_oauth_session:
target: "{{ gitlab_settings_target }}"
check_mode: false
register: gitlab_oauth
- name: Report safe session state
ansible.builtin.debug:
msg: >-
session {{ 'maintained' if gitlab_oauth['changed'] else 'already current' }}
({{ gitlab_oauth['authentication']['refresh_reason'] }}),
expires {{ gitlab_oauth['authentication']['expires_at'] | default('unknown') }}
# Neither task below knows whether OAuth was refreshed, or whether the
# selected settings backend is REST or GraphQL.
- 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 each group
primetheus.gitlab_settings.gitlab_settings:
target: "{{ gitlab_settings_target }}"
resource: group
identifier: "{{ item['identifier'] }}"
settings:
duo:
duo_features_enabled: true
loop: "{{ discovered_groups['resources'] }}"
loop_control:
label: "{{ item['identifier'] }}"
- name: Read a public GitLab resource without any credential
hosts: localhost
gather_facts: false
connection: local
tasks:
# Anonymous access is explicit and is accepted only for operations GitLab
# documents as publicly readable. It is never a fallback after a failure.
# GitLab requires a credential to read instance metadata, so an anonymous
# target declares the compatibility profile instead of probing for it.
- name: Discover public groups on GitLab.com
primetheus.gitlab_settings.gitlab_settings_discover:
target:
url: https://gitlab.com
auth:
type: anonymous
version: 19.2.0-ee
resource: group
scope: all
limit: 20
register: public_groups
- name: Report how many public groups were visible
ansible.builtin.debug:
msg: "{{ public_groups['resources'] | length }} public groups discovered"