32-quay-deploy.yml 14 KB

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