--- - name: Get cluster version kubernetes.core.k8s_info: api_version: config.openshift.io/v1 kind: ClusterVersion name: version kubeconfig: "{{ ocp_cluster_auth }}/kubeconfig" validate_certs: false register: cluster_version - name: Set OCP version fact ansible.builtin.set_fact: ocp_version: "{{ cluster_version.resources[0].status.desired.version }}" - name: Check if cluster supports multi-architecture kubernetes.core.k8s_info: api_version: config.openshift.io/v1 kind: ClusterVersion name: version kubeconfig: "{{ ocp_cluster_auth }}/kubeconfig" validate_certs: false register: cluster_version_detail - name: Enable multi-architecture support if not already enabled ansible.builtin.command: cmd: "/usr/local/bin/oc adm upgrade --to-multi-arch" environment: KUBECONFIG: "{{ ocp_cluster_auth }}/kubeconfig" when: cluster_version_detail.resources[0].spec.architecture is not defined or cluster_version_detail.resources[0].spec.architecture != "Multi" register: multiarch_result failed_when: false - name: Display multi-architecture enablement result ansible.builtin.debug: msg: | Multi-architecture support: {{ 'Already enabled' if (cluster_version_detail.resources[0].spec.architecture | default('') == 'Multi') else 'Enabled for s390x support' }} {% if multiarch_result.changed %} Command output: {{ multiarch_result.stdout }} {% endif %} - name: Validate required configuration ansible.builtin.fail: msg: | Missing required configuration for {{ inventory_hostname }}: {% if dns_servers is not defined or dns_servers | length == 0 %} - dns_servers must be defined in inventory with at least one DNS server {% endif %} {% if worker_ip is not defined %} - worker_ip must be defined {% endif %} {% if worker_gateway is not defined %} - worker_gateway must be defined {% endif %} {% if worker_netmask is not defined %} - worker_netmask must be defined {% endif %} {% if network_interface is not defined %} - network_interface must be defined {% endif %} when: > dns_servers is not defined or dns_servers | length == 0 or worker_ip is not defined or worker_gateway is not defined or worker_netmask is not defined or network_interface is not defined - name: Ensure /srv/tmp directory exists ansible.builtin.file: path: /srv/tmp state: directory mode: '0755' delegate_to: srv08d become: true - name: Create temporary directory for ISO build ansible.builtin.tempfile: state: directory suffix: "-iso-build-{{ inventory_hostname }}" path: /srv/tmp register: temp_dir delegate_to: srv08d become: true - name: Create ISO output directory ansible.builtin.file: path: "/srv/{{ ocp_name | default('ocp') }}/iso" state: directory mode: '0755' owner: "{{ jumphost_user | default('hmc_sftp') }}" group: "{{ jumphost_user | default('hmc_sftp') }}" delegate_to: srv08d become: true - name: Create HTTP boot directory ansible.builtin.file: path: "/srv/{{ ocp_name | default('ocp') }}/www/html/boot" state: directory mode: '0755' owner: apache group: apache delegate_to: srv08d become: true - name: Get worker ignition config from Machine Config Server ansible.builtin.uri: url: "https://api.{{ ocp_name }}.{{ ocp_base_domain }}:22623/config/worker" method: GET validate_certs: false return_content: true timeout: 30 status_code: [200] register: worker_ignition_response retries: 3 delay: 10 until: worker_ignition_response.status == 200 - name: Get MCS CA certificate ansible.builtin.shell: cmd: | echo "q" | openssl s_client -connect api-int.{{ ocp_name }}.{{ ocp_base_domain }}:22623 -showcerts | awk '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/' register: mcs_ca_cert delegate_to: srv08d - name: Get cluster CA certificate ansible.builtin.command: cmd: "oc get configmap/kube-root-ca.crt -n kube-system -o jsonpath='{.data.ca\\.crt}'" environment: KUBECONFIG: "{{ ocp_cluster_auth }}/kubeconfig" register: cluster_ca_cert delegate_to: srv08d - name: Create ignition 3.4.0 config for CA trust ansible.builtin.template: src: add-ca.ign.j2 dest: "{{ temp_dir.path }}/add-ca.ign" mode: '0644' delegate_to: srv08d become: true - name: Read SSH public keys from files if paths provided ansible.builtin.slurp: src: "{{ item }}" register: ssh_key_files_content loop: "{{ ssh_public_key_files | default([]) }}" when: (add_ssh_keys | default(false)) and ssh_public_key_files is defined delegate_to: srv08d - name: Extract SSH key content from slurp results ansible.builtin.set_fact: ssh_keys_content: "{{ ssh_keys_content | default([]) + [item.content | b64decode | trim] }}" loop: "{{ ssh_key_files_content.results | default([]) }}" when: (add_ssh_keys | default(false)) and ssh_key_files_content is defined and not item.skipped | default(false) delegate_to: srv08d - name: Combine SSH keys from variables and files ansible.builtin.set_fact: combined_ssh_keys: "{{ (ssh_public_keys | default([])) + (ssh_keys_content | default([])) }}" when: (add_ssh_keys | default(false)) delegate_to: srv08d - name: Create ignition 3.4.0 config for SSH keys ansible.builtin.template: src: add-ssh-key.ign.j2 dest: "{{ temp_dir.path }}/add-ssh-key.ign" mode: '0644' when: (add_ssh_keys | default(false)) and combined_ssh_keys is defined and combined_ssh_keys | length > 0 delegate_to: srv08d become: true - name: Save worker ignition to file ansible.builtin.copy: content: "{{ worker_ignition_response.content }}" dest: "{{ temp_dir.path }}/worker-orig.ign" mode: '0644' delegate_to: srv08d become: true - name: Create v3.4.0 pointer ignition with merge directive and additions (with SSH) ansible.builtin.shell: cmd: | # Create v3.4.0 pointer ignition with merge directive and our additions jq -s ' .[1] as $ca | .[2] as $ssh | { "ignition": { "version": "3.4.0", "config": { "merge": [ { "source": "http://{{ boot_server_ip }}/boot/worker-base.ign" } ] }, "security": { "tls": { "certificateAuthorities": [ { "source": "data:text/plain;charset=utf-8;base64,{{ mcs_ca_cert.stdout | b64encode }}" } ] } } }, "storage": $ca.storage, "systemd": $ca.systemd, "passwd": $ssh.passwd } ' {{ temp_dir.path }}/worker-orig.ign {{ temp_dir.path }}/add-ca.ign {{ temp_dir.path }}/add-ssh-key.ign > {{ temp_dir.path }}/merged-worker.ign when: (add_ssh_keys | default(false)) and combined_ssh_keys is defined and combined_ssh_keys | length > 0 delegate_to: srv08d become: true - name: Create v3.4.0 pointer ignition with merge directive and additions (no SSH) ansible.builtin.shell: cmd: | # Create v3.4.0 pointer ignition with merge directive and our additions jq -s ' .[1] as $ca | { "ignition": { "version": "3.4.0", "config": { "merge": [ { "source": "http://{{ boot_server_ip }}/boot/worker-base.ign" } ] }, "security": { "tls": { "certificateAuthorities": [ { "source": "data:text/plain;charset=utf-8;base64,{{ mcs_ca_cert.stdout | b64encode }}" } ] } } }, "storage": $ca.storage, "systemd": $ca.systemd } ' {{ temp_dir.path }}/worker-orig.ign {{ temp_dir.path }}/add-ca.ign > {{ temp_dir.path }}/merged-worker.ign when: not ((add_ssh_keys | default(false)) and combined_ssh_keys is defined and combined_ssh_keys | length > 0) delegate_to: srv08d become: true - name: Get RHCOS bootimages from configmap kubernetes.core.k8s_info: api_version: v1 kind: ConfigMap name: coreos-bootimages namespace: openshift-machine-config-operator kubeconfig: "{{ ocp_cluster_auth }}/kubeconfig" validate_certs: false register: bootimages_cm - name: Parse s390x ISO URL and checksum ansible.builtin.set_fact: rhcos_iso_url: "{{ (bootimages_cm.resources[0].data.stream | from_json).architectures.s390x.artifacts.metal.formats.iso.disk.location }}" rhcos_iso_checksum: "{{ (bootimages_cm.resources[0].data.stream | from_json).architectures.s390x.artifacts.metal.formats.iso.disk.sha256 }}" rhcos_rootfs_url: "{{ (bootimages_cm.resources[0].data.stream | from_json).architectures.s390x.artifacts.metal.formats.pxe.rootfs.location }}" rhcos_rootfs_checksum: "{{ (bootimages_cm.resources[0].data.stream | from_json).architectures.s390x.artifacts.metal.formats.pxe.rootfs.sha256 }}" - name: Download rootfs.img for HTTP serving ansible.builtin.get_url: url: "{{ rhcos_rootfs_url }}" dest: "/srv/{{ ocp_name | default('ocp') }}/www/html/boot/rootfs.img" checksum: "sha256:{{ rhcos_rootfs_checksum }}" mode: '0644' timeout: 600 owner: apache group: apache force: yes retries: 2 delay: 30 delegate_to: srv08d become: true - name: Download RHCOS s390x ISO ansible.builtin.get_url: url: "{{ rhcos_iso_url }}" dest: "{{ temp_dir.path }}/rhcos-original.iso" checksum: "sha256:{{ rhcos_iso_checksum }}" mode: '0644' timeout: 600 force: yes retries: 2 delay: 30 delegate_to: srv08d become: true - name: Copy original ISO as working copy ansible.builtin.copy: src: "{{ temp_dir.path }}/rhcos-original.iso" dest: "{{ temp_dir.path }}/rhcos-temp.iso" remote_src: true mode: '0644' delegate_to: srv08d become: true - name: Extract ISO contents to modify generic.ins ansible.builtin.file: path: "{{ temp_dir.path }}/iso_extract" state: directory mode: '0755' delegate_to: srv08d become: true - name: Extract ISO using xorriso ansible.builtin.shell: cmd: | cd {{ temp_dir.path }}/iso_extract xorriso -osirrox on -indev {{ temp_dir.path }}/rhcos-temp.iso -extract / . delegate_to: srv08d become: true - name: Copy kernel and initrd to expected locations ansible.builtin.shell: | cd {{ temp_dir.path }}/iso_extract cp images/pxeboot/kernel.img images/kernel.img cp images/pxeboot/initrd.img images/initrd.img delegate_to: srv08d become: true - name: Create custom PRM file with network/storage configuration ansible.builtin.template: src: generic.prm.j2 dest: "{{ temp_dir.path }}/iso_extract/images/generic.prm" mode: '0644' delegate_to: srv08d become: true - name: Create custom generic.ins file ansible.builtin.template: src: generic.ins.j2 dest: "{{ temp_dir.path }}/iso_extract/generic.ins" mode: '0644' delegate_to: srv08d become: true - name: Rebuild ISO with custom configuration ansible.builtin.shell: cmd: | cd {{ temp_dir.path }}/iso_extract genisoimage \ -R -J \ -V "rhcos-{{ ocp_version | regex_replace('\\\\d+\\\\.\\\\d+\\\\.(\\\\d+)', '\\\\1') }}-s390x" \ -o "/srv/{{ ocp_name | default('ocp') }}/iso/{{ inventory_hostname }}-rhcos.iso" \ . delegate_to: srv08d become: true - name: Create PRM file for reference ansible.builtin.template: src: generic.prm.j2 dest: "/srv/{{ ocp_name | default('ocp') }}/iso/{{ inventory_hostname }}.prm" mode: '0644' owner: "{{ jumphost_user | default('hmc_sftp') }}" group: "{{ jumphost_user | default('hmc_sftp') }}" delegate_to: srv08d become: true - name: Set proper ownership on ISO file ansible.builtin.file: path: "/srv/{{ ocp_name | default('ocp') }}/iso/{{ inventory_hostname }}-rhcos.iso" owner: "{{ jumphost_user | default('hmc_sftp') }}" group: "{{ jumphost_user | default('hmc_sftp') }}" mode: '0644' delegate_to: srv08d become: true - name: Get ISO file size ansible.builtin.stat: path: "/srv/{{ ocp_name | default('ocp') }}/iso/{{ inventory_hostname }}-rhcos.iso" register: iso_size delegate_to: srv08d become: true - name: Convert v2.2.0 worker config to v3.4.0 format ansible.builtin.shell: cmd: | # Convert the v2.2.0 worker config to v3.4.0 format jq ' # Update version .ignition.version = "3.4.0" | # Convert config.append to config.merge if it exists if .ignition.config.append then .ignition.config.merge = .ignition.config.append | del(.ignition.config.append) else . end | # Remove filesystem references from files if .storage.files then .storage.files = [.storage.files[] | del(.filesystem)] else . end | # Remove filesystem references from directories if .storage.directories then .storage.directories = [.storage.directories[] | del(.filesystem)] else . end | # Remove filesystem references from links if .storage.links then .storage.links = [.storage.links[] | del(.filesystem)] else . end ' {{ temp_dir.path }}/worker-orig.ign > {{ temp_dir.path }}/worker-v3.ign chdir: "{{ temp_dir.path }}" delegate_to: srv08d become: true - name: Copy converted worker config to HTTP directory ansible.builtin.copy: src: "{{ temp_dir.path }}/worker-v3.ign" dest: "/srv/{{ ocp_name | default('ocp') }}/www/html/boot/worker-base.ign" remote_src: true mode: '0644' owner: apache group: apache delegate_to: srv08d become: true - name: Copy merged ignition config to HTTP directory ansible.builtin.copy: src: "{{ temp_dir.path }}/merged-worker.ign" dest: "/srv/{{ ocp_name | default('ocp') }}/www/html/boot/worker.ign" remote_src: true mode: '0644' owner: apache group: apache delegate_to: srv08d become: true - name: Set SELinux contexts for HTTP directory ansible.builtin.command: cmd: "{{ item }}" loop: - "semanage fcontext -a -t httpd_sys_content_t '/srv/{{ ocp_name | default('ocp') }}/www/html/boot(/.*)?'" - "restorecon -Rv /srv/{{ ocp_name | default('ocp') }}/www/html/boot/" delegate_to: srv08d become: true failed_when: false changed_when: false - name: Clean up temporary directory ansible.builtin.file: path: "{{ temp_dir.path }}" state: absent delegate_to: srv08d become: true - name: Display ISO build results ansible.builtin.debug: msg: - "" - "╔══════════════════════════════════════════════════════════════════════════════╗" - "║ s390x Worker ISO Built ║" - "╠══════════════════════════════════════════════════════════════════════════════╣" - "║ Node Name: {{ inventory_hostname }}" - "║ Cluster: {{ ocp_name | default('ocp') }}" - "║ OCP Version: {{ ocp_version }}" - "║ ISO Location: /srv/{{ ocp_name | default('ocp') }}/iso/{{ inventory_hostname }}-rhcos.iso" - "║ ISO Size: {{ (iso_size.stat.size / 1024 / 1024) | round(1) }} MB" - "╠══════════════════════════════════════════════════════════════════════════════╣" - "║ Boot Instructions ║" - "╠══════════════════════════════════════════════════════════════════════════════╣" - "║ 1. Copy ISO to HMC: {{ inventory_hostname }}-rhcos.iso" - "║ 2. Boot LPAR {{ inventory_hostname }} from ISO" - "║ 3. Installation will use embedded ignition config" - "║ 4. Network configuration is embedded in ISO" - "║ 5. Node will auto-join cluster as {{ inventory_hostname }}" - "║" - "║ Network Config:" - "║ IP: {{ worker_ip }}" - "║ Gateway: {{ worker_gateway }}" - "║ DNS: {{ dns_servers[0] }}" - "║ Device: {{ network_interface }}" - "╚══════════════════════════════════════════════════════════════════════════════╝" - "" - name: Wait for node to join cluster (optional monitoring) kubernetes.core.k8s_info: api_version: v1 kind: Node name: "{{ inventory_hostname }}" kubeconfig: "{{ ocp_cluster_auth }}/kubeconfig" validate_certs: false register: node_status until: node_status.resources | length > 0 retries: 60 delay: 30 when: wait_for_node | default(false) failed_when: false