From 9d42e0c225ef2f1816a36a216ac0301f34f8ea34 Mon Sep 17 00:00:00 2001 From: Kenny Tordeurs Date: Thu, 16 Jun 2022 14:58:19 +0200 Subject: [PATCH 01/55] replace yum with dnf --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 27bdb469..2456bd69 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ subscription-manager repos \ Enable ansible repo: ``` -yum install -y centos-release-ansible-29.noarch +dnf install -y centos-release-ansible-29.noarch ``` ## Initialize tools @@ -88,7 +88,7 @@ yum install -y centos-release-ansible-29.noarch Install ansible (min version 2.9) and git ``` -yum install -y ansible git +dnf install -y ansible git ``` You are now ready to clone this project to your CentOS system. From c11b6af9ea4dfbd7a78b3dcb289a923331dd9b65 Mon Sep 17 00:00:00 2001 From: Kenny Tordeurs Date: Thu, 16 Jun 2022 14:59:38 +0200 Subject: [PATCH 02/55] add hetzner to dns_provider table --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2456bd69..a848bf13 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ Here is an example about [_cluster.yml_](cluster-example.yml) file that contains | variable | description |Default| |---|---|---| |`cluster_name` |Name of the cluster to be installed | **Required** | -|`dns_provider` |DNS provider, value can be _route53_, _cloudflare_, _gcp_, _azure_,_transip_ or _none_. Check __Setup public DNS records__ for more info. | **Required** | +|`dns_provider` |DNS provider, value can be _route53_, _cloudflare_, _gcp_, _azure_,_transip_, _hetzner_ or _none_. Check __Setup public DNS records__ for more info. | **Required** | |`image_pull_secret` |Token to be used to authenticate to the Red Hat image registry. You can download your pull secret from https://cloud.redhat.com/openshift/install/metal/user-provisioned | **Required** | |`letsencrypt_account_email` |Email address that is used to create LetsEncrypt certs. If _cloudflare_account_email_ is not present for CloudFlare DNS recods, _letsencrypt_account_email_ is also used with CloudFlare DNS account email | **Required** | |`public_domain` |Root domain that will be used for your cluster. | **Required** | From 32daa784b2df1c3ebc889aed203eca2ae818f0d5 Mon Sep 17 00:00:00 2001 From: Andreas Bleischwitz Date: Fri, 19 Aug 2022 17:06:55 +0200 Subject: [PATCH 03/55] updated Ansible module names --- .../letsencrypt/tasks/check-variables.yml | 22 ++++---- .../letsencrypt/tasks/create-hetzner.yml | 4 +- .../letsencrypt/tasks/destroy-hetzner.yml | 2 +- ansible/roles/letsencrypt/tasks/main.yml | 50 +++++++++---------- .../tasks/build-k8s-vars.yml | 16 +++--- .../tasks/certificate-install.yml | 6 +-- .../tasks/certificate-renewal.yml | 2 +- .../tasks/create-ignition.yml | 16 +++--- .../tasks/create-network.yml | 32 ++++++------ .../openshift-4-cluster/tasks/create-vm.yml | 16 +++--- .../openshift-4-cluster/tasks/create.yml | 22 ++++---- .../tasks/destroy-network.yml | 12 ++--- .../tasks/destroy-storage-nfs.yml | 4 +- .../openshift-4-cluster/tasks/destroy-vm.yml | 10 ++-- .../openshift-4-cluster/tasks/destroy.yml | 14 +++--- .../tasks/download-openshift-artifacts.yml | 42 ++++++++-------- .../tasks/post-install-add-ons.yml | 4 +- .../tasks/post-install-storage-nfs.yml | 18 +++---- .../tasks/post-install.yml | 42 ++++++++-------- .../tasks/prepare-host-CentOS-8.yml | 15 +++--- .../tasks/prepare-host-RedHat-8.yml | 12 ++--- .../tasks/prepare-host-Rocky-8.yml | 14 +++--- .../tasks/prepare-host.yml | 14 +++--- .../openshift-4-cluster/tasks/start-vm.yml | 2 +- .../roles/openshift-4-cluster/tasks/start.yml | 6 +-- .../roles/openshift-4-cluster/tasks/stop.yml | 12 ++--- 26 files changed, 204 insertions(+), 205 deletions(-) diff --git a/ansible/roles/letsencrypt/tasks/check-variables.yml b/ansible/roles/letsencrypt/tasks/check-variables.yml index 50a29b0c..1a138477 100644 --- a/ansible/roles/letsencrypt/tasks/check-variables.yml +++ b/ansible/roles/letsencrypt/tasks/check-variables.yml @@ -1,6 +1,6 @@ --- - name: Check required variables - assert: + ansible.builtin.assert: that: - lookup('vars',item) is defined msg: "{{ item }} is not defined!" @@ -10,7 +10,7 @@ - le_letsencrypt_account_email - name: Check required Route53 variables - assert: + ansible.builtin.assert: that: - lookup('vars',item) is defined msg: "{{ item }} is not defined!" @@ -21,7 +21,7 @@ when: le_dns_provider == "route53" - name: Check required CloudFlare variables - assert: + ansible.builtin.assert: that: - lookup('vars',item) is defined msg: "{{ item }} is not defined!" @@ -32,7 +32,7 @@ when: le_dns_provider == "cloudflare" - name: Check required GCP variables - assert: + ansible.builtin.assert: that: - lookup('vars',item) is defined msg: "{{ item }} is not defined!" @@ -43,7 +43,7 @@ when: le_dns_provider == "gcp" - name: Check required Azure variables - assert: + ansible.builtin.assert: that: - lookup('vars',item) is defined msg: "{{ item }} is not defined!" @@ -56,7 +56,7 @@ when: le_dns_provider == "azure" - name: Check required Hetzner variables - assert: + ansible.builtin.assert: that: - lookup('vars',item) is defined msg: "{{ item }} is not defined!" @@ -66,7 +66,7 @@ when: le_dns_provider == "hetzner" - name: Debug var only with -vv CloudFlare - debug: + ansible.builtin.debug: msg: "{{ item }}={{ lookup('vars',item) }}" verbosity: 2 with_items: @@ -80,7 +80,7 @@ when: le_dns_provider == "cloudflare" - name: Debug var only with -vv for Route53 - debug: + ansible.builtin.debug: msg: "{{ item }}={{ lookup('vars',item) }}" verbosity: 2 with_items: @@ -94,7 +94,7 @@ when: le_dns_provider == "route53" - name: Debug var only with -vv for GCP - debug: + ansible.builtin.debug: msg: "{{ item }}={{ lookup('vars',item) }}" verbosity: 2 with_items: @@ -109,7 +109,7 @@ when: le_dns_provider == "gcp" - name: Debug var only with -vv for Azure - debug: + ansible.builtin.debug: msg: "{{ item }}={{ lookup('vars',item) }}" verbosity: 2 with_items: @@ -126,7 +126,7 @@ when: le_dns_provider == "azure" - name: Debug var only with -vv for Hetzner - debug: + ansible.builtin.debug: msg: "{{ item }}={{ lookup('vars',item) }}" verbosity: 2 with_items: diff --git a/ansible/roles/letsencrypt/tasks/create-hetzner.yml b/ansible/roles/letsencrypt/tasks/create-hetzner.yml index 2590932e..d330ab24 100644 --- a/ansible/roles/letsencrypt/tasks/create-hetzner.yml +++ b/ansible/roles/letsencrypt/tasks/create-hetzner.yml @@ -2,7 +2,7 @@ - name: Get DNS zone id at Hetzner delegate_to: localhost - uri: + ansible.builtin.uri: url: "https://dns.hetzner.com/api/v1/zones" body_format: json return_content: true @@ -15,7 +15,7 @@ - name: Create letsencrypt DNS record at Hetzner delegate_to: localhost - uri: + ansible.builtin.uri: url: "https://dns.hetzner.com/api/v1/records" method: POST body_format: json diff --git a/ansible/roles/letsencrypt/tasks/destroy-hetzner.yml b/ansible/roles/letsencrypt/tasks/destroy-hetzner.yml index 43a91354..655c44b7 100644 --- a/ansible/roles/letsencrypt/tasks/destroy-hetzner.yml +++ b/ansible/roles/letsencrypt/tasks/destroy-hetzner.yml @@ -1,7 +1,7 @@ --- - name: Delete DNS record at Hetzner delegate_to: localhost - uri: # noqa no-handler + ansible.builtin.uri: # noqa no-handler url: "https://dns.hetzner.com/api/v1/records/{{ item.json.record.id }}" method: DELETE headers: diff --git a/ansible/roles/letsencrypt/tasks/main.yml b/ansible/roles/letsencrypt/tasks/main.yml index ec3b749e..6815337d 100644 --- a/ansible/roles/letsencrypt/tasks/main.yml +++ b/ansible/roles/letsencrypt/tasks/main.yml @@ -1,38 +1,38 @@ --- - name: Check variables - import_tasks: check-variables.yml + ansible.builtin.import_tasks: check-variables.yml - name: Create certificates dir - file: + ansible.builtin.file: path: "{{ le_certificates_dir }}/{{ le_public_domain }}" state: directory mode: 0755 - name: Create account-key - openssl_privatekey: + community.crypto.openssl_privatekey: path: "{{ le_certificates_dir }}/account.key" type: RSA size: 4096 - name: Fetch letsencrypt root ca - get_url: + ansible.builtin.get_url: url: https://letsencrypt.org/certs/isrgrootx1.pem.txt dest: "{{ le_certificates_dir }}/isrgrootx1.pem" - name: Create {{ le_public_domain }}.key - openssl_privatekey: + community.crypto.openssl_privatekey: path: "{{ le_certificates_dir }}/{{ le_public_domain }}/cert.key" type: RSA size: 4096 - name: Generate an OpenSSL Certificate Signing Request with subjectAltName extension - openssl_csr: + community.crypto.openssl_csr: path: "{{ le_certificates_dir }}/{{ le_public_domain }}/cert.csr" privatekey_path: "{{ le_certificates_dir }}/{{ le_public_domain }}/cert.key" subject_alt_name: "DNS:*.apps.{{ le_public_domain }},DNS:api.{{ le_public_domain }}" - name: Create a challenge for {{ le_public_domain }} using a account key file. - acme_certificate: + community.crypto.acme_certificate: account_key_src: "{{ le_certificates_dir }}/account.key" account_email: "{{ le_letsencrypt_account_email }}" src: "{{ le_certificates_dir }}/{{ le_public_domain }}/cert.csr" @@ -46,18 +46,18 @@ register: sample_com_challenge - name: Debug var only with -vv - debug: + ansible.builtin.debug: var: sample_com_challenge verbosity: 2 - name: Set challenge_data_dns - set_fact: # noqa no-handler + ansible.builtin.set_fact: # noqa no-handler challenge_data_dns: "{{ sample_com_challenge.challenge_data_dns }}" when: sample_com_challenge is changed - name: Create DNS record at CloudFlare delegate_to: localhost - cloudflare_dns: + community.general.net_tools.cloudflare_dns: zone: "{{ le_cloudflare_zone }}" record: "{{ item.0.key }}" # 1 for automatic @@ -72,7 +72,7 @@ - name: Create DNS record at Route53 delegate_to: localhost - route53: + community.aws.route53: state: present zone: "{{ le_aws_zone }}" record: "{{ item.0.key }}" @@ -88,7 +88,7 @@ - name: Create DNS record at GCP delegate_to: localhost - gcp_dns_resource_record_set: + google.cloud.gcp_dns_resource_record_set: name: "{{ item.0.key }}." type: TXT ttl: 60 @@ -107,7 +107,7 @@ - name: Create DNS record at Azure delegate_to: localhost - azure_rm_dnsrecordset: + azure.azcollection.azure_rm_dnsrecordset: client_id: "{{ le_azure_client_id }}" secret: "{{ le_azure_secret }}" subscription_id: "{{ le_azure_subscription_id }}" @@ -126,7 +126,7 @@ - name: Create DNS record at TransIP delegate_to: localhost - uri: + ansible.builtin.uri: url: "https://api.transip.nl/v6/domains/{{ transip_zone }}/dns" method: POST headers: @@ -144,24 +144,24 @@ when: le_dns_provider == "transip" and sample_com_challenge is changed - name: DNS record info - debug: # noqa no-handler + ansible.builtin.debug: # noqa no-handler msg: "{{ item.0.key }} TXT {{ item.1 }}" loop: "{{ challenge_data_dns | default({}) | dict2items | subelements('value') }}" when: sample_com_challenge is changed - name: Include DNS provider - include_tasks: "create-{{ le_dns_provider }}.yml" + ansible.builtin.include_tasks: "create-{{ le_dns_provider }}.yml" when: - le_dns_provider in ['hetzner', 'digitalocean'] - sample_com_challenge is changed - name: Pause, wait for DNS changes - pause: # noqa no-handler + ansible.builtin.pause: # noqa no-handler seconds: 120 when: sample_com_challenge is changed - name: Let the challenge be validated and retrieve the cert and intermediate certificate - acme_certificate: # noqa no-handler + community.crypto.acme_certificate: # noqa no-handler account_key_src: "{{ le_certificates_dir }}/account.key" account_email: "{{ le_letsencrypt_account_email }}" src: "{{ le_certificates_dir }}/{{ le_public_domain }}/cert.csr" @@ -177,7 +177,7 @@ - name: Delete DNS record at CloudFlare delegate_to: localhost - cloudflare_dns: + community.general.net_tools.cloudflare_dns: zone: "{{ le_cloudflare_zone }}" record: "{{ item.0.key }}" # 1 for automatic @@ -192,7 +192,7 @@ - name: Delete DNS record at Route53 delegate_to: localhost - route53: + community.aws.route53: state: absent zone: "{{ le_aws_zone }}" record: "{{ item.0.key }}" @@ -208,7 +208,7 @@ - name: Delete DNS record at GCP delegate_to: localhost - gcp_dns_resource_record_set: + google.cloud.gcp_dns_resource_record_set: name: "{{ item.0.key }}." managed_zone: name: "{{ le_gcp_managed_zone_name }}" @@ -227,7 +227,7 @@ - name: Delete DNS record at Azure delegate_to: localhost - azure_rm_dnsrecordset: + azure.azcollection.azure_rm_dnsrecordset: client_id: "{{ le_azure_client_id }}" secret: "{{ le_azure_secret }}" subscription_id: "{{ le_azure_subscription_id }}" @@ -243,7 +243,7 @@ - name: Delete DNS record at TransIP delegate_to: localhost - uri: + ansible.builtin.uri: url: "https://api.transip.nl/v6/domains/{{ transip_zone }}/dns" method: DELETE headers: @@ -261,12 +261,12 @@ when: le_dns_provider == "transip" and sample_com_challenge is changed - name: Include DNS provider - include_tasks: "destroy-{{ le_dns_provider }}.yml" + ansible.builtin.include_tasks: "destroy-{{ le_dns_provider }}.yml" when: - le_dns_provider in ['hetzner', 'digitalocean'] - sample_com_challenge is changed - name: concat root ca and intermediate - shell: "cat {{ le_certificates_dir }}/isrgrootx1.pem {{ le_certificates_dir }}/{{ le_public_domain }}/intermediate.crt >> {{ le_certificates_dir }}/{{ le_public_domain }}/ca-bundle.pem" # noqa line-length + ansible.builtin.shell: "cat {{ le_certificates_dir }}/isrgrootx1.pem {{ le_certificates_dir }}/{{ le_public_domain }}/intermediate.crt >> {{ le_certificates_dir }}/{{ le_public_domain }}/ca-bundle.pem" # noqa line-length args: creates: "{{ le_certificates_dir }}/{{ le_public_domain }}/ca-bundle.pem" diff --git a/ansible/roles/openshift-4-cluster/tasks/build-k8s-vars.yml b/ansible/roles/openshift-4-cluster/tasks/build-k8s-vars.yml index 11439287..ee66b143 100644 --- a/ansible/roles/openshift-4-cluster/tasks/build-k8s-vars.yml +++ b/ansible/roles/openshift-4-cluster/tasks/build-k8s-vars.yml @@ -1,13 +1,13 @@ --- - name: Create own kubeconfig directory - file: + ansible.builtin.file: state: directory path: "{{ openshift_install_dir }}/config/" mode: 0755 - name: Copy kubeconfig - copy: + ansible.builtin.copy: src: "{{ openshift_install_dir }}/auth/kubeconfig" dest: "{{ openshift_install_dir }}/config/kubeconfig" remote_src: yes @@ -19,7 +19,7 @@ register: kubeconfig_raw - name: Copy content into kubeconfig - set_fact: + ansible.builtin.set_fact: kubeconfig: "{{ kubeconfig_raw['content'] | b64decode | from_yaml }}" # - name: Fetch Kubeconfig @@ -27,12 +27,12 @@ # kubeconfig: "{{ lookup('file', openshift_install_dir ~ '/config/kubeconfig' ) | from_yaml }}" - name: Select cluster & user - set_fact: + ansible.builtin.set_fact: cluster: "{{ kubeconfig | json_query('clusters[?name==`'~ cluster_name ~'`].cluster') | first }}" user: "{{ kubeconfig | json_query('users[?name==`admin`].user') | first }}" - name: Set kube variables - set_fact: + ansible.builtin.set_fact: k8s_kubeconfig: "{{ openshift_install_dir }}/config/kubeconfig" k8s_host: "{{ cluster.server }}" k8s_ca_cert: "{{ openshift_install_dir }}/config/ca.crt" @@ -40,19 +40,19 @@ k8s_client_cert: "{{ openshift_install_dir }}/config/client.crt" - name: Create k8s_ca_cert - copy: + ansible.builtin.copy: content: "{{ cluster['certificate-authority-data'] | b64decode }}" dest: "{{ k8s_ca_cert }}" mode: 0644 - name: Create k8s_client_key - copy: + ansible.builtin.copy: content: "{{ user['client-key-data'] | b64decode }}" dest: "{{ k8s_client_key }}" mode: 0644 - name: Create k8s_client_cert - copy: + ansible.builtin.copy: content: "{{ user['client-certificate-data'] | b64decode }}" dest: "{{ k8s_client_cert }}" mode: 0644 diff --git a/ansible/roles/openshift-4-cluster/tasks/certificate-install.yml b/ansible/roles/openshift-4-cluster/tasks/certificate-install.yml index e00d10b5..8fc348ac 100644 --- a/ansible/roles/openshift-4-cluster/tasks/certificate-install.yml +++ b/ansible/roles/openshift-4-cluster/tasks/certificate-install.yml @@ -1,13 +1,13 @@ --- - name: Build k8s variables - import_tasks: build-k8s-vars.yml + ansible.builtin.import_tasks: build-k8s-vars.yml - name: Check certificates exist - stat: + ansible.builtin.stat: path: "{{ certficate_fullchain }}" register: crt - name: Check ssl key exist - stat: + ansible.builtin.stat: path: "{{ certficate_key }}" register: key diff --git a/ansible/roles/openshift-4-cluster/tasks/certificate-renewal.yml b/ansible/roles/openshift-4-cluster/tasks/certificate-renewal.yml index 35b98a68..4d9f4953 100644 --- a/ansible/roles/openshift-4-cluster/tasks/certificate-renewal.yml +++ b/ansible/roles/openshift-4-cluster/tasks/certificate-renewal.yml @@ -1,6 +1,6 @@ --- - name: Import letsencrypt - import_role: + ansible.builtin.import_role: name: letsencrypt vars: le_dns_provider: "{{ dns_provider }}" diff --git a/ansible/roles/openshift-4-cluster/tasks/create-ignition.yml b/ansible/roles/openshift-4-cluster/tasks/create-ignition.yml index 91a39df8..d53b3057 100644 --- a/ansible/roles/openshift-4-cluster/tasks/create-ignition.yml +++ b/ansible/roles/openshift-4-cluster/tasks/create-ignition.yml @@ -1,29 +1,29 @@ --- - name: Ensure installation directory - file: + ansible.builtin.file: path: "{{ openshift_install_dir }}" state: directory mode: 0755 - name: Create install config - template: + ansible.builtin.template: src: install-config.yaml.j2 dest: "{{ openshift_install_dir }}/install-config.yaml" mode: 0644 - name: Save install-config from deletion - copy: + ansible.builtin.copy: dest: "{{ openshift_install_dir }}/install-config.yaml.original" src: "{{ openshift_install_dir }}/install-config.yaml" remote_src: yes mode: 0644 - name: Create manifest files - command: "{{ openshift_install_command }} create manifests --dir={{ openshift_install_dir }}" + ansible.builtin.command: "{{ openshift_install_command }} create manifests --dir={{ openshift_install_dir }}" when: not masters_schedulable - name: Make Master nodes unschedulable - lineinfile: + ansible.builtin.lineinfile: path: "{{ openshift_install_dir }}/manifests/cluster-scheduler-02-config.yml" regexp: '^(.*)mastersSchedulable(.*)$' line: ' masterSchedulable: False' @@ -31,14 +31,14 @@ when: not masters_schedulable - name: Create ignition files - command: "{{ openshift_install_command }} --dir={{ openshift_install_dir }} create ignition-configs" + ansible.builtin.command: "{{ openshift_install_command }} --dir={{ openshift_install_dir }} create ignition-configs" # # # [connection] # ipv6.dhcp-iaid=mac # ipv6.dhcp-duid=ll - name: Create RHCOS Config - copy: + ansible.builtin.copy: dest: "{{ openshift_install_dir }}/{{ item }}.rcc" mode: 0644 content: | @@ -63,7 +63,7 @@ - worker - name: Mangle ignition config - command: | + ansible.builtin.command: | /opt/openshift-client-{{ openshift_version }}/butane \ --files-dir {{ openshift_install_dir }} \ --output {{ openshift_install_dir }}/{{ item }}-extra.ign \ diff --git a/ansible/roles/openshift-4-cluster/tasks/create-network.yml b/ansible/roles/openshift-4-cluster/tasks/create-network.yml index 3a8e90ea..a4a61d47 100644 --- a/ansible/roles/openshift-4-cluster/tasks/create-network.yml +++ b/ansible/roles/openshift-4-cluster/tasks/create-network.yml @@ -1,18 +1,18 @@ --- # Debug... -# - template: +# - ansible.builtin.template: # src: templates/network.xml.j2 # dest: network.xml - name: Check IPv6 - fail: + ansible.builtin.fail: msg: "IPv6 is enabled via ip_families but your Host system do not have a public IPv6 subnet configured." when: - "'IPv6' in ip_families" - ansible_default_ipv6 | length == 0 - name: Build IPv6 subnet - set_fact: + ansible.builtin.set_fact: vn_subnet_ipv6: "{{ ansible_default_ipv6['address'].split(':')[:4] | join(':') | string }}:{{ '%x' % vn_subnet.split('.')[2] | int }}" ipv6_listen_public: - "{{ listen_address_ipv6 }}" @@ -22,7 +22,7 @@ tags: always - name: Build IPv4 subnet - set_fact: + ansible.builtin.set_fact: vn_subnet_ipv4: "{{ vn_subnet.split('.')[:3] | join('.') }}" ipv4_listen_private: - "{{ vn_subnet.split('.')[:3] | join('.') }}.1" @@ -32,7 +32,7 @@ tags: always - name: Build list of nodes - set_fact: + ansible.builtin.set_fact: __data_structure__: | bootstrap: - name: bootstrap @@ -78,16 +78,16 @@ - name: Print nodes string - debug: + ansible.builtin.debug: msg: "{{ __data_structure__.split('\n') }}" verbosity: 3 - name: Build dict from node yaml - set_fact: + ansible.builtin.set_fact: nodes: "{{ __data_structure__ | from_yaml }}" - name: Print nodes yaml - debug: + ansible.builtin.debug: var: "nodes" verbosity: 1 @@ -96,18 +96,18 @@ # - name: Define network {{ cluster_name }} - virt_net: + community.libvirt.virt_net: command: define name: "{{ cluster_name }}" xml: "{{ lookup('template', 'templates/network.xml.j2') }}" - name: Active network {{ cluster_name }} - virt_net: + community.libvirt.virt_net: state: active name: "{{ cluster_name }}" - name: Activate autostart network {{ cluster_name }} - virt_net: + community.libvirt.virt_net: autostart: yes name: "{{ cluster_name }}" @@ -115,16 +115,16 @@ # Load Balancer # - name: Build haproxy config - set_fact: + ansible.builtin.set_fact: lb_haproxy_cfg: "{{ lookup('template','templates/haproxy.conf.j2') }}" - name: Debug haproxy config - debug: + ansible.builtin.debug: msg: "{{ lb_haproxy_cfg.split('\n') }}" verbosity: 1 - name: Create OpenShift 4 load balancer - import_role: + ansible.builtin.import_role: name: openshift-4-loadbalancer tasks_from: create.yml vars: @@ -137,7 +137,7 @@ # - name: Create public dns entries delegate_to: localhost - import_role: + ansible.builtin.import_role: name: public_dns tasks_from: create.yml vars: @@ -156,7 +156,7 @@ when: dns_provider != 'none' - name: Add api.{{ cluster_name }}.{{ public_domain }} to /etc/hosts - blockinfile: + ansible.builtin.blockinfile: path: /etc/hosts marker: "# {mark} ANSIBLE MANAGED BLOCK {{ cluster_name }}.{{ public_domain }}" block: | diff --git a/ansible/roles/openshift-4-cluster/tasks/create-vm.yml b/ansible/roles/openshift-4-cluster/tasks/create-vm.yml index 9c798264..47f9a2c6 100644 --- a/ansible/roles/openshift-4-cluster/tasks/create-vm.yml +++ b/ansible/roles/openshift-4-cluster/tasks/create-vm.yml @@ -3,19 +3,19 @@ # # /var/lib/libvirt/images/rhcos42.qcow2 # # /var/lib/libvirt/images/CentOS-7-x86_64-GenericCloud.qcow2 - name: Create disk for {{ vm_instance_name }} - command: "qemu-img create -f qcow2 -F qcow2 -b {{ coreos_image_location }} /var/lib/libvirt/images/{{ vm_instance_name }}.qcow2 {{ vm_root_disk_size }}" + ansible.builtin.command: "qemu-img create -f qcow2 -F qcow2 -b {{ coreos_image_location }} /var/lib/libvirt/images/{{ vm_instance_name }}.qcow2 {{ vm_root_disk_size }}" args: creates: "/var/lib/libvirt/images/{{ vm_instance_name }}.qcow2" when: vm_storage_backend == "qcow2" - name: Convert coreos qcow into raw image - command: "qemu-img convert {{ coreos_image_location }} -O raw {{ coreos_image_location }}.raw " + ansible.builtin.command: "qemu-img convert {{ coreos_image_location }} -O raw {{ coreos_image_location }}.raw " args: creates: "{{ coreos_image_location }}.raw" when: vm_storage_backend == "lvm" - name: Create logical volume - lvol: + community.general.system.lvol: vg: "{{ vm_storage_backend_location }}" lv: "{{ vm_instance_name }}" state: present @@ -25,32 +25,32 @@ - ansible_lvm['lvs'][ vm_instance_name ] is not defined - name: Copy/dd coreos into device - command: "dd if={{ coreos_image_location }}.raw of=/dev/{{ vm_storage_backend_location }}/{{ vm_instance_name }} bs=4M" + ansible.builtin.command: "dd if={{ coreos_image_location }}.raw of=/dev/{{ vm_storage_backend_location }}/{{ vm_instance_name }} bs=4M" when: - vm_storage_backend == "lvm" - ansible_lvm['lvs'][ vm_instance_name ] is not defined - name: Copy ignition {{ vm_instance_name }} - copy: + ansible.builtin.copy: src: "{{ vm_ignition_file }}" dest: "/var/lib/libvirt/images/{{ vm_instance_name }}.ign" remote_src: true mode: '0644' - name: Debug - create /tmp/{{ vm_instance_name }}.virt.xml - template: + ansible.builtin.template: src: "vm.xml.j2" dest: "/tmp/{{ vm_instance_name }}.virt.xml" mode: 0664 - name: Define VirtualMachine {{ vm_instance_name }} - virt: + community.libvirt.virt: name: "{{ vm_instance_name }}" command: define xml: "{{ lookup('template', 'templates/vm.xml.j2') }}" - name: Start VirtualMachine {{ vm_instance_name }} - virt: + community.libvirt.virt: name: "{{ vm_instance_name }}" state: running autostart: "{{ vm_autostart }}" diff --git a/ansible/roles/openshift-4-cluster/tasks/create.yml b/ansible/roles/openshift-4-cluster/tasks/create.yml index f447a0be..b93b9400 100644 --- a/ansible/roles/openshift-4-cluster/tasks/create.yml +++ b/ansible/roles/openshift-4-cluster/tasks/create.yml @@ -1,7 +1,7 @@ --- - name: Check add-ons.yml - stat: + ansible.builtin.stat: path: "{{ playbook_dir }}/../add-ons.yml" register: add_ons_yml tags: @@ -10,7 +10,7 @@ - post-install-add-ons - name: Enable add-ons - set_fact: + ansible.builtin.set_fact: add_ons_enabled: true when: add_ons_yml.stat.exists tags: @@ -18,7 +18,7 @@ - add-ons - post-install-add-ons -- import_tasks: create-network.yml +- ansible.builtin.import_tasks: create-network.yml vars: vn_public_domain: "{{ cluster_name }}.{{ public_domain }}" vn_master_count: "{{ master_count }}" @@ -26,12 +26,12 @@ tags: network - name: Disable letsencrypt if dns_provider is none - set_fact: + ansible.builtin.set_fact: letsencrypt_disabled: true when: dns_provider == 'none' - name: Import letsencrypt - import_role: + ansible.builtin.import_role: name: letsencrypt vars: le_dns_provider: "{{ dns_provider }}" @@ -76,17 +76,17 @@ # Work-a-round: tags inheritance don't work without a block. # https://github.com/ansible/ansible/issues/41540#issuecomment-419433375 block: - - include_tasks: download-openshift-artifacts.yml + - ansible.builtin.include_tasks: download-openshift-artifacts.yml tags: download-openshift-artifacts - name: Create ignition files - include_tasks: create-ignition.yml + ansible.builtin.include_tasks: create-ignition.yml vars: ssh_public_key: "{{ lookup('file', '{{ ssh_public_key_location }}.pub') }}" tags: ignition - name: Create bootstrap node - include_tasks: create-vm.yml + ansible.builtin.include_tasks: create-vm.yml vars: vm_instance_name: "{{ cluster_name }}-bootstrap" vm_network: "{{ cluster_name }}" @@ -98,7 +98,7 @@ vm_root_disk_size: '120G' - name: Create master nodes - include_tasks: create-vm.yml + ansible.builtin.include_tasks: create-vm.yml vars: vm_instance_name: "{{ cluster_name }}-master-{{ item }}" vm_network: "{{ cluster_name }}" @@ -112,7 +112,7 @@ with_sequence: start=0 end="{{ master_count|int - 1 }}" stride=1 - name: Create compute node - include_tasks: create-vm.yml + ansible.builtin.include_tasks: create-vm.yml vars: vm_instance_name: "{{ cluster_name }}-compute-{{ item }}" vm_network: "{{ cluster_name }}" @@ -127,5 +127,5 @@ when: compute_count > 0 - name: Include post installation tasks - include_tasks: post-install.yml + ansible.builtin.include_tasks: post-install.yml tags: post-install diff --git a/ansible/roles/openshift-4-cluster/tasks/destroy-network.yml b/ansible/roles/openshift-4-cluster/tasks/destroy-network.yml index 1f4d904a..6f321ac5 100644 --- a/ansible/roles/openshift-4-cluster/tasks/destroy-network.yml +++ b/ansible/roles/openshift-4-cluster/tasks/destroy-network.yml @@ -1,7 +1,7 @@ --- - name: Destroy public dns entries delegate_to: localhost - import_role: + ansible.builtin.import_role: name: public_dns tasks_from: destroy.yml vars: @@ -20,14 +20,14 @@ when: dns_provider != 'none' - name: Remove api.{{ cluster_name }}.{{ public_domain }} to /etc/hosts - blockinfile: + ansible.builtin.blockinfile: path: /etc/hosts marker: "# {mark} ANSIBLE MANAGED BLOCK {{ cluster_name }}.{{ public_domain }}" block: "" tags: public_dns - name: Destroy OpenShift 4 load balancer - import_role: + ansible.builtin.import_role: name: openshift-4-loadbalancer tasks_from: destroy.yml vars: @@ -36,17 +36,17 @@ lb - name: Destroy Network {{ vn_name }} - virt_net: + community.libvirt.virt_net: command: destroy name: "{{ vn_name }}" ignore_errors: yes - name: Undefine Network {{ vn_name }} - virt_net: + community.libvirt.virt_net: command: undefine name: "{{ vn_name }}" - name: Check firewalld source - shell: "firewall-cmd --zone=internal --list-sources | grep -q '{{ vn_subnet }}/24'" + ansible.builtin.shell: "firewall-cmd --zone=internal --list-sources | grep -q '{{ vn_subnet }}/24'" ignore_errors: true register: grep_rc diff --git a/ansible/roles/openshift-4-cluster/tasks/destroy-storage-nfs.yml b/ansible/roles/openshift-4-cluster/tasks/destroy-storage-nfs.yml index 584c6211..8ead76fa 100644 --- a/ansible/roles/openshift-4-cluster/tasks/destroy-storage-nfs.yml +++ b/ansible/roles/openshift-4-cluster/tasks/destroy-storage-nfs.yml @@ -1,7 +1,7 @@ --- - name: Remove nfs exports to /etc/exports - blockinfile: + ansible.builtin.blockinfile: path: /etc/exports backup: yes marker: "# {mark} OpenShift cluster - {{ cluster_name }}" @@ -10,7 +10,7 @@ tags: exports - name: Delete nfs directory - file: + ansible.builtin.file: path: "{{ storage_nfs_path_prefix }}/{{ cluster_name }}-pv-{{ item }}" state: absent with_items: diff --git a/ansible/roles/openshift-4-cluster/tasks/destroy-vm.yml b/ansible/roles/openshift-4-cluster/tasks/destroy-vm.yml index 967ef5ee..f15a3fe1 100644 --- a/ansible/roles/openshift-4-cluster/tasks/destroy-vm.yml +++ b/ansible/roles/openshift-4-cluster/tasks/destroy-vm.yml @@ -1,18 +1,18 @@ --- - name: Destroy VM {{ vm_instance_name }} - virt: + community.libvirt.virt: command: destroy name: "{{ vm_instance_name }}" ignore_errors: yes - name: Undefine VM {{ vm_instance_name }} - virt: + community.libvirt.virt: command: undefine name: "{{ vm_instance_name }}" ignore_errors: yes - name: Delete logical volume - lvol: + community.general.system.lvol: vg: "{{ vm_storage_backend_location }}" lv: "{{ vm_instance_name }}" state: absent @@ -20,12 +20,12 @@ when: vm_storage_backend == "lvm" - name: Delete disk {{ vm_instance_name }} - file: + ansible.builtin.file: path: "/var/lib/libvirt/images/{{ vm_instance_name }}.qcow2" state: absent when: vm_storage_backend == "qcow2" - name: Delete ignition {{ vm_instance_name }} - file: + ansible.builtin.file: path: "/var/lib/libvirt/images/{{ vm_instance_name }}.ign" state: absent diff --git a/ansible/roles/openshift-4-cluster/tasks/destroy.yml b/ansible/roles/openshift-4-cluster/tasks/destroy.yml index a791803e..b94b1e06 100644 --- a/ansible/roles/openshift-4-cluster/tasks/destroy.yml +++ b/ansible/roles/openshift-4-cluster/tasks/destroy.yml @@ -1,36 +1,36 @@ --- -- import_tasks: destroy-network.yml +- ansible.builtin.import_tasks: destroy-network.yml vars: vn_name: "{{ cluster_name }}" tags: network - name: Destroy letsencrypt - debug: + ansible.builtin.debug: msg: "Letsencrypt certifcates stays...." tags: letsencrypt -- include_tasks: destroy-vm.yml +- ansible.builtin.include_tasks: destroy-vm.yml vars: vm_instance_name: "{{ cluster_name }}-bootstrap" -- include_tasks: destroy-vm.yml +- ansible.builtin.include_tasks: destroy-vm.yml vars: vm_instance_name: "{{ cluster_name }}-master-{{ item }}" with_sequence: start=0 end="{{ master_count|int - 1 }}" stride=1 -- include_tasks: destroy-vm.yml +- ansible.builtin.include_tasks: destroy-vm.yml vars: vm_instance_name: "{{ cluster_name }}-compute-{{ item }}" with_sequence: start=0 end="{{ compute_count|int - 1 if compute_count|int > 0 else 0 }}" stride=1 when: compute_count > 0 - name: Destroy storage-nfs - import_tasks: destroy-storage-nfs.yml + ansible.builtin.import_tasks: destroy-storage-nfs.yml when: storage_nfs == true tags: storage - name: Clean OpenShift install directory - file: + ansible.builtin.file: state: absent path: "{{ openshift_install_dir }}/" tags: ignition diff --git a/ansible/roles/openshift-4-cluster/tasks/download-openshift-artifacts.yml b/ansible/roles/openshift-4-cluster/tasks/download-openshift-artifacts.yml index 156fd3f3..de184c19 100644 --- a/ansible/roles/openshift-4-cluster/tasks/download-openshift-artifacts.yml +++ b/ansible/roles/openshift-4-cluster/tasks/download-openshift-artifacts.yml @@ -1,12 +1,12 @@ --- - name: Build openshift download urls - set_fact: + ansible.builtin.set_fact: tmp_openshift_install_download_url: "{{ openshift_location }}/openshift-install-linux-{{ openshift_version }}.tar.gz" tmp_openshift_client_download_url: "{{ openshift_location }}/openshift-client-linux-{{ openshift_version }}.tar.gz" - name: Init check_urls - set_fact: + ansible.builtin.set_fact: check_urls: - "{{ coreos_download_url }}" - "{{ tmp_openshift_install_download_url }}" @@ -16,12 +16,12 @@ - "{{ butane_cli_location }}" - name: Add coreos_csum_url to check_urls - set_fact: + ansible.builtin.set_fact: check_urls: "{{ check_urls + [coreos_csum_url] }}" when: coreos_csum_str is not defined - name: Check download urls - uri: + ansible.builtin.uri: method: HEAD url: "{{ item }}" status_code: @@ -32,13 +32,13 @@ with_items: "{{ check_urls }}" - name: check if coreos image already downloaded and get checksum - stat: + ansible.builtin.stat: checksum_algorithm: sha256 path: "{{ coreos_path }}/{{ coreos_file }}" register: coreos_old_image - name: verify existing coreos image is valid - lineinfile: + ansible.builtin.lineinfile: name: "{{ coreos_path }}/{{ coreos_file }}.sha256.txt" line: "{{ coreos_old_image.stat.checksum }} {{ coreos_file }}" state: present @@ -53,24 +53,24 @@ # Work-a-round for https://github.com/ansible/ansible/issues/71420 # fix not shipped yet - name: Findout exactly sha256 - set_fact: + ansible.builtin.set_fact: coreos_csum_str: "{{ lookup('url', coreos_csum_url, split_lines=False ).split('\n') |map('regex_search','.*' + coreos_download_url | basename + '$') | select('string') | first | regex_search('^([a-z0-9])+') }}" # noqa line-length when: coreos_csum_str is not defined - name: download coreos - get_url: + ansible.builtin.get_url: url: "{{ coreos_download_url }}" dest: "{{ coreos_path }}/{{ coreos_download_url | basename }}" checksum: "sha256:{{ coreos_csum_str }}" register: coreos_download - name: Get filetype - stat: + ansible.builtin.stat: path: "{{ coreos_path }}/{{ coreos_download_url | basename }}" register: coreos_stat - name: unzip the coreos - command: "gunzip {{ coreos_path }}/{{ coreos_download_url | basename }}" + ansible.builtin.command: "gunzip {{ coreos_path }}/{{ coreos_download_url | basename }}" args: chdir: "{{ coreos_path }}" creates: "{{ coreos_path }}/{{ coreos_file }}" @@ -78,7 +78,7 @@ when: coreos_stat.stat.mimetype == 'application/x-gzip' - name: unxz the coreos - command: "unxz {{ coreos_path }}/{{ coreos_download_url | basename }}" + ansible.builtin.command: "unxz {{ coreos_path }}/{{ coreos_download_url | basename }}" args: chdir: "{{ coreos_path }}" creates: "{{ coreos_path }}/{{ coreos_file }}" @@ -86,13 +86,13 @@ when: coreos_stat.stat.mimetype == 'application/x-xz' - name: calculate checksum of the new coreos image - stat: + ansible.builtin.stat: checksum_algorithm: sha256 path: "{{ coreos_path }}/{{ coreos_file }}" register: coreos_csum - name: store the checksum of the file - copy: + ansible.builtin.copy: dest: "{{ coreos_path }}/{{ coreos_file }}.sha256.txt" content: "{{ coreos_csum.stat.checksum }} {{ coreos_file }}\n" mode: 0644 @@ -103,7 +103,7 @@ csum_check.failed - name: Create OpenShift artifacts directory - file: + ansible.builtin.file: path: "{{ item }}" state: directory mode: u+rwx,g+rx,o+rx @@ -113,7 +113,7 @@ - "{{ opm_dest }}" - name: Download Openshift installer - unarchive: + ansible.builtin.unarchive: src: "{{ tmp_openshift_install_download_url }}" dest: "/opt/openshift-install-{{ openshift_version }}/" remote_src: yes @@ -125,7 +125,7 @@ creates: "/opt/openshift-install-{{ openshift_version }}/openshift-install" - name: Download Openshift client - unarchive: + ansible.builtin.unarchive: src: "{{ tmp_openshift_client_download_url }}" dest: "/opt/openshift-client-{{ openshift_version }}/" remote_src: yes @@ -137,7 +137,7 @@ creates: "/opt/openshift-client-{{ openshift_version }}/oc" - name: Download OPM (gz) - unarchive: + ansible.builtin.unarchive: src: "{{ opm_download_url }}" dest: "{{ opm_dest }}" remote_src: yes @@ -150,27 +150,27 @@ when: opm_download_url | regex_search("^(.*)\.tar\.gz$") - name: Download OPM - get_url: + ansible.builtin.get_url: url: "{{ opm_download_url }}" dest: "{{ opm_dest }}/opm" mode: u+rwx,g+rx,o+rx when: not opm_download_url | regex_search("^(.*)\.tar\.gz$") - name: Download Helm client - get_url: + ansible.builtin.get_url: url: "{{ helm_cli_location }}" mode: u+rwx,g+rx,o+rx dest: "/opt/openshift-client-{{ openshift_version }}/helm" - name: Download butane client - get_url: + ansible.builtin.get_url: url: "{{ butane_cli_location }}" mode: u+rwx,g+rx,o+rx dest: "/opt/openshift-client-{{ openshift_version }}/butane" - name: Create a symbolic link - file: + ansible.builtin.file: src: "{{ item.value }}" dest: "{{ item.key }}" state: link diff --git a/ansible/roles/openshift-4-cluster/tasks/post-install-add-ons.yml b/ansible/roles/openshift-4-cluster/tasks/post-install-add-ons.yml index bdbdc280..1919e03a 100644 --- a/ansible/roles/openshift-4-cluster/tasks/post-install-add-ons.yml +++ b/ansible/roles/openshift-4-cluster/tasks/post-install-add-ons.yml @@ -1,9 +1,9 @@ --- - name: Include vars of stuff.yaml into the 'stuff' variable (2.2). - include_vars: "{{ playbook_dir }}/../add-ons.yml" + ansible.builtin.include_vars: "{{ playbook_dir }}/../add-ons.yml" - name: "Handle post_install_add_ons (include_role)" - include_role: + ansible.builtin.include_role: name: "{{ item.name }}" tasks_from: "{{ item.tasks_from | default('main.yml') }}" tags: diff --git a/ansible/roles/openshift-4-cluster/tasks/post-install-storage-nfs.yml b/ansible/roles/openshift-4-cluster/tasks/post-install-storage-nfs.yml index 9b56d4b6..0792c000 100644 --- a/ansible/roles/openshift-4-cluster/tasks/post-install-storage-nfs.yml +++ b/ansible/roles/openshift-4-cluster/tasks/post-install-storage-nfs.yml @@ -6,29 +6,29 @@ # - name: Ensure NFS utilities are installed. - package: + ansible.builtin.package: name: nfs-utils state: present - name: Ensure rpcbind is running as configured. - service: + ansible.builtin.service: name: rpcbind state: started enabled: true - name: Set nfs_server - set_fact: + ansible.builtin.set_fact: nfs_server: "nfs-server" when: ansible_os_family == "RedHat" and ansible_distribution_major_version == '8' - name: Ensure nfs is running. - service: + ansible.builtin.service: name: "{{ nfs_server }}" state: started enabled: yes - name: Add nfs exports to /etc/exports - blockinfile: + ansible.builtin.blockinfile: path: /etc/exports backup: yes create: yes @@ -49,7 +49,7 @@ tags: exports - name: Adjust directory permissions - file: + ansible.builtin.file: path: "{{ storage_nfs_path_prefix }}/{{ cluster_name }}-pv-{{ item }}" state: directory mode: "770" @@ -58,7 +58,7 @@ - "user-pvs" - name: reload nfs - command: 'exportfs -ra' + ansible.builtin.command: 'exportfs -ra' tags: exports @@ -111,7 +111,7 @@ - name: Add pvc registry-storage to image registry # noqa var-spacing # yamllint disable rule:line-length - command: | + ansible.builtin.command: | /opt/openshift-client-{{ openshift_version }}/oc patch configs.imageregistry.operator.openshift.io cluster --type='json' -p='[{"op": "remove", "path": "/spec/storage" },{"op": "add", "path": "/spec/storage", "value": {"pvc":{"claim": "registry-storage"}}}]' --kubeconfig {{ openshift_install_dir }}/auth/kubeconfig # yamllint enable rule:line-length register: registry_status @@ -122,7 +122,7 @@ - storage - name: Adjust directory permissions - file: + ansible.builtin.file: path: "{{ storage_nfs_path_prefix }}/{{ cluster_name }}-pv-{{ item }}" state: directory mode: "770" diff --git a/ansible/roles/openshift-4-cluster/tasks/post-install.yml b/ansible/roles/openshift-4-cluster/tasks/post-install.yml index ea893fe3..855bb344 100644 --- a/ansible/roles/openshift-4-cluster/tasks/post-install.yml +++ b/ansible/roles/openshift-4-cluster/tasks/post-install.yml @@ -1,18 +1,18 @@ - name: Info message - debug: + ansible.builtin.debug: msg: - "If you like to follow the installation run 'tail -f {{ openshift_install_dir }}/.openshift_install.log' in a second terminal." - "For more details, connect to the bootstrap node: ssh -l core {{ vn_subnet.split('.')[:3] | join('.') }}.2" - name: Waiting for bootstrap to complete - command: "/opt/openshift-install-{{ openshift_version }}/openshift-install wait-for bootstrap-complete --dir {{ openshift_install_dir }} --log-level debug" + ansible.builtin.command: "/opt/openshift-install-{{ openshift_version }}/openshift-install wait-for bootstrap-complete --dir {{ openshift_install_dir }} --log-level debug" register: bootstrap_status retries: 60 delay: 60 until: bootstrap_status.rc == 0 - name: Destroy bootstrap node - include_tasks: destroy-vm.yml + ansible.builtin.include_tasks: destroy-vm.yml vars: vm_instance_name: "{{ cluster_name }}-bootstrap" @@ -20,7 +20,7 @@ async: 1800 # 30 minutes poll: 0 # yamllint disable rule:line-length - shell: | + ansible.builtin.shell: | set -o pipefail while true do @@ -32,7 +32,7 @@ # yamllint enable rule:line-length - name: Build k8s variables - import_tasks: build-k8s-vars.yml + ansible.builtin.import_tasks: build-k8s-vars.yml tags: - post-install - add-ons @@ -43,14 +43,14 @@ - rolebindings - name: Provision nfs storage - import_tasks: post-install-storage-nfs.yml + ansible.builtin.import_tasks: post-install-storage-nfs.yml when: storage_nfs tags: storage - name: Set image registry managementState from Removed to Managed # yamllint disable rule:line-length # noqa var-spacing - command: "/opt/openshift-client-{{ openshift_version }}/oc patch configs.imageregistry.operator.openshift.io cluster --type merge --patch '{\"spec\":{\"managementState\": \"Managed\"}}' --kubeconfig {{ openshift_install_dir }}/auth/kubeconfig" + ansible.builtin.command: "/opt/openshift-client-{{ openshift_version }}/oc patch configs.imageregistry.operator.openshift.io cluster --type merge --patch '{\"spec\":{\"managementState\": \"Managed\"}}' --kubeconfig {{ openshift_install_dir }}/auth/kubeconfig" # yamllint enable rule:line-length register: registry_status retries: 60 @@ -60,7 +60,7 @@ tags: storage - name: Waiting installation to complete - command: "/opt/openshift-install-{{ openshift_version }}/openshift-install wait-for install-complete --dir {{ openshift_install_dir }}" + ansible.builtin.command: "/opt/openshift-install-{{ openshift_version }}/openshift-install wait-for install-complete --dir {{ openshift_install_dir }}" register: install_status retries: 60 delay: 60 @@ -71,7 +71,7 @@ ########################################################################################### # Install letsencrypt certificates ########################################################################################### -- include_tasks: certificate-install.yml +- ansible.builtin.include_tasks: certificate-install.yml when: letsencrypt_disabled == false tags: - post-install @@ -84,7 +84,7 @@ tags: - post-install - idp - set_fact: + ansible.builtin.set_fact: identity_providers: "[]" - name: Handle auth_htpasswd @@ -108,7 +108,7 @@ namespace: openshift-config type: Opaque - name: Create htpasswd identity provider template - set_fact: + ansible.builtin.set_fact: htpasswd_idp: htpasswd: fileData: @@ -117,7 +117,7 @@ name: Local type: HTPasswd - name: Push htpasswd_idp to identity_providers - set_fact: + ansible.builtin.set_fact: identity_providers: "{{ identity_providers }} + [ {{ htpasswd_idp }} ]" when: auth_htpasswd is defined tags: @@ -145,7 +145,7 @@ namespace: openshift-config type: Opaque - name: Create google identity provider template - set_fact: + ansible.builtin.set_fact: redhatsso_idp: google: clientID: "{{ auth_redhatsso.client_id }}" @@ -156,7 +156,7 @@ name: RedHatSSO type: Google - name: Push redhatsso_idp to identity_providers - set_fact: + ansible.builtin.set_fact: identity_providers: "{{ identity_providers }} + [ {{ redhatsso_idp }} ]" when: auth_redhatsso is defined tags: @@ -166,11 +166,11 @@ - name: Handle auth_github block: - name: Check if either organizations or teams is defined - fail: + ansible.builtin.fail: msg: "At least one of auth_github.organizations or auth_github.teams must be defined (only one is allowed)" when: (auth_github.organizations is undefined) and (auth_github.teams is undefined) - name: Check if only one of organizations or teams is defined - fail: + ansible.builtin.fail: msg: "Only one of auth_github.organizations or auth_github.teams must be defined (at least one must be defined)" when: (auth_github.organizations is defined) and (auth_github.teams is defined) - name: Create GitHub secret @@ -192,7 +192,7 @@ namespace: openshift-config type: Opaque - name: Create GitHub identity provider template - set_fact: + ansible.builtin.set_fact: github_idp: github: clientID: "{{ auth_github.client_id }}" @@ -204,7 +204,7 @@ name: GitHub type: GitHub - name: Push github_idp to identity_providers - set_fact: + ansible.builtin.set_fact: identity_providers: "{{ identity_providers }} + [ {{ github_idp }} ]" when: auth_github is defined tags: @@ -267,7 +267,7 @@ ########################################################################################### - name: Include post-install-add-ons - include_tasks: post-install-add-ons.yml + ansible.builtin.include_tasks: post-install-add-ons.yml when: add_ons_enabled tags: - post-install @@ -279,7 +279,7 @@ ########################################################################################### - name: DNS info if dns_provider == 'none' - debug: + ansible.builtin.debug: msg: - "Please don't forget to create DNS" - " - api.{{ cluster_name }}.{{ public_domain }} => {{ public_ip | default(listen_address) }}" @@ -289,7 +289,7 @@ - lastmessage - name: Cluster informations - debug: + ansible.builtin.debug: msg: "{{ install_status.stderr_lines | map('regex_replace', 'level=info msg=\"(.*)\"', '\\1') | list }}" tags: - post-install diff --git a/ansible/roles/openshift-4-cluster/tasks/prepare-host-CentOS-8.yml b/ansible/roles/openshift-4-cluster/tasks/prepare-host-CentOS-8.yml index d80b795d..f8a3944a 100644 --- a/ansible/roles/openshift-4-cluster/tasks/prepare-host-CentOS-8.yml +++ b/ansible/roles/openshift-4-cluster/tasks/prepare-host-CentOS-8.yml @@ -1,7 +1,6 @@ --- - - name: Installing KVM Packages - yum: + ansible.builtin.package: name: - "@virtualization-hypervisor" - "@virtualization-client" @@ -13,18 +12,18 @@ state: present - name: Upgrade all packages - yum: + ansible.builtin.package: name: '*' state: latest - name: Enable & Start firewalld - service: + ansible.builtin.service: name: firewalld state: started enabled: true - name: Allow NFS traffic from VM's to Host - firewalld: + ansible.posix.firewalld: zone: libvirt state: enabled permanent: yes @@ -36,7 +35,7 @@ notify: 'reload firewalld' - name: Allow OpenShift traffic from VM's to Host - firewalld: + ansible.posix.firewalld: zone: libvirt state: enabled permanent: yes @@ -49,7 +48,7 @@ notify: 'reload firewalld' - name: Allow OpenShift traffic from public to Host - firewalld: + ansible.posix.firewalld: zone: public state: enabled permanent: yes @@ -61,4 +60,4 @@ notify: 'reload firewalld' - name: firewalld reload - command: firewall-cmd --reload + ansible.builtin.command: firewall-cmd --reload diff --git a/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-8.yml b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-8.yml index 6877dbc0..46b3ed94 100644 --- a/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-8.yml +++ b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-8.yml @@ -1,6 +1,6 @@ --- - name: Installing KVM Packages - yum: + ansible.builtin.package: name: - "@virtualization-hypervisor" - "@virtualization-client" @@ -9,12 +9,12 @@ state: present - name: Upgrade all packages - yum: + ansible.builtin.package: name: '*' state: latest - name: Allow NFS traffic from VM's to Host - firewalld: + ansible.posix.firewalld: zone: libvirt state: enabled permanent: yes @@ -26,7 +26,7 @@ notify: 'reload firewalld' - name: Allow OpenShift traffic from VM's to Host - firewalld: + ansible.posix.firewalld: zone: libvirt state: enabled permanent: yes @@ -39,7 +39,7 @@ notify: 'reload firewalld' - name: Allow OpenShift traffic from public to Host - firewalld: + ansible.posix.firewalld: zone: public state: enabled permanent: yes @@ -51,4 +51,4 @@ notify: 'reload firewalld' - name: firewalld reload - command: firewall-cmd --reload + ansible.builtin.command: firewall-cmd --reload diff --git a/ansible/roles/openshift-4-cluster/tasks/prepare-host-Rocky-8.yml b/ansible/roles/openshift-4-cluster/tasks/prepare-host-Rocky-8.yml index 64836668..f8a3944a 100644 --- a/ansible/roles/openshift-4-cluster/tasks/prepare-host-Rocky-8.yml +++ b/ansible/roles/openshift-4-cluster/tasks/prepare-host-Rocky-8.yml @@ -1,6 +1,6 @@ --- - name: Installing KVM Packages - yum: + ansible.builtin.package: name: - "@virtualization-hypervisor" - "@virtualization-client" @@ -12,18 +12,18 @@ state: present - name: Upgrade all packages - yum: + ansible.builtin.package: name: '*' state: latest - name: Enable & Start firewalld - service: + ansible.builtin.service: name: firewalld state: started enabled: true - name: Allow NFS traffic from VM's to Host - firewalld: + ansible.posix.firewalld: zone: libvirt state: enabled permanent: yes @@ -35,7 +35,7 @@ notify: 'reload firewalld' - name: Allow OpenShift traffic from VM's to Host - firewalld: + ansible.posix.firewalld: zone: libvirt state: enabled permanent: yes @@ -48,7 +48,7 @@ notify: 'reload firewalld' - name: Allow OpenShift traffic from public to Host - firewalld: + ansible.posix.firewalld: zone: public state: enabled permanent: yes @@ -60,4 +60,4 @@ notify: 'reload firewalld' - name: firewalld reload - command: firewall-cmd --reload + ansible.builtin.command: firewall-cmd --reload diff --git a/ansible/roles/openshift-4-cluster/tasks/prepare-host.yml b/ansible/roles/openshift-4-cluster/tasks/prepare-host.yml index 9ed65690..4b4bbfec 100644 --- a/ansible/roles/openshift-4-cluster/tasks/prepare-host.yml +++ b/ansible/roles/openshift-4-cluster/tasks/prepare-host.yml @@ -1,25 +1,25 @@ --- - name: Include OS specific part - include_tasks: "prepare-host-{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml" + ansible.builtin.include_tasks: "prepare-host-{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml" - name: Enable and Start libvirtd - systemd: + ansible.builtin.service: name: libvirtd state: started enabled: yes - name: 'Restart libvirtd because of Issue #146' - systemd: + ansible.builtin.service: name: libvirtd state: restarted - name: Verify KVM module is loaded - shell: "set -o pipefail && lsmod | grep -i kvm" + ansible.builtin.shell: "set -o pipefail && lsmod | grep -i kvm" register: result failed_when: "result.rc != 0" - name: Create SSH key for root - user: + ansible.builtin.user: name: root generate_ssh_key: yes ssh_key_bits: 2048 @@ -27,12 +27,12 @@ # Enable ip forwarding globally - fixed issue #35 - name: Check /etc/systemd/network/10-mainif.network - stat: + ansible.builtin.stat: path: /etc/systemd/network/10-mainif.network register: stat_result - name: Add IPForward=ipv4 to /etc/systemd/network/10-mainif.network - lineinfile: + ansible.builtin.lineinfile: path: /etc/systemd/network/10-mainif.network line: 'IPForward=ipv4' insertafter: '^\[Network\]' diff --git a/ansible/roles/openshift-4-cluster/tasks/start-vm.yml b/ansible/roles/openshift-4-cluster/tasks/start-vm.yml index ac8361b5..fec7c644 100644 --- a/ansible/roles/openshift-4-cluster/tasks/start-vm.yml +++ b/ansible/roles/openshift-4-cluster/tasks/start-vm.yml @@ -1,6 +1,6 @@ --- - name: Start VM {{ vm_instance_name }} - virt: + community.libvirt.virt: command: start name: "{{ vm_instance_name }}" ignore_errors: yes diff --git a/ansible/roles/openshift-4-cluster/tasks/start.yml b/ansible/roles/openshift-4-cluster/tasks/start.yml index 20ffdc47..6dab142f 100644 --- a/ansible/roles/openshift-4-cluster/tasks/start.yml +++ b/ansible/roles/openshift-4-cluster/tasks/start.yml @@ -4,19 +4,19 @@ # vars: # vm_instance_name: "{{ cluster_name }}-bootstrap" -- include_tasks: start-vm.yml +- ansible.builtin.include_tasks: start-vm.yml vars: vm_instance_name: "{{ cluster_name }}-master-{{ item }}" with_sequence: start=0 end="{{ master_count|int - 1 }}" stride=1 -- include_tasks: start-vm.yml +- ansible.builtin.include_tasks: start-vm.yml vars: vm_instance_name: "{{ cluster_name }}-compute-{{ item }}" with_sequence: start=0 end="{{ compute_count|int - 1 if compute_count|int > 0 else 0 }}" stride=1 when: compute_count > 0 - name: Start OpenShift 4 load balancer - import_role: + ansible.builtin.import_role: name: openshift-4-loadbalancer tasks_from: start.yml vars: diff --git a/ansible/roles/openshift-4-cluster/tasks/stop.yml b/ansible/roles/openshift-4-cluster/tasks/stop.yml index df9ad234..a7729e15 100644 --- a/ansible/roles/openshift-4-cluster/tasks/stop.yml +++ b/ansible/roles/openshift-4-cluster/tasks/stop.yml @@ -5,29 +5,29 @@ # vm_instance_name: "{{ cluster_name }}-bootstrap" - name: Init empty vms - set_fact: + ansible.builtin.set_fact: vms: "[]" - name: Add masters to vms - set_fact: + ansible.builtin.set_fact: vms: "{{ vms }} + [ '{{ cluster_name }}-master-{{ item }}' ]" with_sequence: start=0 end="{{ master_count|int - 1 }}" stride=1 - name: Add compute to vms - set_fact: + ansible.builtin.set_fact: vms: "{{ vms }} + [ '{{ cluster_name }}-compute-{{ item }}' ]" with_sequence: start=0 end="{{ compute_count|int - 1 if compute_count|int > 0 else 0 }}" stride=1 when: compute_count > 0 - name: "Shutdown VM" - virt: + community.libvirt.virt: command: shutdown name: "{{ item }}" ignore_errors: yes with_items: "{{ vms }}" - name: "Check VM state" - virt: + community.libvirt.virt: name: "{{ item }}" command: status register: result @@ -37,7 +37,7 @@ with_items: "{{ vms }}" - name: Stop OpenShift 4 load balancer - import_role: + ansible.builtin.import_role: name: openshift-4-loadbalancer tasks_from: stop.yml vars: From 1a34842929f65f02158b536533460b50aaa98179 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 5 Sep 2022 13:40:07 +0200 Subject: [PATCH 04/55] update for Rhel 9 --- ansible-navigator.yaml | 2 +- .../tasks/download-openshift-artifacts.yml | 2 +- .../tasks/prepare-host-RedHat-9.yml | 54 +++++++++++++++ cluster-example.yml | 69 ------------------- 4 files changed, 56 insertions(+), 71 deletions(-) create mode 100644 ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-9.yml delete mode 100644 cluster-example.yml diff --git a/ansible-navigator.yaml b/ansible-navigator.yaml index 221ea557..1389068e 100644 --- a/ansible-navigator.yaml +++ b/ansible-navigator.yaml @@ -9,4 +9,4 @@ ansible-navigator: mode: stdout playbook-artifact: enable: true - save-as: /tmp/hetzner-ocp4-{playbook_name}-artifact-{ts_utc}.json + save-as: /tmp/hetzner-ocp4-{playbook_name}-artifact-{time_stamp}.json diff --git a/ansible/roles/openshift-4-cluster/tasks/download-openshift-artifacts.yml b/ansible/roles/openshift-4-cluster/tasks/download-openshift-artifacts.yml index de184c19..c6311c87 100644 --- a/ansible/roles/openshift-4-cluster/tasks/download-openshift-artifacts.yml +++ b/ansible/roles/openshift-4-cluster/tasks/download-openshift-artifacts.yml @@ -75,7 +75,7 @@ chdir: "{{ coreos_path }}" creates: "{{ coreos_path }}/{{ coreos_file }}" register: unzip - when: coreos_stat.stat.mimetype == 'application/x-gzip' + when: coreos_stat.stat.mimetype == 'application/x-gzip' or coreos_stat.stat.mimetype == 'application/gzip' - name: unxz the coreos ansible.builtin.command: "unxz {{ coreos_path }}/{{ coreos_download_url | basename }}" diff --git a/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-9.yml b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-9.yml new file mode 100644 index 00000000..6877dbc0 --- /dev/null +++ b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-9.yml @@ -0,0 +1,54 @@ +--- +- name: Installing KVM Packages + yum: + name: + - "@virtualization-hypervisor" + - "@virtualization-client" + - "@virtualization-platform" + - "@virtualization-tools" + state: present + +- name: Upgrade all packages + yum: + name: '*' + state: latest + +- name: Allow NFS traffic from VM's to Host + firewalld: + zone: libvirt + state: enabled + permanent: yes + service: "{{ item }}" + with_items: + - nfs + - mountd + - rpc-bind + notify: 'reload firewalld' + +- name: Allow OpenShift traffic from VM's to Host + firewalld: + zone: libvirt + state: enabled + permanent: yes + port: "{{ item }}" + with_items: + - 80/tcp + - 443/tcp + - 6443/tcp + - 22623/tcp + notify: 'reload firewalld' + +- name: Allow OpenShift traffic from public to Host + firewalld: + zone: public + state: enabled + permanent: yes + port: "{{ item }}" + with_items: + - 80/tcp + - 443/tcp + - 6443/tcp + notify: 'reload firewalld' + +- name: firewalld reload + command: firewall-cmd --reload diff --git a/cluster-example.yml b/cluster-example.yml deleted file mode 100644 index 90f2837e..00000000 --- a/cluster-example.yml +++ /dev/null @@ -1,69 +0,0 @@ ---- -cluster_name: ocp4 -public_domain: example.com - -# DualStack: -ip_families: - - IPv4 - - IPv6 - -# set custom public ip for DNS entries. Defaults to: hostvars['localhost']['ansible_default_ipv4']['address'] -# public_ip: 92.100.42.2 -dns_provider: [route53|cloudflare|gcp|azure|hetzner] -letsencrypt_account_email: name@example.com -# Depending on the dns provider: -# CloudFlare -cloudflare_account_email: john@example.com -# Use the global api key! - (API-Token is not supported!) (Details in #86) -cloudflare_account_api_token: 9348234sdsd894..... -cloudflare_zone: example.com -# Route53 -aws_access_key: key -aws_secret_key: secret -aws_zone: example.com -# GCP -gcp_project: project-name -gcp_managed_zone_name: 'zone-name' -gcp_managed_zone_domain: 'example.com.' -gcp_serviceaccount_file: ../gcp_service_account.json -# Azure -azure_client_id: client_id -azure_secret: key -azure_subscription_id: subscription_id -azure_tenant: tenant_id -azure_resource_group: dns_zone_resource_group -# Hetzner -hetzner_account_api_token: 3543ade82AA$73..... -hetzner_zone: example.com - -# Created with: htpasswd -nb admin openshift -# Example password is openshift -auth_htpasswd: - - admin:$apr1$k5WdDLwM$TzqDd.juwTiotMbctaxJt. - - local:$apr1$k5WdDLwM$TzqDd.juwTiotMbctaxJt. - -storage_nfs: true # Default is false -masters_schedulable: false # Default is false - -auth_redhatsso: - client_id: "xxxxx.apps.googleusercontent.com" - client_secret: "xxxxxxx" - -auth_github: - client_id: "yourverysecretclientid" - client_secret: "xxxxxxx" - # Please be sure to define *either* (but at least *one*) of auth_github.organizations OR auth_github.teams - organizations: - - "sa-mw-dach" - # teams: - # - "@sa-mw-dach/hetzner-users" - -cluster_role_bindings: - - cluster_role: sudoer - name: login@redhat.com - - cluster_role: cluster-admin - name: admin - - -image_pull_secret: |- - asdfghfdsa From 389044a8e6747754fdac637a1e12f4d79cb370d4 Mon Sep 17 00:00:00 2001 From: Kim Borup Date: Mon, 5 Sep 2022 13:41:41 +0200 Subject: [PATCH 05/55] update for Rhel 9 --- cluster-example.yml | 69 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 cluster-example.yml diff --git a/cluster-example.yml b/cluster-example.yml new file mode 100644 index 00000000..90f2837e --- /dev/null +++ b/cluster-example.yml @@ -0,0 +1,69 @@ +--- +cluster_name: ocp4 +public_domain: example.com + +# DualStack: +ip_families: + - IPv4 + - IPv6 + +# set custom public ip for DNS entries. Defaults to: hostvars['localhost']['ansible_default_ipv4']['address'] +# public_ip: 92.100.42.2 +dns_provider: [route53|cloudflare|gcp|azure|hetzner] +letsencrypt_account_email: name@example.com +# Depending on the dns provider: +# CloudFlare +cloudflare_account_email: john@example.com +# Use the global api key! - (API-Token is not supported!) (Details in #86) +cloudflare_account_api_token: 9348234sdsd894..... +cloudflare_zone: example.com +# Route53 +aws_access_key: key +aws_secret_key: secret +aws_zone: example.com +# GCP +gcp_project: project-name +gcp_managed_zone_name: 'zone-name' +gcp_managed_zone_domain: 'example.com.' +gcp_serviceaccount_file: ../gcp_service_account.json +# Azure +azure_client_id: client_id +azure_secret: key +azure_subscription_id: subscription_id +azure_tenant: tenant_id +azure_resource_group: dns_zone_resource_group +# Hetzner +hetzner_account_api_token: 3543ade82AA$73..... +hetzner_zone: example.com + +# Created with: htpasswd -nb admin openshift +# Example password is openshift +auth_htpasswd: + - admin:$apr1$k5WdDLwM$TzqDd.juwTiotMbctaxJt. + - local:$apr1$k5WdDLwM$TzqDd.juwTiotMbctaxJt. + +storage_nfs: true # Default is false +masters_schedulable: false # Default is false + +auth_redhatsso: + client_id: "xxxxx.apps.googleusercontent.com" + client_secret: "xxxxxxx" + +auth_github: + client_id: "yourverysecretclientid" + client_secret: "xxxxxxx" + # Please be sure to define *either* (but at least *one*) of auth_github.organizations OR auth_github.teams + organizations: + - "sa-mw-dach" + # teams: + # - "@sa-mw-dach/hetzner-users" + +cluster_role_bindings: + - cluster_role: sudoer + name: login@redhat.com + - cluster_role: cluster-admin + name: admin + + +image_pull_secret: |- + asdfghfdsa From 941be1baf46fd4f3ef8598bc17c0dbad67bb5e29 Mon Sep 17 00:00:00 2001 From: Kim Borup Date: Mon, 5 Sep 2022 13:43:13 +0200 Subject: [PATCH 06/55] update for Rhel 9 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 71cd253f..b86606fc 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Our instructions are based on the CentOS Root Server as provided by https://www. ** Supported root server operating systems: ** - CentOS Stream 8 - RHEL 8 - How to install RHEL8: https://keithtenzer.com/cloud/how-to-create-a-rhel-8-image-for-hetzner-root-servers/ +- RHEL 9 - leapp update from RHEL 8 ## Infra providers * [Hetzner CentOS](docs/hetzner.md) From ddbc7a92865c7c99cc7d7cfb22f73825a9f6e7f6 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Fri, 4 Nov 2022 18:53:41 +0100 Subject: [PATCH 07/55] Update AAP from 2.1 to 2.2 - fixed issue #229 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b86606fc..c957ee19 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ subscription-manager repos \ --enable=rhel-8-for-x86_64-baseos-rpms \ --enable=rhel-8-for-x86_64-appstream-rpms \ --enable=rhel-8-for-x86_64-highavailability-rpms \ - --enable=ansible-automation-platform-2.1-for-rhel-8-x86_64-rpms + --enable=ansible-automation-platform-2.2-for-rhel-8-x86_64-rpms dnf install -y ansible-navigator git podman From 7e7da5be9b57c5c710d5422440e39fea61baae16 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Fri, 18 Nov 2022 13:19:34 +0100 Subject: [PATCH 08/55] Bump openshift version to 4.11.12 --- ansible/roles/openshift-4-cluster/defaults/main.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ansible/roles/openshift-4-cluster/defaults/main.yml b/ansible/roles/openshift-4-cluster/defaults/main.yml index 860e7c92..af817712 100644 --- a/ansible/roles/openshift-4-cluster/defaults/main.yml +++ b/ansible/roles/openshift-4-cluster/defaults/main.yml @@ -42,7 +42,8 @@ vm_autostart: false # Important: OpenShift version must match to RHEL CoreOS version! # reference to OpenShift version -openshift_version: 4.10.16 +openshift_version: 4.11.12 +openshift_install_command: "/root/hetzner-ocp4/openshift-install" openshift_install_command: "/opt/openshift-install-{{ openshift_version }}/openshift-install" # dev-pre: # {{ openshift_mirror }}/pub/openshift-v4/clients/ocp-dev-preview @@ -59,7 +60,7 @@ opm_download_url: "{{ openshift_location }}/opm-linux-{{ opm_version }}.tar.gz" opm_dest: "/opt/openshift-client-{{ openshift_client_version }}/" # reference to coreos qcow file -coreos_version: 4.10.3 +coreos_version: 4.11.9 coreos_download_url: "{{ openshift_mirror }}/pub/openshift-v4/dependencies/rhcos/{{ coreos_version.split('.')[:2]|join('.') }}/{{ coreos_version }}/rhcos-{{coreos_version}}-x86_64-qemu.x86_64.qcow2.gz" # noqa line-length coreos_csum_url: "{{ openshift_mirror }}/pub/openshift-v4/dependencies/rhcos/{{ coreos_version.split('.')[:2]|join('.') }}/{{ coreos_version }}/sha256sum.txt" # noqa line-length From 648a988b40334e437775bb7c64ea2387199bd399 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Fri, 18 Nov 2022 13:33:02 +0100 Subject: [PATCH 09/55] Download & install oc-mirror too --- .../tasks/download-openshift-artifacts.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ansible/roles/openshift-4-cluster/tasks/download-openshift-artifacts.yml b/ansible/roles/openshift-4-cluster/tasks/download-openshift-artifacts.yml index c6311c87..05561668 100644 --- a/ansible/roles/openshift-4-cluster/tasks/download-openshift-artifacts.yml +++ b/ansible/roles/openshift-4-cluster/tasks/download-openshift-artifacts.yml @@ -11,6 +11,7 @@ - "{{ coreos_download_url }}" - "{{ tmp_openshift_install_download_url }}" - "{{ tmp_openshift_client_download_url }}" + - "{{ openshift_location }}/oc-mirror.tar.gz" - "{{ opm_download_url }}" - "{{ helm_cli_location }}" - "{{ butane_cli_location }}" @@ -136,6 +137,18 @@ - 'README.md' creates: "/opt/openshift-client-{{ openshift_version }}/oc" +- name: Download OpenShift Mirror + ansible.builtin.unarchive: + src: "{{ openshift_location }}/oc-mirror.tar.gz" + dest: "/opt/openshift-client-{{ openshift_version }}/" + remote_src: yes + mode: u+rwx,g+rx,o+rx + owner: root + group: root + exclude: + - 'README.md' + creates: "/opt/openshift-client-{{ openshift_version }}/oc-mirror" + - name: Download OPM (gz) ansible.builtin.unarchive: src: "{{ opm_download_url }}" @@ -177,6 +190,7 @@ force: yes with_dict: "/usr/local/bin/oc": "/opt/openshift-client-{{ openshift_version }}/oc" + "/usr/local/bin/oc-mirror": "/opt/openshift-client-{{ openshift_version }}/oc-mirror" "/usr/local/bin/kubectl": "/opt/openshift-client-{{ openshift_version }}/kubectl" "/usr/local/bin/openshift-install": "/opt/openshift-install-{{ openshift_version }}/openshift-install" "/usr/local/bin/helm": "/opt/openshift-client-{{ openshift_version }}/helm" From da37dbc56554005e38746affe0518e07f17dba3b Mon Sep 17 00:00:00 2001 From: Andreas Bleischwitz Date: Wed, 17 Aug 2022 16:59:38 +0200 Subject: [PATCH 10/55] changed ansible-navigator loglevel to info --- ansible-navigator.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible-navigator.yaml b/ansible-navigator.yaml index 1389068e..7e6568c0 100644 --- a/ansible-navigator.yaml +++ b/ansible-navigator.yaml @@ -5,7 +5,7 @@ ansible-navigator: - --net=host image: quay.io/redhat-emea-ssa-team/hetzner-ocp4-ansible-ee:master logging: - level: critical + level: info mode: stdout playbook-artifact: enable: true From e4c2f845378841499f749601283390e1ca9c41f8 Mon Sep 17 00:00:00 2001 From: Andreas Bleischwitz Date: Wed, 17 Aug 2022 17:04:51 +0200 Subject: [PATCH 11/55] added mechanism to provision hetzner-root server with IPv6 address, changed ext4 to xfs, added hetzner_ssh_private_id as variable to provide ssh-key to be used --- ansible/00-provision-hetzner.yml | 27 +-- .../tasks/deploy-centos-streams.yml | 174 +++++++++++++++++ .../roles/provision-hetzner/tasks/main.yml | 181 +++++------------- .../provision-hetzner/templates/autosetup | 8 +- 4 files changed, 238 insertions(+), 152 deletions(-) create mode 100644 ansible/roles/provision-hetzner/tasks/deploy-centos-streams.yml diff --git a/ansible/00-provision-hetzner.yml b/ansible/00-provision-hetzner.yml index c8ca4a72..65bf78c7 100644 --- a/ansible/00-provision-hetzner.yml +++ b/ansible/00-provision-hetzner.yml @@ -1,16 +1,16 @@ --- -- name: Build inventory - hosts: localhost - connection: local - gather_facts: no - vars_files: - - ../cluster.yml - tasks: - - name: Add hetzner server to inventory - add_host: - name: "{{ hetzner_ip }}" - +# - name: Build inventory +# hosts: localhost +# connection: local +# gather_facts: no +# vars_files: +# - ../cluster.yml +# tasks: +# - name: Add hetzner server to inventory +# add_host: +# name: "{{ hetzner_ip }}" +# - name: install hetzner server hosts: all gather_facts: no @@ -18,6 +18,11 @@ vars_files: - ../cluster.yml tasks: + - name: Add hetzner server to inventory + ansible.builtin.add_host: + name: "{{ hetzner_ip }}" + ansible_ssh_private_key_file: '{{ hetzner_ssh_private_id }}' + - name: provision hetzner root server import_role: name: provision-hetzner diff --git a/ansible/roles/provision-hetzner/tasks/deploy-centos-streams.yml b/ansible/roles/provision-hetzner/tasks/deploy-centos-streams.yml new file mode 100644 index 00000000..6293960d --- /dev/null +++ b/ansible/roles/provision-hetzner/tasks/deploy-centos-streams.yml @@ -0,0 +1,174 @@ +--- +- name: Retrieve first public key fingerprint + uri: + url: "{{ robot_base }}/key" + return_content: yes + method: GET + user: "{{ hetzner_webservice_username }}" + password: "{{ hetzner_webservice_password }}" + force_basic_auth: yes + status_code: 200 + register: key + delegate_to: localhost + +### todo: add mechanism to check FP of key with the one give by hetzner_ssh_private_id parameter +- name: Set authorized_key fact + set_fact: + authorized_key: "{{ key.json[0].key.fingerprint }}" + +- name: "Retrieve server number from IP {{ hetzner_ip }}" + uri: + url: "{{ robot_base }}/server" + return_content: yes + method: GET + user: "{{ hetzner_webservice_username }}" + password: "{{ hetzner_webservice_password }}" + force_basic_auth: yes + status_code: 200 + register: servers + delegate_to: localhost + +- name: Query enabled and ready servers for matching ipv4 or ipv6 + set_fact: + query_ready: "[?cancelled == false && status == \"ready\"].{number: server.server_number, ipv4: server.server_ip, ipv6: server.server_ipv6_net, status: server.status, cancelled: server.cancelled}" + query: "[] | [?contains(\'{{ public_ipv6 }}\', ipv6) || contains(ipv4, \'{{ hetzner_ip }}\')].{number: number, ipv4: ipv4, ipv6: ipv6}" + +# - debug: msg="{{ servers.json }}" + +### todo: clean this up!! +- name: Set server_id fact + set_fact: + server_id: "{{ servers.json | to_json | from_json | community.general.json_query(query_ready) | community.general.json_query(query) | community.general.json_query('[].number | [0]') | int }}" + delegate_to: localhost + +- name: Check rescue mode + uri: + url: "{{ robot_base }}/boot/{{ server_id }}/rescue" + method: GET + user: "{{ hetzner_webservice_username }}" + password: "{{ hetzner_webservice_password }}" + force_basic_auth: yes + status_code: 200 + register: rescue + delegate_to: localhost + +- name: Activate rescue mode + uri: + url: "{{ robot_base }}/boot/{{ server_id }}/rescue" + method: POST + user: "{{ hetzner_webservice_username }}" + password: "{{ hetzner_webservice_password }}" + force_basic_auth: yes + body: "os=linux&arch=64&authorized_key={{ authorized_key }}" + status_code: 200 + headers: + Content_Type: "application/x-www-form-urlencoded" + register: activated + delegate_to: localhost + when: not rescue.json.rescue.active + +# - debug: msg="{{ activated }}" + +- name: Pause a bit to allow rescue mode to settle + pause: seconds=5 + +- name: Execute hardware reset + uri: + url: "{{ robot_base }}/reset/{{ server_id }}" + method: POST + user: "{{ hetzner_webservice_username }}" + password: "{{ hetzner_webservice_password }}" + force_basic_auth: yes + body: "type=hw" + status_code: 200 + headers: + Content-Type: "application/x-www-form-urlencoded" + register: reset + delegate_to: localhost + +- name: Remove server from local known_hosts file + command: "/usr/bin/ssh-keygen -R {{ hetzner_ip }}" + register: output + failed_when: output.rc != 0 + changed_when: '"updated" in output.stdout' + delegate_to: localhost + +- name: Pause a bit for the hardware reset to kick in + pause: seconds=5 + +- name: Wait 600 seconds for port 22 to become open + wait_for: + port: 22 + host: '{{ hetzner_ip }}' + delay: 10 + timeout: 600 + connection: local + +- name: Copy autosetup configuration file + template: + src: "{{ hetzner_autosetup_file }}" + dest: /root/autosetup.ansible + owner: root + group: root + mode: 0644 + delegate_to: "{{ hetzner_ip }}" + +- name: Run installimage + command: "/root/.oldroot/nfs/install/installimage -a -c /root/autosetup.ansible" + environment: + TERM: "vt100" + register: result + changed_when: true + failed_when: false + delegate_to: "{{ hetzner_ip }}" + +- name: Print installimage output with -v + debug: + var: result.stdout_lines + verbosity: 1 + delegate_to: localhost + +- name: Check stderr from installimage + debug: + msg: "Something want wrong at installimage: {{ result.stderr_lines | join('\n') }}" + when: + - result.stderr_lines | length > 0 + - not hetzner_image_ignore_errors + delegate_to: localhost + +### todo: add pulling of debug.log which is created by the installer for further analysis +- name: Check stdout from installimage + fail: + msg: "Installation failed, check log: {{ result.stdout_lines | join('\n') }}" + when: + - result.stdout is search('An error occured while installing the new system') + - not hetzner_image_ignore_errors + delegate_to: localhost + +- name: Reboot server + shell: sync && sleep 2 && shutdown -r now + async: 1 + poll: 0 + changed_when: true + failed_when: false + delegate_to: "{{ hetzner_ip }}" + +- name: Remove server from local known_hosts file + command: "/usr/bin/ssh-keygen -R {{ hetzner_ip }}" + register: output + failed_when: output.rc != 0 + changed_when: '"updated" in output.stdout' + delegate_to: localhost + +- name: Wait 600 seconds for port 22 to become open + wait_for: + port: 22 + host: '{{ hetzner_ip }}' + delay: 10 + timeout: 600 + connection: local + +- name: "Refresh information after re-install of {{ hetzner_ip }}" + ansible.builtin.gather_facts: + register: host_facts + delegate_to: "{{ hetzner_ip }}" diff --git a/ansible/roles/provision-hetzner/tasks/main.yml b/ansible/roles/provision-hetzner/tasks/main.yml index ad021f82..f6c6807a 100644 --- a/ansible/roles/provision-hetzner/tasks/main.yml +++ b/ansible/roles/provision-hetzner/tasks/main.yml @@ -1,143 +1,29 @@ --- -- name: Retrieve first public key fingerprint - uri: - url: https://robot-ws.your-server.de/key - return_content: yes - method: GET - user: "{{ hetzner_webservice_username }}" - password: "{{ hetzner_webservice_password }}" - force_basic_auth: yes - status_code: 200 - register: key - delegate_to: localhost - -- name: Set authorized_key fact - set_fact: - authorized_key: "{{ key.json[0].key.fingerprint }}" - -- name: Check rescue mode - uri: - url: "https://robot-ws.your-server.de/boot/{{ hetzner_ip }}/rescue" - method: GET - user: "{{ hetzner_webservice_username }}" - password: "{{ hetzner_webservice_password }}" - force_basic_auth: yes - status_code: 200 - register: rescue - delegate_to: localhost - -- name: Activate rescue mode - when: not rescue.json.rescue.active - uri: - url: "https://robot-ws.your-server.de/boot/{{ hetzner_ip }}/rescue" - method: POST - user: "{{ hetzner_webservice_username }}" - password: "{{ hetzner_webservice_password }}" - force_basic_auth: yes - body: "os=linux&arch=64&authorized_key={{ authorized_key }}" - status_code: 200 - headers: - Content_Type: "application/x-www-form-urlencoded" - register: activated - delegate_to: localhost - -- name: Execute hardware reset - uri: - url: "https://robot-ws.your-server.de/reset/{{ hetzner_ip }}" - method: POST - user: "{{ hetzner_webservice_username }}" - password: "{{ hetzner_webservice_password }}" - force_basic_auth: yes - body: "type=hw" - status_code: 200 - headers: - Content-Type: "application/x-www-form-urlencoded" - delegate_to: localhost - -- name: Remove server from local known_hosts file - delegate_to: localhost - command: "/usr/bin/ssh-keygen -R {{ inventory_hostname }}" - register: output - failed_when: output.rc != 0 - changed_when: '"updated" in output.stdout' - -- name: Pause a bit for the hardware reset to kick in - pause: seconds=15 - -- name: Wait 300 seconds for port 22 to become open - wait_for: - port: 22 - host: '{{ inventory_hostname }}' - delay: 10 - timeout: 300 - connection: local - -- name: Ping rescue system - ping: - retries: 10 - delay: 1 - -- name: Copy autosetup configuration file - template: - src: "{{ hetzner_autosetup_file }}" - dest: /root/autosetup - owner: root - group: root - mode: 0644 - -- name: Run installimage - command: "/root/.oldroot/nfs/install/installimage -a -c /root/autosetup" - environment: - TERM: "vt100" - register: result - changed_when: true - failed_when: false - -- name: Print installimage output with -v - debug: - var: result - verbosity: 1 - -- name: Check stderr from installimage - fail: - msg: "Something want wrong at installimage: {{ result.stderr_lines | join('\n') }}" +- name: define base uri + ansible.builtin.set_fact: + robot_base: https://robot-ws.your-server.de/ + needs_reprovision: False + +- name: "Retrieve current setup of {{ hetzner_ip }}" + ansible.builtin.gather_facts: + register: host_facts + delegate_to: "{{ hetzner_ip }}" + +- ansible.builtin.set_fact: + needs_reprovision: True when: - - result.stderr_lines | length > 0 - - not hetzner_image_ignore_errors - -- name: Check stdout from installimage - fail: - msg: "Something want wrong at installimage: {{ result.stdout_lines | join('\n') }}" - when: - - result.stdout is search('An error occured while installing the new system!') - - not etzner_image_ignore_errors - -- name: Reboot server - shell: sleep 2 && shutdown -r now - async: 1 - poll: 0 - changed_when: true - failed_when: false - -- name: Remove server from local known_hosts file + - host_facts.ansible_facts.ansible_cmdline.BOOT_IMAGE is match("rescue") or (hetzner_force_provisioning is defined and hetzner_force_provisioning == true) delegate_to: localhost - command: "/usr/bin/ssh-keygen -R {{ inventory_hostname }}" - register: output - failed_when: output.rc != 0 - changed_when: '"updated" in output.stdout' -- name: Wait 300 seconds for port 22 to become open - wait_for: - port: 22 - host: '{{ inventory_hostname }}' - delay: 10 - timeout: 300 - connection: local +- name: Provision server + include_tasks: deploy-centos-streams.yml + when: needs_reprovision == True - name: Check ansible_python_interpreter - ping: + ansible.builtin.shell: "which python3" register: rc ignore_errors: true + delegate_to: "{{ hetzner_ip }}" - name: Set ansible_python_interpreter to /usr/libexec/platform-python (RHEL 8) set_fact: @@ -149,8 +35,29 @@ path: /etc/ssh/sshd_config regexp: '^PasswordAuthentication yes' line: 'PasswordAuthentication no' - -- name: Restart sshd - systemd: - name: sshd.service - state: restarted + delegate_to: "{{ hetzner_ip }}" + notify: + - Restart SSH daemon + +- name: Update all packages + ansible.builtin.package: + name: "*" + state: latest + delegate_to: "{{ hetzner_ip }}" + +- name: Install libvirtd + ansible.builtin.package: + name: + - libvirt-client + - libvirt + state: latest + delegate_to: "{{ hetzner_ip }}" + +- name: Install base-tooling + ansible.builtin.package: + name: + - firewalld + - policycoreutils-python-utils + - bind-utils + state: latest + delegate_to: "{{ hetzner_ip }}" diff --git a/ansible/roles/provision-hetzner/templates/autosetup b/ansible/roles/provision-hetzner/templates/autosetup index 7b4f1dea..c84ced95 100644 --- a/ansible/roles/provision-hetzner/templates/autosetup +++ b/ansible/roles/provision-hetzner/templates/autosetup @@ -4,13 +4,13 @@ SWRAID 1 SWRAIDLEVEL 0 BOOTLOADER grub HOSTNAME {{ hetzner_hostname }} -PART /boot ext3 512M +PART /boot ext3 1024M PART lvm vg0 all -LV vg0 root / ext4 50G +LV vg0 root / xfs 50G LV vg0 swap swap swap 8G -LV vg0 home /home ext4 40G -LV vg0 var /var ext4 50G +LV vg0 home /home xfs 10G +LV vg0 var /var xfs 50G LV vg0 libvirt /var/lib/libvirt/images xfs all IMAGE {{ hetzner_image }} From 66a8e0a7454f4795351160f8d2c4b85102681727 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Sat, 26 Nov 2022 15:49:41 +0100 Subject: [PATCH 12/55] Cleanup ansible/00-provision-hetzner.yml - remove comments --- ansible/00-provision-hetzner.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/ansible/00-provision-hetzner.yml b/ansible/00-provision-hetzner.yml index 65bf78c7..7a668abd 100644 --- a/ansible/00-provision-hetzner.yml +++ b/ansible/00-provision-hetzner.yml @@ -1,16 +1,5 @@ --- -# - name: Build inventory -# hosts: localhost -# connection: local -# gather_facts: no -# vars_files: -# - ../cluster.yml -# tasks: -# - name: Add hetzner server to inventory -# add_host: -# name: "{{ hetzner_ip }}" -# - name: install hetzner server hosts: all gather_facts: no From 0fe32ecb4eb32198166f538d100e07c2403f5f6a Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Sat, 26 Nov 2022 15:50:21 +0100 Subject: [PATCH 13/55] Rename deploy-centos-streams.yml into provision-server.yml --- ansible/roles/provision-hetzner/tasks/main.yml | 2 +- .../{deploy-centos-streams.yml => provision-server.yml} | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) rename ansible/roles/provision-hetzner/tasks/{deploy-centos-streams.yml => provision-server.yml} (95%) diff --git a/ansible/roles/provision-hetzner/tasks/main.yml b/ansible/roles/provision-hetzner/tasks/main.yml index f6c6807a..ce51e8fe 100644 --- a/ansible/roles/provision-hetzner/tasks/main.yml +++ b/ansible/roles/provision-hetzner/tasks/main.yml @@ -16,7 +16,7 @@ delegate_to: localhost - name: Provision server - include_tasks: deploy-centos-streams.yml + include_tasks: provision-server.yml when: needs_reprovision == True - name: Check ansible_python_interpreter diff --git a/ansible/roles/provision-hetzner/tasks/deploy-centos-streams.yml b/ansible/roles/provision-hetzner/tasks/provision-server.yml similarity index 95% rename from ansible/roles/provision-hetzner/tasks/deploy-centos-streams.yml rename to ansible/roles/provision-hetzner/tasks/provision-server.yml index 6293960d..16169963 100644 --- a/ansible/roles/provision-hetzner/tasks/deploy-centos-streams.yml +++ b/ansible/roles/provision-hetzner/tasks/provision-server.yml @@ -28,6 +28,7 @@ register: servers delegate_to: localhost +### ToDo: public_ipv6 could be empty or not set, by default public_ipv6 is gathered. - name: Query enabled and ready servers for matching ipv4 or ipv6 set_fact: query_ready: "[?cancelled == false && status == \"ready\"].{number: server.server_number, ipv4: server.server_ip, ipv6: server.server_ipv6_net, status: server.status, cancelled: server.cancelled}" @@ -141,7 +142,9 @@ fail: msg: "Installation failed, check log: {{ result.stdout_lines | join('\n') }}" when: - - result.stdout is search('An error occured while installing the new system') + - > + result.stdout is search('An error occured while installing the new system') or + result.stdout is search('Cancelled') - not hetzner_image_ignore_errors delegate_to: localhost From 3aa9cbf66962e0406843896dc6aaae0939218097 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Sat, 26 Nov 2022 15:56:20 +0100 Subject: [PATCH 14/55] Fixed checking hetzner_force_provisioning (removed '== true') --- ansible/roles/provision-hetzner/tasks/main.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ansible/roles/provision-hetzner/tasks/main.yml b/ansible/roles/provision-hetzner/tasks/main.yml index ce51e8fe..cb629100 100644 --- a/ansible/roles/provision-hetzner/tasks/main.yml +++ b/ansible/roles/provision-hetzner/tasks/main.yml @@ -9,10 +9,11 @@ register: host_facts delegate_to: "{{ hetzner_ip }}" -- ansible.builtin.set_fact: +- name: Check Boot image and hetzner_force_provisioning + ansible.builtin.set_fact: needs_reprovision: True when: - - host_facts.ansible_facts.ansible_cmdline.BOOT_IMAGE is match("rescue") or (hetzner_force_provisioning is defined and hetzner_force_provisioning == true) + - host_facts.ansible_facts.ansible_cmdline.BOOT_IMAGE is match("rescue") or (hetzner_force_provisioning is defined and hetzner_force_provisioning ) delegate_to: localhost - name: Provision server From e4f962ea34d86f364895f2f30a68b9c65e711166 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Sat, 26 Nov 2022 15:56:57 +0100 Subject: [PATCH 15/55] Remove package installation This should be part of openshift-4-cluster/tasks/prepare-host* --- .../roles/provision-hetzner/tasks/main.yml | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/ansible/roles/provision-hetzner/tasks/main.yml b/ansible/roles/provision-hetzner/tasks/main.yml index cb629100..09ca51b3 100644 --- a/ansible/roles/provision-hetzner/tasks/main.yml +++ b/ansible/roles/provision-hetzner/tasks/main.yml @@ -39,26 +39,3 @@ delegate_to: "{{ hetzner_ip }}" notify: - Restart SSH daemon - -- name: Update all packages - ansible.builtin.package: - name: "*" - state: latest - delegate_to: "{{ hetzner_ip }}" - -- name: Install libvirtd - ansible.builtin.package: - name: - - libvirt-client - - libvirt - state: latest - delegate_to: "{{ hetzner_ip }}" - -- name: Install base-tooling - ansible.builtin.package: - name: - - firewalld - - policycoreutils-python-utils - - bind-utils - state: latest - delegate_to: "{{ hetzner_ip }}" From e002ffe58ce85caa3d910a904c72f62de0a49c93 Mon Sep 17 00:00:00 2001 From: Andreas Bleischwitz Date: Wed, 17 Aug 2022 17:05:27 +0200 Subject: [PATCH 16/55] added handler to restart sshd --- ansible/roles/provision-hetzner/handlers/main.yaml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 ansible/roles/provision-hetzner/handlers/main.yaml diff --git a/ansible/roles/provision-hetzner/handlers/main.yaml b/ansible/roles/provision-hetzner/handlers/main.yaml new file mode 100644 index 00000000..ccc279f5 --- /dev/null +++ b/ansible/roles/provision-hetzner/handlers/main.yaml @@ -0,0 +1,7 @@ +--- +- name: Restart SSH daemon + ansible.builtin.service: + name: sshd + state: restarted + delegate_to: "{{ hetzner_ip }}" + From 6402cfff0b28719ae8c6a243cef10f564f98f296 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Sat, 26 Nov 2022 16:01:01 +0100 Subject: [PATCH 17/55] Remove ansible_python_interpreter from defaults --- ansible/roles/provision-hetzner/defaults/main.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/ansible/roles/provision-hetzner/defaults/main.yml b/ansible/roles/provision-hetzner/defaults/main.yml index 90f1388a..9f8858d3 100644 --- a/ansible/roles/provision-hetzner/defaults/main.yml +++ b/ansible/roles/provision-hetzner/defaults/main.yml @@ -8,4 +8,3 @@ hetzner_disk1: sda hetzner_disk2: sdb hetzner_image: "/root/.oldroot/nfs/install/../images/CentOS-80-stream-amd64-base.tar.gz" hetzner_image_ignore_errors: false -ansible_python_interpreter: /usr/bin/python3 # target host python interpreter From 334f2cd33d41ee539ede23157227e27567838337 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Sat, 26 Nov 2022 16:08:05 +0100 Subject: [PATCH 18/55] Added new option hetzner_size_of_libvirt_images --- ansible/roles/provision-hetzner/defaults/main.yml | 1 + ansible/roles/provision-hetzner/templates/autosetup | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ansible/roles/provision-hetzner/defaults/main.yml b/ansible/roles/provision-hetzner/defaults/main.yml index 9f8858d3..4ccda7c5 100644 --- a/ansible/roles/provision-hetzner/defaults/main.yml +++ b/ansible/roles/provision-hetzner/defaults/main.yml @@ -8,3 +8,4 @@ hetzner_disk1: sda hetzner_disk2: sdb hetzner_image: "/root/.oldroot/nfs/install/../images/CentOS-80-stream-amd64-base.tar.gz" hetzner_image_ignore_errors: false +hetzner_size_of_libvirt_images: all \ No newline at end of file diff --git a/ansible/roles/provision-hetzner/templates/autosetup b/ansible/roles/provision-hetzner/templates/autosetup index c84ced95..d8b4f22c 100644 --- a/ansible/roles/provision-hetzner/templates/autosetup +++ b/ansible/roles/provision-hetzner/templates/autosetup @@ -11,6 +11,6 @@ LV vg0 root / xfs 50G LV vg0 swap swap swap 8G LV vg0 home /home xfs 10G LV vg0 var /var xfs 50G -LV vg0 libvirt /var/lib/libvirt/images xfs all +LV vg0 libvirt /var/lib/libvirt/images xfs {{ hetzner_size_of_libvirt_images }} IMAGE {{ hetzner_image }} From 1b0a39dae6cae5ce4becbf85deeeaf08c8db7cb0 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Sat, 26 Nov 2022 18:03:00 +0100 Subject: [PATCH 19/55] Added option to enable RedHat entitlement --- .../tasks/prepare-host-RedHat-8.yml | 14 ++++++++++++++ .../tasks/prepare-host-RedHat-entitlement.yml | 17 +++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-entitlement.yml diff --git a/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-8.yml b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-8.yml index 46b3ed94..c3cafc0c 100644 --- a/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-8.yml +++ b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-8.yml @@ -1,4 +1,18 @@ --- + +- name: Handle Red Hat Entitlement + ansible.builtin.include_tasks: prepare-host-RedHat-entitlement.yml + vars: + rhsm_repository: + - rhel-8-for-x86_64-baseos-rpms + - rhel-8-for-x86_64-appstream-rpms + - rhel-8-for-x86_64-highavailability-rpms + - ansible-automation-platform-2.1-for-rhel-8-x86_64-rpms + when: + - redhat_subscription_activationkey is defined + - redhat_subscription_org_id is defined + - redhat_subscription_pool is defined + - name: Installing KVM Packages ansible.builtin.package: name: diff --git a/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-entitlement.yml b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-entitlement.yml new file mode 100644 index 00000000..38ac5b6a --- /dev/null +++ b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-entitlement.yml @@ -0,0 +1,17 @@ +--- + - name: RHEL Subscription + redhat_subscription: + state: present + activationkey: "{{ redhat_subscription_activationkey }}" + org_id: "{{ redhat_subscription_org_id }}" + pool: "{{ redhat_subscription_pool }}" + + - name: Disable all RHSM repositories + rhsm_repository: + name: '*' + state: disabled + + - name: Enable repos for RHEL 8 + rhsm_repository: + name: "{{ rhsm_repository }}" + state: enabled From f0e2377c0e7517028f4e72b0cfc4561075ac8ebc Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Sat, 26 Nov 2022 18:05:46 +0100 Subject: [PATCH 20/55] Remove duplicated openshift_install_command --- ansible/roles/openshift-4-cluster/defaults/main.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/ansible/roles/openshift-4-cluster/defaults/main.yml b/ansible/roles/openshift-4-cluster/defaults/main.yml index af817712..268c5982 100644 --- a/ansible/roles/openshift-4-cluster/defaults/main.yml +++ b/ansible/roles/openshift-4-cluster/defaults/main.yml @@ -43,7 +43,6 @@ vm_autostart: false # reference to OpenShift version openshift_version: 4.11.12 -openshift_install_command: "/root/hetzner-ocp4/openshift-install" openshift_install_command: "/opt/openshift-install-{{ openshift_version }}/openshift-install" # dev-pre: # {{ openshift_mirror }}/pub/openshift-v4/clients/ocp-dev-preview From 62fd55f5fadd6180dc3a4e82ee198a60504d90dd Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Sat, 26 Nov 2022 18:11:49 +0100 Subject: [PATCH 21/55] Cleanup default of internal variable vn_public_domain --- ansible/roles/openshift-4-cluster/defaults/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/roles/openshift-4-cluster/defaults/main.yml b/ansible/roles/openshift-4-cluster/defaults/main.yml index 268c5982..35552866 100644 --- a/ansible/roles/openshift-4-cluster/defaults/main.yml +++ b/ansible/roles/openshift-4-cluster/defaults/main.yml @@ -6,7 +6,7 @@ ssh_public_key_location: ~/.ssh/id_rsa vn_subnet: "192.168.50.0" vn_name: "openshift-4-cluster" vn_internal_domain: "compute.local" -vn_public_domain: "h42.openshift.pub" +vn_public_domain: "{{ cluster_name }}.{{ public_domain }}" # ip_families: From 09047950e973daf004949647e7b45431260cfa23 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Sat, 26 Nov 2022 18:12:59 +0100 Subject: [PATCH 22/55] Removed add_hosts, we use a propper inventory now --- ansible/00-provision-hetzner.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ansible/00-provision-hetzner.yml b/ansible/00-provision-hetzner.yml index 7a668abd..d0b1d419 100644 --- a/ansible/00-provision-hetzner.yml +++ b/ansible/00-provision-hetzner.yml @@ -7,11 +7,6 @@ vars_files: - ../cluster.yml tasks: - - name: Add hetzner server to inventory - ansible.builtin.add_host: - name: "{{ hetzner_ip }}" - ansible_ssh_private_key_file: '{{ hetzner_ssh_private_id }}' - - name: provision hetzner root server import_role: name: provision-hetzner From 8db4a80f440b25a745a94c02474d583837ff1d5e Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Sat, 26 Nov 2022 19:58:45 +0100 Subject: [PATCH 23/55] Introduce artifacts_dir --- ansible/roles/openshift-4-cluster/defaults/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ansible/roles/openshift-4-cluster/defaults/main.yml b/ansible/roles/openshift-4-cluster/defaults/main.yml index 35552866..f26ca2ab 100644 --- a/ansible/roles/openshift-4-cluster/defaults/main.yml +++ b/ansible/roles/openshift-4-cluster/defaults/main.yml @@ -1,6 +1,6 @@ --- - -openshift_install_dir: "{{ playbook_dir }}/../{{ cluster_name }}" +artifacts_dir: "{{ playbook_dir }}/.." +openshift_install_dir: "{{ artifacts_dir }}./{{ cluster_name }}" ssh_public_key_location: ~/.ssh/id_rsa vn_subnet: "192.168.50.0" @@ -69,7 +69,7 @@ coreos_path: /var/lib/libvirt/images coreos_file: "{{ coreos_download_url | basename | regex_search('.*qcow2') }}" coreos_image_location: "{{ coreos_path }}/{{ coreos_file }}" -certificates_dir: "{{ playbook_dir }}/../certificate" +certificates_dir: "{{ artifacts_dir }}/certificate" certficate_fullchain: "{{ certificates_dir }}/{{ cluster_name }}.{{ public_domain }}/fullchain.crt" certficate_key: "{{ certificates_dir }}/{{ cluster_name }}.{{ public_domain }}/cert.key" # Default production directory From 28efbd2e4d2e25546031f207f207b57b893f0546 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Sat, 26 Nov 2022 19:59:36 +0100 Subject: [PATCH 24/55] Slurp public ssh key instead of local lookup Signed-off-by: Robert Bohne --- ansible/roles/openshift-4-cluster/tasks/create.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ansible/roles/openshift-4-cluster/tasks/create.yml b/ansible/roles/openshift-4-cluster/tasks/create.yml index b93b9400..a7dac908 100644 --- a/ansible/roles/openshift-4-cluster/tasks/create.yml +++ b/ansible/roles/openshift-4-cluster/tasks/create.yml @@ -79,10 +79,15 @@ - ansible.builtin.include_tasks: download-openshift-artifacts.yml tags: download-openshift-artifacts +- name: Slurp ssh public key + ansible.builtin.slurp: + src: "{{ ssh_public_key_location }}.pub" + register: slurped_ssh_public_key + - name: Create ignition files ansible.builtin.include_tasks: create-ignition.yml vars: - ssh_public_key: "{{ lookup('file', '{{ ssh_public_key_location }}.pub') }}" + ssh_public_key: "{{ slurped_ssh_public_key['content'] | b64decode }}" tags: ignition - name: Create bootstrap node From 257da2a7309722629d3f8168d7747d337f090e49 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Sat, 26 Nov 2022 21:12:18 +0100 Subject: [PATCH 25/55] Copy kubeconfig into runner and use it... --- .../tasks/build-k8s-vars.yml | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/ansible/roles/openshift-4-cluster/tasks/build-k8s-vars.yml b/ansible/roles/openshift-4-cluster/tasks/build-k8s-vars.yml index ee66b143..f222cbaa 100644 --- a/ansible/roles/openshift-4-cluster/tasks/build-k8s-vars.yml +++ b/ansible/roles/openshift-4-cluster/tasks/build-k8s-vars.yml @@ -1,24 +1,27 @@ --- -- name: Create own kubeconfig directory - ansible.builtin.file: +- name: Create temporary kubeconfig directory in runner + delegate_to: localhost + ansible.builtin.tempfile: state: directory - path: "{{ openshift_install_dir }}/config/" - mode: 0755 + suffix: "-hetzner-ocp4-{{ cluster_name }}" + register: k8s_tmp_dir -- name: Copy kubeconfig - ansible.builtin.copy: +- name: Fetch kubeconfig into runner + ansible.builtin.fetch: src: "{{ openshift_install_dir }}/auth/kubeconfig" - dest: "{{ openshift_install_dir }}/config/kubeconfig" - remote_src: yes + dest: "{{ k8s_tmp_dir.path }}/kubeconfig" + flat: true mode: 0644 - name: Slurp kubeconfig + delegate_to: localhost ansible.builtin.slurp: - src: "{{ openshift_install_dir }}/config/kubeconfig" + src: "{{ k8s_tmp_dir.path }}/kubeconfig" register: kubeconfig_raw - name: Copy content into kubeconfig + delegate_to: localhost ansible.builtin.set_fact: kubeconfig: "{{ kubeconfig_raw['content'] | b64decode | from_yaml }}" @@ -27,31 +30,36 @@ # kubeconfig: "{{ lookup('file', openshift_install_dir ~ '/config/kubeconfig' ) | from_yaml }}" - name: Select cluster & user + delegate_to: localhost ansible.builtin.set_fact: cluster: "{{ kubeconfig | json_query('clusters[?name==`'~ cluster_name ~'`].cluster') | first }}" user: "{{ kubeconfig | json_query('users[?name==`admin`].user') | first }}" - name: Set kube variables + delegate_to: localhost ansible.builtin.set_fact: - k8s_kubeconfig: "{{ openshift_install_dir }}/config/kubeconfig" + k8s_kubeconfig: "{{ k8s_tmp_dir.path }}/kubeconfig" k8s_host: "{{ cluster.server }}" - k8s_ca_cert: "{{ openshift_install_dir }}/config/ca.crt" - k8s_client_key: "{{ openshift_install_dir }}/config/client.key" - k8s_client_cert: "{{ openshift_install_dir }}/config/client.crt" + k8s_ca_cert: "{{ k8s_tmp_dir.path }}/ca.crt" + k8s_client_key: "{{ k8s_tmp_dir.path }}/client.key" + k8s_client_cert: "{{ k8s_tmp_dir.path }}/client.crt" - name: Create k8s_ca_cert + delegate_to: localhost ansible.builtin.copy: content: "{{ cluster['certificate-authority-data'] | b64decode }}" dest: "{{ k8s_ca_cert }}" mode: 0644 - name: Create k8s_client_key + delegate_to: localhost ansible.builtin.copy: content: "{{ user['client-key-data'] | b64decode }}" dest: "{{ k8s_client_key }}" mode: 0644 - name: Create k8s_client_cert + delegate_to: localhost ansible.builtin.copy: content: "{{ user['client-certificate-data'] | b64decode }}" dest: "{{ k8s_client_cert }}" From 5a0f266f20a596c75df34b5aea9636e2368f3bea Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Sat, 26 Nov 2022 22:16:44 +0100 Subject: [PATCH 26/55] Use defauls/main.yml instead of set_fact --- ansible/roles/provision-hetzner/defaults/main.yml | 5 ++++- ansible/roles/provision-hetzner/tasks/main.yml | 4 ---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ansible/roles/provision-hetzner/defaults/main.yml b/ansible/roles/provision-hetzner/defaults/main.yml index 4ccda7c5..c4e8ce58 100644 --- a/ansible/roles/provision-hetzner/defaults/main.yml +++ b/ansible/roles/provision-hetzner/defaults/main.yml @@ -8,4 +8,7 @@ hetzner_disk1: sda hetzner_disk2: sdb hetzner_image: "/root/.oldroot/nfs/install/../images/CentOS-80-stream-amd64-base.tar.gz" hetzner_image_ignore_errors: false -hetzner_size_of_libvirt_images: all \ No newline at end of file +hetzner_size_of_libvirt_images: all + +robot_base: https://robot-ws.your-server.de/ +needs_reprovision: False diff --git a/ansible/roles/provision-hetzner/tasks/main.yml b/ansible/roles/provision-hetzner/tasks/main.yml index 09ca51b3..15ae9b89 100644 --- a/ansible/roles/provision-hetzner/tasks/main.yml +++ b/ansible/roles/provision-hetzner/tasks/main.yml @@ -1,8 +1,4 @@ --- -- name: define base uri - ansible.builtin.set_fact: - robot_base: https://robot-ws.your-server.de/ - needs_reprovision: False - name: "Retrieve current setup of {{ hetzner_ip }}" ansible.builtin.gather_facts: From a166a715c2dcfd8b1825c6de55154aa32829a7e5 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Sat, 26 Nov 2022 22:17:35 +0100 Subject: [PATCH 27/55] Cleanup hetzner server id hunting --- .../tasks/provision-server.yml | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/ansible/roles/provision-hetzner/tasks/provision-server.yml b/ansible/roles/provision-hetzner/tasks/provision-server.yml index 16169963..fd99f893 100644 --- a/ansible/roles/provision-hetzner/tasks/provision-server.yml +++ b/ansible/roles/provision-hetzner/tasks/provision-server.yml @@ -28,19 +28,21 @@ register: servers delegate_to: localhost -### ToDo: public_ipv6 could be empty or not set, by default public_ipv6 is gathered. -- name: Query enabled and ready servers for matching ipv4 or ipv6 +- name: Filter server by IPv4 or IPv6 + delegate_to: localhost set_fact: - query_ready: "[?cancelled == false && status == \"ready\"].{number: server.server_number, ipv4: server.server_ip, ipv6: server.server_ipv6_net, status: server.status, cancelled: server.cancelled}" - query: "[] | [?contains(\'{{ public_ipv6 }}\', ipv6) || contains(ipv4, \'{{ hetzner_ip }}\')].{number: number, ipv4: ipv4, ipv6: ipv6}" + server_id_list: "{{ servers.json | to_json | from_json | community.general.json_query('[?cancelled == false && status == \"ready\" && ( server.server_ip == `'~ hetzner_ip ~'` || contains(`'~ hetzner_ip ~'`, server.server_ipv6_net) ) ].server.server_number') }}" -# - debug: msg="{{ servers.json }}" +- name: Check server_id + delegate_to: localhost + ansible.builtin.fail: + msg: "Can NOT find Hetzner Server Id, to many or no matching server found." + when: server_id_list|length != 1 -### todo: clean this up!! -- name: Set server_id fact - set_fact: - server_id: "{{ servers.json | to_json | from_json | community.general.json_query(query_ready) | community.general.json_query(query) | community.general.json_query('[].number | [0]') | int }}" +- name: Filter server by IPv4 or IPv6 delegate_to: localhost + set_fact: + server_id: "{{ server_id_list | first }}" - name: Check rescue mode uri: From aee55829531f0b2ecceae4426f3f6eeb40549d4c Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Sat, 26 Nov 2022 22:28:22 +0100 Subject: [PATCH 28/55] Add some notes, ssh key pair comparison is not easy.. --- .../tasks/provision-server.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/ansible/roles/provision-hetzner/tasks/provision-server.yml b/ansible/roles/provision-hetzner/tasks/provision-server.yml index fd99f893..a72411d9 100644 --- a/ansible/roles/provision-hetzner/tasks/provision-server.yml +++ b/ansible/roles/provision-hetzner/tasks/provision-server.yml @@ -12,6 +12,25 @@ delegate_to: localhost ### todo: add mechanism to check FP of key with the one give by hetzner_ssh_private_id parameter +# Sadly, Hetzner provided fingerprint in: +# "key": { +# "created_at": "2020-12-18T14:59:49.000Z", +# "data": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAOfl+764UFbDkkxpsQYjET7ZAWoVApSf4I64L1KImoc rbohne@redhat.com", +# "fingerprint": "cb:fc:61:a3:de:48:2a:fc:5e:75:14:b6:0a:36:d9:1f", +# "name": "AA-ed25519", +# "size": 256, +# "type": "ED25519" +# } +# Ansible module community.crypto.openssh_keypair in SHA356 +# - name: Check OpenSSH private key +# community.crypto.openssh_keypair: +# regenerate: never +# path: "{{ hetzner_ssh_private_id }}" +# register: ssh_private_key +# - debug: +# var: ssh_private_key +# => "fingerprint": "SHA256:MV6mnlC44jtntBj327ya7mump58SUJQDzzAmlJxnMkM", + - name: Set authorized_key fact set_fact: authorized_key: "{{ key.json[0].key.fingerprint }}" From ef6ea974637de26e02704f041e96a238b6b18cf2 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Sat, 26 Nov 2022 22:37:23 +0100 Subject: [PATCH 29/55] Cleanup YAML to pass pre-commit --- ansible-navigator.yaml | 2 +- .../openshift-4-cluster/tasks/create.yml | 6 ++-- .../openshift-4-cluster/tasks/destroy.yml | 16 +++++++---- .../tasks/post-install.yml | 4 ++- .../tasks/prepare-host-RedHat-entitlement.yml | 28 +++++++++---------- .../roles/openshift-4-cluster/tasks/start.yml | 6 ++-- .../roles/provision-hetzner/defaults/main.yml | 2 +- .../provision-hetzner/handlers/main.yaml | 1 - .../roles/provision-hetzner/tasks/main.yml | 6 ++-- .../tasks/provision-server.yml | 2 ++ 10 files changed, 44 insertions(+), 29 deletions(-) diff --git a/ansible-navigator.yaml b/ansible-navigator.yaml index 7e6568c0..aec9f8ab 100644 --- a/ansible-navigator.yaml +++ b/ansible-navigator.yaml @@ -2,7 +2,7 @@ ansible-navigator: execution-environment: container-options: - - --net=host + - --net=host image: quay.io/redhat-emea-ssa-team/hetzner-ocp4-ansible-ee:master logging: level: info diff --git a/ansible/roles/openshift-4-cluster/tasks/create.yml b/ansible/roles/openshift-4-cluster/tasks/create.yml index a7dac908..da02f694 100644 --- a/ansible/roles/openshift-4-cluster/tasks/create.yml +++ b/ansible/roles/openshift-4-cluster/tasks/create.yml @@ -18,7 +18,8 @@ - add-ons - post-install-add-ons -- ansible.builtin.import_tasks: create-network.yml +- name: Create network + ansible.builtin.import_tasks: create-network.yml vars: vn_public_domain: "{{ cluster_name }}.{{ public_domain }}" vn_master_count: "{{ master_count }}" @@ -76,7 +77,8 @@ # Work-a-round: tags inheritance don't work without a block. # https://github.com/ansible/ansible/issues/41540#issuecomment-419433375 block: - - ansible.builtin.include_tasks: download-openshift-artifacts.yml + - name: Include tasks + ansible.builtin.include_tasks: download-openshift-artifacts.yml tags: download-openshift-artifacts - name: Slurp ssh public key diff --git a/ansible/roles/openshift-4-cluster/tasks/destroy.yml b/ansible/roles/openshift-4-cluster/tasks/destroy.yml index b94b1e06..e28b35dc 100644 --- a/ansible/roles/openshift-4-cluster/tasks/destroy.yml +++ b/ansible/roles/openshift-4-cluster/tasks/destroy.yml @@ -1,5 +1,6 @@ --- -- ansible.builtin.import_tasks: destroy-network.yml +- name: Destroy network + ansible.builtin.import_tasks: destroy-network.yml vars: vn_name: "{{ cluster_name }}" tags: network @@ -9,16 +10,19 @@ msg: "Letsencrypt certifcates stays...." tags: letsencrypt -- ansible.builtin.include_tasks: destroy-vm.yml +- name: Destroy bootstrap VM + ansible.builtin.include_tasks: destroy-vm.yml vars: vm_instance_name: "{{ cluster_name }}-bootstrap" -- ansible.builtin.include_tasks: destroy-vm.yml +- name: Destroy master VM's + ansible.builtin.include_tasks: destroy-vm.yml vars: vm_instance_name: "{{ cluster_name }}-master-{{ item }}" with_sequence: start=0 end="{{ master_count|int - 1 }}" stride=1 -- ansible.builtin.include_tasks: destroy-vm.yml +- name: Destroy compute VM's + ansible.builtin.include_tasks: destroy-vm.yml vars: vm_instance_name: "{{ cluster_name }}-compute-{{ item }}" with_sequence: start=0 end="{{ compute_count|int - 1 if compute_count|int > 0 else 0 }}" stride=1 @@ -27,7 +31,9 @@ - name: Destroy storage-nfs ansible.builtin.import_tasks: destroy-storage-nfs.yml when: storage_nfs == true - tags: storage + tags: + - storage + - skip_ansible_lint - name: Clean OpenShift install directory ansible.builtin.file: diff --git a/ansible/roles/openshift-4-cluster/tasks/post-install.yml b/ansible/roles/openshift-4-cluster/tasks/post-install.yml index 855bb344..c240891d 100644 --- a/ansible/roles/openshift-4-cluster/tasks/post-install.yml +++ b/ansible/roles/openshift-4-cluster/tasks/post-install.yml @@ -71,11 +71,13 @@ ########################################################################################### # Install letsencrypt certificates ########################################################################################### -- ansible.builtin.include_tasks: certificate-install.yml +- name: Install certificate + ansible.builtin.include_tasks: certificate-install.yml when: letsencrypt_disabled == false tags: - post-install - certificates + - skip_ansible_lint ########################################################################################### # Configure authentication diff --git a/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-entitlement.yml b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-entitlement.yml index 38ac5b6a..712ebca8 100644 --- a/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-entitlement.yml +++ b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-entitlement.yml @@ -1,17 +1,17 @@ --- - - name: RHEL Subscription - redhat_subscription: - state: present - activationkey: "{{ redhat_subscription_activationkey }}" - org_id: "{{ redhat_subscription_org_id }}" - pool: "{{ redhat_subscription_pool }}" +- name: RHEL Subscription + redhat_subscription: + state: present + activationkey: "{{ redhat_subscription_activationkey }}" + org_id: "{{ redhat_subscription_org_id }}" + pool: "{{ redhat_subscription_pool }}" - - name: Disable all RHSM repositories - rhsm_repository: - name: '*' - state: disabled +- name: Disable all RHSM repositories + rhsm_repository: + name: '*' + state: disabled - - name: Enable repos for RHEL 8 - rhsm_repository: - name: "{{ rhsm_repository }}" - state: enabled +- name: Enable repos for RHEL 8 + rhsm_repository: + name: "{{ rhsm_repository }}" + state: enabled diff --git a/ansible/roles/openshift-4-cluster/tasks/start.yml b/ansible/roles/openshift-4-cluster/tasks/start.yml index 6dab142f..f4e87523 100644 --- a/ansible/roles/openshift-4-cluster/tasks/start.yml +++ b/ansible/roles/openshift-4-cluster/tasks/start.yml @@ -4,12 +4,14 @@ # vars: # vm_instance_name: "{{ cluster_name }}-bootstrap" -- ansible.builtin.include_tasks: start-vm.yml +- name: Start master VM's + ansible.builtin.include_tasks: start-vm.yml vars: vm_instance_name: "{{ cluster_name }}-master-{{ item }}" with_sequence: start=0 end="{{ master_count|int - 1 }}" stride=1 -- ansible.builtin.include_tasks: start-vm.yml +- name: Start compute VM's + ansible.builtin.include_tasks: start-vm.yml vars: vm_instance_name: "{{ cluster_name }}-compute-{{ item }}" with_sequence: start=0 end="{{ compute_count|int - 1 if compute_count|int > 0 else 0 }}" stride=1 diff --git a/ansible/roles/provision-hetzner/defaults/main.yml b/ansible/roles/provision-hetzner/defaults/main.yml index c4e8ce58..00194953 100644 --- a/ansible/roles/provision-hetzner/defaults/main.yml +++ b/ansible/roles/provision-hetzner/defaults/main.yml @@ -11,4 +11,4 @@ hetzner_image_ignore_errors: false hetzner_size_of_libvirt_images: all robot_base: https://robot-ws.your-server.de/ -needs_reprovision: False +needs_reprovision: false diff --git a/ansible/roles/provision-hetzner/handlers/main.yaml b/ansible/roles/provision-hetzner/handlers/main.yaml index ccc279f5..1f74c06e 100644 --- a/ansible/roles/provision-hetzner/handlers/main.yaml +++ b/ansible/roles/provision-hetzner/handlers/main.yaml @@ -4,4 +4,3 @@ name: sshd state: restarted delegate_to: "{{ hetzner_ip }}" - diff --git a/ansible/roles/provision-hetzner/tasks/main.yml b/ansible/roles/provision-hetzner/tasks/main.yml index 15ae9b89..733b48d7 100644 --- a/ansible/roles/provision-hetzner/tasks/main.yml +++ b/ansible/roles/provision-hetzner/tasks/main.yml @@ -7,20 +7,22 @@ - name: Check Boot image and hetzner_force_provisioning ansible.builtin.set_fact: - needs_reprovision: True + needs_reprovision: true when: - host_facts.ansible_facts.ansible_cmdline.BOOT_IMAGE is match("rescue") or (hetzner_force_provisioning is defined and hetzner_force_provisioning ) delegate_to: localhost - name: Provision server include_tasks: provision-server.yml - when: needs_reprovision == True + when: needs_reprovision == true - name: Check ansible_python_interpreter ansible.builtin.shell: "which python3" register: rc ignore_errors: true delegate_to: "{{ hetzner_ip }}" + tags: + - skip_ansible_lint - name: Set ansible_python_interpreter to /usr/libexec/platform-python (RHEL 8) set_fact: diff --git a/ansible/roles/provision-hetzner/tasks/provision-server.yml b/ansible/roles/provision-hetzner/tasks/provision-server.yml index a72411d9..ad505932 100644 --- a/ansible/roles/provision-hetzner/tasks/provision-server.yml +++ b/ansible/roles/provision-hetzner/tasks/provision-server.yml @@ -50,7 +50,9 @@ - name: Filter server by IPv4 or IPv6 delegate_to: localhost set_fact: + # yamllint disable rule:line-length server_id_list: "{{ servers.json | to_json | from_json | community.general.json_query('[?cancelled == false && status == \"ready\" && ( server.server_ip == `'~ hetzner_ip ~'` || contains(`'~ hetzner_ip ~'`, server.server_ipv6_net) ) ].server.server_number') }}" + # yamllint enable rule:line-length - name: Check server_id delegate_to: localhost From 9a02e58845652f929df46635e73e280ddb85f22f Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Sat, 26 Nov 2022 22:46:28 +0100 Subject: [PATCH 30/55] Added 00-provision-hetzner.yml to setup.yaml --- ansible/setup.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/ansible/setup.yml b/ansible/setup.yml index 9ba76fc9..9bfdc36d 100644 --- a/ansible/setup.yml +++ b/ansible/setup.yml @@ -1,3 +1,4 @@ --- +- import_playbook: 00-provision-hetzner.yml - import_playbook: 01-prepare-host.yml - import_playbook: 02-create-cluster.yml From 9a9e11872995cd811d58a3d39cd39f918e3f8757 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Mon, 28 Nov 2022 19:09:02 +0100 Subject: [PATCH 31/55] Handling reboot after new kernel is installed --- .../tasks/prepare-host-CentOS-8.yml | 10 +++++++ .../tasks/prepare-host-RedHat-8.yml | 10 +++++++ .../tasks/prepare-host-RedHat-9.yml | 10 +++++++ .../tasks/prepare-host-Rocky-8.yml | 10 +++++++ .../tasks/prepare-host.yml | 30 +++++++++++++++++++ 5 files changed, 70 insertions(+) diff --git a/ansible/roles/openshift-4-cluster/tasks/prepare-host-CentOS-8.yml b/ansible/roles/openshift-4-cluster/tasks/prepare-host-CentOS-8.yml index f8a3944a..fad10b9b 100644 --- a/ansible/roles/openshift-4-cluster/tasks/prepare-host-CentOS-8.yml +++ b/ansible/roles/openshift-4-cluster/tasks/prepare-host-CentOS-8.yml @@ -15,6 +15,16 @@ ansible.builtin.package: name: '*' state: latest + register: update + +- name: Check if new kernel has been installed and local execution + ansible.builtin.set_fact: + hetzner_ocp4_prepare_host_reboot_needed: true + when: + - update.changed + - update.results | select('match','Installed:.*kernel.*') | length > 0 + tags: + - skip_ansible_lint - name: Enable & Start firewalld ansible.builtin.service: diff --git a/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-8.yml b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-8.yml index c3cafc0c..1a47506f 100644 --- a/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-8.yml +++ b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-8.yml @@ -26,6 +26,16 @@ ansible.builtin.package: name: '*' state: latest + register: update + +- name: Check if new kernel has been installed and local execution + ansible.builtin.set_fact: + hetzner_ocp4_prepare_host_reboot_needed: true + when: + - update.changed + - update.results | select('match','Installed:.*kernel.*') | length > 0 + tags: + - skip_ansible_lint - name: Allow NFS traffic from VM's to Host ansible.posix.firewalld: diff --git a/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-9.yml b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-9.yml index 6877dbc0..a4e15736 100644 --- a/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-9.yml +++ b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-9.yml @@ -12,6 +12,16 @@ yum: name: '*' state: latest + register: update + +- name: Check if new kernel has been installed and local execution + ansible.builtin.set_fact: + hetzner_ocp4_prepare_host_reboot_needed: true + when: + - update.changed + - update.results | select('match','Installed:.*kernel.*') | length > 0 + tags: + - skip_ansible_lint - name: Allow NFS traffic from VM's to Host firewalld: diff --git a/ansible/roles/openshift-4-cluster/tasks/prepare-host-Rocky-8.yml b/ansible/roles/openshift-4-cluster/tasks/prepare-host-Rocky-8.yml index f8a3944a..fad10b9b 100644 --- a/ansible/roles/openshift-4-cluster/tasks/prepare-host-Rocky-8.yml +++ b/ansible/roles/openshift-4-cluster/tasks/prepare-host-Rocky-8.yml @@ -15,6 +15,16 @@ ansible.builtin.package: name: '*' state: latest + register: update + +- name: Check if new kernel has been installed and local execution + ansible.builtin.set_fact: + hetzner_ocp4_prepare_host_reboot_needed: true + when: + - update.changed + - update.results | select('match','Installed:.*kernel.*') | length > 0 + tags: + - skip_ansible_lint - name: Enable & Start firewalld ansible.builtin.service: diff --git a/ansible/roles/openshift-4-cluster/tasks/prepare-host.yml b/ansible/roles/openshift-4-cluster/tasks/prepare-host.yml index 4b4bbfec..c4cbc15c 100644 --- a/ansible/roles/openshift-4-cluster/tasks/prepare-host.yml +++ b/ansible/roles/openshift-4-cluster/tasks/prepare-host.yml @@ -1,4 +1,8 @@ --- +- name: Initial reboot warning (off) + ansible.builtin.set_fact: + hetzner_ocp4_prepare_host_reboot_needed: false + - name: Include OS specific part ansible.builtin.include_tasks: "prepare-host-{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml" @@ -38,3 +42,29 @@ insertafter: '^\[Network\]' regexp: '^IPForward=' when: stat_result.stat.exists + +- name: Check if new kernel has been installed and local execution + ansible.builtin.fail: + msg: "A new kernel has been installed, please reboot and run the playbook again." + when: + - hetzner_ocp4_prepare_host_reboot_needed + - ansible_host == "localhost" + +- name: Reboot in case of remote execution + block: + - name: Reboot server + ansible.builtin.shell: sync && sleep 2 && shutdown -r now + async: 1 + poll: 0 + changed_when: true + failed_when: false + + - name: Wait for the reboot to complete + ansible.builtin.wait_for_connection: + connect_timeout: 10 + sleep: 5 + delay: 5 + timeout: 300 + when: + - hetzner_ocp4_prepare_host_reboot_needed + - ansible_host != "localhost" From 99528a0da4e8053194277239cd9b2e273a1b59fc Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Mon, 28 Nov 2022 19:53:26 +0100 Subject: [PATCH 32/55] Fixed 00-provision-hetzner.yml --- ansible/00-provision-hetzner.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ansible/00-provision-hetzner.yml b/ansible/00-provision-hetzner.yml index d0b1d419..fb41c2a6 100644 --- a/ansible/00-provision-hetzner.yml +++ b/ansible/00-provision-hetzner.yml @@ -7,6 +7,12 @@ vars_files: - ../cluster.yml tasks: + - name: Check remote execution or 'local' + set_fact: + ansible_host: "{{ hetzner_ip }}" + when: + ansible_host == 'localhost' + - name: provision hetzner root server import_role: name: provision-hetzner From e46ec6e609c88f42623a174961facbe6f93fef42 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Tue, 29 Nov 2022 16:11:46 +0100 Subject: [PATCH 33/55] Added # callbacks_enabled = profile_tasks in ansible.cfg --- ansible.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ansible.cfg b/ansible.cfg index 627086d1..756ca65a 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -9,6 +9,9 @@ # Set the log_path log_path = ~/ansible.log +# Output with time statistics +#callbacks_enabled = profile_tasks + # Additional default options for OpenShift Ansible forks = 20 host_key_checking = False From 991cbd6737a478d6e2197abea281f654ac35f065 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Tue, 29 Nov 2022 16:27:36 +0100 Subject: [PATCH 34/55] Update documentation & release notes --- README.md | 1 + docs/release-notes.md | 12 ++++++++++++ docs/remote-execution.md | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 docs/remote-execution.md diff --git a/README.md b/README.md index c957ee19..47c2d14f 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,7 @@ ansible-navigator run -m stdout ./ansible/setup.yml * [How to passthrough nvme or gpu (pci-passthrough](docs/pci-passthrough.md) * [How to install OKD](docs/how-to-install-okd.md) * [Virsh commands cheatsheet to manage KVM guest virtual machines](https://computingforgeeks.com/virsh-commands-cheatsheet/) +* [Remote execution, run the playbooks on your laptop](docs/remote-execution.md) # Useful commands diff --git a/docs/release-notes.md b/docs/release-notes.md index d5ee18f3..1a8f4852 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,5 +1,17 @@ # RELEASE NOTES +## 2022-xx-xx + + * Fixed problem with `ansible_python_interpreter` during `00-provision-hetzner.yml` + * Added new option `hetzner_size_of_libvirt_images` + * Added new option `redhat_subscription_activationkey`, `redhat_subscription_org_id`, `redhat_subscription_pool` to handle Red Hat entitlement during `01-prepare-host.yml` + * Introduce `artifacts_dir` + * Change ssh public key and kubeconfig handling to support remote execution + * Handling reboot after new kernel is installed + * [Added support for remote execution (execute playbooks on your laptop)](remote-execution.md) + + + ## 2022-06-19 * Bump OpenShift Version to 4.10 diff --git a/docs/remote-execution.md b/docs/remote-execution.md new file mode 100644 index 00000000..8bbde4eb --- /dev/null +++ b/docs/remote-execution.md @@ -0,0 +1,37 @@ +# Remote execution + +In case you want to execute the playbooks on your laptop and install OpenShift at your Hetzner Server. Like this: + +``` + ┌───────────────────────────────────┐ + │ │ + │ Hetzner Server ▲ │ + │ │ │ + └────────────────────────────────┼──┘ + │ + SSH + │ + ┌────────────────────────────────┬───┐ + │Local Workstation/Laptop │ │ + │ │ │ + │ ┌──────────────────────────────┼─┐ │ + │ │ Ansible runner (Podman) │ │ │ + │ │ │ │ + │ │ │ │ + │ └────────────────────────────────┘ │ + │ │ + └────────────────────────────────────┘ +``` + + +Just edit `inventory/hosts.yaml` and change `ansible_host` to your Hetzner Server. And strongly recommended to add `artifacts_dir` for exmaple `/root/hetzner-ocp4/` where the artifacts (certifcates, kubeconf) is stored during the installation. + +One example: +```yaml +--- +all: + hosts: + host: + ansible_host: tester.openshift.pub + artifacts_dir: /root/hetzner-ocp4/ +``` From c23f35d3933692652f433ba2433339f26af2d48f Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Wed, 30 Nov 2022 14:21:00 +0100 Subject: [PATCH 35/55] Added missing release not entry (ocp version 4.11.12) --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 1a8f4852..378a2917 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,7 @@ ## 2022-xx-xx + * Bump openshift version to 4.11.12 * Fixed problem with `ansible_python_interpreter` during `00-provision-hetzner.yml` * Added new option `hetzner_size_of_libvirt_images` * Added new option `redhat_subscription_activationkey`, `redhat_subscription_org_id`, `redhat_subscription_pool` to handle Red Hat entitlement during `01-prepare-host.yml` From 95dfc56cc4ef914489f2966db825aec34599f78c Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Wed, 30 Nov 2022 14:22:32 +0100 Subject: [PATCH 36/55] Update ansible-automation-platform to 2.3 --- README.md | 2 +- .../roles/openshift-4-cluster/tasks/prepare-host-RedHat-8.yml | 2 +- docs/release-notes.md | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 47c2d14f..2fdad30c 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ subscription-manager repos \ --enable=rhel-8-for-x86_64-baseos-rpms \ --enable=rhel-8-for-x86_64-appstream-rpms \ --enable=rhel-8-for-x86_64-highavailability-rpms \ - --enable=ansible-automation-platform-2.2-for-rhel-8-x86_64-rpms + --enable=ansible-automation-platform-2.3-for-rhel-8-x86_64-rpms dnf install -y ansible-navigator git podman diff --git a/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-8.yml b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-8.yml index 1a47506f..a65ef4cb 100644 --- a/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-8.yml +++ b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-8.yml @@ -7,7 +7,7 @@ - rhel-8-for-x86_64-baseos-rpms - rhel-8-for-x86_64-appstream-rpms - rhel-8-for-x86_64-highavailability-rpms - - ansible-automation-platform-2.1-for-rhel-8-x86_64-rpms + - ansible-automation-platform-2.3-for-rhel-8-x86_64-rpms when: - redhat_subscription_activationkey is defined - redhat_subscription_org_id is defined diff --git a/docs/release-notes.md b/docs/release-notes.md index 378a2917..fb6f82cf 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -3,6 +3,7 @@ ## 2022-xx-xx * Bump openshift version to 4.11.12 + * Update ansible-automation-platform to 2.3 * Fixed problem with `ansible_python_interpreter` during `00-provision-hetzner.yml` * Added new option `hetzner_size_of_libvirt_images` * Added new option `redhat_subscription_activationkey`, `redhat_subscription_org_id`, `redhat_subscription_pool` to handle Red Hat entitlement during `01-prepare-host.yml` From 8f0987f8b96611b0260494d647b37be65ac556c8 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Wed, 30 Nov 2022 15:36:55 +0100 Subject: [PATCH 37/55] Added `install_config_capabilities` configuration --- README.md | 1 + .../openshift-4-cluster/templates/install-config.yaml.j2 | 4 ++++ docs/release-notes.md | 1 + 3 files changed, 6 insertions(+) diff --git a/README.md b/README.md index 2fdad30c..7470fbaf 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,7 @@ Please configure in `cluster.yml` all necessary credentials: |`letsencrypt_disabled`|`false`|This allows you to disable letsencrypt setup. (Default is enabled letsencrypt.) |`sdn_plugin_name`|`OVNKubernetes`|This allows you to change SDN plugin. Valid values are OpenShiftSDN and OVNKubernetes. (Default is OVNKubernetes.) |`masters_schedulable`|true|Set to false if don't want to allow workload onto the master nodes. (Default is to allow this)| +|`install_config_capabilities`|null|Configure [Cluster capabilities](https://docs.openshift.com/container-platform/latest/post_installation_configuration/cluster-capabilities.html) ## Prepare kvm-host and install OpenShift diff --git a/ansible/roles/openshift-4-cluster/templates/install-config.yaml.j2 b/ansible/roles/openshift-4-cluster/templates/install-config.yaml.j2 index 6e974227..3543cf0d 100644 --- a/ansible/roles/openshift-4-cluster/templates/install-config.yaml.j2 +++ b/ansible/roles/openshift-4-cluster/templates/install-config.yaml.j2 @@ -53,3 +53,7 @@ imageContentSources: proxy: {{ install_config_proxy | to_nice_yaml(indent=2) | indent(width=2) }} {% endif %} +{%- if install_config_capabilities is defined %} +capabilities: + {{ install_config_capabilities | to_nice_yaml(indent=2) | indent(width=2) }} +{% endif %} \ No newline at end of file diff --git a/docs/release-notes.md b/docs/release-notes.md index fb6f82cf..2483797f 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -11,6 +11,7 @@ * Change ssh public key and kubeconfig handling to support remote execution * Handling reboot after new kernel is installed * [Added support for remote execution (execute playbooks on your laptop)](remote-execution.md) + * Added `install_config_capabilities` configuration From 8af050eeaac8c3a6b3dcc3b146927767aeb99c44 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Fri, 2 Dec 2022 20:58:36 +0100 Subject: [PATCH 38/55] Added some playbook documentation - related to #215 --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 7470fbaf..437ff6bb 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,21 @@ ansible-navigator run -m stdout ./ansible/setup.yml * [Virsh commands cheatsheet to manage KVM guest virtual machines](https://computingforgeeks.com/virsh-commands-cheatsheet/) * [Remote execution, run the playbooks on your laptop](docs/remote-execution.md) + +# Playbook overview + +| Playbook | Description | +|---|---| +|`ansible/00-provision-hetzner.yml`|Automated operating system of your Hetzner bare-metal server. detail documentation: [docs/hetzner.md](docs/hetzner.md)| +|`ansible/01-prepare-host.yml`|Install all dependencies like kvm & co on your Hetzner bare-metal server.| +|`ansible/02-create-cluster.yml`|Installation of your OpenShift 4 Cluster| +|`ansible/03-stop-cluster.yml`|Stop all virtual machines related to your OpenShift 4 Cluster| +|`ansible/04-start-cluster.yml`|Start all virtual machines related to your OpenShift 4 Cluster| +|`ansible/99-destroy-cluster.yml`|Delete everything what is created via `ansible/02-create-cluster.yml`| +|`ansible/renewal-certificate.yml`|Renewal your Let's encrypt certificate and replace everything in your OpenShift 4 Cluster. There is no automatically renew process, please run renew on your own behalf.| +|`ansible/run-add-ons.yml`|Run all enabled add-ons agains your OpenShift 4 cluster| +|`ansible/setup.yml`|One shot cluster installation, including operating system installation and configuration of your Hetzner bare-metal server.| + # Useful commands | Problem | Command | From 5cd56194173b8e1ece8e753f5608ce1d9deb4cb4 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Mon, 12 Dec 2022 09:04:02 +0100 Subject: [PATCH 39/55] Added linter via .tekton / pipeline as code --- .tekton/images/pre-commit.Containerfile | 6 ++ .tekton/pre-commit.yaml | 93 +++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 .tekton/images/pre-commit.Containerfile create mode 100644 .tekton/pre-commit.yaml diff --git a/.tekton/images/pre-commit.Containerfile b/.tekton/images/pre-commit.Containerfile new file mode 100644 index 00000000..549a07d0 --- /dev/null +++ b/.tekton/images/pre-commit.Containerfile @@ -0,0 +1,6 @@ +FROM registry.access.redhat.com/ubi9/python-39:latest + +RUN pip install pre-commit + +WORKDIR /workdir +CMD pre-commit run --all-files diff --git a/.tekton/pre-commit.yaml b/.tekton/pre-commit.yaml new file mode 100644 index 00000000..4faa4d53 --- /dev/null +++ b/.tekton/pre-commit.yaml @@ -0,0 +1,93 @@ +--- +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + name: linter + annotations: + # The event we are targeting as seen from the webhook payload + # this can be an array too, i.e: [pull_request, push] + pipelinesascode.tekton.dev/on-event: "[pull_request]" + + # The branch or tag we are targeting (ie: main, refs/tags/*) + pipelinesascode.tekton.dev/on-target-branch: "[master,devel]" + + # Fetch the git-clone task from hub, we are able to reference later on it + # with taskRef and it will automatically be embedded into our pipeline. + pipelinesascode.tekton.dev/task: "git-clone" + + # Use maven task from hub + # pipelinesascode.tekton.dev/task-1: "[pre-commit]" + + # You can add more tasks in here to reuse, browse the one you like from here + # https://hub.tekton.dev/ + # example: + # pipelinesascode.tekton.dev/task-2: "[github-add-labels]" + # pipelinesascode.tekton.dev/task-2: "[.tekton/task/github-add-labels.yaml]" + + # How many runs we want to keep attached to this event + pipelinesascode.tekton.dev/max-keep-runs: "3" +spec: + params: + # The variable with brackets are special to Pipelines as Code + # They will automatically be expanded with the events from Github. + - name: repo_url + value: "{{ repo_url }}" + - name: revision + value: "{{ revision }}" + - name: pull_request_number + value: "{{ pull_request_number }}" + pipelineSpec: + params: + - name: repo_url + - name: revision + - name: pull_request_number + workspaces: + - name: source + - name: basic-auth + tasks: + - name: fetch-repository + taskRef: + name: git-clone + kind: ClusterTask + workspaces: + - name: output + workspace: source + - name: basic-auth + workspace: basic-auth + params: + - name: url + value: $(params.repo_url) + - name: revision + value: $(params.revision) + # Customize this task if you like, or just do a taskRef + # to one of the hub task. + - name: pre-commit + runAfter: + - fetch-repository + workspaces: + - name: source + workspace: source + taskSpec: + workspaces: + - name: source + steps: + - name: pre-commit + image: quay.io/redhat-emea-ssa-team/hetzner-ocp4-pre-commit:latest + workingDir: $(workspaces.source.path) + script: | + pre-commit run --color=never --all-files + + workspaces: + - name: source + volumeClaimTemplate: + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + # This workspace will inject secret to help the git-clone task to be able to + # checkout the private repositories + - name: basic-auth + secret: + secretName: "{{ git_auth_secret }}" From 3c2215c0aa5bc2309f85ac394fbcb4ddd5d01287 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Mon, 12 Dec 2022 09:08:28 +0100 Subject: [PATCH 40/55] Revert fix to test pipeline failure --- ansible-navigator.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible-navigator.yaml b/ansible-navigator.yaml index aec9f8ab..7e6568c0 100644 --- a/ansible-navigator.yaml +++ b/ansible-navigator.yaml @@ -2,7 +2,7 @@ ansible-navigator: execution-environment: container-options: - - --net=host + - --net=host image: quay.io/redhat-emea-ssa-team/hetzner-ocp4-ansible-ee:master logging: level: info From c6428c0172e39f6f69ddcbd8d170bcb1489506cb Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Mon, 12 Dec 2022 09:25:56 +0100 Subject: [PATCH 41/55] Add .tekton/tasks/github-add-comment.yaml --- .tekton/tasks/github-add-comment.yaml | 195 ++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 .tekton/tasks/github-add-comment.yaml diff --git a/.tekton/tasks/github-add-comment.yaml b/.tekton/tasks/github-add-comment.yaml new file mode 100644 index 00000000..82683d8d --- /dev/null +++ b/.tekton/tasks/github-add-comment.yaml @@ -0,0 +1,195 @@ +--- +apiVersion: tekton.dev/v1beta1 +kind: Task +metadata: + name: github-add-comment + labels: + app.kubernetes.io/version: "0.7" + annotations: + tekton.dev/categories: Git + tekton.dev/pipelines.minVersion: "0.17.0" + tekton.dev/tags: github + tekton.dev/displayName: "add github comment" + tekton.dev/platforms: "linux/amd64,linux/s390x,linux/ppc64le" +spec: + description: >- + This Task will add a comment to a pull request or an issue. + + It can take either a filename or a comment as input and can + post the comment back to GitHub accordingly. + + workspaces: + - name: comment-file + optional: true + description: The optional workspace containing comment file to be posted. + + results: + - name: OLD_COMMENT + description: The old text of the comment, if any. + + - name: NEW_COMMENT + description: The new text of the comment, if any. + + params: + - name: GITHUB_HOST_URL + description: | + The GitHub host, adjust this if you run a GitHub enteprise. + default: "api.github.com" + type: string + + - name: API_PATH_PREFIX + description: | + The API path prefix, GitHub Enterprise has a prefix e.g. /api/v3 + default: "" + type: string + + - name: REQUEST_URL + description: | + The GitHub issue or pull request URL where we want to add a new + comment. + type: string + + - name: COMMENT_OR_FILE + description: | + The actual comment to add or the filename containing comment to post. + type: string + + - name: GITHUB_TOKEN_SECRET_NAME + description: | + The name of the Kubernetes Secret that contains the GitHub token. + type: string + default: github + + - name: GITHUB_TOKEN_SECRET_KEY + description: | + The key within the Kubernetes Secret that contains the GitHub token. + type: string + default: token + + - name: AUTH_TYPE + description: | + The type of authentication to use. You could use the less secure "Basic" for example + type: string + default: Bearer + + - name: COMMENT_TAG + description: | + An invisible tag to be added into the comment. The tag is made + invisible by embedding in an an HTML comment. The tag allows for later + retrieval of the comment, and it allows replacing an existing comment. + type: string + default: "" + + - name: REPLACE + description: | + When a tag is specified, and `REPLACE` is `true`, look for a comment + with a matching tag and replace it with the new comment. + type: string + default: "false" # Alternative value: "true" + + steps: + - name: post-comment + workingDir: $(workspaces.comment-file.path) + env: + - name: GITHUBTOKEN + valueFrom: + secretKeyRef: + name: $(params.GITHUB_TOKEN_SECRET_NAME) + key: $(params.GITHUB_TOKEN_SECRET_KEY) + + image: registry.access.redhat.com/ubi8/ubi-minimal:8.2 + script: | + #!/usr/libexec/platform-python + import json + import os + import http.client + import sys + import urllib.parse + + authHeader = "$(params.AUTH_TYPE) " + os.environ["GITHUBTOKEN"] + + split_url = urllib.parse.urlparse( + "$(params.REQUEST_URL)").path.split("/") + + # This will convert https://github.com/foo/bar/pull/202 to + # api url path /repos/foo/issues/ + api_url = "{base}/repos/{package}/issues/{id}".format( + base="$(params.API_PATH_PREFIX)", package="/".join(split_url[1:3]), id=split_url[-1]) + + commentParamValue = """$(params.COMMENT_OR_FILE)""" + + # check if workspace is bound and parameter passed is a filename or not + if "$(workspaces.comment-file.bound)" == "true" and os.path.exists(commentParamValue): + commentParamValue = open(commentParamValue, "r").read() + + # If a tag was specified, append it to the comment + if "$(params.COMMENT_TAG)": + commentParamValue += "".format(tag="$(params.COMMENT_TAG)") + + data = { + "body": commentParamValue, + } + + # This is for our fake github server + if "$(params.GITHUB_HOST_URL)".startswith("http://"): + conn = http.client.HTTPConnection("$(params.GITHUB_HOST_URL)".replace("http://", "")) + else: + conn = http.client.HTTPSConnection("$(params.GITHUB_HOST_URL)") + + # If REPLACE is true, we need to search for comments first + matching_comment = "" + if "$(params.REPLACE)" == "true": + if not "$(params.COMMENT_TAG)": + print("REPLACE requested but no COMMENT_TAG specified") + sys.exit(1) + r = conn.request( + "GET", + api_url + "/comments", + headers={ + "User-Agent": "TektonCD, the peaceful cat", + "Authorization": authHeader, + }) + + resp = conn.getresponse() + if not str(resp.status).startswith("2"): + print("Error: %d" % (resp.status)) + print(resp.read()) + sys.exit(1) + print(resp.status) + + comments = json.loads(resp.read()) + print(comments) + # If more than one comment is found take the last one + matching_comment = [x for x in comments if '$(params.COMMENT_TAG)' in x['body']][-1:] + if matching_comment: + with open("$(results.OLD_COMMENT.path)", "w") as result_old: + result_old.write(str(matching_comment[0])) + matching_comment = matching_comment[0]['url'] + + if matching_comment: + method = "PATCH" + target_url = urllib.parse.urlparse(matching_comment).path + else: + method = "POST" + target_url = api_url + "/comments" + + print("Sending this data to GitHub with {}: ".format(method)) + print(data) + r = conn.request( + method, + target_url, + body=json.dumps(data), + headers={ + "User-Agent": "TektonCD, the peaceful cat", + "Authorization": authHeader, + }) + resp = conn.getresponse() + if not str(resp.status).startswith("2"): + print("Error: %d" % (resp.status)) + print(resp.read()) + sys.exit(1) + else: + with open("$(results.NEW_COMMENT.path)", "wb") as result_new: + result_new.write(resp.read()) + print("a GitHub comment has been {} to $(params.REQUEST_URL)".format( + "updated" if matching_comment else "added")) From 5ee9055ac15929cf9e323891da69ec0ffa51de99 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Mon, 12 Dec 2022 09:30:32 +0100 Subject: [PATCH 42/55] Change github-add-comment to use PAC secret --- .tekton/tasks/github-add-comment.yaml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.tekton/tasks/github-add-comment.yaml b/.tekton/tasks/github-add-comment.yaml index 82683d8d..fd74f780 100644 --- a/.tekton/tasks/github-add-comment.yaml +++ b/.tekton/tasks/github-add-comment.yaml @@ -54,17 +54,16 @@ spec: The actual comment to add or the filename containing comment to post. type: string - - name: GITHUB_TOKEN_SECRET_NAME + - name: PAC_GITHUB_SECRET description: | The name of the Kubernetes Secret that contains the GitHub token. type: string - default: github - - name: GITHUB_TOKEN_SECRET_KEY + - name: PAC_GITHUB_SECRET_KEY description: | The key within the Kubernetes Secret that contains the GitHub token. type: string - default: token + default: .git-credentials - name: AUTH_TYPE description: | @@ -91,11 +90,11 @@ spec: - name: post-comment workingDir: $(workspaces.comment-file.path) env: - - name: GITHUBTOKEN + - name: GIT_CREDENTIALS valueFrom: secretKeyRef: - name: $(params.GITHUB_TOKEN_SECRET_NAME) - key: $(params.GITHUB_TOKEN_SECRET_KEY) + name: $(params.PAC_GITHUB_SECRET) + key: $(params.PAC_GITHUB_SECRET_KEY) image: registry.access.redhat.com/ubi8/ubi-minimal:8.2 script: | @@ -106,7 +105,9 @@ spec: import sys import urllib.parse - authHeader = "$(params.AUTH_TYPE) " + os.environ["GITHUBTOKEN"] + bearer = urllib.parse.urlparse(os.environ["GIT_CREDENTIALS"]) + + authHeader = "$(params.AUTH_TYPE) " + bearer.password split_url = urllib.parse.urlparse( "$(params.REQUEST_URL)").path.split("/") From 146c30fe4dcd3fa6bda8b5adbc5c68fe5a8c4471 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Mon, 12 Dec 2022 09:37:03 +0100 Subject: [PATCH 43/55] Added notify-linter-on-failure --- .tekton/pre-commit.yaml | 45 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/.tekton/pre-commit.yaml b/.tekton/pre-commit.yaml index 4faa4d53..dfd3eb36 100644 --- a/.tekton/pre-commit.yaml +++ b/.tekton/pre-commit.yaml @@ -15,6 +15,7 @@ metadata: # with taskRef and it will automatically be embedded into our pipeline. pipelinesascode.tekton.dev/task: "git-clone" + # Use maven task from hub # pipelinesascode.tekton.dev/task-1: "[pre-commit]" @@ -22,7 +23,7 @@ metadata: # https://hub.tekton.dev/ # example: # pipelinesascode.tekton.dev/task-2: "[github-add-labels]" - # pipelinesascode.tekton.dev/task-2: "[.tekton/task/github-add-labels.yaml]" + pipelinesascode.tekton.dev/task-2: "[.tekton/tasks/github-add-comment.yaml]" # How many runs we want to keep attached to this event pipelinesascode.tekton.dev/max-keep-runs: "3" @@ -36,11 +37,14 @@ spec: value: "{{ revision }}" - name: pull_request_number value: "{{ pull_request_number }}" + - name: git_auth_secret + value: "{{ git_auth_secret }}" pipelineSpec: params: - name: repo_url - name: revision - name: pull_request_number + - name: git_auth_secret workspaces: - name: source - name: basic-auth @@ -68,6 +72,9 @@ spec: - name: source workspace: source taskSpec: + results: + - name: linter-output + description: Output of pre-commit run workspaces: - name: source steps: @@ -75,7 +82,41 @@ spec: image: quay.io/redhat-emea-ssa-team/hetzner-ocp4-pre-commit:latest workingDir: $(workspaces.source.path) script: | - pre-commit run --color=never --all-files + set -euxo pipefail + + echo -e " 💀 There was an error during pre-commit / linter:\n\n```" \ + > $(workspaces.source.path)/notify-linter-on-failure.txt + + pre-commit run --color=never --all-files \ + | sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2};?)?)?[mGK]//g" \ + | tee -a $(workspaces.source.path)/notify-linter-on-failure.txt + + RC=$? + echo "Return code $RC" + + echo -e '\n```\n' \ + > $(workspaces.source.path)/notify-linter-on-failure.txt + + exit $? + + finally: + - name: notify-linter-on-failure + workspaces: + - name: comment-file + workspace: source + when: + - input: $(tasks.pre-commit.status) + operator: in + values: ["Failed"] + params: + - name: REQUEST_URL + value: "$(params.repo_url)/pull/$(params.pull_request_number)" + - name: PAC_GITHUB_SECRET + value: "$(params.git_auth_secret)" + - name: COMMENT_OR_FILE + value: "notify-linter-on-failure.txt" + taskRef: + name: github-add-comment workspaces: - name: source From 1899be32bbf963910de9489bc8219623175292fa Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Mon, 12 Dec 2022 12:42:29 +0100 Subject: [PATCH 44/55] Task github-add-comment only supports file and added better error handling --- .tekton/tasks/github-add-comment.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.tekton/tasks/github-add-comment.yaml b/.tekton/tasks/github-add-comment.yaml index fd74f780..8330f43a 100644 --- a/.tekton/tasks/github-add-comment.yaml +++ b/.tekton/tasks/github-add-comment.yaml @@ -117,12 +117,16 @@ spec: api_url = "{base}/repos/{package}/issues/{id}".format( base="$(params.API_PATH_PREFIX)", package="/".join(split_url[1:3]), id=split_url[-1]) + # Only support FILE on my case commentParamValue = """$(params.COMMENT_OR_FILE)""" # check if workspace is bound and parameter passed is a filename or not if "$(workspaces.comment-file.bound)" == "true" and os.path.exists(commentParamValue): commentParamValue = open(commentParamValue, "r").read() + else: + commentParamValue = """ 😱 An unexpected error has occurred, please check log files.""" + # If a tag was specified, append it to the comment if "$(params.COMMENT_TAG)": commentParamValue += "".format(tag="$(params.COMMENT_TAG)") From 1934bcf1322a667e1e8fad7a1b6b15d5a4b5ff7a Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Mon, 12 Dec 2022 12:56:47 +0100 Subject: [PATCH 45/55] Fixed shell script in pre-commit.yaml --- .tekton/pre-commit.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tekton/pre-commit.yaml b/.tekton/pre-commit.yaml index dfd3eb36..8bbf02c0 100644 --- a/.tekton/pre-commit.yaml +++ b/.tekton/pre-commit.yaml @@ -84,7 +84,7 @@ spec: script: | set -euxo pipefail - echo -e " 💀 There was an error during pre-commit / linter:\n\n```" \ + echo -e ' 💀 There was an error during pre-commit / linter:\n\n```' \ > $(workspaces.source.path)/notify-linter-on-failure.txt pre-commit run --color=never --all-files \ From f2c054794690eeb58be44bc7bc53099c63d7127e Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Mon, 12 Dec 2022 12:58:48 +0100 Subject: [PATCH 46/55] Fixed ansible-lint/yaml-lint --- ansible-navigator.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible-navigator.yaml b/ansible-navigator.yaml index 7e6568c0..aec9f8ab 100644 --- a/ansible-navigator.yaml +++ b/ansible-navigator.yaml @@ -2,7 +2,7 @@ ansible-navigator: execution-environment: container-options: - - --net=host + - --net=host image: quay.io/redhat-emea-ssa-team/hetzner-ocp4-ansible-ee:master logging: level: info From 3a19f0412a41604d86e899141e7562b3443ab12b Mon Sep 17 00:00:00 2001 From: Mathieu Debove Date: Wed, 7 Dec 2022 15:08:25 +0100 Subject: [PATCH 47/55] Add Gandi DNS provider --- README.md | 3 +- ansible/roles/letsencrypt/README.md | 2 ++ .../letsencrypt/tasks/check-variables.yml | 19 +++++++++++ ansible/roles/letsencrypt/tasks/main.yml | 27 +++++++++++++++ .../tasks/certificate-renewal.yml | 3 ++ .../tasks/create-network.yml | 2 ++ .../openshift-4-cluster/tasks/create.yml | 2 ++ .../tasks/destroy-network.yml | 2 ++ .../roles/public_dns/tasks/create-gandi.yml | 34 +++++++++++++++++++ .../roles/public_dns/tasks/destroy-gandi.yml | 28 +++++++++++++++ cluster-example.yml | 3 ++ docs/release-notes.md | 2 +- pipeline/README.md | 10 ++++++ 13 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 ansible/roles/public_dns/tasks/create-gandi.yml create mode 100644 ansible/roles/public_dns/tasks/destroy-gandi.yml diff --git a/README.md b/README.md index 437ff6bb..008d31c8 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ masters_schedulable: false ### Setup public DNS records Current tools allow use of three DNS providers: _AWS Route53_, _Cloudflare_, _DigitalOcean_, _GCP DNS_ or _none_. -If you want to use _Route53_, _Cloudflare_, _DigitalOcean_ or _GCP_ as your DNS provider, you have to add a few variables. Check the instructions below. +If you want to use _Route53_, _Cloudflare_, _DigitalOcean_, _GCP_ or _Gandi_ as your DNS provider, you have to add a few variables. Check the instructions below. DNS records are constructed based on _cluster_name_ and _public_domain_ values. With above values DNS records should be - api._cluster_name_._public_domain_ @@ -183,6 +183,7 @@ Please configure in `cluster.yml` all necessary credentials: |Azure|`azure_client_id: 'client_id'`
`azure_secret: 'key'`
`azure_subscription_id: 'subscription_id'`
`azure_tenant: 'tenant_id'`
`azure_resource_group: 'dns_zone_resource_group'` | |CloudFlare|`cloudflare_account_email: john@example.com`
Use the global api key here! (API-Token is not supported!) (Details in #86)
`cloudflare_account_api_token: 9348234sdsd894.....`
`cloudflare_zone: domain.tld`| |DigitalOcean|`digitalocean_token: e7a6f82c3245b65cf4.....`
`digitalocean_zone: domain.tld`| +|Gandi|`gandi_account_api_token: 0123456...`
`gandi_zone: domain.tld`| |GCP|`gcp_project: project-name `
`gcp_managed_zone_name: 'zone-name'`
`gcp_managed_zone_domain: 'example.com.'`
`gcp_serviceaccount_file: ../gcp_service_account.json` | |Hetzner|`hetzner_account_api_token: 93543ade82AA$73.....`
`hetzner_zone: domain.tld`| |Route53 / AWS|`aws_access_key: key`
`aws_secret_key: secret`
`aws_zone: domain.tld`
| diff --git a/ansible/roles/letsencrypt/README.md b/ansible/roles/letsencrypt/README.md index 715556bb..453ce2b8 100644 --- a/ansible/roles/letsencrypt/README.md +++ b/ansible/roles/letsencrypt/README.md @@ -27,6 +27,8 @@ Role Variables | le_gcp_managed_zone_domain | GCP DNS managed zone domain || non **required if provider is gcp** | | le_hetzner_account_api_token | Hetzner API token for API authentication | `jdu...zalU`| non **required if provider is hetzner** | | le_hetzner_zone | Hetzner zone in which the entries are created and deleted for the dns challenge | `domain.tld` | non **required if provider is hetzner** | +| le_gandi_api_key | Gandi API key for API authentication || non **required if provider is gandi** | +| le_gandi_zone | Gandi zone in which the entries are created and delete for the DNS challenge | `domain.tld` | non **required if provider is gandi** | | le_public_domain | Use to create SAN certificate: `DNS:*.apps.{{ le_public_domain }},DNS:api.{{ le_public_domain }}` | cluster.domain.tld | non **required** | | le_certificates_dir | Here the certificates are stored | `/root/certificates` | `{{ playbook_dir }}../certificate/` | | le_acme_directory | ACME Directory by default it use staging env because of https://letsencrypt.org/docs/rate-limits/ | `https://acme-v02.api.letsencrypt.org/directory` | `https://acme-staging-v02.api.letsencrypt.org/directory` | diff --git a/ansible/roles/letsencrypt/tasks/check-variables.yml b/ansible/roles/letsencrypt/tasks/check-variables.yml index 1a138477..68a2e83a 100644 --- a/ansible/roles/letsencrypt/tasks/check-variables.yml +++ b/ansible/roles/letsencrypt/tasks/check-variables.yml @@ -65,6 +65,16 @@ - le_hetzner_zone when: le_dns_provider == "hetzner" +- name: Check required Gandi variables + ansible.builtin.assert: + that: + - lookup('vars',item) is defined + msg: "{{ item }} is not defined!" + with_items: + - le_gandi_api_key + - le_gandi_zone + when: le_dns_provider == "gandi" + - name: Debug var only with -vv CloudFlare ansible.builtin.debug: msg: "{{ item }}={{ lookup('vars',item) }}" @@ -133,3 +143,12 @@ - le_hetzner_account_api_token - le_hetzner_zone when: le_dns_provider == "hetzner" + +- name: Debug var only with -vv for Gandi + ansible.builtin.debug: + msg: "{{ item }}={{ lookup('vars',item) }}" + verbosity: 2 + with_items: + - le_gandi_api_key + - le_gandi_zone + when: le_dns_provider == "gandi" diff --git a/ansible/roles/letsencrypt/tasks/main.yml b/ansible/roles/letsencrypt/tasks/main.yml index 6815337d..8645afe6 100644 --- a/ansible/roles/letsencrypt/tasks/main.yml +++ b/ansible/roles/letsencrypt/tasks/main.yml @@ -143,6 +143,21 @@ loop: "{{ challenge_data_dns | default({}) | dict2items | subelements('value') }}" when: le_dns_provider == "transip" and sample_com_challenge is changed +- name: Create DNS record at Gandi + delegate_to: localhost + community.general.gandi_livedns: + state: present + domain: "{{ le_gandi_zone }}" + record: "{{ item.0.key | replace(public_domain, '') | regex_replace('\\.$', '') }}" + type: TXT + values: + - "{{ item.1 }}" + ttl: 300 + api_key: "{{ le_gandi_api_key }}" + register: record + loop: "{{ challenge_data_dns | default({}) | dict2items | subelements('value') }}" + when: le_dns_provider == "gandi" and sample_com_challenge is changed + - name: DNS record info ansible.builtin.debug: # noqa no-handler msg: "{{ item.0.key }} TXT {{ item.1 }}" @@ -260,6 +275,18 @@ loop: "{{ challenge_data_dns | default({}) | dict2items | subelements('value') }}" when: le_dns_provider == "transip" and sample_com_challenge is changed +- name: Delete DNS record at Gandi + delegate_to: localhost + community.general.gandi_livedns: + state: absent + domain: "{{ le_gandi_zone }}" + record: "{{ item.0.key | replace(public_domain, '') | regex_replace('\\.$', '') }}" + type: TXT + api_key: "{{ le_gandi_api_key }}" + register: record + loop: "{{ challenge_data_dns | default({}) | dict2items | subelements('value') }}" + when: le_dns_provider == "gandi" and sample_com_challenge is changed + - name: Include DNS provider ansible.builtin.include_tasks: "destroy-{{ le_dns_provider }}.yml" when: diff --git a/ansible/roles/openshift-4-cluster/tasks/certificate-renewal.yml b/ansible/roles/openshift-4-cluster/tasks/certificate-renewal.yml index 4d9f4953..0545d77a 100644 --- a/ansible/roles/openshift-4-cluster/tasks/certificate-renewal.yml +++ b/ansible/roles/openshift-4-cluster/tasks/certificate-renewal.yml @@ -28,5 +28,8 @@ le_hetzner_account_api_token: "{{ hetzner_account_api_token }}" le_hetzner_zone: "{{ hetzner_zone }}" + + le_gandi_api_key: "{{ gandi_api_key }}" + le_gandi_zone: "{{ gandi_zone }}" tags: letsencrypt when: not letsencrypt_disabled diff --git a/ansible/roles/openshift-4-cluster/tasks/create-network.yml b/ansible/roles/openshift-4-cluster/tasks/create-network.yml index a4a61d47..c09c5829 100644 --- a/ansible/roles/openshift-4-cluster/tasks/create-network.yml +++ b/ansible/roles/openshift-4-cluster/tasks/create-network.yml @@ -151,6 +151,8 @@ pd_aws_zone: "{{ aws_zone }}" pd_hetzner_account_api_token: "{{ hetzner_account_api_token }}" pd_hetzner_zone: "{{ hetzner_zone }}" + pd_gandi_api_key: "{{ gandi_api_key }}" + pd_gandi_zone: "{{ gandi_zone }}" pd_public_domain: "{{ cluster_name }}.{{ public_domain }}" tags: public_dns when: dns_provider != 'none' diff --git a/ansible/roles/openshift-4-cluster/tasks/create.yml b/ansible/roles/openshift-4-cluster/tasks/create.yml index da02f694..8e58d205 100644 --- a/ansible/roles/openshift-4-cluster/tasks/create.yml +++ b/ansible/roles/openshift-4-cluster/tasks/create.yml @@ -70,6 +70,8 @@ le_digitalocean_token: "{{ digitalocean_token }}" le_digitalocean_zone: "{{ digitalocean_zone }}" + le_gandi_api_key: "{{ gandi_api_key }}" + le_gandi_zone: "{{ gandi_zone }}" tags: letsencrypt when: not letsencrypt_disabled diff --git a/ansible/roles/openshift-4-cluster/tasks/destroy-network.yml b/ansible/roles/openshift-4-cluster/tasks/destroy-network.yml index 6f321ac5..b3b6c42c 100644 --- a/ansible/roles/openshift-4-cluster/tasks/destroy-network.yml +++ b/ansible/roles/openshift-4-cluster/tasks/destroy-network.yml @@ -15,6 +15,8 @@ pd_aws_zone: "{{ aws_zone }}" pd_hetzner_account_api_token: "{{ hetzner_account_api_token }}" pd_hetzner_zone: "{{ hetzner_zone }}" + pd_gandi_api_key: "{{ gandi_api_key }}" + pd_gandi_zone: "{{ gandi_zone }}" pd_public_domain: "{{ cluster_name }}.{{ public_domain }}" tags: public_dns when: dns_provider != 'none' diff --git a/ansible/roles/public_dns/tasks/create-gandi.yml b/ansible/roles/public_dns/tasks/create-gandi.yml new file mode 100644 index 00000000..d429df55 --- /dev/null +++ b/ansible/roles/public_dns/tasks/create-gandi.yml @@ -0,0 +1,34 @@ +--- +- name: Create Gandi DNS records + community.general.gandi_livedns: + state: present + domain: "{{ pd_gandi_zone }}" + record: "{{ item }}.{{ pd_public_domain }}" + type: A + values: + - "{{ pd_public_ip }}" + ttl: 300 + api_key: "{{ gandi_api_key }}" + with_items: + - api + - '*.apps' + tags: + - public_dns + when: (pd_public_ip is defined) and (pd_public_ip|length > 0) + +- name: Create IPv6 Gandi DNS records + community.general.gandi_livedns: + state: present + domain: "{{ pd_gandi_zone }}" + record: "{{ item }}.{{ pd_public_domain }}" + type: AAAA + values: + - "{{ pd_public_ipv6 }}" + ttl: 300 + api_key: "{{ gandi_api_key }}" + with_items: + - api + - '*.apps' + tags: + - public_dns + when: (pd_public_ipv6 is defined) and (pd_public_ipv6|length > 0) diff --git a/ansible/roles/public_dns/tasks/destroy-gandi.yml b/ansible/roles/public_dns/tasks/destroy-gandi.yml new file mode 100644 index 00000000..0f969fbe --- /dev/null +++ b/ansible/roles/public_dns/tasks/destroy-gandi.yml @@ -0,0 +1,28 @@ +--- +- name: Destroy Gandi DNS records + community.general.gandi_livedns: + state: absent + domain: "{{ pd_gandi_zone }}" + record: "{{ item }}.{{ pd_public_domain }}" + type: A + api_key: "{{ gandi_api_key }}" + with_items: + - api + - '*.apps' + tags: + - public_dns + when: (pd_public_ip is defined) and (pd_public_ip|length > 0) + +- name: Destroy IPv6 Gandi DNS records + community.general.gandi_livedns: + state: absent + domain: "{{ pd_gandi_zone }}" + record: "{{ item }}.{{ pd_public_domain }}" + type: AAAA + api_key: "{{ gandi_api_key }}" + with_items: + - api + - '*.apps' + tags: + - public_dns + when: (pd_public_ipv6 is defined) and (pd_public_ipv6|length > 0) diff --git a/cluster-example.yml b/cluster-example.yml index 90f2837e..56dc3d8e 100644 --- a/cluster-example.yml +++ b/cluster-example.yml @@ -35,6 +35,9 @@ azure_resource_group: dns_zone_resource_group # Hetzner hetzner_account_api_token: 3543ade82AA$73..... hetzner_zone: example.com +# Gandi +gandi_api_key: api_key +gandi_zone: example.com # Created with: htpasswd -nb admin openshift # Example password is openshift diff --git a/docs/release-notes.md b/docs/release-notes.md index 2483797f..919747a0 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -12,7 +12,7 @@ * Handling reboot after new kernel is installed * [Added support for remote execution (execute playbooks on your laptop)](remote-execution.md) * Added `install_config_capabilities` configuration - + * Added Gandi as a DNS provider ## 2022-06-19 diff --git a/pipeline/README.md b/pipeline/README.md index 99f0d72f..ea6b3715 100644 --- a/pipeline/README.md +++ b/pipeline/README.md @@ -93,6 +93,16 @@ hetzner_account_api_token: xxxxx hetzner_zone: example.com ``` +#### gandi-cluster.yml +```yaml +cluster_name: test +public_domain: gandi.ci.example.com +dns_provider: gandi + +gandi_api_key: xxxxx +gandi_zone: example.com +``` + ## Install pipeline ``` From 3d319fc7d23977998a7d24d36dc5168a4d6b6ef3 Mon Sep 17 00:00:00 2001 From: Steffen Froemer Date: Mon, 12 Dec 2022 00:12:38 +0100 Subject: [PATCH 48/55] fixed broken url --- docs/hetzner.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hetzner.md b/docs/hetzner.md index 46e31a07..22b2cde2 100644 --- a/docs/hetzner.md +++ b/docs/hetzner.md @@ -60,7 +60,7 @@ The guest VM setup uses a "root" vg0 volume group for guest. So leave as much as The `installimage` tool is used to install CentOS. It takes instructions from a text file. If you like to install Red Hat Enterprise Linux, create your own RHEL Image. Follow the instructions of how you create an RHEL image for Hetzner: -* [Red Hat Enterprise Linux 8](https://keithtenzer.com/2019/10/24/how-to-create-a-rhel-8-image-for-hetzner-root-servers/) +* [Red Hat Enterprise Linux 8](https://keithtenzer.com/cloud/how-to-create-a-rhel-8-image-for-hetzner-root-servers/) Create a new `config.txt` file From b00683ee302d40a588a5e7f0b5b9eadc01ff6281 Mon Sep 17 00:00:00 2001 From: Steffen Froemer Date: Mon, 12 Dec 2022 00:55:11 +0100 Subject: [PATCH 49/55] add description on howto create a rhel9 image for hetzner --- README.md | 1 + docs/hetzner.md | 2 +- docs/hetzner_rhel9.md | 167 +++++++++++++++++++++++++++++++++ images/rhel9_disk-layout-1.png | Bin 0 -> 55692 bytes images/rhel9_disk-layout-2.png | Bin 0 -> 83120 bytes images/rhel9_disk-layout-3.png | Bin 0 -> 74483 bytes 6 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 docs/hetzner_rhel9.md create mode 100644 images/rhel9_disk-layout-1.png create mode 100644 images/rhel9_disk-layout-2.png create mode 100644 images/rhel9_disk-layout-3.png diff --git a/README.md b/README.md index 008d31c8..152728ab 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Our instructions are based on the CentOS Root Server as provided by https://www. - CentOS Stream 8 - RHEL 8 - How to install RHEL8: https://keithtenzer.com/cloud/how-to-create-a-rhel-8-image-for-hetzner-root-servers/ - RHEL 9 - leapp update from RHEL 8 +- RHEL 9 ([How to install RHEL9](docs/hetzner_rhel9.md)) ## Infra providers * [Hetzner CentOS](docs/hetzner.md) diff --git a/docs/hetzner.md b/docs/hetzner.md index 22b2cde2..f1c69c6a 100644 --- a/docs/hetzner.md +++ b/docs/hetzner.md @@ -61,7 +61,7 @@ The `installimage` tool is used to install CentOS. It takes instructions from a If you like to install Red Hat Enterprise Linux, create your own RHEL Image. Follow the instructions of how you create an RHEL image for Hetzner: * [Red Hat Enterprise Linux 8](https://keithtenzer.com/cloud/how-to-create-a-rhel-8-image-for-hetzner-root-servers/) - +* [Red Hat Enterprise Linux 9](hetzner_rhel9.md) Create a new `config.txt` file ``` diff --git a/docs/hetzner_rhel9.md b/docs/hetzner_rhel9.md new file mode 100644 index 00000000..d08e19d2 --- /dev/null +++ b/docs/hetzner_rhel9.md @@ -0,0 +1,167 @@ +## Howto install RHEL 9 and use it as Hetzner Base image + +### Download the image and install a RHEL-9-minimal virtual machine + +Download RHEL 9.1 DVD image from [Red Hat Customer Portal](https://access.redhat.com/downloads/content/479/ver=/rhel---9/9.1/x86_64/product-software) + +Create a virtual machine and boot from the downloaded ISO. Once the `Anaconda Installer` is loaded, adjust settings as required. Recommend adjustments are `minimal` Software selection and specific filesystem layout. For all other options, the defaults are sufficient. + + +Adjust the filesystem layout to be more comfortable with automation of this repository. + +Open the disk partitioning dialog and select `Manual Partitioning` and create the filesystem layout choosing the automation proposal and LVM. +![](../images/rhel9_disk-layout-1.png) + +Once created, change the volume group for `root` and `swap` logical volume to `vg0` +![](../images/rhel9_disk-layout-2.png) + +When finished, it will look like this. +![](../images/rhel9_disk-layout-3.png) + + +Wait until installation is finished and press reboot. + +### Configure the system to match Hetzner requirements +Once installed and rebooted, login with previously given credentials and adjust to met Hetzner requirements accordingly. + +#### Install package dependencies and upgrade the OS to latest version. +``` +# dnf install -y lvm2 mdadm tar bzip2 +# dnf upgrade +``` + +#### Disable LVM system.devices +In RHEL 9, `system.devices` became [default](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/pdf/configuring_and_managing_logical_volumes/red_hat_enterprise_linux-9-configuring_and_managing_logical_volumes-en-us.pdf), which is not recommended for the image-based installation for Hetzner. For that, let's disable this. +``` +# rm -f /etc/lvm/devices/system.devices +# sed -i -E 's/\s+# use_devicesfile = 0/ use_devicesfile = 0/' /etc/lvm/lvm.conf +``` + +#### enable autoassembly of special devices +To allow RAID and LVM devices scanned during boot, `rd.auto` needs to be enabled. +``` +# grubby --update-kernel=/boot/vmlinuz-5.14.0-162.6.1.el9_1.x86_64 --args=rd.auto +``` + +#### Create a symlink for dracut +Hetzner creates a ram disk and uses the dracut tool. It expects dracut to be installed under /sbin. This is not the case since RHEL 8 so we will add a symlink. +``` +# ln -s /usr/bin/dracut /sbin/dracut +``` + +#### Cleanup and finish image creation +Remove not required wireless firmware-drivers +``` +# dnf remove iwl* +``` + +Unregister and remove cached files +``` +# subscription-manager unregister +# subscription-manager clean +# dnf clean all +# rm -rf /etc/ssh/ssh_host_* +``` + +Finally clean the history +``` +# history -c +``` + +Now it's time to create the image-archive, which can be uploaded to the rescue-shell +``` +# tar cJvf /CentOS-91-el-x86_64-minimal.tar.xz --exclude=/dev --exclude=/proc --exclude=/sys --exclude=/CentOS-91-el-x86_64-minimal.tar.xz / +``` + + +## Install the image to your server +Boot the Hetzner System into Rescue Shell, create `config.txt` and upload the image to your `/root`-folder for use with `installimage` tool. + +Based on the image creation, which name for volume group was chosen, one need to adjust the `PART lvm vg0` in the `config.txt` file. Below is the example, based on the image created above. + +``` +DRIVE1 /dev/sda +DRIVE2 /dev/sdb +DRIVE3 /dev/sdc +DRIVE4 /dev/sdd + +SWRAID 1 +SWRAIDLEVEL 0 +BOOTLOADER grub +HOSTNAME lab.froemer.net +PART /boot ext4 1024M +PART lvm vg0 500G +PART lvm vg1 all + +LV vg0 root / xfs 40G +LV vg0 swap swap swap 15G +LV vg0 home /home xfs 30G +LV vg0 tmp /tmp xfs 5G +LV vg0 var /var xfs 10G +LV vg0 libvirt /var/lib/libvirt/images xfs all + +LV vg1 storage /data xfs all + +IMAGE /root/CentOS-91-el-x86_64-minimal.tar.xz +``` + + +To install the image, run `installimage` command. +``` +# installimage -a -c config.txt +``` + +The output should be expected to look like the following. +```shell + + Hetzner Online GmbH - installimage + + Your server will be installed now, this will take some minutes + You can abort at any time with CTRL+C ... + + : Reading configuration done + : Loading image file variables done + : Loading centos specific functions done + 1/17 : Deleting partitions done + 2/17 : Test partition size done + 3/17 : Creating partitions and /etc/fstab done + 4/17 : Creating software RAID level 0 done + 5/17 : Creating LVM volumes done + 6/17 : Formatting partitions + : formatting /dev/md/0 with ext4 done + : formatting /dev/vg0/root with xfs done + : formatting /dev/vg0/swap with swap done + : formatting /dev/vg0/home with xfs done + : formatting /dev/vg0/tmp with xfs done + : formatting /dev/vg0/var with xfs done + : formatting /dev/vg0/libvirt with xfs done + : formatting /dev/vg1/storage with xfs done + 7/17 : Mounting partitions done + 8/17 : Sync time via ntp done + : Importing public key for image validation done + 9/17 : Validating image before starting extraction warn + : No detached signature file found! + 10/17 : Extracting image (local) done + 11/17 : Setting up network config done + 12/17 : Executing additional commands + : Setting hostname done + : Generating new SSH keys done + : Generating mdadm config done + : Generating ramdisk done + : Generating ntp config done + 13/17 : Setting up miscellaneous files done + 14/17 : Configuring authentication + : Fetching SSH keys done + : Disabling root password done + : Disabling SSH root login with password done + : Copying SSH keys done + 15/17 : Installing bootloader grub done + 16/17 : Running some centos specific functions done + 17/17 : Clearing log files done + + INSTALLATION COMPLETE + You can now reboot and log in to your new system with the + same credentials that you used to log into the rescue system. +``` + +That's it - perform a `reboot` and have fun. \ No newline at end of file diff --git a/images/rhel9_disk-layout-1.png b/images/rhel9_disk-layout-1.png new file mode 100644 index 0000000000000000000000000000000000000000..0f5057bc8669c2d438d8ab93322841f119dc2280 GIT binary patch literal 55692 zcmc$`byQVd^fr0`Q9%#{q`RcMy96YpQ@R`J4n;s(8dSQwyQMp%J0zsL<6Gy|_r1S6 z#=T?QKkmKX9u7RY_u6aC`OIg|`K%Kp|5gGS5f>2xfgrz;6jOvi;L#xvIL4=s!7IXQ z5}n|`M-C!yl%Imb^QqBi@EphSwYsB{t%;+HzP&NT)W+7@nBKwA-q_g2!OYh20Io#< ze25zQAyIo{eMfU!8)9X1Yh#F#Eip4Qv6!(tF$)t53o#2T4?7DF3mdWMYvnteg^v&j zG31Syu(Ip--31qA3^lyRho&y(igegQ7%#+LJev+H>!3E~;L2F5-`b z5J>+!;5y(5ATRzsn(z{2$p3p!`2WKvbfNsMQ&Us;cBubeTY&_XfS|~M$dnT8-b?|Zz>DJWk*cnbrEk5KxQ`VLfTzy;vMcbR;#hmcHEnN zDIg#~rPIh|v(y}>l=D^}eCSL6dfkwySxSDNsqN2c9_8;zNz8h!kju-1Z}H|I`}y;%OQFTv%W-FteyTY_@E+T8s`$0vW8^PUQR!uJbeL<) zZGP|V?L{OdpYAWUlq}Ym4imb%yKh^ua&TaN`0zo~@P&JEdmxI0va)iqeSum@*WfqK zGRNPFZFaxbai8s#)Yfu|Nk|wTt#-PeZbeSATyOU+a!yW8Zp~KFYS!2wb8>R_7_snq z-(mwBnr`-LShh@vZe~ukZejLO)qxZBGTr6RIJp0bS(0`#+KF*dS6po|RUAgh@180ZOH*1^)p@ov zRp#^Hy}QuBsaDvfWoekmmY6$MW+?F~Fpx|@K*z8lHuk0a*>=`R?Z)6YI0yzJd|w=W zNbpPA)uAL_&3+zkZvCy1H1$fe$ltKTqWE3GK|#WbifCF|TJlAj>;s9cPm+_9>v%>I zu#@>c0zQ3u#Kpzcy0Adtb$$9FA_C}qfHtL-(1 zd-x~B!`*qFSL^ZETeL1FnarJ;aypOr+MVyO;=fBk)t69T>U~H_{#nac>IqkU7sRmg+cr^*-pA%F-uF!)BOd! z5G=;Y=|wP+T-@Bh`536Ep8o#*d%DJ!Xtu^S!q*ql^rjKm-EzFHcUzOk#rT{UsY3G_ zC&zO`x_8w`;CgXN&p8%Cog2#?JSb!=>m$$FLze1}M$3L{se zg7W^N<)N*+`^j2YSbIkhx&wh_u)s@2i7csDCWj3fDhx#ENWO8}ifd}(LRx_dt@p=E zSXksM?EMo3BCoQtvQ}2w1GiIvVSTn=?@QBeaK=T*`o4}dakktB_nqGZ6WmBUaFnA7 zHC_E5pAaa+__?^S1D_L!0Lv{VZwG5@AM0~{n%Cw4`INL%=ddZCE{1?a$cNn@NB>h} zD1j@qsmr(YzTW3SfI+L)7ho`$47sLb1v70L1_p+wh=?Kp|LxZMUWTn5+cR8IRHB5w z1kl0nah@udA^_}MqRgO^$$3}3P^%7YI7M*EJye~p3#>P@+p*D^Oa}n6A6;FKf&Fw6 z`P@d*uJCxATdAw7r>d3c0GxfyZiYle zSBlSl-Xh`4m;TYO+&`U=HlXFO5-6xk_n6{y35)M-z`g-=IHKXiHw8<#-I#)adD9{ zXr(8T%=K^?#g_PnZ@EaniorUf7c!mSuEm}4!qJjE=C5dXW9 z)WiZ5fIy|5SN2eVC@wC>X1Bo5X>|Pn=2u~p85n6mQW8P2ZcB059k703fbc8p>#;it zM{C`N06S})_cW6YU%Dg9EV$V2+_S3jf~eE|`)3Y2S`ZENdPYX%&9}$WVS5{mn}bm@ zd>-d?R@T;|84@UG)vc{UwG9mgDR2o&z{Md({N0DQS0|FPvi<-w<`)*K;+9JF;eg|P z;&Iwe1(9-l?2M)g^vsoN|H_40#XC zOsmQLB?P*9VP4r=N+eE3KF+Jv)^m@hWW}I|e{@dHtNq0$%kpbw)+!q1T)lE0;C&E4 z8Co9h@j$3*2f%=eimF$B?O*G8#d5yCV0rw8y9;er7jTU)y+&@uJLh|Il5gI$jpr#T z0K?(yw6NIN_9NhRUfy45xZH0z0J!-Pht=elJEy2~erzN+`o{VBIRv7nHl@{Vl=3N> zNTm2f-p#)Cg3pFU_tX@YLd(tieD1NW^B^e0>1m{+r(fCKMKU!tb#-&oKU{9h)ol^r zu$j+QD7Ki$ueD#7DvnUMx@>9d?d_G6ks0Sr6IHvbPi`W_#}D;;f|8HUClQW6dMy&$ z$h4sqPp1LP$jDe}vv6HhNn6=sj}|1BtJ759HNb7S<9@P%EFM9K%*)GrdwViGUFYx$ zSjHnu%N@R?ki3L)6lDL29fce*%;BUl4MSTPWZe^#W1sa*4aT-uCgYjGi zuxuYPGBSD%es+zpGi4uqV%oej-FM#I+dCSSATn=kCw(=+idEG#--*9ANH;7aBP-hp ztcS^{2Ng;Xr%H4K0SQno()8!FUHnCJu)qJDh=>RTXSt^2hs&cikmm>q3Hc5&2C$kA z5wozM0l(djxA|piVxY6e)K#kAK2r3K6G|!C+u@TcrZwDtNU<)(j;DuB>$Vy55NPiu|a|Ex0r< z)6aDWf(|tqeI>ca^t0fDkdd!XSh`(VrCDA;s%;j6o4sz*T@DrjxqY$N=;pUKS7WJ7 zTl_U>6AE4$PR-5D@131hTbihI{GVZ^+pTp$7lu5(A6R;&Nr_5}zC1Sd&If8(?p2+PO| z^-gdQz=R3Gy$gGKdKTMXxupd(HSq^yF&3XZ&|Pm&o!>SC#@#wOsY=G2y?SsE0SNvk z^;^-hEfDWNeEEVJ3bGpz1vo4lL~aT|egb7PF;W!CfE)~KW}0)Q`S)`WzQ@{uH6!_V&7!}2@y*O6ZyMH7 z-cuTsALv!SXbheK*8#7C@QedZ??47w=5b+@)UV_B5oGfbM1oxaD8^G_VlaTQHT8R} zftY2`iFhW>X+LD(aeK)O`Q>hA3Gi3G(oCVz`%aTU&3hHq0+39A9XzmbaLk^U@596l zbPUxSoarAUVW-}w_go!+s{t_nhCibmq*rz49f9L8ZioBK+o5&8OhXST``B(|d9244X51Bv4AB!c*wQfq!)Z&IR)P4w$T9BfaOwC(arGpz4=-@xVE-7o{O8Cn>CR0e38oH=}$OL=6nbI1PtpD2z3Jk zGPj5AD4P8sflLRc49&#>$d6WAzdMsSyS5TVDVVRe#SPlJLM9RZ zsiK0_w~#vBusa+c@}0}>hmqYcHE%-@6k0)Q28}JiAbu2NUH2N4s1bhzF-XM^hLF7nMA%SiGO0Xt(u7m zW%I+mN2S%wm{AakgoFeVkpRBsWZ`JTfCiO^hX}c!EMG1}1ss*Ds@w zJz*zXqhy7;ErehSC%EB;#${%7ukxhNY@R65+YZutd4YlnvX76CQy~oqf``Y)V+jcu z7#L92kxS&`e7dD@d$pO8!0nK|DDh?8$l5wgr^y|f_uxiQ^dE%SoH0A&LjzDH zBqsIhWB^ZPSNyUR+EJ|Ml3jBo#QQ+ zW-3f0L6Ul)@?K;}fudyL;Ki&FpeYTUKS!Dq- z%QoO$FK}@3B6E8B>!zotTl@Q^*9>=mVQ71mdf&Ml85!w;r06LUk{HM#NAp!^3w4@s zTkfxE!7v^}ph!gd_WS25%PETiRU~BO*T2WIMq7M*lB^>DLITu;2Jw#{KSJ+2QZkeI zy#2?IQp4`&6_glN+Hi)~Z;1>K1Vz2_EnDgiPM%>gKraSNff8DI0tJ9n5JRE)J>WZ^ zL6jk*q3P4(ziQf488{yhdpQxMrd|GO#J=9+g2BMhaAkG%BOo8!Ufzqbv}&aQ;vBa| zgbh6m3`n770>CuYW}%)+DH{oz`hxrkuzjh!Z`qAqARd3U+%DUOC+cG9tm5*y=L^T@ z{%A7zRsZ-`@76+tOEa3i8@toCve@g_X&|2l*}#XQBKo%}0%5>A^?_e}Bs3@}kPAyI z34ckeN=!q82&x1koxv}mksjpRD`1u{R|1I~8QLkKIG6rI6c`~$k)VmV#bjX+V8XDM z2Vkw~)k`7os;zoeHUL!DI_+RWQ&SQamNBCvfPiZtIo-x0H;VuIH5kkmF%uIiuk&t? z10Gm&yhND*ZikIrwI5Akf0uj*U#Rn`#RXJWvbAkSW6c(S zVD`$1D7#2W6PTzHWM`k)y|{8&@(KhoGY!mU0;etEFMDpA`Pww^vS3u`Vv9jS``8e) z9pwIT4d|bP{IfkY@v{^wXE`ahLnO?*rYg!xJiteH03CH~>k;8RWLbs3kIzY8rri8#$|f>M0;);m7;*{ ziM#g?D`YZ>0<>~x3A;4C_?AK@x`b6Ub;KkuT@U3bG5TbbRCK9=Ui&6hAzQf5Ew5S& z{Zc1TEn&cNRR4GInG#^|R0j&r7gh2_^OS|+BA@q+A@b0jpatn2$Row(Ukj5frq|oD zYlSykII>eF^5iPhM5RnSwg@6pfRzOQq++Q9Q-vP**2`o#SebvO z@#*Qq*9|DMH}n2NY#kdzOMIRTPk{JuJsn4W@h4n$bv4niOr|WwAku2*!_)V}KaVJ- zDQPGsC8Ldvc?8AGaY(5sIN%8$|Mx9DYiszdr$kny(&T7uU6i(NbkPDD$D-=9h5dn^ z`~S|!0q**f?*qNVp)%2w=z{YM#DDLC)cs+dH!(M|*NEeV&?NhdAhCbXJ?gOrXc;^K zEKV9gBk+Hu>Q~m|gdW(?VZ%gM6H?v}e+}?6VhhTtUC>pR@zB3l6C?lpCORm)hq5aP zK|)*+3GzbB;FQ`J#^5jRY@=FHCA@Q>E&yVRb-~UP$nswWPU#V zcQU<7Wsdey%G8k})xQ@oM5-f|kzY7li;;riM=rb4(HH3%n``JDFi<-V%M2tF$M(f= zIS3Nmao+iPk$EayoW9VCgF#Hh2;zJ$biPd@h9*57R|MxV{1{RmC)~o%;q9Ws|2|`+D=+VnA zZlyo5Z3m=tklxI0|)%XKY?NC4aWkY2nPkz#lF*P?lS$AlsT+3~6aS{&%LXO5vj`rpQy#s?Iqu!{Bnu$1# zq7iBBOip|M0=#8LRAgjcN6|D=A@?ahNy@d^xZSQe+St0r4T`pFFwMG@5JIX|8Im%C zF0TGkiO~FScCK0n?&M|}p$Yp=#O=D+KZXz^QKHi%0>~<$TOg=ppFkTwXm8r+pOL3u zOzqLQtxjHygf|c^eKhDHf=MxN$i*6Q)a4OL^G>@=1 z#y{LxWWb<)z+w5BMP+{TXgG?-tgwC+Wy3DSwQPzTkI1ZAB5hg+QRJWwKDi548T;fBr-V zoK1A8*-O@iMeI~H(LS-Xx<2N?#>LtBV~5B`*%eDYW8vhmT}8ET#~7s)w5^ z4H-JAc!sQdw~fl)((+{%)38TY+Zn8%`SF80e$p-*dSMAN;snW>jva2r;=Abdy?7O? zr*$JG$yN90HQ_;9&tluhICLlQ*ZX<=5gNrOrw&YKc(avOpw3h6Oi;X7d!#x{(jamH zi}}pM)KkzBRh7n9IT6-Hx3BD%8B4lN4h#fCN^ku9k&LAh+!X2dO04n+CxwT-Cy=sO z0y--x2(>-LS0%{4v>X+tPmC(IwPz0@VZ5AlXzy!a3>*A@^FZq-Z4K$1zbwXHN~+I& zEk}zX5M+p;#uGpa8bR-s#bOw<)eQ=gjkB{DvW}Os`lRT-^Wr^=r<1s7hSRsS3^^uT zXEI*pk|akv-5P^u-T1T`;_Zbxy~_%b0#*L>DPKlCL+wWdbN;C_reTrP@uHOdt$8g& zKYmu=d(x^6`P{nc%^D9;=4Mz4g)Y!>!vtxPZ*(-eYOz+(a{``(LS$0+i$dKB zTpSi0KqJETXyZJe0Z%y&E!=aievQgw$GT%P- zdwkmIIh-g@Q?a=JouuD=I50Mr;{3J}4yWAF$f##=G04Pl?7z5tKsuqgl_Qs8vA_sA z4Lk;GBpd!{amp;&C!M|O(u2k z7b&H~4cUD66gttzd2AZmgbAm!qmd1u6IIoV*kL_-rhfTL=nn|H zdw|T(ACv)b=;8q-235ap&uUw0rW-*l-1Br0@K;_5he*ZEfBuTk=5~Z|DJyRN548+W=b@~Y7a-($eD*=q>Lx3zKYXFE&W6};!SZBK#B9IxYn^|K1Fnkl`vUqs!rAH z+~yH9YNJvaj}Pf3_W@OGmO-q#%_4s}lCo>wvAV-1qgWM>fhaZ_7}BCn0j4wk zM`-`|fAiVTlBksE|2I#*TyP{#b7T@&?d>Wo7QjX+DcbKbuBf9`JDd3XaiJv#$}IUdj4Vy(GSu@`7B1QaFlcX^+wG#idgW>*iJ%6?0% zEc!UsAV;KJv=IXI8Q-$JC)nqk4JAHw5`R+6>i_|1Bvqe`tD!da{uMHlPa7;%tdw6j zB?X24_PF_bx~s?`=3Lub%tyL{g3!yIC-J)1tgGm6DgCt-YsB?u>N3AU`6GeXg<&Hw z0Mn7m0#pv)J35LP7{G##VqjK(+zKcxeExii4270<^k1O&?t1)-nvbt9&1$CH9YmRT zn4oVtGOgOy-*2_oL=U>QEfV+PzhW?;ZA%@Ub-^BNh~D)nj{50d&HK_C(CZhdbw8s4 zMW3*ni+wUWtFTE!fayQlr}TBqr~quA#gPpuj;u+&yYbJG`#a~AZnmbBCN&ll+gcT8 zp9T_G;z7}QRczc&OtevBRk2Q!rXDkn<=r*1*$a$MMRV0Sbi585NZ+}= zq}I?0n) zK_WvOS)o{q8zNL?F#&C^SxuM1%+*+TSDGt15KJ|=*m7}UbY9x&u52zeFgtBm))2U- zsf>6@{c*QU>9piTu2Nkhq}{o0J#OtFF$gxR2^*-A`sU`MPnS95xO)sG1_Aij*SA!7 zh<^Ou-dT8W)(I7}T2;H)Ed;Z)s-hdj$%*cP1ZMOh1_rZp{3naik09=J}1(k8>Uq5H+}jI)`q`8@Uvqa~VIh>&H_{?}Z4et6N{ z_p*OwWhHX+gu^lU`dW0VmWA!!zi-$pra}$|;4thx3=Gw}Cay}pYI{7H_$ufsh-;(# z@xt_5n(Gx$zeBMO!rnZ)402*NZVp{aHKjRt@TshZnr`cR8eG_ghRgE$T?tu$!p zp&cC^HI51CRuX zR06vP2LxKFrR1;KJx$6Vr)L0q0WY5RS%yHC4hk$^f-fRF5(bE-s6i#%5&S|R^`0d-bHcM!6UTSd> zmk3mp!&pGW-Szzgq0Lf#&=MJ$-NlggqeqXR>b?lU=jlEV_hu926p&P}8)x+z27DM8 z7@j#Gz@odoy=4=49rAKeWj`m0_xfp>lWxd4I6TPfqcweQ*#NECvuNgmIm3Dx4lH z5&4$dnMlhd>$nBu}s&?KQco+Bi9d(KhsiFAw#BI2D^o zk65ZMPCREwTfXKk0Z8Yj*6i|vffZl4+F-%wG!4$gaEKv!EhP|GMu)vB|v z1rp9;POr_JrN8k6$?a8CkdW%?`{`vyHgC)*No5Tt9|8Q`y{hI=itBA47DNR7S6l!; z01PGC#lv7AM-L3b&USXf83D+VAUg%M{MFVEa6oGU5?ma}a?KJcadhwd)eZ#ZVlAB5 zMyKKNlRRaF6b_q0@AjihJ5bPYc-2-0TAAoTly`V|*!n8PXwZaCwNSk{GnM0Kj$Au% z1~vh|=1WqwI?&26{LvAV`|iLn=H##=(kD8TaZLx;itLp&qn`O>rFpS^zELhnOSdPA zzD+DjFYF=Ism>r@8xRNcaJA0P@=Qg|k7a4YKwcBz1J5&sv`$z~Jt9Ie;(t{NA}?`0K+0lIyOB7I$Fzdn-M_6p(~ zA#nY4p#dlXw3MWzfX|fnERh69Ry| z(5`_Fw9blTbhUD$H^7u|NN?k*$}1{V9_oMygNyJbD8~}Cr${`A6=_y2FBRg@s1=8T zhVC*@t$`M?85WRw#v!1;O0|q<8Hfh6am5uZIoF?w9z1 z9^yGjK$RZmE7TZ4gwLM{MPS!0Pl19@r}Em8@|mPk)zX3Lq$&jB!RwLKjaV1)7i>b0 zh6ZGmai&X99-p%^MCP&>_2^w*U6IgiKLRi^F-NPC|H#9G7pf?w;Km8V<5ctk^_MIL zEVMC9z-~M)?gMhsUybCeOrmu@CzvGzx2kmSg;(@tlLA(#1}K^*8fCNE;*miV#y z{L}e2RG^WEfn;W7=`Y=TL40ScEbs_SgO!7U1eNZ7pI3}P@OhbfC?FKXpevPmv=MKi zx;yG)+Sl|sY*1xma&Hb>P@hywTN}t&4%a`w;JM-5?Dwg*nG4YMS%3ptpwrzM(Wbk( zgXVUiR+T+3Cl)NtgXed(UJ>L%f5Lm}25Umz(0zh*WioTX6ssnlWiKr+H1Sk)>fpitVa}`+h z{zAQQo-%2zu{4|YMnwLEXP7(pCjlKD9RMCsJ)Y(x_%zY(x7(&{`QrugRi5@gvx!DB zOoz3Gt@~{z3uny6FO|ZE0bdFYe)G;l{=9!mN=g?UFA1T5 z=jU?cg;7$fr*KcaKVS;CK~=Xvat)-4Cod09f3wO1wuhSn3NpAab)8{g?jgPcUNTiPU!J-4GUx^0MedIAZ>;l+G%@18%*>WiXQ1Q)$533tXdPW9xy9V3UsS;45;k=}A zT>;Qjx5$@78cPZk-+F|B=7h=(rDI46*X`fC5 ztD$Q%oXCm?F5Crd)$Z4pLeU&xKK?`B0s+Y=iVhxKkWu@mLrwFrH3)O zM?&VM${cxP7_9zL>s_(Wa(6GB<2}a$`=^Fcfbgz+5N`@7>vcv}K%wVo>7UAp^0L(# zU&@2uxBpQJaBDXr{ZrZke-VEC&nE^7BQU5hGq-5f5P2T`O-xff`iRFlW zQ0172RK)ApRrd^QV0LI{3WTD2%Swx>7bh*+R+H5O=F6pwqdhI$UmrbAGnc15C1Szf zWYii^(!TDxXw}zU-73q~nv)18i13PR+05g&w$2@*xRIcYPR#UnU^;W1a7tR5^h!HE zopA_FWI})`noX5xf#Guel{-|PmuLR;UCWDw`23iD(HBdzKu;A7tlX?6>X3P*~hU&S~&c2wbUKI8eWHNbC!xP8xI(%AfLZ@$?9 zYm#HqY$lx>bhQ*R>KWag4uT09oHuD`(LzcsejCW(KEy8a$iBFl+tszUZYtVgeDuKZ zkIi2XdnC#{@=exm0JY6-E{AhE@7cR&`Km|4PAwMOcYuy3@K|za1~>{eFf5e0-8=`_ z2GNjC-{CZWoTr@6AZRs?jW1}YX!-74^&a#yFD~8i=(HM@n|5VrVhv_@xy>e;(Z9mi zbZi~z7ML>1CNrb2;Bn^dK*1PRvr1S>3UO7p>BpyDr6ae%w_B4rfrsy`~7&2{6N*wd={aGWk{g{nviw{oLX-qq==p-IZ&Hez<*R>2!4) zO6_%o_`CmH+e2?{gMMqI=+(#B>h7`F0hMm&}Q#XpQKKge$`Z!hHx&=Q@J*~b^H*T>uY8~ue1_b?G93ez8D7NR<^ZwxsCHa zT|NEkBQE!|#YJgl1$)>ehH|?2A_z^4r(t=Zr^Ix=V>>=qvv-N08-68a_+5G_bL0AT zyh|q{ZpDs@01BdrMhy#P2*HRqa@AQ$+JUy$F^g)&!_0__f0~2x=tWbS)6WzXimUR8)v?o!e@xNJznhpdr9U;yF>)D^D4Qg^4JUE4MB7Ow-e!r_+~+P*sB9S(l7dul`Y z`JjEuP;W^9;>&6}oZ&q96{Nl0YGmT+buEJToGu*2T1^3E=&x3$2;S-2+D4q7Isz%S z-dy%s-7K7Mn%`qs2w>O&ye*T5 zsnN=Ve51Moq?PlhsaaSr5an({Z9+{ONc|JoyeI{(Y6(S52ZaVg)I(F)G%R(5`wrHC)gYD@_3+-@GDB;js9uDoj5<=id?QK$pL@)4h_>y|HsYIbrhF z+}wPc=;{eMCgvf{0Rq%kjJv+qCGhK>xCoe=vs9W9LWCZ?h>hmWEZ2SpgfScTkwe_@ z;;)Bv=c?G*oX^ezo6Ah8o*Uv^?@wgO4exKAbfV%5T))a!K5v^$64YO~pkg(~TVEJDw6oXYDEZcMmieh)qik*S}l}aj%O}X9cgDIUVm7 zXixtb)U|wL4J$fKs(*_h9RRW23b_^4>`M2W=$zrhtzPug-=hSeaGATTLfGWtDDjIZ z@MQdLbXOSY`1&L2tdG{}!_7B)pg@ShhPI_s))+bsv@9*H5t2;y{rLtINUm-P;p%-* zcg5x~3`8P=Nc8;)gwOr>k;?(U&`w+%1kz%mqR`ig!)ddmS~ImA9Z^+n8k&H``2KE# z{`BDIv$(i8s^43m%WWRCGe~riKD|pE17Wc`r@D6XWOJC%rGc~{Es2{_a}0XIBkoolH|6h{IaqrJZaq4*4&-stL!N-XjPFY z2ndecz}}cd(QIs`tGy|q2b0pWvJVx}=#Ni-`;m2rZ*p@O8Yoc3^u+Yr9o%~X!DMP> z0WCRNP@AiNSF9JENlpQg>?l_Kta2hZBL+0Ay6vFe9>06^LzNC*ul}fr#jwu^pSx6d zO)1Aj_RJgTAxW%r!Tz4EPu)zWd#S=LKoBMXc9WV)(NXBD?7$Zx($1SRT2p5 zA!LEb{uR;D;y6#9czha4{!oR-v3l}6snoHP=5%Wk=lN@brVxf3-(S6B#$riwyo=lg2St<(k^Q9~a3v)tWd+DlsQ;tn_nfNb2| zz5aUBFEE01wG~DvPfXDs@mFFZ1Oj9Uk2r3SxBhMh%?nwH@@;EpzZ&syPpfa5 zt_d#+GadPC1C6PN2*39=(pw_0Ogm&pwhwD@cj8>FO#4_?kW3|C>&YJxhojx$ylHGQYHlxgPCU0gmZ zq?EGXo=;dvD}~#ph1Q`Q_m8MEoobVaotIgDE-o_-=0alLAS~Kl^P6S2SYB)oe0n-1 z5M5?dq&Z!0mvp`9fW^={krngCV368lw`;NG3dh!spM!DfW&O3p(nUaBHxLSe&dtrL z->urQ1cmhK%n3Gt5_NSW?T=VxWmJ3H`3z*3A(3Aap7QcG@YOi1ZODEq(v3Krpj9X| zQ_!k)#0iNnQ9eMiS;UuCD+&l~;01LRI9gg-E?!(*2i8>3r#M9YAsu&nc7~O)n?%{Cs;og29d*Xy80vKVsEP?$o<7EtmKTD21Uug@dzEBT__2TijWl?0HaR7bGFabTR`1(Fa+L}w z^FbvoOwgw5v%aVdM{~;~mCJv%0RAlJ_pDFSE^0=2$|JN%D#Mz(kns{uJ-{{%!|j=q z&05bh8o+bjACH@}SsezoT|Xz}0lHn>Z)D?Irwc7JHs{%LIk!6=7;445>qI>t-P+u;7K_+ENg2tGV%-a3DyAJAGs69SssjzeC3@W;kV6B8 z#OHm?Cb#(;Y-R#m7cB2kzNop$P0!5e1EK(GeP4i@3xH7NWX6Bf9D8x?O6xT+OC1xeT7oC|Sn-O+IGK<~Vvl&)_6r=oWeAfS*_Qh?JInJwgJ zrA~*&vc>^<$(gOHOGX1jS&rs^=9%4a z-`2}9Ok313j97I7~LO?HFZlI8S`?go5JCD_egrA=Pgx2Zm zwO7En5J-yVa(C2gU8!vrS?;3Vd0p5J-LtbTg0%cgOibq4=545ol24%d%_0+md}<3&oZ=Dpq%?bSPX_bTPEpKEAp8nn%8aGgwPK+_-o>(lNaCDZ@8 z96_B1bP24_IBl223!Dj1r>W^vKxNm0S3l*1y;iQKhH=8J@}srZLZV&Qll zuMyi3c{b}Y?7_Ql2;qa++=f9RH&32nF~08q^4x4Lj~1BhNS)ymzq16I;_v_X0jNaL zr0M%zKYdrzX0-N>3Lv+&$v|R=NgF-80865b{7u*V()AW@L%N)%N2y?dgbUm`O^Z8T6r$T_Qn~l|yU^42a*;xTr?R?vaM?dEzHtY>&cXDq1 zsYLU`Lxs!Iih`;+QMV5(^(PDwG%1lt2n{Jib(WtaDy$T7<;aV&#`@{^PS%ok3d+H> zPZhO4!E3Yzr$7Cy45RG={|HvZ7eh*fCR)a1QSb}1u>~T+%1vEy9r?+3hvb_W)8fRr z&4J6Cn>SJAvXs_KbE&xT8O&Nw6)<&v{YnrD=M9YhsQjCmtR2RGZ) zYO?yT(e`u0fL{}#;qfDnu+ix4qjQ&dZO(If;>~XDt(KS!_a(FLWGTKTBBCpTKzk8r zu9Y1_H--}P9#cpLYZuV`2( zFR1Z_u`aKu%V6LOMDp*QEs}m=M2U{XvtWV1nlNaopArd7DQ3Sc| zhJzX`ZaH`N0Lm5)ZG};0TsF_l$-*0T&=7TZ>r;MxMwH9Yi{Q|cOY&l{lM*fTt(y3@39pvrp?S8uQg#Cu- zq|$yu!rk%q61H16flVU}mt*{urK)+CrBapqpr)5f=z)@_@iyB;(C3ls30ijBA-XDy zQ4z4yMy30F@2u_Sz`S#ef(0AtYl~S>QFyPBqP+YURfczkZWX5H{nZC&dvQfA-FpR- z^qRTe3p^CiWriN9F)Fh_^mDd5t0K_cygl}taH2o{dx)Tu=Bw2;b_?}FuqD&Ua7`SS zn@&~N=5*Jl^;3r!0oQ3kS2!*S*g5L&ACT|e#KVFcwmn-H{o}_E44sDR$EQ05WOm(% z}#*MKU^>oMG}FD~|rTn`2ZE8uH$XeJTnaHB;X13_*PQ z^!@bP_wru=L>iZ=Ba(w3JmJZN55clrX>@t1RrK}z;go35b91452ldZc-@jDewjK&+ z3$;r9s;}>UaQC`za8O0jzC5l(J#XvgTC)DTq!spzkmaNgU370|^ifVsLX1{sQdbx* z4KcBhbi5B$!9@PRt`!A2P~KCh;3OsjSY!D%I9u}|QDa!4PWP4Bq5Aoqd+qr3vp<)N z3D{vtG-JbM_u4MtLBq-sxO-yp|)nkE$*kNw(;rvDFYQn0v# z|8rR>KjEl}Xhe-f9Ak_=<0n3gCE0@qG(R1{yUNOqSg-&o+)hxQI=b%GH?qF?^Bvz! zNlNer^mzZQSM1Lthz&{N(?1Ve>HjYw;)@JGC+M+^85Q*B5oBHjRD=IHeBb?VI;pfm zeQ&yX3VGhaT=&GSzwX5Es*JCd45QACgTKgloH-<-jQXl_@5VD`?bv>rf4ka=5$BG< zH`Hd?FZMD;9ENfDXDV7J6o9WMYG}+>dj+yolo$714P||EdKg$cKI7O*df`-MIwpJF z-Q&LPD4n`+tl}B!p6ifcQ>?w7-=U^T>GM6;>4qWB1^aC*d6#9HPjQS35n@V9c_H{! z8*mg$EUps>X_Ttd;c{HVP*74(^EFma7T?N?8_sOV=J>=Fdb_~yF!EbWCpQ!&r~Kq0 zoAr+|mr)ay5>IAf)ac_y%8^1_s~Qnh>uW$cAJWB{g#LmR1TvKYz*gpsEY!5gL3r1o z&dWo5hD={^l4wtVy_>_f=XGWhv0atuDMlnPCKP6IO+!DIo=5b`Vs1qS{tAb^Tst=a zORDzP!6SHbUv?reg6&I-?dY$qbo$MzHM6+?#ol{HHMwnV!|v@-&bGkWDkyy`2-1}* zT}45p2}%tRr9}uGLZsV}Ehs2eI!Y%LDG5DM5s+R(3jq~r0TOB;2_)|d?(>{;p0meO zetctm?-*~!V93Da&V8>n*IaX6*EQ!FsO%;M!`|7GPs-+eU90e0aB140T-_DODs?k& z+W5AM#*_2+K?F=Ti;K3DGj1*4@hZJ9I^ar^;&In1FAs0;OM1*3gd9|bKxLg~T|4Mk zm(^0wYD04z2o@u?!5^K@pAg)yw@Vexe{R6j)Tn&9;ag>bbH||FtDk~GZ$5FeSPwYm zs86?yt01p!UA#s#HOkse$X`m=XsIqNYv8BDds_YI%jx>a$(!b0M@n<}9v+Z0f`xWf zSs^;1TnlXyMknTxfp$l-kXLdjzu+srr#p1N68GVO?bwy>h9NoB-3JqY=`>kvwK3n= zraap1p;7n%Bg;=Yb!f-kOOWZ@!|Vs2 zJ$Kda&&H8sHsoGzr?2$ha`U_Oz~x9$PVIMr7myR>HkT*sS!H)(>C2vmKbb!#4NG6| z-6|@b&_LPSq!n62Gw{_9e+6A-^jSCLhsWx8A`S$(PJhy+EA{8TqGa?{RMpvNlwsGT zEsSgfb^Vkx2fs7F9_X2)1JLegGR)S8x)5Hv{lu4w?$PNwM^*-k*>rr`Ljy(8Y0@;2-$p> zC*5u~w*~C99k&ibgvRk`cQ8I_h@J^qQU5(SZ*N$)fsgJ+t#SAL;Lh zSR{)Qxvk9+S3)+XZjE8;fz{>le+)VC9p;Z}6v1V5_g7S@2GtrI4Ggx6nR(N5mwrDy zwQV;*MJ(qsDKuCKUou;&c3O*XIKQ4#?6p9vRUFgKG_C?Wmvo7CpWh#%sf~so%O-fO z+`9fC+3hcZ@JpQ}3}$V;I<}`)U60&q9XBda`Y^6~46FaZOukz!HH5}tt-O<-ABH?> zoxCCKVFV$MHinVI)Ulo$=so={Uj^q0@-}t*edf z6a4Y#MkjeFwv*z2qs(_b_cb!CN>GLG$eNG0mPzN_$*efk?`Bz=UOaK_ng zlMw$}iy>hI55Jy>m&|j4B)}@v?iD%hRuzak3Lg7n_uFvaCApOKrL4AiEw$-(fjWMe zkE0*vU=$(0rav2tJh+Hr3 z*tAX4@u(n)(@K_sJSbvrN9V5FLOJS~;%=2w(|xE%EP;!QnpD$YtQ4!)F+xCm>>6C7 z-O@m#0;QNCFO_lKgF7d_W?R7+e=jMg?Tk5b${_e#rQ&A1Xyk>Y z-0-sdb5xjp~=V9`2{I2(d)Iw4rr%`v#y)uEu4>SR_nR-B_R=AoYMDs}v3OLnyN>={Jb z6HC*VS;>=x5PJ($!rjXGp$Gwj4@=TM2rR+UDWoZU%YNe`h97R;Z}c@Rb? z7fvrX`r_^rH`nfJHY(aT#4ukO3Iv-R)kL4UoW-DPk7npjtAYn8nEs4_{P zc0ust2K?Ju1e0Ovq4oN7&gE3~;xf*&wq^tgJw=xf`Y#p*@`F8pkv1F22x~Zgiy`(C z$=feGsQY4mroy*`G?>gvXMe<=D+(?l&J@Rn3!i%9fpaMaJ9KKLmgy@cR&D1yFcf?h zs{A^CjKtaC5sMAthZ~(egBeG6&gzfVS%B|;Q~Ui}=!oc^?Y=y+=69uxpk79h#vr=~x$w_ZsyfxB zZO`VX3v3FWzplMB1t1|r<39lsaiC1G@Mv$*t0d9j@)RmH#Sf4{&?0v8O6{#CIgx%xwg3~u~|v39I1)I0$mPxtvo>na@_p2Q2~i1 zFHahA#Rz%?las_$lE-Lfw{9gSCfd0yTUt14dhTazq0Uz%95kWd;@K3G=&S(U6ry&D zZ%eF9&`q=lU%cR3z_`@ZOGC`4^Z=O>?!Bu|n7dL8ySh3CFw5!*DTy=H=@R04{3;%1 zOJ5!ucV4^JHa{q*d`>K&R*2d87yV?)y%1_d@(}F(rK5sM7Qxizh7fX>VUvb)@mo6A z?PU9=%}<}^{Fys}`U4zN7NiCbf|3{W(a!`4LA1FQ>vVJVo&IA-PpR1w850G(3b0Ox ziqRN#w>htJ!+~ImTxBA|8}GI`?}zjC^%d0z>49DF7Cs^&;mllG&7C)IIogUXam>_) zJ8v+Q(V`2}!lomWp-NvWzkR9%Y40_z7@{k5O!_<-pgQT$zXJGkVgHt~kCFq|+GdS200=NLzse=0 zu1?5VeVJP|*R!xN2ff*g-BtAeey>R}Fe+)sFYuM$#k4jq%QCZ#FIKU`h`YoF|0EnP z{NWsRcRR=UY`PC>i92TY^B5myL`82Gf#$J$p|UrVGkIo{DfcR@6x}!W-!QykcK^Od zsU7Xjb6rN@VEpRpp`%AlL35?f0OZnmp0&c{&CM@vakAp`LH^6dD)~nJy)4Exlikh0 zQYiWglgT8FVw=P?=)e1RyEHL}rMsckSHpv;yf2;$76XWcMAcTZG~C$5UMEyhZG+QjwaCmgAVkcNUJ_ZSlvM&CTPD78jK3Wr>Zs|K1_8pk%AWuje# z=uDzd$#LM%S3bO=b0sQWPE_hfV^R@LtDNudoBEnyWb*W4iC0W(sds4ASP56M^lh7<`pgij zD!!z`qT(?}pg>wd1zWg$I9YbEGGd+kSD~^$QhnmZG-41+47c(2n@PsNU~f}4-`%^U zk($`ILDW8VicDYag&a|R0+M_?x`85HdC)#Z&8TM5H+s0-zNmV`A6S5|DyEF&PNX)0 zXU#c4!RCOTT0x3JaT`0G@k3=s@FuMpbC7krep{C-G;Mx*ZQMsW7O0Ex3MM-(IS9M^ z?n1oUJu_Cdw}$%#R2|E`V(5!*GWC33dz0H?kk_bi;jEH5zl{(k~oF5q;vWU%QDm@q@@kNqT%pM zIIP++WY^t>p7F{8k2H(5%}xA^7yLy!4H&f$#U5w%WkR)ut!+B!HkptaM!RtTJ?J@! zy%kr@h!h^edhj>Q!3XQ}K%*spgZMOj@k|!J#Pv96W+5f5csn#`Yxq@;-dX+C0^`N2 zA&t66qZ4atYCPR1u8TyrkUC$aP4Gcsp?;QIWp>}^<_c_-;G~D9IUCEvKp~MPUSTC| zF_Y3Ar5<2dS65ej{MXZGGKOY+H7pY!N>%36yHg=`xP0PN-y?j#PMUt5nuYs`PAK>fcrba!&gk-BwZ9dZVC4FPPrhzVSy&qUG$lDCuHk% zO}u#G>jWo$0(c9lN1fu$7q2O(UcGhSPQ8*5+2l5o*D#J)DD%P+-?k6tqwJE&^#zP3 z&z5iZVs1QcfLG*>{6HI=-+-le3VhYw#~iNBt>yRz7#9Gi)TH^xz8rLRabDO+Q7DGA z1)7QBK7A5R@$=h!FA_VJsc`D~8ns>{7wR=$(yQ$Lvcw`U5B51d*>VY!{s)}otPZ5o z_0ur~G^I`0S$?KihkzkXR6G_6+xf*P#P?Ca?mJ64CgN@9`T-70OMC2c5?<#~A`ClG zBZZuv!5yx?>$Ja=wQ(zXxt;{^87jBblaMINJC=sBV$a9%tU$B%lQQ^R*FkP#O>FB6 z-aJWJY<#(oQ*O^L_P``)9HZHQz|WRKxV6sfa&QU_BqT$nZ!&@UqS!j;X|uY=vXA-P zw27AG)%za!a>&TC@_MYEnVIja3KsSKQ4y|%3OylTYgqtQ$xyHmikyAEzMK{%fC0)=5|V37qi1(@ zK!MwV7(osH>xX&dhCc>UBx`MymRSqhskEMk10CO4VTv%1z>ntY8S2#|nN5tulvhQU zFZ3TOw}ZTS^Jct(rDxZ8-}D7PH!+Fy46JdrP_fHkqnVscPPT9`iz8jVI@)pSg>IgG z2O(G@)JH5|Np@@;cu*T*q&=XD~ii z+oj;Z+|r9usj}G%xPf}DDAbBTolP^9p2VuFw2Iza{)+_QW|#b{B(0n+2Ck5J=R%1#b2J8a4bHK=!sjY9jm(2?~s>Q zu;^funj{N-gEeg;Ms?qcaIAZsRaFJ$t&Kc#j7wX) zLOG(ukU=8=N!l>W%}My2d8Dec=g>{C4s7aobiiXdyTGJQUoegPPN5Wl4m7bO+p@WG z_7?WE96LM*4VziDV&kLl`u^4pd3TdNM&Vx{g{gY?-3>&7A&#>bu3;6`k)8x675XXa zl?}gZ!g7a|<-+>`IndfQrnZ5a_L$ro_5=_E*QbNx6#t4N2VK8S+201c{KnE@IUfC= zXJO}0ZO{`e6Eic*9yaT`jlTA=d3XQW+S=MsvGZZHXa5x)z30O(U*$L}rvY}r9TCYX zP?*yeD-m?P1Vk?j)iNfvZsNdSo<4orjai^Q_H7}=ghTLI^eqO)Bl?2x0f1E-Wzmac zKD88?upXwaY*g{_s9*K4*?mJn5_ovrZSBJA-|8$K+qti)UDSu&Khf7oI zvS}09e55DiP{IPfssZ@^jRKeMsG+UZ)&9jnG582slO6tmnyJm*7&}~>;t%fVP$({| zO3e~*dU}ImxVI{>?*0!tks5r5!IradD}TQ4Ycu;}3ca{<+Biv0O1eKxXkBVy^Vj2{ zxYQ{Ne`!G-KxaZa{>}hx~;PCQ0GfYSS->~3pU-p$JR2A^= zAOFSqnj(_PSfj4?Z&&1WswX%w7Ng4x;RP|I{!)X$$e;I(2uvOZ{wBMT#_<<5jn<53 zmku~oSZ%Zb&Gls7t-#1{uYhwV3@<>rgxOCpRBs;b8^dnTzi(dx<_2TG^n3KZf1#yf z1+;>0cUm$H3XUH$7uT>IEHE@fXJ@Z*{y@$Xn!?`2_B}Wz^8W=Q zT~yuv$tPATAN+`+@HSs)#Ne>Loa33&1%;Aaw~S-xoTqYysT4WN&8SbuxCL{LudZUI zBUrK~dE5p!ZWL#}-cEn})=_b{G^IriY5m;bn$5{fU+!R6YUvX{^;dt_0-Skq>stK} zkd_;oXC!vR3*+Yct8jkhz%3)w)Ios7I_ zX$QIsR#sMamh3*j{$-q5b`w+Qsq5sU2sU|Vze|UcA4QB~9Sd5YhhJrP5d&*K|BaH?=j0C_T} zW^P9Z&@oZYR^p=dnu*EBJJ(=nS1zF*jH5t^wzE?S$VEeXN>zxUh_QTjK zC@5^MB@{)Gv~Su4Z4Mtlo;3)Xl6fF<-a2k9oa8@oT-}z7<@~6bR=4J5*+%5`vJ90& z`S@0Q4h||P($EX+HJkla1noj>3-5wm7|AUtN5+i*6Hhs|3=YBo3-wzeC|K0zQ*|$7 zBELLfb72i_ENX`++HDmUN~OeZ?0hQNtVxEN>*_vL3z*N=WH7h2syqlao!alAjBvVs z+y)NHSqws;h^J3Z-0O9cm-vxk40eqaEKV&BFUHGZhe~({(Lpr%b~ikOoy+0QyH@fk zxfhxYmkc}~$#>gKMbk-XUK6CWKjk%(8G`D!xBTxF=n3(a_l}gBKBjLj538hg)4kiT zgue5}QkRGYc$Ph+WK`qD%;@U0O|5^*Y)=>j8rEnrDAts#_UqRJf2(C-d-%BMD)%&wgn&TGLILBv$dPU5ugvouSs>aQMrd>Mp#5;w!!%Is2=rjIt!#ubxC zBGpG?QL6#?t;$p4utjQO(co?IGglTZ%ZD0OtA>iN9fmZ0E7LcDQUuU5b8~-Za03{7 zBwgV}$x!+JkrB(|SwUA6CNs^yl&R~h{VH6Z>G#D&6hA0MiVW~TY!^;DJ=Y{Q z1iS(jD{lCbA`aab12qtwRDgTtC*buHVaihE(F-%Pv&DdqZES2jlpxVAkdTnGonFd| zL@f19k}oFaF2|Jtgx5g+WXy1jm$-@K1&Q{6p8v=d3UW5x6#4LIKkLIA+-6io;t@0lK-P-iw#9v9TYo=$PXs zg`YLVmFOd(lytPSBH!kOg2}>gzL93e-rn=x+@c7luo}gKB<6{!5OcXv7N~Y3m9b0{ zL&G>#6-zFSwb#q4k3XFAlJh$7m%q%%sC$lJg~Jot6HuHUgkrxo&Yilmrk>(^skA>C)tjsx~#^CK(ppd`c5; zIim1^u|+#qs5;vpJXrG~hD4>UjOOsEdNvB3Qu;`>1*cBT%yFJ+6q|A z2Xy*IM}q62VxHVr6*`9#Bo3+mqmrohX8i``x9amtC|e z=im|?c+?V&_MU7wRAylW{8Fq7cD+n*IncLJx{OOtLr^?;5i>(XN~x+^^F+7geD}~F zcCyCoEwOXjiD7B$+^0|DH{Z{{O2uu*QcR;8zaCU_8@=v@TQBqV^K(YqAVBQBeE+|Q z@gK#FXBp6kLIH;ErlC>_->znB#5 z>WW#8{q-x_JV*ES+R}u+MY`(B*48Z>o784pIT)FkZ+>W(^vyADlX2Ou44SX9EO|(7 zdGQU~BS$>F2lh~)Ucl21pWNtt_}F+63>L^vJ3e<+hn9cC?rPU-;7~N5fX3=_E|>#Cm z`G;qnW9O#Snh$sS7H`>`1J&z6PX401j;*dAzMaM7T3FpH1l?zcc1*(KKHPaJiog~; zW=Gwj1kBl(wtI?h^dU=xsH|})oonbxK^Feq=xMh>QtYR$!@XKL#9(SM<-z43I8}gn zXD;$L0bVul>=+^K{Q1)r?5N=JsYb>7pU%DDW&N&(T*?-*Yp4Yv$DuY~>XK^rYgI@S z<7nUR{>aa5ZIH(f2Tv4PrmLP(^L}ZcsnJdCd>6s$>FwWEi|WOv`V7M?HC!9cBWF9u zYHM%ds>?b4G{82dr2AFdL(uMTfZXQZ{O|pI^1ju;++}q2Y`bHtrt?BWFHX0!m)W06 zG{BKP29;u(u2TO9!-Z=0;@R5zBXJW`krEP83eAW&$1eUu36>0C;(O%ulz{Leo^xE` zp{v}8#@m8VgDFW+VeMvIz+*hbH zl`mZW=`#MhC_t~W2NaT!`?z0$Cu?!&&s&nFF&OdJfgUCRWZLGNTrVZmZ}CJ&Y#jl2 zR=82CQ1NmHH|bvSz1{8QCk~L^e0lKU&P{vlRR<J>YvBd|Bnq})JvmzUhX%*Cm96jFdwG8Cxtd(7 z^oI`TWvxQL5(*Z(8f(;-M8V3f?LafbEGTlc?9c1a(A^h8!E3w_Vv64+nm}F{gblqR zG^uSY{u=A}4b)wNUbgoWR@qc`t+6-k?O_Wp?b86|;eFnEW;4j2QWgu^V%PSI9MWJB zsTm+)Oe?AExTi=OJPLIiTCs|Fa$D{uxfw6q?ojdh&iVTSd+{llcE0Ok_GdH3Yw-MD zO{IgN1-xg5TAXOSgr4>ip&XVK^wFXW{KcjzAVVnV&hzEE^^<1RbCIC{)eOT=AKaH+ zY1O!tWwZ0ax28kud{A!d^HUbBzp}?_LtPPlFHsg2i=?44}Prb)<8s} z7`Ak(cbo%Uz3liz%KW1_NK`3qMAE9)4P6YGGiK}Aj`ICYMt>aR7+D{%ARUc&KtZZ> zU#nrf3eZ_0#dmcMN{EQ)OocRDlS+~S-yD7yF1T;seKGO2+zTRd;l$575z)&@f#5s& zG|yP5s^+73`M|e%cYo>mgeqiX%ib%NdAYfga&jgO0j%tpWx|Pbj%XXDGXcMA*=$u9 zs@Biq)H? zr|lDT4?I+8V=hC+LO}76>HSihdfIH-iFx7QKo*?snX%$cLQnXDh zqVcW-u;exC#Nxr2pN39J2EoW0`Z)1pdPgkFo~Z-t-M0^RU9A4*giH&S1eO9~dBAka zbFz2^w4p5ZD+qz4mk-6SNeA+&YFOk5hgj`WaL|VjnY3du*J^G8JL~=a-4<|Yr)G9C zH3SqKUi*U8>&K>DFSY2&uS&wF^3jv2n=y|b{jB7#o215@M^$t-9!^4u&8GiUANZE8HEk?bfhp)w`N6=uGB zRzJD2Dr%_vLP|n13`4+#U&@xX>qt5)*PHViJmgpuWAg#uly4dct(_G44)2&;GfCXq z+XLQf|JL{Q0@=|2!(i~fDnM)}waX@4^{?@C;T8A&!%wh>{$r8@xKHr96z3#O`SB}o z%lyC%93UI~;VooEQsxi+hkMa?_rst>aqq!!l|dUL*`IZ8%Xnsd>VGF@ttD&qM6Ino&AsZc<-B+?K(PMLh%pQIQM*o!>k$M6QZE&3_$rnHww0I9i3fzpZLVV>cu$Vfbn&? z3Q^j>iW}J52Sx(F+L6-VY4Gsj<>Ha-SC1a)?78kQ^_S)QW2A%+f-x4=#}`LRzOz99 zQ`N8BJqZRXe9a4{%=dzQH*0Gku=#Cof4C6~_6OSWD1hApw=N2{hIe&whK+GoMcGF7 zVDTp~C~H%JFIoG4)(5*$8I(44d?iN;++pqHOGE$DtjH%)`d78igFj5N6|`T1uFCiC z54i_^1+Vv;T~?ES%;COT<^Q>`(jd=7%$UYlWRtakfRhCnRd+&s4tD>+g~(k-G*K6b z%Z)#^sqZvY-rrreW@8aEg1U8*FE-77y*;dCGoD^bt30J+QdW_Wa9i`jswz7qp+gi5 z{OyO?Gr8~fkwmqWdmZilF~kOZg-)=?WIcX4$g4Y04;q55I3lvXv|}C;s^n``!gozK z31Vnz5E#3QH%r>x@Ncq}bF?=#pIwDkhSc#*+%(B5-Mwz2$qdgM4{#RRBVP+YOCscb zKxL`h@P@@EsoyTG#?!CC{tvY3ynJu3_PcOx5c4M8x@u{%cV*Dc*y%s6Y;xnqaJLFk z^znz^pwKBZj|TbJJ&E3JCvexbK7(Qi^5|t5)&w|$NZ8WBAx;#SVIiVTwxg@7Ix*D$ zV?3SASf<};xe;39(JUwnG7udMExgJNOa-fK{u&@DM9pEeb9P#|0KV$wT;B1>+F4|m zR3TnvRr!{E^Fem2H#|-6cD61LggMTW5foBLj6Hwd*=h!D2IB zDKV?br}G~IdsRX9k^m(Px5u3vTmmowlrdEBI65AO%OYbJjXjt>o5W`#j*+BH&gG5I zI@6o9dL=i2Nal75H}d~G=H$L9SglWG7@b%%u3Gn+h{v}oT9x>GOM0iHa*#G;AC z;Mf8@bFm|XO8>IERqe2FjQ7Yvz}A0Spsff0n`Zx`Yg*aW&80U+5a#GXZ0MXZx|U(g zMkzEX-0A_#10}p(^W7u)`fh%HWWK>;DGB)WlO3`a1zFyVL!#xY9)77v^&L(xerGg@Q@xytfN67Q_9>LcW9Y) zl?GNl@-eSd5lJP`*QU{_$_r=l&;BYm;OR5`rpju`HDIxnzMT{9N=++YjFwX&#rNXg z3Ho7V*ZeGc%r(emfv}O6Rdv#fN)-1a#3r&`Mz3i}uf~PT9yaKqTj7C*S>=!%z8VV5PCP$t!UHws1D?K78x`@;F~#d3nqyK|iUaKP_Z9>sDpn$#bLWb@8;g@r^iTkFmG*nnAUQNhzWK zmf&CVN$(kFV{bsufC(&umY0zm*EtJ95y+T{3_sX7#`P=|3MG!#zEZ1(qnh=mC(5Ms z|Cjif+J_>^qB zRn{{{*042=821V2Ya3fUPvau3yR)v(J z;-?%;kEm#2K&g#9AM10IGJr2Ov%*O4jJ>@PGcyEPYfVwBz*!B#)PF{kVC5-ij5F0r zQ^CP}zvtz81O)~b!D`CJ8xg3F9If;3Kj@^ctwnog)x7&RE`E)A(HQ1&R!odL<)q2Q zw485Ubqa6rs7@z&Zd92l{(r!pn-`CEBn+!)>5?WW5C^-`3aJShn4FNwaHIN2 zA2)4oAhXhToz~Wu@fsNF;A&zNOAUl2AOtui=`6nrC{v*HiPwTKUOhsYS81|Vpkntx z5e=Tj#Dcy2M$>&$K!pYnKC^cH{sqk9vi<>Poshylj^qT;MvKiwiroQARQ|5^uAahF z;K*0n`r*Gb(FPB>w#n5ChLlWAMQ8dE;?45R@fx@YsH&;yft&mTRy((rb2yVqw1((S z$8e`44}9xMZ?MrRyO`=2+;;p}{E1)f3Cp85_&S@2^$Toj93WBlDe^{_@sAYc0}7Y# zTbJsY##p4nLofbUsK=h$?6YH~VMCuVkFa`nehn0Lhir3PsuBr~QQwfS{HX@i!_N z2^{#Rua4eUkT#?=;jRM)_{I(6;Wch4?oeo!LrZd8T=DZRLjtmBnZR;Eq-nRO0f1Nl7QnY} z-)bQ>RAKm(p5MNA{r6cJt3trTXW+aq;{-0w-y7BZ=GJ3ZdfGC*dzJgYgw!Vg4yhd? zYFbPn=8MXe!*bHo1;Cj__R-0q*^DsZ{r`C=%_JF0lzT_)T9$ff8iG(5dv`M5Rzj`R zT1)e=fu_hXG_Hl6j_tdt2JFmdI^PRlH?4UslhTJN4cqDMy#W+-{~6JGNzpt1^j~;u zi9OH>ihosmadScY=5dgMH#&Hh#oNAH2!d3b4Rs;-6%+celX=+o2+$7M zxMxBaL%-F^I>Q#qhPOVKyP4OJY&!s&%i1j*6pulLHMcqooc{6dn*p`vJ^Nwt$A@yq zmOaIfooxqiC;Acu)soY1KK*;SrgN=8sCxvk<3GNo3?h15>VbqDE*B^%5|F1jo0R4Rl{^Nn zHU(49QEpE7mbUz$`Mpc;q$${f)njg95fB*p6$6*C-@C*E_yMGtg*Uq{vx(TS017ZA zWHgO5>jUtE$AxNk<^6$D6fgl?%gnpwKmWX=bnpGXma9|ch4ZI0cfRbm!R+YZ3xCaj z*LPmYz3+?8KpfVf?-Ou5=Tg=U;N(yMU!bF|xz=|-6zgg9>gu<|Q#$`;K#g-s5mkL5f}4W|n%5{d zU1NuiDh0UwiF5%%#h*vocN=>;_E!16_n~Z?(U-$!d#Qxn4K@r~rTyGS__xqYR3Fe5 zYTomfF9emY0N)4-+st=^5bm4*6y~A-P2AWfJS8#O1V2fm(TXg(Q-kgdoeOQ)RF9?i z)wd#yePT=q?jO}x;OOBDlj-071mT= zzuu$n-u6N_r#^t3`|jQOh>^0=pLXe_>k}6uD*|nVe#{%SqMOEd+@`7CFuxObBowrG zz?YadV`j#9fi81%Y1ka>#^&4aPYGVUfBlwySik4kA7tU^g@ZtnnAzgMXb8IgZmlcO zv+dRE*SVlYqw4a=NSg9u)lq&?l)38yul(GM>WlWlKMdtul+!mQ>5J#Cx3{;09MALY ziQuiJHxh%e`KLjfLs&b0@T{XTAeSZJegq&5mxw!U1|8M{>=>eW5O&N-Ic;Xy&i|Rv zpNj-Jrx67jKOWB3F>PI`Ps3(?gl4TVG8Np6&Ibm$h-UX%D&Y&MbPsX})#oowCC5&sZ5l5)8KQ zUvUvLTsoK(zOU1sLnxu%S?J>epPZIST94$gCO%8RrE~ z!c9hcRT)(+aC0~Lc5xVYL>jBg1d}hwGnShy2OFnj)Fk7x(o;>_gt97FwO%s;m3IUU z+=@QuL9yqKiyL)($kCnin+rJ&amNM@?qo!Ab;>qBovGytIp3DK{XtPtG55A4+Qtjg zDOXb3J<4dEDS~B^6L#70-+cwCvkQGQE{A7(Ax3%97ryqdjs+0}E#Ul8@EvVdrXZ3x zMJp7pRz3MP;;?+!MAKN<9MSTYbBi5q7j9;4o_I@Z=R>|LM=W246fQI{+QcnPWO3iV z`YjmPOD6f;=$l@MOsV7QcUEk46jrslVErEtM1UR?-j~Igb6=0x1b=obDR~VBQJqpJ z&LD5vBiMG+cUS2Bz)w0OQ|AY>g2krWh-vlp<5w1MTW>Tns%Hw=|$2rElYlX1AQjbP?V zLCDT?g?Lf6;T9Aa1vitvcb8(yWFv(F{h3N(J8m9xcuUyKAS_gi{g(33={5ZLFv)+d zO*@6SH7OUo6nu%Q@#Ra%wqw@k{nNrWMILtaZm*fA!jltj>fy}ACMiSK5Ui)ei^3${ zK#!R58*Dj`S=!mz6}X2Z=9gB-ZM1Qwb1i1QT3aDK8jSQMzm>bHUv-4rg_#?37Rh$+ z?wFQgnXQdT)?+i-WXsnq-4X228ZOOZ25D}&tOFZmWb3SdA2bnPu*z!u6-OYzbJRnz z^X-n=3cmnjz2`D=w^^R~RWH?uU)|G|N6tPS$J`a}&ez`VYaR>Sey38|s1o57!ibun zH3Zk!KR3syZRH>l2xE<}+wPclF%&w9-xI6K?^S&{Y<)dzR-qvLZSx{h{ABn-G;NVW z=^^6kMHpn-WP0diV9EtYUCU(jqcd%83s`AJaakF;dKrm{i5VeJ8pjQdjLi9EX{#{E zO)xUOrPmiUJk03CZZ30&X^Xa(QyQh(LcV-zT={s|8l06JO;{`$pZYxA5CZXXVj(eF zO~D2A#l@)3`#PmD6$9~Ymte=hs_g6PX^xzE)_zMKoK&3eI?W6WH{SgmsX!IpQi4!F zM7F}_G6I5f43<_wkL<()hj{`JyeM}BrOi}NAoIiHgQdeZ_93fz?yE6K;2pU+QS}Xz zc)Tk4b&mY~zHANUyAGDgRgdtRp7o?1(&zijzbqRDIrxHu12P^{r{v7d&Bw@^lpc;4 zZDsAc-8m^~Nb$Lujtj|i=~Y^US!MMcs><3nW@wln&&ntT$9^viHXtpNSD)J0Ip!6_ zJnRt#4kkP0h4RF2I*~`(ML_r$=fwON5jZ;r^ZWiaGH=4F=XkuhxW0YJLS9#fiZ;Y^ zH6|Ys?!RavPkKFFML8WgQ`y<3*nszF?MhK)G|J7s6p8Kd5f8n%rM-P;uu&vN^Ry)t zsS~3?-jyk97z#XTn*jH!En1iZL*bnDn5z>s@RtW?6^E?8jrQyNs^>Cvp>1wfm?a7h zGt-X)4%v=6in6{$UsdSo0`Ou$gz{`pMshTE4K1MI<{uNrdRDg^0~NFOO~bQiFef%V zT#H(yFg{TLq>6I}C&N`agne#P)mE}Y=q0@>cBiS2A)eh+WWh8PHXKm}sTn6_IScwM z{ZdCF!4<$W`F;7S_q_vE-aQ&Vk<69hYeUtV?@M2;t5R6UTc#yJ6T@5=YpXHBog+ch z-P#?m+M2XE8Y#a&r;!s~!TI#J2?JWtdSYVH42BS%Y1$@`wSN+MRm-2$rm#FVmx|vC zpW=&W04u@LmMP!AnC7gvX|wXY?K)#4@+?M9d7!$7Zzlvf?`{qo3{baIfkF4EZ5r-F-$tbg@POVsE{i8X`G0Ki*z-bC9~-f>{yeBh};|<6Yh~Vfa40 zx8ph9nMIrozSckG(P?8xIn5xWVQy}2hc-Lcu;I%H)%!#90?PE)w!zaz5#cp*=HE}D z@vUo7)nntUaLQ{L#N>gKv*Y|wZBB5?t-BiMwMj^iChJiMC3P`%nALCM*%=nuSiZ2x z2VRw1mm`%Kp~QIh#%A?_y_CM{2^qy{ti0dPi)rR8DlGg=7Sa7aIZ|dEa%V?pEO0cY zn>jjh=kexxyzt~}RBMP-c-oD^e!bWZ5oYU~!^o zG@AGPolBlg3v`|lI}$+9JB5=|w5V8yynEa7ATFGhj{I%^|jOaw4` z*Bi8V-ZV49n8|YOL5pQ+(1`$e8J${Fx&Cg%Yns_s>q8rMhW4^TXX2E%ioB+j9;w2i3FL0=<1o6N(4+m>m# z^3b!J%ONUX4?wY^!Gia1R*mfEYGUp_mr`I7;M0%f!qKji9nU+dJ>@eDOj8A6c=aB9 ziCeQ5bXT3MYmA=^56L$PzYxB&4qf}Wu~0XI$(l8o3&V~vXu}Zq6j)X;cA-&7%%`x!Wxcl-p_j2?8ErLoojj%WcQ6n=sDyOLiF*>ho zsHarrfvci#NlvN(I@Wp?XA4kv#F(I7*Y)Q$vX!cIE9l|W2I^K zB@8C5{Q0PJvS>SNN%@Q;g+E!87p&3lM;OsD$M58#va%k6%9OMSlK+){e1WL-n%?|( z36t5r$DD?7?u;^z%AUT{Dd%0y2J1xvh;=Q9Wc2#V-4vx@i_8#uifFqqYb{xzb}B4| zy5tuaG+v$V|8cdbq@;^|P4P_c5HfZyR&bnhGT)?k`Zz0%D{JH7ZF6(ymF&x7-j6p^ zXTz6r#9@y&M-0FL=Y)xZsHmu=rnz7RpcW}n@V^(MvD2pkT8N>Rc^0yqm+L!M*dIp8 z)PTi<>`;byr6>(si9F^w+HsmgaL9k=CTzpt)$~A8=+>lED1D>-C3>Vs&sa?&oW3|^ zQIClkGLuzt@zK%AH_S+-)9EEOgtd@t)5P#|Et)sGEJt@AvtYo^uy?%P(z$89(jkC^2ig=0^EUYml7wFdvo)GDdD} zmyoCYq#URGfOV6 zL;-Lqn=$6vCb*UtE!X}r;;MVX?bSXB!kgAKK^5fWH9{YOSs-i8u+eML=0a>o46qu z68e=^?VamSIeFuvqDELyT7H^)I&_CV0&EGE87d1eM_W`aSLZi{(szYf=~EL3%YjjF zr2Aan-PLc2lNOL+t7#AG#xBq$2jcfa+S6)acwMfsPhz+7HoPsb@TiKFTsc+^b;}K2 zo>b$7weF!$q|lfMmDxuI*d)_Pp}2T$f3u*P^^ChHn-P*wj)y91!mn~pNM!x}cBF4l za!>Do`qnrfbg%$dd&Mr}jmUJkIa-CsVrIw2ZLETjIoxUw8|u%DNzZ|Jq0Iv(JR1xH zNkvNyTbVMl3ht4DzMU`1r;2IitJ78FK7@^JfVm7LSg&`=oYzaLf!xt(c|ujojwWRG zX*SJ0&eFa?4K3nWFS6U{&ff`1TrU)9D*>|&vcJil%}Lx`p@{BGf4Fv%+zw?d0=_U( z4#g12nt>~CBF!L>EO3&6Q?I7(tYbmA8Z0AFw{>^%VaV6;UNh!-#_GKt%%g^L{XF>E@PB?q%Nkl z1#Hs*VnZ)08lJ_S-)$*r=;`~2E_IXP03CxlM`2kSAaY3$Up>ov-usfV2D4VVrk@v4 z+H=9?ld-yc7SglM*>mS>x9aY+Fx)T1C{rF0QEy?c*Ty3C@y=Po9czA1YMuN{)GbL` zZK|cxrkrDqUabzNn)9Mi)0*x@ zWno;dof{{iJ@t#yza&IwLIgE+J24fFF`2!cLbw>AD&X7Y9LpLd)7+~~pK=jgBdFsj z=iXNhb4-p_7_*V48njW!_IyD1mcY@fodC{fQVBhJ&Ez{a4KyCs}os zG%epPJHJH}uU^^aNOa|Fl=9fU9Cyru3vHHr$|y86l#ic(y{j5#SzIPFxf%ne<2yTC z1&iJ*?yMzGiROK8EV3I>R9@a2E!UKg=2fSZ!>KOX9;f|IndmeEwx&iYB3TxnTlz>U1!@9ksP8_5Vp~*N?EG%TTsCz*7;fPkXyr_ zGhl9TIQxz!=l@swtr%kjeCsCxGwJ4F}E=)lWuNwr=E0G8D^uT zBU)in%9!@OVC{wP$%2!Of9I zSMwE;3Exo*>cRxh4X0?r13XzRa(?fzjFR)XCL3KK79@v%d8ly#aRW2yh7xVJr|f1u zr-3#Q9wQl+qxL3jj3xUT*xEC#t6ZFGWDQh)GllaWvoR6I&>rm_p+ZGXi1ZvaCkdLxG_0{j}ECUa%Y+|LRC zmHZ7y_>-o!Rz3&>f8!YJ>+4(kaIj4faa&AVRVgP%Q_GR%11;FH4P?YU_i&7G#+@~ z5Ke%Ot)tLC9JAiyMarYYRRnv6a#Ps-#n{8>DR=(T=S9L3zHs{HV%C1@m^fvOY6rq2 zPqzP2gdisy18MEOVVRG0U-U4v{e8uZQxo9jJI25yMD=-whh3Hc+%cqJf~+m0RwJ0Q z;UNTJFFlad*~LvYQ3XRf|86sxL8OHv^HXc)Pf+Wcxusgx_6P(bGn^U09&Xjm4F%to zha8HUCB^^QYAj}7FV|2eQ#732Fa#=5stasfASrk51}gzW2}2Gt$uw<|^-l>BW1*jZ zw;(M2azkHj1NWGx3rZ6=;{GR?a^F5;V(*m}+yB$vcSkjqcKzZw>Wm`T5GjHML_msE z0RbIU3{~kJkzNAQJ6J{#1VKvZRTD}`LJblKC{^h#AOsQwLP>zoLTDlPMCbkP_s4hF zx^=Dl$D1Wf6T>;rv(Mi9+55NKe}W^ClCKq5^h54%kq~MX^q%eu%VUI2`!vP&{UaxQ zruN=_1&s9jhIi_3P%rz{WJXA+FNu3Q0#Kf+4j^4Q^oitfdWe^4+HVpv4_<5mSioO| z4P;==1)&hmg8B_MAf^_QvL_gH>F_c3Zr~&z#6j=8jeDH0+7dW%6ZolU3npkRtW3;&>5Ym8k&S_?#^~(uXcRob+-4B1m56 zQ>}r&*e3hR%&RJ&d}-Fh69Djo;EH$C)}%yyApgb|RP~It?VkN9h~-9YXvbVB6_`r_ zZcxY2M0otW*T(3HEk0u;djNsTnge3ovd($*Hh!Pk$4V;zf_-pI&?B>UDS$&jUbzV< zxQSCcVC;M^>NP81@W?vNmiaa%GB{OlcQi0R?$1|rWc7JLb0Fkg^~i`uzqK^q)zg}e zsKcC{MSZ(rPTlDPPp!M^fy3FE1w=fjUwqG*v%97fx>a{R%`#QZyisr*2$l80BVcdGy|*L{>3E(LHLR%Rsb=zJH>UNC=&$ zq*-NaGB?cBHk#&Vxz+208GCfwg80ZdzBIt`qcwozUx;_B%Jj0-Tx}ExaECxWHCI+G!h+1l>$9lXm`VjemR{98U+~tKVUfJ<8bv}%7LpM$ zV(pxBo7sGVN7>=w+b*P5-VGw>#YGLa=g6r}06RX8|1X>s5WoWx$wjv_G&q%|L6yt( z=x@pz(GJZYK@y(x_JXQPd}U39pg*0cOC>ktwLY8q;t^cXs~tHBR-U zF4VSXfd$KC;xF%03w9c>QesYP%RHHS&f=5Tbm)ihLG$oG*vggbdUycIVZR`KDM58B z=*J!JvbDqvS z7~61vlewO}yz%jImKpM8jk9JvKvy@fJ403kBoo@3efwsxPm;r=f0PrV=BTsx3vu2w z+<=^G?>c8iikyZkI-Hl(SKI@43`$b8r5dGyJguI`BH8I@ z)%<(_Q!^qd6TR#HRG>mw2w0-29Ie1|s*7@iF&gr0Y<_5iq-i=z z2$-lya1(Qk`7_~N-w1xC+yY4MUqFVP|BWplj@zUV$+u1a9Vz*L5G7K4_#5+Ixd8tT zIQ=`=_U}L=+rNXX|6c`J0j>1^K=1uq*Zm*qy2MVTWWMlOfr~_(FhEK+wys5p-E>rz z2Z8n8FUD10caaSA8_hSKl+p3fwzs!8O)GzCP%?}Y_Isnos{DH5xcNrf+jYCf=$u}O(R(rhI~4i(^X8)E#<<)I*4FL>4k21vp@+Wj2m4k&`?6nF|K)-C4EWU$ zymBv9705FQrgOB^W8zFwyGSWrB%L6?uz>H54yI{CEGxD|Be(1Hg+=?G#XgkCpM;#szL)% z#<8;Z618Ir$pVo=TwJM(mNUoi|J)M{9~JX0Y1RE;qR!m;Rs2x@Bcep(3`!(a)o5w@ zFUaWk^e7(Q3j?f3l8-%*Ut(xzc=e&xeboXc zK((P-m@S#&ZYZg^^(K>*M@rDbkWyoGP5YrY!Npxv8PO7oZhNkuvi?DcCr2PVxk)bZ(t_^=gXq%Pz^B<5%3 zZ+_?^+U}{W>eW56(k9?KvayL5X#O2jiRy)_vsPfnqk2W>T%^AOgH;oLr-K$J{dfyk zXHuZ=P2{nR$qi=Qun&Ey?MxseJuD@v3JU)f-C@|a(8yzg#@epdj#QRQtH z>Gm!)(wP-c_$YF1e#7=wWa>C;Px*Uf9=pOD6~2hpM&cTr=&+t5QnmJgZw^DXPHhYi zDQXDvPI74NRdKEd6O)tm4qUG3WL9e}vu@rW4p88|Bz;f)Kndit+o2927^j*~^!LkA zQ}nZQBh|NpOjB7K{ml0fpkqAzSq%PbIERZjP#(5KT$&GMKBH>^Fl}Stvj1zy-uG{U z<|MQUOua~rW|ZUrM0y_9RZMu;xZj;F<^NgNhRG8+eQl5yLMt^}CmOUJ-)K+Z!%r)3 z%=cI$PaL_n+ca)=kWn_92$c&9ZQzLrv6pO~(OSpuQSq--`xT#zxhE{XMcR0IhzSqt2n&FiV^Rh^|)Kexp|;odBj z`XTh#MwwOQ7HTm$3yD<@++QE9qvVFlIFgfDF)syx3@r3sX*U0`foHM4{RcT#O4d|Q zF|dOi9^IaB?dUh!Wgd~hzbs+Eg@+x&0MjQnE>70#i-~DXVw`|WJg+VE z3}o%(J&+(`P_j7=zaq~#>VUWmZCIb)$k^IUN#T}SA&psvva{dpwZJUn!O z!=sbrBL)}SSa>yq!F+TQ3!!Nd)xxVr68#N^yzXd0kKet*%EME+0GugE{OcI(-X>_d z@W`U&kUD4(KAOZi%-H38NX(3I4O?Ek96vcFdPVziAAp!Tp;n5mBo(nE0+qR0Zb z+}!%Youl*Rkquc!o%1^Su6q$cv##HDP{*E3E|y}T%$43l-v`GbSf5Ijtsm<{07oLN zB#QzDUUrB0I~_%?q1nUm`AA7X{aP4nDF?)izueg{F%<3T3HEn#YFIZQyK&2hCiw>b z@fk8Y)GP&{7?Ya!@81uhuO!Ql3M9$@4nPQghvgZ&waO^q*kbX;XFe1{x*}9fg2l>g z3jzNX=J=nI7C!=^P&2~cnjL}ue^%R5z<~?>>+f9W7hIZHtnG*P*=NtTFz2KTb=cIW2*W@@BZJ?44pz`QD-<12UlZ<|c4 z{KmniS&{<-uHRcGc%ZsL&G}wEk#D%wJFRp=X0L-@gRx2S*zMR@pCzP~tx~TnU;8#k zw@$%)lY$u;=Cos?y0=HH%~$)rfjzx8_9V3?lcf|H6RXop4bSE{ZBUF^tjyHZ@{pNj zURxEnLwjIE+I?p3NVUyIo~5xW-;Ewc(qd7bn+2+73ouCtdjzcP(^gL) z$fsp(4L$(keWb>{b_K9+NrtSmLwCAYfXUCEx_%e|o@*SpT=u##B*DcdB3k#w*hIYm zh#5}6-OyQT8dV5hnHjCEBb1`Egh)qfGPYh&uYueic)YLa<4Y~#tS{>Ur*aQFjjx$j z-$F6fsCvYUiaoL&aUgqZ4Y6H1$euAhM@iLMtnC;vpQnh(xx;ICkL5K+YT;6YSBBk( z%tjLp{2jHzOUDt8{(J3;lDbJie73w{BZu0_8SzYGs39S2Hl<;`Q@n0_dSmGO_jCk$ zT-iA{*hI2!wpn3RJL&Y*fIey@6G#MT)UJAll4yxq&X%a1P`2G}R&sSI1_V*wyy4Sj zP$e)hJv;IsSTt1&z)3Hz$LMFjuj*5)n%sH-G;#z|`F3_ko05Rwn~{kLpcu(c_@B0% zXvhS(7g}mlGPbjvv4d6rNPU+DE2JhpaUuvZlQU0Sel!{r2N__;66Lznv)W|mYgojp zZ-`o~8eE+&GydN4(P!cFu)3mbyubF#vh{}hu4M?X#z*${b4=1&ikQ}-e7RSaya|W( zRr&e`RaDqUtuEf9%>7|-Z?kc!uRpJhgNwqr%gp|Inz}xqK$9G%TmP(^&#m|QeCSwB zou{LtBYJGkx(?`FT!k~S1XfOXJ#OXOb2oU0ls4F3{t*QXv`@rfFiu@^3JOm9+s6za zKh|oBVBkHxdgPBeW`PN*mR?vT8ciEL*8eCE$lK$lvW(5=o*z42$H>=%GhTD5=d3ql zZtyc5+#YzQ3W@farvp*~X;cA29KPYgeW@WICut@%lR_oMi#aEeDx{jxk_>viCVtZ9 zzAn{cyG36POV~P?1fmC6%KJ@%54J3lrYXC+Y9~rVx_R@3qsK0hFi|%mB+hvt+6k5j zmJmWx_*AU4O~;AZ|Pd)Yv$MW_eVCV0t-&=b|K0`gv+f%>B1n zHha{_Ck4tZ^@Y(&sP75>d>8Myg6a?f%`qMSI=Vz(>8^JM1u4>3GzTN8;L0ZxXIbnV zyprO698p`k4B;N08%wat5n8NTeqcT?-T@0}_BG%9UWM@V8ngV6RvR?eptJWma44{& zM`kr##i_p}biPfB0C~-+13rE*2v1qhew}eJmW4-D7+Gh;sv&G+#!Muvp{6L{^uoEn zUVO_f<%ywvlCNLEgdQBwn}k6EAueH2oJ3(dQ5`v{FyEb#d_rpfDSF(E631Vs$)Udg z6<`754Cx<6ett@BruaVu&7ox_d3X^ja zld5}5)p5N3Mq*-OtiTeiY38EkTuD)`n?E))GZRn%c!#fvmzAd941kgfG@@ZZdY}2H z^6%iuvm$z5TVu{vQjqnIR4l#g?>`f2dG~8@1lhjZIUf?#)Bsu5t^H|{Z91rm0a~oD zUY4G#xv_2(UYA3O7TeZGCFSs&ghE2uLT6AUHOs@B@G9EjocuF=LVlF2Bag2((sB=ggR-w=h9 zpfnTL=TN^K@HLzs53cID`|4;>Vc`{2!@3Wk%2^!WoxbLiMX4Sow8kXN&nBxw_`Dhv zfb>oszyv6T!>fQt`BIg|zy0Bx#nRp^{%udM&(h5brMK`rn+nh^AWYN?_A1+|N}gyx zsMVlI7(X6T;UoBj4E4#Pb`0w&ro)@PM-_9?4iD;Xo)An=3Xa^@hvT!D^Bp!#);sbi zrFwc#pe8G=7+d}_3n|z^_nruledWTBqK$QElLfR9AcIQfTr@H3BvM^I-v4b?(ALZD zowman^ytW1b8S=1E?HbQWbs23OxcyL(ejdP}!nzv~G`Pz8)GJOQkis@|3&^4@B|n?l4y6joW`G`EI`xOj?~m@@ zyLU>$_}&-D%gWd{`T0gAmX>LlYKz!UUNy0u`W99|D$enmKSqw6dwl9qY<}70JAa%y zQ>mL|zQSPKbVy3nk@@q{neB-6?u}b?g71pbJV)fV+Wf>268j+T0`!`0=2tWrOlUCQ zn8RAx^SRd(C+d9V!nXgI45c!T|NfiUjT`-yj@m#g(9s{zAxB`ShL}Vi%Rgjeb8%q@ z;iw#^U(owuXFEFE{oPZ7h6?RNIEQTy$_mH?xPDHjhgI!^zTVvMI14|YPoyY5^6izM zzspAZ5u^Vm2sy9}#E^f!rnWBu=@LI*r?`HZSiqadm;V<&yd9g#PW`|^-M7%~V9o|9 z9?|^v{LsAnT(_p2!`Hrdfrh^>|E@lLX7V{E);0B`Q}VG+{}2@=m7=emQYMR>8J)q4 zXHyRn%}F72CF>M9bklT*f8sJ{se^Tt&%9)+m$u0;-7i4fc(6FaC%{$MIn6}I>gMsK zE>qxl#r7AP#CAPi36+fD=DWtAshQR)kB$*{4U$sA7;RdOKc=;4M>ZOcuXLII5Fv_F zm${D51t&bY$J^x8lO*r)9ZH^1(glxK8$W*hUPfk+cCbfv>v*>*HcDUc#sK+6Wk~CK zu~UDzP(~zxNbVI_{&|+pXEVpcMju< zoA2o}ky_zCg*vseVcnab&LxiQ+?*2eY=tOoCgofn3h^V zmemte0(TPR34L$g8gID1N|m#F&aO!UW%ufd`UGe__HZ+}rPCeBG1>|>u=qc%=*fwiGciuE+AFHtU}D34%RJF#wAXN6p?`1I+PycfX= z8%iHItAD%nB;QDMpLN*UDcy8wkkf1D@xuGDA#^qEK6PgH2x1T2zNeI<7tbsIAkAvs zHv{%zuxq!kNZ`utT4-a=c}68&Xz)%LREFK~&p-dvdie0s-quCG zt+8-<-#NdXu*Q(Me+bjUA`COv>Ki&$g)ST1L@0ftS1`dC9G)K4aJ*$dJdNy1xw$I} zGVtg)|Kn1{>I|(jgHgfXGmTq67>EUDm6;56To2!#snot29V8lP4QuV{g$M$=L?MJFJ;$WX3|UsD zn)H{fP<_BMY#Cqs8f9DQ@E&r*+z4!ajB}0*Xt-3Nwhj@O&4migLg5!TV&f}4C6XM% zwrQm}?pOD{I~oI@np@)zYYFZamo+D?-XxA-V_gUC^}wvIJ9nIynX#^n)k+PUy!1_u z)Ypw8gc2ba|IkX>no!ffNWd7soBdrlUP<#9u~=TmaM<`4;%k6u4S4!r%g=ej0 zTU#4ubMU>4uHo3&9&P$XYkl zDqlf7Z0{t^DxtE3N}jC+{-M6m5W9C|pq z9Zn%f56|tmyJHpX9_oXVA_L!PjleXGnK7kL6U|`x`9NDU^Se8Yg0T;(p}ChqySo&w z<43#l7JGAewDgIRAZ6O{%4w?X(|Zf5(Tj_C+K*`FyHfBFZ1N*;NG$@>N|3bi2*$2< z(iW9i1Q5pNub<6_*!nNXmuAGgZ{@sYoMg1ikQ_V6*QqrGo&YFGO0~X|)`$otkqIUh z%SmuVpPvfXC>9~~n()PCEVcJy@76K#BM@8+$hGj@35_oQlP!;QD6piDFOR?-b3RhF z%Rnd_tS|_V1{+>FmDojM(OAf}FIUwVNyLVxDf^SlAQF})mU+N`&G)({eFVDVN#W7r zIVx6{P8FXsGZ6vicnU=ByMhXw#~XrAfClIDq-Mf@;sR__e-AITv|iH)?F!f91{w5b z_g~|=DRH)C9`SNo%<(s+`$ z$Qm$OGOnDQl-YT!dh3=-Xyo>%F4Evzse_tM8gqX>Ou}kAB7Rv;*HFUZqmhzY$fg-f zk4ZII)%;WG-(L1OItD2KPW9>g17z@}(2Hc<2ueElRg~R!#)VvB(EI)ZH^$S}riONu zGej5`!S-79!23sl)&NyY%w2u&@RpQ4x`%@sJb8k2~Pe>@CITbOd51CbCzL-iWee z>B>wQ$5IR+_b(0~TTIqJ_z)L&QIqs*)ods4eh77b%Huq0RFmP4=nhKLY9vC#We<4N zoVilIc5?PS^n=xu8YrOcT@*4RO=(|}jm^&FzF%0(GKpyTUL@g6*ZAT8OdH zxy<+9VY+xRM?*tP79*n$GR|>_)C}8Y)A<1>n)C8FiL` zwR1+z1YP#Cn+e<0y@Bg3t)GVQCCf{c*@u2`2Zo*_;kIJKnpVcNe8EYjGZ7=&QB`5T zIeFlneCR6|FixbAH}vZk=$n2hLJnXjFb7s%3W=K89fbmItqK)XrOoIBuq+V6}?Y8#zs05&@yV$bUPzc1QP?ivs;MHU;4kC{%8=d5p z+ja@Oo0O7fs`jd~yu3V0oMi&DvGL|TsO=u}^;Cqad8Yy*3FQlPr>c5=lV(>+`l~Ag zYos^Bx>HmQlJ+q%b}UUP5lu%0n+ zN@>+O|2@IL6i*S|Nqx62Y5Fu+P2$Do`magR>_W2L6#O53>Jg^MkgZMaN2}&}fXE zbV{=CoxI3Kx#{qOwS>T7xTzNu1|yT#4NTwXfphz#LIjdnfx{Ir* zOY($7J?HQH;~0NmEQN(U19s;dUgPu&Lt-FAUY*5*IE|!PGC`q1!zbgF*A3IHWWnOi zXdP0+s-Uobz1iA)9&CF<|AehQiQApZ*l^stN*!@e+pd}43U=Ems)$v==A1c7H)@{B zHAZfLC&j6zHPH_Mm65tN#wd`2U6ySOJNAQ-AZc`|K7T}6@7}j>J+v(1#O3I#n>s4~ zWPZZsy6z-lL2{4cylclhKkVKL5wNW;HM_iP`y-20B|(XC`?Ote3k+jW?prmZ?KRP<5{Z`wo)@{GXQjA zbw~Va5Rre=)yS@phntV%%!l`~^Jh~%zZpyyN5oRb^i1x?XpJ~tckUHgcjhmyY4F-p zTgJ>C5w;oE%so`pIR=G3wNy-2r{wSB=_Lzqbvtd#Eh@{#`Rp&qWc{rWQNg&o6X*c< z+Ylf%Hs}DXr8!x2HhyT4{aDO^S^1Re@_3CQx0UomYmDFfQ3?(skyZ2LOXzsLB79^W zm?)pAL@6hz{A7$f;X%_^o5oR(JwN~A6t=0zgkOd00B&patHd4n z=DpYd*FJpuF7R+^vby`in&)rtDlI)f{RmS0C8}(iUlSchq0eiJS1-u_vIy8dug$T% zo_CHvkz`;cg@64j#|JW?#D0O;tPck-DL(mikvooP>msn!b20XoThq$_+5m5HC0oVQ z#D~RiFk&Z8gYg{9!0=c1|M8ig9|at`F*Kt2L@ZZ52za>m%^~j|_(9d|FSD@*`fp0GgNzJeuoePd&$7 za=&XNi7arPH;F8#IAUpJR`j#IB(U<3A3QNT$5OFU+C`hU4AEdwmm|WETOS(PH8_SY zw@6pJwgkEjRK}kvw5c~c&9ynKCz^(jY^hK^ewuAMryvqiQ zKRM!XA<6(BFnmy^8tav0HlGnQchl{kZX)C_Xs|g1n)DQPTY&TKm3LxzG-<09Uj6AI z#1XecZ|lDI&y@?OS9%~^Dd0EIo?B%0mPh4(C7OAs&5xh@mB_~?_XvGI@y!S7L#O)6 zZdUvVc-=8d|7WgF|8t{U`S$L6{FeUa(>MNUlfWit)*yNM&yzWa$nw$ke1i$)lutxaj>1 za#+!QL?Cq!N88vQiX0H%AQZ0wRb1g#hb1xh023YQw_jN?lls0wcADOzPPl?Pkf9vW z%~jUr=ENG=BeRP|nEO`4B>iH2Rrzu0wNz94;k_HR0ZY=ev=Qw7a^Nl6E}zJJYxk#H zi|_CZ^1!4rL09RD$cF4s2R)mA>BFjXCQKk=O5dVl(@?_Z(;Y>oTPA@yb(rBjr%~zH zwHSv>aYve@RrhL=BK$q`T2fqekm+yMAj|RM%Eplp{au7m7s|GH|9JPM*~@+q#;HdPVY;*Aq4E02 z0M*F1FhijSWrEP8L@UgV^%sBMd|fEvR`A0|CW_+-?R zsi4}6C?lAPH;wQVTPoWz=sW~XYO6+4fWNy;IB{E`!N|6sSv!AGatsO zQaeXEbQl}0sn1kG4csvn@33sa+ekbdexdB z%l5xt!O40jX>+O>MKl#&De>n^SYMCRVyFf6_e(`C8qCWj^e!!rER9w8yV?5NTcxen zSX)}}T~TexB$t5se<~Z7XYb~GOe~YY;uro9BP`xcWVlazUk_qbh;yl)Crcwh0RxVBUkO>F9brP*aZvl z)B`Ms!~EY=)#>%MU0|x4^-b4U&)uHoB#Db(dj}>Xw?9P1W30|z)5q=7_}rE#9eFo! z`7aLp2jr=Zqr>aBoR;AewJm!Vx)-6)i15vCBD|417k0F@9R)3>!AB&H9DboivW;|a zY}fl_p`rP1z_9N&w+)AlrL9yGnYSlnFScDb+X`Cd;TpMhh_S*+cmH18CBHUalBu?F z87o8WS=~8&%*Vekd`RABA~n-*nO2Zb@2`8MtTc8+HbXx`+# z)o);6K(g&lakmCmIg+3rRO8?sbCi?g!DQn@;K?80fY~<7Z#UW9ON-FnKLt9!KuNjF zd|6E191R69B)8GtNkE3Bb$DO}2*7Ks9i3Z*a_0_pygh(+2xJ#1fGxTp2%d!7gB$;qp@x2R_R7!zk+5?$TQl*RDyO^ZWK2 z_NGypiu#Z)9?LE>&_(6yQFUU3hLozhwsu-%iGG?yvPAtz<1Rx@9z1Ro8R=`2TFJVN zzO0{9pKSH)t0N)1YaP{&mKdFA9<@l~Va|nyja~P(<|Df;*ucd`9?sJI^89w^W|{d9 z-IX7o+wLBO8mi?Dmn0+7|3H1mSGEnqdSELJEvWBiCC>WVffFmEmR`n^(%~3eSJ#3& zy>if-_P=TA*5Dek2ukGQXyj;g)Mke+EMP^hndZOjwzz+y)7ijt+{29#VmN9wlO~iC z>iK9HkGH5ouU{Rj9y1ETn4?0X*#C6v6VSldMmJZ}34sfpYcI5D)bDmKG&jRQ;&Sqk zXpNgAT$@{*ymg;<#-(d?B>LepTu+xatnlvV0haCIfj9ZO@%vZ~%@Fu)9AxdNO@_gmxHEt<}~NU5beeyT&WY?UL!4Nw>`J_v|&TVPO2AxN-1I zH3e4~T$x>Qv-5kMc4%d#AI*4%Brfr|>wFI&npIcO-aBdHeh>PFNNcSiG-Bh}{ypuc z;yr`;=jxYGwPtAR&^G~J^p9P?unG^ykMIT^=+`)g7OU6Las9#bTPXC>ySSY3datHy z_ZI@&d#wRckB(yOJFkbF@E;eQ99FJLjhLtuo|aXz(OZ}l580~T?6wWdTzhUfsw?j| zS#yYSKv%1qG)CzX?TW4IAKfvVt6qT|?AB&RyBzFaz;0Q@d02L;x?F#D9geT7atr5j zbhxm-!+x@%yx}HHw5{C;i)^H|wrkmU9Ibw07Z)MlgW?kB&tyC$1?qT?xs~ejS#oG@ z(oD{|#(j{m#bdA3ppf>D@xM;${!14T%N(WVV!`l&|&0q-p2^pTBTxXl{- zXbx74$!SiY#&r_YUQo#)5sH2Z$-E|9Qe7=%WeX0EHws`LEb;DPy@(Mz;cC{3%X81H zVXmD#XZhALK4p{O_RKWB1?k60?Y5`;x5a-OZx|Z}REHacACC7D-}{kfJF1Ku*;O&OFEDj*4^Zu9zEs#6%|!0nOv;BE?P| z44Y`0^!+V<7_%o(q^H_8*v5SfZk0UIJ%Y53dU@CQ_);M_&xmn>InDj0xNFo8RpZ{{ zhBt*x%a)bo(kMN=9Y3MN=TQp}As;0+WL#c_Y$lk?KeSnb0{l`1Yqz&hz1r=R^2jaG(EhQ#;^Ras)Lr-6I!vh*>Y-N&_ zJz-TZD@)?s=JNf%KG3q4nU;sbxi{gWPY?}-l=v^D=5m1 z!wpmRPg96ZM4Fjxd7c0*LiS_&on()^jsgt7 zLQmU|H<5_2zg{9Hvd8yMm4xH|vu$vTXFE)dLEod&@We?t9L0|sDG^QWohQvxq6H%# zRn$JzJ)3o182Z}bf)-`?v4DPqP3Q*Tg-CdLS5yK`L~SA(8`|!ReeE-}yW7}kQ&-u( z7q~KP^Q|X|78x089?@ib%*VF|=+NngvfkS9$q8=f@oB$hWMp>gz)Jebb4WymmBDqP zZmH|+X-fN{Jb1MKo|or9EUIawTQm33?z@ZYdkigNg$BIQ{4DM}t_H>BPyTMxlYEt4 zudx?EPJ+NR^TM=tf{&3T#D)O~_#FGut7^$JB;atDkv$0%A?o>)mj*CFQ z@&meh4k4HXBf5ZqgvD&4tCknmypVY?YGlEkAc>XJYr^)Rv?c$1P!_p5l^?~*NT$BraLxkLoj1)?8feGrKdWRzD)L$ z7nMxt2i{R5NK5WItig6@&YndgE$@ME#_<%t^ya@E8Qz0Zf6vf4?nLI=J+j zZR7`_(Y*WdqraWb`*xZsN&fceqA4--Z7Hn&P4Bk&j9iEj(D>9i=kl-c%@aKOMwfx~ zh2asw5qMXL>bp;RwIVjK7Vqnn<3~)@Y)f8pIk5+y$t-TQ6#AAZE~(dEF!snqx_B_D zzg-x%SnStJ6;CD3CyVzY($mcVH~Y?KDNUWYSWc3rBhA8O=Wl@_IZ~`|m)yn58`=;q z_S(hOfMfd+JB&uH4&9*?6}7vB=HJ>{Tg65elln{xpA!Ll!Wl}9mSs28OVj4ijAI{4 z1iv0%5|=1JWA#k4g0Mp}eVbU3ANYhaTHE z`_O~o{R)#kV#pD#0dqAOA8up$*;XIIou^wtH4Sf2hgL&QeuLazLvskoRX=^L0TGrk zyU^x&WjimEZsEGj{nH6pRS%FOq%Y9U<3e;F+ucQs9z#5-nb*>a73NA4EM9H;&P9sarG1dVm8npZlC*BFa~ z#~Y0K6b|z1SGK`B2a0IK^i!`?h5Nkp8R$S%O#J2jozW(Kpw`+fd@o1iqRP0%zFo!c zmZPi0Pow6|;OSm9jg|^Wd;oT5D{Q&9(;#cuBxJuJBR1i!$}~?D)BVP;Byl#EOB}Xy z*#TPgnMomj;hQ%rV*TqJcwruR16NSR%P(I;{rTPT3gFvykY9PNY}pIEquKJqt%9pv zg2lT69qm#7fJ6BOZVuW{yr*Ro0)HWIY>$xBK890fa0)g4KfL}#SLJinH!`mIetG6+ zuDM$OPhUkgm*>+H0;%V?I+OcZk!X?hGhPML6Kgav?P^y3E0E7{Cq+U@o?RoRCn+^6 zS)!}*oiF^^m5LkMN@HVM9uTWV`gw`}fOJ}=*)w3G`e838{?rBV6lD@=?)n9Gek!cs z^ogT~JG*)YU?(ZFu7me-w|~!ona>Aa<1L+$oFNo8D^LEs8ynj|71$CD_;&1K7uZWL zNtup?${L%QT@t@=o6w_i)lr#W>!C~GdBk`z$Us(!p9A>A6ZKg5(Ppd2y|Uo|m zh3gg&;jT&te84vUBW|uV*({joklfXyF$O($SNO_*-Aqo}@lZm`k3`qZ)aHBnjh)S{ zm8U9@+S$VYv7wS_kIL8r&VnoC(rIduS{BmipT?8`U5wcyI{#|jjk@!BV0-yLz?$Fw ze&g~-_sqYx!xn$o?#i#bJum*(m)PDM|DXOa_VPD1Li|6(>0iD1t3T%d1Wrg|9x^|p VgYq%w{K*!e`+E0~e?58m-vC=jL`eVu literal 0 HcmV?d00001 diff --git a/images/rhel9_disk-layout-2.png b/images/rhel9_disk-layout-2.png new file mode 100644 index 0000000000000000000000000000000000000000..c1f721541eeae3aed2680c139b99efd85316063e GIT binary patch literal 83120 zcmc$`by!th`!%{j1Vp+Uq`MpGM!G|~ySuwVL`q6PK%}Il8w3PIx&=hK8wAeWKCjR3 zyyv>kIscsVee2>Wo9J_Fhu%jpTk>t89%0f5SSTONn6O`oZAx zahu!dB5MGL(9-5v`g=E5zh4WBl**UpnVzPCIlVC?!+z#=)#U-{bS!J_p>Qy;aM5iC z2#Dxnu*M;9QiH!AM#rml&ZqU2r}Q7kp)E^6$U~z49@eO9Y6zl|e~(K98mySV@5$o+ z|M1`h(GFQ3?;Y-M`j=H4ui;$ zB4DFXNnjzr_gC(jB~^P_1CO7Ei|5DJtlUgDzMHZ=K76=8M1+Gqja@HH5*GLKyM5V6 zV<1e88PmCYzBXTPXKiN}MkROJjz=FO2^-i!W?*2z7j*xeA{o)p|5)lxTH3FK{UxuJ zzDN`z6);$3;cGZ%X6932$L66Sc`k15F2_cRvZ?NU&%&xIv~(5⁡*&4-(e}F3rFj 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 Z))fa|xmxCK;0_Lf7+o~gN9*2*`44n?6T$!h literal 0 HcmV?d00001 diff --git a/images/rhel9_disk-layout-3.png b/images/rhel9_disk-layout-3.png new file mode 100644 index 0000000000000000000000000000000000000000..b92297f7b795d0461521ad07f3d76fa864d918a6 GIT binary patch literal 74483 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 literal 0 HcmV?d00001 From a6ab9682a7f3b7d5b6a803a3bdcac4ff0399792e Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Fri, 16 Dec 2022 21:10:15 +0100 Subject: [PATCH 50/55] added rhel9 instructions to release notes --- docs/release-notes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 919747a0..a475518e 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -13,6 +13,9 @@ * [Added support for remote execution (execute playbooks on your laptop)](remote-execution.md) * Added `install_config_capabilities` configuration * Added Gandi as a DNS provider + * [Added instructions for RHEL9 image creation](hetzner_rhel9.md) + +>>>>>>> 86d562a (added rhel9 instructions to release notes) ## 2022-06-19 From a3bc29ed89c1de8ea0450f86fd9ff39717799efb Mon Sep 17 00:00:00 2001 From: Steffen Froemer Date: Wed, 14 Dec 2022 08:29:16 +0100 Subject: [PATCH 51/55] Changes according to ansible-lint recommendations;solve package dependencies during playbook run --- .../tasks/prepare-host-RedHat-9.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-9.yml b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-9.yml index a4e15736..826b355f 100644 --- a/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-9.yml +++ b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-9.yml @@ -1,6 +1,6 @@ --- - name: Installing KVM Packages - yum: + ansible.builtin.yum: name: - "@virtualization-hypervisor" - "@virtualization-client" @@ -8,10 +8,16 @@ - "@virtualization-tools" state: present +- name: Installing playbook dependencies + ansible.builtin.package: + name: python3-lxml + state: present + - name: Upgrade all packages - yum: + ansible.builtin.yum: name: '*' state: latest + update_only: true register: update - name: Check if new kernel has been installed and local execution @@ -24,7 +30,7 @@ - skip_ansible_lint - name: Allow NFS traffic from VM's to Host - firewalld: + ansible.posix.firewalld: zone: libvirt state: enabled permanent: yes @@ -36,7 +42,7 @@ notify: 'reload firewalld' - name: Allow OpenShift traffic from VM's to Host - firewalld: + ansible.posix.firewalld: zone: libvirt state: enabled permanent: yes @@ -49,7 +55,7 @@ notify: 'reload firewalld' - name: Allow OpenShift traffic from public to Host - firewalld: + ansible.posix.firewalld: zone: public state: enabled permanent: yes @@ -61,4 +67,4 @@ notify: 'reload firewalld' - name: firewalld reload - command: firewall-cmd --reload + ansible.builtin.command: firewall-cmd --reload From db21f2e242faa58fa9bc40cb5b2cc8c4d29bc754 Mon Sep 17 00:00:00 2001 From: Steffen Froemer Date: Wed, 14 Dec 2022 08:30:11 +0100 Subject: [PATCH 52/55] RHEL9 compatibility --- .../tasks/post-install-storage-nfs.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ansible/roles/openshift-4-cluster/tasks/post-install-storage-nfs.yml b/ansible/roles/openshift-4-cluster/tasks/post-install-storage-nfs.yml index 0792c000..972f3f1d 100644 --- a/ansible/roles/openshift-4-cluster/tasks/post-install-storage-nfs.yml +++ b/ansible/roles/openshift-4-cluster/tasks/post-install-storage-nfs.yml @@ -19,19 +19,19 @@ - name: Set nfs_server ansible.builtin.set_fact: nfs_server: "nfs-server" - when: ansible_os_family == "RedHat" and ansible_distribution_major_version == '8' + when: ansible_os_family == "RedHat" and ansible_distribution_major_version >= '8' - name: Ensure nfs is running. ansible.builtin.service: name: "{{ nfs_server }}" state: started - enabled: yes + enabled: true - name: Add nfs exports to /etc/exports ansible.builtin.blockinfile: path: /etc/exports - backup: yes - create: yes + backup: true + create: true marker: "# {mark} OpenShift cluster - {{ cluster_name }}" owner: root group: root From 89fd54a76efa9f661a4521ed916e82265746a58c Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Fri, 16 Dec 2022 21:15:58 +0100 Subject: [PATCH 53/55] Add Rocky Linux 9 support --- .../tasks/prepare-host-Rocky-9.yml | 73 +++++++++++++++++++ docs/release-notes.md | 3 +- 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 ansible/roles/openshift-4-cluster/tasks/prepare-host-Rocky-9.yml diff --git a/ansible/roles/openshift-4-cluster/tasks/prepare-host-Rocky-9.yml b/ansible/roles/openshift-4-cluster/tasks/prepare-host-Rocky-9.yml new file mode 100644 index 00000000..fad10b9b --- /dev/null +++ b/ansible/roles/openshift-4-cluster/tasks/prepare-host-Rocky-9.yml @@ -0,0 +1,73 @@ +--- +- name: Installing KVM Packages + ansible.builtin.package: + name: + - "@virtualization-hypervisor" + - "@virtualization-client" + - "@virtualization-platform" + - "@virtualization-tools" + # ansible virt need lxml + - python3-lxml + - firewalld + state: present + +- name: Upgrade all packages + ansible.builtin.package: + name: '*' + state: latest + register: update + +- name: Check if new kernel has been installed and local execution + ansible.builtin.set_fact: + hetzner_ocp4_prepare_host_reboot_needed: true + when: + - update.changed + - update.results | select('match','Installed:.*kernel.*') | length > 0 + tags: + - skip_ansible_lint + +- name: Enable & Start firewalld + ansible.builtin.service: + name: firewalld + state: started + enabled: true + +- name: Allow NFS traffic from VM's to Host + ansible.posix.firewalld: + zone: libvirt + state: enabled + permanent: yes + service: "{{ item }}" + with_items: + - nfs + - mountd + - rpc-bind + notify: 'reload firewalld' + +- name: Allow OpenShift traffic from VM's to Host + ansible.posix.firewalld: + zone: libvirt + state: enabled + permanent: yes + port: "{{ item }}" + with_items: + - 80/tcp + - 443/tcp + - 6443/tcp + - 22623/tcp + notify: 'reload firewalld' + +- name: Allow OpenShift traffic from public to Host + ansible.posix.firewalld: + zone: public + state: enabled + permanent: yes + port: "{{ item }}" + with_items: + - 80/tcp + - 443/tcp + - 6443/tcp + notify: 'reload firewalld' + +- name: firewalld reload + ansible.builtin.command: firewall-cmd --reload diff --git a/docs/release-notes.md b/docs/release-notes.md index a475518e..4278488d 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -14,8 +14,9 @@ * Added `install_config_capabilities` configuration * Added Gandi as a DNS provider * [Added instructions for RHEL9 image creation](hetzner_rhel9.md) + * Added Rocky Linux 9 support ->>>>>>> 86d562a (added rhel9 instructions to release notes) +>>>>>>> dc2f464 (Add Rocky Linux 9 support) ## 2022-06-19 From a7e1b56aefef34a9e5e27d248953ae7e4369860c Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Fri, 16 Dec 2022 21:22:25 +0100 Subject: [PATCH 54/55] Added RHEL 9 subscription handling --- .../tasks/prepare-host-RedHat-9.yml | 13 +++++++++++++ .../tasks/prepare-host-RedHat-entitlement.yml | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-9.yml b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-9.yml index 826b355f..d06301eb 100644 --- a/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-9.yml +++ b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-9.yml @@ -1,4 +1,17 @@ --- +- name: Handle Red Hat Entitlement + ansible.builtin.include_tasks: prepare-host-RedHat-entitlement.yml + vars: + rhsm_repository: + - rhel-9-for-x86_64-baseos-rpms + - rhel-9-for-x86_64-appstream-rpms + - rhel-9-for-x86_64-highavailability-rpms + - ansible-automation-platform-2.3-for-rhel-9-x86_64-rpms + when: + - redhat_subscription_activationkey is defined + - redhat_subscription_org_id is defined + - redhat_subscription_pool is defined + - name: Installing KVM Packages ansible.builtin.yum: name: diff --git a/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-entitlement.yml b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-entitlement.yml index 712ebca8..64919d41 100644 --- a/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-entitlement.yml +++ b/ansible/roles/openshift-4-cluster/tasks/prepare-host-RedHat-entitlement.yml @@ -11,7 +11,7 @@ name: '*' state: disabled -- name: Enable repos for RHEL 8 +- name: Enable repos for RHEL rhsm_repository: name: "{{ rhsm_repository }}" state: enabled From 6890d828c6bda56c4e19a2c9b0df3531949dd858 Mon Sep 17 00:00:00 2001 From: Robert Bohne Date: Sat, 17 Dec 2022 06:39:43 +0100 Subject: [PATCH 55/55] Prepare release 2022-12-17 --- docs/release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 4278488d..af184de0 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,6 +1,6 @@ # RELEASE NOTES -## 2022-xx-xx +## 2022-12-17 * Bump openshift version to 4.11.12 * Update ansible-automation-platform to 2.3