--- # Tasks required by 10-quay-deploy.adoc. - name: Issue a new Cert for Quay if necessary. hosts: workstation.lab.example.com gather_subset: min tasks: - name: Check if Quay key exists to save time ansible.builtin.stat: path: "{{ ansible_facts['user_dir'] }}/ca/quay-key.pem" get_attributes: no get_checksum: no get_mime: no register: qkey_file - name: Check if Quay cert exists to save time ansible.builtin.stat: path: "{{ ansible_facts['user_dir'] }}/ca/quay-cert.pem" get_attributes: no get_checksum: no get_mime: no register: qcert_file - name: Create a new private key for Quay, if it does not exist yet. community.crypto.openssl_privatekey: path: "{{ ansible_facts['user_dir'] }}/ca/quay-key.pem" type: RSA size: 4096 mode: 0600 when: qkey_file.stat.exists == false - name: Create a CSR for Quay community.crypto.openssl_csr: path: "{{ ansible_facts['user_dir'] }}/ca/quay-csr.pem" privatekey_path: "{{ ansible_facts['user_dir'] }}/ca/quay-key.pem" subject: C: US ST: North Carolina L: Raleigh O: Red Hat OU: RHT CN: registry.ocp4.example.com use_common_name_for_san: yes mode: 0600 when: qcert_file.stat.exists == false - name: Issue a certificate for Quay if one isn't there yet. ansible.builtin.command: 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 creates: "{{ ansible_facts['user_dir'] }}/ca/quay-cert.pem" - name: Load CA cert and Quay cert. ansible.builtin.set_fact: ca_cert: "{{ lookup('file', ansible_facts['user_dir'] + '/ca/ca-cert.pem') }}" quay_cert: "{{ lookup('file', ansible_facts['user_dir'] + '/ca/lab-ca/newcerts/00.pem') }}" - name: Concatenate Quay and CA certs. ansible.builtin.copy: dest: "{{ ansible_facts['user_dir'] }}/ca/quay-cert.pem" content: | {{ quay_cert }} {{ ca_cert }} - name: Prepare registry VM to run Quay services. hosts: registry.ocp4.example.com gather_subset: min tasks: - name: Ensure firewall allows HTTP/HTTPS. become: yes ansible.posix.firewalld: immediate: yes permanent: yes zone: public service: "{{ item }}" state: enabled loop: - http - https - name: Ensure unpriv users can open ports from 80 onwards. become: yes ansible.posix.sysctl: name: net.ipv4.ip_unprivileged_port_start value: "80" state: present sysctl_file: /etc/sysctl.d/quay-low-ports.conf reload: yes - name: Ensure user quay exists. become: yes ansible.builtin.user: name: quay create_home: yes state: present - name: Have the quay user accept student's SSH key. become: yes ansible.posix.authorized_key: key: "{{ lookup('ansible.builtin.file', '/home/student/.ssh/lab_rsa.pub') }}" user: quay state: present - name: Ensure user quay will linger. become: yes ansible.builtin.command: cmd: loginctl enable-linger quay creates: /var/lib/systemd/linger/quay - name: Ensure data directories are there. become: yes ansible.builtin.file: path: "{{ item }}" mode: 0770 owner: quay group: quay state: directory loop: - /local/quay-pg - /local/quay - name: Ensure .docker directory is there become: yes ansible.builtin.file: path: "/home/quay/.docker" mode: 0700 owner: quay group: quay state: directory # TODO: figure out how to customise this with registry host changes - name: Ensure podman will be able to log into the upstream registry become: yes ansible.builtin.copy: dest: "/home/quay/.docker/config.json" content: | {"auths":{"registry.redhat.io":{"auth":"fHVoYy1wb29sLTlmMDA1Mzc2LTM2YTItNDJhMS1hNTQwLTA0NzNkYzg3MzYzMzpleUpoYkdjaU9pSlNVelV4TWlKOS5leUp6ZFdJaU9pSTVPRGc1WVdFeFl6Qm1PV0kwWmpVM1lqazNObUk1WldFeU16SXdaalUwTUNKOS5zWmQ5VE1RbzBXREc2NUc5Qk1ObmtuYlBjRkIzNmhyRFhkMThfdTNLeHFaczdlOG1hQ19QeEFReGpwdVk0YVM2VERIbkxDNWpGYjRRNXFYVEpWbjJCOGE4cDFuY08tM24ySG5QdDg3NmktVUFDU3lldWtpb3k4aHI0V3d1ZkhReFVYMmxxWFhYdjN6blE3am1URUNBc25rWkNRSFU1dFNpRnNUZHhFZGZkeU42Z20xN3VqY2thZG5NbFBZcTZfU1I2bUtLaUpUdFQ3SFlDWXJBVk5zZ0tfNGFkZ2MtRXBlbEtHbGNERWkzNGhYbzFqbEIzRERyUWkxSUxCV0UwZkdXb1czZy1ZUzFGMFlEXzc0bm1XSU5mUE1jM25UOERaQWl0OEw0VlFPTnZnUE51YnVfTVVGUGhqX29VUjF3VUR0a1BRNktJdm82UWYyRkdwMndLM1B6YnRBRFFzRVZTZDlITzQ3a0RKdGFobk95YTFmRmdqZVk1bFNxLW1vT2RqUldCZ3U2XzNIX25lZExJR1lQRHRBZnp5cGJ1eHZ1cEd1M2hYWnVzeWN0aURtR203SkR5RW5KdjF1RFZmYVduU2EzSV9NcFRSVVcyZWU1RF9CanJleTdlU2I0bEpGcmp1eC1nY2JVaHFsWGJZc2l6azdXWHpvRmtrVFlMdXFDQ1FvS1J0OFdSN1UzTmh3c3Q2ckV3eEFOaWJFTlNzUVB3MGg4X0NDRm5qTHFSTl82cWpTc0tpeWRGT2tHVFliT0taTktaSVVhYkZFTjRhYVRVYmlYTVdPS2Eyak1xLUhwazBMNEowUmtOM2JkQVVqWmtERHE0ZFY1ZVFjdXNIeV9LY29nd1VKSjZ4MDNObnM4b0xBdjRJZ3RKeXlxcmE1YUJHSkxReHNjRXVSNzQwWQ=="}}} mode: 0600 owner: quay group: quay - name: Configure containers and their environment on registry VM. hosts: registry.ocp4.example.com gather_subset: min remote_user: quay tasks: - name: Create a podman network, if necessary. containers.podman.podman_network: name: quay state: present - name: Pull all the images if necessary. containers.podman.podman_image: name: "{{ registry_host }}/{{ item }}" pull: yes state: present loop: - rhel9/postgresql-15:latest - rhel9/redis-7:latest - quay/quay-rhel8:v{{ quay_version }} - quay/clair-rhel8:v{{ quay_version }} # TODO: recursive! - name: Ensure PG datadir is owned by the correct user. become_method: containers.podman.podman_unshare become: yes ansible.builtin.file: path: /local/quay-pg state: directory owner: 26 mode: 0770 - name: Start postgres container if necessary. containers.podman.podman_container: name: postgresql image: "{{ registry_host }}/rhel9/postgresql-15:latest" rm: yes detach: yes env: POSTGRESQL_USER: quay POSTGRESQL_PASSWORD: secret POSTGRESQL_DATABASE: quay POSTGRESQL_ADMIN_PASSWORD: verysecret network: - quay volumes: - /local/quay-pg:/var/lib/pgsql/data:Z state: started register: pg_started - name: Wait for the PostgreSQL container to become ready if it was changed in any way. containers.podman.podman_container_info: name: postgresql when: pg_started.changed register: pg_info until: pg_info.containers[0].State.Running retries: 12 delay: 5 - name: Wait for the server inside container to start up. containers.podman.podman_container_exec: name: postgresql command: 'psql -d quay -U postgres -c "SELECT 1"' when: pg_started.changed changed_when: no register: pg_rdy until: pg_rdy.rc == 0 retries: 10 delay: 3 - name: Create the trigram extension if necessary. containers.podman.podman_container_exec: name: postgresql command: 'psql -d quay -U postgres -c "CREATE EXTENSION IF NOT EXISTS pg_trgm"' register: pg_ext changed_when: - not "already exists" in pg_ext.stderr - name: If we started the PG container and created the extension, stop the container now. containers.podman.podman_container: name: postgresql state: stopped when: - pg_started.changed - pg_ext.changed - name: Create Quay config directory if necessary. ansible.builtin.file: path: "{{ ansible_facts['user_dir'] }}/config" state: directory mode: 0770 - name: Publish Quay key on registry. ansible.builtin.copy: src: /home/student/ca/quay-key.pem dest: "{{ ansible_facts['user_dir'] }}/config/ssl.key" mode: 0440 - name: Publish Quay cert on registry. ansible.builtin.copy: src: /home/student/ca/quay-cert.pem dest: "{{ ansible_facts['user_dir'] }}/config/ssl.cert" mode: 0440 - name: Publish Quay config file. ansible.builtin.copy: dest: "{{ ansible_facts['user_dir'] }}/config/config.yaml" content: | BUILDLOGS_REDIS: host: redis password: verysecret port: 6379 CREATE_NAMESPACE_ON_PUSH: true DATABASE_SECRET_KEY: 410c87de-8ad8-4f4c-9670-2ec25bc87191 DB_URI: postgresql://quay:secret@postgresql:5432/quay DISTRIBUTED_STORAGE_CONFIG: default: - LocalStorage - storage_path: /registry DISTRIBUTED_STORAGE_DEFAULT_LOCATIONS: [] DISTRIBUTED_STORAGE_PREFERENCE: - default FEATURE_MAILING: false SECRET_KEY: 7ce58d4d-b6f5-4400-ba6b-77b9f728a115 SERVER_HOSTNAME: registry.ocp4.example.com PREFERRED_URL_SCHEME: https SETUP_COMPLETE: true SUPER_USERS: - admin TESTING: false USER_EVENTS_REDIS: host: redis password: verysecret port: 6379 mode: 0660 # TODO: recursive! - name: Ensure Quay data dirs are owned by the correct user. become_method: containers.podman.podman_unshare become: yes ansible.builtin.file: path: "{{ item }}" state: directory owner: 1001 loop: - /local/quay - "{{ ansible_facts['user_dir'] }}/config" - name: Ensure systemd user dir is there. ansible.builtin.file: path: "{{ ansible_facts['user_dir'] }}/.config/systemd/user" state: directory - name: Deploy service units. ansible.builtin.template: dest: "{{ ansible_facts['user_dir'] }}/.config/systemd/user/{{ item }}" src: "templates/{{ item }}.j2" loop: - quay-pg.service - quay-redis.service - quay.service - name: Reload systemd. ansible.builtin.systemd_service: daemon_reload: yes scope: user - name: Enable services and start them. ansible.builtin.systemd_service: name: "{{ item }}" scope: user state: started enabled: yes loop: - quay-pg - quay-redis - quay register: startup - name: Wait a bit if the Quay container was just started. ansible.builtin.uri: method: GET url: https://registry.ocp4.example.com/ headers: Accept: application/json Content-Type: application/json validate_certs: no status_code: - 200 - 404 - 502 when: startup.results[2].changed register: startup_wait until: startup_wait.status == 200 retries: 30 delay: 5 - name: Check if the admin user exists already. ansible.builtin.uri: method: GET url: https://registry.ocp4.example.com/api/v1/users/admin headers: Accept: application/json Content-Type: application/json validate_certs: no status_code: - 200 - 404 return_content: yes register: adminuser_is_there - name: Create an admin user if not yet there. block: - name: Obtain an encoded CSRF token. ansible.builtin.uri: method: GET url: https://registry.ocp4.example.com/ headers: Accept: application/json Content-Type: application/json validate_certs: no return_content: yes ignore_errors: yes register: csrf_token_payload - ansible.builtin.assert: that: - csrf_token_payload.cookies['_csrf_token'] is defined fail_msg: "No CSRF token returned by registry. Can not proceed." success_msg: "Good, CSRF token found in response." # In case of issues, run with -v and this will show the raw cookie. - ansible.builtin.debug: var: csrf_token_payload.cookies verbosity: 1 - name: Store the cookie as a new fact. We need it later. ansible.builtin.set_fact: csrf_cookie: "{{ csrf_token_payload.cookies['_csrf_token'] }}" # In case of issues, run with -v and this will show the cookie payload. - ansible.builtin.debug: var: csrf_cookie verbosity: 1 # Must chop out the part of the token before the first dot (the rest is control shit). # Next, and pad it (==) at the end to have 112 characters (no checking done here). # Lastly, convert that from JSON to a dict and obtain the value of the token (_csrf_token). - name: Store CSRF token as a new fact. ansible.builtin.set_fact: csrf_token: "{{ (csrf_token_payload.cookies['_csrf_token'] | ansible.builtin.regex_replace('^(\\w+)\\..*$', '\\1==') | ansible.builtin.b64decode | ansible.builtin.from_json)['_csrf_token'] }}" # In case of issues, run with -v and this will show the decoded token. - ansible.builtin.debug: var: csrf_token verbosity: 1 - name: Send a POST request to registry API to create the admin user. ansible.builtin.uri: method: POST url: https://registry.ocp4.example.com/api/v1/user/ headers: Accept: application/json Content-Type: application/json Cookie: _csrf_token={{ csrf_cookie }} X-CSRF-Token: "{{ csrf_token }}" body: | { "username": "admin", "password": "redhat123", "repeatPassword": "redhat123", "email": "admin@example.com" } body_format: json validate_certs: no return_content: yes register: admin_user_response # In case of issues, run with -v and this will show the response. - ansible.builtin.debug: var: admin_user_response verbosity: 1 when: adminuser_is_there.status == 404 ...