123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477 |
- ---
- # Deploys RHBK in a namespace with the Keycloak operator CSV.
- - name: Check if there is a namespace.
- kubernetes.core.k8s_info:
- kubeconfig: tmp/kubeconfig-ocp4
- validate_certs: no
- api_version: v1
- kind: namespace
- name: "{{ rhbk.namespace | default('keycloak') }}"
- register: prereq_ns
- - name: Fail if not so.
- ansible.builtin.assert:
- that:
- - prereq_ns.resources is defined
- - prereq_ns.resources | length == 1
- success_msg: "OK, namespace found."
- fail_msg: "FATAL: namespace to deploy ({{ rhbk.namespace | default('keycloak') }}) not found. Ensure there is an operator already present."
- - name: Check if there is a CSV in the namespace.
- kubernetes.core.k8s_info:
- kubeconfig: tmp/kubeconfig-ocp4
- validate_certs: no
- api_version: operators.coreos.com/v1alpha1
- kind: clusterserviceversion
- namespace: "{{ rhbk.namespace | default('keycloak') }}"
- label_selectors:
- - operators.coreos.com/rhbk-operator.keycloak=
- register: prereq_csv
- - name: Fail if not so.
- ansible.builtin.assert:
- that:
- - prereq_csv.resources is defined
- - prereq_csv.resources | length > 0
- success_msg: "OK, operator CSV found."
- fail_msg: "FATAL: Operator is not deployed in the namespace: {{ rhbk.namespace | default('keycloak') }}. Ensure there is an operator already present."
- - name: Tech hack. Prevent anything from blowing up because rhbk is defined somewhere, but not its structured contents.
- ansible.builtin.set_fact:
- rhbk: "{{ rhbk | default({}) | combine({ 'db': {} }) }}"
- when:
- - rhbk.db is not defined
- - name: Ensure there is a secret containing DB credentials in the project.
- kubernetes.core.k8s:
- kubeconfig: tmp/kubeconfig-ocp4
- validate_certs: no
- api_version: v1
- kind: secret
- namespace: "{{ rhbk.namespace | default('keycloak') }}"
- name: "{{ rhbk.name | default('sso') }}-db-auth"
- resource_definition:
- data:
- username: "{{ rhbk.db.username | default('rhbk') | b64encode }}"
- password: "{{ rhbk.db.password | default('secret') | b64encode }}"
- # TODO: ensure that there is no STS to begin with, or if there is, verify that
- # only the allowed fields would change, if anything. Otherwise:
- #
- # Forbidden: updates to statefulset spec for fields other than
- # 'replicas', 'ordinals', 'template', 'updateStrategy',
- # 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are
- # forbidden
- #
- - name: Ensure there is a database sts in the project
- kubernetes.core.k8s:
- kubeconfig: tmp/kubeconfig-ocp4
- validate_certs: no
- api_version: apps/v1
- kind: statefulset
- namespace: "{{ rhbk.namespace | default('keycloak') }}"
- name: "{{ rhbk.name | default('sso') }}-db"
- resource_definition:
- spec:
- # TODO: implement rhbk.db.replicas at some point?
- replicas: 1
- serviceName: "{{ rhbk.name | default('sso') }}-db"
- selector:
- matchLabels:
- app: "{{ rhbk.name | default('sso') }}-db"
- template:
- metadata:
- labels:
- app: "{{ rhbk.name | default('sso') }}-db"
- spec:
- containers:
- - name: "{{ rhbk.name | default('sso') }}-db"
- image: "{{ rhbk.db.image | default('registry.redhat.io/rhel9/postgresql-15:latest') }}"
- volumeMounts:
- - mountPath: /var/lib/pgsql/data
- name: "{{ rhbk.name | default('sso') }}-db-data"
- env:
- - name: POSTGRESQL_USER
- valueFrom:
- secretKeyRef:
- name: "{{ rhbk.name | default('sso') }}-db-auth"
- key: username
- - name: POSTGRESQL_PASSWORD
- valueFrom:
- secretKeyRef:
- name: "{{ rhbk.name | default('sso') }}-db-auth"
- key: password
- - name: POSTGRESQL_DATABASE
- value: "{{ rhbk.db.name | default('rhbk') }}"
- volumeClaimTemplates:
- - metadata:
- name: "{{ rhbk.name | default('sso') }}-db-data"
- spec:
- accessModes: "{{ rhbk.db.claim_modes | default(['ReadWriteOnce']) }}"
- storageClassName: "{{ rhbk.db.storage_class | default(omit) }}"
- resources:
- requests:
- storage: "{{ rhbk.db.size | default('1Gi') }}"
- - name: Ensure there is a service for the database as well
- kubernetes.core.k8s:
- kubeconfig: tmp/kubeconfig-ocp4
- validate_certs: no
- api_version: v1
- kind: service
- namespace: "{{ rhbk.namespace | default('keycloak') }}"
- name: "{{ rhbk.name | default('sso') }}-db"
- resource_definition:
- spec:
- selector:
- app: "{{ rhbk.name | default('sso') }}-db"
- type: LoadBalancer
- ports:
- - port: 5432
- targetPort: 5432
- protocol: TCP
- - name: Ensure there is a secret containing Keycloak bootstrap credentials
- kubernetes.core.k8s:
- kubeconfig: tmp/kubeconfig-ocp4
- validate_certs: no
- api_version: v1
- kind: secret
- namespace: "{{ rhbk.namespace | default('keycloak') }}"
- name: "{{ rhbk.name | default('sso') }}-auth"
- resource_definition:
- data:
- username: "{{ rhbk.admin.username | default('rhbk') | b64encode }}"
- password: "{{ rhbk.admin.password | default('secret') | b64encode }}"
- - name: If there is no FQDN, check what the default domain is.
- kubernetes.core.k8s_info:
- kubeconfig: tmp/kubeconfig-ocp4
- validate_certs: no
- api_version: operator.openshift.io/v1
- kind: ingresscontroller
- namespace: openshift-ingress-operator
- name: default
- register: default_ingress
- when: rhbk.fqdn is not defined
- - name: Set a fact that reflects either the FQDN as set, or a composition of vars and default ingress info.
- ansible.builtin.set_fact:
- rhbk_fqdn: "{{ rhbk.fqdn | default((rhbk.name | default('sso')) + '-' + (rhbk.namespace | default('keycloak')) + '.' + default_ingress.resources[0].status.domain) }}"
- - name: Announce what hostname would be used.
- ansible.builtin.debug:
- msg: Using "https://{{ rhbk_fqdn }}" as the hostname.
- # TODO: remember if there were changes, and force delete any non-ready pod?
- - name: Lastly, make sure there is a Keycloak
- kubernetes.core.k8s:
- kubeconfig: tmp/kubeconfig-ocp4
- validate_certs: no
- api_version: k8s.keycloak.org/v2alpha1
- kind: keycloak
- namespace: "{{ rhbk.namespace | default('keycloak') }}"
- name: "{{ rhbk.name | default('sso') }}"
- resource_definition:
- spec:
- instances: "{{ rhbk.replicas | default(1) }}"
- db:
- vendor: postgres
- host: "{{ rhbk.name | default('sso') }}-db"
- database: "{{ rhbk.db.name | default('rhbk') }}"
- usernameSecret:
- name: "{{ rhbk.name | default('sso') }}-db-auth"
- key: username
- passwordSecret:
- name: "{{ rhbk.name | default('sso') }}-db-auth"
- key: password
- hostname:
- hostname: "https://{{ rhbk_fqdn }}"
- strict: false
- backchannelDynamic: true
- http:
- httpEnabled: true
- httpPort: 8080
- httpsPort: 8443
- tlsSecret: "{{ rhbk.name | default('sso') }}-tls"
- bootstrapAdmin:
- user:
- secret: "{{ rhbk.name | default('sso') }}-auth"
- ingress:
- enabled: false
- - name: Wait for the service to show up.
- kubernetes.core.k8s_info:
- kubeconfig: tmp/kubeconfig-ocp4
- validate_certs: no
- api_version: v1
- kind: service
- namespace: "{{ rhbk.namespace | default('keycloak') }}"
- name: "{{ rhbk.name | default('sso') }}-service"
- register: rhbk_svc
- until:
- - rhbk_svc.resources is defined
- - (rhbk_svc.resources | length) > 0
- retries: 24
- delay: 5
- - name: Ensure the service is correctly annotated.
- kubernetes.core.k8s:
- kubeconfig: tmp/kubeconfig-ocp4
- validate_certs: no
- api_version: v1
- kind: service
- namespace: "{{ rhbk.namespace | default('keycloak') }}"
- name: "{{ rhbk.name | default('sso') }}-service"
- state: patched
- resource_definition:
- metadata:
- annotations:
- service.beta.openshift.io/serving-cert-secret-name: "{{ rhbk.name | default('sso') }}-tls"
- - name: Make sure there is a re-encrypt route.
- kubernetes.core.k8s:
- kubeconfig: tmp/kubeconfig-ocp4
- validate_certs: no
- api_version: route.openshift.io/v1
- kind: route
- namespace: "{{ rhbk.namespace | default('keycloak') }}"
- name: "{{ rhbk.name | default('sso') }}"
- resource_definition:
- spec:
- to:
- kind: Service
- name: "{{ rhbk.name | default('sso') }}-service"
- port:
- targetPort: 8443
- tls:
- termination: reencrypt
- insecureEdgeTerminationPolicy: Redirect
- - name: Wait for the Keycloak resource to report ready.
- kubernetes.core.k8s_info:
- kubeconfig: tmp/kubeconfig-ocp4
- validate_certs: no
- api_version: k8s.keycloak.org/v2alpha1
- kind: keycloak
- namespace: "{{ rhbk.namespace | default('keycloak') }}"
- name: "{{ rhbk.name | default('sso') }}"
- register: rhbk_ready
- until:
- - rhbk_ready.resources is defined
- - rhbk_ready.resources | length == 1
- - rhbk_ready.resources[0].status is defined
- - (rhbk_ready.resources[0].status | community.general.json_query('conditions[?type==`Ready`].status'))[0]
- retries: 24
- delay: 5
- - name: Wait for the Keycloak pod to become ready.
- ansible.builtin.uri:
- return_content: yes
- validate_certs: no
- url: "https://{{ rhbk_fqdn }}/realms/master"
- register: rhbk_is_ready
- until: rhbk_is_ready.status == 200
- retries: 24
- delay: 5
- - name: Get a fresh bearer token.
- ansible.builtin.include_tasks:
- file: tasks/token.yml
- - name: Get a list of existing realms.
- ansible.builtin.uri:
- method: GET
- return_content: true
- validate_certs: false
- url: "https://{{ rhbk_fqdn }}/admin/realms"
- headers:
- Authorization: Bearer {{ admin_token }}
- Accept: application/json
- register: rhbk_realms
- - name: Store the list of realm names/ids as a fact
- ansible.builtin.set_fact:
- realms: "{{ rhbk_realms.json | items2dict(key_name='realm', value_name='id') }}"
- - name: Import the realm if not present yet
- block:
- - name: Check whether there is already a realm import CR
- kubernetes.core.k8s_info:
- kubeconfig: tmp/kubeconfig-ocp4
- validate_certs: no
- api_version: k8s.keycloak.org/v2alpha1
- kind: keycloakrealmimport
- namespace: "{{ rhbk.namespace | default('keycloak') }}"
- register: realm_imports
- - name: Remove a previous realm import if it happens to be there.
- kubernetes.core.k8s:
- kubeconfig: tmp/kubeconfig-ocp4
- validate_certs: no
- api_version: k8s.keycloak.org/v2alpha1
- kind: keycloakrealmimport
- namespace: "{{ rhbk.namespace | default('keycloak') }}"
- name: "{{ rhbk.name | default('sso') }}-{{ rhbk.realm | default('sample-realm') }}-import"
- state: absent
- when:
- - realm_imports.resources is defined
- - (realm_imports.resources | length) > 0
- - (realm_imports | community.general.json_query('resources[*].metadata.name')) is contains((rhbk.name | default('sso')) + '-' + (rhbk.realm | default('sample-realm')) + '-import')
- # TODO: finish templating this one, introduce more settings as needed
- - name: Apply a template realm import.
- kubernetes.core.k8s:
- kubeconfig: tmp/kubeconfig-ocp4
- validate_certs: no
- api_version: k8s.keycloak.org/v2alpha1
- kind: keycloakrealmimport
- namespace: "{{ rhbk.namespace | default('keycloak') }}"
- name: "{{ rhbk.name | default('sso') }}-{{ rhbk.realm | default('sample-realm') }}-import"
- template: templates/realm-import-template.yaml.j2
- register: created_import
- - name: Wait for the import to rollout.
- kubernetes.core.k8s_info:
- kubeconfig: tmp/kubeconfig-ocp4
- validate_certs: no
- api_version: v1
- kind: pod
- namespace: "{{ rhbk.namespace | default('keycloak') }}"
- label_selectors:
- - app=keycloak-realm-import
- register: import_running
- until:
- - import_running.resources is defined
- - import_running.resources | length > 0
- - import_running.resources[0].status.phase == "Succeeded"
- retries: 12
- delay: 5
- when: created_import.changed
- - name: Wait for the Keycloak pod to become ready.
- ansible.builtin.uri:
- return_content: yes
- validate_certs: no
- url: "https://{{ rhbk_fqdn }}/realms/{{ rhbk.realm | default('sample-realm') }}"
- register: rhbk_is_back
- until: rhbk_is_back.status == 200
- retries: 24
- delay: 5
- when: created_import.changed
- when:
- - realms[rhbk.realm | default('sample-realm')] is not defined
- - name: Get a fresh bearer token.
- ansible.builtin.include_tasks:
- file: tasks/token.yml
- - name: Get a list of existing groups in the realm.
- ansible.builtin.uri:
- method: GET
- return_content: true
- validate_certs: false
- url: "https://{{ rhbk_fqdn }}/admin/realms/{{ rhbk.realm | default('sample-realm') }}/groups"
- headers:
- Authorization: Bearer {{ admin_token }}
- Accept: application/json
- register: rhbk_realm_groups
- - name: Show what groups were found at verbosity 2+.
- ansible.builtin.debug:
- var: rhbk_realm_groups
- verbosity: 2
- - name: Create the groups if necessary.
- ansible.builtin.uri:
- method: POST
- return_content: true
- validate_certs: false
- url: "https://{{ rhbk_fqdn }}/admin/realms/{{ rhbk.realm | default('sample-realm') }}/groups"
- headers:
- Authorization: Bearer {{ admin_token }}
- Accept: application/json
- Content-Type: application/json
- body_format: json
- body: |
- {
- "name": "{{ item }}"
- }
- status_code:
- - 200
- - 201
- register: created_groups
- loop: "{{ rhbk.groups }}"
- when:
- - (rhbk_realm_groups.json | items2dict(key_name='name', value_name='id')).keys() is not contains(item)
- - name: Show what groups were created at verbosity 2+.
- ansible.builtin.debug:
- var: created_groups
- verbosity: 2
- - name: Get a fresh bearer token.
- ansible.builtin.include_tasks:
- file: tasks/token.yml
- - name: Get a list of existing users in the realm.
- ansible.builtin.uri:
- method: GET
- return_content: true
- validate_certs: false
- url: "https://{{ rhbk_fqdn }}/admin/realms/{{ rhbk.realm | default('sample-realm') }}/users"
- headers:
- Authorization: Bearer {{ admin_token }}
- Accept: application/json
- register: rhbk_realm_users
- - name: Show what users were found at verbosity 2+.
- ansible.builtin.debug:
- var: rhbk_realm_users
- verbosity: 2
- - name: Create the users if necessary.
- ansible.builtin.uri:
- method: POST
- return_content: true
- validate_certs: false
- url: "https://{{ rhbk_fqdn }}/admin/realms/{{ rhbk.realm | default('sample-realm') }}/users"
- headers:
- Authorization: Bearer {{ admin_token }}
- Accept: application/json
- Content-Type: application/json
- body_format: json
- body: |
- {
- "username": "{{ item.username }}",
- "email": "{{ item.email | default(item.username + '@example.com') }}",
- "firstName": "{{ item.firstname | default('') }}",
- "lastName": "{{ item.lastname | default('') }}",
- "credentials": [
- {
- "type": "password",
- "temporary": false,
- "value": "{{ item.password | default('secret') }}"
- }
- ],
- "enabled": true,
- "emailVerified": true,
- {% if item.groups is defined and (item.groups | length) > 0 %}
- "groups": [ "{{ item.groups | join('", "') }}" ]
- {% endif %}
- }
- status_code:
- - 200
- - 201
- register: created_users
- loop: "{{ rhbk.users }}"
- when:
- - (rhbk_realm_users.json | items2dict(key_name='username', value_name='id')).keys() is not contains(item.username)
- - name: Show what users were created at verbosity 2+.
- ansible.builtin.debug:
- var: created_users
- verbosity: 2
- ...
|