main.yml 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. ---
  2. # Ensures there is an instance of RHBoK running in a configurable namespace.
  3. #
  4. # Configures it with a realm, and some users.
  5. #
  6. # Required variables (some are supplied by defaults/main.yml):
  7. #
  8. # rhbk:
  9. # namespace: namespace to deploy to (keycloak)
  10. # name: name of the instance (sso)
  11. # replicas: how many instances
  12. # fqdn: fqdn of the route (hostname), detected if omitted
  13. # admin: bootstrap admin credentials
  14. # username: username (rhbk)
  15. # password: password (secret)
  16. # db: database-specific settings
  17. # image: db server image
  18. # name: database name (rhbk)
  19. # username: database owner (rhbk)
  20. # password: db owner's password (secret)
  21. # claim_modes: volume claim template access modes, list (ReadWriteOnce)
  22. # storage_class: storage class name, no default (omitted)
  23. # size: pvc size (1Gi)
  24. # replicas: how many instances (TODO ignored for now)
  25. # realm: name of the realm (sample-realm)
  26. # users: users to create in realm, no default (meaning no users)
  27. # - username: required (as it is key)
  28. # password: optional, defaults to "secret"
  29. # fullname: optional, set to username if empty
  30. # email: optional, set to username@example.com if empty
  31. #
  32. # NOTE: Must have an operator deployed in that namespace prior (use deploy-operators role for that).
  33. #
  34. - name: Check if there is a namespace.
  35. kubernetes.core.k8s_info:
  36. kubeconfig: tmp/kubeconfig-ocp4
  37. validate_certs: no
  38. api_version: v1
  39. kind: namespace
  40. name: "{{ rhbk.namespace | default('keycloak') }}"
  41. register: prereq_ns
  42. - name: Fail if not so.
  43. ansible.builtin.assert:
  44. that:
  45. - prereq_ns.resources is defined
  46. - prereq_ns.resources | length == 1
  47. success_msg: "OK, namespace found."
  48. fail_msg: "FATAL: namespace to deploy ({{ rhbk.namespace | default('keycloak') }}) not found. Ensure there is an operator already present."
  49. # TODO: figure this out. probably look at subscription's CSV status attribute for name or something.
  50. - name: Check if there is a CSV in the namespace.
  51. kubernetes.core.k8s_info:
  52. kubeconfig: tmp/kubeconfig-ocp4
  53. validate_certs: no
  54. api_version: operators.coreos.com/v1alpha1
  55. kind: clusterserviceversion
  56. namespace: "{{ rhbk.namespace | default('keycloak') }}"
  57. register: prereq_csv
  58. # TODO: figure this out.
  59. - name: Fail if not so.
  60. ansible.builtin.assert:
  61. that:
  62. - prereq_csv.resources is defined
  63. - prereq_csv.resources | length > 0
  64. - name: Tech hack. Prevent anything from blowing up because rhbk is defined somewhere, but not its structured contents.
  65. ansible.builtin.set_fact:
  66. rhbk: "{{ rhbk | default({}) | combine({ 'db': {} }) }}"
  67. when:
  68. - rhbk.db is not defined
  69. - name: Ensure there is a secret containing DB credentials in the project.
  70. kubernetes.core.k8s:
  71. kubeconfig: tmp/kubeconfig-ocp4
  72. validate_certs: no
  73. api_version: v1
  74. kind: secret
  75. namespace: "{{ rhbk.namespace | default('keycloak') }}"
  76. name: "{{ rhbk.name | default('sso') }}-db-auth"
  77. resource_definition:
  78. data:
  79. username: "{{ rhbk.db.username | default('rhbk') | b64encode }}"
  80. password: "{{ rhbk.db.password | default('secret') | b64encode }}"
  81. # TODO: ensure that there is no STS to begin with, or if there is, verify that
  82. # only the allowed fields would change, if anything. Otherwise:
  83. #
  84. # Forbidden: updates to statefulset spec for fields other than
  85. # 'replicas', 'ordinals', 'template', 'updateStrategy',
  86. # 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are
  87. # forbidden
  88. #
  89. - name: Ensure there is a database sts in the project
  90. kubernetes.core.k8s:
  91. kubeconfig: tmp/kubeconfig-ocp4
  92. validate_certs: no
  93. api_version: apps/v1
  94. kind: statefulset
  95. namespace: "{{ rhbk.namespace | default('keycloak') }}"
  96. name: "{{ rhbk.name | default('sso') }}-db"
  97. resource_definition:
  98. spec:
  99. # TODO: implement rhbk.db.replicas at some point?
  100. replicas: 1
  101. serviceName: "{{ rhbk.name | default('sso') }}-db"
  102. selector:
  103. matchLabels:
  104. app: "{{ rhbk.name | default('sso') }}-db"
  105. template:
  106. metadata:
  107. labels:
  108. app: "{{ rhbk.name | default('sso') }}-db"
  109. spec:
  110. containers:
  111. - name: "{{ rhbk.name | default('sso') }}-db"
  112. image: "{{ rhbk.db.image | default('registry.redhat.io/rhel9/postgresql-15:latest') }}"
  113. volumeMounts:
  114. - mountPath: /var/lib/pgsql/data
  115. name: "{{ rhbk.name | default('sso') }}-db-data"
  116. env:
  117. - name: POSTGRESQL_USER
  118. valueFrom:
  119. secretKeyRef:
  120. name: "{{ rhbk.name | default('sso') }}-db-auth"
  121. key: username
  122. - name: POSTGRESQL_PASSWORD
  123. valueFrom:
  124. secretKeyRef:
  125. name: "{{ rhbk.name | default('sso') }}-db-auth"
  126. key: password
  127. - name: POSTGRESQL_DATABASE
  128. value: "{{ rhbk.db.name | default('rhbk') }}"
  129. volumeClaimTemplates:
  130. - metadata:
  131. name: "{{ rhbk.name | default('sso') }}-db-data"
  132. spec:
  133. accessModes: "{{ rhbk.db.claim_modes | default(['ReadWriteOnce']) }}"
  134. storageClassName: "{{ rhbk.db.storage_class | default(omit) }}"
  135. resources:
  136. requests:
  137. storage: "{{ rhbk.db.size | default('1Gi') }}"
  138. - name: Ensure there is a service for the database as well
  139. kubernetes.core.k8s:
  140. kubeconfig: tmp/kubeconfig-ocp4
  141. validate_certs: no
  142. api_version: v1
  143. kind: service
  144. namespace: "{{ rhbk.namespace | default('keycloak') }}"
  145. name: "{{ rhbk.name | default('sso') }}-db"
  146. resource_definition:
  147. spec:
  148. selector:
  149. app: "{{ rhbk.name | default('sso') }}-db"
  150. type: LoadBalancer
  151. ports:
  152. - port: 5432
  153. targetPort: 5432
  154. protocol: TCP
  155. - name: Ensure there is a secret containing Keycloak bootstrap credentials
  156. kubernetes.core.k8s:
  157. kubeconfig: tmp/kubeconfig-ocp4
  158. validate_certs: no
  159. api_version: v1
  160. kind: secret
  161. namespace: "{{ rhbk.namespace | default('keycloak') }}"
  162. name: "{{ rhbk.name | default('sso') }}-auth"
  163. resource_definition:
  164. data:
  165. username: "{{ rhbk.admin.username | default('rhbk') | b64encode }}"
  166. password: "{{ rhbk.admin.password | default('secret') | b64encode }}"
  167. - name: If there is no FQDN, check what the default domain is.
  168. kubernetes.core.k8s_info:
  169. kubeconfig: tmp/kubeconfig-ocp4
  170. validate_certs: no
  171. api_version: operator.openshift.io/v1
  172. kind: ingresscontroller
  173. namespace: openshift-ingress-operator
  174. name: default
  175. register: default_ingress
  176. when: rhbk.fqdn is not defined
  177. - name: Set a fact that reflects either the FQDN as set, or a composition of vars and default ingress info.
  178. ansible.builtin.set_fact:
  179. rhbk_fqdn: "{{ rhbk.fqdn | default((rhbk.name | default('sso')) + '-' + (rhbk.namespace | default('keycloak')) + '.' + default_ingress.resources[0].status.domain) }}"
  180. - name: Announce what hostname would be used.
  181. ansible.builtin.debug:
  182. msg: Using "https://{{ rhbk_fqdn }}" as the hostname.
  183. # TODO: remember if there were changes, and force delete any non-ready pod?
  184. - name: Lastly, make sure there is a Keycloak
  185. kubernetes.core.k8s:
  186. kubeconfig: tmp/kubeconfig-ocp4
  187. validate_certs: no
  188. api_version: k8s.keycloak.org/v2alpha1
  189. kind: keycloak
  190. namespace: "{{ rhbk.namespace | default('keycloak') }}"
  191. name: "{{ rhbk.name | default('sso') }}"
  192. resource_definition:
  193. spec:
  194. instances: "{{ rhbk.replicas | default(1) }}"
  195. db:
  196. vendor: postgres
  197. host: "{{ rhbk.name | default('sso') }}-db"
  198. database: "{{ rhbk.db.name | default('rhbk') }}"
  199. usernameSecret:
  200. name: "{{ rhbk.name | default('sso') }}-db-auth"
  201. key: username
  202. passwordSecret:
  203. name: "{{ rhbk.name | default('sso') }}-db-auth"
  204. key: password
  205. hostname:
  206. hostname: "https://{{ rhbk_fqdn }}"
  207. strict: false
  208. backchannelDynamic: true
  209. http:
  210. httpEnabled: true
  211. httpPort: 8080
  212. httpsPort: 8443
  213. tlsSecret: "{{ rhbk.name | default('sso') }}-tls"
  214. bootstrapAdmin:
  215. user:
  216. secret: "{{ rhbk.name | default('sso') }}-auth"
  217. ingress:
  218. enabled: false
  219. - name: Wait for the service to show up.
  220. kubernetes.core.k8s_info:
  221. kubeconfig: tmp/kubeconfig-ocp4
  222. validate_certs: no
  223. api_version: v1
  224. kind: service
  225. namespace: "{{ rhbk.namespace | default('keycloak') }}"
  226. name: "{{ rhbk.name | default('sso') }}-service"
  227. register: rhbk_svc
  228. until:
  229. - rhbk_svc.resources is defined
  230. - (rhbk_svc.resources | length) > 0
  231. retries: 24
  232. delay: 5
  233. - name: Ensure the service is correctly annotated.
  234. kubernetes.core.k8s:
  235. kubeconfig: tmp/kubeconfig-ocp4
  236. validate_certs: no
  237. api_version: v1
  238. kind: service
  239. namespace: "{{ rhbk.namespace | default('keycloak') }}"
  240. name: "{{ rhbk.name | default('sso') }}-service"
  241. state: patched
  242. resource_definition:
  243. metadata:
  244. annotations:
  245. service.beta.openshift.io/serving-cert-secret-name: "{{ rhbk.name | default('sso') }}-tls"
  246. - name: Make sure there is a re-encrypt route.
  247. kubernetes.core.k8s:
  248. kubeconfig: tmp/kubeconfig-ocp4
  249. validate_certs: no
  250. api_version: route.openshift.io/v1
  251. kind: route
  252. namespace: "{{ rhbk.namespace | default('keycloak') }}"
  253. name: "{{ rhbk.name | default('sso') }}"
  254. resource_definition:
  255. spec:
  256. to:
  257. kind: Service
  258. name: "{{ rhbk.name | default('sso') }}-service"
  259. port:
  260. targetPort: 8443
  261. tls:
  262. termination: reencrypt
  263. insecureEdgeTerminationPolicy: Redirect
  264. - name: Check whether there is already a realm import CR
  265. kubernetes.core.k8s_info:
  266. kubeconfig: tmp/kubeconfig-ocp4
  267. validate_certs: no
  268. api_version: k8s.keycloak.org/v2alpha1
  269. kind: keycloakrealmimport
  270. namespace: "{{ rhbk.namespace | default('keycloak') }}"
  271. register: realm_imports
  272. - name: If there are no realm imports, create one.
  273. block:
  274. - name: Apply a template realm import.
  275. kubernetes.core.k8s:
  276. kubeconfig: tmp/kubeconfig-ocp4
  277. validate_certs: no
  278. api_version: k8s.keycloak.org/v2alpha1
  279. kind: keycloakrealmimport
  280. namespace: "{{ rhbk.namespace | default('keycloak') }}"
  281. name: "{{ rhbk.name | default('sso') }}-{{ rhbk.realm | default('sample-realm') }}-import"
  282. template: templates/realm-import-template.yaml.j2
  283. ...