Skip to content

Commit cd97629

Browse files
committed
refactor: migrate Keycloak tasks to use kubernetes.core modules for improved idempotency
1 parent c2a55ed commit cd97629

2 files changed

Lines changed: 73 additions & 128 deletions

File tree

ansible/roles/keycloak/tasks/main.yml

Lines changed: 72 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,14 @@
99
block:
1010
# ── Namespace ──────────────────────────────────────────────────────────────
1111
- name: Ensure Keycloak namespace exists
12-
ansible.builtin.command:
13-
cmd: >-
14-
k3s kubectl create namespace {{ keycloak_namespace }}
15-
--dry-run=client -o yaml
16-
environment:
17-
KUBECONFIG: "{{ keycloak_kubeconfig_path }}"
18-
register: _keycloak_namespace_yaml
19-
changed_when: false
20-
when: not ansible_check_mode
21-
22-
- name: Apply Keycloak namespace manifest
23-
ansible.builtin.command:
24-
cmd: k3s kubectl apply -f -
25-
stdin: "{{ _keycloak_namespace_yaml.stdout }}"
26-
environment:
27-
KUBECONFIG: "{{ keycloak_kubeconfig_path }}"
28-
register: _keycloak_namespace_apply
29-
changed_when: >-
30-
'created' in (_keycloak_namespace_apply.stdout | default(''))
12+
kubernetes.core.k8s:
13+
kubeconfig: "{{ keycloak_kubeconfig_path }}"
14+
state: present
15+
definition:
16+
apiVersion: v1
17+
kind: Namespace
18+
metadata:
19+
name: "{{ keycloak_namespace }}"
3120
when: not ansible_check_mode
3221

3322
# ── OpenBao: generate + persist credentials ─────────────────────────────────
@@ -155,33 +144,21 @@
155144
when: not ansible_check_mode
156145

157146
# The Secret must exist before the Keycloak CR is applied: the operator reads
158-
# spec.bootstrapAdmin.user.secret at first creation. Render via kubectl
159-
# dry-run|apply so the credentials never touch the host filesystem.
160-
- name: Generate master bootstrap-admin Secret manifest
161-
ansible.builtin.command:
162-
cmd: >-
163-
k3s kubectl create secret generic {{ keycloak_bootstrap_admin_secret_name }}
164-
--namespace {{ keycloak_namespace }}
165-
--from-literal=username={{ keycloak_bootstrap_admin_username }}
166-
--from-literal=password={{ _keycloak_bootstrap_admin_password }}
167-
--dry-run=client -o yaml
168-
environment:
169-
KUBECONFIG: "{{ keycloak_kubeconfig_path }}"
170-
register: _keycloak_bootstrap_admin_secret_yaml
171-
changed_when: false
172-
no_log: "{{ not (armory_log_nolog | default(false) | bool) }}"
173-
when: not ansible_check_mode
174-
147+
# spec.bootstrapAdmin.user.secret at first creation.
175148
- name: Apply master bootstrap-admin Secret in the Keycloak namespace
176-
ansible.builtin.command:
177-
cmd: k3s kubectl apply -f -
178-
stdin: "{{ _keycloak_bootstrap_admin_secret_yaml.stdout }}"
179-
environment:
180-
KUBECONFIG: "{{ keycloak_kubeconfig_path }}"
181-
register: _keycloak_bootstrap_admin_secret_apply
182-
changed_when: >-
183-
'configured' in (_keycloak_bootstrap_admin_secret_apply.stdout | default('')) or
184-
'created' in (_keycloak_bootstrap_admin_secret_apply.stdout | default(''))
149+
kubernetes.core.k8s:
150+
kubeconfig: "{{ keycloak_kubeconfig_path }}"
151+
state: present
152+
definition:
153+
apiVersion: v1
154+
kind: Secret
155+
metadata:
156+
name: "{{ keycloak_bootstrap_admin_secret_name }}"
157+
namespace: "{{ keycloak_namespace }}"
158+
type: Opaque
159+
stringData:
160+
username: "{{ keycloak_bootstrap_admin_username }}"
161+
password: "{{ _keycloak_bootstrap_admin_password }}"
185162
no_log: "{{ not (armory_log_nolog | default(false) | bool) }}"
186163
when: not ansible_check_mode
187164

@@ -198,39 +175,26 @@
198175
when: not (use_declarative_ca_distribution | default(false) | bool)
199176

200177
- name: Apply Keycloak VaultConnection and VaultAuth resources
201-
ansible.builtin.command:
202-
cmd: k3s kubectl apply -f -
203-
stdin: "{{ lookup('template', 'vaultconnection.yaml.j2') }}\n---\n{{ lookup('template', 'vaultauth.yaml.j2') }}"
204-
environment:
205-
KUBECONFIG: "{{ keycloak_kubeconfig_path }}"
206-
register: _keycloak_vault_auth_apply
207-
changed_when: >-
208-
'configured' in (_keycloak_vault_auth_apply.stdout | default('')) or
209-
'created' in (_keycloak_vault_auth_apply.stdout | default(''))
178+
kubernetes.core.k8s:
179+
kubeconfig: "{{ keycloak_kubeconfig_path }}"
180+
state: present
181+
definition: >-
182+
{{ (lookup('template', 'vaultconnection.yaml.j2') | from_yaml_all | list)
183+
+ (lookup('template', 'vaultauth.yaml.j2') | from_yaml_all | list) }}
210184
when: not ansible_check_mode
211185

212186
- name: Apply Keycloak VaultStaticSecret (DB credentials)
213-
ansible.builtin.command:
214-
cmd: k3s kubectl apply -f -
215-
stdin: "{{ lookup('template', 'vaultstaticsecret.yaml.j2') }}"
216-
environment:
217-
KUBECONFIG: "{{ keycloak_kubeconfig_path }}"
218-
register: _keycloak_vss_apply
219-
changed_when: >-
220-
'configured' in (_keycloak_vss_apply.stdout | default('')) or
221-
'created' in (_keycloak_vss_apply.stdout | default(''))
187+
kubernetes.core.k8s:
188+
kubeconfig: "{{ keycloak_kubeconfig_path }}"
189+
state: present
190+
definition: "{{ lookup('template', 'vaultstaticsecret.yaml.j2') | from_yaml_all | list }}"
222191
when: not ansible_check_mode
223192

224193
- name: Apply Keycloak VaultStaticSecret (realm-admin login mirror)
225-
ansible.builtin.command:
226-
cmd: k3s kubectl apply -f -
227-
stdin: "{{ lookup('template', 'vaultstaticsecret_realm_admin.yaml.j2') }}"
228-
environment:
229-
KUBECONFIG: "{{ keycloak_kubeconfig_path }}"
230-
register: _keycloak_realm_admin_vss_apply
231-
changed_when: >-
232-
'configured' in (_keycloak_realm_admin_vss_apply.stdout | default('')) or
233-
'created' in (_keycloak_realm_admin_vss_apply.stdout | default(''))
194+
kubernetes.core.k8s:
195+
kubeconfig: "{{ keycloak_kubeconfig_path }}"
196+
state: present
197+
definition: "{{ lookup('template', 'vaultstaticsecret_realm_admin.yaml.j2') | from_yaml_all | list }}"
234198
when: not ansible_check_mode
235199

236200
- name: Wait for keycloak-db-secret to be synced by VSO
@@ -268,15 +232,10 @@
268232

269233
# ── PostgreSQL StatefulSet ──────────────────────────────────────────────────
270234
- name: Apply PostgreSQL StatefulSet and Service
271-
ansible.builtin.command:
272-
cmd: k3s kubectl apply -f -
273-
stdin: "{{ lookup('template', 'postgres.yaml.j2') }}"
274-
environment:
275-
KUBECONFIG: "{{ keycloak_kubeconfig_path }}"
276-
register: _keycloak_pg_apply
277-
changed_when: >-
278-
'configured' in (_keycloak_pg_apply.stdout | default('')) or
279-
'created' in (_keycloak_pg_apply.stdout | default(''))
235+
kubernetes.core.k8s:
236+
kubeconfig: "{{ keycloak_kubeconfig_path }}"
237+
state: present
238+
definition: "{{ lookup('template', 'postgres.yaml.j2') | from_yaml_all | list }}"
280239
when:
281240
- not ansible_check_mode
282241
- keycloak_postgresql_enabled | bool
@@ -314,32 +273,32 @@
314273
- keycloak_postgresql_enabled | bool
315274
- keycloak_pg_tls_enabled | bool
316275

317-
- name: Create Keycloak Postgres CA ConfigMap manifest
318-
ansible.builtin.command:
319-
cmd: >-
320-
k3s kubectl create configmap {{ keycloak_pg_tls_ca_configmap_name }}
321-
--namespace {{ keycloak_namespace }}
322-
--from-file={{ keycloak_pg_tls_ca_file_name }}={{ keycloak_pg_tls_ca_bundle_path }}
323-
--dry-run=client -o yaml
324-
environment:
325-
KUBECONFIG: "{{ keycloak_kubeconfig_path }}"
326-
register: _keycloak_pg_ca_configmap_yaml
327-
changed_when: false
276+
# The bundle is written root-owned by the common prepare task (under become),
277+
# so it must be read by a module that runs on the target under become — a
278+
# controller-side lookup('file', …) runs as the unprivileged user and is
279+
# denied. slurp reads it as root; b64decode inlines the PEM into the ConfigMap.
280+
- name: Read Postgres CA bundle for ConfigMap (privileged read under become)
281+
ansible.builtin.slurp:
282+
src: "{{ keycloak_pg_tls_ca_bundle_path }}"
283+
register: _keycloak_pg_ca_bundle
328284
when:
329285
- not ansible_check_mode
330286
- keycloak_postgresql_enabled | bool
331287
- keycloak_pg_tls_enabled | bool
332288

333289
- name: Apply Keycloak Postgres CA ConfigMap
334-
ansible.builtin.command:
335-
cmd: k3s kubectl apply -f -
336-
stdin: "{{ _keycloak_pg_ca_configmap_yaml.stdout }}"
337-
environment:
338-
KUBECONFIG: "{{ keycloak_kubeconfig_path }}"
339-
register: _keycloak_pg_ca_configmap_apply
340-
changed_when: >-
341-
'configured' in (_keycloak_pg_ca_configmap_apply.stdout | default('')) or
342-
'created' in (_keycloak_pg_ca_configmap_apply.stdout | default(''))
290+
kubernetes.core.k8s:
291+
kubeconfig: "{{ keycloak_kubeconfig_path }}"
292+
state: present
293+
definition:
294+
apiVersion: v1
295+
kind: ConfigMap
296+
metadata:
297+
name: "{{ keycloak_pg_tls_ca_configmap_name }}"
298+
namespace: "{{ keycloak_namespace }}"
299+
# The data key is variable-named; build the dict in one Jinja
300+
# expression so the KEY is evaluated (Ansible does not template dict keys).
301+
data: "{{ {keycloak_pg_tls_ca_file_name: (_keycloak_pg_ca_bundle.content | b64decode)} }}"
343302
when:
344303
- not ansible_check_mode
345304
- keycloak_postgresql_enabled | bool
@@ -468,15 +427,10 @@
468427

469428
# ── Keycloak custom resource ────────────────────────────────────────────────
470429
- name: Apply Keycloak custom resource
471-
ansible.builtin.command:
472-
cmd: k3s kubectl apply -f -
473-
stdin: "{{ lookup('template', 'keycloak.yaml.j2') }}"
474-
environment:
475-
KUBECONFIG: "{{ keycloak_kubeconfig_path }}"
476-
register: _keycloak_cr_apply
477-
changed_when: >-
478-
'configured' in (_keycloak_cr_apply.stdout | default('')) or
479-
'created' in (_keycloak_cr_apply.stdout | default(''))
430+
kubernetes.core.k8s:
431+
kubeconfig: "{{ keycloak_kubeconfig_path }}"
432+
state: present
433+
definition: "{{ lookup('template', 'keycloak.yaml.j2') | from_yaml_all | list }}"
480434
when: not ansible_check_mode
481435

482436
- name: Wait for Keycloak custom resource to become Ready
@@ -546,15 +500,10 @@
546500

547501
# ── Realm import ────────────────────────────────────────────────────────────
548502
- name: Apply Keycloak realm import
549-
ansible.builtin.command:
550-
cmd: k3s kubectl apply -f -
551-
stdin: "{{ lookup('template', 'realmimport.yaml.j2') }}"
552-
environment:
553-
KUBECONFIG: "{{ keycloak_kubeconfig_path }}"
554-
register: _keycloak_realm_apply
555-
changed_when: >-
556-
'configured' in (_keycloak_realm_apply.stdout | default('')) or
557-
'created' in (_keycloak_realm_apply.stdout | default(''))
503+
kubernetes.core.k8s:
504+
kubeconfig: "{{ keycloak_kubeconfig_path }}"
505+
state: present
506+
definition: "{{ lookup('template', 'realmimport.yaml.j2') | from_yaml_all | list }}"
558507
no_log: "{{ not (armory_log_nolog | default(false) | bool) }}"
559508
when: not ansible_check_mode
560509

@@ -581,15 +530,10 @@
581530

582531
# ── Ingress (edge TLS at nginx) ─────────────────────────────────────────────
583532
- name: Apply Keycloak Ingress
584-
ansible.builtin.command:
585-
cmd: k3s kubectl apply -f -
586-
stdin: "{{ lookup('template', 'ingress.yaml.j2') }}"
587-
environment:
588-
KUBECONFIG: "{{ keycloak_kubeconfig_path }}"
589-
register: _keycloak_ingress_apply
590-
changed_when: >-
591-
'configured' in (_keycloak_ingress_apply.stdout | default('')) or
592-
'created' in (_keycloak_ingress_apply.stdout | default(''))
533+
kubernetes.core.k8s:
534+
kubeconfig: "{{ keycloak_kubeconfig_path }}"
535+
state: present
536+
definition: "{{ lookup('template', 'ingress.yaml.j2') | from_yaml_all | list }}"
593537
when:
594538
- not ansible_check_mode
595539
- keycloak_ingress_enabled | bool

doc/handoffs/kubernetes-core-idempotency-plan.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Handoff: `kubernetes.core` Migration + Idempotency Restoration
22

33
Status: Stages 0–2 complete (Helm track done, cause #1 closed) · Stage 3 next · Owner: Copilot · Drafted: 2026-06-17
4+
Status: Stages 0–3 complete · Stage 4 code done, static validation in progress · Owner: Copilot · Drafted: 2026-06-17
45

56
## Goal
67

0 commit comments

Comments
 (0)