32-quay-deploy.yml 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. ---
  2. # Tasks required by 10-quay-deploy.adoc.
  3. - name: Issue a new Cert for Quay if necessary.
  4. hosts: workstation.lab.example.com
  5. gather_subset: min
  6. tasks:
  7. - name: Check if Quay key exists to save time
  8. ansible.builtin.stat:
  9. path: "{{ ansible_facts['user_dir'] }}/ca/quay-key.pem"
  10. register: qkey_file
  11. - name: Check if Quay cert exists to save time
  12. ansible.builtin.stat:
  13. path: "{{ ansible_facts['user_dir'] }}/ca/quay-cert.pem"
  14. register: qcert_file
  15. - name: Create a new private key for Quay, if it does not exist yet.
  16. community.crypto.openssl_privatekey:
  17. path: "{{ ansible_facts['user_dir'] }}/ca/quay-key.pem"
  18. type: RSA
  19. size: 4096
  20. mode: 0600
  21. when: qkey_file.stat.exists == false
  22. - name: Create a CSR for Quay
  23. community.crypto.openssl_csr:
  24. path: "{{ ansible_facts['user_dir'] }}/ca/quay-csr.pem"
  25. privatekey_path: "{{ ansible_facts['user_dir'] }}/ca/quay-key.pem"
  26. subject:
  27. C: US
  28. ST: North Carolina
  29. L: Raleigh
  30. O: Red Hat
  31. OU: RHT
  32. CN: registry.ocp4.example.com
  33. use_common_name_for_san: yes
  34. mode: 0600
  35. when: qcert_file.stat.exists == false
  36. - name: Issue a certificate for Quay if one isn't there yet.
  37. ansible.builtin.command:
  38. cmd: openssl ca -config {{ ansible_facts['user_dir'] }}/ca/openssl.cnf -passin pass:verysecret -in {{ ansible_facts['user_dir'] }}/ca/quay-csr.pem -out {{ ansible_facts['user_dir'] }}/ca/quay-cert.pem -batch -notext
  39. creates: "{{ ansible_facts['user_dir'] }}/ca/quay-cert.pem"
  40. - name: Load CA cert and Quay cert.
  41. ansible.builtin.set_fact:
  42. ca_cert: "{{ lookup('file', ansible_facts['user_dir'] + '/ca/ca-cert.pem') }}"
  43. quay_cert: "{{ lookup('file', ansible_facts['user_dir'] + '/ca/lab-ca/newcerts/00.pem') }}"
  44. - name: Concatenate Quay and CA certs.
  45. ansible.builtin.copy:
  46. dest: "{{ ansible_facts['user_dir'] }}/ca/quay-cert.pem"
  47. content: |
  48. {{ quay_cert }}
  49. {{ ca_cert }}
  50. - name: Prepare registry VM to run Quay services.
  51. hosts: registry.ocp4.example.com
  52. gather_subset: min
  53. tasks:
  54. - name: Ensure firewall allows HTTP/HTTPS.
  55. become: yes
  56. ansible.posix.firewalld:
  57. immediate: yes
  58. permanent: yes
  59. zone: public
  60. service: "{{ item }}"
  61. state: enabled
  62. loop:
  63. - http
  64. - https
  65. - name: Ensure unpriv users can open ports from 80 onwards.
  66. become: yes
  67. ansible.posix.sysctl:
  68. name: net.ipv4.ip_unprivileged_port_start
  69. value: "80"
  70. state: present
  71. sysctl_file: /etc/sysctl.d/quay-low-ports.conf
  72. reload: yes
  73. - name: Ensure user quay exists.
  74. become: yes
  75. ansible.builtin.user:
  76. name: quay
  77. create_home: yes
  78. state: present
  79. - name: Have the quay user accept student's SSH key.
  80. become: yes
  81. ansible.posix.authorized_key:
  82. key: "{{ lookup('ansible.builtin.file', '/home/student/.ssh/lab_rsa.pub') }}"
  83. user: quay
  84. state: present
  85. - name: Ensure user quay will linger.
  86. become: yes
  87. ansible.builtin.command:
  88. cmd: loginctl enable-linger quay
  89. creates: /var/lib/systemd/linger/quay
  90. - name: Ensure data directories are there.
  91. become: yes
  92. ansible.builtin.file:
  93. path: "{{ item }}"
  94. mode: 0770
  95. owner: quay
  96. group: quay
  97. state: directory
  98. loop:
  99. - /local/quay-pg
  100. - /local/quay
  101. - name: Ensure .docker directory is there
  102. become: yes
  103. ansible.builtin.file:
  104. path: "/home/quay/.docker"
  105. mode: 0700
  106. owner: quay
  107. group: quay
  108. state: directory
  109. # TODO: figure out how to customise this with registry host changes
  110. - name: Ensure podman will be able to log into the upstream registry
  111. become: yes
  112. ansible.builtin.copy:
  113. dest: "/home/quay/.docker/config.json"
  114. content: |
  115. {"auths":{"registry.redhat.io":{"auth":"fHVoYy1wb29sLTlmMDA1Mzc2LTM2YTItNDJhMS1hNTQwLTA0NzNkYzg3MzYzMzpleUpoYkdjaU9pSlNVelV4TWlKOS5leUp6ZFdJaU9pSTVPRGc1WVdFeFl6Qm1PV0kwWmpVM1lqazNObUk1WldFeU16SXdaalUwTUNKOS5zWmQ5VE1RbzBXREc2NUc5Qk1ObmtuYlBjRkIzNmhyRFhkMThfdTNLeHFaczdlOG1hQ19QeEFReGpwdVk0YVM2VERIbkxDNWpGYjRRNXFYVEpWbjJCOGE4cDFuY08tM24ySG5QdDg3NmktVUFDU3lldWtpb3k4aHI0V3d1ZkhReFVYMmxxWFhYdjN6blE3am1URUNBc25rWkNRSFU1dFNpRnNUZHhFZGZkeU42Z20xN3VqY2thZG5NbFBZcTZfU1I2bUtLaUpUdFQ3SFlDWXJBVk5zZ0tfNGFkZ2MtRXBlbEtHbGNERWkzNGhYbzFqbEIzRERyUWkxSUxCV0UwZkdXb1czZy1ZUzFGMFlEXzc0bm1XSU5mUE1jM25UOERaQWl0OEw0VlFPTnZnUE51YnVfTVVGUGhqX29VUjF3VUR0a1BRNktJdm82UWYyRkdwMndLM1B6YnRBRFFzRVZTZDlITzQ3a0RKdGFobk95YTFmRmdqZVk1bFNxLW1vT2RqUldCZ3U2XzNIX25lZExJR1lQRHRBZnp5cGJ1eHZ1cEd1M2hYWnVzeWN0aURtR203SkR5RW5KdjF1RFZmYVduU2EzSV9NcFRSVVcyZWU1RF9CanJleTdlU2I0bEpGcmp1eC1nY2JVaHFsWGJZc2l6azdXWHpvRmtrVFlMdXFDQ1FvS1J0OFdSN1UzTmh3c3Q2ckV3eEFOaWJFTlNzUVB3MGg4X0NDRm5qTHFSTl82cWpTc0tpeWRGT2tHVFliT0taTktaSVVhYkZFTjRhYVRVYmlYTVdPS2Eyak1xLUhwazBMNEowUmtOM2JkQVVqWmtERHE0ZFY1ZVFjdXNIeV9LY29nd1VKSjZ4MDNObnM4b0xBdjRJZ3RKeXlxcmE1YUJHSkxReHNjRXVSNzQwWQ=="}}}
  116. mode: 0600
  117. owner: quay
  118. group: quay
  119. - name: Configure containers and their environment on registry VM.
  120. hosts: registry.ocp4.example.com
  121. gather_subset: min
  122. remote_user: quay
  123. tasks:
  124. - name: Create a podman network, if necessary.
  125. containers.podman.podman_network:
  126. name: quay
  127. state: present
  128. - name: Pull all the images if necessary.
  129. containers.podman.podman_image:
  130. name: "{{ registry_host }}/{{ item }}"
  131. pull: yes
  132. state: present
  133. loop:
  134. - rhel9/postgresql-15:latest
  135. - rhel9/redis-7:latest
  136. - quay/quay-rhel8:v{{ quay_version }}
  137. - quay/clair-rhel8:v{{ quay_version }}
  138. # TODO: recursive!
  139. - name: Ensure PG datadir is owned by the correct user.
  140. become_method: containers.podman.podman_unshare
  141. become: yes
  142. ansible.builtin.file:
  143. path: /local/quay-pg
  144. state: directory
  145. owner: 26
  146. mode: 0770
  147. - name: Start postgres container if necessary.
  148. containers.podman.podman_container:
  149. name: postgresql
  150. image: "{{ registry_host }}/rhel9/postgresql-15:latest"
  151. rm: yes
  152. detach: yes
  153. env:
  154. POSTGRESQL_USER: quay
  155. POSTGRESQL_PASSWORD: secret
  156. POSTGRESQL_DATABASE: quay
  157. POSTGRESQL_ADMIN_PASSWORD: verysecret
  158. network:
  159. - quay
  160. volumes:
  161. - /local/quay-pg:/var/lib/pgsql/data:Z
  162. state: started
  163. register: pg_started
  164. - name: Wait a bit if the PG container was just started.
  165. ansible.builtin.pause:
  166. prompt: Waiting for PostgreSQL container to start.
  167. seconds: 5
  168. when: pg_started.changed
  169. - name: Create the trigram extension if necessary.
  170. containers.podman.podman_container_exec:
  171. name: postgresql
  172. command: 'psql -d quay -U postgres -c "CREATE EXTENSION IF NOT EXISTS pg_trgm"'
  173. register: pg_ext
  174. changed_when:
  175. - not "already exists" in pg_ext.stderr
  176. - name: If we started the PG container and created the extension, stop the container now.
  177. containers.podman.podman_container:
  178. name: postgresql
  179. state: stopped
  180. when:
  181. - pg_started.changed
  182. - pg_ext.changed
  183. - name: Create Quay config directory if necessary.
  184. ansible.builtin.file:
  185. path: "{{ ansible_facts['user_dir'] }}/config"
  186. state: directory
  187. mode: 0770
  188. - name: Publish Quay key on registry.
  189. ansible.builtin.copy:
  190. src: /home/student/ca/quay-key.pem
  191. dest: "{{ ansible_facts['user_dir'] }}/config/ssl.key"
  192. mode: 0440
  193. - name: Publish Quay cert on registry.
  194. ansible.builtin.copy:
  195. src: /home/student/ca/quay-cert.pem
  196. dest: "{{ ansible_facts['user_dir'] }}/config/ssl.cert"
  197. mode: 0440
  198. - name: Publish Quay config file.
  199. ansible.builtin.copy:
  200. dest: "{{ ansible_facts['user_dir'] }}/config/config.yaml"
  201. content: |
  202. BUILDLOGS_REDIS:
  203. host: redis
  204. password: verysecret
  205. port: 6379
  206. CREATE_NAMESPACE_ON_PUSH: true
  207. DATABASE_SECRET_KEY: 410c87de-8ad8-4f4c-9670-2ec25bc87191
  208. DB_URI: postgresql://quay:secret@postgresql:5432/quay
  209. DISTRIBUTED_STORAGE_CONFIG:
  210. default:
  211. - LocalStorage
  212. - storage_path: /registry
  213. DISTRIBUTED_STORAGE_DEFAULT_LOCATIONS: []
  214. DISTRIBUTED_STORAGE_PREFERENCE:
  215. - default
  216. FEATURE_MAILING: false
  217. SECRET_KEY: 7ce58d4d-b6f5-4400-ba6b-77b9f728a115
  218. SERVER_HOSTNAME: registry.ocp4.example.com
  219. PREFERRED_URL_SCHEME: https
  220. SETUP_COMPLETE: true
  221. SUPER_USERS:
  222. - admin
  223. TESTING: false
  224. USER_EVENTS_REDIS:
  225. host: redis
  226. password: verysecret
  227. port: 6379
  228. mode: 0660
  229. # TODO: recursive!
  230. - name: Ensure Quay data dirs are owned by the correct user.
  231. become_method: containers.podman.podman_unshare
  232. become: yes
  233. ansible.builtin.file:
  234. path: "{{ item }}"
  235. state: directory
  236. owner: 1001
  237. loop:
  238. - /local/quay
  239. - "{{ ansible_facts['user_dir'] }}/config"
  240. - name: Ensure systemd user dir is there.
  241. ansible.builtin.file:
  242. path: "{{ ansible_facts['user_dir'] }}/.config/systemd/user"
  243. state: directory
  244. - name: Deploy service units.
  245. ansible.builtin.template:
  246. dest: "{{ ansible_facts['user_dir'] }}/.config/systemd/user/{{ item }}"
  247. src: "templates/{{ item }}.j2"
  248. loop:
  249. - quay-pg.service
  250. - quay-redis.service
  251. - quay.service
  252. - name: Reload systemd.
  253. ansible.builtin.systemd_service:
  254. daemon_reload: yes
  255. scope: user
  256. - name: Enable services and start them.
  257. ansible.builtin.systemd_service:
  258. name: "{{ item }}"
  259. scope: user
  260. state: started
  261. enabled: yes
  262. loop:
  263. - quay-pg
  264. - quay-redis
  265. - quay
  266. register: startup
  267. - name: Wait a bit if the Quay container was just started.
  268. ansible.builtin.pause:
  269. prompt: Waiting for Quay container to start.
  270. seconds: 30
  271. when: startup.results[2].changed
  272. - name: Check if the admin user exists already.
  273. ansible.builtin.uri:
  274. method: GET
  275. url: https://registry.ocp4.example.com/api/v1/users/admin
  276. headers:
  277. Accept: application/json
  278. Content-Type: application/json
  279. validate_certs: no
  280. status_code:
  281. - 200
  282. - 404
  283. return_content: yes
  284. register: adminuser_is_there
  285. - name: Create an admin user if not yet there.
  286. block:
  287. - name: Obtain an encoded CSRF token.
  288. ansible.builtin.uri:
  289. method: GET
  290. url: https://registry.ocp4.example.com/
  291. headers:
  292. Accept: application/json
  293. Content-Type: application/json
  294. validate_certs: no
  295. return_content: yes
  296. ignore_errors: yes
  297. register: csrf_token_payload
  298. - ansible.builtin.assert:
  299. that:
  300. - csrf_token_payload.cookies['_csrf_token'] is defined
  301. fail_msg: "No CSRF token returned by registry. Can not proceed."
  302. success_msg: "Good, CSRF token found in response."
  303. # In case of issues, run with -v and this will show the raw cookie.
  304. - ansible.builtin.debug:
  305. var: csrf_token_payload.cookies
  306. verbosity: 1
  307. - name: Store the cookie as a new fact. We need it later.
  308. ansible.builtin.set_fact:
  309. csrf_cookie: "{{ csrf_token_payload.cookies['_csrf_token'] }}"
  310. # In case of issues, run with -v and this will show the cookie payload.
  311. - ansible.builtin.debug:
  312. var: csrf_cookie
  313. verbosity: 1
  314. # Must chop out the part of the token before the first dot (the rest is control shit).
  315. # Next, and pad it (==) at the end to have 112 characters (no checking done here).
  316. # Lastly, convert that from JSON to a dict and obtain the value of the token (_csrf_token).
  317. - name: Store CSRF token as a new fact.
  318. ansible.builtin.set_fact:
  319. csrf_token: "{{ (csrf_token_payload.cookies['_csrf_token'] | ansible.builtin.regex_replace('^(\\w+)\\..*$', '\\1==') | ansible.builtin.b64decode | ansible.builtin.from_json)['_csrf_token'] }}"
  320. # In case of issues, run with -v and this will show the decoded token.
  321. - ansible.builtin.debug:
  322. var: csrf_token
  323. verbosity: 1
  324. - name: Send a POST request to registry API to create the admin user.
  325. ansible.builtin.uri:
  326. method: POST
  327. url: https://registry.ocp4.example.com/api/v1/user/
  328. headers:
  329. Accept: application/json
  330. Content-Type: application/json
  331. Cookie: _csrf_token={{ csrf_cookie }}
  332. X-CSRF-Token: "{{ csrf_token }}"
  333. body: |
  334. {
  335. "username": "admin",
  336. "password": "redhat123",
  337. "repeatPassword": "redhat123",
  338. "email": "admin@example.com"
  339. }
  340. body_format: json
  341. validate_certs: no
  342. return_content: yes
  343. register: admin_user_response
  344. # In case of issues, run with -v and this will show the response.
  345. - ansible.builtin.debug:
  346. var: admin_user_response
  347. verbosity: 1
  348. when: adminuser_is_there.status == 404
  349. ...